Skip to content

Commit 037ad7b

Browse files
committed
[GR-67362] Implement perfect hashing for OTW interface checks.
PullRequest: graal/21404
2 parents 1cb99eb + e0ab4db commit 037ad7b

28 files changed

+900
-156
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
import jdk.graal.compiler.api.replacements.Fold;
8787
import jdk.graal.compiler.asm.amd64.AMD64Assembler;
8888
import jdk.graal.compiler.core.common.GraalOptions;
89+
import jdk.graal.compiler.core.common.NumUtil;
8990
import jdk.graal.compiler.options.Option;
9091
import jdk.graal.compiler.options.OptionKey;
9192
import jdk.graal.compiler.options.OptionStability;
@@ -1318,6 +1319,15 @@ protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Integer o
13181319
@Option(help = "The number of seconds that the isolate teardown can take before a fatal error is thrown. Disabled if less or equal to 0.")//
13191320
public static final RuntimeOptionKey<Long> TearDownFailureSeconds = new RuntimeOptionKey<>(0L, RelevantForCompilationIsolates);
13201321

1322+
/** Use {@link SubstrateOptions#useInterfaceHashing()} instead. */
1323+
@Option(help = "Enables hashing-based interface type checks and interface method dispatch. This option is only available when ClosedTypeWorldHubLayout is disabled.", type = OptionType.Debug) //
1324+
public static final HostedOptionKey<Boolean> UseInterfaceHashing = new HostedOptionKey<>(null, key -> {
1325+
if (key.hasBeenSet() && key.getValue()) {
1326+
UserError.guarantee(!useClosedTypeWorldHubLayout(), "Support for interface hashing is only available with an open world hub layout. " +
1327+
"Enable the open world hub layout with %s",
1328+
SubstrateOptionsParser.commandArgument(ClosedTypeWorldHubLayout, "-"));
1329+
}
1330+
});
13211331
}
13221332

13231333
@Option(help = "Overwrites the available number of processors provided by the OS. Any value <= 0 means using the processor count from the OS.")//
@@ -1556,6 +1566,19 @@ protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Boolean o
15561566
@Option(help = "Use the closed type world dynamic hub representation. This is only allowed when the option ClosedTypeWorld is also set to true.", type = OptionType.Expert) //
15571567
public static final HostedOptionKey<Boolean> ClosedTypeWorldHubLayout = new HostedOptionKey<>(true);
15581568

1569+
@Option(help = "Defines a threshold for interfaceIDs which can be covered by hashing if UseInterfaceHashing is enabled. Must be <= 65535 (ushort max). Larger interfaceIDs are handled by the slow path.", type = OptionType.Debug) //
1570+
public static final HostedOptionKey<Integer> InterfaceHashingMaxId = new HostedOptionKey<>(0xffff, key -> {
1571+
if (key.hasBeenSet()) {
1572+
UserError.guarantee(useInterfaceHashing(), "Interface hashing needs to be enabled for the InterfaceHashingMaxId to be used. " +
1573+
"Enable interface hashing with %s",
1574+
SubstrateOptionsParser.commandArgument(ConcealedOptions.UseInterfaceHashing, "+"));
1575+
}
1576+
int value = key.getValue();
1577+
if (!NumUtil.isUShort(value)) {
1578+
UserError.invalidOptionValue(key, value, "The specified value must be <= 65535 (ushort max)");
1579+
}
1580+
});
1581+
15591582
@Fold
15601583
public static boolean useClosedTypeWorldHubLayout() {
15611584
return ClosedTypeWorldHubLayout.getValue();
@@ -1566,6 +1589,20 @@ public static boolean useClosedTypeWorld() {
15661589
return ClosedTypeWorld.getValue();
15671590
}
15681591

1592+
@Fold
1593+
public static boolean useInterfaceHashing() {
1594+
if (ConcealedOptions.UseInterfaceHashing.getValue() != null) {
1595+
return ConcealedOptions.UseInterfaceHashing.getValue();
1596+
}
1597+
// TODO Include G1 after [GR-69090] is merged
1598+
return !useClosedTypeWorldHubLayout() && !useG1GC();
1599+
}
1600+
1601+
@Fold
1602+
public static int interfaceHashingMaxId() {
1603+
return InterfaceHashingMaxId.getValue();
1604+
}
1605+
15691606
@Option(help = "Throws an exception on potential type conflict during heap persisting if enabled", type = OptionType.Debug) //
15701607
public static final HostedOptionKey<Boolean> AbortOnNameConflict = new HostedOptionKey<>(false);
15711608

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/DynamicHubOffsets.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,17 @@ public class DynamicHubOffsets {
6565
private int numClassTypesOffset = UNINITIALIZED;
6666

6767
@UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) //
68-
private int numInterfaceTypesOffset = UNINITIALIZED;
68+
private int numIterableInterfaceTypesOffset = UNINITIALIZED;
69+
@UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) //
70+
private int interfaceIDOffset = UNINITIALIZED;
6971
@UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) //
7072
private int openTypeWorldTypeCheckSlotsOffset = UNINITIALIZED;
7173

74+
@UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) //
75+
private int openTypeWorldInterfaceHashParamOffset = UNINITIALIZED;
76+
@UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) //
77+
private int openTypeWorldInterfaceHashTableOffset = UNINITIALIZED;
78+
7279
@UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) //
7380
private int monitorOffsetOffset = UNINITIALIZED;
7481
@UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) //
@@ -161,14 +168,26 @@ public int getNumClassTypesOffset() {
161168
return numClassTypesOffset;
162169
}
163170

164-
public int getNumInterfaceTypesOffset() {
165-
return numInterfaceTypesOffset;
171+
public int getNumIterableInterfaceTypesOffset() {
172+
return numIterableInterfaceTypesOffset;
173+
}
174+
175+
public int getInterfaceIDOffset() {
176+
return interfaceIDOffset;
166177
}
167178

168179
public int getOpenTypeWorldTypeCheckSlotsOffset() {
169180
return openTypeWorldTypeCheckSlotsOffset;
170181
}
171182

183+
public int getOpenTypeWorldInterfaceHashParamOffset() {
184+
return openTypeWorldInterfaceHashParamOffset;
185+
}
186+
187+
public int getOpenTypeWorldInterfaceHashTableOffset() {
188+
return openTypeWorldInterfaceHashTableOffset;
189+
}
190+
172191
public int getMonitorOffsetOffset() {
173192
return monitorOffsetOffset;
174193
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/nodes/LoadMethodByIndexNode.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ public final class LoadMethodByIndexNode extends FixedWithNextNode implements Lo
4343

4444
@Input protected ValueNode hub;
4545
@Input protected ValueNode vtableIndex;
46-
@OptionalInput protected ValueNode interfaceTypeID;
46+
@OptionalInput protected ValueNode interfaceID;
4747

48-
protected LoadMethodByIndexNode(@InjectedNodeParameter WordTypes wordTypes, ValueNode hub, ValueNode vtableIndex, ValueNode interfaceTypeID) {
48+
protected LoadMethodByIndexNode(@InjectedNodeParameter WordTypes wordTypes, ValueNode hub, ValueNode vtableIndex, ValueNode interfaceID) {
4949
super(TYPE, StampFactory.forKind(wordTypes.getWordKind()));
5050
this.hub = hub;
5151
this.vtableIndex = vtableIndex;
52-
this.interfaceTypeID = interfaceTypeID;
52+
this.interfaceID = interfaceID;
5353
}
5454

5555
public ValueNode getHub() {
@@ -60,10 +60,10 @@ public ValueNode getVTableIndex() {
6060
return vtableIndex;
6161
}
6262

63-
public ValueNode getInterfaceTypeID() {
64-
return interfaceTypeID;
63+
public ValueNode getInterfaceID() {
64+
return interfaceID;
6565
}
6666

6767
@NodeIntrinsic
68-
public static native CodePointer loadMethodByIndex(Object hub, int vtableIndex, int interfaceTypeID);
68+
public static native CodePointer loadMethodByIndex(Object hub, int vtableIndex, int interfaceID);
6969
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/nodes/LoadOpenTypeWorldDispatchTableStartingOffset.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,30 +47,30 @@ public class LoadOpenTypeWorldDispatchTableStartingOffset extends FixedWithNextN
4747
public static final NodeClass<LoadOpenTypeWorldDispatchTableStartingOffset> TYPE = NodeClass.create(LoadOpenTypeWorldDispatchTableStartingOffset.class);
4848

4949
@Input protected ValueNode hub;
50-
@OptionalInput protected ValueNode interfaceTypeID;
50+
@OptionalInput protected ValueNode interfaceID;
5151

5252
protected final SharedMethod target;
5353

5454
public LoadOpenTypeWorldDispatchTableStartingOffset(ValueNode hub, SharedMethod target) {
5555
super(TYPE, StampFactory.forInteger(64));
5656
this.hub = hub;
5757
this.target = target;
58-
this.interfaceTypeID = null;
58+
this.interfaceID = null;
5959
}
6060

61-
public LoadOpenTypeWorldDispatchTableStartingOffset(ValueNode hub, ValueNode interfaceTypeID) {
61+
public LoadOpenTypeWorldDispatchTableStartingOffset(ValueNode hub, ValueNode interfaceID) {
6262
super(TYPE, StampFactory.forInteger(64));
6363
this.hub = hub;
6464
this.target = null;
65-
this.interfaceTypeID = interfaceTypeID;
65+
this.interfaceID = interfaceID;
6666
}
6767

6868
public ValueNode getHub() {
6969
return hub;
7070
}
7171

72-
public ValueNode getInterfaceTypeID() {
73-
return interfaceTypeID;
72+
public ValueNode getInterfaceID() {
73+
return interfaceID;
7474
}
7575

7676
public SharedMethod getTarget() {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/NonSnippetLowerings.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ private void lowerLoadMethodNode(LoadMethodNode node, LoweringTool tool) {
553553
private void lowerLoadMethodByIndexNode(LoadMethodByIndexNode node, LoweringTool tool) {
554554
LoadOpenTypeWorldDispatchTableStartingOffset tableStartOffset = null;
555555
if (!haveClosedWorldHubLayout) {
556-
tableStartOffset = node.graph().add(new LoadOpenTypeWorldDispatchTableStartingOffset(node.getHub(), node.getInterfaceTypeID()));
556+
tableStartOffset = node.graph().add(new LoadOpenTypeWorldDispatchTableStartingOffset(node.getHub(), node.getInterfaceID()));
557557
}
558558
lowerLoadMethod(node, node.getHub(), tool, node.getVTableIndex(), tableStartOffset);
559559
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/OpenTypeWorldDispatchTableSnippets.java

Lines changed: 103 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,37 @@
2424
*/
2525
package com.oracle.svm.core.graal.snippets;
2626

27+
import static com.oracle.svm.core.hub.DynamicHubTypeCheckUtil.HASHING_INTERFACE_MASK;
28+
import static com.oracle.svm.core.hub.DynamicHubTypeCheckUtil.HASHING_ITABLE_SHIFT;
29+
import static com.oracle.svm.core.hub.DynamicHubTypeCheckUtil.HASHING_SHIFT_OFFSET;
30+
import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.probability;
2731
import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.unknownProbability;
2832

2933
import java.util.Map;
3034

3135
import org.graalvm.nativeimage.ImageSingletons;
3236
import org.graalvm.word.LocationIdentity;
3337

38+
import com.oracle.svm.core.SubstrateOptions;
3439
import com.oracle.svm.core.config.ObjectLayout;
3540
import com.oracle.svm.core.graal.meta.KnownOffsets;
3641
import com.oracle.svm.core.graal.nodes.LoadOpenTypeWorldDispatchTableStartingOffset;
3742
import com.oracle.svm.core.hub.DynamicHub;
43+
import com.oracle.svm.core.hub.DynamicHubTypeCheckUtil;
3844
import com.oracle.svm.core.meta.SharedMethod;
3945
import com.oracle.svm.core.meta.SharedType;
4046

4147
import jdk.graal.compiler.api.replacements.Snippet;
48+
import jdk.graal.compiler.core.common.NumUtil;
4249
import jdk.graal.compiler.graph.Node;
4350
import jdk.graal.compiler.nodes.ConstantNode;
4451
import jdk.graal.compiler.nodes.NamedLocationIdentity;
4552
import jdk.graal.compiler.nodes.UnreachableNode;
53+
import jdk.graal.compiler.nodes.extended.BranchProbabilityNode;
4654
import jdk.graal.compiler.nodes.spi.LoweringTool;
4755
import jdk.graal.compiler.options.OptionValues;
4856
import jdk.graal.compiler.phases.util.Providers;
57+
import jdk.graal.compiler.replacements.ReplacementsUtil;
4958
import jdk.graal.compiler.replacements.SnippetTemplate;
5059
import jdk.graal.compiler.replacements.Snippets;
5160
import jdk.graal.compiler.word.ObjectAccess;
@@ -56,28 +65,40 @@ public final class OpenTypeWorldDispatchTableSnippets extends SubstrateTemplates
5665
@Snippet
5766
private static long loadITableStartingOffset(
5867
@Snippet.NonNullParameter DynamicHub hub,
59-
int interfaceTypeID) {
60-
return determineITableStartingOffset(hub, interfaceTypeID);
68+
int interfaceID,
69+
@Snippet.ConstantParameter boolean useInterfaceHashing) {
70+
if (useInterfaceHashing && probability(BranchProbabilityNode.FAST_PATH_PROBABILITY, interfaceID <= SubstrateOptions.interfaceHashingMaxId())) {
71+
return determineITableStartingOffsetHashed(hub, interfaceID);
72+
}
73+
return determineITableStartingOffsetIterative(hub, interfaceID);
6174
}
6275

6376
@Snippet
6477
private static long loadDispatchTableStartingOffset(
6578
@Snippet.NonNullParameter DynamicHub hub,
66-
int interfaceTypeID, @Snippet.ConstantParameter int vtableStartingOffset) {
67-
if (unknownProbability(interfaceTypeID >= 0)) {
68-
return determineITableStartingOffset(hub, interfaceTypeID);
79+
int interfaceID, @Snippet.ConstantParameter int vtableStartingOffset,
80+
@Snippet.ConstantParameter boolean useInterfaceHashing) {
81+
if (unknownProbability(interfaceID >= 0)) {
82+
if (useInterfaceHashing && probability(BranchProbabilityNode.FAST_PATH_PROBABILITY, interfaceID <= SubstrateOptions.interfaceHashingMaxId())) {
83+
return determineITableStartingOffsetHashed(hub, interfaceID);
84+
}
85+
return determineITableStartingOffsetIterative(hub, interfaceID);
6986
} else {
7087
// the class dispatch table is always first
7188
return vtableStartingOffset;
7289
}
7390
}
7491

75-
public static long determineITableStartingOffset(
92+
/**
93+
* Iterative lookup of itable starting offsets used if
94+
* {@link SubstrateOptions#useInterfaceHashing()} is disabled or if the interfaceID exceeds
95+
* {@link SubstrateOptions#interfaceHashingMaxId()}.
96+
*/
97+
private static long determineITableStartingOffsetIterative(
7698
DynamicHub checkedHub,
7799
int interfaceID) {
78-
79100
int numClassTypes = checkedHub.getNumClassTypes();
80-
int numInterfaceTypes = checkedHub.getNumInterfaceTypes();
101+
int numInterfaceTypes = checkedHub.getNumIterableInterfaceTypes();
81102
int[] checkedTypeIds = checkedHub.getOpenTypeWorldTypeCheckSlots();
82103
for (int i = 0; i < numInterfaceTypes * 2; i += 2) {
83104
// int checkedInterfaceId = checkedTypeIds[numClassTypes + i];
@@ -94,6 +115,76 @@ public static long determineITableStartingOffset(
94115
throw UnreachableNode.unreachable();
95116
}
96117

118+
/**
119+
* If {@link SubstrateOptions#useInterfaceHashing()} is enabled, interfaceIDs and itable
120+
* starting offsets are stored in a hash table (see TypeCheckBuilder for a general
121+
* documentation). This snippet handles the lookup in the hash table and returns the itable
122+
* starting offset for the given interfaceID. See
123+
* {@link DynamicHubTypeCheckUtil#hashParam(int[])} for details on the hashing function and
124+
* hashing parameter.
125+
*/
126+
private static int determineITableStartingOffsetHashed(
127+
DynamicHub checkedHub,
128+
int interfaceID) {
129+
ReplacementsUtil.dynamicAssert(NumUtil.isUShort(interfaceID), "InterfaceIDs must fit in a short to be used for hashing.");
130+
131+
// The upper byte of the hashParam holds the shift value, the lower three bytes hold p
132+
// which is used for bitwise "and": hashParam = shift << HASHING_SHIFT_OFFSET | p.
133+
int hashParam = checkedHub.getOpenTypeWorldInterfaceHashParam();
134+
int shift = hashParam >>> HASHING_SHIFT_OFFSET;
135+
int[] hashTable = checkedHub.getOpenTypeWorldInterfaceHashTable();
136+
137+
// No need to mask hashParam to get "p". interfaceID fits in a short -> the two upper
138+
// bytes are 0.
139+
int hash = (interfaceID >>> shift) & hashParam;
140+
int offset = (int) ImageSingletons.lookup(ObjectLayout.class).getArrayElementOffset(JavaKind.Int, hash);
141+
int hashTableEntry = ObjectAccess.readInt(hashTable, offset, NamedLocationIdentity.FINAL_LOCATION);
142+
143+
// Hashtable entries contain integers which hold the iTableOffset and the interfaceID:
144+
// hashTableEntry = iTableOffset << HASHING_ITABLE_SHIFT | interfaceID
145+
ReplacementsUtil.dynamicAssert(interfaceID == (hashTableEntry & HASHING_INTERFACE_MASK), "InterfaceIDs do not match.");
146+
return (hashTableEntry >>> HASHING_ITABLE_SHIFT);
147+
}
148+
149+
public static long determineITableStartingOffset(
150+
DynamicHub checkedHub,
151+
int interfaceID) {
152+
if (SubstrateOptions.useInterfaceHashing()) {
153+
// Use the non-snippet version which contains no snippet asserts.
154+
return determineITableStartingOffsetHashedNonSnippet(checkedHub, interfaceID);
155+
} else {
156+
return determineITableStartingOffsetIterative(checkedHub, interfaceID);
157+
}
158+
}
159+
160+
/**
161+
* IMPORTANT: Has to be identical to {@link #determineITableStartingOffsetHashed} but with
162+
* "real" {@code assert}s instead of {@link ReplacementsUtil#dynamicAssert}. Required for being
163+
* called outside of snippets.
164+
*/
165+
private static int determineITableStartingOffsetHashedNonSnippet(
166+
DynamicHub checkedHub,
167+
int interfaceID) {
168+
assert NumUtil.isUShort(interfaceID) : "InterfaceIDs must fit in a short to be used for hashing.";
169+
170+
// The upper byte of the hashParam holds the shift value, the lower three bytes hold p
171+
// which is used for bitwise "and": hashParam = shift << HASHING_SHIFT_OFFSET | p.
172+
int hashParam = checkedHub.getOpenTypeWorldInterfaceHashParam();
173+
int shift = hashParam >>> HASHING_SHIFT_OFFSET;
174+
int[] hashTable = checkedHub.getOpenTypeWorldInterfaceHashTable();
175+
176+
// No need to mask hashParam to get "p". interfaceID fits in a short -> the two upper
177+
// bytes are 0.
178+
int hash = (interfaceID >>> shift) & hashParam;
179+
int offset = (int) ImageSingletons.lookup(ObjectLayout.class).getArrayElementOffset(JavaKind.Int, hash);
180+
int hashTableEntry = ObjectAccess.readInt(hashTable, offset, NamedLocationIdentity.FINAL_LOCATION);
181+
182+
// Hashtable entries contain integers which hold the iTableOffset and the interfaceID:
183+
// hashTableEntry = iTableOffset << HASHING_ITABLE_SHIFT | interfaceID
184+
assert interfaceID == (hashTableEntry & HASHING_INTERFACE_MASK) : "InterfaceIDs do not match.";
185+
return (hashTableEntry >>> HASHING_ITABLE_SHIFT);
186+
}
187+
97188
private final SnippetTemplate.SnippetInfo loadITableStartingOffset;
98189
private final SnippetTemplate.SnippetInfo loadDispatchTableStartingOffset;
99190

@@ -136,7 +227,8 @@ public void lower(LoadOpenTypeWorldDispatchTableStartingOffset node, LoweringToo
136227
if (target.getDeclaringClass().isInterface()) {
137228
SnippetTemplate.Arguments args = new SnippetTemplate.Arguments(loadITableStartingOffset, node.graph(), tool.getLoweringStage());
138229
args.add("hub", node.getHub());
139-
args.add("interfaceTypeID", ((SharedType) target.getDeclaringClass()).getTypeID());
230+
args.add("interfaceID", ((SharedType) target.getDeclaringClass()).getInterfaceID());
231+
args.add("useInterfaceHashing", SubstrateOptions.useInterfaceHashing());
140232
template(tool, node, args).instantiate(tool.getMetaAccess(), node, SnippetTemplate.DEFAULT_REPLACER, args);
141233

142234
} else {
@@ -150,8 +242,9 @@ public void lower(LoadOpenTypeWorldDispatchTableStartingOffset node, LoweringToo
150242
*/
151243
SnippetTemplate.Arguments args = new SnippetTemplate.Arguments(loadDispatchTableStartingOffset, node.graph(), tool.getLoweringStage());
152244
args.add("hub", node.getHub());
153-
args.add("interfaceTypeID", node.getInterfaceTypeID());
245+
args.add("interfaceID", node.getInterfaceID());
154246
args.add("vtableStartingOffset", vtableStartingOffset);
247+
args.add("useInterfaceHashing", SubstrateOptions.useInterfaceHashing());
155248
template(tool, node, args).instantiate(tool.getMetaAccess(), node, SnippetTemplate.DEFAULT_REPLACER, args);
156249
}
157250
}

0 commit comments

Comments
 (0)