Skip to content

🚀 Bytecode VM#93

Draft
timfennis wants to merge 175 commits intomasterfrom
feature/bytecode-vm
Draft

🚀 Bytecode VM#93
timfennis wants to merge 175 commits intomasterfrom
feature/bytecode-vm

Conversation

@timfennis
Copy link
Owner

@timfennis timfennis commented Mar 8, 2026

Todo's

  • Clean up the macro's crate

timfennis added 30 commits March 3, 2026 09:49
…layout

- Parser: stop wrapping VariableDeclaration in Statement; add it to
  is_valid_statement so the parse loop continues
- Compiler: split VariableDeclaration (push value only) from Assignment
  (push value + SetLocal + push unit); remove TODO comment
- VM: start with empty stack instead of pushing the script function,
  fixing local slot 0 conflicts that corrupted all local variables
- Tests: add test_declaration and test_assignment compiler tests
## Summary
- Remove `Expression::Index` from the AST — the parser now emits
`Expression::Call` with `"[]"` as the function name, consistent with how
binary operators (`+`, `-`, etc.) are already handled
- Register `[]` as a stdlib `GenericFunction` in
`ndc_stdlib/src/index.rs` that delegates to `get_at_index`
- Add `IndexError` custom error type and `value_to_evaluated_index` to
convert evaluated range values back to `EvaluatedIndex`
- Keep `Lvalue::Index` for assignment targets (`a[i] = val`) with
detection of `Call("[]")` pattern

## Test plan
- [x] All 230 program tests pass
- [x] All 15 compiler tests pass
- [x] Inclusive range without end (`a[0..=]`) correctly errors at parse
time
- [x] Slice with inverted bounds (`[0..7][3..2]`) returns proper error
instead of panicking
timfennis and others added 30 commits March 20, 2026 08:44
…rastructure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dules

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…arison

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- resolve_var now handles Upvalue by reading the closure's upvalue cell
  (Open → stack slot, Closed → stored value); returns owned Value
- Object::static_type: List/Map/Deque compute element/key/value types
  via lub instead of returning Any; Tuple already uses element types
- Add regression test bug0009 for upvalue-in-overload-set dispatch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
static_type() on List/Map/Deque now iterates elements (lub).  The
vectorization_pairs and as_numeric_tuple helpers called it on container
values during every 2-arg dynamic dispatch.  Replace with is_number()
which is O(1) and never allocates.  Add # Performance doc warning to
static_type() pointing to is_number as the safe alternative.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Chunk::opcode now returns &OpCode. The dispatch loop fetches the opcode
by reference instead of cloning the 32-byte enum on every iteration.
Each arm copies its primitive payload immediately (letting NLL end the
borrow before &mut self calls); the Closure arm clones only the Rc.

Benchmarks (quicksort ×200, closures ×20, fibonacci, ackermann):
  fibonacci  1.07×, ackermann 1.06×, quicksort 1.08×, closures 1.04×

Also scales up quicksort and closures benchmarks to give representative
runtimes (~90ms each, up from <5ms).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace Vec<StaticType> collection in resolve_callee with a new
matches_value_args method that short-circuits on Any parameters,
avoiding all StaticType allocations for the common stdlib case.
find_overload now takes &[Value] directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- B1: RangeInclusiveIter::size_hint overflowed when end == i64::MAX; use saturating_add
- B2: Return at top level caused usize underflow on frame_pointer - 1; guard with VmError
- B3: exec_unpack panicked on non-sequence values; return VmError for both cases

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All WithVm HOFs call back closures via VmCallable::call -> call_callback,
which runs inline on the same VM stack. Open upvalue cells remain valid
after the arg drain because they reference the outer frame's stack slots,
not the arg slots. Materialization was only ever needed for call_function
(fresh VM, interpreter bridge) which no WithVm HOF uses.

Deleting the function and its call site fixes the shared-cell divergence
bug where inc and get capturing the same variable would disagree after a
HOF call. Adds reproducer bug0012 and design doc UPVALUE_DESIGN.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lloc, borrow candidates

- P3: replace close+retain two-pass with single retain_mut (~12% on closures bench)
- P4: inline shape/probe check before building vectorization_pairs Vec
- P5: borrow &[ResolvedVar] from stack instead of cloning Vec on every 2-arg dispatch
- R4: OverloadSet Hash+PartialEq now panic instead of silently misbehaving

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Value::matches_param now returns false immediately for typed container
params (List<T>, Deque<T>, Map<K,V>, Sequence<T>, Tuple) rather than
calling static_type().is_subtype() which iterated all elements. Adds
a troubleshooting doc explaining the dispatch behaviour and limitation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Also document map iteration semantics in the manual and add a
manual reference to CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
combinations() now returns a lazy iterator backed by a Vec<Value> pool
(eager drain for List/Tuple/Deque/Map, lazy buffering for Iterator sources).
Avoids materialising all combinations upfront; early exit never touches the
full pool. Also fixes a correctness bug where lazy sources could terminate
early when the pivot search saw only the buffered prefix.

Adds take(seq, n) — a lazy iterator yielding the first n elements of any
sequence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace run_str() with eval() returning Value instead of String
- Re-export Value and NativeFunction from ndc_interpreter
- Add Value::is_unit() helper
- Fix get_output() to return Some(vec![]) before first eval
- Remove ndc_lsp dependency on ndc_vm

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- M1: extract compile_for_iterations helper, removing ~120 lines of duplicated loop scaffolding
- M2: make VmIterator::deep_copy a required method; add missing impls for MinHeapIter, MaxHeapIter, StringIter
- RD1: comment explaining synthetic unit push in no-else branch
- RD2: remove dead `let memo =` binding in dispatch_call
- RD3: descriptive panic messages in Closure opcode handling
- RD4: rename max_local field to num_locals (matching its accessor)
- Remove REVIEW.md (all actionable items resolved)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…() builtin

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant