Skip to content

Commit 8c9aff2

Browse files
committed
[GR-67848] Significantly reduce inlined field allocations for transitive node object inlining.
PullRequest: graal/21582
2 parents b905519 + bb5ce0c commit 8c9aff2

File tree

3 files changed

+194
-56
lines changed

3 files changed

+194
-56
lines changed

truffle/src/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/test/GenerateInlineTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
import static org.junit.Assert.assertEquals;
4444
import static org.junit.Assert.assertFalse;
45+
import static org.junit.Assert.assertNotNull;
4546
import static org.junit.Assert.assertNull;
4647
import static org.junit.Assert.assertSame;
4748
import static org.junit.Assert.assertTrue;
@@ -65,6 +66,7 @@
6566
import com.oracle.truffle.api.dsl.GenerateInline;
6667
import com.oracle.truffle.api.dsl.GeneratePackagePrivate;
6768
import com.oracle.truffle.api.dsl.GenerateUncached;
69+
import com.oracle.truffle.api.dsl.InlineSupport;
6870
import com.oracle.truffle.api.dsl.InlineSupport.InlineTarget;
6971
import com.oracle.truffle.api.dsl.InlineSupport.RequiredField;
7072
import com.oracle.truffle.api.dsl.InlineSupport.StateField;
@@ -93,6 +95,7 @@
9395
import com.oracle.truffle.api.dsl.test.GenerateInlineTestFactory.SharedAndNonSharedInlinedMultipleInstances2NodeGen;
9496
import com.oracle.truffle.api.dsl.test.GenerateInlineTestFactory.SharedProfileInSpecializationClassNodeGen;
9597
import com.oracle.truffle.api.dsl.test.GenerateInlineTestFactory.SpecializationClassAndInlinedNodeGen;
98+
import com.oracle.truffle.api.dsl.test.GenerateInlineTestFactory.TestGR67848NodeGen;
9699
import com.oracle.truffle.api.dsl.test.GenerateInlineTestFactory.Use128BitsNodeGen;
97100
import com.oracle.truffle.api.dsl.test.GenerateInlineTestFactory.Use2048BitsNodeGen;
98101
import com.oracle.truffle.api.dsl.test.GenerateInlineTestFactory.Use32BitsNodeGen;
@@ -133,6 +136,7 @@
133136
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
134137
import com.oracle.truffle.api.strings.TruffleString.CompactionLevel;
135138
import com.oracle.truffle.api.test.polyglot.AbstractPolyglotTest;
139+
import com.oracle.truffle.tck.tests.TruffleTestAssumptions;
136140

137141
@SuppressWarnings({"truffle-neverdefault", "truffle-sharing", "truffle-interpreted-performance"})
138142
public class GenerateInlineTest extends AbstractPolyglotTest {
@@ -2639,4 +2643,70 @@ private static void testInlineByDefaultCachedUser(UseInlinedByDefaultUser userNo
26392643
}
26402644
assertTrue(String.format("Node %s did not throw when it used wrong inlineTarget. Is the UseInlinedByDefault really inlined?", testCaseName), thrown);
26412645
}
2646+
2647+
@GenerateCached(false)
2648+
@GenerateInline(true)
2649+
public abstract static class TestGR67848 extends Node {
2650+
2651+
abstract Object execute(Node node, int arg);
2652+
2653+
@SuppressWarnings("unused")
2654+
// we use an inline cache to trigger a specialization data class
2655+
@Specialization(guards = "arg == cachedArg", limit = "5")
2656+
static String doInt(Node node, int arg,
2657+
@Cached("arg") int cachedArg,
2658+
@Cached InlinedBranchProfile inlinedProfile,
2659+
@Cached InlineInlineCache inlineInlineCache) {
2660+
return null;
2661+
}
2662+
}
2663+
2664+
@Test
2665+
public void testGR67848() {
2666+
// needs reflection, no difference to HotSpot expected
2667+
TruffleTestAssumptions.assumeNotAOT();
2668+
TruffleTestAssumptions.assumeNoIsolateEncapsulation();
2669+
2670+
Class<?> inlinedClass = null;
2671+
for (Class<?> c : TestGR67848NodeGen.class.getDeclaredClasses()) {
2672+
if (c.getSimpleName().equals("Inlined")) {
2673+
inlinedClass = c;
2674+
break;
2675+
}
2676+
}
2677+
assertNotNull(inlinedClass);
2678+
2679+
int referenceFieldCount = 0;
2680+
int stateFieldCount = 0;
2681+
int allFieldCount = 0;
2682+
for (Field m : inlinedClass.getDeclaredFields()) {
2683+
if (Modifier.isStatic(m.getModifiers())) {
2684+
continue;
2685+
}
2686+
allFieldCount++;
2687+
2688+
if (m.getType().isAssignableFrom(InlineSupport.ReferenceField.class)) {
2689+
referenceFieldCount++;
2690+
}
2691+
if (m.getType().isAssignableFrom(InlineSupport.StateField.class)) {
2692+
stateFieldCount++;
2693+
}
2694+
/*
2695+
* Without the fix for GR-67848 we would have a field for an InlinedBranchProfile and
2696+
* InlineInlineCache as well.
2697+
*/
2698+
if (m.getType().isAssignableFrom(InlinedBranchProfile.class)) {
2699+
throw new AssertionError("No InlinedBranchProfile field expected");
2700+
}
2701+
if (m.getType().isAssignableFrom(InlineInlineCache.class)) {
2702+
throw new AssertionError("No InlineInlineCache field expected");
2703+
}
2704+
}
2705+
2706+
assertEquals(1, referenceFieldCount);
2707+
assertEquals(1, stateFieldCount);
2708+
assertEquals(2, allFieldCount);
2709+
2710+
}
2711+
26422712
}

truffle/src/com.oracle.truffle.api.dsl/src/com/oracle/truffle/api/dsl/InlineSupport.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,9 +592,10 @@ public boolean compareAndSet(Node node, T expect, T update) {
592592
*
593593
* @since 23.0
594594
*/
595-
public static <T> ReferenceField<T> create(Lookup declaringLookup, String field, Class<T> valueClass) {
595+
@SuppressWarnings({"cast", "rawtypes", "unchecked"})
596+
public static <T> ReferenceField<T> create(Lookup declaringLookup, String field, Class<? super T> valueClass) {
596597
Class<?> lookupClass = declaringLookup.lookupClass();
597-
return new ReferenceField<>(lookupClass, lookupClass, declaringLookup, field, valueClass);
598+
return (ReferenceField<T>) new ReferenceField(lookupClass, lookupClass, declaringLookup, field, valueClass);
598599
}
599600
}
600601

truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/generator/FlatNodeGenFactory.java

Lines changed: 121 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,7 +1096,7 @@ public CodeTypeElement create(CodeTypeElement clazz) {
10961096
continue;
10971097
}
10981098

1099-
inlined.add(createCacheInlinedField(builder, null, null, cache));
1099+
inlined.addOptional(createCacheInlinedField(builder, null, null, cache));
11001100
}
11011101
}
11021102

@@ -1118,8 +1118,7 @@ public CodeTypeElement create(CodeTypeElement clazz) {
11181118
continue;
11191119
}
11201120

1121-
inlined.add(createCacheInlinedField(builder, specialization, specializationState, cache));
1122-
1121+
inlined.addOptional(createCacheInlinedField(builder, specialization, specializationState, cache));
11231122
}
11241123
}
11251124

@@ -1254,79 +1253,145 @@ private CodeVariableElement createCacheInlinedField(CodeTreeBuilder init, Specia
12541253
final String fieldName = createLocalCachedInlinedName(specialization, cache);
12551254

12561255
// for state access we need use the shared cache
1257-
CacheExpression sharedCache = lookupSharedCacheKey(cache);
1258-
1259-
init.startStatement();
1260-
init.string("this.", fieldName, " = ");
1256+
boolean needsInlineTarget = needsInlineTarget(specialization, cache);
12611257

1262-
init.startStaticCall(cache.getInlinedNode().getMethod());
1263-
init.startStaticCall(types().InlineSupport_InlineTarget, "create");
1264-
init.typeLiteral(cache.getParameter().getType());
1265-
1266-
boolean parentAccess = hasCacheParentAccess(cache);
1258+
CodeTreeBuilder b = init.create();
1259+
b.startStaticCall(cache.getInlinedNode().getMethod());
1260+
b.startStaticCall(types().InlineSupport_InlineTarget, "create");
1261+
b.typeLiteral(cache.getParameter().getType());
12671262

1263+
CacheExpression sharedCache = lookupSharedCacheKey(cache);
12681264
for (InlineFieldData field : sharedCache.getInlinedNode().getFields()) {
12691265
if (field.isState()) {
12701266
BitSet bitSet = allMultiState.findSet(InlinedNodeState.class, field);
12711267

1272-
if (bitSet == null) {
1268+
if (bitSet == null) { // bitset in specialized class
12731269
bitSet = findInlinedState(specializationState, field);
1274-
1275-
CodeVariableElement var = createStateUpdaterField(specialization, specializationState, field, specializationClasses.get(specialization).getEnclosedElements());
1276-
init.startCall(var.getName(), "subUpdater");
1270+
CodeVariableElement stateVariable = createStateUpdaterField(specialization, specializationState, field, specializationClasses.get(specialization).getEnclosedElements());
12771271
BitRange range = bitSet.getStates().queryRange(StateQuery.create(InlinedNodeState.class, field));
1278-
init.string(String.valueOf(range.offset));
1279-
init.string(String.valueOf(range.length));
1280-
init.end();
12811272

1273+
CodeTreeBuilder helper = b.create();
1274+
helper.startCall(stateVariable.getName(), "subUpdater");
1275+
helper.string(String.valueOf(range.offset));
1276+
helper.string(String.valueOf(range.length));
1277+
helper.end();
1278+
1279+
/*
1280+
* If we need a target to instantiate we should create a constant for the
1281+
* individual field to avoid unnecessary allocations for each instance.
1282+
*
1283+
* If we don't need an inline target, we don't need to extract into a constant
1284+
* here as the entire cached initializer result will be stored as constant. No
1285+
* need to create constants for individual fields, which would then be more
1286+
* costly.
1287+
*/
1288+
if (needsInlineTarget) {
1289+
String updaterFieldName = stateVariable.getSimpleName().toString() + "_" + range.offset + "_" + range.length;
1290+
CodeVariableElement var = nodeConstants.updaterReferences.get(updaterFieldName);
1291+
if (var == null) {
1292+
var = new CodeVariableElement(modifiers(PRIVATE, STATIC, FINAL), stateVariable.getType(), updaterFieldName);
1293+
var.createInitBuilder().tree(helper.build());
1294+
nodeConstants.updaterReferences.put(updaterFieldName, var);
1295+
}
1296+
b.string(updaterFieldName);
1297+
} else {
1298+
b.tree(helper.build());
1299+
}
12821300
} else {
12831301
if (specializationState != null && specializationState.findSet(InlinedNodeState.class, field) != null) {
12841302
throw new AssertionError("Inlined field in specializationState and regular state at the same time.");
12851303
}
1286-
init.startGroup();
1287-
init.startCall(bitSet.getName() + "_", "subUpdater");
1304+
b.startGroup();
1305+
b.startCall(bitSet.getName() + "_", "subUpdater");
12881306
BitRange range = bitSet.getStates().queryRange(StateQuery.create(InlinedNodeState.class, field));
1289-
init.string(String.valueOf(range.offset));
1290-
init.string(String.valueOf(range.length));
1291-
init.end();
1292-
init.end();
1307+
b.string(String.valueOf(range.offset));
1308+
b.string(String.valueOf(range.length));
1309+
b.end();
1310+
b.end();
12931311
}
12941312
} else {
1295-
12961313
String inlinedFieldName = createCachedInlinedFieldName(specialization, cache, field);
1297-
if (specialization != null && useSpecializationClass(specialization)) {
1298-
if (parentAccess) {
1299-
init.startGroup();
1300-
init.string("this.", inlinedFieldName);
1301-
init.end();
1302-
} else {
1303-
init.startStaticCall(field.getFieldType(), "create");
1304-
init.startGroup();
1305-
init.tree(createLookupNodeType(createSpecializationClassReferenceType(specialization), specializationClasses.get(specialization).getEnclosedElements()));
1306-
init.end();
1307-
init.doubleQuote(inlinedFieldName);
1308-
if (field.isReference()) {
1309-
init.typeLiteral(field.getType());
1314+
if (specialization != null && useSpecializationClass(specialization) && cache.getSharedGroup() == null) {
1315+
CodeTypeElement specializationDataClass = specializationClasses.get(specialization);
1316+
CodeTreeBuilder helper = b.create();
1317+
1318+
helper.startStaticCall(field.getFieldType(), "create");
1319+
helper.startGroup();
1320+
helper.tree(createLookupNodeType(createSpecializationClassReferenceType(specialization), specializationDataClass.getEnclosedElements()));
1321+
helper.end();
1322+
helper.doubleQuote(inlinedFieldName);
1323+
if (field.isReference()) {
1324+
helper.typeLiteral(field.getType());
1325+
}
1326+
helper.end();
1327+
1328+
if (needsInlineTarget) {
1329+
String updaterFieldName = ElementUtils.createConstantName(specializationDataClass.getSimpleName().toString() + "_" + inlinedFieldName) + "_UPDATER";
1330+
CodeVariableElement var = nodeConstants.updaterReferences.get(updaterFieldName);
1331+
if (var == null) {
1332+
var = new CodeVariableElement(modifiers(PRIVATE, STATIC, FINAL), field.getFieldType(), updaterFieldName);
1333+
var.createInitBuilder().tree(helper.build());
1334+
nodeConstants.updaterReferences.put(updaterFieldName, var);
13101335
}
1311-
init.end();
1336+
b.string(updaterFieldName);
1337+
} else {
1338+
b.tree(helper.build());
13121339
}
13131340

13141341
} else {
1315-
init.string(inlinedFieldName);
1342+
b.string(inlinedFieldName);
13161343
}
13171344
}
1345+
}
1346+
b.end();
1347+
b.end();
13181348

1349+
// if the initializer does not need the target we can create a constant instead
1350+
if (needsInlineTarget) {
1351+
init.startStatement();
1352+
init.string("this.", fieldName, " = ");
1353+
init.tree(b.build());
1354+
init.end();
1355+
CodeVariableElement var = new CodeVariableElement(modifiers(PRIVATE, FINAL), parameter.getType(), fieldName);
1356+
CodeTreeBuilder builder = var.createDocBuilder();
1357+
builder.startJavadoc();
1358+
addSourceDoc(builder, specialization, cache, null);
1359+
builder.end();
1360+
return var;
1361+
} else {
1362+
String name = createStaticInlinedCacheName(specialization, cache);
1363+
CodeVariableElement var = nodeConstants.updaterReferences.get(name);
1364+
if (var == null) {
1365+
var = new CodeVariableElement(modifiers(PRIVATE, STATIC, FINAL), parameter.getType(), name);
1366+
var.createInitBuilder().tree(b.build());
1367+
nodeConstants.updaterReferences.put(name, var);
1368+
}
1369+
return null;
13191370
}
1320-
init.end();
1321-
init.end();
1322-
init.end();
1323-
CodeVariableElement var = new CodeVariableElement(modifiers(PRIVATE, FINAL), parameter.getType(), fieldName);
1324-
CodeTreeBuilder builder = var.createDocBuilder();
1325-
builder.startJavadoc();
1326-
addSourceDoc(builder, specialization, cache, null);
1327-
builder.end();
1371+
}
13281372

1329-
return var;
1373+
/**
1374+
* Returns <code>true</code> if the inlined cache requires any field from the inline target to
1375+
* get initialized, else <code>false</code>. If it does not depend on the inline target the code
1376+
* generator can typically extract the instance into a static final field instead of
1377+
* initializing it in the generated Inlined class constructor.
1378+
*/
1379+
private boolean needsInlineTarget(SpecializationData specialization, CacheExpression cache) {
1380+
if (cache.getSharedGroup() != null) {
1381+
// shared cache -> never in data class
1382+
return true;
1383+
}
1384+
for (InlineFieldData field : cache.getInlinedNode().getFields()) {
1385+
if (field.isState() && allMultiState.findSet(InlinedNodeState.class, field) == null) {
1386+
// bit set located in specialization data class, no access to inline target needed
1387+
} else if (specialization != null && useSpecializationClass(specialization)) {
1388+
// we use a data class for this specialization so the field is stored there
1389+
} else {
1390+
// by default state is stored in the parent node
1391+
return true;
1392+
}
1393+
}
1394+
return false;
13301395
}
13311396

13321397
private String createLocalCachedInlinedName(SpecializationData specialization, CacheExpression cache) {
@@ -2179,6 +2244,7 @@ private void createCachedFieldsImpl(
21792244
if (inline != null) {
21802245
Parameter parameter = cache.getParameter();
21812246
String fieldName = createStaticInlinedCacheName(specialization, cache);
2247+
21822248
ExecutableElement cacheMethod = cache.getInlinedNode().getMethod();
21832249
CodeVariableElement cachedField = new CodeVariableElement(modifiers(PRIVATE, STATIC, FINAL), parameter.getType(), fieldName);
21842250
CodeTreeBuilder builder = cachedField.createInitBuilder();
@@ -2192,12 +2258,13 @@ private void createCachedFieldsImpl(
21922258
if (generateCachedFields) {
21932259
BitSet specializationBitSet = findInlinedState(specializationState, field);
21942260
CodeVariableElement updaterField = createStateUpdaterField(specialization, specializationState, field, specializationClassElements);
2261+
BitRange range = specializationBitSet.getStates().queryRange(StateQuery.create(InlinedNodeState.class, field));
21952262
String updaterFieldName = updaterField.getName();
21962263
builder.startCall(updaterFieldName, "subUpdater");
2197-
BitRange range = specializationBitSet.getStates().queryRange(StateQuery.create(InlinedNodeState.class, field));
21982264
builder.string(String.valueOf(range.offset));
21992265
builder.string(String.valueOf(range.length));
22002266
builder.end();
2267+
22012268
}
22022269
} else {
22032270
/*
@@ -2263,7 +2330,7 @@ private void createCachedFieldsImpl(
22632330
javadoc.end();
22642331

22652332
if (generateCachedFields) {
2266-
nodeElements.add(cachedField);
2333+
nodeConstants.updaterReferences.putIfAbsent(fieldName, cachedField);
22672334
}
22682335
} else {
22692336
Parameter parameter = cache.getParameter();
@@ -2349,7 +2416,7 @@ private CodeVariableElement createStateUpdaterField(SpecializationData specializ
23492416
if (var == null) {
23502417
var = new CodeVariableElement(modifiers(PRIVATE, STATIC, FINAL), field.getFieldType(), updaterFieldName);
23512418
CodeTreeBuilder b = var.createInitBuilder();
2352-
b.startStaticCall(types().InlineSupport_StateField, "create");
2419+
b.startStaticCall(field.getFieldType(), "create");
23532420
if (nodeType == null) {
23542421
b.startStaticCall(context.getType(MethodHandles.class), "lookup").end();
23552422
} else {
@@ -7199,7 +7266,7 @@ private CodeTree createCacheAccess(FrameState frameState, SpecializationData spe
71997266
return initializeCache(frameState, specialization, cache);
72007267
}
72017268
if (cache.getInlinedNode() != null) {
7202-
if (frameState.isInlinedNode()) {
7269+
if (frameState.isInlinedNode() && needsInlineTarget(specialization, cache)) {
72037270
CodeTreeBuilder builder = CodeTreeBuilder.createBuilder();
72047271
String cacheFieldName = createLocalCachedInlinedName(specialization, cache);
72057272
builder.string("this.", cacheFieldName);

0 commit comments

Comments
 (0)