@@ -38,86 +38,104 @@ let add_required_modules ( x : Ident.t list) (meta : Lam_stats.t) =
38
38
*)
39
39
40
40
41
- (*
42
- It's impossible to have a case like below:
43
- {[
44
- (let export_f = ... in export_f)
45
- ]}
46
- Even so, it's still correct
47
- *)
48
- let refine_let
49
- ~kind param
50
- (arg : Lam.t ) (l : Lam.t ) : Lam.t =
51
-
52
- match (kind : Lam_compat.let_kind ), arg, l with
53
- | _, _, Lvar w when Ident. same w param
54
- (* let k = xx in k
55
- there is no [rec] so [k] would not appear in [xx]
56
- *)
57
- -> arg (* TODO: optimize here -- it's safe to do substitution here *)
58
- | _, _, Lprim {primitive ; args = [Lvar w]; loc ; _} when Ident. same w param
59
- && (function | Lam_primitive. Pmakeblock _ -> false | _ -> true ) primitive
60
- (* don't inline inside a block *)
61
- -> Lam. prim ~primitive ~args: [arg] loc
62
- (* we can not do this substitution when capttured *)
63
- (* | _, Lvar _, _ -> (\** let u = h in xxx*\) *)
64
- (* (\* assert false *\) *)
65
- (* Ext_log.err "@[substitution >> @]@."; *)
66
- (* let v= subst_lambda (Map_ident.singleton param arg ) l in *)
67
- (* Ext_log.err "@[substitution << @]@."; *)
68
- (* v *)
69
- | _, _, Lapply {ap_func= fn; ap_args = [Lvar w]; ap_info; ap_transformed_jsx} when
70
- Ident. same w param &&
71
- (not (Lam_hit. hit_variable param fn ))
72
- ->
73
- (* does not work for multiple args since
74
- evaluation order unspecified, does not apply
75
- for [js] in general, since the scope of js ir is loosen
76
-
77
- here we remove the definition of [param]
78
- {[ let k = v in (body) k
79
- ]}
80
- #1667 make sure body does not hit k
81
- *)
82
- Lam. apply fn [arg] ap_info ~ap_transformed_jsx
83
- | (Strict | StrictOpt ),
84
- ( Lvar _ | Lconst _ |
85
- Lprim {primitive = Pfield (_ , Fld_module _) ;
86
- args = [ Lglobal_module _ | Lvar _ ]; _ }) , _ ->
87
- (* (match arg with *)
88
- (* | Lconst _ -> *)
89
- (* Ext_log.err "@[%a %s@]@." *)
90
- (* Ident.print param (string_of_lambda arg) *)
91
- (* | _ -> ()); *)
92
- (* No side effect and does not depend on store,
93
- since function evaluation is always delayed
94
- *)
95
- Lam. let_ Alias param arg l
96
- | ( (Strict | StrictOpt ) ), (Lfunction _ ), _ ->
97
- (* It can be promoted to [Alias], however,
98
- we don't want to do this, since we don't want the
99
- function to be inlined to a block, for example
100
- {[
101
- let f = fun _ -> 1 in
102
- [0, f]
103
- ]}
104
- TODO: punish inliner to inline functions
105
- into a block
106
- *)
107
- Lam. let_ StrictOpt param arg l
108
- (* Not the case, the block itself can have side effects
109
- we can apply [no_side_effects] pass
110
- | Some Strict, Lprim(Pmakeblock (_,_,Immutable),_) ->
111
- Llet(StrictOpt, param, arg, l)
112
- *)
113
- | Strict , _ ,_ when Lam_analysis. no_side_effects arg ->
114
- Lam. let_ StrictOpt param arg l
115
- | Variable , _ , _ ->
116
- Lam. let_ Variable param arg l
117
- | kind , _ , _ ->
118
- Lam. let_ kind param arg l
119
- (* | None , _, _ ->
120
- Lam.let_ Strict param arg l *)
41
+ (* refine_let normalises let-bindings so we avoid redundant locals while
42
+ preserving the semantics encoded by Lambda's let_kind. Downstream passes at
43
+ the JS backend interpret the k-tag as the shape of code they are allowed to
44
+ emit:
45
+ Strict --> emit `const x = e; body`, with `e` evaluated exactly once.
46
+ Reordering `e` or duplicating it would be incorrect.
47
+ StrictOpt --> emit either `const x = e; body` (when `x` is used) or drop
48
+ the declaration entirely (when DCE prunes `x`). Duplicating
49
+ `e` remains forbidden.
50
+ Alias --> emit `const x = e; body` or substitute `e` directly at each
51
+ use site, removing the binding if convenient.
52
+ Variable --> emit a thunked shape like `function() { return e; }` or keep
53
+ the original `let` without forcing; evaluation must stay
54
+ deferred.
55
+
56
+ The function implements this contract through ordered rewrite clauses:
57
+ - (Return) [let[k] x = e in x] ⟶ e
58
+ - (Prim) [let[k] x = e in prim p x] ⟶ prim p e (p ≠ makeblock)
59
+ - (Call) [let[k] x = e in f x] ⟶ f e (x not captured in f)
60
+ - (Alias) [let[k] x = e in body] ⟶ let[Alias] x = e in body
61
+ when k ∈ {Strict, StrictOpt} and SafeAlias(e)
62
+ - (Strict λ) [let[Strict] x = fn in body] ⟶ let[StrictOpt] x = fn in body
63
+ - (Strict Pure) [let[Strict] x = e in body] ⟶ let[StrictOpt] x = e in body
64
+ when no_side_effects(e)
65
+ Falling through keeps the original binding. Only the Alias clause changes
66
+ evaluation strategy downstream, so we keep its predicate intentionally
67
+ syntactic and narrow. *)
68
+ let refine_let ~kind param (arg : Lam.t ) (l : Lam.t ) : Lam.t =
69
+ let is_block_constructor = function
70
+ | Lam_primitive. Pmakeblock _ -> true
71
+ | _ -> false
72
+ in
73
+ (* SafeAlias is the predicate that justifies the (Alias) rewrite
74
+ let[k] x = e in body --> let[Alias] x = e in body
75
+ for strict bindings. Turning a binding into [Alias] authorises JS codegen
76
+ to inline [e] at every use site or drop `const x = e` entirely, so every
77
+ clause below must ensure that duplicate evaluation of [e] is equivalent to
78
+ the single eager evaluation promised by [Strict]/[StrictOpt]. *)
79
+ let rec is_safe_to_alias (lam : Lam.t ) =
80
+ match lam with
81
+ | Lvar _ | Lconst _ ->
82
+ (* var/const --> emitting multiple `const` reads is identical to the
83
+ original eager evaluation, so codegen may inline them freely. *)
84
+ true
85
+ | Lprim { primitive = Pfield (_ , Fld_module _ ); args = [ (Lglobal_module _ | Lvar _ ) ]; _ } ->
86
+ (* field read --> access hits an immutable module block; inlining emits
87
+ the same read the eager binding would have performed once. *)
88
+ true
89
+ | Lprim { primitive = Psome_not_nest ; args = [inner]; _ } ->
90
+ (* some_not_nest(inner) --> expands to two explicit rewrites:
91
+ let[k] x = inner --> let[Alias] x = inner
92
+ let[Alias] x = inner --> let[Alias] x = Some(inner)
93
+ The recursive call discharges the first arrow; the constructor wrap is
94
+ allocation-free in JS, so the second arrow preserves the single eager
95
+ evaluation promised by Strict/StrictOpt. *)
96
+ is_safe_to_alias inner
97
+ | _ -> false
98
+ in
99
+ match (kind : Lam_compat.let_kind ), arg, l with
100
+ | _ , _ , Lvar w when Ident. same w param ->
101
+ (* If the body immediately returns the binding (e.g. `{ let x = value; x }`),
102
+ we skip creating `x` and keep `value`. There is no `rec`, so `value`
103
+ cannot refer back to `x`, and we avoid generating a redundant local. *)
104
+ arg
105
+ | _, _, Lprim { primitive; args = [ Lvar w ]; loc; _ }
106
+ when Ident. same w param && not (is_block_constructor primitive) ->
107
+ (* When we immediately feed the binding into a primitive, like
108
+ `{ let x = value; Array.length(x) }`, we inline the primitive call
109
+ with `value`. This only happens for primitives that are pure and do not
110
+ allocate new blocks, so evaluation order and side effects stay the same. *)
111
+ Lam. prim ~primitive ~args: [arg] loc
112
+ | _, _, Lapply { ap_func = fn; ap_args = [ Lvar w ]; ap_info; ap_transformed_jsx }
113
+ when Ident. same w param && not (Lam_hit. hit_variable param fn) ->
114
+ (* For a function call such as `{ let x = value; someFn(x) }`, we can
115
+ rewrite to `someFn(value)` as long as the callee does not capture `x`.
116
+ This removes the temporary binding while preserving the call semantics. *)
117
+ Lam. apply fn [arg] ap_info ~ap_transformed_jsx
118
+ | (Strict | StrictOpt ), arg , _ when is_safe_to_alias arg ->
119
+ (* `Strict` and `StrictOpt` bindings both evaluate the RHS immediately
120
+ (with `StrictOpt` allowing later elimination if unused). When that RHS
121
+ is pure — `{ let x = Some(value); ... }`, `{ let x = 3; ... }`, or a module
122
+ field read — we mark it as an alias so downstream passes can inline the
123
+ original expression and drop the temporary. *)
124
+ Lam. let_ Alias param arg l
125
+ | Strict , Lfunction _ , _ ->
126
+ (* If we eagerly evaluate a function binding such as
127
+ `{ let makeGreeting = () => "hi"; ... }`, we end up allocating the
128
+ closure immediately. Downgrading `Strict` to `StrictOpt` preserves the
129
+ original laziness while still letting later passes inline when safe. *)
130
+ Lam. let_ StrictOpt param arg l
131
+ | Strict , _ , _ when Lam_analysis. no_side_effects arg ->
132
+ (* A strict binding whose expression has no side effects — think
133
+ `{ let x = computePure(); use(x); }` — can be relaxed to `StrictOpt`.
134
+ This keeps the original semantics yet allows downstream passes to skip
135
+ evaluating `x` when it turns out to be unused. *)
136
+ Lam. let_ StrictOpt param arg l
137
+ | kind , _ , _ ->
138
+ Lam. let_ kind param arg l
121
139
122
140
let alias_ident_or_global (meta : Lam_stats.t ) (k :Ident.t ) (v :Ident.t )
123
141
(v_kind : Lam_id_kind.t ) =
@@ -260,11 +278,3 @@ let is_var (lam : Lam.t) id =
260
278
lapply (let a = 3 in let b = 4 in fun x y -> x + y) 2 3
261
279
]}
262
280
*)
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
0 commit comments