You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#874 makes the guest's out-of-line memcpy a DMA ecall. Which memcpy the guest
actually links was left to chance: the symbol lived in syscalls.rs, and it won
resolution because _start calls sys_halt from that same module, so the linker
extracts the object anyway. Nothing wrote that down, and nothing tests it. Inline the
halt into _start and the strong definition stops being extracted — the guest silently
falls back to the weak compiler-builtinsmemcpy, keeps producing correct output, and
just gets slower.
The EF's "Accelerated Memory Operations" standard (eth-act/zkevm-standards#32, merged)
names this exact failure and requires a vendor to pick a mechanism that makes resolution
deterministic, and to document which one.
Description
Defines memcpy in the always-linked entrypoint object, next to _start, so the
strong definition is in the link graph from the start — mechanism (1) of the standard,
documented in docs/general_flow.md.
Adds a guest whose copies are only compiler-emitted (nothing names memcpy), so a
silent fallback to the weak definition fails a test instead of quietly costing
performance. The two existing DMA guests declare extern "C" { fn memcpy }, which
forces the symbol undefined — that is the easy case, not the one that degrades.
Reports Dma bytes and Dma rows alongside Dma calls in execute --cycles. One memcpy becomes as many ecalls as the stub chunks it into, so the call count follows
the chunking rather than the work. The row formula is shared by trace generation, the
sizing pass and the CLI, so the three cannot drift.
Documents the aligned/misaligned cost difference: chunk width comes from the bytes
remaining, so the DMA table's row count is alignment-independent, but each chunk's two
memory operations are routed by address — an 8-aligned window sharing one old timestamp
reaches MEMW_A (29 columns, one LT), anything else falls to MEMW (49 columns, eight LT). A misaligned copy therefore commits strictly more cells.
Conformance
Satisfies the clauses an accelerator can satisfy on its own:
Semantics / Alignment — memcpy is behaviourally identical to the C function for
every input, including n == 0 and any alignment of dest, src or n. Covered by dma_memcpy_cases (lengths 0–256, a multi-chunk 777-byte copy, both overlap
directions, page-crossing operands) and a 256-case differential fuzz.
Linking and symbol resolution — mechanism (1), "always-linked runtime": memcpy is
defined in the object that defines _start, which every guest links unconditionally.
Documented in docs/general_flow.md, as the standard requires. Verified on the shipped
ELFs: ethrex.elf carries a single memcpy definition, the accelerated one, with the compiler-builtins member never extracted.
Observability (recommended) — execute --cycles reports Dma calls, Dma bytes
and Dma rows. The aligned/misaligned split the standard suggests is not reported: the
statistics come from Log, whose two operand slots already carry src and n. The
reason is written down rather than claimed as done.
It does not satisfy the scope clause, and no memcpy change can. That clause says
the symbols "are exported from the vendor static library defined by the Static Library
and Linker Script standard". Lambda VM has no such library: the guest interface is a Rust
rlib, with no .a, no linker script, no _heap_start/_heap_end and no int main(void)
ABI. That standard is unimplemented repo-wide — the IO interface and the cryptographic
accelerators are in the same position — so adopting it is a repo-level decision rather
than one this PR can make, and belongs in its own issue.
Validation
Three guests execute and assert their DMA ecall counts. The guard is not vacuous: renaming
the symbol by hand and rebuilding drops Dma calls 4 → 0 and raises cycles 12,670 →
13,316 — correct output, more expensive — and the test catches it. Plus cargo fmt, the
four make lint clippy passes, cargo test -p executor, -p cli, the prover DMA suite
including the disk-spill length-drift test.
jotabulacios
changed the title
Align DMA memcpy with the EF's Accelerated Memory Operations standard
Pin the DMA memcpy symbol and report what copies cost
Aug 27, 2026
jotabulacios
changed the title
Pin the DMA memcpy symbol and report what copies cost
Align DMA memcpy with the EF's Accelerated Memory Operations standard
Aug 27, 2026
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
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.
Motivation
#874 makes the guest's out-of-line
memcpya DMA ecall. Whichmemcpythe guestactually links was left to chance: the symbol lived in
syscalls.rs, and it wonresolution because
_startcallssys_haltfrom that same module, so the linkerextracts the object anyway. Nothing wrote that down, and nothing tests it. Inline the
halt into
_startand the strong definition stops being extracted — the guest silentlyfalls back to the weak
compiler-builtinsmemcpy, keeps producing correct output, andjust gets slower.
The EF's "Accelerated Memory Operations" standard (eth-act/zkevm-standards#32, merged)
names this exact failure and requires a vendor to pick a mechanism that makes resolution
deterministic, and to document which one.
Description
memcpyin the always-linked entrypoint object, next to_start, so thestrong definition is in the link graph from the start — mechanism (1) of the standard,
documented in
docs/general_flow.md.memcpy), so asilent fallback to the weak definition fails a test instead of quietly costing
performance. The two existing DMA guests declare
extern "C" { fn memcpy }, whichforces the symbol undefined — that is the easy case, not the one that degrades.
Dma bytesandDma rowsalongsideDma callsinexecute --cycles. Onememcpybecomes as many ecalls as the stub chunks it into, so the call count followsthe chunking rather than the work. The row formula is shared by trace generation, the
sizing pass and the CLI, so the three cannot drift.
remaining, so the DMA table's row count is alignment-independent, but each chunk's two
memory operations are routed by address — an 8-aligned window sharing one old timestamp
reaches MEMW_A (29 columns, one
LT), anything else falls to MEMW (49 columns, eightLT). A misaligned copy therefore commits strictly more cells.Conformance
Satisfies the clauses an accelerator can satisfy on its own:
memcpyis behaviourally identical to the C function forevery input, including
n == 0and any alignment ofdest,srcorn. Covered bydma_memcpy_cases(lengths 0–256, a multi-chunk 777-byte copy, both overlapdirections, page-crossing operands) and a 256-case differential fuzz.
memcpyisdefined in the object that defines
_start, which every guest links unconditionally.Documented in
docs/general_flow.md, as the standard requires. Verified on the shippedELFs:
ethrex.elfcarries a singlememcpydefinition, the accelerated one, with thecompiler-builtinsmember never extracted.execute --cyclesreportsDma calls,Dma bytesand
Dma rows. The aligned/misaligned split the standard suggests is not reported: thestatistics come from
Log, whose two operand slots already carrysrcandn. Thereason is written down rather than claimed as done.
It does not satisfy the scope clause, and no
memcpychange can. That clause saysthe symbols "are exported from the vendor static library defined by the Static Library
and Linker Script standard". Lambda VM has no such library: the guest interface is a Rust
rlib, with no
.a, no linker script, no_heap_start/_heap_endand noint main(void)ABI. That standard is unimplemented repo-wide — the IO interface and the cryptographic
accelerators are in the same position — so adopting it is a repo-level decision rather
than one this PR can make, and belongs in its own issue.
Validation
Three guests execute and assert their DMA ecall counts. The guard is not vacuous: renaming
the symbol by hand and rebuilding drops
Dma calls4 → 0 and raises cycles 12,670 →13,316 — correct output, more expensive — and the test catches it. Plus
cargo fmt, thefour
make lintclippy passes,cargo test -p executor,-p cli, the prover DMA suiteincluding the disk-spill length-drift test.