Skip to content

L1 Compiler

opencode-agent[bot] edited this page May 10, 2026 · 1 revision

L1 Compiler Registers

L1 compiler register allocation and virtual stack management for fast JIT compilation.

Overview

The L1 compiler (org.jnode.vm.x86.compiler.l1a) uses a virtual stack (VirtualStack) and register pools (X86RegisterPool) to translate bytecode to native x86 code. Rather than immediately emitting instructions, values are held as Item objects on a virtual stack, keeping them in registers as long as possible. This delayed-emission strategy maximizes register utilization within basic blocks, trading aggressive optimization for fast compilation speed.

Key Components

Class / File Role
core/src/core/org/jnode/vm/x86/compiler/l1a/X86RegisterPool.java Register pool management for GPR and XMM registers
core/src/core/org/jnode/vm/x86/compiler/l1a/VirtualStack.java Virtual operand stack with delayed emission
core/src/core/org/jnode/vm/x86/compiler/l1a/Item.java Abstract base for stack values (constant, GPR, XMM, STACK, LOCAL)
core/src/core/org/jnode/vm/x86/compiler/l1a/WordItem.java Single-register items (INT, FLOAT, REFERENCE)
core/src/core/org/jnode/vm/x86/compiler/l1a/DoubleWordItem.java Two-register items (LONG, DOUBLE) in 32-bit mode

How It Works

Register Pool Architecture

The X86RegisterPool manages available x86 registers in groups. Each RegisterGroupUsage instance represents a group of aliases (e.g., EAX/RAX for different JVM types) and tracks ownership:

// GPRs32 pools: EAX, EDX, ECX, ESI, EBX (EDI reserved for statics)
public static final class GPRs32 extends X86RegisterPool {
    protected RegisterGroupUsage[] initialize() {
        return new RegisterGroupUsage[]{
            new RegisterGroupUsage(
                new RegisterEntry(X86Register.EAX, JvmType.INT),
                new RegisterEntry(X86Register.EAX, JvmType.REFERENCE),
                new RegisterEntry(X86Register.EAX, JvmType.FLOAT), false),
            // ... EDX, ECX, ESI, EBX
        };
    }
}

32-bit GPR priority (highest cost first): EBX < ESI < ECX < EDX < EAX
64-bit GPR priority (highest cost first): R15 < R14 < R13 < R12 < R10 < R9 < R8 < RSI < RBX < RCX < RDX < RAX

Item State Machine

Values transition through states tracked by Item.Kind:

CONSTANT ──load()──> GPR ──push()──> STACK
                      ↑
                      └──load()───┘
  • load(ec): Brings value into a GPR (requests from pool, spills if necessary)
  • push(ec): Emits code to push value onto the actual stack (releases GPR)
  • spill(ec, reg): Spills a GPR to make room (transfers to STACK kind)
  • release(ec): Frees resources without emission

Virtual Stack

The VirtualStack maintains the operand stack as Item objects:

void push(Item item) {
    if (tos == stack.length) growStack();
    stack[tos++] = item;
}

Item pop() {
    tos--;
    return stack[tos];
}

Type-safe pops validate JVM types:

final IntItem popInt() { return (IntItem) pop(JvmType.INT); }
final LongItem popLong() { return (LongItem) pop(JvmType.LONG); }
final RefItem popRef() { return (RefItem) pop(JvmType.REFERENCE); }
final FloatItem popFloat() { return (FloatItem) pop(JvmType.FLOAT); }
final DoubleItem popDouble() { return (DoubleItem) pop(JvmType.DOUBLE); }

Long/Double Handling

32-bit mode: LONG and DOUBLE occupy two registers (LSB + MSB). DoubleWordItem tracks both registers:

final X86Register.GPR32 getLsbRegister(EmitterContext ec) { return lsb; }
final X86Register.GPR32 getMsbRegister(EmitterContext ec) { return msb; }

64-bit mode: LONG and DOUBLE use single GPR64 registers (e.g., RAX).

XMM Register Pools

Floating-point values use SSE XMM registers:

Mode Registers
32-bit (XMMs32) XMM0–XMM7
64-bit (XMMs64) XMM0–XMM15

Special Register Contracts

Register Usage
EAX/RAX Return values (int/ref), implicit for add/sub operands
ECX Shift amount for ishr/lshr
EDX:EAXX 32-bit long return value
EDI Points to statics (reserved)
R12 Points to current VmProcessor (reserved)

Gotchas

  • Basic block barriers: Virtual stack resets at each basic block start. All pending items flush to real stack, limiting register utilization across branches.

  • Aliasing restriction: Modifying a value on the vstack is forbidden. loadLocal() pins any aliased stack items into registers before store.

  • ECX for shifts: Non-constant shift amounts must be in ECX. The compiler explicitly requests ECX for variable shifts.

  • Register priority: Lower-indexed registers have higher allocation cost. EAX is cheapest in 32-bit mode, R15 is cheapest in 64-bit.

  • FPU stack discipline: FPU operations use a separate 8-slot FPUStack. Items must be on top before fxch/fstp operations.

  • 64-bit constants: 64-bit instructions still take 32-bit immediates. Loading large constants requires MOV to register first.

Related Pages

Clone this wiki locally