DEV Community

Aniket Misra
Aniket Misra

Posted on

Before Solana Went Native: What the EVM Actually Is (And Why It Was Built to Be Slow)

To understand why Solana's decision to repurpose BPF was radical, you first need to understand what it was radical against. That's the Ethereum Virtual Machine — the thing every other "VM for smart contracts" design, Solana included, is implicitly arguing with.

The EVM isn't slow because Ethereum's engineers were careless. It's slow because of a set of design choices that made total sense in 2014, when the problem being solved wasn't "how do we get 50,000 TPS" — it was "how do we get a global network of mutually distrusting strangers to agree on the exact same computation, byte for byte, forever." Every property that makes the EVM feel heavy today is downstream of that one requirement.

This is the prequel to a piece I wrote on how Solana re-engineered a Linux kernel packet filter into its execution layer. Before we get to what Solana did differently, here's what it was different from.


1. A Stack Machine, Not a Register Machine

The EVM is a 256-bit stack-based virtual machine. There are no general-purpose registers. Every operation — arithmetic, comparisons, memory access — pushes and pops values from a stack, one instruction at a time.

PUSH1 0x05
PUSH1 0x03
ADD
Enter fullscreen mode Exit fullscreen mode

That's the entire bytecode for "5 + 3." No register allocation, no scheduling — the EVM interprets it top to bottom, exactly as written. This is also why Solidity compiles down to something conceptually close to this, opcode by opcode, rather than to a register-mapped instruction set the way a Rust program compiling to BPF does.

The 256-bit word size isn't an accident either — it matches the output width of Keccak-256, the hash function baked into almost everything Ethereum does (addresses, storage slots, opcodes like SHA3). The VM's fundamental data type was chosen to fit the cryptography, not the hardware.

Compare that to a register machine like BPF or SBF, where the instruction set is designed to map closely to what a real CPU already does. The EVM was never trying to be fast on real silicon. It was trying to be unambiguous — every implementation, on every machine, computing the exact same stack transitions.


2. Interpreted, All the Way Down

There's no JIT. No AOT compilation to native code. Every EVM opcode, on every node, for every transaction, gets interpreted at runtime, instruction by instruction, by whatever client software that node happens to run (Geth, Nethermind, Besu — doesn't matter, they all have to agree).

This is a deliberate constraint, not an oversight. If clients were free to JIT-compile bytecode to native machine code, you'd be trusting each client's compiler to preserve exact semantics across compilation — including edge cases like integer overflow behavior and gas accounting quirks. One divergent optimization and the network forks. Interpretation is slow, but it's slow in a way that's trivially auditable: the reference behavior is the implementation.

Some newer EVM implementations do experiment with JIT compilation for performance, but the base protocol was never designed assuming it — correctness came first, speed was whatever was left over.


3. Gas: Metering Before Execution, Not Verification Before Execution

The EVM has no static verifier checking that your bytecode terminates before it runs — there's no equivalent of eBPF's compile-time proof-of-termination. Instead, every single opcode has a fixed gas cost, charged as execution proceeds. Run out of gas, execution halts immediately and any state changes revert.

function withdraw(uint amount) public {
    require(amount <= balances[msg.sender], "insufficient balance");
    balances[msg.sender] -= amount;
    payable(msg.sender).transfer(amount);
}
Enter fullscreen mode Exit fullscreen mode

Every line here costs gas — SLOAD to read balances[msg.sender], SSTORE to write it back, CALL to send funds. The sender pre-pays an upper bound before the transaction even starts, and unused gas gets refunded. This is a fundamentally different safety model than a verifier: instead of proving a program can't misbehave, you make misbehaving economically self-limiting. An infinite loop doesn't hang the network — it just burns through the caller's gas and reverts, at their expense.


4. Account-Based State and Why Everything Runs in Order

Ethereum's state is a giant key-value mapping — accounts to balances, contract storage slots to values — represented as a Merkle Patricia Trie so any node can cryptographically prove the current state root. Crucially, a transaction doesn't declare up front which storage slots it's going to touch. It just runs, and reads/writes whatever it wants as it goes, including calling into other contracts that touch state you couldn't have predicted ahead of time.

That's the detail that quietly determines Ethereum's entire concurrency story. If you don't know in advance what a transaction will touch, you can't safely run two transactions at once without risking a race on shared state. So the EVM, as a base protocol, executes transactions strictly sequentially, one at a time, in block order. Parallel execution research exists (both inside and outside core Ethereum client teams), but it's an optimization bolted on after the fact, not a property the VM was built around.

This is precisely the constraint Solana's Sealevel runtime sidesteps — by requiring transactions to declare their account access lists upfront, Solana's runtime can prove non-overlap before execution and schedule accordingly. That's not a coincidence; it's a direct response to this exact bottleneck.


Why It's Built This Way

None of this is Ethereum "doing it wrong." A stack machine with no JIT, gas-metered instead of statically verified, sequential by default — every one of these was the correct trade-off for a network whose founding problem was trustless consensus among strangers, not raw throughput. Determinism and auditability were the product. Speed was never the spec.

That's exactly the gap Solana's architects looked at and decided to close from a completely different direction — not by making a better stack machine, but by throwing the stack-machine model out and grabbing a register-based, hardware-native VM that Linux had already spent two decades optimizing. What that actually involved — stripping the kernel verifier, zero-copy account access, AOT compilation to native code, and the account-declaration trick that unlocks parallelism — is the subject of the piece this one precedes.

If you haven't read it yet: From Packet Filter to High-Performance Execution Layer: How Solana Re-Engineered BPF.

Top comments (0)