Skip to content

Commit deebe32

Browse files
author
Mourad Abbay
committedJun 3, 2024
Lower SwitchExpressionOp
Reviewed-by: psandoz
1 parent 0cafc73 commit deebe32

File tree

2 files changed

+285
-14
lines changed

2 files changed

+285
-14
lines changed
 

‎src/java.base/share/classes/java/lang/reflect/code/op/ExtendedOp.java

+168-14
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ public Block.Builder lower(Block.Builder b, OpTransformer opT) {
203203
}
204204

205205
/**
206-
* The break operation, that can model Java language continue statements with label identifiers.
206+
* The continue operation, that can model Java language continue statements with label identifiers.
207207
*/
208208
@OpFactory.OpDeclaration(JavaContinueOp.NAME)
209209
public static final class JavaContinueOp extends JavaLabelOp {
@@ -237,29 +237,28 @@ record BranchTarget(Block.Builder breakBlock, Block.Builder continueBlock) {
237237

238238
static final String BRANCH_TARGET_MAP_PROPERTY_KEY = "BRANCH_TARGET_MAP";
239239

240-
static BranchTarget getBranchTarget(CopyContext cc, Op op) {
240+
static BranchTarget getBranchTarget(CopyContext cc, CodeElement<?, ?> codeElement) {
241241
@SuppressWarnings("unchecked")
242-
Map<Op, BranchTarget> m = (Map<Op, BranchTarget>) cc.getProperty(BRANCH_TARGET_MAP_PROPERTY_KEY);
242+
Map<CodeElement<?, ?>, BranchTarget> m = (Map<CodeElement<?, ?>, BranchTarget>) cc.getProperty(BRANCH_TARGET_MAP_PROPERTY_KEY);
243243
if (m != null) {
244-
return m.get(op);
244+
return m.get(codeElement);
245245
}
246-
247246
return null;
248247
}
249248

250-
static void setBranchTarget(CopyContext cc, Op label, BranchTarget t) {
249+
static void setBranchTarget(CopyContext cc, CodeElement<?, ?> codeElement, BranchTarget t) {
251250
@SuppressWarnings("unchecked")
252-
Map<Op, BranchTarget> x = (Map<Op, BranchTarget>) cc.computePropertyIfAbsent(
251+
Map<CodeElement<?, ?>, BranchTarget> x = (Map<CodeElement<?, ?>, BranchTarget>) cc.computePropertyIfAbsent(
253252
BRANCH_TARGET_MAP_PROPERTY_KEY, k -> new HashMap<>());
254-
x.put(label, t);
253+
x.put(codeElement, t);
255254
}
256255

257256
/**
258257
* The yield operation, that can model Java language yield statements.
259258
*/
260259
@OpFactory.OpDeclaration(JavaYieldOp.NAME)
261260
public static final class JavaYieldOp extends ExtendedOp
262-
implements Op.BodyTerminating, JavaStatement {
261+
implements Op.BodyTerminating, JavaStatement, Op.Lowerable {
263262
public static final String NAME = "java.yield";
264263

265264
public JavaYieldOp(ExternalizedOp def) {
@@ -297,6 +296,40 @@ public Value yieldValue() {
297296
public TypeElement resultType() {
298297
return VOID;
299298
}
299+
300+
@Override
301+
public Block.Builder lower(Block.Builder b, OpTransformer opT) {
302+
// for now, we will use breakBlock field to indicate java.yield target block
303+
return lower(b, BranchTarget::breakBlock);
304+
}
305+
306+
Block.Builder lower(Block.Builder b, Function<BranchTarget, Block.Builder> f) {
307+
Op opt = target();
308+
BranchTarget t = getBranchTarget(b.context(), opt);
309+
if (t != null) {
310+
b.op(branch(f.apply(t).successor(b.context().getValue(yieldValue()))));
311+
} else {
312+
throw new IllegalStateException("No branch target for operation: " + opt);
313+
}
314+
return b;
315+
}
316+
317+
Op target() {
318+
return innerMostEnclosingTarget();
319+
}
320+
321+
Op innerMostEnclosingTarget() {
322+
Op op = this;
323+
Body b;
324+
do {
325+
b = op.ancestorBody();
326+
op = b.parentOp();
327+
if (op == null) {
328+
throw new IllegalStateException("No enclosing switch");
329+
}
330+
} while (!(op instanceof JavaSwitchExpressionOp));
331+
return op;
332+
}
300333
}
301334

302335
/**
@@ -757,13 +790,119 @@ public List<Body> bodies() {
757790
}
758791

759792
@Override
760-
public Block.Builder lower(Block.Builder b, OpTransformer opT) {
761-
throw new UnsupportedOperationException();
793+
public TypeElement resultType() {
794+
return resultType;
795+
}
796+
797+
private boolean haveNullCase() {
798+
/*
799+
case null is modeled like this:
800+
(%4 : T)boolean -> {
801+
%5 : java.lang.Object = constant @null;
802+
%6 : boolean = invoke %4 %5 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
803+
yield %6;
804+
}
805+
* */
806+
for (int i = 0; i < bodies().size() - 2; i+=2) {
807+
Body labelBody = bodies().get(i);
808+
if (labelBody.blocks().size() != 1) {
809+
continue; // we skip, for now
810+
}
811+
Op terminatingOp = bodies().get(i).entryBlock().terminatingOp();
812+
//@@@ when op pattern matching is ready, we can use it
813+
if (terminatingOp instanceof YieldOp yieldOp &&
814+
yieldOp.yieldValue() instanceof Op.Result opr &&
815+
opr.op() instanceof InvokeOp invokeOp &&
816+
invokeOp.invokeDescriptor().equals(MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class)) &&
817+
invokeOp.operands().stream().anyMatch(o -> o instanceof Op.Result r && r.op() instanceof ConstantOp cop && cop.value() == null)) {
818+
return true;
819+
}
820+
}
821+
return false;
762822
}
763823

764824
@Override
765-
public TypeElement resultType() {
766-
return resultType;
825+
public Block.Builder lower(Block.Builder b, OpTransformer opT) {
826+
827+
Value selectorExpression = b.context().getValue(operands().get(0));
828+
829+
if (!haveNullCase()) {
830+
Block.Builder throwBlock = b.block();
831+
throwBlock.op(_throw(
832+
throwBlock.op(_new(FunctionType.functionType(JavaType.type(NullPointerException.class))))
833+
));
834+
835+
Block.Builder continueBlock = b.block();
836+
837+
Result p = b.op(invoke(MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class),
838+
selectorExpression, b.op(constant(J_L_OBJECT, null))));
839+
b.op(conditionalBranch(p, throwBlock.successor(), continueBlock.successor()));
840+
841+
b = continueBlock;
842+
}
843+
844+
List<Block.Builder> blocks = new ArrayList<>();
845+
for (int i = 0; i < bodies().size(); i++) {
846+
Block.Builder bb = b.block();
847+
if (i == 0) {
848+
bb = b;
849+
}
850+
blocks.add(bb);
851+
}
852+
853+
Block.Builder exit;
854+
if (bodies().isEmpty()) {
855+
exit = b;
856+
} else {
857+
exit = b.block(resultType());
858+
exit.context().mapValue(result(), exit.parameters().get(0));
859+
}
860+
861+
setBranchTarget(b.context(), this, new BranchTarget(exit, null));
862+
// map expr body to nextExprBlock
863+
// this mapping will be used for lowering SwitchFallThroughOp
864+
for (int i = 1; i < bodies().size() - 2; i+=2) {
865+
setBranchTarget(b.context(), bodies().get(i), new BranchTarget(null, blocks.get(i + 2)));
866+
}
867+
868+
for (int i = 0; i < bodies().size(); i++) {
869+
boolean isLabelBody = i % 2 == 0;
870+
Block.Builder curr = blocks.get(i);
871+
if (isLabelBody) {
872+
Block.Builder expression = blocks.get(i + 1);
873+
boolean isDefaultLabel = i == blocks.size() - 2;
874+
Block.Builder nextLabel = isDefaultLabel ? null : blocks.get(i + 2);
875+
curr.transformBody(bodies().get(i), List.of(selectorExpression), opT.andThen((block, op) -> {
876+
switch (op) {
877+
case YieldOp yop -> {
878+
if (isDefaultLabel) {
879+
block.op(branch(expression.successor()));
880+
} else {
881+
block.op(conditionalBranch(
882+
block.context().getValue(yop.yieldValue()),
883+
expression.successor(),
884+
nextLabel.successor()
885+
));
886+
}
887+
}
888+
case Lowerable lop -> block = lop.lower(block);
889+
default -> block.op(op);
890+
}
891+
return block;
892+
}));
893+
} else { // expression body
894+
curr.transformBody(bodies().get(i), blocks.get(i).parameters(), opT.andThen((block, op) -> {
895+
switch (op) {
896+
case YieldOp yop -> block.op(branch(exit.successor(block.context().getValue(yop.yieldValue()))));
897+
case Lowerable lop -> block = lop.lower(block);
898+
default -> block.op(op);
899+
}
900+
return block;
901+
}));
902+
}
903+
}
904+
905+
return exit;
767906
}
768907
}
769908

@@ -773,7 +912,7 @@ public TypeElement resultType() {
773912
*/
774913
@OpFactory.OpDeclaration(JavaSwitchFallthroughOp.NAME)
775914
public static final class JavaSwitchFallthroughOp extends ExtendedOp
776-
implements Op.BodyTerminating {
915+
implements Op.BodyTerminating, Op.Lowerable {
777916
public static final String NAME = "java.switch.fallthrough";
778917

779918
public JavaSwitchFallthroughOp(ExternalizedOp def) {
@@ -797,6 +936,21 @@ public JavaSwitchFallthroughOp transform(CopyContext cc, OpTransformer ot) {
797936
public TypeElement resultType() {
798937
return VOID;
799938
}
939+
940+
@Override
941+
public Block.Builder lower(Block.Builder b, OpTransformer opT) {
942+
return lower(b, BranchTarget::continueBlock);
943+
}
944+
945+
Block.Builder lower(Block.Builder b, Function<BranchTarget, Block.Builder> f) {
946+
BranchTarget t = getBranchTarget(b.context(), parentBlock().parentBody());
947+
if (t != null) {
948+
b.op(branch(f.apply(t).successor()));
949+
} else {
950+
throw new IllegalStateException("No branch target for operation: " + this);
951+
}
952+
return b;
953+
}
800954
}
801955

802956
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import org.testng.Assert;
2+
import org.testng.annotations.Test;
3+
4+
import java.lang.reflect.Method;
5+
import java.lang.reflect.code.Op;
6+
import java.lang.reflect.code.interpreter.Interpreter;
7+
import java.lang.reflect.code.op.CoreOp;
8+
import java.lang.runtime.CodeReflection;
9+
import java.util.Optional;
10+
import java.util.stream.Stream;
11+
12+
/*
13+
* @test
14+
* @run testng TestSwitchExpressionOp
15+
*/
16+
public class TestSwitchExpressionOp {
17+
18+
// TODO more testing
19+
// cover cases where MatchException will be thrown
20+
21+
@CodeReflection
22+
public static Object f1(String r) {
23+
return switch (r) {
24+
case "FOO" -> "FOO";
25+
case "BAR" -> "FOO";
26+
case "BAZ" -> "FOO";
27+
default -> "";
28+
};
29+
}
30+
31+
@Test
32+
public void test1() {
33+
CoreOp.FuncOp lf = lower("f1");
34+
35+
Assert.assertEquals(Interpreter.invoke(lf, "FOO"), f1("FOO"));
36+
Assert.assertEquals(Interpreter.invoke(lf, "BAR"), f1("BAR"));
37+
Assert.assertEquals(Interpreter.invoke(lf, "BAZ"), f1("BAZ"));
38+
Assert.assertEquals(Interpreter.invoke(lf, "ELSE"), f1("ELSE"));
39+
}
40+
41+
@CodeReflection
42+
public static Object f2(String r) { // switch expr with fallthrough
43+
return switch (r) {
44+
case "FOO" : {
45+
}
46+
case "BAR" : {
47+
yield "2";
48+
}
49+
default : yield "";
50+
};
51+
}
52+
53+
@Test
54+
public void test2() {
55+
CoreOp.FuncOp lf = lower("f2");
56+
57+
Assert.assertEquals(Interpreter.invoke(lf, "FOO"), f2("FOO"));
58+
Assert.assertEquals(Interpreter.invoke(lf, "BAR"), f2("BAR"));
59+
Assert.assertEquals(Interpreter.invoke(lf, "ELSE"), f2("ELSE"));
60+
}
61+
62+
@CodeReflection
63+
// null is handled, when selector expr is null the switch will complete normally
64+
private static String f3(String s) {
65+
return switch (s) {
66+
case null -> "null";
67+
default -> "default";
68+
};
69+
}
70+
71+
@Test
72+
public void test3() {
73+
CoreOp.FuncOp lf = lower("f3");
74+
75+
Assert.assertEquals(Interpreter.invoke(lf, "SOMETHING"), f3("SOMETHING"));
76+
Assert.assertEquals(Interpreter.invoke(lf, new Object[]{null}), f3(null));
77+
}
78+
79+
@CodeReflection
80+
// null not handled, when selector expr is null it will throw NPE
81+
private static String f4(String s) {
82+
return switch (s) {
83+
default -> "default";
84+
};
85+
}
86+
87+
@Test
88+
public void test4() {
89+
CoreOp.FuncOp lf = lower("f4");
90+
91+
Assert.assertEquals(Interpreter.invoke(lf, "SOMETHING"), f3("SOMETHING"));
92+
Assert.assertThrows(NullPointerException.class, () -> f4(null));
93+
Assert.assertThrows(NullPointerException.class, () -> Interpreter.invoke(lf, new Object[]{null}));
94+
}
95+
96+
static CoreOp.FuncOp getFuncOp(String name) {
97+
Optional<Method> om = Stream.of(TestSwitchExpressionOp.class.getDeclaredMethods())
98+
.filter(m -> m.getName().equals(name))
99+
.findFirst();
100+
101+
return om.get().getCodeModel().get();
102+
}
103+
104+
private static CoreOp.FuncOp lower(String methodName) {
105+
return lower(getFuncOp(methodName));
106+
}
107+
108+
private static CoreOp.FuncOp lower(CoreOp.FuncOp f) {
109+
f.writeTo(System.out);
110+
111+
CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
112+
113+
lf.writeTo(System.out);
114+
115+
return lf;
116+
}
117+
}

0 commit comments

Comments
 (0)
Please sign in to comment.