Skip to content

Commit cf0d429

Browse files
authored
Zkvm build support (#7)
Signed-off-by: garyschulte <garyschulte@gmail.com>
1 parent 6c51b96 commit cf0d429

38 files changed

+964
-927
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -71,31 +71,6 @@ jobs:
7171
- name: Run benchmarks
7272
run: ./zig-out/bin/zevm-bench
7373

74-
unit-test:
75-
name: Unit Tests
76-
runs-on: ${{ matrix.os }}
77-
strategy:
78-
matrix:
79-
os: [ubuntu-latest, macos-latest]
80-
zig-version: ['0.15.1']
81-
82-
steps:
83-
- name: Checkout code
84-
uses: actions/checkout@v4
85-
86-
- name: Setup Zig
87-
uses: goto-bus-stop/setup-zig@v2
88-
with:
89-
version: ${{ matrix.zig-version }}
90-
91-
- name: Install dependencies
92-
run: make install-deps
93-
94-
- name: Run unit tests
95-
env:
96-
DYLD_LIBRARY_PATH: /opt/homebrew/lib:/usr/local/lib
97-
run: zig build test
98-
9974
lint:
10075
name: Lint
10176
runs-on: ubuntu-latest

build.zig

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,64 @@ pub fn build(b: *std.Build) void {
2929
const mcl_include_path = b.option([]const u8, "mcl-include", "Path to mcl include directory") orelse default_include_path;
3030

3131
// Add compile flags for optional libraries
32+
const enable_secp256k1 = b.option(bool, "secp256k1", "Enable secp256k1 library for ECRECOVER") orelse true;
33+
const enable_openssl = b.option(bool, "openssl", "Enable OpenSSL library for P256Verify") orelse true;
34+
3235
const lib_options = b.addOptions();
3336
lib_options.addOption(bool, "enable_blst", enable_blst);
3437
lib_options.addOption(bool, "enable_mcl", enable_mcl);
38+
lib_options.addOption(bool, "enable_secp256k1", enable_secp256k1);
39+
lib_options.addOption(bool, "enable_openssl", enable_openssl);
3540
const lib_options_module = lib_options.createModule();
3641

42+
// Default allocator module — returns std.heap.c_allocator.
43+
// Downstream builds override this by calling:
44+
// module.addImport("zevm_allocator", their_module)
45+
// after obtaining the module from this dependency.
46+
const zevm_allocator_module = b.addModule("zevm_allocator", .{
47+
.root_source_file = .{ .src_path = .{ .owner = b, .sub_path = "src/allocator.zig" } },
48+
.target = target,
49+
.optimize = optimize,
50+
});
51+
52+
// Core precompile types (no external deps) — shared between precompile
53+
// module and any precompile_overrides module to avoid circular imports.
54+
const precompile_types_module = b.addModule("precompile_types", .{
55+
.root_source_file = .{ .src_path = .{ .owner = b, .sub_path = "src/precompile/types.zig" } },
56+
.target = target,
57+
.optimize = optimize,
58+
});
59+
60+
// Primitives module — defined early so native_impls_module can reference it.
61+
const primitives_module = b.addModule("primitives", .{
62+
.root_source_file = .{ .src_path = .{ .owner = b, .sub_path = "src/primitives/main.zig" } },
63+
.target = target,
64+
.optimize = optimize,
65+
});
66+
67+
// Native precompile implementations — all host-OS (secp256k1, mcl, blst, openssl).
68+
// Downstream freestanding builds replace this by injecting their own module:
69+
// precompile_module.addImport("precompile_implementations", your_module)
70+
const native_impls_module = b.addModule("precompile_implementations", .{
71+
.root_source_file = .{ .src_path = .{ .owner = b, .sub_path = "src/precompile/native_impls.zig" } },
72+
.target = target,
73+
.optimize = optimize,
74+
});
75+
native_impls_module.addImport("precompile_types", precompile_types_module);
76+
native_impls_module.addImport("build_options", lib_options_module);
77+
native_impls_module.addImport("zevm_allocator", zevm_allocator_module);
78+
native_impls_module.addImport("primitives", primitives_module);
79+
80+
// Exposes raw C-library wrapper namespaces for external consumers who need
81+
// direct access to secp256k1/openssl/blst/mcl APIs.
82+
const precompile_backends_native_module = b.addModule("precompile.backends.native", .{
83+
.root_source_file = .{ .src_path = .{ .owner = b, .sub_path = "src/precompile/backends/native.zig" } },
84+
.target = target,
85+
.optimize = optimize,
86+
});
87+
precompile_backends_native_module.addImport("build_options", lib_options_module);
88+
precompile_backends_native_module.addImport("precompile_types", precompile_types_module);
89+
3790
// Helper function to remove duplicate rpaths on macOS
3891
//
3992
// ROOT CAUSE:
@@ -284,12 +337,6 @@ pub fn build(b: *std.Build) void {
284337
b.installArtifact(lib);
285338

286339
// Create modules for each component
287-
const primitives_module = b.addModule("primitives", .{
288-
.root_source_file = .{ .src_path = .{ .owner = b, .sub_path = "src/primitives/main.zig" } },
289-
.target = target,
290-
.optimize = optimize,
291-
});
292-
293340
const bytecode_module = b.addModule("bytecode", .{
294341
.root_source_file = .{ .src_path = .{ .owner = b, .sub_path = "src/bytecode/main.zig" } },
295342
.target = target,
@@ -340,30 +387,37 @@ pub fn build(b: *std.Build) void {
340387

341388
// Add module dependencies
342389
bytecode_module.addImport("primitives", primitives_module);
390+
bytecode_module.addImport("zevm_allocator", zevm_allocator_module);
343391
state_module.addImport("primitives", primitives_module);
344392
state_module.addImport("bytecode", bytecode_module);
393+
state_module.addImport("zevm_allocator", zevm_allocator_module);
345394
database_module.addImport("primitives", primitives_module);
346395
database_module.addImport("state", state_module);
347396
database_module.addImport("bytecode", bytecode_module);
348397
context_module.addImport("primitives", primitives_module);
349398
context_module.addImport("bytecode", bytecode_module);
350399
context_module.addImport("state", state_module);
351400
context_module.addImport("database", database_module);
401+
context_module.addImport("zevm_allocator", zevm_allocator_module);
352402
interpreter_module.addImport("primitives", primitives_module);
353403
interpreter_module.addImport("bytecode", bytecode_module);
354404
interpreter_module.addImport("context", context_module);
355405
interpreter_module.addImport("database", database_module);
356406
interpreter_module.addImport("state", state_module);
357407
interpreter_module.addImport("precompile", precompile_module);
358-
precompile_module.addImport("build_options", lib_options_module);
408+
interpreter_module.addImport("zevm_allocator", zevm_allocator_module);
359409
precompile_module.addImport("primitives", primitives_module);
410+
precompile_module.addImport("zevm_allocator", zevm_allocator_module);
411+
precompile_module.addImport("precompile_types", precompile_types_module);
412+
precompile_module.addImport("precompile_implementations", native_impls_module);
360413
handler_module.addImport("primitives", primitives_module);
361414
handler_module.addImport("bytecode", bytecode_module);
362415
handler_module.addImport("state", state_module);
363416
handler_module.addImport("database", database_module);
364417
handler_module.addImport("interpreter", interpreter_module);
365418
handler_module.addImport("context", context_module);
366419
handler_module.addImport("precompile", precompile_module);
420+
handler_module.addImport("zevm_allocator", zevm_allocator_module);
367421
inspector_module.addImport("primitives", primitives_module);
368422
inspector_module.addImport("interpreter", interpreter_module);
369423

@@ -454,6 +508,7 @@ pub fn build(b: *std.Build) void {
454508
interpreter_tests.root_module.addImport("database", database_module);
455509
interpreter_tests.root_module.addImport("state", state_module);
456510
interpreter_tests.root_module.addImport("precompile", precompile_module);
511+
interpreter_tests.root_module.addImport("zevm_allocator", zevm_allocator_module);
457512
addCryptoLibraries(b, interpreter_tests, enable_blst, enable_mcl, blst_include_path, mcl_include_path, is_windows, target_info.os.tag == .macos);
458513
const run_interpreter_tests = b.addRunArtifact(interpreter_tests);
459514
test_step.dependOn(&run_interpreter_tests.step);
@@ -473,6 +528,7 @@ pub fn build(b: *std.Build) void {
473528
handler_tests.root_module.addImport("state", state_module);
474529
handler_tests.root_module.addImport("interpreter", interpreter_module);
475530
handler_tests.root_module.addImport("precompile", precompile_module);
531+
handler_tests.root_module.addImport("zevm_allocator", zevm_allocator_module);
476532
addCryptoLibraries(b, handler_tests, enable_blst, enable_mcl, blst_include_path, mcl_include_path, is_windows, target_info.os.tag == .macos);
477533
const run_handler_tests = b.addRunArtifact(handler_tests);
478534
test_step.dependOn(&run_handler_tests.step);
@@ -713,6 +769,7 @@ pub fn build(b: *std.Build) void {
713769
runner_mod.addImport("state", state_module);
714770
runner_mod.addImport("precompile", precompile_module);
715771
runner_mod.addImport("handler", handler_module);
772+
runner_mod.addImport("zevm_allocator", zevm_allocator_module);
716773

717774
addCryptoLibraries(b, runner_exe, enable_blst, enable_mcl, blst_include_path, mcl_include_path, is_windows, target_info.os.tag == .macos);
718775
b.installArtifact(runner_exe);

src/allocator.zig

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const std = @import("std");
2+
3+
/// Returns the allocator for zevm internal allocations.
4+
///
5+
/// Defaults to std.heap.c_allocator for native builds.
6+
/// Override at build time by injecting a different "zevm_allocator" module
7+
/// via addImport("zevm_allocator", my_module) in your build.zig.
8+
///
9+
/// The replacement module must export:
10+
/// pub fn get() std.mem.Allocator { ... }
11+
pub fn get() std.mem.Allocator {
12+
return std.heap.c_allocator;
13+
}

src/bytecode/main.zig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const std = @import("std");
22
const primitives = @import("primitives");
3+
const alloc_mod = @import("zevm_allocator");
34

45
/// EVM opcode definitions and utilities. It contains opcode information and utilities to work with opcodes.
56
/// An EVM opcode
@@ -644,7 +645,7 @@ pub const LegacyAnalyzedBytecode = struct {
644645
// - default() / empty input: original_len == 0, data is a static &[_]u8{0}
645646
// - alloc-failure fallback: jump_table.data.len == 0, data is static &[_]u8{}
646647
if (self.original_len > 0 and self.jump_table.data.len > 0) {
647-
std.heap.c_allocator.free(self.jump_table.data);
648+
alloc_mod.get().free(self.jump_table.data);
648649
}
649650
}
650651
};
@@ -745,7 +746,7 @@ fn analyzeLegacy(bytecode: []const u8) LegacyAnalyzedBytecode {
745746

746747
// Allocate bit vector on heap (one bit per bytecode position) to avoid dangling pointer
747748
const bit_vec_len = (bytecode.len + 7) / 8;
748-
const bit_vec = std.heap.c_allocator.alloc(u8, bit_vec_len) catch {
749+
const bit_vec = alloc_mod.get().alloc(u8, bit_vec_len) catch {
749750
// Allocation failed: return bytecode with empty jump table
750751
return LegacyAnalyzedBytecode{
751752
.bytecode = bytecode,

src/context/evm.zig

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const std = @import("std");
22
const primitives = @import("primitives");
33
const Context = @import("context.zig").Context;
4+
const alloc_mod = @import("zevm_allocator");
45

56
/// Main EVM structure that contains all data needed for execution.
67
pub const Evm = struct {
@@ -193,15 +194,15 @@ pub const Frame = struct {
193194
_ = code;
194195
return .{
195196
.pc = 0,
196-
.stack = std.ArrayList(primitives.U256).init(std.heap.page_allocator),
197-
.memory = std.ArrayList(u8).init(std.heap.page_allocator),
197+
.stack = std.ArrayList(primitives.U256).init(alloc_mod.get()),
198+
.memory = std.ArrayList(u8).init(alloc_mod.get()),
198199
.gas = gas,
199-
.return_data = std.ArrayList(u8).init(std.heap.page_allocator),
200+
.return_data = std.ArrayList(u8).init(alloc_mod.get()),
200201
.caller = caller,
201202
.target = target,
202203
.value = value,
203-
.input = std.ArrayList(u8).init(std.heap.page_allocator),
204-
.code = std.ArrayList(u8).init(std.heap.page_allocator),
204+
.input = std.ArrayList(u8).init(alloc_mod.get()),
205+
.code = std.ArrayList(u8).init(alloc_mod.get()),
205206
.is_static = is_static,
206207
.depth = depth,
207208
};

0 commit comments

Comments
 (0)