Skip to content

Commit abe1f24

Browse files
authored
Merge pull request #459 from AbsInt/full-switch
Handle Duff's device and other unstructured `switch` statements
2 parents e637a49 + 5a9f24b commit abe1f24

13 files changed

Lines changed: 500 additions & 142 deletions

File tree

cfrontend/C2C.ml

Lines changed: 18 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,61 +1028,6 @@ let convertAsm loc env txt outputs inputs clobber =
10281028
| None -> e
10291029
| Some lhs -> Eassign (convertLvalue env lhs, e, typeof e)
10301030

1031-
(* Separate the cases of a switch statement body *)
1032-
1033-
type switchlabel =
1034-
| Case of C.exp
1035-
| Default
1036-
1037-
type switchbody =
1038-
| Label of switchlabel
1039-
| Stmt of C.stmt
1040-
1041-
let rec flattenSwitch = function
1042-
| {sdesc = C.Sseq(s1, s2)} ->
1043-
flattenSwitch s1 @ flattenSwitch s2
1044-
| {sdesc = C.Slabeled(C.Scase(e, _), s1)} ->
1045-
Label(Case e) :: flattenSwitch s1
1046-
| {sdesc = C.Slabeled(C.Sdefault, s1)} ->
1047-
Label Default :: flattenSwitch s1
1048-
| {sdesc = C.Slabeled(C.Slabel lbl, s1); sloc = loc} ->
1049-
Stmt {sdesc = C.Slabeled(C.Slabel lbl, Cutil.sskip); sloc = loc}
1050-
:: flattenSwitch s1
1051-
| s ->
1052-
[Stmt s]
1053-
1054-
let rec groupSwitch = function
1055-
| [] ->
1056-
(Cutil.sskip, [])
1057-
| Label case :: rem ->
1058-
let (fst, cases) = groupSwitch rem in
1059-
(Cutil.sskip, (case, fst) :: cases)
1060-
| Stmt s :: rem ->
1061-
let (fst, cases) = groupSwitch rem in
1062-
(Cutil.sseq s.sloc s fst, cases)
1063-
1064-
(* Test whether the statement contains case and give an *)
1065-
let rec contains_case s =
1066-
match s.sdesc with
1067-
| C.Sskip
1068-
| C.Sdo _
1069-
| C.Sbreak
1070-
| C.Scontinue
1071-
| C.Sswitch _ (* Stop at a switch *)
1072-
| C.Sgoto _
1073-
| C.Sreturn _
1074-
| C.Sdecl _
1075-
| C.Sasm _ -> ()
1076-
| C.Sseq (s1,s2)
1077-
| C.Sif(_,s1,s2) -> contains_case s1; contains_case s2
1078-
| C.Swhile (_,s1)
1079-
| C.Sdowhile (s1,_) -> contains_case s1
1080-
| C.Sfor (s1,e,s2,s3) -> contains_case s1; contains_case s2; contains_case s3
1081-
| C.Slabeled(C.Scase _, _) ->
1082-
unsupported "'case' statement not in 'switch' statement"
1083-
| C.Slabeled(_,s) -> contains_case s
1084-
| C.Sblock b -> List.iter contains_case b
1085-
10861031
(** Annotations for line numbers *)
10871032

10881033
(** Statements *)
@@ -1122,20 +1067,8 @@ let rec convertStmt env s =
11221067
| C.Scontinue ->
11231068
Csyntax.Scontinue
11241069
| C.Sswitch(e, s1) ->
1125-
let (init, cases) = groupSwitch (flattenSwitch s1) in
1126-
let rec init_debug s =
1127-
match s.sdesc with
1128-
| Sseq (a,b) -> init_debug a && init_debug b
1129-
| C.Sskip -> true
1130-
| _ -> Cutil.is_debug_stmt s in
1131-
if init.sdesc <> C.Sskip && not (init_debug init) then
1132-
begin
1133-
warning Diagnostics.Unnamed "ignored code at beginning of 'switch'";
1134-
contains_case init
1135-
end;
11361070
let te = convertExpr env e in
1137-
swrap (Ctyping.sswitch te
1138-
(convertSwitch env (is_int64 env e.etyp) cases))
1071+
swrap (Ctyping.sswitch te (convertSwitch env (is_int64 env e.etyp) s1))
11391072
| C.Slabeled(C.Slabel lbl, s1) ->
11401073
Csyntax.Slabel(intern_string lbl, convertStmt env s1)
11411074
| C.Slabeled(C.Scase _, _) ->
@@ -1158,23 +1091,24 @@ let rec convertStmt env s =
11581091
Csyntax.Sdo (convertAsm s.sloc env txt outputs inputs clobber)
11591092

11601093
and convertSwitch env is_64 = function
1161-
| [] ->
1094+
| {sdesc = C.Sskip} ->
11621095
LSnil
1163-
| (lbl, s) :: rem ->
1164-
updateLoc s.sloc;
1165-
let lbl' =
1166-
match lbl with
1167-
| Default ->
1168-
None
1169-
| Case e ->
1170-
match Ceval.integer_expr env e with
1171-
| None -> unsupported "expression is not an integer constant expression";
1172-
None
1173-
| Some v -> Some (if is_64
1174-
then Z.of_uint64 v
1175-
else Z.of_uint32 (Int64.to_int32 v))
1176-
in
1177-
LScons(lbl', convertStmt env s, convertSwitch env is_64 rem)
1096+
| {sdesc = C.Slabeled(lbl, s)} ->
1097+
convertSwitchCase env is_64 lbl s LSnil
1098+
| {sdesc = C.Sseq ({sdesc = C.Slabeled(lbl, s)}, rem)} ->
1099+
convertSwitchCase env is_64 lbl s (convertSwitch env is_64 rem)
1100+
| _ ->
1101+
assert false
1102+
1103+
and convertSwitchCase env is_64 lbl s k =
1104+
let lbl' =
1105+
match lbl with
1106+
| C.Sdefault ->
1107+
None
1108+
| C.Scase(e, v) ->
1109+
Some (if is_64 then Z.of_uint64 v else Z.of_uint32 (Int64.to_int32 v))
1110+
| _ -> assert false in
1111+
LScons(lbl', convertStmt env s, k)
11781112

11791113
(** Function definitions *)
11801114

cparser/Parse.ml

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,23 @@
1616

1717
(* Entry point for the library: parse, elaborate, and transform *)
1818

19-
module CharSet = Set.Make(struct type t = char let compare = compare end)
20-
21-
let transform_program t p =
22-
let run_pass pass flag p =
23-
if CharSet.mem flag t then begin
24-
let p = pass p in
25-
Diagnostics.check_errors ();
26-
p
27-
end else
28-
p
29-
in
30-
p
31-
|> run_pass Unblock.program 'b'
32-
|> run_pass PackedStructs.program 'p'
33-
|> run_pass StructPassing.program 's'
34-
|> Rename.program
35-
36-
let parse_transformations s =
37-
let t = ref CharSet.empty in
38-
let set s = String.iter (fun c -> t := CharSet.add c !t) s in
39-
String.iter
40-
(function 'b' -> set "b"
41-
| 's' -> set "s"
42-
| 'p' -> set "bp"
43-
| _ -> ())
44-
s;
45-
!t
19+
let transform_program ~unblock ~switch_norm ~struct_passing ~packed_structs p =
20+
let run_pass pass p =
21+
let p' = pass p in Diagnostics.check_errors (); p' in
22+
let run_opt_pass pass flag p =
23+
if flag then run_pass pass p else p
24+
and run_opt_pass3 pass flag p =
25+
match flag with
26+
| `Off -> p
27+
| `Partial -> run_pass (pass false) p
28+
| `Full -> run_pass (pass true) p in
29+
let unblock = unblock || switch_norm <> `Off || packed_structs in
30+
p
31+
|> run_opt_pass Unblock.program unblock
32+
|> run_opt_pass3 SwitchNorm.program switch_norm
33+
|> run_opt_pass PackedStructs.program packed_structs
34+
|> run_opt_pass StructPassing.program struct_passing
35+
|> Rename.program
4636

4737
let read_file sourcefile =
4838
let ic = open_in_bin sourcefile in
@@ -65,7 +55,11 @@ let parse_string name text =
6555
Timeout_pr means that we ran for 2^50 steps. *)
6656
Diagnostics.fatal_error Diagnostics.no_loc "internal error while parsing"
6757

68-
let preprocessed_file transfs name sourcefile =
58+
let preprocessed_file ?(unblock = false)
59+
?(switch_norm = `Off)
60+
?(struct_passing = false)
61+
?(packed_structs = false)
62+
name sourcefile =
6963
Diagnostics.reset();
7064
let check_errors x =
7165
Diagnostics.check_errors(); x in
@@ -79,5 +73,6 @@ let preprocessed_file transfs name sourcefile =
7973
|> Timing.time2 "Parsing" parse_string name
8074
|> Timing.time "Elaboration" Elab.elab_file
8175
|> check_errors
82-
|> Timing.time2 "Emulations" transform_program (parse_transformations transfs)
76+
|> Timing.time "Emulations"
77+
(transform_program ~unblock ~switch_norm ~struct_passing ~packed_structs)
8378
|> check_errors

cparser/Parse.mli

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,16 @@
1616

1717
(* Entry point for the library: parse, elaborate, and transform *)
1818

19-
val preprocessed_file: string -> string -> string -> C.program
20-
21-
(* first arg: desired transformations
22-
second arg: source file name before preprocessing
23-
third arg: file after preprocessing *)
19+
val preprocessed_file:
20+
?unblock: bool ->
21+
?switch_norm: [`Off | `Partial | `Full] ->
22+
?struct_passing: bool ->
23+
?packed_structs: bool ->
24+
string -> string -> C.program
25+
(** [preprocessed_file filename sourcetext] performs parsing,
26+
elaboration, and optional source-to-source transformations.
27+
[filename] is the name of the source file, for error messages.
28+
[sourcetext] is the text of the source file after preprocessing.
29+
The optional arguments indicate which source-to-source
30+
transformations to perform. They default to [false] or [`Off]
31+
(do not perform). *)

cparser/SwitchNorm.ml

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
(* *********************************************************************)
2+
(* *)
3+
(* The Compcert verified compiler *)
4+
(* *)
5+
(* Xavier Leroy, Collège de France and Inria *)
6+
(* *)
7+
(* Copyright Institut National de Recherche en Informatique et en *)
8+
(* Automatique. All rights reserved. This file is distributed *)
9+
(* under the terms of the GNU Lesser General Public License as *)
10+
(* published by the Free Software Foundation, either version 2.1 of *)
11+
(* the License, or (at your option) any later version. *)
12+
(* This file is also distributed under the terms of the *)
13+
(* INRIA Non-Commercial License Agreement. *)
14+
(* *)
15+
(* *********************************************************************)
16+
17+
(* Normalization of structured "switch" statements
18+
and emulation of unstructured "switch" statements (e.g. Duff's device) *)
19+
20+
(* Assumes: code without blocks
21+
Produces: code without blocks and with normalized "switch" statements *)
22+
23+
(* A normalized switch has the following form:
24+
Sswitch(e, Sseq (Slabeled(lbl1, case1),
25+
Sseq (...
26+
Sseq (Slabeled(lblN,caseN), Sskip) ...)))
27+
*)
28+
29+
open Printf
30+
open C
31+
open Cutil
32+
33+
let support_unstructured = ref false
34+
35+
type switchlabel =
36+
| Case of exp * int64
37+
| Default
38+
39+
type switchbody =
40+
| Label of switchlabel * location
41+
| Stmt of stmt
42+
43+
let rec flatten_switch = function
44+
| {sdesc = Sseq(s1, s2)} :: rem ->
45+
flatten_switch (s1 :: s2 :: rem)
46+
| {sdesc = Slabeled(Scase(e, n), s1); sloc = loc} :: rem ->
47+
Label(Case(e, n), loc) :: flatten_switch (s1 :: rem)
48+
| {sdesc = Slabeled(Sdefault, s1); sloc = loc} :: rem ->
49+
Label(Default, loc) :: flatten_switch (s1 :: rem)
50+
| {sdesc = Slabeled(Slabel lbl, s1); sloc = loc} :: rem ->
51+
Stmt {sdesc = Slabeled(Slabel lbl, Cutil.sskip); sloc = loc}
52+
:: flatten_switch (s1 :: rem)
53+
| s :: rem ->
54+
Stmt s :: flatten_switch rem
55+
| [] ->
56+
[]
57+
58+
let rec group_switch = function
59+
| [] ->
60+
(Cutil.sskip, [])
61+
| Label(case, loc) :: rem ->
62+
let (fst, cases) = group_switch rem in
63+
(Cutil.sskip, (case, loc, fst) :: cases)
64+
| Stmt s :: rem ->
65+
let (fst, cases) = group_switch rem in
66+
(Cutil.sseq s.sloc s fst, cases)
67+
68+
let label_of_switchlabel = function
69+
| Case(e, n) -> Scase(e, n)
70+
| Default -> Sdefault
71+
72+
let make_slabeled (l, loc, s) =
73+
{ sdesc = Slabeled(label_of_switchlabel l, s); sloc = loc }
74+
75+
let make_sequence sl =
76+
List.fold_right (Cutil.sseq no_loc) sl Cutil.sskip
77+
78+
let make_normalized_switch e cases =
79+
Sswitch(e, make_sequence (List.map make_slabeled cases))
80+
81+
let rec all_cases accu s =
82+
match s.sdesc with
83+
| Sseq(s1, s2) -> all_cases (all_cases accu s1) s2
84+
| Sif(_, s1, s2) -> all_cases (all_cases accu s1) s2
85+
| Swhile(_, s1) -> all_cases accu s1
86+
| Sdowhile(s1, _) -> all_cases accu s1
87+
| Sfor(s1, _, s2, s3) -> all_cases (all_cases (all_cases accu s1) s2) s3
88+
| Sswitch(_, _) -> accu
89+
| Slabeled(Scase(e, n), s1) -> all_cases (Case(e, n) :: accu) s1
90+
| Slabeled(Sdefault, s1) -> all_cases (Default :: accu) s1
91+
| Slabeled(Slabel _, s1) -> all_cases accu s1
92+
| Sblock _ -> assert false
93+
| _ -> accu
94+
95+
let substitute_cases case_table body end_label =
96+
let sub = Hashtbl.create 32 in
97+
List.iter
98+
(fun (case, lbl) -> Hashtbl.add sub case (Slabel lbl))
99+
case_table;
100+
let transf_label = function
101+
| Scase(e, n) ->
102+
(try Hashtbl.find sub (Case(e, n)) with Not_found -> assert false)
103+
| Sdefault ->
104+
(try Hashtbl.find sub Default with Not_found -> assert false)
105+
| Slabel _ as lbl -> lbl in
106+
let rec transf inloop s =
107+
{s with sdesc =
108+
match s.sdesc with
109+
| Sseq(s1, s2) -> Sseq(transf inloop s1, transf inloop s2)
110+
| Sif(e, s1, s2) -> Sif(e, transf inloop s1, transf inloop s2)
111+
| Swhile(e, s1) -> Swhile(e, transf true s1)
112+
| Sdowhile(s1, e) -> Sdowhile(transf true s1, e)
113+
| Sfor(s1, e, s2, s3) ->
114+
Sfor(transf inloop s1, e, transf inloop s2, transf true s3)
115+
| Sbreak -> if inloop then Sbreak else Sgoto end_label
116+
| Slabeled(lbl, s1) -> Slabeled(transf_label lbl, transf inloop s1)
117+
| Sblock _ -> assert false
118+
| sd -> sd }
119+
in transf false body
120+
121+
let rec is_skip_or_debug s =
122+
match s.sdesc with
123+
| Sseq (a, b) -> is_skip_or_debug a && is_skip_or_debug b
124+
| Sskip -> true
125+
| _ -> Cutil.is_debug_stmt s
126+
127+
let new_label = ref 0
128+
129+
let gen_label () = incr new_label; sprintf "@%d" !new_label
130+
131+
let normalize_switch loc e body =
132+
let (init, cases) = [body] |> flatten_switch |> group_switch
133+
and allcases = List.rev (all_cases [] body) in
134+
if is_skip_or_debug init && List.length cases = List.length allcases then
135+
(* This is a structured switch *)
136+
make_normalized_switch e cases
137+
else begin
138+
(* This switch needs to be converted *)
139+
if not !support_unstructured then
140+
Diagnostics.error loc
141+
"unsupported feature: non-structured 'switch' statement \
142+
(consider adding option [-funstructured-switch])";
143+
let case_table = List.map (fun case -> (case, gen_label())) allcases in
144+
let end_lbl = gen_label() in
145+
let newbody = substitute_cases case_table body end_lbl in
146+
let goto_case (case, lbl) =
147+
(case, no_loc, {sdesc = Sgoto lbl; sloc = no_loc}) in
148+
let case_table' =
149+
if List.mem_assoc Default case_table
150+
then case_table
151+
else (Default, end_lbl) :: case_table in
152+
Sseq({sdesc = make_normalized_switch e (List.map goto_case case_table');
153+
sloc = loc},
154+
sseq no_loc newbody
155+
{sdesc = Slabeled(Slabel end_lbl, sskip); sloc = no_loc})
156+
end
157+
158+
let rec transform_stmt s =
159+
{ s with sdesc =
160+
match s.sdesc with
161+
| Sseq(s1, s2) -> Sseq(transform_stmt s1, transform_stmt s2)
162+
| Sif(e, s1, s2) -> Sif(e, transform_stmt s1, transform_stmt s2)
163+
| Swhile(e, s1) -> Swhile(e, transform_stmt s1)
164+
| Sdowhile(s1, e) -> Sdowhile(transform_stmt s1, e)
165+
| Sfor(s1, e, s2, s3) ->
166+
Sfor(transform_stmt s1, e, transform_stmt s2, transform_stmt s3)
167+
| Sswitch(e, s1) -> normalize_switch s.sloc e (transform_stmt s1)
168+
| Slabeled(lbl, s1) -> Slabeled(lbl, transform_stmt s1)
169+
| Sblock sl -> Sblock(List.map transform_stmt sl)
170+
| sd -> sd}
171+
172+
let transform_fundef env loc fd =
173+
{ fd with fd_body = transform_stmt fd.fd_body }
174+
175+
(* Entry point *)
176+
177+
let program unstructured p =
178+
support_unstructured := unstructured;
179+
Transform.program ~fundef: transform_fundef p

0 commit comments

Comments
 (0)