/evm_mlir

An EVM written with MLIR

Primary LanguageRustMIT LicenseMIT

EVM MLIR

Telegram Chat rust license

An EVM-bytecode to machine-bytecode compiler using MLIR and LLVM.

Progress

Implemented opcodes (click to open)
  1. (0x00) STOP
  2. (0x01) ADD
  3. (0x02) MUL
  4. (0x03) SUB
  5. (0x04) DIV
  6. (0x05) SDIV
  7. (0x06) MOD
  8. (0x07) SMOD
  9. (0x08) ADDMOD
  10. (0x09) MULMOD
  11. (0x0A) EXP
  12. (0x0B) SIGNEXTEND
  13. (0x10) LT
  14. (0x11) GT
  15. (0x12) SLT
  16. (0x13) SGT
  17. (0x14) EQ
  18. (0x15) ISZERO
  19. (0x16) AND
  20. (0x17) OR
  21. (0x18) XOR
  22. (0x1A) BYTE
  23. (0x1B) SHL
  24. (0x1C) SHR
  25. (0x1D) SAR
  26. (0x50) POP
  27. (0x52) MSTORE
  28. (0x53) MSTORE8
  29. (0x56) JUMP
  30. (0x57) JUMPI
  31. (0x58) PC
  32. (0x5A) GAS
  33. (0x5B) JUMPDEST
  34. (0x5F) PUSH0
  35. (0x60) PUSH1
  36. (0x61) PUSH2
  37. (0x62) PUSH3
  38. (0x63) PUSH4
  39. (0x64) PUSH5
  40. (0x65) PUSH6
  41. (0x66) PUSH7
  42. (0x67) PUSH8
  43. (0x68) PUSH9
  44. (0x69) PUSH10
  45. (0x6A) PUSH11
  46. (0x6B) PUSH12
  47. (0x6C) PUSH13
  48. (0x6D) PUSH14
  49. (0x6E) PUSH15
  50. (0x6F) PUSH16
  51. (0x70) PUSH17
  52. (0x71) PUSH18
  53. (0x72) PUSH19
  54. (0x73) PUSH20
  55. (0x74) PUSH21
  56. (0x75) PUSH22
  57. (0x76) PUSH23
  58. (0x77) PUSH24
  59. (0x78) PUSH25
  60. (0x79) PUSH26
  61. (0x7A) PUSH27
  62. (0x7B) PUSH28
  63. (0x7C) PUSH29
  64. (0x7D) PUSH30
  65. (0x7E) PUSH31
  66. (0x7F) PUSH32
  67. (0x80) DUP1
  68. (0x81) DUP2
  69. (0x82) DUP3
  70. (0x83) DUP4
  71. (0x84) DUP5
  72. (0x85) DUP6
  73. (0x86) DUP7
  74. (0x87) DUP8
  75. (0x88) DUP9
  76. (0x89) DUP10
  77. (0x8A) DUP11
  78. (0x8B) DUP12
  79. (0x8C) DUP13
  80. (0x8D) DUP14
  81. (0x8E) DUP15
  82. (0x8F) DUP16
  83. (0x90) SWAP1
  84. (0x91) SWAP2
  85. (0x92) SWAP3
  86. (0x93) SWAP4
  87. (0x94) SWAP5
  88. (0x95) SWAP6
  89. (0x96) SWAP7
  90. (0x97) SWAP8
  91. (0x98) SWAP9
  92. (0x99) SWAP10
  93. (0x9A) SWAP11
  94. (0x9B) SWAP12
  95. (0x9C) SWAP13
  96. (0x9D) SWAP14
  97. (0x9E) SWAP15
  98. (0x9F) SWAP16
Not yet implemented opcodes (click to open)
  1. (0x19) NOT
  2. (0x20) KECCAK256
  3. (0x30) ADDRESS
  4. (0x31) BALANCE
  5. (0x32) ORIGIN
  6. (0x33) CALLER
  7. (0x34) CALLVALUE
  8. (0x35) CALLDATALOAD
  9. (0x36) CALLDATASIZE
  10. (0x37) CALLDATACOPY
  11. (0x38) CODESIZE
  12. (0x39) CODECOPY
  13. (0x3A) GASPRICE
  14. (0x3B) EXTCODESIZE
  15. (0x3C) EXTCODECOPY
  16. (0x3D) RETURNDATASIZE
  17. (0x3E) RETURNDATACOPY
  18. (0x3F) EXTCODEHASH
  19. (0x40) BLOCKHASH
  20. (0x41) COINBASE
  21. (0x42) TIMESTAMP
  22. (0x43) NUMBER
  23. (0x44) DIFFICULTY
  24. (0x45) GASLIMIT
  25. (0x46) CHAINID
  26. (0x47) SELFBALANCE
  27. (0x48) BASEFEE
  28. (0x49) BLOBHASH
  29. (0x4A) BLOBBASEFEE
  30. (0x51) MLOAD
  31. (0x54) SLOAD
  32. (0x55) SSTORE
  33. (0x59) MSIZE
  34. (0x5C) TLOAD
  35. (0x5D) TSTORE
  36. (0x5E) MCOPY
  37. (0xA0) LOG0
  38. (0xA1) LOG1
  39. (0xA2) LOG2
  40. (0xA3) LOG3
  41. (0xA4) LOG4
  42. (0xF0) CREATE
  43. (0xF1) CALL
  44. (0xF2) CALLCODE
  45. (0xF3) RETURN
  46. (0xF4) DELEGATECALL
  47. (0xF5) CREATE2
  48. (0xFA) STATICCALL
  49. (0xFD) REVERT
  50. (0xFE) INVALID
  51. (0xFF) SELFDESTRUCT

Getting Started

Dependencies

  • Linux or macOS (aarch64 included) only for now
  • LLVM 18 with MLIR: On debian you can use apt.llvm.org, on macOS you can use brew
  • Rust
  • Git

Setup

This step applies to all operating systems.

Run the following make target to install the dependencies (both Linux and macOS):

make deps

Linux

Since Linux distributions change widely, you need to install LLVM 18 via your package manager, compile it or check if the current release has a Linux binary.

If you are on Debian/Ubuntu, check out the repository https://apt.llvm.org/ Then you can install with:

sudo apt-get install llvm-18 llvm-18-dev llvm-18-runtime clang-18 clang-tools-18 lld-18 libpolly-18-dev libmlir-18-dev mlir-18-tools

If you decide to build from source, here are some indications:

Install LLVM from source instructions
# Go to https://github.com/llvm/llvm-project/releases
# Download the latest LLVM 18 release:
# The blob to download is called llvm-project-18.x.x.src.tar.xz

# For example
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.4/llvm-project-18.1.4.src.tar.xz
tar xf llvm-project-18.1.4.src.tar.xz

cd llvm-project-18.1.4.src.tar
mkdir build
cd build

# The following cmake command configures the build to be installed to /opt/llvm-18
cmake -G Ninja ../llvm \
   -DLLVM_ENABLE_PROJECTS="mlir;clang;clang-tools-extra;lld;polly" \
   -DLLVM_BUILD_EXAMPLES=OFF \
   -DLLVM_TARGETS_TO_BUILD="Native" \
   -DCMAKE_INSTALL_PREFIX=/opt/llvm-18 \
   -DCMAKE_BUILD_TYPE=RelWithDebInfo \
   -DLLVM_PARALLEL_LINK_JOBS=4 \
   -DLLVM_ENABLE_BINDINGS=OFF \
   -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DLLVM_ENABLE_LLD=ON \
   -DLLVM_ENABLE_ASSERTIONS=OFF

ninja install

Setup a environment variable called MLIR_SYS_180_PREFIX, LLVM_SYS_180_PREFIX and TABLEGEN_180_PREFIX pointing to the llvm directory:

# For Debian/Ubuntu using the repository, the path will be /usr/lib/llvm-18
export MLIR_SYS_180_PREFIX=/usr/lib/llvm-18
export LLVM_SYS_180_PREFIX=/usr/lib/llvm-18
export TABLEGEN_180_PREFIX=/usr/lib/llvm-18

Run the deps target to install the other dependencies.

make deps

MacOS

The makefile deps target (which you should have ran before) installs LLVM 18 with brew for you, afterwards you need to execute the env-macos.sh script to setup the environment.

source scripts/env-macos.sh

Running

To run the compiler, call cargo run while passing it a file with the EVM bytecode to compile. There are some example files under programs/, for example:

cargo run programs/push32.bytecode

Debugging the compiler

Compile a program

To generate the necessary artifacts, you need to run cargo run <filepath>, with <filepath> being the path to a file containing the EVM bytecode to compile.

Writing EVM bytecode directly can be a bit difficult, so you can edit src/main.rs, modifying the program variable with the structure of your EVM program. After that you just run cargo run.

An example edit would look like this:

fn main() {
    let program = vec![
            Operation::Push32([0; 32]),
            Operation::Push32([42; 32]),
            Operation::Add,
        ];
    let output_file = "some_other_filename";
    compile_binary(program, output_file).unwrap();
}

Inspecting the artifacts

The most useful ones to inspect are the MLIR-IR (<name>.mlir) and Assembly (<name>.asm) files. The first one has a one-to-one mapping with the operations added in the compiler, while the second one contains the instructions that are executed by your machine.

The other generated artifacts are:

  • Semi-optimized MLIR-IR (<name>.after-pass.mlir)
  • LLVM-IR (<name>.ll)
  • Object file (<name>.o)
  • Executable (<name>)

Running with a debugger

Once we have the executable, we can run it with a debugger (here we use lldb, but you can use others). To run with lldb, use lldb <name>.

To run until we reach our main function, we can use:

br set -n main
run

Running a single step

thread step-inst

Reading registers

All registers: register read

The x0 register: register read x0

Reading memory

To inspect the memory at <address>: memory read <address>

To inspect the memory at the address given by the register x0: memory read $x0

Reading the EVM stack

To pretty-print the EVM stack at address X: memory read -s32 -fu -c4 X

Reference:

  • The -s32 flag groups the bytes in 32-byte chunks.
  • The -fu flag interprets the chunks as unsigned integers.
  • The -c4 flag includes 4 chunks: the one at the given address plus the three next chunks.

Restarting the program

To restart the program, just use run again.