diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 56022fba..109aeedd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,6 +62,24 @@ jobs: - name: Run parity tests run: make parity + # Bytecode VM parity tests + vm-parity: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential gcc make + + - name: Build bytecode VM + run: make vm + + - name: Run VM parity tests + run: make test-vm + # Compile check - verify interpreter tests compile with hemlockc compile-check: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 8c2a6d64..baf01bcc 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ # Compiled executables /hemlock /hemlockc +/hemlockvm hemlock.exe *.out *.app @@ -86,4 +87,7 @@ docs/docs.html # Hemlock bytecode/bundle files *.hmlc *.hmlb -*.hmlp \ No newline at end of file +*.hmlp +# VM binaries +src/backends/vm/hemlockvm +src/backends/vm/hemlockvm_asan diff --git a/Makefile b/Makefile index 50f44ee6..878d00db 100644 --- a/Makefile +++ b/Makefile @@ -280,8 +280,8 @@ runtime-clean: compiler-clean: rm -f $(COMPILER_TARGET) $(COMPILER_OBJS) -# Full clean including compiler, runtime, and release -fullclean: clean compiler-clean runtime-clean release-clean +# Full clean including compiler, runtime, VM, and release +fullclean: clean compiler-clean runtime-clean vm-clean release-clean # Run compiler test suite .PHONY: test-compiler @@ -303,6 +303,46 @@ parity: $(TARGET) compiler stdlib parity-full: $(TARGET) compiler stdlib @bash tests/run_full_parity.sh +# ========== BYTECODE VM ========== + +VM_TARGET = hemlockvm + +# Build the bytecode VM (output to root directory like hemlock and hemlockc) +.PHONY: vm +vm: + @echo "Building bytecode VM..." + $(MAKE) -C src/backends/vm + @cp src/backends/vm/hemlockvm ./$(VM_TARGET) + @echo "✓ Bytecode VM built: $(VM_TARGET)" + +# Clean the bytecode VM +.PHONY: vm-clean +vm-clean: + $(MAKE) -C src/backends/vm clean + rm -f $(VM_TARGET) + +# Run VM parity tests +.PHONY: test-vm +test-vm: vm + @echo "Running VM parity tests..." + @passed=0; failed=0; \ + for test in tests/parity/language/*.hml tests/parity/builtins/*.hml tests/parity/methods/*.hml tests/parity/modules/*.hml; do \ + expected="$${test%.hml}.expected"; \ + if [ -f "$$expected" ]; then \ + output=$$(timeout 5 ./$(VM_TARGET) "$$test" 2>&1); \ + expected_content=$$(cat "$$expected"); \ + if [ "$$output" = "$$expected_content" ]; then \ + echo "✓ PASS: $$(basename $$test)"; \ + passed=$$((passed + 1)); \ + else \ + echo "✗ FAIL: $$(basename $$test)"; \ + failed=$$((failed + 1)); \ + fi; \ + fi; \ + done; \ + echo ""; \ + echo "VM Parity: $$passed passed, $$failed failed" + # Run bundler test suite .PHONY: test-bundler test-bundler: $(TARGET) diff --git a/docs/bytecode-vm-plan.md b/docs/bytecode-vm-plan.md new file mode 100644 index 00000000..bdb5f467 --- /dev/null +++ b/docs/bytecode-vm-plan.md @@ -0,0 +1,737 @@ +# Hemlock Bytecode VM Implementation Plan + +## Overview + +A stack-based bytecode virtual machine as a third execution backend for Hemlock, providing faster execution than tree-walking interpretation while maintaining full parity with existing backends. + +``` +Source (.hml) + ↓ +┌─────────────────────────────┐ +│ SHARED FRONTEND │ +│ - Lexer, Parser, AST │ +│ - Resolver (variable res) │ +└─────────────────────────────┘ + ↓ ↓ ↓ +┌────────┐ ┌────────┐ ┌────────┐ +│Interp │ │ VM │ │Compiler│ +│(tree) │ │(byte) │ │(C gen) │ +└────────┘ └────────┘ └────────┘ +``` + +--- + +## Directory Structure + +``` +src/backends/vm/ +├── main.c # Entry point, CLI handling +├── vm.h # Public VM API +├── vm.c # Main execution loop (dispatch) +├── chunk.h # Bytecode chunk format +├── chunk.c # Chunk creation, constant pool +├── instruction.h # Opcode definitions +├── compiler.h # AST → bytecode compiler API +├── compiler.c # Main compilation driver +├── compile_expr.c # Expression compilation +├── compile_stmt.c # Statement compilation +├── compile_call.c # Function/method call compilation +├── frame.h # Call frame management +├── frame.c # Stack frame operations +├── debug.h # Disassembler, debugging +├── debug.c # Bytecode disassembly +└── Makefile # Build rules +``` + +--- + +## Instruction Set Architecture + +### Encoding Format + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 8-bit opcode │ operands (variable length, 0-3 bytes) │ +└─────────────────────────────────────────────────────────────┘ + +Operand types: +- None: OP alone +- Byte: OP + 1-byte immediate +- Short: OP + 2-byte immediate (big-endian) +- Constant: OP + 2-byte constant pool index +- Jump: OP + 2-byte signed offset +``` + +### Complete Instruction Set (82 opcodes) + +#### Category 1: Constants & Literals (10 opcodes) + +| Opcode | Encoding | Stack Effect | Description | +|--------|----------|--------------|-------------| +| `OP_CONST` | `[op][idx:16]` | +1 | Push constant from pool | +| `OP_CONST_BYTE` | `[op][val:8]` | +1 | Push small integer (0-255) | +| `OP_NULL` | `[op]` | +1 | Push null | +| `OP_TRUE` | `[op]` | +1 | Push true | +| `OP_FALSE` | `[op]` | +1 | Push false | +| `OP_ARRAY` | `[op][count:16]` | -(n-1) | Pop n, push array | +| `OP_OBJECT` | `[op][count:16]` | -(2n-1) | Pop n key-value pairs, push object | +| `OP_STRING_INTERP` | `[op][parts:16]` | -(n-1) | Interpolate n parts into string | +| `OP_CLOSURE` | `[op][idx:16][upvals:8]` | +1 | Create closure with upvalues | +| `OP_ENUM_VALUE` | `[op][idx:16]` | +1 | Push enum variant value | + +#### Category 2: Variables (12 opcodes) + +| Opcode | Encoding | Stack Effect | Description | +|--------|----------|--------------|-------------| +| `OP_GET_LOCAL` | `[op][slot:8]` | +1 | Load local variable | +| `OP_SET_LOCAL` | `[op][slot:8]` | 0 | Store to local (keep on stack) | +| `OP_GET_UPVALUE` | `[op][slot:8]` | +1 | Load captured variable | +| `OP_SET_UPVALUE` | `[op][slot:8]` | 0 | Store to captured variable | +| `OP_GET_GLOBAL` | `[op][idx:16]` | +1 | Load global by name | +| `OP_SET_GLOBAL` | `[op][idx:16]` | 0 | Store to global | +| `OP_DEFINE_GLOBAL` | `[op][idx:16]` | -1 | Define new global | +| `OP_GET_PROPERTY` | `[op][idx:16]` | 0 | Get object property | +| `OP_SET_PROPERTY` | `[op][idx:16]` | -1 | Set object property | +| `OP_GET_INDEX` | `[op]` | -1 | array[index] or object[key] | +| `OP_SET_INDEX` | `[op]` | -2 | array[index] = value | +| `OP_CLOSE_UPVALUE` | `[op]` | 0 | Close upvalue on stack top | + +#### Category 3: Arithmetic (11 opcodes) + +| Opcode | Encoding | Stack Effect | Description | +|--------|----------|--------------|-------------| +| `OP_ADD` | `[op]` | -1 | a + b (type promotion, string concat) | +| `OP_SUB` | `[op]` | -1 | a - b | +| `OP_MUL` | `[op]` | -1 | a * b | +| `OP_DIV` | `[op]` | -1 | a / b (always f64) | +| `OP_MOD` | `[op]` | -1 | a % b | +| `OP_NEGATE` | `[op]` | 0 | -a | +| `OP_INC` | `[op]` | 0 | ++a (in-place) | +| `OP_DEC` | `[op]` | 0 | --a (in-place) | +| `OP_ADD_I32` | `[op]` | -1 | Fast path: i32 + i32 | +| `OP_SUB_I32` | `[op]` | -1 | Fast path: i32 - i32 | +| `OP_MUL_I32` | `[op]` | -1 | Fast path: i32 * i32 | + +#### Category 4: Comparison (8 opcodes) + +| Opcode | Encoding | Stack Effect | Description | +|--------|----------|--------------|-------------| +| `OP_EQ` | `[op]` | -1 | a == b | +| `OP_NE` | `[op]` | -1 | a != b | +| `OP_LT` | `[op]` | -1 | a < b | +| `OP_LE` | `[op]` | -1 | a <= b | +| `OP_GT` | `[op]` | -1 | a > b | +| `OP_GE` | `[op]` | -1 | a >= b | +| `OP_EQ_I32` | `[op]` | -1 | Fast path: i32 == i32 | +| `OP_LT_I32` | `[op]` | -1 | Fast path: i32 < i32 | + +#### Category 5: Logical & Bitwise (9 opcodes) + +| Opcode | Encoding | Stack Effect | Description | +|--------|----------|--------------|-------------| +| `OP_NOT` | `[op]` | 0 | !a | +| `OP_BIT_NOT` | `[op]` | 0 | ~a | +| `OP_BIT_AND` | `[op]` | -1 | a & b | +| `OP_BIT_OR` | `[op]` | -1 | a \| b | +| `OP_BIT_XOR` | `[op]` | -1 | a ^ b | +| `OP_LSHIFT` | `[op]` | -1 | a << b | +| `OP_RSHIFT` | `[op]` | -1 | a >> b | +| `OP_COALESCE` | `[op][offset:16]` | 0 or -1 | a ?? b (short-circuit) | +| `OP_OPTIONAL_CHAIN` | `[op][offset:16]` | 0 | a?.b (short-circuit if null) | + +#### Category 6: Control Flow (12 opcodes) + +| Opcode | Encoding | Stack Effect | Description | +|--------|----------|--------------|-------------| +| `OP_JUMP` | `[op][offset:16]` | 0 | Unconditional jump | +| `OP_JUMP_IF_FALSE` | `[op][offset:16]` | -1 | Jump if top is falsy | +| `OP_JUMP_IF_TRUE` | `[op][offset:16]` | -1 | Jump if top is truthy | +| `OP_JUMP_IF_FALSE_POP` | `[op][offset:16]` | -1 | Jump if false, always pop | +| `OP_LOOP` | `[op][offset:16]` | 0 | Jump backward (loop) | +| `OP_BREAK` | `[op]` | 0 | Break from loop | +| `OP_CONTINUE` | `[op]` | 0 | Continue to next iteration | +| `OP_SWITCH` | `[op][cases:16]` | -1 | Jump table dispatch | +| `OP_CASE` | `[op][offset:16]` | 0 | Case label marker | +| `OP_FOR_IN_INIT` | `[op]` | +1 | Initialize for-in iterator | +| `OP_FOR_IN_NEXT` | `[op][offset:16]` | +1 or jump | Get next or jump to end | +| `OP_POP` | `[op]` | -1 | Discard top of stack | + +#### Category 7: Functions & Calls (8 opcodes) + +| Opcode | Encoding | Stack Effect | Description | +|--------|----------|--------------|-------------| +| `OP_CALL` | `[op][argc:8]` | -(argc) | Call function | +| `OP_CALL_METHOD` | `[op][idx:16][argc:8]` | -(argc) | Call method on object | +| `OP_CALL_BUILTIN` | `[op][id:16][argc:8]` | -(argc) | Call builtin function | +| `OP_RETURN` | `[op]` | special | Return from function | +| `OP_APPLY` | `[op]` | -1 | apply(fn, args_array) | +| `OP_TAIL_CALL` | `[op][argc:8]` | special | Tail call optimization | +| `OP_SUPER` | `[op][idx:16]` | 0 | Access super method | +| `OP_INVOKE` | `[op][idx:16][argc:8]` | -(argc) | Optimized method call | + +#### Category 8: Exception Handling (6 opcodes) + +| Opcode | Encoding | Stack Effect | Description | +|--------|----------|--------------|-------------| +| `OP_TRY` | `[op][catch:16][finally:16]` | 0 | Begin try block | +| `OP_CATCH` | `[op]` | +1 | Begin catch (push exception) | +| `OP_FINALLY` | `[op]` | 0 | Begin finally block | +| `OP_END_TRY` | `[op]` | 0 | End try-catch-finally | +| `OP_THROW` | `[op]` | -1 | Throw exception | +| `OP_DEFER` | `[op][idx:16]` | 0 | Register deferred call | + +#### Category 9: Async & Concurrency (8 opcodes) + +| Opcode | Encoding | Stack Effect | Description | +|--------|----------|--------------|-------------| +| `OP_SPAWN` | `[op][argc:8]` | -(argc) | Spawn async task | +| `OP_AWAIT` | `[op]` | 0 | Await task result | +| `OP_JOIN` | `[op]` | 0 | Join task (explicit) | +| `OP_DETACH` | `[op]` | -1 | Detach task | +| `OP_CHANNEL` | `[op]` | 0 | Create channel (capacity on stack) | +| `OP_SEND` | `[op]` | -2 | Send on channel | +| `OP_RECV` | `[op]` | 0 | Receive from channel | +| `OP_SELECT` | `[op]` | 0 | Select on multiple channels | + +#### Category 10: Type Operations (5 opcodes) + +| Opcode | Encoding | Stack Effect | Description | +|--------|----------|--------------|-------------| +| `OP_TYPEOF` | `[op]` | 0 | Get type string | +| `OP_CAST` | `[op][type:8]` | 0 | Explicit type cast | +| `OP_CHECK_TYPE` | `[op][type:8]` | 0 | Runtime type check | +| `OP_DEFINE_TYPE` | `[op][idx:16]` | 0 | Register type definition | +| `OP_DEFINE_ENUM` | `[op][idx:16]` | 0 | Register enum definition | + +#### Category 11: Debug & Misc (3 opcodes) + +| Opcode | Encoding | Stack Effect | Description | +|--------|----------|--------------|-------------| +| `OP_NOP` | `[op]` | 0 | No operation | +| `OP_PRINT` | `[op][argc:8]` | -(argc) | Print values | +| `OP_ASSERT` | `[op]` | -1 or -2 | Assert with optional message | + +**Total: 82 opcodes** + +--- + +## Constant Pool Design + +```c +typedef enum { + CONST_I32, + CONST_I64, + CONST_F64, + CONST_STRING, + CONST_FUNCTION, + CONST_IDENTIFIER, // for globals/properties +} ConstantType; + +typedef struct { + ConstantType type; + union { + int32_t i32; + int64_t i64; + double f64; + struct { char *data; int len; } string; + struct Chunk *function; + } as; +} Constant; + +typedef struct Chunk { + uint8_t *code; // Bytecode array + int code_count; + int code_capacity; + + Constant *constants; // Constant pool + int const_count; + int const_capacity; + + int *lines; // Line numbers for debugging + + // Function metadata + char *name; + int arity; + int upvalue_count; + bool is_async; + + // Type annotations (optional) + ValueType *param_types; + ValueType return_type; +} Chunk; +``` + +--- + +## VM Execution State + +```c +typedef struct { + Chunk *chunk; // Current bytecode chunk + uint8_t *ip; // Instruction pointer + Value *slots; // Local variable slots + int slot_count; + struct CallFrame *prev; // For call stack +} CallFrame; + +typedef struct { + // Value stack + Value *stack; + Value *stack_top; + int stack_capacity; + + // Call frames + CallFrame *frames; + int frame_count; + int frame_capacity; + + // Upvalues (for closures) + ObjUpvalue *open_upvalues; + + // Global variables + Table globals; + + // Control flow state + bool is_returning; + Value return_value; + bool is_throwing; + Value exception; + bool is_breaking; + bool is_continuing; + + // Defer stack + DeferEntry *defers; + int defer_count; + + // GC/memory + Object *objects; // All allocated objects (for cleanup) + size_t bytes_allocated; +} VM; +``` + +--- + +## Builtin Function Table + +Map builtin IDs to function pointers: + +```c +typedef Value (*BuiltinFn)(VM *vm, int argc, Value *args); + +typedef struct { + const char *name; + BuiltinFn fn; + int min_arity; + int max_arity; // -1 for variadic +} BuiltinEntry; + +// 67 builtins organized by category +static BuiltinEntry builtins[] = { + // Memory (0-10) + {"alloc", builtin_alloc, 1, 1}, + {"talloc", builtin_talloc, 2, 2}, + {"realloc", builtin_realloc, 2, 2}, + {"free", builtin_free, 1, 1}, + {"memset", builtin_memset, 3, 3}, + {"memcpy", builtin_memcpy, 3, 3}, + {"sizeof", builtin_sizeof, 1, 1}, + {"buffer", builtin_buffer, 1, 1}, + {"ptr_to_buffer", builtin_ptr_to_buffer, 2, 2}, + {"buffer_ptr", builtin_buffer_ptr, 1, 1}, + {"ptr_null", builtin_ptr_null, 0, 0}, + + // I/O (11-14) + {"print", builtin_print, 0, -1}, + {"eprint", builtin_eprint, 0, -1}, + {"read_line", builtin_read_line, 0, 0}, + {"open", builtin_open, 2, 2}, + + // Type (15-17) + {"typeof", builtin_typeof, 1, 1}, + {"assert", builtin_assert, 1, 2}, + {"panic", builtin_panic, 1, 1}, + + // Async (18-24) + {"spawn", builtin_spawn, 1, -1}, + {"join", builtin_join, 1, 1}, + {"detach", builtin_detach, 1, 1}, + {"channel", builtin_channel, 1, 1}, + {"select", builtin_select, 1, 2}, + {"task_debug_info", builtin_task_debug_info, 1, 1}, + {"apply", builtin_apply, 2, 2}, + + // Pointer ops (25-50) + {"ptr_read_i32", builtin_ptr_read_i32, 1, 1}, + {"ptr_write_i32", builtin_ptr_write_i32, 2, 2}, + // ... (all ptr_read/write variants) + + // Atomics (51-67) + {"atomic_load_i32", builtin_atomic_load_i32, 1, 1}, + {"atomic_store_i32", builtin_atomic_store_i32, 2, 2}, + {"atomic_add_i32", builtin_atomic_add_i32, 2, 2}, + // ... (all atomic variants) + {"atomic_fence", builtin_atomic_fence, 0, 0}, + + // ... more builtins +}; +``` + +--- + +## Test Suite Strategy + +### Phase 1: Core Language (46 tests) + +Run in order of dependency: + +``` +Phase 1a: Primitives & Variables (8 tests) +├── primitives.hml ✓ literals, basic types +├── booleans.hml ✓ boolean ops +├── constants.hml ✓ const declarations +├── scientific_notation.hml ✓ number parsing +├── variables.hml ✓ let, assignment +├── scope_shadowing.hml ✓ nested scopes +├── runtime_prefix_names.hml ✓ edge case names +└── string_interpolation.hml ✓ template strings + +Phase 1b: Operations (9 tests) +├── arithmetic.hml ✓ +, -, *, /, % +├── compound_assignment.hml ✓ +=, -=, etc. +├── increment_decrement.hml ✓ ++, -- +├── comparisons.hml ✓ ==, <, >, etc. +├── truthiness.hml ✓ truthy/falsy +├── logical.hml ✓ &&, ||, ! +├── null_coalesce.hml ✓ ?? +├── bitwise.hml ✓ &, |, ^, <<, >> +└── ternary.hml ✓ ? : + +Phase 1c: Control Flow (7 tests) +├── if_else.hml ✓ if/else if/else +├── loops.hml ✓ while, for +├── for_in.hml ✓ for-in iteration +├── break_continue.hml ✓ loop control +├── switch.hml ✓ switch/case +├── switch_fallthrough.hml ✓ fallthrough +└── defer.hml ✓ defer statement + +Phase 1d: Functions (8 tests) +├── functions.hml ✓ definition, calls +├── recursion.hml ✓ recursive calls +├── higher_order.hml ✓ first-class functions +├── closures.hml ✓ closure capture +├── nested_closures.hml ✓ multi-level capture +├── optional_params.hml ✓ default parameters +├── rest_params.hml ✓ ...args +└── many_params.hml ✓ large param counts + +Phase 1e: Data Structures (7 tests) +├── arrays.hml ✓ array ops +├── typed_arrays.hml ✓ array +├── objects.hml ✓ object ops +├── object_edge_cases.hml ✓ edge cases +├── type_definitions.hml ✓ define +├── enums.hml ✓ enum +└── string_utf8_iteration.hml ✓ UTF-8 + +Phase 1f: Error Handling & Types (7 tests) +├── exceptions.hml ✓ try/catch/throw +├── conversions.hml ✓ type conversion +├── type_coercion.hml ✓ coercion rules +├── type_promotion.hml ✓ i32 → i64 → f64 +├── type_checking.hml ✓ runtime checks +├── c_keyword_names.hml ✓ edge case +└── string_null_concat.hml ✓ null handling +``` + +### Phase 2: Builtins (36 tests) + +``` +Phase 2a: Memory & Pointers (5 tests) +├── memory.hml ✓ alloc/free/buffer +├── pointer_ops.hml ✓ pointer arithmetic +├── read_ptr.hml ✓ ptr_read/write +├── sizeof.hml ✓ sizeof +└── buffers.hml ✓ bounded buffers + +Phase 2b: I/O & System (8 tests) +├── print_typeof.hml ✓ print, typeof +├── file_io.hml ✓ open/read/write/close +├── env.hml ✓ getenv/setenv +├── exec.hml ✓ exec commands +├── exec_argv.hml ✓ exec with args +├── signals.hml ✓ signal handling +├── signal_tty_constants.hml ✓ signal constants +└── stack_limit.hml ✓ stack limit + +Phase 2c: Async & Concurrency (5 tests) +├── async.hml ✓ spawn/join/await +├── async_many_args.hml ✓ many async args +├── channels.hml ✓ channel ops +├── unbuffered_channels.hml ✓ unbuffered semantics +└── channel_timeout.hml ✓ timeout + +Phase 2d: FFI (5 tests) +├── ffi_callback.hml ✓ callbacks +├── ffi_float.hml ✓ float args +├── ffi_ptr_helpers.hml ✓ ptr helpers +├── ffi_struct.hml ✓ struct support +└── ffi_struct_concurrent.hml ✓ concurrent FFI + +Phase 2e: Math & Misc (8 tests) +├── math.hml ✓ math functions +├── assert.hml ✓ assertions +├── serialization.hml ✓ serialize/deserialize +├── apply.hml ✓ dynamic calls +├── atomic_operations.hml ✓ atomics +├── poll_constants.hml ✓ poll constants +├── socket_constants.hml ✓ socket constants +└── socket_nonblocking.hml ✓ non-blocking IO + +Phase 2f: Network & HTTP (2 tests) +├── http_request_fn.hml ✓ HTTP requests +└── ws_send_binary_fn.hml ✓ WebSocket +``` + +### Phase 3: Methods (4 tests) + +``` +├── string_methods.hml ✓ 19 string methods +├── string_to_bytes.hml ✓ UTF-8 bytes +├── array_methods.hml ✓ 18 array methods +└── map_filter_reduce.hml ✓ functional methods +``` + +### Phase 4: Modules (17 tests) + +``` +Phase 4a: Import/Export (6 tests) +├── named_import.hml ✓ { x } from +├── star_import.hml ✓ * from +├── namespace_import.hml ✓ import module +├── chained_import.hml ✓ re-exports +├── export_define.hml ✓ export types +└── export_extern.hml ✓ export FFI + +Phase 4b: Standard Library (11 tests) +├── stdlib_math.hml ✓ @stdlib/math +├── stdlib_json.hml ✓ @stdlib/json +├── stdlib_collections.hml ✓ @stdlib/collections +├── stdlib_encoding.hml ✓ @stdlib/encoding +├── stdlib_fs.hml ✓ @stdlib/fs +├── stdlib_hash.hml ✓ @stdlib/hash +├── stdlib_os.hml ✓ @stdlib/os +├── stdlib_strings.hml ✓ @stdlib/strings +├── stdlib_time.hml ✓ @stdlib/time +├── stdlib_path.hml ✓ @stdlib/path +└── stdlib_url.hml ✓ @stdlib/url +``` + +--- + +## Implementation Phases + +### Phase 1: Foundation (Week 1-2) +- [ ] Instruction encoding (`instruction.h`) +- [ ] Chunk structure (`chunk.h`, `chunk.c`) +- [ ] Constant pool management +- [ ] Basic VM loop with stack operations +- [ ] Disassembler for debugging (`debug.c`) + +**Milestone:** Can execute `OP_CONST`, `OP_ADD`, `OP_PRINT` + +### Phase 2: Expressions (Week 2-3) +- [ ] Arithmetic operations (all 11 opcodes) +- [ ] Comparison operations (8 opcodes) +- [ ] Logical & bitwise (9 opcodes) +- [ ] Variable load/store (12 opcodes) +- [ ] Array/object literals +- [ ] Property access +- [ ] Index operations + +**Milestone:** Pass `primitives.hml`, `arithmetic.hml`, `comparisons.hml` + +### Phase 3: Control Flow (Week 3-4) +- [ ] Jump instructions +- [ ] If/else compilation +- [ ] While/for loops +- [ ] For-in iteration +- [ ] Break/continue +- [ ] Switch statement +- [ ] Ternary operator + +**Milestone:** Pass all control flow tests (7 tests) + +### Phase 4: Functions (Week 4-5) +- [ ] Function compilation to chunks +- [ ] Call frame management +- [ ] Parameter binding +- [ ] Return handling +- [ ] Closure capture (upvalues) +- [ ] Rest parameters +- [ ] Optional parameters + +**Milestone:** Pass all function tests (8 tests) + +### Phase 5: Error Handling (Week 5-6) +- [ ] Try/catch/finally +- [ ] Exception propagation +- [ ] Defer stack +- [ ] Type checking/casting +- [ ] Runtime type errors + +**Milestone:** Pass `exceptions.hml`, `defer.hml` + +### Phase 6: Builtins (Week 6-7) +- [ ] Builtin dispatch table +- [ ] Memory builtins (alloc, free, etc.) +- [ ] I/O builtins (print, open, etc.) +- [ ] Math builtins +- [ ] Type builtins + +**Milestone:** Pass all builtin tests (36 tests) + +### Phase 7: Methods (Week 7-8) +- [ ] String method dispatch (19 methods) +- [ ] Array method dispatch (18 methods) +- [ ] Method call optimization + +**Milestone:** Pass all method tests (4 tests) + +### Phase 8: Async (Week 8-9) +- [ ] Task creation (spawn) +- [ ] Task join/await +- [ ] Channel implementation +- [ ] Select operation +- [ ] Thread-safe value copying + +**Milestone:** Pass all async tests (5 tests) + +### Phase 9: Modules (Week 9-10) +- [ ] Module loading +- [ ] Import/export handling +- [ ] Stdlib integration + +**Milestone:** Pass all module tests (17 tests) + +### Phase 10: Polish (Week 10-11) +- [ ] Performance optimization +- [ ] Fast paths for i32 operations +- [ ] Memory optimization +- [ ] Full parity verification +- [ ] Documentation + +**Milestone:** 103/103 parity tests passing + +--- + +## Parity Testing Strategy + +### Test Runner + +```bash +#!/bin/bash +# scripts/test_vm_parity.sh + +PASS=0 +FAIL=0 +INTERP_ONLY=0 +VM_ONLY=0 + +for test in tests/parity/**/*.hml; do + expected="${test%.hml}.expected" + + # Run interpreter + interp_out=$(timeout 10 ./hemlock "$test" 2>&1) + interp_rc=$? + + # Run VM + vm_out=$(timeout 10 ./hemlockvm "$test" 2>&1) + vm_rc=$? + + # Compare + if [ "$interp_out" = "$vm_out" ] && [ "$interp_rc" = "$vm_rc" ]; then + if [ "$interp_out" = "$(cat $expected)" ]; then + echo "✓ PASS: $test" + ((PASS++)) + else + echo "✗ BOTH_WRONG: $test" + ((FAIL++)) + fi + elif [ "$interp_out" = "$(cat $expected)" ]; then + echo "◐ INTERP_ONLY: $test" + ((INTERP_ONLY++)) + elif [ "$vm_out" = "$(cat $expected)" ]; then + echo "◑ VM_ONLY: $test" + ((VM_ONLY++)) + else + echo "✗ BOTH_FAIL: $test" + ((FAIL++)) + fi +done + +echo "" +echo "Results: $PASS passed, $FAIL failed, $INTERP_ONLY interp-only, $VM_ONLY vm-only" +echo "Parity: $PASS / $((PASS + INTERP_ONLY + VM_ONLY + FAIL)) tests" +``` + +### Incremental Parity Tracking + +``` +tests/parity/ +├── vm_status.json # Track VM implementation status +└── ... + +{ + "language/primitives.hml": "pass", + "language/closures.hml": "interp_only", + "builtins/async.hml": "not_started", + ... +} +``` + +### CI Integration + +```makefile +# Makefile additions + +vm: src/backends/vm/*.c + $(CC) $(CFLAGS) -o hemlockvm $^ + +test-vm: vm + ./scripts/test_vm_parity.sh + +parity-vm: hemlock hemlockvm + ./scripts/test_vm_parity.sh --strict # Fail if any test doesn't pass both +``` + +--- + +## Performance Targets + +| Metric | Tree-Walker | Bytecode VM | C Compiler | +|--------|-------------|-------------|------------| +| Startup | ~1ms | ~2ms | ~500ms (compile) | +| Fib(30) | ~800ms | ~200ms | ~50ms | +| Loop 1M | ~500ms | ~100ms | ~10ms | +| Memory | Low | Medium | Low (at runtime) | + +Target: **4-8x faster than tree-walker** for compute-heavy code. + +--- + +## Open Questions + +1. **Upvalue representation**: Open upvalues on stack vs. heap-allocated? +2. **NaN boxing**: Pack values into 64-bit with NaN tagging for faster ops? +3. **JIT preparation**: Structure bytecode to enable future JIT compilation? +4. **Debug info**: Full source mapping vs. line numbers only? + +--- + +## References + +- Crafting Interpreters (Bob Nystrom) - Bytecode VM design +- Lua 5.x source - Register-based inspiration +- Python/CPython - Stack-based bytecode +- Existing Hemlock interpreter - Semantic reference diff --git a/src/backends/vm/Makefile b/src/backends/vm/Makefile new file mode 100644 index 00000000..873ff0c4 --- /dev/null +++ b/src/backends/vm/Makefile @@ -0,0 +1,94 @@ +# Hemlock Bytecode VM Makefile + +CC = gcc +CFLAGS = -Wall -Wextra -g -O2 -std=c11 -D_POSIX_C_SOURCE=200809L +CFLAGS += -I../../../include -I. -I../../frontend -I.. -I../../bundler + +# Source files (VM-specific) +VM_SRCS = main.c \ + vm.c \ + chunk.c \ + compiler.c \ + instruction.c \ + debug.c + +VM_OBJS = $(VM_SRCS:.c=.o) + +# Shared frontend objects +FRONTEND_DIR = ../../frontend +FRONTEND_SRCS = $(FRONTEND_DIR)/lexer.c \ + $(FRONTEND_DIR)/parser/core.c \ + $(FRONTEND_DIR)/parser/expressions.c \ + $(FRONTEND_DIR)/parser/statements.c \ + $(FRONTEND_DIR)/ast.c \ + $(FRONTEND_DIR)/ast_serialize.c + +# Bundler +BUNDLER_DIR = ../../bundler +BUNDLER_SRCS = $(BUNDLER_DIR)/bundler.c + +# Interpreter objects we can reuse +INTERP_DIR = ../interpreter +SHARED_SRCS = $(INTERP_DIR)/resolver.c \ + $(INTERP_DIR)/optimizer.c + +# Output binary +TARGET = hemlockvm + +.PHONY: all clean test parity + +all: $(TARGET) + +# Build by compiling all sources directly +$(TARGET): $(VM_SRCS) $(FRONTEND_SRCS) $(BUNDLER_SRCS) $(SHARED_SRCS) + $(CC) $(CFLAGS) -o $@ $^ -lpthread -lm -ldl -lz -lcrypto + +# Individual object compilation +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +# Run VM-specific tests +test: $(TARGET) + @echo "Running bytecode VM tests..." + @./$(TARGET) ../../../tests/parity/language/primitives.hml + +# Quick test +quick: $(TARGET) + @echo "Testing: print(42);" + @echo 'print(42);' | ./$(TARGET) + +# Run parity tests (compare output with interpreter) +parity: $(TARGET) + @echo "Running parity tests..." + @for f in ../../../tests/parity/language/*.hml; do \ + echo -n "Testing $$f... "; \ + expected=$${f%.hml}.expected; \ + if [ -f "$$expected" ]; then \ + output=$$(./$(TARGET) "$$f" 2>&1); \ + expected_content=$$(cat "$$expected"); \ + if [ "$$output" = "$$expected_content" ]; then \ + echo "PASS"; \ + else \ + echo "FAIL"; \ + echo "Expected:"; cat "$$expected"; \ + echo "Got:"; echo "$$output"; \ + fi \ + else \ + echo "SKIP (no expected file)"; \ + fi \ + done + +# Disassemble a file (for debugging) +disasm: $(TARGET) + ./$(TARGET) --disasm $(FILE) + +clean: + rm -f $(VM_OBJS) $(TARGET) + +# Dependencies +main.o: main.c vm.h chunk.h instruction.h compiler.h +vm.o: vm.c vm.h chunk.h instruction.h debug.h +chunk.o: chunk.c chunk.h instruction.h +compiler.o: compiler.c compiler.h chunk.h instruction.h +instruction.o: instruction.c instruction.h +debug.o: debug.c debug.h chunk.h instruction.h diff --git a/src/backends/vm/chunk.c b/src/backends/vm/chunk.c new file mode 100644 index 00000000..d661ed3c --- /dev/null +++ b/src/backends/vm/chunk.c @@ -0,0 +1,620 @@ +/* + * Hemlock Bytecode VM - Chunk Implementation + * + * Bytecode container with constant pool and debug info. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "chunk.h" +#include +#include +#include + +// Initial capacities +#define CHUNK_CODE_INITIAL 256 +#define CHUNK_CONST_INITIAL 64 +#define CHUNK_LINES_INITIAL 64 +#define BUILDER_LOCALS_INITIAL 32 +#define BUILDER_LOOPS_INITIAL 8 + +// ============================================ +// Chunk Lifecycle +// ============================================ + +Chunk* chunk_new(void) { + Chunk *chunk = malloc(sizeof(Chunk)); + if (!chunk) return NULL; + + chunk->code = malloc(CHUNK_CODE_INITIAL); + chunk->code_count = 0; + chunk->code_capacity = CHUNK_CODE_INITIAL; + + chunk->constants = malloc(sizeof(Constant) * CHUNK_CONST_INITIAL); + chunk->const_count = 0; + chunk->const_capacity = CHUNK_CONST_INITIAL; + + chunk->lines = malloc(sizeof(int) * CHUNK_LINES_INITIAL); + chunk->lines_count = 0; + chunk->lines_capacity = CHUNK_LINES_INITIAL; + + chunk->name = NULL; + chunk->arity = 0; + chunk->optional_count = 0; + chunk->has_rest_param = false; + chunk->is_async = false; + + chunk->upvalues = NULL; + chunk->upvalue_count = 0; + + chunk->param_types = NULL; + chunk->return_type = TYPE_ID_NULL; + + chunk->local_count = 0; + chunk->max_stack = 0; + + return chunk; +} + +void chunk_free(Chunk *chunk) { + if (!chunk) return; + + free(chunk->code); + + // Free constant pool + for (int i = 0; i < chunk->const_count; i++) { + Constant *c = &chunk->constants[i]; + if (c->type == CONST_STRING || c->type == CONST_IDENTIFIER) { + free(c->as.string.data); + } else if (c->type == CONST_FUNCTION) { + chunk_free(c->as.function); + } + } + free(chunk->constants); + + free(chunk->lines); + free(chunk->name); + free(chunk->upvalues); + free(chunk->param_types); + free(chunk); +} + +// ============================================ +// Bytecode Writing +// ============================================ + +static void ensure_code_capacity(Chunk *chunk, int needed) { + if (chunk->code_count + needed <= chunk->code_capacity) return; + + int new_capacity = chunk->code_capacity * 2; + while (new_capacity < chunk->code_count + needed) { + new_capacity *= 2; + } + chunk->code = realloc(chunk->code, new_capacity); + chunk->code_capacity = new_capacity; +} + +static void ensure_lines_capacity(Chunk *chunk) { + if (chunk->lines_count + 2 <= chunk->lines_capacity) return; + + chunk->lines_capacity *= 2; + chunk->lines = realloc(chunk->lines, sizeof(int) * chunk->lines_capacity); +} + +void chunk_write_byte(Chunk *chunk, uint8_t byte, int line) { + ensure_code_capacity(chunk, 1); + chunk->code[chunk->code_count++] = byte; + + // Run-length encode line numbers + // Format: [count, line, count, line, ...] + if (chunk->lines_count >= 2 && + chunk->lines[chunk->lines_count - 1] == line) { + // Same line as previous, increment count + chunk->lines[chunk->lines_count - 2]++; + } else { + // New line + ensure_lines_capacity(chunk); + chunk->lines[chunk->lines_count++] = 1; // count + chunk->lines[chunk->lines_count++] = line; // line number + } +} + +void chunk_write_short(Chunk *chunk, uint16_t value, int line) { + chunk_write_byte(chunk, (value >> 8) & 0xFF, line); + chunk_write_byte(chunk, value & 0xFF, line); +} + +int chunk_write_jump(Chunk *chunk, OpCode op, int line) { + chunk_write_byte(chunk, op, line); + chunk_write_byte(chunk, 0xFF, line); // Placeholder + chunk_write_byte(chunk, 0xFF, line); // Placeholder + return chunk->code_count - 2; // Return offset to patch +} + +void chunk_patch_jump(Chunk *chunk, int offset) { + // Calculate jump distance from after the jump instruction + int jump = chunk->code_count - offset - 2; + + if (jump > 0xFFFF) { + fprintf(stderr, "Error: Jump too large (%d bytes)\n", jump); + return; + } + + chunk->code[offset] = (jump >> 8) & 0xFF; + chunk->code[offset + 1] = jump & 0xFF; +} + +// Patch a loop (backward jump) +void chunk_patch_loop(Chunk *chunk, int loop_start) { + // +3 accounts for: 1 byte opcode + 2 byte offset that will be read before jumping + int offset = chunk->code_count - loop_start + 3; + + if (offset > 0xFFFF) { + fprintf(stderr, "Error: Loop too large (%d bytes)\n", offset); + return; + } + + chunk_write_byte(chunk, BC_LOOP, 0); + chunk_write_byte(chunk, (offset >> 8) & 0xFF, 0); + chunk_write_byte(chunk, offset & 0xFF, 0); +} + +// ============================================ +// Constant Pool +// ============================================ + +static int ensure_const_capacity(Chunk *chunk) { + if (chunk->const_count < chunk->const_capacity) { + return 1; + } + + chunk->const_capacity *= 2; + chunk->constants = realloc(chunk->constants, + sizeof(Constant) * chunk->const_capacity); + return chunk->constants != NULL; +} + +int chunk_add_constant(Chunk *chunk, Constant constant) { + ensure_const_capacity(chunk); + chunk->constants[chunk->const_count] = constant; + return chunk->const_count++; +} + +int chunk_add_i32(Chunk *chunk, int32_t value) { + // Check for existing constant + for (int i = 0; i < chunk->const_count; i++) { + Constant *c = &chunk->constants[i]; + if (c->type == CONST_I32 && c->as.i32 == value) { + return i; + } + } + + Constant c = {.type = CONST_I32, .as.i32 = value}; + return chunk_add_constant(chunk, c); +} + +int chunk_add_i64(Chunk *chunk, int64_t value) { + // Check for existing constant + for (int i = 0; i < chunk->const_count; i++) { + Constant *c = &chunk->constants[i]; + if (c->type == CONST_I64 && c->as.i64 == value) { + return i; + } + } + + Constant c = {.type = CONST_I64, .as.i64 = value}; + return chunk_add_constant(chunk, c); +} + +int chunk_add_f64(Chunk *chunk, double value) { + // Check for existing constant (careful with NaN comparison) + for (int i = 0; i < chunk->const_count; i++) { + Constant *c = &chunk->constants[i]; + if (c->type == CONST_F64 && c->as.f64 == value) { + return i; + } + } + + Constant c = {.type = CONST_F64, .as.f64 = value}; + return chunk_add_constant(chunk, c); +} + +// Simple hash function for string interning +static uint32_t hash_string(const char *str, int length) { + uint32_t hash = 2166136261u; + for (int i = 0; i < length; i++) { + hash ^= (uint8_t)str[i]; + hash *= 16777619; + } + return hash; +} + +int chunk_add_string(Chunk *chunk, const char *str, int length) { + uint32_t hash = hash_string(str, length); + + // Check for existing constant (string interning) + for (int i = 0; i < chunk->const_count; i++) { + Constant *c = &chunk->constants[i]; + if (c->type == CONST_STRING && + c->as.string.hash == hash && + c->as.string.length == length && + memcmp(c->as.string.data, str, length) == 0) { + return i; + } + } + + // Add new string + char *copy = malloc(length + 1); + memcpy(copy, str, length); + copy[length] = '\0'; + + Constant c = { + .type = CONST_STRING, + .as.string = {.data = copy, .length = length, .hash = hash} + }; + return chunk_add_constant(chunk, c); +} + +int chunk_add_function(Chunk *chunk, Chunk *function) { + Constant c = {.type = CONST_FUNCTION, .as.function = function}; + return chunk_add_constant(chunk, c); +} + +int chunk_add_identifier(Chunk *chunk, const char *name) { + int length = strlen(name); + uint32_t hash = hash_string(name, length); + + // Check for existing identifier + for (int i = 0; i < chunk->const_count; i++) { + Constant *c = &chunk->constants[i]; + if (c->type == CONST_IDENTIFIER && + c->as.string.hash == hash && + c->as.string.length == length && + memcmp(c->as.string.data, name, length) == 0) { + return i; + } + } + + // Add new identifier + char *copy = malloc(length + 1); + memcpy(copy, name, length); + copy[length] = '\0'; + + Constant c = { + .type = CONST_IDENTIFIER, + .as.string = {.data = copy, .length = length, .hash = hash} + }; + return chunk_add_constant(chunk, c); +} + +// ============================================ +// Query Functions +// ============================================ + +int chunk_get_line(Chunk *chunk, int offset) { + // Decode run-length encoded lines + int current_offset = 0; + for (int i = 0; i < chunk->lines_count; i += 2) { + int count = chunk->lines[i]; + int line = chunk->lines[i + 1]; + current_offset += count; + if (current_offset > offset) { + return line; + } + } + return 0; // Unknown line +} + +Constant* chunk_get_constant(Chunk *chunk, int index) { + if (index < 0 || index >= chunk->const_count) { + return NULL; + } + return &chunk->constants[index]; +} + +// ============================================ +// Chunk Builder +// ============================================ + +ChunkBuilder* chunk_builder_new(ChunkBuilder *enclosing) { + ChunkBuilder *builder = malloc(sizeof(ChunkBuilder)); + if (!builder) return NULL; + + builder->chunk = chunk_new(); + if (!builder->chunk) { + free(builder); + return NULL; + } + + builder->locals = malloc(sizeof(*builder->locals) * BUILDER_LOCALS_INITIAL); + builder->local_count = 0; + builder->local_capacity = BUILDER_LOCALS_INITIAL; + builder->max_local_count = 0; + builder->scope_depth = 0; + + builder->upvalues = NULL; + builder->upvalue_count = 0; + + builder->loops = malloc(sizeof(*builder->loops) * BUILDER_LOOPS_INITIAL); + builder->loop_count = 0; + builder->loop_capacity = BUILDER_LOOPS_INITIAL; + + builder->enclosing = enclosing; + + return builder; +} + +void chunk_builder_free(ChunkBuilder *builder) { + if (!builder) return; + + // Free local names + for (int i = 0; i < builder->local_count; i++) { + free(builder->locals[i].name); + } + free(builder->locals); + + // Free loop break lists + for (int i = 0; i < builder->loop_count; i++) { + free(builder->loops[i].breaks); + } + free(builder->loops); + + free(builder->upvalues); + free(builder); +} + +Chunk* chunk_builder_finish(ChunkBuilder *builder) { + Chunk *chunk = builder->chunk; + + // Transfer upvalue info + if (builder->upvalue_count > 0) { + chunk->upvalues = malloc(sizeof(UpvalueDesc) * builder->upvalue_count); + memcpy(chunk->upvalues, builder->upvalues, + sizeof(UpvalueDesc) * builder->upvalue_count); + chunk->upvalue_count = builder->upvalue_count; + } + + // Use max_local_count to ensure all nested scopes have enough slots + chunk->local_count = builder->max_local_count; + + // Don't free the chunk, we're returning it + builder->chunk = NULL; + chunk_builder_free(builder); + + return chunk; +} + +// ============================================ +// Scope Management +// ============================================ + +void builder_begin_scope(ChunkBuilder *builder) { + builder->scope_depth++; +} + +void builder_end_scope(ChunkBuilder *builder) { + builder->scope_depth--; + + // Handle locals going out of scope + // Note: Locals are stored in slots via BC_SET_LOCAL, so we don't pop them. + // We only need to close upvalues for captured locals. + while (builder->local_count > 0 && + builder->locals[builder->local_count - 1].depth > builder->scope_depth) { + int slot = builder->local_count - 1; + // Close upvalues if captured - emit slot index so VM knows where to close + if (builder->locals[slot].is_captured) { + chunk_write_byte(builder->chunk, BC_CLOSE_UPVALUE, 0); + chunk_write_byte(builder->chunk, (uint8_t)slot, 0); + } + // Don't emit BC_POP - locals are in slots, not on expression stack + builder->local_count--; + } +} + +// ============================================ +// Local Variable Management +// ============================================ + +static void ensure_locals_capacity(ChunkBuilder *builder) { + if (builder->local_count < builder->local_capacity) return; + + builder->local_capacity *= 2; + builder->locals = realloc(builder->locals, + sizeof(*builder->locals) * builder->local_capacity); +} + +int builder_declare_local(ChunkBuilder *builder, const char *name, bool is_const, TypeId type) { + // Check for duplicate in current scope + for (int i = builder->local_count - 1; i >= 0; i--) { + if (builder->locals[i].depth < builder->scope_depth) break; + if (strcmp(builder->locals[i].name, name) == 0) { + return -1; // Duplicate + } + } + + ensure_locals_capacity(builder); + + int slot = builder->local_count; + builder->locals[slot].name = strdup(name); + builder->locals[slot].depth = builder->scope_depth; + builder->locals[slot].is_captured = false; + builder->locals[slot].is_const = is_const; + builder->locals[slot].type = type; + builder->local_count++; + + // Track maximum for slot allocation + if (builder->local_count > builder->max_local_count) { + builder->max_local_count = builder->local_count; + } + + return slot; +} + +int builder_resolve_local(ChunkBuilder *builder, const char *name) { + for (int i = builder->local_count - 1; i >= 0; i--) { + if (strcmp(builder->locals[i].name, name) == 0) { + return i; + } + } + return -1; // Not found +} + +static int add_upvalue(ChunkBuilder *builder, uint8_t index, bool is_local) { + // Check for existing upvalue + for (int i = 0; i < builder->upvalue_count; i++) { + if (builder->upvalues[i].index == index && + builder->upvalues[i].is_local == is_local) { + return i; + } + } + + // Add new upvalue + if (builder->upvalues == NULL) { + builder->upvalues = malloc(sizeof(UpvalueDesc) * 8); + } else if (builder->upvalue_count >= 8) { + // Grow capacity + builder->upvalues = realloc(builder->upvalues, + sizeof(UpvalueDesc) * builder->upvalue_count * 2); + } + + builder->upvalues[builder->upvalue_count].index = index; + builder->upvalues[builder->upvalue_count].is_local = is_local; + return builder->upvalue_count++; +} + +int builder_resolve_upvalue(ChunkBuilder *builder, const char *name) { + if (builder->enclosing == NULL) return -1; + + // Look in enclosing function's locals + int local = builder_resolve_local(builder->enclosing, name); + if (local != -1) { + builder->enclosing->locals[local].is_captured = true; + return add_upvalue(builder, (uint8_t)local, true); + } + + // Look in enclosing function's upvalues + int upvalue = builder_resolve_upvalue(builder->enclosing, name); + if (upvalue != -1) { + return add_upvalue(builder, (uint8_t)upvalue, false); + } + + return -1; +} + +void builder_mark_initialized(ChunkBuilder *builder) { + // The most recent local is now initialized + // (For now, we don't track uninitialized state) +} + +// ============================================ +// Loop Management +// ============================================ + +void builder_begin_loop(ChunkBuilder *builder) { + if (builder->loop_count >= builder->loop_capacity) { + builder->loop_capacity *= 2; + builder->loops = realloc(builder->loops, + sizeof(*builder->loops) * builder->loop_capacity); + } + + int i = builder->loop_count++; + builder->loops[i].start = builder->chunk->code_count; + builder->loops[i].continue_target = -1; // Will be set by for-loops + builder->loops[i].scope_depth = builder->scope_depth; + builder->loops[i].breaks = malloc(sizeof(int) * 8); + builder->loops[i].break_count = 0; + builder->loops[i].break_capacity = 8; + builder->loops[i].continues = malloc(sizeof(int) * 8); + builder->loops[i].continue_count = 0; + builder->loops[i].continue_capacity = 8; +} + +void builder_end_loop(ChunkBuilder *builder) { + if (builder->loop_count == 0) return; + + int i = builder->loop_count - 1; + + // Patch all break jumps + for (int j = 0; j < builder->loops[i].break_count; j++) { + chunk_patch_jump(builder->chunk, builder->loops[i].breaks[j]); + } + + // Note: continues are patched immediately when continue_target is set + // (they're forward jumps that get patched by builder_set_continue_target) + + free(builder->loops[i].breaks); + free(builder->loops[i].continues); + builder->loop_count--; +} + +void builder_emit_break(ChunkBuilder *builder) { + if (builder->loop_count == 0) return; + + int i = builder->loop_count - 1; + + // Close upvalues for captured locals going out of scope + // Note: Non-captured locals are in slots, not on expression stack, so no POP needed + int depth = builder->loops[i].scope_depth; + for (int j = builder->local_count - 1; j >= 0 && builder->locals[j].depth > depth; j--) { + if (builder->locals[j].is_captured) { + chunk_write_byte(builder->chunk, BC_CLOSE_UPVALUE, 0); + chunk_write_byte(builder->chunk, (uint8_t)j, 0); + } + // Don't emit BC_POP - locals are in slots, not on expression stack + } + + // Emit break as jump (to be patched) + if (builder->loops[i].break_count >= builder->loops[i].break_capacity) { + builder->loops[i].break_capacity *= 2; + builder->loops[i].breaks = realloc(builder->loops[i].breaks, + sizeof(int) * builder->loops[i].break_capacity); + } + int offset = chunk_write_jump(builder->chunk, BC_JUMP, 0); + builder->loops[i].breaks[builder->loops[i].break_count++] = offset; +} + +void builder_emit_continue(ChunkBuilder *builder) { + if (builder->loop_count == 0) return; + + int i = builder->loop_count - 1; + + // Close upvalues for captured locals going out of scope + // Note: Non-captured locals are in slots, not on expression stack, so no POP needed + int depth = builder->loops[i].scope_depth; + for (int j = builder->local_count - 1; j >= 0 && builder->locals[j].depth > depth; j--) { + if (builder->locals[j].is_captured) { + chunk_write_byte(builder->chunk, BC_CLOSE_UPVALUE, 0); + chunk_write_byte(builder->chunk, (uint8_t)j, 0); + } + // Don't emit BC_POP - locals are in slots, not on expression stack + } + + if (builder->loops[i].continue_target >= 0) { + // We already know the continue target (while loops, or after increment in for-loops) + chunk_patch_loop(builder->chunk, builder->loops[i].continue_target); + } else { + // For-loop: continue target not yet known, emit forward jump to be patched later + if (builder->loops[i].continue_count >= builder->loops[i].continue_capacity) { + builder->loops[i].continue_capacity *= 2; + builder->loops[i].continues = realloc(builder->loops[i].continues, + sizeof(int) * builder->loops[i].continue_capacity); + } + int offset = chunk_write_jump(builder->chunk, BC_JUMP, 0); + builder->loops[i].continues[builder->loops[i].continue_count++] = offset; + } +} + +void builder_set_continue_target(ChunkBuilder *builder) { + if (builder->loop_count == 0) return; + + int i = builder->loop_count - 1; + + // Record current position as continue target + builder->loops[i].continue_target = builder->chunk->code_count; + + // Patch all pending continue jumps to point here + for (int j = 0; j < builder->loops[i].continue_count; j++) { + chunk_patch_jump(builder->chunk, builder->loops[i].continues[j]); + } + builder->loops[i].continue_count = 0; +} diff --git a/src/backends/vm/chunk.h b/src/backends/vm/chunk.h new file mode 100644 index 00000000..cd9a0162 --- /dev/null +++ b/src/backends/vm/chunk.h @@ -0,0 +1,208 @@ +/* + * Hemlock Bytecode VM - Chunk (Bytecode Container) + * + * A Chunk represents a compiled unit of bytecode (function, script, etc.) + * with its constant pool, debug info, and metadata. + */ + +#ifndef HEMLOCK_VM_CHUNK_H +#define HEMLOCK_VM_CHUNK_H + +#include +#include +#include "instruction.h" + +// Forward declarations +typedef struct Value Value; +typedef struct Chunk Chunk; + +// ============================================ +// Constant Pool Entry +// ============================================ + +typedef enum { + CONST_I32, // 32-bit signed integer + CONST_I64, // 64-bit signed integer + CONST_F64, // 64-bit float + CONST_RUNE, // Unicode codepoint (u32) + CONST_STRING, // Interned string + CONST_FUNCTION, // Compiled function (Chunk*) + CONST_IDENTIFIER, // Variable/property name +} ConstantType; + +typedef struct { + ConstantType type; + union { + int32_t i32; + int64_t i64; + double f64; + uint32_t rune; + struct { + char *data; + int length; + uint32_t hash; // For fast comparison + } string; + Chunk *function; + } as; +} Constant; + +// ============================================ +// Upvalue Description (for closures) +// ============================================ + +typedef struct { + uint8_t index; // Index in enclosing scope + bool is_local; // true = local in enclosing, false = upvalue of enclosing +} UpvalueDesc; + +// ============================================ +// Chunk Structure +// ============================================ + +struct Chunk { + // Bytecode + uint8_t *code; + int code_count; + int code_capacity; + + // Constant pool + Constant *constants; + int const_count; + int const_capacity; + + // Line number info (for error messages) + // Run-length encoded: [count, line, count, line, ...] + int *lines; + int lines_count; + int lines_capacity; + + // Function metadata + char *name; // Function name (NULL for script) + int arity; // Required parameter count + int optional_count; // Optional parameter count + bool has_rest_param; // Has ...rest parameter + bool is_async; // Is async function + + // Closure info + UpvalueDesc *upvalues; + int upvalue_count; + + // Type annotations (optional, for runtime checks) + TypeId *param_types; // Array of parameter types (NULL = any) + TypeId return_type; // Return type (TYPE_ID_NULL = any) + + // Scope info for locals + int local_count; // Number of local variable slots needed + int max_stack; // Maximum stack depth needed +}; + +// ============================================ +// Chunk Operations +// ============================================ + +// Create/destroy +Chunk* chunk_new(void); +void chunk_free(Chunk *chunk); + +// Write bytecode +void chunk_write_byte(Chunk *chunk, uint8_t byte, int line); +void chunk_write_short(Chunk *chunk, uint16_t value, int line); +int chunk_write_jump(Chunk *chunk, OpCode op, int line); // Returns offset for patching +void chunk_patch_jump(Chunk *chunk, int offset); + +// Add constants (returns index) +int chunk_add_constant(Chunk *chunk, Constant constant); +int chunk_add_i32(Chunk *chunk, int32_t value); +int chunk_add_i64(Chunk *chunk, int64_t value); +int chunk_add_f64(Chunk *chunk, double value); +int chunk_add_string(Chunk *chunk, const char *str, int length); +int chunk_add_function(Chunk *chunk, Chunk *function); +int chunk_add_identifier(Chunk *chunk, const char *name); + +// Query +int chunk_get_line(Chunk *chunk, int offset); +Constant* chunk_get_constant(Chunk *chunk, int index); + +// ============================================ +// Debug/Disassembly +// ============================================ + +void chunk_disassemble(Chunk *chunk, const char *name); +int chunk_disassemble_instruction(Chunk *chunk, int offset); + +// ============================================ +// Serialization (for caching compiled bytecode) +// ============================================ + +// Write chunk to binary format +int chunk_serialize(Chunk *chunk, uint8_t **out_data, size_t *out_size); + +// Read chunk from binary format +Chunk* chunk_deserialize(const uint8_t *data, size_t size); + +// ============================================ +// Chunk Builder (for compiler) +// ============================================ + +typedef struct ChunkBuilder { + Chunk *chunk; + + // Current scope tracking + struct { + char *name; + int depth; + bool is_captured; // Captured by closure + bool is_const; // Is const variable + TypeId type; // Declared type (or TYPE_ID_NULL) + } *locals; + int local_count; + int local_capacity; + int max_local_count; // Maximum local_count ever reached (for slot allocation) + int scope_depth; + + // Upvalue tracking + UpvalueDesc *upvalues; + int upvalue_count; + + // Loop tracking (for break/continue) + struct { + int start; // Loop start offset (condition check) + int continue_target;// Continue target (-1 = use start, set for for-loops) + int scope_depth; // Scope depth at loop start + int *breaks; // Break jump offsets to patch + int break_count; + int break_capacity; + int *continues; // Continue jump offsets to patch (for for-loops) + int continue_count; + int continue_capacity; + } *loops; + int loop_count; + int loop_capacity; + + // Enclosing function (for closures) + struct ChunkBuilder *enclosing; + +} ChunkBuilder; + +ChunkBuilder* chunk_builder_new(ChunkBuilder *enclosing); +void chunk_builder_free(ChunkBuilder *builder); +Chunk* chunk_builder_finish(ChunkBuilder *builder); + +// Scope management +void builder_begin_scope(ChunkBuilder *builder); +void builder_end_scope(ChunkBuilder *builder); + +// Local variable management +int builder_declare_local(ChunkBuilder *builder, const char *name, bool is_const, TypeId type); +int builder_resolve_local(ChunkBuilder *builder, const char *name); +int builder_resolve_upvalue(ChunkBuilder *builder, const char *name); +void builder_mark_initialized(ChunkBuilder *builder); + +// Loop management +void builder_begin_loop(ChunkBuilder *builder); +void builder_end_loop(ChunkBuilder *builder); +void builder_emit_break(ChunkBuilder *builder); +void builder_emit_continue(ChunkBuilder *builder); +void builder_set_continue_target(ChunkBuilder *builder); // Mark current position as continue target + +#endif // HEMLOCK_VM_CHUNK_H diff --git a/src/backends/vm/compiler.c b/src/backends/vm/compiler.c new file mode 100644 index 00000000..12e77b09 --- /dev/null +++ b/src/backends/vm/compiler.c @@ -0,0 +1,1657 @@ +/* + * Hemlock Bytecode VM - Compiler Implementation + * + * AST to bytecode compilation. + */ + +#include "compiler.h" +#include "instruction.h" +#include +#include +#include + +// Convert AST TypeKind to VM TypeId +static int type_kind_to_id(TypeKind kind) { + switch (kind) { + case TYPE_I8: return TYPE_ID_I8; + case TYPE_I16: return TYPE_ID_I16; + case TYPE_I32: return TYPE_ID_I32; + case TYPE_I64: return TYPE_ID_I64; + case TYPE_U8: return TYPE_ID_U8; + case TYPE_U16: return TYPE_ID_U16; + case TYPE_U32: return TYPE_ID_U32; + case TYPE_U64: return TYPE_ID_U64; + case TYPE_F32: return TYPE_ID_F32; + case TYPE_F64: return TYPE_ID_F64; + case TYPE_BOOL: return TYPE_ID_BOOL; + case TYPE_STRING: return TYPE_ID_STRING; + case TYPE_RUNE: return TYPE_ID_RUNE; + case TYPE_ARRAY: return TYPE_ID_ARRAY; + case TYPE_NULL: return TYPE_ID_NULL; + default: return -1; // Unknown/unsupported type + } +} + +// ============================================ +// Compiler Lifecycle +// ============================================ + +static Compiler* compiler_new(Compiler *enclosing) { + Compiler *compiler = malloc(sizeof(Compiler)); + compiler->builder = chunk_builder_new(enclosing ? enclosing->builder : NULL); + compiler->enclosing = enclosing; + compiler->function_name = NULL; + compiler->is_async = false; + compiler->current_line = 1; + compiler->had_error = false; + compiler->panic_mode = false; + // Inherit defined globals from enclosing compiler, or start fresh at root + if (enclosing) { + // Nested function: share parent's globals tracking + compiler->defined_globals = enclosing->defined_globals; + compiler->num_defined_globals = enclosing->num_defined_globals; + compiler->defined_globals_capacity = enclosing->defined_globals_capacity; + } else { + // Root compiler: initialize fresh + compiler->defined_globals = NULL; + compiler->num_defined_globals = 0; + compiler->defined_globals_capacity = 0; + } + return compiler; +} + +// Track a defined global name +static void compiler_add_defined_global(Compiler *compiler, const char *name) { + // Find root compiler + Compiler *root = compiler; + while (root->enclosing) { + root = root->enclosing; + } + + // Check if already defined + for (int i = 0; i < root->num_defined_globals; i++) { + if (strcmp(root->defined_globals[i], name) == 0) { + return; // Already tracked + } + } + + // Expand capacity if needed + if (root->num_defined_globals >= root->defined_globals_capacity) { + int new_capacity = root->defined_globals_capacity == 0 ? 16 : root->defined_globals_capacity * 2; + root->defined_globals = realloc(root->defined_globals, sizeof(char*) * new_capacity); + root->defined_globals_capacity = new_capacity; + } + + // Add the name + root->defined_globals[root->num_defined_globals++] = strdup(name); +} + +// Check if a name is a defined global +static bool compiler_is_defined_global(Compiler *compiler, const char *name) { + // Find root compiler + Compiler *root = compiler; + while (root->enclosing) { + root = root->enclosing; + } + + for (int i = 0; i < root->num_defined_globals; i++) { + if (strcmp(root->defined_globals[i], name) == 0) { + return true; + } + } + return false; +} + +static void compiler_free(Compiler *compiler) { + if (compiler->builder) { + chunk_builder_free(compiler->builder); + } + free(compiler->function_name); + // Only free defined_globals at root level + if (!compiler->enclosing && compiler->defined_globals) { + for (int i = 0; i < compiler->num_defined_globals; i++) { + free(compiler->defined_globals[i]); + } + free(compiler->defined_globals); + } + free(compiler); +} + +// ============================================ +// Error Handling +// ============================================ + +void compiler_error(Compiler *compiler, const char *message) { + compiler_error_at(compiler, compiler->current_line, message); +} + +void compiler_error_at(Compiler *compiler, int line, const char *message) { + if (compiler->panic_mode) return; + compiler->panic_mode = true; + compiler->had_error = true; + fprintf(stderr, "[line %d] Error: %s\n", line, message); +} + +// ============================================ +// Bytecode Emission Helpers +// ============================================ + +static void emit_byte(Compiler *compiler, uint8_t byte) { + chunk_write_byte(compiler->builder->chunk, byte, compiler->current_line); +} + +static void emit_bytes(Compiler *compiler, uint8_t b1, uint8_t b2) { + emit_byte(compiler, b1); + emit_byte(compiler, b2); +} + +static void emit_short(Compiler *compiler, uint16_t value) { + chunk_write_short(compiler->builder->chunk, value, compiler->current_line); +} + +static int emit_jump(Compiler *compiler, OpCode op) { + return chunk_write_jump(compiler->builder->chunk, op, compiler->current_line); +} + +static void patch_jump(Compiler *compiler, int offset) { + chunk_patch_jump(compiler->builder->chunk, offset); +} + +static void emit_loop(Compiler *compiler, int loop_start) { + int offset = compiler->builder->chunk->code_count - loop_start + 3; + if (offset > 0xFFFF) { + compiler_error(compiler, "Loop body too large"); + return; + } + emit_byte(compiler, BC_LOOP); + emit_byte(compiler, (offset >> 8) & 0xFF); + emit_byte(compiler, offset & 0xFF); +} + +static int make_constant(Compiler *compiler, Constant constant) { + int idx = chunk_add_constant(compiler->builder->chunk, constant); + if (idx > 0xFFFF) { + compiler_error(compiler, "Too many constants in one chunk"); + return 0; + } + return idx; +} + +static void emit_constant(Compiler *compiler, Constant constant) { + int idx = make_constant(compiler, constant); + emit_byte(compiler, BC_CONST); + emit_short(compiler, idx); +} + +// ============================================ +// Expression Compilation +// ============================================ + +// Forward declarations +static void compile_expression(Compiler *compiler, Expr *expr); +static void compile_statement(Compiler *compiler, Stmt *stmt); + +static void compile_number(Compiler *compiler, Expr *expr) { + // Check if we can use a byte constant (integer 0-255) + if (!expr->as.number.is_float && expr->as.number.int_value >= 0 && expr->as.number.int_value <= 255) { + emit_byte(compiler, BC_CONST_BYTE); + emit_byte(compiler, (uint8_t)expr->as.number.int_value); + return; + } + + Constant c; + if (!expr->as.number.is_float) { + if (expr->as.number.int_value >= INT32_MIN && expr->as.number.int_value <= INT32_MAX) { + c.type = CONST_I32; + c.as.i32 = (int32_t)expr->as.number.int_value; + } else { + c.type = CONST_I64; + c.as.i64 = expr->as.number.int_value; + } + } else { + c.type = CONST_F64; + c.as.f64 = expr->as.number.float_value; + } + emit_constant(compiler, c); +} + +static void compile_bool(Compiler *compiler, Expr *expr) { + emit_byte(compiler, expr->as.boolean ? BC_TRUE : BC_FALSE); +} + +static void compile_null(Compiler *compiler) { + emit_byte(compiler, BC_NULL); +} + +static void compile_string(Compiler *compiler, Expr *expr) { + const char *str = expr->as.string; + int len = strlen(str); + int idx = chunk_add_string(compiler->builder->chunk, str, len); + emit_byte(compiler, BC_CONST); + emit_short(compiler, idx); +} + +static void compile_rune(Compiler *compiler, Expr *expr) { + // Runes are stored as rune constants (u32 codepoint) + Constant c = {.type = CONST_RUNE, .as.rune = expr->as.rune}; + int idx = make_constant(compiler, c); + emit_byte(compiler, BC_CONST); + emit_short(compiler, idx); +} + +static void compile_identifier(Compiler *compiler, Expr *expr) { + const char *name = expr->as.ident.name; + + // Special handling for 'self' - get method receiver + if (strcmp(name, "self") == 0) { + emit_byte(compiler, BC_GET_SELF); + return; + } + + // Check for resolved local + if (expr->as.ident.resolved.is_resolved) { + int depth = expr->as.ident.resolved.depth; + int slot = expr->as.ident.resolved.slot; + + if (depth == 0) { + emit_byte(compiler, BC_GET_LOCAL); + emit_byte(compiler, (uint8_t)slot); + } else { + // Upvalue + int upvalue = builder_resolve_upvalue(compiler->builder, name); + if (upvalue != -1) { + emit_byte(compiler, BC_GET_UPVALUE); + emit_byte(compiler, (uint8_t)upvalue); + } else { + compiler_error(compiler, "Cannot resolve variable"); + } + } + return; + } + + // Try local + int local = builder_resolve_local(compiler->builder, name); + if (local != -1) { + emit_byte(compiler, BC_GET_LOCAL); + emit_byte(compiler, (uint8_t)local); + return; + } + + // Try upvalue + int upvalue = builder_resolve_upvalue(compiler->builder, name); + if (upvalue != -1) { + emit_byte(compiler, BC_GET_UPVALUE); + emit_byte(compiler, (uint8_t)upvalue); + return; + } + + // Global + int idx = chunk_add_identifier(compiler->builder->chunk, name); + emit_byte(compiler, BC_GET_GLOBAL); + emit_short(compiler, idx); +} + +static void compile_binary(Compiler *compiler, Expr *expr) { + BinaryOp op = expr->as.binary.op; + + // Short-circuit operators need special handling + if (op == OP_AND) { + compile_expression(compiler, expr->as.binary.left); + int end_jump = emit_jump(compiler, BC_JUMP_IF_FALSE); + emit_byte(compiler, BC_POP); + compile_expression(compiler, expr->as.binary.right); + patch_jump(compiler, end_jump); + return; + } + + if (op == OP_OR) { + compile_expression(compiler, expr->as.binary.left); + int else_jump = emit_jump(compiler, BC_JUMP_IF_FALSE); + int end_jump = emit_jump(compiler, BC_JUMP); + patch_jump(compiler, else_jump); + emit_byte(compiler, BC_POP); + compile_expression(compiler, expr->as.binary.right); + patch_jump(compiler, end_jump); + return; + } + + // Regular binary ops: compile both operands, then operator + compile_expression(compiler, expr->as.binary.left); + compile_expression(compiler, expr->as.binary.right); + + switch (op) { + case OP_ADD: emit_byte(compiler, BC_ADD); break; + case OP_SUB: emit_byte(compiler, BC_SUB); break; + case OP_MUL: emit_byte(compiler, BC_MUL); break; + case OP_DIV: emit_byte(compiler, BC_DIV); break; + case OP_MOD: emit_byte(compiler, BC_MOD); break; + + case OP_EQUAL: emit_byte(compiler, BC_EQ); break; + case OP_NOT_EQUAL: emit_byte(compiler, BC_NE); break; + case OP_LESS: emit_byte(compiler, BC_LT); break; + case OP_LESS_EQUAL: emit_byte(compiler, BC_LE); break; + case OP_GREATER: emit_byte(compiler, BC_GT); break; + case OP_GREATER_EQUAL: emit_byte(compiler, BC_GE); break; + + case OP_BIT_AND: emit_byte(compiler, BC_BIT_AND); break; + case OP_BIT_OR: emit_byte(compiler, BC_BIT_OR); break; + case OP_BIT_XOR: emit_byte(compiler, BC_BIT_XOR); break; + case OP_BIT_LSHIFT: emit_byte(compiler, BC_LSHIFT); break; + case OP_BIT_RSHIFT: emit_byte(compiler, BC_RSHIFT); break; + + default: + compiler_error(compiler, "Unknown binary operator"); + } +} + +static void compile_unary(Compiler *compiler, Expr *expr) { + compile_expression(compiler, expr->as.unary.operand); + + switch (expr->as.unary.op) { + case UNARY_NEGATE: + emit_byte(compiler, BC_NEGATE); + break; + case UNARY_NOT: + emit_byte(compiler, BC_NOT); + break; + case UNARY_BIT_NOT: + emit_byte(compiler, BC_BIT_NOT); + break; + default: + compiler_error(compiler, "Unknown unary operator"); + } +} + +static void compile_ternary(Compiler *compiler, Expr *expr) { + compile_expression(compiler, expr->as.ternary.condition); + + int then_jump = emit_jump(compiler, BC_JUMP_IF_FALSE); + emit_byte(compiler, BC_POP); // Pop condition + + compile_expression(compiler, expr->as.ternary.true_expr); + + int else_jump = emit_jump(compiler, BC_JUMP); + patch_jump(compiler, then_jump); + emit_byte(compiler, BC_POP); // Pop condition + + compile_expression(compiler, expr->as.ternary.false_expr); + + patch_jump(compiler, else_jump); +} + +static void compile_assign(Compiler *compiler, Expr *expr) { + const char *name = expr->as.assign.name; + + // Compile the value first + compile_expression(compiler, expr->as.assign.value); + + // Check for resolved local + if (expr->as.assign.resolved.is_resolved) { + int depth = expr->as.assign.resolved.depth; + int slot = expr->as.assign.resolved.slot; + + if (depth == 0) { + emit_byte(compiler, BC_SET_LOCAL); + emit_byte(compiler, (uint8_t)slot); + } else { + int upvalue = builder_resolve_upvalue(compiler->builder, name); + emit_byte(compiler, BC_SET_UPVALUE); + emit_byte(compiler, (uint8_t)upvalue); + } + return; + } + + // Try local + int local = builder_resolve_local(compiler->builder, name); + if (local != -1) { + emit_byte(compiler, BC_SET_LOCAL); + emit_byte(compiler, (uint8_t)local); + return; + } + + // Try upvalue + int upvalue = builder_resolve_upvalue(compiler->builder, name); + if (upvalue != -1) { + emit_byte(compiler, BC_SET_UPVALUE); + emit_byte(compiler, (uint8_t)upvalue); + return; + } + + // Global + int idx = chunk_add_identifier(compiler->builder->chunk, name); + emit_byte(compiler, BC_SET_GLOBAL); + emit_short(compiler, idx); +} + +static void compile_call(Compiler *compiler, Expr *expr) { + Expr *func = expr->as.call.func; + int argc = expr->as.call.num_args; + + // Check for builtin calls - but only if there's no local/upvalue/global shadowing it + if (func->type == EXPR_IDENT) { + const char *name = func->as.ident.name; + + // First check if it's a local or upvalue (which would shadow a builtin) + int local = builder_resolve_local(compiler->builder, name); + int upvalue = (local == -1) ? builder_resolve_upvalue(compiler->builder, name) : -1; + bool is_defined_global = compiler_is_defined_global(compiler, name); + + // Only treat as builtin if there's no local/upvalue/global with this name + if (local == -1 && upvalue == -1 && !is_defined_global) { + // Check if it's a builtin + BuiltinId builtin = builtin_lookup(name); + if (builtin != (BuiltinId)-1) { + // Compile arguments + for (int i = 0; i < argc; i++) { + compile_expression(compiler, expr->as.call.args[i]); + } + + // Special case for print (it has its own opcode) + if (builtin == BUILTIN_PRINT) { + emit_byte(compiler, BC_PRINT); + emit_byte(compiler, (uint8_t)argc); + return; + } + + // General builtin call + emit_byte(compiler, BC_CALL_BUILTIN); + emit_short(compiler, (uint16_t)builtin); + emit_byte(compiler, (uint8_t)argc); + return; + } + } + } + + // Check for method call: obj.method(args) + if (func->type == EXPR_GET_PROPERTY) { + // Compile the receiver object + compile_expression(compiler, func->as.get_property.object); + + // Compile arguments + for (int i = 0; i < argc; i++) { + compile_expression(compiler, expr->as.call.args[i]); + } + + // Emit method call with method name + emit_byte(compiler, BC_CALL_METHOD); + const char *method = func->as.get_property.property; + int idx = chunk_add_string(compiler->builder->chunk, method, strlen(method)); + emit_short(compiler, idx); + emit_byte(compiler, (uint8_t)argc); + return; + } + + // General function call + compile_expression(compiler, func); + + // Compile arguments + for (int i = 0; i < argc; i++) { + compile_expression(compiler, expr->as.call.args[i]); + } + + emit_byte(compiler, BC_CALL); + emit_byte(compiler, (uint8_t)argc); +} + +static void compile_array_literal(Compiler *compiler, Expr *expr) { + int count = expr->as.array_literal.num_elements; + + // Compile each element + for (int i = 0; i < count; i++) { + compile_expression(compiler, expr->as.array_literal.elements[i]); + } + + emit_byte(compiler, BC_ARRAY); + emit_short(compiler, (uint16_t)count); +} + +static void compile_object_literal(Compiler *compiler, Expr *expr) { + int count = expr->as.object_literal.num_fields; + + // Compile each key-value pair + for (int i = 0; i < count; i++) { + // Key as string constant + const char *key = expr->as.object_literal.field_names[i]; + int idx = chunk_add_string(compiler->builder->chunk, key, strlen(key)); + emit_byte(compiler, BC_CONST); + emit_short(compiler, idx); + + // Value + compile_expression(compiler, expr->as.object_literal.field_values[i]); + } + + emit_byte(compiler, BC_OBJECT); + emit_short(compiler, (uint16_t)count); +} + +static void compile_get_property(Compiler *compiler, Expr *expr) { + compile_expression(compiler, expr->as.get_property.object); + + const char *name = expr->as.get_property.property; + int idx = chunk_add_identifier(compiler->builder->chunk, name); + emit_byte(compiler, BC_GET_PROPERTY); + emit_short(compiler, idx); +} + +static void compile_set_property(Compiler *compiler, Expr *expr) { + compile_expression(compiler, expr->as.set_property.object); + compile_expression(compiler, expr->as.set_property.value); + + const char *name = expr->as.set_property.property; + int idx = chunk_add_identifier(compiler->builder->chunk, name); + emit_byte(compiler, BC_SET_PROPERTY); + emit_short(compiler, idx); +} + +static void compile_index(Compiler *compiler, Expr *expr) { + compile_expression(compiler, expr->as.index.object); + compile_expression(compiler, expr->as.index.index); + emit_byte(compiler, BC_GET_INDEX); +} + +static void compile_index_assign(Compiler *compiler, Expr *expr) { + compile_expression(compiler, expr->as.index_assign.object); + compile_expression(compiler, expr->as.index_assign.index); + compile_expression(compiler, expr->as.index_assign.value); + emit_byte(compiler, BC_SET_INDEX); +} + +// Helper to compile increment/decrement for different operand types +// is_increment: true for ++, false for -- +// is_prefix: true for ++a/--a (return new value), false for a++/a-- (return old value) +static void compile_inc_dec(Compiler *compiler, Expr *operand, bool is_increment, bool is_prefix) { + if (operand->type == EXPR_IDENT) { + const char *name = operand->as.ident.name; + int slot = builder_resolve_local(compiler->builder, name); + + if (slot != -1) { + // Local variable + emit_byte(compiler, BC_GET_LOCAL); + emit_byte(compiler, (uint8_t)slot); + + if (!is_prefix) { + // Postfix: duplicate old value for return + emit_byte(compiler, BC_DUP); + } + + emit_byte(compiler, BC_CONST_BYTE); + emit_byte(compiler, 1); + emit_byte(compiler, is_increment ? BC_ADD : BC_SUB); + + if (is_prefix) { + // Prefix: duplicate new value for return + emit_byte(compiler, BC_DUP); + } + + emit_byte(compiler, BC_SET_LOCAL); + emit_byte(compiler, (uint8_t)slot); + emit_byte(compiler, BC_POP); // Pop the set result + } else { + // Global variable + int idx = chunk_add_identifier(compiler->builder->chunk, name); + emit_byte(compiler, BC_GET_GLOBAL); + emit_short(compiler, idx); + + if (!is_prefix) { + emit_byte(compiler, BC_DUP); + } + + emit_byte(compiler, BC_CONST_BYTE); + emit_byte(compiler, 1); + emit_byte(compiler, is_increment ? BC_ADD : BC_SUB); + + if (is_prefix) { + emit_byte(compiler, BC_DUP); + } + + emit_byte(compiler, BC_SET_GLOBAL); + emit_short(compiler, idx); + emit_byte(compiler, BC_POP); // Pop the set value + } + } else if (operand->type == EXPR_INDEX) { + // arr[idx]++ or ++arr[idx] + compile_expression(compiler, operand->as.index.object); + compile_expression(compiler, operand->as.index.index); + + // Duplicate object and index for later SET_INDEX + emit_byte(compiler, BC_DUP2); // [arr, idx, arr, idx] + + // Get current value + emit_byte(compiler, BC_GET_INDEX); // [arr, idx, old] + + if (is_prefix) { + // Prefix: compute new value and set + // [arr, idx, old] + emit_byte(compiler, BC_CONST_BYTE); + emit_byte(compiler, 1); + emit_byte(compiler, is_increment ? BC_ADD : BC_SUB); + // [arr, idx, new] + emit_byte(compiler, BC_SET_INDEX); + // SET_INDEX pops [new, idx, arr], sets arr[idx]=new, pushes new + // [new] - this is our result + } else { + // Postfix: save old, compute new, rearrange, set + // [arr, idx, old] + emit_byte(compiler, BC_DUP); // [arr, idx, old, old] + emit_byte(compiler, BC_CONST_BYTE); + emit_byte(compiler, 1); + emit_byte(compiler, is_increment ? BC_ADD : BC_SUB); + // [arr, idx, old, new] + emit_byte(compiler, BC_BURY3); + // [old, arr, idx, new] + emit_byte(compiler, BC_SET_INDEX); + // [old, new] + emit_byte(compiler, BC_POP); + // [old] - this is our result + } + } else if (operand->type == EXPR_GET_PROPERTY) { + // obj.x++ or ++obj.x + int prop_idx = chunk_add_string(compiler->builder->chunk, + operand->as.get_property.property, + strlen(operand->as.get_property.property)); + + compile_expression(compiler, operand->as.get_property.object); + emit_byte(compiler, BC_DUP); // [obj, obj] + + // Get current value + emit_byte(compiler, BC_GET_PROPERTY); + emit_short(compiler, prop_idx); // [obj, old] + + if (is_prefix) { + // Prefix: compute new and set + // [obj, old] + emit_byte(compiler, BC_CONST_BYTE); + emit_byte(compiler, 1); + emit_byte(compiler, is_increment ? BC_ADD : BC_SUB); + // [obj, new] + emit_byte(compiler, BC_SET_PROPERTY); + emit_short(compiler, prop_idx); + // SET_PROPERTY pops [new, obj], sets obj.x=new, pushes new + // [new] - this is our result + } else { + // Postfix: save old, compute new, rearrange, set + // [obj, old] + emit_byte(compiler, BC_DUP); // [obj, old, old] + emit_byte(compiler, BC_CONST_BYTE); + emit_byte(compiler, 1); + emit_byte(compiler, is_increment ? BC_ADD : BC_SUB); + // [obj, old, new] + emit_byte(compiler, BC_ROT3); + // [old, new, obj] + emit_byte(compiler, BC_SWAP); + // [old, obj, new] + emit_byte(compiler, BC_SET_PROPERTY); + emit_short(compiler, prop_idx); + // [old, new] + emit_byte(compiler, BC_POP); + // [old] - this is our result + } + } else { + compiler_error(compiler, "Invalid operand for increment/decrement"); + emit_byte(compiler, BC_NULL); + } +} + +static void compile_prefix_inc(Compiler *compiler, Expr *expr) { + compile_inc_dec(compiler, expr->as.prefix_inc.operand, true, true); +} + +static void compile_prefix_dec(Compiler *compiler, Expr *expr) { + compile_inc_dec(compiler, expr->as.prefix_dec.operand, false, true); +} + +static void compile_postfix_inc(Compiler *compiler, Expr *expr) { + compile_inc_dec(compiler, expr->as.postfix_inc.operand, true, false); +} + +static void compile_postfix_dec(Compiler *compiler, Expr *expr) { + compile_inc_dec(compiler, expr->as.postfix_dec.operand, false, false); +} + +static void compile_string_interpolation(Compiler *compiler, Expr *expr) { + int num_parts = expr->as.string_interpolation.num_parts; + char **string_parts = expr->as.string_interpolation.string_parts; + Expr **expr_parts = expr->as.string_interpolation.expr_parts; + + // String interpolation creates a string from alternating literal parts and expressions + // string_parts has num_parts + 1 elements (strings between expressions) + // expr_parts has num_parts elements (the ${...} expressions) + + // Push all parts onto the stack: str0, expr0, str1, expr1, ..., strN + int total_parts = 0; + for (int i = 0; i <= num_parts; i++) { + // Push string part (even if empty) + if (string_parts[i] && strlen(string_parts[i]) > 0) { + int idx = chunk_add_string(compiler->builder->chunk, string_parts[i], strlen(string_parts[i])); + emit_byte(compiler, BC_CONST); + emit_short(compiler, idx); + total_parts++; + } + + // Push expression part (if not at the end) + if (i < num_parts) { + compile_expression(compiler, expr_parts[i]); + total_parts++; + } + } + + emit_byte(compiler, BC_STRING_INTERP); + emit_short(compiler, (uint16_t)total_parts); +} + +static void compile_null_coalesce(Compiler *compiler, Expr *expr) { + compile_expression(compiler, expr->as.null_coalesce.left); + // If not null, skip the right side + int end_jump = emit_jump(compiler, BC_COALESCE); + emit_byte(compiler, BC_POP); + compile_expression(compiler, expr->as.null_coalesce.right); + patch_jump(compiler, end_jump); +} + +static void compile_optional_chain(Compiler *compiler, Expr *expr) { + // Compile the object expression + compile_expression(compiler, expr->as.optional_chain.object); + + // If null, jump to end (keeping null on stack) + int end_jump = emit_jump(compiler, BC_OPTIONAL_CHAIN); + + // Object is not null - perform the operation + if (expr->as.optional_chain.is_call) { + // Method call: obj?.method(args) + const char *method_name = expr->as.optional_chain.property; + int idx = chunk_add_identifier(compiler->builder->chunk, method_name); + + // Compile arguments + for (int i = 0; i < expr->as.optional_chain.num_args; i++) { + compile_expression(compiler, expr->as.optional_chain.args[i]); + } + + emit_byte(compiler, BC_CALL_METHOD); + emit_short(compiler, idx); + emit_byte(compiler, expr->as.optional_chain.num_args); + } else if (expr->as.optional_chain.is_property) { + // Property access: obj?.prop + const char *name = expr->as.optional_chain.property; + int idx = chunk_add_identifier(compiler->builder->chunk, name); + emit_byte(compiler, BC_GET_PROPERTY); + emit_short(compiler, idx); + } else { + // Index access: obj?.[index] + compile_expression(compiler, expr->as.optional_chain.index); + emit_byte(compiler, BC_GET_INDEX); + } + + patch_jump(compiler, end_jump); +} + +static void compile_function(Compiler *compiler, Expr *expr) { + // Create a new compiler for the function body + Compiler *fn_compiler = malloc(sizeof(Compiler)); + fn_compiler->builder = chunk_builder_new(compiler->builder); + fn_compiler->enclosing = compiler; + fn_compiler->function_name = NULL; + fn_compiler->is_async = expr->as.function.is_async; + fn_compiler->current_line = expr->line; + fn_compiler->had_error = false; + fn_compiler->panic_mode = false; + + // Set function metadata + fn_compiler->builder->chunk->arity = expr->as.function.num_params; + fn_compiler->builder->chunk->optional_count = 0; + fn_compiler->builder->chunk->has_rest_param = expr->as.function.rest_param != NULL; + fn_compiler->builder->chunk->is_async = expr->as.function.is_async; + + // Begin the function scope + builder_begin_scope(fn_compiler->builder); + + // Reserve slot 0 for the function/closure itself + builder_declare_local(fn_compiler->builder, "", false, TYPE_ID_NULL); + builder_mark_initialized(fn_compiler->builder); + + // Declare parameters as locals (starting at slot 1) + for (int i = 0; i < expr->as.function.num_params; i++) { + builder_declare_local(fn_compiler->builder, + expr->as.function.param_names[i], + false, TYPE_ID_NULL); + builder_mark_initialized(fn_compiler->builder); + + // Count optional params + if (expr->as.function.param_defaults && + expr->as.function.param_defaults[i]) { + fn_compiler->builder->chunk->optional_count++; + } + } + + // Declare rest parameter if present (comes after all regular params) + if (expr->as.function.rest_param) { + builder_declare_local(fn_compiler->builder, + expr->as.function.rest_param, + false, TYPE_ID_NULL); + builder_mark_initialized(fn_compiler->builder); + } + + // Emit default value initialization for optional parameters + // Parameter slots are 1, 2, 3, ... (slot 0 is the closure) + for (int i = 0; i < expr->as.function.num_params; i++) { + if (expr->as.function.param_defaults && + expr->as.function.param_defaults[i]) { + // Get current parameter value + emit_byte(fn_compiler, BC_GET_LOCAL); + emit_byte(fn_compiler, (uint8_t)(i + 1)); // +1 because slot 0 is closure + + // If not null, skip default assignment + int skip_jump = emit_jump(fn_compiler, BC_COALESCE); + + // Pop the null value + emit_byte(fn_compiler, BC_POP); + + // Compile the default expression + compile_expression(fn_compiler, expr->as.function.param_defaults[i]); + + // Store in parameter slot + emit_byte(fn_compiler, BC_SET_LOCAL); + emit_byte(fn_compiler, (uint8_t)(i + 1)); + + // Patch the skip jump + patch_jump(fn_compiler, skip_jump); + + // Pop the value (either from coalesce or set_local) + emit_byte(fn_compiler, BC_POP); + } + } + + // Compile the function body + if (expr->as.function.body) { + if (expr->as.function.body->type == STMT_BLOCK) { + // Compile block statements directly (don't create nested scope) + for (int i = 0; i < expr->as.function.body->as.block.count; i++) { + compile_statement(fn_compiler, expr->as.function.body->as.block.statements[i]); + } + } else { + compile_statement(fn_compiler, expr->as.function.body); + } + } + + // Implicit return null if no explicit return + emit_byte(fn_compiler, BC_NULL); + emit_byte(fn_compiler, BC_RETURN); + + // End scope + builder_end_scope(fn_compiler->builder); + + // Finish building the function chunk (uses max_local_count for slot allocation) + Chunk *fn_chunk = chunk_builder_finish(fn_compiler->builder); + fn_compiler->builder = NULL; + + // Check for compilation errors + if (fn_compiler->had_error) { + compiler->had_error = true; + } + + free(fn_compiler); + + // Add the function to the constant pool and emit closure + int fn_index = chunk_add_function(compiler->builder->chunk, fn_chunk); + emit_byte(compiler, BC_CLOSURE); + emit_short(compiler, fn_index); + emit_byte(compiler, (uint8_t)fn_chunk->upvalue_count); // Upvalue count + + // Emit upvalue info + for (int i = 0; i < fn_chunk->upvalue_count; i++) { + emit_byte(compiler, fn_chunk->upvalues[i].is_local ? 1 : 0); + emit_byte(compiler, fn_chunk->upvalues[i].index); + } +} + +static void compile_expression(Compiler *compiler, Expr *expr) { + if (!expr) { + emit_byte(compiler, BC_NULL); + return; + } + + compiler->current_line = expr->line; + + switch (expr->type) { + case EXPR_NUMBER: + compile_number(compiler, expr); + break; + case EXPR_BOOL: + compile_bool(compiler, expr); + break; + case EXPR_STRING: + compile_string(compiler, expr); + break; + case EXPR_RUNE: + compile_rune(compiler, expr); + break; + case EXPR_IDENT: + compile_identifier(compiler, expr); + break; + case EXPR_NULL: + compile_null(compiler); + break; + case EXPR_BINARY: + compile_binary(compiler, expr); + break; + case EXPR_UNARY: + compile_unary(compiler, expr); + break; + case EXPR_TERNARY: + compile_ternary(compiler, expr); + break; + case EXPR_CALL: + compile_call(compiler, expr); + break; + case EXPR_ASSIGN: + compile_assign(compiler, expr); + break; + case EXPR_GET_PROPERTY: + compile_get_property(compiler, expr); + break; + case EXPR_SET_PROPERTY: + compile_set_property(compiler, expr); + break; + case EXPR_INDEX: + compile_index(compiler, expr); + break; + case EXPR_INDEX_ASSIGN: + compile_index_assign(compiler, expr); + break; + case EXPR_ARRAY_LITERAL: + compile_array_literal(compiler, expr); + break; + case EXPR_OBJECT_LITERAL: + compile_object_literal(compiler, expr); + break; + case EXPR_PREFIX_INC: + compile_prefix_inc(compiler, expr); + break; + case EXPR_PREFIX_DEC: + compile_prefix_dec(compiler, expr); + break; + case EXPR_POSTFIX_INC: + compile_postfix_inc(compiler, expr); + break; + case EXPR_POSTFIX_DEC: + compile_postfix_dec(compiler, expr); + break; + case EXPR_NULL_COALESCE: + compile_null_coalesce(compiler, expr); + break; + case EXPR_OPTIONAL_CHAIN: + compile_optional_chain(compiler, expr); + break; + case EXPR_STRING_INTERPOLATION: + compile_string_interpolation(compiler, expr); + break; + case EXPR_FUNCTION: + compile_function(compiler, expr); + break; + case EXPR_AWAIT: + // TODO: Compile await + compile_expression(compiler, expr->as.await_expr.awaited_expr); + emit_byte(compiler, BC_AWAIT); + break; + default: + compiler_error(compiler, "Unsupported expression type"); + emit_byte(compiler, BC_NULL); + } +} + +// ============================================ +// Statement Compilation +// ============================================ + +static void compile_let(Compiler *compiler, Stmt *stmt, bool is_const) { + const char *name = stmt->as.let.name; + + // Compile initializer + if (stmt->as.let.value) { + compile_expression(compiler, stmt->as.let.value); + } else { + emit_byte(compiler, BC_NULL); + } + + // Cast to declared type if type annotation is present + if (stmt->as.let.type_annotation && stmt->as.let.type_annotation->kind != TYPE_INFER) { + int type_id = type_kind_to_id(stmt->as.let.type_annotation->kind); + if (type_id >= 0) { + emit_byte(compiler, BC_CAST); + emit_byte(compiler, (uint8_t)type_id); + } + // Set custom object type name for typeof() + if (stmt->as.let.type_annotation->kind == TYPE_CUSTOM_OBJECT && + stmt->as.let.type_annotation->type_name) { + int idx = chunk_add_identifier(compiler->builder->chunk, + stmt->as.let.type_annotation->type_name); + emit_byte(compiler, BC_SET_OBJ_TYPE); + emit_short(compiler, idx); + } + } + + // Define variable + if (compiler->builder->scope_depth > 0) { + // Local variable + int slot = builder_declare_local(compiler->builder, name, is_const, TYPE_ID_NULL); + if (slot == -1) { + compiler_error(compiler, "Variable already declared in this scope"); + } + builder_mark_initialized(compiler->builder); + // Store the value from the expression stack into the local slot + emit_byte(compiler, BC_SET_LOCAL); + emit_byte(compiler, (uint8_t)slot); + emit_byte(compiler, BC_POP); // Pop the value from expression stack + } else { + // Global variable + int idx = chunk_add_identifier(compiler->builder->chunk, name); + emit_byte(compiler, BC_DEFINE_GLOBAL); + emit_short(compiler, idx); + // Track this global so it can shadow builtins + compiler_add_defined_global(compiler, name); + } +} + +static void compile_expr_stmt(Compiler *compiler, Stmt *stmt) { + compile_expression(compiler, stmt->as.expr); + emit_byte(compiler, BC_POP); +} + +static void compile_if(Compiler *compiler, Stmt *stmt) { + compile_expression(compiler, stmt->as.if_stmt.condition); + + int then_jump = emit_jump(compiler, BC_JUMP_IF_FALSE); + emit_byte(compiler, BC_POP); // Pop condition + + compile_statement(compiler, stmt->as.if_stmt.then_branch); + + int else_jump = emit_jump(compiler, BC_JUMP); + patch_jump(compiler, then_jump); + emit_byte(compiler, BC_POP); // Pop condition + + if (stmt->as.if_stmt.else_branch) { + compile_statement(compiler, stmt->as.if_stmt.else_branch); + } + + patch_jump(compiler, else_jump); +} + +static void compile_while(Compiler *compiler, Stmt *stmt) { + int loop_start = compiler->builder->chunk->code_count; + builder_begin_loop(compiler->builder); + // For while loops, continue jumps directly to condition check + builder_set_continue_target(compiler->builder); + + compile_expression(compiler, stmt->as.while_stmt.condition); + + int exit_jump = emit_jump(compiler, BC_JUMP_IF_FALSE); + emit_byte(compiler, BC_POP); // Pop condition + + compile_statement(compiler, stmt->as.while_stmt.body); + + emit_loop(compiler, loop_start); + + patch_jump(compiler, exit_jump); + emit_byte(compiler, BC_POP); // Pop condition + + builder_end_loop(compiler->builder); +} + +static void compile_for(Compiler *compiler, Stmt *stmt) { + builder_begin_scope(compiler->builder); + + // Initializer + if (stmt->as.for_loop.initializer) { + compile_statement(compiler, stmt->as.for_loop.initializer); + } + + int loop_start = compiler->builder->chunk->code_count; + builder_begin_loop(compiler->builder); + // Note: continue_target left as -1, will be set before increment + + // Condition + int exit_jump = -1; + if (stmt->as.for_loop.condition) { + compile_expression(compiler, stmt->as.for_loop.condition); + exit_jump = emit_jump(compiler, BC_JUMP_IF_FALSE); + emit_byte(compiler, BC_POP); + } + + // Body + compile_statement(compiler, stmt->as.for_loop.body); + + // Set continue target here (before increment) + // This patches any pending continue jumps to this point + builder_set_continue_target(compiler->builder); + + // Increment + if (stmt->as.for_loop.increment) { + compile_expression(compiler, stmt->as.for_loop.increment); + emit_byte(compiler, BC_POP); + } + + emit_loop(compiler, loop_start); + + if (exit_jump != -1) { + patch_jump(compiler, exit_jump); + emit_byte(compiler, BC_POP); + } + + builder_end_loop(compiler->builder); + builder_end_scope(compiler->builder); +} + +static void compile_block(Compiler *compiler, Stmt *stmt) { + builder_begin_scope(compiler->builder); + for (int i = 0; i < stmt->as.block.count; i++) { + compile_statement(compiler, stmt->as.block.statements[i]); + } + builder_end_scope(compiler->builder); +} + +static void compile_return(Compiler *compiler, Stmt *stmt) { + if (stmt->as.return_stmt.value) { + compile_expression(compiler, stmt->as.return_stmt.value); + } else { + emit_byte(compiler, BC_NULL); + } + emit_byte(compiler, BC_RETURN); +} + +static void compile_break(Compiler *compiler) { + builder_emit_break(compiler->builder); +} + +static void compile_continue(Compiler *compiler) { + builder_emit_continue(compiler->builder); +} + +static void compile_for_in(Compiler *compiler, Stmt *stmt) { + builder_begin_scope(compiler->builder); + + // Compile iterable expression -> array/object on stack + compile_expression(compiler, stmt->as.for_in.iterable); + + // Reserve hidden local for iterable and store it + int iterable_slot = builder_declare_local(compiler->builder, " iter", false, TYPE_ID_NULL); + builder_mark_initialized(compiler->builder); + emit_byte(compiler, BC_SET_LOCAL); + emit_byte(compiler, (uint8_t)iterable_slot); + emit_byte(compiler, BC_POP); + + // Push initial index 0 and store in local + emit_byte(compiler, BC_CONST_BYTE); + emit_byte(compiler, 0); + int index_slot = builder_declare_local(compiler->builder, " idx", false, TYPE_ID_NULL); + builder_mark_initialized(compiler->builder); + emit_byte(compiler, BC_SET_LOCAL); + emit_byte(compiler, (uint8_t)index_slot); + emit_byte(compiler, BC_POP); + + // Declare key variable if present (for "for (let key, value in obj)") + int key_slot = -1; + if (stmt->as.for_in.key_var) { + emit_byte(compiler, BC_NULL); + key_slot = builder_declare_local(compiler->builder, stmt->as.for_in.key_var, false, TYPE_ID_NULL); + builder_mark_initialized(compiler->builder); + emit_byte(compiler, BC_SET_LOCAL); + emit_byte(compiler, (uint8_t)key_slot); + emit_byte(compiler, BC_POP); + } + + // Push placeholder for value variable and store in local + emit_byte(compiler, BC_NULL); + int var_slot = builder_declare_local(compiler->builder, stmt->as.for_in.value_var, false, TYPE_ID_NULL); + builder_mark_initialized(compiler->builder); + emit_byte(compiler, BC_SET_LOCAL); + emit_byte(compiler, (uint8_t)var_slot); + emit_byte(compiler, BC_POP); + + // Loop start: check if index < iterable.length + int loop_start = compiler->builder->chunk->code_count; + builder_begin_loop(compiler->builder); + + // Check: index < iterable.length + emit_byte(compiler, BC_GET_LOCAL); + emit_byte(compiler, (uint8_t)index_slot); + emit_byte(compiler, BC_GET_LOCAL); + emit_byte(compiler, (uint8_t)iterable_slot); + emit_byte(compiler, BC_GET_PROPERTY); + int len_idx = chunk_add_identifier(compiler->builder->chunk, "length"); + emit_short(compiler, len_idx); + emit_byte(compiler, BC_LT); // index < length + + int exit_jump = emit_jump(compiler, BC_JUMP_IF_FALSE); + emit_byte(compiler, BC_POP); // Pop condition + + // Set key variable if present (for objects: key = field name at index) + if (key_slot >= 0) { + // We use BC_CALL_BUILTIN with "key_at" to get the key at the index + // Actually, let's use a simpler approach: call keys() method and index into it + // But that's expensive. Instead, let's add a special opcode. + // For now, use BC_GET_KEY opcode (we'll add it) + emit_byte(compiler, BC_GET_LOCAL); + emit_byte(compiler, (uint8_t)iterable_slot); + emit_byte(compiler, BC_GET_LOCAL); + emit_byte(compiler, (uint8_t)index_slot); + emit_byte(compiler, BC_GET_KEY); + emit_byte(compiler, BC_SET_LOCAL); + emit_byte(compiler, (uint8_t)key_slot); + emit_byte(compiler, BC_POP); + } + + // Set value variable: x = iterable[index] + emit_byte(compiler, BC_GET_LOCAL); + emit_byte(compiler, (uint8_t)iterable_slot); + emit_byte(compiler, BC_GET_LOCAL); + emit_byte(compiler, (uint8_t)index_slot); + emit_byte(compiler, BC_GET_INDEX); + emit_byte(compiler, BC_SET_LOCAL); + emit_byte(compiler, (uint8_t)var_slot); + emit_byte(compiler, BC_POP); // Pop assignment result + + // Compile body + compile_statement(compiler, stmt->as.for_in.body); + + // Set continue target here (after body, before increment) + // This patches any pending continue jumps to this point + builder_set_continue_target(compiler->builder); + + // Increment index: index = index + 1 + emit_byte(compiler, BC_GET_LOCAL); + emit_byte(compiler, (uint8_t)index_slot); + emit_byte(compiler, BC_CONST_BYTE); + emit_byte(compiler, 1); + emit_byte(compiler, BC_ADD); + emit_byte(compiler, BC_SET_LOCAL); + emit_byte(compiler, (uint8_t)index_slot); + emit_byte(compiler, BC_POP); // Pop assignment result + + // Loop back + emit_loop(compiler, loop_start); + + // Exit point + patch_jump(compiler, exit_jump); + emit_byte(compiler, BC_POP); // Pop condition + + builder_end_loop(compiler->builder); + builder_end_scope(compiler->builder); +} + +static void compile_switch(Compiler *compiler, Stmt *stmt) { + // Create scope for switch (needed for hidden local at global scope) + builder_begin_scope(compiler->builder); + + // Compile switch expression + compile_expression(compiler, stmt->as.switch_stmt.expr); + + // Store in a hidden local so we can compare multiple times + int switch_slot = builder_declare_local(compiler->builder, " switch", false, TYPE_ID_NULL); + builder_mark_initialized(compiler->builder); + emit_byte(compiler, BC_SET_LOCAL); + emit_byte(compiler, (uint8_t)switch_slot); + emit_byte(compiler, BC_POP); + + // Register as pseudo-loop so break works (exits switch, not enclosing loop) + builder_begin_loop(compiler->builder); + + int num_cases = stmt->as.switch_stmt.num_cases; + int *case_jumps = malloc(sizeof(int) * num_cases); + int default_idx = -1; + + // First pass: emit comparisons and conditional jumps to case bodies + for (int i = 0; i < num_cases; i++) { + if (stmt->as.switch_stmt.case_values[i] == NULL) { + // Default case - remember index, handle after all comparisons + default_idx = i; + case_jumps[i] = -1; + continue; + } + + // Get switch value + emit_byte(compiler, BC_GET_LOCAL); + emit_byte(compiler, (uint8_t)switch_slot); + + // Compile case value + compile_expression(compiler, stmt->as.switch_stmt.case_values[i]); + + // Compare + emit_byte(compiler, BC_EQ); + + // Jump to case body if equal + case_jumps[i] = emit_jump(compiler, BC_JUMP_IF_TRUE); + emit_byte(compiler, BC_POP); // Pop false comparison result + } + + // No case matched - jump to default or end + int default_jump = emit_jump(compiler, BC_JUMP); + + // Second pass: emit case bodies and patch jumps + // Cases without break should fall through to the next case + for (int i = 0; i < num_cases; i++) { + if (i == default_idx) { + // Patch default jump to here + patch_jump(compiler, default_jump); + } else if (case_jumps[i] >= 0) { + // Patch case jump to here + patch_jump(compiler, case_jumps[i]); + emit_byte(compiler, BC_POP); // Pop true comparison result + } + + // Compile case body - no automatic jump at end (allows fallthrough) + // break statements will jump to end via builder_end_loop + if (stmt->as.switch_stmt.case_bodies[i]) { + compile_statement(compiler, stmt->as.switch_stmt.case_bodies[i]); + } + // Fall through to next case (no jump emitted) + } + + // If no default case, patch default_jump to end + if (default_idx < 0) { + patch_jump(compiler, default_jump); + } + + // End pseudo-loop (for break support) - patches break jumps to here + builder_end_loop(compiler->builder); + + // End switch scope (will pop the hidden local) + builder_end_scope(compiler->builder); + + free(case_jumps); +} + +static void compile_try(Compiler *compiler, Stmt *stmt) { + Chunk *chunk = compiler->builder->chunk; + + // Emit BC_TRY with placeholder offsets for catch and finally + emit_byte(compiler, BC_TRY); + int catch_offset_pos = chunk->code_count; + emit_short(compiler, 0); // Placeholder for catch offset + int finally_offset_pos = chunk->code_count; + emit_short(compiler, 0); // Placeholder for finally offset + + // Position after all BC_TRY operands (where ip will be during execution) + int base_pos = chunk->code_count; + + // Compile try block + compile_statement(compiler, stmt->as.try_stmt.try_block); + + // Jump to finally (not to end - finally always runs) + int try_to_finally_jump = emit_jump(compiler, BC_JUMP); + + // Patch catch offset (relative to base_pos) + int catch_offset = chunk->code_count - base_pos; + chunk->code[catch_offset_pos] = (catch_offset >> 8) & 0xFF; + chunk->code[catch_offset_pos + 1] = catch_offset & 0xFF; + + // Compile catch block (if present) + if (stmt->as.try_stmt.catch_block) { + emit_byte(compiler, BC_CATCH); + + // Begin a scope for the catch parameter + builder_begin_scope(compiler->builder); + + // Declare catch parameter and store exception value in it + if (stmt->as.try_stmt.catch_param) { + int catch_slot = builder_declare_local(compiler->builder, stmt->as.try_stmt.catch_param, false, TYPE_ID_STRING); + builder_mark_initialized(compiler->builder); + // Exception value is on stack from throw - store it in local slot + emit_byte(compiler, BC_SET_LOCAL); + emit_byte(compiler, (uint8_t)catch_slot); + emit_byte(compiler, BC_POP); + } else { + // No parameter - just pop the exception + emit_byte(compiler, BC_POP); + } + + compile_statement(compiler, stmt->as.try_stmt.catch_block); + builder_end_scope(compiler->builder); + } + // Note: catch block falls through to finally + + // Patch finally offset (relative to base_pos) + int finally_offset = chunk->code_count - base_pos; + chunk->code[finally_offset_pos] = (finally_offset >> 8) & 0xFF; + chunk->code[finally_offset_pos + 1] = finally_offset & 0xFF; + + // Patch try jump to here (before finally) + patch_jump(compiler, try_to_finally_jump); + + // Compile finally block (if present) + if (stmt->as.try_stmt.finally_block) { + emit_byte(compiler, BC_FINALLY); + compile_statement(compiler, stmt->as.try_stmt.finally_block); + } + + emit_byte(compiler, BC_END_TRY); +} + +static void compile_throw(Compiler *compiler, Stmt *stmt) { + compile_expression(compiler, stmt->as.throw_stmt.value); + emit_byte(compiler, BC_THROW); +} + +static void compile_defer(Compiler *compiler, Stmt *stmt) { + // Create a closure that wraps the deferred call + // The closure captures the current environment and will be called at function exit + + // Create a new compiler for the deferred expression + Compiler *defer_compiler = malloc(sizeof(Compiler)); + defer_compiler->builder = chunk_builder_new(compiler->builder); + defer_compiler->enclosing = compiler; + defer_compiler->function_name = NULL; + defer_compiler->is_async = false; + defer_compiler->current_line = stmt->line; + defer_compiler->had_error = false; + defer_compiler->panic_mode = false; + + // Set function metadata (no params, not async) + defer_compiler->builder->chunk->arity = 0; + defer_compiler->builder->chunk->optional_count = 0; + defer_compiler->builder->chunk->has_rest_param = false; + defer_compiler->builder->chunk->is_async = false; + + // Begin the function scope + builder_begin_scope(defer_compiler->builder); + + // Reserve slot 0 for the closure itself + builder_declare_local(defer_compiler->builder, "", false, TYPE_ID_NULL); + builder_mark_initialized(defer_compiler->builder); + + // Compile the deferred expression (usually a call) + compile_expression(defer_compiler, stmt->as.defer_stmt.call); + emit_byte(defer_compiler, BC_POP); // Discard result + + // Return null + emit_byte(defer_compiler, BC_NULL); + emit_byte(defer_compiler, BC_RETURN); + + // End scope + builder_end_scope(defer_compiler->builder); + + // Finish building the function chunk + Chunk *defer_chunk = chunk_builder_finish(defer_compiler->builder); + defer_compiler->builder = NULL; + + if (defer_compiler->had_error) { + compiler->had_error = true; + } + + free(defer_compiler); + + // Add the deferred function to the constant pool + Constant c = {.type = CONST_FUNCTION, .as.function = defer_chunk}; + int idx = chunk_add_constant(compiler->builder->chunk, c); + + // Emit closure creation with no upvalues (for simplicity) + emit_byte(compiler, BC_CLOSURE); + emit_short(compiler, idx); + emit_byte(compiler, 0); // No upvalues + + // Register the closure as a deferred call + emit_byte(compiler, BC_DEFER); +} + +static void compile_statement(Compiler *compiler, Stmt *stmt) { + if (!stmt) return; + + compiler->current_line = stmt->line; + + switch (stmt->type) { + case STMT_LET: + compile_let(compiler, stmt, false); + break; + case STMT_CONST: + compile_let(compiler, stmt, true); + break; + case STMT_EXPR: + compile_expr_stmt(compiler, stmt); + break; + case STMT_IF: + compile_if(compiler, stmt); + break; + case STMT_WHILE: + compile_while(compiler, stmt); + break; + case STMT_FOR: + compile_for(compiler, stmt); + break; + case STMT_FOR_IN: + compile_for_in(compiler, stmt); + break; + case STMT_BLOCK: + compile_block(compiler, stmt); + break; + case STMT_RETURN: + compile_return(compiler, stmt); + break; + case STMT_BREAK: + compile_break(compiler); + break; + case STMT_CONTINUE: + compile_continue(compiler); + break; + case STMT_SWITCH: + compile_switch(compiler, stmt); + break; + case STMT_TRY: + compile_try(compiler, stmt); + break; + case STMT_THROW: + compile_throw(compiler, stmt); + break; + case STMT_DEFER: + compile_defer(compiler, stmt); + break; + case STMT_ENUM: { + // Create an object with enum variants + // enum Color { RED, GREEN, BLUE } -> { RED: 0, GREEN: 1, BLUE: 2 } + int num_variants = stmt->as.enum_decl.num_variants; + int auto_value = 0; + + // Push each key-value pair + for (int i = 0; i < num_variants; i++) { + // Push variant name as string constant + const char *name = stmt->as.enum_decl.variant_names[i]; + int idx = chunk_add_string(compiler->builder->chunk, name, strlen(name)); + emit_byte(compiler, BC_CONST); + emit_short(compiler, idx); + + // Push variant value + if (stmt->as.enum_decl.variant_values && stmt->as.enum_decl.variant_values[i]) { + compile_expression(compiler, stmt->as.enum_decl.variant_values[i]); + } else { + emit_byte(compiler, BC_CONST_BYTE); + emit_byte(compiler, (uint8_t)auto_value); + } + auto_value++; + } + + // Create the object with N pairs + emit_byte(compiler, BC_OBJECT); + emit_short(compiler, num_variants); + + // Define as global + int name_idx = chunk_add_identifier(compiler->builder->chunk, stmt->as.enum_decl.name); + emit_byte(compiler, BC_DEFINE_GLOBAL); + emit_short(compiler, name_idx); + // Track this global so it can shadow builtins + compiler_add_defined_global(compiler, stmt->as.enum_decl.name); + break; + } + case STMT_DEFINE_OBJECT: { + // Define type statement: define Person { name: string, age: i32, active?: true } + // Push type name + const char *type_name = stmt->as.define_object.name; + int name_idx = chunk_add_string(compiler->builder->chunk, type_name, strlen(type_name)); + emit_byte(compiler, BC_CONST); + emit_short(compiler, name_idx); + + int num_fields = stmt->as.define_object.num_fields; + + // Push each field: name, optional, default + for (int i = 0; i < num_fields; i++) { + // Field name + const char *field_name = stmt->as.define_object.field_names[i]; + int field_idx = chunk_add_string(compiler->builder->chunk, field_name, strlen(field_name)); + emit_byte(compiler, BC_CONST); + emit_short(compiler, field_idx); + + // Optional flag + int is_optional = stmt->as.define_object.field_optional[i]; + emit_byte(compiler, is_optional ? BC_TRUE : BC_FALSE); + + // Default value (or null if none) + if (stmt->as.define_object.field_defaults && stmt->as.define_object.field_defaults[i]) { + compile_expression(compiler, stmt->as.define_object.field_defaults[i]); + } else { + emit_byte(compiler, BC_NULL); + } + } + + // Emit BC_DEFINE_TYPE with field count + emit_byte(compiler, BC_DEFINE_TYPE); + emit_short(compiler, (uint16_t)num_fields); + break; + } + default: + // TODO: Handle other statement types + break; + } +} + +// ============================================ +// Public API +// ============================================ + +void compile_stmt(Compiler *compiler, Stmt *stmt) { + compile_statement(compiler, stmt); +} + +void compile_expr(Compiler *compiler, Expr *expr) { + compile_expression(compiler, expr); +} + +Chunk* compile_program(Stmt **stmts, int count) { + Compiler *compiler = compiler_new(NULL); + + // Compile all statements + for (int i = 0; i < count; i++) { + compile_statement(compiler, stmts[i]); + } + + // End with return + emit_byte(compiler, BC_NULL); + emit_byte(compiler, BC_RETURN); + + if (compiler->had_error) { + chunk_builder_free(compiler->builder); + free(compiler); + return NULL; + } + + Chunk *chunk = chunk_builder_finish(compiler->builder); + compiler->builder = NULL; + free(compiler); + + return chunk; +} diff --git a/src/backends/vm/compiler.h b/src/backends/vm/compiler.h new file mode 100644 index 00000000..89369251 --- /dev/null +++ b/src/backends/vm/compiler.h @@ -0,0 +1,46 @@ +/* + * Hemlock Bytecode VM - Compiler + * + * AST to bytecode compiler. + */ + +#ifndef HEMLOCK_VM_COMPILER_H +#define HEMLOCK_VM_COMPILER_H + +#include "chunk.h" +#include "ast.h" + +// Compiler state +typedef struct Compiler { + ChunkBuilder *builder; // Current chunk being built + struct Compiler *enclosing; // Enclosing compiler (for nested functions) + + // Current function info + char *function_name; + bool is_async; + int current_line; + + // Error state + bool had_error; + bool panic_mode; + + // Tracking defined globals (to allow shadowing builtins) + char **defined_globals; + int num_defined_globals; + int defined_globals_capacity; +} Compiler; + +// Compile a program (list of statements) to bytecode +Chunk* compile_program(Stmt **stmts, int count); + +// Compile a single statement +void compile_stmt(Compiler *compiler, Stmt *stmt); + +// Compile an expression +void compile_expr(Compiler *compiler, Expr *expr); + +// Error reporting +void compiler_error(Compiler *compiler, const char *message); +void compiler_error_at(Compiler *compiler, int line, const char *message); + +#endif // HEMLOCK_VM_COMPILER_H diff --git a/src/backends/vm/debug.c b/src/backends/vm/debug.c new file mode 100644 index 00000000..abe9b6bf --- /dev/null +++ b/src/backends/vm/debug.c @@ -0,0 +1,392 @@ +/* + * Hemlock Bytecode VM - Debug Utilities + * + * Disassembler and debugging tools. + */ + +#include "debug.h" +#include "instruction.h" +#include +#include + +// ============================================ +// Constant Printing +// ============================================ + +void print_constant(Constant *constant) { + switch (constant->type) { + case CONST_I32: + printf("%d", constant->as.i32); + break; + case CONST_I64: + printf("%lld", (long long)constant->as.i64); + break; + case CONST_F64: + printf("%g", constant->as.f64); + break; + case CONST_STRING: + printf("\"%.*s\"", constant->as.string.length, constant->as.string.data); + break; + case CONST_IDENTIFIER: + printf("'%s'", constant->as.string.data); + break; + case CONST_FUNCTION: + if (constant->as.function->name) { + printf("", constant->as.function->name); + } else { + printf(""); + } + break; + default: + printf(""); + } +} + +// ============================================ +// Value Printing (for runtime debugging) +// ============================================ + +void print_value(Value value) { + switch (value.type) { + case VAL_NULL: + printf("null"); + break; + case VAL_BOOL: + printf("%s", value.as.as_bool ? "true" : "false"); + break; + case VAL_I8: + printf("%d", value.as.as_i8); + break; + case VAL_I16: + printf("%d", value.as.as_i16); + break; + case VAL_I32: + printf("%d", value.as.as_i32); + break; + case VAL_I64: + printf("%lld", (long long)value.as.as_i64); + break; + case VAL_U8: + printf("%u", value.as.as_u8); + break; + case VAL_U16: + printf("%u", value.as.as_u16); + break; + case VAL_U32: + printf("%u", value.as.as_u32); + break; + case VAL_U64: + printf("%llu", (unsigned long long)value.as.as_u64); + break; + case VAL_F32: + printf("%g", value.as.as_f32); + break; + case VAL_F64: + printf("%g", value.as.as_f64); + break; + case VAL_STRING: + if (value.as.as_string) { + printf("\"%s\"", value.as.as_string->data); + } else { + printf("\"\""); + } + break; + case VAL_RUNE: + if (value.as.as_rune < 128) { + printf("'%c'", (char)value.as.as_rune); + } else { + printf("'\\u%04X'", value.as.as_rune); + } + break; + case VAL_ARRAY: + printf(""); + break; + case VAL_OBJECT: + printf(""); + break; + case VAL_FUNCTION: + printf(""); + break; + case VAL_PTR: + printf("", value.as.as_ptr); + break; + case VAL_BUFFER: + printf(""); + break; + case VAL_TASK: + printf(""); + break; + case VAL_CHANNEL: + printf(""); + break; + case VAL_FILE: + printf(""); + break; + default: + printf(""); + } +} + +// ============================================ +// Instruction Disassembly +// ============================================ + +static int simple_instruction(const char *name, int offset) { + printf("%s\n", name); + return offset + 1; +} + +static int byte_instruction(const char *name, Chunk *chunk, int offset) { + uint8_t slot = chunk->code[offset + 1]; + printf("%-16s %4d\n", name, slot); + return offset + 2; +} + +static int short_instruction(const char *name, Chunk *chunk, int offset) { + uint16_t value = (chunk->code[offset + 1] << 8) | chunk->code[offset + 2]; + printf("%-16s %4d\n", name, value); + return offset + 3; +} + +static int constant_instruction(const char *name, Chunk *chunk, int offset) { + uint16_t index = (chunk->code[offset + 1] << 8) | chunk->code[offset + 2]; + printf("%-16s %4d ", name, index); + if (index < chunk->const_count) { + print_constant(&chunk->constants[index]); + } + printf("\n"); + return offset + 3; +} + +static int jump_instruction(const char *name, int sign, Chunk *chunk, int offset) { + uint16_t jump = (chunk->code[offset + 1] << 8) | chunk->code[offset + 2]; + int target = offset + 3 + sign * jump; + printf("%-16s %4d -> %d\n", name, jump, target); + return offset + 3; +} + +static int invoke_instruction(const char *name, Chunk *chunk, int offset) { + uint16_t index = (chunk->code[offset + 1] << 8) | chunk->code[offset + 2]; + uint8_t argc = chunk->code[offset + 3]; + printf("%-16s %4d ", name, index); + if (index < chunk->const_count) { + print_constant(&chunk->constants[index]); + } + printf(" (%d args)\n", argc); + return offset + 4; +} + +static int closure_instruction(Chunk *chunk, int offset) { + uint16_t index = (chunk->code[offset + 1] << 8) | chunk->code[offset + 2]; + uint8_t upvalue_count = chunk->code[offset + 3]; + + printf("%-16s %4d ", "CLOSURE", index); + if (index < chunk->const_count) { + print_constant(&chunk->constants[index]); + } + printf("\n"); + + // Print upvalue descriptors + int current = offset + 4; + for (int i = 0; i < upvalue_count; i++) { + uint8_t is_local = chunk->code[current++]; + uint8_t idx = chunk->code[current++]; + printf(" | %s %d\n", + is_local ? "local" : "upvalue", idx); + } + + return current; +} + +int disassemble_instruction(Chunk *chunk, int offset) { + printf("%04d ", offset); + + // Print line number + int line = chunk_get_line(chunk, offset); + if (offset > 0 && line == chunk_get_line(chunk, offset - 1)) { + printf(" | "); + } else { + printf("%4d ", line); + } + + uint8_t instruction = chunk->code[offset]; + const InstructionInfo *info = instruction_info(instruction); + + switch (instruction) { + // Simple instructions (no operands) + case BC_NULL: + case BC_TRUE: + case BC_FALSE: + case BC_ADD: + case BC_SUB: + case BC_MUL: + case BC_DIV: + case BC_MOD: + case BC_NEGATE: + case BC_INC: + case BC_DEC: + case BC_ADD_I32: + case BC_SUB_I32: + case BC_MUL_I32: + case BC_EQ: + case BC_NE: + case BC_LT: + case BC_LE: + case BC_GT: + case BC_GE: + case BC_EQ_I32: + case BC_LT_I32: + case BC_NOT: + case BC_BIT_NOT: + case BC_BIT_AND: + case BC_BIT_OR: + case BC_BIT_XOR: + case BC_LSHIFT: + case BC_RSHIFT: + case BC_GET_INDEX: + case BC_SET_INDEX: + case BC_CLOSE_UPVALUE: + case BC_RETURN: + case BC_APPLY: + case BC_POP: + case BC_CATCH: + case BC_FINALLY: + case BC_END_TRY: + case BC_THROW: + case BC_AWAIT: + case BC_JOIN: + case BC_DETACH: + case BC_CHANNEL: + case BC_SEND: + case BC_RECV: + case BC_SELECT: + case BC_TYPEOF: + case BC_NOP: + case BC_ASSERT: + case BC_DEBUG_BREAK: + case BC_HALT: + case BC_BREAK: + case BC_CONTINUE: + case BC_FOR_IN_INIT: + case BC_GET_SELF: + case BC_SET_SELF: + case BC_GET_KEY: + return simple_instruction(info->name, offset); + + // Byte operand instructions + case BC_CONST_BYTE: + case BC_GET_LOCAL: + case BC_SET_LOCAL: + case BC_GET_UPVALUE: + case BC_SET_UPVALUE: + case BC_CALL: + case BC_TAIL_CALL: + case BC_SPAWN: + case BC_PRINT: + case BC_POPN: + case BC_CAST: + case BC_CHECK_TYPE: + return byte_instruction(info->name, chunk, offset); + + // Short operand instructions (constant pool index) + case BC_CONST: + case BC_GET_GLOBAL: + case BC_SET_GLOBAL: + case BC_DEFINE_GLOBAL: + case BC_GET_PROPERTY: + case BC_SET_PROPERTY: + case BC_SUPER: + case BC_DEFER: + case BC_DEFINE_TYPE: + case BC_DEFINE_ENUM: + case BC_ENUM_VALUE: + case BC_SET_OBJ_TYPE: + return constant_instruction(info->name, chunk, offset); + + // Jump instructions + case BC_JUMP: + case BC_JUMP_IF_FALSE: + case BC_JUMP_IF_TRUE: + case BC_JUMP_IF_FALSE_POP: + case BC_COALESCE: + case BC_OPTIONAL_CHAIN: + case BC_CASE: + case BC_FOR_IN_NEXT: + return jump_instruction(info->name, 1, chunk, offset); + + case BC_LOOP: + return jump_instruction(info->name, -1, chunk, offset); + + // Array/object construction + case BC_ARRAY: + case BC_OBJECT: + case BC_STRING_INTERP: + case BC_SWITCH: + return short_instruction(info->name, chunk, offset); + + // Invoke instructions (idx:16 + argc:8) + case BC_CALL_METHOD: + case BC_CALL_BUILTIN: + case BC_INVOKE: + return invoke_instruction(info->name, chunk, offset); + + // Closure (special handling for upvalue descriptors) + case BC_CLOSURE: + return closure_instruction(chunk, offset); + + // Try (catch:16 + finally:16) + case BC_TRY: { + uint16_t catch_offset = (chunk->code[offset + 1] << 8) | chunk->code[offset + 2]; + uint16_t finally_offset = (chunk->code[offset + 3] << 8) | chunk->code[offset + 4]; + printf("%-16s catch->%d finally->%d\n", "TRY", + offset + 5 + catch_offset, offset + 5 + finally_offset); + return offset + 5; + } + + default: + printf("Unknown opcode %d\n", instruction); + return offset + 1; + } +} + +// ============================================ +// Chunk Disassembly +// ============================================ + +void disassemble_chunk(Chunk *chunk, const char *name) { + printf("== %s ==\n", name); + + // Print function info + if (chunk->arity > 0 || chunk->optional_count > 0 || chunk->has_rest_param) { + printf("arity: %d, optional: %d, rest: %s, async: %s\n", + chunk->arity, chunk->optional_count, + chunk->has_rest_param ? "yes" : "no", + chunk->is_async ? "yes" : "no"); + } + + // Print constants + if (chunk->const_count > 0) { + printf("-- constants --\n"); + for (int i = 0; i < chunk->const_count; i++) { + printf(" %4d: ", i); + print_constant(&chunk->constants[i]); + printf("\n"); + } + } + + // Print bytecode + printf("-- code --\n"); + int offset = 0; + while (offset < chunk->code_count) { + offset = disassemble_instruction(chunk, offset); + } + + // Print nested functions + for (int i = 0; i < chunk->const_count; i++) { + if (chunk->constants[i].type == CONST_FUNCTION) { + Chunk *fn = chunk->constants[i].as.function; + printf("\n"); + disassemble_chunk(fn, fn->name ? fn->name : ""); + } + } +} diff --git a/src/backends/vm/debug.h b/src/backends/vm/debug.h new file mode 100644 index 00000000..33f6ddf0 --- /dev/null +++ b/src/backends/vm/debug.h @@ -0,0 +1,25 @@ +/* + * Hemlock Bytecode VM - Debug Utilities + * + * Disassembler and debugging tools. + */ + +#ifndef HEMLOCK_VM_DEBUG_H +#define HEMLOCK_VM_DEBUG_H + +#include "chunk.h" +#include "interpreter.h" // For Value type + +// Disassemble an entire chunk +void disassemble_chunk(Chunk *chunk, const char *name); + +// Disassemble a single instruction, returns next instruction offset +int disassemble_instruction(Chunk *chunk, int offset); + +// Print constant value +void print_constant(Constant *constant); + +// Print value (for stack traces) +void print_value(Value value); + +#endif // HEMLOCK_VM_DEBUG_H diff --git a/src/backends/vm/instruction.c b/src/backends/vm/instruction.c new file mode 100644 index 00000000..bfe2209e --- /dev/null +++ b/src/backends/vm/instruction.c @@ -0,0 +1,319 @@ +/* + * Hemlock Bytecode VM - Instruction Info + * + * Metadata for each opcode: name, operand bytes, stack effect. + */ + +#include "instruction.h" +#include +#include + +// Instruction info table indexed by opcode +static const InstructionInfo info_table[256] = { + // Category 1: Constants & Literals (0x00-0x0F) + [BC_CONST] = {"CONST", 2, 1}, + [BC_CONST_BYTE] = {"CONST_BYTE", 1, 1}, + [BC_NULL] = {"NULL", 0, 1}, + [BC_TRUE] = {"TRUE", 0, 1}, + [BC_FALSE] = {"FALSE", 0, 1}, + [BC_ARRAY] = {"ARRAY", 2, STACK_EFFECT_VARIABLE}, + [BC_OBJECT] = {"OBJECT", 2, STACK_EFFECT_VARIABLE}, + [BC_STRING_INTERP] = {"STRING_INTERP", 2, STACK_EFFECT_VARIABLE}, + [BC_CLOSURE] = {"CLOSURE", 3, 1}, + [BC_ENUM_VALUE] = {"ENUM_VALUE", 2, 1}, + + // Category 2: Variables (0x10-0x1F) + [BC_GET_LOCAL] = {"GET_LOCAL", 1, 1}, + [BC_SET_LOCAL] = {"SET_LOCAL", 1, 0}, + [BC_GET_UPVALUE] = {"GET_UPVALUE", 1, 1}, + [BC_SET_UPVALUE] = {"SET_UPVALUE", 1, 0}, + [BC_GET_GLOBAL] = {"GET_GLOBAL", 2, 1}, + [BC_SET_GLOBAL] = {"SET_GLOBAL", 2, 0}, + [BC_DEFINE_GLOBAL] = {"DEFINE_GLOBAL", 2, -1}, + [BC_GET_PROPERTY] = {"GET_PROPERTY", 2, 0}, + [BC_SET_PROPERTY] = {"SET_PROPERTY", 2, -1}, + [BC_GET_INDEX] = {"GET_INDEX", 0, -1}, + [BC_SET_INDEX] = {"SET_INDEX", 0, -2}, + [BC_CLOSE_UPVALUE] = {"CLOSE_UPVALUE", 0, 0}, + + // Category 3: Arithmetic (0x20-0x2F) + [BC_ADD] = {"ADD", 0, -1}, + [BC_SUB] = {"SUB", 0, -1}, + [BC_MUL] = {"MUL", 0, -1}, + [BC_DIV] = {"DIV", 0, -1}, + [BC_MOD] = {"MOD", 0, -1}, + [BC_NEGATE] = {"NEGATE", 0, 0}, + [BC_INC] = {"INC", 0, 0}, + [BC_DEC] = {"DEC", 0, 0}, + [BC_ADD_I32] = {"ADD_I32", 0, -1}, + [BC_SUB_I32] = {"SUB_I32", 0, -1}, + [BC_MUL_I32] = {"MUL_I32", 0, -1}, + + // Category 4: Comparison (0x30-0x3F) + [BC_EQ] = {"EQ", 0, -1}, + [BC_NE] = {"NE", 0, -1}, + [BC_LT] = {"LT", 0, -1}, + [BC_LE] = {"LE", 0, -1}, + [BC_GT] = {"GT", 0, -1}, + [BC_GE] = {"GE", 0, -1}, + [BC_EQ_I32] = {"EQ_I32", 0, -1}, + [BC_LT_I32] = {"LT_I32", 0, -1}, + + // Category 5: Logical & Bitwise (0x40-0x4F) + [BC_NOT] = {"NOT", 0, 0}, + [BC_BIT_NOT] = {"BIT_NOT", 0, 0}, + [BC_BIT_AND] = {"BIT_AND", 0, -1}, + [BC_BIT_OR] = {"BIT_OR", 0, -1}, + [BC_BIT_XOR] = {"BIT_XOR", 0, -1}, + [BC_LSHIFT] = {"LSHIFT", 0, -1}, + [BC_RSHIFT] = {"RSHIFT", 0, -1}, + [BC_COALESCE] = {"COALESCE", 2, 0}, + [BC_OPTIONAL_CHAIN] = {"OPTIONAL_CHAIN", 2, 0}, + + // Category 6: Control Flow (0x50-0x5F) + [BC_JUMP] = {"JUMP", 2, 0}, + [BC_JUMP_IF_FALSE] = {"JUMP_IF_FALSE", 2, -1}, + [BC_JUMP_IF_TRUE] = {"JUMP_IF_TRUE", 2, -1}, + [BC_JUMP_IF_FALSE_POP] = {"JUMP_IF_FALSE_POP", 2, -1}, + [BC_LOOP] = {"LOOP", 2, 0}, + [BC_BREAK] = {"BREAK", 0, 0}, + [BC_CONTINUE] = {"CONTINUE", 0, 0}, + [BC_SWITCH] = {"SWITCH", 2, -1}, + [BC_CASE] = {"CASE", 2, 0}, + [BC_FOR_IN_INIT] = {"FOR_IN_INIT", 0, 1}, + [BC_FOR_IN_NEXT] = {"FOR_IN_NEXT", 2, 1}, + [BC_POP] = {"POP", 0, -1}, + [BC_POPN] = {"POPN", 1, STACK_EFFECT_VARIABLE}, + [BC_DUP] = {"DUP", 0, 1}, + [BC_DUP2] = {"DUP2", 0, 2}, + [BC_SWAP] = {"SWAP", 0, 0}, + [BC_BURY3] = {"BURY3", 0, 0}, + [BC_ROT3] = {"ROT3", 0, 0}, + + // Category 7: Functions & Calls (0x60-0x6F) + [BC_CALL] = {"CALL", 1, STACK_EFFECT_VARIABLE}, + [BC_CALL_METHOD] = {"CALL_METHOD", 3, STACK_EFFECT_VARIABLE}, + [BC_CALL_BUILTIN] = {"CALL_BUILTIN", 3, STACK_EFFECT_VARIABLE}, + [BC_RETURN] = {"RETURN", 0, 0}, + [BC_APPLY] = {"APPLY", 0, -1}, + [BC_TAIL_CALL] = {"TAIL_CALL", 1, STACK_EFFECT_VARIABLE}, + [BC_SUPER] = {"SUPER", 2, 0}, + [BC_INVOKE] = {"INVOKE", 3, STACK_EFFECT_VARIABLE}, + + // Category 8: Exception Handling (0x70-0x7F) + [BC_TRY] = {"TRY", 4, 0}, + [BC_CATCH] = {"CATCH", 0, 1}, + [BC_FINALLY] = {"FINALLY", 0, 0}, + [BC_END_TRY] = {"END_TRY", 0, 0}, + [BC_THROW] = {"THROW", 0, -1}, + [BC_DEFER] = {"DEFER", 0, -1}, + [BC_GET_SELF] = {"GET_SELF", 0, 1}, + [BC_SET_SELF] = {"SET_SELF", 0, -1}, + [BC_GET_KEY] = {"GET_KEY", 0, -1}, // [obj, idx] -> [key] + [BC_SET_OBJ_TYPE] = {"SET_OBJ_TYPE", 2, 0}, // Set object type name + + // Category 9: Async & Concurrency (0x80-0x8F) + [BC_SPAWN] = {"SPAWN", 1, STACK_EFFECT_VARIABLE}, + [BC_AWAIT] = {"AWAIT", 0, 0}, + [BC_JOIN] = {"JOIN", 0, 0}, + [BC_DETACH] = {"DETACH", 0, -1}, + [BC_CHANNEL] = {"CHANNEL", 0, 0}, + [BC_SEND] = {"SEND", 0, -2}, + [BC_RECV] = {"RECV", 0, 0}, + [BC_SELECT] = {"SELECT", 0, 0}, + + // Category 10: Type Operations (0x90-0x9F) + [BC_TYPEOF] = {"TYPEOF", 0, 0}, + [BC_CAST] = {"CAST", 1, 0}, + [BC_CHECK_TYPE] = {"CHECK_TYPE", 1, 0}, + [BC_DEFINE_TYPE] = {"DEFINE_TYPE", 2, 0}, + [BC_DEFINE_ENUM] = {"DEFINE_ENUM", 2, 0}, + + // Category 11: Debug & Misc (0xF0-0xFF) + [BC_NOP] = {"NOP", 0, 0}, + [BC_PRINT] = {"PRINT", 1, STACK_EFFECT_VARIABLE}, + [BC_ASSERT] = {"ASSERT", 0, STACK_EFFECT_VARIABLE}, + [BC_DEBUG_BREAK] = {"DEBUG_BREAK", 0, 0}, + [BC_HALT] = {"HALT", 0, 0}, +}; + +const InstructionInfo* instruction_info(OpCode op) { + if (info_table[op].name == NULL) { + static const InstructionInfo unknown = {"UNKNOWN", 0, 0}; + return &unknown; + } + return &info_table[op]; +} + +// Get the size of an instruction in bytes (opcode + operands) +int instruction_size(OpCode op) { + return 1 + instruction_info(op)->operand_bytes; +} + +// Builtin name table +static const char* builtin_names[BUILTIN_COUNT] = { + // Memory (0-10) + [BUILTIN_ALLOC] = "alloc", + [BUILTIN_TALLOC] = "talloc", + [BUILTIN_REALLOC] = "realloc", + [BUILTIN_FREE] = "free", + [BUILTIN_MEMSET] = "memset", + [BUILTIN_MEMCPY] = "memcpy", + [BUILTIN_SIZEOF] = "sizeof", + [BUILTIN_BUFFER] = "buffer", + [BUILTIN_PTR_TO_BUFFER] = "ptr_to_buffer", + [BUILTIN_BUFFER_PTR] = "buffer_ptr", + [BUILTIN_PTR_NULL] = "ptr_null", + + // I/O (11-14) + [BUILTIN_PRINT] = "print", + [BUILTIN_EPRINT] = "eprint", + [BUILTIN_READ_LINE] = "read_line", + [BUILTIN_OPEN] = "open", + + // Type (15-17) + [BUILTIN_TYPEOF] = "typeof", + [BUILTIN_ASSERT] = "assert", + [BUILTIN_PANIC] = "panic", + + // Async (18-24) + [BUILTIN_SPAWN] = "spawn", + [BUILTIN_JOIN] = "join", + [BUILTIN_DETACH] = "detach", + [BUILTIN_CHANNEL] = "channel", + [BUILTIN_SELECT] = "select", + [BUILTIN_TASK_DEBUG_INFO] = "task_debug_info", + [BUILTIN_APPLY] = "apply", + + // Signal (25-26) + [BUILTIN_SIGNAL] = "signal", + [BUILTIN_RAISE] = "raise", + + // Exec (27-28) + [BUILTIN_EXEC] = "exec", + [BUILTIN_EXEC_ARGV] = "exec_argv", + + // Pointer read (29-41) + [BUILTIN_PTR_READ_I8] = "ptr_read_i8", + [BUILTIN_PTR_READ_I16] = "ptr_read_i16", + [BUILTIN_PTR_READ_I32] = "ptr_read_i32", + [BUILTIN_PTR_READ_I64] = "ptr_read_i64", + [BUILTIN_PTR_READ_U8] = "ptr_read_u8", + [BUILTIN_PTR_READ_U16] = "ptr_read_u16", + [BUILTIN_PTR_READ_U32] = "ptr_read_u32", + [BUILTIN_PTR_READ_U64] = "ptr_read_u64", + [BUILTIN_PTR_READ_F32] = "ptr_read_f32", + [BUILTIN_PTR_READ_F64] = "ptr_read_f64", + [BUILTIN_PTR_READ_PTR] = "ptr_read_ptr", + [BUILTIN_PTR_OFFSET] = "ptr_offset", + [BUILTIN_PTR_DEREF_I32] = "ptr_deref_i32", + + // Pointer write (42-52) + [BUILTIN_PTR_WRITE_I8] = "ptr_write_i8", + [BUILTIN_PTR_WRITE_I16] = "ptr_write_i16", + [BUILTIN_PTR_WRITE_I32] = "ptr_write_i32", + [BUILTIN_PTR_WRITE_I64] = "ptr_write_i64", + [BUILTIN_PTR_WRITE_U8] = "ptr_write_u8", + [BUILTIN_PTR_WRITE_U16] = "ptr_write_u16", + [BUILTIN_PTR_WRITE_U32] = "ptr_write_u32", + [BUILTIN_PTR_WRITE_U64] = "ptr_write_u64", + [BUILTIN_PTR_WRITE_F32] = "ptr_write_f32", + [BUILTIN_PTR_WRITE_F64] = "ptr_write_f64", + [BUILTIN_PTR_WRITE_PTR] = "ptr_write_ptr", + + // Atomics i32 (53-61) + [BUILTIN_ATOMIC_LOAD_I32] = "atomic_load_i32", + [BUILTIN_ATOMIC_STORE_I32] = "atomic_store_i32", + [BUILTIN_ATOMIC_ADD_I32] = "atomic_add_i32", + [BUILTIN_ATOMIC_SUB_I32] = "atomic_sub_i32", + [BUILTIN_ATOMIC_AND_I32] = "atomic_and_i32", + [BUILTIN_ATOMIC_OR_I32] = "atomic_or_i32", + [BUILTIN_ATOMIC_XOR_I32] = "atomic_xor_i32", + [BUILTIN_ATOMIC_CAS_I32] = "atomic_cas_i32", + [BUILTIN_ATOMIC_EXCHANGE_I32] = "atomic_exchange_i32", + + // Atomics i64 (62-70) + [BUILTIN_ATOMIC_LOAD_I64] = "atomic_load_i64", + [BUILTIN_ATOMIC_STORE_I64] = "atomic_store_i64", + [BUILTIN_ATOMIC_ADD_I64] = "atomic_add_i64", + [BUILTIN_ATOMIC_SUB_I64] = "atomic_sub_i64", + [BUILTIN_ATOMIC_AND_I64] = "atomic_and_i64", + [BUILTIN_ATOMIC_OR_I64] = "atomic_or_i64", + [BUILTIN_ATOMIC_XOR_I64] = "atomic_xor_i64", + [BUILTIN_ATOMIC_CAS_I64] = "atomic_cas_i64", + [BUILTIN_ATOMIC_EXCHANGE_I64] = "atomic_exchange_i64", + + // Atomics misc (71) + [BUILTIN_ATOMIC_FENCE] = "atomic_fence", + + // Callback (72-73) + [BUILTIN_CALLBACK] = "callback", + [BUILTIN_CALLBACK_FREE] = "callback_free", + + // Stack (74-75) + [BUILTIN_SET_STACK_LIMIT] = "set_stack_limit", + [BUILTIN_GET_STACK_LIMIT] = "get_stack_limit", + + // DNS/Network (76-78) + [BUILTIN_DNS_RESOLVE] = "dns_resolve", + [BUILTIN_SOCKET_CREATE] = "socket_create", + [BUILTIN_POLL] = "poll", + + // Math (79-80) + [BUILTIN_DIVI] = "divi", + [BUILTIN_MODI] = "modi", + + // String helpers (81) + [BUILTIN_STRING_CONCAT_MANY] = "string_concat_many", +}; + +const char* builtin_name(BuiltinId id) { + if (id >= 0 && id < BUILTIN_COUNT && builtin_names[id] != NULL) { + return builtin_names[id]; + } + return "unknown_builtin"; +} + +// Lookup builtin ID by name +BuiltinId builtin_lookup(const char *name) { + for (int i = 0; i < BUILTIN_COUNT; i++) { + if (builtin_names[i] != NULL && strcmp(builtin_names[i], name) == 0) { + return (BuiltinId)i; + } + } + return -1; +} + +// Type name table +static const char* type_names[] = { + [TYPE_ID_I8] = "i8", + [TYPE_ID_I16] = "i16", + [TYPE_ID_I32] = "i32", + [TYPE_ID_I64] = "i64", + [TYPE_ID_U8] = "u8", + [TYPE_ID_U16] = "u16", + [TYPE_ID_U32] = "u32", + [TYPE_ID_U64] = "u64", + [TYPE_ID_F32] = "f32", + [TYPE_ID_F64] = "f64", + [TYPE_ID_BOOL] = "bool", + [TYPE_ID_STRING] = "string", + [TYPE_ID_RUNE] = "rune", + [TYPE_ID_ARRAY] = "array", + [TYPE_ID_OBJECT] = "object", + [TYPE_ID_PTR] = "ptr", + [TYPE_ID_BUFFER] = "buffer", + [TYPE_ID_NULL] = "null", + [TYPE_ID_FUNCTION] = "function", + [TYPE_ID_TASK] = "task", + [TYPE_ID_CHANNEL] = "channel", + [TYPE_ID_FILE] = "file", + [TYPE_ID_ENUM] = "enum", +}; + +const char* type_id_name(TypeId id) { + if (id >= 0 && id <= TYPE_ID_ENUM) { + return type_names[id]; + } + return "unknown"; +} diff --git a/src/backends/vm/instruction.h b/src/backends/vm/instruction.h new file mode 100644 index 00000000..4c8c0b66 --- /dev/null +++ b/src/backends/vm/instruction.h @@ -0,0 +1,351 @@ +/* + * Hemlock Bytecode VM - Instruction Set + * + * 82 opcodes organized into 11 categories. + * Each opcode is 1 byte, with 0-3 bytes of operands. + * + * Prefix: BC_ (bytecode) to avoid conflicts with AST BinaryOp enum + */ + +#ifndef HEMLOCK_VM_INSTRUCTION_H +#define HEMLOCK_VM_INSTRUCTION_H + +#include +#include + +// Builtin function IDs (for BC_CALL_BUILTIN) +typedef enum { + // Memory (0-10) + BUILTIN_ALLOC = 0, + BUILTIN_TALLOC, + BUILTIN_REALLOC, + BUILTIN_FREE, + BUILTIN_MEMSET, + BUILTIN_MEMCPY, + BUILTIN_SIZEOF, + BUILTIN_BUFFER, + BUILTIN_PTR_TO_BUFFER, + BUILTIN_BUFFER_PTR, + BUILTIN_PTR_NULL, + + // I/O (11-14) + BUILTIN_PRINT, + BUILTIN_EPRINT, + BUILTIN_READ_LINE, + BUILTIN_OPEN, + + // Type (15-17) + BUILTIN_TYPEOF, + BUILTIN_ASSERT, + BUILTIN_PANIC, + + // Async (18-24) + BUILTIN_SPAWN, + BUILTIN_JOIN, + BUILTIN_DETACH, + BUILTIN_CHANNEL, + BUILTIN_SELECT, + BUILTIN_TASK_DEBUG_INFO, + BUILTIN_APPLY, + + // Signal (25-26) + BUILTIN_SIGNAL, + BUILTIN_RAISE, + + // Exec (27-28) + BUILTIN_EXEC, + BUILTIN_EXEC_ARGV, + + // Pointer read (29-41) + BUILTIN_PTR_READ_I8, + BUILTIN_PTR_READ_I16, + BUILTIN_PTR_READ_I32, + BUILTIN_PTR_READ_I64, + BUILTIN_PTR_READ_U8, + BUILTIN_PTR_READ_U16, + BUILTIN_PTR_READ_U32, + BUILTIN_PTR_READ_U64, + BUILTIN_PTR_READ_F32, + BUILTIN_PTR_READ_F64, + BUILTIN_PTR_READ_PTR, + BUILTIN_PTR_OFFSET, + BUILTIN_PTR_DEREF_I32, + + // Pointer write (42-52) + BUILTIN_PTR_WRITE_I8, + BUILTIN_PTR_WRITE_I16, + BUILTIN_PTR_WRITE_I32, + BUILTIN_PTR_WRITE_I64, + BUILTIN_PTR_WRITE_U8, + BUILTIN_PTR_WRITE_U16, + BUILTIN_PTR_WRITE_U32, + BUILTIN_PTR_WRITE_U64, + BUILTIN_PTR_WRITE_F32, + BUILTIN_PTR_WRITE_F64, + BUILTIN_PTR_WRITE_PTR, + + // Atomics i32 (53-61) + BUILTIN_ATOMIC_LOAD_I32, + BUILTIN_ATOMIC_STORE_I32, + BUILTIN_ATOMIC_ADD_I32, + BUILTIN_ATOMIC_SUB_I32, + BUILTIN_ATOMIC_AND_I32, + BUILTIN_ATOMIC_OR_I32, + BUILTIN_ATOMIC_XOR_I32, + BUILTIN_ATOMIC_CAS_I32, + BUILTIN_ATOMIC_EXCHANGE_I32, + + // Atomics i64 (62-70) + BUILTIN_ATOMIC_LOAD_I64, + BUILTIN_ATOMIC_STORE_I64, + BUILTIN_ATOMIC_ADD_I64, + BUILTIN_ATOMIC_SUB_I64, + BUILTIN_ATOMIC_AND_I64, + BUILTIN_ATOMIC_OR_I64, + BUILTIN_ATOMIC_XOR_I64, + BUILTIN_ATOMIC_CAS_I64, + BUILTIN_ATOMIC_EXCHANGE_I64, + + // Atomics misc (71) + BUILTIN_ATOMIC_FENCE, + + // Callback (72-73) + BUILTIN_CALLBACK, + BUILTIN_CALLBACK_FREE, + + // Stack (74-75) + BUILTIN_SET_STACK_LIMIT, + BUILTIN_GET_STACK_LIMIT, + + // DNS/Network (76-78) + BUILTIN_DNS_RESOLVE, + BUILTIN_SOCKET_CREATE, + BUILTIN_POLL, + + // Math (79-80) + BUILTIN_DIVI, // Integer division (floor) + BUILTIN_MODI, // Integer modulo + + // String helpers (81) + BUILTIN_STRING_CONCAT_MANY, + + BUILTIN_COUNT // Total number of builtins +} BuiltinId; + +// Type IDs for BC_CAST and BC_CHECK_TYPE +typedef enum { + TYPE_ID_I8 = 0, + TYPE_ID_I16, + TYPE_ID_I32, + TYPE_ID_I64, + TYPE_ID_U8, + TYPE_ID_U16, + TYPE_ID_U32, + TYPE_ID_U64, + TYPE_ID_F32, + TYPE_ID_F64, + TYPE_ID_BOOL, + TYPE_ID_STRING, + TYPE_ID_RUNE, + TYPE_ID_ARRAY, + TYPE_ID_OBJECT, + TYPE_ID_PTR, + TYPE_ID_BUFFER, + TYPE_ID_NULL, + TYPE_ID_FUNCTION, + TYPE_ID_TASK, + TYPE_ID_CHANNEL, + TYPE_ID_FILE, + TYPE_ID_ENUM, +} TypeId; + +// Bytecode opcodes (BC_ prefix to avoid conflicts with AST enums) +typedef enum { + // ======================================== + // Category 1: Constants & Literals (0x00-0x0F) + // ======================================== + BC_CONST = 0x00, // [idx:16] Push constant from pool + BC_CONST_BYTE = 0x01, // [val:8] Push small integer (0-255) + BC_NULL = 0x02, // [] Push null + BC_TRUE = 0x03, // [] Push true + BC_FALSE = 0x04, // [] Push false + BC_ARRAY = 0x05, // [cnt:16] Pop n elements, push array + BC_OBJECT = 0x06, // [cnt:16] Pop n key-value pairs, push object + BC_STRING_INTERP = 0x07, // [cnt:16] Interpolate n parts into string + BC_CLOSURE = 0x08, // [idx:16][upvals:8] Create closure + BC_ENUM_VALUE = 0x09, // [idx:16] Push enum variant value + + // ======================================== + // Category 2: Variables (0x10-0x1F) + // ======================================== + BC_GET_LOCAL = 0x10, // [slot:8] Load local variable + BC_SET_LOCAL = 0x11, // [slot:8] Store to local + BC_GET_UPVALUE = 0x12, // [slot:8] Load captured variable + BC_SET_UPVALUE = 0x13, // [slot:8] Store to captured variable + BC_GET_GLOBAL = 0x14, // [idx:16] Load global by name + BC_SET_GLOBAL = 0x15, // [idx:16] Store to global + BC_DEFINE_GLOBAL = 0x16, // [idx:16] Define new global + BC_GET_PROPERTY = 0x17, // [idx:16] Get object property + BC_SET_PROPERTY = 0x18, // [idx:16] Set object property + BC_GET_INDEX = 0x19, // [] array[index] or object[key] + BC_SET_INDEX = 0x1A, // [] array[index] = value + BC_CLOSE_UPVALUE = 0x1B, // [] Close upvalue on stack top + + // ======================================== + // Category 3: Arithmetic (0x20-0x2F) + // ======================================== + BC_ADD = 0x20, // [] a + b (type promotion, string concat) + BC_SUB = 0x21, // [] a - b + BC_MUL = 0x22, // [] a * b + BC_DIV = 0x23, // [] a / b (always returns f64) + BC_MOD = 0x24, // [] a % b + BC_NEGATE = 0x25, // [] -a + BC_INC = 0x26, // [] ++a (in-place) + BC_DEC = 0x27, // [] --a (in-place) + BC_ADD_I32 = 0x28, // [] Fast path: i32 + i32 + BC_SUB_I32 = 0x29, // [] Fast path: i32 - i32 + BC_MUL_I32 = 0x2A, // [] Fast path: i32 * i32 + + // ======================================== + // Category 4: Comparison (0x30-0x3F) + // ======================================== + BC_EQ = 0x30, // [] a == b + BC_NE = 0x31, // [] a != b + BC_LT = 0x32, // [] a < b + BC_LE = 0x33, // [] a <= b + BC_GT = 0x34, // [] a > b + BC_GE = 0x35, // [] a >= b + BC_EQ_I32 = 0x36, // [] Fast path: i32 == i32 + BC_LT_I32 = 0x37, // [] Fast path: i32 < i32 + + // ======================================== + // Category 5: Logical & Bitwise (0x40-0x4F) + // ======================================== + BC_NOT = 0x40, // [] !a + BC_BIT_NOT = 0x41, // [] ~a + BC_BIT_AND = 0x42, // [] a & b + BC_BIT_OR = 0x43, // [] a | b + BC_BIT_XOR = 0x44, // [] a ^ b + BC_LSHIFT = 0x45, // [] a << b + BC_RSHIFT = 0x46, // [] a >> b + BC_COALESCE = 0x47, // [off:16] a ?? b (short-circuit) + BC_OPTIONAL_CHAIN = 0x48, // [off:16] a?.b (null short-circuit) + + // ======================================== + // Category 6: Control Flow (0x50-0x5F) + // ======================================== + BC_JUMP = 0x50, // [off:16] Unconditional jump + BC_JUMP_IF_FALSE = 0x51, // [off:16] Jump if top is falsy + BC_JUMP_IF_TRUE = 0x52, // [off:16] Jump if top is truthy + BC_JUMP_IF_FALSE_POP= 0x53, // [off:16] Jump if false, always pop + BC_LOOP = 0x54, // [off:16] Jump backward (loop) + BC_BREAK = 0x55, // [] Break from loop + BC_CONTINUE = 0x56, // [] Continue to next iteration + BC_SWITCH = 0x57, // [cnt:16] Jump table dispatch + BC_CASE = 0x58, // [off:16] Case label marker + BC_FOR_IN_INIT = 0x59, // [] Initialize for-in iterator + BC_FOR_IN_NEXT = 0x5A, // [off:16] Get next or jump to end + BC_POP = 0x5B, // [] Discard top of stack + BC_POPN = 0x5C, // [n:8] Discard n values from stack + BC_DUP = 0x5D, // [] Duplicate top of stack + BC_DUP2 = 0x5E, // [] Duplicate top two stack values + BC_SWAP = 0x5F, // [] Swap top two stack values + + // ======================================== + // Category 7: Functions & Calls (0x60-0x6F) + // ======================================== + BC_CALL = 0x60, // [argc:8] Call function + BC_CALL_METHOD = 0x61, // [idx:16][argc:8] Call method on object + BC_CALL_BUILTIN = 0x62, // [id:16][argc:8] Call builtin function + BC_RETURN = 0x63, // [] Return from function + BC_APPLY = 0x64, // [] apply(fn, args_array) + BC_TAIL_CALL = 0x65, // [argc:8] Tail call optimization + BC_SUPER = 0x66, // [idx:16] Access super method + BC_INVOKE = 0x67, // [idx:16][argc:8] Optimized method call + + // ======================================== + // Category 8: Exception Handling (0x70-0x7F) + // ======================================== + BC_TRY = 0x70, // [catch:16][finally:16] Begin try block + BC_CATCH = 0x71, // [] Begin catch (push exception) + BC_FINALLY = 0x72, // [] Begin finally block + BC_END_TRY = 0x73, // [] End try-catch-finally + BC_THROW = 0x74, // [] Throw exception + BC_DEFER = 0x75, // [idx:16] Register deferred call + BC_GET_SELF = 0x76, // [] Get method receiver (self) + BC_SET_SELF = 0x77, // [] Set method receiver (self) + BC_GET_KEY = 0x78, // [] Get key at index (for object iteration) + BC_SET_OBJ_TYPE = 0x79, // [idx:16] Set object type name (for typeof) + + // ======================================== + // Category 9: Async & Concurrency (0x80-0x8F) + // ======================================== + BC_SPAWN = 0x80, // [argc:8] Spawn async task + BC_AWAIT = 0x81, // [] Await task result + BC_JOIN = 0x82, // [] Join task (explicit) + BC_DETACH = 0x83, // [] Detach task + BC_CHANNEL = 0x84, // [] Create channel (capacity on stack) + BC_SEND = 0x85, // [] Send on channel + BC_RECV = 0x86, // [] Receive from channel + BC_SELECT = 0x87, // [] Select on multiple channels + + // ======================================== + // Category 10: Type Operations (0x90-0x9F) + // ======================================== + BC_TYPEOF = 0x90, // [] Get type string + BC_CAST = 0x91, // [type:8] Explicit type cast + BC_CHECK_TYPE = 0x92, // [type:8] Runtime type check + BC_DEFINE_TYPE = 0x93, // [idx:16] Register type definition + BC_DEFINE_ENUM = 0x94, // [idx:16] Register enum definition + + // ======================================== + // Category 11: Debug & Misc (0xF0-0xFF) + // ======================================== + BC_NOP = 0xF0, // [] No operation + BC_PRINT = 0xF1, // [argc:8] Print values + BC_ASSERT = 0xF2, // [] Assert with optional message + BC_BURY3 = 0xF3, // [] [a,b,c,d] -> [c,a,b,d] (move 2nd under 4th) + BC_ROT3 = 0xF4, // [] [a,b,c] -> [b,c,a] (rotate 3, bottom to top) + BC_DEBUG_BREAK = 0xFE, // [] Debugger breakpoint + BC_HALT = 0xFF, // [] Stop execution + +} OpCode; + +// Instruction metadata for disassembly and verification +typedef struct { + const char *name; // Human-readable name + int operand_bytes; // Number of operand bytes (0, 1, 2, 3, 4) + int stack_effect; // Net stack change (-128 = variable) +} InstructionInfo; + +// Get instruction info by opcode +const InstructionInfo* instruction_info(OpCode op); + +// Get instruction size in bytes +int instruction_size(OpCode op); + +// Get builtin function name +const char* builtin_name(BuiltinId id); + +// Lookup builtin ID by name (-1 if not found) +BuiltinId builtin_lookup(const char *name); + +// Get type name by ID +const char* type_id_name(TypeId id); + +// Operand encoding helpers +#define READ_BYTE(ip) (*(ip)++) +#define READ_SHORT(ip) ((ip) += 2, (uint16_t)((ip)[-2] << 8 | (ip)[-1])) +#define READ_SIGNED_SHORT(ip) ((int16_t)READ_SHORT(ip)) + +// Instruction size helpers +#define INSTR_SIZE_0 1 // opcode only +#define INSTR_SIZE_1 2 // opcode + 1 byte operand +#define INSTR_SIZE_2 3 // opcode + 2 byte operand +#define INSTR_SIZE_3 4 // opcode + 3 byte operand +#define INSTR_SIZE_4 5 // opcode + 4 byte operand + +// Stack effect constants +#define STACK_EFFECT_VARIABLE -128 + +#endif // HEMLOCK_VM_INSTRUCTION_H diff --git a/src/backends/vm/main.c b/src/backends/vm/main.c new file mode 100644 index 00000000..750dc15a --- /dev/null +++ b/src/backends/vm/main.c @@ -0,0 +1,347 @@ +/* + * Hemlock Bytecode VM - Main Entry Point + * + * hemlockvm - Bytecode VM interpreter for Hemlock + */ + +#include +#include +#include +#include +#include "vm.h" +#include "compiler.h" +#include "debug.h" +#include "lexer.h" +#include "parser.h" +#include "ast.h" +#include "resolver.h" +#include "optimizer.h" +#include "version.h" +#include "bundler.h" + +// Read entire file into a string +static char* read_file(const char *path) { + FILE *file = fopen(path, "rb"); + if (file == NULL) { + fprintf(stderr, "Error: Could not open file '%s'\n", path); + return NULL; + } + + fseek(file, 0, SEEK_END); + long size = ftell(file); + rewind(file); + + char *buffer = malloc(size + 1); + if (buffer == NULL) { + fprintf(stderr, "Error: Could not allocate memory for file\n"); + fclose(file); + return NULL; + } + + size_t bytes_read = fread(buffer, 1, size, file); + if (bytes_read < (size_t)size) { + fprintf(stderr, "Error: Could not read file\n"); + free(buffer); + fclose(file); + return NULL; + } + + buffer[size] = '\0'; + fclose(file); + return buffer; +} + +// Run source code through the bytecode VM +static int run_source(const char *source, bool disassemble, bool trace, int script_argc, char **script_argv) { + // Parse + Lexer lexer; + lexer_init(&lexer, source); + + Parser parser; + parser_init(&parser, &lexer); + + int stmt_count; + Stmt **statements = parse_program(&parser, &stmt_count); + + if (parser.had_error) { + fprintf(stderr, "Parse failed!\n"); + return 1; + } + + // Note: Skip resolver for VM - the VM compiler handles its own variable resolution + // resolve_program(statements, stmt_count); + + // Optimize AST (constant folding, etc.) + optimize_program(statements, stmt_count); + + // Compile to bytecode + Chunk *chunk = compile_program(statements, stmt_count); + if (!chunk) { + fprintf(stderr, "Compilation failed!\n"); + return 1; + } + + // Disassemble if requested + if (disassemble) { + disassemble_chunk(chunk, "script"); + chunk_free(chunk); + return 0; + } + + // Execute + VM *vm = vm_new(); + vm_trace_execution(vm, trace); + vm_set_args(vm, script_argc, script_argv); // Set command-line args + + VMResult result = vm_run(vm, chunk); + + vm_free(vm); + chunk_free(chunk); + + // Free AST + for (int i = 0; i < stmt_count; i++) { + stmt_free(statements[i]); + } + free(statements); + + return (result == VM_OK) ? 0 : 1; +} + +// Check if source has any import statements +static bool has_imports(const char *source) { + // Quick scan for import keyword + const char *p = source; + while (*p) { + // Skip comments + if (p[0] == '/' && p[1] == '/') { + while (*p && *p != '\n') p++; + continue; + } + if (p[0] == '/' && p[1] == '*') { + p += 2; + while (*p && !(p[0] == '*' && p[1] == '/')) p++; + if (*p) p += 2; + continue; + } + // Skip strings + if (*p == '"') { + p++; + while (*p && *p != '"') { + if (*p == '\\' && p[1]) p++; + p++; + } + if (*p) p++; + continue; + } + // Check for 'import' keyword + if (strncmp(p, "import", 6) == 0 && + (p == source || !isalnum((unsigned char)p[-1])) && + !isalnum((unsigned char)p[6])) { + return true; + } + p++; + } + return false; +} + +// Run file using bundler for imports +static int run_file_with_imports(const char *path, bool disassemble, bool trace, int script_argc, char **script_argv) { + // Use bundler to resolve all imports + BundleOptions opts = bundle_options_default(); + opts.verbose = false; + opts.tree_shake = false; // Don't tree shake for now + + Bundle *bundle = bundle_create(path, &opts); + if (!bundle) { + fprintf(stderr, "Error: Failed to create bundle from '%s'\n", path); + return 1; + } + + // Flatten all modules into single AST + if (bundle_flatten(bundle) != 0) { + fprintf(stderr, "Error: Failed to flatten bundle\n"); + bundle_free(bundle); + return 1; + } + + // Get the flattened statements + int stmt_count; + Stmt **statements = bundle_get_statements(bundle, &stmt_count); + if (!statements || stmt_count == 0) { + fprintf(stderr, "Error: Bundle produced no statements\n"); + bundle_free(bundle); + return 1; + } + + // Optimize AST + optimize_program(statements, stmt_count); + + // Compile to bytecode + Chunk *chunk = compile_program(statements, stmt_count); + if (!chunk) { + fprintf(stderr, "Compilation failed!\n"); + bundle_free(bundle); + return 1; + } + + // Disassemble if requested + if (disassemble) { + disassemble_chunk(chunk, "script"); + chunk_free(chunk); + bundle_free(bundle); + return 0; + } + + // Execute + VM *vm = vm_new(); + vm_trace_execution(vm, trace); + vm_set_args(vm, script_argc, script_argv); // Set command-line args + + VMResult result = vm_run(vm, chunk); + + vm_free(vm); + chunk_free(chunk); + bundle_free(bundle); + + return (result == VM_OK) ? 0 : 1; +} + +// Run file - use bundler if imports detected, otherwise direct parse +static int run_file(const char *path, bool disassemble, bool trace, int script_argc, char **script_argv) { + char *source = read_file(path); + if (!source) { + return 1; + } + + // Check if file has imports - if so, use bundler + if (has_imports(source)) { + free(source); + return run_file_with_imports(path, disassemble, trace, script_argc, script_argv); + } + + int result = run_source(source, disassemble, trace, script_argc, script_argv); + free(source); + return result; +} + +// REPL +static void run_repl(void) { + printf("Hemlock Bytecode VM %s\n", HEMLOCK_VERSION); + printf("Type 'exit' to quit.\n\n"); + + VM *vm = vm_new(); + char line[1024]; + + while (1) { + printf(">>> "); + fflush(stdout); + + if (!fgets(line, sizeof(line), stdin)) { + printf("\n"); + break; + } + + // Remove trailing newline + size_t len = strlen(line); + if (len > 0 && line[len - 1] == '\n') { + line[len - 1] = '\0'; + } + + // Check for exit + if (strcmp(line, "exit") == 0 || strcmp(line, "quit") == 0) { + break; + } + + // Skip empty lines + if (strlen(line) == 0) { + continue; + } + + // Parse and run + Lexer lexer; + lexer_init(&lexer, line); + + Parser parser; + parser_init(&parser, &lexer); + + int stmt_count; + Stmt **statements = parse_program(&parser, &stmt_count); + + if (!parser.had_error && stmt_count > 0) { + resolve_program(statements, stmt_count); + optimize_program(statements, stmt_count); + + Chunk *chunk = compile_program(statements, stmt_count); + if (chunk) { + vm_reset(vm); + vm_run(vm, chunk); + chunk_free(chunk); + } + } + + // Free AST + for (int i = 0; i < stmt_count; i++) { + stmt_free(statements[i]); + } + free(statements); + } + + vm_free(vm); +} + +static void print_usage(const char *program) { + fprintf(stderr, "Usage: %s [options] [file.hml]\n", program); + fprintf(stderr, "\nOptions:\n"); + fprintf(stderr, " --disasm, -d Disassemble bytecode instead of running\n"); + fprintf(stderr, " --trace, -t Trace execution (debug)\n"); + fprintf(stderr, " --version, -v Show version information\n"); + fprintf(stderr, " --help, -h Show this help message\n"); + fprintf(stderr, "\nIf no file is provided, starts an interactive REPL.\n"); +} + +static void print_version(void) { + printf("Hemlock Bytecode VM %s\n", HEMLOCK_VERSION); + printf("Build date: %s %s\n", __DATE__, __TIME__); +} + +int main(int argc, char **argv) { + const char *file = NULL; + bool disassemble = false; + bool trace = false; + int script_arg_start = 0; // Index where script args begin + + // Parse arguments - stop at first non-option (the file) + for (int i = 1; i < argc; i++) { + if (file != NULL) { + // Already found file, remaining args are script args + break; + } + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + print_usage(argv[0]); + return 0; + } else if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) { + print_version(); + return 0; + } else if (strcmp(argv[i], "--disasm") == 0 || strcmp(argv[i], "-d") == 0) { + disassemble = true; + } else if (strcmp(argv[i], "--trace") == 0 || strcmp(argv[i], "-t") == 0) { + trace = true; + } else if (argv[i][0] != '-') { + file = argv[i]; + script_arg_start = i; // Script args start at file (inclusive, like interpreter) + } else { + fprintf(stderr, "Unknown option: %s\n", argv[i]); + print_usage(argv[0]); + return 1; + } + } + + if (file) { + int script_argc = argc - script_arg_start; + char **script_argv = &argv[script_arg_start]; + return run_file(file, disassemble, trace, script_argc, script_argv); + } else { + run_repl(); + return 0; + } +} diff --git a/src/backends/vm/test_parity.sh b/src/backends/vm/test_parity.sh new file mode 100755 index 00000000..bb31b066 --- /dev/null +++ b/src/backends/vm/test_parity.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +PASS=0 +FAIL=0 +FAILED_TESTS="" + +cd /home/user/hemlock + +for f in tests/parity/language/*.hml; do + expected="${f%.hml}.expected" + if [ -f "$expected" ]; then + name=$(basename "$f") + output=$(./src/backends/vm/hemlockvm "$f" 2>&1) + exp=$(cat "$expected") + if [ "$output" = "$exp" ]; then + echo "✓ $name" + PASS=$((PASS + 1)) + else + echo "✗ $name" + FAIL=$((FAIL + 1)) + FAILED_TESTS="$FAILED_TESTS $name" + fi + fi +done + +echo "" +echo "Results: $PASS passed, $FAIL failed" +if [ -n "$FAILED_TESTS" ]; then + echo "Failed tests:$FAILED_TESTS" +fi diff --git a/src/backends/vm/vm.c b/src/backends/vm/vm.c new file mode 100644 index 00000000..0b3c6875 --- /dev/null +++ b/src/backends/vm/vm.c @@ -0,0 +1,7282 @@ +/* + * Hemlock Bytecode VM - Virtual Machine Implementation + * + * Stack-based bytecode interpreter with computed goto dispatch. + */ + +#define _POSIX_C_SOURCE 200809L +#define _GNU_SOURCE // For M_PI, M_E + +#include "vm.h" +#include "debug.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Debug tracing +static int vm_trace_enabled = 0; + +// ============================================ +// Type Registry for define types with defaults +// ============================================ + +typedef struct VMTypeField { + char *name; + bool optional; + Value default_value; // Pre-computed default (for simple cases) + bool has_default; +} VMTypeField; + +typedef struct VMTypeDef { + char *name; + VMTypeField *fields; + int num_fields; +} VMTypeDef; + +typedef struct VMTypeRegistry { + VMTypeDef **types; + int count; + int capacity; +} VMTypeRegistry; + +static VMTypeRegistry g_vm_type_registry = {0}; + +static void vm_type_registry_init(void) { + g_vm_type_registry.types = NULL; + g_vm_type_registry.count = 0; + g_vm_type_registry.capacity = 0; +} + +static void vm_type_registry_free(void) { + for (int i = 0; i < g_vm_type_registry.count; i++) { + VMTypeDef *t = g_vm_type_registry.types[i]; + free(t->name); + for (int j = 0; j < t->num_fields; j++) { + free(t->fields[j].name); + } + free(t->fields); + free(t); + } + free(g_vm_type_registry.types); + g_vm_type_registry.types = NULL; + g_vm_type_registry.count = 0; + g_vm_type_registry.capacity = 0; +} + +static void vm_register_type(const char *name, VMTypeField *fields, int num_fields) { + // Check if type already exists (update it) + for (int i = 0; i < g_vm_type_registry.count; i++) { + if (strcmp(g_vm_type_registry.types[i]->name, name) == 0) { + // Update existing type + VMTypeDef *t = g_vm_type_registry.types[i]; + for (int j = 0; j < t->num_fields; j++) { + free(t->fields[j].name); + } + free(t->fields); + t->fields = fields; + t->num_fields = num_fields; + return; + } + } + + // Add new type + if (g_vm_type_registry.count >= g_vm_type_registry.capacity) { + int new_cap = g_vm_type_registry.capacity == 0 ? 16 : g_vm_type_registry.capacity * 2; + g_vm_type_registry.types = realloc(g_vm_type_registry.types, sizeof(VMTypeDef*) * new_cap); + g_vm_type_registry.capacity = new_cap; + } + + VMTypeDef *t = malloc(sizeof(VMTypeDef)); + t->name = strdup(name); + t->fields = fields; + t->num_fields = num_fields; + g_vm_type_registry.types[g_vm_type_registry.count++] = t; +} + +static VMTypeDef* vm_lookup_type(const char *name) { + for (int i = 0; i < g_vm_type_registry.count; i++) { + if (strcmp(g_vm_type_registry.types[i]->name, name) == 0) { + return g_vm_type_registry.types[i]; + } + } + return NULL; +} + +// Apply default values from a type definition to an object +static void vm_apply_type_defaults(Object *obj, VMTypeDef *type) { + for (int i = 0; i < type->num_fields; i++) { + VMTypeField *field = &type->fields[i]; + if (!field->optional || !field->has_default) continue; + + // Check if field already exists in object + bool found = false; + for (int j = 0; j < obj->num_fields; j++) { + if (strcmp(obj->field_names[j], field->name) == 0) { + found = true; + break; + } + } + + if (!found) { + // Add field with default value + if (obj->num_fields >= obj->capacity) { + obj->capacity *= 2; + obj->field_names = realloc(obj->field_names, sizeof(char*) * obj->capacity); + obj->field_values = realloc(obj->field_values, sizeof(Value) * obj->capacity); + } + obj->field_names[obj->num_fields] = strdup(field->name); + obj->field_values[obj->num_fields] = field->default_value; + obj->num_fields++; + } + } +} + +// ============================================ +// Signal Handling Infrastructure +// ============================================ + +#include + +#define MAX_SIGNALS 64 + +// Signal handler registry +static struct { + VMClosure *handlers[MAX_SIGNALS]; // VM closures for each signal + volatile sig_atomic_t pending[MAX_SIGNALS]; // Flags for pending signals + VM *vm; // Current VM (for calling handlers) +} g_signal_registry = {0}; + +// Forward declaration for vm_call_closure (defined later) +static Value vm_call_closure(VM *vm, VMClosure *closure, Value *args, int argc); + +// C signal handler - just sets the pending flag +static void vm_signal_handler(int signum) { + if (signum >= 0 && signum < MAX_SIGNALS) { + g_signal_registry.pending[signum] = 1; + } +} + +// Check and handle pending signals (call this periodically in VM loop) +static void vm_check_signals(VM *vm) { + for (int i = 0; i < MAX_SIGNALS; i++) { + if (g_signal_registry.pending[i] && g_signal_registry.handlers[i]) { + g_signal_registry.pending[i] = 0; + + // Call the VM closure with signal number as argument + VMClosure *handler = g_signal_registry.handlers[i]; + Value sig_arg = {.type = VAL_I32, .as.as_i32 = i}; + vm_call_closure(vm, handler, &sig_arg, 1); + } + } +} + +// Register a signal handler +static void vm_register_signal(int signum, VMClosure *handler) { + if (signum < 0 || signum >= MAX_SIGNALS) return; + + g_signal_registry.handlers[signum] = handler; + + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + + if (handler) { + // Register our C handler using sigaction for reliable behavior + sa.sa_handler = vm_signal_handler; + sa.sa_flags = SA_RESTART; // Restart interrupted syscalls + sigemptyset(&sa.sa_mask); + sigaction(signum, &sa, NULL); + } else { + // Reset to default + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + sigaction(signum, &sa, NULL); + } +} + +// ============================================ +// Math Builtin Implementations for stdlib +// ============================================ + +// Value constructors for stdlib (simple inline versions) +static inline Value stdlib_val_f64(double f) { + Value v; v.type = VAL_F64; v.as.as_f64 = f; return v; +} +static inline Value stdlib_val_null(void) { + Value v; v.type = VAL_NULL; return v; +} + +static double vm_get_number(Value v) { + switch (v.type) { + case VAL_I8: return (double)v.as.as_i8; + case VAL_I16: return (double)v.as.as_i16; + case VAL_I32: return (double)v.as.as_i32; + case VAL_I64: return (double)v.as.as_i64; + case VAL_U8: return (double)v.as.as_u8; + case VAL_U16: return (double)v.as.as_u16; + case VAL_U32: return (double)v.as.as_u32; + case VAL_U64: return (double)v.as.as_u64; + case VAL_F32: return (double)v.as.as_f32; + case VAL_F64: return v.as.as_f64; + default: return 0.0; + } +} + +#define VM_MATH_UNARY(name, func) \ + static Value vm_builtin_##name(Value *args, int argc, void *ctx) { \ + (void)ctx; (void)argc; \ + return stdlib_val_f64(func(vm_get_number(args[0]))); \ + } + +#define VM_MATH_BINARY(name, func) \ + static Value vm_builtin_##name(Value *args, int argc, void *ctx) { \ + (void)ctx; (void)argc; \ + return stdlib_val_f64(func(vm_get_number(args[0]), vm_get_number(args[1]))); \ + } + +VM_MATH_UNARY(sin, sin) +VM_MATH_UNARY(cos, cos) +VM_MATH_UNARY(tan, tan) +VM_MATH_UNARY(asin, asin) +VM_MATH_UNARY(acos, acos) +VM_MATH_UNARY(atan, atan) +VM_MATH_BINARY(atan2, atan2) +VM_MATH_UNARY(sqrt, sqrt) +VM_MATH_BINARY(pow, pow) +VM_MATH_UNARY(exp, exp) +VM_MATH_UNARY(log, log) +VM_MATH_UNARY(log10, log10) +VM_MATH_UNARY(log2, log2) +VM_MATH_UNARY(floor, floor) +VM_MATH_UNARY(ceil, ceil) +VM_MATH_UNARY(round, round) +VM_MATH_UNARY(trunc, trunc) +VM_MATH_UNARY(vm_fabs, fabs) + +static Value vm_builtin_abs(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + double v = vm_get_number(args[0]); + return stdlib_val_f64(v < 0 ? -v : v); +} + +static Value vm_builtin_min(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + double a = vm_get_number(args[0]); + double b = vm_get_number(args[1]); + return stdlib_val_f64(a < b ? a : b); +} + +static Value vm_builtin_max(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + double a = vm_get_number(args[0]); + double b = vm_get_number(args[1]); + return stdlib_val_f64(a > b ? a : b); +} + +static Value vm_builtin_clamp(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + double v = vm_get_number(args[0]); + double lo = vm_get_number(args[1]); + double hi = vm_get_number(args[2]); + if (v < lo) return stdlib_val_f64(lo); + if (v > hi) return stdlib_val_f64(hi); + return stdlib_val_f64(v); +} + +static Value vm_builtin_rand(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + return stdlib_val_f64((double)rand() / (double)RAND_MAX); +} + +static Value vm_builtin_rand_range(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + double lo = vm_get_number(args[0]); + double hi = vm_get_number(args[1]); + double t = (double)rand() / (double)RAND_MAX; + return stdlib_val_f64(lo + t * (hi - lo)); +} + +static Value vm_builtin_seed(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + srand((unsigned int)vm_get_number(args[0])); + return stdlib_val_null(); +} + +// Integer math builtins - return i64 instead of f64 +static inline Value stdlib_val_i64(int64_t i) { + Value v; v.type = VAL_I64; v.as.as_i64 = i; return v; +} + +static Value vm_builtin_floori(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + return stdlib_val_i64((int64_t)floor(vm_get_number(args[0]))); +} + +static Value vm_builtin_ceili(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + return stdlib_val_i64((int64_t)ceil(vm_get_number(args[0]))); +} + +static Value vm_builtin_roundi(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + return stdlib_val_i64((int64_t)round(vm_get_number(args[0]))); +} + +static Value vm_builtin_trunci(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + return stdlib_val_i64((int64_t)trunc(vm_get_number(args[0]))); +} + +static Value vm_builtin_div(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + double a = vm_get_number(args[0]); + double b = vm_get_number(args[1]); + return stdlib_val_f64(floor(a / b)); +} + +static Value vm_builtin_divi(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + int64_t a = (int64_t)vm_get_number(args[0]); + int64_t b = (int64_t)vm_get_number(args[1]); + return stdlib_val_i64(a / b); +} + +// Forward declarations for functions defined later +static Value vm_make_string(const char *data, int len); +static Value val_bool_vm(int b); +static Value val_i32_vm(int32_t i); + +// Type constructor builtins (parse strings or convert types) +static Value vm_builtin_type_i8(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + Value v = args[0]; + int8_t result; + if (v.type == VAL_STRING && v.as.as_string) { + result = (int8_t)strtoll(v.as.as_string->data, NULL, 0); + } else { + result = (int8_t)vm_get_number(v); + } + Value r = {.type = VAL_I8}; r.as.as_i8 = result; return r; +} + +static Value vm_builtin_type_i16(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + Value v = args[0]; + int16_t result; + if (v.type == VAL_STRING && v.as.as_string) { + result = (int16_t)strtoll(v.as.as_string->data, NULL, 0); + } else { + result = (int16_t)vm_get_number(v); + } + Value r = {.type = VAL_I16}; r.as.as_i16 = result; return r; +} + +static Value vm_builtin_type_i32(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + Value v = args[0]; + int32_t result; + if (v.type == VAL_STRING && v.as.as_string) { + result = (int32_t)strtoll(v.as.as_string->data, NULL, 0); + } else { + result = (int32_t)vm_get_number(v); + } + Value r = {.type = VAL_I32}; r.as.as_i32 = result; return r; +} + +static Value vm_builtin_type_i64(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + Value v = args[0]; + int64_t result; + if (v.type == VAL_STRING && v.as.as_string) { + result = strtoll(v.as.as_string->data, NULL, 0); + } else { + result = (int64_t)vm_get_number(v); + } + Value r = {.type = VAL_I64}; r.as.as_i64 = result; return r; +} + +static Value vm_builtin_type_u8(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + Value v = args[0]; + uint8_t result; + if (v.type == VAL_STRING && v.as.as_string) { + result = (uint8_t)strtoull(v.as.as_string->data, NULL, 0); + } else { + result = (uint8_t)vm_get_number(v); + } + Value r = {.type = VAL_U8}; r.as.as_u8 = result; return r; +} + +static Value vm_builtin_type_u16(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + Value v = args[0]; + uint16_t result; + if (v.type == VAL_STRING && v.as.as_string) { + result = (uint16_t)strtoull(v.as.as_string->data, NULL, 0); + } else { + result = (uint16_t)vm_get_number(v); + } + Value r = {.type = VAL_U16}; r.as.as_u16 = result; return r; +} + +static Value vm_builtin_type_u32(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + Value v = args[0]; + uint32_t result; + if (v.type == VAL_STRING && v.as.as_string) { + result = (uint32_t)strtoull(v.as.as_string->data, NULL, 0); + } else { + result = (uint32_t)vm_get_number(v); + } + Value r = {.type = VAL_U32}; r.as.as_u32 = result; return r; +} + +static Value vm_builtin_type_u64(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + Value v = args[0]; + uint64_t result; + if (v.type == VAL_STRING && v.as.as_string) { + result = strtoull(v.as.as_string->data, NULL, 0); + } else { + result = (uint64_t)vm_get_number(v); + } + Value r = {.type = VAL_U64}; r.as.as_u64 = result; return r; +} + +static Value vm_builtin_type_f32(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + Value v = args[0]; + float result; + if (v.type == VAL_STRING && v.as.as_string) { + result = (float)strtod(v.as.as_string->data, NULL); + } else { + result = (float)vm_get_number(v); + } + Value r = {.type = VAL_F32}; r.as.as_f32 = result; return r; +} + +static Value vm_builtin_type_f64(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + Value v = args[0]; + double result; + if (v.type == VAL_STRING && v.as.as_string) { + result = strtod(v.as.as_string->data, NULL); + } else { + result = vm_get_number(v); + } + Value r = {.type = VAL_F64}; r.as.as_f64 = result; return r; +} + +static Value vm_builtin_type_bool(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + Value v = args[0]; + bool result; + if (v.type == VAL_STRING && v.as.as_string) { + result = strcmp(v.as.as_string->data, "true") == 0; + } else { + result = vm_get_number(v) != 0; + } + return val_bool_vm(result); +} + +// Environment builtins +static Value vm_builtin_getenv(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return stdlib_val_null(); + } + const char *val = getenv(args[0].as.as_string->data); + if (!val) return stdlib_val_null(); + return vm_make_string(val, strlen(val)); +} + +static Value vm_builtin_setenv(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string || + args[1].type != VAL_STRING || !args[1].as.as_string) { + return stdlib_val_null(); + } + setenv(args[0].as.as_string->data, args[1].as.as_string->data, 1); + return stdlib_val_null(); +} + +static Value vm_builtin_unsetenv(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return stdlib_val_null(); + } + unsetenv(args[0].as.as_string->data); + return stdlib_val_null(); +} + +// String from bytes - convert array of bytes to string +static Value vm_builtin_string_from_bytes(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (argc < 1) return stdlib_val_null(); + + char *data = NULL; + int length = 0; + + if (args[0].type == VAL_BUFFER) { + Buffer *buf = args[0].as.as_buffer; + if (!buf || !buf->data) return vm_make_string("", 0); + length = buf->length; + data = malloc(length + 1); + if (!data) return stdlib_val_null(); + memcpy(data, buf->data, length); + data[length] = '\0'; + } else if (args[0].type == VAL_ARRAY) { + Array *arr = args[0].as.as_array; + if (!arr || arr->length == 0) return vm_make_string("", 0); + length = arr->length; + data = malloc(length + 1); + if (!data) return stdlib_val_null(); + for (int i = 0; i < length; i++) { + Value elem = arr->elements[i]; + int byte_val = 0; + switch (elem.type) { + case VAL_I8: byte_val = (unsigned char)elem.as.as_i8; break; + case VAL_I16: byte_val = (unsigned char)elem.as.as_i16; break; + case VAL_I32: byte_val = (unsigned char)elem.as.as_i32; break; + case VAL_I64: byte_val = (unsigned char)elem.as.as_i64; break; + case VAL_U8: byte_val = elem.as.as_u8; break; + case VAL_U16: byte_val = (unsigned char)elem.as.as_u16; break; + case VAL_U32: byte_val = (unsigned char)elem.as.as_u32; break; + case VAL_U64: byte_val = (unsigned char)elem.as.as_u64; break; + default: byte_val = 0; + } + data[i] = (char)byte_val; + } + data[length] = '\0'; + } else { + return stdlib_val_null(); + } + + // Create string value + String *s = malloc(sizeof(String)); + s->data = data; + s->length = length; + s->char_length = -1; + s->capacity = length + 1; + s->ref_count = 1; + + Value v = {.type = VAL_STRING, .as.as_string = s}; + return v; +} + +// File copy builtin +static Value vm_builtin_copy_file(Value *args, int argc, void *ctx) { + (void)ctx; + if (argc < 2) return val_bool_vm(false); + if (args[0].type != VAL_STRING || !args[0].as.as_string || + args[1].type != VAL_STRING || !args[1].as.as_string) { + return val_bool_vm(false); + } + + const char *src = args[0].as.as_string->data; + const char *dest = args[1].as.as_string->data; + + // Open source file + FILE *src_fp = fopen(src, "rb"); + if (!src_fp) return val_bool_vm(false); + + // Open destination file + FILE *dest_fp = fopen(dest, "wb"); + if (!dest_fp) { + fclose(src_fp); + return val_bool_vm(false); + } + + // Copy contents + char buffer[8192]; + size_t bytes_read; + bool success = true; + while ((bytes_read = fread(buffer, 1, sizeof(buffer), src_fp)) > 0) { + if (fwrite(buffer, 1, bytes_read, dest_fp) != bytes_read) { + success = false; + break; + } + } + + fclose(src_fp); + fclose(dest_fp); + return val_bool_vm(success); +} + +// Pointer read builtins +static Value vm_builtin_read_ptr(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_PTR || !args[0].as.as_ptr) { + return stdlib_val_null(); + } + void **ptr = (void**)args[0].as.as_ptr; + Value v = {.type = VAL_PTR, .as.as_ptr = *ptr}; + return v; +} + +// Time builtins +#include +#include + +static Value vm_builtin_now(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + return stdlib_val_i64((int64_t)time(NULL)); +} + +static Value vm_builtin_time_ms(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + struct timeval tv; + gettimeofday(&tv, NULL); + return stdlib_val_i64((int64_t)(tv.tv_sec * 1000LL + tv.tv_usec / 1000LL)); +} + +static Value vm_builtin_sleep(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + double seconds = vm_get_number(args[0]); + if (seconds > 0) { + struct timespec ts; + ts.tv_sec = (time_t)seconds; + ts.tv_nsec = (long)((seconds - ts.tv_sec) * 1e9); + nanosleep(&ts, NULL); + } + return stdlib_val_null(); +} + +static Value vm_builtin_clock(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + return stdlib_val_f64((double)clock() / CLOCKS_PER_SEC); +} + +// Platform builtins +static Value vm_builtin_platform(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; +#ifdef __linux__ + return vm_make_string("linux", 5); +#elif defined(__APPLE__) + return vm_make_string("darwin", 6); +#elif defined(_WIN32) + return vm_make_string("windows", 7); +#else + return vm_make_string("unknown", 7); +#endif +} + +static Value vm_builtin_arch(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; +#if defined(__x86_64__) || defined(_M_X64) + return vm_make_string("x86_64", 6); +#elif defined(__aarch64__) || defined(_M_ARM64) + return vm_make_string("arm64", 5); +#elif defined(__i386__) || defined(_M_IX86) + return vm_make_string("x86", 3); +#elif defined(__arm__) + return vm_make_string("arm", 3); +#else + return vm_make_string("unknown", 7); +#endif +} + +// OS info builtins +#include +#include +#include + +static Value vm_builtin_os_name(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + struct utsname info; + if (uname(&info) == 0) { + return vm_make_string(info.sysname, strlen(info.sysname)); + } + return vm_make_string("unknown", 7); +} + +static Value vm_builtin_os_version(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + struct utsname info; + if (uname(&info) == 0) { + return vm_make_string(info.release, strlen(info.release)); + } + return vm_make_string("unknown", 7); +} + +static Value vm_builtin_hostname(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + char hostname[256]; + if (gethostname(hostname, sizeof(hostname)) == 0) { + return vm_make_string(hostname, strlen(hostname)); + } + return vm_make_string("unknown", 7); +} + +static Value vm_builtin_username(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + struct passwd *pw = getpwuid(getuid()); + if (pw && pw->pw_name) { + return vm_make_string(pw->pw_name, strlen(pw->pw_name)); + } + return vm_make_string("unknown", 7); +} + +static Value vm_builtin_cpu_count(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + long count = sysconf(_SC_NPROCESSORS_ONLN); + if (count < 1) count = 1; + return val_i32_vm((int32_t)count); +} + +static Value vm_builtin_total_memory(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGESIZE); + if (pages > 0 && page_size > 0) { + return stdlib_val_i64((int64_t)pages * page_size); + } + return stdlib_val_i64(0); +} + +static Value vm_builtin_free_memory(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + long pages = sysconf(_SC_AVPHYS_PAGES); + long page_size = sysconf(_SC_PAGESIZE); + if (pages > 0 && page_size > 0) { + return stdlib_val_i64((int64_t)pages * page_size); + } + return stdlib_val_i64(0); +} + +static Value vm_builtin_homedir(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + const char *home = getenv("HOME"); + if (home) { + return vm_make_string(home, strlen(home)); + } + struct passwd *pw = getpwuid(getuid()); + if (pw && pw->pw_dir) { + return vm_make_string(pw->pw_dir, strlen(pw->pw_dir)); + } + return vm_make_string("/", 1); +} + +static Value vm_builtin_tmpdir(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + const char *tmp = getenv("TMPDIR"); + if (!tmp) tmp = getenv("TMP"); + if (!tmp) tmp = getenv("TEMP"); + if (!tmp) tmp = "/tmp"; + return vm_make_string(tmp, strlen(tmp)); +} + +#include + +static Value vm_builtin_uptime(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + struct sysinfo info; + if (sysinfo(&info) == 0) { + return stdlib_val_i64((int64_t)info.uptime); + } + return stdlib_val_i64(0); +} + +// File system builtins +#include + +static Value vm_builtin_exists(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return val_bool_vm(0); + } + struct stat st; + return val_bool_vm(stat(args[0].as.as_string->data, &st) == 0); +} + +static Value vm_builtin_is_file(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return val_bool_vm(0); + } + struct stat st; + if (stat(args[0].as.as_string->data, &st) != 0) return val_bool_vm(0); + return val_bool_vm(S_ISREG(st.st_mode)); +} + +static Value vm_builtin_is_dir(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return val_bool_vm(0); + } + struct stat st; + if (stat(args[0].as.as_string->data, &st) != 0) return val_bool_vm(0); + return val_bool_vm(S_ISDIR(st.st_mode)); +} + +// File stat - returns object with file information +static Value vm_builtin_file_stat(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return stdlib_val_null(); + } + + struct stat st; + if (stat(args[0].as.as_string->data, &st) != 0) { + return stdlib_val_null(); + } + + // Create object with stat info + Object *obj = malloc(sizeof(Object)); + obj->field_names = malloc(sizeof(char*) * 8); + obj->field_values = malloc(sizeof(Value) * 8); + obj->num_fields = 6; + obj->capacity = 8; + obj->type_name = NULL; + + obj->field_names[0] = strdup("size"); + obj->field_values[0] = stdlib_val_i64((int64_t)st.st_size); + + obj->field_names[1] = strdup("mode"); + Value mode_v = {.type = VAL_I32, .as.as_i32 = (int)st.st_mode}; + obj->field_values[1] = mode_v; + + obj->field_names[2] = strdup("is_file"); + Value is_file_v = {.type = VAL_BOOL, .as.as_bool = S_ISREG(st.st_mode)}; + obj->field_values[2] = is_file_v; + + obj->field_names[3] = strdup("is_dir"); + Value is_dir_v = {.type = VAL_BOOL, .as.as_bool = S_ISDIR(st.st_mode)}; + obj->field_values[3] = is_dir_v; + + obj->field_names[4] = strdup("mtime"); + obj->field_values[4] = stdlib_val_i64((int64_t)st.st_mtime); + + obj->field_names[5] = strdup("ctime"); + obj->field_values[5] = stdlib_val_i64((int64_t)st.st_ctime); + + Value v = {.type = VAL_OBJECT, .as.as_object = obj}; + return v; +} + +// Absolute path - resolve to absolute path +static Value vm_builtin_absolute_path(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return stdlib_val_null(); + } + + char resolved[4096]; + char *result = realpath(args[0].as.as_string->data, resolved); + if (!result) return stdlib_val_null(); + + return vm_make_string(resolved, strlen(resolved)); +} + +// Process builtins +#include + +static Value vm_builtin_get_pid(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + return stdlib_val_i64((int64_t)getpid()); +} + +static Value vm_builtin_getppid(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + return stdlib_val_i64((int64_t)getppid()); +} + +static Value vm_builtin_getuid(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + return stdlib_val_i64((int64_t)getuid()); +} + +static Value vm_builtin_geteuid(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + return stdlib_val_i64((int64_t)geteuid()); +} + +static Value vm_builtin_exit(Value *args, int argc, void *ctx) { + (void)ctx; + int code = 0; + if (argc >= 1) { + code = (int)vm_get_number(args[0]); + } + exit(code); + return stdlib_val_null(); // Never reached +} + +// String builtins +static Value vm_builtin_strlen(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return stdlib_val_i64(0); + } + return stdlib_val_i64((int64_t)args[0].as.as_string->length); +} + +// Hashing builtin +#include +#include + +static Value vm_builtin_sha256(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return stdlib_val_null(); + } + unsigned char hash[SHA256_DIGEST_LENGTH]; + SHA256((unsigned char*)args[0].as.as_string->data, + args[0].as.as_string->length, hash); + + // Convert to hex string + char hex[SHA256_DIGEST_LENGTH * 2 + 1]; + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + sprintf(hex + i * 2, "%02x", hash[i]); + } + return vm_make_string(hex, SHA256_DIGEST_LENGTH * 2); +} + +static Value vm_builtin_sha512(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return stdlib_val_null(); + } + unsigned char hash[SHA512_DIGEST_LENGTH]; + SHA512((unsigned char*)args[0].as.as_string->data, + args[0].as.as_string->length, hash); + + // Convert to hex string + char hex[SHA512_DIGEST_LENGTH * 2 + 1]; + for (int i = 0; i < SHA512_DIGEST_LENGTH; i++) { + sprintf(hex + i * 2, "%02x", hash[i]); + } + return vm_make_string(hex, SHA512_DIGEST_LENGTH * 2); +} + +static Value vm_builtin_md5(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return stdlib_val_null(); + } + unsigned char hash[MD5_DIGEST_LENGTH]; + MD5((unsigned char*)args[0].as.as_string->data, + args[0].as.as_string->length, hash); + + // Convert to hex string + char hex[MD5_DIGEST_LENGTH * 2 + 1]; + for (int i = 0; i < MD5_DIGEST_LENGTH; i++) { + sprintf(hex + i * 2, "%02x", hash[i]); + } + return vm_make_string(hex, MD5_DIGEST_LENGTH * 2); +} + +// File I/O builtins +static Value vm_builtin_read_file(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return stdlib_val_null(); + } + FILE *f = fopen(args[0].as.as_string->data, "rb"); + if (!f) return stdlib_val_null(); + + fseek(f, 0, SEEK_END); + long size = ftell(f); + rewind(f); + + char *buffer = malloc(size + 1); + if (!buffer) { + fclose(f); + return stdlib_val_null(); + } + + size_t read = fread(buffer, 1, size, f); + buffer[read] = '\0'; + fclose(f); + + Value result = vm_make_string(buffer, (int)read); + free(buffer); + return result; +} + +static Value vm_builtin_write_file(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return val_bool_vm(0); + } + if (args[1].type != VAL_STRING || !args[1].as.as_string) { + return val_bool_vm(0); + } + + FILE *f = fopen(args[0].as.as_string->data, "wb"); + if (!f) return val_bool_vm(0); + + size_t written = fwrite(args[1].as.as_string->data, 1, + args[1].as.as_string->length, f); + fclose(f); + return val_bool_vm(written == (size_t)args[1].as.as_string->length); +} + +static Value vm_builtin_append_file(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return val_bool_vm(0); + } + if (args[1].type != VAL_STRING || !args[1].as.as_string) { + return val_bool_vm(0); + } + + FILE *f = fopen(args[0].as.as_string->data, "ab"); + if (!f) return val_bool_vm(0); + + size_t written = fwrite(args[1].as.as_string->data, 1, + args[1].as.as_string->length, f); + fclose(f); + return val_bool_vm(written == (size_t)args[1].as.as_string->length); +} + +static Value vm_builtin_remove_file(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return val_bool_vm(0); + } + return val_bool_vm(remove(args[0].as.as_string->data) == 0); +} + +static Value vm_builtin_cwd(Value *args, int argc, void *ctx) { + (void)args; (void)argc; (void)ctx; + char buf[4096]; + if (getcwd(buf, sizeof(buf))) { + return vm_make_string(buf, strlen(buf)); + } + return stdlib_val_null(); +} + +static Value vm_builtin_chdir(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return val_bool_vm(0); + } + return val_bool_vm(chdir(args[0].as.as_string->data) == 0); +} + +static Value vm_builtin_rename(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string || + args[1].type != VAL_STRING || !args[1].as.as_string) { + return val_bool_vm(0); + } + return val_bool_vm(rename(args[0].as.as_string->data, + args[1].as.as_string->data) == 0); +} + +static Value vm_builtin_make_dir(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return val_bool_vm(0); + } + return val_bool_vm(mkdir(args[0].as.as_string->data, 0755) == 0); +} + +static Value vm_builtin_remove_dir(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return val_bool_vm(0); + } + return val_bool_vm(rmdir(args[0].as.as_string->data) == 0); +} + +#include + +static Value vm_builtin_list_dir(Value *args, int argc, void *ctx) { + (void)ctx; (void)argc; + if (args[0].type != VAL_STRING || !args[0].as.as_string) { + return stdlib_val_null(); + } + + DIR *dir = opendir(args[0].as.as_string->data); + if (!dir) return stdlib_val_null(); + + // Count entries first + int count = 0; + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) { + count++; + } + } + rewinddir(dir); + + // Create array + Array *arr = malloc(sizeof(Array)); + arr->elements = malloc(sizeof(Value) * (count > 0 ? count : 1)); + arr->length = 0; + arr->capacity = count > 0 ? count : 1; + arr->element_type = NULL; + arr->ref_count = 1; + + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) { + arr->elements[arr->length++] = vm_make_string(entry->d_name, strlen(entry->d_name)); + } + } + closedir(dir); + + Value result = {.type = VAL_ARRAY, .as.as_array = arr}; + return result; +} + +static Value vm_val_builtin_fn(BuiltinFn fn) { + Value v; + v.type = VAL_BUILTIN_FN; + v.as.as_builtin_fn = fn; + return v; +} + +// ============================================ +// UTF-8 Helpers +// ============================================ + +// Get byte length of UTF-8 character from first byte +static int utf8_char_byte_length(unsigned char first_byte) { + if ((first_byte & 0x80) == 0) return 1; // 0xxxxxxx (ASCII) + if ((first_byte & 0xE0) == 0xC0) return 2; // 110xxxxx + if ((first_byte & 0xF0) == 0xE0) return 3; // 1110xxxx + if ((first_byte & 0xF8) == 0xF0) return 4; // 11110xxx + return 1; // Invalid, treat as 1 byte +} + +// Count Unicode codepoints in UTF-8 string +static int utf8_count_codepoints(const char *data, int byte_length) { + int count = 0; + int pos = 0; + while (pos < byte_length) { + unsigned char byte = (unsigned char)data[pos]; + if ((byte & 0xC0) != 0x80) { // Not a continuation byte + count++; + } + pos++; + } + return count; +} + +// Find byte offset of the i-th codepoint (0-indexed) +static int utf8_byte_offset(const char *data, int byte_length, int char_index) { + int pos = 0; + int codepoint_count = 0; + while (pos < byte_length) { + unsigned char byte = (unsigned char)data[pos]; + if ((byte & 0xC0) != 0x80) { // Start byte + if (codepoint_count == char_index) { + return pos; + } + codepoint_count++; + } + pos++; + } + return pos; +} + +// Decode UTF-8 codepoint at byte position +static uint32_t utf8_decode_at(const char *data, int byte_pos) { + unsigned char b1 = (unsigned char)data[byte_pos]; + + if ((b1 & 0x80) == 0) { + return b1; // ASCII + } + if ((b1 & 0xE0) == 0xC0) { + unsigned char b2 = (unsigned char)data[byte_pos + 1]; + return ((b1 & 0x1F) << 6) | (b2 & 0x3F); + } + if ((b1 & 0xF0) == 0xE0) { + unsigned char b2 = (unsigned char)data[byte_pos + 1]; + unsigned char b3 = (unsigned char)data[byte_pos + 2]; + return ((b1 & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F); + } + if ((b1 & 0xF8) == 0xF0) { + unsigned char b2 = (unsigned char)data[byte_pos + 1]; + unsigned char b3 = (unsigned char)data[byte_pos + 2]; + unsigned char b4 = (unsigned char)data[byte_pos + 3]; + return ((b1 & 0x07) << 18) | ((b2 & 0x3F) << 12) | ((b3 & 0x3F) << 6) | (b4 & 0x3F); + } + return b1; // Invalid, return as-is +} + +// Encode codepoint to UTF-8, return number of bytes written +static int utf8_encode(uint32_t codepoint, char *buf) { + if (codepoint < 0x80) { + buf[0] = (char)codepoint; + return 1; + } + if (codepoint < 0x800) { + buf[0] = (char)(0xC0 | (codepoint >> 6)); + buf[1] = (char)(0x80 | (codepoint & 0x3F)); + return 2; + } + if (codepoint < 0x10000) { + buf[0] = (char)(0xE0 | (codepoint >> 12)); + buf[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); + buf[2] = (char)(0x80 | (codepoint & 0x3F)); + return 3; + } + buf[0] = (char)(0xF0 | (codepoint >> 18)); + buf[1] = (char)(0x80 | ((codepoint >> 12) & 0x3F)); + buf[2] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); + buf[3] = (char)(0x80 | (codepoint & 0x3F)); + return 4; +} + +// ============================================ +// Async Support +// ============================================ + +// Global task ID counter (atomic for thread-safety) +static atomic_int vm_next_task_id = 1; + +// VM Task structure for async execution +typedef struct VMTask { + int id; + TaskState state; + VMClosure *closure; // The function to execute + Value *args; // Arguments + int argc; + Value result; // Return value when completed + int joined; // Flag: task has been joined + int detached; // Flag: task is detached + pthread_t thread; // Thread handle + pthread_mutex_t mutex; // For thread-safe state access + int ref_count; // Reference count + bool has_exception; // Flag: task threw an exception + Value exception; // Exception value if thrown +} VMTask; + +// Forward declarations +static VMResult vm_execute(VM *vm, int base_frame_count); +static Value vm_deep_copy(Value v); + +// Create a new VMTask +static VMTask* vm_task_new(VMClosure *closure, Value *args, int argc) { + VMTask *task = malloc(sizeof(VMTask)); + if (!task) return NULL; + + task->id = atomic_fetch_add(&vm_next_task_id, 1); + task->state = TASK_READY; + task->closure = closure; + closure->ref_count++; // Retain closure + + // Deep copy arguments for thread safety + task->args = NULL; + task->argc = argc; + if (argc > 0) { + task->args = malloc(sizeof(Value) * argc); + for (int i = 0; i < argc; i++) { + task->args[i] = vm_deep_copy(args[i]); + } + } + + task->result.type = VAL_NULL; + task->joined = 0; + task->detached = 0; + task->ref_count = 1; + task->has_exception = false; + task->exception.type = VAL_NULL; + pthread_mutex_init(&task->mutex, NULL); + + return task; +} + +// Free a VMTask +static void vm_task_free(VMTask *task) { + if (!task) return; + + pthread_mutex_destroy(&task->mutex); + if (task->closure) { + task->closure->ref_count--; + if (task->closure->ref_count <= 0) { + vm_closure_free(task->closure); + } + } + if (task->args) { + free(task->args); + } + free(task); +} + +// Retain a VMTask +static void vm_task_retain(VMTask *task) { + if (task) { + __atomic_add_fetch(&task->ref_count, 1, __ATOMIC_SEQ_CST); + } +} + +// Release a VMTask +static void vm_task_release(VMTask *task) { + if (task) { + int old = __atomic_sub_fetch(&task->ref_count, 1, __ATOMIC_SEQ_CST); + if (old == 0) { + vm_task_free(task); + } + } +} + +// Thread wrapper for async execution +typedef struct { + VMTask *task; + Chunk *chunk; +} TaskThreadArg; + +static void* vm_task_thread_wrapper(void *arg) { + TaskThreadArg *thread_arg = (TaskThreadArg*)arg; + VMTask *task = thread_arg->task; + Chunk *chunk = thread_arg->chunk; + free(thread_arg); + + // Block all signals in worker thread + sigset_t set; + sigfillset(&set); + pthread_sigmask(SIG_BLOCK, &set, NULL); + + // Mark as running + pthread_mutex_lock(&task->mutex); + task->state = TASK_RUNNING; + pthread_mutex_unlock(&task->mutex); + + // Create a new VM for this thread + VM *vm = vm_new(); + + // Set up the call frame for the function + CallFrame *frame = &vm->frames[vm->frame_count++]; + frame->chunk = chunk; + frame->ip = chunk->code; + frame->slots = vm->stack; + frame->upvalues = task->closure->upvalues ? task->closure->upvalues[0] : NULL; + frame->slot_count = chunk->local_count; + + // Put closure in slot 0 (placeholder) + vm->stack[0].type = VAL_NULL; + + // Put arguments in local slots (parameters start at slot 1) + for (int i = 0; i < task->argc && i < chunk->arity; i++) { + vm->stack[i + 1] = task->args[i]; // +1 because slot 0 is closure + } + // Fill remaining params with null + for (int i = task->argc; i < chunk->arity + chunk->optional_count; i++) { + vm->stack[i + 1].type = VAL_NULL; // +1 because slot 0 is closure + } + vm->stack_top = vm->stack + chunk->local_count; + + // Execute the function + VMResult result = vm_execute(vm, 0); + + // Get return value + pthread_mutex_lock(&task->mutex); + if (result == VM_OK && vm->is_returning) { + task->result = vm->return_value; + } else if (vm->is_throwing) { + task->has_exception = true; + task->exception = vm->exception; + } + task->state = TASK_COMPLETED; + pthread_mutex_unlock(&task->mutex); + + // Cleanup + vm_free(vm); + + // Release task if detached + if (task->detached) { + vm_task_release(task); + } + + return NULL; +} + +// ============================================ +// Channel Implementation (for VM) +// ============================================ + +static Channel* vm_channel_new(int capacity) { + Channel *ch = malloc(sizeof(Channel)); + if (!ch) return NULL; + + ch->capacity = capacity; + ch->head = 0; + ch->tail = 0; + ch->count = 0; + ch->closed = 0; + ch->ref_count = 1; + + if (capacity > 0) { + ch->buffer = malloc(sizeof(Value) * capacity); + if (!ch->buffer) { + free(ch); + return NULL; + } + } else { + ch->buffer = NULL; + } + + ch->mutex = malloc(sizeof(pthread_mutex_t)); + ch->not_empty = malloc(sizeof(pthread_cond_t)); + ch->not_full = malloc(sizeof(pthread_cond_t)); + ch->rendezvous = malloc(sizeof(pthread_cond_t)); + + if (!ch->mutex || !ch->not_empty || !ch->not_full || !ch->rendezvous) { + if (ch->buffer) free(ch->buffer); + if (ch->mutex) free(ch->mutex); + if (ch->not_empty) free(ch->not_empty); + if (ch->not_full) free(ch->not_full); + if (ch->rendezvous) free(ch->rendezvous); + free(ch); + return NULL; + } + + pthread_mutex_init((pthread_mutex_t*)ch->mutex, NULL); + pthread_cond_init((pthread_cond_t*)ch->not_empty, NULL); + pthread_cond_init((pthread_cond_t*)ch->not_full, NULL); + pthread_cond_init((pthread_cond_t*)ch->rendezvous, NULL); + + ch->unbuffered_value = malloc(sizeof(Value)); + if (ch->unbuffered_value) { + ch->unbuffered_value->type = VAL_NULL; + } + ch->sender_waiting = 0; + ch->receiver_waiting = 0; + + return ch; +} + +// ============================================ +// Value Helpers (matching interpreter semantics) +// ============================================ + +static Value vm_null_value(void) { + Value v = {.type = VAL_NULL}; + return v; +} + +static Value val_bool_vm(int b) { + Value v = {.type = VAL_BOOL, .as.as_bool = b}; + return v; +} + +static Value val_i32_vm(int32_t i) { + Value v = {.type = VAL_I32, .as.as_i32 = i}; + return v; +} + +static Value val_i64_vm(int64_t i) { + Value v = {.type = VAL_I64, .as.as_i64 = i}; + return v; +} + +static Value val_f64_vm(double f) { + Value v = {.type = VAL_F64, .as.as_f64 = f}; + return v; +} + +static int value_is_truthy(Value v) { + switch (v.type) { + case VAL_NULL: return 0; + case VAL_BOOL: return v.as.as_bool != 0; + case VAL_I32: return v.as.as_i32 != 0; + case VAL_I64: return v.as.as_i64 != 0; + case VAL_F64: return v.as.as_f64 != 0.0; + case VAL_STRING: return v.as.as_string && v.as.as_string->length > 0; + case VAL_ARRAY: return v.as.as_array && v.as.as_array->length > 0; + default: return 1; // Non-null objects are truthy + } +} + +// Convert value to double for arithmetic +static double value_to_f64(Value v) { + switch (v.type) { + case VAL_I8: return (double)v.as.as_i8; + case VAL_I16: return (double)v.as.as_i16; + case VAL_I32: return (double)v.as.as_i32; + case VAL_I64: return (double)v.as.as_i64; + case VAL_U8: return (double)v.as.as_u8; + case VAL_U16: return (double)v.as.as_u16; + case VAL_U32: return (double)v.as.as_u32; + case VAL_U64: return (double)v.as.as_u64; + case VAL_F32: return (double)v.as.as_f32; + case VAL_F64: return v.as.as_f64; + default: return 0.0; + } +} + +// Convert value to i64 for integer ops +static int64_t value_to_i64(Value v) { + switch (v.type) { + case VAL_I8: return (int64_t)v.as.as_i8; + case VAL_I16: return (int64_t)v.as.as_i16; + case VAL_I32: return (int64_t)v.as.as_i32; + case VAL_I64: return v.as.as_i64; + case VAL_U8: return (int64_t)v.as.as_u8; + case VAL_U16: return (int64_t)v.as.as_u16; + case VAL_U32: return (int64_t)v.as.as_u32; + case VAL_U64: return (int64_t)v.as.as_u64; + case VAL_F32: return (int64_t)v.as.as_f32; + case VAL_F64: return (int64_t)v.as.as_f64; + case VAL_RUNE: return (int64_t)v.as.as_rune; // Rune to codepoint + default: return 0; + } +} + +// Convert value to i32 +static int32_t value_to_i32(Value v) { + return (int32_t)value_to_i64(v); +} + +// Compare two values for equality +static bool vm_values_equal(Value a, Value b) { + if (a.type != b.type) return false; + switch (a.type) { + case VAL_NULL: return true; + case VAL_BOOL: return a.as.as_bool == b.as.as_bool; + case VAL_I8: return a.as.as_i8 == b.as.as_i8; + case VAL_I16: return a.as.as_i16 == b.as.as_i16; + case VAL_I32: return a.as.as_i32 == b.as.as_i32; + case VAL_I64: return a.as.as_i64 == b.as.as_i64; + case VAL_U8: return a.as.as_u8 == b.as.as_u8; + case VAL_U16: return a.as.as_u16 == b.as.as_u16; + case VAL_U32: return a.as.as_u32 == b.as.as_u32; + case VAL_U64: return a.as.as_u64 == b.as.as_u64; + case VAL_F32: return a.as.as_f32 == b.as.as_f32; + case VAL_F64: return a.as.as_f64 == b.as.as_f64; + case VAL_STRING: + if (!a.as.as_string || !b.as.as_string) return a.as.as_string == b.as.as_string; + return strcmp(a.as.as_string->data, b.as.as_string->data) == 0; + case VAL_ARRAY: return a.as.as_array == b.as.as_array; + case VAL_OBJECT: return a.as.as_object == b.as.as_object; + default: return false; + } +} + +// Create a new string Value +static Value vm_make_string(const char *data, int len) { + String *s = malloc(sizeof(String)); + s->data = malloc(len + 1); + memcpy(s->data, data, len); + s->data[len] = '\0'; + s->length = len; + s->char_length = len; + s->capacity = len + 1; + s->ref_count = 1; + Value v; + v.type = VAL_STRING; + v.as.as_string = s; + return v; +} + +// Deep copy a value for thread isolation +static Value vm_deep_copy(Value v) { + switch (v.type) { + case VAL_NULL: + case VAL_BOOL: + case VAL_I8: + case VAL_I16: + case VAL_I32: + case VAL_I64: + case VAL_U8: + case VAL_U16: + case VAL_U32: + case VAL_U64: + case VAL_F32: + case VAL_F64: + case VAL_RUNE: + // Primitive types can be copied directly + return v; + + case VAL_STRING: + if (v.as.as_string) { + return vm_make_string(v.as.as_string->data, v.as.as_string->length); + } + return vm_null_value(); + + case VAL_ARRAY: + if (v.as.as_array) { + Array *src = v.as.as_array; + Array *dst = malloc(sizeof(Array)); + dst->length = src->length; + dst->capacity = src->capacity; + dst->ref_count = 1; + dst->element_type = NULL; + dst->freed = 0; + dst->elements = malloc(sizeof(Value) * dst->capacity); + for (int i = 0; i < src->length; i++) { + dst->elements[i] = vm_deep_copy(src->elements[i]); + } + Value result; + result.type = VAL_ARRAY; + result.as.as_array = dst; + return result; + } + return vm_null_value(); + + case VAL_OBJECT: + if (v.as.as_object) { + Object *src = v.as.as_object; + Object *dst = malloc(sizeof(Object)); + dst->type_name = src->type_name ? strdup(src->type_name) : NULL; + dst->num_fields = src->num_fields; + dst->capacity = src->capacity; + dst->ref_count = 1; + dst->freed = 0; + dst->hash_table = NULL; + dst->hash_capacity = 0; + dst->field_names = malloc(sizeof(char*) * dst->capacity); + dst->field_values = malloc(sizeof(Value) * dst->capacity); + for (int i = 0; i < src->num_fields; i++) { + dst->field_names[i] = strdup(src->field_names[i]); + dst->field_values[i] = vm_deep_copy(src->field_values[i]); + } + Value result; + result.type = VAL_OBJECT; + result.as.as_object = dst; + return result; + } + return vm_null_value(); + + case VAL_CHANNEL: + // Channels are shared between threads - just return same reference + return v; + + default: + // For other types (functions, files, etc.), return as-is + return v; + } +} + +// ============================================ +// JSON Serialization Helper +// ============================================ + +// Forward declaration +static void vm_json_serialize_value(Value v, char **buf, size_t *pos, size_t *cap); + +static void vm_json_ensure_space(char **buf, size_t *pos, size_t *cap, size_t needed) { + while (*pos + needed >= *cap) { + *cap *= 2; + *buf = realloc(*buf, *cap); + } +} + +static void vm_json_append(char **buf, size_t *pos, size_t *cap, const char *str) { + size_t len = strlen(str); + vm_json_ensure_space(buf, pos, cap, len + 1); + memcpy(*buf + *pos, str, len); + *pos += len; +} + +static void vm_json_append_escaped(char **buf, size_t *pos, size_t *cap, const char *str) { + vm_json_ensure_space(buf, pos, cap, 1); + (*buf)[(*pos)++] = '"'; + while (*str) { + vm_json_ensure_space(buf, pos, cap, 2); + if (*str == '"' || *str == '\\') { + (*buf)[(*pos)++] = '\\'; + } else if (*str == '\n') { + (*buf)[(*pos)++] = '\\'; + (*buf)[(*pos)++] = 'n'; + str++; + continue; + } else if (*str == '\r') { + (*buf)[(*pos)++] = '\\'; + (*buf)[(*pos)++] = 'r'; + str++; + continue; + } else if (*str == '\t') { + (*buf)[(*pos)++] = '\\'; + (*buf)[(*pos)++] = 't'; + str++; + continue; + } + (*buf)[(*pos)++] = *str++; + } + vm_json_ensure_space(buf, pos, cap, 1); + (*buf)[(*pos)++] = '"'; +} + +static void vm_json_serialize_value(Value v, char **buf, size_t *pos, size_t *cap) { + switch (v.type) { + case VAL_NULL: + vm_json_append(buf, pos, cap, "null"); + break; + case VAL_BOOL: + vm_json_append(buf, pos, cap, v.as.as_bool ? "true" : "false"); + break; + case VAL_I8: case VAL_I16: case VAL_I32: case VAL_I64: + case VAL_U8: case VAL_U16: case VAL_U32: case VAL_U64: { + char num[32]; + snprintf(num, sizeof(num), "%lld", (long long)value_to_i64(v)); + vm_json_append(buf, pos, cap, num); + break; + } + case VAL_F32: case VAL_F64: { + char num[64]; + double d = value_to_f64(v); + snprintf(num, sizeof(num), "%g", d); + // Ensure it looks like a number (has decimal or exponent) + if (strchr(num, '.') == NULL && strchr(num, 'e') == NULL) { + strcat(num, ".0"); + } + vm_json_append(buf, pos, cap, num); + break; + } + case VAL_STRING: + if (v.as.as_string) { + vm_json_append_escaped(buf, pos, cap, v.as.as_string->data); + } else { + vm_json_append(buf, pos, cap, "null"); + } + break; + case VAL_ARRAY: + if (v.as.as_array) { + Array *arr = v.as.as_array; + vm_json_ensure_space(buf, pos, cap, 1); + (*buf)[(*pos)++] = '['; + for (int i = 0; i < arr->length; i++) { + if (i > 0) { + vm_json_ensure_space(buf, pos, cap, 1); + (*buf)[(*pos)++] = ','; + } + vm_json_serialize_value(arr->elements[i], buf, pos, cap); + } + vm_json_ensure_space(buf, pos, cap, 1); + (*buf)[(*pos)++] = ']'; + } else { + vm_json_append(buf, pos, cap, "null"); + } + break; + case VAL_OBJECT: + if (v.as.as_object) { + Object *obj = v.as.as_object; + vm_json_ensure_space(buf, pos, cap, 1); + (*buf)[(*pos)++] = '{'; + for (int i = 0; i < obj->num_fields; i++) { + if (i > 0) { + vm_json_ensure_space(buf, pos, cap, 1); + (*buf)[(*pos)++] = ','; + } + vm_json_append_escaped(buf, pos, cap, obj->field_names[i]); + vm_json_ensure_space(buf, pos, cap, 1); + (*buf)[(*pos)++] = ':'; + vm_json_serialize_value(obj->field_values[i], buf, pos, cap); + } + vm_json_ensure_space(buf, pos, cap, 1); + (*buf)[(*pos)++] = '}'; + } else { + vm_json_append(buf, pos, cap, "null"); + } + break; + default: + vm_json_append(buf, pos, cap, "null"); + break; + } +} + +static Value vm_json_serialize(Value v) { + size_t cap = 256; + size_t pos = 0; + char *buf = malloc(cap); + + vm_json_serialize_value(v, &buf, &pos, &cap); + + buf[pos] = '\0'; + String *s = malloc(sizeof(String)); + s->data = buf; + s->length = pos; + s->char_length = pos; + s->capacity = cap; + s->ref_count = 1; + + Value result; + result.type = VAL_STRING; + result.as.as_string = s; + return result; +} + +// ============================================ +// Simple JSON Parser for deserialize() +// ============================================ + +static const char *json_skip_whitespace(const char *p, const char *end) { + while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++; + return p; +} + +static Value vm_json_parse_value(const char **pp, const char *end); + +static Value vm_json_parse_string(const char **pp, const char *end) { + const char *p = *pp; + if (p >= end || *p != '"') return vm_null_value(); + p++; // Skip opening quote + + // Find closing quote and calculate length + const char *start = p; + while (p < end && *p != '"') { + if (*p == '\\' && p + 1 < end) p++; // Skip escaped char + p++; + } + int len = p - start; + char *buf = malloc(len + 1); + + // Copy with escape handling + const char *src = start; + char *dst = buf; + while (src < p) { + if (*src == '\\' && src + 1 < p) { + src++; + switch (*src) { + case 'n': *dst++ = '\n'; break; + case 't': *dst++ = '\t'; break; + case 'r': *dst++ = '\r'; break; + case '"': *dst++ = '"'; break; + case '\\': *dst++ = '\\'; break; + default: *dst++ = *src; break; + } + src++; + } else { + *dst++ = *src++; + } + } + *dst = '\0'; + int actual_len = dst - buf; + + if (p < end) p++; // Skip closing quote + *pp = p; + + Value result = vm_make_string(buf, actual_len); + free(buf); + return result; +} + +static Value vm_json_parse_number(const char **pp, const char *end) { + const char *p = *pp; + const char *start = p; + bool is_float = false; + + if (p < end && *p == '-') p++; + while (p < end && *p >= '0' && *p <= '9') p++; + if (p < end && *p == '.') { + is_float = true; + p++; + while (p < end && *p >= '0' && *p <= '9') p++; + } + if (p < end && (*p == 'e' || *p == 'E')) { + is_float = true; + p++; + if (p < end && (*p == '+' || *p == '-')) p++; + while (p < end && *p >= '0' && *p <= '9') p++; + } + + char buf[64]; + int len = p - start; + if (len >= 63) len = 63; + memcpy(buf, start, len); + buf[len] = '\0'; + + *pp = p; + + Value result; + if (is_float) { + result.type = VAL_F64; + result.as.as_f64 = atof(buf); + } else { + int64_t n = strtoll(buf, NULL, 10); + if (n >= INT32_MIN && n <= INT32_MAX) { + result.type = VAL_I32; + result.as.as_i32 = (int32_t)n; + } else { + result.type = VAL_I64; + result.as.as_i64 = n; + } + } + return result; +} + +static Value vm_json_parse_array(const char **pp, const char *end) { + const char *p = *pp; + p++; // Skip '[' + p = json_skip_whitespace(p, end); + + Array *arr = malloc(sizeof(Array)); + arr->elements = malloc(sizeof(Value) * 8); + arr->length = 0; + arr->capacity = 8; + arr->element_type = NULL; + arr->ref_count = 1; + + while (p < end && *p != ']') { + Value elem = vm_json_parse_value(&p, end); + if (arr->length >= arr->capacity) { + arr->capacity *= 2; + arr->elements = realloc(arr->elements, sizeof(Value) * arr->capacity); + } + arr->elements[arr->length++] = elem; + + p = json_skip_whitespace(p, end); + if (p < end && *p == ',') { + p++; + p = json_skip_whitespace(p, end); + } + } + if (p < end) p++; // Skip ']' + *pp = p; + + Value result; + result.type = VAL_ARRAY; + result.as.as_array = arr; + return result; +} + +static Value vm_json_parse_object(const char **pp, const char *end) { + const char *p = *pp; + p++; // Skip '{' + p = json_skip_whitespace(p, end); + + Object *obj = malloc(sizeof(Object)); + obj->type_name = NULL; + obj->field_names = malloc(sizeof(char*) * 8); + obj->field_values = malloc(sizeof(Value) * 8); + obj->num_fields = 0; + obj->capacity = 8; + obj->ref_count = 1; + + while (p < end && *p != '}') { + // Parse key + Value key = vm_json_parse_string(&p, end); + if (key.type != VAL_STRING) break; + + p = json_skip_whitespace(p, end); + if (p < end && *p == ':') p++; + p = json_skip_whitespace(p, end); + + // Parse value + Value val = vm_json_parse_value(&p, end); + + // Add to object + if (obj->num_fields >= obj->capacity) { + obj->capacity *= 2; + obj->field_names = realloc(obj->field_names, sizeof(char*) * obj->capacity); + obj->field_values = realloc(obj->field_values, sizeof(Value) * obj->capacity); + } + obj->field_names[obj->num_fields] = strdup(key.as.as_string->data); + obj->field_values[obj->num_fields] = val; + obj->num_fields++; + + // Free key string + free(key.as.as_string->data); + free(key.as.as_string); + + p = json_skip_whitespace(p, end); + if (p < end && *p == ',') { + p++; + p = json_skip_whitespace(p, end); + } + } + if (p < end) p++; // Skip '}' + *pp = p; + + Value result; + result.type = VAL_OBJECT; + result.as.as_object = obj; + return result; +} + +static Value vm_json_parse_value(const char **pp, const char *end) { + const char *p = json_skip_whitespace(*pp, end); + *pp = p; + + if (p >= end) return vm_null_value(); + + if (*p == '"') { + return vm_json_parse_string(pp, end); + } else if (*p == '{') { + return vm_json_parse_object(pp, end); + } else if (*p == '[') { + return vm_json_parse_array(pp, end); + } else if (*p == 't' && p + 4 <= end && strncmp(p, "true", 4) == 0) { + *pp = p + 4; + return val_bool_vm(true); + } else if (*p == 'f' && p + 5 <= end && strncmp(p, "false", 5) == 0) { + *pp = p + 5; + return val_bool_vm(false); + } else if (*p == 'n' && p + 4 <= end && strncmp(p, "null", 4) == 0) { + *pp = p + 4; + return vm_null_value(); + } else if (*p == '-' || (*p >= '0' && *p <= '9')) { + return vm_json_parse_number(pp, end); + } + + return vm_null_value(); +} + +static Value vm_json_parse(const char *json, int len) { + const char *p = json; + const char *end = json + len; + return vm_json_parse_value(&p, end); +} + +// Check if value is a numeric type +static int is_numeric(ValueType t) { + return t >= VAL_I8 && t <= VAL_F64; +} + +// Check if value is a float type +static int is_float(ValueType t) { + return t == VAL_F32 || t == VAL_F64; +} + +// Convert ValueType to a string name +static const char* val_type_name(ValueType t) { + switch (t) { + case VAL_NULL: return "null"; + case VAL_BOOL: return "bool"; + case VAL_I8: return "i8"; + case VAL_I16: return "i16"; + case VAL_I32: return "i32"; + case VAL_I64: return "i64"; + case VAL_U8: return "u8"; + case VAL_U16: return "u16"; + case VAL_U32: return "u32"; + case VAL_U64: return "u64"; + case VAL_F32: return "f32"; + case VAL_F64: return "f64"; + case VAL_STRING: return "string"; + case VAL_RUNE: return "rune"; + case VAL_PTR: return "ptr"; + case VAL_BUFFER: return "buffer"; + case VAL_ARRAY: return "array"; + case VAL_OBJECT: return "object"; + case VAL_FILE: return "file"; + case VAL_FUNCTION: return "function"; + case VAL_TASK: return "task"; + case VAL_CHANNEL: return "channel"; + default: return "unknown"; + } +} + +// Convert a value to a string representation (allocates memory) +static char* value_to_string_alloc(Value v) { + char buf[256]; + switch (v.type) { + case VAL_NULL: + return strdup("null"); + case VAL_BOOL: + return strdup(v.as.as_bool ? "true" : "false"); + case VAL_I8: + snprintf(buf, sizeof(buf), "%d", v.as.as_i8); + return strdup(buf); + case VAL_I16: + snprintf(buf, sizeof(buf), "%d", v.as.as_i16); + return strdup(buf); + case VAL_I32: + snprintf(buf, sizeof(buf), "%d", v.as.as_i32); + return strdup(buf); + case VAL_I64: + snprintf(buf, sizeof(buf), "%ld", (long)v.as.as_i64); + return strdup(buf); + case VAL_U8: + snprintf(buf, sizeof(buf), "%u", v.as.as_u8); + return strdup(buf); + case VAL_U16: + snprintf(buf, sizeof(buf), "%u", v.as.as_u16); + return strdup(buf); + case VAL_U32: + snprintf(buf, sizeof(buf), "%u", v.as.as_u32); + return strdup(buf); + case VAL_U64: + snprintf(buf, sizeof(buf), "%lu", (unsigned long)v.as.as_u64); + return strdup(buf); + case VAL_F32: + case VAL_F64: { + double d = v.type == VAL_F32 ? v.as.as_f32 : v.as.as_f64; + if (d == (long)d) { + snprintf(buf, sizeof(buf), "%.0f", d); + } else { + // Use full precision for type conversions to string + snprintf(buf, sizeof(buf), "%.17g", d); + } + return strdup(buf); + } + case VAL_STRING: + if (v.as.as_string) { + return strdup(v.as.as_string->data); + } + return strdup(""); + case VAL_RUNE: { + // Convert rune to UTF-8 string + uint32_t r = v.as.as_rune; + if (r < 0x80) { + buf[0] = (char)r; + buf[1] = '\0'; + } else if (r < 0x800) { + buf[0] = (char)(0xC0 | (r >> 6)); + buf[1] = (char)(0x80 | (r & 0x3F)); + buf[2] = '\0'; + } else if (r < 0x10000) { + buf[0] = (char)(0xE0 | (r >> 12)); + buf[1] = (char)(0x80 | ((r >> 6) & 0x3F)); + buf[2] = (char)(0x80 | (r & 0x3F)); + buf[3] = '\0'; + } else { + buf[0] = (char)(0xF0 | (r >> 18)); + buf[1] = (char)(0x80 | ((r >> 12) & 0x3F)); + buf[2] = (char)(0x80 | ((r >> 6) & 0x3F)); + buf[3] = (char)(0x80 | (r & 0x3F)); + buf[4] = '\0'; + } + return strdup(buf); + } + case VAL_ARRAY: + return strdup("[array]"); + case VAL_OBJECT: + return strdup("[object]"); + case VAL_FUNCTION: + return strdup("[function]"); + default: + return strdup("[unknown]"); + } +} + +// ============================================ +// Stdlib Initialization +// ============================================ + +// Forward declaration +void vm_define_global(VM *vm, const char *name, Value value, bool is_const); + +static void vm_init_stdlib(VM *vm) { + // Math constants (both __ prefixed and non-prefixed for bundler compatibility) + vm_define_global(vm, "__PI", stdlib_val_f64(M_PI), true); + vm_define_global(vm, "__E", stdlib_val_f64(M_E), true); + vm_define_global(vm, "__TAU", stdlib_val_f64(2.0 * M_PI), true); + vm_define_global(vm, "__INF", stdlib_val_f64(INFINITY), true); + vm_define_global(vm, "__NAN", stdlib_val_f64(NAN), true); + + // Math functions - using VM-specific implementations + // __ prefixed versions (for stdlib/math.hml exports) + vm_define_global(vm, "__sin", vm_val_builtin_fn((BuiltinFn)vm_builtin_sin), true); + vm_define_global(vm, "__cos", vm_val_builtin_fn((BuiltinFn)vm_builtin_cos), true); + vm_define_global(vm, "__tan", vm_val_builtin_fn((BuiltinFn)vm_builtin_tan), true); + vm_define_global(vm, "__asin", vm_val_builtin_fn((BuiltinFn)vm_builtin_asin), true); + vm_define_global(vm, "__acos", vm_val_builtin_fn((BuiltinFn)vm_builtin_acos), true); + vm_define_global(vm, "__atan", vm_val_builtin_fn((BuiltinFn)vm_builtin_atan), true); + vm_define_global(vm, "__atan2", vm_val_builtin_fn((BuiltinFn)vm_builtin_atan2), true); + vm_define_global(vm, "__sqrt", vm_val_builtin_fn((BuiltinFn)vm_builtin_sqrt), true); + vm_define_global(vm, "__pow", vm_val_builtin_fn((BuiltinFn)vm_builtin_pow), true); + vm_define_global(vm, "__exp", vm_val_builtin_fn((BuiltinFn)vm_builtin_exp), true); + vm_define_global(vm, "__log", vm_val_builtin_fn((BuiltinFn)vm_builtin_log), true); + vm_define_global(vm, "__log10", vm_val_builtin_fn((BuiltinFn)vm_builtin_log10), true); + vm_define_global(vm, "__log2", vm_val_builtin_fn((BuiltinFn)vm_builtin_log2), true); + vm_define_global(vm, "__floor", vm_val_builtin_fn((BuiltinFn)vm_builtin_floor), true); + vm_define_global(vm, "__ceil", vm_val_builtin_fn((BuiltinFn)vm_builtin_ceil), true); + vm_define_global(vm, "__round", vm_val_builtin_fn((BuiltinFn)vm_builtin_round), true); + vm_define_global(vm, "__trunc", vm_val_builtin_fn((BuiltinFn)vm_builtin_trunc), true); + vm_define_global(vm, "__abs", vm_val_builtin_fn((BuiltinFn)vm_builtin_abs), true); + vm_define_global(vm, "__min", vm_val_builtin_fn((BuiltinFn)vm_builtin_min), true); + vm_define_global(vm, "__max", vm_val_builtin_fn((BuiltinFn)vm_builtin_max), true); + vm_define_global(vm, "__clamp", vm_val_builtin_fn((BuiltinFn)vm_builtin_clamp), true); + vm_define_global(vm, "__rand", vm_val_builtin_fn((BuiltinFn)vm_builtin_rand), true); + vm_define_global(vm, "__rand_range", vm_val_builtin_fn((BuiltinFn)vm_builtin_rand_range), true); + vm_define_global(vm, "__seed", vm_val_builtin_fn((BuiltinFn)vm_builtin_seed), true); + + // Non-prefixed versions (for bundler which skips stdlib exports for these) + vm_define_global(vm, "sin", vm_val_builtin_fn((BuiltinFn)vm_builtin_sin), true); + vm_define_global(vm, "cos", vm_val_builtin_fn((BuiltinFn)vm_builtin_cos), true); + vm_define_global(vm, "tan", vm_val_builtin_fn((BuiltinFn)vm_builtin_tan), true); + vm_define_global(vm, "asin", vm_val_builtin_fn((BuiltinFn)vm_builtin_asin), true); + vm_define_global(vm, "acos", vm_val_builtin_fn((BuiltinFn)vm_builtin_acos), true); + vm_define_global(vm, "atan", vm_val_builtin_fn((BuiltinFn)vm_builtin_atan), true); + vm_define_global(vm, "atan2", vm_val_builtin_fn((BuiltinFn)vm_builtin_atan2), true); + vm_define_global(vm, "sqrt", vm_val_builtin_fn((BuiltinFn)vm_builtin_sqrt), true); + vm_define_global(vm, "pow", vm_val_builtin_fn((BuiltinFn)vm_builtin_pow), true); + vm_define_global(vm, "exp", vm_val_builtin_fn((BuiltinFn)vm_builtin_exp), true); + vm_define_global(vm, "log", vm_val_builtin_fn((BuiltinFn)vm_builtin_log), true); + vm_define_global(vm, "log10", vm_val_builtin_fn((BuiltinFn)vm_builtin_log10), true); + vm_define_global(vm, "log2", vm_val_builtin_fn((BuiltinFn)vm_builtin_log2), true); + vm_define_global(vm, "floor", vm_val_builtin_fn((BuiltinFn)vm_builtin_floor), true); + vm_define_global(vm, "ceil", vm_val_builtin_fn((BuiltinFn)vm_builtin_ceil), true); + vm_define_global(vm, "round", vm_val_builtin_fn((BuiltinFn)vm_builtin_round), true); + vm_define_global(vm, "trunc", vm_val_builtin_fn((BuiltinFn)vm_builtin_trunc), true); + + // Integer math builtins + vm_define_global(vm, "__floori", vm_val_builtin_fn((BuiltinFn)vm_builtin_floori), true); + vm_define_global(vm, "__ceili", vm_val_builtin_fn((BuiltinFn)vm_builtin_ceili), true); + vm_define_global(vm, "__roundi", vm_val_builtin_fn((BuiltinFn)vm_builtin_roundi), true); + vm_define_global(vm, "__trunci", vm_val_builtin_fn((BuiltinFn)vm_builtin_trunci), true); + vm_define_global(vm, "__div", vm_val_builtin_fn((BuiltinFn)vm_builtin_div), true); + vm_define_global(vm, "__divi", vm_val_builtin_fn((BuiltinFn)vm_builtin_divi), true); + vm_define_global(vm, "floori", vm_val_builtin_fn((BuiltinFn)vm_builtin_floori), true); + vm_define_global(vm, "ceili", vm_val_builtin_fn((BuiltinFn)vm_builtin_ceili), true); + vm_define_global(vm, "roundi", vm_val_builtin_fn((BuiltinFn)vm_builtin_roundi), true); + vm_define_global(vm, "trunci", vm_val_builtin_fn((BuiltinFn)vm_builtin_trunci), true); + vm_define_global(vm, "div", vm_val_builtin_fn((BuiltinFn)vm_builtin_div), true); + vm_define_global(vm, "divi", vm_val_builtin_fn((BuiltinFn)vm_builtin_divi), true); + + // Type constructor builtins + vm_define_global(vm, "i8", vm_val_builtin_fn((BuiltinFn)vm_builtin_type_i8), true); + vm_define_global(vm, "i16", vm_val_builtin_fn((BuiltinFn)vm_builtin_type_i16), true); + vm_define_global(vm, "i32", vm_val_builtin_fn((BuiltinFn)vm_builtin_type_i32), true); + vm_define_global(vm, "i64", vm_val_builtin_fn((BuiltinFn)vm_builtin_type_i64), true); + vm_define_global(vm, "u8", vm_val_builtin_fn((BuiltinFn)vm_builtin_type_u8), true); + vm_define_global(vm, "u16", vm_val_builtin_fn((BuiltinFn)vm_builtin_type_u16), true); + vm_define_global(vm, "u32", vm_val_builtin_fn((BuiltinFn)vm_builtin_type_u32), true); + vm_define_global(vm, "u64", vm_val_builtin_fn((BuiltinFn)vm_builtin_type_u64), true); + vm_define_global(vm, "f32", vm_val_builtin_fn((BuiltinFn)vm_builtin_type_f32), true); + vm_define_global(vm, "f64", vm_val_builtin_fn((BuiltinFn)vm_builtin_type_f64), true); + vm_define_global(vm, "bool", vm_val_builtin_fn((BuiltinFn)vm_builtin_type_bool), true); + // Type aliases + vm_define_global(vm, "integer", vm_val_builtin_fn((BuiltinFn)vm_builtin_type_i32), true); + vm_define_global(vm, "number", vm_val_builtin_fn((BuiltinFn)vm_builtin_type_f64), true); + vm_define_global(vm, "byte", vm_val_builtin_fn((BuiltinFn)vm_builtin_type_u8), true); + + // Environment builtins + vm_define_global(vm, "__getenv", vm_val_builtin_fn((BuiltinFn)vm_builtin_getenv), true); + vm_define_global(vm, "__setenv", vm_val_builtin_fn((BuiltinFn)vm_builtin_setenv), true); + vm_define_global(vm, "__unsetenv", vm_val_builtin_fn((BuiltinFn)vm_builtin_unsetenv), true); + vm_define_global(vm, "getenv", vm_val_builtin_fn((BuiltinFn)vm_builtin_getenv), true); + vm_define_global(vm, "setenv", vm_val_builtin_fn((BuiltinFn)vm_builtin_setenv), true); + vm_define_global(vm, "unsetenv", vm_val_builtin_fn((BuiltinFn)vm_builtin_unsetenv), true); + + // String conversion builtins + vm_define_global(vm, "__string_from_bytes", vm_val_builtin_fn((BuiltinFn)vm_builtin_string_from_bytes), true); + + // File operations + vm_define_global(vm, "__copy_file", vm_val_builtin_fn((BuiltinFn)vm_builtin_copy_file), true); + + // Time builtins + vm_define_global(vm, "__now", vm_val_builtin_fn((BuiltinFn)vm_builtin_now), true); + vm_define_global(vm, "__time_ms", vm_val_builtin_fn((BuiltinFn)vm_builtin_time_ms), true); + vm_define_global(vm, "__sleep", vm_val_builtin_fn((BuiltinFn)vm_builtin_sleep), true); + vm_define_global(vm, "__clock", vm_val_builtin_fn((BuiltinFn)vm_builtin_clock), true); + + // Platform builtins + vm_define_global(vm, "__platform", vm_val_builtin_fn((BuiltinFn)vm_builtin_platform), true); + vm_define_global(vm, "__arch", vm_val_builtin_fn((BuiltinFn)vm_builtin_arch), true); + + // OS info builtins + vm_define_global(vm, "__os_name", vm_val_builtin_fn((BuiltinFn)vm_builtin_os_name), true); + vm_define_global(vm, "__os_version", vm_val_builtin_fn((BuiltinFn)vm_builtin_os_version), true); + vm_define_global(vm, "__hostname", vm_val_builtin_fn((BuiltinFn)vm_builtin_hostname), true); + vm_define_global(vm, "__username", vm_val_builtin_fn((BuiltinFn)vm_builtin_username), true); + vm_define_global(vm, "__cpu_count", vm_val_builtin_fn((BuiltinFn)vm_builtin_cpu_count), true); + vm_define_global(vm, "__total_memory", vm_val_builtin_fn((BuiltinFn)vm_builtin_total_memory), true); + vm_define_global(vm, "__free_memory", vm_val_builtin_fn((BuiltinFn)vm_builtin_free_memory), true); + vm_define_global(vm, "__homedir", vm_val_builtin_fn((BuiltinFn)vm_builtin_homedir), true); + vm_define_global(vm, "__tmpdir", vm_val_builtin_fn((BuiltinFn)vm_builtin_tmpdir), true); + vm_define_global(vm, "__uptime", vm_val_builtin_fn((BuiltinFn)vm_builtin_uptime), true); + + // File system builtins + vm_define_global(vm, "__exists", vm_val_builtin_fn((BuiltinFn)vm_builtin_exists), true); + vm_define_global(vm, "__is_file", vm_val_builtin_fn((BuiltinFn)vm_builtin_is_file), true); + vm_define_global(vm, "__is_dir", vm_val_builtin_fn((BuiltinFn)vm_builtin_is_dir), true); + vm_define_global(vm, "__file_stat", vm_val_builtin_fn((BuiltinFn)vm_builtin_file_stat), true); + vm_define_global(vm, "__absolute_path", vm_val_builtin_fn((BuiltinFn)vm_builtin_absolute_path), true); + + // Process builtins + vm_define_global(vm, "__get_pid", vm_val_builtin_fn((BuiltinFn)vm_builtin_get_pid), true); + vm_define_global(vm, "__getppid", vm_val_builtin_fn((BuiltinFn)vm_builtin_getppid), true); + vm_define_global(vm, "__getuid", vm_val_builtin_fn((BuiltinFn)vm_builtin_getuid), true); + vm_define_global(vm, "__geteuid", vm_val_builtin_fn((BuiltinFn)vm_builtin_geteuid), true); + vm_define_global(vm, "__exit", vm_val_builtin_fn((BuiltinFn)vm_builtin_exit), true); + vm_define_global(vm, "get_pid", vm_val_builtin_fn((BuiltinFn)vm_builtin_get_pid), true); + + // String builtins + vm_define_global(vm, "__strlen", vm_val_builtin_fn((BuiltinFn)vm_builtin_strlen), true); + + // Pointer read builtins + vm_define_global(vm, "__read_ptr", vm_val_builtin_fn((BuiltinFn)vm_builtin_read_ptr), true); + + // Hash builtins + vm_define_global(vm, "__sha256", vm_val_builtin_fn((BuiltinFn)vm_builtin_sha256), true); + vm_define_global(vm, "__sha512", vm_val_builtin_fn((BuiltinFn)vm_builtin_sha512), true); + vm_define_global(vm, "__md5", vm_val_builtin_fn((BuiltinFn)vm_builtin_md5), true); + + // File I/O builtins + vm_define_global(vm, "__read_file", vm_val_builtin_fn((BuiltinFn)vm_builtin_read_file), true); + vm_define_global(vm, "__write_file", vm_val_builtin_fn((BuiltinFn)vm_builtin_write_file), true); + vm_define_global(vm, "__append_file", vm_val_builtin_fn((BuiltinFn)vm_builtin_append_file), true); + vm_define_global(vm, "__remove_file", vm_val_builtin_fn((BuiltinFn)vm_builtin_remove_file), true); + vm_define_global(vm, "__cwd", vm_val_builtin_fn((BuiltinFn)vm_builtin_cwd), true); + vm_define_global(vm, "__chdir", vm_val_builtin_fn((BuiltinFn)vm_builtin_chdir), true); + vm_define_global(vm, "__rename", vm_val_builtin_fn((BuiltinFn)vm_builtin_rename), true); + vm_define_global(vm, "__make_dir", vm_val_builtin_fn((BuiltinFn)vm_builtin_make_dir), true); + vm_define_global(vm, "__remove_dir", vm_val_builtin_fn((BuiltinFn)vm_builtin_remove_dir), true); + vm_define_global(vm, "__list_dir", vm_val_builtin_fn((BuiltinFn)vm_builtin_list_dir), true); + + // Poll constants + vm_define_global(vm, "POLLIN", val_i32_vm(POLLIN), true); + vm_define_global(vm, "POLLOUT", val_i32_vm(POLLOUT), true); + vm_define_global(vm, "POLLERR", val_i32_vm(POLLERR), true); + vm_define_global(vm, "POLLHUP", val_i32_vm(POLLHUP), true); + vm_define_global(vm, "POLLNVAL", val_i32_vm(POLLNVAL), true); + vm_define_global(vm, "POLLPRI", val_i32_vm(POLLPRI), true); + + // Socket constants + vm_define_global(vm, "AF_INET", val_i32_vm(AF_INET), true); + vm_define_global(vm, "AF_INET6", val_i32_vm(AF_INET6), true); + vm_define_global(vm, "AF_UNIX", val_i32_vm(AF_UNIX), true); + vm_define_global(vm, "SOCK_STREAM", val_i32_vm(SOCK_STREAM), true); + vm_define_global(vm, "SOCK_DGRAM", val_i32_vm(SOCK_DGRAM), true); + vm_define_global(vm, "SOCK_RAW", val_i32_vm(SOCK_RAW), true); + vm_define_global(vm, "IPPROTO_TCP", val_i32_vm(IPPROTO_TCP), true); + vm_define_global(vm, "IPPROTO_UDP", val_i32_vm(IPPROTO_UDP), true); + vm_define_global(vm, "SOL_SOCKET", val_i32_vm(SOL_SOCKET), true); + vm_define_global(vm, "SO_REUSEADDR", val_i32_vm(SO_REUSEADDR), true); + vm_define_global(vm, "SO_KEEPALIVE", val_i32_vm(SO_KEEPALIVE), true); + vm_define_global(vm, "SO_RCVBUF", val_i32_vm(SO_RCVBUF), true); + vm_define_global(vm, "SO_SNDBUF", val_i32_vm(SO_SNDBUF), true); + vm_define_global(vm, "SO_ERROR", val_i32_vm(SO_ERROR), true); + vm_define_global(vm, "TCP_NODELAY", val_i32_vm(1), true); // Typically 1 + vm_define_global(vm, "MSG_DONTWAIT", val_i32_vm(MSG_DONTWAIT), true); + vm_define_global(vm, "MSG_PEEK", val_i32_vm(MSG_PEEK), true); + vm_define_global(vm, "SHUT_RD", val_i32_vm(SHUT_RD), true); + vm_define_global(vm, "SHUT_WR", val_i32_vm(SHUT_WR), true); + vm_define_global(vm, "SHUT_RDWR", val_i32_vm(SHUT_RDWR), true); + + // Signal constants + vm_define_global(vm, "SIGINT", val_i32_vm(SIGINT), true); + vm_define_global(vm, "SIGTERM", val_i32_vm(SIGTERM), true); + vm_define_global(vm, "SIGKILL", val_i32_vm(SIGKILL), true); + vm_define_global(vm, "SIGHUP", val_i32_vm(SIGHUP), true); + vm_define_global(vm, "SIGQUIT", val_i32_vm(SIGQUIT), true); + vm_define_global(vm, "SIGPIPE", val_i32_vm(SIGPIPE), true); + vm_define_global(vm, "SIGALRM", val_i32_vm(SIGALRM), true); + vm_define_global(vm, "SIGUSR1", val_i32_vm(SIGUSR1), true); + vm_define_global(vm, "SIGUSR2", val_i32_vm(SIGUSR2), true); + vm_define_global(vm, "SIGCHLD", val_i32_vm(SIGCHLD), true); + vm_define_global(vm, "SIGCONT", val_i32_vm(SIGCONT), true); + vm_define_global(vm, "SIGSTOP", val_i32_vm(SIGSTOP), true); + vm_define_global(vm, "SIGTSTP", val_i32_vm(SIGTSTP), true); + vm_define_global(vm, "SIGTTIN", val_i32_vm(SIGTTIN), true); + vm_define_global(vm, "SIGTTOU", val_i32_vm(SIGTTOU), true); + + // TTY constants + vm_define_global(vm, "STDIN_FILENO", val_i32_vm(STDIN_FILENO), true); + vm_define_global(vm, "STDOUT_FILENO", val_i32_vm(STDOUT_FILENO), true); + vm_define_global(vm, "STDERR_FILENO", val_i32_vm(STDERR_FILENO), true); + vm_define_global(vm, "TCSANOW", val_i32_vm(TCSANOW), true); + vm_define_global(vm, "TCSADRAIN", val_i32_vm(TCSADRAIN), true); + vm_define_global(vm, "TCSAFLUSH", val_i32_vm(TCSAFLUSH), true); + vm_define_global(vm, "ECHO", val_i32_vm(ECHO), true); + vm_define_global(vm, "ICANON", val_i32_vm(ICANON), true); +} + +// ============================================ +// VM Lifecycle +// ============================================ + +VM* vm_new(void) { + VM *vm = malloc(sizeof(VM)); + if (!vm) return NULL; + + // Initialize stack (use calloc to zero out memory) + vm->stack = calloc(VM_STACK_INITIAL, sizeof(Value)); + vm->stack_top = vm->stack; + vm->stack_capacity = VM_STACK_INITIAL; + + // Initialize call frames + vm->frames = malloc(sizeof(CallFrame) * VM_FRAMES_INITIAL); + vm->frame_count = 0; + vm->frame_capacity = VM_FRAMES_INITIAL; + + // Initialize globals + vm->globals.names = malloc(sizeof(char*) * VM_GLOBALS_INITIAL); + vm->globals.values = malloc(sizeof(Value) * VM_GLOBALS_INITIAL); + vm->globals.is_const = malloc(sizeof(bool) * VM_GLOBALS_INITIAL); + vm->globals.count = 0; + vm->globals.capacity = VM_GLOBALS_INITIAL; + vm->globals.hash_table = NULL; + vm->globals.hash_capacity = 0; + + // Control flow state + vm->is_returning = false; + vm->return_value = vm_null_value(); + vm->is_throwing = false; + vm->exception = vm_null_value(); + vm->exception_frame = NULL; + vm->is_breaking = false; + vm->is_continuing = false; + + // Defers + vm->defers = malloc(sizeof(DeferEntry) * VM_DEFER_INITIAL); + vm->defer_count = 0; + vm->defer_capacity = VM_DEFER_INITIAL; + + // Exception handlers + vm->handlers = malloc(sizeof(ExceptionHandler) * 16); + vm->handler_count = 0; + vm->handler_capacity = 16; + + // Module cache + vm->module_cache.paths = NULL; + vm->module_cache.modules = NULL; + vm->module_cache.count = 0; + vm->module_cache.capacity = 0; + + // GC/memory + vm->open_upvalues = NULL; + vm->objects = NULL; + vm->bytes_allocated = 0; + vm->next_gc = 1024 * 1024; // 1MB + + vm->max_stack_depth = 1024; + vm->task = NULL; + + vm->pending_error = NULL; + + // Initialize stdlib globals + vm_init_stdlib(vm); + + return vm; +} + +void vm_free(VM *vm) { + if (!vm) return; + + free(vm->stack); + free(vm->frames); + + // Free globals + for (int i = 0; i < vm->globals.count; i++) { + free(vm->globals.names[i]); + // TODO: VALUE_RELEASE(vm->globals.values[i]); + } + free(vm->globals.names); + free(vm->globals.values); + free(vm->globals.is_const); + free(vm->globals.hash_table); + + free(vm->defers); + + // Free module cache + for (int i = 0; i < vm->module_cache.count; i++) { + free(vm->module_cache.paths[i]); + } + free(vm->module_cache.paths); + free(vm->module_cache.modules); + + // TODO: Free all allocated objects + + free(vm); +} + +void vm_reset(VM *vm) { + vm->stack_top = vm->stack; + vm->frame_count = 0; + vm->is_returning = false; + vm->is_throwing = false; + vm->is_breaking = false; + vm->is_continuing = false; + vm->defer_count = 0; +} + +// ============================================ +// Stack Operations +// ============================================ + +void vm_push(VM *vm, Value value) { + if (vm->stack_top - vm->stack >= vm->stack_capacity) { + // Grow stack + int old_top = vm->stack_top - vm->stack; + vm->stack_capacity *= 2; + vm->stack = realloc(vm->stack, sizeof(Value) * vm->stack_capacity); + vm->stack_top = vm->stack + old_top; + } + *vm->stack_top++ = value; +} + +Value vm_pop(VM *vm) { + if (vm->stack_top <= vm->stack) { + vm_runtime_error(vm, "Stack underflow"); + return vm_null_value(); + } + return *--vm->stack_top; +} + +Value vm_peek(VM *vm, int distance) { + return vm->stack_top[-1 - distance]; +} + +void vm_popn(VM *vm, int count) { + vm->stack_top -= count; + if (vm->stack_top < vm->stack) { + vm->stack_top = vm->stack; + vm_runtime_error(vm, "Stack underflow"); + } +} + +// ============================================ +// Globals +// ============================================ + +static uint32_t hash_string_vm(const char *str) { + uint32_t hash = 2166136261u; + while (*str) { + hash ^= (uint8_t)*str++; + hash *= 16777619; + } + return hash; +} + +void vm_define_global(VM *vm, const char *name, Value value, bool is_const) { + // Check for existing + for (int i = 0; i < vm->globals.count; i++) { + if (strcmp(vm->globals.names[i], name) == 0) { + vm->globals.values[i] = value; + return; + } + } + + // Add new + if (vm->globals.count >= vm->globals.capacity) { + vm->globals.capacity *= 2; + vm->globals.names = realloc(vm->globals.names, sizeof(char*) * vm->globals.capacity); + vm->globals.values = realloc(vm->globals.values, sizeof(Value) * vm->globals.capacity); + vm->globals.is_const = realloc(vm->globals.is_const, sizeof(bool) * vm->globals.capacity); + } + + vm->globals.names[vm->globals.count] = strdup(name); + vm->globals.values[vm->globals.count] = value; + vm->globals.is_const[vm->globals.count] = is_const; + vm->globals.count++; +} + +bool vm_get_global(VM *vm, const char *name, Value *out) { + for (int i = 0; i < vm->globals.count; i++) { + if (strcmp(vm->globals.names[i], name) == 0) { + *out = vm->globals.values[i]; + return true; + } + } + return false; +} + +// Helper macros for setting catchable errors from static helper functions +// After calling a helper function, check vm->pending_error and handle it +#define SET_ERROR(vm, msg) do { (vm)->pending_error = (msg); } while(0) +#define SET_ERROR_FMT(vm, fmt, ...) do { \ + snprintf((vm)->error_buf, sizeof((vm)->error_buf), fmt, ##__VA_ARGS__); \ + (vm)->pending_error = (vm)->error_buf; \ +} while(0) + +bool vm_set_global(VM *vm, const char *name, Value value) { + for (int i = 0; i < vm->globals.count; i++) { + if (strcmp(vm->globals.names[i], name) == 0) { + if (vm->globals.is_const[i]) { + SET_ERROR_FMT(vm, "Cannot reassign constant '%s'", name); + return false; + } + vm->globals.values[i] = value; + return true; + } + } + SET_ERROR_FMT(vm, "Undefined variable '%s'", name); + return false; +} + +void vm_set_args(VM *vm, int argc, char **argv) { + // Create an array of strings from command-line arguments + Array *arr = malloc(sizeof(Array)); + arr->elements = malloc(sizeof(Value) * (argc > 0 ? argc : 1)); + arr->length = argc; + arr->capacity = argc > 0 ? argc : 1; + arr->element_type = NULL; + arr->ref_count = 1; + + for (int i = 0; i < argc; i++) { + arr->elements[i] = vm_make_string(argv[i], strlen(argv[i])); + } + + Value args_val = {.type = VAL_ARRAY, .as.as_array = arr}; + vm_define_global(vm, "args", args_val, false); +} + +// ============================================ +// VM Closures +// ============================================ + +VMClosure* vm_closure_new(Chunk *chunk) { + VMClosure *closure = malloc(sizeof(VMClosure)); + closure->chunk = chunk; + closure->upvalue_count = chunk->upvalue_count; + if (closure->upvalue_count > 0) { + closure->upvalues = malloc(sizeof(ObjUpvalue*) * closure->upvalue_count); + for (int i = 0; i < closure->upvalue_count; i++) { + closure->upvalues[i] = NULL; + } + } else { + closure->upvalues = NULL; + } + closure->ref_count = 1; + return closure; +} + +void vm_closure_free(VMClosure *closure) { + if (!closure) return; + if (--closure->ref_count <= 0) { + free(closure->upvalues); + // Note: Don't free the chunk - it's owned by the constant pool + free(closure); + } +} + +// Create a Value from a VMClosure (stores as function pointer) +static Value val_vm_closure(VMClosure *closure) { + Value v; + v.type = VAL_FUNCTION; + // Store the closure pointer - we'll cast it back when calling + v.as.as_function = (Function*)closure; + return v; +} + +// Check if a Value is a VM closure (vs an interpreter function) +// VM closures have a special marker - they came from the VM context +// For now, all functions in the VM are closures +static bool is_vm_closure(Value v) { + return v.type == VAL_FUNCTION && v.as.as_function != NULL; +} + +// Get the VMClosure from a Value +static VMClosure* as_vm_closure(Value v) { + return (VMClosure*)v.as.as_function; +} + +// ============================================ +// Error Handling +// ============================================ + +void vm_runtime_error(VM *vm, const char *format, ...) { + fflush(stdout); // Ensure all output is printed before error + va_list args; + va_start(args, format); + fprintf(stderr, "Runtime error: "); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + va_end(args); + + // Print stack trace + vm_print_stack_trace(vm); + + vm->is_throwing = true; +} + +int vm_current_line(VM *vm) { + if (vm->frame_count == 0) return 0; + CallFrame *frame = &vm->frames[vm->frame_count - 1]; + int offset = frame->ip - frame->chunk->code; + return chunk_get_line(frame->chunk, offset); +} + +void vm_print_stack_trace(VM *vm) { + for (int i = vm->frame_count - 1; i >= 0; i--) { + CallFrame *frame = &vm->frames[i]; + int offset = frame->ip - frame->chunk->code; + int line = chunk_get_line(frame->chunk, offset); + fprintf(stderr, " at %s:%d\n", + frame->chunk->name ? frame->chunk->name : "