-
Notifications
You must be signed in to change notification settings - Fork 0
Exceptions Implementation
How JNode implements Java exception handling at the VM and JIT levels.
JNode's exception handling spans two layers: bytecode interpretation and native code compilation. The JIT compiler (NativeCodeCompiler) transforms Java's exception table structure into a compact native representation, while the VM runtime (VmSystem.findThrowableHandler) performs stack unwinding to locate handlers.
| Class | Purpose |
|---|---|
VmCompiledExceptionHandler |
Native representation of a bytecode exception entry |
VmCompiledCode |
Holds compiled native code + exception handler table |
VmExceptions |
Declared exceptions from method's Exceptions attribute |
AbstractExceptionHandler |
Base class; stores the catchType (VmConstClass) |
NativeCodeCompiler |
Converts bytecode exception table to VmCompiledExceptionHandler[]
|
Source path: core/src/core/org/jnode/vm/classmgr/
The bytecode exception table is converted at JIT compile time into an array of VmCompiledExceptionHandler objects. Each handler stores raw native addresses:
public final class VmCompiledExceptionHandler extends AbstractExceptionHandler {
private final VmAddress handler; // Native address of catch block
private final VmAddress startPtr; // PC range start
private final VmAddress endPtr; // PC range end
public boolean isInScope(Address address) {
final Address start = Address.fromAddress(startPtr);
final Address end = Address.fromAddress(endPtr);
return address.GE(start) && address.LT(end);
}
}VmCompiledCode holds the handler table and a defaultExceptionHandler:
public final class VmCompiledCode extends AbstractCode {
private final VmCompiledExceptionHandler[] eTable;
private final VmAddress defaultExceptionHandler;
// ...
}When an exception is thrown, VmSystem.findThrowableHandler() walks the call stack:
public static Address findThrowableHandler(Throwable ex, Address frame, Address address) {
// 1. Get current method + compiled code
final VmMethod method = reader.getMethod(frame);
final VmCompiledCode cc = reader.getCompiledCode(frame);
// 2. Iterate exception handlers
for (int i = 0; i < cc.getNoExceptionHandlers(); i++) {
final VmCompiledExceptionHandler ceh = cc.getExceptionHandler(i);
if (ceh.isInScope(address)) {
final VmConstClass catchType = eh.getCatchType();
if (catchType == null) {
// Catch-all (finally block)
return Address.fromAddress(ceh.getHandler());
} else {
// Check assignability
if (handlerClass.isAssignableFrom(exClass)) {
return Address.fromAddress(ceh.getHandler());
}
}
}
}
// 3. If PC is in compiled code but no handler matched
if (cc.contains(address)) {
return Address.fromAddress(cc.getDefaultExceptionHandler());
}
return null;
}The process repeats for each stack frame until a handler is found or the stack is exhausted.
The defaultExceptionHandler is invoked when:
- The exception PC is within the method's compiled code bounds
- No explicit handler matched (wrong exception type)
It typically transfers control to an interpreter fallback or performs a fatal error dump.
Methods annotated with @Uninterruptible cannot throw exceptions because:
- They run without GC safepoints — throwing creates a safepoint
- Stack unwinding requires potentially blocking operations
- Thread switching is disabled, but handler dispatch may need it
Code marked @Uninterruptible must handle all error conditions inline. If an error occurs, the typical pattern is:
@Uninterruptible
public final void criticalOperation() {
if (somethingWrong) {
Unsafe.die("Fatal error in criticalOperation");
}
}Attempting to throw from uninterruptible code results in undefined behavior or a fatal VM halt.
- Exception-Handler-Table — Detailed exception handler table transformation
- JIT-Compilers — L1/L2 compiler pipeline
- VM-Magic — Magic annotations
- Code-Conventions — @Uninterruptible usage
- Architecture — VM subsystem overview