Skip to content

Commit ff64867

Browse files
committed
Fix hoisted statement-block declarations dropping ifdef guards
1 parent f43a6fb commit ff64867

2 files changed

Lines changed: 112 additions & 11 deletions

File tree

crates/emitter/src/emitter.rs

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,18 +1366,16 @@ impl Emitter {
13661366
fn emit_statement_block(&mut self, arg: &StatementBlock, begin_kw: &str, end_kw: &str) {
13671367
self.token_will_push(&arg.l_brace.l_brace_token.replace(begin_kw));
13681368

1369-
let statement_block_list: Vec<_> = arg
1370-
.statement_block_list
1371-
.iter()
1372-
.map(|x| Into::<Vec<_>>::into(x.statement_block_group.as_ref()))
1373-
.collect();
1374-
13751369
let mut base = 0;
13761370
let mut n_newlines = 0;
1377-
for x in &statement_block_list {
1378-
for x in x {
1379-
(base, n_newlines) = self.emit_declaration_in_statement_block(x, base, n_newlines);
1380-
}
1371+
let mut suppress_newline = false;
1372+
for x in &arg.statement_block_list {
1373+
(base, n_newlines) = self.hoist_declarations_in_group(
1374+
&x.statement_block_group,
1375+
base,
1376+
n_newlines,
1377+
&mut suppress_newline,
1378+
);
13811379
}
13821380

13831381
let mut n_newlines = 0;
@@ -1394,6 +1392,72 @@ impl Emitter {
13941392
self.token(&arg.r_brace.r_brace_token.replace(end_kw));
13951393
}
13961394

1395+
/// Phase-1 hoist of let/var/const declarations to the block top, keeping
1396+
/// each group's `ifdef/`elsif/`else/`endif guards: flattening them would
1397+
/// emit both arms of an #[ifdef]/#[else] pair unguarded into one scope.
1398+
fn hoist_declarations_in_group(
1399+
&mut self,
1400+
group: &StatementBlockGroup,
1401+
mut base: usize,
1402+
mut n_newlines: usize,
1403+
suppress_newline: &mut bool,
1404+
) -> (usize, usize) {
1405+
let items: Vec<&StatementBlockItem> = group.into();
1406+
let has_declaration = items.iter().any(|x| {
1407+
!matches!(
1408+
x,
1409+
StatementBlockItem::Statement(_) | StatementBlockItem::ConcatenationAssignment(_)
1410+
)
1411+
});
1412+
if !has_declaration {
1413+
return (base, n_newlines);
1414+
}
1415+
1416+
let ifdef_attributes: Vec<_> = group
1417+
.statement_block_group_list
1418+
.iter()
1419+
.filter(|x| is_conditional_attribute(&x.attribute))
1420+
.collect();
1421+
1422+
for (j, x) in ifdef_attributes.iter().enumerate() {
1423+
if j == 0 && !*suppress_newline {
1424+
self.newline_list(n_newlines);
1425+
n_newlines += 1;
1426+
base += 1;
1427+
}
1428+
self.attribute(&x.attribute);
1429+
}
1430+
if !ifdef_attributes.is_empty() {
1431+
*suppress_newline = true;
1432+
}
1433+
1434+
match &*group.statement_block_group_group {
1435+
StatementBlockGroupGroup::BlockLBraceStatementBlockGroupGroupListRBrace(x) => {
1436+
for x in &x.statement_block_group_group_list {
1437+
(base, n_newlines) = self.hoist_declarations_in_group(
1438+
&x.statement_block_group,
1439+
base,
1440+
n_newlines,
1441+
suppress_newline,
1442+
);
1443+
}
1444+
}
1445+
StatementBlockGroupGroup::StatementBlockItem(x) => {
1446+
(base, n_newlines) = self.emit_declaration_in_statement_block(
1447+
x.statement_block_item.as_ref(),
1448+
base,
1449+
n_newlines,
1450+
suppress_newline,
1451+
);
1452+
}
1453+
}
1454+
1455+
for _ in ifdef_attributes {
1456+
self.attribute_end();
1457+
}
1458+
(base, n_newlines)
1459+
}
1460+
13971461
/// Emit one `StatementBlockGroup`, opening its conditional attributes once
13981462
/// around the whole group: a multi-statement `#[ifdef] block` gets a single
13991463
/// `ifdef/`endif pair, and nested groups keep their own guards. `suppress_newline`
@@ -1470,6 +1534,7 @@ impl Emitter {
14701534
arg: &StatementBlockItem,
14711535
base: usize,
14721536
n_newlines: usize,
1537+
suppress_newline: &mut bool,
14731538
) -> (usize, usize) {
14741539
if matches!(
14751540
arg,
@@ -1478,7 +1543,11 @@ impl Emitter {
14781543
return (base, n_newlines);
14791544
}
14801545

1481-
self.newline_list(n_newlines);
1546+
if *suppress_newline {
1547+
*suppress_newline = false;
1548+
} else {
1549+
self.newline_list(n_newlines);
1550+
}
14821551
self.clear_adjust_line();
14831552
match arg {
14841553
StatementBlockItem::VarDeclaration(x) => {

crates/emitter/src/tests.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4665,3 +4665,35 @@ endinterface
46654665
println!("ret\n{}exp\n{}", ret, expect);
46664666
assert_eq!(ret, expect);
46674667
}
4668+
4669+
#[test]
4670+
fn hoisted_declaration_keeps_ifdef_guard() {
4671+
// Statement-block declarations are hoisted to the top of the begin/end
4672+
// block; the hoist used to flatten the groups and drop their
4673+
// `ifdef/`else guards, emitting both arms' declarations unguarded into
4674+
// the same scope (duplicate declaration, rejected by every SV tool).
4675+
let metadata = Metadata::create_default("prj").unwrap();
4676+
4677+
let code = r#"module M {
4678+
var x: logic;
4679+
always_comb {
4680+
#[ifdef(DEFINE_E)]
4681+
let e: logic<8> = 1;
4682+
#[else]
4683+
let e: logic<16> = 3;
4684+
x = e[0];
4685+
}
4686+
}
4687+
"#;
4688+
let ret = emit(&metadata, code);
4689+
let hoist = ret.find("logic [8-1:0] e;").unwrap();
4690+
let guarded = &ret[..hoist];
4691+
assert!(
4692+
guarded.trim_end().ends_with("`ifdef DEFINE_E"),
4693+
"hoisted declaration must sit inside its ifdef guard:\n{ret}"
4694+
);
4695+
assert!(
4696+
ret.contains("`else\n logic [16-1:0] e;\n `endif"),
4697+
"else-arm declaration must sit inside the guard:\n{ret}"
4698+
);
4699+
}

0 commit comments

Comments
 (0)