Skip to content

Add division, bitwise, shift ops and support >256 globals - #22

Merged
vbergeron merged 2 commits into
mainfrom
claude/festive-goodall-tfsu0g
Sep 23, 2026
Merged

vbergeron merged 2 commits into
mainfrom
claude/festive-goodall-tfsu0g

Conversation

@vbergeron

Copy link
Copy Markdown
Owner

Summary

This PR extends the VM and compiler to support:

  1. New integer operations: division, modulo, saturating subtraction, bitwise (AND/OR/XOR), and logical shifts
  2. Unlimited globals: removes the 64-global limit by carving global slots from the heap buffer and using u16 indices instead of u8

Key Changes

New Integer Operations

  • Added int:: module with 24-bit semantics for all operations (wrapping, saturation, zero-fill shifts)
  • New opcodes: INT_LE, INT_DIV, INT_MOD, INT_SUB_SAT, INT_AND, INT_OR, INT_XOR, INT_SHL, INT_SHR
  • Implemented in VM execution loop and constant folder for compile-time evaluation
  • Added comprehensive tests in vm_tests.rs and fleche_tests.rs covering edge cases (division by zero, overflow, out-of-range shifts)

Global Slots Management

  • Globals now carved from top of heap buffer in vm.load() instead of fixed 64-slot array
  • Changed global indices from u8 to u16 throughout compiler and VM
  • Added GLOBAL_W opcode (0x04) for wide (u16) global indices; GLOBAL (0x03) still used for u8 indices
  • Compiler emits GLOBAL_W when index > 255
  • New error: VmError::GlobalsOverflow when globals don't fit in available heap space
  • Updated Program to support unlimited globals via Globals enum (slice or raw bytes)

Compiler Limits & Error Handling

  • New CompileError enum with variants for limit violations:
    • TooManyGlobals (>65535)
    • CodeTooLarge (>65535 bytes)
    • BytesLiteralTooLong (>255)
    • TooManyCaptures (>125)
    • CtorTooWide (>126 fields)
  • pipeline::compile_module() now returns Result<Vec<u8>, CompileError>
  • Emitter tracks and reports first error encountered during code generation

Documentation & Tests

  • Updated VM.md and FLECHE.md with new opcodes and semantics
  • Added SCHEME.md documentation for Scheme primitives
  • Test coverage: 300-global stress test, many edge cases for new ops, deterministic compilation check
  • All test helpers updated to use MAIN constant and handle .unwrap() on compile results

Implementation Details

  • Integer operations use i32 with 24-bit masking/sign-extension via int::wrap()
  • Division/modulo follow Rocq conventions: x / 0 = 0, x mod 0 = x
  • Shifts are logical (zero-fill) with out-of-range shifts returning 0
  • Globals are zero-initialized on load; previous globals not reclaimed (allows stacking multiple programs)
  • Heap addresses remain u16 with 0xFFFF reserved for NULL; max heap is 65535 words

https://claude.ai/code/session_013oP2CQNhSb824xmzwM9kqx

New integer opcodes, end to end (VM, IntOp, constant folding, emit,
disasm, Scheme and Fleche): INT_DIV, INT_MOD (Rocq Nat convention:
x / 0 = 0, x mod 0 = x, truncating), INT_SUB_SAT (Nat.sub), INT_LE,
INT_AND/OR/XOR, INT_SHL and logical INT_SHR (shift amounts outside
0..24 give 0). The semantics live in encore_vm::int, shared by the VM
and the constant folder so they agree bit for bit.

Globals: the VM no longer has a fixed [_; 64] table. load() carves one
word per global from the top of the heap buffer, and fails with
GlobalsOverflow when they do not fit. Program reads globals from its
source instead of copying into a fixed array. The asm IR uses u16
global indices, emitted as GLOBAL_W for indices >= 256. The global-name
metadata index is now u16.

Hard limits are reported instead of wrapping: CompileError for too
many globals, code over 64 KB, over-long byte literals, too many
captures and too-wide constructors; a parse error past 256
constructor tags. compile_module now returns a Result.

Closes #20

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013oP2CQNhSb824xmzwM9kqx
Resolve conflicts with the 24-bit overflow trap. The new integer ops
follow the same contract: INT_DIV (INT_MIN / -1), INT_SUB_SAT and
INT_SHL (exact a * 2^b) now trap with IntOverflow instead of wrapping,
and the constant folder leaves those cases unfolded. INT_MOD, the
bitwise ops and INT_SHR cannot overflow and are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013oP2CQNhSb824xmzwM9kqx
@vbergeron
vbergeron merged commit 4a451b6 into main Sep 23, 2026
2 checks passed
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.

2 participants