Skip to content

Commit db0d23f

Browse files
authored
Add codegen support for goto (#163)
Depends on #161. See #161 for a more detailed description of how goto is implemented. On the codegen part, this PR emits the goto_block and goto macros when it hits functions that use labels and gotos internally. It also hoists local variables at the start of the function so that all goto_block arms can see and use the local variables.
1 parent 77f486e commit db0d23f

27 files changed

Lines changed: 1216 additions & 1 deletion

cpp2rust/converter/converter.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,59 @@ bool Converter::VisitFunctionDecl(clang::FunctionDecl *decl) {
364364
return false;
365365
}
366366

367+
void Converter::EmitHoistedDecls(clang::CompoundStmt *body) {
368+
for (auto *child : body->body()) {
369+
if (auto *decl_stmt = clang::dyn_cast<clang::DeclStmt>(child)) {
370+
for (auto *decl : decl_stmt->decls()) {
371+
if (auto *var = clang::dyn_cast<clang::VarDecl>(decl);
372+
var && var->isLocalVarDecl() && !IsGlobalVar(var)) {
373+
hoisted_decls_.insert(var);
374+
if (ConvertVarDeclSkipInit(var)) {
375+
StrCat(token::kAssign, ConvertVarDefaultInit(var->getType()),
376+
token::kSemiColon);
377+
}
378+
}
379+
}
380+
}
381+
}
382+
}
383+
384+
void Converter::ConvertGotoBlock(clang::CompoundStmt *body) {
385+
PushHoistedDecls push(hoisted_decls_);
386+
EmitHoistedDecls(body);
387+
388+
StrCat("goto_block!");
389+
{
390+
PushParen paren(*this);
391+
PushBrace outer(*this);
392+
StrCat("'__entry: ");
393+
std::optional<PushBrace> arm;
394+
arm.emplace(*this);
395+
for (auto *child : body->body()) {
396+
if (auto *label = clang::dyn_cast<clang::LabelStmt>(child)) {
397+
arm.reset();
398+
StrCat(std::format("'{}: ", label->getDecl()->getName().str()));
399+
arm.emplace(*this);
400+
Convert(label->getSubStmt());
401+
} else {
402+
Convert(child);
403+
}
404+
}
405+
}
406+
StrCat(token::kSemiColon);
407+
}
408+
367409
void Converter::ConvertFunctionBody(clang::FunctionDecl *decl) {
410+
if (auto compound = clang::dyn_cast<clang::CompoundStmt>(decl->getBody())) {
411+
if (CompoundHasTopLevelLabel(compound)) {
412+
ConvertGotoBlock(compound);
413+
if (!decl->getReturnType()->isVoidType()) {
414+
StrCat(R"(panic!("ub: non-void function does not return a value"))");
415+
}
416+
return;
417+
}
418+
}
419+
368420
Convert(decl->getBody());
369421
if (!decl->getReturnType()->isVoidType()) {
370422
if (auto compound = clang::dyn_cast<clang::CompoundStmt>(decl->getBody())) {
@@ -467,7 +519,20 @@ void Converter::ConvertVarDeclInitializer(clang::VarDecl *decl) {
467519
}
468520
}
469521

522+
void Converter::EmitHoistedInArmAssignment(clang::VarDecl *decl) {
523+
if (!decl->hasInit()) {
524+
return;
525+
}
526+
StrCat(GetNamedDeclAsString(decl), token::kAssign);
527+
ConvertVarInit(decl->getType(), decl->getInit());
528+
StrCat(token::kSemiColon);
529+
}
530+
470531
void Converter::ConvertVarDecl(clang::VarDecl *decl) {
532+
if (hoisted_decls_.contains(decl)) {
533+
EmitHoistedInArmAssignment(decl);
534+
return;
535+
}
471536
if (!ConvertVarDeclSkipInit(decl)) {
472537
// Skip global variables declared extern
473538
return;
@@ -985,6 +1050,10 @@ bool Converter::Convert(clang::Stmt *stmt) {
9851050
}
9861051

9871052
bool Converter::VisitCompoundStmt(clang::CompoundStmt *stmt) {
1053+
if (CompoundHasTopLevelLabel(stmt)) {
1054+
ConvertGotoBlock(stmt);
1055+
return false;
1056+
}
9881057
for (auto *child : stmt->body()) {
9891058
Convert(child);
9901059
}
@@ -1011,6 +1080,11 @@ bool Converter::VisitReturnStmt(clang::ReturnStmt *stmt) {
10111080
return false;
10121081
}
10131082

1083+
bool Converter::VisitGotoStmt(clang::GotoStmt *stmt) {
1084+
StrCat(std::format("goto!('{})", stmt->getLabel()->getName().str()));
1085+
return false;
1086+
}
1087+
10141088
void Converter::ConvertCondition(clang::Expr *cond) {
10151089
PushExprKind push(*this, ExprKind::RValue);
10161090
Convert(NormalizeToBool(cond, ctx_));

cpp2rust/converter/converter.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,18 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
8282

8383
virtual void ConvertFunctionBody(clang::FunctionDecl *decl);
8484

85+
void ConvertGotoBlock(clang::CompoundStmt *body);
86+
87+
void EmitHoistedDecls(clang::CompoundStmt *body);
88+
8589
virtual bool VisitFunctionTemplateDecl(clang::FunctionTemplateDecl *decl);
8690

8791
virtual bool VisitVarDecl(clang::VarDecl *decl);
8892

8993
void ConvertVarDecl(clang::VarDecl *decl);
9094

95+
virtual void EmitHoistedInArmAssignment(clang::VarDecl *decl);
96+
9197
void ConvertVarDeclInitializer(clang::VarDecl *decl);
9298

9399
virtual void ConvertGlobalVarDecl(clang::VarDecl *decl);
@@ -125,6 +131,8 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
125131

126132
virtual bool VisitReturnStmt(clang::ReturnStmt *stmt);
127133

134+
virtual bool VisitGotoStmt(clang::GotoStmt *stmt);
135+
128136
void ConvertCondition(clang::Expr *cond);
129137

130138
virtual bool VisitIfStmt(clang::IfStmt *stmt);
@@ -646,6 +654,24 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
646654

647655
std::unordered_set<const clang::VarDecl *> map_iter_decls_;
648656

657+
// Local variables hoisted outside a goto_block so that all labels can see and
658+
// use the variables.
659+
std::unordered_set<const clang::VarDecl *> hoisted_decls_;
660+
class PushHoistedDecls {
661+
public:
662+
PushHoistedDecls(std::unordered_set<const clang::VarDecl *> &field)
663+
: field_(field), saved_(std::move(field)) {
664+
field_.clear();
665+
}
666+
~PushHoistedDecls() { field_ = std::move(saved_); }
667+
PushHoistedDecls(const PushHoistedDecls &) = delete;
668+
PushHoistedDecls &operator=(const PushHoistedDecls &) = delete;
669+
670+
private:
671+
std::unordered_set<const clang::VarDecl *> &field_;
672+
std::unordered_set<const clang::VarDecl *> saved_;
673+
};
674+
649675
struct ScopedMapIterDecl {
650676
Converter &c;
651677
const clang::VarDecl *decl;

cpp2rust/converter/converter_lib.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,8 @@ static bool SwitchCaseHasFallthrough(clang::Stmt *stmt) {
811811
}
812812
if (clang::isa<clang::BreakStmt>(stmt) ||
813813
clang::isa<clang::ContinueStmt>(stmt) ||
814-
clang::isa<clang::ReturnStmt>(stmt)) {
814+
clang::isa<clang::ReturnStmt>(stmt) ||
815+
clang::isa<clang::GotoStmt>(stmt)) {
815816
return false;
816817
}
817818
return true;
@@ -829,6 +830,15 @@ bool SwitchHasFallthrough(clang::SwitchStmt *stmt) {
829830
return false;
830831
}
831832

833+
bool CompoundHasTopLevelLabel(const clang::CompoundStmt *compound) {
834+
for (const auto *child : compound->body()) {
835+
if (clang::isa<clang::LabelStmt>(child)) {
836+
return true;
837+
}
838+
}
839+
return false;
840+
}
841+
832842
std::string_view Trim(std::string_view s) {
833843
auto is_space = [](unsigned char c) { return std::isspace(c); };
834844
auto b = std::find_if_not(s.begin(), s.end(), is_space);

cpp2rust/converter/converter_lib.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ std::vector<clang::Stmt *> GetSwitchCaseBody(clang::CompoundStmt *body,
169169

170170
bool SwitchHasFallthrough(clang::SwitchStmt *stmt);
171171

172+
bool CompoundHasTopLevelLabel(const clang::CompoundStmt *compound);
173+
172174
std::string_view Trim(std::string_view s);
173175

174176
void Unwrap(std::string &s, std::string_view prefix, std::string_view suffix);

cpp2rust/converter/models/converter_refcount.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,24 @@ bool ConverterRefCount::ConvertLambdaVarDecl(clang::VarDecl *decl) {
621621
return false;
622622
}
623623

624+
bool ConverterRefCount::ConvertVarDeclSkipInit(clang::VarDecl *decl) {
625+
bool unboxed = in_function_formals_;
626+
PushConversionKind push(*this, unboxed ? ConversionKind::Unboxed
627+
: ConversionKind::FullRefCount);
628+
return Converter::ConvertVarDeclSkipInit(decl);
629+
}
630+
631+
void ConverterRefCount::EmitHoistedInArmAssignment(clang::VarDecl *decl) {
632+
if (!decl->hasInit()) {
633+
return;
634+
}
635+
PushConversionKind push(*this, ConversionKind::Unboxed);
636+
StrCat(token::kStar, GetNamedDeclAsString(decl), ".borrow_mut()",
637+
token::kAssign);
638+
Convert(decl->getInit());
639+
StrCat(token::kSemiColon);
640+
}
641+
624642
void ConverterRefCount::ConvertGlobalVarDecl(clang::VarDecl *decl) {
625643
StrCat("thread_local!");
626644
{

cpp2rust/converter/models/converter_refcount.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ class ConverterRefCount final : public Converter {
5757

5858
void ConvertVaListVarDecl(clang::VarDecl *decl) override;
5959

60+
bool ConvertVarDeclSkipInit(clang::VarDecl *decl) override;
61+
62+
void EmitHoistedInArmAssignment(clang::VarDecl *decl) override;
63+
6064
bool ConvertLambdaVarDecl(clang::VarDecl *decl) override;
6165

6266
bool VisitDeclRefExpr(clang::DeclRefExpr *expr) override;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#include <assert.h>
2+
#include <stdio.h>
3+
4+
struct Point {
5+
int x;
6+
int y;
7+
};
8+
9+
static int agg(int n) {
10+
char buf40[40];
11+
unsigned char buf256[256];
12+
int arr64[64];
13+
long longs[33];
14+
struct Point p;
15+
int *ptr;
16+
int (*fp)(int);
17+
FILE *file;
18+
int total = 0;
19+
if (n < 0) {
20+
goto out;
21+
}
22+
total = 1;
23+
out:
24+
return total;
25+
}
26+
27+
int main(void) {
28+
assert(agg(-1) == 0);
29+
assert(agg(1) == 1);
30+
return 0;
31+
}

tests/unit/goto_backward.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#include <assert.h>
2+
3+
static int retry(int n) {
4+
int count = 0;
5+
int acc = 0;
6+
again:
7+
count += 1;
8+
acc += n;
9+
if (count < 3) {
10+
goto again;
11+
}
12+
return acc;
13+
}
14+
15+
int main(void) {
16+
assert(retry(4) == 12);
17+
return 0;
18+
}

tests/unit/goto_cleanup.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#include <assert.h>
2+
3+
static int early(int n) {
4+
int ret = 0;
5+
if (n < 0) {
6+
ret = -1;
7+
goto out;
8+
}
9+
ret = 100;
10+
out:
11+
return ret;
12+
}
13+
14+
static int from_loop(int n) {
15+
int ret = 0;
16+
for (int i = 0; i < n; i++) {
17+
if (i == 3) {
18+
ret = 7;
19+
goto out;
20+
}
21+
ret += i;
22+
}
23+
ret = 999;
24+
out:
25+
return ret;
26+
}
27+
28+
static int from_switch(int n) {
29+
int ret = 0;
30+
switch (n) {
31+
case 1:
32+
ret = 10;
33+
goto out;
34+
default:
35+
ret = 20;
36+
break;
37+
}
38+
ret = 999;
39+
out:
40+
return ret;
41+
}
42+
43+
int main(void) {
44+
assert(early(-1) == -1);
45+
assert(early(5) == 100);
46+
assert(from_loop(2) == 999);
47+
assert(from_loop(10) == 7);
48+
assert(from_switch(1) == 10);
49+
assert(from_switch(2) == 999);
50+
return 0;
51+
}

tests/unit/goto_loop_control.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#include <assert.h>
2+
3+
static int loopctl(void) {
4+
int sum = 0;
5+
for (int i = 0; i < 5; i++) {
6+
if (i == 1) {
7+
continue;
8+
}
9+
if (i == 4) {
10+
break;
11+
}
12+
goto add;
13+
add:
14+
sum += 1;
15+
}
16+
return sum;
17+
}
18+
19+
int main(void) {
20+
assert(loopctl() == 3);
21+
return 0;
22+
}

0 commit comments

Comments
 (0)