diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/test/LambdaStableNameTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/test/LambdaStableNameTest.java index c22d936a56c1..8e0f80faa413 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/test/LambdaStableNameTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/test/LambdaStableNameTest.java @@ -25,24 +25,29 @@ package jdk.graal.compiler.hotspot.test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.math.BigInteger; import java.util.Collections; -import jdk.vm.ci.meta.ResolvedJavaType; -import jdk.vm.ci.runtime.JVMCI; + +import org.junit.Test; +import org.objectweb.asm.Type; + import jdk.graal.compiler.api.runtime.GraalJVMCICompiler; import jdk.graal.compiler.debug.DebugContext; import jdk.graal.compiler.debug.DebugContext.Builder; import jdk.graal.compiler.hotspot.meta.HotSpotJITClassInitializationPlugin; +import jdk.graal.compiler.java.GraphBuilderPhase; import jdk.graal.compiler.java.LambdaUtils; import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.phases.OptimisticOptimizations; import jdk.graal.compiler.phases.util.Providers; import jdk.graal.compiler.runtime.RuntimeProvider; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import org.junit.Test; -import org.objectweb.asm.Type; +import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.runtime.JVMCI; public class LambdaStableNameTest { private String findStableLambdaName(ResolvedJavaType type) { @@ -51,7 +56,8 @@ private String findStableLambdaName(ResolvedJavaType type) { GraalJVMCICompiler compiler = (GraalJVMCICompiler) JVMCI.getRuntime().getCompiler(); Providers providers = compiler.getGraalRuntime().getCapability(RuntimeProvider.class).getHostBackend().getProviders(); final HotSpotJITClassInitializationPlugin initializationPlugin = new HotSpotJITClassInitializationPlugin(); - return LambdaUtils.findStableLambdaName(initializationPlugin, providers, type, options, debug, this); + return LambdaUtils.findStableLambdaName(initializationPlugin, providers, type, options, debug, this, + config -> new GraphBuilderPhase.Instance(providers, config, OptimisticOptimizations.NONE, null)); } @Test diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/BytecodeParser.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/BytecodeParser.java index bc05f845e39a..770d22fc45fb 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/BytecodeParser.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/BytecodeParser.java @@ -1792,7 +1792,7 @@ private boolean genDynamicInvokeHelper(ResolvedJavaMethod target, int cpi, int o } - boolean hasReceiver = (opcode == INVOKEDYNAMIC) ? false : !target.isStatic(); + boolean hasReceiver = opcode != INVOKEDYNAMIC && !target.isStatic(); ValueNode[] args = frameState.popArguments(target.getSignature().getParameterCount(hasReceiver)); if (hasReceiver) { appendInvoke(InvokeKind.Virtual, target, args, null); @@ -2089,12 +2089,7 @@ private static boolean checkPartialIntrinsicExit(ValueNode[] originalArgs, Value protected Invoke createNonInlinedInvoke(ExceptionEdgeAction exceptionEdge, int invokeBci, ValueNode[] invokeArgs, ResolvedJavaMethod targetMethod, InvokeKind invokeKind, JavaKind resultType, JavaType returnType, JavaTypeProfile profile) { - StampPair returnStamp = graphBuilderConfig.getPlugins().getOverridingStamp(this, returnType, false); - if (returnStamp == null) { - returnStamp = StampFactory.forDeclaredType(graph.getAssumptions(), returnType, false); - } - - MethodCallTargetNode callTarget = graph.add(createMethodCallTarget(invokeKind, targetMethod, invokeArgs, returnStamp, profile)); + MethodCallTargetNode callTarget = graph.add(createMethodCallTarget(invokeKind, targetMethod, invokeArgs, returnType, profile)); Invoke invoke = createNonInlinedInvoke(exceptionEdge, invokeBci, callTarget, resultType); for (InlineInvokePlugin plugin : graphBuilderConfig.getPlugins().getInlineInvokePlugins()) { @@ -2105,11 +2100,14 @@ protected Invoke createNonInlinedInvoke(ExceptionEdgeAction exceptionEdge, int i } protected Invoke createNonInlinedInvoke(ExceptionEdgeAction exceptionEdge, int invokeBci, CallTargetNode callTarget, JavaKind resultType) { + Invoke invoke; if (exceptionEdge == ExceptionEdgeAction.OMIT) { - return createInvoke(invokeBci, callTarget, resultType); + invoke = append(createInvoke(invokeBci, callTarget, resultType)); } else { - return createInvokeWithException(invokeBci, callTarget, resultType, exceptionEdge); + invoke = append(createInvokeWithException(invokeBci, callTarget, resultType, exceptionEdge)); } + invoke.setStateAfter(createFrameState(stream.nextBCI(), invoke)); + return invoke; } /** @@ -2675,14 +2673,22 @@ private ValueNode processCalleeReturn(ResolvedJavaMethod targetMethod, InliningS return null; } + public MethodCallTargetNode createMethodCallTarget(InvokeKind invokeKind, ResolvedJavaMethod targetMethod, ValueNode[] args, JavaType returnType, JavaTypeProfile profile) { + StampPair returnStamp = graphBuilderConfig.getPlugins().getOverridingStamp(this, returnType, false); + if (returnStamp == null) { + returnStamp = StampFactory.forDeclaredType(graph.getAssumptions(), returnType, false); + } + + return createMethodCallTarget(invokeKind, targetMethod, args, returnStamp, profile); + } + public MethodCallTargetNode createMethodCallTarget(InvokeKind invokeKind, ResolvedJavaMethod targetMethod, ValueNode[] args, StampPair returnStamp, JavaTypeProfile profile) { return new MethodCallTargetNode(invokeKind, targetMethod, args, returnStamp, profile); } protected InvokeNode createInvoke(int invokeBci, CallTargetNode callTarget, JavaKind resultType) { - InvokeNode invoke = append(new InvokeNode(callTarget, invokeBci)); + InvokeNode invoke = new InvokeNode(callTarget, invokeBci); frameState.pushReturn(resultType, invoke); - invoke.setStateAfter(createFrameState(stream.nextBCI(), invoke)); return invoke; } @@ -2696,9 +2702,8 @@ protected InvokeWithExceptionNode createInvokeWithException(int invokeBci, CallT } AbstractBeginNode exceptionEdge = handleException(null, bci(), exceptionEdgeAction == ExceptionEdgeAction.INCLUDE_AND_DEOPTIMIZE); - InvokeWithExceptionNode invoke = append(new InvokeWithExceptionNode(callTarget, exceptionEdge, invokeBci)); + InvokeWithExceptionNode invoke = new InvokeWithExceptionNode(callTarget, exceptionEdge, invokeBci); frameState.pushReturn(resultType, invoke); - invoke.setStateAfter(createFrameState(stream.nextBCI(), invoke)); return invoke; } @@ -3939,7 +3944,7 @@ public BailoutException bailout(String string) { throw GraphUtil.createBailoutException(string, bailout, elements); } - private FrameState createFrameState(int bci, StateSplit forStateSplit) { + protected FrameState createFrameState(int bci, StateSplit forStateSplit) { assert !(forStateSplit instanceof BytecodeExceptionNode) : Assertions.errorMessageContext("forStateSplit", forStateSplit); if (currentBlock != null && bci > currentBlock.getEndBci()) { frameState.clearNonLiveLocals(currentBlock, liveness, false); @@ -4023,19 +4028,24 @@ public void storeLocal(JavaKind kind, int index) { protected void genLoadConstant(int cpi, int opcode) { Object con = lookupConstant(cpi, opcode, false); + genLoadConstantHelper(con, opcode); + } + + protected void genLoadConstantHelper(Object con, int opcode) { if (con == null) { handleUnresolvedLoadConstant(null); - } else if (con instanceof JavaType) { + } else if (con instanceof JavaType type) { // this is a load of class constant which might be unresolved - JavaType type = (JavaType) con; if (typeIsResolved(type)) { + assert opcode != LDC2_W : "Type cannot use two slots"; frameState.push(JavaKind.Object, appendConstant(getConstantReflection().asJavaClass((ResolvedJavaType) type))); } else { handleUnresolvedLoadConstant(type); } - } else if (con instanceof JavaConstant) { - JavaConstant constant = (JavaConstant) con; - frameState.push(constant.getJavaKind(), appendConstant(constant)); + } else if (con instanceof JavaConstant constant) { + JavaKind javaKind = constant.getJavaKind(); + assert (opcode == LDC2_W) == javaKind.needsTwoSlots() : "Constant required incorrect number of slots: needsTwoSlots is " + javaKind.needsTwoSlots(); + frameState.push(javaKind, appendConstant(constant)); } else if (!(con instanceof Throwable)) { /** * We use the exceptional return value of {@link #lookupConstant(int, int)} as sentinel diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/LambdaUtils.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/LambdaUtils.java index 27cecb8c68f2..52a7fd119eda 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/LambdaUtils.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/LambdaUtils.java @@ -29,25 +29,23 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.List; +import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; import jdk.graal.compiler.nodes.Invoke; import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.graphbuilderconf.ClassInitializationPlugin; import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; -import jdk.graal.compiler.nodes.graphbuilderconf.IntrinsicContext; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugins; -import jdk.graal.compiler.nodes.spi.CoreProviders; import jdk.graal.compiler.options.OptionValues; import jdk.graal.compiler.phases.OptimisticOptimizations; import jdk.graal.compiler.phases.tiers.HighTierContext; import jdk.graal.compiler.phases.util.Providers; - import jdk.vm.ci.common.JVMCIError; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; @@ -99,7 +97,8 @@ private LambdaUtils() { * @return stable name for the lambda class */ @SuppressWarnings("try") - public static String findStableLambdaName(ClassInitializationPlugin cip, Providers providers, ResolvedJavaType lambdaType, OptionValues options, DebugContext debug, Object ctx) + public static String findStableLambdaName(ClassInitializationPlugin cip, Providers providers, ResolvedJavaType lambdaType, OptionValues options, DebugContext debug, Object ctx, + Function graphBuilderSupplier) throws RuntimeException { ResolvedJavaMethod[] lambdaProxyMethods = Arrays.stream(lambdaType.getDeclaredMethods(false)).filter(m -> !m.isBridge() && m.isPublic()).toArray(ResolvedJavaMethod[]::new); /* @@ -108,7 +107,7 @@ public static String findStableLambdaName(ClassInitializationPlugin cip, Provide */ StructuredGraph graph = new StructuredGraph.Builder(options, debug).method(lambdaProxyMethods[0]).build(); try (DebugContext.Scope ignored = debug.scope("Lambda target method analysis", graph, lambdaType, ctx)) { - GraphBuilderPhase lambdaParserPhase = new LambdaGraphBuilder(LambdaUtils.buildLambdaParserConfig(cip)); + GraphBuilderPhase.Instance lambdaParserPhase = graphBuilderSupplier.apply(buildLambdaParserConfig(cip)); HighTierContext context = new HighTierContext(providers, null, OptimisticOptimizations.NONE); lambdaParserPhase.apply(graph, context); } catch (Throwable e) { @@ -171,44 +170,4 @@ public static String digest(String value) { public static String capturingClass(String className) { return className.split(LambdaUtils.SERIALIZATION_TEST_LAMBDA_CLASS_SPLIT_PATTERN)[0]; } - - private static final class LambdaGraphBuilder extends GraphBuilderPhase { - - private LambdaGraphBuilder(GraphBuilderConfiguration config) { - super(config); - } - - @Override - protected GraphBuilderPhase.Instance createInstance(CoreProviders providers, GraphBuilderConfiguration instanceGBConfig, OptimisticOptimizations optimisticOpts, - IntrinsicContext initialIntrinsicContext) { - return new Instance(providers, instanceGBConfig, optimisticOpts, initialIntrinsicContext); - } - - private static class Instance extends GraphBuilderPhase.Instance { - Instance(CoreProviders providers, GraphBuilderConfiguration instanceGBConfig, OptimisticOptimizations optimisticOpts, IntrinsicContext initialIntrinsicContext) { - super(providers, instanceGBConfig, optimisticOpts, initialIntrinsicContext); - } - - @Override - protected BytecodeParser createBytecodeParser(StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, IntrinsicContext intrinsicContext) { - return new LambdaBytecodeParser(this, graph, parent, method, entryBCI, intrinsicContext); - } - } - } - - private static class LambdaBytecodeParser extends BytecodeParser { - - LambdaBytecodeParser(GraphBuilderPhase.Instance instance, StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, IntrinsicContext intrinsicContext) { - super(instance, graph, parent, method, entryBCI, intrinsicContext); - } - - @Override - protected Object lookupConstant(int cpi, int opcode, boolean allowBootstrapMethodInvocation) { - /* - * Native Image forces bootstrap method invocation at build time until support has been - * added for doing the invocation at runtime (GR-45806) - */ - return super.lookupConstant(cpi, opcode, true); - } - } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/BootstrapMethodIntrospectionImpl.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/BootstrapMethodIntrospectionImpl.java new file mode 100644 index 000000000000..86051dc7ff3d --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/BootstrapMethodIntrospectionImpl.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 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 jdk.graal.compiler.serviceprovider; + +import java.lang.reflect.Method; +import java.util.List; + +import jdk.graal.compiler.core.common.BootstrapMethodIntrospection; +import jdk.graal.compiler.debug.GraalError; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +public class BootstrapMethodIntrospectionImpl implements BootstrapMethodIntrospection { + private final Object wrapped; + + /** + * The interface jdk.vm.ci.meta.ConstantPool.BootstrapMethodInvocation was introduced in JVMCI + * 22.1. + */ + private static final Class bsmClass; + private static final Method bsmGetMethod; + private static final Method bsmIsInvokeDynamic; + private static final Method bsmGetName; + private static final Method bsmGetType; + private static final Method bsmGetStaticArguments; + + static { + Class bootstrapMethodClass = null; + try { + bootstrapMethodClass = Class.forName("jdk.vm.ci.meta.ConstantPool$BootstrapMethodInvocation"); + } catch (ClassNotFoundException e) { + } + bsmClass = bootstrapMethodClass; + + Method bootstrapMethodGetMethod = null; + Method bootstrapMethodIsInvokeDynamic = null; + Method bootstrapMethodGetName = null; + Method bootstrapMethodGetType = null; + Method bootstrapMethodGetStaticArguments = null; + + try { + bootstrapMethodGetMethod = bsmClass == null ? null : bsmClass.getMethod("getMethod"); + } catch (NoSuchMethodException e) { + } + + try { + bootstrapMethodIsInvokeDynamic = bsmClass == null ? null : bsmClass.getMethod("isInvokeDynamic"); + } catch (NoSuchMethodException e) { + } + + try { + bootstrapMethodGetName = bsmClass == null ? null : bsmClass.getMethod("getName"); + } catch (NoSuchMethodException e) { + } + + try { + bootstrapMethodGetType = bsmClass == null ? null : bsmClass.getMethod("getType"); + } catch (NoSuchMethodException e) { + } + + try { + bootstrapMethodGetStaticArguments = bsmClass == null ? null : bsmClass.getMethod("getStaticArguments"); + } catch (NoSuchMethodException e) { + } + + bsmGetMethod = bootstrapMethodGetMethod; + bsmIsInvokeDynamic = bootstrapMethodIsInvokeDynamic; + bsmGetName = bootstrapMethodGetName; + bsmGetType = bootstrapMethodGetType; + bsmGetStaticArguments = bootstrapMethodGetStaticArguments; + + } + + public BootstrapMethodIntrospectionImpl(Object wrapped) { + this.wrapped = wrapped; + } + + @Override + public ResolvedJavaMethod getMethod() { + try { + return (ResolvedJavaMethod) bsmGetMethod.invoke(wrapped); + } catch (Throwable t) { + throw GraalError.shouldNotReachHere(t); // ExcludeFromJacocoGeneratedReport + } + } + + @Override + public boolean isInvokeDynamic() { + try { + return (boolean) bsmIsInvokeDynamic.invoke(wrapped); + } catch (Throwable t) { + throw GraalError.shouldNotReachHere(t); // ExcludeFromJacocoGeneratedReport + } + } + + @Override + public String getName() { + try { + return (String) bsmGetName.invoke(wrapped); + } catch (Throwable t) { + throw GraalError.shouldNotReachHere(t); // ExcludeFromJacocoGeneratedReport + } + } + + @Override + public JavaConstant getType() { + try { + return (JavaConstant) bsmGetType.invoke(wrapped); + } catch (Throwable t) { + throw GraalError.shouldNotReachHere(t); // ExcludeFromJacocoGeneratedReport + } + } + + @SuppressWarnings("unchecked") + @Override + public List getStaticArguments() { + try { + return (List) bsmGetStaticArguments.invoke(wrapped); + } catch (Throwable t) { + throw GraalError.shouldNotReachHere(t); // ExcludeFromJacocoGeneratedReport + } + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/GraalServices.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/GraalServices.java index 0f586ca9a1de..c50328fe0d53 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/GraalServices.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/GraalServices.java @@ -40,6 +40,7 @@ import java.util.ServiceConfigurationError; import java.util.ServiceLoader; +import jdk.graal.compiler.core.common.BootstrapMethodIntrospection; import jdk.graal.compiler.debug.GraalError; import jdk.vm.ci.meta.ConstantPool; import jdk.vm.ci.meta.EncodedSpeculationReason; @@ -61,10 +62,12 @@ public final class GraalServices { private static final Method constantPoolLookupMethodWithCaller; private static final Method constantPoolLookupConstantWithResolve; + private static final Method constantPoolLookupBootstrapMethodInvocation; static { Method lookupMethodWithCaller = null; Method lookupConstantWithResolve = null; + Method lookupBootstrapMethodInvocation = null; try { lookupMethodWithCaller = ConstantPool.class.getDeclaredMethod("lookupMethod", Integer.TYPE, Integer.TYPE, ResolvedJavaMethod.class); @@ -76,8 +79,14 @@ public final class GraalServices { } catch (NoSuchMethodException e) { } + try { + lookupBootstrapMethodInvocation = ConstantPool.class.getDeclaredMethod("lookupBootstrapMethodInvocation", Integer.TYPE, Integer.TYPE); + } catch (NoSuchMethodException e) { + } + constantPoolLookupMethodWithCaller = lookupMethodWithCaller; constantPoolLookupConstantWithResolve = lookupConstantWithResolve; + constantPoolLookupBootstrapMethodInvocation = lookupBootstrapMethodInvocation; } private GraalServices() { @@ -478,6 +487,22 @@ public static Object lookupConstant(ConstantPool constantPool, int cpi, boolean return constantPool.lookupConstant(cpi); } + public static BootstrapMethodIntrospection lookupBootstrapMethodIntrospection(ConstantPool constantPool, int cpi, int opcode) { + if (constantPoolLookupBootstrapMethodInvocation != null) { + try { + Object bootstrapMethodInvocation = constantPoolLookupBootstrapMethodInvocation.invoke(constantPool, cpi, opcode); + if (bootstrapMethodInvocation != null) { + return new BootstrapMethodIntrospectionImpl(bootstrapMethodInvocation); + } + } catch (InvocationTargetException e) { + throw rethrow(e.getCause()); + } catch (IllegalAccessException e) { + throw GraalError.shouldNotReachHere(e, "The method lookupBootstrapMethodInvocation should be accessible"); + } + } + return null; + } + @SuppressWarnings("unchecked") static RuntimeException rethrow(Throwable ex) throws E { throw (E) ex; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/WrappedConstantPool.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/WrappedConstantPool.java index 8c6cf91844f5..df789714db1a 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/WrappedConstantPool.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/WrappedConstantPool.java @@ -32,10 +32,13 @@ import java.util.stream.Collectors; import com.oracle.graal.pointsto.constraints.UnresolvedElementException; +import com.oracle.graal.pointsto.util.GraalAccess; import com.oracle.svm.util.ReflectionUtil; +import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; import jdk.graal.compiler.core.common.BootstrapMethodIntrospection; import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.serviceprovider.BootstrapMethodIntrospectionImpl; import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.vm.ci.meta.ConstantPool; import jdk.vm.ci.meta.JavaConstant; @@ -63,7 +66,27 @@ public int length() { } private JavaConstant lookupConstant(JavaConstant constant) { - return universe.lookup(constant); + return universe.lookup(extractResolvedType(constant)); + } + + public JavaConstant extractResolvedType(JavaConstant constant) { + if (constant != null && constant.getJavaKind().isObject() && !constant.isNull()) { + SnippetReflectionProvider snippetReflection = GraalAccess.getOriginalSnippetReflection(); + if (snippetReflection.asObject(Object.class, constant) instanceof ResolvedJavaType resolvedJavaType) { + /* + * BootstrapMethodInvocation.getStaticArguments can output a constant containing a + * HotspotJavaType when a static argument of type Class if loaded lazily in pull + * mode. In this case, the type has to be converted back to a Class, as it would + * cause a hotspot value to be reachable otherwise. + * + * If the constant contains an UnresolvedJavaType, it cannot be converted as a + * Class. It is not a problem for this type to be reachable, so those constants can + * be handled later. + */ + return snippetReflection.forObject(OriginalClassProvider.getJavaClass(resolvedJavaType)); + } + } + return constant; } /** @@ -78,17 +101,6 @@ private JavaConstant lookupConstant(JavaConstant constant) { */ private static final Method lookupMethodWithCaller = ReflectionUtil.lookupMethod(true, ConstantPool.class, "lookupMethod", int.class, int.class, ResolvedJavaMethod.class); - /** - * The interface jdk.vm.ci.meta.ConstantPool.BootstrapMethodInvocation was introduced in JVMCI - * 22.1. - */ - private static final Class bsmClass = ReflectionUtil.lookupClass(true, "jdk.vm.ci.meta.ConstantPool$BootstrapMethodInvocation"); - private static final Method bsmGetMethod = bsmClass == null ? null : ReflectionUtil.lookupMethod(bsmClass, "getMethod"); - private static final Method bsmIsInvokeDynamic = bsmClass == null ? null : ReflectionUtil.lookupMethod(bsmClass, "isInvokeDynamic"); - private static final Method bsmGetName = bsmClass == null ? null : ReflectionUtil.lookupMethod(bsmClass, "getName"); - private static final Method bsmGetType = bsmClass == null ? null : ReflectionUtil.lookupMethod(bsmClass, "getType"); - private static final Method bsmGetStaticArguments = bsmClass == null ? null : ReflectionUtil.lookupMethod(bsmClass, "getStaticArguments"); - @Override public void loadReferencedType(int cpi, int opcode, boolean initialize) { GraalError.guarantee(!initialize, "Must not initialize classes"); @@ -199,80 +211,42 @@ public BootstrapMethodIntrospection lookupBootstrapMethodIntrospection(int cpi, if (cpLookupBootstrapMethodInvocation != null) { try { Object bootstrapMethodInvocation = cpLookupBootstrapMethodInvocation.invoke(wrapped, cpi, opcode); - return new WrappedBootstrapMethodInvocation(bootstrapMethodInvocation); - } catch (Throwable ignored) { - // GR-38955 - understand why exception is thrown + if (bootstrapMethodInvocation != null) { + return new WrappedBootstrapMethodInvocation(bootstrapMethodInvocation); + } + } catch (InvocationTargetException ex) { + throw rethrow(ex.getCause()); + } catch (IllegalAccessException e) { + throw GraalError.shouldNotReachHere(e, "The method lookupBootstrapMethodInvocation should be accessible."); } } return null; } - public class WrappedBootstrapMethodInvocation implements BootstrapMethodIntrospection { - private final Object wrapped; + @SuppressWarnings("unchecked") + static RuntimeException rethrow(Throwable ex) throws E { + throw (E) ex; + } + + public class WrappedBootstrapMethodInvocation extends BootstrapMethodIntrospectionImpl { public WrappedBootstrapMethodInvocation(Object wrapped) { - this.wrapped = wrapped; + super(wrapped); } @Override public ResolvedJavaMethod getMethod() { - if (bsmGetMethod != null) { - try { - return universe.lookup((ResolvedJavaMethod) bsmGetMethod.invoke(wrapped)); - } catch (Throwable t) { - throw GraalError.shouldNotReachHere(t); // ExcludeFromJacocoGeneratedReport - } - } - throw GraalError.shouldNotReachHere("unexpected null"); // ExcludeFromJacocoGeneratedReport - } - - @Override - public boolean isInvokeDynamic() { - if (bsmIsInvokeDynamic != null) { - try { - return (boolean) bsmIsInvokeDynamic.invoke(wrapped); - } catch (Throwable t) { - throw GraalError.shouldNotReachHere(t); // ExcludeFromJacocoGeneratedReport - } - } - throw GraalError.shouldNotReachHere("unexpected null"); // ExcludeFromJacocoGeneratedReport - } - - @Override - public String getName() { - if (bsmGetName != null) { - try { - return (String) bsmGetName.invoke(wrapped); - } catch (Throwable t) { - throw GraalError.shouldNotReachHere(t); // ExcludeFromJacocoGeneratedReport - } - } - throw GraalError.shouldNotReachHere("unexpected null"); // ExcludeFromJacocoGeneratedReport + return universe.lookup(super.getMethod()); } @Override public JavaConstant getType() { - if (bsmGetType != null) { - try { - return lookupConstant((JavaConstant) bsmGetType.invoke(wrapped)); - } catch (Throwable t) { - throw GraalError.shouldNotReachHere(t); // ExcludeFromJacocoGeneratedReport - } - } - throw GraalError.shouldNotReachHere("unexpected null"); // ExcludeFromJacocoGeneratedReport + return lookupConstant(super.getType()); } @Override public List getStaticArguments() { - if (bsmGetStaticArguments != null) { - try { - List original = (List) bsmGetStaticArguments.invoke(wrapped); - return original.stream().map(e -> lookupConstant((JavaConstant) e)).collect(Collectors.toList()); - } catch (Throwable t) { - throw GraalError.shouldNotReachHere(t); // ExcludeFromJacocoGeneratedReport - } - } - throw GraalError.shouldNotReachHere("unexpected null"); // ExcludeFromJacocoGeneratedReport + return super.getStaticArguments().stream().map(WrappedConstantPool.this::lookupConstant).collect(Collectors.toList()); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java index c565552cab19..b3e38c7ceea0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java @@ -37,9 +37,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import jdk.graal.compiler.graph.Node.NodeIntrinsic; -import jdk.graal.compiler.java.LambdaUtils; -import jdk.graal.compiler.nodes.BreakpointNode; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.type.CCharPointer; @@ -57,6 +54,9 @@ import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.StringUtil; +import jdk.graal.compiler.graph.Node.NodeIntrinsic; +import jdk.graal.compiler.java.LambdaUtils; +import jdk.graal.compiler.nodes.BreakpointNode; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.Signature; @@ -455,4 +455,30 @@ public static UUID getUUIDFromString(String digest) { long leastSigBits = new BigInteger(digest.substring(16), 16).longValue(); return new UUID(mostSigBits, leastSigBits); } + + public static Class toUnboxedClass(Class clazz) { + return toUnboxedClassWithDefault(clazz, clazz); + } + + public static Class toUnboxedClassWithDefault(Class clazz, Class defaultClass) { + if (clazz == Boolean.class) { + return boolean.class; + } else if (clazz == Byte.class) { + return byte.class; + } else if (clazz == Short.class) { + return short.class; + } else if (clazz == Character.class) { + return char.class; + } else if (clazz == Integer.class) { + return int.class; + } else if (clazz == Long.class) { + return long.class; + } else if (clazz == Float.class) { + return float.class; + } else if (clazz == Double.class) { + return double.class; + } else { + return defaultClass; + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/bootstrap/BootstrapMethodConfiguration.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/bootstrap/BootstrapMethodConfiguration.java new file mode 100644 index 000000000000..b684efa32ff4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/bootstrap/BootstrapMethodConfiguration.java @@ -0,0 +1,156 @@ +/* + * 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.core.bootstrap; + +import java.lang.invoke.ConstantBootstraps; +import java.lang.invoke.LambdaMetafactory; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.StringConcatFactory; +import java.lang.invoke.TypeDescriptor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.lang.runtime.ObjectMethods; +import java.lang.runtime.SwitchBootstraps; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.RuntimeReflection; + +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.vm.ci.meta.ResolvedJavaMethod; + +/** + * Class storing a list of bootstrap methods that are allowed to be executed at build time. Those + * methods are trusted methods from the JDK. Additionally used to register methods used by some + * bootstrap methods for runtime reflection. + */ +@AutomaticallyRegisteredFeature +public class BootstrapMethodConfiguration implements InternalFeature { + + public record BootstrapMethodRecord(int bci, int cpi, ResolvedJavaMethod method) { + } + + /* + * Map used to cache the BootstrapMethodInfo and reuse it for duplicated bytecode, avoiding to + * execute the bootstrap method for the same bci and method pair. This can happen during + * bytecode parsing as some blocks are duplicated, or for methods that are parsed multiple times + * (see MultiMethod). + */ + private final ConcurrentMap bootstrapMethodInfoCache = new ConcurrentHashMap<>(); + private final Set indyBuildTimeAllowList; + private final Set condyBuildTimeAllowList; + private final Set trustedCondy; + + public static BootstrapMethodConfiguration singleton() { + return ImageSingletons.lookup(BootstrapMethodConfiguration.class); + } + + public BootstrapMethodConfiguration() { + /* + * Bootstrap method used for Lambdas. Executing this method at run time implies defining + * hidden class at run time, which is unsupported. + */ + Method metafactory = ReflectionUtil.lookupMethod(LambdaMetafactory.class, "metafactory", MethodHandles.Lookup.class, String.class, MethodType.class, MethodType.class, MethodHandle.class, + MethodType.class); + /* Alternate version of LambdaMetafactory.metafactory. */ + Method altMetafactory = ReflectionUtil.lookupMethod(LambdaMetafactory.class, "altMetafactory", MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class); + + /* + * Bootstrap method used to optimize String concatenation. Executing it at run time + * currently causes a StackOverFlow error as it infinitely calls itself. + */ + Method makeConcat = ReflectionUtil.lookupMethod(StringConcatFactory.class, "makeConcat", MethodHandles.Lookup.class, String.class, MethodType.class); + /* Alternate version of StringConcatFactory.makeConcat with constant arguments. */ + Method makeConcatWithConstants = ReflectionUtil.lookupMethod(StringConcatFactory.class, "makeConcatWithConstants", MethodHandles.Lookup.class, String.class, MethodType.class, String.class, + Object[].class); + + /* Causes deadlock in Permission feature. */ + Method bootstrap = ReflectionUtil.lookupMethod(ObjectMethods.class, "bootstrap", MethodHandles.Lookup.class, String.class, TypeDescriptor.class, Class.class, String.class, + MethodHandle[].class); + Method typeSwitch = ReflectionUtil.lookupMethod(SwitchBootstraps.class, "typeSwitch", MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class); + + indyBuildTimeAllowList = Set.of(metafactory, altMetafactory, makeConcat, makeConcatWithConstants, bootstrap, typeSwitch); + + /* Bootstrap methods used for various dynamic constants. */ + Method nullConstant = ReflectionUtil.lookupMethod(ConstantBootstraps.class, "nullConstant", MethodHandles.Lookup.class, String.class, Class.class); + Method primitiveClass = ReflectionUtil.lookupMethod(ConstantBootstraps.class, "primitiveClass", MethodHandles.Lookup.class, String.class, Class.class); + Method enumConstant = ReflectionUtil.lookupMethod(ConstantBootstraps.class, "enumConstant", MethodHandles.Lookup.class, String.class, Class.class); + Method getStaticFinal = ReflectionUtil.lookupMethod(ConstantBootstraps.class, "getStaticFinal", MethodHandles.Lookup.class, String.class, Class.class, Class.class); + Method invoke = ReflectionUtil.lookupMethod(ConstantBootstraps.class, "invoke", MethodHandles.Lookup.class, String.class, Class.class, MethodHandle.class, Object[].class); + Method explicitCast = ReflectionUtil.lookupMethod(ConstantBootstraps.class, "explicitCast", MethodHandles.Lookup.class, String.class, Class.class, Object.class); + + /* Bootstrap methods used for dynamic constants representing class data. */ + Method classData = ReflectionUtil.lookupMethod(MethodHandles.class, "classData", MethodHandles.Lookup.class, String.class, Class.class); + Method classDataAt = ReflectionUtil.lookupMethod(MethodHandles.class, "classDataAt", MethodHandles.Lookup.class, String.class, Class.class, int.class); + + /* Set of bootstrap methods for constant dynamic allowed at build time is empty for now */ + condyBuildTimeAllowList = Set.of(); + trustedCondy = Set.of(nullConstant, primitiveClass, enumConstant, getStaticFinal, invoke, explicitCast, classData, classDataAt); + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess a) { + /* + * Those methods are used by ObjectMethods.bootstrap to combine the Strings of the records + * into one String + */ + Class stringConcatHelper = ReflectionUtil.lookupClass(false, "java.lang.StringConcatHelper"); + Class formatConcatItem = ReflectionUtil.lookupClass(false, "jdk.internal.util.FormatConcatItem"); + RuntimeReflection.register(ReflectionUtil.lookupMethod(stringConcatHelper, "prepend", long.class, byte[].class, formatConcatItem, String.class)); + RuntimeReflection.register(ReflectionUtil.lookupMethod(stringConcatHelper, "mix", long.class, formatConcatItem)); + } + + /** + * Check if the provided method is allowed to be executed at build time. + */ + public boolean isIndyAllowedAtBuildTime(Executable method) { + return indyBuildTimeAllowList.contains(method); + } + + /** + * Check if the provided method is allowed to be executed at build time. + */ + public boolean isCondyAllowedAtBuildTime(Executable method) { + return condyBuildTimeAllowList.contains(method); + } + + /** + * Check if the provided method is defined in the JDK. + */ + public boolean isCondyTrusted(Executable method) { + return trustedCondy.contains(method); + } + + public ConcurrentMap getBootstrapMethodInfoCache() { + return bootstrapMethodInfoCache; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/bootstrap/BootstrapMethodInfo.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/bootstrap/BootstrapMethodInfo.java new file mode 100644 index 000000000000..1d6365d78d33 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/bootstrap/BootstrapMethodInfo.java @@ -0,0 +1,61 @@ +/* + * 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.core.bootstrap; + +/** + * Information about a bootstrap method on an invoke dynamic or constant dynamic call. This object + * stores the CallSite or the constant linked to the call, allowing to execute the bootstrap method + * only once. + *

+ * As in Hotspot, there is no synchronization mechanism, meaning it is possible to have a bootstrap + * method executed twice for the same call if multiple threads execute it at the same time. + *

+ * In Hotspot, the CallSite or the constant is stored in the constant pool of the Class, but in + * Native Image, we only store it using this object, which is attached to the call. + */ +public final class BootstrapMethodInfo { + /** + * All field accesses are manually generated in Graal graphs. + */ + @SuppressWarnings("unused") private Object object; + + /** + * Class used to wrap an exception and store it in {@link BootstrapMethodInfo#object} to + * distinguish from a {@link Throwable} outputted by a constant dynamic. + *

+ * If a {@link ExceptionWrapper} is stored in {@link BootstrapMethodInfo#object}, the + * {@link ExceptionWrapper#throwable} is rethrown instead of calling the bootstrap method again. + *

+ * This class is not implemented as a record as it might cause cycles since it would have + * bootstrapped methods. + */ + public static class ExceptionWrapper { + public final Throwable throwable; + + public ExceptionWrapper(Throwable throwable) { + this.throwable = throwable; + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaProxyRenamingSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaProxyRenamingSubstitutionProcessor.java index b83a0841c6cd..547dd543372b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaProxyRenamingSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaProxyRenamingSubstitutionProcessor.java @@ -28,18 +28,18 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor; +import com.oracle.graal.pointsto.phases.NoClassInitializationPlugin; +import com.oracle.graal.pointsto.util.GraalAccess; + import jdk.graal.compiler.debug.DebugContext; import jdk.graal.compiler.debug.DebugContext.Builder; import jdk.graal.compiler.java.LambdaUtils; import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.phases.OptimisticOptimizations; import jdk.graal.compiler.phases.util.Providers; import jdk.graal.compiler.printer.GraalDebugHandlersFactory; - -import com.oracle.graal.pointsto.BigBang; -import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor; -import com.oracle.graal.pointsto.phases.NoClassInitializationPlugin; -import com.oracle.graal.pointsto.util.GraalAccess; - import jdk.vm.ci.meta.ResolvedJavaType; /** @@ -89,7 +89,8 @@ private LambdaSubstitutionType getSubstitution(ResolvedJavaType original) { DebugContext debug = new Builder(options, new GraalDebugHandlersFactory(bb.getSnippetReflectionProvider())).build(); Providers providers = GraalAccess.getOriginalProviders(); - String lambdaTargetName = LambdaUtils.findStableLambdaName(new NoClassInitializationPlugin(), providers, key, options, debug, this); + String lambdaTargetName = LambdaUtils.findStableLambdaName(new NoClassInitializationPlugin(), providers, key, options, debug, this, + config -> new LambdaSubstrateGraphBuilderPhase.LambdaSubstrateGraphBuilderInstance(providers, config, OptimisticOptimizations.NONE, null)); return new LambdaSubstitutionType(key, findUniqueLambdaProxyName(lambdaTargetName)); }); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaSubstrateGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaSubstrateGraphBuilderPhase.java new file mode 100644 index 000000000000..375b2372a739 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaSubstrateGraphBuilderPhase.java @@ -0,0 +1,90 @@ +/* + * 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.hosted.lambda; + +import com.oracle.graal.pointsto.infrastructure.OriginalMethodProvider; +import com.oracle.svm.core.bootstrap.BootstrapMethodConfiguration; + +import jdk.graal.compiler.core.common.BootstrapMethodIntrospection; +import jdk.graal.compiler.java.BytecodeParser; +import jdk.graal.compiler.java.GraphBuilderPhase; +import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; +import jdk.graal.compiler.nodes.graphbuilderconf.IntrinsicContext; +import jdk.graal.compiler.nodes.spi.CoreProviders; +import jdk.graal.compiler.phases.OptimisticOptimizations; +import jdk.graal.compiler.serviceprovider.GraalServices; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +public class LambdaSubstrateGraphBuilderPhase extends GraphBuilderPhase { + public LambdaSubstrateGraphBuilderPhase(GraphBuilderConfiguration config) { + super(config); + } + + @Override + protected Instance createInstance(CoreProviders providers, GraphBuilderConfiguration instanceGBConfig, OptimisticOptimizations optimisticOpts, IntrinsicContext initialIntrinsicContext) { + return new LambdaSubstrateGraphBuilderInstance(providers, instanceGBConfig, optimisticOpts, initialIntrinsicContext); + } + + public static class LambdaSubstrateGraphBuilderInstance extends GraphBuilderPhase.Instance { + LambdaSubstrateGraphBuilderInstance(CoreProviders theProviders, GraphBuilderConfiguration graphBuilderConfig, OptimisticOptimizations optimisticOpts, + IntrinsicContext initialIntrinsicContext) { + super(theProviders, graphBuilderConfig, optimisticOpts, initialIntrinsicContext); + } + + @Override + protected BytecodeParser createBytecodeParser(StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, IntrinsicContext intrinsicContext) { + return new LambdaSubstrateBytecodeParser(this, graph, parent, method, entryBCI, intrinsicContext); + } + } + + public static class LambdaSubstrateBytecodeParser extends BytecodeParser { + protected LambdaSubstrateBytecodeParser(Instance graphBuilderInstance, StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, + IntrinsicContext intrinsicContext) { + super(graphBuilderInstance, graph, parent, method, entryBCI, intrinsicContext); + } + + @Override + protected void genLoadConstant(int cpi, int opcode) { + Object con = lookupConstant(cpi, opcode, false); + BootstrapMethodIntrospection bootstrap = GraalServices.lookupBootstrapMethodIntrospection(constantPool, cpi, -1); + if (con == null && bootstrap != null && BootstrapMethodConfiguration.singleton().isCondyTrusted(OriginalMethodProvider.getJavaMethod(bootstrap.getMethod()))) { + /* + * With the current implementation of LambdaUtils#findStableLambdaName, each lambda + * must contain at least one method invocation to compute its name. Looking up the + * constant with allowBootstrapMethodInvocation set to false will produce null if + * the constant is dynamic. Returning it will cause a runtime exception and if the + * lambda starts with the constant lookup, the lambda will contain no method + * invocation. At this stage, the AnalysisBytecodeParser cannot be created, so the + * graph with the bootstrap method call cannot be created. Thus, at the moment, + * allowing those bootstrap method to be executed at build time is the only way to + * obtain correct lambda names. + */ + con = lookupConstant(cpi, opcode, true); + } + genLoadConstantHelper(con, opcode); + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java index df68872714bf..c111b4964515 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java @@ -24,29 +24,68 @@ */ package com.oracle.svm.hosted.phases; +import java.lang.invoke.CallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.lang.invoke.WrongMethodTypeException; +import java.util.List; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; + +import com.oracle.graal.pointsto.heap.ImageHeapInstance; import com.oracle.graal.pointsto.infrastructure.AnalysisConstantPool; +import com.oracle.graal.pointsto.infrastructure.OriginalMethodProvider; import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.svm.core.bootstrap.BootstrapMethodConfiguration; +import com.oracle.svm.core.meta.DirectSubstrateObjectConstant; import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.ameta.AnalysisConstantReflectionProvider; import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; import com.oracle.svm.util.ModuleSupport; import jdk.graal.compiler.core.common.BootstrapMethodIntrospection; +import jdk.graal.compiler.core.common.type.StampFactory; +import jdk.graal.compiler.core.common.type.TypeReference; import jdk.graal.compiler.java.BciBlockMapping; import jdk.graal.compiler.java.BytecodeParser; import jdk.graal.compiler.java.FrameStateBuilder; import jdk.graal.compiler.java.GraphBuilderPhase; +import jdk.graal.compiler.nodes.AbstractBeginNode; import jdk.graal.compiler.nodes.CallTargetNode.InvokeKind; +import jdk.graal.compiler.nodes.ConstantNode; +import jdk.graal.compiler.nodes.EndNode; +import jdk.graal.compiler.nodes.FixedNode; +import jdk.graal.compiler.nodes.FixedWithNextNode; +import jdk.graal.compiler.nodes.IfNode; +import jdk.graal.compiler.nodes.InvokeWithExceptionNode; +import jdk.graal.compiler.nodes.LogicNode; +import jdk.graal.compiler.nodes.MergeNode; +import jdk.graal.compiler.nodes.NodeView; import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.calc.IsNullNode; +import jdk.graal.compiler.nodes.calc.ObjectEqualsNode; +import jdk.graal.compiler.nodes.extended.BoxNode; +import jdk.graal.compiler.nodes.extended.BranchProbabilityNode; +import jdk.graal.compiler.nodes.extended.BytecodeExceptionNode.BytecodeExceptionKind; +import jdk.graal.compiler.nodes.extended.UnboxNode; import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; import jdk.graal.compiler.nodes.graphbuilderconf.IntrinsicContext; import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugin; import jdk.graal.compiler.nodes.graphbuilderconf.NodePlugin; +import jdk.graal.compiler.nodes.java.InstanceOfNode; +import jdk.graal.compiler.nodes.java.NewArrayNode; +import jdk.graal.compiler.nodes.java.StoreIndexedNode; import jdk.graal.compiler.nodes.spi.CoreProviders; import jdk.graal.compiler.phases.OptimisticOptimizations; +import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.JavaMethod; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.UnresolvedJavaType; public class AnalysisGraphBuilderPhase extends SharedGraphBuilderPhase { @@ -67,6 +106,7 @@ public static class AnalysisBytecodeParser extends SharedBytecodeParser { private final SVMHost hostVM; + @SuppressWarnings("this-escape") protected AnalysisBytecodeParser(GraphBuilderPhase.Instance graphBuilderInstance, StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, IntrinsicContext intrinsicContext, SVMHost hostVM, boolean explicitExceptionEdges) { super(graphBuilderInstance, graph, parent, method, entryBCI, intrinsicContext, explicitExceptionEdges); @@ -114,11 +154,146 @@ private boolean tryNodePluginForDynamicInvocation(BootstrapMethodIntrospection b @Override protected void genInvokeDynamic(int cpi, int opcode) { - BootstrapMethodIntrospection bootstrap = ((AnalysisConstantPool) constantPool).lookupBootstrapMethodIntrospection(cpi, opcode); + BootstrapMethodIntrospection bootstrap; + try { + bootstrap = ((AnalysisConstantPool) constantPool).lookupBootstrapMethodIntrospection(cpi, opcode); + } catch (Throwable ex) { + bootstrapMethodHandler.handleBootstrapException(ex, "invoke dynamic"); + return; + } if (bootstrap != null && tryNodePluginForDynamicInvocation(bootstrap)) { return; } - super.genInvokeDynamic(cpi, opcode); + JavaMethod calleeMethod = lookupMethodInPool(cpi, opcode); + /* + * Bootstrap methods are executed at build time for Web Image due to an issue with + * BoundMethodHandle$SpeciesData + */ + if (bootstrap == null || calleeMethod instanceof ResolvedJavaMethod || + BootstrapMethodConfiguration.singleton().isIndyAllowedAtBuildTime(OriginalMethodProvider.getJavaMethod(bootstrap.getMethod())) || + ImageSingletons.lookup(Platform.class).getOS().equals("js")) { + super.genInvokeDynamic(cpi, opcode); + return; + } + + int parameterLength = bootstrap.getMethod().getParameters().length; + List staticArgumentsList = bootstrap.getStaticArguments(); + boolean isVarargs = bootstrap.getMethod().isVarArgs(); + int bci = bci(); + JavaConstant type = ((ImageHeapInstance) bootstrap.getType()).getHostedObject(); + MethodType methodType = (MethodType) ((DirectSubstrateObjectConstant) type).getObject(); + + for (JavaConstant argument : staticArgumentsList) { + if (argument instanceof ImageHeapInstance imageHeapInstance) { + Object arg = ((DirectSubstrateObjectConstant) imageHeapInstance.getHostedObject()).getObject(); + if (arg instanceof UnresolvedJavaType unresolvedJavaType) { + handleUnresolvedType(unresolvedJavaType); + return; + } + } + } + + if (!bootstrapMethodHandler.checkBootstrapParameters(bootstrap.getMethod(), staticArgumentsList, false)) { + WrongMethodTypeException cause = new WrongMethodTypeException("Cannot convert " + methodType + " to correct MethodType"); + replaceWithThrowingAtRuntime(this, new BootstrapMethodError("Bootstrap method initialization exception", cause)); + return; + } + + /* + * Steps 1-4: Fetch the linked call site and execute the bootstrap method if needed (see + * resolveLinkedObject for details). + */ + + Object initializedCallSite = bootstrapMethodHandler.resolveLinkedObject(bci, cpi, opcode, bootstrap, parameterLength, staticArgumentsList, isVarargs, false); + if (initializedCallSite instanceof UnresolvedJavaType unresolvedJavaType) { + handleUnresolvedType(unresolvedJavaType); + return; + } + if (initializedCallSite instanceof Throwable) { + return; + } + ValueNode initializedCallSiteNode = (ValueNode) initializedCallSite; + + /* + * Check if the CallSite returned is null and throw a BootstrapMethodError if it is. + */ + + LogicNode isInitializedCallSiteNodeNull = graph.unique(IsNullNode.create(initializedCallSiteNode)); + createBytecodeExceptionCheck(bci, isInitializedCallSiteNodeNull, BytecodeExceptionKind.NULL_POINTER, false); + + /* + * Cast the object stored in the BootstrapMethodInfo to ensure it is a CallSite. Doing + * so allows to correctly throw a ClassCastException as hotspot does. + */ + ResolvedJavaType callSiteType = getMetaAccess().lookupJavaType(CallSite.class); + LogicNode isInstanceOfCallSite = graph.unique(InstanceOfNode.create(TypeReference.create(getAssumptions(), callSiteType), initializedCallSiteNode)); + ValueNode callSiteClass = ConstantNode.forConstant(StampFactory.forKind(JavaKind.Object), getConstantReflection().asJavaClass(callSiteType), getMetaAccess(), getGraph()); + createBytecodeExceptionCheck(bci, isInstanceOfCallSite, BytecodeExceptionKind.CLASS_CAST, true, initializedCallSiteNode, callSiteClass); + + bootstrapMethodHandler.invokeMethodAndAppend(bci, CallSite.class, MethodHandle.class, "dynamicInvoker", InvokeKind.Virtual, new ValueNode[]{initializedCallSiteNode}); + ValueNode methodHandleNode = frameState.pop(JavaKind.Object); + + bootstrapMethodHandler.invokeMethodAndAppend(bci, MethodHandle.class, MethodType.class, "type", InvokeKind.Virtual, new ValueNode[]{methodHandleNode}); + ValueNode callSiteMethodTypeNode = frameState.pop(JavaKind.Object); + + LogicNode checkMethodTypeEqual = graph.unique( + ObjectEqualsNode.create(callSiteMethodTypeNode, ConstantNode.forConstant(bootstrap.getType(), getMetaAccess(), getGraph()), getConstantReflection(), NodeView.DEFAULT)); + + EndNode checkMethodTypeEqualTrueEnd = graph.add(new EndNode()); + EndNode checkMethodTypeEqualFalseEnd = graph.add(new EndNode()); + + JavaConstant wrongMethodTypeException = ((AnalysisConstantReflectionProvider) getConstantReflection()) + .forObject(new WrongMethodTypeException("CallSite MethodType should be of type " + methodType)); + ConstantNode wrongMethodTypeExceptionNode = ConstantNode.forConstant(StampFactory.forKind(JavaKind.Object), wrongMethodTypeException, getMetaAccess(), getGraph()); + InvokeWithExceptionNode throwWrongMethodTypeNode = bootstrapMethodHandler.throwBootstrapMethodError(bci, wrongMethodTypeExceptionNode); + throwWrongMethodTypeNode.setNext(checkMethodTypeEqualFalseEnd); + + append(new IfNode(checkMethodTypeEqual, checkMethodTypeEqualTrueEnd, throwWrongMethodTypeNode, BranchProbabilityNode.NOT_LIKELY_PROFILE)); + + MergeNode checkMethodTypeEqualMergeNode = append(new MergeNode()); + checkMethodTypeEqualMergeNode.setStateAfter(createFrameState(stream.nextBCI(), checkMethodTypeEqualMergeNode)); + checkMethodTypeEqualMergeNode.addForwardEnd(checkMethodTypeEqualTrueEnd); + checkMethodTypeEqualMergeNode.addForwardEnd(checkMethodTypeEqualFalseEnd); + + /* Step 5.1: Prepare the arguments for invoking the MethodHandle. */ + + int paramLength = calleeMethod.getSignature().getParameterCount(false); + ValueNode[] invokeExactArguments = popArguments(paramLength); + NewArrayNode newArrayNode = append(new NewArrayNode(getMetaAccess().lookupJavaType(Object.class), ConstantNode.forInt(paramLength, getGraph()), true)); + for (int i = 0; i < paramLength; ++i) { + JavaKind stackKind = invokeExactArguments[i].getStackKind(); + if (stackKind.isPrimitive()) { + /* + * Primitive parameter have to be boxed because invokeExact takes a list of + * Objects as argument. + */ + invokeExactArguments[i] = append(BoxNode.create(invokeExactArguments[i], getMetaAccess().lookupJavaType(stackKind.toBoxedJavaClass()), stackKind)); + } + append(new StoreIndexedNode(newArrayNode, ConstantNode.forInt(i, getGraph()), null, null, JavaKind.Object, invokeExactArguments[i])); + } + ValueNode[] invokeArguments = new ValueNode[]{methodHandleNode, newArrayNode}; + + /* Step 5.2: Invoke the MethodHandle. */ + + Class returnType = methodType.returnType(); + JavaKind returnKind = getMetaAccessExtensionProvider().getStorageKind(getMetaAccess().lookupJavaType(returnType)); + bootstrapMethodHandler.invokeMethodAndAppend(bci, MethodHandle.class, Object.class, "invokeExact", InvokeKind.Virtual, invokeArguments, Object.class.arrayType()); + if (returnKind.equals(JavaKind.Void)) { + frameState.pop(JavaKind.Object); + } else if (returnKind.isPrimitive()) { + /* If the return type is a primitive, unbox the result of invokeExact. */ + frameState.push(returnKind, append(UnboxNode.create(getMetaAccess(), getConstantReflection(), frameState.pop(JavaKind.Object), returnKind))); + } + } + + private void createBytecodeExceptionCheck(int bci, LogicNode logicNode, BytecodeExceptionKind exception, boolean passingOnTrue, ValueNode... arguments) { + AbstractBeginNode passingPath = emitBytecodeExceptionCheck(logicNode, passingOnTrue, exception, arguments); + IfNode bytecodeExceptionIfNode = (IfNode) passingPath.predecessor(); + FixedWithNextNode bytecodeException = (FixedWithNextNode) (passingOnTrue ? bytecodeExceptionIfNode.falseSuccessor() : bytecodeExceptionIfNode.trueSuccessor()).next(); + InvokeWithExceptionNode bootstrapMethodError = bootstrapMethodHandler.throwBootstrapMethodError(bci, bytecodeException); + FixedNode bytecodeExceptionNext = bytecodeException.next(); + bytecodeException.setNext(bootstrapMethodError); + bootstrapMethodError.setNext(bytecodeExceptionNext); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java index fea99b3fcf07..824f7feb9c50 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java @@ -24,9 +24,15 @@ */ package com.oracle.svm.hosted.phases; +import static com.oracle.svm.core.SubstrateUtil.toUnboxedClass; +import static jdk.graal.compiler.bytecode.Bytecodes.LDC2_W; + import java.lang.invoke.LambdaConversionException; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; @@ -35,66 +41,107 @@ import com.oracle.graal.pointsto.constraints.TypeInstantiationException; import com.oracle.graal.pointsto.constraints.UnresolvedElementException; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.graal.pointsto.heap.ImageHeapInstance; +import com.oracle.graal.pointsto.infrastructure.AnalysisConstantPool; import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; +import com.oracle.graal.pointsto.infrastructure.OriginalMethodProvider; +import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; +import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.svm.common.meta.MultiMethod; import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.bootstrap.BootstrapMethodConfiguration; +import com.oracle.svm.core.bootstrap.BootstrapMethodConfiguration.BootstrapMethodRecord; +import com.oracle.svm.core.bootstrap.BootstrapMethodInfo; +import com.oracle.svm.core.bootstrap.BootstrapMethodInfo.ExceptionWrapper; import com.oracle.svm.core.deopt.DeoptimizationSupport; import com.oracle.svm.core.graal.nodes.DeoptEntryBeginNode; 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.LazyConstantNode; import com.oracle.svm.core.graal.nodes.LoweredDeadEndNode; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.meta.DirectSubstrateObjectConstant; import com.oracle.svm.core.nodes.SubstrateMethodCallTargetNode; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.UserError.UserException; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ExceptionSynthesizer; import com.oracle.svm.hosted.LinkAtBuildTimeSupport; +import com.oracle.svm.hosted.ameta.AnalysisConstantReflectionProvider; import com.oracle.svm.hosted.code.FactoryMethodSupport; import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; import com.oracle.svm.hosted.nodes.DeoptProxyNode; +import com.oracle.svm.hosted.snippets.SubstrateGraphBuilderPlugins.FieldOffsetConstantProvider; import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.core.common.BootstrapMethodIntrospection; import jdk.graal.compiler.core.common.calc.Condition; +import jdk.graal.compiler.core.common.memory.MemoryOrderMode; +import jdk.graal.compiler.core.common.type.StampFactory; import jdk.graal.compiler.core.common.type.StampPair; +import jdk.graal.compiler.core.common.type.TypeReference; +import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.graph.Node.NodeIntrinsic; +import jdk.graal.compiler.graph.NodeSourcePosition; import jdk.graal.compiler.java.BciBlockMapping; import jdk.graal.compiler.java.BytecodeParser; import jdk.graal.compiler.java.FrameStateBuilder; import jdk.graal.compiler.java.GraphBuilderPhase; import jdk.graal.compiler.nodes.AbstractBeginNode; import jdk.graal.compiler.nodes.BeginNode; +import jdk.graal.compiler.nodes.CallTargetNode; import jdk.graal.compiler.nodes.CallTargetNode.InvokeKind; import jdk.graal.compiler.nodes.ConstantNode; +import jdk.graal.compiler.nodes.EndNode; +import jdk.graal.compiler.nodes.FieldLocationIdentity; import jdk.graal.compiler.nodes.FixedNode; import jdk.graal.compiler.nodes.FixedWithNextNode; import jdk.graal.compiler.nodes.FrameState; import jdk.graal.compiler.nodes.IfNode; import jdk.graal.compiler.nodes.Invoke; +import jdk.graal.compiler.nodes.InvokeWithExceptionNode; +import jdk.graal.compiler.nodes.LogicNode; +import jdk.graal.compiler.nodes.MergeNode; +import jdk.graal.compiler.nodes.StateSplit; import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.UnreachableBeginNode; import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.ValuePhiNode; import jdk.graal.compiler.nodes.calc.IsNullNode; +import jdk.graal.compiler.nodes.extended.BoxNode; import jdk.graal.compiler.nodes.extended.BranchProbabilityNode; +import jdk.graal.compiler.nodes.extended.UnboxNode; import jdk.graal.compiler.nodes.graphbuilderconf.GeneratedInvocationPlugin; import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderPlugin; import jdk.graal.compiler.nodes.graphbuilderconf.IntrinsicContext; import jdk.graal.compiler.nodes.java.ExceptionObjectNode; +import jdk.graal.compiler.nodes.java.InstanceOfNode; +import jdk.graal.compiler.nodes.java.LoadFieldNode; import jdk.graal.compiler.nodes.java.MethodCallTargetNode; +import jdk.graal.compiler.nodes.java.NewArrayNode; +import jdk.graal.compiler.nodes.java.NewInstanceNode; +import jdk.graal.compiler.nodes.java.StoreIndexedNode; +import jdk.graal.compiler.nodes.java.UnsafeCompareAndSwapNode; import jdk.graal.compiler.nodes.spi.CoreProviders; import jdk.graal.compiler.phases.OptimisticOptimizations; import jdk.graal.compiler.replacements.SnippetTemplate; +import jdk.internal.org.objectweb.asm.Opcodes; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaField; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.JavaMethod; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.JavaTypeProfile; +import jdk.vm.ci.meta.PrimitiveConstant; +import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.UnresolvedJavaType; public abstract class SharedGraphBuilderPhase extends GraphBuilderPhase.Instance { @@ -114,6 +161,7 @@ public abstract static class SharedBytecodeParser extends BytecodeParser { private final boolean explicitExceptionEdges; private final boolean linkAtBuildTime; + protected final BootstrapMethodHandler bootstrapMethodHandler; protected SharedBytecodeParser(GraphBuilderPhase.Instance graphBuilderInstance, StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, IntrinsicContext intrinsicContext, boolean explicitExceptionEdges) { @@ -125,6 +173,7 @@ protected SharedBytecodeParser(GraphBuilderPhase.Instance graphBuilderInstance, super(graphBuilderInstance, graph, parent, method, entryBCI, intrinsicContext); this.explicitExceptionEdges = explicitExceptionEdges; this.linkAtBuildTime = linkAtBuildTime; + this.bootstrapMethodHandler = new BootstrapMethodHandler(); } @Override @@ -202,18 +251,22 @@ protected JavaMethod lookupMethodInPool(int cpi, int opcode) { } @Override - protected Object lookupConstant(int cpi, int opcode, boolean allowBootstrapMethodInvocation) { + protected void genLoadConstant(int cpi, int opcode) { try { - // Native Image forces bootstrap method invocation at build time - // until support has been added for doing the invocation at runtime (GR-45806) - return super.lookupConstant(cpi, opcode, true); - } catch (BootstrapMethodError | IncompatibleClassChangeError | IllegalArgumentException ex) { - if (linkAtBuildTime) { - reportUnresolvedElement("constant", method.format("%H.%n(%P)"), ex); - } else { - replaceWithThrowingAtRuntime(this, ex); + if (super.lookupConstant(cpi, opcode, false) == null) { + Object resolvedObject = bootstrapMethodHandler.loadConstantDynamic(cpi, opcode); + if (resolvedObject instanceof ValueNode valueNode) { + JavaKind javaKind = valueNode.getStackKind(); + assert (opcode == LDC2_W) == javaKind.needsTwoSlots(); + frameState.push(javaKind, valueNode); + } else { + super.genLoadConstantHelper(resolvedObject, opcode); + } + return; } - return ex; + super.genLoadConstant(cpi, opcode); + } catch (BootstrapMethodError | IncompatibleClassChangeError | IllegalArgumentException | NoClassDefFoundError ex) { + bootstrapMethodHandler.handleBootstrapException(ex, "constant"); } } @@ -366,15 +419,25 @@ public static void replaceWithThrowingAtRuntime(SharedByte Throwable cause = throwable.getCause(); if (cause != null) { var metaAccess = (AnalysisMetaAccess) b.getMetaAccess(); - /* Invoke method that creates a cause-instance with cause-message */ - var causeCtor = ReflectionUtil.lookupConstructor(cause.getClass(), String.class); - ResolvedJavaMethod causeCtorMethod = FactoryMethodSupport.singleton().lookup(metaAccess, metaAccess.lookupJavaMethod(causeCtor), false); - ValueNode causeMessageNode = ConstantNode.forConstant(b.getConstantReflection().forString(cause.getMessage()), metaAccess, b.getGraph()); - Invoke causeCtorInvoke = (Invoke) b.appendInvoke(InvokeKind.Static, causeCtorMethod, new ValueNode[]{causeMessageNode}, null); /* * Invoke method that creates and throws throwable-instance with message and cause */ - var errorCtor = ReflectionUtil.lookupConstructor(throwable.getClass(), String.class, Throwable.class); + var errorCtor = ReflectionUtil.lookupConstructor(true, throwable.getClass(), String.class, Throwable.class); + boolean hasCause = errorCtor != null; + Invoke causeCtorInvoke = null; + if (!hasCause) { + /* + * Invoke method that creates and throws throwable-instance with message + */ + errorCtor = ReflectionUtil.lookupConstructor(throwable.getClass(), String.class); + } else { + /* Invoke method that creates a cause-instance with cause-message */ + var causeCtor = ReflectionUtil.lookupConstructor(cause.getClass(), String.class); + ResolvedJavaMethod causeCtorMethod = FactoryMethodSupport.singleton().lookup(metaAccess, metaAccess.lookupJavaMethod(causeCtor), false); + ValueNode causeMessageNode = ConstantNode.forConstant(b.getConstantReflection().forString(cause.getMessage()), metaAccess, b.getGraph()); + causeCtorInvoke = (Invoke) b.appendInvoke(InvokeKind.Static, causeCtorMethod, new ValueNode[]{causeMessageNode}, null); + } + ResolvedJavaMethod throwingMethod = FactoryMethodSupport.singleton().lookup(metaAccess, metaAccess.lookupJavaMethod(errorCtor), true); ValueNode messageNode = ConstantNode.forConstant(b.getConstantReflection().forString(throwable.getMessage()), metaAccess, b.getGraph()); /* @@ -382,7 +445,8 @@ public static void replaceWithThrowingAtRuntime(SharedByte * stack effect. */ boolean verifyStates = b.getFrameStateBuilder().disableStateVerification(); - b.appendInvoke(InvokeKind.Static, throwingMethod, new ValueNode[]{messageNode, causeCtorInvoke.asNode()}, null); + ValueNode[] args = hasCause ? new ValueNode[]{messageNode, causeCtorInvoke.asNode()} : new ValueNode[]{messageNode}; + b.appendInvoke(InvokeKind.Static, throwingMethod, args, null); b.getFrameStateBuilder().setStateVerification(verifyStates); b.add(new LoweredDeadEndNode()); } else { @@ -414,7 +478,7 @@ public static void replaceWithThrowingAtRuntime(SharedBytecodeParser b, Class[] signatureToClasses(JavaMethod method) { int paramCount = method.getSignature().getParameterCount(false); Class[] result = new Class[paramCount]; for (int i = 0; i < paramCount; i++) { - JavaType parameterType = method.getSignature().getParameterType(0, null); + JavaType parameterType = method.getSignature().getParameterType(i, null); if (parameterType instanceof ResolvedJavaType) { result[i] = OriginalClassProvider.getJavaClass((ResolvedJavaType) parameterType); } @@ -572,6 +636,11 @@ public MethodCallTargetNode createMethodCallTarget(InvokeKind invokeKind, Resolv return new SubstrateMethodCallTargetNode(invokeKind, targetMethod, args, returnStamp); } + public MethodCallTargetNode createMethodCallTarget(InvokeKind invokeKind, ResolvedJavaMethod targetMethod, ValueNode[] args, Class returnClass, JavaTypeProfile profile) { + JavaType returnType = getMetaAccess().lookupJavaType(returnClass); + return createMethodCallTarget(invokeKind, targetMethod, args, returnType, profile); + } + @Override protected void genReturn(ValueNode returnVal, JavaKind returnKind) { checkWordType(returnVal, method.getSignature().getReturnType(null), "return value"); @@ -828,5 +897,431 @@ protected boolean forceLoopPhis() { public boolean allowDeoptInPlugins() { return super.allowDeoptInPlugins(); } + + public class BootstrapMethodHandler { + + private Object loadConstantDynamic(int cpi, int opcode) { + BootstrapMethodIntrospection bootstrap; + try { + bootstrap = ((AnalysisConstantPool) constantPool).lookupBootstrapMethodIntrospection(cpi, -1); + } catch (Throwable ex) { + handleBootstrapException(ex, "constant"); + return ex; + } + if (bootstrap != null && !BootstrapMethodConfiguration.singleton().isCondyAllowedAtBuildTime(OriginalMethodProvider.getJavaMethod(bootstrap.getMethod()))) { + int parameterLength = bootstrap.getMethod().getParameters().length; + List staticArguments = bootstrap.getStaticArguments(); + boolean isVarargs = bootstrap.getMethod().isVarArgs(); + JavaConstant type = ((ImageHeapInstance) bootstrap.getType()).getHostedObject(); + DynamicHub typeClass = (DynamicHub) ((DirectSubstrateObjectConstant) type).getObject(); + boolean isPrimitive = typeClass.isPrimitive(); + + for (JavaConstant argument : staticArguments) { + if (argument instanceof ImageHeapInstance imageHeapInstance) { + Object arg = ((DirectSubstrateObjectConstant) imageHeapInstance.getHostedObject()).getObject(); + if (arg instanceof UnresolvedJavaType) { + return arg; + } + } + } + + if (isBootstrapInvocationInvalid(bootstrap, parameterLength, staticArguments, isVarargs, typeClass.getHostedJavaClass())) { + /* + * The number of provided arguments does not match the signature of the + * bootstrap method or the provided type does not match the return type of + * the bootstrap method. Calling lookupConstant with + * allowBootstrapMethodInvocation set to true correctly throws the intended + * BootstrapMethodError. + */ + return SharedBytecodeParser.super.lookupConstant(cpi, opcode, true); + } + + Object resolvedObject = resolveLinkedObject(bci(), cpi, opcode, bootstrap, parameterLength, staticArguments, isVarargs, isPrimitive); + if (resolvedObject instanceof Throwable) { + return resolvedObject; + } + ValueNode resolvedObjectNode = (ValueNode) resolvedObject; + + if (typeClass.isPrimitive()) { + JavaKind constantKind = getMetaAccessExtensionProvider().getStorageKind(getMetaAccess().lookupJavaType(typeClass.getHostedJavaClass())); + resolvedObjectNode = append(UnboxNode.create(getMetaAccess(), getConstantReflection(), resolvedObjectNode, constantKind)); + } + + return resolvedObjectNode; + } + return SharedBytecodeParser.super.lookupConstant(cpi, opcode, true); + } + + /** + * Produces a graph executing the given bootstrap method and linking the result if it is + * the first execution. This corresponds to the following code: + * + *

+             * if (bootstrapMethodInfo.object == null) {
+             *     MethodHandles.Lookup lookup = MethodHandles.lookup();
+             *     try {
+             *         bootstrapMethodInfo.object = bootstrapMethod(lookup, name, type, staticArguments);
+             *     } catch (Throwable throwable) {
+             *         bootstrapMethodInfo.object = new ExceptionWrapper(throwable);
+             *     }
+             * }
+             * Object result = bootstrapMethodInfo.object;
+             * if (result instanceOf ExceptionWrapper exceptionWrapper) {
+             *     throw exceptionWrapper.throwable;
+             * }
+             * return result;
+             * 
+ */ + protected Object resolveLinkedObject(int bci, int cpi, int opcode, BootstrapMethodIntrospection bootstrap, int parameterLength, List staticArgumentsList, + boolean isVarargs, boolean isPrimitiveConstant) { + AnalysisConstantReflectionProvider analysisConstantReflection = (AnalysisConstantReflectionProvider) getConstantReflection(); + ResolvedJavaMethod bootstrapMethod = bootstrap.getMethod(); + + /* Step 1: Initialize the BootstrapMethodInfo. */ + + BootstrapMethodRecord bootstrapMethodRecord = new BootstrapMethodRecord(bci, cpi, ((AnalysisMethod) method).getMultiMethod(MultiMethod.ORIGINAL_METHOD)); + BootstrapMethodInfo bootstrapMethodInfo = BootstrapMethodConfiguration.singleton().getBootstrapMethodInfoCache().computeIfAbsent(bootstrapMethodRecord, + key -> new BootstrapMethodInfo()); + ConstantNode bootstrapMethodInfoNode = ConstantNode.forConstant(analysisConstantReflection.forObject(bootstrapMethodInfo), getMetaAccess(), getGraph()); + + /* + * Step 2: Check if the call site or the constant is linked or if it previously + * threw an exception to rethrow it. + */ + + Field bootstrapObjectField = ReflectionUtil.lookupField(BootstrapMethodInfo.class, "object"); + AnalysisField bootstrapObjectResolvedField = (AnalysisField) getMetaAccess().lookupJavaField(bootstrapObjectField); + LoadFieldNode bootstrapObjectFieldNode = append(LoadFieldNode.create(getAssumptions(), bootstrapMethodInfoNode, bootstrapObjectResolvedField, MemoryOrderMode.ACQUIRE)); + + ValueNode[] arguments = new ValueNode[parameterLength]; + + /* + * The bootstrap method can be VarArgs, which means we have to group the last static + * arguments in an array. + */ + if (isVarargs) { + JavaType varargClass = bootstrapMethod.getParameters()[parameterLength - 1].getType().getComponentType(); + arguments[arguments.length - 1] = append(new NewArrayNode(((AnalysisMetaAccess) getMetaAccess()).getUniverse().lookup(varargClass), + ConstantNode.forInt(staticArgumentsList.size() - arguments.length + 4, getGraph()), true)); + } + + /* + * Prepare the static arguments before continuing as an exception in a nested + * constant dynamic aborts the graph generation. + */ + for (int i = 0; i < staticArgumentsList.size(); ++i) { + JavaConstant constant = staticArgumentsList.get(i); + ValueNode currentNode; + if (constant instanceof PrimitiveConstant primitiveConstant) { + int argCpi = primitiveConstant.asInt(); + Object argConstant = loadConstantDynamic(argCpi, opcode == Opcodes.INVOKEDYNAMIC ? Opcodes.LDC : opcode); + if (argConstant instanceof ValueNode valueNode) { + currentNode = valueNode; + } else if (argConstant instanceof Throwable || argConstant instanceof UnresolvedJavaType) { + /* A nested constant dynamic threw. */ + return argConstant; + } else { + currentNode = ConstantNode.forConstant(analysisConstantReflection.forObject(argConstant), getMetaAccess(), getGraph()); + } + } else { + /* + * Primitive arguments in the non vararg area have to be unboxed to match + * the parameters of the bootstrap method, which is handled by + * createConstant. The arguments in the vararg area have to stay boxed as + * they are passed as Objects. + */ + currentNode = (isVarargs && i + 4 >= parameterLength) ? ConstantNode.forConstant(constant, getMetaAccess(), getGraph()) : createConstant(constant); + } + addArgument(isVarargs, arguments, i + 3, currentNode); + } + + LogicNode condition = graph.unique(IsNullNode.create(bootstrapObjectFieldNode)); + + EndNode falseEnd = graph.add(new EndNode()); + + /* + * Step 3: If the call site or the constant is not linked, execute the bootstrap + * method and link the outputted call site or constant to the constant pool index. + * Otherwise, go to step 4. + */ + + InvokeWithExceptionNode lookup = invokeMethodAndAdd(bci, MethodHandles.class, MethodHandles.Lookup.class, "lookup", InvokeKind.Static, ValueNode.EMPTY_ARRAY); + ValueNode lookupNode = frameState.pop(JavaKind.Object); + + /* + * Step 3.1: Prepare the arguments for the bootstrap method. The first three are + * always the same. The other ones are the static arguments. + */ + + addArgument(isVarargs, arguments, 0, lookupNode); + ConstantNode bootstrapName = ConstantNode.forConstant(analysisConstantReflection.forString(bootstrap.getName()), getMetaAccess(), getGraph()); + addArgument(isVarargs, arguments, 1, bootstrapName); + addArgument(isVarargs, arguments, 2, ConstantNode.forConstant(bootstrap.getType(), getMetaAccess(), getGraph())); + + if (bootstrapMethod.isConstructor()) { + ValueNode[] oldArguments = arguments; + arguments = new ValueNode[arguments.length + 1]; + arguments[0] = graph.add(new NewInstanceNode(bootstrapMethod.getDeclaringClass(), true)); + System.arraycopy(oldArguments, 0, arguments, 1, oldArguments.length); + } + + Class returnClass = OriginalClassProvider.getJavaClass((ResolvedJavaType) bootstrapMethod.getSignature().getReturnType(null)); + + InvokeWithExceptionNode bootstrapObject; + ValueNode bootstrapObjectNode; + /* A bootstrap method can only be either static or a constructor. */ + if (bootstrapMethod.isConstructor()) { + bootstrapObject = invokeMethodAndAddCustomExceptionHandler(bci, void.class, InvokeKind.Special, arguments, bootstrapMethod); + bootstrapObjectNode = arguments[0]; + } else { + bootstrapObject = invokeMethodAndAddCustomExceptionHandler(bci, returnClass, InvokeKind.Static, arguments, bootstrapMethod); + bootstrapObjectNode = frameState.pop(bootstrapObject.getStackKind()); + } + ValueNode finalBootstrapObjectNode = bootstrapObjectNode; + FixedWithNextNode fixedFinalBootstrapObjectNode = null; + if (isPrimitiveConstant) { + fixedFinalBootstrapObjectNode = graph.add( + BoxNode.create(bootstrapObjectNode, getMetaAccess().lookupJavaType(bootstrapObjectNode.getStackKind().toBoxedJavaClass()), bootstrapObjectNode.getStackKind())); + finalBootstrapObjectNode = fixedFinalBootstrapObjectNode; + } + + /* + * If an exception occurs during the bootstrap method execution, store it in the + * BootstrapMethodInfo instead of directly throwing. + */ + InvokeWithExceptionNode exceptionWrapperNode = wrapException(bci, bootstrapObject.exceptionEdge()); + bootstrapObject.exceptionEdge().setNext(exceptionWrapperNode); + MergeNode linkMerge = graph.add(new MergeNode()); + linkMerge.setStateAfter(createFrameState(stream.nextBCI(), linkMerge)); + EndNode n = graph.add(new EndNode()); + exceptionWrapperNode.setNext(n); + linkMerge.addForwardEnd(n); + + EndNode noExceptionEnd = graph.add(new EndNode()); + finalBootstrapObjectNode = graph.unique(new ValuePhiNode(StampFactory.object(), linkMerge, exceptionWrapperNode, finalBootstrapObjectNode)); + + /* + * Step 3.2: Link the call site or the constant outputted by the bootstrap method. + */ + + ConstantNode nullConstant = ConstantNode.forConstant(JavaConstant.NULL_POINTER, getMetaAccess(), getGraph()); + ValueNode offset = graph.addOrUniqueWithInputs( + LazyConstantNode.create(StampFactory.forKind(JavaKind.Long), new FieldOffsetConstantProvider(bootstrapObjectField), SharedBytecodeParser.this)); + FieldLocationIdentity fieldLocationIdentity = new FieldLocationIdentity(bootstrapObjectResolvedField); + FixedWithNextNode linkBootstrapObject = graph.add( + new UnsafeCompareAndSwapNode(bootstrapMethodInfoNode, offset, nullConstant, finalBootstrapObjectNode, JavaKind.Object, fieldLocationIdentity, MemoryOrderMode.RELEASE)); + ((StateSplit) linkBootstrapObject).setStateAfter(createFrameState(stream.nextBCI(), (StateSplit) linkBootstrapObject)); + + NodeSourcePosition nodeSourcePosition = getGraph().currentNodeSourcePosition(); + Object reason = nodeSourcePosition == null ? "Unknown graph builder location." : nodeSourcePosition; + bootstrapObjectResolvedField.registerAsAccessed(reason); + bootstrapObjectResolvedField.registerAsUnsafeAccessed(reason); + + EndNode trueEnd = graph.add(new EndNode()); + + if (bootstrapMethod.isConstructor()) { + lookup.setNext((FixedNode) bootstrapObjectNode); + ((FixedWithNextNode) bootstrapObjectNode).setNext(bootstrapObject); + } else { + lookup.setNext(bootstrapObject); + } + if (isPrimitiveConstant) { + bootstrapObject.setNext(fixedFinalBootstrapObjectNode); + fixedFinalBootstrapObjectNode.setNext(noExceptionEnd); + } else { + bootstrapObject.setNext(noExceptionEnd); + } + linkMerge.addForwardEnd(noExceptionEnd); + linkMerge.setNext(linkBootstrapObject); + linkBootstrapObject.setNext(trueEnd); + + append(new IfNode(condition, lookup, falseEnd, BranchProbabilityNode.NOT_FREQUENT_PROFILE)); + + MergeNode mergeNode = append(new MergeNode()); + mergeNode.setStateAfter(createFrameState(stream.nextBCI(), mergeNode)); + mergeNode.addForwardEnd(trueEnd); + mergeNode.addForwardEnd(falseEnd); + + /* Step 4: Fetch the object from the BootstrapMethodInfo. */ + + LoadFieldNode result = append(LoadFieldNode.create(getAssumptions(), bootstrapMethodInfoNode, bootstrapObjectResolvedField, MemoryOrderMode.ACQUIRE)); + + /* If the object is an exception, it is thrown. */ + TypeReference exceptionWrapper = TypeReference.create(getAssumptions(), getMetaAccess().lookupJavaType(ExceptionWrapper.class)); + LogicNode instanceOfException = graph.unique(InstanceOfNode.create(exceptionWrapper, result)); + EndNode checkExceptionTrueEnd = graph.add(new EndNode()); + EndNode checkExceptionFalseEnd = graph.add(new EndNode()); + + Field exceptionWrapperField = ReflectionUtil.lookupField(ExceptionWrapper.class, "throwable"); + ResolvedJavaField exceptionWrapperResolvedField = getMetaAccess().lookupJavaField(exceptionWrapperField); + LoadFieldNode throwable = graph.add(LoadFieldNode.create(getAssumptions(), result, exceptionWrapperResolvedField)); + InvokeWithExceptionNode bootstrapMethodError = throwBootstrapMethodError(bci, throwable); + throwable.setNext(bootstrapMethodError); + bootstrapMethodError.setNext(checkExceptionTrueEnd); + + append(new IfNode(instanceOfException, throwable, checkExceptionFalseEnd, BranchProbabilityNode.NOT_FREQUENT_PROFILE)); + + MergeNode checkExceptionMergeNode = append(new MergeNode()); + checkExceptionMergeNode.setStateAfter(createFrameState(stream.nextBCI(), checkExceptionMergeNode)); + checkExceptionMergeNode.addForwardEnd(checkExceptionTrueEnd); + checkExceptionMergeNode.addForwardEnd(checkExceptionFalseEnd); + + return result; + } + + private void addArgument(boolean isVarargs, ValueNode[] arguments, int i, ValueNode currentNode) { + if (isVarargs && i >= arguments.length - 1) { + StoreIndexedNode storeIndexedNode = append(new StoreIndexedNode(arguments[arguments.length - 1], ConstantNode.forInt(i + 1 - arguments.length, getGraph()), null, null, + JavaKind.Object, currentNode)); + storeIndexedNode.setStateAfter(createFrameState(stream.nextBCI(), storeIndexedNode)); + } else { + arguments[i] = currentNode; + } + } + + private InvokeWithExceptionNode wrapException(int bci, ValueNode exception) { + Constructor exceptionWrapperCtor = ReflectionUtil.lookupConstructor(ExceptionWrapper.class, Throwable.class); + AnalysisMetaAccess metaAccess = (AnalysisMetaAccess) getMetaAccess(); + ResolvedJavaMethod exceptionWrapperMethod = FactoryMethodSupport.singleton().lookup(metaAccess, metaAccess.lookupJavaMethod(exceptionWrapperCtor), false); + InvokeWithExceptionNode exceptionWrapper = invokeMethodAndAdd(bci, ExceptionWrapper.class, InvokeKind.Static, new ValueNode[]{exception}, exceptionWrapperMethod); + frameState.pop(JavaKind.Object); + return exceptionWrapper; + } + + protected InvokeWithExceptionNode throwBootstrapMethodError(int bci, ValueNode exception) { + Constructor errorCtor = ReflectionUtil.lookupConstructor(BootstrapMethodError.class, Throwable.class); + AnalysisMetaAccess metaAccess = (AnalysisMetaAccess) getMetaAccess(); + ResolvedJavaMethod bootstrapMethodErrorMethod = FactoryMethodSupport.singleton().lookup(metaAccess, metaAccess.lookupJavaMethod(errorCtor), true); + return invokeMethodAndAdd(bci, void.class, InvokeKind.Static, new ValueNode[]{exception}, bootstrapMethodErrorMethod); + } + + /** + * Perform checks on a bootstrap method invocation. The checks verifying the number of + * arguments cannot be performed at run time as they cause build time errors when not + * met. The type checks could be replaced by run time class casts. + *

+ * HotSpot performs those checks in {@code ConstantBootstraps.makeConstant} and + * {@code CallSite.makeSite} by casting the classes and method types. Since the + * bootstrap method is converted to a {@link java.lang.invoke.MethodHandle}, incorrect + * types in the bootstrap method declaration are detected in + * {@link java.lang.invoke.MethodHandle#invoke(Object...)}. + */ + private boolean isBootstrapInvocationInvalid(BootstrapMethodIntrospection bootstrap, int parameterLength, List staticArgumentsList, boolean isVarargs, Class typeClass) { + ResolvedJavaMethod method = bootstrap.getMethod(); + return (isVarargs && parameterLength > (3 + staticArgumentsList.size())) || (!isVarargs && parameterLength != (3 + staticArgumentsList.size())) || + !(OriginalClassProvider.getJavaClass((ResolvedJavaType) method.getSignature().getReturnType(null)).isAssignableFrom(typeClass) || method.isConstructor()) || + !checkBootstrapParameters(method, bootstrap.getStaticArguments(), true); + } + + protected boolean checkBootstrapParameters(ResolvedJavaMethod bootstrapMethod, List staticArguments, boolean condy) { + int parametersLength = bootstrapMethod.getParameters().length; + Class[] parameters = signatureToClasses(bootstrapMethod); + if (bootstrapMethod.isVarArgs()) { + /* + * The mismatch in the number of arguments causes a WrongMethodTypeException in + * MethodHandle.invoke on the bootstrap method invocation. + */ + parameters[parametersLength - 1] = parameters[parametersLength - 1].getComponentType(); + } + if (!bootstrapMethod.isVarArgs() && 3 + staticArguments.size() != parameters.length) { + return false; + } + for (int i = 0; i < parametersLength; ++i) { + if (i == 0) { + /* HotSpot performs this check in ConstantBootstraps#makeConstant. */ + if (!(condy ? parameters[i].equals(MethodHandles.Lookup.class) : parameters[i].isAssignableFrom(MethodHandles.Lookup.class))) { + return false; + } + } else if (i == 1) { + /* + * This parameter is converted to a String in + * MethodHandleNatives#linkCallSite and + * MethodHandleNatives#linkDynamicConstant. Not having a String here causes + * a ClassCastException in MethodHandle.invoke. + */ + if (!parameters[i].isAssignableFrom(String.class)) { + return false; + } + } else if (i == 2) { + /* + * This parameter is converted to a Class/MethodType in + * MethodHandleNatives#linkCallSite/MethodHandleNatives#linkDynamicConstant. + * Not having a Class/MethodType here causes a ClassCastException in + * MethodHandle.invoke. + */ + if (!parameters[i].isAssignableFrom(condy ? Class.class : MethodType.class)) { + return false; + } + } else { + if (!(bootstrapMethod.isVarArgs() && staticArguments.size() == i - 3) && staticArguments.get(i - 3) instanceof ImageHeapConstant imageHeapConstant) { + Class parameterClass = OriginalClassProvider.getJavaClass(imageHeapConstant.getType(getMetaAccess())); + /* + * Having incompatible types here causes a ClassCastException in + * MethodHandle.invoke on the bootstrap method invocation. + */ + if (!(parameters[i].isAssignableFrom(parameterClass) || toUnboxedClass(parameters[i]).isAssignableFrom(toUnboxedClass(parameterClass)))) { + return false; + } + } + } + } + return true; + } + + protected void handleBootstrapException(Throwable ex, String elementKind) { + if (linkAtBuildTime) { + reportUnresolvedElement(elementKind, method.format("%H.%n(%P)"), ex); + } else { + replaceWithThrowingAtRuntime(SharedBytecodeParser.this, ex); + } + } + + protected ConstantNode createConstant(JavaConstant constant) { + JavaConstant primitiveConstant = getConstantReflection().unboxPrimitive(constant); + return ConstantNode.forConstant(primitiveConstant == null ? constant : primitiveConstant, getMetaAccess(), getGraph()); + } + + protected Invoke invokeMethodAndAppend(int bci, Class clazz, Class returnClass, String name, InvokeKind invokeKind, ValueNode[] arguments, Class... classes) { + ResolvedJavaMethod invokedMethod = lookupResolvedJavaMethod(clazz, name, classes); + CallTargetNode callTarget = graph.add(createMethodCallTarget(invokeKind, invokedMethod, arguments, returnClass, null)); + return createNonInlinedInvoke(ExceptionEdgeAction.INCLUDE_AND_HANDLE, bci, callTarget, callTarget.returnStamp().getTrustedStamp().getStackKind()); + } + + protected InvokeWithExceptionNode invokeMethodAndAdd(int bci, Class clazz, Class returnClass, String name, InvokeKind invokeKind, ValueNode[] arguments, + Class... classes) { + ResolvedJavaMethod invokedMethod = lookupResolvedJavaMethod(clazz, name, classes); + return invokeMethodAndAdd(bci, returnClass, invokeKind, arguments, invokedMethod); + } + + protected InvokeWithExceptionNode invokeMethodAndAdd(int bci, Class returnClass, InvokeKind invokeKind, ValueNode[] arguments, ResolvedJavaMethod invokedMethod) { + CallTargetNode callTarget = graph.add(createMethodCallTarget(invokeKind, invokedMethod, arguments, returnClass, null)); + InvokeWithExceptionNode invoke = graph + .add(createInvokeWithException(bci, callTarget, callTarget.returnStamp().getTrustedStamp().getStackKind(), ExceptionEdgeAction.INCLUDE_AND_HANDLE)); + invoke.setStateAfter(createFrameState(stream.nextBCI(), invoke)); + return invoke; + } + + protected InvokeWithExceptionNode invokeMethodAndAddCustomExceptionHandler(int bci, Class returnClass, InvokeKind invokeKind, ValueNode[] arguments, ResolvedJavaMethod invokedMethod) { + CallTargetNode callTarget = graph.add(createMethodCallTarget(invokeKind, invokedMethod, arguments, returnClass, null)); + ExceptionObjectNode exceptionObject = graph.add(new ExceptionObjectNode(getMetaAccess())); + FrameStateBuilder dispatchState = frameState.copy(); + dispatchState.clearStack(); + dispatchState.pushReturn(JavaKind.Object, exceptionObject); + dispatchState.setRethrowException(true); + exceptionObject.setStateAfter(dispatchState.create(stream.nextBCI(), exceptionObject)); + InvokeWithExceptionNode invoke = graph.add(new InvokeWithExceptionNode(callTarget, exceptionObject, bci)); + frameState.pushReturn(callTarget.returnStamp().getTrustedStamp().getStackKind(), invoke); + invoke.setStateAfter(createFrameState(stream.nextBCI(), invoke)); + return invoke; + } + + private ResolvedJavaMethod lookupResolvedJavaMethod(Class clazz, String name, Class... classes) { + try { + return getMetaAccess().lookupJavaMethod(clazz.getDeclaredMethod(name, classes)); + } catch (NoSuchMethodException e) { + throw GraalError.shouldNotReachHere("Could not find method in " + clazz + " named " + name); + } + } + } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java index 0730b36ebb00..909a466dc167 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java @@ -850,11 +850,11 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec }); } - private static class FieldOffsetConstantProvider implements Function { + public static class FieldOffsetConstantProvider implements Function { private final Field javaField; - FieldOffsetConstantProvider(Field javaField) { + public FieldOffsetConstantProvider(Field javaField) { this.javaField = javaField; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java index 75fab2f43059..0a7724cb6da3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.hosted.substitute; +import static com.oracle.svm.core.SubstrateUtil.toUnboxedClass; import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.AtomicFieldUpdaterOffset; import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.FieldOffset; import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.StaticFieldBase; @@ -42,7 +43,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import org.graalvm.collections.EconomicMap; -import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; import org.graalvm.nativeimage.hosted.FieldValueTransformer; import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; @@ -65,6 +65,7 @@ import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.ReflectionUtil.ReflectionUtilError; +import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; import jdk.internal.misc.Unsafe; import jdk.vm.ci.common.NativeImageReinitialize; import jdk.vm.ci.meta.JavaConstant; @@ -396,28 +397,6 @@ private void checkValue(Object newValue) { } } - private static Class toUnboxedClass(Class clazz) { - if (clazz == Boolean.class) { - return boolean.class; - } else if (clazz == Byte.class) { - return byte.class; - } else if (clazz == Short.class) { - return short.class; - } else if (clazz == Character.class) { - return char.class; - } else if (clazz == Integer.class) { - return int.class; - } else if (clazz == Long.class) { - return long.class; - } else if (clazz == Float.class) { - return float.class; - } else if (clazz == Double.class) { - return double.class; - } else { - return clazz; - } - } - private String fieldFormat() { return "field " + original.format("%H.%n") + (annotated != null ? " specified by alias " + annotated.format("%H.%n") : ""); }