Skip to content

Architecture

This document provides an overview of Zig EVM's architecture and design.

System Overview

┌─────────────────────────────────────────────────────────────────┐
│                        Language Bindings                         │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐  │
│  │  Python  │    │   Rust   │    │    JS    │    │    C     │  │
│  │ (ctypes) │    │  (FFI)   │    │ (N-API)  │    │ (native) │  │
│  └────┬─────┘    └────┬─────┘    └────┬─────┘    └────┬─────┘  │
└───────┼───────────────┼───────────────┼───────────────┼─────────┘
        │               │               │               │
        └───────────────┴───────┬───────┴───────────────┘
┌───────────────────────────────▼─────────────────────────────────┐
│                         FFI Layer (C ABI)                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  src/ffi.zig                                            │    │
│  │  - Opaque handles (EVMHandle, BatchHandle)              │    │
│  │  - Error codes enum                                      │    │
│  │  - C-compatible structs                                  │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────┬───────────────────────────────┘
┌─────────────────────────────────▼───────────────────────────────┐
│                         Core EVM Engine                          │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────┐   │
│  │    Stack     │  │    Memory    │  │      Accounts        │   │
│  │  (1024 max)  │  │  (dynamic)   │  │  (balance, storage)  │   │
│  └──────────────┘  └──────────────┘  └──────────────────────┘   │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────┐   │
│  │   Opcodes    │  │  Call Stack  │  │        Logs          │   │
│  │  (141 impl)  │  │  (nested)    │  │    (LOG0-LOG4)       │   │
│  └──────────────┘  └──────────────┘  └──────────────────────┘   │
└─────────────────────────────────┬───────────────────────────────┘
┌─────────────────────────────────▼───────────────────────────────┐
│                     Parallel Execution Layer                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  BatchExecutor                                          │    │
│  │  - Dependency analysis (O(n) hash-based)                │    │
│  │  - Work-stealing thread pool                            │    │
│  │  - Wave-based parallel execution                        │    │
│  │  - Speculative execution with rollback                  │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘

Core Components

EVM Struct

The central execution engine in src/main.zig:

pub const EVM = struct {
    // Execution state
    stack: Stack,           // 256-bit word stack (max 1024)
    memory: Memory,         // Dynamic byte array
    pc: usize,              // Program counter
    gas: u64,               // Remaining gas
    code: []const u8,       // Bytecode being executed

    // Account state
    accounts: HashMap([20]u8, Account),

    // Environment
    current_address: [20]u8,
    caller_address: [20]u8,
    origin_address: [20]u8,
    call_value: BigInt,
    calldata: []const u8,

    // Block context
    block_number: u64,
    block_timestamp: u64,
    coinbase: [20]u8,
    chain_id: u64,

    // Execution results
    return_data: []u8,
    logs: ArrayList(Log),
    stop_execution: bool,
    execution_reverted: bool,
};

Stack

LIFO stack for 256-bit EVM words (src/stack.zig):

Property Value
Maximum depth 1024 items
Word size 256 bits (32 bytes)
Operations push, pop, peek, dup, swap
pub const Stack = struct {
    items: [1024]BigInt,
    depth: usize,

    pub fn push(self: *Stack, value: BigInt) !void;
    pub fn pop(self: *Stack) ?BigInt;
    pub fn peek(self: *Stack, index: usize) ?BigInt;
    pub fn dup(self: *Stack, n: usize) !void;
    pub fn swap(self: *Stack, n: usize) !void;
};

Memory

Dynamic byte array with word-aligned access (src/memory.zig):

Property Description
Expansion Automatic on access
Word size 32 bytes
Gas cost Quadratic for expansion
pub const Memory = struct {
    data: ArrayList(u8),

    pub fn load(self: *Memory, offset: usize) !BigInt;
    pub fn store(self: *Memory, offset: usize, value: BigInt) !void;
    pub fn store8(self: *Memory, offset: usize, value: u8) !void;
    pub fn size(self: *Memory) usize;
};

BigInt

256-bit integer arithmetic (src/bigint.zig):

pub const BigInt = struct {
    data: [4]u64,  // 4 x 64-bit words = 256 bits

    // Arithmetic
    pub fn add(self: BigInt, other: BigInt) BigInt;
    pub fn sub(self: BigInt, other: BigInt) BigInt;
    pub fn mul(self: BigInt, other: BigInt) BigInt;
    pub fn div(self: BigInt, other: BigInt) BigInt;
    pub fn mod(self: BigInt, other: BigInt) BigInt;
    pub fn exp(self: BigInt, other: BigInt) BigInt;

    // Modular arithmetic
    pub fn addmod(a: BigInt, b: BigInt, n: BigInt) BigInt;
    pub fn mulmod(a: BigInt, b: BigInt, n: BigInt) BigInt;

    // Bitwise
    pub fn @"and"(self: BigInt, other: BigInt) BigInt;
    pub fn @"or"(self: BigInt, other: BigInt) BigInt;
    pub fn xor(self: BigInt, other: BigInt) BigInt;
    pub fn not(self: BigInt) BigInt;
    pub fn shl(self: BigInt, shift: u8) BigInt;
    pub fn shr(self: BigInt, shift: u8) BigInt;

    // Comparison
    pub fn lt(self: BigInt, other: BigInt) bool;
    pub fn gt(self: BigInt, other: BigInt) bool;
    pub fn eq(self: BigInt, other: BigInt) bool;
};

Account State

pub const Account = struct {
    balance: BigInt,
    nonce: u64,
    code: []const u8,
    storage: HashMap(BigInt, BigInt),
};

Call Frame

Nested execution contexts for CALL operations (src/call_frame.zig):

pub const CallFrame = struct {
    caller: [20]u8,
    callee: [20]u8,
    value: BigInt,
    calldata: []const u8,
    gas: u64,
    return_offset: usize,
    return_size: usize,
    is_static: bool,
    is_delegate: bool,
    depth: u16,
};

Opcode Implementation

Each opcode is implemented as a separate file in src/opcodes/:

// Example: src/opcodes/add.zig
const EVM = @import("../main.zig").EVM;
const OpcodeImpl = @import("../main.zig").OpcodeImpl;
const Opcode = @import("../main.zig").Opcode;

pub fn getImpl() struct { code: u8, impl: OpcodeImpl } {
    return .{
        .code = @intFromEnum(Opcode.ADD),
        .impl = OpcodeImpl{ .execute = execute },
    };
}

fn execute(evm: *EVM) !void {
    const a = evm.stack.pop() orelse return error.StackUnderflow;
    const b = evm.stack.pop() orelse return error.StackUnderflow;
    try evm.stack.push(a.add(b));
}

Opcode Categories

Category Opcodes Count
Arithmetic ADD, SUB, MUL, DIV, MOD, EXP, etc. 12
Comparison LT, GT, EQ, ISZERO, etc. 6
Bitwise AND, OR, XOR, NOT, SHL, SHR, etc. 8
Stack PUSH1-32, DUP1-16, SWAP1-16, POP 66
Memory MLOAD, MSTORE, MSTORE8, MSIZE 4
Storage SLOAD, SSTORE 2
Flow JUMP, JUMPI, JUMPDEST, PC, STOP 5
Environment ADDRESS, BALANCE, CALLER, etc. 16
Block NUMBER, TIMESTAMP, COINBASE, etc. 9
Logging LOG0-LOG4 5
Call CALL, DELEGATECALL, STATICCALL, etc. 6
Create CREATE, CREATE2 2

Execution Flow

Single Transaction

1. Transaction Validation
   ├─ Check gas limit
   ├─ Verify balance
   └─ Validate nonce

2. EVM Initialization
   ├─ Set execution context
   ├─ Load contract code
   └─ Initialize stack/memory

3. Opcode Execution Loop
   ├─ Fetch opcode at PC
   ├─ Check gas cost
   ├─ Execute opcode
   ├─ Update PC
   └─ Repeat until STOP/RETURN/REVERT

4. Result Processing
   ├─ Apply state changes
   ├─ Emit logs
   └─ Return result

Parallel Execution

1. Dependency Analysis
   ├─ Detect address conflicts
   ├─ Detect storage conflicts
   └─ Build dependency graph

2. Wave Creation
   ├─ Group independent transactions
   └─ Order waves by dependencies

3. Parallel Execution
   ├─ Execute wave transactions concurrently
   ├─ Thread pool with work-stealing
   └─ Process waves sequentially

4. State Merging
   ├─ Combine results
   └─ Maintain consistency

File Structure

zig-evm/
├── src/
│   ├── main.zig              # Core EVM struct
│   ├── stack.zig             # Stack implementation
│   ├── memory.zig            # Memory implementation
│   ├── bigint.zig            # 256-bit integers
│   ├── call_frame.zig        # Call stack
│   ├── ffi.zig               # C ABI exports
│   ├── batch_executor.zig    # Parallel execution
│   ├── parallel_optimized.zig # Optimized scheduler
│   └── opcodes/              # Opcode implementations
│       ├── add.zig
│       ├── sub.zig
│       ├── call.zig
│       └── ... (141 files)
├── include/
│   └── zigevm.h              # C header
├── bindings/
│   ├── python/               # Python bindings
│   ├── rust/                 # Rust bindings
│   └── js/                   # JavaScript bindings
└── tests/
    ├── test_*.zig            # Unit tests
    └── eth_compliance.zig    # Ethereum tests

Performance Characteristics

Metric Value
Single execution ~1M gas/second
Parallel throughput 5-6x improvement (8 threads)
Memory overhead ~100KB per EVM instance
Startup time <1ms for instance creation

Thread Safety

  • Each EVM instance is not thread-safe
  • Create separate instances for concurrent use
  • BatchExecutor handles internal threading
  • FFI functions are safe to call from any thread