Docs / Architecture
8. Triple Execution Model
Dryad's execution engine is not a single interpreter or compiler — it is a three-layer pipeline where each layer feeds the next with progressively richer type information. Layer 1 (AST Interpreter) walks the syntax tree directly and collects runtime type observations for every variable slot, call site, and branch. Layer 2 (Bytecode VM) consumes the AST and the interpreter's type observations to emit type-specialized opcodes — ADD_F64 instead of ADD_GENERIC when the observed type is f64. Each compiled function carries a Metadata Header with the observed type signatures, allowing subsequent layers to skip type inference. Layer 3 (AOT Compiler) reads the type-annotated bytecode and emits LLVM IR with zero type checks for statically-known paths, falling back to guarded branches (fast path with runtime check) only when types are genuinely ambiguous. This design distributes type intelligence across all three layers: the interpreter observes, the VM annotates, and the AOT consumes.
| Execution Mode | Input | Type Info | Performance Profile | Use Case |
|---|---|---|---|---|
| AST Interpreter | Source code (.dryad) | Runtime observations collected | Fastest startup, slowest execution | Development, debugging, rapid iteration |
| Bytecode VM | AST + Type Observations | Metadata Header: STATIC/OBSERVED/UNKNOWN | Moderate startup, 10x over interpreter | Automation, scripts, development servers |
| AOT Compiler (LLVM) | Type-Annotated Bytecode (.dryadc) | Bind Once: all types resolved at compile time | 10s startup, native C++ speed | Production binaries, hot paths, deployment |
1 // Dryad source: the interpreter observes that 'sum' is always number 2 function compute(arr) { 3 let sum = 0; 4 for (let i = 0; i < arr.len(); i++) { 5 sum = sum + arr[i]; 6 } 7 return sum; 8 } 9 10 // Bytecode emitted by VM (with type observations from interpreter): 11 // Metadata Header: 12 // FuncID: compute 13 // Slot 0 (arr): observed f64[] → confidence: OBSERVED 14 // Slot 1 (sum): observed f64 → confidence: OBSERVED 15 // Slot 2 (i): observed i32 → confidence: OBSERVED 16 // 17 // Emitted opcodes (type-specialized): 18 // LOAD_CONST 0 19 // STORE_FAST sum ; sum: f64 20 // LOAD_CONST 0 21 // STORE_FAST i ; i: i32 22 // .loop: 23 // LOAD_FAST i ; i: i32 24 // LOAD_FAST arr ; arr: f64[] 25 // LOAD_FAST i ; i: i32 26 // GET_ELEMENT_F64 ; arr[i] as f64 — native load 27 // ADD_F64 ; sum + arr[i] — native f64 add 28 // STORE_FAST sum 29 // ...loop control... 30 // JUMP_IF .loop
The VM emits type-specialized opcodes (ADD_F64, GET_ELEMENT_F64) based on observations from the interpreter. The Metadata Header pre-populates the AOT compiler's symbol table, eliminating redundant type inference.
1 // Scoped compilation replaces global set_compile_mode() 2 // Each @compile() applies only to its annotated scope. 3 4 // Function-level: compile only this function to AOT 5 @compile("AOT") 6 function hotPath(data: Buffer) { 7 // This function is compiled to native even if the 8 // surrounding module uses interpreter mode 9 let result = process(data); 10 return transform(result); 11 } 12 13 // Block-level: compile a specific block to bytecode 14 function mixedMode() { 15 // This part runs in the interpreter (default) 16 let config = io_read_file("config.json"); 17 let parsed = json_parse(config); 18 19 // This block runs in bytecode VM for performance 20 @compile("bytecode") { 21 for (let item in parsed.items) { 22 heavyProcessing(item); 23 } 24 } 25 26 // Back to interpreter mode 27 return parsed.status; 28 } 29 30 // The decorator is transitive: functions called from 31 // within @compile("AOT") are also AOT-compiled, unless 32 // they have their own @compile override.
The @compile() decorator provides fine-grained compilation control. It replaces the older set_compile_mode() API and works at both function and block granularity.
Found an error in the spec? Open a PR.
Official Version: 1.0 · May 2026