Skip to content

Commit 36b14d6

Browse files
cristianocclaude
andcommitted
Eliminate Pjs_fn_make, Pjs_fn_make_unit, and unsafe_adjust_to_arity
With structural arity, "function of arity n" is a construction invariant rather than a goal state: translcore builds every Lfunction with exactly the parameters its type declares. The arity-enforcement layer therefore disappears: - Function literals are emitted directly; the Pjs_fn_make wrapper that every function passed through was resolved as a no-op by lam_pass_alpha_conversion, but only *after* deep_flatten, simplify_exits and simplify_alias had run with the function hidden inside an Lprim, acting as an accidental optimization barrier. - Pjs_fn_make_unit was a one-bit metadata channel: its entire effect was setting one_unit_arg so js_exp_make drops the unit parameter. translcore now sets the attribute directly, gated on the parameter pattern binding no identifiers (a () or _ pattern) - a more principled test than the alpha pass's check that the parameter was named "param". - The active-pattern currying split in transl_function is deleted. It preserved pattern-effect timing across curried application steps, which no longer exist: total applications supply all arguments at once and explicit partial application eta-defers the entire call. The old output proves the point - the split's closures were immediately applied by the arity adapter, so only the allocations are gone (see mutable_uncurry_test). - The I<N> unboxed-record producer (the @this method-callback encoding) is removed: the general Record_unboxed translation already returns the single field unboxed, and the wrapped value is a literal of matching arity. With no producers left, both primitive constructors and every consumer arm are deleted, including the 230-line unsafe_adjust_to_arity (its only callers were the two Pjs_fn_make resolution sites). On recursive modules: removing the wrapper lets the static recursive-module compilation path see module members that are plain functions, replacing the Primitive_module.init/update bootstrap with hoisted function declarations. This is safe because the static path's own applicability check now sees the functions it was designed to check - the wrapper was hiding them, pessimizing compilation - and the bootstrap demonstrably remains for members that are not plain functions (rec_module_test keeps its lazy/value cases dynamic). Verified: stdlib byte-identical; full test suite green; JS output changes limited to removed adapter closures, removed no-op module bootstraps, better name preservation, and constant propagation. Keep beta-residue let chains (immutable blocks and aliases feeding a final apply) local in Lam_pass_deep_flatten instead of hoisting them into the enclosing group: with the wrapper gone, beta reduction happens before flatten2, and hoisting the argument bindings to toplevel put them beyond Lam_pass_lets_dce's reach (a_recursive_type kept a named intermediate that master inlined; the snapshot pins the restored form). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Cristiano Calcagno <cristianoc@users.noreply.github.com>
1 parent b7b622a commit 36b14d6

42 files changed

Lines changed: 187 additions & 720 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848

4949
- Sync the platform npm package's compiler binaries (`packages/@rescript/<platform>/bin`) via dune promotion on every `dune build`, instead of Makefile/CI copy steps that only ran when make did: a plain `dune build` can no longer leave `cli/*.js` and the test harnesses running a stale compiler. https://github.com/rescript-lang/rescript/pull/8560
5050
- Remove unused compiler IR definitions, modules, helpers, error variants, and Typedtree fields. https://github.com/rescript-lang/rescript/pull/8551 https://github.com/rescript-lang/rescript/pull/8555
51+
- Eliminate the `Pjs_fn_make`/`Pjs_fn_make_unit` arity-adjustment primitives and the `unsafe_adjust_to_arity` machinery: with structural arity, functions are constructed at their final arity, so the enforcement layer (and the active-pattern currying split it compensated for) is deleted. Generated code improves: no adapter closures for patterns on mutable fields, better constant propagation and name preservation, and recursive modules whose members are plain functions compile statically without the runtime bootstrap. https://github.com/rescript-lang/rescript/pull/8570
5152
- Cleanups enabled by structural arity: remove the unreachable `Too_many_arguments` error and the `?in_function` threading through the type checker that existed only to decorate it; remove the dead `function$`-vs-arrow unification bridge, `Ctype.arity`, and the unused parsetree arity helpers; deduplicate the analysis arrow-flattening helpers. https://github.com/rescript-lang/rescript/pull/8569
5253

5354
- Make the typed layers n-ary as well: `Types.Tarrow` carries a parameter list, `Texp_function` carries typed parameters (label, ident, pattern, per-parameter exhaustiveness) and a body, and `Ttyp_arrow`/`Otyp_arrow` follow. The `arity` annotation and its `int option` phantom state are gone from the compiler entirely; `push_defaults` in translcore and the hand-rolled gather-until-arity walks in gentype, reanalyze, and the outcome printer are deleted. The cmi and cmt magic numbers are bumped (`Caml1999I023`/`Caml1999T023`). Generated JavaScript is byte-identical across the test suite (optional-parameter internals are named `*opt_<label>*` instead of `*opt*`, visible only in the rare unprettified case); reanalyze no longer emits spurious empty optional-argument references, and genType recovers real parameter names after defaulted parameters. https://github.com/rescript-lang/rescript/pull/8568

compiler/core/design.md

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -173,20 +173,6 @@ We can simply do inlining, it may have side efffect in `b0`, `b1`, our optimizer
173173

174174
Maybe in the future, we should lift the restriction about `variadic` (delegate to `slow` mode when we can not resolve it statically, my personal expereince is that people will complain about why it fails to compile more than why it is slow in some corner cases)
175175

176-
Note this also interacts with `[@uncurry]`
177-
178-
for example
179-
180-
```ocaml
181-
external filter : 'a array -> ('a -> bool [@uncurry]) -> 'a array = "filter"
182-
[@@send]
183-
184-
let f xs =
185-
xs |. filter (fun x -> x > 2)
186-
```
187-
188-
Here whether the callback gets inlined to the call of `filter` will have an effect on how `Pjs_fn_make` gets cancelled.
189-
190176
Note when we pattern match over the original lamba,`Levent` needs to be removed as early as possible. Due to the existence of `Levent`, we can not pattern match over nested original raw lambda.
191177

192178
We turned off event generation temporarily
@@ -474,4 +460,3 @@ we do the optimizer, e.g, `Js_exp_make.int_comp`, we need make sure the peepwhol
474460

475461

476462

477-

compiler/core/lam.ml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,6 @@ let rec is_eta_conversion_exn params inner_args outer_args : t list =
140140
match (params, inner_args, outer_args) with
141141
| x :: xs, Lvar y :: ys, r :: rest when Ident.same x y ->
142142
r :: is_eta_conversion_exn xs ys rest
143-
| ( x :: xs,
144-
Lprim
145-
({primitive = Pjs_fn_make _ | Pjs_fn_make_unit; args = [Lvar y]} as p)
146-
:: ys,
147-
r :: rest )
148-
when Ident.same x y ->
149-
Lprim {p with args = [r]} :: is_eta_conversion_exn xs ys rest
150143
| [], [], [] -> []
151144
| _, _, _ -> raise_notrace Not_simple_form
152145

compiler/core/lam_analysis.ml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ let rec no_side_effects (lam : Lam.t) : bool =
4646
| _ -> false)
4747
| Pcreate_extension _ | Ptypeof | Pis_null | Pis_not_none | Psome
4848
| Psome_not_nest | Pis_undefined | Pis_null_undefined | Pnull_to_opt
49-
| Pnull_undefined_to_opt | Pjs_fn_make _ | Pjs_fn_make_unit
50-
| Pjs_object_create _ | Pimport
49+
| Pnull_undefined_to_opt | Pjs_object_create _ | Pimport
5150
(* TODO: check *)
5251
| Pmakeblock _
5352
(* whether it's mutable or not *)

compiler/core/lam_compile.ml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1828,12 +1828,6 @@ let compile output_prefix =
18281828
}
18291829
body))))
18301830
| _ -> assert false)
1831-
| {primitive = Pjs_fn_make arity; args = [fn]; loc} ->
1832-
compile_lambda lambda_cxt
1833-
(Lam_eta_conversion.unsafe_adjust_to_arity loc ~to_:arity ?from:None fn)
1834-
| {primitive = Pjs_fn_make_unit; args = [fn]; loc} ->
1835-
compile_lambda lambda_cxt fn
1836-
| {primitive = Pjs_fn_make _; args = [] | _ :: _ :: _} -> assert false
18371831
| {primitive = Pjs_object_create labels; args} ->
18381832
let args_block, args_expr =
18391833
if args = [] then ([], [])

compiler/core/lam_compile_primitive.ml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,7 @@ let translate output_prefix loc (cxt : Lam_compile_context.t)
155155
| Pis_undefined -> E.is_undef (Ext_list.singleton_exn args)
156156
| Pis_null_undefined -> E.is_null_undefined (Ext_list.singleton_exn args)
157157
| Ptypeof -> E.typeof (Ext_list.singleton_exn args)
158-
| Pjs_unsafe_downgrade _ | Pdebugger | Pjs_fn_make _ | Pjs_fn_make_unit
159-
| Pjs_fn_method ->
158+
| Pjs_unsafe_downgrade _ | Pdebugger | Pjs_fn_method ->
160159
assert false (* already handled by {!Lam_compile} *)
161160
| Pstringadd -> (
162161
match args with

compiler/core/lam_convert.ml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,6 @@ let lam_prim ~primitive:(p : Lambda.primitive) ~args loc : Lam.t =
315315
| Pis_poly_var_block -> prim ~primitive:Pis_poly_var_block ~args loc
316316
| Pjs_raw_expr -> assert false
317317
| Pjs_raw_stmt -> assert false
318-
| Pjs_fn_make arity -> prim ~primitive:(Pjs_fn_make arity) ~args loc
319-
| Pjs_fn_make_unit -> prim ~primitive:Pjs_fn_make_unit ~args loc
320318
| Pjs_fn_method -> prim ~primitive:Pjs_fn_method ~args loc
321319

322320
(* Does not exist since we compile array in js backend unlike native backend *)

compiler/core/lam_eta_conversion.ml

Lines changed: 0 additions & 230 deletions
Original file line numberDiff line numberDiff line change
@@ -73,233 +73,3 @@ let transform_under_supply n ap_info fn args =
7373
Ext_list.fold_left bindings rest (fun lam (id, x) ->
7474
Lam.let_ Strict id x lam)
7575
| _, _ -> assert false
76-
77-
(* Invariant: mk0 : (unit -> 'a0) -> 'a0 t
78-
TODO: this case should be optimized,
79-
we need check where we handle [arity=0]
80-
as a special case --
81-
if we do an optimization before compiling
82-
into lambda
83-
84-
{[Fn.mk0]} is not intended for use by normal users
85-
86-
so we assume [Fn.mk0] is only used in such cases
87-
{[
88-
Fn.mk0 (fun _ -> .. )
89-
]}
90-
when it is passed as a function directly
91-
*)
92-
(*TODO: can be optimized ?
93-
{[\ x y -> (\u -> body x) x y]}
94-
{[\u x -> body x]}
95-
rewrite rules
96-
{[
97-
\x -> body
98-
--
99-
\y (\x -> body ) y
100-
]}
101-
{[\ x y -> (\a b c -> g a b c) x y]}
102-
{[ \a b -> \c -> g a b c ]}
103-
*)
104-
105-
(** Unsafe function, we are changing arity here, it should be applied
106-
cautiously, since
107-
[let u = f] and we are chaning the arity of [f] it will affect
108-
the collection of [u]
109-
A typical use case is to pass an OCaml function to JS side as a callback (i.e, [@uncurry])
110-
*)
111-
let unsafe_adjust_to_arity loc ~(to_ : int) ?(from : int option) (fn : Lam.t) :
112-
Lam.t =
113-
let ap_info : Lam.ap_info =
114-
{ap_loc = loc; ap_inlined = Default_inline; ap_status = App_na}
115-
in
116-
let is_async_fn =
117-
match fn with
118-
| Lfunction {attr = {async}} -> async
119-
| _ -> false
120-
in
121-
match (from, fn) with
122-
| Some from, _ | None, Lfunction {arity = from} -> (
123-
if from = to_ || is_async_fn then fn
124-
else if to_ = 0 then
125-
match fn with
126-
| Lfunction {params = [param]; body; loc} ->
127-
Lam.function_ ~loc ~arity:0 ~attr:Lambda.default_function_attribute
128-
~params:[]
129-
~body:(Lam.let_ Alias param Lam.unit body)
130-
(* could be only introduced by
131-
{[ Pjs_fn_make 0 ]} <-
132-
{[ fun [@bs] () -> .. ]}
133-
*)
134-
| _ -> (
135-
let wrapper, new_fn =
136-
match fn with
137-
| Lvar _
138-
| Lprim
139-
{
140-
primitive = Pfield (_, Fld_module _);
141-
args = [(Lglobal_module _ | Lvar _)];
142-
_;
143-
} ->
144-
(None, fn)
145-
| _ ->
146-
let partial_arg = Ext_ident.create Literals.partial_arg in
147-
(Some partial_arg, Lam.var partial_arg)
148-
in
149-
150-
let cont =
151-
Lam.function_ ~loc ~attr:Lambda.default_function_attribute ~arity:0
152-
~params:[]
153-
~body:(Lam.apply new_fn [Lam.unit] ap_info)
154-
in
155-
156-
match wrapper with
157-
| None -> cont
158-
| Some partial_arg -> Lam.let_ Strict partial_arg fn cont)
159-
else if to_ > from then
160-
match fn with
161-
| Lfunction {params; body; loc} ->
162-
(* {[fun x -> f]} ->
163-
{[ fun x y -> f y ]}
164-
*)
165-
let extra_args =
166-
Ext_list.init (to_ - from) (fun _ -> Ident.create Literals.param)
167-
in
168-
let rec mk_apply body vars =
169-
match vars with
170-
| [] -> body
171-
| var :: vars -> mk_apply (Lam.apply body [var] ap_info) vars
172-
in
173-
Lam.function_ ~loc ~attr:Lambda.default_function_attribute ~arity:to_
174-
~params:(Ext_list.append params extra_args)
175-
~body:(mk_apply body (Ext_list.map extra_args Lam.var))
176-
| _ -> (
177-
let arity = to_ in
178-
let extra_args =
179-
Ext_list.init to_ (fun _ -> Ident.create Literals.param)
180-
in
181-
let wrapper, new_fn =
182-
match fn with
183-
| Lvar _
184-
| Lprim
185-
{
186-
primitive = Pfield (_, Fld_module _);
187-
args = [(Lglobal_module _ | Lvar _)];
188-
_;
189-
} ->
190-
(None, fn)
191-
| _ ->
192-
let partial_arg = Ext_ident.create Literals.partial_arg in
193-
(Some partial_arg, Lam.var partial_arg)
194-
in
195-
let cont =
196-
Lam.function_ ~loc ~arity ~attr:Lambda.default_function_attribute
197-
~params:extra_args
198-
~body:
199-
(let first_args, rest_args = Ext_list.split_at extra_args from in
200-
Lam.apply
201-
(Lam.apply new_fn
202-
(Ext_list.map first_args Lam.var)
203-
{ap_info with ap_status = App_infer_full})
204-
(Ext_list.map rest_args Lam.var)
205-
ap_info)
206-
in
207-
match wrapper with
208-
| None -> cont
209-
| Some partial_arg -> Lam.let_ Strict partial_arg fn cont)
210-
else
211-
(* add3 --adjust to arity 1 ->
212-
fun x -> (fun y z -> add3 x y z )
213-
214-
[fun x y z -> f x y z ]
215-
[fun x -> [fun y z -> f x y z ]]
216-
This is okay if the function is not held by other..
217-
*)
218-
match fn with
219-
| Lfunction {params; body; loc}
220-
(* TODO check arity = List.length params in debug mode *) ->
221-
let arity = to_ in
222-
let extra_outer_args, extra_inner_args =
223-
Ext_list.split_at params arity
224-
in
225-
Lam.function_ ~loc ~arity ~attr:Lambda.default_function_attribute
226-
~params:extra_outer_args
227-
~body:
228-
(Lam.function_ ~loc ~arity:(from - to_)
229-
~attr:Lambda.default_function_attribute ~params:extra_inner_args
230-
~body)
231-
| _ -> (
232-
let extra_outer_args =
233-
Ext_list.init to_ (fun _ -> Ident.create Literals.param)
234-
in
235-
let wrapper, new_fn =
236-
match fn with
237-
| Lvar _
238-
| Lprim
239-
{
240-
primitive = Pfield (_, Fld_module _);
241-
args = [(Lglobal_module _ | Lvar _)];
242-
_;
243-
} ->
244-
(None, fn)
245-
| _ ->
246-
let partial_arg = Ext_ident.create Literals.partial_arg in
247-
(Some partial_arg, Lam.var partial_arg)
248-
in
249-
let cont =
250-
Lam.function_ ~loc ~arity:to_ ~params:extra_outer_args
251-
~attr:Lambda.default_function_attribute
252-
~body:
253-
(let arity = from - to_ in
254-
let extra_inner_args =
255-
Ext_list.init arity (fun _ -> Ident.create Literals.param)
256-
in
257-
Lam.function_ ~loc ~arity ~params:extra_inner_args
258-
~attr:Lambda.default_function_attribute
259-
~body:
260-
(Lam.apply new_fn
261-
(Ext_list.map_append extra_outer_args
262-
(Ext_list.map extra_inner_args Lam.var)
263-
Lam.var)
264-
{ap_info with ap_status = App_infer_full}))
265-
in
266-
match wrapper with
267-
| None -> cont
268-
| Some partial_arg -> Lam.let_ Strict partial_arg fn cont))
269-
| None, _ ->
270-
(* In this case [fn] is not [Lfunction], otherwise we would get [arity] *)
271-
if to_ = 0 then
272-
let wrapper, new_fn =
273-
match fn with
274-
| Lvar _
275-
| Lprim
276-
{
277-
primitive = Pfield (_, Fld_module _);
278-
args = [(Lglobal_module _ | Lvar _)];
279-
_;
280-
} ->
281-
(None, fn)
282-
| _ ->
283-
let partial_arg = Ext_ident.create Literals.partial_arg in
284-
(Some partial_arg, Lam.var partial_arg)
285-
in
286-
287-
let cont =
288-
Lam.function_ ~loc ~attr:Lambda.default_function_attribute ~arity:0
289-
~params:[]
290-
~body:(Lam.apply new_fn [Lam.unit] ap_info)
291-
in
292-
293-
match wrapper with
294-
| None -> cont
295-
| Some partial_arg -> Lam.let_ Strict partial_arg fn cont
296-
else transform_under_supply to_ ap_info fn []
297-
298-
(* | _ ->
299-
let partial_arg = Ext_ident.create Literals.partial_arg in
300-
Lam.let_ Strict partial_arg fn
301-
(let arity = to_ in
302-
let extra_args = Ext_list.init arity (fun _ -> Ident.create Literals.param) in
303-
Lam.function_ ~arity ~kind:Curried ~params:extra_args
304-
~body:(Lam.apply fn (Ext_list.map Lam.var extra_args ) loc Lam.App_na )
305-
) *)

compiler/core/lam_eta_conversion.mli

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,3 @@
2929
*)
3030

3131
val transform_under_supply : int -> Lam.ap_info -> Lam.t -> Lam.t list -> Lam.t
32-
33-
val unsafe_adjust_to_arity :
34-
Location.t -> to_:int -> ?from:int -> Lam.t -> Lam.t

compiler/core/lam_pass_alpha_conversion.ml

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -64,25 +64,6 @@ let alpha_conversion (meta : Lam_stats.t) (lam : Lam.t) : Lam.t =
6464
let bindings = Ext_list.map_snd bindings simpl in
6565
Lam.letrec bindings (simpl body)
6666
| Lglobal_module _ -> lam
67-
| Lprim {primitive = Pjs_fn_make len as primitive; args = [arg]; loc} -> (
68-
match
69-
Lam_arity.get_first_arity (Lam_arity_analysis.get_arity meta arg)
70-
with
71-
| Some x ->
72-
let arg = simpl arg in
73-
Lam_eta_conversion.unsafe_adjust_to_arity loc ~to_:len ~from:x arg
74-
| None -> Lam.prim ~primitive ~args:[simpl arg] loc)
75-
| Lprim {primitive = Pjs_fn_make_unit; args = [arg]; loc} ->
76-
let arg =
77-
match arg with
78-
| Lfunction {arity = 1; params = [x]; attr; body; loc}
79-
when Ident.name x = "param" (* "()" *) ->
80-
Lam.function_ ~loc ~params:[x]
81-
~attr:{attr with one_unit_arg = true}
82-
~body ~arity:1
83-
| _ -> arg
84-
in
85-
simpl arg
8667
| Lprim {primitive; args; loc} ->
8768
Lam.prim ~primitive ~args:(Ext_list.map args simpl) loc
8869
| Lfunction {arity; params; body; attr; loc} ->

0 commit comments

Comments
 (0)