From fc4c523ffcb9ac63c6c5cc8b8094f2c323210936 Mon Sep 17 00:00:00 2001 From: Tom Shull Date: Mon, 11 Mar 2024 16:41:54 +0100 Subject: [PATCH] Introduce open type world call dispatching. --- .../svm/core/graal/meta/KnownOffsets.java | 13 +- .../meta/SubstrateBasicLoweringProvider.java | 57 +- ...nTypeWorldDispatchTableStartingOffset.java | 82 +++ .../graal/snippets/NonSnippetLowerings.java | 56 +- .../OpenTypeWorldDispatchTableSnippets.java | 152 ++++++ .../graal/snippets/OpenTypeWorldSnippets.java | 10 +- .../com/oracle/svm/core/hub/DynamicHub.java | 40 +- .../svm/core/hub/DynamicHubSupport.java | 2 +- .../core/jni/access/JNIAccessibleMethod.java | 40 +- .../com/oracle/svm/core/meta/SharedType.java | 2 + .../core/reflect/SubstrateMethodAccessor.java | 43 +- .../oracle/svm/graal/meta/SubstrateType.java | 5 + .../svm/hosted/HostedConfiguration.java | 23 +- .../svm/hosted/NativeImageGenerator.java | 6 +- .../svm/hosted/OpenTypeWorldFeature.java | 2 +- .../svm/hosted/config/DynamicHubLayout.java | 25 +- .../hosted/image/NativeImageCodeCache.java | 7 +- .../svm/hosted/image/NativeImageHeap.java | 6 +- .../hosted/image/NativeImageHeapWriter.java | 4 +- .../svm/hosted/jni/JNIAccessFeature.java | 22 +- .../oracle/svm/hosted/meta/HostedMethod.java | 7 + .../oracle/svm/hosted/meta/HostedType.java | 66 +-- .../svm/hosted/meta/KnownOffsetsFeature.java | 2 +- .../svm/hosted/meta/TypeCheckBuilder.java | 77 +-- .../svm/hosted/meta/UniverseBuilder.java | 387 +++----------- .../oracle/svm/hosted/meta/VTableBuilder.java | 499 ++++++++++++++++++ .../svm/hosted/reflect/ReflectionFeature.java | 31 +- 27 files changed, 1165 insertions(+), 501 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/nodes/LoadOpenTypeWorldDispatchTableStartingOffset.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/OpenTypeWorldDispatchTableSnippets.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/VTableBuilder.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/KnownOffsets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/KnownOffsets.java index 9efff1860612..798117830974 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/KnownOffsets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/KnownOffsets.java @@ -78,9 +78,18 @@ private boolean isFullyInitialized() { return vtableEntrySize > 0; } - public int getVTableOffset(int vTableIndex) { + /** + * Returns of the offset of the index either relative to the start of the vtable + * ({@code fromDynamicHubStart} == false) or start of the dynamic hub + * ({@code fromDynamicHubStart} == true). + */ + public int getVTableOffset(int vTableIndex, boolean fromDynamicHubStart) { assert isFullyInitialized(); - return vtableBaseOffset + vTableIndex * vtableEntrySize; + if (fromDynamicHubStart) { + return vtableBaseOffset + vTableIndex * vtableEntrySize; + } else { + return vTableIndex * vtableEntrySize; + } } public int getTypeIDSlotsOffset() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SubstrateBasicLoweringProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SubstrateBasicLoweringProvider.java index 87039cac7749..9f0d18179c10 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SubstrateBasicLoweringProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SubstrateBasicLoweringProvider.java @@ -31,10 +31,12 @@ import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.StaticFieldsSupport; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.config.ObjectLayout; import com.oracle.svm.core.graal.code.SubstrateBackend; import com.oracle.svm.core.graal.nodes.FloatingWordCastNode; +import com.oracle.svm.core.graal.nodes.LoadOpenTypeWorldDispatchTableStartingOffset; import com.oracle.svm.core.graal.nodes.LoweredDeadEndNode; import com.oracle.svm.core.graal.nodes.SubstrateCompressionNode; import com.oracle.svm.core.graal.nodes.SubstrateFieldLocationIdentity; @@ -46,7 +48,6 @@ import com.oracle.svm.core.identityhashcode.IdentityHashCodeSupport; import com.oracle.svm.core.meta.SharedField; import com.oracle.svm.core.meta.SharedMethod; -import com.oracle.svm.core.meta.SubstrateMethodPointerStamp; import com.oracle.svm.core.snippets.SubstrateIsArraySnippets; import jdk.graal.compiler.core.common.memory.BarrierType; @@ -65,10 +66,12 @@ import jdk.graal.compiler.nodes.DeadEndNode; import jdk.graal.compiler.nodes.FieldLocationIdentity; import jdk.graal.compiler.nodes.FixedNode; +import jdk.graal.compiler.nodes.FixedWithNextNode; import jdk.graal.compiler.nodes.NamedLocationIdentity; import jdk.graal.compiler.nodes.NodeView; import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.calc.AddNode; import jdk.graal.compiler.nodes.calc.AndNode; import jdk.graal.compiler.nodes.calc.LeftShiftNode; import jdk.graal.compiler.nodes.calc.UnsignedRightShiftNode; @@ -144,29 +147,51 @@ public void lower(Node n, LoweringTool tool) { } else if (n instanceof DeadEndNode) { lowerDeadEnd((DeadEndNode) n); } else if (n instanceof LoadMethodNode) { - lowerLoadMethodNode((LoadMethodNode) n); + lowerLoadMethodNode((LoadMethodNode) n, tool); } else { super.lower(n, tool); } } - private void lowerLoadMethodNode(LoadMethodNode loadMethodNode) { + private void lowerLoadMethodNode(LoadMethodNode loadMethodNode, LoweringTool tool) { StructuredGraph graph = loadMethodNode.graph(); SharedMethod method = (SharedMethod) loadMethodNode.getMethod(); - ReadNode methodPointer = createReadVirtualMethod(graph, loadMethodNode.getHub(), method); - graph.replaceFixed(loadMethodNode, methodPointer); - } + ValueNode hub = loadMethodNode.getHub(); + + if (SubstrateOptions.closedTypeWorld()) { + + int vtableEntryOffset = knownOffsets.getVTableOffset(method.getVTableIndex(), true); + assert vtableEntryOffset > 0; + /* + * Method pointer will always exist in the vtable due to the fact that all reachable + * methods through method pointer constant references will be compiled. + */ + AddressNode address = createOffsetAddress(graph, hub, vtableEntryOffset); + ReadNode virtualMethod = graph.add(new ReadNode(address, SubstrateBackend.getVTableIdentity(), loadMethodNode.stamp(NodeView.DEFAULT), BarrierType.NONE, MemoryOrderMode.PLAIN)); + graph.replaceFixed(loadMethodNode, virtualMethod); - private ReadNode createReadVirtualMethod(StructuredGraph graph, ValueNode hub, SharedMethod method) { - int vtableEntryOffset = knownOffsets.getVTableOffset(method.getVTableIndex()); - assert vtableEntryOffset > 0; - /* - * Method pointer will always exist in the vtable due to the fact that all reachable methods - * through method pointer constant references will be compiled. - */ - Stamp methodStamp = SubstrateMethodPointerStamp.methodNonNull(); - AddressNode address = createOffsetAddress(graph, hub, vtableEntryOffset); - return graph.add(new ReadNode(address, SubstrateBackend.getVTableIdentity(), methodStamp, BarrierType.NONE, MemoryOrderMode.PLAIN)); + } else { + // First compute the dispatch table starting offset + LoadOpenTypeWorldDispatchTableStartingOffset tableStartingOffset = graph.add(new LoadOpenTypeWorldDispatchTableStartingOffset(hub, method)); + + // Add together table starting offset and index offset + ValueNode methodAddress = graph.unique( + new AddNode(tableStartingOffset, ConstantNode.forIntegerKind(ConfigurationValues.getWordKind(), knownOffsets.getVTableOffset(method.getVTableIndex(), false), graph))); + + // The load the method address for the dispatch table + AddressNode dispatchTableAddress = graph.unique(new OffsetAddressNode(hub, methodAddress)); + ReadNode virtualMethod = graph + .add(new ReadNode(dispatchTableAddress, SubstrateBackend.getVTableIdentity(), loadMethodNode.stamp(NodeView.DEFAULT), BarrierType.NONE, MemoryOrderMode.PLAIN)); + + // wire in the new nodes + FixedWithNextNode predecessor = (FixedWithNextNode) loadMethodNode.predecessor(); + predecessor.setNext(tableStartingOffset); + tableStartingOffset.setNext(virtualMethod); + graph.replaceFixed(loadMethodNode, virtualMethod); + + // Lower logic associated with loading starting offset + tableStartingOffset.lower(tool); + } } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/nodes/LoadOpenTypeWorldDispatchTableStartingOffset.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/nodes/LoadOpenTypeWorldDispatchTableStartingOffset.java new file mode 100644 index 000000000000..eccbbe13a079 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/nodes/LoadOpenTypeWorldDispatchTableStartingOffset.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.graal.nodes; + +import com.oracle.svm.core.graal.snippets.OpenTypeWorldDispatchTableSnippets; +import com.oracle.svm.core.meta.SharedMethod; + +import jdk.graal.compiler.core.common.type.StampFactory; +import jdk.graal.compiler.graph.NodeClass; +import jdk.graal.compiler.nodeinfo.NodeCycles; +import jdk.graal.compiler.nodeinfo.NodeInfo; +import jdk.graal.compiler.nodeinfo.NodeSize; +import jdk.graal.compiler.nodes.FixedWithNextNode; +import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.spi.Lowerable; + +/** + * When using the open type world, each interface a type implements has a unique dispatch table. For + * a given type, all the tables are stored together with the DynamicHubs' vtable slots. The logic + * for determining the starting offset of the dispatch table is contained in + * {@link OpenTypeWorldDispatchTableSnippets}. + */ +@NodeInfo(size = NodeSize.SIZE_UNKNOWN, cycles = NodeCycles.CYCLES_UNKNOWN) +public class LoadOpenTypeWorldDispatchTableStartingOffset extends FixedWithNextNode implements Lowerable { + public static final NodeClass TYPE = NodeClass.create(LoadOpenTypeWorldDispatchTableStartingOffset.class); + + @Input protected ValueNode hub; + @OptionalInput protected ValueNode interfaceTypeID; + + protected final SharedMethod target; + + public LoadOpenTypeWorldDispatchTableStartingOffset(ValueNode hub, SharedMethod target) { + super(TYPE, StampFactory.forInteger(64)); + this.hub = hub; + this.target = target; + this.interfaceTypeID = null; + } + + protected LoadOpenTypeWorldDispatchTableStartingOffset(ValueNode hub, ValueNode interfaceTypeID) { + super(TYPE, StampFactory.forInteger(64)); + this.hub = hub; + this.target = null; + this.interfaceTypeID = interfaceTypeID; + } + + public ValueNode getHub() { + return hub; + } + + public ValueNode getInterfaceTypeID() { + return interfaceTypeID; + } + + public SharedMethod getTarget() { + return target; + } + + @NodeIntrinsic + public static native long createOpenTypeWorldLoadDispatchTableStartingOffset(Object hub, int interfaceTypeID); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/NonSnippetLowerings.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/NonSnippetLowerings.java index 269f2c861692..c4527e285a66 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/NonSnippetLowerings.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/NonSnippetLowerings.java @@ -42,6 +42,7 @@ import com.oracle.svm.core.graal.code.SubstrateBackend; import com.oracle.svm.core.graal.meta.KnownOffsets; import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.graal.nodes.LoadOpenTypeWorldDispatchTableStartingOffset; import com.oracle.svm.core.graal.nodes.LoweredDeadEndNode; import com.oracle.svm.core.graal.nodes.ThrowBytecodeExceptionNode; import com.oracle.svm.core.meta.SharedMethod; @@ -78,6 +79,7 @@ import jdk.graal.compiler.nodes.PiNode; import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.calc.AddNode; import jdk.graal.compiler.nodes.calc.IsNullNode; import jdk.graal.compiler.nodes.extended.BranchProbabilityNode; import jdk.graal.compiler.nodes.extended.BytecodeExceptionNode; @@ -92,6 +94,7 @@ import jdk.graal.compiler.nodes.memory.ReadNode; import jdk.graal.compiler.nodes.memory.address.AddressNode; import jdk.graal.compiler.nodes.memory.address.OffsetAddressNode; +import jdk.graal.compiler.nodes.spi.Lowerable; import jdk.graal.compiler.nodes.spi.LoweringTool; import jdk.graal.compiler.nodes.spi.LoweringTool.StandardLoweringStage; import jdk.graal.compiler.nodes.spi.StampProvider; @@ -295,10 +298,12 @@ public void lower(FixedNode node, LoweringTool tool) { NodeInputList parameters = callTarget.arguments(); ValueNode receiver = parameters.size() <= 0 ? null : parameters.get(0); FixedGuardNode nullCheck = null; + List nodesToLower = new ArrayList<>(3); if (!callTarget.isStatic() && receiver.getStackKind() == JavaKind.Object && !StampTool.isPointerNonNull(receiver)) { LogicNode isNull = graph.unique(IsNullNode.create(receiver)); nullCheck = graph.add(new FixedGuardNode(isNull, DeoptimizationReason.NullCheckException, DeoptimizationAction.None, true)); graph.addBeforeFixed(node, nullCheck); + nodesToLower.add(nullCheck); } SharedMethod method = (SharedMethod) callTarget.targetMethod(); JavaType[] signature = method.getSignature().toParameterTypes(callTarget.isStatic() ? null : method.getDeclaringClass()); @@ -349,7 +354,6 @@ public void lower(FixedNode node, LoweringTool tool) { reportError.setNext(graph.add(new LoweredDeadEndNode())); } - LoadHubNode hub = null; CallTargetNode loweredCallTarget; if (invokeKind.isDirect() || implementations.length == 1) { SharedMethod targetMethod = method; @@ -408,25 +412,51 @@ public void lower(FixedNode node, LoweringTool tool) { loweredCallTarget = createUnreachableCallTarget(tool, node, parameters, callTarget.returnStamp(), signature, method, callType, invokeKind); } else { - int vtableEntryOffset = knownOffsets.getVTableOffset(method.getVTableIndex()); + LoadHubNode hub = graph.unique(new LoadHubNode(runtimeConfig.getProviders().getStampProvider(), graph.addOrUnique(PiNode.create(receiver, nullCheck)))); + nodesToLower.add(hub); - hub = graph.unique(new LoadHubNode(runtimeConfig.getProviders().getStampProvider(), graph.addOrUnique(PiNode.create(receiver, nullCheck)))); - AddressNode address = graph.unique(new OffsetAddressNode(hub, ConstantNode.forIntegerKind(ConfigurationValues.getWordKind(), vtableEntryOffset, graph))); - ReadNode entry = graph.add(new ReadNode(address, SubstrateBackend.getVTableIdentity(), FrameAccess.getWordStamp(), BarrierType.NONE, MemoryOrderMode.PLAIN)); - loweredCallTarget = createIndirectCall(graph, callTarget, parameters, method, signature, callType, invokeKind, entry); + if (SubstrateOptions.closedTypeWorld()) { + int vtableEntryOffset = knownOffsets.getVTableOffset(method.getVTableIndex(), true); - graph.addBeforeFixed(node, entry); + AddressNode address = graph.unique(new OffsetAddressNode(hub, ConstantNode.forIntegerKind(ConfigurationValues.getWordKind(), vtableEntryOffset, graph))); + ReadNode entry = graph.add(new ReadNode(address, SubstrateBackend.getVTableIdentity(), FrameAccess.getWordStamp(), BarrierType.NONE, MemoryOrderMode.PLAIN)); + + loweredCallTarget = createIndirectCall(graph, callTarget, parameters, method, signature, callType, invokeKind, entry); + + graph.addBeforeFixed(node, entry); + } else { + + // Compute the dispatch table starting offset + LoadOpenTypeWorldDispatchTableStartingOffset tableStartingOffset = graph.add(new LoadOpenTypeWorldDispatchTableStartingOffset(hub, method)); + nodesToLower.add(tableStartingOffset); + + // Add together table starting offset and index offset + ValueNode methodAddressOffset = graph.unique(new AddNode(tableStartingOffset, + ConstantNode.forIntegerKind(ConfigurationValues.getWordKind(), knownOffsets.getVTableOffset(method.getVTableIndex(), false), graph))); + + // The load the method address for the dispatch table + AddressNode dispatchTableAddress = graph.unique(new OffsetAddressNode(hub, methodAddressOffset)); + ReadNode entry = graph.add(new ReadNode(dispatchTableAddress, SubstrateBackend.getVTableIdentity(), FrameAccess.getWordStamp(), BarrierType.NONE, MemoryOrderMode.PLAIN)); + + loweredCallTarget = createIndirectCall(graph, callTarget, parameters, method, signature, callType, invokeKind, entry); + + // wire in the new nodes + FixedWithNextNode predecessor = (FixedWithNextNode) node.predecessor(); + predecessor.setNext(tableStartingOffset); + tableStartingOffset.setNext(entry); + entry.setNext(node); + + /* + * note here we don't delete the invoke because it remains in the graph, + * albeit with a different call target + */ + } } callTarget.replaceAndDelete(loweredCallTarget); // Recursive lowering - if (nullCheck != null) { - nullCheck.lower(tool); - } - if (hub != null) { - hub.lower(tool); - } + nodesToLower.forEach(n -> n.lower(tool)); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/OpenTypeWorldDispatchTableSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/OpenTypeWorldDispatchTableSnippets.java new file mode 100644 index 000000000000..2309dffa2cd4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/OpenTypeWorldDispatchTableSnippets.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.graal.snippets; + +import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.unknownProbability; + +import java.util.Map; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.word.LocationIdentity; + +import com.oracle.svm.core.config.ObjectLayout; +import com.oracle.svm.core.graal.meta.KnownOffsets; +import com.oracle.svm.core.graal.nodes.LoadOpenTypeWorldDispatchTableStartingOffset; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.meta.SharedType; + +import jdk.graal.compiler.api.replacements.Snippet; +import jdk.graal.compiler.graph.Node; +import jdk.graal.compiler.nodes.ConstantNode; +import jdk.graal.compiler.nodes.NamedLocationIdentity; +import jdk.graal.compiler.nodes.UnreachableNode; +import jdk.graal.compiler.nodes.spi.LoweringTool; +import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.phases.util.Providers; +import jdk.graal.compiler.replacements.SnippetTemplate; +import jdk.graal.compiler.replacements.Snippets; +import jdk.graal.compiler.word.ObjectAccess; +import jdk.vm.ci.meta.JavaKind; + +public final class OpenTypeWorldDispatchTableSnippets extends SubstrateTemplates implements Snippets { + + @Snippet + private static long loadITableStartingOffset( + @Snippet.NonNullParameter DynamicHub hub, + int interfaceTypeID) { + return determineITableStartingOffset(hub, interfaceTypeID); + } + + @Snippet + private static long loadDispatchTableStartingOffset( + @Snippet.NonNullParameter DynamicHub hub, + int interfaceTypeID, @Snippet.ConstantParameter int vtableStartingOffset) { + if (unknownProbability(interfaceTypeID >= 0)) { + return determineITableStartingOffset(hub, interfaceTypeID); + } else { + // the class dispatch table is always first + return vtableStartingOffset; + } + } + + private static long determineITableStartingOffset( + DynamicHub checkedHub, + int interfaceID) { + + int numClassTypes = checkedHub.getNumClassTypes(); + int numInterfaceTypes = checkedHub.getNumInterfaceTypes(); + int[] checkedTypeIds = checkedHub.getOpenTypeWorldTypeCheckSlots(); + for (int i = 0; i < numInterfaceTypes * 2; i += 2) { + // int checkedInterfaceId = checkedTypeIds[numClassTypes + i]; + int offset = (int) ImageSingletons.lookup(ObjectLayout.class).getArrayElementOffset(JavaKind.Int, numClassTypes + i); + // GR-51603 can make a floating read + int checkedInterfaceId = ObjectAccess.readInt(checkedTypeIds, offset, NamedLocationIdentity.FINAL_LOCATION); + if (checkedInterfaceId == interfaceID) { + offset = (int) ImageSingletons.lookup(ObjectLayout.class).getArrayElementOffset(JavaKind.Int, numClassTypes + i + 1); + long result = ObjectAccess.readInt(checkedTypeIds, offset, NamedLocationIdentity.FINAL_LOCATION); + return result; + } + } + + throw UnreachableNode.unreachable(); + } + + private final SnippetTemplate.SnippetInfo loadITableStartingOffset; + private final SnippetTemplate.SnippetInfo loadDispatchTableStartingOffset; + + private OpenTypeWorldDispatchTableSnippets(OptionValues options, Providers providers, Map, NodeLoweringProvider> lowerings) { + super(options, providers); + + this.loadITableStartingOffset = snippet(providers, OpenTypeWorldDispatchTableSnippets.class, "loadITableStartingOffset", getKilledLocations()); + this.loadDispatchTableStartingOffset = snippet(providers, OpenTypeWorldDispatchTableSnippets.class, "loadDispatchTableStartingOffset", getKilledLocations()); + + lowerings.put(LoadOpenTypeWorldDispatchTableStartingOffset.class, new DispatchTableStartingOffsetLowering()); + } + + private static LocationIdentity[] getKilledLocations() { + return new LocationIdentity[0]; + } + + @SuppressWarnings("unused") + public static void registerLowerings(OptionValues options, Providers providers, Map, NodeLoweringProvider> lowerings) { + new OpenTypeWorldDispatchTableSnippets(options, providers, lowerings); + } + + class DispatchTableStartingOffsetLowering implements NodeLoweringProvider { + + @Override + public void lower(LoadOpenTypeWorldDispatchTableStartingOffset node, LoweringTool tool) { + SharedMethod target = node.getTarget(); + int vtableStartingOffset = KnownOffsets.singleton().getVTableOffset(0, true); + if (target != null) { + /* + * If the target is known, then we know whether to use the class dispatch table or + * an interface dispatch table. + */ + if (target.getDeclaringClass().isInterface()) { + SnippetTemplate.Arguments args = new SnippetTemplate.Arguments(loadITableStartingOffset, node.graph().getGuardsStage(), tool.getLoweringStage()); + args.add("hub", node.getHub()); + args.add("interfaceTypeID", ((SharedType) target.getDeclaringClass()).getTypeID()); + template(tool, node, args).instantiate(tool.getMetaAccess(), node, SnippetTemplate.DEFAULT_REPLACER, args); + + } else { + // We know the call uses the class dispatch table, which is always first + var graph = node.graph(); + graph.replaceFixedWithFloating(node, ConstantNode.forLong(vtableStartingOffset, graph)); + } + } else { + /* + * Otherwise we must search on the interfaceID + */ + SnippetTemplate.Arguments args = new SnippetTemplate.Arguments(loadDispatchTableStartingOffset, node.graph().getGuardsStage(), tool.getLoweringStage()); + args.add("hub", node.getHub()); + args.add("interfaceTypeID", node.getInterfaceTypeID()); + args.addConst("vtableStartingOffset", vtableStartingOffset); + template(tool, node, args).instantiate(tool.getMetaAccess(), node, SnippetTemplate.DEFAULT_REPLACER, args); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/OpenTypeWorldSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/OpenTypeWorldSnippets.java index aa5893d300b7..2616cc9766d9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/OpenTypeWorldSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/OpenTypeWorldSnippets.java @@ -70,7 +70,7 @@ protected static SubstrateIntrinsics.Any typeEqualitySnippet( SubstrateIntrinsics.Any trueValue, SubstrateIntrinsics.Any falseValue, @Snippet.ConstantParameter boolean allowsNull, - @Snippet.ConstantParameter DynamicHub exactType) { + @Snippet.NonNullParameter DynamicHub exactType) { if (allowsNull) { if (probability(NOT_FREQUENT_PROBABILITY, object == null)) { return trueValue; @@ -166,7 +166,7 @@ private static SubstrateIntrinsics.Any classTypeCheck( if (typeIDDepth >= numClassTypes) { return falseValue; } - int[] checkedTypeIds = checkedHub.getOpenWorldTypeIDSlots(); + int[] checkedTypeIds = checkedHub.getOpenTypeWorldTypeCheckSlots(); // int checkedClassId = checkedTypeIds[typeIDDepth]; int offset = (int) ImageSingletons.lookup(ObjectLayout.class).getArrayElementOffset(JavaKind.Int, typeIDDepth); // GR-51603 can make a floating read @@ -186,8 +186,8 @@ private static SubstrateIntrinsics.Any interfaceTypeCheck( SubstrateIntrinsics.Any falseValue) { int numClassTypes = checkedHub.getNumClassTypes(); int numInterfaceTypes = checkedHub.getNumInterfaceTypes(); - int[] checkedTypeIds = checkedHub.getOpenWorldTypeIDSlots(); - for (int i = 0; i < numInterfaceTypes; i++) { + int[] checkedTypeIds = checkedHub.getOpenTypeWorldTypeCheckSlots(); + for (int i = 0; i < numInterfaceTypes * 2; i += 2) { // int checkedInterfaceId = checkedTypeIds[numClassTypes + i]; int offset = (int) ImageSingletons.lookup(ObjectLayout.class).getArrayElementOffset(JavaKind.Int, numClassTypes + i); // GR-51603 can make a floating read @@ -260,7 +260,7 @@ protected SnippetTemplate.Arguments makeArguments(InstanceOfUsageReplacer replac args.add("trueValue", replacer.trueValue); args.add("falseValue", replacer.falseValue); args.addConst("allowsNull", node.allowsNull()); - args.add("typeID", hub.getTypeID()); + args.addConst("typeID", hub.getTypeID()); args.addConst("typeIDDepth", hub.getTypeIDDepth()); return args; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index b2686b028634..e397fe5f7c70 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -186,9 +186,7 @@ public final class DynamicHub implements AnnotatedElement, java.lang.reflect.Typ @UnknownPrimitiveField(availability = AfterHostedUniverse.class)// private int typeID; - /* - * ### Start closed-world only fields ### - */ + // region closed-world only fields /** * In our current version, type checks are accomplished by performing a range check on a value @@ -217,15 +215,11 @@ public final class DynamicHub implements AnnotatedElement, java.lang.reflect.Typ * match the assignee's type. */ @UnknownObjectField(availability = AfterHostedUniverse.class)// - private short[] closedWorldTypeCheckSlots; + private short[] closedTypeWorldTypeCheckSlots; - /* - * ### End closed-world only fields ### - */ + // endregion closed-world only fields - /* - * ### Start open-world only fields ### - */ + // region open-world only fields /** * This stores the depth of the type in the inheritance hierarchy. If the type is an interface @@ -245,11 +239,9 @@ public final class DynamicHub implements AnnotatedElement, java.lang.reflect.Typ * searched for a matching typeID. */ @UnknownObjectField(availability = AfterHostedUniverse.class)// - private int[] openWorldTypeIDSlots; + private int[] openTypeWorldTypeCheckSlots; - /* - * ### End open-world only fields ### - */ + // endregion open-world only fields /** * The offset of the synthetic field which stores whatever is used for monitorEnter/monitorExit @@ -498,9 +490,8 @@ public void setClassInitializationInfo(ClassInitializationInfo classInitializati } @Platforms(Platform.HOSTED_ONLY.class) - public void setSharedData(int layoutEncoding, int monitorOffset, int identityHashOffset, - long referenceMapIndex, boolean isInstantiated, boolean canInstantiateAsInstance, - boolean isRegisteredForSerialization) { + public void setSharedData(int layoutEncoding, int monitorOffset, int identityHashOffset, long referenceMapIndex, + boolean isInstantiated, boolean canInstantiateAsInstance, boolean isRegisteredForSerialization) { assert !(!isInstantiated && canInstantiateAsInstance); VMError.guarantee(monitorOffset == (char) monitorOffset, "Class %s has an invalid monitor field offset. Most likely, its objects are larger than supported.", name); VMError.guarantee(identityHashOffset == (char) identityHashOffset, "Class %s has an invalid identity hash code field offset. Most likely, its objects are larger than supported.", name); @@ -519,28 +510,27 @@ public void setSharedData(int layoutEncoding, int monitorOffset, int identityHas } @Platforms(Platform.HOSTED_ONLY.class) - public void setClosedWorldData(CFunctionPointer[] vtable, int typeID, short typeCheckStart, short typeCheckRange, short typeCheckSlot, - short[] typeCheckSlots) { + public void setClosedTypeWorldData(CFunctionPointer[] vtable, int typeID, short typeCheckStart, short typeCheckRange, short typeCheckSlot, short[] typeCheckSlots) { assert this.vtable == null : "Initialization must be called only once"; this.typeID = typeID; this.typeCheckStart = typeCheckStart; this.typeCheckRange = typeCheckRange; this.typeCheckSlot = typeCheckSlot; - this.closedWorldTypeCheckSlots = typeCheckSlots; + this.closedTypeWorldTypeCheckSlots = typeCheckSlots; this.vtable = vtable; } @Platforms(Platform.HOSTED_ONLY.class) - public void setOpenWorldData(CFunctionPointer[] vtable, int typeID, - int typeCheckDepth, int numClassTypes, int numInterfaceTypes, int[] openWorldTypeCheckSlots) { + public void setOpenTypeWorldData(CFunctionPointer[] vtable, int typeID, + int typeCheckDepth, int numClassTypes, int numInterfaceTypes, int[] typeCheckSlots) { assert this.vtable == null : "Initialization must be called only once"; this.typeID = typeID; this.typeIDDepth = typeCheckDepth; this.numClassTypes = numClassTypes; this.numInterfaceTypes = numInterfaceTypes; - this.openWorldTypeIDSlots = openWorldTypeCheckSlots; + this.openTypeWorldTypeCheckSlots = typeCheckSlots; this.vtable = vtable; } @@ -707,8 +697,8 @@ public int getNumInterfaceTypes() { return numInterfaceTypes; } - public int[] getOpenWorldTypeIDSlots() { - return openWorldTypeIDSlots; + public int[] getOpenTypeWorldTypeCheckSlots() { + return openTypeWorldTypeCheckSlots; } public int getMonitorOffset() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubSupport.java index 8261e019ec48..2e21e3fd5430 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubSupport.java @@ -57,7 +57,7 @@ public int getMaxTypeId() { } @Platforms(Platform.HOSTED_ONLY.class) - public void setData(NonmovableArray referenceMapEncoding) { + public void setReferenceMapEncoding(NonmovableArray referenceMapEncoding) { this.referenceMapEncoding = NonmovableArrays.getHostedArray(referenceMapEncoding); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleMethod.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleMethod.java index dfeb709e08f6..7d3fcc16aded 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleMethod.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleMethod.java @@ -27,21 +27,24 @@ import java.lang.reflect.Modifier; import org.graalvm.collections.EconomicSet; -import jdk.graal.compiler.nodes.NamedLocationIdentity; -import jdk.graal.compiler.word.BarrieredAccess; import org.graalvm.nativeimage.Platform.HOSTED_ONLY; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.PointerBase; +import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.BuildPhaseProvider.ReadyForCompilation; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.graal.nodes.LoadOpenTypeWorldDispatchTableStartingOffset; import com.oracle.svm.core.heap.UnknownPrimitiveField; import com.oracle.svm.core.jni.CallVariant; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ReflectionUtil; +import jdk.graal.compiler.nodes.NamedLocationIdentity; +import jdk.graal.compiler.word.BarrieredAccess; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaField; @@ -51,6 +54,9 @@ public final class JNIAccessibleMethod extends JNIAccessibleMember { public static final int STATICALLY_BOUND_METHOD = -1; public static final int VTABLE_OFFSET_NOT_YET_COMPUTED = -2; + public static final int INTERFACE_TYPEID_CLASS_TABLE = -1; + public static final int INTERFACE_TYPEID_NOT_YET_COMPUTED = -2; + public static final int INTERFACE_TYPEID_UNNEEDED = -3; public static final int NEW_OBJECT_INVALID_FOR_ABSTRACT_TYPE = -1; @Platforms(HOSTED_ONLY.class) @@ -76,6 +82,8 @@ public static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider me @UnknownPrimitiveField(availability = ReadyForCompilation.class)// private int vtableOffset = VTABLE_OFFSET_NOT_YET_COMPUTED; @UnknownPrimitiveField(availability = ReadyForCompilation.class)// + private int interfaceTypeID = INTERFACE_TYPEID_NOT_YET_COMPUTED; + @UnknownPrimitiveField(availability = ReadyForCompilation.class)// private CodePointer nonvirtualTarget; @UnknownPrimitiveField(availability = ReadyForCompilation.class)// private PointerBase newObjectTarget; // for constructors @@ -109,9 +117,18 @@ CodePointer getCallWrapperAddress() { @AlwaysInline("Work around an issue with the LLVM backend with which the return value was accessed incorrectly.") CodePointer getJavaCallAddress(Object instance, boolean nonVirtual) { if (!nonVirtual) { - assert vtableOffset != JNIAccessibleMethod.VTABLE_OFFSET_NOT_YET_COMPUTED; - if (vtableOffset != JNIAccessibleMethod.STATICALLY_BOUND_METHOD) { - return BarrieredAccess.readWord(instance.getClass(), vtableOffset, NamedLocationIdentity.FINAL_LOCATION); + if (SubstrateOptions.closedTypeWorld()) { + assert vtableOffset != JNIAccessibleMethod.VTABLE_OFFSET_NOT_YET_COMPUTED; + if (vtableOffset != JNIAccessibleMethod.STATICALLY_BOUND_METHOD) { + return BarrieredAccess.readWord(instance.getClass(), vtableOffset, NamedLocationIdentity.FINAL_LOCATION); + } + } else { + assert vtableOffset != JNIAccessibleMethod.VTABLE_OFFSET_NOT_YET_COMPUTED; + if (vtableOffset != STATICALLY_BOUND_METHOD) { + long tableStartingOffset = LoadOpenTypeWorldDispatchTableStartingOffset.createOpenTypeWorldLoadDispatchTableStartingOffset(instance.getClass(), interfaceTypeID); + + return BarrieredAccess.readWord(instance.getClass(), WordFactory.pointer(tableStartingOffset + vtableOffset), NamedLocationIdentity.FINAL_LOCATION); + } } } return nonvirtualTarget; @@ -134,11 +151,14 @@ boolean isStatic() { } @Platforms(HOSTED_ONLY.class) - public void finishBeforeCompilation(EconomicSet> hidingSubclasses, int vtableEntryOffset, CodePointer nonvirtualEntry, PointerBase newObjectEntry, CodePointer callWrapperEntry, - CodePointer varargs, CodePointer array, CodePointer valist, CodePointer varargsNonvirtual, CodePointer arrayNonvirtual, CodePointer valistNonvirtual) { - assert this.vtableOffset == VTABLE_OFFSET_NOT_YET_COMPUTED && (vtableEntryOffset == STATICALLY_BOUND_METHOD || vtableEntryOffset >= 0); - - this.vtableOffset = vtableEntryOffset; + public void finishBeforeCompilation(EconomicSet> hidingSubclasses, int vtableOffsetEntry, int interfaceTypeIDEntry, CodePointer nonvirtualEntry, PointerBase newObjectEntry, + CodePointer callWrapperEntry, CodePointer varargs, CodePointer array, CodePointer valist, CodePointer varargsNonvirtual, CodePointer arrayNonvirtual, + CodePointer valistNonvirtual) { + assert this.vtableOffset == VTABLE_OFFSET_NOT_YET_COMPUTED && (vtableOffsetEntry == STATICALLY_BOUND_METHOD || vtableOffsetEntry >= 0); + assert this.interfaceTypeID == INTERFACE_TYPEID_NOT_YET_COMPUTED && interfaceTypeIDEntry != INTERFACE_TYPEID_NOT_YET_COMPUTED; + + this.vtableOffset = vtableOffsetEntry; + this.interfaceTypeID = interfaceTypeIDEntry; this.nonvirtualTarget = nonvirtualEntry; this.newObjectTarget = newObjectEntry; this.callWrapper = callWrapperEntry; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/SharedType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/SharedType.java index 5b495351c8b5..99a6e0f238c9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/SharedType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/SharedType.java @@ -45,6 +45,8 @@ public interface SharedType extends ResolvedJavaType { */ JavaKind getStorageKind(); + int getTypeID(); + @Override default ResolvedJavaMethod resolveMethod(ResolvedJavaMethod method, ResolvedJavaType callerType) { /* diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java index 90a03acca074..f0d5addafad8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java @@ -29,8 +29,11 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.function.CFunctionPointer; +import org.graalvm.word.WordFactory; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; +import com.oracle.svm.core.graal.nodes.LoadOpenTypeWorldDispatchTableStartingOffset; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.jdk.InternalVMMethod; import com.oracle.svm.core.reflect.ReflectionAccessorHolder.MethodInvokeFunctionPointer; @@ -51,6 +54,7 @@ public final class SubstrateMethodAccessor extends SubstrateAccessor implements public static final int STATICALLY_BOUND = -1; public static final int OFFSET_NOT_YET_COMPUTED = 0xdead0001; + public static final int INTERFACE_TYPEID_CLASS_TABLE = -1; /** * The expected receiver type, which is checked before invoking the {@link #expandSignature} @@ -59,6 +63,7 @@ public final class SubstrateMethodAccessor extends SubstrateAccessor implements private final Class receiverType; /** The actual value is computed after static analysis using a field value transformer. */ private int vtableOffset; + private int interfaceTypeID; private final boolean callerSensitiveAdapter; @Platforms(Platform.HOSTED_ONLY.class) @@ -67,6 +72,7 @@ public SubstrateMethodAccessor(Executable member, Class receiverType, CFuncti super(member, expandSignature, directTarget, targetMethod, initializeBeforeInvoke); this.receiverType = receiverType; this.vtableOffset = vtableOffset; + this.interfaceTypeID = OFFSET_NOT_YET_COMPUTED; this.callerSensitiveAdapter = callerSensitiveAdapter; } @@ -74,6 +80,10 @@ public int getVTableOffset() { return vtableOffset; } + public int getInterfaceTypeID() { + return interfaceTypeID; + } + private void preInvoke(Object obj) { if (initializeBeforeInvoke != null) { EnsureClassInitializedNode.ensureClassInitialized(DynamicHub.toClass(initializeBeforeInvoke)); @@ -96,15 +106,34 @@ private CFunctionPointer invokeTarget(Object obj) { * In case we have both a vtableOffset and a directTarget, the vtable lookup wins. For such * methods, the directTarget is only used when doing an invokeSpecial. */ - CFunctionPointer target; - if (vtableOffset == OFFSET_NOT_YET_COMPUTED) { - throw VMError.shouldNotReachHere("Missed vtableOffset recomputation at image build time"); - } else if (vtableOffset != STATICALLY_BOUND) { - target = BarrieredAccess.readWord(obj.getClass(), vtableOffset, NamedLocationIdentity.FINAL_LOCATION); + if (SubstrateOptions.closedTypeWorld()) { + CFunctionPointer target; + if (vtableOffset == OFFSET_NOT_YET_COMPUTED) { + throw VMError.shouldNotReachHere("Missed vtableOffset recomputation at image build time"); + } else if (vtableOffset != STATICALLY_BOUND) { + target = BarrieredAccess.readWord(obj.getClass(), vtableOffset, NamedLocationIdentity.FINAL_LOCATION); + } else { + target = directTarget; + } + return target; } else { - target = directTarget; + CFunctionPointer target; + if (vtableOffset == OFFSET_NOT_YET_COMPUTED) { + throw VMError.shouldNotReachHere("Missed vtableOffset recomputation at image build time"); + } else if (vtableOffset != STATICALLY_BOUND) { + long tableStartingOffset = LoadOpenTypeWorldDispatchTableStartingOffset.createOpenTypeWorldLoadDispatchTableStartingOffset(obj.getClass(), interfaceTypeID); + + /* + * Must also add in the vtable base offset as well as the offset within the table. + */ + long methodOffset = tableStartingOffset + vtableOffset; + + target = BarrieredAccess.readWord(obj.getClass(), WordFactory.pointer(methodOffset), NamedLocationIdentity.FINAL_LOCATION); + } else { + target = directTarget; + } + return target; } - return target; } @Override diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateType.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateType.java index 1bea997ff956..9973c932bad2 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateType.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateType.java @@ -120,6 +120,11 @@ public JavaKind getJavaKind() { // return Kind.fromJavaClass(hub.asClass()); } + @Override + public int getTypeID() { + return hub.getTypeID(); + } + @Override public ResolvedJavaType resolve(ResolvedJavaType accessingClass) { return this; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java index 0c73e192be17..c58ad7d62299 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java @@ -177,35 +177,36 @@ private static DynamicHubLayout createDynamicHubLayout(HostedMetaAccess hMetaAcc JavaKind vTableSlotStorageKind = vtableField.getType().getComponentType().getStorageKind(); int vTableSlotSize = layout.sizeInBytes(vTableSlotStorageKind); - var closedWorldTypeCheckSlotsField = hMetaAccess.lookupJavaField(ReflectionUtil.lookupField(DynamicHub.class, "closedWorldTypeCheckSlots")); - int closedWorldTypeCheckSlotsOffset; - int closedWorldTypeCheckSlotSize; + var closedTypeWorldTypeCheckSlotsField = hMetaAccess.lookupJavaField(ReflectionUtil.lookupField(DynamicHub.class, "closedTypeWorldTypeCheckSlots")); + int closedTypeWorldTypeCheckSlotsOffset; + int closedTypeWorldTypeCheckSlotSize; Set ignoredFields; if (SubstrateOptions.closedTypeWorld()) { - closedWorldTypeCheckSlotsOffset = layout.getArrayLengthOffset() + layout.sizeInBytes(JavaKind.Int); - closedWorldTypeCheckSlotSize = layout.sizeInBytes(closedWorldTypeCheckSlotsField.getType().getComponentType().getStorageKind()); + closedTypeWorldTypeCheckSlotsOffset = layout.getArrayLengthOffset() + layout.sizeInBytes(JavaKind.Int); + closedTypeWorldTypeCheckSlotSize = layout.sizeInBytes(closedTypeWorldTypeCheckSlotsField.getType().getComponentType().getStorageKind()); ignoredFields = Set.of( - closedWorldTypeCheckSlotsField, + closedTypeWorldTypeCheckSlotsField, vtableField, hMetaAccess.lookupJavaField(ReflectionUtil.lookupField(DynamicHub.class, "typeIDDepth")), hMetaAccess.lookupJavaField(ReflectionUtil.lookupField(DynamicHub.class, "numClassTypes")), hMetaAccess.lookupJavaField(ReflectionUtil.lookupField(DynamicHub.class, "numInterfaceTypes")), - hMetaAccess.lookupJavaField(ReflectionUtil.lookupField(DynamicHub.class, "openWorldTypeIDSlots"))); + hMetaAccess.lookupJavaField(ReflectionUtil.lookupField(DynamicHub.class, "openTypeWorldTypeCheckSlots"))); } else { - closedWorldTypeCheckSlotsOffset = -1; - closedWorldTypeCheckSlotSize = -1; + closedTypeWorldTypeCheckSlotsOffset = -1; + closedTypeWorldTypeCheckSlotSize = -1; ignoredFields = Set.of( - closedWorldTypeCheckSlotsField, + closedTypeWorldTypeCheckSlotsField, vtableField, hMetaAccess.lookupJavaField(ReflectionUtil.lookupField(DynamicHub.class, "typeCheckStart")), hMetaAccess.lookupJavaField(ReflectionUtil.lookupField(DynamicHub.class, "typeCheckRange")), hMetaAccess.lookupJavaField(ReflectionUtil.lookupField(DynamicHub.class, "typeCheckSlot"))); } - return new DynamicHubLayout(layout, dynamicHubType, closedWorldTypeCheckSlotsField, closedWorldTypeCheckSlotsOffset, closedWorldTypeCheckSlotSize, vtableField, vTableSlotStorageKind, + return new DynamicHubLayout(layout, dynamicHubType, closedTypeWorldTypeCheckSlotsField, closedTypeWorldTypeCheckSlotsOffset, closedTypeWorldTypeCheckSlotSize, vtableField, + vTableSlotStorageKind, vTableSlotSize, ignoredFields); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index 4436047deb2e..089ab5a2c3d6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -159,6 +159,7 @@ import com.oracle.svm.core.graal.phases.SubstrateSafepointInsertionPhase; import com.oracle.svm.core.graal.snippets.DeoptTester; import com.oracle.svm.core.graal.snippets.NodeLoweringProvider; +import com.oracle.svm.core.graal.snippets.OpenTypeWorldDispatchTableSnippets; import com.oracle.svm.core.graal.snippets.OpenTypeWorldSnippets; import com.oracle.svm.core.graal.snippets.TypeSnippets; import com.oracle.svm.core.graal.word.SubstrateWordOperationPlugins; @@ -1468,6 +1469,7 @@ public static void registerReplacements(DebugContext debug, FeatureHandler featu TypeSnippets.registerLowerings(options, providers, lowerings); } else { OpenTypeWorldSnippets.registerLowerings(options, providers, lowerings); + OpenTypeWorldDispatchTableSnippets.registerLowerings(options, providers, lowerings); } featureHandler.forEachGraalFeature(feature -> feature.registerLowerings(runtimeConfig, options, providers, lowerings, hosted)); @@ -1834,10 +1836,10 @@ private void printTypes(PrintWriter writer) { if (SubstrateOptions.closedTypeWorld()) { writer.format("type check start %d range %d slot # %d ", type.getTypeCheckStart(), type.getTypeCheckRange(), type.getTypeCheckSlot()); - writer.format("type check slots %s ", slotsToString(type.getClosedWorldTypeCheckSlots())); + writer.format("type check slots %s ", slotsToString(type.getClosedTypeWorldTypeCheckSlots())); } else { writer.format("type id %s depth %s num class types %s num interface types %s ", type.getTypeID(), type.getTypeIDDepth(), type.getNumClassTypes(), type.getNumInterfaceTypes()); - writer.format("type check slots %s ", String.join(" ", Arrays.stream(type.getOpenWorldTypeIDSlots()).mapToObj(Integer::toString).toArray(String[]::new))); + writer.format("type check slots %s ", String.join(" ", Arrays.stream(type.getOpenTypeWorldTypeCheckSlots()).mapToObj(Integer::toString).toArray(String[]::new))); } // if (type.findLeafConcreteSubtype() != null) { // writer.format("unique %d %s ", type.findLeafConcreteSubtype().getTypeID(), diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/OpenTypeWorldFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/OpenTypeWorldFeature.java index 96e5a34840f9..4c49898d044e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/OpenTypeWorldFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/OpenTypeWorldFeature.java @@ -46,7 +46,7 @@ public void beforeCompilation(BeforeCompilationAccess access) { var impl = (FeatureImpl.BeforeCompilationAccessImpl) access; for (HostedType type : impl.getUniverse().getTypes()) { DynamicHub hub = type.getHub(); - impl.registerAsImmutable(hub.getOpenWorldTypeIDSlots()); + impl.registerAsImmutable(hub.getOpenTypeWorldTypeCheckSlots()); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/DynamicHubLayout.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/DynamicHubLayout.java index cec46e5faaa2..3d754e1eb068 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/DynamicHubLayout.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/DynamicHubLayout.java @@ -80,9 +80,9 @@ public class DynamicHubLayout { private final ObjectLayout layout; private final HostedInstanceClass dynamicHubType; - public final HostedField closedWorldTypeCheckSlotsField; - private final int closedWorldTypeCheckSlotsOffset; - private final int closedWorldTypeCheckSlotSize; + public final HostedField closedTypeWorldTypeCheckSlotsField; + private final int closedTypeWorldTypeCheckSlotsOffset; + private final int closedTypeWorldTypeCheckSlotSize; public final HostedField vTableField; public final int vTableSlotSize; public final JavaKind vTableSlotStorageKind; @@ -97,15 +97,16 @@ public class DynamicHubLayout { /** * See {@code HostedConfiguration#DynamicHubLayout} for the exact initialization values. */ - public DynamicHubLayout(ObjectLayout layout, HostedType dynamicHubType, HostedField closedWorldTypeCheckSlotsField, int closedWorldTypeCheckSlotsOffset, int closedWorldTypeCheckSlotSize, + public DynamicHubLayout(ObjectLayout layout, HostedType dynamicHubType, HostedField closedTypeWorldTypeCheckSlotsField, int closedTypeWorldTypeCheckSlotsOffset, + int closedTypeWorldTypeCheckSlotSize, HostedField vTableField, JavaKind vTableSlotStorageKind, int vTableSlotSize, Set ignoredFields) { this.layout = layout; this.dynamicHubType = (HostedInstanceClass) dynamicHubType; - this.closedWorldTypeCheckSlotsField = closedWorldTypeCheckSlotsField; - this.closedWorldTypeCheckSlotsOffset = closedWorldTypeCheckSlotsOffset; - this.closedWorldTypeCheckSlotSize = closedWorldTypeCheckSlotSize; + this.closedTypeWorldTypeCheckSlotsField = closedTypeWorldTypeCheckSlotsField; + this.closedTypeWorldTypeCheckSlotsOffset = closedTypeWorldTypeCheckSlotsOffset; + this.closedTypeWorldTypeCheckSlotSize = closedTypeWorldTypeCheckSlotSize; this.vTableField = vTableField; this.vTableSlotStorageKind = vTableSlotStorageKind; this.vTableSlotSize = vTableSlotSize; @@ -133,21 +134,21 @@ public boolean isDynamicHub(HostedType type) { } public boolean isInlinedField(HostedField field) { - return field.equals(closedWorldTypeCheckSlotsField) || field.equals(vTableField); + return field.equals(closedTypeWorldTypeCheckSlotsField) || field.equals(vTableField); } public int getVTableSlotOffset(int index) { return vTableOffset() + index * vTableSlotSize; } - public int getClosedWorldTypeCheckSlotsOffset() { + public int getClosedTypeWorldTypeCheckSlotsOffset() { assert SubstrateOptions.closedTypeWorld(); - return closedWorldTypeCheckSlotsOffset; + return closedTypeWorldTypeCheckSlotsOffset; } - public int getClosedWorldTypeCheckSlotsOffset(int index) { + public int getClosedTypeWorldTypeCheckSlotsOffset(int index) { assert SubstrateOptions.closedTypeWorld(); - return closedWorldTypeCheckSlotsOffset + index * closedWorldTypeCheckSlotSize; + return closedTypeWorldTypeCheckSlotsOffset + index * closedTypeWorldTypeCheckSlotSize; } public int getVTableLengthOffset() { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java index 284133fe159e..5daddbcdd098 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java @@ -689,10 +689,11 @@ private void printCompilationResults(PrintWriter writer) { } writer.println("--- vtables:"); for (HostedType type : imageHeap.hUniverse.getTypes()) { - for (int i = 0; i < type.getVTable().length; i++) { - HostedMethod method = type.getVTable()[i]; + HostedMethod[] vtable = type.getVTable(); + for (int i = 0; i < vtable.length; i++) { + HostedMethod method = vtable[i]; if (method != null) { - CompilationResult comp = compilationResultFor(type.getVTable()[i]); + CompilationResult comp = compilationResultFor(vtable[i]); if (comp != null) { writer.format("%d %s @ %d: %s = 0x%x%n", type.getTypeID(), type.toJavaName(false), i, method.format("%r %n(%p)"), method.getCodeAddressOffset()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java index 38f02d253de9..6a6a42c88066 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java @@ -479,12 +479,12 @@ private void addObjectToImageHeap(final JavaConstant constant, boolean immutable * to check this. */ if (SubstrateOptions.closedTypeWorld()) { - Object typeIDSlots = readInlinedField(dynamicHubLayout.closedWorldTypeCheckSlotsField, constant); - assert typeIDSlots != null : "Cannot read value for field " + dynamicHubLayout.closedWorldTypeCheckSlotsField.format("%H.%n"); + Object typeIDSlots = readInlinedField(dynamicHubLayout.closedTypeWorldTypeCheckSlotsField, constant); + assert typeIDSlots != null : "Cannot read value for field " + dynamicHubLayout.closedTypeWorldTypeCheckSlotsField.format("%H.%n"); blacklist.add(typeIDSlots); } else { if (SubstrateUtil.assertionsEnabled()) { - Object typeIDSlots = readInlinedField(dynamicHubLayout.closedWorldTypeCheckSlotsField, constant); + Object typeIDSlots = readInlinedField(dynamicHubLayout.closedTypeWorldTypeCheckSlotsField, constant); assert typeIDSlots == null : typeIDSlots; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeapWriter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeapWriter.java index c1fb68e44b8d..6fbb938a271e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeapWriter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeapWriter.java @@ -353,10 +353,10 @@ private void writeObject(ObjectInfo info, RelocatableBuffer buffer) { * is stored in a separate array since it has a variable length. */ if (SubstrateOptions.closedTypeWorld()) { - short[] typeIDSlots = (short[]) heap.readInlinedField(dynamicHubLayout.closedWorldTypeCheckSlotsField, con); + short[] typeIDSlots = (short[]) heap.readInlinedField(dynamicHubLayout.closedTypeWorldTypeCheckSlotsField, con); int typeIDSlotsLength = typeIDSlots.length; for (int i = 0; i < typeIDSlotsLength; i++) { - int index = getIndexInBuffer(info, dynamicHubLayout.getClosedWorldTypeCheckSlotsOffset(i)); + int index = getIndexInBuffer(info, dynamicHubLayout.getClosedTypeWorldTypeCheckSlotsOffset(i)); short value = typeIDSlots[i]; bufferBytes.putShort(index, value); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java index 60a75e6f05a3..e6d3559426d2 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java @@ -58,6 +58,7 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.config.ObjectLayout; import com.oracle.svm.core.configure.ConfigurationConditionResolver; import com.oracle.svm.core.configure.ConfigurationFile; @@ -499,10 +500,23 @@ private static void finishMethodBeforeCompilation(JNICallableJavaMethod method, AnalysisUniverse aUniverse = access.getUniverse().getBigBang().getUniverse(); HostedMethod hTarget = hUniverse.lookup(aUniverse.lookup(method.targetMethod)); int vtableOffset; - if (hTarget.canBeStaticallyBound()) { - vtableOffset = JNIAccessibleMethod.STATICALLY_BOUND_METHOD; + int interfaceTypeID; + if (SubstrateOptions.closedTypeWorld()) { + interfaceTypeID = JNIAccessibleMethod.INTERFACE_TYPEID_UNNEEDED; + if (hTarget.canBeStaticallyBound()) { + vtableOffset = JNIAccessibleMethod.STATICALLY_BOUND_METHOD; + } else { + vtableOffset = KnownOffsets.singleton().getVTableOffset(hTarget.getVTableIndex(), true); + } } else { - vtableOffset = KnownOffsets.singleton().getVTableOffset(hTarget.getVTableIndex()); + if (hTarget.canBeStaticallyBound()) { + vtableOffset = JNIAccessibleMethod.STATICALLY_BOUND_METHOD; + interfaceTypeID = JNIAccessibleMethod.INTERFACE_TYPEID_UNNEEDED; + } else { + vtableOffset = KnownOffsets.singleton().getVTableOffset(hTarget.getVTableIndex(), false); + HostedType declaringClass = hTarget.getDeclaringClass(); + interfaceTypeID = declaringClass.isInterface() ? declaringClass.getTypeID() : JNIAccessibleMethod.INTERFACE_TYPEID_CLASS_TABLE; + } } CodePointer nonvirtualTarget = new MethodPointer(hTarget); PointerBase newObjectTarget = null; @@ -525,7 +539,7 @@ private static void finishMethodBeforeCompilation(JNICallableJavaMethod method, valistNonvirtual = new MethodPointer(hUniverse.lookup(aUniverse.lookup(method.nonvirtualVariantWrappers.valist))); } EconomicSet> hidingSubclasses = findHidingSubclasses(hTarget.getDeclaringClass(), sub -> anyMethodMatchesIgnoreReturnType(sub, method.descriptor)); - method.jniMethod.finishBeforeCompilation(hidingSubclasses, vtableOffset, nonvirtualTarget, newObjectTarget, callWrapper, + method.jniMethod.finishBeforeCompilation(hidingSubclasses, vtableOffset, interfaceTypeID, nonvirtualTarget, newObjectTarget, callWrapper, varargs, array, valist, varargsNonvirtual, arrayNonvirtual, valistNonvirtual); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java index 5acaa7bb7524..7b9f244126ab 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java @@ -96,6 +96,13 @@ public final class HostedMethod extends HostedElement implements SharedMethod, W private final ResolvedSignature signature; private final ConstantPool constantPool; private final ExceptionHandler[] handlers; + /** + * Contains the index of the method within the appropriate table. + * + * Within the closed type world, there exists a single table which describes all methods. + * However, within the open type world, each type and interface has a unique table, so this + * index is relative to the start of the appropriate table. + */ int vtableIndex = -1; /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedType.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedType.java index 590fe2d4c626..eaa383586b1e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedType.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedType.java @@ -59,15 +59,13 @@ public abstract class HostedType extends HostedElement implements SharedType, Wr protected HostedType[] subTypes; protected HostedField[] staticFields; - protected HostedMethod[] vtable; - protected int typeID; protected HostedType uniqueConcreteImplementation; protected HostedMethod[] allDeclaredMethods; - /* - * ### Start closed-world only fields ### - */ + // region closed-world only fields + + protected HostedMethod[] closedTypeWorldVTable; /** * Start of type check range check. See {@link DynamicHub}.typeCheckStart @@ -89,15 +87,15 @@ public abstract class HostedType extends HostedElement implements SharedType, Wr /** * Array used within type checks. See {@link DynamicHub}.typeCheckSlots */ - protected short[] closedWorldTypeCheckSlots; + protected short[] closedTypeWorldTypeCheckSlots; - /* - * ### End closed-world only fields ### - */ + // endregion closed-world only fields - /* - * ### Start open-world only fields ### - */ + // region open-world only fields + + protected HostedType[] typeCheckInterfaceOrder; + protected HostedMethod[] openTypeWorldDispatchTables; + protected int[] itableStartingOffsets; /** * Instance class depth. Due to single-inheritance a parent class will be at the same depth in @@ -106,19 +104,17 @@ public abstract class HostedType extends HostedElement implements SharedType, Wr protected int typeIDDepth; /* - * Since we store interfaces within the openWorldTypeIDSlots, we must know both the number of - * class and interface types to ensure we read from the correct locations. + * Since we store interfaces within the openTypeWorldTypeCheckSlots, we must know both the + * number of class and interface types to ensure we read from the correct locations. */ protected int numClassTypes; protected int numInterfaceTypes; - protected int[] openWorldTypeIDSlots; + protected int[] openTypeWorldTypeCheckSlots; - /* - * ### End open-world only fields ### - */ + // endregion open-world only fields /** * A more precise subtype that can replace this type as the declared type of values. Null if @@ -147,11 +143,21 @@ public HostedType[] getSubTypes() { return subTypes; } + protected HostedMethod[] getClosedTypeWorldVTable() { + assert closedTypeWorldVTable != null; + return closedTypeWorldVTable; + } + + protected HostedMethod[] getOpenTypeWorldDispatchTables() { + assert openTypeWorldDispatchTables != null; + return openTypeWorldDispatchTables; + } + public HostedMethod[] getVTable() { - assert vtable != null; - return vtable; + return SubstrateOptions.closedTypeWorld() ? getClosedTypeWorldVTable() : getOpenTypeWorldDispatchTables(); } + @Override public int getTypeID() { assert typeID != -1; return typeID; @@ -168,9 +174,9 @@ public void setTypeCheckSlot(short typeCheckSlot) { this.typeCheckSlot = typeCheckSlot; } - public void setClosedWorldTypeCheckSlots(short[] closedWorldTypeCheckSlots) { + public void setClosedTypeWorldTypeCheckSlots(short[] closedTypeWorldTypeCheckSlots) { assert SubstrateOptions.closedTypeWorld(); - this.closedWorldTypeCheckSlots = closedWorldTypeCheckSlots; + this.closedTypeWorldTypeCheckSlots = closedTypeWorldTypeCheckSlots; } public short getTypeCheckStart() { @@ -188,10 +194,10 @@ public short getTypeCheckSlot() { return typeCheckSlot; } - public short[] getClosedWorldTypeCheckSlots() { + public short[] getClosedTypeWorldTypeCheckSlots() { assert SubstrateOptions.closedTypeWorld(); - assert closedWorldTypeCheckSlots != null; - return closedWorldTypeCheckSlots; + assert closedTypeWorldTypeCheckSlots != null; + return closedTypeWorldTypeCheckSlots; } public void setTypeIDDepth(int typeIDDepth) { @@ -209,9 +215,9 @@ public void setNumInterfaceTypes(int numInterfaceTypes) { this.numInterfaceTypes = numInterfaceTypes; } - public void setOpenWorldTypeIDSlots(int[] openWorldTypeIDSlots) { + public void setOpenTypeWorldTypeCheckSlots(int[] openTypeWorldTypeCheckSlots) { assert !SubstrateOptions.closedTypeWorld(); - this.openWorldTypeIDSlots = openWorldTypeIDSlots; + this.openTypeWorldTypeCheckSlots = openTypeWorldTypeCheckSlots; } public int getTypeIDDepth() { @@ -229,10 +235,10 @@ public int getNumInterfaceTypes() { return numInterfaceTypes; } - public int[] getOpenWorldTypeIDSlots() { + public int[] getOpenTypeWorldTypeCheckSlots() { assert !SubstrateOptions.closedTypeWorld(); - assert openWorldTypeIDSlots != null : this; - return openWorldTypeIDSlots; + assert openTypeWorldTypeCheckSlots != null : this; + return openTypeWorldTypeCheckSlots; } /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/KnownOffsetsFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/KnownOffsetsFeature.java index b86fc7f888c0..295c1ee8b831 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/KnownOffsetsFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/KnownOffsetsFeature.java @@ -67,7 +67,7 @@ public void beforeCompilation(BeforeCompilationAccess a) { DynamicHubLayout dynamicHubLayout = DynamicHubLayout.singleton(); int vtableBaseOffset = dynamicHubLayout.vTableOffset(); int vtableEntrySize = dynamicHubLayout.vTableSlotSize; - int typeIDSlotsOffset = SubstrateOptions.closedTypeWorld() ? dynamicHubLayout.getClosedWorldTypeCheckSlotsOffset() : -1; + int typeIDSlotsOffset = SubstrateOptions.closedTypeWorld() ? dynamicHubLayout.getClosedTypeWorldTypeCheckSlotsOffset() : -1; int componentHubOffset = findFieldOffset(access, DynamicHub.class, "componentType"); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/TypeCheckBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/TypeCheckBuilder.java index 542ac9e082fe..558328314ea7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/TypeCheckBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/TypeCheckBuilder.java @@ -109,7 +109,9 @@ * * The implementation of the open-world typecheck can be found in {@link OpenTypeWorldSnippets}. */ -public class TypeCheckBuilder { +public final class TypeCheckBuilder { + public static final int UNINITIALIZED_TYPECHECK_SLOTS = -1; + private static final int SLOT_CAPACITY = 1 << 16; private final HostedType objectType; @@ -134,7 +136,7 @@ public class TypeCheckBuilder { */ private final List heightOrderedTypes; - private int numTypeCheckSlots = -1; + private int numTypeCheckSlots = UNINITIALIZED_TYPECHECK_SLOTS; /** * Map created to describe the type hierarchy graph. @@ -153,7 +155,19 @@ public class TypeCheckBuilder { return result; }; - public TypeCheckBuilder(Collection types, HostedType objectType, HostedType cloneableType, HostedType serializableType) { + public static int buildTypeMetadata(HostedUniverse hUniverse, Collection types, HostedType objectType, HostedType cloneableType, HostedType serializableType) { + var builder = new TypeCheckBuilder(types, objectType, cloneableType, serializableType); + builder.buildTypeInformation(hUniverse); + if (SubstrateOptions.closedTypeWorld()) { + builder.calculateClosedTypeWorldTypeMetadata(); + return builder.getNumTypeCheckSlots(); + } else { + builder.calculateOpenTypeWorldTypeMetadata(); + return UNINITIALIZED_TYPECHECK_SLOTS; + } + } + + private TypeCheckBuilder(Collection types, HostedType objectType, HostedType cloneableType, HostedType serializableType) { this.allTypes = types; this.objectType = objectType; this.cloneableType = cloneableType; @@ -173,13 +187,13 @@ public TypeCheckBuilder(Collection types, HostedType objectType, Hos heightOrderedTypes = generateHeightOrder(allIncludedRoots, subtypeMap); } - public int getNumTypeCheckSlots() { - assert numTypeCheckSlots != -1; + private int getNumTypeCheckSlots() { + assert numTypeCheckSlots != UNINITIALIZED_TYPECHECK_SLOTS; return numTypeCheckSlots; } private void setNumTypeCheckSlots(int num) { - assert numTypeCheckSlots == -1; + assert numTypeCheckSlots == UNINITIALIZED_TYPECHECK_SLOTS; numTypeCheckSlots = num; } @@ -505,13 +519,13 @@ public void buildTypeInformation(HostedUniverse hUniverse) { /** * Calculates all of the needed type check id information and stores it in the HostedTypes. */ - public boolean calculateClosedWorldTypeMetadata() { + public boolean calculateClosedTypeWorldTypeMetadata() { ClassIDBuilder classBuilder = new ClassIDBuilder(objectType, allIncludedRoots, heightOrderedTypes, subtypeMap); classBuilder.computeSlots(); InterfaceIDBuilder interfaceBuilder = new InterfaceIDBuilder(classBuilder.numClassSlots, heightOrderedTypes, subtypeMap); interfaceBuilder.computeSlots(); - generateClosedWorldTypeMetadata(classBuilder, interfaceBuilder); - assert ClosedWorldTypeCheckValidator.compareTypeIDResults(heightOrderedTypes); + generateClosedTypeWorldTypeMetadata(classBuilder, interfaceBuilder); + assert ClosedTypeWorldTypeCheckValidator.compareTypeIDResults(heightOrderedTypes); return true; } @@ -519,7 +533,7 @@ public boolean calculateClosedWorldTypeMetadata() { * Combines the class and interface slots array into one array of shorts and sets this * information in the hosted type. */ - private void generateClosedWorldTypeMetadata(ClassIDBuilder classBuilder, InterfaceIDBuilder interfaceBuilder) { + private void generateClosedTypeWorldTypeMetadata(ClassIDBuilder classBuilder, InterfaceIDBuilder interfaceBuilder) { int numClassSlots = classBuilder.numClassSlots; int numSlots = numClassSlots + interfaceBuilder.numInterfaceSlots; setNumTypeCheckSlots(numSlots); @@ -540,7 +554,7 @@ private void generateClosedWorldTypeMetadata(ClassIDBuilder classBuilder, Interf } } - type.setClosedWorldTypeCheckSlots(typeCheckSlots); + type.setClosedTypeWorldTypeCheckSlots(typeCheckSlots); } } @@ -1799,7 +1813,7 @@ void calculateInterfaceIDs(Graph graph) { } } - private static final class ClosedWorldTypeCheckValidator { + private static final class ClosedTypeWorldTypeCheckValidator { static boolean compareTypeIDResults(List types) { if (!SubstrateOptions.DisableTypeIdResultVerification.getValue()) { @@ -1832,12 +1846,12 @@ static boolean runtimeIsAssignableFrom(HostedType superType, HostedType checkedT int typeCheckStart = Short.toUnsignedInt(superType.getTypeCheckStart()); int typeCheckRange = Short.toUnsignedInt(superType.getTypeCheckRange()); int typeCheckSlot = Short.toUnsignedInt(superType.getTypeCheckSlot()); - int checkedTypeID = Short.toUnsignedInt(checkedType.getClosedWorldTypeCheckSlots()[typeCheckSlot]); + int checkedTypeID = Short.toUnsignedInt(checkedType.getClosedTypeWorldTypeCheckSlots()[typeCheckSlot]); return UnsignedMath.belowThan(checkedTypeID - typeCheckStart, typeCheckRange); } } - private static class OpenWorldTypeInfo { + private static class OpenTypeWorldTypeInfo { /** * Within {@link com.oracle.svm.core.hub.DynamicHub} typecheck metadata the ids of the @@ -1867,15 +1881,15 @@ private void computeClassDisplay(int[] parent, int typeID) { } } - public void calculateOpenWorldTypeMetadata() { - Map typeInfoMap = new HashMap<>(); + public void calculateOpenTypeWorldTypeMetadata() { + Map typeInfoMap = new HashMap<>(); - BiFunction getTypeInfo = (type, allowMissing) -> { + BiFunction getTypeInfo = (type, allowMissing) -> { if (allowMissing) { - typeInfoMap.computeIfAbsent(type, (k) -> new OpenWorldTypeInfo()); + typeInfoMap.computeIfAbsent(type, (k) -> new OpenTypeWorldTypeInfo()); } - OpenWorldTypeInfo typeInfo = typeInfoMap.get(type); + OpenTypeWorldTypeInfo typeInfo = typeInfoMap.get(type); assert typeInfo != null; return typeInfo; }; @@ -1888,7 +1902,7 @@ public void calculateOpenWorldTypeMetadata() { } for (HostedType type : heightOrderedTypes) { - OpenWorldTypeInfo info = getTypeInfo.apply(type, false); + OpenTypeWorldTypeInfo info = getTypeInfo.apply(type, false); if (info.classDisplay == null) { assert isInterface(type); /* @@ -1924,39 +1938,42 @@ public void calculateOpenWorldTypeMetadata() { }); } - generateOpenWorldTypeMetadata(typeInfoMap); - assert OpenWorldTypeCheckValidator.compareTypeIDResults(heightOrderedTypes); + generateOpenTypeWorldTypeMetadata(typeInfoMap); + assert OpenTypeWorldTypeCheckValidator.compareTypeIDResults(heightOrderedTypes); } /** * Stores type check information in the HostedTypes. */ - private static void generateOpenWorldTypeMetadata(Map typeInfoMap) { + private static void generateOpenTypeWorldTypeMetadata(Map typeInfoMap) { for (var entry : typeInfoMap.entrySet()) { HostedType type = entry.getKey(); - OpenWorldTypeInfo info = entry.getValue(); + OpenTypeWorldTypeInfo info = entry.getValue(); int idDepth = isInterface(type) ? -1 : info.classDisplay.length - 1; if (idDepth >= 0) { assert info.classDisplay[idDepth] == type.typeID : String.format("Mismatch between class display and type. idDepth: %s typeID: %s info: %s ", idDepth, type.typeID, info); } + int numInterfaceTypes = info.implementedInterfaces.size(); - int[] interfaceTypes = info.implementedInterfaces.stream().mapToInt(HostedType::getTypeID).sorted().toArray(); + List orderedInterfaces = info.implementedInterfaces.stream().sorted(Comparator.comparingInt(HostedType::getTypeID)).toList(); + int[] interfaceTypeIDs = orderedInterfaces.stream().mapToInt(HostedType::getTypeID).toArray(); int numClassTypes = info.classDisplay.length; int[] typeIDSlots = new int[numClassTypes + numInterfaceTypes]; System.arraycopy(info.classDisplay, 0, typeIDSlots, 0, numClassTypes); - System.arraycopy(interfaceTypes, 0, typeIDSlots, numClassTypes, numInterfaceTypes); - type.setOpenWorldTypeIDSlots(typeIDSlots); + System.arraycopy(interfaceTypeIDs, 0, typeIDSlots, numClassTypes, numInterfaceTypes); + type.setOpenTypeWorldTypeCheckSlots(typeIDSlots); type.setTypeIDDepth(idDepth); type.setNumInterfaceTypes(numInterfaceTypes); type.setNumClassTypes(numClassTypes); + type.typeCheckInterfaceOrder = orderedInterfaces.toArray(HostedType.EMPTY_ARRAY); } } - private static final class OpenWorldTypeCheckValidator { + private static final class OpenTypeWorldTypeCheckValidator { static String printTypeInfo(HostedType type) { return String.format("%s%ntypeID: %s, typeDepth: %s, numClasses: %s, numInterfaces: %s%nslots: %s", type, type.getTypeID(), type.getTypeIDDepth(), type.getNumClassTypes(), - type.getNumInterfaceTypes(), Arrays.toString(type.getOpenWorldTypeIDSlots())); + type.getNumInterfaceTypes(), Arrays.toString(type.getOpenTypeWorldTypeCheckSlots())); } static boolean compareTypeIDResults(List types) { @@ -1989,7 +2006,7 @@ static boolean compareTypeIDResults(List types) { static boolean runtimeIsAssignableFrom(HostedType superType, HostedType checkedType) { int typeID = superType.getTypeID(); int typeIDDepth = superType.getTypeIDDepth(); - int[] typeIDSlots = checkedType.getOpenWorldTypeIDSlots(); + int[] typeIDSlots = checkedType.getOpenTypeWorldTypeCheckSlots(); int numClassTypes = checkedType.getNumClassTypes(); if (typeIDDepth >= 0) { // this is a class check diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java index 999c8ce66167..43a649ed9213 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java @@ -44,7 +44,6 @@ import java.util.function.Function; import java.util.stream.Collectors; -import org.graalvm.collections.Pair; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.function.CEntryPointLiteral; import org.graalvm.nativeimage.c.function.CFunction; @@ -118,7 +117,6 @@ public class UniverseBuilder { private final HostedMetaAccess hMetaAccess; private StrengthenGraphs strengthenGraphs; private final UnsupportedFeatures unsupportedFeatures; - private TypeCheckBuilder typeCheckBuilder; public UniverseBuilder(AnalysisUniverse aUniverse, AnalysisMetaAccess aMetaAccess, HostedUniverse hUniverse, HostedMetaAccess hMetaAccess, StrengthenGraphs strengthenGraphs, UnsupportedFeatures unsupportedFeatures) { @@ -179,24 +177,18 @@ public void build(DebugContext debug) { HostedType objectType = hUniverse.objectType(); HostedType cloneableType = hUniverse.types.get(aMetaAccess.lookupJavaType(Cloneable.class)); HostedType serializableType = hUniverse.types.get(aMetaAccess.lookupJavaType(Serializable.class)); - typeCheckBuilder = new TypeCheckBuilder(allTypes, objectType, cloneableType, serializableType); - typeCheckBuilder.buildTypeInformation(hUniverse); - if (SubstrateOptions.closedTypeWorld()) { - typeCheckBuilder.calculateClosedWorldTypeMetadata(); - } else { - typeCheckBuilder.calculateOpenWorldTypeMetadata(); - } + int numTypeCheckSlots = TypeCheckBuilder.buildTypeMetadata(hUniverse, allTypes, objectType, cloneableType, serializableType); collectDeclaredMethods(); collectMonitorFieldInfo(aUniverse.getBigbang()); ForkJoinTask profilingInformationBuildTask = ForkJoinTask.adapt(this::buildProfilingInformation).fork(); - layoutInstanceFields(); + layoutInstanceFields(numTypeCheckSlots); layoutStaticFields(); collectMethodImplementations(); - buildVTables(); + VTableBuilder.buildTables(hUniverse, hMetaAccess); buildHubs(); processFieldLocations(); @@ -423,10 +415,10 @@ public static boolean isKnownImmutableType(Class clazz) { return IMMUTABLE_TYPES.contains(clazz); } - private void layoutInstanceFields() { + private void layoutInstanceFields(int numTypeCheckSlots) { BitSet usedBytes = new BitSet(); usedBytes.set(0, ConfigurationValues.getObjectLayout().getFirstFieldOffset()); - layoutInstanceFields(hUniverse.getObjectClass(), new HostedField[0], usedBytes); + layoutInstanceFields(hUniverse.getObjectClass(), new HostedField[0], usedBytes, numTypeCheckSlots); } private static boolean mustReserveArrayFields(HostedInstanceClass clazz) { @@ -453,7 +445,7 @@ private static void reserveAtEnd(BitSet usedBytes, int size) { usedBytes.set(endOffset, endOffset + size); } - private void layoutInstanceFields(HostedInstanceClass clazz, HostedField[] superFields, BitSet usedBytes) { + private void layoutInstanceFields(HostedInstanceClass clazz, HostedField[] superFields, BitSet usedBytes, int numTypeCheckSlots) { ArrayList rawFields = new ArrayList<>(); ArrayList allFields = new ArrayList<>(); ObjectLayout layout = ConfigurationValues.getObjectLayout(); @@ -468,20 +460,21 @@ private void layoutInstanceFields(HostedInstanceClass clazz, HostedField[] super * Reserve the vtable and typeslots */ int intSize = layout.sizeInBytes(JavaKind.Int); - int afterVtableLengthOffset = dynamicHubLayout.getVTableLengthOffset() + intSize; + int afterVTableLengthOffset = dynamicHubLayout.getVTableLengthOffset() + intSize; /* * Reserve the extra memory that DynamicHub fields may use (at least the vtable length * field). */ - int fieldBytes = afterVtableLengthOffset - minimumFirstFieldOffset; + int fieldBytes = afterVTableLengthOffset - minimumFirstFieldOffset; assert fieldBytes >= intSize; reserve(usedBytes, minimumFirstFieldOffset, fieldBytes); if (SubstrateOptions.closedTypeWorld()) { /* Each type check id slot is 2 bytes. */ - int slotsSize = typeCheckBuilder.getNumTypeCheckSlots() * 2; - int typeIDSlotsBaseOffset = dynamicHubLayout.getClosedWorldTypeCheckSlotsOffset(); + assert numTypeCheckSlots != TypeCheckBuilder.UNINITIALIZED_TYPECHECK_SLOTS : "numTypeCheckSlots is uninitialized"; + int slotsSize = numTypeCheckSlots * 2; + int typeIDSlotsBaseOffset = dynamicHubLayout.getClosedTypeWorldTypeCheckSlotsOffset(); reserve(usedBytes, typeIDSlotsBaseOffset, slotsSize); firstInstanceFieldOffset = typeIDSlotsBaseOffset + slotsSize; } else { @@ -489,7 +482,7 @@ private void layoutInstanceFields(HostedInstanceClass clazz, HostedField[] super * In the open world we do not inline the type checks into the dynamic hub since the * typeIDSlots array will be of variable length. */ - firstInstanceFieldOffset = afterVtableLengthOffset; + firstInstanceFieldOffset = afterVTableLengthOffset; } } else if (mustReserveArrayFields(clazz)) { @@ -615,7 +608,7 @@ private void layoutInstanceFields(HostedInstanceClass clazz, HostedField[] super for (HostedType subClass : clazz.subTypes) { if (subClass.isInstanceClass()) { - layoutInstanceFields((HostedInstanceClass) subClass, clazz.instanceFieldsWithSuper, (BitSet) usedBytesInSubclasses.clone()); + layoutInstanceFields((HostedInstanceClass) subClass, clazz.instanceFieldsWithSuper, (BitSet) usedBytesInSubclasses.clone(), numTypeCheckSlots); } } } @@ -806,290 +799,6 @@ private void collectMethodImplementations() { } } - private void buildVTables() { - /* - * We want to pack the vtables as tight as possible, i.e., we want to avoid filler slots as - * much as possible. Filler slots are unavoidable because we use the vtable also for - * interface calls, i.e., an interface method needs a vtable index that is filled for all - * classes that implement that interface. - * - * Note that because of interface methods the same implementation method can be registered - * multiple times in the same vtable, with a different index used by different interface - * methods. - * - * The optimization goal is to reduce the overall number of vtable slots. To achieve a good - * result, we process types in three steps: 1) java.lang.Object, 2) interfaces, 3) classes. - */ - - /* - * The mutable vtables while this algorithm is running. Contains an ArrayList for each type, - * which is in the end converted to the vtable array. - */ - Map> vtablesMap = new HashMap<>(); - - /* - * A bit set of occupied vtable slots for each type. - */ - - Map usedSlotsMap = new HashMap<>(); - /* - * The set of vtable slots used for this method. Because of interfaces, one method can have - * multiple vtable slots. The assignment algorithm uses this table to find out if a suitable - * vtable index already exists for a method. - */ - Map> vtablesSlots = new HashMap<>(); - - for (HostedType type : hUniverse.getTypes()) { - vtablesMap.put(type, new ArrayList<>()); - BitSet initialBitSet = new BitSet(); - usedSlotsMap.put(type, initialBitSet); - } - - /* - * 1) Process java.lang.Object first because the methods defined there (equals, hashCode, - * toString, clone) are in every vtable. We must not have filler slots before these methods. - */ - HostedInstanceClass objectClass = hUniverse.getObjectClass(); - assignImplementations(objectClass, vtablesMap, usedSlotsMap, vtablesSlots); - - /* - * 2) Process interfaces. Interface methods have higher constraints on vtable slots because - * the same slots need to be used in all implementation classes, which can be spread out - * across the type hierarchy. We assign an importance level to each interface and then sort - * by that number, to further reduce the filler slots. - */ - List> interfaces = new ArrayList<>(); - for (HostedType type : hUniverse.getTypes()) { - if (type.isInterface()) { - /* - * We use the number of subtypes as the importance for an interface: If an interface - * is implemented often, then it can produce more unused filler slots than an - * interface implemented rarely. We do not multiply with the number of methods that - * the interface implements: there are usually no filler slots in between methods of - * an interface, i.e., an interface that declares many methods does not lead to more - * filler slots than an interface that defines only one method. - */ - int importance = collectSubtypes(type, new HashSet<>()).size(); - interfaces.add(Pair.create(type, importance)); - } - } - interfaces.sort((pair1, pair2) -> pair2.getRight() - pair1.getRight()); - for (Pair pair : interfaces) { - assignImplementations(pair.getLeft(), vtablesMap, usedSlotsMap, vtablesSlots); - } - - /* - * 3) Process all implementation classes, starting with java.lang.Object and going - * depth-first down the tree. - */ - buildVTable(objectClass, vtablesMap, usedSlotsMap, vtablesSlots); - - /* - * To avoid segfaults when jumping to address 0, all unused vtable entries are filled with a - * stub that reports a fatal error. - */ - HostedMethod invalidVTableEntryHandler = hMetaAccess.lookupJavaMethod(InvalidMethodPointerHandler.INVALID_VTABLE_ENTRY_HANDLER_METHOD); - - for (HostedType type : hUniverse.getTypes()) { - if (type.isArray()) { - type.vtable = objectClass.vtable; - } - if (type.vtable == null) { - assert type.isInterface() || type.isPrimitive(); - type.vtable = HostedMethod.EMPTY_ARRAY; - } - - HostedMethod[] vtableArray = type.vtable; - for (int i = 0; i < vtableArray.length; i++) { - if (vtableArray[i] == null) { - vtableArray[i] = invalidVTableEntryHandler; - } - } - } - - if (SubstrateUtil.assertionsEnabled()) { - /* Check that all vtable entries are the correctly resolved methods. */ - for (HostedType type : hUniverse.getTypes()) { - for (HostedMethod m : type.vtable) { - assert m == null || m.equals(invalidVTableEntryHandler) || m.equals(hUniverse.lookup(type.wrapped.resolveConcreteMethod(m.wrapped, type.wrapped))); - } - } - } - } - - /** Collects all subtypes of the provided type in the provided set. */ - private static Set collectSubtypes(HostedType type, Set allSubtypes) { - if (allSubtypes.add(type)) { - for (HostedType subtype : type.subTypes) { - collectSubtypes(subtype, allSubtypes); - } - } - return allSubtypes; - } - - private void buildVTable(HostedClass clazz, Map> vtablesMap, Map usedSlotsMap, Map> vtablesSlots) { - assignImplementations(clazz, vtablesMap, usedSlotsMap, vtablesSlots); - - ArrayList vtable = vtablesMap.get(clazz); - HostedMethod[] vtableArray = vtable.toArray(new HostedMethod[vtable.size()]); - assert vtableArray.length == 0 || vtableArray[vtableArray.length - 1] != null : "Unnecessary entry at end of vtable"; - clazz.vtable = vtableArray; - - for (HostedType subClass : clazz.subTypes) { - if (!subClass.isInterface() && !subClass.isArray()) { - buildVTable((HostedClass) subClass, vtablesMap, usedSlotsMap, vtablesSlots); - } - } - } - - private void assignImplementations(HostedType type, Map> vtablesMap, Map usedSlotsMap, Map> vtablesSlots) { - for (HostedMethod method : type.getAllDeclaredMethods()) { - /* We only need to look at methods that the static analysis registered as invoked. */ - if (method.wrapped.isInvoked() || method.wrapped.isImplementationInvoked()) { - /* - * Methods with 1 implementations do not need a vtable because invokes can be done - * as direct calls without the need for a vtable. Methods with 0 implementations are - * unreachable. - * - * Methods manually registered as virtual root methods always need a vtable slot, - * even if there are 0 or 1 implementations. - */ - if (method.implementations.length > 1 || method.wrapped.isVirtualRootMethod()) { - /* - * Find a suitable vtable slot for the method, taking the existing vtable - * assignments into account. - */ - int slot = findSlot(method, vtablesMap, usedSlotsMap, vtablesSlots); - method.vtableIndex = slot; - - /* Assign the vtable slot for the type and all subtypes. */ - assignImplementations(method.getDeclaringClass(), method, slot, vtablesMap); - } - } - } - } - - /** - * Assign the vtable slot to the correct resolved method for all subtypes. - */ - private void assignImplementations(HostedType type, HostedMethod method, int slot, Map> vtablesMap) { - if (type.wrapped.isInstantiated()) { - assert (type.isInstanceClass() && !type.isAbstract()) || type.isArray(); - - HostedMethod resolvedMethod = resolveMethod(type, method); - if (resolvedMethod != null) { - ArrayList vtable = vtablesMap.get(type); - if (slot < vtable.size() && vtable.get(slot) != null) { - /* We already have a vtable entry from a supertype. Check that it is correct. */ - assert vtable.get(slot).equals(resolvedMethod); - } else { - resize(vtable, slot + 1); - assert vtable.get(slot) == null; - vtable.set(slot, resolvedMethod); - } - resolvedMethod.vtableIndex = slot; - } - } - - for (HostedType subtype : type.subTypes) { - if (!subtype.isArray()) { - assignImplementations(subtype, method, slot, vtablesMap); - } - } - } - - private HostedMethod resolveMethod(HostedType type, HostedMethod method) { - AnalysisMethod resolved = type.wrapped.resolveConcreteMethod(method.wrapped, type.wrapped); - if (resolved == null || !resolved.isImplementationInvoked()) { - return null; - } else { - assert !resolved.isAbstract(); - return hUniverse.lookup(resolved); - } - } - - private static void resize(ArrayList list, int minSize) { - list.ensureCapacity(minSize); - while (list.size() < minSize) { - list.add(null); - } - } - - private int findSlot(HostedMethod method, Map> vtablesMap, Map usedSlotsMap, Map> vtablesSlots) { - /* - * Check if all implementation methods already have a common slot assigned. Each - * implementation method can have multiple slots because of interfaces. We compute the - * intersection of the slot sets for all implementation methods. - */ - if (method.implementations.length > 0) { - Set resultSlots = vtablesSlots.get(method.implementations[0]); - for (HostedMethod impl : method.implementations) { - Set implSlots = vtablesSlots.get(impl); - if (implSlots == null) { - resultSlots = null; - break; - } - resultSlots.retainAll(implSlots); - } - if (resultSlots != null && !resultSlots.isEmpty()) { - /* - * All implementations already have the same vtable slot assigned, so we can re-use - * that. If we have multiple candidates, we use the slot with the lowest number. - */ - int resultSlot = Integer.MAX_VALUE; - for (int slot : resultSlots) { - resultSlot = Math.min(resultSlot, slot); - } - return resultSlot; - } - } - /* - * No slot found, we need to compute a new one. Check the whole subtype hierarchy for - * constraints using bitset union, and then use the lowest slot number that is available in - * all subtypes. - */ - BitSet usedSlots = new BitSet(); - collectUsedSlots(method.getDeclaringClass(), usedSlots, usedSlotsMap); - for (HostedMethod impl : method.implementations) { - collectUsedSlots(impl.getDeclaringClass(), usedSlots, usedSlotsMap); - } - - /* - * The new slot number is the lowest slot number not occupied by any subtype, i.e., the - * lowest index not set in the union bitset. - */ - int resultSlot = usedSlots.nextClearBit(0); - - markSlotAsUsed(resultSlot, method.getDeclaringClass(), vtablesMap, usedSlotsMap); - for (HostedMethod impl : method.implementations) { - markSlotAsUsed(resultSlot, impl.getDeclaringClass(), vtablesMap, usedSlotsMap); - - vtablesSlots.computeIfAbsent(impl, k -> new HashSet<>()).add(resultSlot); - } - - return resultSlot; - } - - private void collectUsedSlots(HostedType type, BitSet usedSlots, Map usedSlotsMap) { - usedSlots.or(usedSlotsMap.get(type)); - for (HostedType sub : type.subTypes) { - if (!sub.isArray()) { - collectUsedSlots(sub, usedSlots, usedSlotsMap); - } - } - } - - private void markSlotAsUsed(int resultSlot, HostedType type, Map> vtablesMap, Map usedSlotsMap) { - assert resultSlot >= vtablesMap.get(type).size() || vtablesMap.get(type).get(resultSlot) == null; - - usedSlotsMap.get(type).set(resultSlot); - for (HostedType sub : type.subTypes) { - if (!sub.isArray()) { - markSlotAsUsed(resultSlot, sub, vtablesMap, usedSlotsMap); - } - } - } - private void buildHubs() { InstanceReferenceMapEncoder referenceMapEncoder = new InstanceReferenceMapEncoder(); Map referenceMaps = new HashMap<>(); @@ -1099,10 +808,11 @@ private void buildHubs() { referenceMaps.put(type, referenceMap); referenceMapEncoder.add(referenceMap); } - ImageSingletons.lookup(DynamicHubSupport.class).setData(referenceMapEncoder.encodeAll()); + ImageSingletons.lookup(DynamicHubSupport.class).setReferenceMapEncoding(referenceMapEncoder.encodeAll()); ObjectLayout ol = ConfigurationValues.getObjectLayout(); DynamicHubLayout dynamicHubLayout = DynamicHubLayout.singleton(); + for (HostedType type : hUniverse.getTypes()) { hUniverse.hostVM().recordActivity(); @@ -1143,19 +853,6 @@ private void buildHubs() { throw VMError.shouldNotReachHereUnexpectedInput(type); // ExcludeFromJacocoGeneratedReport } - /* - * The vtable entry values are available only after the code cache layout is fixed, so - * leave them 0. - */ - CFunctionPointer[] vtable = new CFunctionPointer[type.vtable.length]; - for (int idx = 0; idx < type.vtable.length; idx++) { - /* - * We install a CodePointer in the vtable; when generating relocation info, we will - * know these point into .text - */ - vtable[idx] = new MethodPointer(type.vtable[idx]); - } - // pointer maps in Dynamic Hub ReferenceMapEncoder.Input referenceMap = referenceMaps.get(type); assert referenceMap != null; @@ -1167,12 +864,58 @@ private void buildHubs() { hub.setSharedData(layoutHelper, monitorOffset, identityHashOffset, referenceMapIndex, type.isInstantiated(), canInstantiateAsInstance, s.isRegisteredForSerialization(type.getJavaClass())); + if (SubstrateOptions.closedTypeWorld()) { - hub.setClosedWorldData(vtable, type.getTypeID(), type.getTypeCheckStart(), type.getTypeCheckRange(), - type.getTypeCheckSlot(), type.getClosedWorldTypeCheckSlots()); + CFunctionPointer[] vtable = new CFunctionPointer[type.closedTypeWorldVTable.length]; + for (int idx = 0; idx < type.closedTypeWorldVTable.length; idx++) { + /* + * We install a CodePointer in the vtable; when generating relocation info, we + * will know these point into .text + */ + vtable[idx] = new MethodPointer(type.closedTypeWorldVTable[idx]); + } + hub.setClosedTypeWorldData(vtable, type.getTypeID(), type.getTypeCheckStart(), type.getTypeCheckRange(), + type.getTypeCheckSlot(), type.getClosedTypeWorldTypeCheckSlots()); } else { - hub.setOpenWorldData(vtable, type.getTypeID(), - type.getTypeIDDepth(), type.getNumClassTypes(), type.getNumInterfaceTypes(), type.getOpenWorldTypeIDSlots()); + + /* + * Within the open type world, interface type checks are two entries long and + * contain information about both the implemented interface ids as well as their + * itable starting offset within the dispatch table. + */ + int numClassTypes = type.getNumClassTypes(); + int[] openTypeWorldTypeCheckSlots = new int[numClassTypes + (type.getNumInterfaceTypes() * 2)]; + System.arraycopy(type.openTypeWorldTypeCheckSlots, 0, openTypeWorldTypeCheckSlots, 0, numClassTypes); + int typeSlotIdx = numClassTypes; + for (int interfaceIdx = 0; interfaceIdx < type.numInterfaceTypes; interfaceIdx++) { + int typeID = type.getOpenTypeWorldTypeCheckSlots()[numClassTypes + interfaceIdx]; + int itableStartingOffset; + if (type.itableStartingOffsets.length > 0) { + itableStartingOffset = type.itableStartingOffsets[interfaceIdx]; + } else { + itableStartingOffset = 0xBADD0D1D; + } + openTypeWorldTypeCheckSlots[typeSlotIdx] = typeID; + /* + * We directly encode the offset of the itable within the DynamicHub to limit + * the amount of arithmetic needed to be performed at runtime. + */ + int itableDynamicHubOffset = dynamicHubLayout.vTableOffset() + (itableStartingOffset * dynamicHubLayout.vTableSlotSize); + openTypeWorldTypeCheckSlots[typeSlotIdx + 1] = itableDynamicHubOffset; + typeSlotIdx += 2; + } + + CFunctionPointer[] vtable = new CFunctionPointer[type.openTypeWorldDispatchTables.length]; + for (int idx = 0; idx < type.openTypeWorldDispatchTables.length; idx++) { + /* + * We install a CodePointer in the open world vtable; when generating relocation + * info, we will know these point into .text + */ + vtable[idx] = new MethodPointer(type.openTypeWorldDispatchTables[idx]); + } + + hub.setOpenTypeWorldData(vtable, type.getTypeID(), + type.getTypeIDDepth(), type.getNumClassTypes(), type.getNumInterfaceTypes(), openTypeWorldTypeCheckSlots); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/VTableBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/VTableBuilder.java new file mode 100644 index 000000000000..d410a80790cd --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/VTableBuilder.java @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.meta; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.graalvm.collections.Pair; + +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.svm.core.InvalidMethodPointerHandler; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.SubstrateUtil; + +import jdk.graal.compiler.debug.Assertions; + +public final class VTableBuilder { + private final HostedUniverse hUniverse; + private final HostedMetaAccess hMetaAccess; + + private VTableBuilder(HostedUniverse hUniverse, HostedMetaAccess hMetaAccess) { + this.hUniverse = hUniverse; + this.hMetaAccess = hMetaAccess; + + } + + public static void buildTables(HostedUniverse hUniverse, HostedMetaAccess hMetaAccess) { + VTableBuilder builder = new VTableBuilder(hUniverse, hMetaAccess); + if (SubstrateOptions.closedTypeWorld()) { + builder.buildClosedTypeWorldVTables(); + } else { + builder.buildOpenTypeWorldDispatchTables(); + assert builder.verifyOpenTypeWorldDispatchTables(); + } + } + + private boolean verifyOpenTypeWorldDispatchTables() { + HostedMethod invalidVTableEntryHandler = hMetaAccess.lookupJavaMethod(InvalidMethodPointerHandler.INVALID_VTABLE_ENTRY_HANDLER_METHOD); + for (HostedType type : hUniverse.getTypes()) { + if (type.isInterface() || type.isAbstract()) { + // don't need to check these types - they don't have/need real dynamic hubs + continue; + } + + for (int i = 0; i < type.openTypeWorldDispatchTables.length; i++) { + HostedMethod method = type.openTypeWorldDispatchTables[i]; + if (method.equals(invalidVTableEntryHandler)) { + continue; + } + + // retrieve method from open world + if (method.getDeclaringClass().isInterface()) { + int interfaceTypeID = method.getDeclaringClass().getTypeID(); + int[] typeCheckSlots = type.getOpenTypeWorldTypeCheckSlots(); + boolean found = false; + for (int itableIdx = 0; itableIdx < type.getNumInterfaceTypes(); itableIdx++) { + if (typeCheckSlots[type.getNumClassTypes() + itableIdx] == interfaceTypeID) { + HostedMethod dispatchResult = type.openTypeWorldDispatchTables[type.itableStartingOffsets[itableIdx] + method.getVTableIndex()]; + assert dispatchResult.equals(method) : Assertions.errorMessage(method, dispatchResult); + found = true; + break; + } + } + assert found : Assertions.errorMessage(method, type); + } else { + /* + * The class vtable starts at position 0 within the openTypeWorldDispatchTables, + * so it is unnecessary to check the itableStartingOffset. + */ + HostedMethod openTypeWorldMethod = type.openTypeWorldDispatchTables[method.getVTableIndex()]; + assert openTypeWorldMethod.equals(method) : Assertions.errorMessage(method, openTypeWorldMethod); + } + } + + } + return true; + } + + private static List generateITable(HostedType type) { + return generateDispatchTable(type, 0); + } + + private static List generateDispatchTable(HostedType type, int startingIndex) { + var table = Arrays.stream(type.getAllDeclaredMethods()).filter(method -> { + if (method.wrapped.isInvoked() || method.wrapped.isImplementationInvoked()) { + if (method.implementations.length >= 1 || method.wrapped.isVirtualRootMethod()) { + return true; + } + } + return false; + }).toList(); + + int index = startingIndex; + for (HostedMethod method : table) { + assert method.vtableIndex == -1 : method.vtableIndex; + method.vtableIndex = index; + index++; + } + + return table; + } + + private void generateOpenTypeWorldDispatchTable(HostedInstanceClass type, Map> dispatchTablesMap, HostedMethod invalidDispatchTableEntryHandler) { + var superClass = type.getSuperclass(); + List parentClassTable = superClass == null ? List.of() : dispatchTablesMap.get(superClass); + List classTableWithoutSuper = generateDispatchTable(type, parentClassTable.size()); + List resultClassTableMethods; + if (!classTableWithoutSuper.isEmpty()) { + resultClassTableMethods = new ArrayList<>(parentClassTable); + resultClassTableMethods.addAll(classTableWithoutSuper); + } else { + /* + * If the type doesn't declare any new methods, then we can use the parent's class + * table. + */ + resultClassTableMethods = parentClassTable; + } + dispatchTablesMap.put(type, resultClassTableMethods); + + if (!type.isAbstract()) { + // create concrete dispatch classes + List aggregatedTable = new ArrayList<>(resultClassTableMethods); + HostedType[] interfaces = type.typeCheckInterfaceOrder; + type.itableStartingOffsets = new int[interfaces.length]; + int currentITableOffset = resultClassTableMethods.size(); + for (int i = 0; i < interfaces.length; i++) { + HostedType interfaceType = interfaces[i]; + List interfaceMethods = dispatchTablesMap.get(interfaceType); + + type.itableStartingOffsets[i] = currentITableOffset; + aggregatedTable.addAll(interfaceMethods); + currentITableOffset += interfaceMethods.size(); + } + type.openTypeWorldDispatchTables = new HostedMethod[aggregatedTable.size()]; + for (int i = 0; i < aggregatedTable.size(); i++) { + HostedMethod method = aggregatedTable.get(i); + /* + * To avoid segfaults when jumping to address 0, all unused dispatch table entries + * are filled with a stub that reports a fatal error. + */ + if (type.isInstantiated()) { + var resolvedMethod = (HostedMethod) type.resolveConcreteMethod(method, type); + type.openTypeWorldDispatchTables[i] = resolvedMethod == null ? invalidDispatchTableEntryHandler : resolvedMethod; + } else { + type.openTypeWorldDispatchTables[i] = invalidDispatchTableEntryHandler; + } + } + } + + for (HostedType subType : type.subTypes) { + if (subType instanceof HostedInstanceClass instanceClass) { + generateOpenTypeWorldDispatchTable(instanceClass, dispatchTablesMap, invalidDispatchTableEntryHandler); + } + } + } + + private void buildOpenTypeWorldDispatchTables() { + Map> dispatchTablesMap = new HashMap<>(); + + for (HostedType type : hUniverse.getTypes()) { + /* + * Each interface has its own dispatch table. These can be directly determined via + * looking at their declared methods. + */ + if (type.isInterface()) { + dispatchTablesMap.put(type, generateITable(type)); + } + } + + HostedMethod invalidDispatchTableEntryHandler = hMetaAccess.lookupJavaMethod(InvalidMethodPointerHandler.INVALID_VTABLE_ENTRY_HANDLER_METHOD); + generateOpenTypeWorldDispatchTable((HostedInstanceClass) hUniverse.objectType(), dispatchTablesMap, invalidDispatchTableEntryHandler); + + int[] emptyITableOffsets = new int[0]; + var objectType = hUniverse.getObjectClass(); + for (HostedType type : hUniverse.getTypes()) { + if (type.isArray()) { + type.openTypeWorldDispatchTables = objectType.openTypeWorldDispatchTables; + type.itableStartingOffsets = objectType.itableStartingOffsets; + } + if (type.openTypeWorldDispatchTables == null) { + assert type.isInterface() || type.isPrimitive() || type.isAbstract(); + type.openTypeWorldDispatchTables = HostedMethod.EMPTY_ARRAY; + type.itableStartingOffsets = emptyITableOffsets; + } + } + } + + private void buildClosedTypeWorldVTables() { + /* + * We want to pack the vtables as tight as possible, i.e., we want to avoid filler slots as + * much as possible. Filler slots are unavoidable because we use the vtable also for + * interface calls, i.e., an interface method needs a vtable index that is filled for all + * classes that implement that interface. + * + * Note that because of interface methods the same implementation method can be registered + * multiple times in the same vtable, with a different index used by different interface + * methods. + * + * The optimization goal is to reduce the overall number of vtable slots. To achieve a good + * result, we process types in three steps: 1) java.lang.Object, 2) interfaces, 3) classes. + */ + + /* + * The mutable vtables while this algorithm is running. Contains an ArrayList for each type, + * which is in the end converted to the vtable array. + */ + Map> vtablesMap = new HashMap<>(); + + /* + * A bit set of occupied vtable slots for each type. + */ + + Map usedSlotsMap = new HashMap<>(); + /* + * The set of vtable slots used for this method. Because of interfaces, one method can have + * multiple vtable slots. The assignment algorithm uses this table to find out if a suitable + * vtable index already exists for a method. + */ + Map> vtablesSlots = new HashMap<>(); + + for (HostedType type : hUniverse.getTypes()) { + vtablesMap.put(type, new ArrayList<>()); + BitSet initialBitSet = new BitSet(); + usedSlotsMap.put(type, initialBitSet); + } + + /* + * 1) Process java.lang.Object first because the methods defined there (equals, hashCode, + * toString, clone) are in every vtable. We must not have filler slots before these methods. + */ + HostedInstanceClass objectClass = hUniverse.getObjectClass(); + assignImplementations(objectClass, vtablesMap, usedSlotsMap, vtablesSlots); + + /* + * 2) Process interfaces. Interface methods have higher constraints on vtable slots because + * the same slots need to be used in all implementation classes, which can be spread out + * across the type hierarchy. We assign an importance level to each interface and then sort + * by that number, to further reduce the filler slots. + */ + List> interfaces = new ArrayList<>(); + for (HostedType type : hUniverse.getTypes()) { + if (type.isInterface()) { + /* + * We use the number of subtypes as the importance for an interface: If an interface + * is implemented often, then it can produce more unused filler slots than an + * interface implemented rarely. We do not multiply with the number of methods that + * the interface implements: there are usually no filler slots in between methods of + * an interface, i.e., an interface that declares many methods does not lead to more + * filler slots than an interface that defines only one method. + */ + int importance = collectSubtypes(type, new HashSet<>()).size(); + interfaces.add(Pair.create(type, importance)); + } + } + interfaces.sort((pair1, pair2) -> pair2.getRight() - pair1.getRight()); + for (Pair pair : interfaces) { + assignImplementations(pair.getLeft(), vtablesMap, usedSlotsMap, vtablesSlots); + } + + /* + * 3) Process all implementation classes, starting with java.lang.Object and going + * depth-first down the tree. + */ + buildVTable(objectClass, vtablesMap, usedSlotsMap, vtablesSlots); + + /* + * To avoid segfaults when jumping to address 0, all unused vtable entries are filled with a + * stub that reports a fatal error. + */ + HostedMethod invalidVTableEntryHandler = hMetaAccess.lookupJavaMethod(InvalidMethodPointerHandler.INVALID_VTABLE_ENTRY_HANDLER_METHOD); + + for (HostedType type : hUniverse.getTypes()) { + if (type.isArray()) { + type.closedTypeWorldVTable = objectClass.closedTypeWorldVTable; + } + if (type.closedTypeWorldVTable == null) { + assert type.isInterface() || type.isPrimitive(); + type.closedTypeWorldVTable = HostedMethod.EMPTY_ARRAY; + } + + HostedMethod[] vtableArray = type.closedTypeWorldVTable; + for (int i = 0; i < vtableArray.length; i++) { + if (vtableArray[i] == null) { + vtableArray[i] = invalidVTableEntryHandler; + } + } + } + + if (SubstrateUtil.assertionsEnabled()) { + /* Check that all vtable entries are the correctly resolved methods. */ + for (HostedType type : hUniverse.getTypes()) { + for (HostedMethod m : type.closedTypeWorldVTable) { + assert m.equals(invalidVTableEntryHandler) || m.equals(hUniverse.lookup(type.wrapped.resolveConcreteMethod(m.wrapped, type.wrapped))); + } + } + } + } + + /** Collects all subtypes of the provided type in the provided set. */ + private static Set collectSubtypes(HostedType type, Set allSubtypes) { + if (allSubtypes.add(type)) { + for (HostedType subtype : type.subTypes) { + collectSubtypes(subtype, allSubtypes); + } + } + return allSubtypes; + } + + private void buildVTable(HostedClass clazz, Map> vtablesMap, Map usedSlotsMap, Map> vtablesSlots) { + assignImplementations(clazz, vtablesMap, usedSlotsMap, vtablesSlots); + + ArrayList vtable = vtablesMap.get(clazz); + HostedMethod[] vtableArray = vtable.toArray(new HostedMethod[vtable.size()]); + assert vtableArray.length == 0 || vtableArray[vtableArray.length - 1] != null : "Unnecessary entry at end of vtable"; + clazz.closedTypeWorldVTable = vtableArray; + + for (HostedType subClass : clazz.subTypes) { + if (!subClass.isInterface() && !subClass.isArray()) { + buildVTable((HostedClass) subClass, vtablesMap, usedSlotsMap, vtablesSlots); + } + } + } + + private void assignImplementations(HostedType type, Map> vtablesMap, Map usedSlotsMap, Map> vtablesSlots) { + for (HostedMethod method : type.getAllDeclaredMethods()) { + /* We only need to look at methods that the static analysis registered as invoked. */ + if (method.wrapped.isInvoked() || method.wrapped.isImplementationInvoked()) { + /* + * Methods with 1 implementations do not need a vtable because invokes can be done + * as direct calls without the need for a vtable. Methods with 0 implementations are + * unreachable. + * + * Methods manually registered as virtual root methods always need a vtable slot, + * even if there are 0 or 1 implementations. + */ + if (method.implementations.length > 1 || method.wrapped.isVirtualRootMethod()) { + /* + * Find a suitable vtable slot for the method, taking the existing vtable + * assignments into account. + */ + int slot = findSlot(method, vtablesMap, usedSlotsMap, vtablesSlots); + method.vtableIndex = slot; + + /* Assign the vtable slot for the type and all subtypes. */ + assignImplementations(method.getDeclaringClass(), method, slot, vtablesMap); + } + } + } + } + + /** + * Assign the vtable slot to the correct resolved method for all subtypes. + */ + private void assignImplementations(HostedType type, HostedMethod method, int slot, Map> vtablesMap) { + if (type.wrapped.isInstantiated()) { + assert (type.isInstanceClass() && !type.isAbstract()) || type.isArray(); + + HostedMethod resolvedMethod = resolveMethod(type, method); + if (resolvedMethod != null) { + ArrayList vtable = vtablesMap.get(type); + if (slot < vtable.size() && vtable.get(slot) != null) { + /* We already have a vtable entry from a supertype. Check that it is correct. */ + assert vtable.get(slot).equals(resolvedMethod); + } else { + resize(vtable, slot + 1); + assert vtable.get(slot) == null; + vtable.set(slot, resolvedMethod); + } + resolvedMethod.vtableIndex = slot; + } + } + + for (HostedType subtype : type.subTypes) { + if (!subtype.isArray()) { + assignImplementations(subtype, method, slot, vtablesMap); + } + } + } + + private HostedMethod resolveMethod(HostedType type, HostedMethod method) { + AnalysisMethod resolved = type.wrapped.resolveConcreteMethod(method.wrapped, type.wrapped); + if (resolved == null || !resolved.isImplementationInvoked()) { + return null; + } else { + assert !resolved.isAbstract(); + return hUniverse.lookup(resolved); + } + } + + private static void resize(ArrayList list, int minSize) { + list.ensureCapacity(minSize); + while (list.size() < minSize) { + list.add(null); + } + } + + private int findSlot(HostedMethod method, Map> vtablesMap, Map usedSlotsMap, Map> vtablesSlots) { + /* + * Check if all implementation methods already have a common slot assigned. Each + * implementation method can have multiple slots because of interfaces. We compute the + * intersection of the slot sets for all implementation methods. + */ + if (method.implementations.length > 0) { + Set resultSlots = vtablesSlots.get(method.implementations[0]); + for (HostedMethod impl : method.implementations) { + Set implSlots = vtablesSlots.get(impl); + if (implSlots == null) { + resultSlots = null; + break; + } + resultSlots.retainAll(implSlots); + } + if (resultSlots != null && !resultSlots.isEmpty()) { + /* + * All implementations already have the same vtable slot assigned, so we can re-use + * that. If we have multiple candidates, we use the slot with the lowest number. + */ + int resultSlot = Integer.MAX_VALUE; + for (int slot : resultSlots) { + resultSlot = Math.min(resultSlot, slot); + } + return resultSlot; + } + } + /* + * No slot found, we need to compute a new one. Check the whole subtype hierarchy for + * constraints using bitset union, and then use the lowest slot number that is available in + * all subtypes. + */ + BitSet usedSlots = new BitSet(); + collectUsedSlots(method.getDeclaringClass(), usedSlots, usedSlotsMap); + for (HostedMethod impl : method.implementations) { + collectUsedSlots(impl.getDeclaringClass(), usedSlots, usedSlotsMap); + } + + /* + * The new slot number is the lowest slot number not occupied by any subtype, i.e., the + * lowest index not set in the union bitset. + */ + int resultSlot = usedSlots.nextClearBit(0); + + markSlotAsUsed(resultSlot, method.getDeclaringClass(), vtablesMap, usedSlotsMap); + for (HostedMethod impl : method.implementations) { + markSlotAsUsed(resultSlot, impl.getDeclaringClass(), vtablesMap, usedSlotsMap); + + vtablesSlots.computeIfAbsent(impl, k -> new HashSet<>()).add(resultSlot); + } + + return resultSlot; + } + + private void collectUsedSlots(HostedType type, BitSet usedSlots, Map usedSlotsMap) { + usedSlots.or(usedSlotsMap.get(type)); + for (HostedType sub : type.subTypes) { + if (!sub.isArray()) { + collectUsedSlots(sub, usedSlots, usedSlotsMap); + } + } + } + + private void markSlotAsUsed(int resultSlot, HostedType type, Map> vtablesMap, Map usedSlotsMap) { + assert resultSlot >= vtablesMap.get(type).size() || vtablesMap.get(type).get(resultSlot) == null; + + usedSlotsMap.get(type).set(resultSlot); + for (HostedType sub : type.subTypes) { + if (!sub.isArray()) { + markSlotAsUsed(resultSlot, sub, vtablesMap, usedSlotsMap); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java index d67e8bf1db30..c5db5554fb69 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java @@ -48,6 +48,7 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.core.ParsingReason; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.configure.ConfigurationFile; @@ -80,6 +81,7 @@ import com.oracle.svm.hosted.config.ConfigurationParserUtils; import com.oracle.svm.hosted.meta.HostedField; import com.oracle.svm.hosted.meta.HostedMetaAccess; +import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.hosted.snippets.ReflectionPlugins; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; import com.oracle.svm.util.ModuleSupport; @@ -309,6 +311,9 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { * to see SubstrateMethodAccessor.vtableOffset before we register the transformer. */ access.registerFieldValueTransformer(ReflectionUtil.lookupField(SubstrateMethodAccessor.class, "vtableOffset"), new ComputeVTableOffset()); + if (!SubstrateOptions.closedTypeWorld()) { + access.registerFieldValueTransformer(ReflectionUtil.lookupField(SubstrateMethodAccessor.class, "interfaceTypeID"), new ComputeInterfaceTypeID()); + } /* Make sure array classes don't need to be registered for reflection. */ RuntimeReflection.register(Object.class.getDeclaredMethods()); @@ -445,12 +450,36 @@ public ValueAvailability valueAvailability() { @Override public Object transform(Object receiver, Object originalValue) { SubstrateMethodAccessor accessor = (SubstrateMethodAccessor) receiver; + if (accessor.getVTableOffset() == SubstrateMethodAccessor.OFFSET_NOT_YET_COMPUTED) { SharedMethod member = ImageSingletons.lookup(ReflectionFeature.class).hostedMetaAccess().lookupJavaMethod(accessor.getMember()); - return KnownOffsets.singleton().getVTableOffset(member.getVTableIndex()); + if (SubstrateOptions.closedTypeWorld()) { + return KnownOffsets.singleton().getVTableOffset(member.getVTableIndex(), true); + } else { + return KnownOffsets.singleton().getVTableOffset(member.getVTableIndex(), false); + } } else { VMError.guarantee(accessor.getVTableOffset() == SubstrateMethodAccessor.STATICALLY_BOUND); return accessor.getVTableOffset(); } } } + +final class ComputeInterfaceTypeID implements FieldValueTransformerWithAvailability { + @Override + public ValueAvailability valueAvailability() { + return ValueAvailability.AfterAnalysis; + } + + @Override + public Object transform(Object receiver, Object originalValue) { + SubstrateMethodAccessor accessor = (SubstrateMethodAccessor) receiver; + VMError.guarantee(accessor.getInterfaceTypeID() == SubstrateMethodAccessor.OFFSET_NOT_YET_COMPUTED); + + HostedMethod member = ImageSingletons.lookup(ReflectionFeature.class).hostedMetaAccess().lookupJavaMethod(accessor.getMember()); + if (member.getDeclaringClass().isInterface()) { + return member.getDeclaringClass().getTypeID(); + } + return SubstrateMethodAccessor.INTERFACE_TYPEID_CLASS_TABLE; + } +}