Super-Instructions
Super-Instructions
Section titled “Super-Instructions”These are instructions that perform complex operations in a single VM cycle.
The Problem
Section titled “The Problem”Consider the simple operation obj.count += 5. In a naive register-based VM, this requires:
1. GET_FIELD R1, R_obj, "count" // Load the property2. LDC R2, 5 // Load the constant3. IADD R3, R1, R2 // Perform the addition4. SET_FIELD R_obj, "count", R3 // Store the result backThat’s 4 VM loop iterations, 4 instruction decodes, and 2 temporary registers, for an operation that a single Kotlin function call could handle in microseconds.
The Solution: Intent-Based Execution
Section titled “The Solution: Intent-Based Execution”Instead of emulating a CPU, we treat the VM as a dispatcher. We encode the programmer’s intent into a single instruction and let the JVM host execute it directly.
HMOD [SubOp: ADD_INT] [Obj: R_obj] [Key: "count"] [Val: 5]
JVM host executes: obj.addProperty("count", obj.get("count").getAsInt() + 5);One instruction. Zero temporaries. Maximum speed.
The Three Super-Instructions
Section titled “The Three Super-Instructions”HMOD — Host Modify
Section titled “HMOD — Host Modify”Purpose: Modify a property on a host object in-place.
Use Cases: +=, -=, *=, =, string append, array push, property assignment.
Instruction Layout
Section titled “Instruction Layout”┌────────┬──────────┬──────────┬──────────┬──────────┐│ HMOD │ Sub-Op │ Object │ Key/Path │ Value ││ │ (intent) │ (reg) │ (pool) │ (reg) │└────────┴──────────┴──────────┴──────────┴──────────┘Sub-Opcodes
Section titled “Sub-Opcodes”| Sub-Opcode | NSL Operation | Kotlin Host Action |
|---|---|---|
SET_INT | obj.x = 42 | obj.put("x", 42) |
SET_STR | obj.name = "Alice" | obj.put("name", "Alice") |
SET_BOOL | obj.active = true | obj.put("active", true) |
SET_DBL | obj.rate = 3.14 | obj.put("rate", 3.14) |
SET_OBJ | obj.child = other | obj.put("child", other) |
ADD_INT | obj.count += 5 | obj.put("count", current + 5) |
SUB_INT | obj.count -= 1 | obj.put("count", current - 1) |
ADD_DBL | obj.total += 1.5 | obj.put("total", current + 1.5) |
APPEND_STR | obj.log += "line" | obj.put("log", current + "line") |
Example: config.retries += 1
Section titled “Example: config.retries += 1”// Compiler emits:HMOD [SUB_OP: ADD_INT] R_config, "retries", R_one
// VM execution (single Kotlin call):NoxObject config = (NoxObject) rMem[bp + R_config];int current = (int) config.get("retries");config.put("retries", current + 1);Result: Zero register swapping, zero temporary objects, and it is done in one VM cycle.
HACC — Host Access
Section titled “HACC — Host Access”Purpose: Read a property from a host object into a register.
Use Cases: Property reads, type conversions, existence checks.
Instruction Layout
Section titled “Instruction Layout”┌────────┬──────────┬──────────┬──────────┬──────────┐│ HACC │ Sub-Op │ Dest │ Object │ Key/Path ││ │ (intent) │ (reg) │ (reg) │ (pool) │└────────┴──────────┴──────────┴──────────┴──────────┘Sub-Opcodes
Section titled “Sub-Opcodes”| Sub-Opcode | NSL Operation | Kotlin Host Action |
|---|---|---|
GET_INT | int x = obj.count | pMem[dest] = (long) obj.get("count") |
GET_STR | string s = obj.name | rMem[dest] = (String) obj.get("name") |
GET_BOOL | boolean b = obj.active | pMem[dest] = (boolean) obj.get("active") ? 1 : 0 |
GET_DBL | double d = obj.rate | pMem[dest] = doubleToRawLongBits((double) obj.get("rate")) |
GET_OBJ | json child = obj.data | rMem[dest] = obj.get("data") |
HAS_KEY | json.has("key") | pMem[dest] = obj.has("key") ? 1 : 0 |
SCONCAT — String Concatenation
Section titled “SCONCAT — String Concatenation”Purpose: Concatenate two strings into one.
Use Cases: Template literals, string + operator.
Instruction Layout
Section titled “Instruction Layout”┌────────┬──────────┬──────────┬──────────┬──────────┐│ SCONCAT│ (0) │ Dest │ Left │ Right ││ │ │ (reg) │ (reg) │ (reg) │└────────┴──────────┴──────────┴──────────┴──────────┘rMem[Dest] = rMem[Left] + rMem[Right]
Deep Nesting: The Accessor Family
Section titled “Deep Nesting: The Accessor Family”Complex data access patterns like data.rows[i].value are handled by a family of specialized accessor opcodes.
The Challenge
Section titled “The Challenge”Given config.server.db.port, a naive approach would create temporaries for every step:
GET_FIELD R1, R_config, "server" // temp1 = config.serverGET_FIELD R2, R1, "db" // temp2 = temp1.dbGET_FIELD R3, R2, "port" // result = temp2.port// 3 opcodes, 2 temporary registersAGET_PATH — Static Path Traversal
Section titled “AGET_PATH — Static Path Traversal”For paths known at compile time, the compiler pre-parses the path into a cached string array:
AGET_PATH [SubOp] R_result, R_config, "server.db"The VM traverses the path and applies the SubOp cast, writing the result to either primitive memory (pMem) or reference memory (rMem) depending on the SubOp.
AGET_IDX — Dynamic Index Access
Section titled “AGET_IDX — Dynamic Index Access”For array/map access with a runtime index or key:
AGET_IDX [SubOp] R_result, R_collection, R_indexThis instruction is polymorphic, it checks the type at runtime and then applies the SubOp cast:
| Type of Collection | Action |
|---|---|
List / ArrayList | list.get((int) pMem[R_index]) then cast via SubOp |
Map / Struct | map.get(String.valueOf(rMem[R_index])) then cast via SubOp |
Chaining Accessors
Section titled “Chaining Accessors”To resolve data.rows[i].value into an integer, the compiler emits a chain. Intermediate steps use GET_OBJ (which stores the intermediate reference in rMem), and the final step uses GET_INT (which stores the final primitive in pMem):
HACC [GET_OBJ] R1, R_data, "rows" // R1 = data.rows (the array reference)AGET_IDX [GET_OBJ] R2, R1, R_i // R2 = rows[i] (one element reference)HACC [GET_INT] R3, R2, "value" // pMem[R3] = element.value (integer)Three clean, linear instructions. No recursion. No complex resolution logic. The VM simply executes them in sequence.
Performance Impact
Section titled “Performance Impact”Benchmark: JSON Processing Loop
Section titled “Benchmark: JSON Processing Loop”Processing 1000 JSON objects with item.value += bonus:
| Approach | Instructions Executed | Relative Speed |
|---|---|---|
| Naive (GET → ADD → SET) | ~4,000 | 1× |
Super-Instructions (HMOD) | ~1,000 | ~3.5× |
Why It Works
Section titled “Why It Works”- Fewer VM loop iterations: Less dispatch overhead
- No temporary registers: Less memory traffic
- Direct Kotlin calls: HotSpot can inline and optimize the host code
- Cache-friendly: Linear instruction stream, no tree walking