Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
915f619
fix: preserve boolean flag defaults and explicit config overrides
7layermagik Sep 16, 2026
cb360c4
fix: own retained vote deque storage before pooled reuse
7layermagik Sep 16, 2026
be7a914
sbpf: reject overflow in contiguous virtual memory ranges
7layermagik Sep 16, 2026
584791f
sealevel: remove SHA-256 syscall descriptor and digest allocations
7layermagik Sep 16, 2026
f944a0e
test: measure captured SHA loop and document Zen 5 acceleration
7layermagik Sep 16, 2026
75db2e0
sbpf: cut per-instruction overhead in the interpreter (~2x on SPL Tok…
claude Sep 16, 2026
5b8f33f
sbpf: add interpreter benchmarks and a differential test corpus
claude Sep 16, 2026
3f0ec6c
sealevel: add native program micro-benchmarks (System transfer, Vote …
claude Sep 16, 2026
e8dcadc
test: update VASA stack registers for fixed-size interpreter state
7layermagik Sep 16, 2026
203e105
test: benchmark verified token arithmetic and CPI workloads with warm…
7layermagik Sep 16, 2026
1f4a69b
docs: record individual-program and matched Alpenglow replay measurem…
7layermagik Sep 16, 2026
2361bc9
sealevel: copy, compare and fill VM memory without temporaries or byt…
claude Sep 16, 2026
32e149e
lthash: vectorize MixIn/MixOut with AVX2 on amd64
claude Sep 16, 2026
bfddc29
test: preserve zero-length memory validation and repair VM fixtures
7layermagik Sep 16, 2026
4ad9805
test: cover unaligned and aliased AVX2 hash lanes
7layermagik Sep 16, 2026
1ca0fb3
sbpf: account for resolved call targets in program cache cost
7layermagik Sep 16, 2026
a6f3586
test: retain syscall differential coverage and execution reproduction…
7layermagik Sep 16, 2026
14c5404
test: trim execution benchmark experiments and documentation
7layermagik Sep 16, 2026
1992ab4
sbpf: restore v2 memory opcodes and differential coverage
7layermagik Sep 20, 2026
a520264
sbpf: guard pooled write tracking beyond bitmap capacity
7layermagik Sep 20, 2026
3830a18
sealevel: match Agave zero-length memory copy behavior
7layermagik Sep 20, 2026
a317eb6
sealevel: track sibling header writes in pooled VM memory
7layermagik Sep 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions cmd/mithril/node/config_bool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package node

import "github.com/spf13/pflag"

// resolveBoolOption preserves explicit false at either precedence level.
// Defaults use DefValue, not a flag value potentially left by a previous run.
func resolveBoolOption(flag *pflag.Flag, configured bool, configuredValue bool) bool {
if flag != nil && flag.Changed {
return flag.Value.String() == "true"
}
if configured {
return configuredValue
}
return flag != nil && flag.DefValue == "true"
}
38 changes: 38 additions & 0 deletions cmd/mithril/node/config_bool_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package node

import (
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"strings"
"testing"
)

func TestResolveBoolOptionPrecedence(t *testing.T) {
for _, tc := range []struct {
name, toml, cli string
defaultValue, want bool
}{
{"omitted true default", "", "", true, true},
{"omitted false default", "", "", false, false},
{"TOML false", "enabled=false", "", true, false},
{"TOML true", "enabled=true", "", false, true},
{"CLI false beats TOML true", "enabled=true", "false", true, false},
{"CLI true beats TOML false", "enabled=false", "true", false, true},
{"CLI false without TOML", "", "false", true, false},
} {
t.Run(tc.name, func(t *testing.T) {
v := viper.New()
v.SetConfigType("toml")
require.NoError(t, v.ReadConfig(strings.NewReader(tc.toml)))
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
flags.Bool("enabled", tc.defaultValue, "")
if tc.cli != "" {
require.NoError(t, flags.Set("enabled", tc.cli))
}
require.Equal(t, tc.want, resolveBoolOption(flags.Lookup("enabled"), v.IsSet("enabled"), v.GetBool("enabled")))
})
}
require.False(t, resolveBoolOption(nil, false, false))
require.True(t, resolveBoolOption(nil, true, true))
}
9 changes: 2 additions & 7 deletions cmd/mithril/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -742,14 +742,9 @@ func initConfigAndBindFlags(cmd *cobra.Command) error {
return 0
}

// Helper to get bool: CLI flag if explicitly set, otherwise TOML config
// Match numeric options: explicit CLI, configured value, then flag default.
getBool := func(cliKey, tomlKey string) bool {
if flagChanged(cliKey) {
if f := cmd.Flags().Lookup(cliKey); f != nil {
return f.Value.String() == "true"
}
}
return config.GetBool(tomlKey)
return resolveBoolOption(cmd.Flags().Lookup(cliKey), config.IsSet(tomlKey), config.GetBool(tomlKey))
}

// Helper to get string slice: CLI flag if explicitly set, otherwise TOML config
Expand Down
71 changes: 71 additions & 0 deletions docs/sbpf-interpreter-benchmarks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Execution performance validation

## Program workloads

The program harness measures instruction setup, serialization, execution and
publication with a warm program cache and fresh account data per invocation.
It checks return values, account updates, CPI and CU consumption. Loader-only
benchmarks separately measure VM execution and program loading.

Set `MITHRIL_PROGRAM_BENCH_DIR` to a directory containing `rotation_compute.so`
and `token2022.so` to enable those external fixtures. Without it, the in-repository
BPF/CPI fixtures still run. Pinned input SHA-256 values:

- Arithmetic ELF: `db7c55d6563c879e35dfe2b24edb0fe0515d5a5ae627fe00e3e247001c786441`.
Source: `ag-transaction-bench` at `7e5a263fa5a1c72088f191daf5b7c5d2484c997c`,
`transaction-bench/program/src/rotation_compute.c`.
- Token-2022 ELF: `a794161408080f690dac00832f45b3c3e2b71f1339586667ad1f979cf91d5b68`.
Public Alpenglow program `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb`,
fetched at RPC context slot 4,231,444, program-data account
`DoU57AYuPFu2QU514RktNPG22QhApEjnKxnBcu4BHDTY`. Strip its 45-byte upgradeable
loader metadata before saving the ELF. Verify the hash; do not silently replace
it with a later deployment.

```
MITHRIL_PROGRAM_BENCH_DIR=/path/to/pinned-fixtures GOMAXPROCS=1 \
go test ./pkg/sealevel -run '^TestProgramWorkloadResults$' -v
MITHRIL_PROGRAM_BENCH_DIR=/path/to/pinned-fixtures GOMAXPROCS=1 \
go test ./pkg/sealevel -run '^$' -bench '^BenchmarkProgramWorkloads$' \
-benchtime=1s -count=5 -benchmem
```

## Correctness and comparison boundaries

The generated-program harness compares return values, errors, CU usage and memory
for 100,000 generated programs, evenly divided across SBPF v0–v3. The generator
uses v2-specific memory, arithmetic and constant-loading encodings; the dump
reports verifier rejection or execution results, and logs accepted counts per version.
Run the same harness on both trees: set `SBPF_DIFF_OUT` separately on the reference
and candidate and compare the files. `SBPF_CHECK_POOL_ZERO=1` additionally checks
the candidate’s clear-on-return pool invariant; older references may clear on
acquisition instead. ARSH and verifier semantics changes are excluded from this
performance work.

For replay comparisons, use fresh isolated AccountsDBs from the same snapshots,
identical transaction parallelism, and the same slot interval. Compare normalized
per-slot bank hashes and slot sets before interpreting timings. Compare exact
`ProcessBlock` wall-clock timers, not summed instruction/worker timers. Alternate
run order and retain raw outputs plus commit IDs outside the merge diff.

Record tested commit IDs, hardware, Go version, affinity and parallelism with results.
Single-core shared-host results do not establish multicore contention or live FAST
inclusion gains.

## Memory syscalls and LtHash

Memory syscalls retain CU charges, source-before-destination error order,
zero-length behavior, memcpy overlap rejection and memmove overlap support.
Tests cover copy-on-write/growing regions and differential memory/CU results.

LtHash uses AVX2 only when supported by both CPU and OS; other architectures and
`-tags purego` use portable loops. The vector path preserves 16-bit wraparound and
in-place operand aliasing. Randomized, unaligned, inverse and fallback tests cover
both paths. Component speedups are not block-latency speedups.

```sh
go test ./pkg/lthash ./pkg/sbpf ./pkg/sbpf/loader
go test -tags purego ./pkg/lthash
go test -race ./pkg/sealevel -run 'TestSyscallMem|TestMemoryCopyDifferential|TestProgramWorkloadResults'
SBPF_DIFF_OUT=/tmp/candidate-diff.txt SBPF_CHECK_POOL_ZERO=1 go test ./pkg/sbpf -run TestDifferentialDump -count=1
go test ./pkg/lthash -run '^$' -bench BenchmarkMix -benchmem -count=5
```
44 changes: 44 additions & 0 deletions docs/sha256-syscall.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# SHA-256 syscall validation

The syscall decodes the translated slice descriptors directly and writes the
final digest into the translated output buffer. It retains streaming SHA-256,
slice order, memory translations, CU charges and validation order. Output is
written only after all inputs have been read, preserving overlapping-buffer
behavior.

The frozen reference implementation in the test harness supports differential
checks and before/after benchmarks. Both variants use the same VM, including
its contiguous-region bounds-overflow fix.

Differential tests compare hashes, return/error values, remaining CU and all
input/output memory over valid inputs, invalid descriptors/addresses, depleted
budgets and output aliasing. The existing SHA program fixture also executes.

## Benchmarks

`BenchmarkSha256Syscall` measures the syscall through a real interpreter's memory
translation and CU meter, with VM creation outside the timed region. It covers
empty, single-slice, multiple-slice and larger inputs; it excludes instruction
dispatch and transaction execution.

`BenchmarkSha256CapturedLoop` uses a captured SBF v0 hash loop with 1,000
iterations and zero initial state. It retains descriptor setup, stack accesses,
digest copying, counter updates and branching. Its test checks the result against
a Go hash chain and checks equal CU consumption for both syscall implementations.
The captured ELF hash and extracted instruction range are recorded in the test.
Transaction loading, CPI and the rest of the original program are excluded.

The dispatch-only control omits hashing, translations and syscall CU charging;
it is an overhead diagnostic, not a valid execution implementation. The raw Go
hash chain provides another comparison outside the VM. Neither control can
establish a full-block speedup.

```sh
go test ./pkg/sealevel -run 'TestSha256SyscallDifferential|TestInterpreter_Sha256|TestSha256CapturedLoop'
go test -race ./pkg/sealevel -run 'TestSha256SyscallDifferential|TestInterpreter_Sha256'
go test ./pkg/sealevel -run '^$' -bench '^BenchmarkSha256(Syscall|CapturedLoop)$' -benchtime=1s -count=5 -benchmem
```

Alternate baseline/candidate run order and record commit IDs, Go version,
hardware and CPU affinity. Report component timings separately from full-program
and replay timings; hardware SHA acceleration also affects these results.
5 changes: 5 additions & 0 deletions pkg/cu/cu.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ func (cm *ComputeMeter) Consume(cost uint64) error {
return nil
}

// Disabled reports whether metering is currently switched off (Consume is a no-op).
func (cm *ComputeMeter) Disabled() bool {
return cm.disable
}

func (cm *ComputeMeter) Used() uint64 {
return cm.startingBalance - cm.computeMeter
}
Expand Down
18 changes: 6 additions & 12 deletions pkg/lthash/lthash.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,15 @@ func (ltHash *LtHash) Clone() *LtHash {
return new
}

// MixIn adds other's 1024 lanes to ltHash, lane-wise modulo 2^16. The
// lane arithmetic is vectorized where the platform supports it (see mix.go).
func (ltHash *LtHash) MixIn(other *LtHash) {
for i := range numElements {
ltHash.value[i] = ltHash.value[i] + other.value[i]
}
mixIn(&ltHash.value, &other.value)
}

// MixOut subtracts other's lanes from ltHash, the inverse of MixIn.
func (ltHash *LtHash) MixOut(other *LtHash) {
for i := range numElements {
ltHash.value[i] = ltHash.value[i] - other.value[i]
}
mixOut(&ltHash.value, &other.value)
}

func (ltHash *LtHash) Add(other *LtHash) *LtHash {
Expand All @@ -143,12 +142,7 @@ func (ltHash *LtHash) Sub(other *LtHash) *LtHash {
}

func (ltHash *LtHash) Equals(other *LtHash) bool {
for i, element := range ltHash.value {
if element != other.value[i] {
return false
}
}
return true
return ltHash.value == other.value
}

func (ltHash *LtHash) Checksum() []byte {
Expand Down
16 changes: 16 additions & 0 deletions pkg/lthash/mix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package lthash

// mixInGeneric and mixOutGeneric are the portable lane loops. Every
// architecture-specific implementation must produce identical results: the
// lanes are independent uint16 additions and subtractions modulo 2^16.
func mixInGeneric(dst, src *[numElements]uint16) {
for i := range numElements {
dst[i] += src[i]
}
}

func mixOutGeneric(dst, src *[numElements]uint16) {
for i := range numElements {
dst[i] -= src[i]
}
}
32 changes: 32 additions & 0 deletions pkg/lthash/mix_amd64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//go:build amd64 && !purego

package lthash

import "golang.org/x/sys/cpu"

// useAVX2 selects the vector lane loops. cpu.X86.HasAVX2 already includes
// the operating-system XSAVE/YMM-state check. Tests flip it to compare the
// two implementations on the same machine.
var useAVX2 = cpu.X86.HasAVX2

func mixIn(dst, src *[numElements]uint16) {
if useAVX2 {
mixInAVX2(dst, src)
return
}
mixInGeneric(dst, src)
}

func mixOut(dst, src *[numElements]uint16) {
if useAVX2 {
mixOutAVX2(dst, src)
return
}
mixOutGeneric(dst, src)
}

//go:noescape
func mixInAVX2(dst, src *[numElements]uint16)

//go:noescape
func mixOutAVX2(dst, src *[numElements]uint16)
56 changes: 56 additions & 0 deletions pkg/lthash/mix_amd64.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//go:build amd64 && !purego

#include "textflag.h"

// The LtHash value is 1024 uint16 lanes = 2048 bytes = 16 iterations of
// four 32-byte YMM vectors. VPADDW/VPSUBW operate on 16-bit lanes modulo
// 2^16, exactly like the generic Go loop. Loads and stores are unaligned
// (VMOVDQU): LtHash values live inside Go structs with 2-byte alignment.

// func mixInAVX2(dst, src *[1024]uint16)
TEXT ·mixInAVX2(SB), NOSPLIT, $0-16
MOVQ dst+0(FP), DI
MOVQ src+8(FP), SI
XORQ AX, AX
mixin_loop:
VMOVDQU (DI)(AX*1), Y0
VMOVDQU 32(DI)(AX*1), Y1
VMOVDQU 64(DI)(AX*1), Y2
VMOVDQU 96(DI)(AX*1), Y3
VPADDW (SI)(AX*1), Y0, Y0
VPADDW 32(SI)(AX*1), Y1, Y1
VPADDW 64(SI)(AX*1), Y2, Y2
VPADDW 96(SI)(AX*1), Y3, Y3
VMOVDQU Y0, (DI)(AX*1)
VMOVDQU Y1, 32(DI)(AX*1)
VMOVDQU Y2, 64(DI)(AX*1)
VMOVDQU Y3, 96(DI)(AX*1)
ADDQ $128, AX
CMPQ AX, $2048
JB mixin_loop
VZEROUPPER
RET

// func mixOutAVX2(dst, src *[1024]uint16)
TEXT ·mixOutAVX2(SB), NOSPLIT, $0-16
MOVQ dst+0(FP), DI
MOVQ src+8(FP), SI
XORQ AX, AX
mixout_loop:
VMOVDQU (DI)(AX*1), Y0
VMOVDQU 32(DI)(AX*1), Y1
VMOVDQU 64(DI)(AX*1), Y2
VMOVDQU 96(DI)(AX*1), Y3
VPSUBW (SI)(AX*1), Y0, Y0
VPSUBW 32(SI)(AX*1), Y1, Y1
VPSUBW 64(SI)(AX*1), Y2, Y2
VPSUBW 96(SI)(AX*1), Y3, Y3
VMOVDQU Y0, (DI)(AX*1)
VMOVDQU Y1, 32(DI)(AX*1)
VMOVDQU Y2, 64(DI)(AX*1)
VMOVDQU Y3, 96(DI)(AX*1)
ADDQ $128, AX
CMPQ AX, $2048
JB mixout_loop
VZEROUPPER
RET
Loading
Loading