Skip to content

Commit 657a5b8

Browse files
lthlsgasche
authored andcommitted
Bytecode: linear computation of closure environments
1 parent 7ea7f35 commit 657a5b8

File tree

5 files changed

+118
-41
lines changed

5 files changed

+118
-41
lines changed

Changes

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ Working version
163163
type to be ignored (see tests/typing-misc/constraints.ml).
164164
(Jacques Garrigue, report by Richard Eisenberg, review by Leo White)
165165

166+
- #12207, #12222: Make closure computation linear in the number of recursive
167+
functions instead of quadratic
168+
(Vincent Laviron, report by François Pottier, review by Nathanaëlle Courant
169+
and Gabriel Scherer)
170+
166171

167172
OCaml 5.1.0
168173
---------------

bytecomp/bytegen.ml

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,53 @@ let new_label () =
3434
(**** Operations on compilation environments. ****)
3535

3636
let empty_env =
37-
{ ce_stack = Ident.empty; ce_heap = Ident.empty; ce_rec = Ident.empty }
37+
{ ce_stack = Ident.empty; ce_closure = Not_in_closure }
3838

3939
(* Add a stack-allocated variable *)
4040

4141
let add_var id pos env =
4242
{ ce_stack = Ident.add id pos env.ce_stack;
43-
ce_heap = env.ce_heap;
44-
ce_rec = env.ce_rec }
43+
ce_closure = env.ce_closure }
4544

4645
let rec add_vars idlist pos env =
4746
match idlist with
4847
[] -> env
4948
| id :: rem -> add_vars rem (pos + 1) (add_var id pos env)
5049

50+
(* Compute the closure environment *)
51+
52+
let rec add_positions entries pos_to_entry ~pos ~delta = function
53+
| [] -> entries, pos
54+
| id :: rem ->
55+
let entries =
56+
Ident.add id (pos_to_entry pos) entries
57+
in
58+
add_positions entries pos_to_entry ~pos:(pos + delta) ~delta rem
59+
60+
type function_definition =
61+
| Single_non_recursive
62+
| Multiple_recursive of Ident.t list
63+
64+
let closure_entries fun_defs fvs =
65+
let funct_entries, pos_end_functs =
66+
match fun_defs with
67+
| Single_non_recursive ->
68+
(* No need to store the function in the environment, but we still need to
69+
reserve a slot in the closure block *)
70+
Ident.empty, 3
71+
| Multiple_recursive functs ->
72+
add_positions Ident.empty (fun pos -> Function pos) ~pos:0 ~delta:3 functs
73+
in
74+
(* Note: [pos_end_functs] is the position where we would store the next
75+
function if there was one, and points after an eventual infix tag.
76+
Since that was the last function, we don't need the last infix tag
77+
and start storing free variables at [pos_end_functs - 1]. *)
78+
let all_entries, _end_pos =
79+
add_positions funct_entries (fun pos -> Free_variable pos)
80+
~pos:(pos_end_functs - 1) ~delta:1 fvs
81+
in
82+
all_entries
83+
5184
(**** Examination of the continuation ****)
5285

5386
(* Return a label to the beginning of the given continuation.
@@ -370,9 +403,8 @@ type function_to_compile =
370403
{ params: Ident.t list; (* function parameters *)
371404
body: lambda; (* the function body *)
372405
label: label; (* the label of the function entry *)
373-
free_vars: Ident.t list; (* free variables of the function *)
374-
num_defs: int; (* number of mutually recursive definitions *)
375-
rec_vars: Ident.t list; (* mutually recursive fn names *)
406+
entries: closure_entry Ident.tbl; (* the offsets for the free variables
407+
and mutually recursive functions *)
376408
rec_pos: int } (* rank in recursive definition *)
377409

378410
let functions_to_compile = (Stack.create () : function_to_compile Stack.t)
@@ -570,15 +602,18 @@ let rec comp_expr stack_info env exp sz cont =
570602
let pos = Ident.find_same id env.ce_stack in
571603
Kacc(sz - pos) :: cont
572604
with Not_found ->
573-
try
574-
let pos = Ident.find_same id env.ce_heap in
575-
Kenvacc(pos) :: cont
576-
with Not_found ->
577-
try
578-
let ofs = Ident.find_same id env.ce_rec in
579-
Koffsetclosure(ofs) :: cont
580-
with Not_found ->
605+
let not_found () =
581606
fatal_error ("Bytegen.comp_expr: var " ^ Ident.unique_name id)
607+
in
608+
match env.ce_closure with
609+
| Not_in_closure -> not_found ()
610+
| In_closure { entries; env_pos } ->
611+
match Ident.find_same id entries with
612+
| Free_variable pos ->
613+
Kenvacc(pos - env_pos) :: cont
614+
| Function pos ->
615+
Koffsetclosure(pos - env_pos) :: cont
616+
| exception Not_found -> not_found ()
582617
end
583618
| Lconst cst ->
584619
Kconst cst :: cont
@@ -627,9 +662,10 @@ let rec comp_expr stack_info env exp sz cont =
627662
let cont = add_pseudo_event loc !compunit_name cont in
628663
let lbl = new_label() in
629664
let fv = Ident.Set.elements(free_variables exp) in
665+
let entries = closure_entries Single_non_recursive fv in
630666
let to_compile =
631667
{ params = List.map fst params; body = body; label = lbl;
632-
free_vars = fv; num_defs = 1; rec_vars = []; rec_pos = 0 } in
668+
entries = entries; rec_pos = 0 } in
633669
Stack.push to_compile functions_to_compile;
634670
comp_args stack_info env (List.map (fun n -> Lvar n) fv) sz
635671
(Kclosure(lbl, List.length fv) :: cont)
@@ -646,14 +682,16 @@ let rec comp_expr stack_info env exp sz cont =
646682
let fv =
647683
Ident.Set.elements (free_variables (Lletrec(decl, lambda_unit))) in
648684
let rec_idents = List.map (fun (id, _lam) -> id) decl in
685+
let entries =
686+
closure_entries (Multiple_recursive rec_idents) fv
687+
in
649688
let rec comp_fun pos = function
650689
[] -> []
651690
| (_id, Lfunction{params; body}) :: rem ->
652691
let lbl = new_label() in
653692
let to_compile =
654693
{ params = List.map fst params; body = body; label = lbl;
655-
free_vars = fv; num_defs = ndecl; rec_vars = rec_idents;
656-
rec_pos = pos} in
694+
entries = entries; rec_pos = pos} in
657695
Stack.push to_compile functions_to_compile;
658696
lbl :: comp_fun (pos + 1) rem
659697
| _ -> assert false in
@@ -1119,13 +1157,15 @@ let comp_block env exp sz cont =
11191157

11201158
let comp_function tc cont =
11211159
let arity = List.length tc.params in
1122-
let rec positions pos delta = function
1123-
[] -> Ident.empty
1124-
| id :: rem -> Ident.add id pos (positions (pos + delta) delta rem) in
1160+
let ce_stack, _last_pos =
1161+
add_positions Ident.empty Fun.id ~pos:arity ~delta:(-1) tc.params
1162+
in
11251163
let env =
1126-
{ ce_stack = positions arity (-1) tc.params;
1127-
ce_heap = positions (3 * (tc.num_defs - tc.rec_pos) - 1) 1 tc.free_vars;
1128-
ce_rec = positions (-3 * tc.rec_pos) 3 tc.rec_vars } in
1164+
{ ce_stack;
1165+
ce_closure =
1166+
In_closure { entries = tc.entries; env_pos = 3 * tc.rec_pos }
1167+
}
1168+
in
11291169
let cont =
11301170
comp_block env tc.body arity (Kreturn arity :: cont) in
11311171
if arity > 1 then

bytecomp/instruct.ml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,20 @@
1515

1616
open Lambda
1717

18+
type closure_entry =
19+
| Free_variable of int
20+
| Function of int
21+
22+
type closure_env =
23+
| Not_in_closure
24+
| In_closure of {
25+
entries: closure_entry Ident.tbl;
26+
env_pos: int;
27+
}
28+
1829
type compilation_env =
1930
{ ce_stack: int Ident.tbl;
20-
ce_heap: int Ident.tbl;
21-
ce_rec: int Ident.tbl }
31+
ce_closure: closure_env }
2232

2333
type debug_event =
2434
{ mutable ev_pos: int; (* Position in bytecode *)

bytecomp/instruct.mli

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,35 @@ open Lambda
1919

2020
(* Structure of compilation environments *)
2121

22+
type closure_entry =
23+
| Free_variable of int
24+
| Function of int
25+
26+
type closure_env =
27+
| Not_in_closure
28+
| In_closure of {
29+
entries: closure_entry Ident.tbl; (* Offsets of the free variables and
30+
recursive functions from the start of
31+
the block *)
32+
env_pos: int; (* Offset of the current function from
33+
the start of the block *)
34+
}
35+
2236
type compilation_env =
23-
{ ce_stack: int Ident.tbl; (* Positions of variables in the stack *)
24-
ce_heap: int Ident.tbl; (* Structure of the heap-allocated env *)
25-
ce_rec: int Ident.tbl } (* Functions bound by the same let rec *)
37+
{ ce_stack: int Ident.tbl; (* Positions of variables in the stack *)
38+
ce_closure: closure_env } (* Structure of the heap-allocated env *)
2639

2740
(* The ce_stack component gives locations of variables residing
2841
in the stack. The locations are offsets w.r.t. the origin of the
2942
stack frame.
30-
The ce_heap component gives the positions of variables residing in the
31-
heap-allocated environment.
32-
The ce_rec component associates offsets to identifiers for functions
33-
bound by the same let rec as the current function. The offsets
34-
are used by the OFFSETCLOSURE instruction to recover the closure
35-
pointer of the desired function from the env register (which
36-
points to the closure for the current function). *)
43+
The ce_closure component gives the positions of variables residing in the
44+
heap-allocated environment. The env_pos component gives the position of
45+
the current function from the start of the closure block, and the entries
46+
component gives the positions of free variables and functions bound by the
47+
same let rec as the current function, from the start of the closure block.
48+
These are used by the ENVACC and OFFSETCLOSURE instructions to recover the
49+
relevant value from the env register (which points to the current function).
50+
*)
3751

3852
(* Debugging events *)
3953

debugger/eval.ml

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,28 @@ let rec address path event = function
5151
with Symtable.Error _ -> raise(Error(Unbound_identifier id))
5252
end
5353
| None ->
54+
let not_found () =
55+
raise(Error(Unbound_identifier id))
56+
in
5457
begin match event with
5558
Some {ev_ev = ev} ->
5659
begin try
5760
let pos = Ident.find_same id ev.ev_compenv.ce_stack in
5861
Debugcom.Remote_value.local (ev.ev_stacksize - pos)
5962
with Not_found ->
60-
try
61-
let pos = Ident.find_same id ev.ev_compenv.ce_heap in
62-
Debugcom.Remote_value.from_environment pos
63-
with Not_found ->
64-
raise(Error(Unbound_identifier id))
63+
match ev.ev_compenv.ce_closure with
64+
| Not_in_closure -> not_found ()
65+
| In_closure { entries; env_pos } ->
66+
match Ident.find_same id entries with
67+
| Free_variable pos ->
68+
Debugcom.Remote_value.from_environment (pos - env_pos)
69+
| Function _pos ->
70+
(* Recursive functions seem to be unhandled *)
71+
not_found ()
72+
| exception Not_found -> not_found ()
6573
end
6674
| None ->
67-
raise(Error(Unbound_identifier id))
75+
not_found ()
6876
end
6977
end
7078
| Env.Adot(root, pos) ->

0 commit comments

Comments
 (0)