feat(codegen): thread imported TopConst across module boundaries (closes #107)#109
Merged
Conversation
#107) Closes #107. Before this commit, `Codegen.gen_imports` and `Module_loader.flatten_imports` both pattern-matched only `TopFn` in the imported module's `prog_decls`, silently dropping public `TopConst` items. A `use M::{some_const}` that the typechecker accepted would then fail at WASM codegen with `Codegen.UnboundVariable some_const`. ## Changes ### `lib/codegen.ml` — `gen_imports` - Match `TopFn` and `TopConst` in declaration order; first match wins (same-name fn+const in one module would be a resolver error). - For an imported `TopFn`: unchanged (emits the WASM `(import "<mod>" "<fn>" ...)` and registers the positive function index). - For an imported `TopConst`: compile the initialiser against the importer's context via `gen_expr`, append a fresh global, register `(local_name, -(global_idx + 1))` in `func_indices` — same negative-sentinel encoding as locally-declared consts, so use-site lookup is uniform. - Glob expansion (`use M::*`) likewise includes public consts. WASM module-linking for globals isn't standard yet, so the const value is inlined per importer. Identity is by value, which matches AffineScript's immutability guarantee. ### `lib/module_loader.ml` — `flatten_imports` - Local-decl name suppression set now covers fns AND consts. - Public consts get inlined into the importer's `prog_decls` alongside public fns, with the same alias-renaming machinery for `use M::{c as d}`. - Non-WASM back-ends (Rust, JS, OCaml, Lua, …) see imported consts as if they were locally declared, so they inherit the fix without per-target changes. ### Tests — `test/test_e2e.ml` Two regression tests in the `E2E Xmod Other Codegens` group: - `flatten_imports inlines imported public consts (#107)` — parses `cross_const_caller.affine`, runs the loader, asserts the imported `input_marker` const and `marker_plus` fn both appear in the flattened `prog_decls`. - `WASM gen_imports threads imported consts (#107)` — compiles `PortNames.affine` then `cross_const_caller.affine`; asserts the caller emits at least one global initialised to the const's value (256). The earlier failure mode would have errored out at `compile_fixture_to_wasm` before the assertion. Two new fixtures: - `test/e2e/fixtures/PortNames.affine` — exports `pub const input_marker` + a fn that uses it. - `test/e2e/fixtures/cross_const_caller.affine` — imports and uses both. ### Docs - `docs/specs/SPEC.adoc` §8.4 — drops the "do not yet flow" bullet for consts; the cross-module flow table now lists const-as-inlined-value as supported. - `docs/specs/codegen-environment.adoc` §5 — split into 5.1 (TopFn), 5.2 (TopConst inlining algorithm + identity-by-value note), 5.3 (glob expansion). §10 records #107 as closed and points at the regression tests. ## Inline-record-escape note `TopConst` is declared with an inline record. The OCaml compiler rejects `| TopConst tc when ...` followed by using `tc` as a value ("the type of the inlined record could escape"), so the match destructures the needed fields directly: `| TopConst { tc_name; tc_value; _ } when tc_name.name = orig_name -> Some (`Const tc_value)`. The glob path destructures `{ tc_vis; tc_name; _ }`. Reconstruction in the alias-rename path uses inline-literal construction, which is allowed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #107.
Threads imported
TopConstitems through both code paths that previously dropped them, souse M::{some_const}now compiles end-to-end instead of failing at codegen withUnboundVariable.Implementation
Codegen.gen_imports— matchesTopFnandTopConsttogether. Imported consts: compile the initialiser inline against the importer's context, append a fresh global, register(local_name, -(global_idx + 1))infunc_indices(same negative-sentinel encoding as locally-declared consts, so use-site lookup is uniform).Module_loader.flatten_imports— local-decl name suppression now covers fns and consts; public consts flow through the same alias-rename and dedup pipeline as fns, so all non-WASM back-ends pick up the fix unchanged.WASM module-linking for globals isn't standard yet, so cross-module const identity is by value — each importer materialises its own copy of the constant. Fine because AffineScript consts are immutable.
Tests
E2E Xmod Other Codegensgains two cases (#107 regression):flatten_imports inlines imported public consts— non-WASM path, asserts the imported const appears inprog_declsafter flattening.WASM gen_imports threads imported consts— full compile path, asserts the caller emits a global initialised to the imported const's value. The prior failure mode would have errored atcompile_fixture_to_wasmitself.Two new fixtures:
PortNames.affine(exportspub const input_marker),cross_const_caller.affine(imports + uses it).Spec updates
SPEC.adoc§8.4 — drops the "do not yet flow" bullet; cross-module flow now lists const-as-inlined-value as supported.codegen-environment.adoc§5 — split into 5.1 (TopFn), 5.2 (TopConst inlining algorithm + identity-by-value note), 5.3 (glob expansion). §10 records Cross-module imports: TopConst not threaded by gen_imports / flatten_imports #107 as closed.Inline-record-escape note
TopConstis an inline-record constructor. Capturing the whole record as a variable triggers OCaml's "type of the inlined record could escape" error. The match instead destructures the needed fields directly:| TopConst { tc_name; tc_value; _ } when tc_name.name = orig_name -> Some (\Const tc_value)`. The alias-rename path reconstructs by inline-literal — that form is allowed.Test plan
dune build lib bin testclean.E2E Xmod Other Codegens— all 4 tests pass (2 pre-existing + 2 new).dune runtestclean on CI.