fix(inline): map full→local function index so imported calls don't break inlining — closes #153 (v1.1.5)#154
Merged
Conversation
…eak inlining — closes #153 gale follow-up (#153): on v1.1.4, `inline_functions` reverted the inline of a local callee whenever the CALLER also called an imported function. This blocked the gale C↔Rust seam (z_impl_k_sem_give inlines gale_k_sem_give_decide but also calls 5 env:: kernel imports). Root cause: a function-INDEX-SPACE confusion, not a verifier gap. `Call(func_idx)` uses the FULL WebAssembly index space — imported functions occupy 0..num_imported_funcs, local functions follow. But the inliner: - built `function_sizes` keyed by LOCAL index (module.functions, from 0) while `call_counts` (from count_calls_recursive) keys by FULL index; - indexed `all_functions` (local-only) with the FULL `func_idx`. With an import present the spaces diverge: the import's index (0) collides with local function 0, so the inliner treated a VOID imported call as a call to local function 0, emitted a `local.set` to bind its params with NO argument on the stack, and produced a malformed body. The verifier correctly rejected the bad encode ("Stack underflow in LocalSet") → the whole inline reverted. (The reporter hypothesised a missing verifier model for imports; the real bug was upstream in the inliner emitting nonsense.) Fix (loom-core/src/lib.rs, inline_functions / inline_calls_in_block): - compute num_imported_funcs once per iteration; - key `function_sizes` by FULL index (local idx + offset) to match call_counts; - exclude imported indices (< offset) from inline candidates — they have no body to inline; - in inline_calls_in_block, map func_idx → local via checked_sub(offset); an imported index yields None → keep the original call untouched; thread the offset through the Block/Loop/If recursion. Modules with no imported functions are unaffected (offset 0), so all existing inline behaviour is preserved. Regression test test_inline_caller_with_imported_call: a caller that calls an import AND a local i64 callee now inlines the local callee (call removed, verified) while preserving the import call. 386 lib tests pass; #151 i64/i32 repros still inline; gale wasm + full pipeline dogfood clean. The 7 pre-existing LICM/DCE integration failures (#150) are unrelated and unchanged. Closes #153. Trace: REQ-8
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.
gale #153 — inline a local callee even when the caller calls an import
Bug-fix release v1.1.5. On v1.1.4,
inline_functionsreverted the inline of a local callee whenever the caller also called an imported function — blocking the gale C↔Rust seam (z_impl_k_sem_giveinlinesgale_k_sem_give_decidebut also calls 5env::kernel imports).Root cause — a function-index-space confusion (not a verifier gap)
Call(func_idx)uses the full WebAssembly index space (imports0..N, locals after). But the inliner builtfunction_sizeskeyed by local index whilecall_countskeyed by full index, and indexed the local-onlyall_functionswith the fullfunc_idx. With an import present, the import's index collides with a local function's slot — so the inliner treated a void imported call as a call to local function 0, emitted alocal.setto bind params with no argument on the stack, and produced a malformed body. The verifier correctly rejected it (Stack underflow in LocalSet) → the whole inline reverted.(The report hypothesised a missing verifier model for imports; ground-truthing showed the real bug was upstream — the inliner emitting nonsense. The verifier was right to reject it.)
Fix
num_imported_funcsonce per iteration.function_sizes+ the candidate gate by the full index (matchescall_counts).func_idx - num_imported_funcs) when indexing the local-function table; an imported index yieldsNone→ keep the original call untouched. Thread the offset through the Block/Loop/If recursion.Validation
test_inline_caller_with_imported_call: import call preserved, local i64 callee inlined + verified.Known-red gates (pre-existing)
Closes #153.
🤖 Generated with Claude Code