-
Notifications
You must be signed in to change notification settings - Fork 1
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) |
The L1 compiler reuses X86BytecodeVisitor instances across method compilations via ThreadLocal. When a compilation fails (e.g., unresolvable class), registers can remain allocated and items can remain on the virtual stack. Without cleanup, the next method compilation would fail with Error("Register(s) in use") in X86RegisterPool.reset().
Recovery chain:
-
NativeCodeCompiler.doCompile()tracks success via aboolean compiledflag. On failure, callscleanupBytecodeVisitor(bcv)beforereleaseBytecodeVisitor(bcv). -
X86Level1ACompiler.cleanupBytecodeVisitor()overrides the hook. Unwraps the visitor chain (Optimizing → Verifying →X86BytecodeVisitor) and callscleanupAfterFailedCompilation(). -
X86BytecodeVisitor.cleanupAfterFailedCompilation()force-resets GPR pool, XMM pool, and virtual stack. -
X86RegisterPool.forceReset()releases each allocated register with a warning log, then resets the pool index (unlikereset()which throws on in-use registers).
| Class | Method | Role |
|---|---|---|
NativeCodeCompiler |
cleanupBytecodeVisitor() |
No-op hook for subclasses |
X86Level1ACompiler |
cleanupBytecodeVisitor() |
Unwraps visitor chain, triggers cleanup |
X86BytecodeVisitor |
cleanupAfterFailedCompilation() |
Resets all pools and vstack |
X86RegisterPool |
forceReset() |
Releases allocated registers with warning |
-
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.
-
Constant-folding item leaks: The
ItemFactorytracks item creation/release balance viaCHECK_BALANCED_ITEM_FACTORY. When folding constants (e.g.,ineg,i2l,lneg), the old constant must be released after creating the new constant. Prior to the fix,ineg,i2l, andlnegconstant-folding paths leaked items, causing unbalanced warnings. The fix addsval.release(eContext)afterifac.createIConst()in each constant-folding branch. The warning now includes the leak count for diagnostics. -
SSE constant folding for sub/mul/div:
FPCompilerSSEpreviously folded all SSE binary operations as addition (fpv1 + fpv2). The fix dispatches by operation type (SSE_SUB,SSE_MUL,SSE_DIV,SSE_ADD) and releases both operand items after folding.