Files
zeekay 924a8b00bf papers/dag-evm-formal: update for luxcpp/evm v0.23.0
Reflects the v0.20.0 -> v0.23.0 GPU-EVM release cycle:

- s8 (gpu-native): rewritten to cover the spec-complete Metal + CUDA
  kernel surface, precompile dispatch (0x01..0x11), the EVMC host
  bridge for static-target CALL/CREATE pre-resolution, the unified
  pipeline (EVM + consensus state hash + sig-verify on one Metal
  device, 1.07x pipelined speedup), and a measured-speedup table that
  separates the cryptographic critical path from the bytecode kernel.

- s9 (NEW, four-way-parity): 133-vector bytecode corpus run through
  CPU_Sequential / CPU_Parallel / GPU_Metal / GPU_CUDA in a single
  GoogleTest driver, asserting bit-exact (gas_used, status, output).
  Documents Expectation::{Agree, GasOnly, KernelCpuMissing} taxonomy
  and explains why cross-implementation parity is a stronger argument
  than aggregate test count.

- s10 (NEW, cuda-port): four-pass porting methodology, the three
  consensus bugs the port surfaced (u256_mulmod carry-loss propagation,
  unrecognized-opcode all-gas consumption, MSTORE offset-overflow
  bounds check), the algebraic form of each fix, and what 90.87% line
  / 100% function LLVM coverage on the kernel CPU spec witness
  contributes. Includes the AWS EKS Karpenter g4dn/g5 spot CI path.

- s11 (NEW, empirical-results): consolidated headline table organised
  by workload x backend, with explicit pointers to the bench source
  files at lib/evm/gpu/{kernel,metal,cuda}.

- s12 (renumbered from s9): PQZ cross-chain parallelism, unchanged.

- 01-introduction.tex: updated bullet list of contributions.
- 99-conclusion.tex: updated to cite v0.23.0 results.
2026-02-28 22:00:00 -08:00

190 lines
9.5 KiB
TeX

\section{Four-Way Parity as a Production Argument}
\label{sec:parity}
%% ============================================================================
The claim of consensus-equivalent execution across four backends is not a
slogan; it is a single test driver that compiles, links, and runs all four
in the same process.
\subsection{The Four Backends}
\texttt{evm::gpu::Backend} enumerates four routes, each invoked through a
shared dispatcher entrypoint
\texttt{execute\_block(config, txs, nullptr)}:
\begin{enumerate}
\item \texttt{CPU\_Sequential} --- the kernel CPU interpreter
(\texttt{lib/evm/gpu/kernel/evm\_interpreter.hpp}, header-only),
single-threaded, opcode-for-opcode reflection of the Metal kernel.
This is the \emph{spec witness}: it shares the source of truth with
the GPU kernels.
\item \texttt{CPU\_Parallel} --- the same interpreter run by the
Block-STM scheduler with $N$ worker threads
(\texttt{lib/evm/gpu/parallel\_engine.cpp}).
\item \texttt{GPU\_Metal} --- the Apple Metal kernel
(\texttt{evm\_kernel.metal}), one threadgroup per transaction, dispatched
by \texttt{lib/evm/gpu/kernel/evm\_kernel\_host.mm}.
\item \texttt{GPU\_CUDA} --- the NVIDIA CUDA kernel
(\texttt{lib/evm/gpu/cuda/evm\_kernel.cu}), one warp per transaction,
dispatched by \texttt{lib/evm/gpu/cuda/evm\_kernel\_host.cpp}.
\end{enumerate}
The CUDA arm is gated on the build define \texttt{EVM\_CUDA}. On macOS the
define is absent so the CUDA assertions compile out and only Apple GPU
remains in the loop; on Linux/CUDA CI (the AWS EKS workflow described in
\S\ref{sec:cuda:ci}) the define flips on and all four backends are
checked.
\subsection{Vector Corpus}
The corpus is built once per process startup
(\texttt{test/unittests/parity\_test.cpp}, lines 92--573). It contains
\num{133} bytecode vectors hand-written to exercise:
\begin{itemize}
\item \emph{Arithmetic}: \texttt{ADD}, \texttt{SUB}, \texttt{MUL},
\texttt{DIV}, \texttt{MOD}, \texttt{ADDMOD}, \texttt{MULMOD},
\texttt{EXP}, signed variants;
\item \emph{Comparison} and \emph{bitwise}: signed/unsigned
\texttt{LT}/\texttt{GT}/\texttt{SLT}/\texttt{SGT}, \texttt{ISZERO},
\texttt{AND}/\texttt{OR}/\texttt{XOR}/\texttt{NOT},
\texttt{BYTE}/\texttt{SHL}/\texttt{SHR}/\texttt{SAR};
\item \emph{Hashing}: \texttt{KECCAK256} on empty / \texttt{abc} /
32-byte-zero / 256-byte-zero inputs;
\item \emph{Context}: \texttt{ADDRESS}, \texttt{ORIGIN},
\texttt{CALLER}, \texttt{CALLVALUE}, \texttt{CHAINID},
\texttt{GASPRICE}, \texttt{COINBASE}, \texttt{TIMESTAMP},
\texttt{NUMBER}, \texttt{GASLIMIT}, \texttt{BASEFEE},
\texttt{BLOBHASH}, \texttt{BLOBBASEFEE}, \texttt{BLOCKHASH};
\item \emph{Calldata}, \emph{code}, \emph{memory}
(\texttt{MSTORE}/\texttt{MLOAD}/\texttt{MSTORE8}/\texttt{MSIZE}/\texttt{MCOPY});
\item \emph{Storage} (\texttt{SSTORE}/\texttt{SLOAD},
\texttt{TSTORE}/\texttt{TLOAD}, zero-load);
\item \emph{Control flow} (\texttt{JUMP}, \texttt{JUMPI},
\texttt{PC}, \texttt{GAS}, \texttt{STOP}, \texttt{INVALID},
undefined opcode, bad jump);
\item \emph{Stack}: \texttt{PUSH0} through \texttt{PUSH32},
\texttt{DUP1}--\texttt{DUP16}, \texttt{SWAP1}--\texttt{SWAP16},
\texttt{POP};
\item \emph{Logging} (\texttt{LOG0}--\texttt{LOG4});
\item \emph{Returndata} (\texttt{RETURN}/\texttt{REVERT}/empty/word);
\item \emph{Account state} (default-zero through the kernel);
\item \emph{CALL family} expected to return
\texttt{CallNotSupported} (\texttt{CALL}, \texttt{CALLCODE},
\texttt{DELEGATECALL}, \texttt{STATICCALL}, \texttt{CREATE},
\texttt{CREATE2}, \texttt{SELFDESTRUCT});
\item \emph{Out-of-gas}.
\end{itemize}
Each vector carries an \texttt{Expectation} tag --- \texttt{Agree},
\texttt{GasOnly}, or \texttt{KernelCpuMissing} --- that tells the test
which divergences are tolerated. \texttt{Agree} demands bit-exact match on
\texttt{(gas\_used, status, output)} across all four backends.
\texttt{GasOnly} permits gas-accounting drift (typically the cost of
\texttt{MSTORE} memory expansion in different revisions) but still requires
status and output to match. \texttt{KernelCpuMissing} marks vectors where
the kernel CPU interpreter has not yet implemented an opcode that the GPU
kernels do; these vectors expect CPU \texttt{Error} and GPU success ---
catching the case where a new opcode lands on GPU before the CPU spec
witness catches up.
\subsection{The Test}
\texttt{TEST(Parity, AllBackendsAgreeOnEveryVector)} loops the corpus
once, runs each vector through all four backends, and counts the
\texttt{(Agree~ok, GasOnly~ok, Missing~ok)} totals. Any divergence under
the active expectation registers as \texttt{ADD\_FAILURE}; the test
asserts at the end that the failure count is zero. The test is a single
GoogleTest case so it can run inside \texttt{ctest -j} alongside the other
unit suites.
The output prints, on every CI run:
\begin{verbatim}
[parity] runs: CPU_Sequential=N CPU_Parallel=N GPU_Metal=N [GPU_CUDA=N]
[parity] Agree: <ok> ok, <fail> fail
[parity] GasOnly: <ok> ok, <fail> fail
[parity] KernelCpuMissing: <ok> ok, <fail> fail
[parity] total <pass> / <total> vectors meet expectations
\end{verbatim}
\subsection{Why This Is Not a Smoke Test}
Three properties make four-way parity a production-grade argument rather
than a developer-confidence test.
\textbf{(P1) Independent implementations of the same spec.} The kernel
CPU interpreter and the Metal kernel share a directory and review cycle
but no opcode-level code. CUDA was ported from Metal in a separate
branch by line-by-line translation (\S\ref{sec:cuda}) and produces an
independent control-flow graph: the Metal version uses
\texttt{threadgroup} reductions for stack ops, the CUDA version uses
warp shuffles. Block-STM adds a fourth code path because the scheduler's
read/write tracking is a different layer of the system. A bit-exact
result across all four is therefore evidence that the EVM specification
was internalised, not that one implementation was copy-pasted.
\textbf{(P2) Status, gas, and output checked exactly.} The default
\texttt{Expectation::Agree} requires equality on three independent
quantities. Status is a discrete enum; gas is a 64-bit integer; output
is a byte vector of any length. There are 13 vectors marked
\texttt{GasOnly} and a small set marked \texttt{KernelCpuMissing} ---
each tagged exception is a documented divergence with a specific algebraic
cause, not a mass-pardon. The test writes a $\texttt{[parity]}$ promotion
hint to \texttt{stdout} when a \texttt{GasOnly} vector starts agreeing,
forcing the engineer either to upgrade the tag or explain the regression.
\textbf{(P3) The four backends are co-deployed.} The same dispatcher
\texttt{execute\_block(config, txs, nullptr)} is what production callers
use. There is no separate ``test mode" path. A consensus-relevant bug
that crept into the Metal kernel would be caught by the CUDA assertion;
a CUDA-only bug would fail the Metal assertion; a kernel-CPU-only bug
would fail either GPU. The cross-checking is symmetric and runs on
every PR through CI.
\subsection{What the Coverage Numbers Add}
Source-based LLVM coverage is built into the same target set
(\texttt{lib/evm/COVERAGE.md}). The parity-driver run at v0.23.0 reports
\textbf{90.87\% line coverage and 100\% function coverage} on the kernel
CPU interpreter (\texttt{evm\_interpreter.hpp}). The 9.13\% of unexecuted
lines are precisely the unimplemented \texttt{CALL}/\texttt{CREATE}
arms (which the test catches under \texttt{Expectation::Agree} for
\texttt{CallNotSupported}) and the dead branches behind compile-time
\texttt{assert}s. The combination --- four backends agreeing across
133 hand-crafted vectors plus 100\% function coverage on the spec
witness --- means we have a counter-example finder if any backend silently
diverges from the others on an opcode the corpus already touches.
\subsection{Limitations}
\begin{itemize}
\item \emph{Coverage on \texttt{.metal} and \texttt{.cu} sources is
structural only.} LLVM source-based coverage instruments the
kernel-CPU interpreter (which is a \texttt{.hpp}) and the host
launchers (\texttt{.mm}/\texttt{.cpp}). Metal AIR and CUDA PTX are
outside LLVM's coverage scope (Metal compiles to AIR, nvcc emits PTX
without coverage-mapped IR). Coverage on the GPU code itself is
indirect: every vector that executes through the GPU dispatcher
necessarily executes the corresponding device path. The four-way
parity test is the corroboration.
\item \emph{The corpus is per-opcode, not stateful.} Each vector
runs in its own fresh state. A consensus bug that requires multiple
transactions to manifest (a Block-STM hazard, an SSTORE refund
accumulator inconsistency across txs) is not currently in the corpus.
The Block-STM scheduler has its own unit suite
(\texttt{lib/evm/gpu/parallel\_engine.cpp} drives
\texttt{evm-test-modes}) but it does not cross-validate against the
other three backends. Path: extend \texttt{ParityVector} to a
\texttt{ParitySequence} type carrying multiple \texttt{Transaction}s
and a state-checkpoint diff. Not yet implemented.
\item \emph{No fuzzing layer.} The corpus is hand-written and grows by
inspection. A differential fuzzer that emits random bytecode and
checks four-way agreement would close more of the long tail.
\texttt{test/fuzzer/} hosts the existing evmone fuzzer; wiring it
through the GPU dispatcher is on the v0.24 roadmap.
\end{itemize}
%% ============================================================================