ExceptionRegion modeling issues and proposed improvements

Adam Sotona adam.sotona at oracle.com
Tue Oct 8 12:28:00 UTC 2024


Hi,
Current exception regions model is weak in modeling nested try blocks, shared catch handlers and transitions between try blocks.  Each of this complication multiplies complexity of the code model.
Following example consists of 9 bytecode instructions, wrapped in 3-level try catch, with two gaps and transitions between the try blocks:

- method name: tryMethod
    flags: [STATIC]
    method type: ()V
    attributes: [Code]
    code:
        max stack: 1
        max locals: 0
        0: {opcode: ICONST_0, constant value: 0}
        1: {opcode: IFEQ, target: 8}
        4: {opcode: ICONST_0, constant value: 0}
        5: {opcode: IFEQ, target: 9}
        8: {opcode: NOP}
        9: {opcode: RETURN}
        10: {opcode: ATHROW}
        11: {opcode: ATHROW}
        12: {opcode: ATHROW}
        exception handlers:
            handler 1: {start: 0, end: 4, handler: 10, type: java/lang/NullPointerException}
            handler 2: {start: 4, end: 8, handler: 10, type: java/lang/NullPointerException}
            handler 3: {start: 8, end: 10, handler: 10, type: java/lang/NullPointerException}
            handler 4: {start: 0, end: 4, handler: 11, type: java/lang/RuntimeException}
            handler 5: {start: 4, end: 8, handler: 11, type: java/lang/RuntimeException}
            handler 6: {start: 8, end: 10, handler: 11, type: java/lang/RuntimeException}
            handler 7: {start: 0, end: 4, handler: 12, type: java/lang/Throwable}
            handler 8: {start: 4, end: 8, handler: 12, type: java/lang/Throwable}
            handler 9: {start: 8, end: 10, handler: 12, type: java/lang/Throwable}


When we try to lift the above bytecode, we get following 47 blocks of model:

func @"tryMethod" ()void -> {
    %0 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = exception.region.enter ^block_1 ^block_46;

  ^block_1:
    %1 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = exception.region.enter ^block_2 ^block_44;

  ^block_2:
    %2 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = exception.region.enter ^block_3 ^block_41;

  ^block_3:
    %3 : int = constant @"0";
    %4 : boolean = neq %3 %3;
    cbranch %4 ^block_4 ^block_21;

  ^block_4:
    exception.region.exit %2 ^block_5;

  ^block_5:
    exception.region.exit %1 ^block_6;

  ^block_6:
    exception.region.exit %0 ^block_7;

  ^block_7:
    %5 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = exception.region.enter ^block_8 ^block_46;

.
. (skipped due to mailing list size limit)
.

  ^block_41(%24 : java.lang.NullPointerException):
    exception.region.exit %1 ^block_42(%24);

  ^block_42(%25 : java.lang.NullPointerException):
    exception.region.exit %0 ^block_43(%25);

  ^block_43(%26 : java.lang.NullPointerException):
    throw %26;

  ^block_44(%27 : java.lang.RuntimeException):
    exception.region.exit %0 ^block_45(%27);

  ^block_45(%28 : java.lang.RuntimeException):
    throw %28;

  ^block_46(%29 : java.lang.Throwable):
    throw %29;
};

My first proposal consists of:
- detach exception table entry declaration from the region entry
- allow exception.region.enter and exception.region.exit to enter resp. exit one or more exception table entries
- behavioral change: an exception thrown causes to leave all levels exception regions (transition to a handler clears the actual exception stack). The change requires each exception handler to explicitly declare relevant exception regions re-entries.

The above example model will significantly simplify and may be easily optimized even more:

func @"tryMethod" ()void -> {
    %0 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = exception.region ^block_8;
    %1 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = exception.region ^block_9;
    %2 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = exception.region ^block_10;
    exception.region.enter %0 %1 %2 ^block_1;

  ^block_1:
    %3 : int = constant @"0";
    %4 : boolean = neq %3 %3;
    cbranch %4 ^block_2 ^block_5;

  ^block_2:
    exception.region.exit %0 %1 %2 ^block_3;

  ^block_3:
    exception.region.enter %0 %1 %2 ^block_4;

  ^block_4:
    %5 : boolean = neq %3 %3;
    cbranch %5 ^block_5 ^block_5;

  ^block_5:
    exception.region.exit %0 %1 %2 ^block_6;

  ^block_6:
    exception.region.enter %0 %1 %2 ^block_7;

  ^block_7:
    return;

  ^block_8(%6 : java.lang.NullPointerException):
    throw %6;

  ^block_9(%7 : java.lang.RuntimeException):
    throw %7;

  ^block_10(%8 : java.lang.Throwable):
    throw %8;
};



My second concern and proposal related to inability to model multi-catch try blocks. Following code:

    @CodeReflection
    static void multicatch() {
        try {
            System.out.println("do something");
        } catch (NullPointerException | IllegalArgumentException e) {
            throw e;
        } catch (RuntimeException e) {
            return;
        }
    }

is lowered to:

func @"multicatch" ()void -> {
    %0 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = exception.region.enter ^block_1 ^block_4 ^block_5;

  ^block_1:
    %1 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
    %2 : java.lang.String = constant @"do something";
    invoke %1 %2 @"java.io.PrintStream::println(java.lang.String)void";
    branch ^block_2;

  ^block_2:
    exception.region.exit %0 ^block_3;

  ^block_3:
    return;

  ^block_4(%3 : java.lang.RuntimeException):
    %4 : Var<java.lang.RuntimeException> = var %3 @"e";
    %5 : java.lang.RuntimeException = var.load %4;
    throw %5;

  ^block_5(%6 : java.lang.RuntimeException):
    %7 : Var<java.lang.RuntimeException> = var %6 @"e";
    return;
};
Which is not correct and information about exact catch type is lost.

I propose to add specific catch type to the exception table entry declaration, so the model will change to:

func @"multicatch" ()void -> {
    %0 : java.lang.reflect.code.op.CoreOp$ExceptionRegion<java.lang.NullPointerException> = exception.region ^block_4;
    %1 : java.lang.reflect.code.op.CoreOp$ExceptionRegion<java.lang.IllegalArgumentException> = exception.region ^block_4;
    %2 : java.lang.reflect.code.op.CoreOp$ExceptionRegion<java.lang.RuntimeException> = exception.region ^block_5;
    exception.region.enter %0 %1 %2 ^block_1;

  ^block_1:
    %3 : java.io.PrintStream = field.load @"java.lang.System::out()java.io.PrintStream";
    %4 : java.lang.String = constant @"do something";
    invoke %3 %4 @"java.io.PrintStream::println(java.lang.String)void";
    branch ^block_2;

  ^block_2:
    exception.region.exit %0 %1 %2 ^block_3;

  ^block_3:
    return;

  ^block_4(%3 : java.lang.RuntimeException):
    %4 : Var<java.lang.RuntimeException> = var %3 @"e";
    %5 : java.lang.RuntimeException = var.load %4;
    throw %5;

  ^block_5(%6 : java.lang.RuntimeException):
    %7 : Var<java.lang.RuntimeException> = var %6 @"e";
    return;
};


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/babylon-dev/attachments/20241008/14dae724/attachment-0001.htm>


More information about the babylon-dev mailing list