Skip to content

Commit 2fcd4a4

Browse files
cristianocclaude
andcommitted
Hand back a recursive group nothing was extracted from
The Lletrec arm of deep_flatten rebuilt unconditionally: it mapped every binding into a fresh list, split that with a fold carrying a stop flag, and reassembled through lambda_of_groups, whether or not a binding could be lifted out of the group. It now maps with sharing, replaces the fold with a walk that stops at the first binding referring back into the group, and returns the original when nothing was extracted and nothing underneath changed. That arm was where nearly all the remaining waste was. Over a stdlib build, runs of deep_flatten that hand back their input rise from 253 to 324 of 447 and rebuilds producing an identical tree fall from 72 to 1, with 122 runs changing the tree either way. The unit test covers the sharing rather than the extraction. Removing the sharing changes no generated output, so no snapshot can catch it, while breaking the extraction moves output the existing suite already compares. Signed-off-by: Cristiano Calcagno <ccrisccris@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W8g8qwBARAcvW9MyuKQq8H
1 parent 515d0c1 commit 2fcd4a4

3 files changed

Lines changed: 42 additions & 19 deletions

File tree

compiler/core/lam_pass_deep_flatten.ml

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -223,15 +223,15 @@ let deep_flatten (lam : Lambda.t) : Lambda.t =
223223
| Llet _ ->
224224
let res, groups = flatten [] lam in
225225
lambda_of_groups res ~rev_bindings:groups
226-
| Lletrec (bind_args, body) ->
226+
| Lletrec (bind_args, body) as original -> (
227227
(* Attention: don't mess up with internal {let rec} *)
228-
let rec iter bind_args groups set =
229-
match bind_args with
230-
| [] -> (List.rev groups, set)
231-
| (id, arg) :: rest ->
232-
iter rest ((id, aux arg) :: groups) (Set_ident.add set id)
228+
(* Keep the mapped list so a group from which nothing can be extracted
229+
remains physically shared when neither its bindings nor body change. *)
230+
let groups = Ext_list.map_snd_sharing bind_args aux in
231+
let collections =
232+
Ext_list.fold_left groups Set_ident.empty (fun set (id, _) ->
233+
Set_ident.add set id)
233234
in
234-
let groups, collections = iter bind_args [] Set_ident.empty in
235235
(* Try to extract some value definitions from recursive values as [wrap],
236236
it will stop whenever it find it could not move forward
237237
{[
@@ -241,19 +241,23 @@ let deep_flatten (lam : Lambda.t) : Lambda.t =
241241
...
242242
]}
243243
*)
244-
let rev_bindings, rev_wrap, _ =
245-
Ext_list.fold_left groups ([], [], false)
246-
(fun (inner_recursive_bindings, wrap, stop) (id, lam) ->
247-
if stop || Lam_hit.hit_variables collections lam then
248-
((id, lam) :: inner_recursive_bindings, wrap, true)
249-
else
250-
( inner_recursive_bindings,
251-
Lam_group.Single (Strict, id, lam) :: wrap,
252-
false ))
244+
let rec extract rev_wrap = function
245+
| [] -> (rev_wrap, [])
246+
| (_, binding) :: _ as bindings
247+
when Lam_hit.hit_variables collections binding ->
248+
(rev_wrap, bindings)
249+
| (id, binding) :: rest ->
250+
extract (Lam_group.Single (Strict, id, binding) :: rev_wrap) rest
253251
in
254-
lambda_of_groups
255-
~rev_bindings:rev_wrap (* These bindings are extracted from [letrec] *)
256-
(Lambda.letrec (List.rev rev_bindings) (aux body))
252+
let rev_wrap, recursive_bindings = extract [] groups in
253+
let body' = aux body in
254+
match rev_wrap with
255+
| [] when groups == bind_args && body' == body -> original
256+
| [] -> Lambda.letrec groups body'
257+
| _ ->
258+
lambda_of_groups
259+
~rev_bindings:rev_wrap (* Extracted bindings from [letrec]. *)
260+
(Lambda.letrec recursive_bindings body'))
257261
| _ -> Lambda_traverse.shallow_map_sharing aux lam
258262
in
259263
aux lam
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
open OUnit
2+
3+
(* Sharing is invisible to the generated output: a pass that rebuilds a term
4+
into an identical one produces the same JavaScript, so no snapshot can tell.
5+
This is the only place that notices. *)
6+
let suites =
7+
__FILE__
8+
>::: [
9+
( "shares an unchanged recursive group" >:: fun _ ->
10+
let recursive = Ident.create "recursive" in
11+
let lam =
12+
Lambda.letrec
13+
[(recursive, Lambda.var recursive)]
14+
(Lambda.var recursive)
15+
in
16+
assert_bool "the recursive group is physically unchanged"
17+
(Lam_pass_deep_flatten.deep_flatten lam == lam) );
18+
]

tests/ounit_tests/ounit_tests_main.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ let suites =
2020
Ounit_util_tests.suites;
2121
Ounit_rec_check_tests.suites;
2222
Ounit_lambda_constant_tests.suites;
23+
Ounit_deep_flatten_tests.suites;
2324
Ounit_sroa_tests.suites;
2425
Ounit_ast_mapper0_tests.suites;
2526
Ounit_object_mutability_tests.suites;

0 commit comments

Comments
 (0)