- 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:
publicfinalclassVmCompiledExceptionHandlerextendsAbstractExceptionHandler {
privatefinalVmAddresshandler; // Native address of catch blockprivatefinalVmAddressstartPtr; // PC range startprivatefinalVmAddressendPtr; // PC range endpublicbooleanisInScope(Addressaddress) {
finalAddressstart = Address.fromAddress(startPtr);
finalAddressend = Address.fromAddress(endPtr);
returnaddress.GE(start) && address.LT(end);
}
}VmCompiledCode holds the handler table and a defaultExceptionHandler:
publicfinalclassVmCompiledCodeextendsAbstractCode {
privatefinalVmCompiledExceptionHandler[] eTable;
privatefinalVmAddressdefaultExceptionHandler;
// ...
}When an exception is thrown, VmSystem.findThrowableHandler() walks the call stack:
publicstaticAddressfindThrowableHandler(Throwableex, Addressframe, Addressaddress) {
// 1. Get current method + compiled codefinalVmMethodmethod = reader.getMethod(frame);
finalVmCompiledCodecc = reader.getCompiledCode(frame);
// 2. Iterate exception handlersfor (inti = 0; i < cc.getNoExceptionHandlers(); i++) {
finalVmCompiledExceptionHandlerceh = cc.getExceptionHandler(i);
if (ceh.isInScope(address)) {
finalVmConstClasscatchType = eh.getCatchType();
if (catchType == null) {
// Catch-all (finally block)returnAddress.fromAddress(ceh.getHandler());
} else {
// Check assignabilityif (handlerClass.isAssignableFrom(exClass)) {
returnAddress.fromAddress(ceh.getHandler());
}
}
}
}
// 3. If PC is in compiled code but no handler matchedif (cc.contains(address)) {
returnAddress.fromAddress(cc.getDefaultExceptionHandler());
}
returnnull;
}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:
@UninterruptiblepublicfinalvoidcriticalOperation() {
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