Skip to content

Commit c48eecc

Browse files
authored
Translate goto label attached to switch case (#189)
Translates a label atttached to a switch case, from: ```cpp switch (n) { target: case 0: ... case 1: ... goto target; ``` to ```rs switch!(match n { __v if __v == 0 => 'target: { ... } __v if __v == 1 => { ... goto!('target); } ``` To make this work, I refactored all switch-analysing functions into a single struct, called SwitchArm. Now, VisitSwitchStmt first calls AnalyzeSwitchArms, then iterates over the SwitchArms and generates the corresponding Rust code. Each SwitchArm has a field called label used for generating ` __v if __v == 0 => 'target: {`, i.e. a labeled block in Rust. The changes in libcc2rs-macros/src/switch.rs are minimal. I added support for accepting a labeled block in the switch! macro.
1 parent 3466732 commit c48eecc

8 files changed

Lines changed: 182 additions & 84 deletions

File tree

cpp2rust/converter/converter.cpp

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <llvm/ADT/DenseMap.h>
1111
#include <llvm/Support/ConvertUTF.h>
1212

13+
#include <algorithm>
1314
#include <format>
1415
#include <utility>
1516

@@ -3210,46 +3211,53 @@ bool Converter::ConvertSwitchCaseCondition(clang::SwitchCase *stmt) {
32103211
}
32113212

32123213
if (clang::isa<clang::CaseStmt>(last)) {
3213-
StrCat(" => {");
3214+
StrCat(" => ");
32143215
} else /* DefaultStmt */ {
3215-
StrCat("_ => {");
3216+
StrCat("_ => ");
32163217
}
32173218
return false;
32183219
}
32193220

3220-
void Converter::EmitSwitchArm(clang::CompoundStmt *body, clang::SwitchCase *sc,
3221-
bool is_default) {
3221+
void Converter::EmitSwitchArm(const SwitchArm &arm, bool is_default) {
32223222
if (is_default) {
3223-
StrCat("_ => {");
3223+
StrCat("_ => ");
32243224
} else {
32253225
StrCat("__v if __v == ");
3226-
ConvertSwitchCaseCondition(sc);
3226+
ConvertSwitchCaseCondition(arm.head);
32273227
}
3228-
for (auto *t : GetSwitchCaseBody(body, sc)) {
3228+
if (!arm.label.empty()) {
3229+
StrCat(std::format("'{}: ", arm.label.str()));
3230+
}
3231+
StrCat(token::kOpenCurlyBracket);
3232+
for (auto *t : arm.body) {
32293233
Convert(t);
32303234
}
32313235
StrCat("},");
32323236
}
32333237

32343238
bool Converter::VisitSwitchStmt(clang::SwitchStmt *stmt) {
3235-
bool has_fallthrough = SwitchHasFallthrough(stmt);
3236-
PushBreakTarget push(break_target_, has_fallthrough
3237-
? BreakTarget::FallthroughSwitch
3238-
: BreakTarget::Switch);
32393239
auto *body = clang::dyn_cast<clang::CompoundStmt>(stmt->getBody());
32403240
assert(body);
3241+
auto arms = AnalyzeSwitchArms(body);
3242+
3243+
bool needs_switch_macro = std::ranges::any_of(arms, [](const SwitchArm &arm) {
3244+
return !arm.label.empty() || arm.has_fallthrough;
3245+
});
3246+
3247+
PushBreakTarget push(break_target_, needs_switch_macro
3248+
? BreakTarget::FallthroughSwitch
3249+
: BreakTarget::Switch);
32413250

3242-
if (has_fallthrough) {
3243-
// Use the switch-with-fallthrough macro
3251+
if (needs_switch_macro) {
32443252
StrCat("switch!");
32453253
} else {
32463254
StrCat("'switch:");
32473255
}
32483256

3249-
PushParen switch_macro_paren(*this, has_fallthrough);
3250-
PushBrace switch_label_brace(*this, !has_fallthrough);
3257+
PushParen switch_macro_paren(*this, needs_switch_macro);
3258+
PushBrace switch_label_brace(*this, !needs_switch_macro);
32513259

3252-
if (has_fallthrough) {
3260+
if (needs_switch_macro) {
32533261
StrCat("match", ToString(stmt->getCond()));
32543262
} else {
32553263
StrCat(
@@ -3259,17 +3267,17 @@ bool Converter::VisitSwitchStmt(clang::SwitchStmt *stmt) {
32593267

32603268
PushBrace match_brace(*this);
32613269

3262-
clang::SwitchCase *default_case = nullptr;
3263-
for (auto *sc : GetTopLevelSwitchCases(stmt)) {
3264-
if (SwitchCaseContainsDefault(sc)) {
3265-
default_case = sc;
3270+
const SwitchArm *default_arm = nullptr;
3271+
for (const auto &arm : arms) {
3272+
if (arm.is_default_case) {
3273+
default_arm = &arm;
32663274
continue;
32673275
}
3268-
EmitSwitchArm(body, sc, /*is_default=*/false);
3276+
EmitSwitchArm(arm, /*is_default=*/false);
32693277
}
32703278

3271-
if (default_case) {
3272-
EmitSwitchArm(body, default_case, /*is_default=*/true);
3279+
if (default_arm) {
3280+
EmitSwitchArm(*default_arm, /*is_default=*/true);
32733281
} else {
32743282
StrCat(R"( _ => {})");
32753283
}

cpp2rust/converter/converter.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <utility>
1717
#include <vector>
1818

19+
#include "converter/converter_lib.h"
1920
#include "converter/lex.h"
2021
#include "converter/translation_rule.h"
2122
#include "logging.h"
@@ -367,8 +368,7 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
367368

368369
virtual bool VisitSwitchStmt(clang::SwitchStmt *stmt);
369370

370-
void EmitSwitchArm(clang::CompoundStmt *body, clang::SwitchCase *sc,
371-
bool is_default);
371+
void EmitSwitchArm(const SwitchArm &arm, bool is_default);
372372

373373
bool ConvertSwitchCaseCondition(clang::SwitchCase *stmt);
374374

cpp2rust/converter/converter_lib.cpp

Lines changed: 30 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -876,20 +876,15 @@ clang::Expr *NormalizeToBool(clang::Expr *expr, clang::ASTContext &ctx) {
876876
/*BasePath=*/nullptr, clang::VK_PRValue, clang::FPOptionsOverride());
877877
}
878878

879-
std::vector<clang::SwitchCase *>
880-
GetTopLevelSwitchCases(clang::SwitchStmt *stmt) {
881-
std::vector<clang::SwitchCase *> cases;
882-
if (auto *body = llvm::dyn_cast<clang::CompoundStmt>(stmt->getBody())) {
883-
for (auto *s : body->body()) {
884-
if (auto *sc = clang::dyn_cast<clang::SwitchCase>(s)) {
885-
cases.push_back(sc);
886-
}
887-
}
879+
static clang::Stmt *GetLastStmtOfSwitchCase(clang::SwitchCase *c) {
880+
clang::Stmt *cur = c->getSubStmt();
881+
while (auto *sc = clang::dyn_cast<clang::SwitchCase>(cur)) {
882+
cur = sc->getSubStmt();
888883
}
889-
return cases;
884+
return cur;
890885
}
891886

892-
bool SwitchCaseContainsDefault(clang::SwitchCase *c) {
887+
static bool CaseChainHasDefault(clang::SwitchCase *c) {
893888
for (clang::Stmt *cur = c;;) {
894889
if (clang::isa<clang::DefaultStmt>(cur)) {
895890
return true;
@@ -900,32 +895,6 @@ bool SwitchCaseContainsDefault(clang::SwitchCase *c) {
900895
}
901896
cur = sc->getSubStmt();
902897
}
903-
return false;
904-
}
905-
906-
static clang::Stmt *GetLastStmtOfSwitchCase(clang::SwitchCase *c) {
907-
clang::Stmt *cur = c->getSubStmt();
908-
while (auto *sc = clang::dyn_cast<clang::SwitchCase>(cur)) {
909-
cur = sc->getSubStmt();
910-
}
911-
return cur;
912-
}
913-
914-
std::vector<clang::Stmt *> GetSwitchCaseBody(clang::CompoundStmt *body,
915-
clang::SwitchCase *head) {
916-
std::vector<clang::Stmt *> out;
917-
out.push_back(GetLastStmtOfSwitchCase(head));
918-
auto it = body->body_begin(), end = body->body_end();
919-
while (it != end && *it != head) {
920-
++it;
921-
}
922-
assert(it != end);
923-
++it;
924-
while (it != end && !clang::isa<clang::SwitchCase>(*it)) {
925-
out.push_back(*it);
926-
++it;
927-
}
928-
return out;
929898
}
930899

931900
static bool SwitchCaseHasFallthrough(clang::Stmt *stmt) {
@@ -947,16 +916,32 @@ static bool SwitchCaseHasFallthrough(clang::Stmt *stmt) {
947916
return true;
948917
}
949918

950-
bool SwitchHasFallthrough(clang::SwitchStmt *stmt) {
951-
if (auto *body = clang::dyn_cast<clang::CompoundStmt>(stmt->getBody())) {
952-
for (auto top_level_case : GetTopLevelSwitchCases(stmt)) {
953-
auto arm = GetSwitchCaseBody(body, top_level_case);
954-
if (arm.empty() || SwitchCaseHasFallthrough(arm.back())) {
955-
return true;
956-
}
919+
std::vector<SwitchArm> AnalyzeSwitchArms(clang::CompoundStmt *body) {
920+
std::vector<SwitchArm> arms;
921+
for (clang::Stmt *s : body->body()) {
922+
llvm::StringRef label;
923+
clang::Stmt *inner = s;
924+
if (auto *outer = clang::dyn_cast<clang::LabelStmt>(inner)) {
925+
label = outer->getDecl()->getName();
926+
do {
927+
inner = clang::cast<clang::LabelStmt>(inner)->getSubStmt();
928+
} while (clang::isa<clang::LabelStmt>(inner));
929+
}
930+
931+
if (auto *sc = clang::dyn_cast<clang::SwitchCase>(inner)) {
932+
arms.emplace_back(std::vector<clang::Stmt *>{GetLastStmtOfSwitchCase(sc)},
933+
label, sc, CaseChainHasDefault(sc),
934+
/*has_fallthrough=*/false);
935+
} else if (!arms.empty()) {
936+
arms.back().body.push_back(s);
957937
}
958938
}
959-
return false;
939+
940+
for (SwitchArm &arm : arms) {
941+
arm.has_fallthrough =
942+
arm.body.empty() || SwitchCaseHasFallthrough(arm.body.back());
943+
}
944+
return arms;
960945
}
961946

962947
bool CompoundHasTopLevelLabel(const clang::CompoundStmt *compound) {

cpp2rust/converter/converter_lib.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -183,15 +183,15 @@ bool ContainsVAArgExpr(const clang::Stmt *stmt);
183183

184184
clang::Expr *NormalizeToBool(clang::Expr *expr, clang::ASTContext &ctx);
185185

186-
std::vector<clang::SwitchCase *>
187-
GetTopLevelSwitchCases(clang::SwitchStmt *stmt);
188-
189-
bool SwitchCaseContainsDefault(clang::SwitchCase *c);
190-
191-
std::vector<clang::Stmt *> GetSwitchCaseBody(clang::CompoundStmt *body,
192-
clang::SwitchCase *head);
186+
struct SwitchArm {
187+
std::vector<clang::Stmt *> body;
188+
llvm::StringRef label;
189+
clang::SwitchCase *head;
190+
bool is_default_case;
191+
bool has_fallthrough;
192+
};
193193

194-
bool SwitchHasFallthrough(clang::SwitchStmt *stmt);
194+
std::vector<SwitchArm> AnalyzeSwitchArms(clang::CompoundStmt *body);
195195

196196
bool CompoundHasTopLevelLabel(const clang::CompoundStmt *compound);
197197

libcc2rs-macros/src/switch.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use proc_macro::TokenStream;
55
use syn::parse::{Parse, ParseStream};
6-
use syn::{Expr, Pat, parse_macro_input};
6+
use syn::{Expr, ExprBlock, Pat, parse_macro_input};
77

88
use crate::state_machine::{
99
Arm, DispatchCase, GotoStateMachine, StateMachine, StateMachineNames, SwitchStateMachine,
@@ -14,16 +14,23 @@ pub fn expand(input: TokenStream) -> TokenStream {
1414
let mut cases = Vec::with_capacity(arms.len());
1515
let mut cfg_arms = Vec::with_capacity(arms.len());
1616
for (i, a) in arms.into_iter().enumerate() {
17-
let label = format!("__c{}", i);
17+
let (label, body) = match a.body {
18+
Expr::Block(eb) if eb.label.is_some() => (
19+
eb.label.unwrap().name.ident.to_string(),
20+
Expr::Block(ExprBlock {
21+
attrs: eb.attrs,
22+
label: None,
23+
block: eb.block,
24+
}),
25+
),
26+
other => (format!("__c{}", i), other),
27+
};
1828
cases.push(DispatchCase {
1929
pat: a.pat,
2030
guard: a.guard,
2131
target: label.clone(),
2232
});
23-
cfg_arms.push(Arm {
24-
label,
25-
body: a.body,
26-
});
33+
cfg_arms.push(Arm { label, body });
2734
}
2835
SwitchStateMachine {
2936
goto: GotoStateMachine {

tests/unit/goto_switch_self_case.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <assert.h>
2+
3+
static int sm(int n) {
4+
int steps = 0;
5+
switch (n) {
6+
target:
7+
case 0:
8+
steps += 1;
9+
break;
10+
case 1:
11+
steps += 10;
12+
goto target;
13+
default:
14+
steps = -1;
15+
break;
16+
}
17+
return steps;
18+
}
19+
20+
int main(void) {
21+
assert(sm(0) == 1);
22+
assert(sm(1) == 11);
23+
assert(sm(7) == -1);
24+
return 0;
25+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 sm_0(n: i32) -> i32 {
10+
let n: Value<i32> = Rc::new(RefCell::new(n));
11+
let steps: Value<i32> = Rc::new(RefCell::new(0));
12+
switch!(match (*n.borrow()) {
13+
__v if __v == 0 => 'target: {
14+
(*steps.borrow_mut()) += 1;
15+
break;
16+
}
17+
__v if __v == 1 => {
18+
(*steps.borrow_mut()) += 10;
19+
goto!('target);
20+
}
21+
_ => {
22+
(*steps.borrow_mut()) = -1_i32;
23+
break;
24+
}
25+
});
26+
return (*steps.borrow());
27+
}
28+
pub fn main() {
29+
std::process::exit(main_0());
30+
}
31+
fn main_0() -> i32 {
32+
assert!((((({ sm_0(0,) }) == 1) as i32) != 0));
33+
assert!((((({ sm_0(1,) }) == 11) as i32) != 0));
34+
assert!((((({ sm_0(7,) }) == -1_i32) as i32) != 0));
35+
return 0;
36+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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 unsafe fn sm_0(mut n: i32) -> i32 {
10+
let mut steps: i32 = 0;
11+
switch!(match n {
12+
__v if __v == 0 => 'target: {
13+
steps += 1;
14+
break;
15+
}
16+
__v if __v == 1 => {
17+
steps += 10;
18+
goto!('target);
19+
}
20+
_ => {
21+
steps = -1_i32;
22+
break;
23+
}
24+
});
25+
return steps;
26+
}
27+
pub fn main() {
28+
unsafe {
29+
std::process::exit(main_0() as i32);
30+
}
31+
}
32+
unsafe fn main_0() -> i32 {
33+
assert!(((((unsafe { sm_0(0,) }) == (1)) as i32) != 0));
34+
assert!(((((unsafe { sm_0(1,) }) == (11)) as i32) != 0));
35+
assert!(((((unsafe { sm_0(7,) }) == (-1_i32)) as i32) != 0));
36+
return 0;
37+
}

0 commit comments

Comments
 (0)