Skip to content

Super-Instructions

These are instructions that perform complex operations in a single VM cycle.

Consider the simple operation obj.count += 5. In a naive register-based VM, this requires:

1. GET_FIELD R1, R_obj, "count" // Load the property
2. LDC R2, 5 // Load the constant
3. IADD R3, R1, R2 // Perform the addition
4. SET_FIELD R_obj, "count", R3 // Store the result back

That’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.

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.

Purpose: Modify a property on a host object in-place.

Use Cases: +=, -=, *=, =, string append, array push, property assignment.

┌────────┬──────────┬──────────┬──────────┬──────────┐
│ HMOD │ Sub-Op │ Object │ Key/Path │ Value │
│ │ (intent) │ (reg) │ (pool) │ (reg) │
└────────┴──────────┴──────────┴──────────┴──────────┘
Sub-OpcodeNSL OperationKotlin Host Action
SET_INTobj.x = 42obj.put("x", 42)
SET_STRobj.name = "Alice"obj.put("name", "Alice")
SET_BOOLobj.active = trueobj.put("active", true)
SET_DBLobj.rate = 3.14obj.put("rate", 3.14)
SET_OBJobj.child = otherobj.put("child", other)
ADD_INTobj.count += 5obj.put("count", current + 5)
SUB_INTobj.count -= 1obj.put("count", current - 1)
ADD_DBLobj.total += 1.5obj.put("total", current + 1.5)
APPEND_STRobj.log += "line"obj.put("log", current + "line")
// 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.

Purpose: Read a property from a host object into a register.

Use Cases: Property reads, type conversions, existence checks.

┌────────┬──────────┬──────────┬──────────┬──────────┐
│ HACC │ Sub-Op │ Dest │ Object │ Key/Path │
│ │ (intent) │ (reg) │ (reg) │ (pool) │
└────────┴──────────┴──────────┴──────────┴──────────┘
Sub-OpcodeNSL OperationKotlin Host Action
GET_INTint x = obj.countpMem[dest] = (long) obj.get("count")
GET_STRstring s = obj.namerMem[dest] = (String) obj.get("name")
GET_BOOLboolean b = obj.activepMem[dest] = (boolean) obj.get("active") ? 1 : 0
GET_DBLdouble d = obj.ratepMem[dest] = doubleToRawLongBits((double) obj.get("rate"))
GET_OBJjson child = obj.datarMem[dest] = obj.get("data")
HAS_KEYjson.has("key")pMem[dest] = obj.has("key") ? 1 : 0

Purpose: Concatenate two strings into one.

Use Cases: Template literals, string + operator.

┌────────┬──────────┬──────────┬──────────┬──────────┐
│ SCONCAT│ (0) │ Dest │ Left │ Right │
│ │ │ (reg) │ (reg) │ (reg) │
└────────┴──────────┴──────────┴──────────┴──────────┘

rMem[Dest] = rMem[Left] + rMem[Right]

Complex data access patterns like data.rows[i].value are handled by a family of specialized accessor opcodes.

Given config.server.db.port, a naive approach would create temporaries for every step:

GET_FIELD R1, R_config, "server" // temp1 = config.server
GET_FIELD R2, R1, "db" // temp2 = temp1.db
GET_FIELD R3, R2, "port" // result = temp2.port
// 3 opcodes, 2 temporary registers

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.

For array/map access with a runtime index or key:

AGET_IDX [SubOp] R_result, R_collection, R_index

This instruction is polymorphic, it checks the type at runtime and then applies the SubOp cast:

Type of CollectionAction
List / ArrayListlist.get((int) pMem[R_index]) then cast via SubOp
Map / Structmap.get(String.valueOf(rMem[R_index])) then cast via SubOp

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.

Processing 1000 JSON objects with item.value += bonus:

ApproachInstructions ExecutedRelative Speed
Naive (GET → ADD → SET)~4,000
Super-Instructions (HMOD)~1,000~3.5×
  1. Fewer VM loop iterations: Less dispatch overhead
  2. No temporary registers: Less memory traffic
  3. Direct Kotlin calls: HotSpot can inline and optimize the host code
  4. Cache-friendly: Linear instruction stream, no tree walking