Skip to content

Commit db14023

Browse files
Preserve loop exits through nested control-flow macros
Signed-off-by: Perfloop Agent <agent@perfloop.ai> Signed-off-by: Tomás Senart <ts@perfloop.ai>
1 parent d33cf56 commit db14023

5 files changed

Lines changed: 245 additions & 5 deletions

File tree

‎libcc2rs-macros/src/state_machine.rs‎

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,10 +189,7 @@ impl GotoRewriter<'_> {
189189

190190
fn recurse_into_inner_goto_block(&mut self, mac: &mut syn::Macro) -> bool {
191191
if mac.path.is_ident("switch") || mac.path.is_ident("goto_block") {
192-
if let Ok(mut inner) = syn::parse2::<Expr>(mac.tokens.clone()) {
193-
self.visit_expr_mut(&mut inner);
194-
mac.tokens = quote!(#inner);
195-
}
192+
visit_control_flow_macro(self, mac, true);
196193
return true;
197194
}
198195
false
@@ -284,12 +281,47 @@ impl StateMachine for SwitchStateMachine {
284281
}
285282
}
286283

284+
// Macro tokens are opaque to syn's normal traversal. A nested goto block is
285+
// transparent to exits; a nested switch owns breaks in its arms, but not in
286+
// its condition or guards. Neither macro owns continues.
287+
fn visit_control_flow_macro(
288+
visitor: &mut impl VisitMut,
289+
mac: &mut syn::Macro,
290+
visit_switch_arms: bool,
291+
) {
292+
let Some(name) = mac.path.segments.last().map(|s| &s.ident) else {
293+
return;
294+
};
295+
if name == "goto_block"
296+
&& let Ok(mut body) = syn::parse2::<Expr>(mac.tokens.clone())
297+
{
298+
visitor.visit_expr_mut(&mut body);
299+
mac.tokens = quote!(#body);
300+
} else if name == "switch"
301+
&& let Ok(mut body) = syn::parse2::<syn::ExprMatch>(mac.tokens.clone())
302+
{
303+
visitor.visit_expr_mut(&mut body.expr);
304+
for arm in &mut body.arms {
305+
if let Some((_, guard)) = &mut arm.guard {
306+
visitor.visit_expr_mut(guard);
307+
}
308+
if visit_switch_arms {
309+
visitor.visit_expr_mut(&mut arm.body);
310+
}
311+
}
312+
mac.tokens = quote!(#body);
313+
}
314+
}
315+
287316
// Rewrite break into break '__sm
288317
struct ExitSwitchRewriter {
289318
label: Lifetime,
290319
}
291320

292321
impl VisitMut for ExitSwitchRewriter {
322+
fn visit_macro_mut(&mut self, mac: &mut syn::Macro) {
323+
visit_control_flow_macro(self, mac, false);
324+
}
293325
fn visit_expr_break_mut(&mut self, node: &mut ExprBreak) {
294326
if node.label.is_none() {
295327
node.label = Some(self.label.clone());
@@ -333,6 +365,9 @@ impl PropagateRewriter {
333365
}
334366

335367
impl VisitMut for PropagateRewriter {
368+
fn visit_macro_mut(&mut self, mac: &mut syn::Macro) {
369+
visit_control_flow_macro(self, mac, matches!(self.kind, ControlKind::Continue));
370+
}
336371
fn visit_expr_mut(&mut self, expr: &mut Expr) {
337372
let hit = match self.kind {
338373
ControlKind::Break => {

‎libcc2rs-macros/tests/control_flow.rs‎

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ fn nested_loop_exit_targets() {
594594
}
595595
};
596596
}
597-
// Reduced nested-goto replay first, then every other word of depth 0..3.
597+
// Start with nested goto blocks, then every other word of depth 0..3.
598598
check!(G G);
599599
check!();
600600
check!(G);
@@ -617,3 +617,104 @@ fn nested_loop_exit_targets() {
617617
mismatches.first()
618618
);
619619
}
620+
621+
#[test]
622+
fn nested_exits_stop_at_user_loops() {
623+
let mut sum = 0;
624+
switch!(match 0 {
625+
_ => {
626+
goto_block!({
627+
'entry: {
628+
for i in 0..3 {
629+
goto_block!({
630+
'inner: {
631+
if i == 1 {
632+
continue;
633+
}
634+
if i == 2 {
635+
break;
636+
}
637+
sum += 1;
638+
}
639+
});
640+
sum += 10;
641+
}
642+
let mut i = 0;
643+
while i < 3 {
644+
i += 1;
645+
goto_block!({
646+
'inner: {
647+
if i == 2 {
648+
continue;
649+
}
650+
if i == 3 {
651+
break;
652+
}
653+
sum += 1;
654+
}
655+
});
656+
sum += 10;
657+
}
658+
loop {
659+
goto_block!({
660+
'inner: {
661+
break;
662+
}
663+
});
664+
sum += 1000;
665+
}
666+
sum += 100;
667+
}
668+
});
669+
}
670+
});
671+
assert_eq!(sum, 122);
672+
}
673+
674+
#[test]
675+
fn nested_switch_dispatch_exits() {
676+
for in_guard in [false, true] {
677+
let mut visits = Vec::new();
678+
for i in 0..3 {
679+
visits.push(i);
680+
goto_block!({
681+
'entry: {
682+
let _: () = libcc2rs_macros::switch!(match {
683+
if !in_guard {
684+
break;
685+
}
686+
i
687+
} {
688+
_ if {
689+
if in_guard {
690+
break;
691+
}
692+
true
693+
} => {}
694+
_ => {}
695+
});
696+
}
697+
});
698+
}
699+
assert_eq!(visits, vec![0], "in_guard = {in_guard}");
700+
}
701+
let mut visits = Vec::new();
702+
switch!(match 0 {
703+
_ => {
704+
let _: () = libcc2rs_macros::switch!(match {
705+
let _: () = libcc2rs_macros::goto_block!({
706+
'inner: {
707+
visits.push(1);
708+
break;
709+
}
710+
});
711+
0
712+
} {
713+
_ => {}
714+
});
715+
visits.push(2);
716+
}
717+
});
718+
visits.push(3);
719+
assert_eq!(visits, vec![1, 3]);
720+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include <assert.h>
2+
3+
int main(void) {
4+
int visits = 0;
5+
int tail = 0;
6+
for (int i = 0; i < 3; i++) {
7+
outer:
8+
visits++;
9+
{
10+
inner:
11+
if (i == 0) {
12+
break;
13+
}
14+
}
15+
tail++;
16+
}
17+
assert(visits == 1);
18+
assert(tail == 0);
19+
return 0;
20+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
extern crate libcc2rs;
2+
use libcc2rs::*;
3+
use std::cell::RefCell;
4+
use std::collections::BTreeMap;
5+
use std::io::prelude::*;
6+
use std::io::{Read, Seek, Write};
7+
use std::os::fd::AsFd;
8+
use std::rc::{Rc, Weak};
9+
pub fn main() {
10+
__cpp2rust_init_globals();
11+
std::process::exit(main_0());
12+
}
13+
fn main_0() -> i32 {
14+
let mut visits: i32 = 0;
15+
let mut tail: i32 = 0;
16+
let mut i: i32 = 0;
17+
'loop_: while (((i < 3) as i32) != 0) {
18+
goto_block!({
19+
'__entry: {}
20+
'outer: {
21+
visits.postfix_inc();
22+
{
23+
goto_block!({
24+
'__entry: {}
25+
'inner: {
26+
if (((i == 0) as i32) != 0) {
27+
break;
28+
}
29+
}
30+
});
31+
}
32+
tail.postfix_inc();
33+
}
34+
});
35+
i.postfix_inc();
36+
}
37+
assert!((((visits == 1) as i32) != 0));
38+
assert!((((tail == 0) as i32) != 0));
39+
return 0;
40+
}
41+
pub fn __cpp2rust_init_globals() {}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
extern crate libc;
2+
use libc::*;
3+
extern crate libcc2rs;
4+
use libcc2rs::*;
5+
use std::collections::BTreeMap;
6+
use std::io::{Read, Seek, Write};
7+
use std::os::fd::{AsFd, FromRawFd, IntoRawFd};
8+
use std::rc::Rc;
9+
pub fn main() {
10+
unsafe {
11+
__cpp2rust_init_globals();
12+
std::process::exit(main_0() as i32);
13+
}
14+
}
15+
unsafe fn main_0() -> i32 {
16+
let mut visits: i32 = 0;
17+
let mut tail: i32 = 0;
18+
let mut i: i32 = 0;
19+
'loop_: while ((((i) < (3)) as i32) != 0) {
20+
goto_block!({
21+
'__entry: {}
22+
'outer: {
23+
visits.postfix_inc();
24+
{
25+
goto_block!({
26+
'__entry: {}
27+
'inner: {
28+
if ((((i) == (0)) as i32) != 0) {
29+
break;
30+
}
31+
}
32+
});
33+
}
34+
tail.postfix_inc();
35+
}
36+
});
37+
i.postfix_inc();
38+
}
39+
assert!(((((visits) == (1)) as i32) != 0));
40+
assert!(((((tail) == (0)) as i32) != 0));
41+
return 0;
42+
}
43+
pub unsafe fn __cpp2rust_init_globals() {}

0 commit comments

Comments
 (0)