22. Optimization Passes
In the Quorlin compiler and Kortana Virtual Machine (KVM) ecosystem, performance and gas efficiency are primary design goals. Rather than relying on heavyweight, multi-pass IR (Intermediate Representation) transformations that increase binary size and risk introducing subtle compilation bugs, Quorlin and KVM achieve code optimization through a streamlined compile-time pipeline coupled with register-based bytecode generation and state-aware host execution.
This chapter details how code optimization occurs across the compiler lifecycle, from semantic validation and register-based Instruction Set Architecture (ISA) lowering to constant pool deduplication, label resolution, and gas-aware execution paths.
22.1 Architecture Overview of the Compilation and Optimization Pipeline
The Quorlin compilation process follows a clean, single-pass pipeline managed by compile() in quorlin/compiler.cpp. Optimization principles are baked directly into each stage:
- Lexical Analysis (
Lexer): Source text is tokenized into structural constructs, enforcing literal byte bounds early. - Parsing (
Parser): The token stream is converted into an Abstract Syntax Tree (AST) representing the contractSourceUnit. - Semantic Analysis (
Analyzer): Symbol resolution, type checking, field validation, and mutability verifications take place. - Instruction Lowering (
CodeGenerator): The validated AST is lowered into KVM instructions using 32-bit fixed-width register operands. - Module Serialization (
kvm/module.cpp): Program bytecode and 256-bit numerical constants are structured into an optimized binary file header.
+-------------------------------------------------------+
| Quorlin Source (.ql) |
+-------------------------------------------------------+
|
v
+-------------------------------------------------------+
| Lexer & Parser |
+-------------------------------------------------------+
|
v
+-------------------------------------------------------+
| Semantic Analyzer (Sema) |
+-------------------------------------------------------+
|
[Diagnostics Check Gate]
if (has_errors) STOP
|
v
+-------------------------------------------------------+
| CodeGenerator (Register ISA) |
+-------------------------------------------------------+
|
[Label Fixups & Constant Deduplication]
|
v
+-------------------------------------------------------+
| KVM Binary Module (.kvm) |
+-------------------------------------------------------+
The Diagnostic Gate
A core principle of Quorlin’s compiler is that code generation never runs on incomplete or invalid semantical trees:
// quorlin/compiler.cpp Analyzer analyzer{result.diagnostics}; const AnalysisResult analysis = analyzer.analyze(unit); if (result.diagnostics.has_errors()) return result;
By gating code generation behind AnalysisResult, the compiler ensures that the code generator works exclusively with fully type-checked identifiers, accurate struct layout offsets, and validated mutability contracts (reads vs writes). This eliminates the need for speculative error-handling instructions inside the generated bytecode.
22.2 Register-Based ISA Optimization vs. Stack Models
Traditional smart contract virtual machines (such as EVM) use stack-based execution architectures. Stack machines rely heavily on stack manipulation instructions such as PUSH, DUP, and SWAP to bring operands to the top of the evaluation stack. In heavily nested arithmetic expressions, over 40% of the total executed bytecode instructions can consist of pure stack shuffling.
KVM completely eliminates stack-shuffling overhead by utilizing a 32-bit fixed-length register-based Instruction Set Architecture (ISA) (kvm/isa.cpp).
KVM Instruction Formats
Instructions in KVM are explicitly categorized into four precise encoding formats:
| Format | Description | Field Allocations |
|---|---|---|
Format::None | No operands (Stop, Ret, Invalid) | Opcode (8 bits) |
Format::R | Three-register format (Add, Sub, Mul, Lt, And, etc.) | Opcode (8b), $r_d$ (5b), $r_{s1}$ (5b), $r_{s2}$ (5b), Funct (9b) |
Format::I | Two-register plus Immediate (LoadI, LoadK, branch offsets) | Opcode (8b), $r_d$ (5b), $r_{s1}$ (5b), Immediate (14b) |
Format::J | Unconditional jump / long offset | Opcode (8b), Immediate (24b) |
In quorlin/codegen.cpp, register instruction helpers construct these bit-aligned payloads:
// quorlin/codegen.cpp [[nodiscard]] Instruction r_form(Opcode op, uint8_t rd, uint8_t rs1, uint8_t rs2 = 0, uint16_t funct = 0) { Instruction out; out.opcode = op; out.rd = rd; out.rs1 = rs1; out.rs2 = rs2; out.funct = funct; return out; }
The standard register layout supports up to 32 registers ($r_0$ to $r_{31}$), addressed via 5-bit fields (kRegisterMask = 0x1F):
kOpcodeShift = 24kRdShift = 19kRs1Shift = 14kRs2Shift = 9
Register Allocation Benefit Example
Consider calculating a basic expression: c = a + b.
-
Stack Architecture (EVM):
PUSH1 <address_a>SLOADPUSH1 <address_b>SLOADADDPUSH1 <address_c>SSTORE(7 instructions, multiple stack operations)
-
Register Architecture (KVM):
Add r_c, r_a, r_b(1 instruction)
By carrying out arithmetic directly between registers $r_{s1}$ and $r_{s2}$ into destination $r_d$, KVM reduces program instruction counts, shrinks binary size, and drastically lowers total execution gas.
22.3 Constant Deduplication and Constant Pool Optimization
Smart contracts frequently utilize large 256-bit numbers (e.g., initial token supplies, fixed block durations, maximum bitmasks). Directly embedding 32-byte immediates within an instruction stream bloats binary code size and hurts instruction cache efficiency.
Binary Module Constant Table Layout
The KVM binary module header (kvm/module.cpp) structures constants into a dedicated payload section preceding the instruction stream:
+---------------------------------------------------------------+
| Magic ("KVM\0") | Version (u16) | Constant Count (u32) |
+---------------------------------------------------------------+
| Instruction Count (u32) | Entry Point Index (u32) |
+---------------------------------------------------------------+
| Constant 0 (32 bytes, big-endian uint256_t) |
| Constant 1 (32 bytes, big-endian uint256_t) |
| ... |
+---------------------------------------------------------------+
| Code Stream (4 bytes per instruction, big-endian) |
| ... |
+---------------------------------------------------------------+
Constant Table Extraction (LoadK)
When the Quorlin compiler generates code for literal values, it registers 256-bit numbers into the module's constant pool. Duplicate numbers share a single constant table entry.
To access constants at runtime, code generation uses the I-form LoadK opcode, which indexes directly into the module’s constant table:
contract SupplyTracker { number maxTokens; constructor { // Loads from constant table index offset using LoadK instruction maxTokens = 1000000000000000000; } }
Instead of pushing 32 raw bytes over the execution memory pipeline, LoadK executes as a kGasVeryLow operation, fetching the pre-parsed 256-bit word from the module’s pre-loaded constant vector.
22.4 Control Flow Resolution and Label Fixups
Conditional statements (if, else) and loops require jumping across bytecode instruction indexes. In quorlin/codegen.cpp, jump locations are managed using dynamic label fixups rather than arbitrary byte offset calculations.
Dynamic Label Allocation and Fixups
Labels are initially placed as unassigned sentinels (SIZE_MAX):
// quorlin/codegen.cpp size_t CodeGenerator::make_label() { labels_.push_back(SIZE_MAX); return labels_.size() - 1; } void CodeGenerator::place(size_t label) { if (label < labels_.size()) { labels_[label] = code_.size(); } }
During the codegen pass, conditional branches emit jump instructions referencing these unresolved labels. Once code generation completes, resolve_fixups() iterates through all emitted branch instructions, calculates the exact target instruction indexes, and updates the immediate operand fields (kImm14Mask / kImm24Mask).
This two-stage fixup resolution guarantees:
- Zero Dead Jumps: Trampoline jumps and unreferenced code paths are identified and stripped.
- Exact Instruction Offset Indexing: Jump target addresses point strictly to valid instruction offsets, preventing invalid middle-of-instruction jumps.
22.5 Gas-Aware Codegen and Execution Cost Reduction
In the Kortana Virtual Machine, gas costs are directly tied to instruction execution complexity (kvm/gas.cpp). The compiler's code generator intentionally optimizes instruction output according to these defined cost tiers:
// kvm/gas.cpp uint64_t base_cost(Opcode opcode, const params::GasSchedule& schedule) noexcept { switch (opcode) { // Free operations case Opcode::Stop: case Opcode::Return: case Opcode::Revert: return kGasZero; // ALU, Bitwise, Register moves, and Constant loads case Opcode::Add: case Opcode::Sub: case Opcode::Lt: case Opcode::Gt: case Opcode::Eq: case Opcode::And: case Opcode::Or: case Opcode::Xor: case Opcode::Mov: case Opcode::LoadK: case Opcode::LoadI: return kGasVeryLow; // Higher complexity arithmetic case Opcode::Mul: case Opcode::Div: case Opcode::Mod: return kGasLow; default: ... } }
Free Termination Path Optimization
Halting execution via explicit completion opcodes (Stop, Return, Revert) costs zero gas (kGasZero). The compiler leverages this by generating immediate return sequences upon reaching explicit control terminal paths, preventing unnecessary PC (Program Counter) steps.
Storage Read/Write Optimization in State Host
State modification is the most gas-intensive operation in smart contract execution. KVM's host environment (kvm/state_host.cpp) minimizes storage cost overhead by inspecting the current slot state during execution:
// kvm/state_host.cpp Result<uint256_t> StateHost::set_storage(const Address& address, const uint256_t& key, const uint256_t& value) { // Returns previous value so interpreter can accurately price the write cost: // Writing to an empty slot costs significantly more than overwriting an active slot. KORTANA_TRY_ASSIGN(const uint256_t previous, world_.get_storage(address, key)); KORTANA_TRY(world_.put_storage(address, key, value)); return previous; }
Because set_storage returns the previous stored value during state updating:
- Fresh Storage Writes ($0 \rightarrow v$): Priced at full initialization cost.
- Storage Modifying Writes ($v_1 \rightarrow v_2$): Priced at a lower update fee.
- No-Op Storage Writes ($v_1 \rightarrow v_1$): Can be efficiently recognized and discounted by the interpreter.
22.6 Pragmatic Quorlin Optimizations in Smart Contracts
Developers writing Quorlin code can utilize native language primitives to help the compiler emit optimal KVM instruction streams.
Mutability Keywords: reads vs writes
Quorlin explicitly segregates contract methods using reads and writes modifiers (quorlin/LANGUAGE.md):
contract Vault { number totalDeposits; map<address, number> balances; // Optimized for state-read paths (no storage modification overhead) reads number getBalance(address account) { return balances[account]; } // Explicit state-writing path writes truth deposit(number amount) { balances[caller] = balances[caller] + amount; totalDeposits = totalDeposits + amount; return yes; } }
- Method signatures marked with
readsnotify the analyzer and host environment that state-trie modification opcodes (set_storage) will never occur. This allows execution in read-only static call environments without setting up state modification journals. - Method signatures marked with
writesreserve state write capabilities and enable state-tracking journals inside the KVM host state.
Explicit Register Usage with Standard Built-ins
Quorlin's native keywords compile directly to dedicated environment opcodes:
writes truth fastTransfer(address to, number amount) { // 'caller' maps directly to an instruction retrieving the transaction sender register address sender = caller; number currentBalance = balances[sender]; require currentBalance >= amount, "insufficient funds"; balances[sender] = currentBalance - amount; balances[to] = balances[to] + amount; return yes; }
By assigning caller to a local variable sender, the compiler assigns sender to a dedicated KVM register ($r_x$). Subsequent reads of sender pull directly from register $r_x$ without re-querying the host call frame context.
Summary of Optimization Pass Highlights
- Semantic Gating: Erroneous paths are filtered prior to code generation, guaranteeing that optimization stages process only valid programs.
- 32-Bit Fixed Register ISA: Replaces EVM-style stack manipulation instructions with 3-operand register arithmetic, cutting instruction counts and saving execution gas.
- Module Constant Deduplication: Large 256-bit values are moved out of code streams into a unified constant table accessed via indexed
LoadKinstructions. - Label Fixups: Two-pass control flow resolution ensures jumps land on valid instruction boundaries with zero dead-branch overhead.
- Dynamic Storage Pricing: KVM's host environment tracks prior storage state during
set_storageexecutions, allowing the runtime to price storage writes accurately according to state transitions.