ExceptionRegion modeling issues and proposed improvements

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Wed Oct 9 09:58:38 UTC 2024


On 09/10/2024 09:37, Adam Sotona wrote:
>
> When looking back to the example I realized it is not the best 
> illustrative case I could use.
>
> All nested exception regions in the example have identical start and 
> end positions, so they can be theoretically merged during lift and 
> lifted model can be much simpler (and I'm taking a ToDo note to 
> implement this merge in the BytecodeLift).
>
> However, in many cases nested try blocks differ in try start or try 
> end position, so they cannot be merged into one exception region start 
> according to the current model. And that is the situation with 
> significantly raised complexity.
>
> Here is better example of jumping out of the nested exception stack:
>
> @CodeReflection
>
>     static void nestedCatch(int i) {
>
>         while (true) {
>
>             try {
>
> try {
>
> if (i == 0) {
>
> break;
>
> } else if (i == 1) {
>
> continue;
>
> }
>
> } catch (NullPointerException npe) {
>
> npe.printStackTrace();
>
> }
>
> doSomething();
>
>             } catch (RuntimeException re) {
>
> re.printStackTrace();
>
>             }
>
>         }
>
>     }
>
> The above source is compiled into following model:
>
> func @"nestedCatch" 
> @loc="26:5:file:///Users/asotona/NetBeansProjects/JavaApplication6/src/javaapplication6/NewClass10.java" 
> (%0 : int)void -> {
>
>     %1 : Var<int> = var %0 @"i";
>
>     branch ^block_1;
>
>   ^block_1:
>
>     %2 : boolean = constant @"true";
>
>     cbranch %2 ^block_2 ^block_16;
>
>   ^block_2:
>
>     %3 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = 
> exception.region.enter ^block_3 ^block_14;
>
>   ^block_3:
>
>     %4 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = 
> exception.region.enter ^block_4 ^block_11;
>
>   ^block_4:
>
>     %5 : int = var.load %1;
>
>     %6 : int = constant @"0";
>
>     %7 : boolean = eq %5 %6;
>
>     cbranch %7 ^block_5 ^block_6;
>
>   ^block_5:
>
>     branch ^block_16;
>
>   ^block_6:
>
>     %8 : int = var.load %1;
>
>     %9 : int = constant @"1";
>
>     %10 : boolean = eq %8 %9;
>
>     cbranch %10 ^block_7 ^block_8;
>
>   ^block_7:
>
>     branch ^block_1;
>
>   ^block_8:
>
>     branch ^block_9;
>
>   ^block_9:
>
>     branch ^block_10;
>
>   ^block_10:
>
> exception.region.exit %4 ^block_12;
>
>   ^block_11(%11 : java.lang.NullPointerException):
>
>     %12 : Var<java.lang.NullPointerException> = var %11 @"npe";
>
>     %13 : java.lang.NullPointerException = var.load %12;
>
>     invoke %13 @"java.lang.NullPointerException::printStackTrace()void";
>
>     branch ^block_12;
>
>   ^block_12:
>
>     invoke @"javaapplication6.NewClass10::doSomething()void";
>
>     branch ^block_13;
>
>   ^block_13:
>
> exception.region.exit %3 ^block_15;
>
>   ^block_14(%14 : java.lang.RuntimeException):
>
>     %15 : Var<java.lang.RuntimeException> = var %14 @"re";
>
>     %16 : java.lang.RuntimeException = var.load %15;
>
>     invoke %16 @"java.lang.RuntimeException::printStackTrace()void";
>
>     branch ^block_15;
>
>   ^block_15:
>
>     branch ^block_1;
>
>   ^block_16:
>
>     return;
>
> };
>
> Which seems to invalid because the exception regions are not always 
> leaved (break; and continue; seems to have a bug).
>
I agree this seems to be an issue. e.g. block 16 is reachable from both 
block_1 and block_5. The latter is inside both exception regions, the 
former isn't. At the same time, the cbranch in block_1 seems also bogus: 
the condition is always true, so it's never possible to leave the while 
loop - meaning block16 is not really reachable from block_1. So there's 
an issue of which code should be generated here. If block_16 wants to be 
reachable by both blocks, then I think it needs to be split in two (one 
w/, the other w/o the exception region exit). If block_16 is only 
reachable from block_5, then we can keep one block, and always 
unconditionally exit the regions (but then we need to remove the cbranch 
in block_1).
>
> Here is lifted version of the same code, where 11 more synthetic 
> blocks are necessary to exit the exception regions correctly:
>
> func @"nestedCatch" (%0 : int)void -> {
>
>     branch ^block_1;
>
>   ^block_1:
>
>     %1 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = 
> exception.region.enter ^block_2 ^block_27;
>
>   ^block_2:
>
>     %2 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = 
> exception.region.enter ^block_3 ^block_20;
>
>   ^block_3:
>
>     %3 : int = constant @"0";
>
>     %4 : boolean = eq %0 %3;
>
>     cbranch %4 ^block_4 ^block_7;
>
>   ^block_4:
>
> exception.region.exit %2 ^block_5;
>
>   ^block_5:
>
> exception.region.exit %1 ^block_6;
>
>   ^block_6:
>
>     return;
>
>   ^block_7:
>
> exception.region.exit %2 ^block_8;
>
>   ^block_8:
>
> exception.region.exit %1 ^block_9;
>
>   ^block_9:
>
>     %5 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = 
> exception.region.enter ^block_10 ^block_27;
>
>   ^block_10:
>
>     %6 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = 
> exception.region.enter ^block_11 ^block_19;
>
>   ^block_11:
>
>     %7 : int = constant @"1";
>
>     %8 : boolean = eq %0 %7;
>
>     cbranch %8 ^block_12 ^block_15;
>
>   ^block_12:
>
> exception.region.exit %6 ^block_13;
>
>   ^block_13:
>
> exception.region.exit %5 ^block_14;
>
>   ^block_14:
>
>     branch ^block_1;
>
>   ^block_15:
>
> exception.region.exit %6 ^block_16;
>
>   ^block_16:
>
> exception.region.exit %5 ^block_17;
>
>   ^block_17:
>
>     %9 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = 
> exception.region.enter ^block_18 ^block_27;
>
>   ^block_18:
>
> exception.region.exit %9 ^block_24;
>
>   ^block_19(%10 : java.lang.NullPointerException):
>
> exception.region.exit %5 ^block_21(%10);
>
>   ^block_20(%11 : java.lang.NullPointerException):
>
> exception.region.exit %1 ^block_21(%11);
>
>   ^block_21(%12 : java.lang.NullPointerException):
>
>     %13 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = 
> exception.region.enter ^block_22 ^block_27;
>
>   ^block_22:
>
>     invoke %12 @"java.lang.NullPointerException::printStackTrace()void";
>
> exception.region.exit %13 ^block_23;
>
>   ^block_23:
>
>     branch ^block_24;
>
>   ^block_24:
>
>     %14 : java.lang.reflect.code.op.CoreOp$ExceptionRegion = 
> exception.region.enter ^block_25 ^block_27;
>
>   ^block_25:
>
>     invoke @"javaapplication6.NewClass10::doSomething()void";
>
> exception.region.exit %14 ^block_26;
>
>   ^block_26:
>
>     branch ^block_1;
>
>   ^block_27(%15 : java.lang.RuntimeException):
>
>     invoke %15 @"java.lang.RuntimeException::printStackTrace()void";
>
>     branch ^block_1;
>
> };
>
While this looks more correct, I don't really get the exception region 
exits in block_7 and block_8 (only to be re-entered again). There also 
seem to be a slight discrepancy - the first exception region always has 
its handler set to block_27. The second exception region has the handler 
at block_19 and block_20.

My feeling is that these little discrepancies add up quite a lot... if 
we could see that e.g. we leave the regions in 7 and 8 only to reenter 
them again in 9 and 10, then surely we can omit these redundant blocks 
(given they don't seem to be reachable from anywhere else). Also, the 
duplication between block 19 and 20 is annoying as well.


> With proposed changes the above example model may look like this:
>
> func @"nestedCatch" (%0 : int)void -> {
>
>     %1 : 
> java.lang.reflect.code.op.CoreOp$ExceptionRegion<java.lang.RuntimeException> 
> = exception.region ^block_13;
>
>     %2 : 
> java.lang.reflect.code.op.CoreOp$ExceptionRegion<java.lang.NullPointerException> 
> = exception.region ^block_9;
>
>     branch ^block_1;
>
>   ^block_1:
>
> exception.region.enter %1 %2 ^block_2; // entering nested try blocks
>
>   ^block_2:
>
>     %3 : int = constant @"0";
>
>     %4 : boolean = eq %0 %3;
>
>     cbranch %4 ^block_3 ^block_5;
>
>   ^block_3:
>
> exception.region.exit %2 %1 ^block_4; // leaving nested try blocks
>
>   ^block_4:
>
>     return;
>
>   ^block_5:
>
>     %5 : int = constant @"1";
>
>     %6 : boolean = eq %0 %5;
>
>     cbranch %6 ^block_6 ^block_8;
>
>   ^block_6:
>
> exception.region.exit %2 %1 ^block_7; ; // leaving nested try blocks
>
>   ^block_7:
>
>     branch ^block_1;
>
>   ^block_8:
>
> exception.region.exit %2 ^block_11;  // leaving only the inner NPE try 
> block
>
>   ^block_9(%7 : java.lang.NullPointerException):
>
> exception.region.enter %1 ^block_10; // re-entering the outer try block
>
>   ^block_10:
>
>     invoke %7 @"java.lang.NullPointerException::printStackTrace()void";
>
>     branch ^block_11;
>
>   ^block_11:
>
>     invoke @"javaapplication6.NewClass10::doSomething()void";
>
> exception.region.exit %1 ^block_12; // leaving the outer try block
>
>   ^block_12:
>
>     branch ^block_1;
>
>   ^block_13(%8 : java.lang.RuntimeException):
>
>     invoke %8 @"java.lang.RuntimeException::printStackTrace()void";
>
>     branch ^block_1;
>
> };
>
This does look better - but in part also because some of the problems 
highlighted above have been addressed? (e.g. more sharing of code paths, 
avoid duplicted exception region...)

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


More information about the babylon-dev mailing list