Skip to content

8351898: [lworld] javac is generating superfluous assert_unset_fields frame #1416

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

Draft
wants to merge 5 commits into
base: lworld
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 34 additions & 5 deletions src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ public class Flow {
private Env<AttrContext> attrEnv;
private Lint lint;
private final Infer infer;
private final UnsetFieldsInfo unsetFieldsInfo;
private UnsetFieldsInfo unsetFieldsInfo;

public static Flow instance(Context context) {
Flow instance = context.get(flowKey);
Expand Down Expand Up @@ -2306,7 +2306,7 @@ void letInit(JCTree tree, JCAssign assign) {
/* we are initializing a strict field inside of a constructor, we now need to find which fields
* haven't been initialized yet
*/
unsetFieldsInfo.addUnsetFieldsInfo(classDef.sym, assign != null ? assign : tree, findUninitStrictFields());
unsetFieldsInfo.addUnsetFieldsInfo(classDef.sym, sym, assign != null ? assign : tree, findUninitStrictFields());
}
}
}
Expand Down Expand Up @@ -2359,6 +2359,35 @@ protected void merge() {
* Visitor methods for statements and definitions
*************************************************************************/

@Override
public void scan(JCTree tree) {
UnsetFieldsInfo prevUnsetFieldInfo = unsetFieldsInfo;
if (isBranchy(tree)) {
unsetFieldsInfo = new UnsetFieldsInfo();
}
try {
super.scan(tree);
} finally {
if (isBranchy(tree)) {
Set<VarSymbol> unsetFields = findUninitStrictFields();
for (Symbol sym : unsetFields) {
unsetFieldsInfo.removeSymInfo(classDef.sym, sym);
}
if (!unsetFieldsInfo.isEmpty()) {
prevUnsetFieldInfo.addAll(unsetFieldsInfo);
}
unsetFieldsInfo = prevUnsetFieldInfo;
}
}
}

// where
boolean isBranchy(JCTree tree) {
return tree != null && (tree.hasTag(IF) || tree.hasTag(SWITCH) || tree.hasTag(SWITCH_EXPRESSION) ||
tree.hasTag(WHILELOOP) || tree.hasTag(FOREACHLOOP) || tree.hasTag(FORLOOP) ||
tree.hasTag(DOLOOP) || tree.hasTag(TRY) || tree.hasTag(CONDEXPR));
}

/** Analyze an expression. Make sure to set (un)inits rather than
* (un)initsWhenTrue(WhenFalse) on exit.
*/
Expand Down Expand Up @@ -2542,7 +2571,7 @@ public void visitMethodDef(JCMethodDecl tree) {
if (isConstructor) {
Set<VarSymbol> unsetFields = findUninitStrictFields();
if (unsetFields != null && !unsetFields.isEmpty()) {
unsetFieldsInfo.addUnsetFieldsInfo(classDef.sym, tree.body, unsetFields);
unsetFieldsInfo.addUnsetFieldsInfo(classDef.sym, null, tree.body, unsetFields);
}
}

Expand Down Expand Up @@ -2603,9 +2632,9 @@ public void visitMethodDef(JCMethodDecl tree) {

Set<VarSymbol> findUninitStrictFields() {
Set<VarSymbol> unsetFields = new LinkedHashSet<>();
for (int i = uninits.nextBit(0); i >= 0; i = uninits.nextBit(i + 1)) {
for (int i = 0; i < nextadr; i++) {
JCVariableDecl variableDecl = vardecls[i];
if (variableDecl.sym.isStrict()) {
if (!inits.isMember(i) && variableDecl.sym.isStrict()) {
unsetFields.add(variableDecl.sym);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@

package com.sun.tools.javac.comp;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.util.List;

import com.sun.tools.javac.code.Symbol.ClassSymbol;
Expand Down Expand Up @@ -62,26 +65,93 @@ protected UnsetFieldsInfo(Context context) {
context.put(unsetFieldsInfoKey, this);
}

private WeakHashMap<ClassSymbol, Map<JCTree, Set<VarSymbol>>> unsetFieldsMap = new WeakHashMap<>();
public UnsetFieldsInfo() {}

public void addUnsetFieldsInfo(ClassSymbol csym, JCTree tree, Set<VarSymbol> unsetFields) {
Map<JCTree, Set<VarSymbol>> treeToFieldsMap = unsetFieldsMap.get(csym);
/* this record will be the key of map unsetFieldsMap there could be cases where we need to look for the tree
* in other cases we need to look for the symbol, the symbol could be null sometimes and in other occasions will
* be the VarSymbol to the left side of an assignment statement like if we have:
* i = 1;
* the symbol will be `i` the tree will be `i = 1` there could be cases when we need to remove all assignments to
* a given symbol if at the end of an statement the associated strict field is not DA for example for code like:
*
* if (cond) {
* i = 1;
* } else {
* j = 2;
* }
*
* at the end of this `if` statement either `i` nor `j` will be DA and thus we need to remove any entry associated
* to them, remember that when we assign to a field we enter what other fields are still unassigned so for tree:
* `i = 1`
* there will be an entry for symbol `i` and the tree above indicating that field `j` is still unassigned and
* vice versa for tree `j = 2`, for the example above, both entries should be removed, and to remove them we need to
* search in the map using the corresponding VarSymbol `i` or `j` depending on the case
*/
private record SymPlusTreeKey(Symbol sym, JCTree tree) {}

private WeakHashMap<ClassSymbol, Map<SymPlusTreeKey, Set<VarSymbol>>> unsetFieldsMap = new WeakHashMap<>();
// as we use a record with two components as the key of the map above, we need a way to relate both components to be
// able to generate a key if only the tree is available for some searches
private WeakHashMap<JCTree, Symbol> treeToSymbolMap = new WeakHashMap<>();

public void addUnsetFieldsInfo(ClassSymbol csym, Symbol sym, JCTree tree, Set<VarSymbol> unsetFields) {
Map<SymPlusTreeKey, Set<VarSymbol>> treeToFieldsMap = unsetFieldsMap.get(csym);
if (treeToFieldsMap == null) {
treeToFieldsMap = new HashMap<>();
treeToFieldsMap.put(tree, unsetFields);
treeToFieldsMap.put(new SymPlusTreeKey(sym, tree), unsetFields);
unsetFieldsMap.put(csym, treeToFieldsMap);
treeToSymbolMap.put(tree, sym);
} else {
if (!treeToFieldsMap.containsKey(tree)) {
SymPlusTreeKey key = new SymPlusTreeKey(sym, tree);
if (!treeToFieldsMap.containsKey(key)) { // use treeToSymbolMap with the key instead?
// only add if there is no info for the given tree
treeToFieldsMap.put(tree, unsetFields);
treeToFieldsMap.put(key, unsetFields);
treeToSymbolMap.put(tree, sym);
}
}
}

/* removes all the info associated to a given symbol
*/
public void removeSymInfo(ClassSymbol csym, Symbol sym) {
Map<SymPlusTreeKey, Set<VarSymbol>> treeToFieldsMap = unsetFieldsMap.get(csym);
if (treeToFieldsMap != null) {
java.util.List<SymPlusTreeKey> keysToRemove = new ArrayList<>();
for (SymPlusTreeKey symtree : treeToFieldsMap.keySet()) {
if (symtree.sym() == sym) {
keysToRemove.add(symtree);
}
}
for (SymPlusTreeKey key : keysToRemove) {
treeToFieldsMap.remove(key);
treeToSymbolMap.remove(key.tree());
}
if (treeToFieldsMap.isEmpty()) {
unsetFieldsMap.remove(csym);
} else {
unsetFieldsMap.put(csym, treeToFieldsMap);
}
}
}

public void addAll(UnsetFieldsInfo unsetFieldsInfo) {
for (ClassSymbol cs : unsetFieldsInfo.unsetFieldsMap.keySet()) {
Map<SymPlusTreeKey, Set<VarSymbol>> treeSetMap = unsetFieldsInfo.unsetFieldsMap.get(cs);
for (SymPlusTreeKey symTree: treeSetMap.keySet()) {
addUnsetFieldsInfo(cs, symTree.sym(), symTree.tree(), treeSetMap.get(symTree));
}
}
}

public boolean isEmpty() {
return unsetFieldsMap.isEmpty();
}

public Set<VarSymbol> getUnsetFields(ClassSymbol csym, JCTree tree) {
Map<JCTree, Set<VarSymbol>> treeToFieldsMap = unsetFieldsMap.get(csym);
Map<SymPlusTreeKey, Set<VarSymbol>> treeToFieldsMap = unsetFieldsMap.get(csym);
if (treeToFieldsMap != null) {
Set<VarSymbol> result = treeToFieldsMap.get(tree);
Symbol sym = treeToSymbolMap.get(tree);
Set<VarSymbol> result = treeToFieldsMap.get(new SymPlusTreeKey(sym, tree));
if (result != null) {
return result;
}
Expand All @@ -90,9 +160,12 @@ public Set<VarSymbol> getUnsetFields(ClassSymbol csym, JCTree tree) {
}

public void removeUnsetFieldInfo(ClassSymbol csym, JCTree tree) {
Map<JCTree, Set<VarSymbol>> treeToFieldsMap = unsetFieldsMap.get(csym);
Map<SymPlusTreeKey, Set<VarSymbol>> treeToFieldsMap = unsetFieldsMap.get(csym);
if (treeToFieldsMap != null) {
treeToFieldsMap.remove(tree);
if (treeToFieldsMap.isEmpty()) {
unsetFieldsMap.remove(csym);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1406,6 +1406,7 @@ StackMapFrame getInitialFrame() {
frame.pc = -1;
frame.stack = null;
frame.unsetFields = initialUnsetFields;
// frame.unsetFields = cpToUnsetFieldsMap.get(frame.pc);
return frame;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,7 @@ else if (tree.body != null) {
if (meth.isConstructor()) {
code.currentUnsetFields = unsetFieldsInfo.getUnsetFields(env.enclClass.sym, tree.body);
code.initialUnsetFields = unsetFieldsInfo.getUnsetFields(env.enclClass.sym, tree.body);
// code.addUnsetFieldsAtPC(-1, code.currentUnsetFields); // starting point for the firs frame
}

try {
Expand Down
25 changes: 13 additions & 12 deletions test/jdk/jdk/classfile/StrictStackMapsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@
import static org.junit.jupiter.api.Assertions.assertThrows;

class StrictStackMapsTest {
@Test
//@Test
void basicBranchTest() throws Throwable {
var className = "Test";
var classDesc = ClassDesc.of(className);
var classBytes = ClassFile.of().build(classDesc, clb -> clb
/*var classBytes = */ClassFile.of().buildTo(java.nio.file.Paths.get("Test.class"), classDesc, clb -> clb
.withField("fs", CD_int, ACC_STRICT)
.withField("fsf", CD_int, ACC_STRICT | ACC_FINAL)
.withMethodBody(INIT_NAME, MTD_void, 0, cob -> cob
Expand All @@ -68,7 +68,7 @@ void basicBranchTest() throws Throwable {
.aload(0)
.invokespecial(CD_Object, INIT_NAME, MTD_void)
.return_()));
var clazz = ByteCodeLoader.load(className, classBytes); // sanity check to pass verification
/*var clazz = ByteCodeLoader.load(className, classBytes); // sanity check to pass verification
var classModel = ClassFile.of().parse(classBytes);
var ctorModel = classModel.methods().getFirst();
var stackMaps = ctorModel.code().orElseThrow().findAttribute(Attributes.stackMapTable()).orElseThrow();
Expand All @@ -78,10 +78,10 @@ void basicBranchTest() throws Throwable {
assertEquals(List.of(ConstantPoolBuilder.of().nameAndTypeEntry("fsf", CD_int)), elseAssertFrame.unsetFields());
var mergedAssertFrame = stackMaps.entries().get(2); // then jump to join else
assertEquals(246, mergedAssertFrame.frameType());
assertEquals(List.of(), mergedAssertFrame.unsetFields());
assertEquals(List.of(), mergedAssertFrame.unsetFields());*/
}

@Test
//@Test
void skipUnnecessaryUnsetFramesTest() throws Throwable {
var className = "Test";
var classDesc = ClassDesc.of(className);
Expand Down Expand Up @@ -116,7 +116,7 @@ void skipUnnecessaryUnsetFramesTest() throws Throwable {
assertEquals(List.of(ConstantPoolBuilder.of().nameAndTypeEntry("fsf", CD_int)), assertFrame.unsetFields());
}

@Test
//@Test
void clearUnsetAfterThisConstructorCallTest() throws Throwable {
var className = "Test";
var classDesc = ClassDesc.of(className);
Expand Down Expand Up @@ -164,9 +164,10 @@ void clearUnsetAfterThisConstructorCallTest() throws Throwable {

@Test
void allowMultiAssignTest() throws Throwable {
var className = "Test";
var className = "TestAllowMultiAssignTest";
var classDesc = ClassDesc.of(className);
var classBytes = ClassFile.of().build(classDesc, clb -> clb
System.err.println("about to generate for " + classDesc.toString());
/*var classBytes = */ClassFile.of().buildTo(java.nio.file.Paths.get("Test.class"), classDesc, clb -> clb
.withField("fs", CD_int, ACC_STRICT)
.withField("fsf", CD_int, ACC_STRICT | ACC_FINAL)
.withMethodBody(INIT_NAME, MTD_void, 0, cob -> cob
Expand All @@ -188,14 +189,14 @@ void allowMultiAssignTest() throws Throwable {
.aload(0)
.invokespecial(CD_Object, INIT_NAME, MTD_void)
.return_()));
var clazz = ByteCodeLoader.load(className, classBytes); // sanity check to pass verification
/*var clazz = ByteCodeLoader.load(className, classBytes); // sanity check to pass verification
var classModel = ClassFile.of().parse(classBytes);
var ctorModel = classModel.methods().getFirst();
var stackMaps = ctorModel.code().orElseThrow().findAttribute(Attributes.stackMapTable()).orElseThrow();
assertEquals(2, stackMaps.entries().size(), () -> stackMaps.entries().toString()); // no assert frames
assertEquals(2, stackMaps.entries().size(), () -> stackMaps.entries().toString()); // no assert frames*/
}

@Test
//@Test
void failOnUnsetNotClearTest() throws Throwable {
var className = "Test";
var classDesc = ClassDesc.of(className);
Expand All @@ -215,7 +216,7 @@ void failOnUnsetNotClearTest() throws Throwable {
.return_())));
}

@Test
//@Test
void basicTransformToStrictTest() throws Throwable {
var className = "Test";
var classDesc = ClassDesc.of(className);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1521,7 +1521,7 @@ class Test {
Code_attribute code = (Code_attribute)method.attributes.get("Code");
StackMapTable_attribute stackMapTable = (StackMapTable_attribute)code.attributes.get("StackMapTable");
int entryIndex = 0;
Assert.check(data.expectedFrameTypes().length == stackMapTable.entries.length, "unexpected stackmap length");
Assert.check(data.expectedFrameTypes().length == stackMapTable.entries.length, "unexpected stackmap length got " + stackMapTable.entries.length + " expected " + data.expectedFrameTypes().length);
int expectedUnsetFieldsIndex = 0;
for (StackMapTable_attribute.stack_map_entry entry : stackMapTable.entries) {
Assert.check(data.expectedFrameTypes()[entryIndex++] == entry.entry_type, "expected " + data.expectedFrameTypes()[entryIndex - 1] + " found " + entry.entry_type);
Expand Down