Skip to content

Split Lambda into the IR and its traversals - #8618

Open
cristianoc wants to merge 3 commits into
lambda/scalar-replacementfrom
lambda/split-traversals
Open

Split Lambda into the IR and its traversals#8618
cristianoc wants to merge 3 commits into
lambda/scalar-replacementfrom
lambda/split-traversals

Conversation

@cristianoc

Copy link
Copy Markdown
Collaborator

lambda.ml held the IR and everything that walked it, so the module defining
Lambda.t reached into Env, Path and Parsetree. Traversals, static exit
handling and path translation move to lambda_traverse.ml, lambda_exits.ml
and transl_path.ml, leaving the IR module with types and constructors.

make_key was the remaining way to build a term without going through a
constructor. The escape route it existed for, recognising a jump to an exit,
is now handled directly, and make_key itself builds through the constructors
before moving out with the other traversals.

Part of #8573. Stacked on #8617.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 57bf99c06f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CHANGELOG.md Outdated

- Normalize Lambda terms where they are built: a match guard stays structured data until its fallthrough is known, and `apply` and `mk_builtin` go through the folding constructors. https://github.com/rescript-lang/rescript/pull/8615
- Replace non-escaping local mutable blocks with scalar bindings when all uses are direct field accesses, generalizing reference unboxing to multi-field records and references captured by JavaScript closures. https://github.com/rescript-lang/rescript/pull/8617
- Split `lambda.ml` into the IR and its traversals, static exits and path translation, so the module defining `Lambda.t` no longer reaches into `Env`, `Path` or `Parsetree`. https://github.com/rescript-lang/rescript/pull/8618

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove the remaining Parsetree dependency from Lambda

The advertised dependency split is incomplete: compiler/ml/lambda.mli:55 still exposes find_name : Parsetree.attribute -> ..., and lambda.ml:66-67 still consumes Parsetree.attribute through Ast_payload. Consequently, Lambda continues to reach into Parsetree, contrary to this new changelog entry and the intended layering boundary; move the attribute-decoding/field-metadata helpers out of Lambda, or revise the claim if retaining that dependency is intentional.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, and I've taken the second of the two options you offer: the changelog now claims only what this PR delivers — Lambda no longer reaches into Env or Path.

The Parsetree dependency is genuinely still here at this commit, exactly as you describe. It disappears one PR up, in #8619, where @as stops being an attribute and find_name goes away with it. Narrowing the claim rather than moving the helper keeps that removal in the PR that actually earns it.

Fixed in b415a7e.

@cristianoc
cristianoc force-pushed the lambda/split-traversals branch from 57bf99c to e1e5ae0 Compare September 4, 2026 12:47
@cristianoc
cristianoc changed the base branch from lambda/scalar-replacement to master September 4, 2026 12:48
@codecov

codecov Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.46862% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.35%. Comparing base (7b26062) to head (ffad101).

Files with missing lines Patch % Lines
compiler/ml/lambda_traverse.ml 91.50% 13 Missing ⚠️
compiler/ml/translcore.ml 60.00% 2 Missing ⚠️
compiler/ml/lambda_exits.ml 93.33% 1 Missing ⚠️
compiler/ml/matching.ml 96.87% 1 Missing ⚠️
compiler/ml/transl_path.ml 90.00% 1 Missing ⚠️
Additional details and impacted files
@@                    Coverage Diff                     @@
##           lambda/scalar-replacement    #8618   +/-   ##
==========================================================
  Coverage                      77.35%   77.35%           
==========================================================
  Files                            468      471    +3     
  Lines                          63464    63476   +12     
==========================================================
+ Hits                           49090    49102   +12     
  Misses                         14374    14374           
Files with missing lines Coverage Δ
compiler/core/lam_compile.ml 89.74% <100.00%> (ø)
compiler/core/lam_compile_main.ml 89.27% <ø> (ø)
compiler/core/lam_dce.ml 92.10% <100.00%> (+0.21%) ⬆️
compiler/core/lam_exit_code.ml 88.88% <100.00%> (ø)
compiler/core/lam_pass_exits.ml 94.17% <100.00%> (ø)
compiler/core/lam_pass_guard_raises.ml 100.00% <100.00%> (ø)
compiler/core/lam_pass_sroa.ml 85.71% <100.00%> (+0.18%) ⬆️
compiler/core/polyvar_pattern_match.ml 100.00% <100.00%> (ø)
compiler/ml/lambda.ml 65.40% <ø> (-7.45%) ⬇️
compiler/ml/switch.ml 87.50% <ø> (ø)
... and 8 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cristianoc
cristianoc force-pushed the lambda/split-traversals branch from e1e5ae0 to b415a7e Compare September 4, 2026 13:21
@cristianoc
cristianoc changed the base branch from master to lambda/scalar-replacement September 4, 2026 13:28
@pkg-pr-new

pkg-pr-new Bot commented Sep 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript-lang/rescript@8618

@rescript/belt

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/belt@8618

@rescript/darwin-arm64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/darwin-arm64@8618

@rescript/darwin-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/darwin-x64@8618

@rescript/linux-arm64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/linux-arm64@8618

@rescript/linux-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/linux-x64@8618

@rescript/runtime

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/runtime@8618

@rescript/win32-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/win32-x64@8618

commit: ffad101

@cristianoc
cristianoc requested a review from cknitt September 4, 2026 13:50
@cristianoc
cristianoc force-pushed the lambda/split-traversals branch from b415a7e to 2e7adbf Compare September 5, 2026 00:18
cristianoc and others added 3 commits September 5, 2026 02:47
compile_orhandlers asked whether the term it had built was a jump to the exit
it was about to wrap a catch around, and asked by calling make_key - which
builds a canonical form for comparison - then matching the result and binding
the raise's arguments from it. So a fragment of a key reached generated code,
and keys are the one place in Lambda built without the constructors: make_key
rebuilds raw, so a term it produced was not normalized.

The substitution was load-bearing rather than incidental. The term may be
[let x = e in exit j x], and it is discarded once recognized, so taking the
arguments as written would leave x unbound. make_key happened to substitute
alias bindings while canonicalizing, which is what made the arguments safe.

as_exit_call does that deliberately: peel alias bindings, substitute them into
the arguments, and report the exit. raw_action then has no callers - it was
the only consumer of make_key whose result was emitted rather than compared.

Generated JavaScript is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W8g8qwBARAcvW9MyuKQq8H
make_key was the last thing in lambda.ml constructing terms raw. It could,
owning the type, and the argument for letting it was that folding would change
the key: it substitutes alias bindings as it canonicalizes, substitution can
put a constant where a variable was, and the constructors would then fold what
the raw rebuild left alone.

That is true of the mechanism and false of the outcome. Routing every
construction through the constructors leaves generated JavaScript unchanged
across the runtime, Belt and the 626 test modules, so the keys still make the
same sharing decisions. The argument was worth checking rather than asserting.

It is safe to fold here now for a second reason: since the preceding commit,
no key reaches generated code. They are compared - in same_actions,
up_ok_action and the action store - and nothing else.

What remains of L-constructors in make_key is pattern matching.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W8g8qwBARAcvW9MyuKQq8H
lambda.ml defined the representation and then a good deal that merely used it.
The distinction that matters is whether a function needs to construct terms
without going through the constructors: the constructors and the folds they
run do, and nothing else does. Everything in the second category can live
outside, and saying so in the module structure makes the rule checkable rather
than a convention.

Lambda_traverse takes the generic walks - shallow_exists, shallow_map_sharing,
iter, free_variables, subst_lambda - and make_key, which the preceding commit
made expressible through the constructors.

Lambda_exits takes the static exit mechanism: the counters, make_exit,
as_simple_exit, make_catch_delayed. An exit number and the catch a handler is
wrapped in are a protocol over the representation, not part of it.

Transl_path takes transl_normal_path and its three callers. This is
translation, and it was the only reason lambda.ml depended on Env and Path.

Moving the last two out made the type private to them, which found two places
building raw because they could rather than because they had to: make_exit's
Lstaticraise and transl_normal_path's Lconst, Lglobal_module, Lvar and Lprim.
Both now go through the constructors, which for these is the same term.

Five helpers that only the folds use - eq_primitive_approx, eq_comparison,
const_eq_approx, cmp_int32, cmp_float - stop being exported.

lambda.ml goes from 1495 lines to 1200. Generated JavaScript is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W8g8qwBARAcvW9MyuKQq8H
@cristianoc
cristianoc force-pushed the lambda/split-traversals branch from 2e7adbf to ffad101 Compare September 5, 2026 00:50
@cknitt

cknitt commented Sep 5, 2026

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-05T05:04:38.713698Z ffad101 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ffad101c51

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread compiler/ml/matching.ml
Comment on lines +2328 to +2331
if env == Ident.empty then args
else Ext_list.map args (Lambda_traverse.subst_lambda env) )
| Llet (Alias, x, ex, body) ->
go (Ident.add x (Lambda_traverse.subst_lambda env ex) env) body

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add regression tests for alias-exit substitution

This new path drops alias bindings and substitutes their expressions into static-exit arguments, but the only test-file changes in this commit rename existing traversal calls; there is no focused coverage for chained aliases, multiple exit arguments, or the resulting generated JavaScript. A regression here can leave exit arguments unbound or change their evaluation, so add Lambda-level and end-to-end fixtures for these cases as required by the repository’s testing guidance.

AGENTS.md reference: AGENTS.md:L41-L43

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants