From b3f1660fd41406870b00dff510140de198bbc435 Mon Sep 17 00:00:00 2001 From: Tom Shull Date: Thu, 30 Nov 2023 11:43:08 +0100 Subject: [PATCH] cleanup (truffle) runtime compilation hierarchy --- .../graal/pointsto/flow/MethodTypeFlow.java | 4 +- .../hosted/ParseOnceDeoptTestFeature.java | 253 ---- .../hosted/RuntimeCompilationFeature.java | 730 ----------- .../runtimecompilation/CallTreeInfo.java | 337 +++++ .../GraalGraphObjectReplacer.java | 2 +- .../RuntimeCompilationCandidate.java | 63 + .../RuntimeCompilationFeature.java} | 1098 ++++++----------- .../RuntimeCompiledMethod.java | 80 ++ .../RuntimeCompiledMethodSupport.java | 570 +++++++++ .../SubstrateGraalCompilerSetup.java | 2 +- .../SubstrateProviders.java | 2 +- .../graal/isolated/IsolateAwareProviders.java | 2 +- .../substitutions/GraalSubstitutions.java | 2 +- .../svm/hosted/NativeImageGenerator.java | 16 +- ....java => RuntimeCompilationCallbacks.java} | 5 +- .../hosted/analysis/SVMParsingSupport.java | 7 +- .../svm/truffle/TruffleBaseFeature.java | 6 +- .../oracle/svm/truffle/TruffleFeature.java | 69 +- .../oracle/svm/truffle/TruffleSupport.java | 18 +- 19 files changed, 1532 insertions(+), 1734 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/ParseOnceDeoptTestFeature.java delete mode 100644 substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/RuntimeCompilationFeature.java create mode 100644 substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/CallTreeInfo.java rename substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/{ => runtimecompilation}/GraalGraphObjectReplacer.java (99%) create mode 100644 substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationCandidate.java rename substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/{ParseOnceRuntimeCompilationFeature.java => runtimecompilation/RuntimeCompilationFeature.java} (52%) create mode 100644 substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompiledMethod.java create mode 100644 substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompiledMethodSupport.java rename substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/{ => runtimecompilation}/SubstrateGraalCompilerSetup.java (98%) rename substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/{ => runtimecompilation}/SubstrateProviders.java (98%) rename substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/{RuntimeCompilationSupport.java => RuntimeCompilationCallbacks.java} (89%) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java index be8af5a17052..e840e5e016ce 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java @@ -180,7 +180,7 @@ private synchronized void createFlowsGraph(PointsToAnalysis bb, InvokeTypeFlow r initFlowsGraph(bb, builder.postInitFlows); } catch (Throwable t) { - /* Wrap all other errors as parsing errors. */ + /* Wrap all errors as parsing errors. */ throw AnalysisError.parsingError(method, t); } } @@ -342,7 +342,7 @@ public synchronized boolean updateFlowsGraph(PointsToAnalysis bb, MethodFlowsGra } } } catch (Throwable t) { - /* Wrap all other errors as parsing errors. */ + /* Wrap all errors as parsing errors. */ throw AnalysisError.parsingError(method, t); } return true; diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/ParseOnceDeoptTestFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/ParseOnceDeoptTestFeature.java deleted file mode 100644 index aa9d43fe94f8..000000000000 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/ParseOnceDeoptTestFeature.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright (c) 2023, 2023, 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.graal.hosted; - -import static com.oracle.svm.common.meta.MultiMethod.DEOPT_TARGET_METHOD; -import static com.oracle.svm.common.meta.MultiMethod.ORIGINAL_METHOD; - -import java.util.Collection; -import java.util.List; -import java.util.function.Function; -import java.util.function.Supplier; - -import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.nodes.StructuredGraph; -import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.hosted.Feature; - -import com.oracle.graal.pointsto.BigBang; -import com.oracle.graal.pointsto.PointsToAnalysis; -import com.oracle.graal.pointsto.api.HostVM; -import com.oracle.graal.pointsto.flow.InvokeTypeFlow; -import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.meta.HostedProviders; -import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; -import com.oracle.graal.pointsto.phases.InlineBeforeAnalysisPolicy; -import com.oracle.svm.common.meta.MultiMethod; -import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.graal.snippets.DeoptTester; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.SVMHost; -import com.oracle.svm.hosted.analysis.SVMParsingSupport; -import com.oracle.svm.hosted.code.DeoptimizationUtils; -import com.oracle.svm.hosted.phases.InlineBeforeAnalysisPolicyUtils; - -import jdk.vm.ci.meta.ResolvedJavaType; - -/** - * For Deoptimization Testing we create two versions of candidate methods: - *
    - *
  1. The "original" method which can deoptimize
  2. - *
  3. A deoptimization target method to deoptimize to
  4. - *
- * - * This is different than our runtime compilation support, as in that environment also a runtime - * compilation version for methods are created. In addition, in that environment "original" methods - * are not allowed to deoptimize. - */ -public class ParseOnceDeoptTestFeature implements InternalFeature { - @Override - public List> getRequiredFeatures() { - return List.of(DeoptimizationFeature.class); - } - - @Override - public void afterRegistration(AfterRegistrationAccess access) { - ImageSingletons.add(SVMParsingSupport.class, new DeoptTestingParsingSupport()); - ImageSingletons.add(HostVM.MultiMethodAnalysisPolicy.class, new DeoptTestingAnalysisPolicy()); - } - - private class DeoptTestingParsingSupport implements SVMParsingSupport { - - @Override - public Object parseGraph(BigBang bb, DebugContext debug, AnalysisMethod method) { - /* Regular parsing is always used. */ - return HostVM.PARSING_UNHANDLED; - } - - @Override - public GraphBuilderConfiguration updateGraphBuilderConfiguration(GraphBuilderConfiguration config, AnalysisMethod method) { - if (method.isDeoptTarget()) { - /* - * Local variables are never retained to help ensure the state of the deoptimization - * source will always be a superset of the deoptimization target. - */ - return config.withRetainLocalVariables(false); - } - return config; - } - - @Override - public boolean validateGraph(PointsToAnalysis bb, StructuredGraph graph) { - PointsToAnalysisMethod aMethod = (PointsToAnalysisMethod) graph.method(); - Supplier graphInvalidator = DeoptimizationUtils.createGraphInvalidator(graph); - if (aMethod.isDeoptTarget()) { - return !graphInvalidator.get(); - } else { - boolean canDeoptForTesting = aMethod.isOriginalMethod() && - DeoptimizationUtils.canDeoptForTesting(aMethod, DeoptTester.enabled(), graphInvalidator); - if (canDeoptForTesting) { - DeoptimizationUtils.registerDeoptEntriesForDeoptTesting(bb, graph, aMethod); - } - } - - return true; - } - - @Override - public boolean allowAssumptions(AnalysisMethod method) { - /* Assumptions are not allowed it AOT compiled methods */ - return false; - } - - @Override - public boolean recordInlinedMethods(AnalysisMethod method) { - return false; - } - - @Override - public HostedProviders getHostedProviders(MultiMethod.MultiMethodKey key) { - /* The buildtime providers are always used. */ - return null; - } - - @Override - public void initializeInlineBeforeAnalysisPolicy(SVMHost svmHost, InlineBeforeAnalysisPolicyUtils inliningUtils) { - /* We do not use a custom analysis policy for deopt testing. */ - } - - /** - * Currently we do not support inlining before analysis during deopt testing. More work is - * needed to support this. - */ - @Override - public InlineBeforeAnalysisPolicy inlineBeforeAnalysisPolicy(MultiMethod.MultiMethodKey multiMethodKey, InlineBeforeAnalysisPolicy defaultPolicy) { - if (multiMethodKey == ORIGINAL_METHOD) { - /* - * Since we can deopt from original methods, we do not inline here. Doing so would - * require us to track the flows into these inlined methods. - */ - return InlineBeforeAnalysisPolicy.NO_INLINING; - } else if (multiMethodKey == DEOPT_TARGET_METHOD) { - return InlineBeforeAnalysisPolicy.NO_INLINING; - } else { - throw VMError.shouldNotReachHere("Unexpected method key: %s", multiMethodKey); - } - } - - @Override - public Function getStrengthenGraphsToTargetFunction(MultiMethod.MultiMethodKey key) { - /* No customization is needed to deopt testing. */ - return null; - } - } - - private static class DeoptTestingAnalysisPolicy implements HostVM.MultiMethodAnalysisPolicy { - - @Override - public Collection determineCallees(BigBang bb, T implementation, T target, MultiMethod.MultiMethodKey callerMultiMethodKey, InvokeTypeFlow invokeFlow) { - if (callerMultiMethodKey == ORIGINAL_METHOD) { - if (DeoptimizationUtils.canDeoptForTesting(implementation, DeoptTester.enabled(), () -> false)) { - /* - * If the target is registered for deoptimization, then we must also make a - * deoptimized version. - */ - return List.of(implementation, getDeoptVersion(implementation)); - } else { - return List.of(implementation); - } - } else { - assert callerMultiMethodKey == DEOPT_TARGET_METHOD; - /* - * A deoptimization target will always call the original method. However, the return - * can also be from a deoptimized version when a deoptimization is triggered in an - * inlined callee. - */ - return List.of(implementation, getDeoptVersion(implementation)); - } - } - - @SuppressWarnings("unchecked") - protected T getDeoptVersion(T implementation) { - /* - * Flows for deopt versions are only created once a frame state for the method is seen - * within a runtime compiled method. - */ - return (T) implementation.getOrCreateMultiMethod(DEOPT_TARGET_METHOD, (newMethod) -> ((PointsToAnalysisMethod) newMethod).getTypeFlow().setAsStubFlow()); - } - - @Override - public boolean performParameterLinking(MultiMethod.MultiMethodKey callerMultiMethodKey, MultiMethod.MultiMethodKey calleeMultiMethodKey) { - if (callerMultiMethodKey == DEOPT_TARGET_METHOD) { - /* A deopt method can call the original version only. */ - return calleeMultiMethodKey == ORIGINAL_METHOD; - } else { - assert callerMultiMethodKey == ORIGINAL_METHOD; - /* An original method can call the deopt target as well. */ - return true; - } - } - - @Override - public boolean performReturnLinking(MultiMethod.MultiMethodKey callerMultiMethodKey, MultiMethod.MultiMethodKey calleeMultiMethodKey) { - if (callerMultiMethodKey == DEOPT_TARGET_METHOD) { - /* A deopt method can be returned to from the deopt target or an original method. */ - return true; - } else { - assert callerMultiMethodKey == ORIGINAL_METHOD; - /* - * An original method can be returned to from the deopt target or an original - * method. - */ - return true; - } - } - - @Override - public boolean canComputeReturnedParameterIndex(MultiMethod.MultiMethodKey multiMethodKey) { - /* - * Since Deopt Target Methods may have their flow created multiple times, this - * optimization is not allowed. - */ - return multiMethodKey != DEOPT_TARGET_METHOD; - } - - @Override - public boolean insertPlaceholderParamAndReturnFlows(MultiMethod.MultiMethodKey multiMethodKey) { - /* - * Since Deopt Target Methods may have their flow created multiple times, placeholder - * flows are needed. - */ - return multiMethodKey == DEOPT_TARGET_METHOD; - } - - @Override - public boolean unknownReturnValue(BigBang bb, MultiMethod.MultiMethodKey callerMultiMethodKey, AnalysisMethod implementation) { - return false; - } - } -} diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/RuntimeCompilationFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/RuntimeCompilationFeature.java deleted file mode 100644 index 25829a88652a..000000000000 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/RuntimeCompilationFeature.java +++ /dev/null @@ -1,730 +0,0 @@ -/* - * Copyright (c) 2013, 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.graal.hosted; - -import static com.oracle.svm.core.util.VMError.guarantee; - -import java.lang.reflect.Executable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BooleanSupplier; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.hosted.Feature; -import org.graalvm.nativeimage.hosted.Feature.AfterCompilationAccess; -import org.graalvm.nativeimage.hosted.Feature.AfterHeapLayoutAccess; -import org.graalvm.nativeimage.hosted.Feature.BeforeAnalysisAccess; -import org.graalvm.nativeimage.hosted.Feature.BeforeHeapLayoutAccess; -import org.graalvm.nativeimage.hosted.Feature.DuringSetupAccess; -import org.graalvm.word.LocationIdentity; - -import com.oracle.graal.pointsto.BigBang; -import com.oracle.graal.pointsto.heap.ImageHeapConstant; -import com.oracle.graal.pointsto.heap.ImageHeapScanner; -import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; -import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.meta.HostedProviders; -import com.oracle.graal.pointsto.util.GraalAccess; -import com.oracle.svm.core.FrameAccess; -import com.oracle.svm.core.ParsingReason; -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.graal.RuntimeCompilationCanaryFeature; -import com.oracle.svm.core.graal.code.SubstrateBackend; -import com.oracle.svm.core.graal.code.SubstrateMetaAccessExtensionProvider; -import com.oracle.svm.core.graal.code.SubstratePlatformConfigurationProvider; -import com.oracle.svm.core.graal.meta.RuntimeConfiguration; -import com.oracle.svm.core.graal.meta.SubstrateReplacements; -import com.oracle.svm.core.graal.nodes.ThrowBytecodeExceptionNode; -import com.oracle.svm.core.graal.word.SubstrateWordTypes; -import com.oracle.svm.core.heap.BarrierSetProvider; -import com.oracle.svm.core.jdk.RuntimeSupport; -import com.oracle.svm.core.meta.SharedType; -import com.oracle.svm.core.option.HostedOptionKey; -import com.oracle.svm.core.option.LocatableMultiOptionValue; -import com.oracle.svm.core.option.RuntimeOptionValues; -import com.oracle.svm.core.util.UserError; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.graal.GraalSupport; -import com.oracle.svm.graal.SubstrateGraalRuntime; -import com.oracle.svm.graal.TruffleRuntimeCompilationSupport; -import com.oracle.svm.graal.meta.SubstrateField; -import com.oracle.svm.graal.meta.SubstrateMethod; -import com.oracle.svm.graal.meta.SubstrateType; -import com.oracle.svm.graal.meta.SubstrateUniverseFactory; -import com.oracle.svm.hosted.FeatureHandler; -import com.oracle.svm.hosted.FeatureImpl; -import com.oracle.svm.hosted.FeatureImpl.AfterHeapLayoutAccessImpl; -import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; -import com.oracle.svm.hosted.FeatureImpl.CompilationAccessImpl; -import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; -import com.oracle.svm.hosted.NativeImageGenerator; -import com.oracle.svm.hosted.ProgressReporter; -import com.oracle.svm.hosted.analysis.Inflation; -import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; -import com.oracle.svm.hosted.meta.HostedMetaAccess; -import com.oracle.svm.hosted.meta.HostedUniverse; -import com.oracle.svm.hosted.phases.SubstrateClassInitializationPlugin; - -import jdk.graal.compiler.api.runtime.GraalRuntime; -import jdk.graal.compiler.core.common.spi.ConstantFieldProvider; -import jdk.graal.compiler.core.common.spi.MetaAccessExtensionProvider; -import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.graph.NodeClass; -import jdk.graal.compiler.lir.phases.LIRSuites; -import jdk.graal.compiler.nodes.GraphDecoder; -import jdk.graal.compiler.nodes.GraphEncoder; -import jdk.graal.compiler.nodes.StructuredGraph; -import jdk.graal.compiler.nodes.extended.BytecodeExceptionNode; -import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; -import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.BytecodeExceptionMode; -import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; -import jdk.graal.compiler.options.Option; -import jdk.graal.compiler.options.OptionStability; -import jdk.graal.compiler.phases.OptimisticOptimizations; -import jdk.graal.compiler.phases.tiers.Suites; -import jdk.graal.compiler.phases.util.Providers; -import jdk.graal.compiler.truffle.nodes.ObjectLocationIdentity; -import jdk.vm.ci.code.Architecture; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.JavaType; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; - -/** - * The main handler for running the Graal compiler in the Substrate VM at run time. This feature - * (and features it depends on like {@link FieldsOffsetsFeature}) encodes Graal graphs for runtime - * compilation, ensures that all required {@link SubstrateType}, {@link SubstrateMethod}, - * {@link SubstrateField} objects are created by {@link GraalGraphObjectReplacer} and added to the - * image. Data that is prepared during image generation and used at run time is stored in - * {@link TruffleRuntimeCompilationSupport}. - */ -public abstract class RuntimeCompilationFeature { - - public static class Options { - @Option(help = "Print methods available for runtime compilation")// - public static final HostedOptionKey PrintRuntimeCompileMethods = new HostedOptionKey<>(false); - - @Option(help = "Print call tree of methods reachable for runtime compilation")// - public static final HostedOptionKey PrintRuntimeCompilationCallTree = new HostedOptionKey<>(false); - - @Option(help = "Maximum number of methods allowed for runtime compilation.", stability = OptionStability.STABLE)// - public static final HostedOptionKey MaxRuntimeCompileMethods = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); - - @Option(help = "Enforce checking of maximum number of methods allowed for runtime compilation. Useful for checking in the gate that the number of methods does not go up without a good reason.")// - public static final HostedOptionKey EnforceMaxRuntimeCompileMethods = new HostedOptionKey<>(false); - } - - public static final class IsEnabled implements BooleanSupplier { - @Override - public boolean getAsBoolean() { - return ImageSingletons.contains(RuntimeCompilationFeature.class); - } - } - - public static RuntimeCompilationFeature singleton() { - return ImageSingletons.lookup(RuntimeCompilationFeature.class); - } - - public static Class getRuntimeCompilationFeature() { - return ParseOnceRuntimeCompilationFeature.class; - } - - public interface RuntimeCompilationCandidatePredicate { - boolean allowRuntimeCompilation(ResolvedJavaMethod method); - } - - public interface AllowInliningPredicate { - enum InlineDecision { - INLINE, - INLINING_DISALLOWED, - NO_DECISION - } - - InlineDecision allowInlining(GraphBuilderContext builder, ResolvedJavaMethod target); - } - - public abstract static class AbstractCallTreeNode implements Comparable { - private final AnalysisMethod implementationMethod; - private final AnalysisMethod targetMethod; - private final AbstractCallTreeNode parent; - private final int level; - private Set children; - - protected AbstractCallTreeNode(AbstractCallTreeNode parent, AnalysisMethod targetMethod, AnalysisMethod implementationMethod) { - this.implementationMethod = implementationMethod; - this.targetMethod = targetMethod; - this.parent = parent; - this.children = null; - if (parent != null) { - level = parent.level + 1; - } else { - level = 0; - } - } - - public AnalysisMethod getImplementationMethod() { - return implementationMethod; - } - - public AnalysisMethod getTargetMethod() { - return targetMethod; - } - - protected AbstractCallTreeNode getParent() { - return parent; - } - - /** - * Helper method to create parent->child link when this node becomes part of the call tree. - */ - protected void linkAsChild() { - if (parent != null) { - if (parent.children == null) { - parent.children = new LinkedHashSet<>(); - } - boolean added = parent.children.add(this); - assert added : "child linked to parent multiple times."; - } - } - - private Collection getChildren() { - return children == null ? List.of() : children; - } - - protected int getLevel() { - return level; - } - - public abstract String getPosition(); - - public abstract int getNodeCount(); - - @Override - public int compareTo(AbstractCallTreeNode o) { - int result = implementationMethod.getQualifiedName().compareTo(o.implementationMethod.getQualifiedName()); - if (result != 0) { - return result; - } - result = targetMethod.getQualifiedName().compareTo(o.targetMethod.getQualifiedName()); - if (result != 0) { - return result; - } - - result = Integer.compare(level, o.level); - if (result != 0) { - return result; - } - - if (parent != null && o.parent != null) { - return parent.compareTo(o.parent); - } - - // this must be true otherwise the level check should return different value - assert parent == null && o.parent == null; - return 0; - } - - /** - * For equality purposes we only care whether it is an equivalent call site (i.e., the - * target and implementation match). - */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - AbstractCallTreeNode that = (AbstractCallTreeNode) o; - - return implementationMethod.equals(that.implementationMethod) && targetMethod.equals(that.targetMethod); - } - - @Override - public int hashCode() { - return Objects.hash(implementationMethod, targetMethod); - } - } - - protected abstract AbstractCallTreeNode getCallTreeNode(RuntimeCompilationCandidate candidate); - - protected abstract AbstractCallTreeNode getCallTreeNode(RuntimeCompiledMethod method); - - protected abstract AbstractCallTreeNode getCallTreeNode(ResolvedJavaMethod method); - - public interface RuntimeCompiledMethod { - AnalysisMethod getMethod(); - - Collection getInlinedMethods(); - - Collection getInvokeTargets(); - } - - public abstract Collection getRuntimeCompiledMethods(); - - public interface RuntimeCompilationCandidate { - AnalysisMethod getImplementationMethod(); - - AnalysisMethod getTargetMethod(); - } - - public abstract Collection getAllRuntimeCompilationCandidates(); - - public final Comparator getRuntimeCompilationComparator() { - return (o1, o2) -> getCallTreeNode(o1).compareTo(getCallTreeNode(o2)); - } - - public final List getCallTrace(ResolvedJavaMethod method) { - return getCallTraceHelper(getCallTreeNode(method)); - } - - public final List getCallTrace(RuntimeCompilationCandidate candidate) { - return getCallTraceHelper(getCallTreeNode(candidate)); - } - - private static List getCallTraceHelper(AbstractCallTreeNode node) { - List trace = new ArrayList<>(); - for (AbstractCallTreeNode cur = node; cur != null; cur = cur.getParent()) { - trace.add(cur.getPosition()); - } - return trace; - } - - protected GraalGraphObjectReplacer objectReplacer; - protected HostedProviders hostedProviders; - protected GraphEncoder graphEncoder; - - private boolean initialized; - protected GraphBuilderConfiguration graphBuilderConfig; - protected OptimisticOptimizations optimisticOpts; - protected RuntimeCompilationCandidatePredicate runtimeCompilationCandidatePredicate; - private boolean runtimeCompilationCandidatePredicateUpdated = false; - protected Predicate deoptimizeOnExceptionPredicate; - - private SubstrateUniverseFactory universeFactory = new SubstrateUniverseFactory(); - - public HostedProviders getHostedProviders() { - return hostedProviders; - } - - public GraalGraphObjectReplacer getObjectReplacer() { - return objectReplacer; - } - - protected static List> getRequiredFeaturesHelper() { - return List.of(RuntimeCompilationCanaryFeature.class, DeoptimizationFeature.class, GraalCompilerFeature.class); - } - - public void setUniverseFactory(SubstrateUniverseFactory universeFactory) { - this.universeFactory = universeFactory; - } - - protected final void duringSetupHelper(DuringSetupAccess c) { - if (SubstrateOptions.useLLVMBackend()) { - throw UserError.abort("Runtime compilation is currently unimplemented on the LLVM backend (GR-43073)."); - } - ImageSingletons.add(TruffleRuntimeCompilationSupport.class, new TruffleRuntimeCompilationSupport()); - if (!ImageSingletons.contains(SubstrateGraalCompilerSetup.class)) { - ImageSingletons.add(SubstrateGraalCompilerSetup.class, new SubstrateGraalCompilerSetup()); - } - - DuringSetupAccessImpl config = (DuringSetupAccessImpl) c; - AnalysisMetaAccess aMetaAccess = config.getMetaAccess(); - SubstrateWordTypes wordTypes = new SubstrateWordTypes(aMetaAccess, FrameAccess.getWordKind()); - SubstrateProviders substrateProviders = ImageSingletons.lookup(SubstrateGraalCompilerSetup.class).getSubstrateProviders(aMetaAccess, wordTypes); - objectReplacer = new GraalGraphObjectReplacer(config.getUniverse(), substrateProviders, universeFactory); - config.registerObjectReplacer(objectReplacer); - } - - private void installRuntimeConfig(BeforeAnalysisAccessImpl config) { - Function backendProvider = TruffleRuntimeCompilationSupport.getRuntimeBackendProvider(); - ClassInitializationSupport classInitializationSupport = config.getHostVM().getClassInitializationSupport(); - Providers originalProviders = GraalAccess.getOriginalProviders(); - SubstratePlatformConfigurationProvider platformConfig = new SubstratePlatformConfigurationProvider(ImageSingletons.lookup(BarrierSetProvider.class).createBarrierSet(config.getMetaAccess())); - RuntimeConfiguration runtimeConfig = ImageSingletons.lookup(SubstrateGraalCompilerSetup.class) - .createRuntimeConfigurationBuilder(RuntimeOptionValues.singleton(), config.getHostVM(), config.getUniverse(), config.getMetaAccess(), - backendProvider, classInitializationSupport, originalProviders.getLoopsDataProvider(), platformConfig, config.getBigBang().getSnippetReflectionProvider()) - .build(); - - Providers runtimeProviders = runtimeConfig.getProviders(); - hostedProviders = new HostedProviders(runtimeProviders.getMetaAccess(), runtimeProviders.getCodeCache(), runtimeProviders.getConstantReflection(), runtimeProviders.getConstantFieldProvider(), - runtimeProviders.getForeignCalls(), runtimeProviders.getLowerer(), runtimeProviders.getReplacements(), runtimeProviders.getStampProvider(), - runtimeConfig.getSnippetReflection(), runtimeProviders.getWordTypes(), runtimeProviders.getPlatformConfigurationProvider(), new GraphPrepareMetaAccessExtensionProvider(), - runtimeProviders.getLoopsDataProvider()); - - FeatureHandler featureHandler = config.getFeatureHandler(); - final boolean supportsStubBasedPlugins = !SubstrateOptions.useLLVMBackend(); - - NativeImageGenerator.registerGraphBuilderPlugins(featureHandler, runtimeConfig, hostedProviders, config.getMetaAccess(), config.getUniverse(), null, config.getNativeLibraries(), - config.getImageClassLoader(), ParsingReason.JITCompilation, ((Inflation) config.getBigBang()).getAnnotationSubstitutionProcessor(), - new SubstrateClassInitializationPlugin(config.getHostVM()), ConfigurationValues.getTarget(), supportsStubBasedPlugins); - - NativeImageGenerator.registerReplacements(DebugContext.forCurrentThread(), featureHandler, runtimeConfig, runtimeConfig.getProviders(), false, true, - new RuntimeCompilationGraphEncoder(ConfigurationValues.getTarget().arch, config.getUniverse().getHeapScanner())); - - featureHandler.forEachGraalFeature(feature -> feature.registerCodeObserver(runtimeConfig)); - Suites suites = NativeImageGenerator.createSuites(featureHandler, runtimeConfig, runtimeConfig.getSnippetReflection(), false); - LIRSuites lirSuites = NativeImageGenerator.createLIRSuites(featureHandler, runtimeConfig.getProviders(), false); - Suites firstTierSuites = NativeImageGenerator.createFirstTierSuites(featureHandler, runtimeConfig, runtimeConfig.getSnippetReflection(), false); - LIRSuites firstTierLirSuites = NativeImageGenerator.createFirstTierLIRSuites(featureHandler, runtimeConfig.getProviders(), false); - - TruffleRuntimeCompilationSupport.setRuntimeConfig(runtimeConfig, suites, lirSuites, firstTierSuites, firstTierLirSuites); - } - - /** - * A graph encoder that unwraps the {@link ImageHeapConstant} objects. This is used both after - * analysis and after compilation. The corresponding graph decoder used during AOT compilation, - * {@link RuntimeCompilationGraphDecoder}, looks-up the constant in the shadow heap and re-wraps - * it. - *

- * The reason why we need to unwrap the {@link ImageHeapConstant}s after analysis, and not only - * when we finally encode the graphs for run time compilation, is because the values in - * {@link GraphEncoder#objectsArray} are captured in GraalSupport#graphObjects and - * SubstrateReplacements#snippetObjects which are then scanned. - */ - public static class RuntimeCompilationGraphEncoder extends GraphEncoder { - - private final ImageHeapScanner heapScanner; - /** - * Cache already converted location identity objects to avoid creating multiple new - * instances for the same underlying location identity. - */ - private final Map locationIdentityCache; - - public RuntimeCompilationGraphEncoder(Architecture architecture, ImageHeapScanner heapScanner) { - super(architecture); - this.heapScanner = heapScanner; - this.locationIdentityCache = new ConcurrentHashMap<>(); - } - - @Override - protected void addObject(Object object) { - super.addObject(unwrap(object)); - } - - @Override - protected void writeObjectId(Object object) { - super.writeObjectId(unwrap(object)); - } - - @Override - protected GraphDecoder graphDecoderForVerification(StructuredGraph decodedGraph) { - return new RuntimeCompilationGraphDecoder(architecture, decodedGraph, heapScanner); - } - - private Object unwrap(Object object) { - if (object instanceof ImageHeapConstant ihc) { - VMError.guarantee(ihc.getHostedObject() != null); - return ihc.getHostedObject(); - } else if (object instanceof ObjectLocationIdentity oli && oli.getObject() instanceof ImageHeapConstant heapConstant) { - return locationIdentityCache.computeIfAbsent(heapConstant, (hc) -> ObjectLocationIdentity.create(hc.getHostedObject())); - } - return object; - } - } - - static class RuntimeCompilationGraphDecoder extends GraphDecoder { - - private final ImageHeapScanner heapScanner; - /** - * Cache already converted location identity objects to avoid creating multiple new - * instances for the same underlying location identity. - */ - private final Map locationIdentityCache; - - RuntimeCompilationGraphDecoder(Architecture architecture, StructuredGraph graph, ImageHeapScanner heapScanner) { - super(architecture, graph); - this.heapScanner = heapScanner; - this.locationIdentityCache = new ConcurrentHashMap<>(); - } - - @Override - protected Object readObject(MethodScope methodScope) { - Object object = super.readObject(methodScope); - if (object instanceof JavaConstant constant) { - return heapScanner.getImageHeapConstant(constant); - } else if (object instanceof ObjectLocationIdentity oli) { - return locationIdentityCache.computeIfAbsent(oli.getObject(), (obj) -> ObjectLocationIdentity.create(heapScanner.getImageHeapConstant(obj))); - } - return object; - } - } - - protected final void beforeAnalysisHelper(BeforeAnalysisAccess c) { - - BeforeAnalysisAccessImpl config = (BeforeAnalysisAccessImpl) c; - installRuntimeConfig(config); - - SubstrateGraalRuntime graalRuntime = new SubstrateGraalRuntime(); - objectReplacer.setGraalRuntime(graalRuntime); - objectReplacer.setAnalysisAccess(config); - ImageSingletons.add(GraalRuntime.class, graalRuntime); - RuntimeSupport.getRuntimeSupport().addShutdownHook(new GraalSupport.GraalShutdownHook()); - - /* Initialize configuration with reasonable default values. */ - graphBuilderConfig = GraphBuilderConfiguration.getDefault(hostedProviders.getGraphBuilderPlugins()).withBytecodeExceptionMode(BytecodeExceptionMode.ExplicitOnly); - runtimeCompilationCandidatePredicate = RuntimeCompilationFeature::defaultAllowRuntimeCompilation; - optimisticOpts = OptimisticOptimizations.ALL.remove(OptimisticOptimizations.Optimization.UseLoopLimitChecks); - graphEncoder = new RuntimeCompilationGraphEncoder(ConfigurationValues.getTarget().arch, config.getUniverse().getHeapScanner()); - - /* - * Ensure all snippet types are registered as used. - */ - SubstrateReplacements replacements = (SubstrateReplacements) TruffleRuntimeCompilationSupport.getRuntimeConfig().getProviders().getReplacements(); - for (NodeClass nodeClass : replacements.getSnippetNodeClasses()) { - config.getMetaAccess().lookupJavaType(nodeClass.getClazz()).registerAsAllocated("All " + NodeClass.class.getName() + " classes are marked as instantiated eagerly."); - } - /* - * Ensure runtime snippet graphs are analyzed. - */ - NativeImageGenerator.performSnippetGraphAnalysis(config.getBigBang(), replacements, config.getBigBang().getOptions()); - - /* - * Ensure that all snippet methods have their SubstrateMethod object created by the object - * replacer, to avoid corner cases later when writing the native image. - */ - for (ResolvedJavaMethod method : replacements.getSnippetMethods()) { - objectReplacer.apply(method); - } - } - - @SuppressWarnings("unused") - private static boolean defaultAllowRuntimeCompilation(ResolvedJavaMethod method) { - return false; - } - - public void initializeRuntimeCompilationForTesting(FeatureImpl.BeforeAnalysisAccessImpl config, RuntimeCompilationCandidatePredicate newRuntimeCompilationCandidatePredicate) { - initializeRuntimeCompilationConfiguration(hostedProviders, graphBuilderConfig, newRuntimeCompilationCandidatePredicate, deoptimizeOnExceptionPredicate); - initializeRuntimeCompilationForTesting(config); - } - - public void initializeRuntimeCompilationForTesting(BeforeAnalysisAccessImpl config) { - initializeAnalysisProviders(config.getBigBang(), provider -> provider); - } - - public void initializeRuntimeCompilationConfiguration(HostedProviders newHostedProviders, GraphBuilderConfiguration newGraphBuilderConfig, - RuntimeCompilationCandidatePredicate newRuntimeCompilationCandidatePredicate, - Predicate newDeoptimizeOnExceptionPredicate) { - guarantee(initialized == false, "runtime compilation configuration already initialized"); - initialized = true; - - hostedProviders = newHostedProviders; - graphBuilderConfig = newGraphBuilderConfig.withNodeSourcePosition(true); - assert !runtimeCompilationCandidatePredicateUpdated : "Updated compilation predicate multiple times"; - runtimeCompilationCandidatePredicate = newRuntimeCompilationCandidatePredicate; - runtimeCompilationCandidatePredicateUpdated = true; - deoptimizeOnExceptionPredicate = newDeoptimizeOnExceptionPredicate; - } - - public SubstrateMethod requireFrameInformationForMethod(ResolvedJavaMethod method, BeforeAnalysisAccessImpl config, boolean registerAsRoot) { - AnalysisMethod aMethod = (AnalysisMethod) method; - SubstrateMethod sMethod = objectReplacer.createMethod(aMethod); - - requireFrameInformationForMethodHelper(aMethod, config, registerAsRoot); - - return sMethod; - } - - protected abstract void requireFrameInformationForMethodHelper(AnalysisMethod aMethod, BeforeAnalysisAccessImpl config, boolean registerAsRoot); - - public SubstrateMethod prepareMethodForRuntimeCompilation(Executable method, BeforeAnalysisAccessImpl config) { - return prepareMethodForRuntimeCompilation(config.getMetaAccess().lookupJavaMethod(method), config); - } - - public abstract void initializeAnalysisProviders(BigBang bb, Function generator); - - public abstract void registerAllowInliningPredicate(AllowInliningPredicate predicate); - - public abstract SubstrateMethod prepareMethodForRuntimeCompilation(ResolvedJavaMethod method, BeforeAnalysisAccessImpl config); - - protected final void afterAnalysisHelper() { - ProgressReporter.singleton().setNumRuntimeCompiledMethods(getRuntimeCompiledMethods().size()); - } - - /** - * Checks if any illegal nodes are present within the graph. Runtime Compiled methods should - * never have explicit BytecodeExceptions; instead they should have deoptimizations. - */ - protected static boolean verifyNodes(StructuredGraph graph) { - for (var node : graph.getNodes()) { - boolean invalidNodeKind = node instanceof BytecodeExceptionNode || node instanceof ThrowBytecodeExceptionNode; - assert !invalidNodeKind : "illegal node in graph: " + node + " method: " + graph.method(); - } - return true; - } - - protected final void beforeCompilationHelper() { - if (Options.PrintRuntimeCompileMethods.getValue()) { - System.out.println("****Start Print Runtime Compile Methods***"); - getRuntimeCompiledMethods().stream().map(m -> m.getMethod().format("%H.%n(%p)")).sorted().collect(Collectors.toList()).forEach(System.out::println); - System.out.println("****End Print Runtime Compile Methods***"); - } - - if (Options.PrintRuntimeCompilationCallTree.getValue()) { - System.out.println("****Start Print Runtime Compile Call Tree***"); - printCallTree(); - System.out.println("****End Print Runtime Compile Call Tree***"); - } - - int maxMethods = 0; - for (String value : Options.MaxRuntimeCompileMethods.getValue().values()) { - String numberStr = null; - try { - /* Strip optional comment string from MaxRuntimeCompileMethods value */ - numberStr = value.split("#")[0]; - maxMethods += Integer.parseInt(numberStr); - } catch (NumberFormatException ex) { - throw UserError.abort("Invalid value for option 'MaxRuntimeCompileMethods': '%s' is not a valid number", numberStr); - } - } - if (Options.EnforceMaxRuntimeCompileMethods.getValue() && maxMethods != 0 && getRuntimeCompiledMethods().size() > maxMethods) { - printDeepestLevelPath(); - throw VMError.shouldNotReachHere("Number of methods for runtime compilation exceeds the allowed limit: " + getRuntimeCompiledMethods().size() + " > " + maxMethods); - } - } - - private void printDeepestLevelPath() { - AbstractCallTreeNode maxLevelCallTreeNode = null; - for (var method : getRuntimeCompiledMethods()) { - var callTreeNode = getCallTreeNode(method); - if (maxLevelCallTreeNode == null || maxLevelCallTreeNode.getLevel() < callTreeNode.getLevel()) { - maxLevelCallTreeNode = callTreeNode; - } - } - - System.out.println(String.format("Deepest level call tree path (%d calls):", maxLevelCallTreeNode.getLevel())); - AbstractCallTreeNode node = maxLevelCallTreeNode; - while (node != null) { - AnalysisMethod implementationMethod = node.getImplementationMethod(); - AnalysisMethod targetMethod = node.getTargetMethod(); - - System.out.format("%5d ; %s ; %s", node.getNodeCount(), node.getPosition(), implementationMethod == null ? "" - : implementationMethod.format("%H.%n(%p)")); - if (targetMethod != null && !targetMethod.equals(implementationMethod)) { - System.out.print(" ; " + targetMethod.format("%H.%n(%p)")); - } - System.out.println(); - node = node.getParent(); - } - } - - private void printCallTree() { - System.out.println("depth;method;Graal nodes;invoked from source;full method name;full name of invoked virtual method"); - for (var method : getRuntimeCompiledMethods()) { - var node = getCallTreeNode(method); - if (node.getLevel() == 0) { - printCallTreeNode(node); - } - } - } - - private void printCallTreeNode(AbstractCallTreeNode node) { - AnalysisMethod implementationMethod = node.getImplementationMethod(); - AnalysisMethod targetMethod = node.getTargetMethod(); - - StringBuilder indent = new StringBuilder(); - for (int i = 0; i < node.getLevel(); i++) { - indent.append(" "); - } - if (implementationMethod != null) { - indent.append(implementationMethod.format("%h.%n")); - } - - System.out.format("%4d ; %-80s ;%5d ; %s ; %s", node.getLevel(), indent, node.getNodeCount(), node.getPosition(), - implementationMethod == null ? "" : implementationMethod.format("%H.%n(%p)")); - if (targetMethod != null && !targetMethod.equals(implementationMethod)) { - System.out.print(" ; " + targetMethod.format("%H.%n(%p)")); - } - System.out.println(); - - for (AbstractCallTreeNode child : node.getChildren()) { - printCallTreeNode(child); - } - } - - protected final void afterCompilationHelper(AfterCompilationAccess a) { - CompilationAccessImpl config = (CompilationAccessImpl) a; - - HostedMetaAccess hMetaAccess = config.getMetaAccess(); - HostedUniverse hUniverse = hMetaAccess.getUniverse(); - objectReplacer.updateSubstrateDataAfterCompilation(hUniverse, config.getProviders()); - } - - protected final void beforeHeapLayoutHelper(BeforeHeapLayoutAccess a) { - objectReplacer.registerImmutableObjects(a); - TruffleRuntimeCompilationSupport.registerImmutableObjects(a); - ((SubstrateReplacements) TruffleRuntimeCompilationSupport.getRuntimeConfig().getProviders().getReplacements()).registerImmutableObjects(a); - } - - protected final void afterHeapLayoutHelper(AfterHeapLayoutAccess a) { - AfterHeapLayoutAccessImpl config = (AfterHeapLayoutAccessImpl) a; - HostedMetaAccess hMetaAccess = config.getMetaAccess(); - HostedUniverse hUniverse = hMetaAccess.getUniverse(); - objectReplacer.updateSubstrateDataAfterHeapLayout(hUniverse); - } -} - -/** - * Same behavior as {@link SubstrateMetaAccessExtensionProvider}, but operating on - * {@link AnalysisType} instead of {@link SharedType} since parsing of graphs for runtime - * compilation happens in the Analysis universe. - */ -class GraphPrepareMetaAccessExtensionProvider implements MetaAccessExtensionProvider { - - @Override - public JavaKind getStorageKind(JavaType type) { - return ((AnalysisType) type).getStorageKind(); - } - - @Override - public boolean canConstantFoldDynamicAllocation(ResolvedJavaType type) { - assert type instanceof AnalysisType : "AnalysisType is required; AnalysisType lazily creates array types of any depth, so type cannot be null"; - return ((AnalysisType) type).isInstantiated(); - } - - @Override - public boolean isGuaranteedSafepoint(ResolvedJavaMethod method, boolean isDirect) { - throw VMError.shouldNotReachHereAtRuntime(); // ExcludeFromJacocoGeneratedReport - } - - @Override - public boolean canVirtualize(ResolvedJavaType instanceType) { - return true; - } -} diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/CallTreeInfo.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/CallTreeInfo.java new file mode 100644 index 000000000000..0ed531bea9bb --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/CallTreeInfo.java @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2023, 2023, 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.graal.hosted.runtimecompilation; + +import static com.oracle.svm.common.meta.MultiMethod.ORIGINAL_METHOD; +import static com.oracle.svm.hosted.code.SubstrateCompilationDirectives.RUNTIME_COMPILED_METHOD; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; +import java.util.stream.Collectors; + +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.graal.pointsto.meta.InvokeInfo; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; + +import jdk.vm.ci.code.BytecodeFrame; +import jdk.vm.ci.code.BytecodePosition; + +public final class CallTreeInfo { + private final Map runtimeCompilations; + private Map runtimeCandidateMap; + private Map analysisMethodMap; + private boolean callTreeInitialized = false; + private boolean callTraceInitialized = false; + + private CallTreeInfo(Map runtimeCompilations) { + this.runtimeCompilations = runtimeCompilations; + } + + public Collection runtimeCompilations() { + return runtimeCompilations.values(); + } + + public static CallTreeInfo create(AnalysisUniverse aUniverse, Map invalidForRuntimeCompilation) { + Map runtimeCompilations = new HashMap<>(); + for (var method : aUniverse.getMethods()) { + var rMethod = method.getMultiMethod(RUNTIME_COMPILED_METHOD); + if (rMethod != null && rMethod.isReachable() && !invalidForRuntimeCompilation.containsKey(rMethod)) { + var origInlinedMethods = rMethod.getAnalyzedGraph().getInlinedMethods().stream().map(inlinedMethod -> { + AnalysisMethod orig = ((AnalysisMethod) inlinedMethod).getMultiMethod(ORIGINAL_METHOD); + assert orig != null; + return orig; + }).collect(Collectors.toUnmodifiableSet()); + var previous = runtimeCompilations.put(rMethod, new RuntimeCompiledMethod(rMethod, origInlinedMethods)); + assert previous == null : previous; + } + } + + return new CallTreeInfo(runtimeCompilations); + } + + private void initializeCallTraceInfo() { + if (callTraceInitialized) { + return; + } + + callTraceInitialized = true; + analysisMethodMap = new HashMap<>(); + runtimeCandidateMap = new HashMap<>(); + + for (var runtimeCompilation : runtimeCompilations()) { + AnalysisMethod method = runtimeCompilation.runtimeMethod; + MethodNode callerMethodNode = analysisMethodMap.computeIfAbsent(method, MethodNode::new); + + for (InvokeInfo invokeInfo : method.getInvokes()) { + AnalysisMethod invokeTarget = invokeInfo.getTargetMethod(); + boolean deoptInvokeTypeFlow = invokeInfo.isDeoptInvokeTypeFlow(); + if (deoptInvokeTypeFlow) { + assert SubstrateCompilationDirectives.isRuntimeCompiledMethod(invokeTarget); + invokeTarget = invokeTarget.getMultiMethod(ORIGINAL_METHOD); + } + assert invokeTarget.isOriginalMethod(); + for (AnalysisMethod callee : invokeInfo.getAllCallees()) { + /* + * Special handling is needed for deoptInvokeTypeFlows because they only have + * the deopt method variant as a callee. + */ + if (deoptInvokeTypeFlow || SubstrateCompilationDirectives.isRuntimeCompiledMethod(callee)) { + MethodNode calleeMethodNode = analysisMethodMap.computeIfAbsent(callee, MethodNode::new); + InvokeNode invoke = new InvokeNode(callerMethodNode, invokeInfo.getPosition()); + calleeMethodNode.addCaller(invoke); + + var origCallee = callee.getMultiMethod(ORIGINAL_METHOD); + assert origCallee != null; + runtimeCandidateMap.putIfAbsent(new RuntimeCompilationCandidate(origCallee, invokeTarget), invoke); + } else if (callee.isOriginalMethod() && callee.getMultiMethod(RUNTIME_COMPILED_METHOD) == null) { + /* + * Recording that this call was reachable, but not converted to a runtime + * compiled method. + */ + runtimeCandidateMap.computeIfAbsent(new RuntimeCompilationCandidate(callee, invokeTarget), + (candidate) -> { + return new InvokeNode(callerMethodNode, invokeInfo.getPosition()); + }); + } + } + } + } + } + + public void initializeCallTreeInfo(Set registeredRoots) { + if (callTreeInitialized) { + return; + } + + initializeCallTraceInfo(); + callTreeInitialized = true; + + // ensure invokeInfo calculated + + Queue worklist = new LinkedList<>(); + /* + * First initialize all nodes with no callers. + */ + for (var methodNode : analysisMethodMap.values()) { + if (methodNode.getCallers().isEmpty() || registeredRoots.contains(methodNode.method.getMultiMethod(ORIGINAL_METHOD))) { + worklist.add(methodNode); + methodNode.trace = new TraceInfo(0, new BytecodePosition(null, methodNode.method, BytecodeFrame.UNKNOWN_BCI), null); + } + } + + /* Walk through to find a reachable path for all nodes */ + while (!worklist.isEmpty()) { + MethodNode callerMethodNode = worklist.remove(); + TraceInfo callerTrace = callerMethodNode.trace; + VMError.guarantee(callerTrace != null); + + for (InvokeInfo invokeInfo : callerMethodNode.method.getInvokes()) { + boolean deoptInvokeTypeFlow = invokeInfo.isDeoptInvokeTypeFlow(); + if (deoptInvokeTypeFlow) { + // we do not need to trace deoptInvokes + continue; + } + InvokeNode callerInvokeNode = null; + for (AnalysisMethod callee : invokeInfo.getAllCallees()) { + if (SubstrateCompilationDirectives.isRuntimeCompiledMethod(callee)) { + MethodNode calleeMethodNode = analysisMethodMap.get(callee); + if (calleeMethodNode.trace == null) { + /* + * If this was the first time this node was reached, then add to + * worklist. + */ + if (callerInvokeNode == null) { + callerInvokeNode = new InvokeNode(callerMethodNode, invokeInfo.getPosition()); + } + worklist.add(calleeMethodNode); + calleeMethodNode.trace = new TraceInfo(callerTrace.level + 1, invokeInfo.getPosition(), callerInvokeNode); + callerTrace.addTraceTarget(calleeMethodNode); + } + } + } + } + } + } + + private static final String[] UNKNOWN_TRACE = new String[]{"Unknown"}; + private static final String[] EMPTY_STRING = new String[0]; + + public static String[] getCallTrace(CallTreeInfo callTreeInfo, AnalysisMethod method) { + callTreeInfo.initializeCallTraceInfo(); + MethodNode methodNode = callTreeInfo.analysisMethodMap.get(method); + if (methodNode == null) { + return UNKNOWN_TRACE; + } + + ArrayList trace = new ArrayList<>(); + findCallTraceHelper(trace, methodNode); + return trace.toArray(EMPTY_STRING); + } + + public static String[] getCallTrace(CallTreeInfo callTreeInfo, RuntimeCompilationCandidate candidate) { + callTreeInfo.initializeCallTraceInfo(); + InvokeNode invokeNode = callTreeInfo.runtimeCandidateMap.get(candidate); + if (invokeNode == null) { + return UNKNOWN_TRACE; + } + + ArrayList trace = new ArrayList<>(); + findCallTraceHelper(trace, invokeNode.method); + return trace.toArray(EMPTY_STRING); + } + + private static void findCallTraceHelper(ArrayList trace, MethodNode first) { + Set covered = new HashSet<>(); + MethodNode current = first; + covered.add(current); + + while (current != null) { + // find parent + MethodNode parent = null; + for (InvokeNode caller : current.getCallers()) { + if (covered.add(caller.method)) { + parent = caller.method; + trace.add(caller.position.toString()); + break; + } + } + current = parent; + } + } + + public static void printCallTree(CallTreeInfo info, Set registeredRuntimeCompilations) { + info.initializeCallTreeInfo(registeredRuntimeCompilations); + + System.out.println("depth;method;invoke position"); + for (MethodNode methodNode : info.analysisMethodMap.values()) { + if (methodNode.trace != null && methodNode.trace.level == 0) { + printCallTreeNode(methodNode); + } + } + } + + private static void printCallTreeNode(MethodNode node) { + TraceInfo trace = node.trace; + StringBuilder indent = new StringBuilder(); + indent.append(" ".repeat(Math.max(0, trace.level))); + indent.append(node.method.format("%H.%n")); + System.out.format("%4d ; %-80s ; %s%n", trace.level, indent, trace.position); + for (MethodNode child : trace.getTraceTargets()) { + printCallTreeNode(child); + } + } + + public static void printDeepestPath(CallTreeInfo info, Set registeredRuntimeCompilations) { + info.initializeCallTreeInfo(registeredRuntimeCompilations); + + Optional deepestNode = info.analysisMethodMap.values().stream().max(Comparator.comparingInt(t -> t.trace == null ? -1 : t.trace.level)); + + if (deepestNode.isEmpty() || deepestNode.get().trace == null) { + System.out.println("Could not find a trace"); + return; + } + + MethodNode node = deepestNode.get(); + System.out.printf("Deepest level call tree path (%s calls):%n", node.trace.level); + System.out.println("depth;method;invoke position"); + do { + TraceInfo trace = node.trace; + StringBuilder indent = new StringBuilder(); + indent.append(" ".repeat(Math.max(0, trace.level))); + indent.append(node.method.format("%H.%n")); + System.out.format("%4d ; %-80s ; %s%n", trace.level, indent, trace.position); + InvokeNode call = trace.invokeParent; + node = call == null ? null : call.method; + } while (node != null); + } +} + +class MethodNode { + public AnalysisMethod method; + public List callers; + public TraceInfo trace; + + MethodNode(AnalysisMethod method) { + this.method = method; + this.callers = null; + + } + + List getCallers() { + return callers == null ? List.of() : callers; + } + + void addCaller(InvokeNode invoke) { + if (callers == null) { + callers = new ArrayList<>(); + } + callers.add(invoke); + } +} + +class InvokeNode { + final MethodNode method; + final BytecodePosition position; + + InvokeNode(MethodNode method, BytecodePosition position) { + this.method = method; + this.position = position; + } +} + +class TraceInfo { + final int level; + final BytecodePosition position; + final InvokeNode invokeParent; + List traceTargets; + + TraceInfo(int level, BytecodePosition position, InvokeNode invokeParent) { + this.level = level; + this.position = position; + this.invokeParent = invokeParent; + } + + List getTraceTargets() { + return traceTargets == null ? List.of() : traceTargets; + } + + void addTraceTarget(MethodNode node) { + if (traceTargets == null) { + traceTargets = new ArrayList<>(); + } + traceTargets.add(node); + } +} diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/GraalGraphObjectReplacer.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java similarity index 99% rename from substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/GraalGraphObjectReplacer.java rename to substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java index cc3ed6852370..de2e922f1857 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/GraalGraphObjectReplacer.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.graal.hosted; +package com.oracle.svm.graal.hosted.runtimecompilation; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationCandidate.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationCandidate.java new file mode 100644 index 000000000000..361cd8532c46 --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationCandidate.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023, 2023, 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.graal.hosted.runtimecompilation; + +import java.util.Objects; + +import com.oracle.graal.pointsto.meta.AnalysisMethod; + +public final class RuntimeCompilationCandidate { + AnalysisMethod implementationMethod; + AnalysisMethod targetMethod; + + RuntimeCompilationCandidate(AnalysisMethod implementationMethod, AnalysisMethod targetMethod) { + this.implementationMethod = implementationMethod; + this.targetMethod = targetMethod; + } + + public AnalysisMethod getImplementationMethod() { + return implementationMethod; + } + + public AnalysisMethod getTargetMethod() { + return targetMethod; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RuntimeCompilationCandidate that = (RuntimeCompilationCandidate) o; + return implementationMethod.equals(that.implementationMethod) && targetMethod.equals(that.targetMethod); + } + + @Override + public int hashCode() { + return Objects.hash(implementationMethod, targetMethod); + } +} diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/ParseOnceRuntimeCompilationFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java similarity index 52% rename from substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/ParseOnceRuntimeCompilationFeature.java rename to substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java index a77c64d41a88..2ed14e40d39d 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/ParseOnceRuntimeCompilationFeature.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 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 @@ -22,31 +22,29 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.graal.hosted; +package com.oracle.svm.graal.hosted.runtimecompilation; import static com.oracle.svm.common.meta.MultiMethod.DEOPT_TARGET_METHOD; import static com.oracle.svm.common.meta.MultiMethod.ORIGINAL_METHOD; +import static com.oracle.svm.core.util.VMError.guarantee; import static com.oracle.svm.hosted.code.SubstrateCompilationDirectives.RUNTIME_COMPILED_METHOD; import static com.oracle.svm.hosted.phases.InlineBeforeAnalysisPolicyUtils.Options.InlineBeforeAnalysisAllowedDepth; import static jdk.graal.compiler.java.BytecodeParserOptions.InlineDuringParsingMaxDepth; -import java.util.ArrayList; +import java.lang.reflect.Executable; +import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.Supplier; -import java.util.stream.Collectors; +import java.util.stream.Stream; -import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; @@ -55,330 +53,395 @@ import com.oracle.graal.pointsto.api.HostVM; import com.oracle.graal.pointsto.flow.InvokeTypeFlow; import com.oracle.graal.pointsto.flow.MethodFlowsGraph; -import com.oracle.graal.pointsto.heap.ImageHeapScanner; import com.oracle.graal.pointsto.infrastructure.GraphProvider; -import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.meta.HostedProviders; -import com.oracle.graal.pointsto.meta.InvokeInfo; import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; import com.oracle.graal.pointsto.phases.InlineBeforeAnalysisPolicy; -import com.oracle.graal.pointsto.util.CompletionExecutor; +import com.oracle.graal.pointsto.util.AnalysisError; +import com.oracle.graal.pointsto.util.GraalAccess; +import com.oracle.graal.pointsto.util.ParallelExecutionException; import com.oracle.svm.common.meta.MultiMethod; +import com.oracle.svm.core.FrameAccess; import com.oracle.svm.core.ParsingReason; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.graal.nodes.DeoptEntryNode; -import com.oracle.svm.core.graal.nodes.DeoptEntrySupport; -import com.oracle.svm.core.graal.nodes.DeoptProxyAnchorNode; +import com.oracle.svm.core.graal.RuntimeCompilationCanaryFeature; +import com.oracle.svm.core.graal.code.SubstrateBackend; +import com.oracle.svm.core.graal.code.SubstrateMetaAccessExtensionProvider; +import com.oracle.svm.core.graal.code.SubstratePlatformConfigurationProvider; +import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.graal.meta.SubstrateReplacements; import com.oracle.svm.core.graal.nodes.InlinedInvokeArgumentsNode; +import com.oracle.svm.core.graal.word.SubstrateWordTypes; +import com.oracle.svm.core.heap.BarrierSetProvider; +import com.oracle.svm.core.jdk.RuntimeSupport; +import com.oracle.svm.core.meta.SharedType; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.HostedOptionValues; +import com.oracle.svm.core.option.LocatableMultiOptionValue; +import com.oracle.svm.core.option.RuntimeOptionValues; +import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.graal.GraalSupport; +import com.oracle.svm.graal.SubstrateGraalRuntime; import com.oracle.svm.graal.TruffleRuntimeCompilationSupport; +import com.oracle.svm.graal.hosted.DeoptimizationFeature; +import com.oracle.svm.graal.hosted.FieldsOffsetsFeature; +import com.oracle.svm.graal.hosted.GraalCompilerFeature; +import com.oracle.svm.graal.meta.SubstrateField; import com.oracle.svm.graal.meta.SubstrateMethod; +import com.oracle.svm.graal.meta.SubstrateType; +import com.oracle.svm.graal.meta.SubstrateUniverseFactory; +import com.oracle.svm.hosted.FeatureHandler; import com.oracle.svm.hosted.FeatureImpl; -import com.oracle.svm.hosted.HeapBreakdownProvider; -import com.oracle.svm.hosted.RuntimeCompilationSupport; +import com.oracle.svm.hosted.FeatureImpl.AfterHeapLayoutAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.CompilationAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; +import com.oracle.svm.hosted.NativeImageGenerator; +import com.oracle.svm.hosted.ProgressReporter; +import com.oracle.svm.hosted.RuntimeCompilationCallbacks; import com.oracle.svm.hosted.SVMHost; -import com.oracle.svm.hosted.ameta.AnalysisConstantFieldProvider; -import com.oracle.svm.hosted.ameta.AnalysisConstantReflectionProvider; +import com.oracle.svm.hosted.analysis.Inflation; import com.oracle.svm.hosted.analysis.SVMParsingSupport; import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import com.oracle.svm.hosted.code.CompileQueue; import com.oracle.svm.hosted.code.DeoptimizationUtils; import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; -import com.oracle.svm.hosted.meta.HostedConstantFieldProvider; -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.meta.HostedUniverse; -import com.oracle.svm.hosted.nodes.DeoptProxyNode; -import com.oracle.svm.hosted.phases.AnalysisGraphBuilderPhase; import com.oracle.svm.hosted.phases.ConstantFoldLoadFieldPlugin; import com.oracle.svm.hosted.phases.InlineBeforeAnalysisPolicyUtils; -import com.oracle.svm.hosted.phases.InlineBeforeAnalysisPolicyUtils.AccumulativeInlineScope; -import com.oracle.svm.hosted.phases.InlineBeforeAnalysisPolicyUtils.AlwaysInlineScope; +import com.oracle.svm.hosted.phases.SubstrateClassInitializationPlugin; +import jdk.graal.compiler.api.runtime.GraalRuntime; import jdk.graal.compiler.core.common.PermanentBailoutException; import jdk.graal.compiler.core.common.spi.ConstantFieldProvider; +import jdk.graal.compiler.core.common.spi.MetaAccessExtensionProvider; import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.debug.DebugHandlersFactory; import jdk.graal.compiler.debug.Indent; import jdk.graal.compiler.graph.NodeClass; import jdk.graal.compiler.graph.NodeSourcePosition; -import jdk.graal.compiler.java.BytecodeParser; -import jdk.graal.compiler.java.GraphBuilderPhase; +import jdk.graal.compiler.lir.phases.LIRSuites; import jdk.graal.compiler.loop.phases.ConvertDeoptimizeToGuardPhase; -import jdk.graal.compiler.nodes.CallTargetNode; import jdk.graal.compiler.nodes.FixedWithNextNode; -import jdk.graal.compiler.nodes.FrameState; -import jdk.graal.compiler.nodes.StateSplit; +import jdk.graal.compiler.nodes.GraphEncoder; import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.ValueNode; import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.BytecodeExceptionMode; import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; import jdk.graal.compiler.nodes.graphbuilderconf.InlineInvokePlugin; -import jdk.graal.compiler.nodes.graphbuilderconf.IntrinsicContext; import jdk.graal.compiler.nodes.graphbuilderconf.NodePlugin; -import jdk.graal.compiler.nodes.java.ExceptionObjectNode; import jdk.graal.compiler.options.Option; -import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.options.OptionStability; import jdk.graal.compiler.phases.OptimisticOptimizations; -import jdk.graal.compiler.phases.Phase; -import jdk.graal.compiler.phases.PhaseSuite; import jdk.graal.compiler.phases.common.CanonicalizerPhase; -import jdk.graal.compiler.phases.common.DominatorBasedGlobalValueNumberingPhase; -import jdk.graal.compiler.phases.common.IterativeConditionalEliminationPhase; -import jdk.graal.compiler.phases.tiers.HighTierContext; +import jdk.graal.compiler.phases.tiers.Suites; import jdk.graal.compiler.phases.util.Providers; -import jdk.graal.compiler.printer.GraalDebugHandlersFactory; import jdk.graal.compiler.truffle.phases.DeoptimizeOnExceptionPhase; -import jdk.vm.ci.code.BytecodeFrame; -import jdk.vm.ci.code.BytecodePosition; -import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.MetaAccessProvider; -import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; -public class ParseOnceRuntimeCompilationFeature extends RuntimeCompilationFeature implements Feature, RuntimeCompilationSupport { +/** + * The main handler for running the Graal compiler in the Substrate VM at run time. This feature + * (and features it depends on like {@link FieldsOffsetsFeature}) encodes Graal graphs for runtime + * compilation, ensures that all required {@link SubstrateType}, {@link SubstrateMethod}, + * {@link SubstrateField} objects are created by {@link GraalGraphObjectReplacer} and added to the + * image. Data that is prepared during image generation and used at run time is stored in + * {@link TruffleRuntimeCompilationSupport}. + */ +public final class RuntimeCompilationFeature implements Feature, RuntimeCompilationCallbacks { public static class Options { - @Option(help = "Remove Deopt(Entries,Anchors,Proxies) determined to be unneeded after the runtime compiled graphs have been finalized.")// - public static final HostedOptionKey RemoveUnneededDeoptSupport = new HostedOptionKey<>(true); + @Option(help = "Print methods available for runtime compilation")// + public static final HostedOptionKey PrintRuntimeCompileMethods = new HostedOptionKey<>(false); + + @Option(help = "Print call tree of methods reachable for runtime compilation")// + public static final HostedOptionKey PrintRuntimeCompilationCallTree = new HostedOptionKey<>(false); + + @Option(help = "Maximum number of methods allowed for runtime compilation.", stability = OptionStability.STABLE)// + public static final HostedOptionKey MaxRuntimeCompileMethods = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); + + @Option(help = "Enforce checking of maximum number of methods allowed for runtime compilation. Useful for checking in the gate that the number of methods does not go up without a good reason.")// + public static final HostedOptionKey EnforceMaxRuntimeCompileMethods = new HostedOptionKey<>(false); @Option(help = "Perform InlineBeforeAnalysis on runtime compiled methods")// public static final HostedOptionKey RuntimeCompilationInlineBeforeAnalysis = new HostedOptionKey<>(true); } - public static final class CallTreeNode extends AbstractCallTreeNode { - final BytecodePosition position; - final boolean inlined; - - CallTreeNode(AnalysisMethod implementationMethod, AnalysisMethod targetMethod, CallTreeNode parent, BytecodePosition position, boolean inlined) { - super(parent, targetMethod, implementationMethod); - this.position = position; - this.inlined = inlined; + public static final class IsEnabled implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + return ImageSingletons.contains(RuntimeCompilationFeature.class); } + } - @Override - public String getPosition() { - String message = ""; - if (inlined) { - message += "(inlined: some parent frames may be missing) "; + public static RuntimeCompilationFeature singleton() { + return ImageSingletons.lookup(RuntimeCompilationFeature.class); + } - } - if (getParent() == null) { - message += "[root] "; - } - return message + position; - } + public interface RuntimeCompilationCandidatePredicate { + boolean allowRuntimeCompilation(ResolvedJavaMethod method); + } - /** - * It is not worthwhile to decode the graph to get the node count. - */ - @Override - public int getNodeCount() { - return -1; + public interface AllowInliningPredicate { + enum InlineDecision { + INLINE, + INLINING_DISALLOWED, + NO_DECISION } + InlineDecision allowInlining(GraphBuilderContext builder, ResolvedJavaMethod target); } - static class RuntimeCompilationCandidateImpl implements RuntimeCompilationCandidate { - AnalysisMethod implementationMethod; - AnalysisMethod targetMethod; + private GraalGraphObjectReplacer objectReplacer; + private HostedProviders hostedProviders; + private GraphEncoder graphEncoder; - RuntimeCompilationCandidateImpl(AnalysisMethod implementationMethod, AnalysisMethod targetMethod) { - this.implementationMethod = implementationMethod; - this.targetMethod = targetMethod; - } + private boolean initialized; + private GraphBuilderConfiguration graphBuilderConfig; + private OptimisticOptimizations optimisticOpts; + private RuntimeCompilationCandidatePredicate runtimeCompilationCandidatePredicate; + private boolean runtimeCompilationCandidatePredicateUpdated = false; + private Predicate deoptimizeOnExceptionPredicate; - @Override - public AnalysisMethod getImplementationMethod() { - return implementationMethod; - } + private SubstrateUniverseFactory universeFactory = new SubstrateUniverseFactory(); - @Override - public AnalysisMethod getTargetMethod() { - return targetMethod; - } + private final Set registeredRuntimeCompilations = ConcurrentHashMap.newKeySet(); + private final Set substrateAnalysisMethods = ConcurrentHashMap.newKeySet(); + private final Map invalidForRuntimeCompilation = new ConcurrentHashMap<>(); + private final Set runtimeCompilationCandidates = ConcurrentHashMap.newKeySet(); + private CallTreeInfo callTreeMetadata = null; + private HostedProviders analysisProviders = null; + private AllowInliningPredicate allowInliningPredicate = (builder, target) -> AllowInliningPredicate.InlineDecision.INLINING_DISALLOWED; + private boolean allowInliningPredicateUpdated = false; + private Function constantFieldProviderWrapper = Function.identity(); + private Consumer blocklistChecker = (ignore) -> { + }; - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - RuntimeCompilationCandidateImpl that = (RuntimeCompilationCandidateImpl) o; - return implementationMethod.equals(that.implementationMethod) && targetMethod.equals(that.targetMethod); - } + public HostedProviders getHostedProviders() { + return hostedProviders; + } - @Override - public int hashCode() { - return Objects.hash(implementationMethod, targetMethod); - } + public GraalGraphObjectReplacer getObjectReplacer() { + return objectReplacer; } - static final class RuntimeCompiledMethodImpl implements RuntimeCompiledMethod { - final AnalysisMethod method; - final Collection inlinedMethods; + public void setUniverseFactory(SubstrateUniverseFactory universeFactory) { + this.universeFactory = universeFactory; + } - private RuntimeCompiledMethodImpl(AnalysisMethod method, Collection inlinedMethods) { - this.method = method; - this.inlinedMethods = inlinedMethods; - } + @SuppressWarnings("unused") + private static boolean defaultAllowRuntimeCompilation(ResolvedJavaMethod method) { + return false; + } - @Override - public AnalysisMethod getMethod() { - return method; - } + public void initializeRuntimeCompilationForTesting(FeatureImpl.BeforeAnalysisAccessImpl config, RuntimeCompilationCandidatePredicate newRuntimeCompilationCandidatePredicate) { + initializeRuntimeCompilationConfiguration(hostedProviders, graphBuilderConfig, newRuntimeCompilationCandidatePredicate, deoptimizeOnExceptionPredicate, blocklistChecker); + initializeRuntimeCompilationForTesting(config); + } - @Override - public Collection getInlinedMethods() { - return inlinedMethods; - } + public void initializeRuntimeCompilationForTesting(BeforeAnalysisAccessImpl config) { + initializeAnalysisProviders(config.getBigBang(), provider -> provider); + } - @Override - public Collection getInvokeTargets() { - List targets = new ArrayList<>(); - for (var invoke : method.getInvokes()) { - targets.add(invoke.getTargetMethod()); - } - return targets; - } + public void initializeRuntimeCompilationConfiguration(HostedProviders newHostedProviders, GraphBuilderConfiguration newGraphBuilderConfig, + RuntimeCompilationCandidatePredicate newRuntimeCompilationCandidatePredicate, + Predicate newDeoptimizeOnExceptionPredicate, Consumer newBlocklistChecker) { + guarantee(initialized == false, "runtime compilation configuration already initialized"); + initialized = true; + + hostedProviders = newHostedProviders; + graphBuilderConfig = newGraphBuilderConfig.withNodeSourcePosition(true); + assert !runtimeCompilationCandidatePredicateUpdated : "Updated compilation predicate multiple times"; + runtimeCompilationCandidatePredicate = newRuntimeCompilationCandidatePredicate; + runtimeCompilationCandidatePredicateUpdated = true; + deoptimizeOnExceptionPredicate = newDeoptimizeOnExceptionPredicate; + blocklistChecker = newBlocklistChecker; + } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - RuntimeCompiledMethodImpl that = (RuntimeCompiledMethodImpl) o; - return method.equals(that.method); - } + public SubstrateMethod requireFrameInformationForMethod(ResolvedJavaMethod method, BeforeAnalysisAccessImpl config, boolean registerAsRoot) { + AnalysisMethod aMethod = (AnalysisMethod) method; + SubstrateMethod sMethod = objectReplacer.createMethod(aMethod); - @Override - public int hashCode() { - return Objects.hash(method); + assert aMethod.isOriginalMethod(); + AnalysisMethod deoptTarget = aMethod.getOrCreateMultiMethod(DEOPT_TARGET_METHOD); + SubstrateCompilationDirectives.singleton().registerFrameInformationRequired(aMethod, deoptTarget); + if (registerAsRoot) { + config.registerAsRoot(aMethod, true, "Frame information required, registered in " + RuntimeCompilationFeature.class, DEOPT_TARGET_METHOD); } - } - private static final class RuntimeGraphBuilderPhase extends AnalysisGraphBuilderPhase { + return sMethod; + } - private RuntimeGraphBuilderPhase(Providers providers, - GraphBuilderConfiguration graphBuilderConfig, OptimisticOptimizations optimisticOpts, IntrinsicContext initialIntrinsicContext, SVMHost hostVM) { - super(providers, graphBuilderConfig, optimisticOpts, initialIntrinsicContext, hostVM); - } + public SubstrateMethod prepareMethodForRuntimeCompilation(Executable method, BeforeAnalysisAccessImpl config) { + return prepareMethodForRuntimeCompilation(config.getMetaAccess().lookupJavaMethod(method), config); + } - static RuntimeGraphBuilderPhase createRuntimeGraphBuilderPhase(BigBang bb, Providers providers, - GraphBuilderConfiguration graphBuilderConfig, OptimisticOptimizations optimisticOpts) { + public void registerAllowInliningPredicate(AllowInliningPredicate predicate) { + assert !allowInliningPredicateUpdated; + allowInliningPredicate = predicate; + allowInliningPredicateUpdated = true; + } - // Adjust graphbuilderconfig to match analysis phase - var newGraphBuilderConfig = graphBuilderConfig - .withEagerResolving(true) - .withUnresolvedIsError(false); - return new RuntimeGraphBuilderPhase(providers, newGraphBuilderConfig, optimisticOpts, null, (SVMHost) bb.getHostVM()); - } + public void initializeAnalysisProviders(BigBang bb, Function generator) { + HostedProviders defaultProviders = bb.getProviders(ORIGINAL_METHOD); + HostedProviders customHostedProviders = defaultProviders.copyWith(generator.apply(defaultProviders.getConstantFieldProvider())); + constantFieldProviderWrapper = generator; + customHostedProviders.setGraphBuilderPlugins(hostedProviders.getGraphBuilderPlugins()); + analysisProviders = customHostedProviders; + } - @Override - protected BytecodeParser createBytecodeParser(StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, IntrinsicContext intrinsicContext) { - return new RuntimeBytecodeParser(this, graph, parent, method, entryBCI, intrinsicContext, hostVM); - } + public CallTreeInfo getCallTreeInfo() { + VMError.guarantee(callTreeMetadata != null); + return callTreeMetadata; } - private static final class RuntimeBytecodeParser extends AnalysisGraphBuilderPhase.AnalysisBytecodeParser { + public Collection getAllRuntimeCompilationCandidates() { + return runtimeCompilationCandidates; + } - RuntimeBytecodeParser(GraphBuilderPhase.Instance graphBuilderInstance, StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, - IntrinsicContext intrinsicContext, SVMHost svmHost) { - super(graphBuilderInstance, graph, parent, method, entryBCI, intrinsicContext, svmHost, false); - } + public SubstrateMethod prepareMethodForRuntimeCompilation(ResolvedJavaMethod method, FeatureImpl.BeforeAnalysisAccessImpl config) { + AnalysisMethod aMethod = (AnalysisMethod) method; + assert aMethod.isOriginalMethod(); - @Override - protected boolean tryInvocationPlugin(CallTargetNode.InvokeKind invokeKind, ValueNode[] args, ResolvedJavaMethod targetMethod, JavaKind resultType) { - boolean result = super.tryInvocationPlugin(invokeKind, args, targetMethod, resultType); - if (result) { - SubstrateCompilationDirectives.singleton().registerAsDeoptInlininingExclude(targetMethod); - } - return result; - } + SubstrateMethod sMethod = objectReplacer.createMethod(aMethod); + substrateAnalysisMethods.add(sMethod); - @Override - protected boolean shouldVerifyFrameStates() { + if (registeredRuntimeCompilations.add(aMethod)) { + aMethod.getOrCreateMultiMethod(RUNTIME_COMPILED_METHOD); /* - * (GR-46115) Ideally we should verify frame states in methods registered for runtime - * compilations, as well as any other methods that can deoptimize. Because runtime - * compiled methods can pull in almost arbitrary code, this means most frame states - * should be verified. We currently use illegal states as placeholders in many places, - * so this cannot be enabled at the moment. + * For static methods it is important to also register the deopt targets to ensure the + * method will be linked appropriately. However, we do not need to make the entire flow + * until we see what FrameStates exist. */ - return false; + var deoptMethod = aMethod.getOrCreateMultiMethod(DEOPT_TARGET_METHOD, (newMethod) -> ((PointsToAnalysisMethod) newMethod).getTypeFlow().setAsStubFlow()); + SubstrateCompilationDirectives.singleton().registerDeoptTarget(deoptMethod); + config.registerAsRoot(aMethod, true, "Runtime compilation, registered in " + RuntimeCompilationFeature.class, RUNTIME_COMPILED_METHOD, DEOPT_TARGET_METHOD); } - } - private final Set registeredRuntimeCompilations = ConcurrentHashMap.newKeySet(); - private final Set substrateAnalysisMethods = ConcurrentHashMap.newKeySet(); - private final Map invalidForRuntimeCompilation = new ConcurrentHashMap<>(); - private final Set runtimeCompilationCandidates = ConcurrentHashMap.newKeySet(); - private Set runtimeCompilations = null; - private Map runtimeCandidateCallTree = null; - private Map runtimeCompiledMethodCallTree = null; - private HostedProviders analysisProviders = null; - private ImageHeapScanner heapScanner; - private HostedProviders runtimeCompilationProviders = null; - private AllowInliningPredicate allowInliningPredicate = (builder, target) -> AllowInliningPredicate.InlineDecision.INLINING_DISALLOWED; - private boolean allowInliningPredicateUpdated = false; - private Function constantFieldProviderWrapper = Function.identity(); + return sMethod; + } @Override public List> getRequiredFeatures() { - return RuntimeCompilationFeature.getRequiredFeaturesHelper(); + return List.of(RuntimeCompilationCanaryFeature.class, DeoptimizationFeature.class, GraalCompilerFeature.class); } @Override - public void afterRegistration(AfterRegistrationAccess access) { + public void afterRegistration(Feature.AfterRegistrationAccess access) { ImageSingletons.add(SVMParsingSupport.class, new RuntimeCompilationParsingSupport()); ImageSingletons.add(HostVM.MultiMethodAnalysisPolicy.class, new RuntimeCompilationAnalysisPolicy()); - ImageSingletons.add(RuntimeCompilationFeature.class, this); - ImageSingletons.add(RuntimeCompilationSupport.class, this); + ImageSingletons.add(RuntimeCompilationCallbacks.class, this); } @Override public void duringSetup(DuringSetupAccess c) { - duringSetupHelper(c); - } + if (SubstrateOptions.useLLVMBackend()) { + throw UserError.abort("Runtime compilation is currently unimplemented on the LLVM backend (GR-43073)."); + } + ImageSingletons.add(TruffleRuntimeCompilationSupport.class, new TruffleRuntimeCompilationSupport()); + if (!ImageSingletons.contains(SubstrateGraalCompilerSetup.class)) { + ImageSingletons.add(SubstrateGraalCompilerSetup.class, new SubstrateGraalCompilerSetup()); + } - @Override - public void beforeAnalysis(BeforeAnalysisAccess c) { - beforeAnalysisHelper(c); + DuringSetupAccessImpl config = (DuringSetupAccessImpl) c; + AnalysisMetaAccess aMetaAccess = config.getMetaAccess(); + SubstrateWordTypes wordTypes = new SubstrateWordTypes(aMetaAccess, FrameAccess.getWordKind()); + SubstrateProviders substrateProviders = ImageSingletons.lookup(SubstrateGraalCompilerSetup.class).getSubstrateProviders(aMetaAccess, wordTypes); + objectReplacer = new GraalGraphObjectReplacer(config.getUniverse(), substrateProviders, universeFactory); + config.registerObjectReplacer(objectReplacer); } - @Override - public void registerAllowInliningPredicate(AllowInliningPredicate predicate) { - assert !allowInliningPredicateUpdated; - allowInliningPredicate = predicate; - allowInliningPredicateUpdated = true; + private void installRuntimeConfig(BeforeAnalysisAccessImpl config) { + Function backendProvider = TruffleRuntimeCompilationSupport.getRuntimeBackendProvider(); + ClassInitializationSupport classInitializationSupport = config.getHostVM().getClassInitializationSupport(); + Providers originalProviders = GraalAccess.getOriginalProviders(); + SubstratePlatformConfigurationProvider platformConfig = new SubstratePlatformConfigurationProvider( + ImageSingletons.lookup(BarrierSetProvider.class).createBarrierSet(config.getMetaAccess())); + RuntimeConfiguration runtimeConfig = ImageSingletons.lookup(SubstrateGraalCompilerSetup.class) + .createRuntimeConfigurationBuilder(RuntimeOptionValues.singleton(), config.getHostVM(), config.getUniverse(), config.getMetaAccess(), + backendProvider, classInitializationSupport, originalProviders.getLoopsDataProvider(), platformConfig, + config.getBigBang().getSnippetReflectionProvider()) + .build(); + + Providers runtimeProviders = runtimeConfig.getProviders(); + hostedProviders = new HostedProviders(runtimeProviders.getMetaAccess(), runtimeProviders.getCodeCache(), runtimeProviders.getConstantReflection(), + runtimeProviders.getConstantFieldProvider(), + runtimeProviders.getForeignCalls(), runtimeProviders.getLowerer(), runtimeProviders.getReplacements(), runtimeProviders.getStampProvider(), + runtimeConfig.getSnippetReflection(), runtimeProviders.getWordTypes(), runtimeProviders.getPlatformConfigurationProvider(), new GraphPrepareMetaAccessExtensionProvider(), + runtimeProviders.getLoopsDataProvider()); + + FeatureHandler featureHandler = config.getFeatureHandler(); + final boolean supportsStubBasedPlugins = !SubstrateOptions.useLLVMBackend(); + + NativeImageGenerator.registerGraphBuilderPlugins(featureHandler, runtimeConfig, hostedProviders, config.getMetaAccess(), config.getUniverse(), null, config.getNativeLibraries(), + config.getImageClassLoader(), ParsingReason.JITCompilation, ((Inflation) config.getBigBang()).getAnnotationSubstitutionProcessor(), + new SubstrateClassInitializationPlugin(config.getHostVM()), ConfigurationValues.getTarget(), supportsStubBasedPlugins); + + NativeImageGenerator.registerReplacements(DebugContext.forCurrentThread(), featureHandler, runtimeConfig, runtimeConfig.getProviders(), false, true, + new RuntimeCompiledMethodSupport.RuntimeCompilationGraphEncoder(ConfigurationValues.getTarget().arch, config.getUniverse().getHeapScanner())); + + featureHandler.forEachGraalFeature(feature -> feature.registerCodeObserver(runtimeConfig)); + Suites suites = NativeImageGenerator.createSuites(featureHandler, runtimeConfig, runtimeConfig.getSnippetReflection(), false); + LIRSuites lirSuites = NativeImageGenerator.createLIRSuites(featureHandler, runtimeConfig.getProviders(), false); + Suites firstTierSuites = NativeImageGenerator.createFirstTierSuites(featureHandler, runtimeConfig, runtimeConfig.getSnippetReflection(), false); + LIRSuites firstTierLirSuites = NativeImageGenerator.createFirstTierLIRSuites(featureHandler, runtimeConfig.getProviders(), false); + + TruffleRuntimeCompilationSupport.setRuntimeConfig(runtimeConfig, suites, lirSuites, firstTierSuites, firstTierLirSuites); } @Override - public void initializeAnalysisProviders(BigBang bb, Function generator) { - HostedProviders defaultProviders = bb.getProviders(ORIGINAL_METHOD); - HostedProviders customHostedProviders = defaultProviders.copyWith(generator.apply(defaultProviders.getConstantFieldProvider())); - constantFieldProviderWrapper = generator; - customHostedProviders.setGraphBuilderPlugins(hostedProviders.getGraphBuilderPlugins()); - analysisProviders = customHostedProviders; - heapScanner = bb.getUniverse().getHeapScanner(); + public void beforeAnalysis(BeforeAnalysisAccess c) { + + BeforeAnalysisAccessImpl config = (BeforeAnalysisAccessImpl) c; + installRuntimeConfig(config); + + SubstrateGraalRuntime graalRuntime = new SubstrateGraalRuntime(); + objectReplacer.setGraalRuntime(graalRuntime); + objectReplacer.setAnalysisAccess(config); + ImageSingletons.add(GraalRuntime.class, graalRuntime); + RuntimeSupport.getRuntimeSupport().addShutdownHook(new GraalSupport.GraalShutdownHook()); + + /* Initialize configuration with reasonable default values. */ + graphBuilderConfig = GraphBuilderConfiguration.getDefault(hostedProviders.getGraphBuilderPlugins()).withBytecodeExceptionMode(BytecodeExceptionMode.ExplicitOnly); + runtimeCompilationCandidatePredicate = RuntimeCompilationFeature::defaultAllowRuntimeCompilation; + optimisticOpts = OptimisticOptimizations.ALL.remove(OptimisticOptimizations.Optimization.UseLoopLimitChecks); + graphEncoder = new RuntimeCompiledMethodSupport.RuntimeCompilationGraphEncoder(ConfigurationValues.getTarget().arch, config.getUniverse().getHeapScanner()); + + /* + * Ensure all snippet types are registered as used. + */ + SubstrateReplacements replacements = (SubstrateReplacements) TruffleRuntimeCompilationSupport.getRuntimeConfig().getProviders().getReplacements(); + for (NodeClass nodeClass : replacements.getSnippetNodeClasses()) { + config.getMetaAccess().lookupJavaType(nodeClass.getClazz()).registerAsAllocated("All " + NodeClass.class.getName() + " classes are marked as instantiated eagerly."); + } + /* + * Ensure runtime snippet graphs are analyzed. + */ + NativeImageGenerator.performSnippetGraphAnalysis(config.getBigBang(), replacements, config.getBigBang().getOptions()); + + /* + * Ensure that all snippet methods have their SubstrateMethod object created by the object + * replacer, to avoid corner cases later when writing the native image. + */ + for (ResolvedJavaMethod method : replacements.getSnippetMethods()) { + objectReplacer.apply(method); + } } boolean newRuntimeMethodsSeen = false; @Override - public void duringAnalysis(DuringAnalysisAccess c) { + public void duringAnalysis(Feature.DuringAnalysisAccess c) { /* * Note this will be removed once graphEncoder and the graal graph object replacer are * thread friendly. @@ -403,375 +466,129 @@ public void duringAnalysis(DuringAnalysisAccess c) { } } - @Override - public void afterAnalysis(AfterAnalysisAccess access) { - /* - * At this point need to determine which methods are actually valid for runtime compilation - * and calculate their reachability info. - */ - buildCallTrees(); - - runtimeCompilations = new HashSet<>(); - FeatureImpl.AfterAnalysisAccessImpl impl = (FeatureImpl.AfterAnalysisAccessImpl) access; - for (var method : impl.getUniverse().getMethods()) { - var rMethod = method.getMultiMethod(RUNTIME_COMPILED_METHOD); - if (rMethod != null && rMethod.isReachable() && !invalidForRuntimeCompilation.containsKey(rMethod)) { - var runtimeInlinedMethods = rMethod.getAnalyzedGraph().getInlinedMethods(); - var inlinedMethods = runtimeInlinedMethods.stream().map(inlinedMethod -> { - ResolvedJavaMethod orig = ((AnalysisMethod) inlinedMethod).getMultiMethod(ORIGINAL_METHOD); - assert orig != null; - return orig; - }).collect(Collectors.toUnmodifiableSet()); - boolean added = runtimeCompilations.add(new RuntimeCompiledMethodImpl(method, inlinedMethods)); - assert added; - assert runtimeCompiledMethodCallTree.containsKey(method); + private void checkMaxRuntimeCompiledMethods(CallTreeInfo callTreeInfo) { + int maxMethods = 0; + for (String value : Options.MaxRuntimeCompileMethods.getValue().values()) { + String numberStr = null; + try { + /* Strip optional comment string from MaxRuntimeCompileMethods value */ + numberStr = value.split("#")[0]; + maxMethods += Integer.parseInt(numberStr); + } catch (NumberFormatException ex) { + throw UserError.abort("Invalid value for option 'MaxRuntimeCompileMethods': '%s' is not a valid number", numberStr); } } - // call super after - afterAnalysisHelper(); - - // after analysis has completed we must ensure no new SubstrateTypes are introduced - objectReplacer.forbidNewTypes(); - } - - @Override - protected AbstractCallTreeNode getCallTreeNode(RuntimeCompilationCandidate candidate) { - var result = runtimeCandidateCallTree.get(candidate); - assert result != null; - return result; - } - - @Override - protected AbstractCallTreeNode getCallTreeNode(RuntimeCompiledMethod method) { - return getCallTreeNode(method.getMethod()); - } - - @Override - protected AbstractCallTreeNode getCallTreeNode(ResolvedJavaMethod method) { - ResolvedJavaMethod origMethod = method instanceof MultiMethod m ? (ResolvedJavaMethod) m.getMultiMethod(ORIGINAL_METHOD) : method; - var result = runtimeCompiledMethodCallTree.get(origMethod); - assert result != null; - return result; - } - - @Override - public Collection getRuntimeCompiledMethods() { - return runtimeCompilations; - } - - @Override - public Collection getAllRuntimeCompilationCandidates() { - return runtimeCompilationCandidates; - } - - private void buildCallTrees() { - /* - * While it is possible to dynamically calculate call traces by enabling - * PointstoOptions#TraceAccessChain, creating call trees post-analysis for runtime compiled - * methods allows use to not have this overhead during analysis and also to determine the - * access chains for multiple call sites with the same destination. - * - * This is useful to create more stringent blocklist checks. - */ - assert runtimeCandidateCallTree == null && runtimeCompiledMethodCallTree == null; - runtimeCandidateCallTree = new HashMap<>(); - runtimeCompiledMethodCallTree = new HashMap<>(); - - Queue worklist = new LinkedList<>(); - - /* First initialize with registered runtime compilations */ - for (AnalysisMethod root : registeredRuntimeCompilations) { - var runtimeRoot = root.getMultiMethod(RUNTIME_COMPILED_METHOD); - if (runtimeRoot != null) { - runtimeCandidateCallTree.computeIfAbsent(new RuntimeCompilationCandidateImpl(root, root), (candidate) -> { - var result = new CallTreeNode(root, root, null, new BytecodePosition(null, root, BytecodeFrame.UNKNOWN_BCI), false); - worklist.add(result); - return result; - }); - } - } - - /* - * Find all runtime compiled methods reachable from registered runtime compilations. - * - * Note within the maps we store the original methods, not the runtime methods. - */ - while (!worklist.isEmpty()) { - CallTreeNode caller = worklist.remove(); - caller.linkAsChild(); - - /* - * We only need to record one trace for methods - */ - var method = caller.getImplementationMethod(); - if (runtimeCompiledMethodCallTree.containsKey(method)) { - // This method has already been processed - continue; - } else { - runtimeCompiledMethodCallTree.put(method, caller); - } - AnalysisMethod runtimeMethod = method.getMultiMethod(RUNTIME_COMPILED_METHOD); - assert runtimeMethod != null; - - for (InvokeInfo invokeInfo : runtimeMethod.getInvokes()) { - AnalysisMethod invokeTarget = invokeInfo.getTargetMethod(); - boolean deoptInvokeTypeFlow = invokeInfo.isDeoptInvokeTypeFlow(); - if (deoptInvokeTypeFlow) { - assert SubstrateCompilationDirectives.isRuntimeCompiledMethod(invokeTarget); - invokeTarget = invokeTarget.getMultiMethod(ORIGINAL_METHOD); - } - AnalysisMethod target = invokeTarget; - assert target.isOriginalMethod(); - for (AnalysisMethod implementation : invokeInfo.getAllCallees()) { - if (deoptInvokeTypeFlow || SubstrateCompilationDirectives.isRuntimeCompiledMethod(implementation)) { - var origImpl = implementation.getMultiMethod(ORIGINAL_METHOD); - assert origImpl != null; - runtimeCandidateCallTree.computeIfAbsent(new RuntimeCompilationCandidateImpl(origImpl, target), (candidate) -> { - var result = new CallTreeNode(origImpl, target, caller, invokeInfo.getPosition(), deoptInvokeTypeFlow); - worklist.add(result); - return result; - }); - } else if (implementation.isOriginalMethod() && implementation.getMultiMethod(RUNTIME_COMPILED_METHOD) == null) { - /* - * Recording that this call was reachable, but not converted to a runtime - * compiled method. - */ - runtimeCandidateCallTree.computeIfAbsent(new RuntimeCompilationCandidateImpl(implementation, target), - (candidate) -> { - var result = new CallTreeNode(implementation, target, caller, invokeInfo.getPosition(), false); - result.linkAsChild(); - return result; - }); - } - } - } - } - } - - public Set parsedRuntimeMethods = ConcurrentHashMap.newKeySet(); - public AtomicLong totalParsedRuntimeMethods = new AtomicLong(); - public Set parsedDeoptMethods = ConcurrentHashMap.newKeySet(); - public AtomicLong totalParsedDeoptMethods = new AtomicLong(); - - private class RuntimeCompileTask implements CompletionExecutor.DebugContextRunnable { - final HostedMethod method; - - RuntimeCompileTask(HostedMethod method) { - this.method = method; - } - - @Override - public DebugContext getDebug(OptionValues options, List factories) { - return new DebugContext.Builder(options, factories).description(getDescription()).build(); - } - - @Override - public void run(DebugContext debug) { - compileRuntimeCompiledMethod(debug, method); + if (Options.EnforceMaxRuntimeCompileMethods.getValue() && maxMethods != 0 && callTreeInfo.runtimeCompilations().size() > maxMethods) { + CallTreeInfo.printDeepestPath(callTreeInfo, registeredRuntimeCompilations); + throw VMError.shouldNotReachHere("Number of methods for runtime compilation exceeds the allowed limit: " + callTreeInfo.runtimeCompilations().size() + " > " + maxMethods); } } - private final Map runtimeGraphs = new ConcurrentHashMap<>(); - - @SuppressWarnings("try") - private void compileRuntimeCompiledMethod(DebugContext debug, HostedMethod method) { - assert method.getMultiMethodKey() == RUNTIME_COMPILED_METHOD; - - AnalysisMethod aMethod = method.getWrapped(); - StructuredGraph graph = aMethod.decodeAnalyzedGraph(debug, null, false, - (arch, analyzedGraph) -> new RuntimeCompilationGraphDecoder(arch, analyzedGraph, heapScanner)); - if (graph == null) { - throw VMError.shouldNotReachHere("Method not parsed during static analysis: " + aMethod.format("%r %H.%n(%p)")); - } - /* - * The graph in the analysis universe is no longer necessary once it is transplanted into - * the hosted universe. - */ - aMethod.setAnalyzedGraph(null); - - try (DebugContext.Scope s = debug.scope("RuntimeOptimize", graph, method, this)) { - CanonicalizerPhase canonicalizer = CanonicalizerPhase.create(); - canonicalizer.apply(graph, runtimeCompilationProviders); - - new DominatorBasedGlobalValueNumberingPhase(canonicalizer).apply(graph, runtimeCompilationProviders); - - new IterativeConditionalEliminationPhase(canonicalizer, true).apply(graph, runtimeCompilationProviders); - - /* - * ConvertDeoptimizeToGuardPhase was already executed after parsing, but optimizations - * applied in between can provide new potential. - */ - new ConvertDeoptimizeToGuardPhase(canonicalizer).apply(graph, runtimeCompilationProviders); + @Override + public void afterAnalysis(Feature.AfterAnalysisAccess access) { + VMError.guarantee(callTreeMetadata == null); + callTreeMetadata = CallTreeInfo.create(((FeatureImpl.AfterAnalysisAccessImpl) access).getUniverse(), invalidForRuntimeCompilation); - /* - * More optimizations can be added here. - */ - } catch (Throwable e) { - throw debug.handle(e); - } + ProgressReporter.singleton().setNumRuntimeCompiledMethods(callTreeMetadata.runtimeCompilations().size()); + // after analysis has completed we must ensure no new SubstrateTypes are introduced + objectReplacer.forbidNewTypes(); - /* - * Registering all deopt entries seen within the optimized graph. This should be strictly a - * subset of the deopt entrypoints seen during evaluation. - */ - AnalysisMethod origMethod = method.getMultiMethod(ORIGINAL_METHOD).getWrapped(); - DeoptimizationUtils.registerDeoptEntries(graph, registeredRuntimeCompilations.contains(origMethod), - (deoptEntryMethod -> { - PointsToAnalysisMethod deoptMethod = (PointsToAnalysisMethod) ((PointsToAnalysisMethod) deoptEntryMethod).getMultiMethod(DEOPT_TARGET_METHOD); - VMError.guarantee(deoptMethod != null, "New deopt target method seen: %s", deoptEntryMethod); - return deoptMethod; - })); - - assert RuntimeCompilationFeature.verifyNodes(graph); - var previous = runtimeGraphs.put(method, graph); - assert previous == null; - - // graph encoder is not currently threadsafe - synchronized (this) { - graphEncoder.prepare(graph); + VMError.guarantee(callTreeMetadata != null); + if (Options.PrintRuntimeCompileMethods.getValue()) { + System.out.println("****Start Print Runtime Compile Methods***"); + callTreeMetadata.runtimeCompilations().stream().map(m -> m.getRuntimeMethod().format("%H.%n(%p)")).sorted().toList().forEach(System.out::println); + System.out.println("****End Print Runtime Compile Methods***"); } - } - @SuppressWarnings("try") - private void encodeRuntimeCompiledMethods() { - graphEncoder.finishPrepare(); - - // at this point no new deoptimization entrypoints can be registered. - SubstrateCompilationDirectives.singleton().sealDeoptimizationInfo(); - - for (var runtimeInfo : runtimeGraphs.entrySet()) { - var graph = runtimeInfo.getValue(); - var method = runtimeInfo.getKey(); - DebugContext debug = new DebugContext.Builder(graph.getOptions(), new GraalDebugHandlersFactory(runtimeCompilationProviders.getSnippetReflection())).build(); - graph.resetDebug(debug); - try (DebugContext.Scope s = debug.scope("Graph Encoding", graph); - DebugContext.Activation a = debug.activate()) { - long startOffset = graphEncoder.encode(graph); - objectReplacer.createMethod(method).setEncodedGraphStartOffset(startOffset); - } catch (Throwable ex) { - debug.handle(ex); - } + if (Options.PrintRuntimeCompilationCallTree.getValue()) { + System.out.println("****Start Print Runtime Compile Call Tree***"); + CallTreeInfo.printCallTree(callTreeMetadata, registeredRuntimeCompilations); + System.out.println("****End Print Runtime Compile Call Tree***"); } - HeapBreakdownProvider.singleton().setGraphEncodingByteLength(graphEncoder.getEncoding().length); - TruffleRuntimeCompilationSupport.setGraphEncoding(null, graphEncoder.getEncoding(), graphEncoder.getObjects(), graphEncoder.getNodeClasses()); - - objectReplacer.setMethodsImplementations(); - - /* All the temporary data structures used during encoding are no longer necessary. */ - graphEncoder = null; - } - - @Override - public void beforeCompilation(BeforeCompilationAccess c) { - beforeCompilationHelper(); + checkMaxRuntimeCompiledMethods(callTreeMetadata); } @Override public void onCompileQueueCreation(BigBang bb, HostedUniverse hUniverse, CompileQueue compileQueue) { - /* - * Start fresh with a new GraphEncoder, since we are going to optimize all graphs now that - * the static analysis results are available. - */ - graphEncoder = new RuntimeCompilationGraphEncoder(ConfigurationValues.getTarget().arch, bb.getUniverse().getHeapScanner()); - assert runtimeCompilationProviders == null; - runtimeCompilationProviders = hostedProviders - .copyWith(constantFieldProviderWrapper.apply(new RuntimeCompilationFieldProvider(hostedProviders.getMetaAccess(), hUniverse))) - .copyWith(new RuntimeCompilationReflectionProvider(bb, hUniverse.hostVM().getClassInitializationSupport())); - - SubstrateCompilationDirectives.singleton().resetDeoptEntries(); - /* - * Customize runtime compile methods for compiling them into substrate graphs. - */ - CompletionExecutor executor = compileQueue.getExecutor(); - try { - compileQueue.runOnExecutor(() -> { - hUniverse.getMethods().stream().map(method -> method.getMultiMethod(RUNTIME_COMPILED_METHOD)).filter(method -> { - if (method != null) { - AnalysisMethod aMethod = method.getWrapped(); - return aMethod.isImplementationInvoked() && !invalidForRuntimeCompilation.containsKey(aMethod); - } - return false; - }).forEach(method -> { - executor.execute(new RuntimeCompileTask(method)); - }); - }); - } catch (InterruptedException exception) { - VMError.shouldNotReachHere(exception); - } - encodeRuntimeCompiledMethods(); - - /* - * For Deoptimization Targets add a custom phase which removes all deoptimization - * entrypoints which are deemed no longer necessary. - */ - CompileQueue.ParseHooks deoptParseHooks = new CompileQueue.ParseHooks(compileQueue) { - @Override - protected PhaseSuite getAfterParseSuite() { - PhaseSuite suite = super.getAfterParseSuite(); - if (Options.RemoveUnneededDeoptSupport.getValue()) { - suite.prependPhase(new RemoveUnneededDeoptSupport()); - } - - return suite; - } - }; - - hUniverse.getMethods().stream().map(method -> method.getMultiMethod(DEOPT_TARGET_METHOD)).filter(method -> { + graphEncoder = null; + Stream methodsToCompile = hUniverse.getMethods().stream().map(method -> method.getMultiMethod(RUNTIME_COMPILED_METHOD)).filter(method -> { if (method != null) { - return compileQueue.isRegisteredDeoptTarget(method); + AnalysisMethod aMethod = method.getWrapped(); + return aMethod.isImplementationInvoked() && !invalidForRuntimeCompilation.containsKey(aMethod); } return false; - }).forEach(method -> method.compilationInfo.setCustomParseHooks(deoptParseHooks)); - + }); + RuntimeCompiledMethodSupport.onCompileQueueCreation(bb, hUniverse, compileQueue, hostedProviders, constantFieldProviderWrapper, objectReplacer, + registeredRuntimeCompilations, methodsToCompile); } @Override public void afterCompilation(AfterCompilationAccess a) { - super.afterCompilationHelper(a); + CompilationAccessImpl config = (CompilationAccessImpl) a; + + HostedMetaAccess hMetaAccess = config.getMetaAccess(); + HostedUniverse hUniverse = hMetaAccess.getUniverse(); + objectReplacer.updateSubstrateDataAfterCompilation(hUniverse, config.getProviders()); } @Override public void beforeHeapLayout(BeforeHeapLayoutAccess a) { - super.beforeHeapLayoutHelper(a); + objectReplacer.registerImmutableObjects(a); + TruffleRuntimeCompilationSupport.registerImmutableObjects(a); + ((SubstrateReplacements) TruffleRuntimeCompilationSupport.getRuntimeConfig().getProviders().getReplacements()).registerImmutableObjects(a); } @Override public void afterHeapLayout(AfterHeapLayoutAccess a) { - afterHeapLayoutHelper(a); + AfterHeapLayoutAccessImpl config = (AfterHeapLayoutAccessImpl) a; + HostedMetaAccess hMetaAccess = config.getMetaAccess(); + HostedUniverse hUniverse = hMetaAccess.getUniverse(); + objectReplacer.updateSubstrateDataAfterHeapLayout(hUniverse); } + /** + * When a failure occurs during parsing, it is likely due to a missing truffle boundary. Check + * to see if anything on the blocklist has been reached and/or the number of runtime compiled + * methods exceeds the maximum allowed. + */ @Override - public SubstrateMethod prepareMethodForRuntimeCompilation(ResolvedJavaMethod method, FeatureImpl.BeforeAnalysisAccessImpl config) { - AnalysisMethod aMethod = (AnalysisMethod) method; - assert aMethod.isOriginalMethod(); - - SubstrateMethod sMethod = objectReplacer.createMethod(aMethod); - substrateAnalysisMethods.add(sMethod); - - if (registeredRuntimeCompilations.add(aMethod)) { - aMethod.getOrCreateMultiMethod(RUNTIME_COMPILED_METHOD); - /* - * For static methods it is important to also register the deopt targets to ensure the - * method will be linked appropriately. However, we do not need to make the entire flow - * until we see what FrameStates exist. - */ - var deoptMethod = aMethod.getOrCreateMultiMethod(DEOPT_TARGET_METHOD, (newMethod) -> ((PointsToAnalysisMethod) newMethod).getTypeFlow().setAsStubFlow()); - SubstrateCompilationDirectives.singleton().registerDeoptTarget(deoptMethod); - config.registerAsRoot(aMethod, true, "Runtime compilation, registered in " + ParseOnceRuntimeCompilationFeature.class, RUNTIME_COMPILED_METHOD, DEOPT_TARGET_METHOD); + public void reportAnalysisError(AnalysisUniverse aUniverse, Throwable t) { + var treeInfo = CallTreeInfo.create(aUniverse, invalidForRuntimeCompilation); + + blocklistChecker.accept(treeInfo); + + checkMaxRuntimeCompiledMethods(treeInfo); + + boolean foundError = false; + if (t instanceof ParallelExecutionException exception) { + for (var e : exception.getExceptions()) { + if (e instanceof AnalysisError.ParsingError parsingError) { + AnalysisMethod errorMethod = parsingError.getMethod(); + if (errorMethod.isDeoptTarget() || SubstrateCompilationDirectives.isRuntimeCompiledMethod(errorMethod)) { + foundError = true; + AnalysisMethod failingRuntimeMethod = null; + if (SubstrateCompilationDirectives.isRuntimeCompiledMethod(errorMethod)) { + failingRuntimeMethod = errorMethod; + } else if (errorMethod.isDeoptTarget()) { + failingRuntimeMethod = errorMethod.getMultiMethod(RUNTIME_COMPILED_METHOD); + } + System.out.println("Parsing failed on a special method version: " + errorMethod.format("%H.%n")); + System.out.println("Method reachability trace"); + if (failingRuntimeMethod != null) { + Arrays.stream(CallTreeInfo.getCallTrace(treeInfo, failingRuntimeMethod)).forEach(System.out::println); + } else { + System.out.println("trace unavailable"); + } + System.out.println("error: " + e.getMessage()); + } + } + } } - return sMethod; - } - - @Override - protected void requireFrameInformationForMethodHelper(AnalysisMethod aMethod, FeatureImpl.BeforeAnalysisAccessImpl config, boolean registerAsRoot) { - assert aMethod.isOriginalMethod(); - AnalysisMethod deoptTarget = aMethod.getOrCreateMultiMethod(DEOPT_TARGET_METHOD); - SubstrateCompilationDirectives.singleton().registerFrameInformationRequired(aMethod, deoptTarget); - if (registerAsRoot) { - config.registerAsRoot(aMethod, true, "Frame information required, registered in " + ParseOnceRuntimeCompilationFeature.class, DEOPT_TARGET_METHOD); + if (foundError) { + throw VMError.shouldNotReachHere("Analysis failed while parsing deopt and/or runtime methods"); } } @@ -845,7 +662,7 @@ private Object parseRuntimeCompiledMethod(BigBang bb, DebugContext debug, Analys if (parsed) { // enable this logging to get log output in compilation passes try (Indent indent2 = debug.logAndIndent("parse graph phases")) { - RuntimeGraphBuilderPhase.createRuntimeGraphBuilderPhase(bb, analysisProviders, graphBuilderConfig, optimisticOpts).apply(graph); + RuntimeCompiledMethodSupport.RuntimeGraphBuilderPhase.createRuntimeGraphBuilderPhase(bb, analysisProviders, graphBuilderConfig, optimisticOpts).apply(graph); } catch (PermanentBailoutException ex) { bb.getUnsupportedFeatures().addMessage(method.format("%H.%n(%p)"), method, ex.getLocalizedMessage(), null, ex); recordFailed(method); @@ -893,8 +710,6 @@ public boolean validateGraph(PointsToAnalysis bb, StructuredGraph graph) { } } if (multiMethodKey == RUNTIME_COMPILED_METHOD) { - parsedRuntimeMethods.add(aMethod); - totalParsedRuntimeMethods.incrementAndGet(); /* * Register all FrameStates as DeoptEntries. */ @@ -924,10 +739,7 @@ public boolean validateGraph(PointsToAnalysis bb, StructuredGraph graph) { substrateAnalysisMethods.add(sMethod); graphEncoder.prepare(graph); } - assert RuntimeCompilationFeature.verifyNodes(graph); - } else if (multiMethodKey == DEOPT_TARGET_METHOD) { - parsedDeoptMethods.add(aMethod); - totalParsedDeoptMethods.incrementAndGet(); + assert RuntimeCompiledMethodSupport.verifyNodes(graph); } return true; @@ -935,7 +747,7 @@ public boolean validateGraph(PointsToAnalysis bb, StructuredGraph graph) { @Override public void initializeInlineBeforeAnalysisPolicy(SVMHost svmHost, InlineBeforeAnalysisPolicyUtils inliningUtils) { - if (Options.RuntimeCompilationInlineBeforeAnalysis.getValue()) { + if (RuntimeCompilationFeature.Options.RuntimeCompilationInlineBeforeAnalysis.getValue()) { assert runtimeInlineBeforeAnalysisPolicy == null; runtimeInlineBeforeAnalysisPolicy = new RuntimeCompilationInlineBeforeAnalysisPolicy(svmHost, inliningUtils); } @@ -948,7 +760,7 @@ public InlineBeforeAnalysisPolicy inlineBeforeAnalysisPolicy(MultiMethod.MultiMe } else if (multiMethodKey == DEOPT_TARGET_METHOD) { return InlineBeforeAnalysisPolicy.NO_INLINING; } else if (multiMethodKey == RUNTIME_COMPILED_METHOD) { - if (Options.RuntimeCompilationInlineBeforeAnalysis.getValue()) { + if (RuntimeCompilationFeature.Options.RuntimeCompilationInlineBeforeAnalysis.getValue()) { assert runtimeInlineBeforeAnalysisPolicy != null; return runtimeInlineBeforeAnalysisPolicy; } @@ -1061,7 +873,7 @@ protected AbstractPolicyScope createRootScope() { @Override protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess, AnalysisMethod method, boolean[] constArgsWithReceiver, boolean intrinsifiedMethodHandle) { - if (outer instanceof AccumulativeInlineScope accOuter) { + if (outer instanceof InlineBeforeAnalysisPolicyUtils.AccumulativeInlineScope accOuter) { /* * once the accumulative policy is activated, then we cannot return to the trivial * policy @@ -1069,13 +881,13 @@ protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, Analysi return inliningUtils.createAccumulativeInlineScope(accOuter, metaAccess, method, constArgsWithReceiver, intrinsifiedMethodHandle); } - assert outer == null || outer instanceof AlwaysInlineScope : "unexpected outer scope: " + outer; + assert outer == null || outer instanceof InlineBeforeAnalysisPolicyUtils.AlwaysInlineScope : "unexpected outer scope: " + outer; // check if trivial is possible boolean trivialInlineAllowed = hostVM.isAnalysisTrivialMethod(method); int inliningDepth = outer == null ? 1 : outer.inliningDepth + 1; if (trivialInlineAllowed && inliningDepth <= trivialAllowingInliningDepth) { - return new AlwaysInlineScope(inliningDepth); + return new InlineBeforeAnalysisPolicyUtils.AlwaysInlineScope(inliningDepth); } else { // start with a new accumulative inline scope return inliningUtils.createAccumulativeInlineScope(null, metaAccess, method, constArgsWithReceiver, intrinsifiedMethodHandle); @@ -1096,7 +908,7 @@ public Collection determineCallees(BigBang bb, T i assert SubstrateCompilationDirectives.isRuntimeCompiledMethod(implementation); var originalTarget = implementation.getMultiMethod(ORIGINAL_METHOD); assert originalTarget != null; - runtimeCompilationCandidates.add(new RuntimeCompilationCandidateImpl(originalTarget, originalTarget)); + runtimeCompilationCandidates.add(new RuntimeCompilationCandidate(originalTarget, originalTarget)); return List.of(getStubDeoptVersion(implementation)); } assert implementation.isOriginalMethod() && target.isOriginalMethod(); @@ -1129,7 +941,7 @@ public Collection determineCallees(BigBang bb, T i if (callerMultiMethodKey == RUNTIME_COMPILED_METHOD) { // recording compilation candidate - runtimeCompilationCandidates.add(new RuntimeCompilationCandidateImpl(implementation, target)); + runtimeCompilationCandidates.add(new RuntimeCompilationCandidate(implementation, target)); /* * The runtime method can call all three types: original (if it is not partial * evaluated), runtime (if it is partial evaluated), and deoptimized (if the @@ -1224,7 +1036,9 @@ public boolean performParameterLinking(MultiMethod.MultiMethodKey callerMultiMet @Override public boolean performReturnLinking(MultiMethod.MultiMethodKey callerMultiMethodKey, MultiMethod.MultiMethodKey calleeMultiMethodKey) { if (callerMultiMethodKey == RUNTIME_COMPILED_METHOD) { - /* A runtime method can be returned to from either a runtime or original method. */ + /* + * A runtime method can be returned to from either a runtime or original method. + */ return calleeMultiMethodKey == RUNTIME_COMPILED_METHOD || calleeMultiMethodKey == ORIGINAL_METHOD; } else if (callerMultiMethodKey == DEOPT_TARGET_METHOD) { /* A deopt method can be returned to from all three. */ @@ -1266,143 +1080,33 @@ public boolean unknownReturnValue(BigBang bb, MultiMethod.MultiMethodKey callerM return false; } } +} - /** - * Since we perform runtime compilation after universe creation, we can leverage components of - * the hosted universe provider for identifying final fields. - */ - private static class RuntimeCompilationFieldProvider extends AnalysisConstantFieldProvider { - final HostedUniverse hUniverse; - - RuntimeCompilationFieldProvider(MetaAccessProvider metaAccess, HostedUniverse hUniverse) { - super(metaAccess, hUniverse.hostVM()); - this.hUniverse = hUniverse; - } +/** + * Same behavior as {@link SubstrateMetaAccessExtensionProvider}, but operating on + * {@link AnalysisType} instead of {@link SharedType} since parsing of graphs for runtime + * compilation happens in the Analysis universe. + */ +class GraphPrepareMetaAccessExtensionProvider implements MetaAccessExtensionProvider { - @Override - public boolean isFinalField(ResolvedJavaField f, ConstantFieldTool tool) { - HostedField hField = hUniverse.lookup(f); - if (HostedConstantFieldProvider.isFinalField(hField)) { - return true; - } - return super.isFinalField(f, tool); - } + @Override + public JavaKind getStorageKind(JavaType type) { + return ((AnalysisType) type).getStorageKind(); } - private static class RuntimeCompilationReflectionProvider extends AnalysisConstantReflectionProvider { - - RuntimeCompilationReflectionProvider(BigBang bb, ClassInitializationSupport classInitializationSupport) { - super(bb.getUniverse(), bb.getMetaAccess(), classInitializationSupport); - } - - @Override - public JavaConstant readFieldValue(ResolvedJavaField field, JavaConstant receiver) { - /* - * We cannot fold simulated values during initial before-analysis graph creation; - * however, this runs after analysis has completed. - */ - return readValue(metaAccess, (AnalysisField) field, receiver, true); - } + @Override + public boolean canConstantFoldDynamicAllocation(ResolvedJavaType type) { + assert type instanceof AnalysisType : "AnalysisType is required; AnalysisType lazily creates array types of any depth, so type cannot be null"; + return ((AnalysisType) type).isInstantiated(); } - /** - * Removes {@link DeoptEntryNode}s, {@link DeoptProxyAnchorNode}s, and {@link DeoptProxyNode}s - * which are determined to be unnecessary after the runtime compilation methods are optimized. - */ - static class RemoveUnneededDeoptSupport extends Phase { - enum RemovalDecision { - KEEP, - PROXIFY, - REMOVE - } - - @Override - protected void run(StructuredGraph graph) { - EconomicMap decisionCache = EconomicMap.create(); - - // First go through and delete all unneeded proxies - for (DeoptProxyNode proxyNode : graph.getNodes(DeoptProxyNode.TYPE).snapshot()) { - ValueNode proxyPoint = proxyNode.getProxyPoint(); - if (proxyPoint instanceof StateSplit) { - if (getDecision((StateSplit) proxyPoint, decisionCache) == RemovalDecision.REMOVE) { - proxyNode.replaceAtAllUsages(proxyNode.getOriginalNode(), true); - proxyNode.safeDelete(); - } - } - } - - // Next, remove all unneeded DeoptEntryNodes - for (DeoptEntryNode deoptEntry : graph.getNodes().filter(DeoptEntryNode.class).snapshot()) { - switch (getDecision(deoptEntry, decisionCache)) { - case REMOVE -> { - deoptEntry.killExceptionEdge(); - graph.removeSplit(deoptEntry, deoptEntry.getPrimarySuccessor()); - } - case PROXIFY -> { - deoptEntry.killExceptionEdge(); - DeoptProxyAnchorNode newAnchor = graph.add(new DeoptProxyAnchorNode(deoptEntry.getProxifiedInvokeBci())); - newAnchor.setStateAfter(deoptEntry.stateAfter()); - graph.replaceSplitWithFixed(deoptEntry, newAnchor, deoptEntry.getPrimarySuccessor()); - } - } - } - - // Finally, remove all unneeded DeoptProxyAnchorNodes - for (DeoptProxyAnchorNode proxyAnchor : graph.getNodes().filter(DeoptProxyAnchorNode.class).snapshot()) { - if (getDecision(proxyAnchor, decisionCache) == RemovalDecision.REMOVE) { - graph.removeFixed(proxyAnchor); - } - } - } - - RemovalDecision getDecision(StateSplit node, EconomicMap decisionCache) { - RemovalDecision cached = decisionCache.get(node); - if (cached != null) { - return cached; - } - - DeoptEntrySupport proxyNode; - if (node instanceof ExceptionObjectNode exceptionObject) { - /* - * For the exception edge of a DeoptEntryNode, we insert the proxies on the - * exception object. - */ - proxyNode = (DeoptEntrySupport) exceptionObject.predecessor(); - } else { - proxyNode = (DeoptEntrySupport) node; - } - - RemovalDecision decision = RemovalDecision.REMOVE; - var directive = SubstrateCompilationDirectives.singleton(); - FrameState state = proxyNode.stateAfter(); - HostedMethod method = (HostedMethod) state.getMethod(); - if (proxyNode instanceof DeoptEntryNode) { - if (directive.isDeoptEntry(method, state.bci, state.getStackState())) { - // must keep all deopt entries which are still guarding nodes - decision = RemovalDecision.KEEP; - } - } - - if (decision == RemovalDecision.REMOVE) { - // now check for any implicit deopt entry being protected against - int proxifiedInvokeBci = proxyNode.getProxifiedInvokeBci(); - if (proxifiedInvokeBci != BytecodeFrame.UNKNOWN_BCI && directive.isDeoptEntry(method, proxifiedInvokeBci, FrameState.StackState.AfterPop)) { - // must keep still keep a proxy for nodes which are "proxifying" an invoke - decision = proxyNode instanceof DeoptEntryNode ? RemovalDecision.PROXIFY : RemovalDecision.KEEP; - } - } - - // cache the decision - decisionCache.put(node, decision); - if (proxyNode != node) { - decisionCache.put(proxyNode, decision); - } - return decision; - } + @Override + public boolean isGuaranteedSafepoint(ResolvedJavaMethod method, boolean isDirect) { + throw VMError.shouldNotReachHereAtRuntime(); // ExcludeFromJacocoGeneratedReport + } - @Override - public CharSequence getName() { - return "RemoveUnneededDeoptSupport"; - } + @Override + public boolean canVirtualize(ResolvedJavaType instanceType) { + return true; } } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompiledMethod.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompiledMethod.java new file mode 100644 index 000000000000..427c9641640c --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompiledMethod.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023, 2023, 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.graal.hosted.runtimecompilation; + +import static com.oracle.svm.common.meta.MultiMethod.ORIGINAL_METHOD; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.svm.common.meta.MultiMethod; +import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; + +import jdk.vm.ci.meta.ResolvedJavaMethod; + +public class RuntimeCompiledMethod { + final AnalysisMethod runtimeMethod; + final AnalysisMethod originalMethod; + + /** + * Collection of all methods inlined into this method. All methods contained here are of the + * {@link MultiMethod#ORIGINAL_METHOD} type. + */ + final Collection inlinedMethods; + + RuntimeCompiledMethod(AnalysisMethod runtimeMethod, Collection inlinedMethods) { + this.runtimeMethod = runtimeMethod; + assert SubstrateCompilationDirectives.isRuntimeCompiledMethod(runtimeMethod) : runtimeMethod; + this.originalMethod = runtimeMethod.getMultiMethod(ORIGINAL_METHOD); + assert originalMethod != null; + this.inlinedMethods = inlinedMethods; + } + + public AnalysisMethod getRuntimeMethod() { + return runtimeMethod; + } + + public AnalysisMethod getOriginalMethod() { + return originalMethod; + } + + public Collection getInlinedMethods() { + return inlinedMethods; + } + + public Collection getInvokeTargets() { + List targets = new ArrayList<>(); + for (var invoke : runtimeMethod.getInvokes()) { + if (invoke.isDeoptInvokeTypeFlow()) { + // deopt invoke type flows are not real targets + continue; + } + targets.add(invoke.getTargetMethod()); + } + return targets; + } +} diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompiledMethodSupport.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompiledMethodSupport.java new file mode 100644 index 000000000000..954cdc5616b8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompiledMethodSupport.java @@ -0,0 +1,570 @@ +/* + * Copyright (c) 2023, 2023, 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.graal.hosted.runtimecompilation; + +import static com.oracle.svm.common.meta.MultiMethod.DEOPT_TARGET_METHOD; +import static com.oracle.svm.common.meta.MultiMethod.ORIGINAL_METHOD; +import static com.oracle.svm.hosted.code.SubstrateCompilationDirectives.RUNTIME_COMPILED_METHOD; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.word.LocationIdentity; + +import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.graal.pointsto.heap.ImageHeapScanner; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.HostedProviders; +import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; +import com.oracle.graal.pointsto.util.CompletionExecutor; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.graal.nodes.DeoptEntryNode; +import com.oracle.svm.core.graal.nodes.DeoptEntrySupport; +import com.oracle.svm.core.graal.nodes.DeoptProxyAnchorNode; +import com.oracle.svm.core.graal.nodes.ThrowBytecodeExceptionNode; +import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.HeapBreakdownProvider; +import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.ameta.AnalysisConstantFieldProvider; +import com.oracle.svm.hosted.ameta.AnalysisConstantReflectionProvider; +import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; +import com.oracle.svm.hosted.code.CompileQueue; +import com.oracle.svm.hosted.code.DeoptimizationUtils; +import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; +import com.oracle.svm.hosted.meta.HostedConstantFieldProvider; +import com.oracle.svm.hosted.meta.HostedField; +import com.oracle.svm.hosted.meta.HostedMethod; +import com.oracle.svm.hosted.meta.HostedUniverse; +import com.oracle.svm.hosted.nodes.DeoptProxyNode; +import com.oracle.svm.hosted.phases.AnalysisGraphBuilderPhase; + +import jdk.graal.compiler.core.common.spi.ConstantFieldProvider; +import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.debug.DebugHandlersFactory; +import jdk.graal.compiler.java.BytecodeParser; +import jdk.graal.compiler.java.GraphBuilderPhase; +import jdk.graal.compiler.loop.phases.ConvertDeoptimizeToGuardPhase; +import jdk.graal.compiler.nodes.CallTargetNode; +import jdk.graal.compiler.nodes.FrameState; +import jdk.graal.compiler.nodes.GraphDecoder; +import jdk.graal.compiler.nodes.GraphEncoder; +import jdk.graal.compiler.nodes.StateSplit; +import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.extended.BytecodeExceptionNode; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; +import jdk.graal.compiler.nodes.graphbuilderconf.IntrinsicContext; +import jdk.graal.compiler.nodes.java.ExceptionObjectNode; +import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.phases.OptimisticOptimizations; +import jdk.graal.compiler.phases.Phase; +import jdk.graal.compiler.phases.PhaseSuite; +import jdk.graal.compiler.phases.common.CanonicalizerPhase; +import jdk.graal.compiler.phases.common.DominatorBasedGlobalValueNumberingPhase; +import jdk.graal.compiler.phases.common.IterativeConditionalEliminationPhase; +import jdk.graal.compiler.phases.tiers.HighTierContext; +import jdk.graal.compiler.phases.util.Providers; +import jdk.graal.compiler.printer.GraalDebugHandlersFactory; +import jdk.graal.compiler.truffle.nodes.ObjectLocationIdentity; +import jdk.vm.ci.code.Architecture; +import jdk.vm.ci.code.BytecodeFrame; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +/** + * This class infrastructure needed for creating the RuntimeCompiledMethods variants. + */ +public class RuntimeCompiledMethodSupport { + + public static class Options { + @Option(help = "Remove Deopt(Entries,Anchors,Proxies) determined to be unneeded after the runtime compiled graphs have been finalized.")// + public static final HostedOptionKey RemoveUnneededDeoptSupport = new HostedOptionKey<>(true); + } + + private record CompilationState( + GraalGraphObjectReplacer objectReplacer, + GraphEncoder graphEncoder, + HostedProviders runtimeCompilationProviders, + ImageHeapScanner heapScanner, + Map runtimeGraphs, + Set registeredRuntimeCompilations) { + } + + public static void onCompileQueueCreation(BigBang bb, HostedUniverse hUniverse, CompileQueue compileQueue, HostedProviders hostedProviders, + Function constantFieldProviderWrapper, + GraalGraphObjectReplacer objectReplacer, Set registeredRuntimeCompilations, Stream methodsToCompile) { + + ImageHeapScanner imageScanner = bb.getUniverse().getHeapScanner(); + + GraphEncoder graphEncoder = new RuntimeCompiledMethodSupport.RuntimeCompilationGraphEncoder(ConfigurationValues.getTarget().arch, imageScanner); + HostedProviders runtimeCompilationProviders = hostedProviders + .copyWith(constantFieldProviderWrapper.apply(new RuntimeCompilationFieldProvider(hostedProviders.getMetaAccess(), hUniverse))) + .copyWith(new RuntimeCompilationReflectionProvider(bb, hUniverse.hostVM().getClassInitializationSupport())); + + SubstrateCompilationDirectives.singleton().resetDeoptEntries(); + + CompilationState compilationState = new CompilationState(objectReplacer, graphEncoder, runtimeCompilationProviders, imageScanner, new ConcurrentHashMap<>(), + registeredRuntimeCompilations); + + /* + * Customize runtime compile methods for compiling them into substrate graphs. + */ + CompletionExecutor executor = compileQueue.getExecutor(); + try { + compileQueue.runOnExecutor(() -> { + methodsToCompile.forEach(method -> { + executor.execute(new RuntimeCompileTask(method, compilationState)); + }); + }); + } catch (InterruptedException exception) { + VMError.shouldNotReachHere(exception); + } + + encodeRuntimeCompiledMethods(compilationState); + + /* + * For Deoptimization Targets add a custom phase which removes all deoptimization + * entrypoints which are deemed no longer necessary. + */ + CompileQueue.ParseHooks deoptParseHooks = new CompileQueue.ParseHooks(compileQueue) { + @Override + protected PhaseSuite getAfterParseSuite() { + PhaseSuite suite = super.getAfterParseSuite(); + if (Options.RemoveUnneededDeoptSupport.getValue()) { + suite.prependPhase(new RemoveUnneededDeoptSupport()); + } + + return suite; + } + }; + + hUniverse.getMethods().stream().map(method -> method.getMultiMethod(DEOPT_TARGET_METHOD)).filter(method -> { + if (method != null) { + return compileQueue.isRegisteredDeoptTarget(method); + } + return false; + }).forEach(method -> method.compilationInfo.setCustomParseHooks(deoptParseHooks)); + + } + + private static class RuntimeCompileTask implements CompletionExecutor.DebugContextRunnable { + final HostedMethod method; + final CompilationState compilationState; + + RuntimeCompileTask(HostedMethod method, CompilationState compilationState) { + this.method = method; + this.compilationState = compilationState; + } + + @Override + public DebugContext getDebug(OptionValues options, List factories) { + return new DebugContext.Builder(options, factories).description(getDescription()).build(); + } + + @Override + public void run(DebugContext debug) { + compileRuntimeCompiledMethod(debug, method); + } + + @SuppressWarnings("try") + private void compileRuntimeCompiledMethod(DebugContext debug, HostedMethod method) { + assert method.getMultiMethodKey() == RUNTIME_COMPILED_METHOD; + + AnalysisMethod aMethod = method.getWrapped(); + StructuredGraph graph = aMethod.decodeAnalyzedGraph(debug, null, false, + (arch, analyzedGraph) -> new RuntimeCompilationGraphDecoder(arch, analyzedGraph, compilationState.heapScanner)); + if (graph == null) { + throw VMError.shouldNotReachHere("Method not parsed during static analysis: " + aMethod.format("%r %H.%n(%p)")); + } + /* + * The graph in the analysis universe is no longer necessary once it is transplanted + * into the hosted universe. + */ + aMethod.setAnalyzedGraph(null); + + try (DebugContext.Scope s = debug.scope("RuntimeOptimize", graph, method, this)) { + CanonicalizerPhase canonicalizer = CanonicalizerPhase.create(); + canonicalizer.apply(graph, compilationState.runtimeCompilationProviders); + + new DominatorBasedGlobalValueNumberingPhase(canonicalizer).apply(graph, compilationState.runtimeCompilationProviders); + + new IterativeConditionalEliminationPhase(canonicalizer, true).apply(graph, compilationState.runtimeCompilationProviders); + + /* + * ConvertDeoptimizeToGuardPhase was already executed after parsing, but + * optimizations applied in between can provide new potential. + */ + new ConvertDeoptimizeToGuardPhase(canonicalizer).apply(graph, compilationState.runtimeCompilationProviders); + + /* + * More optimizations can be added here. + */ + } catch (Throwable e) { + throw debug.handle(e); + } + + /* + * Registering all deopt entries seen within the optimized graph. This should be + * strictly a subset of the deopt entrypoints seen during evaluation. + */ + AnalysisMethod origMethod = method.getMultiMethod(ORIGINAL_METHOD).getWrapped(); + DeoptimizationUtils.registerDeoptEntries(graph, compilationState.registeredRuntimeCompilations.contains(origMethod), + (deoptEntryMethod -> { + PointsToAnalysisMethod deoptMethod = (PointsToAnalysisMethod) ((PointsToAnalysisMethod) deoptEntryMethod).getMultiMethod(DEOPT_TARGET_METHOD); + VMError.guarantee(deoptMethod != null, "New deopt target method seen: %s", deoptEntryMethod); + return deoptMethod; + })); + + assert verifyNodes(graph); + var previous = compilationState.runtimeGraphs.put(method, graph); + assert previous == null; + + // graph encoder is not currently threadsafe + synchronized (compilationState.graphEncoder) { + compilationState.graphEncoder.prepare(graph); + } + } + } + + /** + * Checks if any illegal nodes are present within the graph. Runtime Compiled methods should + * never have explicit BytecodeExceptions; instead they should have deoptimizations. + */ + static boolean verifyNodes(StructuredGraph graph) { + for (var node : graph.getNodes()) { + boolean invalidNodeKind = node instanceof BytecodeExceptionNode || node instanceof ThrowBytecodeExceptionNode; + assert !invalidNodeKind : "illegal node in graph: " + node + " method: " + graph.method(); + } + return true; + } + + @SuppressWarnings("try") + private static void encodeRuntimeCompiledMethods(CompilationState compilationState) { + compilationState.graphEncoder.finishPrepare(); + + // at this point no new deoptimization entrypoints can be registered. + SubstrateCompilationDirectives.singleton().sealDeoptimizationInfo(); + + for (var runtimeInfo : compilationState.runtimeGraphs.entrySet()) { + var graph = runtimeInfo.getValue(); + var method = runtimeInfo.getKey(); + DebugContext debug = new DebugContext.Builder(graph.getOptions(), new GraalDebugHandlersFactory(compilationState.runtimeCompilationProviders.getSnippetReflection())).build(); + graph.resetDebug(debug); + try (DebugContext.Scope s = debug.scope("Graph Encoding", graph); + DebugContext.Activation a = debug.activate()) { + long startOffset = compilationState.graphEncoder.encode(graph); + compilationState.objectReplacer.createMethod(method).setEncodedGraphStartOffset(startOffset); + } catch (Throwable ex) { + debug.handle(ex); + } + } + + HeapBreakdownProvider.singleton().setGraphEncodingByteLength(compilationState.graphEncoder.getEncoding().length); + com.oracle.svm.graal.TruffleRuntimeCompilationSupport.setGraphEncoding(null, compilationState.graphEncoder.getEncoding(), compilationState.graphEncoder.getObjects(), + compilationState.graphEncoder.getNodeClasses()); + + compilationState.objectReplacer.setMethodsImplementations(); + } + + /** + * Since we perform runtime compilation after universe creation, we can leverage components of + * the hosted universe provider for identifying final fields. + */ + static class RuntimeCompilationFieldProvider extends AnalysisConstantFieldProvider { + final HostedUniverse hUniverse; + + RuntimeCompilationFieldProvider(MetaAccessProvider metaAccess, HostedUniverse hUniverse) { + super(metaAccess, hUniverse.hostVM()); + this.hUniverse = hUniverse; + } + + @Override + public boolean isFinalField(ResolvedJavaField f, ConstantFieldTool tool) { + HostedField hField = hUniverse.lookup(f); + if (HostedConstantFieldProvider.isFinalField(hField)) { + return true; + } + return super.isFinalField(f, tool); + } + } + + static class RuntimeCompilationReflectionProvider extends AnalysisConstantReflectionProvider { + + RuntimeCompilationReflectionProvider(BigBang bb, ClassInitializationSupport classInitializationSupport) { + super(bb.getUniverse(), bb.getMetaAccess(), classInitializationSupport); + } + + @Override + public JavaConstant readFieldValue(ResolvedJavaField field, JavaConstant receiver) { + /* + * We cannot fold simulated values during initial before-analysis graph creation; + * however, this runs after analysis has completed. + */ + return readValue(metaAccess, (AnalysisField) field, receiver, true); + } + } + + /** + * A graph encoder that unwraps the {@link ImageHeapConstant} objects. This is used both after + * analysis and after compilation. The corresponding graph decoder used during AOT compilation, + * {@link RuntimeCompilationGraphDecoder}, looks-up the constant in the shadow heap and re-wraps + * it. + *

+ * The reason why we need to unwrap the {@link ImageHeapConstant}s after analysis, and not only + * when we finally encode the graphs for run time compilation, is because the values in + * {@link GraphEncoder#objectsArray} are captured in GraalSupport#graphObjects and + * SubstrateReplacements#snippetObjects which are then scanned. + */ + public static class RuntimeCompilationGraphEncoder extends GraphEncoder { + + private final ImageHeapScanner heapScanner; + /** + * Cache already converted location identity objects to avoid creating multiple new + * instances for the same underlying location identity. + */ + private final Map locationIdentityCache; + + public RuntimeCompilationGraphEncoder(Architecture architecture, ImageHeapScanner heapScanner) { + super(architecture); + this.heapScanner = heapScanner; + this.locationIdentityCache = new ConcurrentHashMap<>(); + } + + @Override + protected void addObject(Object object) { + super.addObject(unwrap(object)); + } + + @Override + protected void writeObjectId(Object object) { + super.writeObjectId(unwrap(object)); + } + + @Override + protected GraphDecoder graphDecoderForVerification(StructuredGraph decodedGraph) { + return new RuntimeCompilationGraphDecoder(architecture, decodedGraph, heapScanner); + } + + private Object unwrap(Object object) { + if (object instanceof ImageHeapConstant ihc) { + VMError.guarantee(ihc.getHostedObject() != null); + return ihc.getHostedObject(); + } else if (object instanceof ObjectLocationIdentity oli && oli.getObject() instanceof ImageHeapConstant heapConstant) { + return locationIdentityCache.computeIfAbsent(heapConstant, (hc) -> ObjectLocationIdentity.create(hc.getHostedObject())); + } + return object; + } + } + + static class RuntimeCompilationGraphDecoder extends GraphDecoder { + + private final ImageHeapScanner heapScanner; + /** + * Cache already converted location identity objects to avoid creating multiple new + * instances for the same underlying location identity. + */ + private final Map locationIdentityCache; + + RuntimeCompilationGraphDecoder(Architecture architecture, StructuredGraph graph, ImageHeapScanner heapScanner) { + super(architecture, graph); + this.heapScanner = heapScanner; + this.locationIdentityCache = new ConcurrentHashMap<>(); + } + + @Override + protected Object readObject(MethodScope methodScope) { + Object object = super.readObject(methodScope); + if (object instanceof JavaConstant constant) { + return heapScanner.getImageHeapConstant(constant); + } else if (object instanceof ObjectLocationIdentity oli) { + return locationIdentityCache.computeIfAbsent(oli.getObject(), (obj) -> ObjectLocationIdentity.create(heapScanner.getImageHeapConstant(obj))); + } + return object; + } + } + + static final class RuntimeGraphBuilderPhase extends AnalysisGraphBuilderPhase { + + private RuntimeGraphBuilderPhase(Providers providers, + GraphBuilderConfiguration graphBuilderConfig, OptimisticOptimizations optimisticOpts, IntrinsicContext initialIntrinsicContext, SVMHost hostVM) { + super(providers, graphBuilderConfig, optimisticOpts, initialIntrinsicContext, hostVM); + } + + static RuntimeGraphBuilderPhase createRuntimeGraphBuilderPhase(BigBang bb, Providers providers, + GraphBuilderConfiguration graphBuilderConfig, OptimisticOptimizations optimisticOpts) { + + // Adjust graphbuilderconfig to match analysis phase + var newGraphBuilderConfig = graphBuilderConfig.withEagerResolving(true).withUnresolvedIsError(false); + return new RuntimeGraphBuilderPhase(providers, newGraphBuilderConfig, optimisticOpts, null, (SVMHost) bb.getHostVM()); + } + + @Override + protected BytecodeParser createBytecodeParser(StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, IntrinsicContext intrinsicContext) { + return new RuntimeBytecodeParser(this, graph, parent, method, entryBCI, intrinsicContext, hostVM); + } + } + + private static final class RuntimeBytecodeParser extends AnalysisGraphBuilderPhase.AnalysisBytecodeParser { + + RuntimeBytecodeParser(GraphBuilderPhase.Instance graphBuilderInstance, StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, + IntrinsicContext intrinsicContext, SVMHost svmHost) { + super(graphBuilderInstance, graph, parent, method, entryBCI, intrinsicContext, svmHost, false); + } + + @Override + protected boolean tryInvocationPlugin(CallTargetNode.InvokeKind invokeKind, ValueNode[] args, ResolvedJavaMethod targetMethod, JavaKind resultType) { + boolean result = super.tryInvocationPlugin(invokeKind, args, targetMethod, resultType); + if (result) { + SubstrateCompilationDirectives.singleton().registerAsDeoptInlininingExclude(targetMethod); + } + return result; + } + + @Override + protected boolean shouldVerifyFrameStates() { + /* + * (GR-46115) Ideally we should verify frame states in methods registered for runtime + * compilations, as well as any other methods that can deoptimize. Because runtime + * compiled methods can pull in almost arbitrary code, this means most frame states + * should be verified. We currently use illegal states as placeholders in many places, + * so this cannot be enabled at the moment. + */ + return false; + } + } + + /** + * Removes {@link DeoptEntryNode}s, {@link DeoptProxyAnchorNode}s, and {@link DeoptProxyNode}s + * which are determined to be unnecessary after the runtime compilation methods are optimized. + */ + private static class RemoveUnneededDeoptSupport extends Phase { + enum RemovalDecision { + KEEP, + PROXIFY, + REMOVE + } + + @Override + protected void run(StructuredGraph graph) { + EconomicMap decisionCache = EconomicMap.create(); + + // First go through and delete all unneeded proxies + for (DeoptProxyNode proxyNode : graph.getNodes(DeoptProxyNode.TYPE).snapshot()) { + ValueNode proxyPoint = proxyNode.getProxyPoint(); + if (proxyPoint instanceof StateSplit) { + if (getDecision((StateSplit) proxyPoint, decisionCache) == RemovalDecision.REMOVE) { + proxyNode.replaceAtAllUsages(proxyNode.getOriginalNode(), true); + proxyNode.safeDelete(); + } + } + } + + // Next, remove all unneeded DeoptEntryNodes + for (DeoptEntryNode deoptEntry : graph.getNodes().filter(DeoptEntryNode.class).snapshot()) { + switch (getDecision(deoptEntry, decisionCache)) { + case REMOVE -> { + deoptEntry.killExceptionEdge(); + graph.removeSplit(deoptEntry, deoptEntry.getPrimarySuccessor()); + } + case PROXIFY -> { + deoptEntry.killExceptionEdge(); + DeoptProxyAnchorNode newAnchor = graph.add(new DeoptProxyAnchorNode(deoptEntry.getProxifiedInvokeBci())); + newAnchor.setStateAfter(deoptEntry.stateAfter()); + graph.replaceSplitWithFixed(deoptEntry, newAnchor, deoptEntry.getPrimarySuccessor()); + } + } + } + + // Finally, remove all unneeded DeoptProxyAnchorNodes + for (DeoptProxyAnchorNode proxyAnchor : graph.getNodes().filter(DeoptProxyAnchorNode.class).snapshot()) { + if (getDecision(proxyAnchor, decisionCache) == RemovalDecision.REMOVE) { + graph.removeFixed(proxyAnchor); + } + } + } + + RemovalDecision getDecision(StateSplit node, EconomicMap decisionCache) { + RemovalDecision cached = decisionCache.get(node); + if (cached != null) { + return cached; + } + + DeoptEntrySupport proxyNode; + if (node instanceof ExceptionObjectNode exceptionObject) { + /* + * For the exception edge of a DeoptEntryNode, we insert the proxies on the + * exception object. + */ + proxyNode = (DeoptEntrySupport) exceptionObject.predecessor(); + } else { + proxyNode = (DeoptEntrySupport) node; + } + + RemovalDecision decision = RemovalDecision.REMOVE; + var directive = SubstrateCompilationDirectives.singleton(); + FrameState state = proxyNode.stateAfter(); + HostedMethod method = (HostedMethod) state.getMethod(); + if (proxyNode instanceof DeoptEntryNode) { + if (directive.isDeoptEntry(method, state.bci, state.getStackState())) { + // must keep all deopt entries which are still guarding nodes + decision = RemovalDecision.KEEP; + } + } + + if (decision == RemovalDecision.REMOVE) { + // now check for any implicit deopt entry being protected against + int proxifiedInvokeBci = proxyNode.getProxifiedInvokeBci(); + if (proxifiedInvokeBci != BytecodeFrame.UNKNOWN_BCI && directive.isDeoptEntry(method, proxifiedInvokeBci, FrameState.StackState.AfterPop)) { + // must keep still keep a proxy for nodes which are "proxifying" an invoke + decision = proxyNode instanceof DeoptEntryNode ? RemovalDecision.PROXIFY : RemovalDecision.KEEP; + } + } + + // cache the decision + decisionCache.put(node, decision); + if (proxyNode != node) { + decisionCache.put(proxyNode, decision); + } + return decision; + } + + @Override + public CharSequence getName() { + return "RemoveUnneededDeoptSupport"; + } + } +} diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/SubstrateGraalCompilerSetup.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/SubstrateGraalCompilerSetup.java similarity index 98% rename from substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/SubstrateGraalCompilerSetup.java rename to substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/SubstrateGraalCompilerSetup.java index 43b70d36c3d8..8124c7ddc8da 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/SubstrateGraalCompilerSetup.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/SubstrateGraalCompilerSetup.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.graal.hosted; +package com.oracle.svm.graal.hosted.runtimecompilation; import java.util.function.Function; diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/SubstrateProviders.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/SubstrateProviders.java similarity index 98% rename from substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/SubstrateProviders.java rename to substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/SubstrateProviders.java index c05496a5cd8a..cd135cee5ec0 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/SubstrateProviders.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/SubstrateProviders.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.graal.hosted; +package com.oracle.svm.graal.hosted.runtimecompilation; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.svm.core.graal.meta.SubstrateSnippetReflectionProvider; diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolateAwareProviders.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolateAwareProviders.java index c0d50ef2ffea..88c434d54775 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolateAwareProviders.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolateAwareProviders.java @@ -25,7 +25,7 @@ package com.oracle.svm.graal.isolated; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; -import com.oracle.svm.graal.hosted.SubstrateProviders; +import com.oracle.svm.graal.hosted.runtimecompilation.SubstrateProviders; import com.oracle.svm.graal.meta.SubstrateConstantFieldProvider; public final class IsolateAwareProviders extends SubstrateProviders { diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java index b6fb0a8b5d25..4c43000bf0dc 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java @@ -63,7 +63,7 @@ import com.oracle.svm.graal.TruffleRuntimeCompilationSupport; import com.oracle.svm.graal.hosted.FieldsOffsetsFeature; import com.oracle.svm.graal.hosted.GraalCompilerFeature; -import com.oracle.svm.graal.hosted.RuntimeCompilationFeature; +import com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationFeature; import com.oracle.svm.graal.meta.SubstrateMethod; import com.oracle.svm.util.ReflectionUtil; 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 fbce62ddb497..4b2b2f91a1af 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 @@ -648,8 +648,8 @@ protected void doRun(Map entryPoints, JavaMainSupport j CompileQueue compileQueue; try (StopTimer t = TimerCollection.createTimerAndStart(TimerCollection.Registry.COMPILE_TOTAL)) { compileQueue = HostedConfiguration.instance().createCompileQueue(debug, featureHandler, hUniverse, runtimeConfiguration, DeoptTester.enabled(), bb.getSnippetReflectionProvider()); - if (ImageSingletons.contains(RuntimeCompilationSupport.class)) { - ImageSingletons.lookup(RuntimeCompilationSupport.class).onCompileQueueCreation(bb, hUniverse, compileQueue); + if (ImageSingletons.contains(RuntimeCompilationCallbacks.class)) { + ImageSingletons.lookup(RuntimeCompilationCallbacks.class).onCompileQueueCreation(bb, hUniverse, compileQueue); } compileQueue.finish(debug); BuildPhaseProvider.markCompileQueueFinished(); @@ -799,8 +799,16 @@ protected boolean runPointsToAnalysis(String imageName, OptionValues options, De } return !config.getAndResetRequireAnalysisIteration(); }); - } catch (AnalysisError e) { - throw UserError.abort(e, "Analysis step failed. Reason: %s.", e.getMessage()); + } catch (Throwable t) { + if (ImageSingletons.contains(RuntimeCompilationCallbacks.class)) { + ImageSingletons.lookup(RuntimeCompilationCallbacks.class).reportAnalysisError(aUniverse, t); + } + + if (t instanceof AnalysisError e) { + throw UserError.abort(e, "Analysis step failed. Reason: %s.", e.getMessage()); + } else { + throw t; + } } assert verifyAssignableTypes(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/RuntimeCompilationSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/RuntimeCompilationCallbacks.java similarity index 89% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/RuntimeCompilationSupport.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/RuntimeCompilationCallbacks.java index a6530ef70880..d1a6414c64fc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/RuntimeCompilationSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/RuntimeCompilationCallbacks.java @@ -25,9 +25,12 @@ package com.oracle.svm.hosted; import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.hosted.code.CompileQueue; import com.oracle.svm.hosted.meta.HostedUniverse; -public interface RuntimeCompilationSupport { +public interface RuntimeCompilationCallbacks { void onCompileQueueCreation(BigBang bb, HostedUniverse hUniverse, CompileQueue compileQueue); + + void reportAnalysisError(AnalysisUniverse aUniverse, Throwable error); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/SVMParsingSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/SVMParsingSupport.java index ae0911f99a81..ee2b1cc5d8dc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/SVMParsingSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/SVMParsingSupport.java @@ -26,10 +26,6 @@ import java.util.function.Function; -import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.nodes.StructuredGraph; -import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; - import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.PointsToAnalysis; import com.oracle.graal.pointsto.meta.AnalysisMethod; @@ -40,6 +36,9 @@ import com.oracle.svm.hosted.SVMHost; import com.oracle.svm.hosted.phases.InlineBeforeAnalysisPolicyUtils; +import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; import jdk.vm.ci.meta.ResolvedJavaType; /** diff --git a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java index a91e7eef28d0..1c430527e219 100644 --- a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java +++ b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java @@ -102,9 +102,9 @@ import com.oracle.svm.core.reflect.target.ReflectionSubstitutionSupport; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.graal.hosted.GraalGraphObjectReplacer; -import com.oracle.svm.graal.hosted.SubstrateGraalCompilerSetup; -import com.oracle.svm.graal.hosted.SubstrateProviders; +import com.oracle.svm.graal.hosted.runtimecompilation.GraalGraphObjectReplacer; +import com.oracle.svm.graal.hosted.runtimecompilation.SubstrateGraalCompilerSetup; +import com.oracle.svm.graal.hosted.runtimecompilation.SubstrateProviders; import com.oracle.svm.graal.meta.SubstrateUniverseFactory; import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; diff --git a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleFeature.java b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleFeature.java index e3b26b3e93c3..27f42f9680c7 100644 --- a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleFeature.java +++ b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleFeature.java @@ -25,9 +25,9 @@ package com.oracle.svm.truffle; -import static com.oracle.svm.graal.hosted.RuntimeCompilationFeature.AllowInliningPredicate.InlineDecision.INLINE; -import static com.oracle.svm.graal.hosted.RuntimeCompilationFeature.AllowInliningPredicate.InlineDecision.INLINING_DISALLOWED; -import static com.oracle.svm.graal.hosted.RuntimeCompilationFeature.AllowInliningPredicate.InlineDecision.NO_DECISION; +import static com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationFeature.AllowInliningPredicate.InlineDecision.INLINE; +import static com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationFeature.AllowInliningPredicate.InlineDecision.INLINING_DISALLOWED; +import static com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationFeature.AllowInliningPredicate.InlineDecision.NO_DECISION; import java.lang.reflect.Executable; import java.lang.reflect.Field; @@ -124,9 +124,10 @@ import com.oracle.svm.core.stack.JavaStackWalker; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.graal.hosted.RuntimeCompilationFeature; -import com.oracle.svm.graal.hosted.RuntimeCompilationFeature.RuntimeCompilationCandidate; -import com.oracle.svm.graal.hosted.RuntimeCompilationFeature.RuntimeCompiledMethod; +import com.oracle.svm.graal.hosted.runtimecompilation.CallTreeInfo; +import com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationCandidate; +import com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationFeature; +import com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompiledMethod; import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.FeatureImpl.AfterAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; @@ -248,7 +249,7 @@ public void registerLowerings(RuntimeConfiguration runtimeConfig, OptionValues o @Override public List> getRequiredFeatures() { - return List.of(RuntimeCompilationFeature.getRuntimeCompilationFeature(), TruffleBaseFeature.class); + return List.of(RuntimeCompilationFeature.class, TruffleBaseFeature.class); } /* @@ -389,7 +390,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { runtimeCompilationFeature.getHostedProviders().getLoopsDataProvider()); newHostedProviders.setGraphBuilderPlugins(graphBuilderConfig.getPlugins()); - runtimeCompilationFeature.initializeRuntimeCompilationConfiguration(newHostedProviders, graphBuilderConfig, this::allowRuntimeCompilation, this::deoptimizeOnException); + runtimeCompilationFeature.initializeRuntimeCompilationConfiguration(newHostedProviders, graphBuilderConfig, this::allowRuntimeCompilation, this::deoptimizeOnException, this::checkBlockList); for (ResolvedJavaMethod method : partialEvaluator.getCompilationRootMethods()) { runtimeCompilationFeature.prepareMethodForRuntimeCompilation(method, config); } @@ -780,10 +781,11 @@ private void warnAllMethods(MetaAccessProvider metaAccess, Class clazz) { } } - @Override - public void beforeCompilation(BeforeCompilationAccess config) { - FeatureImpl.BeforeCompilationAccessImpl access = (FeatureImpl.BeforeCompilationAccessImpl) config; + private record BlocklistViolationInfo(RuntimeCompilationCandidate candidate, String[] callTrace) { + }; + private void checkBlockList(CallTreeInfo treeInfo) { + RuntimeCompilationFeature runtimeCompilation = RuntimeCompilationFeature.singleton(); boolean failBlockListViolations; if (Options.TruffleCheckBlackListedMethods.hasBeenSet()) { failBlockListViolations = Options.TruffleCheckBlackListedMethods.getValue(); @@ -792,8 +794,8 @@ public void beforeCompilation(BeforeCompilationAccess config) { } boolean printBlockListViolations = RuntimeCompilationFeature.Options.PrintRuntimeCompileMethods.getValue() || failBlockListViolations; if (printBlockListViolations) { - Set blocklistViolations = new TreeSet<>(RuntimeCompilationFeature.singleton().getRuntimeCompilationComparator()); - for (RuntimeCompilationCandidate candidate : RuntimeCompilationFeature.singleton().getAllRuntimeCompilationCandidates()) { + Set blocklistViolations = new TreeSet<>((o1, o2) -> Arrays.compare(o1.callTrace(), o2.callTrace())); + for (RuntimeCompilationCandidate candidate : runtimeCompilation.getAllRuntimeCompilationCandidates()) { // Determine blocklist violations if (!runtimeCompilationForbidden(candidate.getImplementationMethod())) { @@ -802,7 +804,8 @@ public void beforeCompilation(BeforeCompilationAccess config) { tempTargetAllowlistMethods.contains(candidate.getTargetMethod()) && !isBlocklisted(candidate.getImplementationMethod()); if (!tempAllow) { - blocklistViolations.add(candidate); + BlocklistViolationInfo violation = new BlocklistViolationInfo(candidate, CallTreeInfo.getCallTrace(treeInfo, candidate)); + blocklistViolations.add(violation); } } } @@ -811,11 +814,13 @@ public void beforeCompilation(BeforeCompilationAccess config) { System.out.println(); System.out.println("=== Found " + blocklistViolations.size() + " compilation blocklist violations ==="); System.out.println(); - for (RuntimeCompilationCandidate violation : blocklistViolations) { + for (BlocklistViolationInfo violation : blocklistViolations) { System.out.println("Blocklisted method"); - System.out.format(" %s (target: %s)%n", violation.getImplementationMethod().format("%H.%n(%p)"), violation.getTargetMethod().format("%H.%n(%p)")); + System.out.format(" %s (target: %s)%n", violation.candidate.getImplementationMethod().format("%H.%n(%p)"), violation.candidate.getTargetMethod().format("%H.%n(%p)")); System.out.println("trace:"); - RuntimeCompilationFeature.singleton().getCallTrace(violation).forEach(item -> System.out.println(" " + item)); + for (String item : violation.callTrace()) { + System.out.println(" " + item); + } } if (failBlockListViolations) { throw VMError.shouldNotReachHere("Blocklisted methods are reachable for runtime compilation"); @@ -824,7 +829,7 @@ public void beforeCompilation(BeforeCompilationAccess config) { } Set warnViolations = new HashSet<>(); - for (RuntimeCompilationCandidate node : RuntimeCompilationFeature.singleton().getAllRuntimeCompilationCandidates()) { + for (RuntimeCompilationCandidate node : runtimeCompilation.getAllRuntimeCompilationCandidates()) { var method = node.getImplementationMethod(); if (warnMethods.contains(method)) { warnViolations.add(node); @@ -841,7 +846,9 @@ public void beforeCompilation(BeforeCompilationAccess config) { for (RuntimeCompilationCandidate violation : warnViolations) { System.out.println("Suspicious method: " + violation.getImplementationMethod().format("%H.%n(%p)")); System.out.println("trace:"); - RuntimeCompilationFeature.singleton().getCallTrace(violation).forEach(item -> System.out.println(" " + item)); + for (String item : CallTreeInfo.getCallTrace(treeInfo, violation)) { + System.out.println(" " + item); + } } } @@ -850,10 +857,16 @@ public void beforeCompilation(BeforeCompilationAccess config) { for (Pair violation : neverPartOfCompilationViolations) { System.out.println("called from"); System.out.println("(inlined call path): " + violation.getRight()); - RuntimeCompilationFeature.singleton().getCallTrace(violation.getLeft()).forEach(item -> System.out.println(" " + item)); + for (String item : CallTreeInfo.getCallTrace(treeInfo, (AnalysisMethod) violation.getLeft())) { + System.out.println(" " + item); + } } throw VMError.shouldNotReachHere("CompilerAsserts.neverPartOfCompilation reachable for runtime compilation"); } + } + + @Override + public void beforeCompilation(BeforeCompilationAccess config) { if (Options.TruffleCheckFrameImplementation.getValue()) { /* @@ -865,7 +878,7 @@ public void beforeCompilation(BeforeCompilationAccess config) { * DefaultMaterializedFrame, ReadOnlyFrame) to detect wrong usages of the Frame API, so * we can only check when running with compilation enabled. */ - Optional optionalFrameType = access.getMetaAccess().optionalLookupJavaType(Frame.class); + Optional optionalFrameType = ((FeatureImpl.BeforeCompilationAccessImpl) config).getMetaAccess().optionalLookupJavaType(Frame.class); if (optionalFrameType.isPresent()) { HostedType frameType = (HostedType) optionalFrameType.get(); Set implementations = new HashSet<>(); @@ -919,8 +932,12 @@ private static void collectImplementations(HostedType type, Set impl @Override public void afterAnalysis(AfterAnalysisAccess access) { + CallTreeInfo treeInfo = RuntimeCompilationFeature.singleton().getCallTreeInfo(); + + checkBlockList(treeInfo); + if (Options.PrintStaticTruffleBoundaries.getValue()) { - printStaticTruffleBoundaries(); + printStaticTruffleBoundaries(treeInfo); } SubstrateTruffleRuntime truffleRuntime = (SubstrateTruffleRuntime) Truffle.getRuntime(); @@ -931,9 +948,9 @@ public void afterAnalysis(AfterAnalysisAccess access) { runtimeCompiledMethods.addAll(Arrays.asList(config.getMetaAccess().lookupJavaType(CompilerDirectives.class).getDeclaredMethods(false))); runtimeCompiledMethods.addAll(Arrays.asList(config.getMetaAccess().lookupJavaType(CompilerAsserts.class).getDeclaredMethods(false))); - for (RuntimeCompiledMethod runtimeCompiledMethod : RuntimeCompilationFeature.singleton().getRuntimeCompiledMethods()) { + for (RuntimeCompiledMethod runtimeCompiledMethod : treeInfo.runtimeCompilations()) { - runtimeCompiledMethods.add(runtimeCompiledMethod.getMethod()); + runtimeCompiledMethods.add(runtimeCompiledMethod.getOriginalMethod()); /* * The list of runtime compiled methods is not containing all methods that are always @@ -955,11 +972,11 @@ public void afterAnalysis(AfterAnalysisAccess access) { } } - private static void printStaticTruffleBoundaries() { + private static void printStaticTruffleBoundaries(CallTreeInfo treeInfo) { HashSet foundBoundaries = new HashSet<>(); int callSiteCount = 0; int calleeCount = 0; - for (RuntimeCompiledMethod runtimeCompiledMethod : RuntimeCompilationFeature.singleton().getRuntimeCompiledMethods()) { + for (RuntimeCompiledMethod runtimeCompiledMethod : treeInfo.runtimeCompilations()) { for (ResolvedJavaMethod targetMethod : runtimeCompiledMethod.getInvokeTargets()) { TruffleBoundary truffleBoundary = targetMethod.getAnnotation(TruffleBoundary.class); if (truffleBoundary != null) { diff --git a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleSupport.java b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleSupport.java index 3f37a934c36f..71542b803ef0 100644 --- a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleSupport.java +++ b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleSupport.java @@ -28,14 +28,6 @@ import java.util.function.Consumer; import java.util.function.Supplier; -import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; -import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; -import jdk.graal.compiler.phases.util.Providers; -import jdk.graal.compiler.truffle.EconomyPartialEvaluatorConfiguration; -import jdk.graal.compiler.truffle.PartialEvaluatorConfiguration; -import jdk.graal.compiler.truffle.TruffleCompilerConfiguration; -import jdk.graal.compiler.truffle.TruffleCompilerImpl; -import jdk.graal.compiler.truffle.TruffleTierConfiguration; import org.graalvm.nativeimage.ImageSingletons; import com.oracle.svm.core.SubstrateOptions; @@ -43,7 +35,7 @@ import com.oracle.svm.core.graal.code.SubstrateBackend; import com.oracle.svm.core.meta.SubstrateObjectConstant; import com.oracle.svm.graal.TruffleRuntimeCompilationSupport; -import com.oracle.svm.graal.hosted.RuntimeCompilationFeature; +import com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationFeature; import com.oracle.svm.truffle.api.SubstrateKnownTruffleTypes; import com.oracle.svm.truffle.api.SubstrateOptimizedCallTarget; import com.oracle.svm.truffle.api.SubstrateOptimizedCallTargetInstalledCode; @@ -64,6 +56,14 @@ import com.oracle.truffle.runtime.OptimizedCallTarget; import com.oracle.truffle.runtime.OptimizedDirectCallNode; +import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; +import jdk.graal.compiler.phases.util.Providers; +import jdk.graal.compiler.truffle.EconomyPartialEvaluatorConfiguration; +import jdk.graal.compiler.truffle.PartialEvaluatorConfiguration; +import jdk.graal.compiler.truffle.TruffleCompilerConfiguration; +import jdk.graal.compiler.truffle.TruffleCompilerImpl; +import jdk.graal.compiler.truffle.TruffleTierConfiguration; import jdk.vm.ci.meta.JavaConstant; public class TruffleSupport {