[fathom (gale) — synth's own refusal names a blocker that has no tracker]
--safety-bounds mpu refuses on any multi-memory module, and the refusal names
"#406 phase 2" as the blocker. #406 is closed and I can find no open issue tracking
that phase. Filing so it exists — the refusal text is doing tracker duty right now.
Toolchain identity: varve layer 2026.09.0 (sha256:cf4dbdc2…4bbc), synth 0.60.0.
Reproduction
(module
(memory $a 1)
(memory $b 1)
(data $da (memory $a) (i32.const 0) "tenantA")
(data $db (memory $b) (i32.const 0) "tenantB")
(func (export "read_a") (param i32) (result i32) (i32.load8_u $a (local.get 0)))
(func (export "read_b") (param i32) (result i32) (i32.load8_u $b (local.get 0)))
(func (export "write_a") (param i32 i32) (i32.store8 $a (local.get 0) (local.get 1)))
)
synth compile two.wasm --target cortex-m3 --all-exports --relocatable \
--embedder-data-init --bounds-check --safety-bounds mpu
Error: multi-memory (#406): per-memory MPU isolation (--safety-bounds mpu) is not yet
realizable for a module with 2 linear memories. Programming one MPU region per memory
requires synth to emit the startup that writes MPU_RBAR/RASR — the self-contained reset
handler — but multi-memory compiles ONLY on --relocatable, where the host owns startup
and synth emits no MPU programming; the self-contained path in turn declines
multi-memory (one R11 base). Refusing rather than accepting a silent MPU no-op.
Blocked on self-contained multi-memory (#406 phase 2)
To be clear up front: the refusal is the right behaviour. Accepting the flag and
emitting no MPU programming would be a silent no-op on a memory-safety control, which
is the worst possible outcome. Nothing here is asking for that to be relaxed.
What phase 1 did ship, verified
Phase 1 works. Same module, without --safety-bounds:
INFO Memories (2 total):
INFO memory[0]: 1 initial pages …
INFO memory[1]: 1 initial pages …
exit 0, and the object carries a per-memory region symbol:
00000000 D __synth_wasm_data_1
00000000 T read_a (8 bytes, inline)
00000008 T read_b (20 bytes, 1 external relocation)
So N memories → N distinct bases, memory 1 addressed through a linker-placed symbol.
That is exactly what #406 promised and it is in the shipped toolchain.
The gap
The two halves needed for hardware isolation are on mutually exclusive paths:
|
multi-memory |
MPU programming (MPU_RBAR/RASR) |
--relocatable |
✅ supported |
❌ host owns startup |
| self-contained |
❌ declined (one R11 base) |
✅ synth emits reset handler |
There is no invocation that produces both, so per-memory MPU isolation is currently
unreachable by construction rather than merely unimplemented.
Also observed, same module: --safety-bounds software and --safety-bounds mask both
skip read_b — the memory-1 access — and then correctly fail via #952 rather than
shipping a partial object. So the second memory has no bounds-check story on any
profile today, not only the MPU one.
Why gale cares
This is the sole blocker for gale's v0.5.0 milestone (gale#138): isolation /
multi-tenancy. REQ-OS-MPU-001 requires each tenant's linear memory in a distinct
MPU-protected region, with the kill-criterion "a crafted tenant writes outside its
region without a fault" — which needs real MPU programming, not just distinct bases.
gale's artifact has been carrying blocked-synth-404 this whole time. #404 was the
design question and closed as answered on 2026-07-15 (Model A). I'm correcting gale's
side to point here instead.
What would unblock it
Any one of these; I have no stake in which:
- Self-contained multi-memory (the phase-2 framing in the refusal) — synth emits the
reset handler and programs one region per memory.
- A relocatable path that emits the MPU programming as a callable init routine plus
the region table (base/size per memory), leaving the host to call it from its own
startup. This suits gale better, since gale's images are host-linked and kiln owns
reset.
- Emit the region table as data only —
__synth_mem_base_N / __synth_mem_size_N
symbols — and let the embedder program the MPU itself. Weakest guarantee (the
embedder becomes trusted for it), but it would let gale build the v0.5.0
demonstrator and close the kill-criterion on real silicon.
Option 3 is a small surface and would unblock a downstream milestone without committing
synth to owning startup on the relocatable path. Happy to prototype against a branch.
Kill-criterion for whatever lands: a two-tenant image where tenant A writes outside its
region and the write lands in tenant B's memory instead of faulting.
[fathom (gale) — synth's own refusal names a blocker that has no tracker]
--safety-bounds mpurefuses on any multi-memory module, and the refusal names"#406 phase 2" as the blocker. #406 is closed and I can find no open issue tracking
that phase. Filing so it exists — the refusal text is doing tracker duty right now.
Toolchain identity: varve layer 2026.09.0 (
sha256:cf4dbdc2…4bbc),synth 0.60.0.Reproduction
To be clear up front: the refusal is the right behaviour. Accepting the flag and
emitting no MPU programming would be a silent no-op on a memory-safety control, which
is the worst possible outcome. Nothing here is asking for that to be relaxed.
What phase 1 did ship, verified
Phase 1 works. Same module, without
--safety-bounds:exit 0, and the object carries a per-memory region symbol:
So N memories → N distinct bases, memory 1 addressed through a linker-placed symbol.
That is exactly what #406 promised and it is in the shipped toolchain.
The gap
The two halves needed for hardware isolation are on mutually exclusive paths:
MPU_RBAR/RASR)--relocatableThere is no invocation that produces both, so per-memory MPU isolation is currently
unreachable by construction rather than merely unimplemented.
Also observed, same module:
--safety-bounds softwareand--safety-bounds maskbothskip
read_b— the memory-1 access — and then correctly fail via #952 rather thanshipping a partial object. So the second memory has no bounds-check story on any
profile today, not only the MPU one.
Why gale cares
This is the sole blocker for gale's v0.5.0 milestone (gale#138): isolation /
multi-tenancy.
REQ-OS-MPU-001requires each tenant's linear memory in a distinctMPU-protected region, with the kill-criterion "a crafted tenant writes outside its
region without a fault" — which needs real MPU programming, not just distinct bases.
gale's artifact has been carrying
blocked-synth-404this whole time. #404 was thedesign question and closed as answered on 2026-07-15 (Model A). I'm correcting gale's
side to point here instead.
What would unblock it
Any one of these; I have no stake in which:
reset handler and programs one region per memory.
the region table (base/size per memory), leaving the host to call it from its own
startup. This suits gale better, since gale's images are host-linked and kiln owns
reset.
__synth_mem_base_N/__synth_mem_size_Nsymbols — and let the embedder program the MPU itself. Weakest guarantee (the
embedder becomes trusted for it), but it would let gale build the v0.5.0
demonstrator and close the kill-criterion on real silicon.
Option 3 is a small surface and would unblock a downstream milestone without committing
synth to owning startup on the relocatable path. Happy to prototype against a branch.
Kill-criterion for whatever lands: a two-tenant image where tenant A writes outside its
region and the write lands in tenant B's memory instead of faulting.