Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 32 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -728,9 +728,18 @@ jobs:
path: |
zig/.clear-cache
zig/.clear-transpile-cache
examples/minivm/vm
examples/minivm/vm_opt
key: clear-build-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-${{ hashFiles('compiler/ruby/**', 'zig/runtime/**', 'zig/lib/**', 'Gemfile.lock') }}
restore-keys: |
clear-build-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-
# Build the register VM binary once, serially, before the suite fans
# out. bc_run.rb guards the build with an exclusive flock, so without
# this the first of 32 prspec workers builds it while the other 31
# block on the lock -- and a cache miss pays that cost inside the
# parallel run rather than ahead of it.
- name: Warm the register VM binary
run: bundle exec ruby examples/minivm/bc_run.rb examples/minivm/arith.clear --run
- run: bundle exec prspec compiler/spec/ --tag integration
# Collate workers + clear-CLI subprocess resultsets into a single
# Cobertura XML, same as the unit job. Both unit and integration
Expand Down Expand Up @@ -796,6 +805,8 @@ jobs:
path: |
zig/.clear-cache
zig/.clear-transpile-cache
examples/minivm/vm
examples/minivm/vm_opt
key: clear-build-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-${{ hashFiles('compiler/ruby/**', 'zig/runtime/**', 'zig/lib/**', 'Gemfile.lock') }}
restore-keys: |
clear-build-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-
Expand Down Expand Up @@ -898,9 +909,11 @@ jobs:
path: |
zig/.clear-cache
zig/.clear-transpile-cache
key: clear-examples-coverage-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-${{ hashFiles('compiler/ruby/**', 'zig/runtime/**', 'zig/lib/**', 'examples/**/*.clear', 'benchmarks/**/*.clear', 'Gemfile.lock') }}
examples/minivm/vm
examples/minivm/vm_opt
key: clear-build-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-${{ hashFiles('compiler/ruby/**', 'zig/runtime/**', 'zig/lib/**', 'Gemfile.lock') }}
restore-keys: |
clear-examples-coverage-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-
clear-build-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-
- run: bundle exec ruby tools/corpus_transpile_coverage.rb --strict --shard ${{ matrix.shard }}/5
- run: bundle exec ruby tools/corpus_runtime_coverage.rb --strict --shard ${{ matrix.shard }}/5
- run: bundle exec ruby compiler/spec/collate_coverage.rb
Expand Down Expand Up @@ -978,9 +991,11 @@ jobs:
path: |
zig/.clear-cache
zig/.clear-transpile-cache
key: clear-fuzz-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-${{ hashFiles('compiler/ruby/**', 'zig/runtime/**', 'zig/lib/**', 'tools/fuzz/**', 'Gemfile.lock') }}
examples/minivm/vm
examples/minivm/vm_opt
key: clear-build-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-${{ hashFiles('compiler/ruby/**', 'zig/runtime/**', 'zig/lib/**', 'Gemfile.lock') }}
restore-keys: |
clear-fuzz-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-
clear-build-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-
# Ruby coverage records compile/lower/emit. ZIG_COVERAGE=1 also bundles
# positive fuzz cells and runs that Zig test root under kcov; negative
# cells stay compile-only so SimpleCov does not fan out through every
Expand Down Expand Up @@ -1110,6 +1125,8 @@ jobs:
path: |
zig/.clear-cache
zig/.clear-transpile-cache
examples/minivm/vm
examples/minivm/vm_opt
key: clear-build-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-${{ hashFiles('compiler/ruby/**', 'zig/runtime/**', 'zig/lib/**', 'Gemfile.lock') }}
restore-keys: |
clear-build-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-
Expand Down Expand Up @@ -1144,7 +1161,13 @@ jobs:
# 2026-05-17: 244 -> 245. OR PASS sentinel no-op (524, heap
# @list through fallible OR PASS). Full 245-entry allowlist
# green, 0 pending.
- run: bundle exec ruby examples/minivm/run_tests.rb --vm=register --min-pass=245
#
# 2026-08-01: 245 -> 237. Re-baselined, not a regression: this job
# has been `if: false` since the native-binary compile timed out on
# hosted runners, so the ratchet went unenforced while the corpus
# moved under it. 237 passed / 39 pending / 0 failed measured
# identically with and without the stack-machine removal.
- run: bundle exec ruby examples/minivm/run_tests.rb --vm=register --min-pass=237

module-integration:
name: transpile-tests/module-integration (zig build test)
Expand Down Expand Up @@ -1222,9 +1245,11 @@ jobs:
path: |
zig/.clear-cache
zig/.clear-transpile-cache
key: clear-bench-leak-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-${{ hashFiles('compiler/ruby/**', 'zig/runtime/**', 'zig/lib/**', 'benchmarks/**/*.clear', 'Gemfile.lock') }}
examples/minivm/vm
examples/minivm/vm_opt
key: clear-build-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-${{ hashFiles('compiler/ruby/**', 'zig/runtime/**', 'zig/lib/**', 'Gemfile.lock') }}
restore-keys: |
clear-bench-leak-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-
clear-build-${{ runner.os }}-zig${{ env.ZIG_VERSION }}-
- run: ruby benchmarks/runner.rb --leak --all --shard=${{ matrix.shard }}/5 --cores=2 --bencher-json tmp/benchmark-leak-${{ matrix.shard }}.json
- name: Validate Bencher JSON
run: |
Expand Down
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

## MiniVM Rules

Active MiniVM: `examples/minivm/bc_emitter.rb` + `examples/minivm/_bc_runner.clear`.
Active MiniVM: `examples/minivm/register_bc_emitter.rb` + `examples/minivm/register_debugger.clear`. The register machine is the only supported target. The stack machine is gone -- do not reintroduce a `--vm=stack` path, a `StackTarget`, or a `bc_emitter`.

**NEVER parse Zig code strings in the MiniVM.** `MIR::InlineZig` and `MIR::RawZig` are Zig backend artifacts. The bc_emitter must use the AST fallback (`compile_ast_stmt` / `compile_ast_expr`); never inspect `.code`. If no AST is available, raise `Unimplemented`.
`target: :bc` in `MIRLoweringInput` is the *bytecode* lowering mode and is what the register machine uses. It is not a stack-machine flag; `bc_target?` in MIR lowering stays.

**NEVER parse Zig code strings in the MiniVM.** `MIR::InlineZig` and `MIR::RawZig` are Zig backend artifacts. The register emitter must use the AST fallback (`compile_ast_stmt` / `compile_ast_expr`); never inspect `.code`. If no AST is available, raise `Unimplemented`.

## Build & Test

Expand Down
6 changes: 3 additions & 3 deletions benchmarks/vm/run.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash
# Benchmark runner: BC VM (CLEAR) vs Python / Ruby / Lua / Node.
# Benchmark runner: register VM (CLEAR) vs Python / Ruby / Lua / Node.
LUA=${LUA:-/tmp/lua-5.4.7/src/lua}
DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$(dirname "$DIR")/.."
Expand All @@ -14,11 +14,11 @@ run_one() {
printf "%-12s %10s\n" "lang" "ms"
printf -- "------------ ----------\n"

# CLEAR BC VM
# CLEAR register VM
if [ -f "$DIR/${name}.clear" ]; then
out=$(timeout 60 ruby "$DIR/../../examples/minivm/bc_run.rb" "$DIR/${name}.clear" 2>&1)
ms=$(echo "$out" | extract_bench_ms)
printf "%-12s %10s\n" "clear-bc" "${ms:-TIMEOUT}"
printf "%-12s %10s\n" "clear-reg" "${ms:-TIMEOUT}"
fi

# Puck tutorial Ruby VM (v9)
Expand Down
38 changes: 8 additions & 30 deletions compiler/spec/minivm_golden_harness_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,6 @@
skip e.message
end

# MiniVM::Golden.*.run builds vm.clear to a native binary and executes
# it. That compile times out on GitHub-hosted runners (same reason
# the Register-VM allowlist CI job is disabled). The compile/snapshot
# tests above use the in-process Ruby emitter and are unaffected.
def skip_vm_binary_on_ci!
skip "vm.clear native-binary execution times out on GitHub runners; run locally" if ENV["CI"]
end

def run_or_skip(target, test_case)
target.run(test_case.source, source_dir: test_case.source_dir)
rescue MiniVM::Golden::PendingTarget => e
Expand Down Expand Up @@ -61,13 +53,6 @@
rel = test_case.relative_path(File.expand_path("../../examples/minivm/vm-tests", __dir__))
register_pending = REGISTER_PENDING_FIXTURES.include?(rel)

it "compiles the stack VM bytecode snapshot for #{rel}" do
bytecode = compile_or_skip(MiniVM::Golden.stack, test_case)
expected_path = test_case.bytecode_snapshot_path(:stack)

expect(File).to exist(expected_path)
expect(MiniVM::Golden.normalize_snapshot(bytecode.snapshot)).to eq(MiniVM::Golden.normalize_snapshot(File.read(expected_path)))
end

unless register_pending
it "compiles the register VM bytecode snapshot for #{rel}" do
Expand Down Expand Up @@ -114,13 +99,11 @@
}.to raise_error(MiniVM::Golden::PendingTarget, /support|returns/)
end

it "exposes runner hooks for both targets" do
expect(MiniVM::Golden.stack).to respond_to(:run)
it "exposes the register runner hook" do
expect(MiniVM::Golden.register).to respond_to(:run)
end

it "runs register bytecode through vm.clear for an Int64 return" do
skip_vm_binary_on_ci!
source = <<~CHT
FN main() RETURNS Int64 ->
RETURN 42_i64;
Expand All @@ -134,7 +117,6 @@
end

it "uses truncating signed integer division" do
skip_vm_binary_on_ci!
source = <<~CHT
FN main() RETURNS Int64 ->
RETURN -7_i64 / 2_i64;
Expand All @@ -148,7 +130,6 @@
end

it "runs integer modulo bytecode" do
skip_vm_binary_on_ci!
source = <<~CHT
FN main() RETURNS Int64 ->
RETURN 200_i64 MOD 150_i64;
Expand All @@ -162,7 +143,6 @@
end

it "runs compiled register bytecode for the first Int64 fixture" do
skip_vm_binary_on_ci!
test_case = MiniVM::Golden::Case.new(path: source_path)

result = MiniVM::Golden.register.run(test_case.source, source_dir: test_case.source_dir)
Expand All @@ -172,7 +152,6 @@
end

it "runs scalar register match expressions" do
skip_vm_binary_on_ci!
source = <<~CHT
FN score(n: Int64) RETURNS Int64 ->
RETURN PARTIAL MATCH n START
Expand All @@ -194,7 +173,6 @@
end

it "runs every register-supported golden fixture to its expected output" do
skip_vm_binary_on_ci!
# Conformance check: only fixtures with both a committed register
# snapshot AND a committed expected-output file. Fixtures missing
# either are surfaced as `pending` per-case above; including them
Expand Down Expand Up @@ -247,29 +225,29 @@
)
end

it "updates missing stack bytecode snapshots" do
it "updates missing register bytecode snapshots" do
Dir.mktmpdir("minivm-golden-") do |dir|
fixture_dir = File.join(dir, "basics")
FileUtils.mkdir_p(fixture_dir)
FileUtils.cp(source_path, File.join(fixture_dir, "return_i64.clear"))

results = MiniVM::Golden.update_snapshots(root: dir, targets: [:stack])
snapshot_path = File.join(fixture_dir, "return_i64.stack.bc")
results = MiniVM::Golden.update_snapshots(root: dir, targets: [:register])
snapshot_path = File.join(fixture_dir, "return_i64.register.bc")

expect(results.map(&:status)).to eq([:written])
expect(File.read(snapshot_path)).to include("instructions:\n0000 LOAD_CONST_I64")
expect(File.read(snapshot_path)).to include("register instructions:\n0000 ICONST r0 0")
end
end

it "checks stack bytecode snapshots without rewriting stale files" do
it "checks register bytecode snapshots without rewriting stale files" do
Dir.mktmpdir("minivm-golden-") do |dir|
fixture_dir = File.join(dir, "basics")
FileUtils.mkdir_p(fixture_dir)
FileUtils.cp(source_path, File.join(fixture_dir, "return_i64.clear"))
snapshot_path = File.join(fixture_dir, "return_i64.stack.bc")
snapshot_path = File.join(fixture_dir, "return_i64.register.bc")
File.write(snapshot_path, "stale\n")

results = MiniVM::Golden.update_snapshots(root: dir, targets: [:stack], check: true)
results = MiniVM::Golden.update_snapshots(root: dir, targets: [:register], check: true)

expect(results.map(&:status)).to eq([:stale])
expect(File.read(snapshot_path)).to eq("stale\n")
Expand Down
1 change: 0 additions & 1 deletion compiler/spec/minivm_register_debugger_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
# Every example here runs bc_run.rb --vm=register, which builds
# vm.clear to a native binary -- that compile times out on GitHub
# runners (same reason the Register-VM allowlist CI job is disabled).
before { skip "vm.clear native-binary execution times out on GitHub runners; run locally" if ENV["CI"] }

PROJECT_ROOT = File.expand_path("../..", __dir__)
BC_RUN_RB = File.expand_path("examples/minivm/bc_run.rb", PROJECT_ROOT)
Expand Down
Loading
Loading