Skip to content
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

Closed
wants to merge 28 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
07a2f3e
Model switch statement
mabbay Aug 6, 2024
a10c8e9
Add default label when generating switch code model if enhanced switc…
mabbay Aug 8, 2024
d96e292
Lower switch statement
mabbay Aug 8, 2024
193e06d
Test switch statement model
mabbay Aug 9, 2024
ff5f5ed
Test switch statement model
mabbay Aug 9, 2024
d998905
Test switch statement model
mabbay Aug 12, 2024
288bfb1
rewrite test cases to return a result, to check its behaviour through…
mabbay Aug 12, 2024
0e98314
Test switch statement model
mabbay Aug 12, 2024
1db7b4d
Test switch statement model when case is pattern
mabbay Aug 13, 2024
ea945b0
Runtime test of switch statement
mabbay Aug 14, 2024
b9cc325
Runtime test of switch statement
mabbay Aug 14, 2024
2bba4d4
Revert unnecessary changes
mabbay Aug 16, 2024
28367d2
Merge branch 'code-reflection' into 8337158
mabbay Aug 16, 2024
d788513
Revert changes lost during merge
mabbay Aug 16, 2024
5d6bd43
Runtime tests of switch statement
mabbay Aug 16, 2024
be050a2
Add a test case of switch statement model
mabbay Aug 16, 2024
5af32cc
Simplify logic of when to add a default case to the switch statement …
mabbay Aug 18, 2024
02c1b3d
Remove outdated comments
mabbay Aug 19, 2024
cc085ca
Refactor
mabbay Aug 19, 2024
90698d0
Add a comment about the concat of a string with a primitive value
mabbay Aug 19, 2024
a800fd8
Update the expected model due to the change in modeling of string concat
mabbay Aug 19, 2024
c3c7028
Runtime tests of switch statement
mabbay Aug 19, 2024
136d9ad
Runtime tests of switch statement
mabbay Aug 19, 2024
19d73c2
Refactor
mabbay Aug 19, 2024
abce9b3
Runtime tests of switch statement
mabbay Aug 19, 2024
0d13a6e
Always have the case default as last bodies
mabbay Aug 19, 2024
28fba34
Correct the arguments passed in a switch expression test
mabbay Aug 19, 2024
63d71d1
Remove outdated comment
mabbay Aug 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Collaborator

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.

Copy link
Collaborator

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:

for (JCTree.JCCase c : tree.cases) {
    ...
    switch (c.caseKind) {

Seems to be virtually identical for both switch expression and statement, except for one line:

bodies.add(stack.body);

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).

Copy link
Member

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.

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) {
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,
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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test that generates this part of the model?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you asking if the tests cover this ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Copy link
Member Author

Choose a reason for hiding this comment

The 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);