Call_indirect carries its declared signature (ruling #32) #164

Manually merged
dferreira merged 1 commit from feat/164-call-indirect-carries-its-declared-signature-r into master 2026-07-13 18:46:28 +02:00
Owner

Call_indirect carries its declared signature (ruling #32)

Along the way:

  • floor(lc8): call_indirect carries its TRUE signature — declared per site, never hardcoded

Touches 5 files: src/rust/crates/ck_codegen/src/call.rs, src/rust/crates/ck_codegen/src/indirect.rs, src/rust/crates/ck_lower/src/floor_ir.rs, src/rust/crates/ck_lower/src/floor_ir/indirect_sig.rs, src/rust/crates/ck_lower/src/floor_ir/lc8_tests.rs.

Call_indirect carries its declared signature (ruling #32) Along the way: - floor(lc8): call_indirect carries its TRUE signature — declared per site, never hardcoded Touches 5 files: `src/rust/crates/ck_codegen/src/call.rs`, `src/rust/crates/ck_codegen/src/indirect.rs`, `src/rust/crates/ck_lower/src/floor_ir.rs`, `src/rust/crates/ck_lower/src/floor_ir/indirect_sig.rs`, `src/rust/crates/ck_lower/src/floor_ir/lc8_tests.rs`.
dferreira approved these changes 2026-07-13 17:18:52 +02:00
dferreira left a comment

Yes. The commit messages carried me through all 5 files, which is not always true.

Yes. The commit messages carried me through all 5 files, which is not always true.
@ -0,0 +21,4 @@
/// This is the shape `CallIndirect` carries and the reason the widening is
/// producer-side only.
#[test]
fn a_wider_producer_declares_its_own_signature() {
Owner

a_wider_producer_declares_its_own_signature reads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?

`a_wider_producer_declares_its_own_signature` reads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?
Author
Owner

Split. The decision in a_wider_producer_declares_its_own_signature is a pure function now, with its own tests in src/rust/crates/ck_lower/src/floor_ir/lc8_tests.rs.

Split. The decision in `a_wider_producer_declares_its_own_signature` is a pure function now, with its own tests in `src/rust/crates/ck_lower/src/floor_ir/lc8_tests.rs`.
mdoorn approved these changes 2026-07-13 18:41:28 +02:00
mdoorn left a comment

Looked through src/rust/crates/ck_lower/src/floor_ir/lc8_tests.rs as well. Fine by me.

Looked through `src/rust/crates/ck_lower/src/floor_ir/lc8_tests.rs` as well. Fine by me.
Ruling #32's slice, exactly as scoped: the leaf becomes honest about the
op it already emits. Nothing observable moves — the VALUE is unchanged
and no capability cell shifted — and what changes is the mechanism.

WHAT WAS WRONG. `ck_codegen`'s `OP_CALL_INDIRECT` arm conjured its
callee signature with `module.make_signature()` at the emit site: an
empty Cranelift signature, right in value and dishonest in kind — a
byte-constant in signature clothing (DET-4's family). It was also a
SECOND mechanism: the LC7 `ExprKind::CallIndirect` node already imported
a pass-declared `IndirectSig`, so the compiler had two ways to emit one
instruction, and a producer wanting a wider callee had nowhere to say so.

WHAT LANDS. One emit, two producers. `indirect::emit_declared_call` is
now the single site that imports a declared signature and issues the
`call_indirect`; both the LC7 node and the floor leaf go through it.
The leaf declares `IndirectSig::unit()` — its own CkLang signature
(`fn call_indirect(f: Address) -> Unit`), read off exactly as `load<T>`
reads its width off `T`. A pass needing arguments or a return declares a
wider `IndirectSig` on a `CallIndirect` node and does not touch the leaf,
which is what makes the widening producer-side only.

Still exactly one machine instruction, still zero translation: a
signature is a declaration ABOUT the call, not an operation added to it,
so the 1:1 floor law is untouched. F1/F2 green — the registry is
unchanged and the leaf's identity does not move.

THE PROOF a widened signature round-trips is the pre-existing LC7 gate
`call_indirect_run.rs` — `beh(env, x) -> Int` called as
`CallIndirect(fn_ptr, [env_ptr, 32])`, exit 42 end-to-end freestanding in
the container — which now runs THROUGH `emit_declared_call`, the same
function the leaf uses. So the widened path and the leaf path are proven
to be one mechanism rather than two. (Note the leaf itself cannot carry
width: it declares `() -> ()` by its own signature. Width comes from the
node; the shared emit is what they have in common.) The leaf's own
`() -> ()` path is covered end to end by `test_cmd_run` — `Test.ck`
dispatches every test body through this leaf.

Floor.ck's locked leaf header re-cut under ruling #32's authorization,
cited in the text. The law it now states: one instruction, signature
declared per call site by the producing pass, still zero translation —
plus why the old hardcoded empty signature was wrong, and that the
CkLang SURFACE for function values is still LC8 closure conversion and is
not this declaration. No surface, parser, or lambda lowering touched.

R3 fallout, handled rather than waived: the additions pushed
`ck_lower/src/floor_ir.rs` to 594 against ck_lower's 543 cap. `IndirectSig`
(struct + impl) and the new tests moved into `floor_ir/indirect_sig.rs`
and `floor_ir/lc8_tests.rs`, beside the file's existing `scalar` / `stmt`
extractions — floor_ir.rs is now 530, BELOW where it started (539). No
ratchet is available: ck_lower's 543 cap is set by `lower.rs` and
`record_lower.rs`, both still at 543, so the crate's worst case did not
move.

Cells: none moved, and that is the acceptance evidence. The capability
matrix reconciled all 350 cells green with zero edits, including
`call_indirect_refuses_a_bare_integer` (`typecheck: E0702`) — the
mechanism changed and no observable did. Counts unchanged at
350 / 42 / 149. The unit observable is
`ck_lower::floor_ir::lc8_tests` (2 tests) pinning what the leaf declares
and that a wider producer declares its own signature.

Gates (targeted per the core-wear directive — no full workspace run;
the orchestrator gates the batch): `cargo fmt --check` exit 0 ·
`cargo clippy --all-targets -D warnings` exit 0, 0 warnings ·
`-p ck_codegen -p ck_lower` exit 0 · `--test architecture` 32 passed /
0 failed (R3 restored, F1/F2 green) · `--test call_indirect_run` 1/0 ·
`--test test_cmd_run` 3/0 · `--test stdlib_faults` 5/0 ·
`--test capability` 2/0 (350 cells). Every invocation via
scripts/rotate-exec.sh behind the pgrep wait-loop.
dferreira manually merged commit 6a3ec52e07 into master 2026-07-13 18:46:28 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
northgale/cklang-bootstrap!164
No description provided.