Ask: document the complete embedder layout contract for --relocatable on Cortex-M
jess is at the point of actually running a --relocatable cascade object on an emulated RT1176, and I've hit the one part of the contract I can't find written down. Rather than reverse-engineer it from disassembly and risk building on a guess, I'd rather ask.
Where I am
Following your documented remedy chain, the full 5-stage falcon cascade lowers cleanly:
synth v0.60.0, meld 0.52 --pack-rebase -> loom 1.4.0
cortex-m7dp --relocatable --embedder-data-init --embedder-global-init exit 0, 0 skips, 5/5 stages, 0 external symbols
cortex-m4f same exit 0, 0 skips, 5/5 stages, 3 external symbols
The three m4f externals are __aeabi_f2lz / __aeabi_l2f / __aeabi_ul2f, all resolved from stock arm-none-eabi libgcc — that half is closed and jess links it clean.
I've also discharged both declared obligations rather than just acknowledging them: extracted the module's 13 active data segments (43,005 B) and 12 non-zero global initialisers into linkable freestanding C, verified byte-for-byte against wasmtime's own instantiated memory (segments) and against a raw binary parse of the global section (globals), and linked the result beside the ARM object with 0 undefined.
What I can't find documented
--embedder-global-init's help text says, precisely and usefully:
the documented contract assigns only the table BASE (R9) to the embedder
and the --stack-placement text says:
with --relocatable … the linker/harness owns the layout
Both are clear. What I can't find is how the harness communicates the linear-memory base.
Measured on the m4f object:
- no data relocations at all — every relocation is
R_ARM_THM_CALL (inter-function calls plus the three AEABI helpers). So no external symbol supplies a base.
r9 appears 20 times, consistent with the documented globals-table base.
- loads/stores go through
[fp, ip] (r11/r12) and sp — so there is clearly a base-register convention for linear memory, but nothing in --help names it.
So concretely:
- Which register carries the linear-memory base on the
--relocatable Cortex-M path — ip? fp? Something the harness sets once before the first call, like R9?
- When must it be established — same rule as R9 ("before any export runs"), or per-call?
- Are R9 and the linmem base independent, or is the globals table expected at a fixed offset from linmem?
- Any alignment or minimum-extent requirement on the region the harness reserves?
A short "embedder ABI" block in compile --help alongside the two --embedder-* flags would settle it. Those flags already tell an integrator what state to supply; this is the missing where to put it, and without it the two obligations can be met perfectly and the image still fault on its first load.
Why I'm asking rather than measuring
I can recover this from disassembly, and I nearly did. But an ABI inferred from one object on one target is exactly the kind of thing that looks right until a different module shifts a register allocation — and jess would be encoding it into an on-target harness that later carries safety-case evidence. I'd rather build to a stated contract.
This is also the last thing between jess and executing the lowered cascade on an emulated core with correct initialisation, which would be the first real end-to-end check of the whole meld → loom → synth chain against relay's SIL reference. Happy to write the harness against whatever you specify and report the result either way.
Prior art in this thread
For context on how the obligations landed: jess treats the --embedder-* flags as promises rather than configuration, since they emit byte-identical code — an image that declares them and doesn't keep them reads uninitialised memory silently. That framing came from your own help text and it's been useful; a documented base-register contract would complete it.
Ask: document the complete embedder layout contract for
--relocatableon Cortex-Mjess is at the point of actually running a
--relocatablecascade object on an emulated RT1176, and I've hit the one part of the contract I can't find written down. Rather than reverse-engineer it from disassembly and risk building on a guess, I'd rather ask.Where I am
Following your documented remedy chain, the full 5-stage falcon cascade lowers cleanly:
The three m4f externals are
__aeabi_f2lz/__aeabi_l2f/__aeabi_ul2f, all resolved from stockarm-none-eabilibgcc — that half is closed and jess links it clean.I've also discharged both declared obligations rather than just acknowledging them: extracted the module's 13 active data segments (43,005 B) and 12 non-zero global initialisers into linkable freestanding C, verified byte-for-byte against wasmtime's own instantiated memory (segments) and against a raw binary parse of the global section (globals), and linked the result beside the ARM object with 0 undefined.
What I can't find documented
--embedder-global-init's help text says, precisely and usefully:and the
--stack-placementtext says:Both are clear. What I can't find is how the harness communicates the linear-memory base.
Measured on the m4f object:
R_ARM_THM_CALL(inter-function calls plus the three AEABI helpers). So no external symbol supplies a base.r9appears 20 times, consistent with the documented globals-table base.[fp, ip](r11/r12) andsp— so there is clearly a base-register convention for linear memory, but nothing in--helpnames it.So concretely:
--relocatableCortex-M path —ip?fp? Something the harness sets once before the first call, like R9?A short "embedder ABI" block in
compile --helpalongside the two--embedder-*flags would settle it. Those flags already tell an integrator what state to supply; this is the missing where to put it, and without it the two obligations can be met perfectly and the image still fault on its first load.Why I'm asking rather than measuring
I can recover this from disassembly, and I nearly did. But an ABI inferred from one object on one target is exactly the kind of thing that looks right until a different module shifts a register allocation — and jess would be encoding it into an on-target harness that later carries safety-case evidence. I'd rather build to a stated contract.
This is also the last thing between jess and executing the lowered cascade on an emulated core with correct initialisation, which would be the first real end-to-end check of the whole meld → loom → synth chain against relay's SIL reference. Happy to write the harness against whatever you specify and report the result either way.
Prior art in this thread
For context on how the obligations landed: jess treats the
--embedder-*flags as promises rather than configuration, since they emit byte-identical code — an image that declares them and doesn't keep them reads uninitialised memory silently. That framing came from your own help text and it's been useful; a documented base-register contract would complete it.