diff --git a/CHANGELOG.md b/CHANGELOG.md index 28a6f68..f8ac589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,47 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [0.41.0] - 2026-07-15 + +MCU-dissolve completion + a const-expr correctness fix. v0.40.0 made +`--memory shared` *correct* (addresses rebased, #326); this release makes the +fused node *cleanly dissolvable* on a real MCU and fixes a silent miscompile of +the position-independent const-expr shape that lowering relies on. Together with +#326, gale's gust:os node fuses **and** dissolves with no hand WAT surgery. + +**Falsification:** if SP-coalescing regressed, `shared_stack_pointer_runtime_non_clobber` +(two providers' frames alias) fails. If the `cabi_realloc` drop deferred a *live* +grow, `live_internal_grow_keeps_realloc_and_hard_errors` fails. If the const-expr +truncation returned, `test_338_multimodule_const_first_embedded_globalget_remaps` +(reads the un-remapped base 5100 instead of 1100) fails. + +### Added + +- **`--memory shared` MCU-dissolve fixups (#334).** A post-merge pass coalesces + the N per-component `__stack_pointer` globals into one shared shadow stack + (leaving them separate is unsound — providers clobber each other's frames) and + drops the dead wac lowered-import shim trampoline's keep-alive export so synth + DCEs it. The fused node now lowers with zero closed-world/shadow-stack + warnings. SP detection uses only the authoritative `__stack_pointer` name (an + init-value heuristic was rejected — it could mis-coalesce unrelated globals). +- **Vestigial `cabi_realloc` drop (#298).** When a component boundary is fully + internalised (core output, no adapters, all lifts scalar) AND the allocator is + provably dead, `meld fuse` drops the `cabi_realloc*` exports and defers the + dead `memory.grow` — unblocking the lean `--memory shared --address-rebase` + fuse (which otherwise hard-errors on `memory.grow`). A call-graph reachability + gate ensures a component that allocates internally keeps its allocator and the + clean compile error rather than trapping at runtime. + +### Fixed + +- **Silent miscompile of `global.get` extended-const exprs (#338).** `meld fuse` + truncated any wasm-2.0 extended-const expression containing a `global.get` + (both `base + N` and `N + base`) to just `global.get $base`, dropping the + arithmetic — corrupting global initializers and data/element-segment offsets + (the PIE `__memory_base + N` shape). The full expression is now preserved and + re-emitted with global indices remapped; a load-bearing ordering fix ensures + the imported-global remap is populated before init-exprs are converted. + ## [0.40.0] - 2026-07-11 MCU-lowering release: `fuse --memory shared` is now **sound** for relocatable diff --git a/Cargo.lock b/Cargo.lock index f009772..5862bc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1381,7 +1381,7 @@ checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" [[package]] name = "meld-cli" -version = "0.40.0" +version = "0.41.0" dependencies = [ "anyhow", "clap", @@ -1396,7 +1396,7 @@ dependencies = [ [[package]] name = "meld-core" -version = "0.40.0" +version = "0.41.0" dependencies = [ "anyhow", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index 1e49156..7ad55b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ exclude = [ ] [workspace.package] -version = "0.40.0" +version = "0.41.0" authors = ["PulseEngine "] edition = "2024" license = "Apache-2.0" diff --git a/safety/requirements/safety-requirements.yaml b/safety/requirements/safety-requirements.yaml index 4de0229..322b5b4 100644 --- a/safety/requirements/safety-requirements.yaml +++ b/safety/requirements/safety-requirements.yaml @@ -1737,7 +1737,7 @@ artifacts: budget-fitting relocatable object — gale's gust:os v0.4.0 buffer node fits the STM32F100 8 KiB SRAM budget without the hand WAT surgery gale currently applies. - status: proposed + status: verified tags: [memory-strategy, shared-memory, mcu, dissolve, dce, v0.41.0] links: - type: derives-from @@ -1755,15 +1755,19 @@ artifacts: - meld-core/src/rewriter.rs verification-method: test verification-description: > - PLANNED. (1) A fused two-provider shared-memory module exposes exactly - one mutable `i32` `__stack_pointer`-class global (init == sp_init), and - every use of a coalesced duplicate is redirected to the survivor - (global-index remap verified; a runtime check that two providers' frames - do not alias). (2) The fused output contains no exported `call_indirect` - trampoline over an import table — the dead shim's keep-alive export is - dropped so it DCEs. End-to-end oracle on the gale app-tl repro: the fused - module lowers under `synth ... --native-pointer-abi --shadow-stack-size` - with zero closed-world/shadow-stack warnings and fits the 8 KiB budget. + VERIFIED (#343, merged 2026-07-14). meld-core/tests/mcu_dissolve.rs: + (1) `stack_pointer_globals_coalesced` — a fused module ends with exactly + one mutable-i32 SP-class global and every `global.get`/`global.set` of a + coalesced duplicate is redirected to the survivor; + `shared_stack_pointer_runtime_non_clobber` (wasmtime) — pre-coalesce two + providers' frames alias, post-coalesce they do not. + (2) `dead_import_trampoline_export_dropped` — the dead numeric-name + `call_indirect`-over-import trampoline export is dropped while real/vtable + exports are kept. `conservative_no_coalesce_when_unsure` — single-SP, + differing-init, AND unnamed same-init globals are NOT coalesced (Mythos + review: coalescing uses only the authoritative `__stack_pointer` name). + End-to-end synth-dissolve on the gale app-tl node is gale-side downstream + validation (gale#168), pending gale's probe re-run. - id: SR-50 type: sw-req @@ -1788,7 +1792,7 @@ artifacts: hard-failing), so the lean single-address-space fuse succeeds. Over-drop is silent marshalling corruption, so the verdict's conservatism is load-bearing and must not be weakened. - status: proposed + status: verified tags: [memory-strategy, shared-memory, mcu, dce, realloc, v0.41.0] links: - type: derives-from @@ -1807,16 +1811,19 @@ artifacts: - meld-core/src/rewriter.rs verification-method: test verification-description: > - PLANNED. Positive: a fused fully-internalised core (scalar lifts, no - adapters) exports no `cabi_realloc*` and, under `--memory shared - --address-rebase`, fuses successfully (the vestigial `memory.grow` is - deferred, not a hard error) — verified by re-parsing the output exports - and asserting the fuse returns Ok. Negative controls (the verdict's - conservatism): a component with a NON-scalar lift (string/list param or - result), a P2 component-wrapped output, or a fuse with adapter sites all - KEEP `cabi_realloc` and do not defer grow. A live-allocator fuse still - rejects `memory.grow` under rebasing (no behaviour change off the - provably-safe path). + VERIFIED (#345, merged 2026-07-14). meld-core/tests/drop_realloc.rs + + memory_probe unit tests. Positive `scalar_boundary_drops_realloc_and_defers_grow`: + a fused fully-internalised core exports no `cabi_realloc*` and fuses Ok + under `--memory shared --address-rebase` (the vestigial `memory.grow` is + deferred, not a hard error). Negative controls (verdict conservatism): + `string_lift`/`component_output`/`adapter_site`_keeps_realloc_and_hard_errors + all KEEP `cabi_realloc` and hard-error on grow. Mythos-added + `live_internal_grow_keeps_realloc_and_hard_errors`: a scalar-interface + component that allocates internally KEEPS realloc (the call-graph + reachability gate `module_has_reachable_memory_grow` — 11 unit tests — + prevents deferring a LIVE grow to a runtime trap). + `tight_match_preserves_realloc_lookalike_export` guards the digits-only + name match. `non_shared_fuse_preserves_realloc` guards the off-path. - id: SR-51 type: sw-req title: Extended-const exprs beginning with global.get are preserved, not truncated @@ -1832,7 +1839,7 @@ artifacts: `global.get`-first case. A truncated offset is a silent miscompile: the module still validates but places data / initialises globals at `base + 0` instead of `base + N`. - status: proposed + status: verified tags: [const-expr, miscompile, pie, correctness, v0.41.0] links: - type: derives-from @@ -1850,9 +1857,14 @@ artifacts: - meld-core/src/merger.rs verification-method: test verification-description: > - PLANNED. Executed-vs-wasmtime oracles reproducing #338: a global - initializer `g = base + 100` (base=1000) yields 1100 (not 1000) after - fuse; a data-segment active offset `base + N` places the segment at - base+N. Negative: the const-first case and a bare `global.get` (no - trailing arithmetic) are unchanged. + VERIFIED (#346, merged 2026-07-15). meld-core/tests/const_expr_globalget.rs, + executed-vs-wasmtime: a global initializer `g = base + 100` (base=1000) + yields 1100 (not the pre-fix 1000); the operand-swapped + `test_338_multimodule_const_first_embedded_globalget_remaps` — a + multi-module fuse shifting `__memory_base` off import-index 0 — reads the + correct base (1100), not the pre-fix un-remapped `__stack_pointer` (5100); + data-segment offsets `base+N` and `N+base` land at base+N (pre-fix + truncated to base+0 / hard-errored). Negative: bare `global.get` and the + const-first pure fold are unchanged. Two Mythos passes (the second caught + the operand-swapped sibling + the imported-global index-map ordering bug).