Skip to content

Commit e67ac84

Browse files
nikswamyCopilot
andcommitted
Add -goto_for_early_return transformation
Adds a -goto_for_early_return CLI option. When enabled, functions with early returns are transformed to use goto-based control flow: - A result variable is allocated at the top (for non-void functions) - Every early return is replaced by assignment + goto - A label and final return are appended at the end The pass operates on the CStar AST (before lowering to C11) for simpler type access, natural block flattening, and use of existing helpers. Detection uses a position-aware walk that distinguishes terminal returns (in tail position) from early returns (in non-terminal position). Includes tests covering int32, uint32, uint64, int64, bool, void, struct, name collision avoidance, no-early-return passthrough, and unit/void interaction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 154d840 commit e67ac84

12 files changed

Lines changed: 541 additions & 1 deletion

File tree

lib/C11.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ and stmt =
139139
| Continue
140140
| Comment of string
141141
(** note: this is not in the C grammar *)
142+
| Goto of ident
143+
| Label of ident
142144

143145
and program =
144146
declaration_or_function list

lib/CStar.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ and stmt =
4545
| BufFill of typ * expr * expr * expr
4646
| BufFree of typ * expr
4747
| Block of block
48+
| Goto of ident
49+
| Label of ident
4850
| Comment of string
4951

5052
and expr =

lib/CStarToC11.ml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ and vars_of_stmt m = function
109109
| Return _
110110
| Break
111111
| Continue
112+
| Goto _
113+
| Label _
112114
| Comment _ ->
113115
S.empty
114116
| Ignore e
@@ -637,6 +639,12 @@ and mk_stmt m (stmt: stmt): C.stmt list =
637639
| Block stmts ->
638640
[ Compound (mk_stmts m stmts) ]
639641

642+
| Goto label ->
643+
[ Goto label ]
644+
645+
| Label label ->
646+
[ Label label ]
647+
640648
| Break ->
641649
[ Break ]
642650

lib/GotoForEarlyReturn.ml

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
(* Copyright (c) INRIA and Microsoft Corporation. All rights reserved. *)
2+
(* Licensed under the Apache 2.0 and MIT Licenses. *)
3+
4+
(** Transform functions with early returns to use goto. When activated via
5+
-goto_for_early_return, any function whose body contains a return statement
6+
in non-tail position is rewritten so that:
7+
- a return variable is allocated at the top (for non-void functions),
8+
- every return is replaced by an assignment + goto,
9+
- a label and final return are appended at the end.
10+
11+
This pass operates on the CStar AST, before lowering to C11. *)
12+
13+
open CStar
14+
15+
(* Collect all identifier names appearing in a block so we can pick fresh
16+
names that don't collide. We walk expressions and statements collecting
17+
variable references, declaration names, binder names, etc. *)
18+
let collect_names (body: block): (string, unit) Hashtbl.t =
19+
let names = Hashtbl.create 32 in
20+
let add n = Hashtbl.replace names n () in
21+
let rec collect_expr (e: expr) =
22+
match e with
23+
| Var v -> add v
24+
| Qualified (_, n) | Macro (_, n) -> add n
25+
| Constant _ | Bool _ | StringLiteral _ | Any | EAbort _ | Type _
26+
| BufNull | Op _ -> ()
27+
| Cast (e, _) | Field (e, _) | AddrOf e | InlineComment (_, e, _) ->
28+
collect_expr e
29+
| BufRead (e1, e2) | BufSub (e1, e2) | Comma (e1, e2)
30+
| BufCreate (_, e1, e2) ->
31+
collect_expr e1; collect_expr e2
32+
| Call (e, es) -> collect_expr e; List.iter collect_expr es
33+
| BufCreateL (_, es) -> List.iter collect_expr es
34+
| Struct (_, fes) -> List.iter (fun (_, e) -> collect_expr e) fes
35+
| Stmt ss -> List.iter collect_stmt ss
36+
and collect_stmt (s: stmt) =
37+
match s with
38+
| Abort _ | Break | Continue | Comment _ | Goto _ | Label _ -> ()
39+
| Return (Some e) -> collect_expr e
40+
| Return None -> ()
41+
| Ignore e | BufFree (_, e) -> collect_expr e
42+
| Decl (b, e) -> add b.name; collect_expr e
43+
| Assign (e1, _, e2) -> collect_expr e1; collect_expr e2
44+
| BufWrite (e1, e2, e3) ->
45+
collect_expr e1; collect_expr e2; collect_expr e3
46+
| BufBlit (_, e1, e2, e3, e4, e5) ->
47+
List.iter collect_expr [e1; e2; e3; e4; e5]
48+
| BufFill (_, e1, e2, e3) ->
49+
List.iter collect_expr [e1; e2; e3]
50+
| IfThenElse (_, e, b1, b2) ->
51+
collect_expr e; collect_block b1; collect_block b2
52+
| While (e, b) -> collect_expr e; collect_block b
53+
| For (init, e, iter, b) ->
54+
(match init with
55+
| `Decl (bi, e') -> add bi.name; collect_expr e'
56+
| `Stmt s -> collect_stmt s
57+
| `Skip -> ());
58+
collect_expr e; collect_stmt iter; collect_block b
59+
| Switch (e, branches, default) ->
60+
collect_expr e;
61+
List.iter (fun (_, b) -> collect_block b) branches;
62+
Option.iter collect_block default
63+
| Block b -> collect_block b
64+
and collect_block b = List.iter collect_stmt b
65+
in
66+
collect_block body;
67+
names
68+
69+
(* Position-aware early-return detection. A return in "terminal" position
70+
(i.e., last statement in the function body, or in a branch of an
71+
if-then-else/switch that is itself in terminal position) is NOT early.
72+
Any return in non-terminal position IS early. *)
73+
let has_early_return (body: block): bool =
74+
let found = ref false in
75+
(* Walk statements. [terminal] tracks whether the current position could
76+
be the "last thing" before the function returns naturally. *)
77+
let rec walk_block ~terminal (stmts: block) =
78+
match stmts with
79+
| [] -> ()
80+
| [s] -> walk_stmt ~terminal s
81+
| s :: rest -> walk_stmt ~terminal:false s; walk_block ~terminal rest
82+
and walk_stmt ~terminal (s: stmt) =
83+
match s with
84+
| Return _ ->
85+
if not terminal then found := true
86+
| IfThenElse (_, _, b1, b2) ->
87+
walk_block ~terminal b1;
88+
walk_block ~terminal b2
89+
| Switch (_, branches, default) ->
90+
List.iter (fun (_, b) -> walk_block ~terminal b) branches;
91+
Option.iter (walk_block ~terminal) default
92+
| While (_, b) ->
93+
(* Loop body is not terminal — the loop may exit without returning *)
94+
walk_block ~terminal:false b
95+
| For (_, _, _, b) ->
96+
walk_block ~terminal:false b
97+
| Block b ->
98+
walk_block ~terminal b
99+
| Abort _ | Break | Continue | Comment _ | Goto _ | Label _
100+
| Ignore _ | Decl _ | Assign _ | BufWrite _ | BufBlit _ | BufFill _
101+
| BufFree _ ->
102+
()
103+
in
104+
walk_block ~terminal:true body;
105+
!found
106+
107+
(* Generate a type-specific zero initializer expression for the return
108+
variable. *)
109+
let zero_initializer (t: typ): expr =
110+
match t with
111+
| Int w -> Constant (w, "0")
112+
| Bool -> Bool false
113+
| Pointer _ -> BufNull
114+
| Void -> failwith "zero_initializer called on Void"
115+
| Qualified _ | Struct _ | Enum _ | Union _ | Array _ | Function _
116+
| Const _ ->
117+
(* For aggregate/named types, produce a struct literal with a single
118+
zero field. CStarToC11 translates this to { 0U }. *)
119+
Struct (None, [(None, Constant (Constant.UInt8, "0"))])
120+
121+
(* Rewrite a block, replacing Return statements with assignment + goto. *)
122+
let rewrite_block (ret_var: ident) (ret_typ: typ) (label: ident) (is_void: bool) (body: block): block =
123+
let rec rewrite_stmts (stmts: block): block =
124+
List.concat_map rewrite_one stmts
125+
and rewrite_one (s: stmt): block =
126+
match s with
127+
| Return (Some e) when not is_void ->
128+
[Assign (Var ret_var, ret_typ, e); Goto label]
129+
| Return (Some _e) when is_void ->
130+
Warn.fatal_error "void function returning a value"
131+
| Return None ->
132+
[Goto label]
133+
| IfThenElse (ifdef, e, b1, b2) ->
134+
[IfThenElse (ifdef, e, rewrite_stmts b1, rewrite_stmts b2)]
135+
| Switch (e, branches, default) ->
136+
[Switch (e,
137+
List.map (fun (c, b) -> (c, rewrite_stmts b)) branches,
138+
Option.map rewrite_stmts default)]
139+
| While (e, b) ->
140+
[While (e, rewrite_stmts b)]
141+
| For (init, e, iter, b) ->
142+
[For (init, e, iter, rewrite_stmts b)]
143+
| Block b ->
144+
[Block (rewrite_stmts b)]
145+
| s -> [s]
146+
in
147+
rewrite_stmts body
148+
149+
(* Rewrite a single CStar declaration if it has early returns. *)
150+
let rewrite_decl (d: decl): decl =
151+
match d with
152+
| Function (cc, flags, ret_typ, lid, binders, body) when has_early_return body ->
153+
let is_void = (ret_typ = Void) in
154+
let used = collect_names body in
155+
let is_used n = Hashtbl.mem used n in
156+
let ret_var = Idents.mk_fresh "result" is_used in
157+
let label = Idents.mk_fresh "exit" is_used in
158+
let rewritten = rewrite_block ret_var ret_typ label is_void body in
159+
let new_body =
160+
if is_void then
161+
rewritten @ [Label label]
162+
else
163+
let init = zero_initializer ret_typ in
164+
[Decl ({ name = ret_var; typ = ret_typ }, init)]
165+
@ rewritten
166+
@ [Label label; Return (Some (Var ret_var))]
167+
in
168+
Function (cc, flags, ret_typ, lid, binders, new_body)
169+
| d -> d
170+
171+
let rewrite_file (decls: decl list): decl list =
172+
List.map rewrite_decl decls

lib/Options.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,5 @@ let allow_tapps = ref false
166166
(* Rust only: foo_bar_baz.fst gets emitted as foo/bar_baz.fst with depth=1 and
167167
foo/bar/baz.fst with depth = 2 (you get the idea). *)
168168
let depth = ref 1
169+
170+
let goto_for_early_return = ref false

lib/PrintC.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,10 @@ and p_stmt (s: stmt) =
493493
string "break" ^^ semi
494494
| Continue ->
495495
string "continue" ^^ semi
496+
| Goto label ->
497+
group (string "goto" ^^ space ^^ string label ^^ semi)
498+
| Label label ->
499+
group (string label ^^ colon ^^ space ^^ semi)
496500

497501
and p_stmts stmts = separate_map hardline p_stmt stmts
498502

src/Karamel.ml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,9 @@ Supported options:|}
357357
one of the included system headers";
358358
"-faggressive-inlining", Arg.Set Options.aggressive_inlining, " attempt to inline \
359359
every variable for more compact code-generation";
360+
"-goto_for_early_return", Arg.Set Options.goto_for_early_return, " replace early \
361+
returns with assignments to a return variable and gotos to a label at the end \
362+
of the function";
360363
"", Arg.Unit (fun _ -> ()), " ";
361364

362365
(* For developers *)
@@ -806,6 +809,14 @@ Supported options:|}
806809

807810
let files = List.filter (fun (_, decls) -> List.length decls > 0) files in
808811

812+
(* Apply goto-for-early-return on C* before lowering to C11 *)
813+
let files =
814+
if !Options.goto_for_early_return then
815+
List.map (fun (name, decls) -> name, GotoForEarlyReturn.rewrite_file decls) files
816+
else
817+
files
818+
in
819+
809820
(* ... then to C *)
810821
let headers = CStarToC11.mk_headers c_name_map files in
811822
let deps = CStarToC11.drop_empty_headers deps headers in

test/Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ FSTAR = $(FSTAR_EXE) \
9090
--trivial_pre_for_unannotated_effectful_fns false \
9191
--cmi --warn_error -274
9292

93-
all: $(FILES) rust $(WASM_FILES) $(CUSTOM) ctypes-test sepcomp-test rust-val-test bundle-test rust-propererasure-bundle-test
93+
all: $(FILES) rust $(WASM_FILES) $(CUSTOM) ctypes-test sepcomp-test rust-val-test bundle-test rust-propererasure-bundle-test goto-return-test
9494

9595
# Needs node
9696
wasm: $(WASM_FILES)
@@ -314,6 +314,10 @@ rust-propererasure-bundle-test:
314314
+$(MAKE) -C rust-propererasure-bundle
315315
.PHONY: rust-propererasure-bundle-test
316316

317+
goto-return-test:
318+
+$(MAKE) -C goto_return
319+
.PHONY: goto-return-test
320+
317321
CTYPES_HAND_WRITTEN_FILES=Client.ml Makefile
318322

319323
.PHONY: $(LOWLEVEL_DIR)/Client.native

0 commit comments

Comments
 (0)