-
Notifications
You must be signed in to change notification settings - Fork 27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
8337158: Modeling and lowering of switch statement #211
Changes from 1 commit
07a2f3e
a10c8e9
d96e292
193e06d
ff5f5ed
d998905
288bfb1
0e98314
1db7b4d
ea945b0
b9cc325
2bba4d4
28367d2
d788513
5d6bd43
be050a2
5af32cc
02c1b3d
cc085ca
90698d0
a800fd8
c3c7028
136d9ad
19d73c2
abce9b3
0d13a6e
28fba34
63d71d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -449,7 +449,7 @@ class BodyScanner extends FilterScanner { | |
// unsupported tree nodes | ||
private static final EnumSet<JCTree.Tag> UNSUPPORTED_TAGS = EnumSet.of( | ||
// statements | ||
Tag.SWITCH, Tag.SYNCHRONIZED, | ||
Tag.SYNCHRONIZED, | ||
|
||
// the nodes below are not as relevant, either because they have already | ||
// been handled by an earlier compiler pass, or because they are typically | ||
|
@@ -1672,6 +1672,180 @@ public void visitSwitchExpression(JCTree.JCSwitchExpression tree) { | |
result = append(ExtendedOp.switchExpression(actionType.returnType(), target, bodies)); | ||
} | ||
|
||
@Override | ||
public void visitSwitch(JCTree.JCSwitch tree) { | ||
Value target = toValue(tree.selector); | ||
|
||
FunctionType caseLabelType = FunctionType.functionType(JavaType.BOOLEAN, target.type()); | ||
FunctionType actionType = FunctionType.VOID; | ||
List<Body.Builder> bodies = new ArrayList<>(); | ||
for (JCTree.JCCase c : tree.cases) { | ||
// Labels body | ||
JCTree.JCCaseLabel headCl = c.labels.head; | ||
if (headCl instanceof JCTree.JCPatternCaseLabel pcl) { | ||
if (c.labels.size() > 1) { | ||
mabbay marked this conversation as resolved.
Show resolved
Hide resolved
|
||
throw unsupported(c); | ||
} | ||
|
||
pushBody(pcl, caseLabelType); | ||
|
||
Value localTarget = stack.block.parameters().get(0); | ||
final Value localResult; | ||
if (c.guard != null) { | ||
List<Body.Builder> clBodies = new ArrayList<>(); | ||
|
||
pushBody(pcl.pat, FunctionType.functionType(JavaType.BOOLEAN)); | ||
Value patVal = scanPattern(pcl.pat, localTarget); | ||
append(CoreOp._yield(patVal)); | ||
clBodies.add(stack.body); | ||
popBody(); | ||
|
||
pushBody(c.guard, FunctionType.functionType(JavaType.BOOLEAN)); | ||
append(CoreOp._yield(toValue(c.guard))); | ||
clBodies.add(stack.body); | ||
popBody(); | ||
|
||
localResult = append(ExtendedOp.conditionalAnd(clBodies)); | ||
} else { | ||
localResult = scanPattern(pcl.pat, localTarget); | ||
} | ||
// Yield the boolean result of the condition | ||
append(CoreOp._yield(localResult)); | ||
bodies.add(stack.body); | ||
|
||
// Pop label | ||
popBody(); | ||
} else if (headCl instanceof JCTree.JCConstantCaseLabel ccl) { | ||
pushBody(headCl, caseLabelType); | ||
|
||
Value localTarget = stack.block.parameters().get(0); | ||
final Value localResult; | ||
if (c.labels.size() == 1) { | ||
Value expr = toValue(ccl.expr); | ||
// per java spec, constant type is compatible with the type of the selector expression | ||
// so, we convert constant to the type of the selector expression | ||
expr = convert(expr, tree.selector.type); | ||
if (tree.selector.type.isPrimitive()) { | ||
localResult = append(CoreOp.eq(localTarget, expr)); | ||
} else { | ||
localResult = append(CoreOp.invoke( | ||
MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class), | ||
localTarget, expr)); | ||
} | ||
} else { | ||
List<Body.Builder> clBodies = new ArrayList<>(); | ||
for (JCTree.JCCaseLabel cl : c.labels) { | ||
ccl = (JCTree.JCConstantCaseLabel) cl; | ||
pushBody(ccl, FunctionType.functionType(JavaType.BOOLEAN)); | ||
|
||
Value expr = toValue(ccl.expr); | ||
expr = convert(expr, tree.selector.type); | ||
final Value labelResult; | ||
if (tree.selector.type.isPrimitive()) { | ||
labelResult = append(CoreOp.eq(localTarget, expr)); | ||
} else { | ||
labelResult = append(CoreOp.invoke( | ||
MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class), | ||
localTarget, expr)); | ||
} | ||
|
||
append(CoreOp._yield(labelResult)); | ||
clBodies.add(stack.body); | ||
|
||
// Pop label | ||
popBody(); | ||
} | ||
|
||
localResult = append(ExtendedOp.conditionalOr(clBodies)); | ||
} | ||
|
||
append(CoreOp._yield(localResult)); | ||
bodies.add(stack.body); | ||
|
||
// Pop labels | ||
popBody(); | ||
} else if (headCl instanceof JCTree.JCDefaultCaseLabel) { | ||
// @@@ Do we need to model the default label body? | ||
pushBody(headCl, FunctionType.VOID); | ||
|
||
append(CoreOp._yield()); | ||
bodies.add(stack.body); | ||
|
||
// Pop label | ||
popBody(); | ||
} else { | ||
throw unsupported(tree); | ||
} | ||
|
||
// Statements body | ||
switch (c.caseKind) { | ||
case RULE -> { | ||
pushBody(c.body, actionType); | ||
if (c.body instanceof JCTree.JCBlock b) { | ||
toValue(b); | ||
if (!(b.stats.last() instanceof JCTree.JCBreak)) { | ||
append(CoreOp._yield()); // @@@ _break is also an option | ||
} | ||
} | ||
else if (c.body instanceof JCTree.JCStatement s) { | ||
toValue(s); | ||
if (!(s instanceof JCTree.JCThrow)) { | ||
append(CoreOp._yield()); | ||
} | ||
} | ||
bodies.add(stack.body); | ||
|
||
// Pop block | ||
popBody(); | ||
} | ||
case STATEMENT -> { | ||
// @@@ Avoid nesting for a single block? Goes against "say what you see" | ||
// boolean oneBlock = c.stats.size() == 1 && c.stats.head instanceof JCBlock; | ||
pushBody(c, actionType); | ||
|
||
scan(c.stats); | ||
|
||
appendTerminating(c.completesNormally ? | ||
headCl instanceof JCTree.JCDefaultCaseLabel ? CoreOp::_yield : ExtendedOp::switchFallthroughOp | ||
: CoreOp::unreachable); | ||
|
||
bodies.add(stack.body); | ||
|
||
// Pop block | ||
popBody(); | ||
} | ||
}; | ||
} | ||
|
||
// if enhanced switch and no default label | ||
// enhanced: target type not byte, char short, int, String, or, has case pattern or case null | ||
boolean enhancedSw = !List.of(JavaType.BYTE, JavaType.CHAR, JavaType.SHORT, JavaType.INT, | ||
mabbay marked this conversation as resolved.
Show resolved
Hide resolved
|
||
JavaType.J_L_BYTE, JavaType.J_L_CHARACTER, JavaType.J_L_SHORT, JavaType.J_L_INTEGER, | ||
JavaType.J_L_STRING).contains(typeToTypeElement(tree.selector.type)); | ||
enhancedSw = enhancedSw && !Flags.isEnum(tree.selector.type.tsym); | ||
enhancedSw = enhancedSw || tree.patternSwitch; | ||
enhancedSw = enhancedSw || tree.cases.stream().anyMatch(c -> c.labels.stream().anyMatch(l -> { | ||
return l instanceof JCTree.JCConstantCaseLabel ccl && ccl.expr instanceof JCLiteral literal && literal.value == null; | ||
})); | ||
if (enhancedSw && !tree.hasUnconditionalPattern) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a test that generates this part of the model? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you asking if the tests cover this ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do have tests that check we only add default when switch statement is enhanced and there is no implicit/explicit default. These tests can be found in SwitchStatementTest and are named: nonEnhancedSwStatNoDefault, enhancedSwStatNoDefault1, enhancedSwStatNoDefault2 and enhancedSwStatUnconditionalPattern. |
||
// label | ||
pushBody(tree, FunctionType.VOID); | ||
append(CoreOp._yield()); | ||
bodies.add(stack.body); | ||
popBody(); | ||
|
||
// statement | ||
pushBody(tree, actionType); | ||
append(CoreOp._throw( | ||
append(CoreOp._new(FunctionType.functionType(JavaType.type(MatchException.class)))) | ||
)); | ||
bodies.add(stack.body); | ||
popBody(); | ||
} | ||
|
||
result = append(ExtendedOp.switchStatement(target, bodies)); | ||
} | ||
|
||
@Override | ||
public void visitYield(JCTree.JCYield tree) { | ||
Value retVal = toValue(tree.value, bodyTarget); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method seems 90% the same as the one for switch expression, we should try to consolidate. I've left a comment on how to maybe simplify the logic to add a throwing default - let's see where we are after that's addressed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In particular, this big chunk of code:
Seems to be virtually identical for both switch expression and statement, except for one line:
Which is added more lazily in the cases of switch expressions. I'm not super sure as to what is the reason for that discrepancy. If we could somehow eliminate that, at least all this big portion of code could be shared. (and, even if the difference cannot be eliminated, perhaps a shared routine which returns the default body would enable clients to use that as they see fit).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mourad and I chatted off-line about this. My suggestion was to swiftly follow up with another PR that consolidates, since this PR contains a comprehensive set of tests to detect any bugs in such refactoring.