-
Notifications
You must be signed in to change notification settings - Fork 0
Stack Frame Layout
Documents the stack frame structure used by JNode's x86 JIT compilers (L1 and L2).
JNode uses a frame-pointer-based (EBP/RBP) stack frame layout for both L1 and L2 JIT compilers. The layout supports method arguments, local variables, operand stack slots, and exception handling. The same underlying structure is used by both compilers, implemented in X86StackFrame.java.
| File | Purpose |
|---|---|
org/jnode/vm/x86/compiler/l2/X86StackFrame.java |
Core stack frame generation (header/trailer, EBP offsets, exception handlers) |
org/jnode/vm/x86/compiler/X86CompilerHelper.java |
Defines BP (frame pointer), SP (stack pointer), STATICS register, and SLOTSIZE |
org/jnode/vm/x86/compiler/l2/GenericX86CodeGenerator.java |
L2 code generator using EBP-relative addressing for locals/stack |
org/jnode/vm/x86/compiler/l2/X86CodeGenerator.java |
L2 entry point, constructs GenericX86CodeGenerator with X86StackFrame
|
High addresses
+------------------+
| Arg N | <-- positive EBP offset (callee's args)
+------------------+
| ... |
+------------------+
| Arg 1 |
+------------------+ <-- EBP + EbpFrameRefOffset (saved EBP)
| saved EBP | EbpFrameRefOffset = slotSize (4 or 8)
+------------------+
| method ID | (pushed by prologue)
+------------------+
| local 0 | <-- EBP - slotSize (first local after args)
+------------------+
| local 1 |
+------------------+
| ... |
+------------------+
| local M | <-- EBP - (M * slotSize)
+------------------+ <-- ESP (low water mark during execution)
| operand stack | grows downward via PUSH/POP
+------------------+
Low addresses
| Register | Role |
|---|---|
| EBP/RBP | Frame pointer — base for accessing locals and args |
| ESP/RSP | Stack pointer — top of operand stack |
| STATICS (EDI/RDI) | Pointer to statics table |
| AAX (EAX/RAX) | Scratch / return value |
X86CompilerHelper initializes these registers based on 32/64-bit mode:
// 32-bit
AAX = EAX; ABX = EBX; SP = ESP; BP = EBP; STATICS = EDI; ADDRSIZE = 4; SLOTSIZE = 4;
// 64-bit
AAX = RAX; ABX = RBX; SP = RSP; BP = RBP; STATICS = RDI; ADDRSIZE = 8; SLOTSIZE = 8;The method prologue (written by X86StackFrame.emitTrailer) performs:
-
Stack overflow test — compares ESP/RSP against
VmProcessor.stackEndvia FS prefix (32-bit) or direct comparison (64-bit) -
Optional statics load — for methods with
@LoadStaticspragma - Stack alignment test — checks 16-byte alignment (conditionally enabled in 64-bit)
- Class initialization check — emits test-and-jump for uninitialized classes
-
Save registers —
saveRegisters()currently empty (reserved space) -
Push saved EBP —
push ebp -
Push method ID —
push cm.getCompiledCodeId() -
Copy SP to BP —
mov ebp, esp - Allocate locals — pushes zero-filled slots for non-argument locals
saveRegisters();
os.writePUSH(abp); // save caller's EBP
os.writePUSH(cm.getCompiledCodeId()); // method identifier
os.writeMOV(size, abp, asp);
// Create and clear all local variables
final int noLocalVars = maxLocals - argSlotCount;
if (noLocalVars > 0) {
os.writeXOR(aax, aax); // aax = 0
for (int i = 0; i < noLocalVars; i++) {
os.writePUSH(aax); // push zero
}
}The method epilogue (written in emitTrailer at footerLabel) performs:
- Monitor exit — for synchronized methods
- Restore SP from BP:
lea esp, [ebp + EbpFrameRefOffset] - Pop saved EBP:
pop ebp - Restore registers
- Return:
ret [argSlotCount * slotSize]
os.setObjectRef(footerLabel);
emitSynchronizationCode(typeSizeInfo, entryPoints.getMonitorExitMethod());
os.writeLEA(asp, abp, EbpFrameRefOffset);
os.writePOP(abp);
restoreRegisters();
if (argSlotCount > 0) {
os.writeRET(argSlotCount * slotSize);
} else {
os.writeRET();
}X86StackFrame.getEbpOffset maps Java local/arg indices to stack offsets:
public final short getEbpOffset(TypeSizeInfo typeSizeInfo, int index) {
final int noArgs = method.getArgSlotCount();
final int stackSlot = index;
if (stackSlot < noArgs) {
// Argument: positive offset (above saved EBP)
return toShort(((noArgs - stackSlot + 1) * slotSize) + EbpFrameRefOffset + SAVED_REGISTERSPACE);
} else {
// Local variable: negative offset (below saved EBP)
return toShort((stackSlot - noArgs + 1) * -slotSize);
}
}Example (32-bit, slotSize=4, noArgs=2):
| Java Index | Type | EBP Offset |
|---|---|---|
| 0 | arg | +16 |
| 1 | arg | +12 |
| 2 | local | -4 |
| 3 | local | -8 |
The operand stack is not pre-allocated — it grows downward from locals via PUSH/POP instructions. L2 compiler tracks operand stack slots using the IR's addressing modes:
- TOPS — Top-of-Stack (pop to register or push from register)
- STACK — EBP-relative stack slot for spilled values
L1 compiler uses a simpler stack-based model where bytecode aload/astore map directly to push/pop instructions.
When generating exception handlers, the stack is cleared by adjusting ESP to point just above the locals:
final int ofs = Math.max(0, noLocalVars) * slotSize;
os.writeLEA(asp, abp, -ofs);
os.writePUSH(aax); // push exception objectEach method's bytecode contains an exception table. X86StackFrame.emitTrailer converts this to native code:
for (int i = 0; i < count; i++) {
VmInterpretedExceptionHandler eh = bc.getExceptionHandler(i);
Label handlerLabel = helper.genLabel("$$ex_handler" + i);
// ... clear stack, push exception, jump to handler
ceh[i].setStartPc(os.getObjectRef(getInstrLabel(eh.getStartPC())));
ceh[i].setEndPc(os.getObjectRef(getInstrLabel(eh.getEndPC())));
ceh[i].setHandler(handlerRef);
}Synchronized methods emit monitor-enter/exit around the method body:
private void emitSynchronizationCode(TypeSizeInfo typeSizeInfo, VmMethod monitorMethod) {
if (method.isSynchronized()) {
if (method.isStatic()) {
// Get declaring class via statics table
final int typeOfs = helper.getSharedStaticsOffset(method.getDeclaringClass());
os.writePUSH(helper.STATICS, typeOfs);
} else {
// Push 'this' reference
os.writePUSH(helper.BP, getEbpOffset(typeSizeInfo, 0));
}
helper.invokeJavaMethod(monitorMethod);
}
}-
SAVED_REGISTERSPACE = 0 * 4— no callee-saved registers are currently saved, but the space calculation exists for future use - Wide locals (long/double) occupy two consecutive stack slots;
getWideEbpOffsetreturns the first slot offset and the second slot is atoffset - slotSize - In 64-bit mode, the argument slot count is multiplied by 2 in jump table indexing to account for 8-byte slots
- Stack alignment test is conditionally disabled (
false && os.isCode64()) — may need enabling for strict ABI compliance - Default exception handler jumps to
VM_ATHROW_NOTRACEwithout aret— the return address on stack becomes the exception's context
- JIT-Compilers — L1/L2 pipeline overview
- Exceptions-Implementation — exception table format and stack unwinding
- VM-Magic — unboxed Address/Word types
- Glossary — L1 compiler, L2 compiler, TIB, vtable