Skip to content

Commit a124773

Browse files
authored
Lean: split up files along import lines (#1173)
1 parent 864b074 commit a124773

34 files changed

+238
-191
lines changed

src/sail_lean_backend/pretty_print_lean.ml

+79-15
Original file line numberDiff line numberDiff line change
@@ -1116,25 +1116,40 @@ let doc_val ctx pat exp =
11161116
let base_pp = doc_exp false ctx exp in
11171117
nest 2 (group (string "def" ^^ space ^^ idpp ^^ typpp ^^ space ^^ coloneq ^/^ base_pp))
11181118

1119-
let rec doc_defs_rec ctx defs types docdefs =
1119+
let should_print_function_def def =
1120+
match def with
1121+
| DEF_aux (DEF_fundef fdef, dannot) -> not (Env.is_extern (id_of_fundef fdef) dannot.env "lean")
1122+
| DEF_aux (DEF_let (LB_aux (LB_val (pat, exp), _)), _) -> true
1123+
| _ -> false
1124+
1125+
let rec doc_defs_rec ctx defs types (former_funcs : document list) (docdefs : document) =
11201126
match defs with
1121-
| [] -> (types, docdefs)
1127+
| [] -> (types, former_funcs @ [docdefs])
11221128
| DEF_aux (DEF_fundef fdef, dannot) :: defs' ->
11231129
let env = dannot.env in
11241130
let pp_f =
11251131
if Env.is_extern (id_of_fundef fdef) env "lean" then docdefs
11261132
else docdefs ^^ group (doc_fundef ctx fdef) ^/^ hardline
11271133
in
1128-
doc_defs_rec ctx defs' types pp_f
1134+
doc_defs_rec ctx defs' types former_funcs pp_f
11291135
| DEF_aux (DEF_type tdef, _) :: defs' when List.mem (string_of_id (id_of_type_def tdef)) !opt_extern_types ->
1130-
doc_defs_rec ctx defs' types docdefs
1136+
doc_defs_rec ctx defs' types former_funcs docdefs
11311137
| DEF_aux (DEF_type tdef, _) :: defs' ->
1132-
doc_defs_rec ctx defs' (types ^^ group (doc_typdef ctx tdef) ^/^ hardline) docdefs
1138+
doc_defs_rec ctx defs' (types ^^ group (doc_typdef ctx tdef) ^/^ hardline) former_funcs docdefs
11331139
| DEF_aux (DEF_let (LB_aux (LB_val (pat, exp), _)), _) :: defs' ->
1134-
doc_defs_rec ctx defs' types (docdefs ^^ group (doc_val ctx pat exp) ^/^ hardline)
1135-
| _ :: defs' -> doc_defs_rec ctx defs' types docdefs
1136-
1137-
let doc_defs ctx defs = doc_defs_rec ctx defs empty empty
1140+
doc_defs_rec ctx defs' types former_funcs (docdefs ^^ group (doc_val ctx pat exp) ^/^ hardline)
1141+
| DEF_aux (DEF_pragma ("include_start", Pragma_line (file, _)), _) :: defs'
1142+
| DEF_aux (DEF_pragma ("file_start", Pragma_line (file, _)), _) :: defs'
1143+
| DEF_aux (DEF_pragma ("include_end", Pragma_line (file, _)), _) :: defs'
1144+
| DEF_aux (DEF_pragma ("file_end", Pragma_line (file, _)), _) :: defs'
1145+
when Filename.check_suffix file ".sail" ->
1146+
if docdefs = empty then doc_defs_rec ctx defs' types former_funcs docdefs
1147+
else doc_defs_rec ctx defs' types (former_funcs @ [docdefs]) empty
1148+
| d :: defs' ->
1149+
if should_print_function_def d then failwith "this case of doc_defs_rec should be unreachable"
1150+
else doc_defs_rec ctx defs' types former_funcs docdefs
1151+
1152+
let doc_defs ctx defs = doc_defs_rec ctx defs empty [] empty
11381153

11391154
(* Remove all imports for now, they will be printed in other files. Probably just for testing. *)
11401155
let rec remove_imports (defs : (Libsail.Type_check.tannot, Libsail.Type_check.env) def list) depth =
@@ -1258,8 +1273,40 @@ let populate_fun_args defs =
12581273
in
12591274
List.fold_left (fun args d -> add_args args d) Bindings.empty defs
12601275

1261-
let pp_ast_lean (env : Type_check.env) effect_info ({ defs; _ } as ast : Libsail.Type_check.typed_ast) types_file
1262-
funcs_file =
1276+
let rec collect_import_files_aux defs file_stack last_namespace ret =
1277+
match defs with
1278+
| [] -> ret
1279+
| DEF_aux (DEF_pragma ("include_start", Pragma_line (file, _)), _) :: ds
1280+
| DEF_aux (DEF_pragma ("file_start", Pragma_line (file, _)), _) :: ds
1281+
when Filename.check_suffix file ".sail" ->
1282+
collect_import_files_aux ds (file :: file_stack) last_namespace ret
1283+
| DEF_aux (DEF_pragma ("include_end", Pragma_line (file, _)), _) :: ds
1284+
| DEF_aux (DEF_pragma ("file_end", Pragma_line (file, _)), _) :: ds
1285+
when Filename.check_suffix file ".sail" -> (
1286+
match file_stack with
1287+
| f :: fs -> collect_import_files_aux ds fs last_namespace ret
1288+
| _ -> failwith "should not be reachable"
1289+
)
1290+
| d :: ds -> (
1291+
match file_stack with
1292+
| f :: _ ->
1293+
if should_print_function_def d && not (last_namespace = Some f) then
1294+
collect_import_files_aux ds file_stack (Some f) (ret @ [f])
1295+
else collect_import_files_aux ds file_stack last_namespace ret
1296+
| _ -> failwith "should not be reachable"
1297+
)
1298+
1299+
let collect_import_files defs base =
1300+
let res = collect_import_files_aux defs [base] None [] in
1301+
if res = [] then [base] else res
1302+
1303+
let rec take n xs = match (n, xs) with 0, _ -> [] | n, x :: xs -> x :: take (n - 1) xs | n, xs -> xs
1304+
1305+
let rec last xs =
1306+
match xs with [] -> failwith "cannot take last element of empty list" | [x] -> x | x :: xs -> last xs
1307+
1308+
let pp_ast_lean (env : Type_check.env) effect_info ({ defs; _ } as ast : Libsail.Type_check.typed_ast) out_name_camel
1309+
types_file imp_funcs_files funcs_file noncomputable =
12631310
let regs = State.find_registers defs in
12641311
let fun_args = populate_fun_args defs in
12651312
let global = { effect_info; fun_args } in
@@ -1271,12 +1318,29 @@ let pp_ast_lean (env : Type_check.env) effect_info ({ defs; _ } as ast : Libsail
12711318
in
12721319
let monad = doc_monad_abbrev defs has_registers in
12731320
let instantiations = doc_instantiations ctx env in
1274-
let types, fundefs = doc_defs ctx defs in
1275-
let fundefs = string "namespace Functions\n\n" ^^ fundefs ^^ string "end Functions\n" in
1321+
let types, all_fundefss = doc_defs ctx defs in
1322+
let imp_fundefss, main_fundefs =
1323+
if imp_funcs_files = [] then ([], concat all_fundefss)
1324+
else (
1325+
let imp_fundefss = take (List.length all_fundefss - 1) all_fundefss in
1326+
let main_fundefs = last all_fundefss in
1327+
(imp_fundefss, main_fundefs)
1328+
)
1329+
in
1330+
let main_fundefs = main_fundefs ^^ string ("end " ^ out_name_camel ^ ".Functions") ^^ hardline in
12761331
let main_function =
1277-
if !the_main_function_has_been_seen then main_function_stub effect_info has_registers else empty
1332+
if !the_main_function_has_been_seen then (
1333+
let stub = main_function_stub effect_info has_registers in
1334+
[string ("open " ^ out_name_camel ^ ".Functions\n\n") ^^ stub]
1335+
)
1336+
else []
12781337
in
12791338
let opens = IdSet.fold (fun id doc -> string "open " ^^ doc_id_ctor id ^^ hardline ^^ doc) !opens empty in
12801339
print types_file (types ^^ register_refs ^^ monad ^^ instantiations);
1281-
print funcs_file (opens ^^ hardline ^^ fundefs ^^ string "open Functions\n\n" ^^ main_function);
1340+
let _ =
1341+
List.map2
1342+
(fun file defs -> print file (separate hardline (remove_empties [opens; defs])))
1343+
imp_funcs_files imp_fundefss
1344+
in
1345+
print funcs_file (separate hardline (remove_empties ([opens; main_fundefs] @ main_function)));
12821346
!the_main_function_has_been_seen

src/sail_lean_backend/sail_plugin_lean.ml

+70-20
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ let opt_lean_import_files : string list ref = ref []
7777

7878
let opt_lean_noncomputable : bool ref = ref false
7979

80+
let opt_single_file : bool ref = ref false
81+
8082
let lean_version : string = "lean4:nightly-2025-03-17"
8183

8284
let lean_options =
@@ -89,6 +91,10 @@ let lean_options =
8991
Arg.Unit (fun () -> opt_lean_force_output := true),
9092
"removes the content of the output directory if it is non-empty"
9193
);
94+
( Flag.create ~prefix:["lean"] "single_file",
95+
Arg.Unit (fun () -> opt_single_file := true),
96+
"puts the entire output in a single .lean file"
97+
);
9298
( Flag.create ~prefix:["lean"] "noncomputable",
9399
Arg.Unit (fun () -> opt_lean_noncomputable := true),
94100
"add a 'noncomputable section' at the beginning of the output"
@@ -183,6 +189,7 @@ type lean_context = {
183189
sail_dir : string;
184190
types_file : out_channel;
185191
funcs_file : out_channel;
192+
import_files : out_channel list;
186193
lakefile : out_channel;
187194
}
188195

@@ -205,7 +212,24 @@ let path_to_static_libarary sail_dir str = Filename.quote (sail_dir ^ "/src/sail
205212
let copy_from_static_library sail_dir lean_sail_dir str =
206213
Unix.system ("cp " ^ path_to_static_libarary sail_dir str ^ " " ^ Filename.quote lean_sail_dir)
207214

208-
let start_lean_output (out_name : string) default_sail_dir =
215+
let print_function_file_prelude file out_name_camel (prev_file : string option) =
216+
let _ =
217+
match prev_file with
218+
| None ->
219+
output_string file ("import " ^ out_name_camel ^ ".Sail.Sail\n");
220+
output_string file ("import " ^ out_name_camel ^ ".Sail.BitVec\n");
221+
output_string file ("import " ^ out_name_camel ^ ".Sail.IntRange\n");
222+
output_string file ("import " ^ out_name_camel ^ ".Defs\n");
223+
List.iter
224+
(fun filename -> output_string file ("import " ^ out_name_camel ^ "." ^ file_to_module filename ^ "\n"))
225+
!opt_lean_import_files
226+
| Some n -> output_string file ("import " ^ out_name_camel ^ "." ^ n ^ "\n")
227+
in
228+
output_string file ("\n" ^ file_prelude);
229+
if !opt_lean_noncomputable then output_string file "noncomputable section\n\n";
230+
output_string file ("namespace " ^ out_name_camel ^ ".Functions\n\n")
231+
232+
let start_lean_output (out_name : string) (import_names : string list) default_sail_dir =
209233
let base_dir = match !opt_lean_output_dir with Some dir -> dir | None -> "." in
210234
let project_dir = Filename.concat base_dir out_name in
211235
if !opt_lean_force_output && Sys.file_exists project_dir && Sys.is_directory project_dir then (
@@ -222,6 +246,7 @@ let start_lean_output (out_name : string) default_sail_dir =
222246
close_out lean_toolchain;
223247
let sail_dir = Reporting.get_sail_dir default_sail_dir in
224248
let out_name_camel = Libsail.Util.to_upper_camel_case out_name in
249+
let import_names_camel = List.map Libsail.Util.to_upper_camel_case import_names in
225250
let lean_src_dir = Filename.concat project_dir out_name_camel in
226251
if not (Sys.file_exists lean_src_dir) then Unix.mkdir lean_src_dir 0o775;
227252
let lean_sail_dir = lean_src_dir ^ "/Sail/" in
@@ -244,17 +269,21 @@ let start_lean_output (out_name : string) default_sail_dir =
244269
output_string types_file "open PreSail\n\n";
245270
output_string types_file file_prelude;
246271
let funcs_file = open_out (Filename.concat project_dir (out_name_camel ^ ".lean")) in
247-
output_string funcs_file ("import " ^ out_name_camel ^ ".Sail.Sail\n");
248-
output_string funcs_file ("import " ^ out_name_camel ^ ".Sail.BitVec\n");
249-
output_string funcs_file ("import " ^ out_name_camel ^ ".Sail.IntRange\n");
250-
output_string funcs_file ("import " ^ out_name_camel ^ ".Defs\n");
251-
List.iter
252-
(fun filename -> output_string funcs_file ("import " ^ out_name_camel ^ "." ^ file_to_module filename ^ "\n"))
253-
!opt_lean_import_files;
254-
output_string funcs_file ("\n" ^ file_prelude);
255-
if !opt_lean_noncomputable then output_string funcs_file "noncomputable section\n\n";
256272
let lakefile = open_out (Filename.concat project_dir "lakefile.toml") in
257-
{ out_name; out_name_camel; sail_dir; types_file; funcs_file; lakefile }
273+
let import_files =
274+
List.map (fun name -> open_out (Filename.concat lean_src_dir (name ^ ".lean"))) import_names_camel
275+
in
276+
let last_import_name =
277+
List.fold_left
278+
(fun prev_name (out, n) ->
279+
print_function_file_prelude out out_name_camel prev_name;
280+
Some n
281+
)
282+
None
283+
(List.combine import_files import_names_camel)
284+
in
285+
print_function_file_prelude funcs_file out_name_camel last_import_name;
286+
{ out_name; out_name_camel; sail_dir; types_file; funcs_file; import_files; lakefile }
258287

259288
let close_context ctx =
260289
close_out ctx.types_file;
@@ -265,27 +294,48 @@ let create_lake_project (ctx : lean_context) executable =
265294
(* Change the base directory if the option '--lean-output-dir' is set *)
266295
output_string ctx.lakefile
267296
("name = \"" ^ ctx.out_name ^ "\"\ndefaultTargets = [\"" ^ ctx.out_name_camel
268-
^ "\"]\n\
269-
moreLeanArgs = [\"--tstack=400000\"]\n\
270-
moreGlobalServerArgs = [\"--tstack=400000\"]\n\n\
271-
[[lean_lib]]\n\
272-
name = \"" ^ ctx.out_name_camel ^ "\""
297+
^ "\"]\nmoreLeanArgs = [\"--tstack=400000\"]\n\n[[lean_lib]]\nname = \"" ^ ctx.out_name_camel ^ "\""
273298
);
274299
if executable then (
275300
output_string ctx.lakefile "\n\n[[lean_exe]]\n";
276301
output_string ctx.lakefile "name = \"run\"\n";
277302
output_string ctx.lakefile ("root = \"" ^ ctx.out_name_camel ^ "\"\n")
278303
)
279304

280-
let output (out_name : string) env effect_info ast default_sail_dir =
281-
let ctx = start_lean_output out_name default_sail_dir in
282-
let executable = Pretty_print_lean.pp_ast_lean env effect_info ast ctx.types_file ctx.funcs_file in
305+
let rec dedup_files (files : string list) (acc : string list) =
306+
match files with
307+
| [] -> acc
308+
| f :: fs -> (
309+
match List.fold_left (fun n y -> if y = f then n + 1 else n) 0 acc with
310+
| 0 -> dedup_files fs (acc @ [f])
311+
| n -> dedup_files fs (acc @ [f ^ Int.to_string (n - 1)])
312+
)
313+
314+
let output (out_name : string) env effect_info ({ defs; _ } as ast : Libsail.Type_check.typed_ast) default_sail_dir
315+
single_file noncomputable =
316+
let imports =
317+
if single_file then []
318+
else (
319+
(* Collect all non-empty slices between include pragmas in the file *)
320+
let imports = Pretty_print_lean.collect_import_files defs (out_name ^ ".sail") in
321+
let imports = List.map file_to_module imports in
322+
(* Discard the last import file, as we will use the main file instead *)
323+
let imports = Pretty_print_lean.take (List.length imports - 1) imports in
324+
dedup_files imports []
325+
)
326+
in
327+
let ctx = start_lean_output out_name imports default_sail_dir in
328+
let out_name_camel = Libsail.Util.to_upper_camel_case out_name in
329+
let executable =
330+
Pretty_print_lean.pp_ast_lean env effect_info ast out_name_camel ctx.types_file ctx.import_files ctx.funcs_file
331+
noncomputable
332+
in
283333
create_lake_project ctx executable
284334
(* Uncomment for debug output of the Sail code after the rewrite passes *)
285335
(* Pretty_print_sail.output_ast stdout (Type_check.strip_ast ast) *)
286336

287337
let lean_target out_name { default_sail_dir; ctx; ast; effect_info; env; _ } =
288338
let out_name = match out_name with Some f -> f | None -> "out" in
289-
output out_name env effect_info ast default_sail_dir
339+
output out_name env effect_info ast default_sail_dir !opt_single_file !opt_lean_noncomputable
290340

291341
let _ = Target.register ~name:"lean" ~options:lean_options ~rewrites:lean_rewrites ~asserts_termination:true lean_target

test/lean/SailTinyArm.expected.lean

+3-5
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,8 @@ set_option match.ignoreUnusedAlts true
474474

475475
open Sail
476476

477+
namespace Out.Functions
478+
477479
open option
478480
open ast
479481
open arm_acc_type
@@ -504,8 +506,6 @@ open CacheOp
504506
open Barrier
505507
open AccessType
506508

507-
namespace Functions
508-
509509
/-- Type quantifiers: k_ex6347# : Bool, k_ex6346# : Bool -/
510510
def neq_bool (x : Bool) (y : Bool) : Bool :=
511511
(Bool.not (BEq.beq x y))
@@ -2160,6 +2160,4 @@ def initialize_registers (_ : Unit) : SailM Unit := do
21602160
def sail_model_init (x_0 : Unit) : SailM Unit := do
21612161
(initialize_registers ())
21622162

2163-
end Functions
2164-
open Functions
2165-
2163+
end Out.Functions

test/lean/append.expected.lean

+3-5
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ set_option match.ignoreUnusedAlts true
4141

4242
open Sail
4343

44-
open option
44+
namespace Out.Functions
4545

46-
namespace Functions
46+
open option
4747

4848
/-- Type quantifiers: k_ex699# : Bool, k_ex698# : Bool -/
4949
def neq_bool (x : Bool) (y : Bool) : Bool :=
@@ -135,6 +135,4 @@ def initialize_registers (_ : Unit) : Unit :=
135135
def sail_model_init (x_0 : Unit) : Unit :=
136136
(initialize_registers ())
137137

138-
end Functions
139-
open Functions
140-
138+
end Out.Functions

test/lean/atom_bool.expected.lean

+2-5
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ set_option match.ignoreUnusedAlts true
3333

3434
open Sail
3535

36-
37-
namespace Functions
36+
namespace Out.Functions
3837

3938
def foo (_ : Unit) : Bool :=
4039
true
@@ -45,6 +44,4 @@ def initialize_registers (_ : Unit) : Unit :=
4544
def sail_model_init (x_0 : Unit) : Unit :=
4645
(initialize_registers ())
4746

48-
end Functions
49-
open Functions
50-
47+
end Out.Functions

test/lean/bitfield.expected.lean

+3-5
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@ set_option match.ignoreUnusedAlts true
5050

5151
open Sail
5252

53+
namespace Out.Functions
54+
5355
open option
5456
open Register
5557

56-
namespace Functions
57-
5858
/-- Type quantifiers: k_ex700# : Bool, k_ex699# : Bool -/
5959
def neq_bool (x : Bool) (y : Bool) : Bool :=
6060
(Bool.not (BEq.beq x y))
@@ -198,6 +198,4 @@ def initialize_registers (_ : Unit) : SailM Unit := do
198198
def sail_model_init (x_0 : Unit) : SailM Unit := do
199199
(initialize_registers ())
200200

201-
end Functions
202-
open Functions
203-
201+
end Out.Functions

test/lean/bitvec_operation.expected.lean

+3-5
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ set_option match.ignoreUnusedAlts true
4141

4242
open Sail
4343

44-
open option
44+
namespace Out.Functions
4545

46-
namespace Functions
46+
open option
4747

4848
/-- Type quantifiers: k_ex820# : Bool, k_ex819# : Bool -/
4949
def neq_bool (x : Bool) (y : Bool) : Bool :=
@@ -185,6 +185,4 @@ def initialize_registers (_ : Unit) : Unit :=
185185
def sail_model_init (x_0 : Unit) : Unit :=
186186
(initialize_registers ())
187187

188-
end Functions
189-
open Functions
190-
188+
end Out.Functions

0 commit comments

Comments
 (0)