-
Notifications
You must be signed in to change notification settings - Fork 0
L1 Compiler
L1 compiler register allocation and virtual stack management for fast JIT compilation.
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.
| 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 |
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
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
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); }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).
Floating-point values use SSE XMM registers:
| Mode | Registers |
|---|---|
32-bit (XMMs32) |
XMM0–XMM7 |
64-bit (XMMs64) |
XMM0–XMM15 |
| 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) |
-
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.