Skip to content

Commit 20cd6e8

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/disk-spill-v2-parallel-r2
2 parents dbb012a + 62af4c2 commit 20cd6e8

16 files changed

Lines changed: 540 additions & 44 deletions

File tree

.github/scripts/publish_bench_vs.sh

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,20 @@ if [ -n "$LAMBDA_PROJECTED_H" ] || [ -n "$SP1_PROJECTED_H" ]; then
7979
PROJ_SECTION=',{"type":"divider"},{"type":"header","text":{"type":"plain_text","text":"Linear Projection"}},{"type":"section","text":{"type":"mrkdwn","text":"'"$PROJ_MRKDWN"'"}}'
8080
fi
8181

82+
ETHREX_METRICS_FILE="bench_vs_artifacts/ethrex_metrics.txt"
83+
ETHREX_SECTION=""
84+
if [ -f "$ETHREX_METRICS_FILE" ]; then
85+
ETHREX_TIME=$(grep '^ethrex_empty_block_time_s=' "$ETHREX_METRICS_FILE" | cut -d= -f2-)
86+
ETHREX_CYCLES=$(grep '^ethrex_empty_block_cycles=' "$ETHREX_METRICS_FILE" | cut -d= -f2-)
87+
if [ -n "$ETHREX_TIME" ]; then
88+
ETHREX_MRKDWN="*Empty block:* ${ETHREX_TIME}s"
89+
if [ -n "$ETHREX_CYCLES" ] && [ "$ETHREX_CYCLES" != "n/a" ]; then
90+
ETHREX_MRKDWN="${ETHREX_MRKDWN} (${ETHREX_CYCLES} cycles)"
91+
fi
92+
ETHREX_SECTION=',{"type":"divider"},{"type":"header","text":{"type":"plain_text","text":"Lambda VM - Ethrex Empty"}},{"type":"section","text":{"type":"mrkdwn","text":"'"$ETHREX_MRKDWN"'"}}'
93+
fi
94+
fi
95+
8296
curl -X POST "$WEBHOOK_URL" \
8397
-H 'Content-Type: application/json; charset=utf-8' \
84-
--data '{"blocks":[{"type":"header","text":{"type":"plain_text","text":"Lambda VM vs SP1 v6 - Nightly Benchmark"}},{"type":"context","elements":[{"type":"mrkdwn","text":"*Program:* Fibonacci · *Device:* CPU"}]},{"type":"divider"},{"type":"section","text":{"type":"mrkdwn","text":"'"$RESULTS_MRKDWN"'"}}'"$PROJ_SECTION"']}'
98+
--data '{"blocks":[{"type":"header","text":{"type":"plain_text","text":"Lambda VM vs SP1 v6 - Nightly Benchmark"}},{"type":"context","elements":[{"type":"mrkdwn","text":"*Program:* Fibonacci · *Device:* CPU"}]},{"type":"divider"},{"type":"section","text":{"type":"mrkdwn","text":"'"$RESULTS_MRKDWN"'"}}'"$PROJ_SECTION$ETHREX_SECTION"']}'

.github/workflows/bench-vs-nightly.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,21 @@ jobs:
4747
--report-dir bench_vs_artifacts \
4848
--no-color
4949
50+
- name: Restore cached ethrex.elf (TEMPORARY — until /opt/lambda-vm-sysroot is provisioned on the bench runner)
51+
continue-on-error: true
52+
run: |
53+
mkdir -p executor/program_artifacts/rust
54+
cp /home/app/cached_artifacts/ethrex.elf executor/program_artifacts/rust/ethrex.elf
55+
ls -la executor/program_artifacts/rust/ethrex.elf
56+
sha256sum executor/program_artifacts/rust/ethrex.elf
57+
58+
- name: Run ethrex empty block benchmark
59+
continue-on-error: true
60+
run: |
61+
bash ./bench_vs/run_ethrex.sh \
62+
--report-dir bench_vs_artifacts \
63+
--no-color
64+
5065
- name: Upload nightly benchmark artifact
5166
uses: actions/upload-artifact@v4
5267
with:

Makefile

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,15 @@ BENCH_ARTIFACTS := $(addprefix $(BENCH_ARTIFACTS_DIR)/, $(addsuffix .elf, $(BENC
4646
ETHREX_FILE := executor/tests/ethrex_hoodi.bin
4747
ETHREX_URL := https://lambda.alignedlayer.com/ethrex_hoodi.bin
4848

49-
SYSROOT_DIR := /opt/lambda-vm-sysroot
49+
# Override with: make ... SYSROOT_DIR=$HOME/.lambda-vm-sysroot
50+
# to install the sysroot in a user-writable location and avoid sudo.
51+
SYSROOT_DIR ?= /opt/lambda-vm-sysroot
5052
SYSROOT_TARBALL := /tmp/lambda-vm-sysroot-rv64im.tar.gz
5153
SYSROOT_URL := https://lambda.alignedlayer.com/lambda-vm-sysroot-rv64im.tar.gz
54+
# CFLAGS for ckzg / ethrex guest programs: overrides the hardcoded `/opt/lambda-vm-sysroot`
55+
# in their .cargo/config.toml so cargo picks up our $(SYSROOT_DIR) instead.
56+
# $(abspath ...) because the build rule cd's into the program dir before invoking cargo.
57+
SYSROOT_CFLAGS := --target=riscv64 -march=rv64im -mabi=lp64 --sysroot=$(abspath $(SYSROOT_DIR))
5258

5359
# Custom RV64IM target spec location
5460
RV64_TARGET_SPEC=$(CURDIR)/executor/programs/riscv64im-lambda-vm-elf.json
@@ -64,15 +70,26 @@ prepare-test-data:
6470
fi
6571

6672
prepare-sysroot:
67-
@if [ ! -d "$(SYSROOT_DIR)" ]; then \
73+
@if [ -d "$(SYSROOT_DIR)/include" ] && [ -d "$(SYSROOT_DIR)/lib" ]; then \
74+
echo "Sysroot already exists at $(SYSROOT_DIR)"; \
75+
else \
6876
echo "Downloading lambda-vm-sysroot-rv64im.tar.gz..."; \
6977
curl -L "$(SYSROOT_URL)" -o "$(SYSROOT_TARBALL)"; \
7078
echo "Extracting sysroot to $(SYSROOT_DIR)..."; \
71-
sudo mkdir -p /opt && sudo tar -xzf "$(SYSROOT_TARBALL)" -C /opt; \
79+
if mkdir -p "$(SYSROOT_DIR)" 2>/dev/null && [ -w "$(SYSROOT_DIR)" ]; then \
80+
tar -xzf "$(SYSROOT_TARBALL)" -C "$(SYSROOT_DIR)" --strip-components=1 \
81+
|| { rm -rf "$(SYSROOT_DIR)" "$(SYSROOT_TARBALL)"; exit 1; }; \
82+
else \
83+
echo "$(SYSROOT_DIR) is not writable; using sudo."; \
84+
echo "Tip: re-run with SYSROOT_DIR=\$$HOME/.lambda-vm-sysroot to avoid sudo."; \
85+
sudo mkdir -p "$(SYSROOT_DIR)" \
86+
&& sudo tar -xzf "$(SYSROOT_TARBALL)" -C "$(SYSROOT_DIR)" --strip-components=1 \
87+
|| { sudo rm -rf "$(SYSROOT_DIR)"; rm -f "$(SYSROOT_TARBALL)"; exit 1; }; \
88+
fi; \
7289
rm "$(SYSROOT_TARBALL)"; \
73-
else \
74-
echo "Sysroot already exists at $(SYSROOT_DIR)"; \
7590
fi
91+
# Note: the tarball rm above only runs on success — each error handler
92+
# cleans up the tarball itself before `exit 1`.
7693

7794
compile-programs-asm:
7895
@mkdir -p $(ASM_ARTIFACTS_DIR)
@@ -83,7 +100,7 @@ compile-programs-asm:
83100

84101
compile-programs-rust: prepare-sysroot $(RUST_ARTIFACTS)
85102

86-
compile-bench: $(BENCH_ARTIFACTS)
103+
compile-bench: prepare-sysroot $(BENCH_ARTIFACTS)
87104

88105
compile-programs: compile-programs-asm compile-programs-rust compile-bench
89106

@@ -93,6 +110,7 @@ $(RUST_ARTIFACTS_DIR)/%.elf: $(RUST_PROGRAMS_DIR)/%/Cargo.toml
93110
@mkdir -p $(RUST_ARTIFACTS_DIR)
94111
cd $(RUST_PROGRAMS_DIR)/$* && \
95112
CARGO_TARGET_DIR=$(abspath $(SHARED_TARGET_DIR)) \
113+
CFLAGS_riscv64im_lambda_vm_elf="$(SYSROOT_CFLAGS)" \
96114
rustup run nightly-2026-02-01 cargo build --release \
97115
--target $(RV64_TARGET_SPEC) \
98116
-Z build-std=core,alloc,std,compiler_builtins,panic_abort \
@@ -105,6 +123,7 @@ $(BENCH_ARTIFACTS_DIR)/%.elf: $(BENCH_PROGRAMS_DIR)/%/Cargo.toml
105123
@mkdir -p $(BENCH_ARTIFACTS_DIR)
106124
cd $(BENCH_PROGRAMS_DIR)/$* && \
107125
CARGO_TARGET_DIR=$(abspath $(SHARED_TARGET_DIR)) \
126+
CFLAGS_riscv64im_lambda_vm_elf="$(SYSROOT_CFLAGS)" \
108127
rustup run nightly-2026-02-01 cargo build --release \
109128
--target $(RV64_TARGET_SPEC) \
110129
-Z build-std=core,alloc,std,compiler_builtins,panic_abort \

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ Some of the tests require linking with C libraries.
4141

4242
##### Download pre-installed C libraries
4343

44+
The easiest way is to let `make` do it:
45+
46+
```sh
47+
make prepare-sysroot # installs to /opt (uses sudo)
48+
SYSROOT_DIR=$HOME/.lambda-vm-sysroot make prepare-sysroot # user-writable, no sudo
49+
```
50+
51+
Or do it manually:
52+
4453
```sh
4554
wget https://lambda.alignedlayer.com/lambda-vm-sysroot-rv64im.tar.gz
4655
sudo mkdir -p /opt && sudo tar -xzf lambda-vm-sysroot-rv64im.tar.gz -C /opt

bench_vs/run_ethrex.sh

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
#!/usr/bin/env bash
2+
# Benchmark: Lambda VM proving an empty ethrex block.
3+
#
4+
# Usage: ./bench_vs/run_ethrex.sh [--report-dir DIR] [--no-color]
5+
#
6+
# Prerequisites:
7+
# - Lambda VM CLI build dependencies available
8+
# - Sysroot present at /opt/lambda-vm-sysroot (run `make prepare-sysroot` first)
9+
# - Rust stable + nightly-2026-02-01 installed
10+
11+
set -euo pipefail
12+
13+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14+
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
15+
TMP_DIR="/tmp/bench_ethrex"
16+
REPORT_DIR=""
17+
NO_COLOR=false
18+
19+
RED='\033[0;31m'
20+
GREEN='\033[0;32m'
21+
YELLOW='\033[1;33m'
22+
BOLD='\033[1m'
23+
NC='\033[0m'
24+
25+
# --- Parse args -------------------------------------------------------------
26+
while [[ $# -gt 0 ]]; do
27+
case $1 in
28+
--report-dir)
29+
if [[ $# -lt 2 ]]; then echo "--report-dir requires an argument"; exit 1; fi
30+
REPORT_DIR=$2
31+
shift 2
32+
;;
33+
--no-color)
34+
NO_COLOR=true
35+
shift
36+
;;
37+
-h|--help)
38+
echo "Usage: $0 [--report-dir DIR] [--no-color]"
39+
exit 0
40+
;;
41+
*)
42+
echo "Unknown option: $1"
43+
exit 1
44+
;;
45+
esac
46+
done
47+
48+
if $NO_COLOR; then
49+
RED=''
50+
GREEN=''
51+
YELLOW=''
52+
BOLD=''
53+
NC=''
54+
fi
55+
56+
mkdir -p "$TMP_DIR"
57+
rm -rf "${TMP_DIR:?}"/*
58+
59+
if [ -n "$REPORT_DIR" ]; then
60+
mkdir -p "$REPORT_DIR/raw"
61+
fi
62+
63+
extract_proving_time() {
64+
sed -nE '/Proving time: [0-9.]+s/ {
65+
s/.*Proving time: ([0-9.]+)s.*/\1/
66+
p
67+
q
68+
}'
69+
}
70+
71+
extract_cycles() {
72+
sed -nE '/Cycles: [0-9]+/ {
73+
s/.*Cycles: ([0-9]+).*/\1/
74+
p
75+
q
76+
}'
77+
}
78+
79+
# --- Pre-build --------------------------------------------------------------
80+
81+
CLI="$ROOT_DIR/target/release/cli"
82+
ETHREX_ELF="$ROOT_DIR/executor/program_artifacts/rust/ethrex.elf"
83+
ETHREX_INPUT="$ROOT_DIR/executor/tests/ethrex_empty_block.bin"
84+
echo -e "${BOLD}=== Ethrex Empty Block Benchmark: Lambda VM ===${NC}"
85+
echo ""
86+
87+
echo -e "${GREEN}[Lambda VM] Building CLI...${NC}"
88+
cargo build --release -p cli --manifest-path "$ROOT_DIR/Cargo.toml" 2>&1 | tail -5
89+
90+
if [ -f "$ETHREX_ELF" ]; then
91+
echo -e "${YELLOW}[Lambda VM] Using pre-existing ethrex.elf at $ETHREX_ELF${NC}"
92+
else
93+
echo -e "${GREEN}[Lambda VM] Building ethrex guest ELF...${NC}"
94+
make -C "$ROOT_DIR" executor/program_artifacts/rust/ethrex.elf 2>&1 | tail -5
95+
fi
96+
97+
if [ ! -f "$ETHREX_ELF" ]; then
98+
echo -e "${RED}[Lambda VM] Build failed — ethrex.elf not found at $ETHREX_ELF${NC}"
99+
exit 1
100+
fi
101+
102+
if [ ! -f "$ETHREX_INPUT" ]; then
103+
echo -e "${RED}Input file not found: $ETHREX_INPUT${NC}"
104+
exit 1
105+
fi
106+
107+
# --- Run benchmark ---------------------------------------------------
108+
echo ""
109+
echo -e "${BOLD}--- Proving empty ethrex block ---${NC}"
110+
111+
proof_file="$TMP_DIR/ethrex_empty_block.proof"
112+
stderr_file="$TMP_DIR/ethrex_empty_block.stderr"
113+
114+
echo -e " ${GREEN}[Lambda VM] Proving...${NC}"
115+
if ! lambda_output=$("$CLI" prove "$ETHREX_ELF" \
116+
-o "$proof_file" \
117+
--private-input "$ETHREX_INPUT" \
118+
--time --cycles 2>"$stderr_file"); then
119+
echo -e " ${RED}[Lambda VM] FAILED:${NC}"
120+
cat "$stderr_file"
121+
exit 1
122+
fi
123+
rm -f "$proof_file"
124+
125+
lambda_time=$(printf "%s\n" "$lambda_output" | extract_proving_time)
126+
lambda_cycles=$(printf "%s\n" "$lambda_output" | extract_cycles)
127+
128+
if [ -z "$lambda_time" ]; then
129+
echo -e " ${RED}[Lambda VM] FAILED: could not parse proving time${NC}"
130+
printf "%s\n" "$lambda_output"
131+
exit 1
132+
fi
133+
if [ -z "$lambda_cycles" ]; then
134+
lambda_cycles="n/a"
135+
fi
136+
137+
if [ "$lambda_cycles" != "n/a" ]; then
138+
echo -e " Lambda VM: ${BOLD}${lambda_time}s${NC} (${lambda_cycles} cycles)"
139+
else
140+
echo -e " Lambda VM: ${BOLD}${lambda_time}s${NC}"
141+
fi
142+
143+
if [ -n "$REPORT_DIR" ]; then
144+
printf "%s\n" "$lambda_output" > "$REPORT_DIR/raw/ethrex_empty_block.stdout"
145+
cp "$stderr_file" "$REPORT_DIR/raw/ethrex_empty_block.stderr"
146+
fi
147+
148+
# --- Summary table ----------------------------------------------------------
149+
150+
echo ""
151+
echo -e "${BOLD}=== Summary ===${NC}"
152+
echo -e "Program: ethrex empty block"
153+
echo ""
154+
155+
printf " %-22s %14s %14s\n" "Program" "Lambda (s)" "Lambda cycles"
156+
printf " %-22s %14s %14s\n" "----------------------" "----------" "-------------"
157+
printf " %-22s %13ss %14s\n" "ethrex empty block" "$lambda_time" "$lambda_cycles"
158+
159+
echo ""
160+
echo -e "Timing window covers single-shot end-to-end proving; excludes verification."
161+
echo "Raw data in $TMP_DIR/"
162+
163+
# --- Machine-readable report ------------------------------------------------
164+
165+
if [ -n "$REPORT_DIR" ]; then
166+
{
167+
echo "program=ethrex_empty_block"
168+
echo "input_file=$ETHREX_INPUT"
169+
echo "timing_window=single_shot_end_to_end_prove_no_verify"
170+
echo "ethrex_empty_block_time_s=$lambda_time"
171+
echo "ethrex_empty_block_cycles=$lambda_cycles"
172+
} > "$REPORT_DIR/ethrex_metrics.txt"
173+
174+
{
175+
echo "# Ethrex Empty Block — Lambda VM"
176+
echo
177+
echo "Timing window: \`single-shot end-to-end prove\` (excludes verification)."
178+
echo
179+
echo "| Program | Lambda VM (s) | Lambda cycles |"
180+
echo "|---------|--------------:|--------------:|"
181+
printf "| ethrex empty block | %s | %s |\n" "$lambda_time" "$lambda_cycles"
182+
} > "$REPORT_DIR/ethrex_summary.md"
183+
fi
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[target.riscv64im-lambda-vm-elf]
2+
rustflags = [
3+
"--cfg", "getrandom_backend=\"custom\"",
4+
"-C", "passes=lower-atomic"
5+
]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[workspace]
2+
3+
[package]
4+
name = "ef_io_demo"
5+
version = "0.1.0"
6+
edition = "2024"
7+
8+
[dependencies]
9+
lambda-vm-syscalls = { path = "../../../../syscalls" }
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Demo guest exercising the EF zkVM IO interface (`read_input` / `write_output`).
2+
//
3+
// Reads the private input via the EF zero-copy `read_input` shim, then emits it
4+
// back as the public output in TWO `write_output` calls (split in halves) to
5+
// exercise the multi-call concatenation requirement of the EF spec.
6+
use lambda_vm_syscalls as syscalls;
7+
8+
pub fn main() {
9+
let mut buf_ptr: *const u8 = core::ptr::null();
10+
let mut buf_size: usize = 0;
11+
unsafe {
12+
syscalls::ef_io::read_input(&mut buf_ptr, &mut buf_size);
13+
}
14+
15+
if buf_size > 0 {
16+
let half = buf_size / 2;
17+
unsafe {
18+
syscalls::ef_io::write_output(buf_ptr, half);
19+
syscalls::ef_io::write_output(buf_ptr.add(half), buf_size - half);
20+
}
21+
}
22+
}

executor/src/vm/instruction/execution.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ impl Instruction {
304304
// It is not the correct implementation of ecall/ebreak
305305
let pointer = registers.read(10)?;
306306
let len = registers.read(11)?;
307-
let bytes = memory.load_bytes(pointer, len);
307+
let bytes = memory.load_bytes(pointer, len)?;
308308
let value =
309309
str::from_utf8(&bytes).map_err(|_| ExecutionError::IncorrectMessage)?;
310310
println!("PRINT VM: {}", value);
@@ -313,7 +313,7 @@ impl Instruction {
313313
// panic
314314
let pointer = registers.read(10)?;
315315
let len = registers.read(11)?;
316-
let bytes = memory.load_bytes(pointer, len);
316+
let bytes = memory.load_bytes(pointer, len)?;
317317
let value =
318318
str::from_utf8(&bytes).map_err(|_| ExecutionError::IncorrectMessage)?;
319319
return Err(ExecutionError::Panic(value.to_owned()));

0 commit comments

Comments
 (0)