diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/dialect/core/NormalizeBlocksTransformer.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/dialect/core/NormalizeBlocksTransformer.java index f1245e55fe7..599f3c1ea8e 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/dialect/core/NormalizeBlocksTransformer.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/dialect/core/NormalizeBlocksTransformer.java @@ -81,11 +81,12 @@ case CoreOp.BranchOp bop when isPureConditionalDispatchingBlock(bop.branch().tar && or.op() instanceof CoreOp.ConstantOp cop -> { // Skip intermediate conditional branch with constant boolean argument and re-target // directly to the true or false branch, based on the constant value. - CoreOp.ConditionalBranchOp cbo = (CoreOp.ConditionalBranchOp)bop.branch().targetBlock().terminatingOp(); - Block.Reference br = (Boolean)cop.value() ? cbo.trueBranch() : cbo.falseBranch(); + CoreOp.ConditionalBranchOp cbo = (CoreOp.ConditionalBranchOp) bop.branch().targetBlock().terminatingOp(); + Block.Reference br = (boolean) cop.value() ? cbo.trueBranch() : cbo.falseBranch(); // Remove the conditional dispatching block if all predecessor reference args are constants if (bop.branch().targetBlock().predecessorReferences().stream() - .allMatch(r -> r.arguments().getFirst() instanceof Op.Result orr && orr.op() instanceof CoreOp.ConstantOp)) { + .allMatch(r -> r.arguments().getFirst() instanceof Op.Result orr && + orr.op() instanceof CoreOp.ConstantOp)) { mergedBlocks.add(bop.branch().targetBlock()); if (br.targetBlock().predecessors().size() == 1) { // Merge the successor's target block with this block @@ -95,17 +96,21 @@ case CoreOp.BranchOp bop when isPureConditionalDispatchingBlock(bop.branch().tar } b.add(CoreOp.branch(b.context().getReferenceOrCreate(br))); } + case CoreOp.ConditionalBranchOp cbo -> { + if (!replaceConditionalBranchTarget(b, cbo, cbo.trueBranch())) { + removeUnusedBlockParameters(b, cbo.trueBranch()); + } + if (!replaceConditionalBranchTarget(b, cbo, cbo.falseBranch())) { + removeUnusedBlockParameters(b, cbo.falseBranch()); + } + b.add(op); + } case CoreOp.BranchOp bop when bop.branch().targetBlock().predecessors().size() == 1 -> { // Merge the successor's target block with this block, and so on // The terminal branch operation is replaced with the operations in the // successor's target block mergeBlock(b, bop); } - case CoreOp.ConstantOp cop when cop.resultType().equals(JavaType.BOOLEAN) - && cop.result().uses().stream().allMatch(cr -> cr.op() instanceof CoreOp.BranchOp bop - && isPureConditionalDispatchingBlock(bop.branch().targetBlock())) -> { - // Remove boolean ConstantOp used purelly as BranchOp successor arguments to a conditional dispatching block - } case JavaOp.ExceptionRegionEnter ere -> { // Cannot remove block parameters from exception handlers removeUnusedBlockParameters(b, ere.startReference()); @@ -129,6 +134,71 @@ && isPureConditionalDispatchingBlock(bop.branch().targetBlock())) -> { return b; } + private boolean replaceConditionalBranchTarget(Block.Builder b, + CoreOp.ConditionalBranchOp cbo, + Block.Reference successor) { + assert cbo.successors().contains(successor); + + Block target = successor.targetBlock(); + if (isPureConditionalDispatchingBlock(target)) { + if (successor.arguments().getFirst() == cbo.predicateOperand()) { + /* + func @"m" (%0 : java.type:"boolean")java.type:"void" -> { + cbranch %0 ^block_1 ^block_2(%0); + -> + cbranch %0 ^block_1 ^block_3; + + ^block_1: + branch ^block_3; + + ^block_2(%1 : java.type:"boolean"): + cbranch %1 ^block_3 ^block_4; + + ^block_3: + branch ^block_5; + */ + CoreOp.ConditionalBranchOp targetCbo = (CoreOp.ConditionalBranchOp) target.terminatingOp(); + Block.Reference replacementSuccessor = cbo.trueBranch() == successor + ? targetCbo.trueBranch() + : targetCbo.falseBranch(); + b.context().mapReference(successor, + b.context().getReferenceOrCreate(replacementSuccessor)); + return true; + } else if (successor.arguments().getFirst() instanceof Op.Result or + && or.op() instanceof CoreOp.ConstantOp cop) { + /* + func @"m" ()java.type:"void" -> { + %false : java.type:"boolean" = constant @false; + cbranch %0 ^block_1 ^block_2(%false); + -> + cbranch %0 ^block_1 ^block_3; + + ^block_1: + branch ^block_3; + + ^block_2(%1 : java.type:"boolean"): + cbranch %1 ^block_3 ^block_4; + + ^block_3: + branch ^block_5; + */ + + CoreOp.ConditionalBranchOp targetCbo = (CoreOp.ConditionalBranchOp) target.terminatingOp(); + Block.Reference replacementSuccessor = (boolean) cop.value() + ? targetCbo.trueBranch() + : targetCbo.falseBranch(); + b.context().mapReference(successor, + b.context().getReferenceOrCreate(replacementSuccessor)); + return true; + } + } + return false; + } + + /* + ^b(%pred : java.type:"boolean"): + cbranch %pred ^true ^false; + */ private static boolean isPureConditionalDispatchingBlock(Block b) { return b.parameters().size() == 1 && b.parameters().getFirst().type().equals(JavaType.BOOLEAN) diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/dialect/java/JavaOp.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/dialect/java/JavaOp.java index d6b42a519c1..d6145763dc8 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/dialect/java/JavaOp.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/dialect/java/JavaOp.java @@ -4597,6 +4597,9 @@ static Block.Builder lower(Block.Builder startBlock, BiFunction inherited) { - // No match block - Block.Builder endNoMatchBlock = b.block(); - // Match block - Block.Builder endMatchBlock = b.block(); + public Block.Builder lower(Block.Builder startBlock, BiFunction inherited) { // End block - Block.Builder endBlock = b.block(); + Block.Builder endBlock = startBlock.block(); Block.Parameter matchResult = endBlock.parameter(resultType()); // Map match operation result - b.context().mapValue(result(), matchResult); + startBlock.context().mapValue(result(), matchResult); + Block.Reference noMatchRef = endBlock.reference(startBlock.add(constant(BOOLEAN, false))); List patternValues = new ArrayList<>(); Op patternYieldOp = patternBody.entryBlock().terminatingOp(); Op.Result rootPatternValue = (Op.Result) patternYieldOp.operands().get(0); - Block.Builder currentBlock = lower(endNoMatchBlock, b, + Block.Builder matchedBlock = lower(noMatchRef, startBlock, patternValues, rootPatternValue.op(), - b.context().getValue(targetOperand())); - currentBlock.add(branch(endMatchBlock.reference())); - - // No match block - // Pass false - endNoMatchBlock.add(branch(endBlock.reference( - endNoMatchBlock.add(constant(BOOLEAN, false))))); + startBlock.context().getValue(targetOperand())); // Match block // Lower match body and pass true - endMatchBlock.transformBody(matchBody, patternValues, loweringTransformer(inherited, (block, op) -> { + matchedBlock.transformBody(matchBody, patternValues, loweringTransformer(inherited, (block, op) -> { if (op instanceof CoreOp.YieldOp) { block.add(branch(endBlock.reference( block.add(constant(BOOLEAN, true))))); @@ -6178,18 +6172,18 @@ public Block.Builder lower(Block.Builder b, BiFunction bindings, Op pattern, Value target) { return switch (pattern) { - case RecordPatternOp rp -> lowerRecordPattern(endNoMatchBlock, currentBlock, bindings, rp, target); - case TypePatternOp tp -> lowerTypePattern(endNoMatchBlock, currentBlock, bindings, tp, target); - case MatchAllPatternOp map -> lowerMatchAllPattern(currentBlock); + case RecordPatternOp rp -> lowerRecordPattern(noMatchRef, currentBlock, bindings, rp, target); + case TypePatternOp tp -> lowerTypePattern(noMatchRef, currentBlock, bindings, tp, target); + case MatchAllPatternOp _ -> lowerMatchAllPattern(currentBlock); case null, default -> throw new UnsupportedOperationException("Unknown pattern op: " + pattern); }; } - static Block.Builder lowerRecordPattern(Block.Builder endNoMatchBlock, Block.Builder currentBlock, + static Block.Builder lowerRecordPattern(Block.Reference noMatchRef, Block.Builder currentBlock, List bindings, JavaOp.PatternOps.RecordPatternOp rpOp, Value target) { CodeType targetType = rpOp.targetType(); @@ -6198,7 +6192,7 @@ static Block.Builder lowerRecordPattern(Block.Builder endNoMatchBlock, Block.Bui // Check if instance of target type Op.Result isInstance = currentBlock.add(instanceOf(targetType, target)); - currentBlock.add(conditionalBranch(isInstance, nextBlock.reference(), endNoMatchBlock.reference())); + currentBlock.add(conditionalBranch(isInstance, nextBlock.reference(), noMatchRef)); currentBlock = nextBlock; @@ -6209,15 +6203,15 @@ static Block.Builder lowerRecordPattern(Block.Builder endNoMatchBlock, Block.Bui for (int i = 0; i < dArgs.size(); i++) { Op.Result nestedPattern = (Op.Result) dArgs.get(i); // @@@ Handle exceptions? - Value nestedTarget = currentBlock.add(invoke(rpOp.recordReference().methodForComponent(i), target)); + Value nestedTarget = currentBlock.add(invoke(rpOp.recordReference().methodForComponent(i), target)); - currentBlock = lower(endNoMatchBlock, currentBlock, bindings, nestedPattern.op(), nestedTarget); + currentBlock = lower(noMatchRef, currentBlock, bindings, nestedPattern.op(), nestedTarget); } return currentBlock; } - static Block.Builder lowerTypePattern(Block.Builder endNoMatchBlock, Block.Builder currentBlock, + static Block.Builder lowerTypePattern(Block.Reference noMatchRef, Block.Builder currentBlock, List bindings, TypePatternOp tpOp, Value target) { CodeType targetType = tpOp.targetType(); @@ -6276,7 +6270,7 @@ static Block.Builder lowerTypePattern(Block.Builder endNoMatchBlock, Block.Build if (p != null) { // p != null, we need to perform type check at runtime Block.Builder nextBlock = currentBlock.block(); - currentBlock.add(conditionalBranch(currentBlock.add(p), nextBlock.reference(), endNoMatchBlock.reference())); + currentBlock.add(conditionalBranch(currentBlock.add(p), nextBlock.reference(), noMatchRef)); currentBlock = nextBlock; } if (c != null) { diff --git a/test/jdk/jdk/incubator/code/TestNormalizeBlocksTransformer.java b/test/jdk/jdk/incubator/code/TestNormalizeBlocksTransformer.java index 5cbaaea91de..688c253bbcd 100644 --- a/test/jdk/jdk/incubator/code/TestNormalizeBlocksTransformer.java +++ b/test/jdk/jdk/incubator/code/TestNormalizeBlocksTransformer.java @@ -35,9 +35,29 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.stream.Stream; public class TestNormalizeBlocksTransformer { + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface NormalizedModel { + String value(); + } + + @NormalizedModel(""" + func @"f" (%0 : java.type:"int")java.type:"int" -> { + %1 : java.type:"int" = invoke @java.ref:"C::m():int"; + %2 : java.type:"int" = invoke %1 @java.ref:"C::m(int):int"; + %3 : java.type:"int" = invoke %2 %2 @java.ref:"C::m(int, int):int"; + %4 : java.type:"int" = invoke %3 %2 %2 @java.ref:"C::m(int, int, int):int"; + return %4; + }; + """) static final String TEST1_INPUT = """ func @"f" (%0 : java.type:"int")java.type:"int" -> { %1 : java.type:"int" = invoke @java.ref:"C::m():int"; @@ -59,16 +79,34 @@ public class TestNormalizeBlocksTransformer { return %6; }; """; - static final String TEST1_EXPECTED = """ - func @"f" (%0 : java.type:"int")java.type:"int" -> { - %1 : java.type:"int" = invoke @java.ref:"C::m():int"; - %2 : java.type:"int" = invoke %1 @java.ref:"C::m(int):int"; - %3 : java.type:"int" = invoke %2 %2 @java.ref:"C::m(int, int):int"; - %4 : java.type:"int" = invoke %3 %2 %2 @java.ref:"C::m(int, int, int):int"; - return %4; - }; - """; + @NormalizedModel(""" + func @"f" (%0 : java.type:"java.lang.Object")java.type:"void" -> { + %1 : Var = var %0 @"o"; + %10 : java.type:"java.lang.RuntimeException" = constant @null; + %2 : java.type:"void" = exception.region.enter ^block_1 ^block_5(%10) ^block_2(%10); + + ^block_1: + %3 : java.type:"int" = invoke @java.ref:"A::try_():int"; + exception.region.exit %2 ^block_4; + + ^block_2(%4 : java.type:"java.lang.RuntimeException"): + %11 : java.type:"java.lang.Throwable" = constant @null; + %5 : java.type:"void" = exception.region.enter ^block_3 ^block_5(%11); + + ^block_3: + %6 : Var = var %4 @"e"; + exception.region.exit %5 ^block_4; + + ^block_4: + %7 : java.type:"int" = invoke @java.ref:"A::finally_():int"; + return; + + ^block_5(%8 : java.type:"java.lang.Throwable"): + %9 : java.type:"int" = invoke @java.ref:"A::finally_():int"; + throw %8; + }; + """) static final String TEST2_INPUT = """ func @"f" (%0 : java.type:"java.lang.Object")java.type:"void" -> { %1 : Var = var %0 @"o"; @@ -105,33 +143,25 @@ public class TestNormalizeBlocksTransformer { throw %8; }; """; - static final String TEST2_EXPECTED = """ - func @"f" (%0 : java.type:"java.lang.Object")java.type:"void" -> { - %1 : Var = var %0 @"o"; - %10 : java.type:"java.lang.RuntimeException" = constant @null; - %2 : java.type:"void" = exception.region.enter ^block_1 ^block_5(%10) ^block_2(%10); - - ^block_1: - %3 : java.type:"int" = invoke @java.ref:"A::try_():int"; - exception.region.exit %2 ^block_4; - ^block_2(%4 : java.type:"java.lang.RuntimeException"): - %11 : java.type:"java.lang.Throwable" = constant @null; - %5 : java.type:"void" = exception.region.enter ^block_3 ^block_5(%11); - - ^block_3: - %6 : Var = var %4 @"e"; - exception.region.exit %5 ^block_4; + @NormalizedModel(""" + func @"f" (%0 : java.type:"int")java.type:"int" -> { + %1 : java.type:"int" = constant @0; + %2 : java.type:"boolean" = gt %0 %1; + cbranch %2 ^block_1 ^block_2; - ^block_4: - %7 : java.type:"int" = invoke @java.ref:"A::finally_():int"; - return; + ^block_1: + %3 : java.type:"int" = constant @1; + branch ^block_3(%3); - ^block_5(%8 : java.type:"java.lang.Throwable"): - %9 : java.type:"int" = invoke @java.ref:"A::finally_():int"; - throw %8; - };"""; + ^block_2: + %4 : java.type:"int" = constant @-1; + branch ^block_3(%4); + ^block_3(%5 : java.type:"int"): + return %5; + }; + """) static final String TEST3_INPUT = """ func @"f" (%0 : java.type:"int")java.type:"int" -> { %1 : java.type:"int" = constant @0; @@ -155,7 +185,8 @@ public class TestNormalizeBlocksTransformer { ^block_3(%5 : java.type:"int"): return %5; };"""; - static final String TEST3_EXPECTED = """ + + @NormalizedModel(""" func @"f" (%0 : java.type:"int")java.type:"int" -> { %1 : java.type:"int" = constant @0; %2 : java.type:"boolean" = gt %0 %1; @@ -172,8 +203,7 @@ public class TestNormalizeBlocksTransformer { ^block_3(%5 : java.type:"int"): return %5; }; - """; - + """) static final String TEST4_INPUT = """ func @"f" (%0 : java.type:"int")java.type:"int" -> { %1 : java.type:"int" = constant @0; @@ -197,25 +227,26 @@ public class TestNormalizeBlocksTransformer { ^block_3(%unused_1 : java.type:"int", %5 : java.type:"int", %unused_2 : java.type:"int"): return %5; };"""; - static final String TEST4_EXPECTED = """ - func @"f" (%0 : java.type:"int")java.type:"int" -> { - %1 : java.type:"int" = constant @0; - %2 : java.type:"boolean" = gt %0 %1; - cbranch %2 ^block_1 ^block_2; + + @NormalizedModel(""" + func @"f" ()java.type:"void" -> { + %1 : java.type:"java.lang.Throwable" = constant @null; + %0 : java.type:"void" = exception.region.enter ^block_1 ^block_3(%1); ^block_1: - %3 : java.type:"int" = constant @1; - branch ^block_3(%3); + invoke @java.ref:"A::m():void"; + exception.region.exit %0 ^block_2; ^block_2: - %4 : java.type:"int" = constant @-1; - branch ^block_3(%4); + branch ^block_4; - ^block_3(%5 : java.type:"int"): - return %5; - }; - """; + ^block_3(%1 : java.type:"java.lang.Throwable"): + branch ^block_4; + ^block_4: + return; + }; + """) static final String TEST5_INPUT = """ func @"f" ()java.type:"void" -> { %2 : java.type:"java.lang.Throwable" = constant @null; @@ -238,26 +269,58 @@ public class TestNormalizeBlocksTransformer { return; }; """; - static final String TEST5_EXPECTED = """ - func @"f" ()java.type:"void" -> { - %1 : java.type:"java.lang.Throwable" = constant @null; - %0 : java.type:"void" = exception.region.enter ^block_1 ^block_3(%1); + + @NormalizedModel(""" + func @"m" (%0 : java.type:"java.lang.Object")java.type:"int" -> { + %1 : java.type:"java.lang.Object" = constant @null; + %2 : java.type:"boolean" = invoke %0 %1 @java.ref:"java.util.Objects::equals(java.lang.Object, java.lang.Object):boolean"; + cbranch %2 ^block_1 ^block_2; ^block_1: - invoke @java.ref:"A::m():void"; - exception.region.exit %0 ^block_2; + %3 : java.type:"java.lang.NullPointerException" = new @java.ref:"java.lang.NullPointerException::()"; + throw %3; ^block_2: - branch ^block_4; + %4 : java.type:"boolean" = instanceof %0 @java.type:"java.util.List"; + cbranch %4 ^block_3 ^block_4; - ^block_3(%1 : java.type:"java.lang.Throwable"): - branch ^block_4; + ^block_3: + %5 : java.type:"java.util.List" = cast %0 @java.type:"java.util.List"; + %6 : java.type:"boolean" = constant @true; + %7 : java.type:"int" = constant @1; + branch ^block_9(%7); ^block_4: - return; - }; - """; + %8 : java.type:"boolean" = constant @false; + %9 : java.type:"boolean" = instanceof %0 @java.type:"java.lang.String"; + cbranch %9 ^block_5 ^block_6; + + ^block_5: + %10 : java.type:"java.lang.String" = cast %0 @java.type:"java.lang.String"; + %11 : java.type:"boolean" = constant @true; + %12 : java.type:"int" = constant @2; + branch ^block_9(%12); + + ^block_6: + %13 : java.type:"boolean" = constant @false; + %14 : java.type:"boolean" = instanceof %0 @java.type:"java.util.Map"; + cbranch %14 ^block_7 ^block_8; + + ^block_7: + %15 : java.type:"java.util.Map" = cast %0 @java.type:"java.util.Map"; + %16 : java.type:"boolean" = constant @true; + %17 : java.type:"int" = constant @3; + branch ^block_9(%17); + + ^block_8: + %18 : java.type:"boolean" = constant @false; + %19 : java.type:"int" = constant @-1; + branch ^block_9(%19); + ^block_9(%20 : java.type:"int"): + return %20; + }; + """) static final String TEST6_INPUT = """ func @"m" (%0 : java.type:"java.lang.Object")java.type:"int" -> { %1 : java.type:"java.lang.Object" = constant @null; @@ -345,54 +408,117 @@ public class TestNormalizeBlocksTransformer { return %23; }; """; - static final String TEST6_EXPECTED = """ - func @"m" (%0 : java.type:"java.lang.Object")java.type:"int" -> { - %1 : java.type:"java.lang.Object" = constant @null; - %2 : java.type:"boolean" = invoke %0 %1 @java.ref:"java.util.Objects::equals(java.lang.Object, java.lang.Object):boolean"; - cbranch %2 ^block_1 ^block_2; + + @NormalizedModel(""" + func @"m" (%0 : java.type:"boolean")java.type:"void" -> { + cbranch %0 ^block_1 ^block_3; ^block_1: - %3 : java.type:"java.lang.NullPointerException" = new @java.ref:"java.lang.NullPointerException::()"; - throw %3; + %1 : java.type:"boolean" = constant @true; + branch ^block_2; ^block_2: - %4 : java.type:"boolean" = instanceof %0 @java.type:"java.util.List"; - cbranch %4 ^block_3 ^block_4; + %2 : java.type:"int" = constant @1; + branch ^block_4; ^block_3: - %5 : java.type:"java.util.List" = cast %0 @java.type:"java.util.List"; - %6 : java.type:"int" = constant @1; - branch ^block_9(%6); + %3 : java.type:"int" = constant @2; + branch ^block_4; + + ^block_4: + return; + }; + """) + static final String TEST7_INPUT = """ + func @"m" (%0 : java.type:"boolean")java.type:"void" -> { + cbranch %0 ^block_1 ^block_2(%0); + + ^block_1: + %1 : java.type:"boolean" = constant @true; + branch ^block_2(%1); + + ^block_2(%2 : java.type:"boolean"): + cbranch %2 ^block_3 ^block_4; + + ^block_3: + %one : java.type:"int" = constant @1; + branch ^block_5; ^block_4: - %7 : java.type:"boolean" = instanceof %0 @java.type:"java.lang.String"; - cbranch %7 ^block_5 ^block_6; + %two : java.type:"int" = constant @2; + branch ^block_5; ^block_5: - %8 : java.type:"java.lang.String" = cast %0 @java.type:"java.lang.String"; - %9 : java.type:"int" = constant @2; - branch ^block_9(%9); + return; + }; + """; - ^block_6: - %10 : java.type:"boolean" = instanceof %0 @java.type:"java.util.Map"; - cbranch %10 ^block_7 ^block_8; + @NormalizedModel(""" + func @"m" (%0 : java.type:"boolean")java.type:"void" -> { + cbranch %0 ^block_1 ^block_2; - ^block_7: - %11 : java.type:"java.util.Map" = cast %0 @java.type:"java.util.Map"; - %12 : java.type:"int" = constant @3; - branch ^block_9(%12); + ^block_1: + %1 : java.type:"int" = constant @1; + branch ^block_4; - ^block_8: - %13 : java.type:"int" = constant @-1; - branch ^block_9(%13); + ^block_2: + %2 : java.type:"boolean" = constant @false; + branch ^block_3; + + ^block_3: + %3 : java.type:"int" = constant @2; + branch ^block_4; - ^block_9(%14 : java.type:"int"): - return %14; + ^block_4: + return; + }; + """) + static final String TEST8_INPUT = """ + func @"m" (%0 : java.type:"boolean")java.type:"void" -> { + cbranch %0 ^block_2(%0) ^block_1; + + ^block_1: + %1 : java.type:"boolean" = constant @false; + branch ^block_2(%1); + + ^block_2(%2 : java.type:"boolean"): + cbranch %2 ^block_3 ^block_4; + + ^block_3: + %one : java.type:"int" = constant @1; + branch ^block_5; + + ^block_4: + %two : java.type:"int" = constant @2; + branch ^block_5; + + ^block_5: + return; }; """; - static final String TEST7_INPUT = """ + + @NormalizedModel(""" func @"m" (%0 : java.type:"boolean")java.type:"void" -> { - cbranch %0 ^block_1 ^block_2(%0); + %1 : java.type:"boolean" = constant @false; + cbranch %0 ^block_1 ^block_2; + + ^block_1: + %2 : java.type:"boolean" = constant @true; + %3 : java.type:"int" = constant @1; + branch ^block_3; + + ^block_2: + %4 : java.type:"int" = constant @2; + branch ^block_3; + + ^block_3: + return; + }; + """) + static final String TEST9_INPUT = """ + func @"m" (%0 : java.type:"boolean")java.type:"void" -> { + %false : java.type:"boolean" = constant @false; + cbranch %0 ^block_1 ^block_2(%false); ^block_1: %1 : java.type:"boolean" = constant @true; @@ -402,45 +528,98 @@ public class TestNormalizeBlocksTransformer { cbranch %2 ^block_3 ^block_4; ^block_3: + %one : java.type:"int" = constant @1; branch ^block_5; ^block_4: + %two : java.type:"int" = constant @2; branch ^block_5; ^block_5: return; }; """; - static final String TEST7_EXPECTED = """ + + @NormalizedModel(""" func @"m" (%0 : java.type:"boolean")java.type:"void" -> { - cbranch %0 ^block_1 ^block_2(%0); + %1 : java.type:"boolean" = constant @true; + cbranch %0 ^block_1 ^block_2; ^block_1: + %2 : java.type:"int" = constant @1; branch ^block_3; - ^block_2(%1 : java.type:"boolean"): - cbranch %1 ^block_3 ^block_4; + ^block_2: + %3 : java.type:"boolean" = constant @false; + %4 : java.type:"int" = constant @2; + branch ^block_3; + + ^block_3: + return; + }; + """) + static final String TEST10_INPUT = """ + func @"m" (%0 : java.type:"boolean")java.type:"void" -> { + %true : java.type:"boolean" = constant @true; + cbranch %0 ^block_2(%true) ^block_1; + + ^block_1: + %1 : java.type:"boolean" = constant @false; + branch ^block_2(%1); + + ^block_2(%2 : java.type:"boolean"): + cbranch %2 ^block_3 ^block_4; ^block_3: + %one : java.type:"int" = constant @1; branch ^block_5; ^block_4: + %two : java.type:"int" = constant @2; branch ^block_5; ^block_5: return; }; """; + + @NormalizedModel(""" + func @"m" (%0 : java.type:"int")java.type:"int" -> { + %1 : java.type:"int" = constant @1; + %2 : java.type:"int" = constant @2; + %3 : java.type:"boolean" = constant @false; + cbranch %3 ^block_2(%1) ^block_2(%1); + + ^block_2(%5 : java.type:"int"): + return %5; + }; + """) + static final String TEST11_INPUT = """ + func @"m" (%0 : java.type:"int")java.type:"int" -> { + %1 : java.type:"int" = constant @1; + %2 : java.type:"int" = constant @2; + %2 : java.type:"boolean" = constant @false; + cbranch %2 ^block_2(%0, %1, %2) ^block_2(%0, %1, %2); + + ^block_2(%unused_1 : java.type:"int", %used : java.type:"int", %unused_2 : java.type:"int"): + return %used; + }; + """; + static Object[][] testModels() { - return new Object[][]{ - parse(TEST1_INPUT, TEST1_EXPECTED), - parse(TEST2_INPUT, TEST2_EXPECTED), - parse(TEST3_INPUT, TEST3_EXPECTED), - parse(TEST4_INPUT, TEST4_EXPECTED), - parse(TEST5_INPUT, TEST5_EXPECTED), - parse(TEST6_INPUT, TEST6_EXPECTED), - parse(TEST7_INPUT, TEST7_EXPECTED), - }; + return Stream.of(TestNormalizeBlocksTransformer.class.getDeclaredFields()) + .mapMulti((field, downstream) -> { + NormalizedModel normalizedModel = field.getAnnotation(NormalizedModel.class); + String model = null; + try { + model = (String) field.get(null); + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } + if (normalizedModel != null) { + downstream.accept(parse(model, normalizedModel.value())); + } + }).toArray(Object[][]::new); } static Object[] parse(String... models) { diff --git a/test/jdk/jdk/incubator/code/lower/CodeReflectionTester.java b/test/jdk/jdk/incubator/code/lower/CodeReflectionTester.java index d383b04103a..f0f768e25e6 100644 --- a/test/jdk/jdk/incubator/code/lower/CodeReflectionTester.java +++ b/test/jdk/jdk/incubator/code/lower/CodeReflectionTester.java @@ -24,9 +24,12 @@ import java.io.StringWriter; import java.lang.reflect.Member; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.EnumSet; import jdk.incubator.code.CodeTransformer; import jdk.incubator.code.Op; +import jdk.incubator.code.dialect.core.NormalizeBlocksTransformer; import jdk.incubator.code.dialect.core.SSA; import jdk.incubator.code.dialect.core.CoreOp; import jdk.incubator.code.dialect.java.JavaOp; @@ -61,7 +64,7 @@ static void check(Method method) throws ReflectiveOperationException { CoreOp.FuncOp f = Op.ofMethod(method).orElseThrow(() -> new AssertionError("No code model for reflective method")); - f = lower(f, lma.ssa()); + f = lower(f, lma.transform()); // we remove the source attribute, to avoid updating the tests, which we will do later String actual = canonicalizeModel(method, removeSourceAttribute(f)); @@ -71,15 +74,19 @@ static void check(Method method) throws ReflectiveOperationException { } } - static CoreOp.FuncOp lower(CoreOp.FuncOp f, boolean ssa) { + static CoreOp.FuncOp lower(CoreOp.FuncOp f, LoweredModel.Transform[] transforms) { f = f.transform(CodeTransformer.LOWERING_TRANSFORMER); System.out.println(f.toText()); - if (ssa) { + EnumSet transformSet = transforms.length == 0 ? EnumSet.noneOf(LoweredModel.Transform.class) : EnumSet.copyOf(Arrays.asList(transforms)); + if (transformSet.contains(LoweredModel.Transform.NORMALIZE_BLOCKS)) { + f = NormalizeBlocksTransformer.transform(f); + System.out.println(f.toText()); + } + if (transformSet.contains(LoweredModel.Transform.SSA)) { f = SSA.transform(f); System.out.println(f.toText()); } - return f; } diff --git a/test/jdk/jdk/incubator/code/lower/LoweredModel.java b/test/jdk/jdk/incubator/code/lower/LoweredModel.java index 3c35b20871f..38cc636d73a 100644 --- a/test/jdk/jdk/incubator/code/lower/LoweredModel.java +++ b/test/jdk/jdk/incubator/code/lower/LoweredModel.java @@ -26,6 +26,8 @@ @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface LoweredModel { + enum Transform { NORMALIZE_BLOCKS, SSA } + String value(); - boolean ssa() default false; + Transform[] transform() default {}; } diff --git a/test/jdk/jdk/incubator/code/lower/TestAssert.java b/test/jdk/jdk/incubator/code/lower/TestAssert.java index b513017f310..36f73969feb 100644 --- a/test/jdk/jdk/incubator/code/lower/TestAssert.java +++ b/test/jdk/jdk/incubator/code/lower/TestAssert.java @@ -52,7 +52,7 @@ public class TestAssert { %7 : java.type:"java.lang.AssertionError" = new %6 @java.ref:"java.lang.AssertionError::(java.lang.Object)"; throw %7; }; - """, ssa = false) + """) static int test1(int i) { assert i >= 0 : "Failed"; return i; @@ -75,7 +75,7 @@ static int test1(int i) { %6 : java.type:"java.lang.AssertionError" = new @java.ref:"java.lang.AssertionError::()"; throw %6; }; - """, ssa = false) + """) static int test2(int i) { assert i >= 0; return i; diff --git a/test/jdk/jdk/incubator/code/lower/TestLoop.java b/test/jdk/jdk/incubator/code/lower/TestLoop.java index 113fa06215b..7a2f0466415 100644 --- a/test/jdk/jdk/incubator/code/lower/TestLoop.java +++ b/test/jdk/jdk/incubator/code/lower/TestLoop.java @@ -70,7 +70,7 @@ public class TestLoop { %18 : java.type:"int" = var.load %3; return %18; }; - """, ssa = false) + """) static int testFor(int[] a) { int sum = 0; for (int i = 0; i < a.length; i++) { @@ -104,7 +104,7 @@ static int testFor(int[] a) { ^block_4: return %4; }; - """, ssa = true) + """, transform = LoweredModel.Transform.SSA) static int testForSSA(int[] a) { int sum = 0; for (int i = 0; i < a.length; i++) { @@ -150,7 +150,7 @@ static int testForSSA(int[] a) { ^block_8: return; }; - """, ssa = false) + """) static void testLoopWithSwitchAndContinue(boolean repeat, int value) { while (repeat) { switch (value) { diff --git a/test/jdk/jdk/incubator/code/lower/TestPattern.java b/test/jdk/jdk/incubator/code/lower/TestPattern.java new file mode 100644 index 00000000000..7fbd62567ad --- /dev/null +++ b/test/jdk/jdk/incubator/code/lower/TestPattern.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @modules jdk.incubator.code + * @summary test lowering of synchronized blocks + * @build TestPattern + * @build CodeReflectionTester + * @run main CodeReflectionTester TestPattern + */ + +import jdk.incubator.code.Reflect; + +public class TestPattern { + + @Reflect + @LoweredModel(value = """ + func @"match" (%0 : java.type:"java.lang.Object")java.type:"void" -> { + %1 : Var = var %0 @"o"; + %2 : java.type:"java.lang.String" = constant @null; + %3 : Var = var %2 @"s"; + %4 : java.type:"java.lang.Object" = var.load %1; + %5 : java.type:"boolean" = constant @false; + %6 : java.type:"boolean" = instanceof %4 @java.type:"java.lang.String"; + cbranch %6 ^block_1 ^block_2(%5); + + ^block_1: + %7 : java.type:"java.lang.String" = cast %4 @java.type:"java.lang.String"; + var.store %3 %7; + %8 : java.type:"boolean" = constant @true; + branch ^block_2(%8); + + ^block_2(%9 : java.type:"boolean"): + cbranch %9 ^block_3 ^block_4; + + ^block_3: + %10 : java.type:"java.lang.String" = var.load %3; + invoke %10 @java.ref:"java.lang.IO::println(java.lang.Object):void"; + branch ^block_5; + + ^block_4: + branch ^block_5; + + ^block_5: + return; + }; + """) + static void match(Object o) { + if (o instanceof String s) { + IO.println(s); + } + } + + + @Reflect + @LoweredModel(value = """ + func @"match2" (%0 : java.type:"java.lang.Object")java.type:"void" -> { + %1 : java.type:"java.lang.String" = constant @null; + %2 : java.type:"boolean" = constant @false; + %3 : java.type:"boolean" = instanceof %0 @java.type:"java.lang.String"; + cbranch %3 ^block_1 ^block_2; + + ^block_1: + %4 : java.type:"java.lang.String" = cast %0 @java.type:"java.lang.String"; + %5 : java.type:"boolean" = constant @true; + invoke %4 @java.ref:"java.lang.IO::println(java.lang.Object):void"; + branch ^block_3; + + ^block_2: + branch ^block_3; + + ^block_3: + return; + }; + """, transform = { LoweredModel.Transform.NORMALIZE_BLOCKS, LoweredModel.Transform.SSA}) + static void match2(Object o) { + if (o instanceof String s) { + IO.println(s); + } + } +} diff --git a/test/jdk/jdk/incubator/code/lower/TestSynchronized.java b/test/jdk/jdk/incubator/code/lower/TestSynchronized.java index 73da420c2fe..6adbc47cf0d 100644 --- a/test/jdk/jdk/incubator/code/lower/TestSynchronized.java +++ b/test/jdk/jdk/incubator/code/lower/TestSynchronized.java @@ -70,7 +70,7 @@ public class TestSynchronized { ^block_6: throw %11; }; - """, ssa = false) + """) static int test1(Object m, int i) { synchronized (m) { i++; @@ -132,7 +132,7 @@ static int test1(Object m, int i) { ^block_10: throw %15; }; - """, ssa = false) + """) static int test2(Object m, int i) { synchronized (m) { if (i > 0) { diff --git a/test/jdk/jdk/incubator/code/lower/TestUninitializedVariable.java b/test/jdk/jdk/incubator/code/lower/TestUninitializedVariable.java new file mode 100644 index 00000000000..dbb65d79266 --- /dev/null +++ b/test/jdk/jdk/incubator/code/lower/TestUninitializedVariable.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @modules jdk.incubator.code + * @summary test lowering of synchronized blocks + * @build TestUninitializedVariable + * @build CodeReflectionTester + * @run main CodeReflectionTester TestUninitializedVariable + */ + +import jdk.incubator.code.Reflect; + +public class TestUninitializedVariable { + + @Reflect + @LoweredModel(value = """ + func @"definitiveAssignment1" (%0 : java.type:"int")java.type:"int" -> { + %1 : Var = var %0 @"i"; + %2 : Var = var @"assigned"; + %3 : java.type:"boolean" = constant @false; + %4 : java.type:"int" = var.load %1; + %5 : java.type:"int" = constant @0; + %6 : java.type:"boolean" = gt %4 %5; + cbranch %6 ^block_1 ^block_2(%3); + + ^block_1: + %7 : java.type:"int" = var.load %1; + var.store %2 %7; + %8 : java.type:"int" = constant @1; + %9 : java.type:"boolean" = gt %7 %8; + branch ^block_2(%9); + + ^block_2(%10 : java.type:"boolean"): + cbranch %10 ^block_3 ^block_4; + + ^block_3: + %11 : java.type:"int" = var.load %2; + return %11; + + ^block_4: + branch ^block_5; + + ^block_5: + %12 : java.type:"int" = constant @-1; + return %12; + }; + """) + static int definitiveAssignment1(int i) { + int assigned; + if (i > 0 && (assigned = i) > 1) { + return assigned; + } + return -1; + } + + + @Reflect + @LoweredModel(value = """ + func @"definitiveAssignment2" (%0 : java.type:"int")java.type:"int" -> { + %1 : java.type:"boolean" = constant @false; + %2 : java.type:"int" = constant @0; + %3 : java.type:"boolean" = gt %0 %2; + cbranch %3 ^block_1 ^block_4; + + ^block_1: + %4 : java.type:"int" = constant @1; + %5 : java.type:"boolean" = gt %0 %4; + branch ^block_2(%5); + + ^block_2(%6 : java.type:"boolean"): + cbranch %6 ^block_3 ^block_4; + + ^block_3: + return %0; + + ^block_4: + %7 : java.type:"int" = constant @-1; + return %7; + }; + """, transform = { LoweredModel.Transform.NORMALIZE_BLOCKS, LoweredModel.Transform.SSA}) + static int definitiveAssignment2(int i) { + int assigned; + if (i > 0 && (assigned = i) > 1) { + return assigned; + } + return -1; + } +}