From 16412b38dda7b07d8e88a6e7d7eb34388552c22c Mon Sep 17 00:00:00 2001 From: Tom Shull Date: Thu, 22 Sep 2022 18:15:19 +0200 Subject: [PATCH] Introduce MultiMethod concept. --- substratevm/mx.substratevm/suite.py | 1 + .../oracle/svm/common/meta/MultiMethod.java | 67 +++++++ .../svm/hosted/code/CompilationInfo.java | 36 +--- .../oracle/svm/hosted/code/CompileQueue.java | 52 +++--- .../code/SubstrateCompilationDirectives.java | 17 +- .../hosted/image/LIRNativeImageCodeCache.java | 2 +- .../image/NativeImageDebugInfoProvider.java | 2 +- .../oracle/svm/hosted/meta/HostedMethod.java | 169 +++++++++++++++--- .../svm/hosted/meta/HostedUniverse.java | 16 +- .../svm/hosted/meta/UniverseBuilder.java | 12 +- 10 files changed, 270 insertions(+), 104 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/meta/MultiMethod.java diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 79e302aa14cd..ee5085a22e55 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -1569,6 +1569,7 @@ "name" : "org.graalvm.nativeimage.base", "exports" : [ "com.oracle.svm.util to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.llvm,org.graalvm.nativeimage.agent.jvmtibase,org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.agent.diagnostics,org.graalvm.nativeimage.junitsupport,com.oracle.svm.svm_enterprise", + "com.oracle.svm.common.meta to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder", "com.oracle.svm.common.option to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.driver", ], } diff --git a/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/meta/MultiMethod.java b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/meta/MultiMethod.java new file mode 100644 index 000000000000..1df765f6d609 --- /dev/null +++ b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/meta/MultiMethod.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022, 2022, 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.common.meta; + +import java.util.Collection; + +/** + * Multi-methods can have multiple implementations of the original Java method. This functionality + * is used to create the different variants needed for different compilation scenarios. + */ +public interface MultiMethod { + + /** + * Key for accessing a multi-method. + */ + interface MultiMethodKey { + } + + MultiMethodKey ORIGINAL_METHOD = new MultiMethodKey() { + @Override + public String toString() { + return "Original_Method_Key"; + } + }; + + /** + * Each method is assigned a unique key which denotes the purpose of the method. + */ + MultiMethodKey getMultiMethodKey(); + + /** + * @return method implementation with the requested key, creating it if necessary. + */ + MultiMethod getOrCreateMultiMethod(MultiMethodKey key); + + /** + * @return method implementation with the requested key, or null if it does not exist. + */ + MultiMethod getMultiMethod(MultiMethodKey key); + + /** + * @return all implementations of this method. + */ + Collection getAllMultiMethods(); +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CompilationInfo.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CompilationInfo.java index 12d1abb065be..a04bc881a620 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CompilationInfo.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CompilationInfo.java @@ -35,6 +35,7 @@ import org.graalvm.compiler.options.OptionValues; import com.oracle.graal.pointsto.flow.AnalysisParsedGraph; +import com.oracle.svm.common.meta.MultiMethod; import com.oracle.svm.core.deopt.DeoptTest; import com.oracle.svm.core.deopt.Specialize; import com.oracle.svm.hosted.code.CompileQueue.CompileFunction; @@ -65,20 +66,6 @@ public class CompilationInfo { */ protected ConstantNode[] specializedArguments; - /** - * A link to the deoptimization target method if this method can deoptimize. - */ - protected HostedMethod deoptTarget; - - /** - * A link to the regular compiled method if this method is a deoptimization target. - * - * Note that it is important that this field is final: the {@link HostedMethod#getName() method - * name} depends on this field (to distinguish a regular method from a deoptimization target - * method), so mutating this field would mutate the name of a method. - */ - protected final HostedMethod deoptOrigin; - /* Custom parsing and compilation code that is executed instead of that of CompileQueue */ protected ParseFunction customParseFunction; protected CompileFunction customCompileFunction; @@ -95,22 +82,13 @@ public class CompilationInfo { protected final AtomicLong numVirtualCalls = new AtomicLong(); protected final AtomicLong numEntryPointCalls = new AtomicLong(); - public CompilationInfo(HostedMethod method, HostedMethod deoptOrigin) { + public CompilationInfo(HostedMethod method) { this.method = method; - this.deoptOrigin = deoptOrigin; - - if (deoptOrigin != null) { - assert deoptOrigin.compilationInfo.deoptTarget == null; - deoptOrigin.compilationInfo.deoptTarget = method; - } - } - - public boolean isDeoptTarget() { - return deoptOrigin != null; } public boolean isDeoptEntry(int bci, boolean duringCall, boolean rethrowException) { - return isDeoptTarget() && (deoptOrigin.compilationInfo.canDeoptForTesting || SubstrateCompilationDirectives.singleton().isDeoptEntry(method, bci, duringCall, rethrowException)); + return method.isDeoptTarget() && (method.getMultiMethod(MultiMethod.ORIGINAL_METHOD).compilationInfo.canDeoptForTesting || + SubstrateCompilationDirectives.singleton().isDeoptEntry(method, bci, duringCall, rethrowException)); } /** @@ -118,7 +96,7 @@ public boolean isDeoptEntry(int bci, boolean duringCall, boolean rethrowExceptio * {@link SubstrateCompilationDirectives#registerDeoptEntry}. */ public boolean isRegisteredDeoptEntry(int bci, boolean duringCall, boolean rethrowException) { - return isDeoptTarget() && SubstrateCompilationDirectives.singleton().isDeoptTarget(method) && + return method.isDeoptTarget() && SubstrateCompilationDirectives.singleton().isDeoptTarget(method) && SubstrateCompilationDirectives.singleton().isDeoptEntry(method, bci, duringCall, rethrowException); } @@ -126,10 +104,6 @@ public boolean canDeoptForTesting() { return canDeoptForTesting; } - public HostedMethod getDeoptTargetMethod() { - return deoptTarget; - } - public CompilationGraph getCompilationGraph() { return compilationGraph; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CompileQueue.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CompileQueue.java index 08436c68fbdd..3225916a20fa 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CompileQueue.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CompileQueue.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.hosted.code; +import static com.oracle.svm.hosted.code.SubstrateCompilationDirectives.DEOPT_TARGET_METHOD; + import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; @@ -32,7 +34,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Objects; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -102,9 +103,10 @@ import com.oracle.graal.pointsto.phases.SubstrateIntrinsicGraphBuilder; import com.oracle.graal.pointsto.util.CompletionExecutor; import com.oracle.graal.pointsto.util.CompletionExecutor.DebugContextRunnable; +import com.oracle.svm.common.meta.MultiMethod; import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.SubstrateOptions.OptimizationLevel; +import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.deopt.DeoptTest; import com.oracle.svm.core.deopt.Specialize; import com.oracle.svm.core.graal.code.SubstrateBackend; @@ -511,18 +513,19 @@ private void printMethodHistogram() { CompilationResult result = task.result; CompilationInfo ci = method.compilationInfo; - if (!ci.isDeoptTarget()) { + if (!method.isDeoptTarget()) { numberOfMethods += 1; sizeAllMethods += result.getTargetCodeSize(); System.out.format("%8d; %5d; %5d; %5d; %s;", result.getTargetCodeSize(), ci.numNodesAfterParsing, ci.numNodesBeforeCompilation, ci.numNodesAfterCompilation, ci.isTrivialMethod ? "T" : " "); int deoptMethodSize = 0; - if (ci.deoptTarget != null) { - CompilationInfo dci = ci.deoptTarget.compilationInfo; + HostedMethod deoptTargetMethod = method.getMultiMethod(DEOPT_TARGET_METHOD); + if (deoptTargetMethod != null) { + CompilationInfo dci = deoptTargetMethod.compilationInfo; numberOfDeopt += 1; - deoptMethodSize = compilations.get(ci.deoptTarget).result.getTargetCodeSize(); + deoptMethodSize = compilations.get(deoptTargetMethod).result.getTargetCodeSize(); sizeDeoptMethods += deoptMethodSize; sizeDeoptMethodsInNonDeopt += result.getTargetCodeSize(); totalNumDeoptEntryPoints += dci.numDeoptEntryPoints; @@ -607,7 +610,7 @@ private void parseDeoptimizationTargetMethods() { */ universe.getMethods().stream() .filter(method -> SubstrateCompilationDirectives.singleton().isDeoptTarget(method)) - .forEach(method -> ensureParsed(universe.createDeoptTarget(method), null, new EntryPointReason())); + .forEach(method -> ensureParsed(method.getOrCreateMultiMethod(DEOPT_TARGET_METHOD), null, new EntryPointReason())); /* * Deoptimization target code for deoptimization testing: all methods that are not @@ -616,12 +619,10 @@ private void parseDeoptimizationTargetMethods() { */ universe.getMethods().stream() .filter(method -> method.getWrapped().isImplementationInvoked() && DeoptimizationUtils.canDeoptForTesting(universe, method, deoptimizeAll)) - .forEach(this::ensureParsedForDeoptTesting); - } - - private void ensureParsedForDeoptTesting(HostedMethod method) { - method.compilationInfo.canDeoptForTesting = true; - ensureParsed(universe.createDeoptTarget(method), null, new EntryPointReason()); + .forEach(method -> { + method.compilationInfo.canDeoptForTesting = true; + ensureParsed(method.getOrCreateMultiMethod(DEOPT_TARGET_METHOD), null, new EntryPointReason()); + }); } private static boolean checkTrivial(HostedMethod method, StructuredGraph graph) { @@ -643,12 +644,15 @@ protected void inlineTrivialMethods(DebugContext debug) throws InterruptedExcept try (Indent ignored = debug.logAndIndent("==== Trivial Inlining round %d\n", round)) { executor.init(); - universe.getMethods().stream() - .filter(method -> method.compilationInfo.getCompilationGraph() != null) - .forEach(method -> executor.execute(new TrivialInlineTask(method))); - universe.getMethods().stream() - .map(method -> method.compilationInfo.getDeoptTargetMethod()).filter(Objects::nonNull) - .forEach(deoptTargetMethod -> executor.execute(new TrivialInlineTask(deoptTargetMethod))); + universe.getMethods().forEach(method -> { + assert method.getMultiMethodKey() == MultiMethod.ORIGINAL_METHOD; + for (MultiMethod multiMethod : method.getAllMultiMethods()) { + HostedMethod hMethod = (HostedMethod) multiMethod; + if (hMethod.compilationInfo.getCompilationGraph() != null) { + executor.execute(new TrivialInlineTask(hMethod)); + } + } + }); executor.start(); executor.complete(); executor.shutdown(); @@ -799,7 +803,7 @@ public void scheduleEntryPoints() { ensureCompiled(impl, new EntryPointReason()); } } - HostedMethod deoptTargetMethod = method.compilationInfo.getDeoptTargetMethod(); + HostedMethod deoptTargetMethod = method.getMultiMethod(DEOPT_TARGET_METHOD); if (deoptTargetMethod != null) { ensureCompiled(deoptTargetMethod, new EntryPointReason()); } @@ -1011,7 +1015,7 @@ protected boolean canBeUsedForInlining(Invoke invoke) { } private static void handleSpecialization(final HostedMethod method, CallTargetNode targetNode, HostedMethod invokeTarget, HostedMethod invokeImplementation) { - if (method.getAnnotation(Specialize.class) != null && !method.compilationInfo.isDeoptTarget() && invokeTarget.getAnnotation(DeoptTest.class) != null) { + if (method.getAnnotation(Specialize.class) != null && !method.isDeoptTarget() && invokeTarget.getAnnotation(DeoptTest.class) != null) { /* * Collect the constant arguments to a method which should be specialized. */ @@ -1138,8 +1142,8 @@ private CompilationResult defaultCompileFunction(DebugContext debug, HostedMetho .filter(invoke -> method.compilationInfo.isDeoptEntry(invoke.bci(), true, false)) .count(); - Suites suites = method.compilationInfo.isDeoptTarget() ? deoptTargetSuites : regularSuites; - LIRSuites lirSuites = method.compilationInfo.isDeoptTarget() ? deoptTargetLIRSuites : regularLIRSuites; + Suites suites = method.isDeoptTarget() ? deoptTargetSuites : regularSuites; + LIRSuites lirSuites = method.isDeoptTarget() ? deoptTargetLIRSuites : regularLIRSuites; CompilationResult result = backend.newCompilationResult(compilationIdentifier, method.format("%H.%n(%p)")); @@ -1149,7 +1153,7 @@ private CompilationResult defaultCompileFunction(DebugContext debug, HostedMetho } method.compilationInfo.numNodesAfterCompilation = graph.getNodeCount(); - if (method.compilationInfo.isDeoptTarget()) { + if (method.isDeoptTarget()) { assert DeoptimizationUtils.verifyDeoptTarget(method, graph, result); } ensureCalleesCompiled(method, reason, result); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SubstrateCompilationDirectives.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SubstrateCompilationDirectives.java index 8cfe4c96af4c..7cab61d50608 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SubstrateCompilationDirectives.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SubstrateCompilationDirectives.java @@ -33,6 +33,7 @@ import org.graalvm.nativeimage.ImageSingletons; import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.svm.common.meta.MultiMethod; import com.oracle.svm.core.code.FrameInfoEncoder; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.util.VMError; @@ -44,6 +45,20 @@ @AutomaticallyRegisteredImageSingleton public class SubstrateCompilationDirectives { + public static final MultiMethod.MultiMethodKey DEOPT_TARGET_METHOD = new MultiMethod.MultiMethodKey() { + @Override + public String toString() { + return "Deopt_Target_Method_Key"; + } + }; + + public static final MultiMethod.MultiMethodKey RUNTIME_COMPILED_METHOD = new MultiMethod.MultiMethodKey() { + @Override + public String toString() { + return "Runtime_Compiled_Method_Key"; + } + }; + /** * Stores the value kinds present at a deoptimization point's (deoptimization source) * FrameState. This information is used to validate the deoptimization point's target @@ -180,7 +195,7 @@ public boolean isDeoptTarget(ResolvedJavaMethod method) { return deoptEntries.containsKey(toAnalysisMethod(method)); } - protected boolean isDeoptEntry(ResolvedJavaMethod method, int bci, boolean duringCall, boolean rethrowException) { + public boolean isDeoptEntry(ResolvedJavaMethod method, int bci, boolean duringCall, boolean rethrowException) { assert seal(); Map bciMap = deoptEntries.get(toAnalysisMethod(method)); assert bciMap != null : "can only query for deopt entries for methods registered as deopt targets"; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/LIRNativeImageCodeCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/LIRNativeImageCodeCache.java index ea30cbee121b..a8a9f3519364 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/LIRNativeImageCodeCache.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/LIRNativeImageCodeCache.java @@ -190,7 +190,7 @@ public void layoutMethods(DebugContext debug, BigBang bb, ForkJoinPool threadPoo * Need to have snapshot of trampoline key set since we update their * positions. */ - for (HostedMethod callTarget : trampolines.keySet().toArray(new HostedMethod[0])) { + for (HostedMethod callTarget : trampolines.keySet().toArray(HostedMethod.EMPTY_ARRAY)) { position = NumUtil.roundUp(position, trampolineSupport.getTrampolineAlignment()); trampolines.put(callTarget, position); sortedTrampolines.add(Pair.create(callTarget, position)); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java index 31943a101269..498c24f9950d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java @@ -1081,7 +1081,7 @@ public boolean isDeoptTarget() { if (method instanceof HostedMethod) { return ((HostedMethod) method).isDeoptTarget(); } - return name().endsWith(HostedMethod.METHOD_NAME_DEOPT_SUFFIX); + return name().endsWith(HostedMethod.MULTI_METHOD_KEY_SEPARATOR); } @Override 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 17bc3f0b63fb..e1ac35f8daf8 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 @@ -26,14 +26,27 @@ import static com.oracle.svm.core.util.VMError.shouldNotReachHere; import static com.oracle.svm.core.util.VMError.unimplemented; +import static com.oracle.svm.hosted.code.SubstrateCompilationDirectives.DEOPT_TARGET_METHOD; +import static com.oracle.svm.hosted.code.SubstrateCompilationDirectives.RUNTIME_COMPILED_METHOD; import java.lang.annotation.Annotation; import java.lang.reflect.Executable; import java.lang.reflect.Type; - +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; + +import org.graalvm.collections.Pair; import org.graalvm.compiler.debug.DebugContext; import org.graalvm.compiler.debug.JavaMethodContext; import org.graalvm.compiler.nodes.StructuredGraph; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.infrastructure.GraphProvider; @@ -42,9 +55,12 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.HostedProviders; import com.oracle.graal.pointsto.results.StaticAnalysisResults; +import com.oracle.svm.common.meta.MultiMethod; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.deopt.Deoptimizer; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.graal.code.StubCallingConvention; import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.core.meta.SharedMethod; @@ -66,10 +82,10 @@ import jdk.vm.ci.meta.Signature; import jdk.vm.ci.meta.SpeculationLog; -public final class HostedMethod implements SharedMethod, WrappedJavaMethod, GraphProvider, JavaMethodContext, OriginalMethodProvider { +public final class HostedMethod implements SharedMethod, WrappedJavaMethod, GraphProvider, JavaMethodContext, OriginalMethodProvider, MultiMethod { - public static final String METHOD_NAME_COLLISION_SUFFIX = "*"; - public static final String METHOD_NAME_DEOPT_SUFFIX = "**"; + public static final String METHOD_NAME_COLLISION_SEPARATOR = "*"; + public static final String MULTI_METHOD_KEY_SEPARATOR = "**"; public final AnalysisMethod wrapped; @@ -100,19 +116,52 @@ public final class HostedMethod implements SharedMethod, WrappedJavaMethod, Grap private final String name; private final String uniqueShortName; - public static HostedMethod create(HostedUniverse universe, AnalysisMethod wrapped, HostedType holder, Signature signature, - ConstantPool constantPool, ExceptionHandler[] handlers, HostedMethod deoptOrigin) { + private final MultiMethodKey multiMethodKey; + + /** + * Map from a key to the corresponding implementation. All multi-method implementations for a + * given Java method share the same map. This allows one to easily switch between different + * implementations when needed. When {@code multiMethodMap} is null, then + * {@link #multiMethodKey} points to {@link #ORIGINAL_METHOD} and no other implementations exist + * for the method. This is done to reduce the memory overhead in the common case when only this + * one implementation is present. + */ + private volatile Map multiMethodMap; + + @SuppressWarnings("rawtypes") // + private static final AtomicReferenceFieldUpdater MULTIMETHOD_MAP_UPDATER = AtomicReferenceFieldUpdater.newUpdater(HostedMethod.class, Map.class, + "multiMethodMap"); + + public static final HostedMethod[] EMPTY_ARRAY = new HostedMethod[0]; + + static HostedMethod create(HostedUniverse universe, AnalysisMethod wrapped, HostedType holder, Signature signature, + ConstantPool constantPool, ExceptionHandler[] handlers) { LocalVariableTable localVariableTable = createLocalVariableTable(universe, wrapped); - String name = deoptOrigin != null ? wrapped.getName() + METHOD_NAME_DEOPT_SUFFIX : wrapped.getName(); - String uniqueShortName = SubstrateUtil.uniqueShortName(holder.getJavaClass().getClassLoader(), holder, name, signature, wrapped.isConstructor()); - int collisionCount = universe.uniqueHostedMethodNames.merge(uniqueShortName, 0, (oldValue, value) -> oldValue + 1); - if (collisionCount > 0) { - name = name + METHOD_NAME_COLLISION_SUFFIX + collisionCount; - String fixedUniqueShortName = SubstrateUtil.uniqueShortName(holder.getJavaClass().getClassLoader(), holder, name, signature, wrapped.isConstructor()); - VMError.guarantee(!fixedUniqueShortName.equals(uniqueShortName), "failed to generate uniqueShortName for HostedMethod created for " + wrapped); - uniqueShortName = fixedUniqueShortName; - } - return new HostedMethod(wrapped, holder, signature, constantPool, handlers, deoptOrigin, name, uniqueShortName, localVariableTable); + + return create0(wrapped, holder, signature, constantPool, handlers, ORIGINAL_METHOD, null, localVariableTable); + } + + private static HostedMethod create0(AnalysisMethod wrapped, HostedType holder, Signature signature, + ConstantPool constantPool, ExceptionHandler[] handlers, MultiMethodKey key, Map multiMethodMap, LocalVariableTable localVariableTable) { + assert key == ORIGINAL_METHOD || key == DEOPT_TARGET_METHOD; + assert !(multiMethodMap == null && key != ORIGINAL_METHOD); + + Function> nameGenerator = (collisionCount) -> { + String name = wrapped.getName(); + if (key != ORIGINAL_METHOD) { + name += MULTI_METHOD_KEY_SEPARATOR + key; + } + if (collisionCount > 0) { + name = name + METHOD_NAME_COLLISION_SEPARATOR + collisionCount; + } + String uniqueShortName = SubstrateUtil.uniqueShortName(holder.getJavaClass().getClassLoader(), holder, name, signature, wrapped.isConstructor()); + + return Pair.create(name, uniqueShortName); + }; + + Pair names = ImageSingletons.lookup(HostedMethodNameFactory.class).createNames(nameGenerator); + + return new HostedMethod(wrapped, holder, signature, constantPool, handlers, names.getLeft(), names.getRight(), localVariableTable, key, multiMethodMap); } private static LocalVariableTable createLocalVariableTable(HostedUniverse universe, AnalysisMethod wrapped) { @@ -139,16 +188,19 @@ private static LocalVariableTable createLocalVariableTable(HostedUniverse univer } private HostedMethod(AnalysisMethod wrapped, HostedType holder, Signature signature, ConstantPool constantPool, - ExceptionHandler[] handlers, HostedMethod deoptOrigin, String name, String uniqueShortName, LocalVariableTable localVariableTable) { + ExceptionHandler[] handlers, String name, String uniqueShortName, LocalVariableTable localVariableTable, MultiMethodKey multiMethodKey, + Map multiMethodMap) { this.wrapped = wrapped; this.holder = holder; this.signature = signature; this.constantPool = constantPool; this.handlers = handlers; - this.compilationInfo = new CompilationInfo(this, deoptOrigin); + this.compilationInfo = new CompilationInfo(this); this.localVariableTable = localVariableTable; this.name = name; this.uniqueShortName = uniqueShortName; + this.multiMethodKey = multiMethodKey; + this.multiMethodMap = multiMethodMap; } @Override @@ -215,14 +267,11 @@ public int getCodeOffsetInImage() { @Override public int getDeoptOffsetInImage() { - HostedMethod deoptTarget = compilationInfo.getDeoptTargetMethod(); int result = 0; + HostedMethod deoptTarget = getMultiMethod(DEOPT_TARGET_METHOD); if (deoptTarget != null && deoptTarget.isCodeAddressOffsetValid()) { result = deoptTarget.getCodeAddressOffset(); assert result != 0; - } else if (compilationInfo.isDeoptTarget()) { - result = getCodeAddressOffset(); - assert result != 0; } return result; } @@ -239,12 +288,12 @@ public Parameter[] getParameters() { @Override public boolean isDeoptTarget() { - return compilationInfo.isDeoptTarget(); + return multiMethodKey == DEOPT_TARGET_METHOD; } @Override public boolean canDeoptimize() { - return compilationInfo.canDeoptForTesting(); + return compilationInfo.canDeoptForTesting() || multiMethodKey == RUNTIME_COMPILED_METHOD; } public boolean hasVTableIndex() { @@ -464,4 +513,76 @@ public int hashCode() { public Executable getJavaMethod() { return OriginalMethodProvider.getJavaMethod(getDeclaringClass().universe.getSnippetReflection(), wrapped); } + + @Override + public MultiMethodKey getMultiMethodKey() { + return multiMethodKey; + } + + @Override + public HostedMethod getOrCreateMultiMethod(MultiMethodKey key) { + if (key == multiMethodKey) { + return this; + } + + if (multiMethodMap == null) { + ConcurrentHashMap newMultiMethodMap = new ConcurrentHashMap<>(); + newMultiMethodMap.put(multiMethodKey, this); + MULTIMETHOD_MAP_UPDATER.compareAndSet(this, null, newMultiMethodMap); + } + + return (HostedMethod) multiMethodMap.computeIfAbsent(key, (k) -> { + HostedMethod newMultiMethod = create0(wrapped, holder, signature, constantPool, handlers, k, multiMethodMap, localVariableTable); + newMultiMethod.staticAnalysisResults = staticAnalysisResults; + return newMultiMethod; + }); + } + + @Override + public HostedMethod getMultiMethod(MultiMethodKey key) { + if (key == multiMethodKey) { + return this; + } else if (multiMethodMap == null) { + return null; + } else { + return (HostedMethod) multiMethodMap.get(key); + } + } + + @Override + public Collection getAllMultiMethods() { + if (multiMethodMap == null) { + return Collections.singleton(this); + } else { + return multiMethodMap.values(); + } + } +} + +@Platforms(Platform.HOSTED_ONLY.class) +@AutomaticallyRegisteredFeature +class HostedMethodNameFactory implements InternalFeature { + Map methodNameCount = new ConcurrentHashMap<>(); + Set uniqueShortNames = ConcurrentHashMap.newKeySet(); + + Pair createNames(Function> nameGenerator) { + Pair result = nameGenerator.apply(0); + + int collisionCount = methodNameCount.merge(result.getRight(), 0, (oldValue, value) -> oldValue + 1); + + if (collisionCount != 0) { + result = nameGenerator.apply(collisionCount); + } + + boolean added = uniqueShortNames.add(result.getRight()); + VMError.guarantee(added, "failed to generate uniqueShortName for HostedMethod: " + result.getRight()); + + return result; + } + + @Override + public void afterCompilation(AfterCompilationAccess access) { + methodNameCount = null; + uniqueShortNames = null; + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedUniverse.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedUniverse.java index e28e5c8b76d7..4fc1fd0af0fc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedUniverse.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedUniverse.java @@ -33,7 +33,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; import org.graalvm.compiler.nodes.StructuredGraph; @@ -294,8 +293,6 @@ public class HostedUniverse implements Universe { protected List orderedFields; protected List orderedMethods; - Map uniqueHostedMethodNames = new ConcurrentHashMap<>(); - public HostedUniverse(Inflation bb) { this.bb = bb; } @@ -311,17 +308,6 @@ public HostedInstanceClass getObjectClass() { return result; } - public synchronized HostedMethod createDeoptTarget(HostedMethod deoptOrigin) { - assert !deoptOrigin.isDeoptTarget(); - if (deoptOrigin.compilationInfo.getDeoptTargetMethod() == null) { - HostedMethod deoptTarget = HostedMethod.create(this, deoptOrigin.getWrapped(), deoptOrigin.getDeclaringClass(), - deoptOrigin.getSignature(), deoptOrigin.getConstantPool(), deoptOrigin.getExceptionHandlers(), deoptOrigin); - assert deoptOrigin.staticAnalysisResults != null; - deoptTarget.staticAnalysisResults = deoptOrigin.staticAnalysisResults; - } - return deoptOrigin.compilationInfo.getDeoptTargetMethod(); - } - public boolean contains(JavaType type) { return types.containsKey(type); } @@ -539,7 +525,7 @@ public int compare(HostedMethod o1, HostedMethod o2) { * executed, and we do not want a deoptimization target as the first method (because * offset 0 means no deoptimization target available). */ - int result = Boolean.compare(o1.compilationInfo.isDeoptTarget(), o2.compilationInfo.isDeoptTarget()); + int result = Boolean.compare(o1.isDeoptTarget(), o2.isDeoptTarget()); if (result != 0) { return result; } 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 93c80f18595c..3971d8f5f96c 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 @@ -172,7 +172,6 @@ public void build(DebugContext debug) { processFieldLocations(); - hUniverse.uniqueHostedMethodNames.clear(); hUniverse.orderedMethods = new ArrayList<>(hUniverse.methods.values()); Collections.sort(hUniverse.orderedMethods, HostedUniverse.METHOD_COMPARATOR); hUniverse.orderedFields = new ArrayList<>(hUniverse.fields.values()); @@ -287,9 +286,9 @@ private void makeMethod(AnalysisMethod aMethod) { sHandlers[i] = new ExceptionHandler(h.getStartBCI(), h.getEndBCI(), h.getHandlerBCI(), h.catchTypeCPI(), catchType); } - HostedMethod sMethod = HostedMethod.create(hUniverse, aMethod, holder, signature, constantPool, sHandlers, null); + HostedMethod hMethod = HostedMethod.create(hUniverse, aMethod, holder, signature, constantPool, sHandlers); assert !hUniverse.methods.containsKey(aMethod); - hUniverse.methods.put(aMethod, sMethod); + hUniverse.methods.put(aMethod, hMethod); boolean isCFunction = aMethod.getAnnotation(CFunction.class) != null; boolean hasCFunctionOptions = aMethod.getAnnotation(CFunctionOptions.class) != null; @@ -626,14 +625,13 @@ private void collectDeclaredMethods() { list.add(method); } - HostedMethod[] noMethods = new HostedMethod[0]; for (HostedType type : hUniverse.getTypes()) { List list = methodsOfType[type.getTypeID()]; if (list != null) { Collections.sort(list, HostedUniverse.METHOD_COMPARATOR); - type.allDeclaredMethods = list.toArray(new HostedMethod[list.size()]); + type.allDeclaredMethods = list.toArray(HostedMethod.EMPTY_ARRAY); } else { - type.allDeclaredMethods = noMethods; + type.allDeclaredMethods = HostedMethod.EMPTY_ARRAY; } } } @@ -737,7 +735,7 @@ private void buildVTables() { } if (type.vtable == null) { assert type.isInterface() || type.isPrimitive(); - type.vtable = new HostedMethod[0]; + type.vtable = HostedMethod.EMPTY_ARRAY; } HostedMethod[] vtableArray = type.vtable;