diff --git a/compiler/mx.compiler/mx_compiler.py b/compiler/mx.compiler/mx_compiler.py index 7f0f821343fd..3f116e48cf6d 100644 --- a/compiler/mx.compiler/mx_compiler.py +++ b/compiler/mx.compiler/mx_compiler.py @@ -748,6 +748,8 @@ def __init__(self, option_strings, nargs=None, **kwargs): def __call__(self, parser, namespace, values, option_string=None): # do not override existing values old_values = getattr(namespace, self.dest) + # shlex.split interprets '\' as an escape char so it needs to be escaped itself + values = values.replace("\\", "\\\\") setattr(namespace, self.dest, (old_values if old_values else []) + shlex.split(values)) mx_gate.add_gate_runner(_suite, _graal_gate_runner) diff --git a/compiler/mx.compiler/suite.py b/compiler/mx.compiler/suite.py index 21968e5600be..06120da3326f 100644 --- a/compiler/mx.compiler/suite.py +++ b/compiler/mx.compiler/suite.py @@ -173,7 +173,7 @@ ], "requiresConcealed" : { "java.base" : [ - "jdk.internal.misc", + "jdk.internal.misc" ], "jdk.internal.vm.ci" : [ "jdk.vm.ci.meta", diff --git a/compiler/src/jdk.graal.compiler.microbenchmarks/src/jdk/graal/compiler/microbenchmarks/lir/GraalCompilerState.java b/compiler/src/jdk.graal.compiler.microbenchmarks/src/jdk/graal/compiler/microbenchmarks/lir/GraalCompilerState.java index 95eb4911950e..6e4141333fca 100644 --- a/compiler/src/jdk.graal.compiler.microbenchmarks/src/jdk/graal/compiler/microbenchmarks/lir/GraalCompilerState.java +++ b/compiler/src/jdk.graal.compiler.microbenchmarks/src/jdk/graal/compiler/microbenchmarks/lir/GraalCompilerState.java @@ -299,7 +299,7 @@ protected PhaseSuite getDefaultGraphBuilderSuite() { } protected LIRSuites getLIRSuites() { - return request.lirSuites; + return request.lirSuites(); } private Request request; @@ -325,15 +325,15 @@ protected final void prepareRequest() { ResolvedJavaMethod installedCodeOwner = graph.method(); request = new Request<>(graph, installedCodeOwner, getProviders(), getBackend(), getDefaultGraphBuilderSuite(), OptimisticOptimizations.ALL, graph.getProfilingInfo(), createSuites(getOptions()), createLIRSuites(getOptions()), new CompilationResult(graph.compilationId()), CompilationResultBuilderFactory.Default, - null, true); + null, null, true); } /** * Executes the high-level (FrontEnd) part of the compiler. */ protected final void emitFrontEnd() { - GraalCompiler.emitFrontEnd(request.providers, request.backend, request.graph, request.graphBuilderSuite, request.optimisticOpts, request.profilingInfo, request.suites); - request.graph.freeze(); + GraalCompiler.emitFrontEnd(request.providers(), request.backend(), request.graph(), request.graphBuilderSuite(), request.optimisticOpts(), request.profilingInfo(), request.suites()); + request.graph().freeze(); } /** @@ -364,24 +364,24 @@ protected final void generateLIR() { * Sets up {@link LIR} generation. */ protected final void preLIRGeneration() { - assert request.graph.isFrozen() : "Graph not frozen."; + assert request.graph().isFrozen() : "Graph not frozen."; Object stub = null; - schedule = request.graph.getLastSchedule(); + schedule = request.graph().getLastSchedule(); ControlFlowGraph cfg = deepCopy(schedule.getCFG()); HIRBlock[] blocks = cfg.getBlocks(); HIRBlock startBlock = cfg.getStartBlock(); assert startBlock != null; assert startBlock.getPredecessorCount() == 0; - blockOrder = request.backend.newBlockOrder(blocks.length, startBlock); + blockOrder = request.backend().newBlockOrder(blocks.length, startBlock); linearScanOrder = LinearScanOrder.computeLinearScanOrder(blocks.length, startBlock); LIR lir = new LIR(cfg, linearScanOrder, getGraphOptions(), getGraphDebug()); - LIRGenerationProvider lirBackend = (LIRGenerationProvider) request.backend; - RegisterAllocationConfig registerAllocationConfig = request.backend.newRegisterAllocationConfig(registerConfig, null, stub); - lirGenRes = lirBackend.newLIRGenerationResult(graph.compilationId(), lir, registerAllocationConfig, request.graph, stub); + LIRGenerationProvider lirBackend = (LIRGenerationProvider) request.backend(); + RegisterAllocationConfig registerAllocationConfig = request.backend().newRegisterAllocationConfig(registerConfig, null, stub); + lirGenRes = lirBackend.newLIRGenerationResult(graph.compilationId(), lir, registerAllocationConfig, request.graph(), stub); lirGenTool = lirBackend.newLIRGenerator(lirGenRes); - nodeLirGen = lirBackend.newNodeLIRBuilder(request.graph, lirGenTool); + nodeLirGen = lirBackend.newNodeLIRBuilder(request.graph(), lirGenTool); } protected OptionValues getGraphOptions() { @@ -401,8 +401,8 @@ private static ControlFlowGraph deepCopy(ControlFlowGraph cfg) { * Executes the {@link LIRGenerationPhase}. */ protected final void lirGeneration() { - LIRGenerationContext context = new LIRGenerationContext(lirGenTool, nodeLirGen, request.graph, schedule); - new LIRGenerationPhase().apply(request.backend.getTarget(), lirGenRes, context); + LIRGenerationContext context = new LIRGenerationContext(lirGenTool, nodeLirGen, request.graph(), schedule); + new LIRGenerationPhase().apply(request.backend().getTarget(), lirGenRes, context); } /** @@ -418,7 +418,7 @@ protected final void emitLowLevel() { * Executes a {@link LIRPhase} within a given {@code context}. */ protected void applyLIRPhase(LIRPhase phase, C context) { - phase.apply(request.backend.getTarget(), lirGenRes, context); + phase.apply(request.backend().getTarget(), lirGenRes, context); } /** @@ -464,12 +464,12 @@ protected PostAllocationOptimizationContext createPostAllocationOptimizationCont * Emits the machine code. */ protected final void emitCode() { - int bytecodeSize = request.graph.method() == null ? 0 : request.graph.getBytecodeSize(); + int bytecodeSize = request.graph().method() == null ? 0 : request.graph().getBytecodeSize(); SpeculationLog speculationLog = null; - request.compilationResult.setHasUnsafeAccess(request.graph.hasUnsafeAccess()); - LIRCompilerBackend.emitCode(request.backend, request.graph.getAssumptions(), request.graph.method(), request.graph.getMethods(), speculationLog, - bytecodeSize, lirGenRes, request.compilationResult, - request.installedCodeOwner, request.factory, request.entryPointDecorator); + request.compilationResult().setHasUnsafeAccess(request.graph().hasUnsafeAccess()); + LIRCompilerBackend.emitCode(request.backend(), request.graph().getAssumptions(), request.graph().method(), request.graph().getMethods(), speculationLog, + bytecodeSize, lirGenRes, request.compilationResult(), + request.installedCodeOwner(), request.factory(), request.entryPointDecorator()); } protected StructuredGraph graph() { @@ -495,7 +495,7 @@ public void setup() { public CompilationResult compile() { emitFrontEnd(); emitBackEnd(); - return super.request.compilationResult; + return super.request.compilationResult(); } } @@ -538,7 +538,7 @@ public void setupGraph() { public CompilationResult compile() { emitBackEnd(); - return super.request.compilationResult; + return super.request.compilationResult(); } } diff --git a/compiler/src/jdk.graal.compiler.processor/src/jdk/graal/compiler/core/match/processor/MatchProcessor.java b/compiler/src/jdk.graal.compiler.processor/src/jdk/graal/compiler/core/match/processor/MatchProcessor.java index 94578d1e808b..04481b549d58 100644 --- a/compiler/src/jdk.graal.compiler.processor/src/jdk/graal/compiler/core/match/processor/MatchProcessor.java +++ b/compiler/src/jdk.graal.compiler.processor/src/jdk/graal/compiler/core/match/processor/MatchProcessor.java @@ -518,6 +518,7 @@ private void createFiles(MatchRuleDescriptor info) { out.println("import jdk.graal.compiler.core.match.*;"); out.println("import jdk.graal.compiler.core.gen.NodeMatchRules;"); out.println("import jdk.graal.compiler.graph.Position;"); + for (String p : info.requiredPackages) { if (p.equals(pkg)) { continue; @@ -593,6 +594,17 @@ private void createFiles(MatchRuleDescriptor info) { out.println(); + out.println(" @Override"); + out.println(" public String getArchitecture() {"); + String archStr = topDeclaringClass.toString().replace("NodeMatchRules", ""); + if (!archStr.equals("AMD64")) { + // The AMD64 JVMCI class is the exception in terms of using an uppercase value + // for the name field. + archStr = archStr.toLowerCase(); + } + out.println(" return " + '"' + archStr + "\";"); + out.println(" }"); + out.println("}"); } this.createProviderFile(pkg + "." + matchStatementClassName, "jdk.graal.compiler.core.match.MatchStatementSet", originatingElements); diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/GraalCompilerTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/GraalCompilerTest.java index 978eb7658cc8..cd76c71f01fe 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/GraalCompilerTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/GraalCompilerTest.java @@ -1269,7 +1269,7 @@ protected CompilationResult compile(ResolvedJavaMethod installedCodeOwner, Struc } Request request = new Request<>(graphToCompile, installedCodeOwner, getProviders(), getBackend(), getDefaultGraphBuilderSuite(), getOptimisticOptimizations(), - graphToCompile.getProfilingInfo(), suites, createLIRSuites(options), compilationResult, CompilationResultBuilderFactory.Default, null, true); + graphToCompile.getProfilingInfo(), suites, createLIRSuites(options), compilationResult, CompilationResultBuilderFactory.Default, null, null, true); CompilationResult result = GraalCompiler.compile(request); graphToCompile.getOptimizationLog().emit(new StableMethodNameFormatter(getDefaultGraphBuilderPhase(), getProviders(), graphToCompile.getDebug())); return result; diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/InfopointReasonTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/InfopointReasonTest.java index 9add301c345e..57af73853cee 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/InfopointReasonTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/InfopointReasonTest.java @@ -24,12 +24,14 @@ */ package jdk.graal.compiler.core.test; -import static jdk.graal.compiler.core.GraalCompiler.compileGraph; import static jdk.graal.compiler.core.common.GraalOptions.OptAssumptions; import static org.junit.Assert.assertNotNull; import jdk.graal.compiler.code.CompilationResult; +import jdk.graal.compiler.core.GraalCompiler; +import jdk.graal.compiler.core.target.Backend; import jdk.graal.compiler.lir.asm.CompilationResultBuilderFactory; +import jdk.graal.compiler.lir.phases.LIRSuites; import jdk.graal.compiler.nodes.FullInfopointNode; import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.StructuredGraph.AllowAssumptions; @@ -37,6 +39,9 @@ import jdk.graal.compiler.phases.OptimisticOptimizations; import jdk.graal.compiler.phases.PhaseSuite; import jdk.graal.compiler.phases.tiers.HighTierContext; +import jdk.graal.compiler.phases.tiers.Suites; +import jdk.graal.compiler.phases.util.Providers; +import jdk.vm.ci.meta.ProfilingInfo; import org.junit.Test; import jdk.vm.ci.code.site.Call; @@ -70,8 +75,25 @@ public String testMethod() { public void callInfopoints() { final ResolvedJavaMethod method = getResolvedJavaMethod("testMethod"); final StructuredGraph graph = parseEager(method, AllowAssumptions.YES); - final CompilationResult cr = compileGraph(graph, graph.method(), getProviders(), getBackend(), getDefaultGraphBuilderSuite(), OptimisticOptimizations.ALL, graph.getProfilingInfo(), - createSuites(graph.getOptions()), createLIRSuites(graph.getOptions()), new CompilationResult(graph.compilationId()), CompilationResultBuilderFactory.Default, true); + Providers providers = getProviders(); + Backend backend = getBackend(); + PhaseSuite graphBuilderSuite = getDefaultGraphBuilderSuite(); + ProfilingInfo profilingInfo = graph.getProfilingInfo(); + Suites suites = createSuites(graph.getOptions()); + LIRSuites lirSuites = createLIRSuites(graph.getOptions()); + CompilationResult compilationResult = new CompilationResult(graph.compilationId()); + final CompilationResult cr = GraalCompiler.compile(new GraalCompiler.Request<>(graph, + graph.method(), + providers, + backend, + graphBuilderSuite, + OptimisticOptimizations.ALL, + profilingInfo, + suites, + lirSuites, + compilationResult, + CompilationResultBuilderFactory.Default, + true)); for (Infopoint sp : cr.getInfopoints()) { assertNotNull(sp.reason); if (sp instanceof Call) { @@ -92,8 +114,24 @@ public void lineInfopoints() { } assertTrue(graphLineSPs > 0); PhaseSuite graphBuilderSuite = getCustomGraphBuilderSuite(GraphBuilderConfiguration.getDefault(getDefaultGraphBuilderPlugins()).withFullInfopoints(true)); - final CompilationResult cr = compileGraph(graph, graph.method(), getProviders(), getBackend(), graphBuilderSuite, OptimisticOptimizations.ALL, graph.getProfilingInfo(), - createSuites(graph.getOptions()), createLIRSuites(graph.getOptions()), new CompilationResult(graph.compilationId()), CompilationResultBuilderFactory.Default, true); + Providers providers = getProviders(); + Backend backend = getBackend(); + ProfilingInfo profilingInfo = graph.getProfilingInfo(); + Suites suites = createSuites(graph.getOptions()); + LIRSuites lirSuites = createLIRSuites(graph.getOptions()); + CompilationResult compilationResult = new CompilationResult(graph.compilationId()); + final CompilationResult cr = GraalCompiler.compile(new GraalCompiler.Request<>(graph, + graph.method(), + providers, + backend, + graphBuilderSuite, + OptimisticOptimizations.ALL, + profilingInfo, + suites, + lirSuites, + compilationResult, + CompilationResultBuilderFactory.Default, + true)); int lineSPs = 0; for (Infopoint sp : cr.getInfopoints()) { assertNotNull(sp.reason); diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifySystemPropertyUsage.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifySystemPropertyUsage.java index 70b616ef4a4f..44ee0fe41b4d 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifySystemPropertyUsage.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifySystemPropertyUsage.java @@ -24,14 +24,12 @@ */ package jdk.graal.compiler.core.test; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.java.MethodCallTargetNode; import jdk.graal.compiler.nodes.spi.CoreProviders; import jdk.graal.compiler.phases.VerifyPhase; +import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; @@ -39,34 +37,25 @@ /** * Checks against calls to {@link System#getProperty(String)}, - * {@link System#getProperty(String, String)} and {@link System#getProperties()}. System properties - * can be modified by application code so {@link Services#getSavedProperties()} should be used - * instead. + * {@link System#getProperty(String, String)} and {@link System#getProperties()}. + *

+ * System properties can be modified by application code so {@link GraalServices#getSavedProperty} + * should be used instead. + *

+ * In the context of GuestGraal, JVMCI code is run at image build time in a non-boot class loader + * and so JVMCI native methods will fail to link (they are only linked by the boot loader). As + * {@link Services#getSavedProperties()} calls a JVMCI native method, it cannot be used during build + * time initialization. Instead, {@link GraalServices#getSavedProperties()} must be used. */ public class VerifySystemPropertyUsage extends VerifyPhase { static final Class[] BOXES = {Integer.class, Long.class, Boolean.class, Float.class, Double.class}; - static final int JVMCI_VERSION_MAJOR; - static final int JVMCI_VERSION_MINOR; - static { - int major = -1; - int minor = -1; - String vmVersion = System.getProperty("java.vm.version"); - if (System.getProperty("java.specification.version").compareTo("1.9") < 0) { - Pattern re = Pattern.compile(".*-jvmci-(\\d+)\\.(\\d+).*"); - Matcher matcher = re.matcher(vmVersion); - if (matcher.matches()) { - major = Integer.parseInt(matcher.group(1)); - minor = Integer.parseInt(matcher.group(2)); - } - } - JVMCI_VERSION_MAJOR = major; - JVMCI_VERSION_MINOR = minor; - } @Override protected void verify(StructuredGraph graph, CoreProviders context) { MetaAccessProvider metaAccess = context.getMetaAccess(); + final ResolvedJavaType servicesType = metaAccess.lookupJavaType(Services.class); + final ResolvedJavaType graalServicesType = metaAccess.lookupJavaType(GraalServices.class); final ResolvedJavaType systemType = metaAccess.lookupJavaType(System.class); final ResolvedJavaType[] boxTypes = new ResolvedJavaType[BOXES.length]; for (int i = 0; i < boxTypes.length; i++) { @@ -77,20 +66,12 @@ protected void verify(StructuredGraph graph, CoreProviders context) { String holderQualified = caller.format("%H"); String holderUnqualified = caller.format("%h"); String packageName = holderQualified.equals(holderUnqualified) ? "" : holderQualified.substring(0, holderQualified.length() - holderUnqualified.length() - 1); - if (packageName.startsWith("jdk.vm.ci")) { - if (JVMCI_VERSION_MAJOR >= 0 && JVMCI_VERSION_MINOR > 56) { - // This JVMCI version should not use non-saved system properties - } else { - // This JVMCI version still has some calls that need to be removed - return; - } - } else if (holderQualified.equals("jdk.graal.compiler.hotspot.JVMCIVersionCheck") && caller.getName().equals("main")) { + if (holderQualified.equals("jdk.graal.compiler.hotspot.JVMCIVersionCheck") && caller.getName().equals("main")) { // The main method in JVMCIVersionCheck is only called from the shell return; } else if (packageName.startsWith("com.oracle.truffle") || packageName.startsWith("org.graalvm.polyglot") || packageName.startsWith("org.graalvm.home") || packageName.equals("com.oracle.truffle.runtime.hotspot")) { - // Truffle, SDK and Truffle runtime do not depend on JVMCI so they cannot use - // Services.getSavedProperties() + // Truffle, SDK and Truffle runtime cannot use GraalServices return; } else if (packageName.startsWith("com.oracle.svm")) { // SVM must read system properties in: @@ -108,18 +89,19 @@ protected void verify(StructuredGraph graph, CoreProviders context) { } for (MethodCallTargetNode t : graph.getNodes(MethodCallTargetNode.TYPE)) { ResolvedJavaMethod callee = t.targetMethod(); - if (callee.getDeclaringClass().equals(systemType)) { - if (callee.getName().equals("getProperty") || callee.getName().equals("getProperties")) { - throw new VerificationError(t, "call to %s is prohibited. Call Services.getSavedProperty(String) instead.", - callee.format("%H.%n(%p)")); + if (forbiddenCallee(callee, servicesType, systemType)) { + if (caller.getDeclaringClass().equals(graalServicesType)) { + continue; } + throw new VerificationError(t, "call to %s is prohibited. Call GraalServices.%s instead.", + callee.format("%H.%n(%p)"), callee.getName()); } else { for (int i = 0; i < boxTypes.length; i++) { ResolvedJavaType boxType = boxTypes[i]; if (callee.getDeclaringClass().equals(boxType)) { String simpleName = boxType.toJavaName(false); if (callee.getName().equals("get" + simpleName)) { - throw new VerificationError(t, "call to %s is prohibited. Call %s.parse%s(Services.getSavedProperty(String)) instead.", + throw new VerificationError(t, "call to %s is prohibited. Call %s.parse%s(GraalServices.getSavedProperty(String)) instead.", callee.format("%H.%n(%p)"), simpleName, simpleName); } @@ -129,4 +111,12 @@ protected void verify(StructuredGraph graph, CoreProviders context) { } } + private static boolean forbiddenCallee(ResolvedJavaMethod callee, ResolvedJavaType servicesType, ResolvedJavaType systemType) { + if (callee.getDeclaringClass().equals(systemType)) { + return callee.getName().equals("getProperty") || callee.getName().equals("getProperties"); + } else if (callee.getDeclaringClass().equals(servicesType)) { + return callee.getName().equals("getSavedProperty") || callee.getName().equals("getSavedProperties"); + } + return false; + } } diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/tutorial/InvokeGraal.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/tutorial/InvokeGraal.java index 038ac331b898..0a3540f80b08 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/tutorial/InvokeGraal.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/tutorial/InvokeGraal.java @@ -129,7 +129,18 @@ protected InstalledCode compileAndInstallMethod(ResolvedJavaMethod method) { CompilationResultBuilderFactory factory = CompilationResultBuilderFactory.Default; /* Invoke the whole Graal compilation pipeline. */ - GraalCompiler.compileGraph(graph, method, providers, backend, graphBuilderSuite, optimisticOpts, profilingInfo, suites, lirSuites, compilationResult, factory, true); + GraalCompiler.compile(new GraalCompiler.Request<>(graph, + method, + providers, + backend, + graphBuilderSuite, + optimisticOpts, + profilingInfo, + suites, + lirSuites, + compilationResult, + factory, + true)); /* * Install the compilation result into the VM, i.e., copy the byte[] array that contains diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/debug/test/CompilationAlarmTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/debug/test/CompilationAlarmTest.java index f5ca81953128..736d7296eddd 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/debug/test/CompilationAlarmTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/debug/test/CompilationAlarmTest.java @@ -111,7 +111,7 @@ public void run() { CompilationIdentifier compilationId = getOrCreateCompilationId(codeOwner, graph); Request request = new Request<>(graph, codeOwner, getProviders(), getBackend(), getDefaultGraphBuilderSuite(), getOptimisticOptimizations(), graph.getProfilingInfo(), getSuites(thread, workSeconds, withProgressCounterEvents, opt), createLIRSuites(opt), new CompilationResult(compilationId), - CompilationResultBuilderFactory.Default, null, true); + CompilationResultBuilderFactory.Default, null, null, true); try { GraalCompiler.compile(request); if (expectedExceptionText != null) { diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/SubstitutionNodeSourcePositionTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/SubstitutionNodeSourcePositionTest.java index ccf1dba8e3b2..4caeb7ef0da1 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/SubstitutionNodeSourcePositionTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/SubstitutionNodeSourcePositionTest.java @@ -24,7 +24,6 @@ */ package jdk.graal.compiler.replacements.test; -import static jdk.graal.compiler.core.GraalCompiler.compileGraph; import static jdk.graal.compiler.core.common.GraalOptions.TrackNodeSourcePosition; import java.util.List; @@ -32,11 +31,19 @@ import jdk.graal.compiler.api.directives.GraalDirectives; import jdk.graal.compiler.code.CompilationResult; import jdk.graal.compiler.code.SourceMapping; +import jdk.graal.compiler.core.GraalCompiler; +import jdk.graal.compiler.core.target.Backend; import jdk.graal.compiler.graph.NodeSourcePosition; import jdk.graal.compiler.lir.asm.CompilationResultBuilderFactory; +import jdk.graal.compiler.lir.phases.LIRSuites; import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.options.OptionValues; import jdk.graal.compiler.phases.OptimisticOptimizations; +import jdk.graal.compiler.phases.PhaseSuite; +import jdk.graal.compiler.phases.tiers.HighTierContext; +import jdk.graal.compiler.phases.tiers.Suites; +import jdk.graal.compiler.phases.util.Providers; +import jdk.vm.ci.meta.ProfilingInfo; import org.junit.Assert; import org.junit.Test; @@ -122,8 +129,25 @@ private List getSourceMappings(String name) { final ResolvedJavaMethod method = getResolvedJavaMethod(name); final OptionValues options = new OptionValues(getInitialOptions(), TrackNodeSourcePosition, true); final StructuredGraph graph = parseEager(method, StructuredGraph.AllowAssumptions.YES, options); - final CompilationResult cr = compileGraph(graph, graph.method(), getProviders(), getBackend(), getDefaultGraphBuilderSuite(), OptimisticOptimizations.ALL, graph.getProfilingInfo(), - createSuites(graph.getOptions()), createLIRSuites(graph.getOptions()), new CompilationResult(graph.compilationId()), CompilationResultBuilderFactory.Default, true); + Providers providers = getProviders(); + Backend backend = getBackend(); + PhaseSuite graphBuilderSuite = getDefaultGraphBuilderSuite(); + ProfilingInfo profilingInfo = graph.getProfilingInfo(); + Suites suites = createSuites(graph.getOptions()); + LIRSuites lirSuites = createLIRSuites(graph.getOptions()); + CompilationResult compilationResult = new CompilationResult(graph.compilationId()); + final CompilationResult cr = GraalCompiler.compile(new GraalCompiler.Request<>(graph, + graph.method(), + providers, + backend, + graphBuilderSuite, + OptimisticOptimizations.ALL, + profilingInfo, + suites, + lirSuites, + compilationResult, + CompilationResultBuilderFactory.Default, + true)); return cr.getSourceMappings(); } } diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/test/SubprocessUtil.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/test/SubprocessUtil.java index 2f682c7e69d3..9c11b45fa550 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/test/SubprocessUtil.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/test/SubprocessUtil.java @@ -45,17 +45,17 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import jdk.graal.compiler.serviceprovider.GraalServices; import org.junit.Assume; import jdk.graal.compiler.util.CollectionsUtil; -import jdk.vm.ci.services.Services; /** * Utility methods for spawning a VM in a subprocess during unit tests. */ public final class SubprocessUtil { - private static final boolean DEBUG = Boolean.parseBoolean(Services.getSavedProperty("debug." + SubprocessUtil.class.getName())); + private static final boolean DEBUG = Boolean.parseBoolean(GraalServices.getSavedProperty("debug." + SubprocessUtil.class.getName())); private SubprocessUtil() { } diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/LazyClassLoadingTargetNegativeTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/LazyClassLoadingTargetNegativeTest.java deleted file mode 100644 index ea084fe8e4f6..000000000000 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/LazyClassLoadingTargetNegativeTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package jdk.graal.compiler.truffle.test; - -import org.graalvm.polyglot.Engine; -import org.junit.Test; - -import com.oracle.truffle.api.Truffle; -import com.oracle.truffle.api.frame.VirtualFrame; -import com.oracle.truffle.api.nodes.RootNode; - -/** - * Test lazy initialization of Graal in the context of Truffle. When simply executing Truffle code, - * Graal should not be initialized unless there is an actual call targets created. - */ -/* - * This test is used indirectly by jdk.graal.compiler.truffle.test.LazyInitializationTest. - */ -public class LazyClassLoadingTargetNegativeTest { - - @Test - @SuppressWarnings("unused") - public void testInit() { - RootNode root = new RootNode(null) { - @Override - public Object execute(VirtualFrame frame) { - return null; - } - }; - Truffle.getRuntime().createAssumption(); - Truffle.getRuntime().createIndirectCallNode(); - // we can no longer test the creation of a context here as a context now always initializes - // the runtime - Engine.create().close(); - } - -} diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/LazyClassLoadingTargetPositiveTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/LazyClassLoadingTargetPositiveTest.java deleted file mode 100644 index 6f87f344bfd1..000000000000 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/LazyClassLoadingTargetPositiveTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2015, 2019, 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.truffle.test; - -import org.graalvm.polyglot.Context; -import org.junit.Test; - -/** - * Test lazy initialization of Graal in the context of Truffle. When simply executing Truffle code, - * Graal should not be initialized unless there is an actual call targets created. - */ -/* - * This test is used indirectly by jdk.graal.compiler.truffle.test.LazyInitializationTest. - */ -public class LazyClassLoadingTargetPositiveTest { - - @Test - public void testInit() { - Context c = Context.newBuilder().allowExperimentalOptions(true).option("engine.BackgroundCompilation", "false").build(); - c.initialize("sl"); // creates builtin call targets and triggers initialization - c.close(); - } - -} diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/LazyClassLoadingTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/LazyClassLoadingTest.java deleted file mode 100644 index 9c38f2f136e1..000000000000 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/LazyClassLoadingTest.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package jdk.graal.compiler.truffle.test; - -import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.CompileImmediately; -import static jdk.graal.compiler.test.SubprocessUtil.getVMCommandLine; -import static jdk.graal.compiler.test.SubprocessUtil.withoutDebuggerArguments; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import jdk.graal.compiler.core.CompilerThreadFactory; -import jdk.graal.compiler.core.GraalCompilerOptions; -import jdk.graal.compiler.core.common.util.Util; -import jdk.graal.compiler.debug.Assertions; -import jdk.graal.compiler.hotspot.CommunityCompilerConfigurationFactory; -import jdk.graal.compiler.hotspot.CompilerConfigurationFactory; -import jdk.graal.compiler.hotspot.EconomyCompilerConfigurationFactory; -import jdk.graal.compiler.hotspot.HotSpotGraalVMEventListener; -import jdk.graal.compiler.nodes.Cancellable; -import jdk.graal.compiler.options.OptionDescriptor; -import jdk.graal.compiler.options.OptionDescriptors; -import jdk.graal.compiler.options.OptionKey; -import jdk.graal.compiler.options.OptionStability; -import jdk.graal.compiler.options.OptionValues; -import jdk.graal.compiler.options.OptionsParser; -import jdk.graal.compiler.test.SubprocessUtil; -import jdk.graal.compiler.test.SubprocessUtil.Subprocess; -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Test; - -import com.oracle.truffle.api.nodes.RootNode; -import com.oracle.truffle.runtime.OptimizedCallTarget; - -import jdk.vm.ci.runtime.JVMCICompilerFactory; -import jdk.vm.ci.services.JVMCIServiceLocator; - -/** - * Test lazy initialization of Graal in the context of Truffle. When simply executing Truffle code, - * Graal should not be initialized unless there is an actual compilation request. - */ -public class LazyClassLoadingTest extends TestWithPolyglotOptions { - - private final Class hotSpotVMEventListener; - private final Class hotSpotGraalCompilerFactoryOptions; - private final Class hotSpotGraalJVMCIServiceLocatorShared; - private final Class jvmciVersionCheck; - - public LazyClassLoadingTest() { - hotSpotVMEventListener = forNameOrNull("jdk.vm.ci.hotspot.services.HotSpotVMEventListener"); - hotSpotGraalCompilerFactoryOptions = forNameOrNull("jdk.graal.compiler.hotspot.HotSpotGraalCompilerFactory$Options"); - hotSpotGraalJVMCIServiceLocatorShared = forNameOrNull("jdk.graal.compiler.hotspot.HotSpotGraalJVMCIServiceLocator$Shared"); - jvmciVersionCheck = forNameOrNull("jdk.graal.compiler.hotspot.JVMCIVersionCheck"); - } - - private static Class forNameOrNull(String name) { - try { - return Class.forName(name); - } catch (ClassNotFoundException e) { - return null; - } - } - - @Test - public void testClassLoading() throws IOException, InterruptedException { - setupContext(); - OptimizedCallTarget target = (OptimizedCallTarget) RootNode.createConstantNode(0).getCallTarget(); - Assume.assumeFalse(target.getOptionValue(CompileImmediately)); - List vmCommandLine = getVMCommandLine(); - Assume.assumeFalse("Explicitly enables JVMCI compiler", vmCommandLine.contains("-XX:+UseJVMCINativeLibrary") || vmCommandLine.contains("-XX:+UseJVMCICompiler")); - runTest(LazyClassLoadingTargetNegativeTest.class, false); - runTest(LazyClassLoadingTargetPositiveTest.class, true); - } - - private void runTest(Class testClass, boolean expectGraalClassesLoaded) throws IOException, InterruptedException, AssertionError { - List vmCommandLine = getVMCommandLine(); - List vmArgs = withoutDebuggerArguments(vmCommandLine); - vmArgs.add("-Xlog:class+init=info"); - vmArgs.add(SubprocessUtil.PACKAGE_OPENING_OPTIONS); - vmArgs.add("-dsa"); - vmArgs.add("-da"); - vmArgs.add("-Dpolyglot.engine.AssertProbes=false"); - vmArgs.add("-Dpolyglot.engine.AllowExperimentalOptions=true"); - vmArgs.add("-XX:-UseJVMCICompiler"); - - // Remove -Djdk.graal.CompilationFailureAction as it drags in CompilationWrapper - vmArgs = vmArgs.stream().filter(e -> !e.contains(GraalCompilerOptions.CompilationFailureAction.getName())).collect(Collectors.toList()); - - Subprocess proc = SubprocessUtil.java(vmArgs, "com.oracle.mxtool.junit.MxJUnitWrapper", testClass.getName()); - int exitCode = proc.exitCode; - if (exitCode != 0) { - Assert.fail(String.format("non-zero exit code %d for command:%n%s", exitCode, proc)); - } - - ArrayList loadedGraalClassNames = new ArrayList<>(); - int testCount = 0; - for (String line : proc.output) { - String loadedClass = extractClass(line); - if (loadedClass != null) { - if (isGraalClass(loadedClass)) { - loadedGraalClassNames.add(loadedClass); - } - } else if (line.startsWith("OK (")) { - Assert.assertTrue(testCount == 0); - int start = "OK (".length(); - int end = line.indexOf(' ', start); - testCount = Integer.parseInt(line.substring(start, end)); - } - } - if (testCount == 0) { - Assert.fail(String.format("no tests found in output of command:%n%s", testCount, proc)); - } - - try { - List graalClasses = filterGraalCompilerClasses(loadedGraalClassNames); - if (expectGraalClassesLoaded) { - if (graalClasses.isEmpty()) { - Assert.fail(String.format("Failure for command:%n%s", proc) + ". Expected Graal classes loaded but weren't."); - } - } else { - if (!graalClasses.isEmpty()) { - Assert.fail(String.format("Failure for command:%n%s", proc) + ". Loaded forbidden classes:\n " + graalClasses.stream().collect(Collectors.joining("\n ")) + "\n"); - } - } - } catch (AssertionError e) { - throw new AssertionError(String.format("Failure for command:%n%s", proc), e); - } - } - - private static final Pattern CLASS_INIT_LOG_PATTERN = Pattern.compile("\\[info\\]\\[class,init\\] \\d+ Initializing '([^']+)'"); - - /** - * Extracts the class name from a line of log output. - */ - private static String extractClass(String line) { - Matcher matcher = CLASS_INIT_LOG_PATTERN.matcher(line); - if (matcher.find()) { - return matcher.group(1).replace('/', '.'); - } - return null; - } - - private static boolean isGraalClass(String className) { - if (className.startsWith("jdk.graal.compiler.truffle.") || className.startsWith("jdk.graal.compiler.serviceprovider.")) { - // Ignore classes in the com.oracle.graal.truffle package, they are all allowed. - // Also ignore classes in the Graal service provider package, as they might not be - // lazily loaded. - return false; - } else { - return className.startsWith("jdk.graal.compiler."); - } - } - - private List filterGraalCompilerClasses(List loadedGraalClassNames) { - HashSet> allowList = new HashSet<>(); - List> loadedGraalClasses = new ArrayList<>(); - - for (String name : loadedGraalClassNames) { - try { - loadedGraalClasses.add(Class.forName(name)); - } catch (ClassNotFoundException e) { - if (e.getMessage().contains("$$Lambda")) { - // lambdas may not be found. - continue; - } - Assert.fail("loaded class " + name + " not found"); - } - } - /* - * Look for all loaded OptionDescriptors classes, and allow the classes that declare the - * options. They may be loaded by the option parsing code. - */ - for (Class cls : loadedGraalClasses) { - if (OptionDescriptors.class.isAssignableFrom(cls)) { - try { - OptionDescriptors optionDescriptors = cls.asSubclass(OptionDescriptors.class).getDeclaredConstructor().newInstance(); - for (OptionDescriptor option : optionDescriptors) { - allowList.add(option.getDeclaringClass()); - allowList.add(option.getOptionValueType()); - allowList.add(option.getOptionType().getDeclaringClass()); - } - } catch (ReflectiveOperationException e) { - } - } - } - - // classes needed to find out whether enterprise is installed in the JDK - allowList.add(OptionStability.class); - allowList.add(CompilerConfigurationFactory.class); - allowList.add(CompilerConfigurationFactory.Options.class); - allowList.add(CompilerConfigurationFactory.ShowConfigurationLevel.class); - allowList.add(EconomyCompilerConfigurationFactory.class); - allowList.add(CommunityCompilerConfigurationFactory.class); - - allowList.add(Cancellable.class); - - List forbiddenClasses = new ArrayList<>(); - for (Class cls : loadedGraalClasses) { - if (allowList.contains(cls)) { - continue; - } - - if (!isGraalClassAllowed(cls)) { - forbiddenClasses.add(cls.getName()); - } - } - return forbiddenClasses; - } - - private boolean isGraalClassAllowed(Class cls) { - if (CompilerThreadFactory.class.equals(cls)) { - // The HotSpotTruffleRuntime creates a CompilerThreadFactory for Truffle. - return true; - } - - if (cls.equals(hotSpotGraalCompilerFactoryOptions)) { - // Graal initialization needs to access this class. - return true; - } - - if (cls.equals(Util.class)) { - // Provider of the Java runtime check utility used during Graal initialization. - return true; - } - - if (cls.equals(jvmciVersionCheck) || Objects.equals(cls.getEnclosingClass(), jvmciVersionCheck)) { - // The Graal initialization needs to check the JVMCI version. - return true; - } - - if (JVMCICompilerFactory.class.isAssignableFrom(cls) || cls.getName().startsWith("jdk.graal.compiler.hotspot.IsGraalPredicate")) { - // The compiler factories have to be loaded and instantiated by the JVMCI. - return true; - } - - if (JVMCIServiceLocator.class.isAssignableFrom(cls) || cls == hotSpotGraalJVMCIServiceLocatorShared || HotSpotGraalVMEventListener.class.isAssignableFrom(cls)) { - return true; - } - - if (OptionDescriptors.class.isAssignableFrom(cls) || OptionDescriptor.class.isAssignableFrom(cls)) { - // If options are specified, the corresponding *_OptionDescriptors classes are loaded. - return true; - } - - if (cls == Assertions.class || cls == OptionsParser.class || cls == OptionValues.class || - cls.getName().equals("jdk.graal.compiler.hotspot.HotSpotGraalOptionValues")) { - // Classes implementing Graal option loading - return true; - } - - if (OptionKey.class.isAssignableFrom(cls)) { - // If options are specified, that may implicitly load a custom OptionKey subclass. - return true; - } - - if (cls.getName().equals("jdk.graal.compiler.hotspot.HotSpotGraalMBean")) { - // MBean is OK and fast - return true; - } - - if (hotSpotVMEventListener != null && hotSpotVMEventListener.isAssignableFrom(cls)) { - // HotSpotVMEventListeners need to be loaded on JVMCI startup. - return true; - } - - // No other class from the jdk.graal.compiler. package should be loaded. - return false; - } -} diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/util/test/ObjectCopierTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/util/test/ObjectCopierTest.java new file mode 100644 index 000000000000..9c1565788301 --- /dev/null +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/util/test/ObjectCopierTest.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.graal.compiler.util.test; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import jdk.graal.compiler.core.test.SubprocessTest; +import org.graalvm.collections.EconomicMap; +import org.junit.Assert; +import org.junit.Test; + +import jdk.graal.compiler.util.ObjectCopier; + +/** + * Tests for {@link ObjectCopier}. + * + * Run as follows to see the encoded form of objects: + * + *

+ *     mx unittest --verbose -Ddebug.jdk.graal.compiler.util.test.ObjectCopierTest=true ObjectCopierTest
+ * 
+ */ +public class ObjectCopierTest extends SubprocessTest { + + @SuppressWarnings("unused") + static class BaseClass { + String baseString1 = "base1"; + int baseInt1 = 34; + double baseDouble1 = 8765.4321D; + + static final Object BASE_SINGLETON = new Object() { + @Override + public String toString() { + return "BASE_SINGLETON"; + } + }; + } + + @SuppressWarnings("unused") + static class TestObject extends BaseClass { + boolean bTrue = true; + boolean bFalse = false; + byte byteField1 = 87; + byte byteField2 = Byte.MAX_VALUE; + byte byteField3 = Byte.MIN_VALUE; + short shortField1 = -456; + short shortField2 = Short.MIN_VALUE; + short shortField3 = Short.MAX_VALUE; + char charField1 = 'A'; + char charField2 = '\u4433'; + char charField3 = '\r'; + char charField4 = '\n'; + char charField5 = Character.MAX_VALUE; + char charField6 = Character.MIN_VALUE; + int intField1 = 0; + int intField2 = 42; + int intField3 = Integer.MIN_VALUE; + int intField4 = Integer.MAX_VALUE; + int intField5 = -1; + float floatField1 = -1.4F; + float floatField2 = Float.MIN_NORMAL; + float floatField3 = Float.MIN_VALUE; + float floatField4 = Float.MAX_VALUE; + float floatField5 = Float.NaN; + float floatField6 = Float.NEGATIVE_INFINITY; + float floatField7 = Float.POSITIVE_INFINITY; + double doubleField1 = -1.45678D; + double doubleField2 = Double.MIN_NORMAL; + double doubleField3 = Double.MIN_VALUE; + double doubleField4 = Double.MAX_VALUE; + double doubleField5 = Double.NaN; + double doubleField6 = Double.NEGATIVE_INFINITY; + double doubleField7 = Double.POSITIVE_INFINITY; + + boolean[] zArray = {true, false, true}; + byte[] bArray = "12340987 something random!@#".getBytes(); + short[] sArray = {123, 456, 789}; + char[] cArray = "&^%^&%__blah blah".toCharArray(); + int[] iArray = {1, 2, 3, 4}; + float[] fArray = {123.567F, 876.23F, -67.999999F}; + double[] dArray = {123444.567D, 8722226.23D, -6708987.999999D}; + Object[] oArray = {this, TEST_OBJECT_SINGLETON, BASE_SINGLETON, "last", new String[]{"in", "a", "nested", "array"}}; + + static final Object TEST_OBJECT_SINGLETON = new Object() { + @Override + public String toString() { + return "TEST_OBJECT_SINGLETON"; + } + }; + + private static List fieldValues(Object obj) { + List values = new ArrayList<>(); + Class c = obj.getClass(); + while (c != Object.class) { + for (Field f : c.getDeclaredFields()) { + try { + values.add(f.get(obj)); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + c = c.getSuperclass(); + } + return values; + } + + @Override + public String toString() { + return fieldValues(this).stream().map(String::valueOf).collect(Collectors.joining(", ")); + } + } + + private static final String DEBUG_PROP = "debug." + ObjectCopierTest.class.getName(); + private static final boolean DEBUG = Boolean.getBoolean(DEBUG_PROP); + + @SuppressWarnings("unchecked") + public void testIt() { + ClassLoader loader = getClass().getClassLoader(); + TestObject testObject = new TestObject(); + + List timeUnits = List.of(TimeUnit.MICROSECONDS, TimeUnit.DAYS, TimeUnit.SECONDS); + EconomicMap emap = EconomicMap.create(); + emap.put(42, Map.of("1", 1, "2", 2)); + emap.put(-12345, testObject); + + Map hmap = new HashMap<>(Map.of("1000", "one thousand")); + Map idMap = new IdentityHashMap<>(Map.of(new Object(), "some obj")); + + Map root = new LinkedHashMap<>(); + root.put("one", "normal string"); + root.put("two", "string with\nembedded\rnewline characters\r\n"); + root.put("3", ObjectCopierTest.class); + root.put("singleton1", BaseClass.BASE_SINGLETON); + root.put("vier", emap); + root.put("hmap", hmap); + root.put("idMap", idMap); + root.put("null", null); + root.put("singleton1_2", BaseClass.BASE_SINGLETON); + root.put("5", timeUnits); + root.put("6", new ArrayList<>(timeUnits)); + root.put("singleton2", TestObject.TEST_OBJECT_SINGLETON); + root.put("singleton2_2", TestObject.TEST_OBJECT_SINGLETON); + + List externalValues = List.of(ObjectCopier.getField(BaseClass.class, "BASE_SINGLETON"), + ObjectCopier.getField(TestObject.class, "TEST_OBJECT_SINGLETON")); + + String encoded = ObjectCopier.encode(root, externalValues); + if (DEBUG) { + System.out.printf("encoded:%n%s%n", encoded); + } + Object decoded = ObjectCopier.decode(encoded, loader); + if (DEBUG) { + System.out.printf("root:%n%s%n", root); + System.out.printf("decoded:%n%s%n", decoded); + } + String reencoded = ObjectCopier.encode(decoded, externalValues); + if (DEBUG) { + System.out.printf("reencoded:%n%s%n", reencoded); + } + Assert.assertEquals(encoded, reencoded); + + Map root2 = (Map) ObjectCopier.decode(reencoded, loader); + + Assert.assertSame(root.get("singleton1"), root2.get("singleton1")); + Assert.assertSame(root.get("singleton1_2"), root2.get("singleton1_2")); + Assert.assertSame(root2.get("singleton1"), root2.get("singleton1_2")); + + Assert.assertSame(root.get("singleton2"), root2.get("singleton2")); + Assert.assertSame(root.get("singleton2_2"), root2.get("singleton2_2")); + Assert.assertSame(root2.get("singleton2"), root2.get("singleton2_2")); + } + + @Test + public void test() throws IOException, InterruptedException { + launchSubprocess(this::testIt, + "-D" + DEBUG_PROP + "=" + DEBUG, + "--add-opens=java.base/java.util=jdk.graal.compiler", + "--add-opens=java.base/java.lang=jdk.graal.compiler"); + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/code/HexCodeFileDisassemblerProvider.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/code/HexCodeFileDisassemblerProvider.java index e1f1225fecf9..a44dce10299c 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/code/HexCodeFileDisassemblerProvider.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/code/HexCodeFileDisassemblerProvider.java @@ -31,6 +31,7 @@ import java.util.Locale; import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.graal.compiler.serviceprovider.ServiceProvider; import jdk.vm.ci.code.CodeCacheProvider; import jdk.vm.ci.code.CodeUtil; @@ -44,7 +45,6 @@ import jdk.vm.ci.code.site.DataPatch; import jdk.vm.ci.code.site.ExceptionHandler; import jdk.vm.ci.code.site.Infopoint; -import jdk.vm.ci.services.Services; /** * {@link HexCodeFile} based implementation of {@link DisassemblerProvider}. @@ -149,11 +149,11 @@ static class HexCodeFileDisTool { // current platform). if (toolMethod != null) { byte[] code = {}; - String arch = Services.getSavedProperty("os.arch"); + String arch = GraalServices.getSavedProperty("os.arch"); if (arch.equals("x86_64")) { arch = "amd64"; } - int wordWidth = arch.endsWith("64") ? 64 : Integer.parseInt(Services.getSavedProperty("sun.arch.data.model", "64")); + int wordWidth = arch.endsWith("64") ? 64 : Integer.parseInt(GraalServices.getSavedProperty("sun.arch.data.model", "64")); String hcf = new HexCodeFile(code, 0L, arch.toLowerCase(Locale.ROOT), wordWidth).toEmbeddedString(); try { toolMethod.invokeExact(hcf); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/code/ObjdumpDisassemblerProvider.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/code/ObjdumpDisassemblerProvider.java index 37cbdb035670..d6cc6d4ed72a 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/code/ObjdumpDisassemblerProvider.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/code/ObjdumpDisassemblerProvider.java @@ -40,6 +40,7 @@ import jdk.graal.compiler.options.OptionKey; import jdk.graal.compiler.options.OptionType; import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.graal.compiler.serviceprovider.ServiceProvider; import jdk.graal.compiler.util.CollectionsUtil; @@ -54,7 +55,6 @@ import jdk.vm.ci.code.site.Call; import jdk.vm.ci.code.site.DataPatch; import jdk.vm.ci.code.site.Infopoint; -import jdk.vm.ci.services.Services; /** * A provider that uses the {@code GNU objdump} utility to disassemble code. @@ -102,7 +102,7 @@ public String disassembleCompiledCode(OptionValues options, CodeCacheProvider co } String[] cmdline; - String arch = Services.getSavedProperty("os.arch"); + String arch = GraalServices.getSavedProperty("os.arch"); if (arch.equals("amd64") || arch.equals("x86_64")) { cmdline = new String[]{objdump, "-D", "-b", "binary", "-M", "x86-64", "-m", "i386", tmp.getAbsolutePath()}; } else if (arch.equals("aarch64")) { diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalRuntimeOptionKey.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/ArchitectureSpecific.java similarity index 69% rename from substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalRuntimeOptionKey.java rename to compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/ArchitectureSpecific.java index dd8536e553b3..f598858e25b5 100644 --- a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalRuntimeOptionKey.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/ArchitectureSpecific.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,16 +22,15 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.graal.hotspot.libgraal; -import com.oracle.svm.core.option.RuntimeOptionKey; +package jdk.graal.compiler.core; -/** - * Libgraal-specific subclass so that we can distinguish between Native Image and libgraal runtime - * options. - */ -public class LibGraalRuntimeOptionKey extends RuntimeOptionKey { - LibGraalRuntimeOptionKey(T defaultValue, RuntimeOptionKeyFlag... flags) { - super(defaultValue, flags); - } +import jdk.vm.ci.code.Architecture; + +public interface ArchitectureSpecific { + /** + * Gets the {@linkplain Architecture#getName() name} of the architecture this Graal component is + * associated with. + */ + String getArchitecture(); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/CompilationWatchDog.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/CompilationWatchDog.java index 88a94fc826cb..077366ddcd45 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/CompilationWatchDog.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/CompilationWatchDog.java @@ -229,7 +229,7 @@ private boolean recordStackTrace(StackTraceElement[] newStackTrace) { return false; } - private final boolean debug = Boolean.parseBoolean(Services.getSavedProperty("debug.graal.CompilationWatchDog")); + private final boolean debug = Boolean.parseBoolean(GraalServices.getSavedProperty("debug.graal.CompilationWatchDog")); /** * Handle to scheduled task. diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalCompiler.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalCompiler.java index c6777576569c..3a9f68039fd2 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalCompiler.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalCompiler.java @@ -41,6 +41,7 @@ import jdk.graal.compiler.lir.asm.EntryPointDecorator; import jdk.graal.compiler.lir.phases.LIRSuites; import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.options.OptionValues; import jdk.graal.compiler.phases.OptimisticOptimizations; import jdk.graal.compiler.phases.PhaseSuite; import jdk.graal.compiler.phases.common.DeadCodeEliminationPhase; @@ -64,52 +65,61 @@ public class GraalCompiler { /** * Encapsulates all the inputs to a {@linkplain GraalCompiler#compile(Request) compilation}. + * + * @param graph the graph to be compiled + * @param installedCodeOwner the method the compiled code will be associated with once + * installed. This argument can be null. + * @param providers + * @param backend + * @param graphBuilderSuite + * @param optimisticOpts + * @param profilingInfo + * @param suites + * @param lirSuites + * @param compilationResult + * @param factory */ - public static class Request { - public final StructuredGraph graph; - public final ResolvedJavaMethod installedCodeOwner; - public final Providers providers; - public final Backend backend; - public final PhaseSuite graphBuilderSuite; - public final OptimisticOptimizations optimisticOpts; - public final ProfilingInfo profilingInfo; - public final Suites suites; - public final LIRSuites lirSuites; - public final T compilationResult; - public final CompilationResultBuilderFactory factory; - public final EntryPointDecorator entryPointDecorator; - public final boolean verifySourcePositions; + public record Request(StructuredGraph graph, + ResolvedJavaMethod installedCodeOwner, + Providers providers, + Backend backend, + PhaseSuite graphBuilderSuite, + OptimisticOptimizations optimisticOpts, + ProfilingInfo profilingInfo, + Suites suites, + LIRSuites lirSuites, + T compilationResult, + CompilationResultBuilderFactory factory, + EntryPointDecorator entryPointDecorator, + RequestedCrashHandler requestedCrashHandler, + boolean verifySourcePositions) { - /** - * @param graph the graph to be compiled - * @param installedCodeOwner the method the compiled code will be associated with once - * installed. This argument can be null. - * @param providers - * @param backend - * @param graphBuilderSuite - * @param optimisticOpts - * @param profilingInfo - * @param suites - * @param lirSuites - * @param compilationResult - * @param factory - */ - public Request(StructuredGraph graph, ResolvedJavaMethod installedCodeOwner, Providers providers, Backend backend, PhaseSuite graphBuilderSuite, - OptimisticOptimizations optimisticOpts, ProfilingInfo profilingInfo, Suites suites, LIRSuites lirSuites, T compilationResult, CompilationResultBuilderFactory factory, - EntryPointDecorator entryPointDecorator, boolean verifySourcePositions) { - this.graph = graph; - this.installedCodeOwner = installedCodeOwner; - this.providers = providers; - this.backend = backend; - this.graphBuilderSuite = graphBuilderSuite; - this.optimisticOpts = optimisticOpts; - this.profilingInfo = profilingInfo; - this.suites = suites; - this.lirSuites = lirSuites; - this.compilationResult = compilationResult; - this.factory = factory; - this.entryPointDecorator = entryPointDecorator; - this.verifySourcePositions = verifySourcePositions; + public Request(StructuredGraph graph, + ResolvedJavaMethod installedCodeOwner, + Providers providers, + Backend backend, + PhaseSuite graphBuilderSuite, + OptimisticOptimizations optimisticOpts, + ProfilingInfo profilingInfo, + Suites suites, + LIRSuites lirSuites, + T compilationResult, + CompilationResultBuilderFactory factory, + boolean verifySourcePositions) { + this(graph, + installedCodeOwner, + providers, + backend, + graphBuilderSuite, + optimisticOpts, + profilingInfo, + suites, + lirSuites, + compilationResult, + factory, + null, + null, + verifySourcePositions); } /** @@ -122,36 +132,6 @@ public T execute() { } } - /** - * Requests compilation of a given graph. - * - * @param graph the graph to be compiled - * @param installedCodeOwner the method the compiled code will be associated with once - * installed. This argument can be null. - * @return the result of the compilation - */ - public static T compileGraph(StructuredGraph graph, ResolvedJavaMethod installedCodeOwner, Providers providers, Backend backend, - PhaseSuite graphBuilderSuite, OptimisticOptimizations optimisticOpts, ProfilingInfo profilingInfo, Suites suites, LIRSuites lirSuites, T compilationResult, - CompilationResultBuilderFactory factory, boolean verifySourcePositions) { - return compile(new Request<>(graph, installedCodeOwner, providers, backend, graphBuilderSuite, optimisticOpts, profilingInfo, suites, lirSuites, compilationResult, factory, null, - verifySourcePositions)); - } - - /** - * Requests compilation of a given graph. - * - * @param graph the graph to be compiled - * @param installedCodeOwner the method the compiled code will be associated with once - * installed. This argument can be null. - * @return the result of the compilation - */ - public static T compileGraph(StructuredGraph graph, ResolvedJavaMethod installedCodeOwner, Providers providers, Backend backend, - PhaseSuite graphBuilderSuite, OptimisticOptimizations optimisticOpts, ProfilingInfo profilingInfo, Suites suites, LIRSuites lirSuites, T compilationResult, - CompilationResultBuilderFactory factory, EntryPointDecorator entryPointDecorator, boolean verifySourcePositions) { - return compile(new Request<>(graph, installedCodeOwner, providers, backend, graphBuilderSuite, optimisticOpts, profilingInfo, suites, lirSuites, compilationResult, factory, - entryPointDecorator, verifySourcePositions)); - } - /** * Services a given compilation request. * @@ -170,7 +150,7 @@ public static T compile(Request r) { if (r.verifySourcePositions) { assert r.graph.verifySourcePositions(true); } - checkForRequestedCrash(r.graph); + checkForRequestedCrash(r.graph, r.requestedCrashHandler()); } catch (Throwable e) { throw debug.handle(e); } @@ -179,15 +159,27 @@ public static T compile(Request r) { } } + /** + * Support for extra processing of a crash triggered by {@link GraalCompilerOptions#CrashAt}. + */ + public interface RequestedCrashHandler { + /** + * @return true if the caller should proceed to throw an exception + */ + @SuppressWarnings("unused") + boolean notifyCrash(OptionValues options, String crashMessage); + } + /** * Checks whether the {@link GraalCompilerOptions#CrashAt} option indicates that the compilation * of {@code graph} should result in an exception. * * @param graph a graph currently being compiled + * @param requestedCrashHandler * @throws RuntimeException if the value of {@link GraalCompilerOptions#CrashAt} matches * {@code graph.method()} or {@code graph.name} */ - private static void checkForRequestedCrash(StructuredGraph graph) { + private static void checkForRequestedCrash(StructuredGraph graph, RequestedCrashHandler requestedCrashHandler) { String value = GraalCompilerOptions.CrashAt.getValue(graph.getOptions()); if (value != null) { boolean bailout = false; @@ -203,7 +195,7 @@ private static void checkForRequestedCrash(StructuredGraph graph) { String matchedLabel = match(graph, methodPattern); if (matchedLabel != null) { String crashMessage = "Forced crash after compiling " + matchedLabel; - if (notifyCrash(crashMessage)) { + if (requestedCrashHandler == null || requestedCrashHandler.notifyCrash(graph.getOptions(), crashMessage)) { if (permanentBailout) { throw new PermanentBailoutException(crashMessage); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalServiceThread.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalServiceThread.java index 29d3afaefeff..094fb5e0ade2 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalServiceThread.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalServiceThread.java @@ -24,7 +24,7 @@ */ package jdk.graal.compiler.core; -import jdk.vm.ci.services.Services; +import jdk.graal.compiler.serviceprovider.GraalServices; /** * This is a utility class for Threads started by the compiler itself. In certain execution @@ -65,7 +65,7 @@ public final void run() { * @param error the error */ protected void onAttachError(InternalError error) { - if (Boolean.parseBoolean(Services.getSavedProperty("GraalServiceThread.verbose", "false"))) { + if (Boolean.parseBoolean(GraalServices.getSavedProperty("GraalServiceThread.verbose", "false"))) { error.printStackTrace(); } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/common/util/CompilationAlarm.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/common/util/CompilationAlarm.java index 1f5006207bc1..5d1bc5e0dc99 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/common/util/CompilationAlarm.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/common/util/CompilationAlarm.java @@ -35,6 +35,7 @@ import jdk.graal.compiler.options.OptionKey; import jdk.graal.compiler.options.OptionType; import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.vm.ci.services.Services; /** @@ -60,7 +61,7 @@ public static class Options { } public static final boolean LOG_PROGRESS_DETECTION = !Services.IS_IN_NATIVE_IMAGE && - Boolean.parseBoolean(Services.getSavedProperty("debug." + CompilationAlarm.class.getName() + ".logProgressDetection")); + Boolean.parseBoolean(GraalServices.getSavedProperty("debug." + CompilationAlarm.class.getName() + ".logProgressDetection")); private CompilationAlarm(double period) { this.period = period; diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/match/MatchStatementSet.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/match/MatchStatementSet.java index ccfab68e5c54..156189f959bb 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/match/MatchStatementSet.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/match/MatchStatementSet.java @@ -26,10 +26,11 @@ import java.util.List; +import jdk.graal.compiler.core.ArchitectureSpecific; import jdk.graal.compiler.core.gen.NodeLIRBuilder; import jdk.graal.compiler.core.gen.NodeMatchRules; -public interface MatchStatementSet { +public interface MatchStatementSet extends ArchitectureSpecific { /** * @return the {@link NodeLIRBuilder} subclass which defined this set of {@link MatchStatement} * instances. diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/IgvDumpChannel.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/IgvDumpChannel.java index 4f318cf789b9..443cd625acaa 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/IgvDumpChannel.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/IgvDumpChannel.java @@ -42,6 +42,7 @@ import jdk.graal.compiler.debug.DebugOptions.PrintGraphTarget; import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.vm.ci.common.NativeImageReinitialize; import jdk.vm.ci.services.Services; @@ -57,7 +58,7 @@ final class IgvDumpChannel implements WritableByteChannel { * To enable IGV dumping to the network during libgraal based development, set the * {@value #ENABLE_NETWORK_DUMPING_PROP} system property to true when building libgraal. */ - private static final boolean ENABLE_NETWORK_DUMPING = Boolean.parseBoolean(Services.getSavedProperty(ENABLE_NETWORK_DUMPING_PROP)); + private static final boolean ENABLE_NETWORK_DUMPING = Boolean.parseBoolean(GraalServices.getSavedProperty(ENABLE_NETWORK_DUMPING_PROP)); private final Supplier pathProvider; private final OptionValues options; diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/Versions.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/Versions.java index cb8037cc051e..cf24c7beaafe 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/Versions.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/Versions.java @@ -32,13 +32,13 @@ import java.util.HashMap; import java.util.Map; -import jdk.vm.ci.services.Services; +import jdk.graal.compiler.serviceprovider.GraalServices; /** Avoid using directly. Only public for the needs of unit testing. */ public final class Versions { static final Versions VERSIONS; static { - String home = Services.getSavedProperty("java.home"); + String home = GraalServices.getSavedProperty("java.home"); VERSIONS = new Versions(home); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graph/Node.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graph/Node.java index 469e93eada84..ca9e12dc62d4 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graph/Node.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graph/Node.java @@ -40,6 +40,7 @@ import java.util.function.Predicate; import java.util.function.Supplier; +import jdk.graal.compiler.serviceprovider.GraalServices; import org.graalvm.collections.EconomicSet; import jdk.graal.compiler.core.common.Fields; @@ -62,7 +63,6 @@ import jdk.graal.compiler.nodes.spi.Simplifiable; import jdk.graal.compiler.options.OptionValues; import jdk.internal.misc.Unsafe; -import jdk.vm.ci.services.Services; /** * This class is the base class for all nodes. It represents a node that can be inserted in a @@ -97,7 +97,7 @@ public abstract class Node implements Cloneable, Formattable { public static final NodeClass TYPE = null; - public static final boolean TRACK_CREATION_POSITION = Boolean.parseBoolean(Services.getSavedProperty("debug.graal.TrackNodeCreationPosition")); + public static final boolean TRACK_CREATION_POSITION = Boolean.parseBoolean(GraalServices.getSavedProperty("debug.graal.TrackNodeCreationPosition")); static final int DELETED_ID_START = -1000000000; static final int INITIAL_ID = -1; diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graph/NodeSourcePosition.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graph/NodeSourcePosition.java index 726eb84569ea..f75120d1d91a 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graph/NodeSourcePosition.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graph/NodeSourcePosition.java @@ -35,18 +35,18 @@ import jdk.graal.compiler.bytecode.Bytecodes; import jdk.graal.compiler.debug.Assertions; +import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.vm.ci.code.BytecodeFrame; import jdk.vm.ci.code.BytecodePosition; import jdk.vm.ci.code.CodeUtil; import jdk.vm.ci.meta.JavaMethod; import jdk.vm.ci.meta.MetaUtil; import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.services.Services; public class NodeSourcePosition extends BytecodePosition implements Iterable { - private static final boolean STRICT_SOURCE_POSITION = Boolean.parseBoolean(Services.getSavedProperty("debug.graal.SourcePositionStrictChecks")); - private static final boolean SOURCE_POSITION_BYTECODES = Boolean.parseBoolean(Services.getSavedProperty("debug.graal.SourcePositionDisassemble")); + private static final boolean STRICT_SOURCE_POSITION = Boolean.parseBoolean(GraalServices.getSavedProperty("debug.graal.SourcePositionStrictChecks")); + private static final boolean SOURCE_POSITION_BYTECODES = Boolean.parseBoolean(GraalServices.getSavedProperty("debug.graal.SourcePositionDisassemble")); private final int hashCode; private final Marker marker; diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/BootstrapWatchDog.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/BootstrapWatchDog.java index 1f91bca98dfe..af396d731f6c 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/BootstrapWatchDog.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/BootstrapWatchDog.java @@ -37,8 +37,8 @@ import jdk.graal.compiler.options.OptionType; import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.vm.ci.code.CompilationRequest; -import jdk.vm.ci.services.Services; /** * A watch dog that monitors the duration and compilation rate during a @@ -108,7 +108,7 @@ private BootstrapWatchDog(HotSpotGraalRuntimeProvider graalRuntime, int timeout, /** * Set to true to debug the watch dog. */ - private static final boolean DEBUG = Boolean.parseBoolean(Services.getSavedProperty("debug.graal.BootstrapWatchDog")); + private static final boolean DEBUG = Boolean.parseBoolean(GraalServices.getSavedProperty("debug.graal.BootstrapWatchDog")); /** * Seconds to delay before starting to measure the compilation rate. diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/CompilationStatistics.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/CompilationStatistics.java index 92861aa0392b..9cf7dd5f61f3 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/CompilationStatistics.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/CompilationStatistics.java @@ -48,7 +48,6 @@ import jdk.vm.ci.hotspot.HotSpotInstalledCode; import jdk.vm.ci.hotspot.HotSpotResolvedJavaMethod; -import jdk.vm.ci.services.Services; @SuppressWarnings("unused") public final class CompilationStatistics { @@ -190,7 +189,7 @@ public static void clear(String dumpName) { timeLeft = RESOLUTION; } } - String timelineName = Services.getSavedProperty("stats.timeline.name"); + String timelineName = GraalServices.getSavedProperty("stats.timeline.name"); if (timelineName != null && !timelineName.isEmpty()) { out.printf("%s%c", CSVUtil.Escape.escape(timelineName), CSVUtil.SEPARATOR); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/CompilerConfigurationFactory.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/CompilerConfigurationFactory.java index 783c63c7e80c..217ec9f98595 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/CompilerConfigurationFactory.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/CompilerConfigurationFactory.java @@ -135,14 +135,14 @@ public interface BackendMap { public static class DefaultBackendMap implements BackendMap { - private final EconomicMap, HotSpotBackendFactory> backends = EconomicMap.create(); + private final EconomicMap backends = EconomicMap.create(); @SuppressWarnings("try") public DefaultBackendMap(String backendName) { try (InitTimer t = timer("HotSpotBackendFactory.register")) { for (HotSpotBackendFactory backend : GraalServices.load(HotSpotBackendFactory.class)) { if (backend.getName().equals(backendName)) { - Class arch = backend.getArchitecture(); + String arch = backend.getArchitecture(); if (arch != null) { HotSpotBackendFactory oldEntry = backends.put(arch, backend); assert oldEntry == null || oldEntry == backend : "duplicate Graal backend"; @@ -154,7 +154,7 @@ public DefaultBackendMap(String backendName) { @Override public final HotSpotBackendFactory getBackendFactory(Architecture arch) { - return backends.get(arch.getClass()); + return backends.get(arch.getName()); } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/GraalHotSpotVMConfigAccess.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/GraalHotSpotVMConfigAccess.java index d3ffa824f76f..acf78f09c6a9 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/GraalHotSpotVMConfigAccess.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/GraalHotSpotVMConfigAccess.java @@ -24,7 +24,7 @@ */ package jdk.graal.compiler.hotspot; -import static jdk.vm.ci.services.Services.getSavedProperty; +import static jdk.graal.compiler.serviceprovider.GraalServices.getSavedProperty; import java.util.ArrayList; import java.util.Arrays; diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotBackendFactory.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotBackendFactory.java index 7dc8062944db..64b5caf88f06 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotBackendFactory.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotBackendFactory.java @@ -29,6 +29,7 @@ import static jdk.vm.ci.services.Services.IS_IN_NATIVE_IMAGE; import jdk.graal.compiler.bytecode.BytecodeProvider; +import jdk.graal.compiler.core.ArchitectureSpecific; import jdk.graal.compiler.core.common.spi.ConstantFieldProvider; import jdk.graal.compiler.debug.Assertions; import jdk.graal.compiler.debug.DebugContext; @@ -67,7 +68,6 @@ import jdk.graal.compiler.options.OptionValues; import jdk.graal.compiler.phases.tiers.CompilerConfiguration; import jdk.graal.compiler.replacements.classfile.ClassfileBytecodeProvider; -import jdk.vm.ci.code.Architecture; import jdk.vm.ci.code.RegisterConfig; import jdk.vm.ci.code.TargetDescription; import jdk.vm.ci.common.InitTimer; @@ -80,7 +80,7 @@ import jdk.vm.ci.meta.Value; import jdk.vm.ci.runtime.JVMCIBackend; -public abstract class HotSpotBackendFactory { +public abstract class HotSpotBackendFactory implements ArchitectureSpecific { protected HotSpotGraalConstantFieldProvider createConstantFieldProvider(GraalHotSpotVMConfig config, MetaAccessProvider metaAccess) { return new HotSpotGraalConstantFieldProvider(config, metaAccess); @@ -121,12 +121,6 @@ protected HotSpotSnippetReflectionProvider createSnippetReflection(HotSpotGraalR */ public abstract String getName(); - /** - * Gets the class describing the architecture the backend created by this factory is associated - * with. - */ - public abstract Class getArchitecture(); - protected LoopsDataProvider createLoopsDataProvider() { return new LoopsDataProviderImpl(); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotForeignCallLinkage.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotForeignCallLinkage.java index 9c3b81233d0f..146ac8959f6d 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotForeignCallLinkage.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotForeignCallLinkage.java @@ -24,9 +24,18 @@ */ package jdk.graal.compiler.hotspot; +import java.util.BitSet; +import java.util.List; + +import jdk.vm.ci.services.Services; +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.EconomicSet; + import jdk.graal.compiler.core.common.spi.ForeignCallDescriptor.CallSideEffect; import jdk.graal.compiler.core.common.spi.ForeignCallLinkage; +import jdk.graal.compiler.core.common.spi.ForeignCallSignature; import jdk.graal.compiler.core.target.Backend; +import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.hotspot.meta.HotSpotForeignCallDescriptor; import jdk.graal.compiler.hotspot.meta.HotSpotForeignCallDescriptor.Transition; import jdk.graal.compiler.hotspot.replacements.HotSpotAllocationSnippets; @@ -42,6 +51,10 @@ import jdk.graal.compiler.nodes.java.MonitorEnterNode; import jdk.graal.compiler.nodes.java.NewArrayNode; import jdk.graal.compiler.replacements.SnippetTemplate; +import jdk.graal.compiler.serviceprovider.GlobalAtomicLong; +import jdk.internal.misc.Unsafe; +import jdk.vm.ci.code.Register; +import jdk.vm.ci.code.RegisterArray; import jdk.vm.ci.meta.InvokeTarget; /** @@ -269,4 +282,129 @@ enum RegisterEffect { * Gets the VM symbol associated with the target {@linkplain #getAddress() address} of the call. */ String getSymbol(); + + /** + * Encapsulates a stub's entry point and set of registers it kills. + * + * @param start address of first instruction in the stub + * @param killedRegisters see {@link Stub#getDestroyedCallerRegisters()} + */ + record CodeInfo(long start, EconomicSet killedRegisters) { + public static CodeInfo fromMemory(long memory, RegisterArray allRegisters) { + Unsafe unsafe = Unsafe.getUnsafe(); + // @formatter:off + int offset = 0; + long start = unsafe.getLong(memory + offset); offset += Long.BYTES; + int bsLongsLength = unsafe.getInt(memory + offset); offset += Integer.BYTES; + long[] bsLongs = new long[bsLongsLength]; + for (int i = 0; i < bsLongsLength; i++) { + bsLongs[i] = unsafe.getLong(memory + offset); offset += Long.BYTES; + } + // @formatter:on + BitSet bs = BitSet.valueOf(bsLongs); + EconomicSet killedRegisters = EconomicSet.create(bs.cardinality()); + for (int regNum = bs.nextSetBit(0); regNum >= 0; regNum = bs.nextSetBit(regNum + 1)) { + killedRegisters.add(allRegisters.get(regNum)); + } + return new CodeInfo(start, killedRegisters); + } + + public long toMemory() { + BitSet bs = new BitSet(); + for (Register reg : killedRegisters) { + bs.set(reg.number); + } + Unsafe unsafe = Unsafe.getUnsafe(); + long[] bsLongs = bs.toLongArray(); + int memorySize = Long.BYTES + Integer.BYTES + Long.BYTES * bsLongs.length; + long memory = unsafe.allocateMemory(memorySize); + int offset = 0; + // @formatter:off + unsafe.putLong(memory + offset, start); offset += Long.BYTES; + unsafe.putInt(memory + offset, bsLongs.length); offset += Integer.BYTES; + for (long l : bsLongs) { + unsafe.putLong(memory + offset, l); offset += Long.BYTES; + } + // @formatter:on + GraalError.guarantee(memorySize == offset, "%s != %s", memorySize, offset); + return memory; + } + } + + class Stubs { + /** + * Map from a foreign call signature to global long that is the address of a block of memory + * containing the result of {@link HotSpotForeignCallLinkageImpl.CodeInfo#toMemory()}. + *

+ * This map is used to mitigate against libgraal isolates compiling and installing duplicate + * {@code RuntimeStub}s for a foreign call. Completely preventing duplicates requires + * inter-isolate synchronization for which there is currently no support. A small number of + * duplicates is acceptable given how few foreign call runtime stubs there are in practice. + * Duplicates will only result when libgraal isolates race to install code for the same + * stub. Testing shows that this is extremely rare. + * + * In the context of libgraal, this map is initialized at image build-time (see callers of + * {@link #initStubs}) and is part of the image heap (which is shared among all isolates). + */ + private static final EconomicMap STUBS = EconomicMap.create(); + + static HotSpotForeignCallLinkageImpl.CodeInfo getCodeInfo(Stub stub, Backend backend) { + ForeignCallSignature sig = stub.getLinkage().getDescriptor().getSignature(); + GlobalAtomicLong data = getStubData(sig); + long codeInfoInMemory = data.get(); + if (codeInfoInMemory == 0L) { + // Racy between isolates but as stated in STUBS javadoc, it's not + // a problem in practice. + HotSpotForeignCallLinkageImpl.CodeInfo codeInfo = new HotSpotForeignCallLinkageImpl.CodeInfo(stub.getCode(backend).getStart(), stub.getDestroyedCallerRegisters()); + data.set(codeInfo.toMemory()); + return codeInfo; + } + RegisterArray allRegisters = backend.getCodeCache().getTarget().arch.getRegisters(); + return HotSpotForeignCallLinkageImpl.CodeInfo.fromMemory(codeInfoInMemory, allRegisters); + } + + private static GlobalAtomicLong getStubData(ForeignCallSignature sig) { + GlobalAtomicLong data; + if (Services.IS_IN_NATIVE_IMAGE) { + data = STUBS.get(sig); + GraalError.guarantee(data != null, "missing global data for %s", sig); + } else { + synchronized (STUBS) { + data = STUBS.get(sig); + if (data == null) { + data = new GlobalAtomicLong(0L); + STUBS.put(sig, data); + } + } + } + return data; + } + + /** + * Initializes the map for avoiding duplicate {@code RuntimeStub}s for foreign calls. + * + * @param sigs signatures for which runtime stubs will be created + */ + public static void initStubs(List sigs) { + GraalError.guarantee(STUBS.isEmpty(), "cannot re-initialize STUBS: %s", STUBS); + for (ForeignCallSignature sig : sigs) { + initStub(sig); + } + } + + /** + * Creates an entry in the map for avoiding duplicate {@code RuntimeStub}s for {@code sig} + * if no entry currently exists. + * + * @return {@code true} if an entry was added by this call, {@code false} if an entry + * already existed + */ + public static boolean initStub(ForeignCallSignature sig) { + if (!STUBS.containsKey(sig)) { + STUBS.put(sig, new GlobalAtomicLong(0L)); + return true; + } + return false; + } + } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotForeignCallLinkageImpl.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotForeignCallLinkageImpl.java index f77b4f147304..dbeeb30c7f89 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotForeignCallLinkageImpl.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotForeignCallLinkageImpl.java @@ -297,41 +297,13 @@ private boolean checkStubCondition() { return true; } - /** - * Encapsulates a stub's entry point and set of killed registers. - */ - public static final class CodeInfo { - /** - * Address of first instruction in the stub. - */ - final long start; - - /** - * @see Stub#getDestroyedCallerRegisters() - */ - final EconomicSet killedRegisters; - - public CodeInfo(long start, EconomicSet killedRegisters) { - this.start = start; - this.killedRegisters = killedRegisters; - } - } - - /** - * Substituted by - * {@code com.oracle.svm.graal.hotspot.libgraal.Target_jdk_graal_compiler_hotspot_HotSpotForeignCallLinkageImpl}. - */ - private static CodeInfo getCodeInfo(Stub stub, Backend backend) { - return new CodeInfo(stub.getCode(backend).getStart(), stub.getDestroyedCallerRegisters()); - } - @Override public void finalizeAddress(Backend backend) { if (address == 0) { assert checkStubCondition(); - CodeInfo codeInfo = getCodeInfo(stub, backend); + CodeInfo codeInfo = HotSpotForeignCallLinkage.Stubs.getCodeInfo(stub, backend); - EconomicSet killedRegisters = codeInfo.killedRegisters; + EconomicSet killedRegisters = codeInfo.killedRegisters(); if (!killedRegisters.isEmpty()) { AllocatableValue[] temporaryLocations = new AllocatableValue[killedRegisters.size()]; int i = 0; @@ -343,7 +315,7 @@ public void finalizeAddress(Backend backend) { } temporaries = temporaryLocations; } - address = codeInfo.start; + address = codeInfo.start(); } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalCompiler.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalCompiler.java index 4dbd368310da..cf64dffdc1f1 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalCompiler.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalCompiler.java @@ -26,8 +26,10 @@ import static jdk.graal.compiler.core.common.GraalOptions.OptAssumptions; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; import jdk.graal.compiler.api.runtime.GraalJVMCICompiler; import jdk.graal.compiler.code.CompilationResult; @@ -51,6 +53,8 @@ import jdk.graal.compiler.nodes.StructuredGraph.AllowAssumptions; import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; import jdk.graal.compiler.nodes.spi.ProfileProvider; +import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.options.OptionKey; import jdk.graal.compiler.options.OptionValues; import jdk.graal.compiler.phases.OptimisticOptimizations; import jdk.graal.compiler.phases.OptimisticOptimizations.Optimization; @@ -58,6 +62,8 @@ import jdk.graal.compiler.phases.tiers.HighTierContext; import jdk.graal.compiler.phases.tiers.Suites; import jdk.graal.compiler.printer.GraalDebugHandlersFactory; +import jdk.graal.compiler.serviceprovider.GlobalAtomicLong; +import jdk.graal.compiler.serviceprovider.VMSupport; import jdk.internal.misc.Unsafe; import jdk.vm.ci.code.CompilationRequest; import jdk.vm.ci.code.CompilationRequestResult; @@ -73,7 +79,24 @@ import jdk.vm.ci.runtime.JVMCICompiler; import jdk.vm.ci.services.Services; -public class HotSpotGraalCompiler implements GraalJVMCICompiler, Cancellable, JVMCICompilerShadow { +public class HotSpotGraalCompiler implements GraalJVMCICompiler, Cancellable, JVMCICompilerShadow, GraalCompiler.RequestedCrashHandler { + + public static final class Options { + @Option(help = "If non-zero, converts an exception triggered by the CrashAt option into a fatal error " + + "if a non-null pointer was passed in the _fatal option to JNI_CreateJavaVM. " + + "The value of this option is the number of milliseconds to sleep before calling _fatal. " + + "This option exists for the purpose of testing fatal error handling in libgraal.") // + public static final OptionKey CrashAtIsFatal = new OptionKey<>(0); + @Option(help = "The fully qualified name of a no-arg, void, static method to be invoked " + + "in HotSpot when a HotSpotGraalRuntime is being shutdown." + + "This option exists for the purpose of testing callbacks in this context.") // + public static final OptionKey OnShutdownCallback = new OptionKey<>(null); + @Option(help = "Replaces first exception thrown by the CrashAt option with an OutOfMemoryError. " + + "Subsequently CrashAt exceptions are suppressed. " + + "This option exists to test HeapDumpOnOutOfMemoryError. " + + "See the MethodFilter option for the pattern syntax.") // + public static final OptionKey CrashAtThrowsOOME = new OptionKey<>(false); + } private static final Unsafe UNSAFE = Unsafe.getUnsafe(); private final HotSpotJVMCIRuntime jvmciRuntime; @@ -102,18 +125,14 @@ public HotSpotGraalRuntimeProvider getGraalRuntime() { return graalRuntime; } + @SuppressWarnings("try") @Override public CompilationRequestResult compileMethod(CompilationRequest request) { - return compileMethod(this, request); - } - - /** - * Substituted by - * {@code com.oracle.svm.graal.hotspot.libgraal.Target_jdk_graal_compiler_hotspot_HotSpotGraalCompiler} - * to create a {@code org.graalvm.libgraal.jni.JNILibGraalScope}. - */ - private static CompilationRequestResult compileMethod(HotSpotGraalCompiler compiler, CompilationRequest request) { - return compiler.compileMethod(request, true, compiler.getGraalRuntime().getOptions()); + try (AutoCloseable ignored = VMSupport.getCompilationRequestScope()) { + return compileMethod(request, true, getGraalRuntime().getOptions()); + } catch (Exception e) { + return HotSpotCompilationRequestResult.failure(e.toString(), false); + } } @SuppressWarnings("try") @@ -150,13 +169,10 @@ public CompilationRequestResult compileMethod(CompilationRequest request, boolea if (compilationCounters != null) { compilationCounters.countCompilation(method); } - CompilationRequestResult r = null; try (DebugContext debug = graalRuntime.openDebugContext(options, task.getCompilationIdentifier(), method, getDebugHandlersFactories(), DebugContext.getDefaultLogStream()); Activation a = debug.activate()) { - r = task.runCompilation(debug); + return Objects.requireNonNull(task.runCompilation(debug)); } - assert r != null; - return r; } } } @@ -254,7 +270,20 @@ public CompilationResult compileHelper(CompilationResultBuilderFactory crbf, Com PhaseSuite graphBuilderSuite = configGraphBuilderSuite(providers.getSuites().getDefaultGraphBuilderSuite(), shouldDebugNonSafepoints, shouldRetainLocalVariables, shouldUsePreciseUnresolvedDeopts, eagerResolving, isOSR); - GraalCompiler.compileGraph(graph, method, providers, backend, graphBuilderSuite, optimisticOpts, profilingInfo, suites, lirSuites, result, crbf, true); + GraalCompiler.compile(new GraalCompiler.Request<>(graph, + method, + providers, + backend, + graphBuilderSuite, + optimisticOpts, + profilingInfo, + suites, + lirSuites, + result, + crbf, + null, + this, + true)); graph.getOptimizationLog().emit(new HotSpotStableMethodNameFormatter(providers, graph.getDebug())); if (!isOSR) { profilingInfo.setCompilerIRSize(StructuredGraph.class, graph.getNodeCount()); @@ -335,4 +364,31 @@ public boolean isGCSupported(int gcIdentifier) { return false; } + // Support for CrashAtThrowsOOME + private static final GlobalAtomicLong OOME_CRASH_DONE = new GlobalAtomicLong(0); + + @Override + public boolean notifyCrash(OptionValues options, String crashMessage) { + if (Services.IS_IN_NATIVE_IMAGE) { + if (HotSpotGraalCompiler.Options.CrashAtThrowsOOME.getValue(options)) { + if (OOME_CRASH_DONE.compareAndSet(0L, 1L)) { + // The -Djdk.libgraal.Xmx option should also be employed to make + // this allocation fail quicky + String largeString = Arrays.toString(new int[Integer.MAX_VALUE - 1]); + throw new InternalError("Failed to trigger OOME: largeString.length=" + largeString.length()); + } else { + // Remaining compilations should proceed so that test finishes quickly. + return false; + } + } else { + int crashAtIsFatal = HotSpotGraalCompiler.Options.CrashAtIsFatal.getValue(options); + if (crashAtIsFatal != 0) { + VMSupport.fatalError(crashMessage, crashAtIsFatal); + // If changing this message, update the test for it in mx_vm_gate.py + System.out.println("CrashAtIsFatal: no fatalError function pointer installed"); + } + } + } + return true; + } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalCompilerFactory.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalCompilerFactory.java index ed2d2347459b..61a314f04dfb 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalCompilerFactory.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalCompilerFactory.java @@ -38,6 +38,7 @@ import jdk.graal.compiler.options.OptionType; import jdk.graal.compiler.options.OptionValues; import jdk.graal.compiler.phases.tiers.CompilerConfiguration; +import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.vm.ci.common.InitTimer; import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; import jdk.vm.ci.hotspot.HotSpotResolvedJavaMethod; @@ -111,7 +112,7 @@ public void onSelection() { } private void initialize() { - JVMCIVersionCheck.check(Services.getSavedProperties(), false, null); + JVMCIVersionCheck.check(GraalServices.getSavedProperties(), false, null); assert options == null : "cannot select " + getClass() + " service more than once"; try { options = HotSpotGraalOptionValues.defaultOptions(); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalOptionValues.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalOptionValues.java index 8a455d88c1be..936bb51dd2cf 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalOptionValues.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalOptionValues.java @@ -31,6 +31,7 @@ import java.util.Set; import jdk.graal.compiler.serviceprovider.GlobalAtomicLong; +import jdk.graal.compiler.serviceprovider.GraalServices; import org.graalvm.collections.EconomicMap; import jdk.graal.compiler.options.Option; import jdk.graal.compiler.options.OptionDescriptors; @@ -40,7 +41,6 @@ import jdk.vm.ci.common.InitTimer; import jdk.vm.ci.common.NativeImageReinitialize; -import jdk.vm.ci.services.Services; /** * The {@link #defaultOptions()} method returns the options values initialized in a HotSpot VM. The @@ -98,7 +98,7 @@ public static OptionValues defaultOptions() { } /** - * Gets and parses options based on {@linkplain Services#getSavedProperties() saved system + * Gets and parses options based on {@linkplain GraalServices#getSavedProperties() saved system * properties}. The values for these options are initialized by parsing system properties whose * names start with {@value #GRAAL_OPTION_PROPERTY_PREFIX}. */ @@ -108,7 +108,7 @@ public static EconomicMap, Object> parseOptions() { try (InitTimer t = timer("InitializeOptions")) { Iterable descriptors = OptionsParser.getOptionsLoader(); - Map savedProps = jdk.vm.ci.services.Services.getSavedProperties(); + Map savedProps = GraalServices.getSavedProperties(); EconomicMap compilerOptionSettings = EconomicMap.create(); EconomicMap vmOptionSettings = EconomicMap.create(); @@ -176,14 +176,13 @@ private static void notifyLibgraalOptions(EconomicMap, Object> comp } } - /** - * Substituted by - * {@code com.oracle.svm.graal.hotspot.libgraal.Target_jdk_graal_compiler_hotspot_HotSpotGraalOptionValues} - * to update {@code com.oracle.svm.core.option.RuntimeOptionValues.singleton()} instead of - * creating a new {@link OptionValues} object. - */ private static OptionValues initializeOptions() { - return new OptionValues(parseOptions()); + EconomicMap, Object> values = parseOptions(); + OptionValues options = new OptionValues(values); + if (HotSpotGraalCompiler.Options.CrashAtThrowsOOME.getValue(options) && HotSpotGraalCompiler.Options.CrashAtIsFatal.getValue(options) != 0) { + throw new IllegalArgumentException("CrashAtThrowsOOME and CrashAtIsFatal cannot both be enabled"); + } + return options; } static void printProperties(OptionValues compilerOptions, PrintStream out) { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalRuntime.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalRuntime.java index 7b888598ea4c..1c16b4c2a6dd 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalRuntime.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotGraalRuntime.java @@ -35,6 +35,7 @@ import java.util.Map; import java.util.function.Predicate; +import jdk.vm.ci.services.Services; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.Equivalence; @@ -65,6 +66,7 @@ import jdk.graal.compiler.replacements.SnippetCounter.Group; import jdk.graal.compiler.runtime.RuntimeProvider; import jdk.graal.compiler.serviceprovider.GraalServices; +import jdk.graal.compiler.serviceprovider.VMSupport; import jdk.vm.ci.code.Architecture; import jdk.vm.ci.code.stack.StackIntrospection; import jdk.vm.ci.common.InitTimer; @@ -191,7 +193,7 @@ public GlobalMetrics getMetricValues() { this.compilerProfiler = GraalServices.loadSingle(CompilerProfiler.class, false); - startupLibGraal(this); + VMSupport.startupLibGraal(); } /** @@ -437,27 +439,19 @@ synchronized void shutdown() { outputDirectory.close(); - shutdownLibGraal(this); - } - - /** - * Substituted by - * {@code com.oracle.svm.graal.hotspot.libgraal.Target_jdk_graal_compiler_hotspot_HotSpotGraalRuntime} - * to notify {@code org.graalvm.libgraal.LibGraalIsolate} and call - * {@code org.graalvm.nativeimage.VMRuntime.initialize()}. - */ - @SuppressWarnings("unused") - private static void startupLibGraal(HotSpotGraalRuntime runtime) { - } - - /** - * Substituted by - * {@code com.oracle.svm.graal.hotspot.libgraal.Target_jdk_graal_compiler_hotspot_HotSpotGraalRuntime} - * to notify {@code org.graalvm.libgraal.LibGraalIsolate} and call - * {@code org.graalvm.nativeimage.VMRuntime.shutdown()}. - */ - @SuppressWarnings("unused") - private static void shutdownLibGraal(HotSpotGraalRuntime runtime) { + if (Services.IS_IN_NATIVE_IMAGE) { + String callback = HotSpotGraalCompiler.Options.OnShutdownCallback.getValue(options); + if (callback != null) { + int lastDot = callback.lastIndexOf('.'); + if (lastDot < 1 || lastDot == callback.length() - 1) { + throw new IllegalArgumentException(HotSpotGraalCompiler.Options.OnShutdownCallback.getName() + " value does not have . format: " + callback); + } + String cbClassName = callback.substring(0, lastDot); + String cbMethodName = callback.substring(lastDot + 1); + VMSupport.invokeShutdownCallback(cbClassName, cbMethodName); + } + VMSupport.shutdownLibGraal(); + } } void clearMetrics() { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotReplacementsImpl.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotReplacementsImpl.java index 04e56b2fae07..01fb172b2f05 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotReplacementsImpl.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotReplacementsImpl.java @@ -84,9 +84,9 @@ public HotSpotProviders getProviders() { return (HotSpotProviders) super.getProviders(); } - public void maybeInitializeEncoder() { + public SymbolicSnippetEncoder maybeInitializeEncoder() { if (IS_IN_NATIVE_IMAGE) { - return; + return null; } if (IS_BUILDING_NATIVE_IMAGE) { synchronized (HotSpotReplacementsImpl.class) { @@ -95,6 +95,7 @@ public void maybeInitializeEncoder() { } } } + return snippetEncoder; } @Override @@ -228,7 +229,7 @@ public void clearSnippetParameterNames() { snippetEncoder.clearSnippetParameterNames(); } - static void setEncodedSnippets(EncodedSnippets encodedSnippets) { + public static void setEncodedSnippets(EncodedSnippets encodedSnippets) { HotSpotReplacementsImpl.encodedSnippets = encodedSnippets; } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotTTYStreamProvider.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotTTYStreamProvider.java index aa54acac928c..b8d21bc88617 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotTTYStreamProvider.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/HotSpotTTYStreamProvider.java @@ -47,7 +47,6 @@ import jdk.vm.ci.common.NativeImageReinitialize; import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; -import jdk.vm.ci.services.Services; @ServiceProvider(TTYStreamProvider.class) public class HotSpotTTYStreamProvider implements TTYStreamProvider { @@ -225,7 +224,7 @@ private void printVMConfig(final boolean enableAutoflush, FileOutputStream resul if (inputArguments != null) { ps.println("VM Arguments: " + String.join(" ", inputArguments)); } - String cmd = Services.getSavedProperty("sun.java.command"); + String cmd = GraalServices.getSavedProperty("sun.java.command"); if (cmd != null) { ps.println("sun.java.command=" + cmd); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/SymbolicSnippetEncoder.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/SymbolicSnippetEncoder.java index baa6beeb6e86..6d987a4c91aa 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/SymbolicSnippetEncoder.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/SymbolicSnippetEncoder.java @@ -38,6 +38,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.function.BiFunction; @@ -63,6 +64,7 @@ import jdk.graal.compiler.graph.NodeMap; import jdk.graal.compiler.graph.NodeSourcePosition; import jdk.graal.compiler.hotspot.meta.HotSpotProviders; +import jdk.graal.compiler.hotspot.stubs.AbstractForeignCallStub; import jdk.graal.compiler.hotspot.stubs.ForeignCallStub; import jdk.graal.compiler.hotspot.word.HotSpotWordTypes; import jdk.graal.compiler.java.BytecodeParser; @@ -108,6 +110,7 @@ import jdk.graal.compiler.replacements.SnippetIntegerHistogram; import jdk.graal.compiler.replacements.SnippetTemplate; import jdk.graal.compiler.replacements.classfile.ClassfileBytecode; +import jdk.graal.compiler.util.ObjectCopier; import jdk.graal.compiler.word.WordTypes; import jdk.vm.ci.code.Architecture; import jdk.vm.ci.code.TargetDescription; @@ -138,7 +141,7 @@ * This class performs graph encoding using {@link GraphEncoder} but also converts JVMCI type and * method references into a symbolic form that can be resolved at graph decode time using * {@link SymbolicJVMCIReference}. - * + *

* An instance of this class only exist when * {@link jdk.vm.ci.services.Services#IS_BUILDING_NATIVE_IMAGE} is true. */ @@ -403,7 +406,7 @@ private boolean verifySnippetEncodeDecode(DebugContext debug, ResolvedJavaMethod * Encode all pending graphs and return the result. */ @SuppressWarnings("try") - private synchronized EncodedSnippets encodeSnippets(OptionValues options) { + public synchronized EncodedSnippets encodeSnippets(OptionValues options) { GraphBuilderConfiguration.Plugins plugins = originalReplacements.getGraphBuilderPlugins(); InvocationPlugins invocationPlugins = plugins.getInvocationPlugins(); GraphBuilderConfiguration.Plugins copy = new GraphBuilderConfiguration.Plugins(plugins, invocationPlugins); @@ -548,6 +551,13 @@ private synchronized EncodedSnippets encodeSnippets(DebugContext debug, Economic lookupSnippetType(SnippetTemplate.EagerSnippetInfo.class); lookupSnippetType(ForeignCallStub.class); + // Ensure AbstractForeignCallStub.getGraph is available + MetaAccessProvider metaAccess = originalReplacements.getProviders().getMetaAccess(); + ResolvedJavaMethod[] stubMethods = metaAccess.lookupJavaType(AbstractForeignCallStub.class).getDeclaredMethods(); + Optional abstractForeignCallStubGetName = Arrays.stream(stubMethods).filter(resolvedJavaMethod -> resolvedJavaMethod.getName().equals("getGraph")).findFirst(); + GraalError.guarantee(abstractForeignCallStubGetName.isPresent(), "ResolvedJavaMethod for AbstractForeignCallStub.getGraph() unavailable"); + findSnippetMethod(abstractForeignCallStubGetName.get()); + SnippetObjectFilter filter = new SnippetObjectFilter(originalReplacements.getProviders()); byte[] snippetEncoding = encoder.getEncoding(); Object[] snippetObjects = encoder.getObjects(); @@ -1061,7 +1071,11 @@ protected boolean tryInvocationPlugin(CallTargetNode.InvokeKind invokeKind, Valu } } - private static final Map, SnippetResolvedJavaType> snippetTypes = new HashMap<>(); + /** + * To prevent this field being considered as an externalValue by + * {@link ObjectCopier#encode(Object, List)}, it must not be {@code final}. + */ + private static Map, SnippetResolvedJavaType> snippetTypes = new HashMap<>(); private static synchronized SnippetResolvedJavaType lookupSnippetType(Class clazz) { SnippetResolvedJavaType type = null; diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/aarch64/AArch64HotSpotBackendFactory.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/aarch64/AArch64HotSpotBackendFactory.java index 6ecc2aefb33f..683c07cbeac0 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/aarch64/AArch64HotSpotBackendFactory.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/aarch64/AArch64HotSpotBackendFactory.java @@ -60,7 +60,6 @@ import jdk.graal.compiler.serviceprovider.ServiceProvider; import jdk.vm.ci.aarch64.AArch64; -import jdk.vm.ci.code.Architecture; import jdk.vm.ci.code.Register; import jdk.vm.ci.code.RegisterConfig; import jdk.vm.ci.code.TargetDescription; @@ -80,8 +79,8 @@ public String getName() { } @Override - public Class getArchitecture() { - return AArch64.class; + public String getArchitecture() { + return "aarch64"; } @Override diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/amd64/AMD64HotSpotBackendFactory.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/amd64/AMD64HotSpotBackendFactory.java index c09c6447fa2c..d979e5f84cde 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/amd64/AMD64HotSpotBackendFactory.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/amd64/AMD64HotSpotBackendFactory.java @@ -52,7 +52,6 @@ import jdk.graal.compiler.serviceprovider.ServiceProvider; import jdk.vm.ci.amd64.AMD64; -import jdk.vm.ci.code.Architecture; import jdk.vm.ci.code.Register; import jdk.vm.ci.code.RegisterConfig; import jdk.vm.ci.code.TargetDescription; @@ -71,8 +70,8 @@ public String getName() { } @Override - public Class getArchitecture() { - return AMD64.class; + public String getArchitecture() { + return "AMD64"; } @Override diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/guestgraal/BuildTime.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/guestgraal/BuildTime.java new file mode 100644 index 000000000000..1b1d13751687 --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/guestgraal/BuildTime.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.graal.compiler.hotspot.guestgraal; + +import static java.lang.invoke.MethodType.methodType; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import jdk.graal.compiler.code.DisassemblerProvider; +import jdk.graal.compiler.core.common.CompilerProfiler; +import jdk.graal.compiler.core.common.spi.ForeignCallSignature; +import jdk.graal.compiler.core.match.MatchStatementSet; +import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.debug.TTY; +import jdk.graal.compiler.graph.NodeClass; +import jdk.graal.compiler.hotspot.CompilerConfigurationFactory; +import jdk.graal.compiler.hotspot.EncodedSnippets; +import jdk.graal.compiler.hotspot.HotSpotBackendFactory; +import jdk.graal.compiler.hotspot.HotSpotCodeCacheListener; +import jdk.graal.compiler.hotspot.HotSpotForeignCallLinkage; +import jdk.graal.compiler.hotspot.HotSpotReplacementsImpl; +import jdk.graal.compiler.hotspot.meta.DefaultHotSpotLoweringProvider; +import jdk.graal.compiler.hotspot.meta.HotSpotInvocationPluginProvider; +import jdk.graal.compiler.nodes.graphbuilderconf.GeneratedInvocationPlugin; +import jdk.graal.compiler.options.OptionDescriptors; +import jdk.graal.compiler.options.OptionsParser; +import jdk.graal.compiler.serviceprovider.GraalServices; +import jdk.graal.compiler.truffle.PartialEvaluatorConfiguration; +import jdk.graal.compiler.truffle.substitutions.GraphBuilderInvocationPluginProvider; +import jdk.graal.compiler.truffle.substitutions.GraphDecoderInvocationPluginProvider; +import jdk.graal.compiler.util.ObjectCopier; +import jdk.vm.ci.hotspot.HotSpotJVMCIBackendFactory; +import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; +import jdk.vm.ci.services.JVMCIServiceLocator; +import org.graalvm.collections.EconomicMap; + +/** + * This class is used at image build-time when a libgraal image gets built. Its static methods are + * called from {@code com.oracle.svm.graal.hotspot.guestgraal.GuestGraalFeature} before static + * analysis. These methods ensure the static field state of Graal and JVMCI classes loaded by the + * GuestGraalClassLoader is set up correctly for getting built into libgraal. + */ +public class BuildTime { + + private static final String VALID_LOADER_NAME = "GuestGraalClassLoader"; + private static final ClassLoader LOADER = BuildTime.class.getClassLoader(); + + /** + * This method gets called from {@code GuestGraalFeature#afterRegistration()} to ensure static + * field {@code OptionsParser#cachedOptionDescriptors} gets initialized only with + * OptionDescriptors service providers from the classes in the GuestGraalClassLoader. + */ + @SuppressWarnings("unused") + public static void configureOptionsParserCachedOptionDescriptors() { + GraalError.guarantee(VALID_LOADER_NAME.equals(LOADER.getName()), + "Only call this method from classloader " + VALID_LOADER_NAME); + + Iterable optionsLoaderIterable = OptionsParser.getOptionsLoader(LOADER); + List cachedOptionDescriptors = new ArrayList<>(); + optionsLoaderIterable.forEach(cachedOptionDescriptors::add); + OptionsParser.setCachedOptionDescriptors(List.copyOf(cachedOptionDescriptors)); + } + + @SuppressWarnings("unused") + public static long[] getInputEdgesOffsets(Object rawNodeClass) { + /* Used by GuestGraalFieldsOffsetsFeature.IterationMaskRecomputation */ + NodeClass nodeclass = (NodeClass) rawNodeClass; + return nodeclass.getInputEdges().getOffsets(); + } + + @SuppressWarnings("unused") + public static long[] getSuccessorEdgesOffsets(Object rawNodeClass) { + /* Used by GuestGraalFieldsOffsetsFeature.IterationMaskRecomputation */ + NodeClass nodeclass = (NodeClass) rawNodeClass; + return nodeclass.getSuccessorEdges().getOffsets(); + } + + /** + * This method gets called from {@code GuestGraalFeature#beforeAnalysis()}. It ensures static + * field {@code GraalServices#servicesCache} is set up correctly for Graal-usage needed in + * libgraal. It creates a new instance of EncodedSnippets exclusively from instances of + * GuestGraalClassLoader classes. The information how the EncodedSnippets object graph needs to + * look like comes from parameter encodedSnippetsData. This EncodedSnippets instance is then + * installed into static field {@code HotSpotReplacementsImpl#encodedSnippets} so that at + * libgraal run-time Graal has access to it. Finally, it passes the FoldNodePlugin Classes to + * the caller via a Consumer so that hosted GeneratedInvocationPlugin Graal class knows about + * them. + */ + @SuppressWarnings({"try", "unused", "unchecked"}) + public static void configureGraalForLibGraal(String arch, + Consumer> registerAsInHeap, + Consumer>> hostedGraalSetFoldNodePluginClasses, + String encodedGuestObjects) { + GraalError.guarantee(VALID_LOADER_NAME.equals(LOADER.getName()), + "Only call this method from classloader " + VALID_LOADER_NAME); + + GraalServices.setLibgraalConfig(new GraalServices.LibgraalConfig(LOADER, arch)); + // Services that will be used in libgraal. + GraalServices.load(CompilerConfigurationFactory.class); + GraalServices.load(HotSpotBackendFactory.class); + GraalServices.load(GraphBuilderInvocationPluginProvider.class); + GraalServices.load(GraphDecoderInvocationPluginProvider.class); + GraalServices.load(PartialEvaluatorConfiguration.class); + GraalServices.load(HotSpotCodeCacheListener.class); + GraalServices.load(DisassemblerProvider.class); + GraalServices.load(HotSpotInvocationPluginProvider.class); + GraalServices.load(DefaultHotSpotLoweringProvider.Extensions.class); + GraalServices.load(CompilerProfiler.class); + GraalServices.load(MatchStatementSet.class); + + try { + Field cachedHotSpotJVMCIBackendFactoriesField = ObjectCopier.getField(HotSpotJVMCIRuntime.class, "cachedHotSpotJVMCIBackendFactories"); + GraalError.guarantee(cachedHotSpotJVMCIBackendFactoriesField.get(null) == null, "Expect cachedHotSpotJVMCIBackendFactories to be null"); + ServiceLoader load = ServiceLoader.load(HotSpotJVMCIBackendFactory.class, LOADER); + List backendFactories = load.stream()// + .map(ServiceLoader.Provider::get)// + .filter(s -> s.getArchitecture().equals(arch))// + .toList(); + cachedHotSpotJVMCIBackendFactoriesField.set(null, backendFactories); + GraalError.guarantee(backendFactories.size() == 1, "%s", backendFactories); + + var jvmciServiceLocatorCachedLocatorsField = ObjectCopier.getField(JVMCIServiceLocator.class, "cachedLocators"); + GraalError.guarantee(jvmciServiceLocatorCachedLocatorsField.get(null) == null, "Expect cachedLocators to be null"); + Iterable serviceLocators = ServiceLoader.load(JVMCIServiceLocator.class, LOADER); + List cachedLocators = new ArrayList<>(); + serviceLocators.forEach(cachedLocators::add); + jvmciServiceLocatorCachedLocatorsField.set(null, cachedLocators); + + EconomicMap guestObjects = (EconomicMap) ObjectCopier.decode(encodedGuestObjects, LOADER); + EncodedSnippets encodedSnippets = (EncodedSnippets) guestObjects.get("encodedSnippets"); + + // Mark all the Node classes as allocated so they are available during graph decoding. + for (NodeClass nodeClass : encodedSnippets.getSnippetNodeClasses()) { + registerAsInHeap.accept(nodeClass.getClazz()); + } + HotSpotReplacementsImpl.setEncodedSnippets(encodedSnippets); + + List foreignCallSignatures = (List) guestObjects.get("foreignCallSignatures"); + HotSpotForeignCallLinkage.Stubs.initStubs(foreignCallSignatures); + + hostedGraalSetFoldNodePluginClasses.accept(GeneratedInvocationPlugin.getFoldNodePluginClasses()); + + } catch (ReflectiveOperationException e) { + throw GraalError.shouldNotReachHere(e); + } + } + + private static final Lookup MHL = MethodHandles.lookup(); + + /** + * Gets method handles to call Graal and JVMCI methods. + * + * @return a named set of handles + */ + public static Map getRuntimeHandles() { + try { + return Map.of("compileMethod", MHL.findStatic(RunTime.class, "compileMethod", + methodType(long.class, long.class, + boolean.class, boolean.class, boolean.class, boolean.class, + long.class, int.class, int.class, + String.class, BiConsumer.class, Supplier.class)), + "getJNIEnv", MHL.findStatic(RunTime.class, "getJNIEnv", + methodType(long.class)), + "getSavedProperty", MHL.findStatic(GraalServices.class, "getSavedProperty", + methodType(String.class, String.class)), + "ttyPrintf", MHL.findStatic(TTY.class, "printf", + methodType(void.class, String.class, Object[].class))); + } catch (Throwable e) { + throw GraalError.shouldNotReachHere(e); + } + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/guestgraal/CompilerConfig.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/guestgraal/CompilerConfig.java new file mode 100644 index 000000000000..0884a947c99c --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/guestgraal/CompilerConfig.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.graal.compiler.hotspot.guestgraal; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +import jdk.graal.compiler.core.common.spi.ForeignCallSignature; +import jdk.graal.compiler.core.target.Backend; +import jdk.graal.compiler.hotspot.HotSpotForeignCallLinkage; +import jdk.graal.compiler.hotspot.meta.HotSpotHostForeignCallsProvider; +import jdk.graal.compiler.truffle.hotspot.HotSpotTruffleCompilerImpl; +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; +import org.graalvm.word.LocationIdentity; + +import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.hotspot.EncodedSnippets; +import jdk.graal.compiler.hotspot.HotSpotGraalCompiler; +import jdk.graal.compiler.hotspot.HotSpotGraalRuntimeProvider; +import jdk.graal.compiler.hotspot.HotSpotReplacementsImpl; +import jdk.graal.compiler.hotspot.SymbolicSnippetEncoder; +import jdk.graal.compiler.hotspot.meta.HotSpotProviders; +import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.util.ObjectCopier; +import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; + +/** + * A command line program that initializes the compiler data structures to be serialized into the + * libgraal image. The data structures are returned in a map with the following entries: + *

    + * * + *
  • "encodedSnippets" -> value returned by + * {@link SymbolicSnippetEncoder#encodeSnippets(OptionValues)}
  • + *
  • "foreignCallSignatures" -> value that is passed to + * {@link jdk.graal.compiler.hotspot.HotSpotForeignCallLinkage.Stubs#initStubs}
  • + *
+ */ +public class CompilerConfig { + + /** + * A program that prints the compiler configuration serialized to a String by + * {@link ObjectCopier#encode}. The output is printed to the file path in {@code args[0]}. + */ + public static void main(String[] args) throws Exception { + HotSpotGraalCompiler graalCompiler = (HotSpotGraalCompiler) HotSpotJVMCIRuntime.runtime().getCompiler(); + HotSpotGraalRuntimeProvider graalRuntime = graalCompiler.getGraalRuntime(); + HotSpotProviders hostProviders = graalRuntime.getHostProviders(); + HotSpotReplacementsImpl replacements = (HotSpotReplacementsImpl) hostProviders.getReplacements(); + OptionValues options = graalRuntime.getCapability(OptionValues.class); + + List foreignCallSignatures = getForeignCallSignatures(replacements, options, graalRuntime); + EncodedSnippets encodedSnippets = getEncodedSnippets(replacements, options); + List externalValues = getExternalValues(); + + EconomicMap encodedObjects = EconomicMap.create(); + encodedObjects.put("encodedSnippets", encodedSnippets); + encodedObjects.put("foreignCallSignatures", foreignCallSignatures); + + String encoded = ObjectCopier.encode(encodedObjects, externalValues); + + Files.writeString(Path.of(args[0]), encoded); + } + + private static EncodedSnippets getEncodedSnippets(HotSpotReplacementsImpl replacements, OptionValues options) { + SymbolicSnippetEncoder snippetEncoder = replacements.maybeInitializeEncoder(); + return snippetEncoder.encodeSnippets(options); + } + + private static List getForeignCallSignatures(HotSpotReplacementsImpl replacements, OptionValues options, HotSpotGraalRuntimeProvider graalRuntime) { + List sigs = new ArrayList<>(); + EconomicMap foreignCalls = collectForeignCalls(replacements, options); + MapCursor cursor = foreignCalls.getEntries(); + while (cursor.advance()) { + ForeignCallSignature sig = cursor.getKey(); + HotSpotForeignCallLinkage linkage = cursor.getValue(); + sigs.add(sig); + if (linkage != null) { + // Construct the stub so that all types it uses are registered in + // SymbolicSnippetEncoder.snippetTypes + linkage.finalizeAddress(graalRuntime.getHostBackend()); + } + } + return sigs; + } + + private static EconomicMap collectForeignCalls(HotSpotReplacementsImpl replacements, + OptionValues options) { + EconomicMap allForeignCalls = EconomicMap.create(); + HotSpotProviders providers = replacements.getProviders(); + collectForeignCalls(providers.getForeignCalls(), allForeignCalls); + + // Instantiate the Truffle compiler to collect its foreign calls as well + for (Backend truffleBackend : HotSpotTruffleCompilerImpl.ensureBackendsInitialized(options)) { + HotSpotProviders truffleProviders = (HotSpotProviders) truffleBackend.getProviders(); + collectForeignCalls(truffleProviders.getForeignCalls(), allForeignCalls); + } + return allForeignCalls; + } + + private static void collectForeignCalls(HotSpotHostForeignCallsProvider foreignCalls, + EconomicMap allForeignCalls) { + foreignCalls.forEachForeignCall((sig, linkage) -> { + if (linkage == null || linkage.isCompiledStub()) { + if (!allForeignCalls.containsKey(sig)) { + allForeignCalls.put(sig, linkage); + } + } + }); + } + + private static List getExternalValues() throws IOException { + List externalValues = new ArrayList<>(); + addImmutableCollectionsFields(externalValues); + addStaticFinalObjectFields(LocationIdentity.class, externalValues); + + try (FileSystem fs = FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap())) { + for (String module : List.of("jdk.internal.vm.ci", "jdk.graal.compiler", "com.oracle.graal.graal_enterprise")) { + Path top = fs.getPath("/modules/" + module); + try (Stream files = Files.find(top, Integer.MAX_VALUE, (path, attrs) -> attrs.isRegularFile())) { + files.forEach(p -> { + String fileName = p.getFileName().toString(); + if (fileName.endsWith(".class") && !fileName.equals("module-info.class")) { + // Strip module prefix and convert to dotted form + int nameCount = p.getNameCount(); + String className = p.subpath(2, nameCount).toString().replace('/', '.'); + // Strip ".class" suffix + className = className.replace('/', '.').substring(0, className.length() - ".class".length()); + try { + Class graalClass = Class.forName(className); + addStaticFinalObjectFields(graalClass, externalValues); + } catch (ClassNotFoundException e) { + throw new GraalError(e); + } + } + }); + } + } + } + return externalValues; + } + + /** + * Adds the static, final, non-primitive fields of non-enum {@code declaringClass} to + * {@code fields}. In the process, the fields are made {@linkplain Field#setAccessible + * accessible}. + */ + private static void addStaticFinalObjectFields(Class declaringClass, List fields) { + if (Enum.class.isAssignableFrom(declaringClass)) { + return; + } + for (Field field : declaringClass.getDeclaredFields()) { + int fieldModifiers = field.getModifiers(); + int fieldMask = Modifier.STATIC | Modifier.FINAL; + if ((fieldModifiers & fieldMask) != fieldMask) { + continue; + } + if (field.getType().isPrimitive()) { + continue; + } + field.setAccessible(true); + fields.add(field); + } + } + + /** + * Adds the EMPTY* fields from {@code java.util.ImmutableCollections} to {@code fields}, making + * them {@linkplain Field#setAccessible accessible} in the process. + */ + private static void addImmutableCollectionsFields(List fields) { + Class c = List.of().getClass().getDeclaringClass(); + GraalError.guarantee(c.getName().equals("java.util.ImmutableCollections"), "Incompatible ImmutableCollections class"); + for (Field f : c.getDeclaredFields()) { + if (f.getName().startsWith("EMPTY")) { + int modifiers = f.getModifiers(); + GraalError.guarantee(Modifier.isStatic(modifiers), "Expect %s to be static", f); + GraalError.guarantee(Modifier.isFinal(modifiers), "Expect %s to be final", f); + GraalError.guarantee(!f.getType().isPrimitive(), "Expect %s to be non-primitive", f); + f.setAccessible(true); + fields.add(f); + } + } + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/guestgraal/RunTime.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/guestgraal/RunTime.java new file mode 100644 index 000000000000..3208ba3d4b43 --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/guestgraal/RunTime.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.graal.compiler.hotspot.guestgraal; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Supplier; + +import jdk.vm.ci.hotspot.HotSpotVMConfigAccess; +import jdk.vm.ci.hotspot.HotSpotVMConfigStore; +import org.graalvm.collections.EconomicMap; + +import jdk.graal.compiler.debug.GlobalMetrics; +import jdk.graal.compiler.hotspot.CompilationContext; +import jdk.graal.compiler.hotspot.CompilationTask; +import jdk.graal.compiler.hotspot.HotSpotGraalCompiler; +import jdk.graal.compiler.hotspot.HotSpotGraalRuntime; +import jdk.graal.compiler.hotspot.HotSpotGraalServices; +import jdk.graal.compiler.hotspot.ProfileReplaySupport; +import jdk.graal.compiler.options.OptionDescriptors; +import jdk.graal.compiler.options.OptionKey; +import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.options.OptionsParser; +import jdk.graal.compiler.serviceprovider.GraalUnsafeAccess; +import jdk.graal.compiler.util.OptionsEncoder; +import jdk.vm.ci.hotspot.HotSpotCompilationRequest; +import jdk.vm.ci.hotspot.HotSpotInstalledCode; +import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; +import jdk.vm.ci.hotspot.HotSpotResolvedJavaMethod; +import jdk.vm.ci.runtime.JVMCICompiler; +import sun.misc.Unsafe; + +/** + * This class provides implementations for {@code @CEntryPoint}s that libgraal has to provide as JVM + * JIT compiler as public methods. + */ +public class RunTime { + + private static final Unsafe UNSAFE = GraalUnsafeAccess.getUnsafe(); + + private record CachedOptions(OptionValues options, long hash) { + } + + private static final ThreadLocal CACHED_OPTIONS_THREAD_LOCAL = new ThreadLocal<>(); + + private static OptionValues decodeOptions(long address, int size, int hash) { + CachedOptions options = CACHED_OPTIONS_THREAD_LOCAL.get(); + if (options == null || options.hash != hash) { + byte[] buffer = new byte[size]; + UNSAFE.copyMemory(null, address, buffer, Unsafe.ARRAY_BYTE_BASE_OFFSET, size); + int actualHash = Arrays.hashCode(buffer); + if (actualHash != hash) { + throw new IllegalArgumentException(actualHash + " != " + hash); + } + Map srcMap = OptionsEncoder.decode(buffer); + final EconomicMap, Object> dstMap = OptionValues.newOptionMap(); + final Iterable loader = OptionsParser.getOptionsLoader(); + for (Map.Entry e : srcMap.entrySet()) { + final String optionName = e.getKey(); + final Object optionValue = e.getValue(); + OptionsParser.parseOption(optionName, optionValue, dstMap, loader); + } + + options = new CachedOptions(new OptionValues(dstMap), hash); + CACHED_OPTIONS_THREAD_LOCAL.set(options); + } + return options.options; + } + + /** + * This is the implementation that {@code @CEntryPoint}-method + * {@code com.oracle.svm.graal.hotspot.guestgraal.GuestGraal#compileMethod} delegates to. Most + * parameters are identical to the caller method parameters except for the following: + * + * @param profileLoadPath value of the {@code Options#LoadProfiles} option or null + * @param timeAndMemConsumer allows caller to get info about compile time and memory consumption + * @param currentThreadAllocatedBytes gives access to + * {@code com.sun.management.ThreadMXBean#getCurrentThreadAllocatedBytes()} needed to + * compute memory consumption during compilation + */ + @SuppressWarnings("try") + public static long compileMethod(long methodHandle, boolean useProfilingInfo, + boolean installAsDefault, boolean printMetrics, boolean eagerResolving, + long optionsAddress, int optionsSize, int optionsHash, + String profileLoadPath, BiConsumer timeAndMemConsumer, + Supplier currentThreadAllocatedBytes) { + + HotSpotJVMCIRuntime runtime = HotSpotJVMCIRuntime.runtime(); + HotSpotGraalCompiler compiler = (HotSpotGraalCompiler) runtime.getCompiler(); + + int entryBCI = JVMCICompiler.INVOCATION_ENTRY_BCI; + HotSpotResolvedJavaMethod method = HotSpotJVMCIRuntime.runtime().unhand(HotSpotResolvedJavaMethod.class, methodHandle); + HotSpotCompilationRequest request = new HotSpotCompilationRequest(method, entryBCI, 0L); + try (CompilationContext ignored = HotSpotGraalServices.openLocalCompilationContext(request)) { + CompilationTask task = new CompilationTask(runtime, compiler, request, useProfilingInfo, false, eagerResolving, installAsDefault); + long allocatedBytesBefore = 0; + long timeBefore = 0; + if (timeAndMemConsumer != null) { + allocatedBytesBefore = currentThreadAllocatedBytes.get(); + timeBefore = System.currentTimeMillis(); + } + OptionValues options = decodeOptions(optionsAddress, optionsSize, optionsHash); + if (profileLoadPath != null) { + options = new OptionValues(options, ProfileReplaySupport.Options.LoadProfiles, profileLoadPath); + } + task.runCompilation(options); + if (timeAndMemConsumer != null) { + long allocatedBytesAfter = currentThreadAllocatedBytes.get(); + long bytesAllocated = allocatedBytesAfter - allocatedBytesBefore; + long timeAfter = System.currentTimeMillis(); + long timeSpent = timeAfter - timeBefore; + timeAndMemConsumer.accept(timeSpent, bytesAllocated); + } + HotSpotInstalledCode installedCode = task.getInstalledCode(); + if (printMetrics) { + GlobalMetrics metricValues = ((HotSpotGraalRuntime) compiler.getGraalRuntime()).getMetricValues(); + metricValues.print(options); + metricValues.clear(); + } + return HotSpotJVMCIRuntime.runtime().translate(installedCode); + } + } + + private static long jniEnvironmentOffset = Integer.MAX_VALUE; + + private static long getJniEnvironmentOffset() { + if (jniEnvironmentOffset == Integer.MAX_VALUE) { + HotSpotJVMCIRuntime jvmciRuntime = HotSpotJVMCIRuntime.runtime(); + HotSpotVMConfigStore store = jvmciRuntime.getConfigStore(); + HotSpotVMConfigAccess config = new HotSpotVMConfigAccess(store); + jniEnvironmentOffset = config.getFieldOffset("JavaThread::_jni_environment", Integer.class, "JNIEnv"); + } + return jniEnvironmentOffset; + } + + /** + * Gets the JNIEnv value for the current HotSpot thread. + */ + static long getJNIEnv() { + HotSpotJVMCIRuntime jvmciRuntime = HotSpotJVMCIRuntime.runtime(); + long offset = getJniEnvironmentOffset(); + long javaThreadAddr = jvmciRuntime.getCurrentJavaThread(); + return javaThreadAddr + offset; + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/riscv64/RISCV64HotSpotBackendFactory.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/riscv64/RISCV64HotSpotBackendFactory.java index c79e671a5e26..0b62a620b916 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/riscv64/RISCV64HotSpotBackendFactory.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/riscv64/RISCV64HotSpotBackendFactory.java @@ -57,7 +57,6 @@ import jdk.graal.compiler.phases.common.AddressLoweringPhase; import jdk.graal.compiler.phases.tiers.CompilerConfiguration; import jdk.graal.compiler.serviceprovider.ServiceProvider; -import jdk.vm.ci.code.Architecture; import jdk.vm.ci.code.Register; import jdk.vm.ci.code.RegisterConfig; import jdk.vm.ci.code.TargetDescription; @@ -77,8 +76,8 @@ public String getName() { } @Override - public Class getArchitecture() { - return RISCV64.class; + public String getArchitecture() { + return "riscv64"; } @Override diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/NamedLocationIdentity.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/NamedLocationIdentity.java index 181fe6746ac2..b34e08e2764b 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/NamedLocationIdentity.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/NamedLocationIdentity.java @@ -126,14 +126,23 @@ public static LocationIdentity getArrayLocation(JavaKind elementKind) { return ARRAY_LOCATIONS.get(elementKind); } - private static final EnumMap ARRAY_LOCATIONS = initArrayLocations(); - - private static EnumMap initArrayLocations() { - EnumMap result = new EnumMap<>(JavaKind.class); - for (JavaKind kind : JavaKind.values()) { - result.put(kind, NamedLocationIdentity.mutable("Array: " + kind.getJavaName())); - } - return result; + private static final EnumMap ARRAY_LOCATIONS = new EnumMap<>(JavaKind.class); + + // These exist so that GuestGraal construction can read these values from static fields. + public static final LocationIdentity BOOLEAN_ARRAY_LOCATION = initArrayLocation(JavaKind.Boolean); + public static final LocationIdentity BYTE_ARRAY_LOCATION = initArrayLocation(JavaKind.Byte); + public static final LocationIdentity CHAR_ARRAY_LOCATION = initArrayLocation(JavaKind.Char); + public static final LocationIdentity SHORT_ARRAY_LOCATION = initArrayLocation(JavaKind.Short); + public static final LocationIdentity INT_ARRAY_LOCATION = initArrayLocation(JavaKind.Int); + public static final LocationIdentity FLOAT_ARRAY_LOCATION = initArrayLocation(JavaKind.Float); + public static final LocationIdentity LONG_ARRAY_LOCATION = initArrayLocation(JavaKind.Long); + public static final LocationIdentity DOUBLE_ARRAY_LOCATION = initArrayLocation(JavaKind.Double); + public static final LocationIdentity OBJECT_ARRAY_LOCATION = initArrayLocation(JavaKind.Object); + + private static NamedLocationIdentity initArrayLocation(JavaKind kind) { + NamedLocationIdentity loc = NamedLocationIdentity.mutable("Array: " + kind.getJavaName()); + ARRAY_LOCATIONS.put(kind, loc); + return loc; } public static boolean isArrayLocation(LocationIdentity l) { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/calc/PointerEqualsNode.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/calc/PointerEqualsNode.java index bf92226507a1..ed84ebd79d46 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/calc/PointerEqualsNode.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/calc/PointerEqualsNode.java @@ -49,6 +49,7 @@ import jdk.graal.compiler.nodes.util.GraphUtil; import jdk.graal.compiler.nodes.virtual.AllocatedObjectNode; import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.vm.ci.meta.ConstantReflectionProvider; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; @@ -56,7 +57,6 @@ import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.TriState; -import jdk.vm.ci.services.Services; @NodeInfo(shortName = "==") public class PointerEqualsNode extends CompareNode implements Canonicalizable.BinaryCommutative { @@ -252,7 +252,7 @@ private static boolean isCached(long l, JavaKind kind) { return l <= 127; case Int: long low = -128; - String arg = Services.getSavedProperty("java.lang.Integer.IntegerCache.high"); + String arg = GraalServices.getSavedProperty("java.lang.Integer.IntegerCache.high"); long high = arg == null ? 127 : Integer.parseInt(arg); return l >= low && l <= high; case Long: diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GeneratedInvocationPlugin.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GeneratedInvocationPlugin.java index cb1f516111de..f69e9130b109 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GeneratedInvocationPlugin.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GeneratedInvocationPlugin.java @@ -30,6 +30,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Type; +import java.util.List; import jdk.graal.compiler.api.replacements.Fold; import jdk.graal.compiler.debug.GraalError; @@ -37,7 +38,6 @@ import jdk.graal.compiler.nodes.PluginReplacementNode; import jdk.graal.compiler.nodes.ValueNode; import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugin.RequiredInlineOnlyInvocationPlugin; - import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; @@ -48,6 +48,16 @@ */ public abstract class GeneratedInvocationPlugin extends RequiredInlineOnlyInvocationPlugin { + private static List> foldNodePluginClasses = List.of(GeneratedFoldInvocationPlugin.class, PluginReplacementNode.ReplacementFunction.class); + + public static void setFoldNodePluginClasses(List> customFoldNodePluginClasses) { + foldNodePluginClasses = customFoldNodePluginClasses; + } + + public static List> getFoldNodePluginClasses() { + return foldNodePluginClasses; + } + private ResolvedJavaMethod executeMethod; public GeneratedInvocationPlugin(String name, Type... argumentTypes) { @@ -90,13 +100,11 @@ protected boolean checkInjectedArgument(GraphBuilderContext b, ValueNode arg, Re if (IS_BUILDING_NATIVE_IMAGE) { // The use of this plugin in the plugin itself shouldn't be folded since that defeats // the purpose of the fold. - ResolvedJavaType foldNodeClass = b.getMetaAccess().lookupJavaType(PluginReplacementNode.ReplacementFunction.class); - if (foldNodeClass.isAssignableFrom(b.getMethod().getDeclaringClass())) { - return false; - } - ResolvedJavaType foldPluginClass = b.getMetaAccess().lookupJavaType(GeneratedFoldInvocationPlugin.class); - if (foldPluginClass.isAssignableFrom(b.getMethod().getDeclaringClass())) { - return false; + for (Class foldNodePluginClass : foldNodePluginClasses) { + ResolvedJavaType foldNodeClass = b.getMetaAccess().lookupJavaType(foldNodePluginClass); + if (foldNodeClass.isAssignableFrom(b.getMethod().getDeclaringClass())) { + return false; + } } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderContext.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderContext.java index a74c72c1bef7..0ebe3f841228 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderContext.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderContext.java @@ -493,7 +493,7 @@ default ExternalInliningContext getExternalInliningContext() { } /** - * Adds masking to a given subword value according to a given {@Link JavaKind}, such that the + * Adds masking to a given subword value according to a given {@link JavaKind}, such that the * masked value falls in the range of the given kind. In the cases where the given kind is not a * subword kind, the input value is returned immediately. * diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/options/OptionsParser.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/options/OptionsParser.java index 242a57bb61cf..99d0fa9d478c 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/options/OptionsParser.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/options/OptionsParser.java @@ -50,8 +50,12 @@ public class OptionsParser { /** * Gets an iterable of available {@link OptionDescriptors}. */ - @ExcludeFromJacocoGeneratedReport("contains libgraal only path") public static Iterable getOptionsLoader() { + return getOptionsLoader(ClassLoader.getSystemClassLoader()); + } + + @ExcludeFromJacocoGeneratedReport("contains libgraal only path") + public static Iterable getOptionsLoader(ClassLoader loader) { if (IS_IN_NATIVE_IMAGE || cachedOptionDescriptors != null) { return cachedOptionDescriptors; } @@ -61,7 +65,7 @@ public static Iterable getOptionsLoader() { * need to start the provider search at the app class loader instead of the platform class * loader. */ - return ServiceLoader.load(OptionDescriptors.class, ClassLoader.getSystemClassLoader()); + return ServiceLoader.load(OptionDescriptors.class, loader); } @ExcludeFromJacocoGeneratedReport("only called when building libgraal") diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/PhaseSuite.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/PhaseSuite.java index 87b90ef8f64e..1ff92bbc3403 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/PhaseSuite.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/PhaseSuite.java @@ -41,7 +41,7 @@ import jdk.graal.compiler.options.Option; import jdk.graal.compiler.options.OptionKey; -import jdk.vm.ci.services.Services; +import jdk.graal.compiler.serviceprovider.GraalServices; /** * A compiler phase that can apply an ordered collection of phases to a graph. @@ -397,7 +397,7 @@ protected void run(StructuredGraph graph, C context) { graphStateBefore = graph.getGraphState().copy(); } } catch (Throwable t) { - if (Boolean.parseBoolean(Services.getSavedProperty("test.graal.compilationplan.fuzzing"))) { + if (Boolean.parseBoolean(GraalServices.getSavedProperty("test.graal.compilationplan.fuzzing"))) { TTY.println("========================================================================================================================"); TTY.println("An error occurred while executing phase %s.", phase.getClass().getName()); TTY.printf("The graph state after the failing phase is:%n%s", graph.getGraphState().toString("\t")); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/DominatorBasedGlobalValueNumberingPhase.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/DominatorBasedGlobalValueNumberingPhase.java index e55ed43883d1..d6f1e4838d53 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/DominatorBasedGlobalValueNumberingPhase.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/DominatorBasedGlobalValueNumberingPhase.java @@ -178,23 +178,23 @@ public GVNVisitor(ControlFlowGraph cfg, LoopsData ld) { } /** - * Traversal order and kill effects: The visit order of the dominator tree guarantees - * that predecessors (except loop edges which are handled explicitly) are visited before - * a block itself (i.e. the post dominator of a split). This ensures we will have seen - * all necessary predecessor kills before we process a merge block. + * Traversal order and kill effects: The visit order of the dominator tree guarantees that + * predecessors (except loop edges which are handled explicitly) are visited before a block + * itself (i.e. the post dominator of a split). This ensures we will have seen all necessary + * predecessor kills before we process a merge block. * * Example: Block b0,b1,b2,b3 * - * @formatter:off + *
          * if()  b0
          *  b1
          * else
          *  b2
          * merge b3
+         * 
* * The order of traversal would be b0,b1,b2,b3(post dom) where the effects of b1 and b2 are * collected and applied to b3 as well - * @formatter:on */ @Override public ValueMap enter(HIRBlock b) { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/ProfileCompiledMethodsPhase.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/ProfileCompiledMethodsPhase.java index 7f90434b5a00..6e7c47f02b7e 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/ProfileCompiledMethodsPhase.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/ProfileCompiledMethodsPhase.java @@ -24,7 +24,7 @@ */ package jdk.graal.compiler.phases.common; -import static jdk.vm.ci.services.Services.getSavedProperty; +import static jdk.graal.compiler.serviceprovider.GraalServices.getSavedProperty; import java.util.Arrays; import java.util.Collection; diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/printer/CFGPrinterObserver.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/printer/CFGPrinterObserver.java index bf5f01f09526..255d55c8c452 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/printer/CFGPrinterObserver.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/printer/CFGPrinterObserver.java @@ -59,7 +59,6 @@ import jdk.vm.ci.code.InstalledCode; import jdk.vm.ci.meta.JavaMethod; import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.services.Services; /** * Observes compilation events and uses {@link CFGPrinter} to produce a control flow graph for the @@ -235,7 +234,7 @@ public void dumpSandboxed(DebugContext debug, Object object, String message) { private static DisassemblerProvider selectDisassemblerProvider(OptionValues options) { DisassemblerProvider selected = null; - String arch = Services.getSavedProperty("os.arch"); + String arch = GraalServices.getSavedProperty("os.arch"); final boolean isAArch64 = arch.equals("aarch64"); Iterator load = GraalServices.load(DisassemblerProvider.class).iterator(); while (load.hasNext()) { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/printer/GraphPrinterDumpHandler.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/printer/GraphPrinterDumpHandler.java index 772dfaefbc27..15c7d046fc21 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/printer/GraphPrinterDumpHandler.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/printer/GraphPrinterDumpHandler.java @@ -54,7 +54,6 @@ import jdk.vm.ci.meta.JavaMethod; import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.services.Services; //JaCoCo Exclude @@ -92,7 +91,7 @@ public GraphPrinterDumpHandler(GraphPrinterSupplier printerSupplier) { this.printerSupplier = printerSupplier; /* Add the JVM and Java arguments to the graph properties to help identify it. */ this.jvmArguments = jvmArguments(); - this.sunJavaCommand = Services.getSavedProperty("sun.java.command"); + this.sunJavaCommand = GraalServices.getSavedProperty("sun.java.command"); } private static String jvmArguments() { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/InlineDuringParsingPlugin.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/InlineDuringParsingPlugin.java index 3f25d29be00d..2af8a4b8f98a 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/InlineDuringParsingPlugin.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/InlineDuringParsingPlugin.java @@ -35,13 +35,13 @@ import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; import jdk.graal.compiler.nodes.graphbuilderconf.InlineInvokePlugin; +import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.services.Services; public final class InlineDuringParsingPlugin implements InlineInvokePlugin { private static int getInteger(String name, int def) { - String value = Services.getSavedProperty(name); + String value = GraalServices.getSavedProperty(name); if (value != null) { return Integer.parseInt(value); } 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 060a0b5263f2..bf7c988cfe9e 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 @@ -37,7 +37,11 @@ import java.util.Map; import java.util.ServiceConfigurationError; import java.util.ServiceLoader; +import java.util.concurrent.atomic.AtomicReference; +import jdk.graal.compiler.core.ArchitectureSpecific; +import jdk.internal.misc.VM; +import jdk.vm.ci.code.Architecture; import jdk.vm.ci.meta.EncodedSpeculationReason; import jdk.vm.ci.meta.SpeculationLog.SpeculationReason; import jdk.vm.ci.runtime.JVMCI; @@ -54,7 +58,7 @@ private GraalServices() { } /** - * Gets an {@link Iterable} of the providers available for a given service. + * Gets an {@link Iterable} of the providers available for {@code service}. */ @SuppressWarnings("unchecked") public static Iterable load(Class service) { @@ -74,8 +78,7 @@ public static Iterable load(Class service) { synchronized (servicesCache) { ArrayList providersList = new ArrayList<>(); for (S provider : providers) { - Module module = provider.getClass().getModule(); - if (isHotSpotGraalModule(module.getName())) { + if (isLibgraalProvider(provider)) { providersList.add(provider); } } @@ -89,10 +92,16 @@ public static Iterable load(Class service) { } /** - * Determines if the module named by {@code name} is part of Graal when it is configured as a - * HotSpot JIT compiler. + * Determines if {@code provider} is a service provider used in libgraal. */ - private static boolean isHotSpotGraalModule(String name) { + private static boolean isLibgraalProvider(Object provider) { + LibgraalConfig config = libgraalConfig(); + if (config != null) { + return config.isLibgraalProvider(provider); + } + Class c = provider.getClass(); + Module module = c.getModule(); + String name = module.getName(); if (name != null) { return name.equals("jdk.graal.compiler") || name.equals("jdk.graal.compiler.management") || @@ -101,6 +110,90 @@ private static boolean isHotSpotGraalModule(String name) { return false; } + /** + * Gets an unmodifiable copy of the system properties in their state at system initialization + * time. This method must be used instead of calling {@link Services#getSavedProperties()} + * directly for any caller that will end up in libgraal. + * + * @see VM#getSavedProperties + */ + public static Map getSavedProperties() { + if (IS_BUILDING_NATIVE_IMAGE && !IS_IN_NATIVE_IMAGE) { + return jdk.internal.misc.VM.getSavedProperties(); + } + return Services.getSavedProperties(); + } + + /** + * Helper method equivalent to {@link #getSavedProperties()}{@code .getOrDefault(name, def)}. + */ + public static String getSavedProperty(String name, String def) { + if (IS_BUILDING_NATIVE_IMAGE && !IS_IN_NATIVE_IMAGE) { + return jdk.internal.misc.VM.getSavedProperties().getOrDefault(name, def); + } + return Services.getSavedProperties().getOrDefault(name, def); + } + + /** + * Helper method equivalent to {@link #getSavedProperties()}{@code .get(name)}. + */ + public static String getSavedProperty(String name) { + if (IS_BUILDING_NATIVE_IMAGE && !IS_IN_NATIVE_IMAGE) { + return jdk.internal.misc.VM.getSavedProperty(name); + } + return Services.getSavedProperty(name); + } + + /** + * Configuration relevant for building libgraal. + * + * @param classLoader the class loader used to load libgraal destined classes + * @param arch the {@linkplain Architecture#getName() name} of the architecture on which + * libgraal will be deployed + */ + public record LibgraalConfig(ClassLoader classLoader, String arch) { + + /** + * Determines if {@code provider} will be used in libgraal. + */ + boolean isLibgraalProvider(Object provider) { + if (provider.getClass().getClassLoader() != classLoader) { + return false; + } + if (provider instanceof ArchitectureSpecific as) { + return as.getArchitecture().equals(arch); + } + return true; + } + } + + /** + * Sentinel value for representing "default" initialization of {@link #libgraalConfig}. + */ + private static final LibgraalConfig LIBGRAAL_CONFIG_DEFAULT = new LibgraalConfig(null, null); + + private static final AtomicReference libgraalConfig = new AtomicReference<>(); + + /** + * Sets the config for building libgraal. Can only be called at most once. If called, it must be + * before any call to {@link #load(Class)}. + */ + public static void setLibgraalConfig(LibgraalConfig config) { + if (!IS_BUILDING_NATIVE_IMAGE) { + throw new InternalError("Can only set libgraal config when building libgraal"); + } + LibgraalConfig expectedValue = libgraalConfig.compareAndExchange(null, config); + if (expectedValue != null) { + throw new InternalError("Libgraal config already initialized: " + expectedValue); + } + } + + private static LibgraalConfig libgraalConfig() { + libgraalConfig.compareAndSet(null, LIBGRAAL_CONFIG_DEFAULT); + LibgraalConfig config = libgraalConfig.get(); + return config == LIBGRAAL_CONFIG_DEFAULT ? null : config; + } + private static Iterable load0(Class service) { Module module = GraalServices.class.getModule(); // Graal cannot know all the services used by another module @@ -109,8 +202,13 @@ private static Iterable load0(Class service) { module.addUses(service); } - ModuleLayer layer = module.getLayer(); - Iterable iterable = ServiceLoader.load(layer, service); + LibgraalConfig config = libgraalConfig(); + Iterable iterable; + if (config != null && config.classLoader != null) { + iterable = ServiceLoader.load(service, config.classLoader); + } else { + iterable = ServiceLoader.load(module.getLayer(), service); + } return new Iterable<>() { @Override public Iterator iterator() { @@ -446,7 +544,6 @@ public static void notifyLowMemoryPoint() { * @param forceFullGC controls whether to explicitly perform a full GC */ private static void notifyLowMemoryPoint(boolean hintFullGC, boolean forceFullGC) { - // Substituted by - // com.oracle.svm.hotspot.libgraal.Target_jdk_graal_compiler_serviceprovider_GraalServices + VMSupport.notifyLowMemoryPoint(hintFullGC, forceFullGC); } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/IsolateUtil.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/IsolateUtil.java index f6b23c47a54d..769b59941ac3 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/IsolateUtil.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/IsolateUtil.java @@ -33,9 +33,7 @@ public final class IsolateUtil { * Gets the address of the current isolate or 0 if this not an isolate-aware runtime. */ public static long getIsolateAddress() { - // Substituted by - // com.oracle.svm.graal.Target_jdk_graal_compiler_serviceprovider_IsolateUtil - return 0; + return VMSupport.getIsolateAddress(); } /** @@ -44,9 +42,7 @@ public static long getIsolateAddress() { * process. */ public static long getIsolateID() { - // Substituted by - // com.oracle.svm.graal.Target_jdk_graal_compiler_serviceprovider_IsolateUtil - return 0; + return VMSupport.getIsolateID(); } /** diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/VMSupport.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/VMSupport.java new file mode 100644 index 000000000000..a84195a2932c --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/VMSupport.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.graal.compiler.serviceprovider; + +/** + * Interface between the compiler and its Native Image based runtime (i.e. libgraal). + * + * The methods of this class are substituted by the libgraal implementation. + */ +public final class VMSupport { + /** + * @see IsolateUtil#getIsolateAddress + */ + public static long getIsolateAddress() { + return 0L; + } + + /** + * @see IsolateUtil#getIsolateID + */ + public static long getIsolateID() { + return 0L; + } + + /** + * Gets a scope that performs setup/cleanup actions around a libgraal compilation. + */ + public static AutoCloseable getCompilationRequestScope() { + return null; + } + + /** + * Notifies that a fatal error has occurred. + * + * @param message description of the error + * @param delayMS milliseconds to sleep before exiting the VM + */ + public static void fatalError(String message, int delayMS) { + + } + + /** + * Notifies libgraal when a Graal runtime is being started. + */ + public static void startupLibGraal() { + } + + /** + * Notifies libgraal when a Graal runtime is being shutdown. + */ + public static void shutdownLibGraal() { + } + + /** + * @param cbClassName name of class declaring the call back method + * @param cbMethodName name of the call back method + */ + public static void invokeShutdownCallback(String cbClassName, String cbMethodName) { + } + + /** + * @param hintFullGC controls whether the hinted GC should be a full GC. + * @param forceFullGC controls whether to explicitly perform a full GC + * @see GraalServices#notifyLowMemoryPoint() + */ + public static void notifyLowMemoryPoint(boolean hintFullGC, boolean forceFullGC) { + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/TruffleCompilerImpl.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/TruffleCompilerImpl.java index 227996e1e286..33693fce4487 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/TruffleCompilerImpl.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/TruffleCompilerImpl.java @@ -41,6 +41,7 @@ import java.util.Map; import java.util.function.Consumer; +import jdk.vm.ci.meta.ProfilingInfo; import org.graalvm.collections.EconomicMap; import com.oracle.truffle.compiler.OptimizedAssumptionDependency; @@ -606,8 +607,19 @@ public CompilationResult compilePEGraph(StructuredGraph graph, LIRSuites selectedLirSuites = tier.lirSuites(); Providers selectedProviders = tier.providers(); CompilationResult compilationResult = createCompilationResult(name, graph.compilationId(), compilable); - result = GraalCompiler.compileGraph(graph, graph.method(), selectedProviders, tier.backend(), graphBuilderSuite, Optimizations, graph.getProfilingInfo(), selectedSuites, - selectedLirSuites, compilationResult, CompilationResultBuilderFactory.Default, false); + ProfilingInfo profilingInfo = graph.getProfilingInfo(); + result = GraalCompiler.compile(new GraalCompiler.Request<>(graph, + graph.method(), + selectedProviders, + tier.backend(), + graphBuilderSuite, + Optimizations, + profilingInfo, + selectedSuites, + selectedLirSuites, + compilationResult, + CompilationResultBuilderFactory.Default, + false)); } catch (Throwable e) { throw debug.handle(e); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/hotspot/HotSpotTruffleCompilerImpl.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/hotspot/HotSpotTruffleCompilerImpl.java index 31a6f4a5abed..462fbecb1354 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/hotspot/HotSpotTruffleCompilerImpl.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/hotspot/HotSpotTruffleCompilerImpl.java @@ -24,7 +24,7 @@ */ package jdk.graal.compiler.truffle.hotspot; -import static jdk.graal.compiler.core.GraalCompiler.compileGraph; +import static jdk.graal.compiler.core.GraalCompiler.compile; import static jdk.graal.compiler.debug.DebugOptions.DebugStubsAndSnippets; import static jdk.graal.compiler.hotspot.meta.HotSpotSuitesProvider.withNodeSourcePosition; @@ -34,6 +34,7 @@ import java.util.ListIterator; import java.util.Map; +import jdk.graal.compiler.core.GraalCompiler; import org.graalvm.collections.EconomicMap; import com.oracle.truffle.compiler.TruffleCompilable; @@ -382,8 +383,8 @@ private CompilationResult compileTruffleStub(DebugContext debug, ResolvedJavaMet new HotSpotGraphBuilderInstance(lastTierProviders, newBuilderConfig, OptimisticOptimizations.ALL, null).apply(graph); PhaseSuite graphBuilderSuite = getGraphBuilderSuite(codeCache, backend.getSuites()); - return compileGraph(graph, javaMethod, lastTierProviders, backend, graphBuilderSuite, OptimisticOptimizations.ALL, graph.getProfilingInfo(), newSuites, tier.lirSuites(), - new CompilationResult(compilationId), CompilationResultBuilderFactory.Default, resultFactory, false); + return compile(new GraalCompiler.Request<>(graph, javaMethod, lastTierProviders, backend, graphBuilderSuite, OptimisticOptimizations.ALL, graph.getProfilingInfo(), newSuites, tier.lirSuites(), + new CompilationResult(compilationId), CompilationResultBuilderFactory.Default, resultFactory, null, false)); } @Override diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopier.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopier.java new file mode 100644 index 000000000000..10b2cc55fb12 --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopier.java @@ -0,0 +1,1044 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.graal.compiler.util; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.EconomicMapWrap; +import org.graalvm.collections.Equivalence; +import org.graalvm.collections.UnmodifiableEconomicMap; +import org.graalvm.collections.UnmodifiableMapCursor; +import org.graalvm.word.LocationIdentity; + +import jdk.graal.compiler.core.common.FieldIntrospection; +import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.serviceprovider.GraalUnsafeAccess; +import sun.misc.Unsafe; + +/** + * Support for deep copying an object across processes by {@linkplain #encode encoding} it to a + * String in the first process and {@linkplain #decode decoding} it back into an object in the + * second process. This copying requires that the classes of the copied objects are the same in both + * processes with respect to fields. + * + * Encoded format in EBNF: + * + *
+ *  enc = line "\n" { line "\n" }
+ *  line = header | objectField
+ *  header = id ":" ( builtin | object | array | fieldRef )
+ *  object = "{" className ":" fieldCount "}"
+ *  objectField = "  " fieldName ":" id " = " fieldValue
+ *  fieldValue = ( primitive | id )
+ *  id = int
+ *  array = "[" className "] = " elements
+ *  elements = [ fieldValue { " " fieldValue } ]
+ *  fieldRef = "@" className "." fieldName
+ *  builtin = "<"  className [ ":" encodingName ] "> = " builtinValue
+ * 
+ * + * See the {@link Builtin} subclasses for the EBNF of builtinValue. + */ +public class ObjectCopier { + + private static final Pattern BUILTIN_LINE = Pattern.compile("<(?[^:}]+)(?::(?\\w+))?> = (?.*)"); + private static final Pattern OBJECT_LINE = Pattern.compile("\\{(?[\\w.$]+):(?\\d+)}"); + private static final Pattern ARRAY_LINE = Pattern.compile("\\[(?[^]]+)] = (?.*)"); + private static final Pattern FIELD_LINE = Pattern.compile("\\s*(?[^:]+):(?[^ ]+) = (?.*)"); + + /** + * A builtin is specialized support for encoded and decoding values of specific types. + */ + abstract static class Builtin { + /** + * The primary type for this builtin. + */ + final Class clazz; + + final Set> concreteClasses; + + protected Builtin(Class clazz, Class... concreteClasses) { + this.clazz = clazz; + if (Modifier.isAbstract(clazz.getModifiers())) { + this.concreteClasses = Set.of(concreteClasses); + } else { + ArrayList> l = new ArrayList<>(List.of(concreteClasses)); + l.add(clazz); + this.concreteClasses = Set.copyOf(l); + } + } + + /** + * Checks that {@code obj} is of a type supported by this builtin. + */ + final void checkObject(Object obj) { + checkClass(obj == Class.class ? (Class) obj : obj.getClass()); + } + + /** + * Checks that {@code c} is a type supported by this builtin. + */ + final void checkClass(Class c) { + GraalError.guarantee(c.isEnum() || concreteClasses.contains(c), + "Unsupported %s type: %s", this.clazz.getName(), c.getName()); + } + + /** + * Gets the name of a non-default encoded used by this builtin for {@code obj}. + * + * @return null if the default encoded is used for {@code obj} + */ + @SuppressWarnings("unused") + String encodingName(Object obj) { + return null; + } + + /** + * Ensures object ids have are created for the values referenced by {@code obj} that will be + * handled by this builtin when {@code obj} is encoded. For example, the values in a map. + */ + @SuppressWarnings("unused") + void makeChildIds(Encoder encoder, Object obj, ObjectPath objectPath) { + } + + /** + * Encodes the value of {@code obj} to a String that does not contain {@code '\n'} or + * {@code '\r'}. + */ + abstract String encode(Encoder encoder, Object obj); + + /** + * Decodes {@code encoded} to an object of a type handled by this builtin. + * + * @param encoding the non-default encoded used when encoded the object or null if the + * default encoded was used + */ + abstract Object decode(Decoder decoder, Class concreteType, String encoding, String encoded); + + @Override + public String toString() { + return "builtin:" + clazz.getName(); + } + } + + /** + * Builtin for handling {@link Class} values. + * + * EBNF: + * + *
+     * builtinValue = className
+     * 
+ * + * The className is in {@link Class#getName()} format. + */ + static final class ClassBuiltin extends Builtin { + + ClassBuiltin() { + super(Class.class); + } + + @Override + String encode(Encoder encoder, Object obj) { + return ((Class) obj).getName(); + } + + @Override + Object decode(Decoder decoder, Class concreteType, String encoding, String encoded) { + return switch (encoded) { + case "boolean" -> boolean.class; + case "byte" -> byte.class; + case "char" -> char.class; + case "short" -> short.class; + case "int" -> int.class; + case "float" -> float.class; + case "long" -> long.class; + case "double" -> double.class; + case "void" -> void.class; + default -> decoder.loadClass(encoded); + }; + } + } + + /** + * Builtin for handling {@link String} values. + * + * EBNF: + * + *
+     * builtinValue = string
+     * 
+ * + * The string has no embedded \r or \n characters. + */ + static final class StringBuiltin extends Builtin { + + StringBuiltin() { + super(String.class, char[].class); + } + + @Override + String encodingName(Object obj) { + String s = obj instanceof String ? (String) obj : new String((char[]) obj); + if (s.indexOf('\n') != -1 || s.indexOf('\r') != -1) { + return "escaped"; + } + return super.encodingName(obj); + } + + @Override + String encode(Encoder encoder, Object obj) { + String s = obj instanceof String ? (String) obj : new String((char[]) obj); + if ("escaped".equals(encodingName(s))) { + return s.replace("\\", "\\\\").replace("\n", "\\n").replace("\r", "\\r"); + } + return s; + } + + @Override + Object decode(Decoder decoder, Class concreteType, String encoding, String encoded) { + String s = encoded; + if (encoding != null) { + GraalError.guarantee(encoding.equals("escaped"), "Unknown encoded: %s", encoding); + s = encoded.replace("\\r", "\r").replace("\\n", "\n").replace("\\\\", "\\"); + } + if (concreteType == char[].class) { + return s.toCharArray(); + } + return s; + } + } + + /** + * Builtin for handling {@link Enum} values. + * + * EBNF: + * + *
+     * builtinValue = enumName
+     * 
+ * + * The enumName is given by {@link Enum#name()}. + */ + static final class EnumBuiltin extends Builtin { + + EnumBuiltin() { + super(Enum.class); + } + + @Override + String encode(Encoder encoder, Object obj) { + return ((Enum) obj).name(); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + Object decode(Decoder decoder, Class concreteType, String encoding, String encoded) { + return Enum.valueOf((Class) concreteType, encoded); + } + } + + /** + * Builtin for handling {@link HashMap} or {@link IdentityHashMap} values. + * + * EBNF: + * + *
+     *  builtinValue = [ key ":" value { " " } key ":" value ]
+     *  key = fieldValue
+     *  value = fieldValue
+     * 
+ */ + static final class HashMapBuiltin extends Builtin { + + final Map, Supplier> factories; + + HashMapBuiltin() { + super(HashMap.class, IdentityHashMap.class, LinkedHashMap.class); + factories = Map.of( + HashMap.class, HashMap::new, + IdentityHashMap.class, IdentityHashMap::new, + LinkedHashMap.class, LinkedHashMap::new); + } + + @Override + void makeChildIds(Encoder encoder, Object obj, ObjectPath objectPath) { + Map map = (Map) obj; + encoder.makeMapChildIds(new EconomicMapWrap<>(map), objectPath); + } + + @Override + String encode(Encoder encoder, Object obj) { + Map map = (Map) obj; + return encoder.encodeMap(new EconomicMapWrap<>(map)); + } + + @SuppressWarnings("unchecked") + @Override + Object decode(Decoder decoder, Class concreteType, String encoding, String encoded) { + Map map = (Map) factories.get(concreteType).get(); + decoder.decodeMap(encoded, map::put); + return map; + } + } + + /** + * Builtin for handling {@link EconomicMap} values. + * + * EBNF: + * + *
+     *  builtinValue = [ key ":" value { " " } key ":" value ]
+     *  key = fieldValue
+     *  value = fieldValue
+     * 
+ */ + static final class EconomicMapBuiltin extends Builtin { + EconomicMapBuiltin() { + super(EconomicMap.class, EconomicMap.create().getClass()); + } + + @Override + void makeChildIds(Encoder encoder, Object obj, ObjectPath objectPath) { + EconomicMap map = (EconomicMap) obj; + GraalError.guarantee(map.getEquivalenceStrategy() == Equivalence.DEFAULT, + "Only DEFAULT strategy supported: %s", map.getEquivalenceStrategy()); + encoder.makeMapChildIds(map, objectPath); + } + + @Override + String encode(Encoder encoder, Object obj) { + return encoder.encodeMap((UnmodifiableEconomicMap) obj); + } + + @Override + Object decode(Decoder decoder, Class concreteType, String encoding, String encoded) { + if (EconomicMap.class.isAssignableFrom(concreteType)) { + EconomicMap map = EconomicMap.create(); + decoder.decodeMap(encoded, map::put); + return map; + } else { + throw new GraalError("Unexpected concrete Map type: ", concreteType); + } + } + } + + /** + * Caches metadata needs for encoded and decoding the non-static fields of a class. + * + * @param fields a map from a descriptor to a field for each non-static field in {@code clazz}'s + * super type hierarchy. A descriptor is the simple name of a field unless 2 fields + * have the same name in which case the descriptor includes the qualified name of the + * class declaring the field as a prefix. + */ + record ClassInfo(Class clazz, Map fields) { + static ClassInfo of(Class declaringClass) { + Map fields = new HashMap<>(); + for (Class c = declaringClass; !c.equals(Object.class); c = c.getSuperclass()) { + for (Field f : c.getDeclaredFields()) { + if (!Modifier.isStatic(f.getModifiers())) { + f.setAccessible(true); + String fieldDesc = f.getName(); + if (fields.containsKey(fieldDesc)) { + fieldDesc = c.getName() + "." + fieldDesc; + } + Field conflict = fields.put(fieldDesc, f); + GraalError.guarantee(conflict == null, "Cannot support 2 fields with same name and declaring class: %s and %s", conflict, f); + + // Try to avoid problems with identity hash codes + GraalError.guarantee(!f.getName().toLowerCase(Locale.ROOT).contains("hash"), "Cannot serialize hash field: %s", f); + } + } + } + return new ClassInfo(declaringClass, fields); + } + } + + private static final Unsafe UNSAFE = GraalUnsafeAccess.getUnsafe(); + + final Map, ClassInfo> classInfos = new HashMap<>(); + final Map, Builtin> builtinClasses = new HashMap<>(); + final Set> notBuiltins = new HashSet<>(); + + final void addBuiltin(Builtin builtin) { + addBuiltin(builtin, builtin.clazz); + } + + /** + * Registers {@code builtin} and ensures the type {@code clazz} it handles is not handled by an + * existing registered builtin. + */ + final void addBuiltin(Builtin builtin, Class clazz) { + Builtin conflict = getBuiltin(clazz, true); + GraalError.guarantee(conflict == null, "Conflicting builtins: %s and %s", conflict, builtin); + builtinClasses.put(clazz, builtin); + } + + public ObjectCopier() { + addBuiltin(new ClassBuiltin()); + addBuiltin(new EconomicMapBuiltin()); + addBuiltin(new EnumBuiltin()); + + HashMapBuiltin hashMapBuiltin = new HashMapBuiltin(); + addBuiltin(hashMapBuiltin); + addBuiltin(hashMapBuiltin, IdentityHashMap.class); + + StringBuiltin stringBuiltin = new StringBuiltin(); + addBuiltin(stringBuiltin); + addBuiltin(stringBuiltin, char[].class); + } + + static String[] splitSpaceSeparatedElements(String elements) { + if (elements.isEmpty()) { + return new String[0]; + } + return elements.split(" "); + } + + /** + * Encodes {@code root} to a String. + * + * @param externalValues static fields whose values should not be encoded but instead + * represented as a reference to the field + */ + public static String encode(Object root, List externalValues) { + Encoder encoder = new Encoder(externalValues); + int rootId = encoder.makeId(root, ObjectPath.of("[root]")).id(); + GraalError.guarantee(rootId == 1, "The root object should have id of 1, not %d", rootId); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (PrintStream ps = new PrintStream(baos)) { + encoder.encode(ps); + } + return baos.toString(); + } + + public static Object decode(String encoded, ClassLoader loader) { + Decoder decoder = new Decoder(loader); + return decoder.decode(encoded); + } + + static class Decoder extends ObjectCopier { + + private final Map idToObject = new HashMap<>(); + private final ClassLoader loader; + + Decoder(ClassLoader loader) { + this.loader = loader; + } + + Class loadClass(String className) { + try { + return Class.forName(className, false, loader); + } catch (ClassNotFoundException e) { + throw new GraalError(e); + } + } + + Object getObject(int id, boolean requireNonNull) { + Object obj = idToObject.get(id); + GraalError.guarantee(obj != null || id == 0 || !requireNonNull, "Could not resolve object id: %d", id); + return obj; + } + + void decodeMap(String encoded, BiConsumer putMethod) { + for (String e : splitSpaceSeparatedElements(encoded)) { + String[] keyValue = e.split(":"); + GraalError.guarantee(keyValue.length == 2, "Invalid encoded key:value: %s", e); + resolveId(keyValue[0], k -> resolveId(keyValue[1], v -> putMethod.accept(k, v))); + } + } + + private void addDecodedObject(int id, Object obj) { + Object conflict = idToObject.put(id, obj); + GraalError.guarantee(conflict == null, "Objects both have id %d: %s and %s", id, obj, conflict); + } + + private static void writeField(Field field, Object receiver, Object value) { + try { + field.set(receiver, value); + } catch (IllegalAccessException e) { + throw new GraalError(e); + } + } + + /** + * Action deferred due to unresolved object id. + */ + record Deferred(Runnable runnable, int lineNum) { + } + + List deferred; + int lineNum = -1; + + private Object decode(String encoded) { + deferred = new ArrayList<>(); + lineNum = 0; + + List lines = encoded.lines().toList(); + Iterator iter = lines.iterator(); + try { + while (iter.hasNext()) { + String line = iter.next(); + lineNum++; + int colon = line.indexOf(':'); + GraalError.guarantee(colon != -1, "Missing ':' in line: %s", line); + int id = Integer.parseInt(line.substring(0, colon)); + switch (line.charAt(colon + 1)) { + case '<': { + Matcher matcher = BUILTIN_LINE.matcher(line.substring(colon + 1)); + GraalError.guarantee(matcher.matches(), "Invalid builtin line: %s", line); + String className = matcher.group("class"); + String encodingName = matcher.group("encodingName"); + String value = matcher.group("value"); + Class clazz = loadClass(className); + Builtin builtin = getBuiltin(clazz); + GraalError.guarantee(builtin != null, "No builtin for %s: %s", className, line); + builtin.checkClass(clazz); + addDecodedObject(id, builtin.decode(this, clazz, encodingName, value)); + break; + } + case '[': { + Matcher matcher = ARRAY_LINE.matcher(line.substring(colon + 1)); + GraalError.guarantee(matcher.matches(), "Invalid array line: %s", line); + String componentTypeName = matcher.group("componentType"); + String[] elements = splitSpaceSeparatedElements(matcher.group("elements")); + switch (componentTypeName) { + case "boolean": { + boolean[] arr = new boolean[elements.length]; + for (int i = 0; i < elements.length; i++) { + arr[i] = Boolean.parseBoolean(elements[i]); + } + addDecodedObject(id, arr); + break; + } + case "byte": { + byte[] arr = new byte[elements.length]; + for (int i = 0; i < elements.length; i++) { + arr[i] = Byte.parseByte(elements[i]); + } + addDecodedObject(id, arr); + break; + } + case "char": { + throw GraalError.shouldNotReachHere("char[] should be handled by " + StringBuiltin.class); + } + case "short": { + short[] arr = new short[elements.length]; + for (int i = 0; i < elements.length; i++) { + arr[i] = Short.parseShort(elements[i]); + } + addDecodedObject(id, arr); + break; + } + case "int": { + int[] arr = new int[elements.length]; + for (int i = 0; i < elements.length; i++) { + arr[i] = Integer.parseInt(elements[i]); + } + addDecodedObject(id, arr); + break; + } + case "float": { + float[] arr = new float[elements.length]; + for (int i = 0; i < elements.length; i++) { + arr[i] = Float.parseFloat(elements[i]); + } + addDecodedObject(id, arr); + break; + } + case "long": { + long[] arr = new long[elements.length]; + for (int i = 0; i < elements.length; i++) { + arr[i] = Long.parseLong(elements[i]); + } + addDecodedObject(id, arr); + break; + } + case "double": { + double[] arr = new double[elements.length]; + for (int i = 0; i < elements.length; i++) { + arr[i] = Double.parseDouble(elements[i]); + } + addDecodedObject(id, arr); + break; + } + default: { + Class componentType = loadClass(componentTypeName); + Object[] arr = (Object[]) Array.newInstance(componentType, elements.length); + addDecodedObject(id, arr); + for (int i = 0; i < elements.length; i++) { + int elementId = Integer.parseInt(elements[i]); + int index = i; + resolveId(elementId, o -> arr[index] = o); + } + break; + } + } + break; + } + case '@': { + String fieldDesc = line.substring(colon + 2); + int lastDot = fieldDesc.lastIndexOf('.'); + GraalError.guarantee(lastDot != -1, "Invalid field name: %s", fieldDesc); + String className = fieldDesc.substring(0, lastDot); + String fieldName = fieldDesc.substring(lastDot + 1); + Class declaringClass = loadClass(className); + Field field = getField(declaringClass, fieldName); + addDecodedObject(id, readField(field, null)); + break; + } + case '{': { + Matcher matcher = OBJECT_LINE.matcher(line.substring(colon + 1)); + GraalError.guarantee(matcher.matches(), "Invalid object line: %s", line); + String className = matcher.group("class"); + int fieldCount = Integer.parseInt(matcher.group("fieldCount")); + Class clazz = loadClass(className); + Object obj = allocateInstance(clazz); + addDecodedObject(id, obj); + ClassInfo classInfo = classInfos.computeIfAbsent(clazz, ClassInfo::of); + for (int i = 0; i < fieldCount; i++) { + GraalError.guarantee(iter.hasNext(), "Truncated input"); + String fieldLine = iter.next(); + lineNum++; + Matcher fieldMatcher = FIELD_LINE.matcher(fieldLine); + GraalError.guarantee(fieldMatcher.matches(), "Invalid field line: %s", fieldLine); + String fieldDesc = fieldMatcher.group("desc"); + String value = fieldMatcher.group("value"); + Field field = classInfo.fields().get(fieldDesc); + GraalError.guarantee(field != null, "Unknown field: %s", fieldDesc); + Class type = field.getType(); + + int expectTypeId = Integer.parseInt(fieldMatcher.group("typeId")); + resolveId(expectTypeId, o -> checkFieldType(expectTypeId, field)); + + if (type.isPrimitive()) { + switch (type.getName()) { + case "boolean": { + writeField(field, obj, Boolean.parseBoolean(value)); + break; + } + case "byte": { + writeField(field, obj, Byte.parseByte(value)); + break; + } + case "char": { + writeField(field, obj, (char) Integer.parseInt(value)); + break; + } + case "short": { + writeField(field, obj, Short.parseShort(value)); + break; + } + case "int": { + writeField(field, obj, Integer.parseInt(value)); + break; + } + case "float": { + writeField(field, obj, Float.parseFloat(value)); + break; + } + case "long": { + writeField(field, obj, Long.parseLong(value)); + break; + } + case "double": { + writeField(field, obj, Double.parseDouble(value)); + break; + } + default: { + throw new GraalError("Unexpected primitive type: %s", type.getName()); + } + } + } else { + resolveId(value, o -> writeField(field, obj, o)); + } + } + break; + } + default: { + throw new GraalError("Invalid char after ':' in line: %s", line); + } + } + } + for (Deferred d : deferred) { + lineNum = d.lineNum(); + d.runnable().run(); + } + } catch (Throwable e) { + throw new GraalError(e, "Error on line %d: %s", lineNum, lines.get(lineNum - 1)); + } finally { + deferred = null; + lineNum = -1; + } + return getObject(1, true); + } + + void resolveId(String id, Consumer c) { + try { + resolveId(Integer.parseInt(id), c); + } catch (NumberFormatException e) { + throw new GraalError(e, "Invalid object id: %s", id); + } + } + + void resolveId(int id, Consumer c) { + if (id != 0) { + Object objValue = getObject(id, false); + if (objValue != null) { + c.accept(objValue); + } else { + deferred.add(new Deferred(() -> c.accept(getObject(id, true)), lineNum)); + } + } else { + c.accept(null); + } + } + + private void checkFieldType(int expectTypeId, Field field) { + Class actualType = field.getType(); + Class expectType = (Class) idToObject.get(expectTypeId); + GraalError.guarantee(actualType.equals(expectType), "Type of %s has changed: %s != %s", field, expectType, actualType); + } + + private static Object allocateInstance(Class clazz) { + try { + return UNSAFE.allocateInstance(clazz); + } catch (InstantiationException e) { + throw new GraalError(e); + } + } + } + + final Builtin getBuiltin(Class clazz) { + return getBuiltin(clazz, false); + } + + final Builtin getBuiltin(Class clazz, boolean onlyCheck) { + if (notBuiltins.contains(clazz)) { + return null; + } + Builtin b = builtinClasses.get(clazz); + if (b == null) { + for (var e : builtinClasses.entrySet()) { + if (e.getKey().isAssignableFrom(clazz)) { + b = e.getValue(); + break; + } + } + if (!onlyCheck) { + if (b == null) { + notBuiltins.add(clazz); + } else { + builtinClasses.put(clazz, b); + } + } + } + return b; + } + + static class Encoder extends ObjectCopier { + + final Map objectToId = new IdentityHashMap<>(); + final List objects = new ArrayList<>(); + + /** + * Map from values to static final fields. In a serialized object graph, references to such + * values are encoded with a reference to the field. + */ + final Map externalValues = new IdentityHashMap<>(); + + Encoder(List externalValues) { + objects.add(null); + objectToId.put(null, new ObjectID(0, null)); + for (Field f : externalValues) { + addExternalValue(f); + } + } + + private void addExternalValue(Field field) { + GraalError.guarantee(Modifier.isStatic(field.getModifiers()), "Field '%s' is not static. Only a static field can be used as known location for an instance.", field); + Object value = readField(field, null); + if (value == null) { + /* There is only one null, no need to know where it came from */ + return; + } + Field oldField = externalValues.put(value, field); + if (oldField != null) { + Object oldValue = readField(oldField, null); + GraalError.guarantee(oldValue == value, + "%s and %s have different values: %s != %s", field, oldField, value, oldValue); + } + + } + + private String encodeMap(UnmodifiableEconomicMap map) { + UnmodifiableMapCursor cursor = map.getEntries(); + StringBuilder value = new StringBuilder(); + while (cursor.advance()) { + if (!value.isEmpty()) { + value.append(" "); + } + value.append(makeId(cursor.getKey(), null).id()).append(":").append(makeId(cursor.getValue(), null).id()); + } + return value.toString(); + } + + void makeMapChildIds(EconomicMap map, ObjectPath objectPath) { + UnmodifiableMapCursor cursor = map.getEntries(); + while (cursor.advance()) { + Object key = cursor.getKey(); + String keyString = key instanceof String ? "\"" + key + '"' : String.valueOf(key); + makeId(key, objectPath.add("{key:" + keyString + "}")); + makeId(cursor.getValue(), objectPath.add("{" + keyString + "}")); + } + } + + /** + * Checks that {@code value} is not an instance of {@code type}. + * + * @param reason reason to use in the error message if the check fails + */ + static void checkIllegalValue(Class type, Object value, ObjectPath objectPath, String reason) { + if (type.isInstance(value)) { + throw new GraalError("Illegal instance of %s: %s%n Type: %s%n Value: %s%n Path: %s", + type.getName(), reason, value.getClass().getName(), value, objectPath); + } + } + + ObjectID makeId(Object obj, ObjectPath objectPath) { + if (!objectToId.containsKey(obj)) { + ObjectID id = new ObjectID(objects.size(), objectPath); + Field field = externalValues.get(obj); + if (field != null) { + objects.add(field); + objectToId.put(obj, id); + objectToId.put(field, id); + return id; + } + checkIllegalValue(Field.class, obj, objectPath, "Field type is used in object copying implementation"); + checkIllegalValue(FieldIntrospection.class, obj, objectPath, "Graal metadata type cannot be copied"); + + objects.add(obj); + objectToId.put(obj, id); + + Class clazz = obj.getClass(); + Builtin builtin = getBuiltin(clazz); + if (builtin != null) { + builtin.checkObject(obj); + builtin.makeChildIds(this, obj, objectPath); + return id; + } + if (clazz.isArray()) { + Class componentType = clazz.getComponentType(); + if (!componentType.isPrimitive()) { + Object[] objArray = (Object[]) obj; + int index = 0; + for (Object element : objArray) { + makeId(element, objectPath.add(index)); + index++; + } + } + } else { + checkIllegalValue(LocationIdentity.class, obj, objectPath, "must come from a static field"); + checkIllegalValue(HashSet.class, obj, objectPath, "hashes are typically not stable across VM executions"); + + ClassInfo classInfo = classInfos.computeIfAbsent(clazz, ClassInfo::of); + for (Field f : classInfo.fields().values()) { + makeId(f.getType(), objectPath.add(f.getName() + ":type")); + if (!f.getType().isPrimitive()) { + Object fieldValue = readField(f, obj); + makeId(fieldValue, objectPath.add(f.getName())); + } + } + } + + } + return objectToId.get(obj); + } + + private void encode(PrintStream out) { + for (int id = 1; id < objects.size(); id++) { + Object obj = objects.get(id); + Class clazz = obj.getClass(); + Builtin builtin = getBuiltin(clazz); + if (builtin != null) { + String encodingName = builtin.encodingName(obj); + String encoding = encodingName == null ? "" : ":" + encodingName; + out.printf("%d:<%s%s> = %s%n", id, clazz.getName(), encoding, builtin.encode(this, obj)); + } else if (clazz.isArray()) { + Class componentType = clazz.getComponentType(); + if (!componentType.isPrimitive()) { + String elements = Stream.of((Object[]) obj).map(this::getIdString).collect(Collectors.joining(" ")); + out.printf("%d:[%s] = %s%n", id, componentType.getName(), elements); + } else { + int length = Array.getLength(obj); + StringBuilder elements = new StringBuilder(length * 5); + for (int i = 0; i < length; i++) { + elements.append(' ').append(Array.get(obj, i)); + } + out.printf("%d:[%s] =%s%n", id, componentType.getName(), elements); + } + } else { + if (clazz == Field.class) { + Field field = (Field) obj; + out.printf("%d:@%s.%s%n", id, field.getDeclaringClass().getName(), field.getName()); + } else { + ClassInfo classInfo = classInfos.get(clazz); + out.printf("%d:{%s:%d}%n", id, clazz.getName(), classInfo.fields().size()); + for (var e : classInfo.fields().entrySet()) { + Field f = e.getValue(); + Object fValue = readField(f, obj); + Class fieldType = f.getType(); + int fieldTypeId = makeId(fieldType, null).id(); + if (!fieldType.isPrimitive()) { + fValue = getIdString(fValue); + } else if (fieldType == char.class) { + fValue = (int) (Character) fValue; + } + out.printf(" %s:%d = %s%n", e.getKey(), fieldTypeId, fValue); + } + } + } + } + } + + private String getIdString(Object o) { + GraalError.guarantee(objectToId.containsKey(o), "Unknown object: %s", o); + return String.valueOf(objectToId.get(o).id()); + } + } + + public static Object readField(Field field, Object receiver) { + try { + return field.get(receiver); + } catch (IllegalAccessException | Error e) { + throw new GraalError(e, "Error reading %s", field); + } + } + + /** + * Gets the declared field {@code fieldName} from {@code declaredClass}. The field is made + * accessible before returning. + * + * @throws GraalError if the field does not exist + */ + public static Field getField(Class declaredClass, String fieldName) { + try { + Field f = declaredClass.getDeclaredField(fieldName); + f.setAccessible(true); + return f; + } catch (NoSuchFieldException e) { + throw GraalError.shouldNotReachHere(e); + } + } + + /** + * Describes the path from a root object to a target object. That is, the sequence of field and + * array reads performed on the root object to access the target object. + * + * @param prefix the prefix path + * @param name the last field or array index read in the path + */ + record ObjectPath(ObjectPath prefix, Object name) { + /** + * Creates an object path for a root object. + * + * @param rootName names a reference to the root object (e.g. "[root]" or the qualified name + * of a static field) + */ + public static ObjectPath of(String rootName) { + return new ObjectPath(null, rootName); + } + + /** + * Extends this path with a field read. + * + * @param fieldName name of a field (or pseudo field) + * @return extended path + */ + ObjectPath add(String fieldName) { + return new ObjectPath(this, fieldName); + } + + /** + * Extends this path with an array index. + * + * @param index an array index + * @return extended path + */ + ObjectPath add(int index) { + return new ObjectPath(this, index); + } + + /** + * Gets the path as a string, starting at the root of the path. Examples: + * + *
+         * [root].{"encodedSnippets"}.snippetObjects.[16]
+         * StampFactory.stampCache.[1]
+         * 
+ */ + @Override + public String toString() { + List components = new ArrayList<>(); + for (ObjectPath p = this; p != null; p = p.prefix) { + if (p.name instanceof String s) { + components.add(s); + } else { + components.add("[" + p.name + "]"); + } + } + return String.join(".", components.reversed()); + } + } + + /** + * A unique int id for an object as well as the path by which it was (first) reached. + */ + record ObjectID(int id, ObjectPath path) { + } +} diff --git a/sdk/src/com.oracle.svm.core.annotate/src/com/oracle/svm/core/annotate/TargetClass.java b/sdk/src/com.oracle.svm.core.annotate/src/com/oracle/svm/core/annotate/TargetClass.java index 84854afb7240..6e15dc34be08 100644 --- a/sdk/src/com.oracle.svm.core.annotate/src/com/oracle/svm/core/annotate/TargetClass.java +++ b/sdk/src/com.oracle.svm.core.annotate/src/com/oracle/svm/core/annotate/TargetClass.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -47,6 +47,7 @@ import java.util.function.BooleanSupplier; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -140,6 +141,21 @@ */ String[] innerClass() default {}; + /** + * Specifies a custom classloader that will be used to look up the substitutee class name. + * + * @since 24.2 + */ + Class> classLoader() default NoClassLoaderProvider.class; + + /** + * Marker value for {@link #classLoader} that no custom classloader should be used. + * + * @since 24.2 + */ + interface NoClassLoaderProvider extends Supplier { + } + /** * Substitute only if all provided predicates are true (default: unconditional substitution that * is always included). diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/IsolateSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/IsolateSupport.java index ee5b9822b95c..9b4112bc6a44 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/IsolateSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/IsolateSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -58,4 +58,12 @@ public interface IsolateSupport { void detachThread(IsolateThread thread) throws IsolateException; void tearDownIsolate(IsolateThread thread) throws IsolateException; + + /** + * Gets an identifier for the current isolate that is guaranteed to be unique for the first + * {@code 2^64 - 1} isolates in the process. + * + * @return a non-zero value + */ + long getIsolateID(); } diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 3820e477a8fc..e9b32b3fd9cf 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -1310,13 +1310,74 @@ "jacoco" : "exclude", }, + "com.oracle.svm.graal.hotspot" : { + "subDir": "src", + "sourceDirs" : [ + "src" + ], + "dependencies": [ + "sdk:JNIUTILS", + "compiler:GRAAL", + "SVM", + ], + "requiresConcealed" : { + "jdk.internal.vm.ci" : [ + "jdk.vm.ci.services", + "jdk.vm.ci.runtime", + ], + }, + "annotationProcessors": [ + "compiler:GRAAL_PROCESSOR", + ], + "checkstyle" : "com.oracle.svm.hosted", + "javaCompliance" : "21+", + "workingSets" : "SVM", + "jacoco" : "exclude", + }, + + "com.oracle.svm.graal.hotspot.guestgraal" : { + "subDir": "src", + "sourceDirs" : [ + "src", + "resources", + ], + "dependencies": [ + "com.oracle.svm.graal.hotspot", + "sdk:NATIVEIMAGE", + "sdk:NATIVEBRIDGE", + "compiler:GRAAL", + "SVM", + ], + "requires": [ + "java.management", + "jdk.management", + ], + "requiresConcealed" : { + "java.base" : [ + "jdk.internal.jimage", + "jdk.internal.misc", + ], + "jdk.internal.vm.ci" : [ + "jdk.vm.ci.services", + "jdk.vm.ci.runtime", + ], + }, + "annotationProcessors": [ + "compiler:GRAAL_PROCESSOR", + ], + "checkstyle" : "com.oracle.svm.hosted", + "javaCompliance" : "21+", + "workingSets" : "SVM", + "jacoco" : "exclude", + }, + "com.oracle.svm.graal.hotspot.libgraal" : { "subDir": "src", "sourceDirs": ["src"], "dependencies": [ + "com.oracle.svm.graal.hotspot", "com.oracle.svm.graal", "compiler:GRAAL", - "sdk:JNIUTILS", "sdk:NATIVEBRIDGE", ], "requires" : [ @@ -1819,7 +1880,8 @@ "com.oracle.svm.graal.hotspot.libgraal", ], "overlaps" : [ - "LIBRARY_SUPPORT" + "LIBRARY_SUPPORT", + "GUESTGRAAL_LIBRARY" ], "distDependencies": [ "SVM", @@ -1830,6 +1892,21 @@ "maven": False, }, + "GUESTGRAAL_LIBRARY": { + "subDir": "src", + "description" : "GuestGraal HotSpot Graal library support", + "javaCompliance" : "21+", + "dependencies": [ + "com.oracle.svm.graal.hotspot.guestgraal", + ], + "distDependencies": [ + "SVM", + "sdk:JNIUTILS", + "sdk:NATIVEBRIDGE", + ], + "maven": False, + }, + # # Native Projects # diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapInstance.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapInstance.java index c7c66d9e9a19..a2d1a845f70d 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapInstance.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapInstance.java @@ -121,6 +121,7 @@ Object[] getFieldValues() { * is marked as read. */ void setFieldTask(AnalysisField field, AnalysisFuture task) { + Objects.requireNonNull(task); arrayHandle.setVolatile(getFieldValues(), field.getPosition(), task); } @@ -130,6 +131,7 @@ void setFieldTask(AnalysisField field, AnalysisFuture task) { * and replaced. */ public void setFieldValue(AnalysisField field, JavaConstant value) { + Objects.requireNonNull(value); arrayHandle.setVolatile(getFieldValues(), field.getPosition(), value); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/CallTreePrinter.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/CallTreePrinter.java index db223a78457c..655fb64ce19c 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/CallTreePrinter.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/CallTreePrinter.java @@ -107,7 +107,7 @@ static class MethodNodeReference implements Node { @Override public String format() { - return methodNode.method.format(METHOD_FORMAT) + " id-ref=" + methodNode.id; + return ReportUtils.loaderName(methodNode.method.getDeclaringClass()) + ':' + methodNode.method.format(METHOD_FORMAT) + " id-ref=" + methodNode.id; } } @@ -137,7 +137,7 @@ void addInvoke(InvokeNode invoke) { @Override public String format() { - return method.format(METHOD_FORMAT) + " id=" + id; + return ReportUtils.loaderName(method.getDeclaringClass()) + ':' + method.format(METHOD_FORMAT) + " id=" + id; } } @@ -317,7 +317,7 @@ private static void printCallTreeNode(PrintWriter out, String prefix, MethodNode private void printUsedMethods(PrintWriter out) { List methodsList = new ArrayList<>(); for (AnalysisMethod method : methodToNode.keySet()) { - methodsList.add(method.format("%H.%n(%p):%r")); + methodsList.add(ReportUtils.loaderName(method.getDeclaringClass()) + ':' + method.format(METHOD_FORMAT)); } methodsList.sort(null); for (String name : methodsList) { @@ -335,8 +335,8 @@ private void printClasses(PrintWriter out, boolean packageNameOnly) { public Set classesSet(boolean packageNameOnly) { Set classSet = new HashSet<>(); - for (AnalysisMethod method : methodToNode.keySet()) { - String name = method.getDeclaringClass().toJavaName(true); + for (AnalysisType type : usedAnalysisTypes()) { + String name = type.toJavaName(true); if (packageNameOnly) { name = packagePrefix(name); if (LambdaUtils.isLambdaClassName(name)) { @@ -344,7 +344,15 @@ public Set classesSet(boolean packageNameOnly) { name = packagePrefix(name); } } - classSet.add(name); + classSet.add(ReportUtils.loaderName(type) + ':' + name); + } + return classSet; + } + + public Set usedAnalysisTypes() { + Set classSet = new HashSet<>(); + for (AnalysisMethod method : methodToNode.keySet()) { + classSet.add(method.getDeclaringClass()); } return classSet; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ObjectTreePrinter.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ObjectTreePrinter.java index b23fa89f1cfe..1861df62ad54 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ObjectTreePrinter.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ObjectTreePrinter.java @@ -45,9 +45,11 @@ import com.oracle.graal.pointsto.ObjectScanner; import com.oracle.graal.pointsto.ObjectScanningObserver; import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import jdk.graal.compiler.options.OptionValues; +import jdk.vm.ci.code.BytecodePosition; import jdk.vm.ci.common.JVMCIError; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; @@ -77,16 +79,16 @@ static class RootSource { } String format() { - if (source instanceof ResolvedJavaField) { - ResolvedJavaField field = (ResolvedJavaField) source; - return field.format("%H.%n:%T"); - } else if (source instanceof ResolvedJavaMethod) { - ResolvedJavaMethod method = (ResolvedJavaMethod) source; - return method.format("%H.%n(%p)"); - } else if (source != null) { - return source.toString(); - } - throw JVMCIError.shouldNotReachHere("null source"); + return format(source); + } + + private static String format(Object srcObj) { + return switch (srcObj) { + case AnalysisField field -> ReportUtils.loaderName(field.getDeclaringClass()) + ':' + field.format("%H.%n:%T"); + case AnalysisMethod method -> ReportUtils.loaderName(method.getDeclaringClass()) + ':' + method.format("%H.%n(%p)"); + case BytecodePosition bcp -> "%s [bci: %d]".formatted(format(bcp.getMethod()), bcp.getBCI()); + default -> throw JVMCIError.shouldNotReachHere("unknown srcObj"); + }; } } @@ -396,6 +398,10 @@ public void forScannedConstant(JavaConstant scannedValue, ScanReason reason) { static String constantAsString(BigBang bb, JavaConstant constant) { Object object = constantAsObject(bb, constant); + String loaderPrefix = ""; + if (object != null) { + loaderPrefix = ReportUtils.loaderName(object.getClass().getClassLoader()) + ':'; + } if (object instanceof String) { String str = (String) object; str = escape(str); @@ -403,9 +409,9 @@ static String constantAsString(BigBang bb, JavaConstant constant) { str = str.substring(0, 10); str = str + "..."; } - return "\"" + str + "\""; + return loaderPrefix + "\"" + str + "\""; } else { - return escape(JavaKind.Object.format(object)); + return loaderPrefix + escape(JavaKind.Object.format(object)); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ReportUtils.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ReportUtils.java index ddacb8868156..280e10433dad 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ReportUtils.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ReportUtils.java @@ -319,4 +319,24 @@ private static void followInput(TypeFlow flow, AnalysisType type, String inde } } } + + public static String loaderName(AnalysisType type) { + var declaringJavaClass = type.getJavaClass(); + if (declaringJavaClass == null) { + return "err"; + } + return loaderName(declaringJavaClass.getClassLoader()); + } + + public static String loaderName(ClassLoader loader) { + if (loader == null) { + return "null"; + } + var loaderName = loader.getName(); + if (loaderName == null || loaderName.isBlank()) { + return loader.getClass().getName(); + } else { + return loaderName; + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/IsolateSupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/IsolateSupportImpl.java index a64bc2ac4104..90777b9a6100 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/IsolateSupportImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/IsolateSupportImpl.java @@ -26,6 +26,8 @@ import java.util.List; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; import org.graalvm.nativeimage.Isolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Isolates.CreateIsolateParameters; @@ -35,6 +37,7 @@ import org.graalvm.nativeimage.c.type.CCharPointerPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.nativeimage.impl.IsolateSupport; +import org.graalvm.word.Pointer; import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; @@ -47,6 +50,8 @@ import com.oracle.svm.core.os.MemoryProtectionProvider; import com.oracle.svm.core.os.MemoryProtectionProvider.UnsupportedDomainException; +import static org.graalvm.word.LocationIdentity.ANY_LOCATION; + @AutomaticallyRegisteredImageSingleton(IsolateSupport.class) public final class IsolateSupportImpl implements IsolateSupport { private static final String ISOLATES_DISABLED_MESSAGE = "Spawning of multiple isolates is disabled, use " + @@ -167,4 +172,31 @@ private static void throwOnError(int code) { throw new IsolateException(message); } } + + private static final CGlobalData nextIsolateId = CGlobalDataFactory.createWord((Pointer) WordFactory.unsigned(1L)); + + private volatile long isolateId = 0; + + @Override + public long getIsolateID() { + if (isolateId == 0) { + synchronized (this) { + if (isolateId == 0) { + Pointer p = nextIsolateId.get(); + long value; + long nextValue; + do { + value = p.readLong(0); + nextValue = value + 1; + if (nextValue == 0) { + // Avoid setting id to reserved 0 value after long integer overflow + nextValue = 1; + } + } while (p.compareAndSwapLong(0, value, nextValue, ANY_LOCATION) != value); + isolateId = value; + } + } + } + return isolateId; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java index 2e3dccc75dad..18fd7e0f1cb0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java @@ -69,6 +69,17 @@ public void registerClass(ConfigurationCondition condition, Class clazz) { ConditionalRuntimeValue exisingEntry = knownClasses.get(name); Object currentValue = exisingEntry == null ? null : exisingEntry.getValueUnconditionally(); + /* TODO: Remove workaround once GR-53985 is implemented */ + if (currentValue instanceof Class currentClazz && clazz.getClassLoader() != currentClazz.getClassLoader()) { + /* Ensure runtime lookup of GuestGraalClassLoader classes */ + if (isGuestGraalClass(currentClazz)) { + return; + } + if (isGuestGraalClass(clazz)) { + currentValue = null; + } + } + if (currentValue == null || // never seen currentValue == NEGATIVE_QUERY || currentValue == clazz) { @@ -94,6 +105,14 @@ accessible through the builder class loader, and it was already registered by na } } + private static boolean isGuestGraalClass(Class clazz) { + var loader = clazz.getClassLoader(); + if (loader == null) { + return false; + } + return "GuestGraalClassLoader".equals(loader.getName()); + } + public static ConditionalRuntimeValue updateConditionalValue(ConditionalRuntimeValue existingConditionalValue, Object newValue, ConfigurationCondition additionalCondition) { if (existingConditionalValue == null) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java index 730742e77080..910fbbbfb5eb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.jni.functions; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Isolate; import org.graalvm.nativeimage.LogHandler; import org.graalvm.nativeimage.StackValue; @@ -34,6 +35,7 @@ import org.graalvm.nativeimage.c.type.CCharPointerPointer; import org.graalvm.nativeimage.c.type.CIntPointer; import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.nativeimage.impl.IsolateSupport; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; @@ -78,8 +80,6 @@ import com.oracle.svm.core.thread.PlatformThreads; import com.oracle.svm.core.util.Utf8; -import jdk.graal.compiler.serviceprovider.IsolateUtil; - /** * Implementation of the JNI invocation API for interacting with a Java VM without having an * existing context and without linking against the Java VM library beforehand. @@ -212,6 +212,10 @@ static int enter(JNIJavaVMPointer vmBuf, JNIEnvironmentPointer penv, JNIJavaVMIn | | 0-terminated C string describing the error if a description is available, | | | otherwise extraInfo is set to null. | |--------------------|-----------------------------------------------------------------------------------| + | _javavm_id | extraInfo is a "unsigned long*" value. | + | | A non-zero identifier for the current isolate that is guaranteed to be unique for | + | | the first 2^64 - 1 isolates in the process is returned in *value. | + |--------------------|-----------------------------------------------------------------------------------| * * * @see LogHandler @@ -392,7 +396,7 @@ private static int finishInitialization0(JNIJavaVMPointer vmBuf, JNIEnvironmentP JNIJavaVM javaVm = JNIFunctionTables.singleton().getGlobalJavaVM(); JNIJavaVMList.addJavaVM(javaVm); if (javaVmIdPointer.isNonNull()) { - long javaVmId = IsolateUtil.getIsolateID(); + long javaVmId = ImageSingletons.lookup(IsolateSupport.class).getIsolateID(); javaVmIdPointer.write(WordFactory.pointer(javaVmId)); } RuntimeSupport.getRuntimeSupport().addTearDownHook(new RuntimeSupport.Hook() { diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/resources/META-INF/native-image/com.oracle.svm.graal.hotspot.guestgraal/native-image.properties b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/resources/META-INF/native-image/com.oracle.svm.graal.hotspot.guestgraal/native-image.properties new file mode 100644 index 000000000000..76901326f985 --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/resources/META-INF/native-image/com.oracle.svm.graal.hotspot.guestgraal/native-image.properties @@ -0,0 +1,26 @@ +JavaArgs = \ + -ea -esa \ + --add-exports=jdk.graal.compiler/jdk.graal.compiler.options=ALL-UNNAMED \ + --add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.option=ALL-UNNAMED + +Args = \ + -Djdk.vm.ci.services.aot=true \ + -Dtruffle.TruffleRuntime= \ + --add-exports=org.graalvm.nativeimage.base/com.oracle.svm.util=ALL-UNNAMED \ + --features=com.oracle.svm.graal.hotspot.guestgraal.GuestGraalFeature \ + --enable-monitoring=heapdump \ + --no-fallback \ + --shared \ + -o jvmcicompiler \ + -H:+UnlockExperimentalVMOptions \ + -H:+ReportExceptionStackTraces \ + -H:-UseServiceLoaderFeature \ + -H:+AllowFoldMethods \ + -H:+JNIEnhancedErrorCodes \ + -H:InitialCollectionPolicy=LibGraal \ + -H:+PreserveFramePointer \ + -H:-DeleteLocalSymbols \ + -H:HeapDumpDefaultFilenamePrefix=libgraal_pid \ + -H:-AllowVMInternalThreads \ + -R:-AutomaticReferenceHandling \ + -H:-UseContainerSupport \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraal.java b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraal.java new file mode 100644 index 000000000000..9a21c7e29b36 --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraal.java @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.graal.hotspot.guestgraal; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.invoke.MethodHandle; +import java.lang.management.ManagementFactory; +import java.util.Arrays; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Supplier; + +import jdk.graal.compiler.debug.GraalError; +import org.graalvm.jniutils.JNI; +import org.graalvm.jniutils.JNI.JByteArray; +import org.graalvm.jniutils.JNI.JClass; +import org.graalvm.jniutils.JNI.JNIEnv; +import org.graalvm.jniutils.JNI.JObject; +import org.graalvm.jniutils.JNI.JObjectArray; +import org.graalvm.jniutils.JNI.JString; +import org.graalvm.jniutils.JNIMethodScope; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Isolate; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.c.function.CEntryPoint; +import org.graalvm.nativeimage.c.function.CEntryPoint.Builtin; +import org.graalvm.nativeimage.c.function.CEntryPoint.IsolateContext; +import org.graalvm.nativeimage.c.type.CTypeConversion; +import org.graalvm.nativeimage.impl.IsolateSupport; +import org.graalvm.word.PointerBase; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.heap.Heap; +import com.sun.management.ThreadMXBean; + +import jdk.internal.misc.Unsafe; + +/** + * Encapsulates {@link CEntryPoint} implementations as well as method handles for invoking guest + * Graal and JVMCI functionality via {@link MethodHandle}s. The method handles are only invoked in + * static methods which allows Native Image to fold them to direct calls to the method handle + * targets. + */ +final class GuestGraal { + + private final MethodHandle getJNIEnv; + private final MethodHandle getSavedProperty; + private final MethodHandle ttyPrintf; + private final MethodHandle compileMethod; + + /** + * Returns the {@link GuestGraal} instance registered in the {@link ImageSingletons}. + */ + private static GuestGraal singleton() { + return ImageSingletons.lookup(GuestGraal.class); + } + + GuestGraal(Map handles) { + this.getJNIEnv = handles.get("getJNIEnv"); + this.getSavedProperty = handles.get("getSavedProperty"); + this.ttyPrintf = handles.get("ttyPrintf"); + this.compileMethod = handles.get("compileMethod"); + } + + /** + * Calls {@code jdk.graal.compiler.hotspot.guestgraal.RunTime#getJNIEnv()}. + */ + static JNI.JNIEnv getJNIEnv() { + try { + long raw = (long) singleton().getJNIEnv.invoke(); + return WordFactory.unsigned(raw); + } catch (RuntimeException | Error e) { + throw e; + } catch (Throwable e) { + throw new GraalError(e); + } + } + + /** + * Calls {@code jdk.graal.compiler.serviceprovider.GraalServices#getSavedProperty(String)}. + */ + static String getSavedProperty(String name) { + try { + return (String) singleton().getSavedProperty.invoke(name); + } catch (RuntimeException | Error e) { + throw e; + } catch (Throwable e) { + throw new GraalError(e); + } + } + + /** + * Calls {@code jdk.graal.compiler.debug.TTY#printf(String, Object...)}. + */ + static void ttyPrintf(String format, Object... args) { + try { + singleton().ttyPrintf.invoke(format, args); + } catch (RuntimeException | Error e) { + throw e; + } catch (Throwable e) { + throw new GraalError(e); + } + } + + /** + * The implementation of + * {@code jdk.graal.compiler.hotspot.test.LibGraalCompilationDriver#compileMethodInLibgraal}. + * + * @param methodHandle the method to be compiled. This is a handle to a + * {@code HotSpotResolvedJavaMethod} in HotSpot's heap. A value of 0L can be passed + * to use this method for the side effect of initializing a + * {@code HotSpotGraalCompiler} instance without doing any compilation. + * @param useProfilingInfo specifies if profiling info should be used during the compilation + * @param installAsDefault specifies if the compiled code should be installed for the + * {@code Method*} associated with {@code methodHandle} + * @param printMetrics specifies if global metrics should be printed and reset + * @param optionsAddress native byte buffer storing a serialized {@code OptionValues} object + * @param optionsSize the number of bytes in the buffer + * @param optionsHash hash code of bytes in the buffer (computed with + * {@link Arrays#hashCode(byte[])}) + * @param stackTraceAddress a native buffer in which a serialized stack trace can be returned. + * The caller will only read from this buffer if this method returns 0. A returned + * serialized stack trace is returned in this buffer with the following format: + * + *
+     *            struct {
+     *                int   length;
+     *                byte  data[length]; // Bytes from a stack trace printed to a ByteArrayOutputStream.
+     *            }
+     *            
+ * + * where {@code length} truncated to {@code stackTraceCapacity - 4} if necessary + * + * @param stackTraceCapacity the size of the stack trace buffer + * @param timeAndMemBufferAddress 16-byte native buffer to store result of time and memory + * measurements of the compilation + * @param profilePathBufferAddress native buffer containing a 0-terminated C string representing + * {@code Options#LoadProfiles} path. + * @return a handle to a {@code InstalledCode} in HotSpot's heap or 0 if compilation failed + */ + @SuppressWarnings({"unused", "try"}) + @CEntryPoint(name = "Java_jdk_graal_compiler_hotspot_test_LibGraalCompilationDriver_compileMethodInLibgraal", include = GuestGraalFeature.IsEnabled.class) + private static long compileMethod(JNIEnv jniEnv, + PointerBase jclass, + @CEntryPoint.IsolateThreadContext long isolateThread, + long methodHandle, + boolean useProfilingInfo, + boolean installAsDefault, + boolean printMetrics, + boolean eagerResolving, + long optionsAddress, + int optionsSize, + int optionsHash, + long stackTraceAddress, + int stackTraceCapacity, + long timeAndMemBufferAddress, + long profilePathBufferAddress) { + try (JNIMethodScope jniScope = new JNIMethodScope("compileMethod", jniEnv)) { + if (methodHandle == 0L) { + return 0L; + } + String profileLoadPath; + if (profilePathBufferAddress > 0) { + profileLoadPath = CTypeConversion.toJavaString(WordFactory.pointer(profilePathBufferAddress)); + } else { + profileLoadPath = null; + } + BiConsumer timeAndMemConsumer; + Supplier currentThreadAllocatedBytes; + if (timeAndMemBufferAddress != 0) { + timeAndMemConsumer = (timeSpent, bytesAllocated) -> { + Unsafe.getUnsafe().putLong(timeAndMemBufferAddress, bytesAllocated); + Unsafe.getUnsafe().putLong(timeAndMemBufferAddress + 8, timeSpent); + }; + ThreadMXBean threadMXBean = (ThreadMXBean) ManagementFactory.getThreadMXBean(); + currentThreadAllocatedBytes = () -> threadMXBean.getCurrentThreadAllocatedBytes(); + } else { + timeAndMemConsumer = null; + currentThreadAllocatedBytes = null; + } + + return (long) singleton().compileMethod.invoke(methodHandle, useProfilingInfo, + installAsDefault, printMetrics, eagerResolving, + optionsAddress, optionsSize, optionsHash, + profileLoadPath, timeAndMemConsumer, currentThreadAllocatedBytes); + } catch (Throwable t) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + t.printStackTrace(new PrintStream(baos)); + byte[] stackTrace = baos.toByteArray(); + int length = Math.min(stackTraceCapacity - Integer.BYTES, stackTrace.length); + Unsafe.getUnsafe().putInt(stackTraceAddress, length); + Unsafe.getUnsafe().copyMemory(stackTrace, Unsafe.ARRAY_BYTE_BASE_OFFSET, null, stackTraceAddress + Integer.BYTES, length); + return 0L; + } finally { + /* + * libgraal doesn't use a dedicated reference handler thread, so we trigger the + * reference handling manually when a compilation finishes. + */ + doReferenceHandling(); + } + } + + /** + * Since reference handling is synchronous in libgraal, explicitly perform it here and then run + * any code which is expecting to process a reference queue to let it clean up. + */ + static void doReferenceHandling() { + Heap.getHeap().doReferenceHandling(); + synchronized (GuestGraalJVMCISubstitutions.Target_jdk_vm_ci_hotspot_Cleaner.class) { + GuestGraalJVMCISubstitutions.Target_jdk_vm_ci_hotspot_Cleaner.clean(); + } + } +} + +final class GuestGraalLibGraalScope { + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_LibGraalScope_getIsolateThreadIn", builtin = Builtin.GET_CURRENT_THREAD) + private static native IsolateThread getIsolateThreadIn(PointerBase env, PointerBase hsClazz, @IsolateContext Isolate isolate); + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_LibGraalScope_attachThreadTo", builtin = Builtin.ATTACH_THREAD) + static native long attachThreadTo(PointerBase env, PointerBase hsClazz, @IsolateContext long isolate); + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_LibGraalScope_detachThreadFrom", builtin = Builtin.DETACH_THREAD) + static native void detachThreadFrom(PointerBase env, PointerBase hsClazz, @CEntryPoint.IsolateThreadContext long isolateThread); + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_LibGraalScope_getIsolateId") + @SuppressWarnings("unused") + public static long getIsolateId(PointerBase env, PointerBase jclass, @CEntryPoint.IsolateThreadContext long isolateThreadId) { + return ImageSingletons.lookup(IsolateSupport.class).getIsolateID(); + } +} + +final class GuestGraalTruffleToLibGraalEntryPoints { + @SuppressWarnings({"unused", "try"}) + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_initializeIsolate") + public static void initializeIsolate(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, JClass runtimeClass) { + } + + @SuppressWarnings({"unused", "try"}) + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_registerRuntime") + public static boolean registerRuntime(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, JObject truffleRuntime) { + return false; + } + + @SuppressWarnings({"unused", "try"}) + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_initializeRuntime") + public static long initializeRuntime(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, + JObject truffleRuntime, JClass hsClassLoaderDelegate) { + return 0L; + } + + @SuppressWarnings({"unused", "try"}) + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_newCompiler") + public static long newCompiler(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long truffleRuntimeHandle) { + return 0; + } + + @SuppressWarnings("unused") + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_initializeCompiler") + public static void initializeCompiler(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long compilerHandle, JObject hsCompilable, + boolean firstInitialization) { + } + + @SuppressWarnings({"unused", "try"}) + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_getCompilerConfigurationFactoryName") + public static JString getCompilerConfigurationFactoryName(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long truffleRuntimeHandle) { + return WordFactory.nullPointer(); + } + + @SuppressWarnings({"unused", "try"}) + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_doCompile") + public static void doCompile(JNIEnv env, + JClass hsClazz, + @CEntryPoint.IsolateThreadContext long isolateThreadId, + long compilerHandle, + JObject hsTask, + JObject hsCompilable, + JObject hsListener) { + } + + @SuppressWarnings({"unused", "try"}) + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_shutdown") + public static void shutdown(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long handle) { + } + + @SuppressWarnings({"unused", "try"}) + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_installTruffleCallBoundaryMethod") + public static void installTruffleCallBoundaryMethod(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long handle, long methodHandle) { + } + + @SuppressWarnings({"unused", "try"}) + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_installTruffleReservedOopMethod") + public static void installTruffleReservedOopMethod(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long handle, long methodHandle) { + } + + @SuppressWarnings("unused") + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_pendingTransferToInterpreterOffset") + public static int pendingTransferToInterpreterOffset(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long handle, JObject hsCompilable) { + return 0; + } + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_getSuppliedString") + @SuppressWarnings({"unused", "unchecked", "try"}) + public static JString getString(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long handle) { + return WordFactory.nullPointer(); + } + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_getNodeCount") + @SuppressWarnings({"unused", "try"}) + public static int getNodeCount(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long handle) { + return 0; + } + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_getNodeTypes") + @SuppressWarnings({"unused", "try"}) + public static JObjectArray getNodeTypes(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long handle, boolean simpleNames) { + return WordFactory.nullPointer(); + } + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_getTargetCodeSize") + @SuppressWarnings({"unused", "try"}) + public static int getTargetCodeSize(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long handle) { + return 0; + } + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_getTotalFrameSize") + @SuppressWarnings({"unused", "try"}) + public static int getTotalFrameSize(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long handle) { + return 0; + } + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_getExceptionHandlersCount") + @SuppressWarnings({"unused", "try"}) + public static int getExceptionHandlersCount(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long handle) { + return 0; + } + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_getInfopointsCount") + @SuppressWarnings({"unused", "try"}) + public static int getInfopointsCount(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long handle) { + return 0; + } + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_getInfopoints") + @SuppressWarnings({"unused", "try"}) + public static JObjectArray getInfopoints(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long handle) { + return WordFactory.nullPointer(); + } + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_listCompilerOptions") + @SuppressWarnings({"unused", "try"}) + public static JByteArray listCompilerOptions(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId) { + return WordFactory.nullPointer(); + } + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_compilerOptionExists") + @SuppressWarnings({"unused", "try"}) + public static boolean existsCompilerOption(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, JString optionName) { + return false; + } + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_validateCompilerOption") + @SuppressWarnings({"unused", "try"}) + public static JString validateCompilerOption(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, JString optionName, JString optionValue) { + return WordFactory.nullPointer(); + } + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_getMarksCount") + @SuppressWarnings({"unused", "try"}) + public static int getMarksCount(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long handle) { + return 0; + } + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_getDataPatchesCount") + @SuppressWarnings({"unused", "try"}) + public static int getDataPatchesCount(JNIEnv env, JClass hsClazz, @CEntryPoint.IsolateThreadContext long isolateThreadId, long handle) { + return 0; + } + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_purgePartialEvaluationCaches") + @SuppressWarnings({"unused", "try"}) + public static void purgePartialEvaluationCaches(JNIEnv env, JClass hsClass, @CEntryPoint.IsolateThreadContext long isolateThreadId, long compilerHandle) { + } + + @CEntryPoint(name = "Java_com_oracle_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_getCompilerVersion") + @SuppressWarnings({"unused", "try"}) + public static JString getCompilerVersion(JNIEnv env, JClass hsClass, @CEntryPoint.IsolateThreadContext long isolateThreadId) { + return WordFactory.nullPointer(); + } +} diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalClassLoader.java b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalClassLoader.java new file mode 100644 index 000000000000..346a81dd70c4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalClassLoader.java @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.graal.hotspot.guestgraal; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.module.ModuleDescriptor; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; + +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.util.VMError; + +import com.oracle.svm.util.ModuleSupport; +import jdk.internal.jimage.BasicImageReader; +import jdk.internal.jimage.ImageLocation; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +/** + * A classloader, that reads class files and resources from a jimage file at image build time. + */ +public class GuestGraalClassLoader extends ClassLoader { + + /** + * Reader for the image. + */ + @Platforms(Platform.HOSTED_ONLY.class) // + private final BasicImageReader imageReader; + + /** + * Map from the name of a resource (without module qualifier) to its path in the image. + */ + @Platforms(Platform.HOSTED_ONLY.class) // + private final Map resources = new HashMap<>(); + + /** + * Map from the {@linkplain Class#forName(String) name} of a class to the image path of its + * class file. + */ + @Platforms(Platform.HOSTED_ONLY.class) // + private final Map classes = new HashMap<>(); + + /** + * Map from a service name to a list of providers. + */ + @Platforms(Platform.HOSTED_ONLY.class) // + private final Map> services = new HashMap<>(); + + /** + * Modules in which Graal classes and their dependencies are defined. + */ + private static final Set MODULE_PREFIXES = Set.of( + "/jdk.internal.vm.ci/", + "/org.graalvm.collections/", + "/org.graalvm.word/", + "/jdk.graal.compiler/", + "/org.graalvm.truffle.compiler/"); + + static { + ClassLoader.registerAsParallelCapable(); + } + + /** + * @param imagePath path to the runtime image of a Java installation + */ + @Platforms(Platform.HOSTED_ONLY.class) + GuestGraalClassLoader(Path imagePath) { + super("GuestGraalClassLoader", null); + try { + // Need access to jdk.internal.jimage + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, getClass(), false, + "java.base", "jdk.internal.jimage"); + this.imageReader = BasicImageReader.open(imagePath); + for (var entry : imageReader.getEntryNames()) { + int secondSlash = entry.indexOf('/', 1); + if (secondSlash != -1) { + String modulePrefix = entry.substring(0, secondSlash + 1); + if (MODULE_PREFIXES.contains(modulePrefix)) { + String resource = entry.substring(secondSlash + 1); + resources.put(resource, entry); + if (resource.endsWith(".class")) { + String className = resource.substring(0, resource.length() - ".class".length()).replace('/', '.'); + classes.put(className, entry); + if (resource.equals("module-info.class")) { + ModuleDescriptor md = ModuleDescriptor.read(imageReader.getResourceBuffer(imageReader.findLocation(entry))); + for (var p : md.provides()) { + services.computeIfAbsent(p.service(), k -> new ArrayList<>(p.providers())); + } + } + } + } + } + } + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (!SubstrateUtil.HOSTED || !classes.containsKey(name)) { + return super.loadClass(name, resolve); + } + synchronized (getClassLoadingLock(name)) { + Class c = findLoadedClass(name); + if (c != null) { + return c; + } + return findClass(name); + } + } + + @Platforms(Platform.HOSTED_ONLY.class) + Class loadClassOrFail(String name) { + try { + return loadClass(name); + } catch (ClassNotFoundException e) { + throw VMError.shouldNotReachHere("%s unable to load class '%s'", getName(), name); + } + } + + @Override + protected Class findClass(final String name) + throws ClassNotFoundException { + if (SubstrateUtil.HOSTED) { + String path = name.replace('.', '/').concat(".class"); + + String pathInImage = resources.get(path); + if (pathInImage != null) { + ImageLocation location = imageReader.findLocation(pathInImage); + if (location != null) { + ByteBuffer bb = Objects.requireNonNull(imageReader.getResourceBuffer(location)); + ProtectionDomain pd = null; + return super.defineClass(name, bb, pd); + } + } + } + throw new ClassNotFoundException(name); + } + + /** + * Name of the protocol for accessing a {@link java.util.ServiceLoader} provider-configuration + * file named by the {@code "META-INF/services/"} convention. + */ + private static final String SERVICE_PROTOCOL = "service-config"; + + /** + * Name of the protocol for accessing entries in {@link #resources}. + */ + private static final String RESOURCE_PROTOCOL = "resource"; + + @Platforms(Platform.HOSTED_ONLY.class) // + private URLStreamHandler serviceHandler; + + @Override + protected URL findResource(String name) { + if (SubstrateUtil.HOSTED) { + URLStreamHandler handler = this.serviceHandler; + if (handler == null) { + this.serviceHandler = handler = new ImageURLStreamHandler(); + } + if (name.startsWith("META-INF/services/")) { + String service = name.substring("META-INF/services/".length()); + if (services.containsKey(service)) { + try { + var uri = new URI(SERVICE_PROTOCOL, service, null); + return URL.of(uri, handler); + } catch (URISyntaxException | MalformedURLException e) { + return null; + } + } + } else { + String path = resources.get(name); + if (path != null) { + try { + var uri = new URI(RESOURCE_PROTOCOL, name, null); + return URL.of(uri, handler); + } catch (URISyntaxException | MalformedURLException e) { + return null; + } + } + } + } + return null; + } + + @Override + protected Enumeration findResources(String name) throws IOException { + if (!SubstrateUtil.HOSTED) { + return Collections.emptyEnumeration(); + } + return new Enumeration<>() { + private URL next = findResource(name); + + @Override + public boolean hasMoreElements() { + return (next != null); + } + + @Override + public URL nextElement() { + if (next == null) { + throw new NoSuchElementException(); + } + URL u = next; + next = null; + return u; + } + }; + } + + /** + * A {@link URLStreamHandler} for use with URLs returned by + * {@link GuestGraalClassLoader#findResource(java.lang.String)}. + */ + @Platforms(Platform.HOSTED_ONLY.class) + private class ImageURLStreamHandler extends URLStreamHandler { + @Override + public URLConnection openConnection(URL u) { + String protocol = u.getProtocol(); + if (protocol.equalsIgnoreCase(SERVICE_PROTOCOL)) { + List providers = services.get(u.getPath()); + if (providers != null) { + return new ImageURLConnection(u, String.join("\n", providers).getBytes()); + } + } else if (protocol.equalsIgnoreCase(RESOURCE_PROTOCOL)) { + String pathInImage = resources.get(u.getPath()); + if (pathInImage != null) { + byte[] bytes = Objects.requireNonNull(imageReader.getResource(pathInImage)); + return new ImageURLConnection(u, bytes); + } + } + throw new IllegalArgumentException(u.toString()); + } + } + + @Platforms(Platform.HOSTED_ONLY.class) + private static class ImageURLConnection extends URLConnection { + private final byte[] bytes; + private InputStream in; + + ImageURLConnection(URL u, byte[] bytes) { + super(u); + this.bytes = bytes; + } + + @Override + public void connect() { + if (!connected) { + in = new ByteArrayInputStream(bytes); + connected = true; + } + } + + @Override + public InputStream getInputStream() { + connect(); + return in; + } + + @Override + public long getContentLengthLong() { + return bytes.length; + } + + @Override + public String getContentType() { + return "application/octet-stream"; + } + } +} diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalCompilerSupport.java b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalCompilerSupport.java new file mode 100644 index 000000000000..03f06c1a1cb0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalCompilerSupport.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.graal.hotspot.guestgraal; + +import java.util.HashMap; +import java.util.List; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.util.ImageHeapMap; + +/** + * Holds data that is pre-computed during native image generation and accessed at run time during a + * Graal compilation. + */ +public class GuestGraalCompilerSupport { + + public final EconomicMap, Object> nodeClasses = ImageHeapMap.create(); + public final EconomicMap, Object> instructionClasses = ImageHeapMap.create(); + public final EconomicMap, Object> compositeValueClasses = ImageHeapMap.create(); + public HashMap, EconomicMap, List>> matchRuleRegistry; + + protected EconomicMap, Object> basePhaseStatistics = ImageHeapMap.create(); + protected EconomicMap, Object> lirPhaseStatistics = ImageHeapMap.create(); + + @Platforms(Platform.HOSTED_ONLY.class) + static void registerStatistics(Class phaseSubClass, EconomicMap, Object> cache, Object newStatistics) { + assert !cache.containsKey(phaseSubClass); + cache.put(phaseSubClass, newStatistics); + } + + public HashMap, EconomicMap, List>> getMatchRuleRegistry() { + return matchRuleRegistry; + } + + public void setMatchRuleRegistry(HashMap, EconomicMap, List>> matchRuleRegistry) { + this.matchRuleRegistry = matchRuleRegistry; + } + + public static GuestGraalCompilerSupport get() { + return ImageSingletons.lookup(GuestGraalCompilerSupport.class); + } + + public EconomicMap, Object> getBasePhaseStatistics() { + return basePhaseStatistics; + } + + public EconomicMap, Object> getLirPhaseStatistics() { + return lirPhaseStatistics; + } +} diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalFeature.java b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalFeature.java new file mode 100644 index 000000000000..39bc2df2d98b --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalFeature.java @@ -0,0 +1,427 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.graal.hotspot.guestgraal; + +import static java.lang.invoke.MethodType.methodType; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Modifier; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.regex.Pattern; + +import com.oracle.svm.graal.hotspot.GetCompilerConfig; +import com.oracle.svm.graal.hotspot.GetJNIConfig; +import com.oracle.svm.hosted.FeatureImpl; +import org.graalvm.jniutils.NativeBridgeSupport; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; +import org.graalvm.nativeimage.hosted.RuntimeReflection; + +import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.reports.CallTreePrinter; +import com.oracle.svm.core.SubstrateTargetDescription; +import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.FeatureImpl.AfterAnalysisAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; +import com.oracle.svm.util.ModuleSupport; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.nodes.graphbuilderconf.GeneratedInvocationPlugin; +import jdk.graal.compiler.options.Option; +import jdk.vm.ci.code.TargetDescription; + +/** + * This feature builds the libgraal shared library (e.g., libjvmcicompiler.so on linux). + *

+ * With use of {@code -H:GuestJavaHome}, the Graal and JVMCI classes from which libgraal is built + * can be from a "guest" JDK that may be different from the JDK on which Native Image is running. + *

+ * To build libgraal, invoke {@code native-image} with the jar containing this feature and it + * dependencies. For example: + * + *

+ *      native-image -p jniutils.jar -cp guestgraal-library.jar
+ * 
+ * + * If building with mx, execute this from the {@code vm} suite: + * + *
+ *      mx --env guestgraal native-image \
+ *          -p $(mx --env guestgraal --quiet path JNIUTILS) \
+ *          -cp $(mx --env guestgraal --quiet path GUESTGRAAL_LIBRARY)
+ * 
+ * + * This feature is composed of these key classes: + *
    + *
  • {@link GuestGraalClassLoader}
  • + *
  • {@link GuestGraal}
  • + *
  • {@link GuestGraalSubstitutions}
  • + *
+ * + * Additionally, it defines + * {@code META-INF/native-image/com.oracle.svm.graal.hotspot.guestgraal/native-image.properties}. + */ +@Platforms(Platform.HOSTED_ONLY.class) +public final class GuestGraalFeature implements Feature { + + static class Options { + @Option(help = "The value of the java.home system property reported by the Java " + + "installation that includes the Graal classes in its runtime image " + + "from which libgraal will be built. If not provided, the java.home " + + "of the Java installation running native-image will be used.") // + public static final HostedOptionKey GuestJavaHome = new HostedOptionKey<>(Path.of(System.getProperty("java.home"))); + } + + public static final class IsEnabled implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + return ImageSingletons.contains(GuestGraalFeature.class); + } + } + + private GuestGraalFeature() { + // GuestGraalFieldsOffsetsFeature implements InternalFeature which is in + // the non-public package com.oracle.svm.core.feature + accessModulesToClass(ModuleSupport.Access.EXPORT, GuestGraalFeature.class, "org.graalvm.nativeimage.builder"); + + // GuestGraalFeature accesses a few Graal classes (see import statements above) + accessModulesToClass(ModuleSupport.Access.EXPORT, GuestGraalFeature.class, "jdk.graal.compiler"); + } + + @Override + public List> getRequiredFeatures() { + return List.of(GuestGraalFieldsOffsetsFeature.class); + } + + final MethodHandles.Lookup mhl = MethodHandles.lookup(); + + /** + * Loader used for loading classes from the guest GraalVM. + */ + GuestGraalClassLoader loader; + + /** + * Handle to {@link jdk.graal.compiler.hotspot.guestgraal.BuildTime} in the guest. + */ + Class buildTimeClass; + + /** + * Handle to {@link jdk.graal.compiler.phases.BasePhase} in the guest. + */ + private Class basePhaseClass; + + private Function, Object> newBasePhaseStatistics; + + /** + * Handle to {@link jdk.graal.compiler.lir.phases.LIRPhase} in the guest. + */ + private Class lirPhaseClass; + + private Function, Object> newLIRPhaseStatistics; + + MethodHandle handleGlobalAtomicLongGetInitialValue; + + /** + * Performs tasks once this feature is registered. + *
    + *
  • Create the {@link GuestGraalClassLoader} instance.
  • + *
  • Get a handle to the {@link jdk.graal.compiler.hotspot.guestgraal.BuildTime} class in the + * guest.
  • + *
  • Initializes the options in the guest.
  • + *
  • Initializes some state needed by {@link GuestGraalSubstitutions}.
  • + *
+ */ + @Override + public void afterRegistration(AfterRegistrationAccess access) { + ImageSingletons.add(NativeBridgeSupport.class, new GuestGraalNativeBridgeSupport()); + // Target_jdk_graal_compiler_serviceprovider_VMSupport.getIsolateID needs access to + // org.graalvm.nativeimage.impl.IsolateSupport + accessModulesToClass(ModuleSupport.Access.EXPORT, GuestGraalFeature.class, "org.graalvm.nativeimage"); + + loader = new GuestGraalClassLoader(Options.GuestJavaHome.getValue().resolve(Path.of("lib", "modules"))); + + try { + buildTimeClass = loader.loadClassOrFail("jdk.graal.compiler.hotspot.guestgraal.BuildTime"); + + // Guest JVMCI and Graal need access to some JDK internal packages + String[] basePackages = {"jdk.internal.misc", "jdk.internal.util"}; + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, null, false, "java.base", basePackages); + + /* + * Ensure OptionsParser.cachedOptionDescriptors is initialized with OptionDescriptors + * from GuestGraalClassLoader. + */ + mhl.findStatic(buildTimeClass, "configureOptionsParserCachedOptionDescriptors", methodType(void.class)).invoke(); + } catch (Throwable e) { + throw VMError.shouldNotReachHere("Failed to invoke jdk.graal.compiler.hotspot.guestgraal.BuildTime.configureOptionsParserCachedOptionDescriptors", e); + } + + try { + /* + * Get GlobalAtomicLong.getInitialValue() method from GuestGraalClassLoader for + * GuestGraalGraalSubstitutions.GlobalAtomicLongAddressProvider FieldValueTransformer + */ + handleGlobalAtomicLongGetInitialValue = mhl.findVirtual(loader.loadClassOrFail("jdk.graal.compiler.serviceprovider.GlobalAtomicLong"), + "getInitialValue", methodType(long.class)); + + } catch (Throwable e) { + VMError.shouldNotReachHere(e); + } + } + + private static void accessModulesToClass(ModuleSupport.Access access, Class accessingClass, String... moduleNames) { + for (String moduleName : moduleNames) { + var module = getBootModule(moduleName); + ModuleSupport.accessPackagesToClass(access, accessingClass, false, + module.getName(), module.getPackages().toArray(String[]::new)); + } + } + + static Module getBootModule(String moduleName) { + return ModuleLayer.boot().findModule(moduleName).orElseThrow(); + } + + @Override + public void duringSetup(DuringSetupAccess access) { + try { + var basePhaseStatisticsClass = loader.loadClassOrFail("jdk.graal.compiler.phases.BasePhase$BasePhaseStatistics"); + var lirPhaseStatisticsClass = loader.loadClassOrFail("jdk.graal.compiler.lir.phases.LIRPhase$LIRPhaseStatistics"); + MethodType statisticsCTorType = methodType(void.class, Class.class); + var basePhaseStatisticsCTor = mhl.findConstructor(basePhaseStatisticsClass, statisticsCTorType); + var lirPhaseStatisticsCTor = mhl.findConstructor(lirPhaseStatisticsClass, statisticsCTorType); + newBasePhaseStatistics = new StatisticsCreator(basePhaseStatisticsCTor)::create; + newLIRPhaseStatistics = new StatisticsCreator(lirPhaseStatisticsCTor)::create; + + basePhaseClass = loader.loadClassOrFail("jdk.graal.compiler.phases.BasePhase"); + lirPhaseClass = loader.loadClassOrFail("jdk.graal.compiler.lir.phases.LIRPhase"); + + ImageSingletons.add(GuestGraalCompilerSupport.class, new GuestGraalCompilerSupport()); + } catch (Throwable e) { + throw VMError.shouldNotReachHere("Failed to invoke jdk.graal.compiler.hotspot.guestgraal.BuildTime methods", e); + } + + DuringSetupAccessImpl accessImpl = (DuringSetupAccessImpl) access; + + accessImpl.registerClassReachabilityListener(this::registerPhaseStatistics); + + GetJNIConfig.register(loader); + } + + private record StatisticsCreator(MethodHandle ctorHandle) { + Object create(Class clazz) { + try { + return ctorHandle.invoke(clazz); + } catch (Throwable e) { + throw VMError.shouldNotReachHere("Failed to create new instance of Statistics clazz with MethodHandle " + ctorHandle, e); + } + } + } + + private void registerPhaseStatistics(DuringAnalysisAccess a, Class newlyReachableClass) { + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + + if (!Modifier.isAbstract(newlyReachableClass.getModifiers())) { + boolean requireAnalysisIteration = true; + if (basePhaseClass.isAssignableFrom(newlyReachableClass)) { + GuestGraalCompilerSupport.registerStatistics(newlyReachableClass, GuestGraalCompilerSupport.get().basePhaseStatistics, + newBasePhaseStatistics.apply(newlyReachableClass)); + + } else if (lirPhaseClass.isAssignableFrom(newlyReachableClass)) { + GuestGraalCompilerSupport.registerStatistics(newlyReachableClass, GuestGraalCompilerSupport.get().lirPhaseStatistics, + newLIRPhaseStatistics.apply(newlyReachableClass)); + } else { + requireAnalysisIteration = false; + } + + if (requireAnalysisIteration) { + access.requireAnalysisIteration(); + } + } + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess baa) { + BeforeAnalysisAccessImpl impl = (BeforeAnalysisAccessImpl) baa; + var bb = impl.getBigBang(); + + /* Contains static fields that depend on HotSpotJVMCIRuntime */ + RuntimeClassInitialization.initializeAtRunTime(loader.loadClassOrFail("jdk.vm.ci.hotspot.HotSpotModifiers")); + RuntimeClassInitialization.initializeAtRunTime(loader.loadClassOrFail("jdk.vm.ci.hotspot.HotSpotCompiledCodeStream")); + RuntimeClassInitialization.initializeAtRunTime(loader.loadClassOrFail("jdk.vm.ci.hotspot.HotSpotCompiledCodeStream$Tag")); + /* ThreadLocal in static field jdk.graal.compiler.debug.DebugContext.activated */ + RuntimeClassInitialization.initializeAtRunTime(loader.loadClassOrFail("jdk.graal.compiler.debug.DebugContext")); + + /* Needed for runtime calls to BoxingSnippets.Templates.getCacheClass(JavaKind) */ + RuntimeReflection.registerAllDeclaredClasses(Character.class); + RuntimeReflection.register(ReflectionUtil.lookupField(ReflectionUtil.lookupClass("java.lang.Character$CharacterCache"), "cache")); + RuntimeReflection.registerAllDeclaredClasses(Byte.class); + RuntimeReflection.register(ReflectionUtil.lookupField(ReflectionUtil.lookupClass("java.lang.Byte$ByteCache"), "cache")); + RuntimeReflection.registerAllDeclaredClasses(Short.class); + RuntimeReflection.register(ReflectionUtil.lookupField(ReflectionUtil.lookupClass("java.lang.Short$ShortCache"), "cache")); + RuntimeReflection.registerAllDeclaredClasses(Integer.class); + RuntimeReflection.register(ReflectionUtil.lookupField(ReflectionUtil.lookupClass("java.lang.Integer$IntegerCache"), "cache")); + RuntimeReflection.registerAllDeclaredClasses(Long.class); + RuntimeReflection.register(ReflectionUtil.lookupField(ReflectionUtil.lookupClass("java.lang.Long$LongCache"), "cache")); + + /* Configure static state of Graal so that suitable for the GuestGraal use case */ + try { + TargetDescription targetDescription = ImageSingletons.lookup(SubstrateTargetDescription.class); + String arch = targetDescription.arch.getName(); + + Consumer> registerAsInHeap = nodeClass -> impl.getMetaAccess().lookupJavaType(nodeClass) + .registerAsInstantiated("All NodeClass classes are marked as instantiated eagerly."); + + Consumer>> hostedGraalSetFoldNodePluginClasses = GeneratedInvocationPlugin::setFoldNodePluginClasses; + + MethodHandle configureGraalForLibGraal = mhl.findStatic(buildTimeClass, + "configureGraalForLibGraal", + methodType(void.class, + String.class, // arch + Consumer.class, // registerAsInHeap + Consumer.class, // hostedGraalSetFoldNodePluginClasses + String.class // encodedGuestObjects + )); + GetCompilerConfig.Result configResult = GetCompilerConfig.from(Options.GuestJavaHome.getValue(), bb.getOptions()); + for (var e : configResult.opens().entrySet()) { + for (String source : e.getValue()) { + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, buildTimeClass, false, e.getKey(), source); + } + } + + configureGraalForLibGraal.invoke(arch, + registerAsInHeap, + hostedGraalSetFoldNodePluginClasses, + configResult.encodedConfig()); + + initRuntimeHandles(mhl.findStatic(buildTimeClass, "getRuntimeHandles", methodType(Map.class))); + + } catch (Throwable e) { + throw VMError.shouldNotReachHere("Failed to invoke jdk.graal.compiler.hotspot.guestgraal.BuildTime methods", e); + } + } + + @SuppressWarnings("unchecked") + private static void initRuntimeHandles(MethodHandle getRuntimeHandles) throws Throwable { + ImageSingletons.add(GuestGraal.class, new GuestGraal((Map) getRuntimeHandles.invoke())); + } + + @SuppressWarnings("try") + @Override + public void afterAnalysis(AfterAnalysisAccess a) { + /* + * Verify we only have JVMCI & Graal classes reachable that are coming from + * GuestGraalClassLoader except for hosted JVMCI & Graal classes that are legitimately used + * by SubstrateVM runtime implementation classes (mostly from package com.oracle.svm.core). + */ + List hostedAllowed = List.of( + classesPattern("jdk.graal.compiler.core.common", + "NumUtil"), + classesPattern("jdk.graal.compiler.core.common.util", + "AbstractTypeReader", "TypeConversion", "TypeReader", "UnsafeArrayTypeReader"), + classesPattern("jdk.graal.compiler.core.common.type", + "CompressibleConstant"), + classesPattern("jdk.graal.compiler.debug", + "GraalError"), + classesPattern("jdk.graal.compiler.options", + "ModifiableOptionValues", "Option.*"), + classesPattern("org.graalvm.collections", + "EconomicMap.*", "EmptyMap.*", "Equivalence.*", "Pair"), + classesPattern("jdk.vm.ci.amd64", + "AMD64.*"), + classesPattern("jdk.vm.ci.aarch64", + "AArch64.*"), + classesPattern("jdk.vm.ci.riscv64", + "RISCV64.*"), + classesPattern("jdk.vm.ci.code", + "Architecture", "Register.*", "TargetDescription"), + classesPattern("jdk.vm.ci.meta", + "JavaConstant", "JavaKind", "MetaUtil", "NullConstant", "PrimitiveConstant")); + + Set forbiddenHostedModules = Set.of("jdk.internal.vm.ci", "org.graalvm.collections", "org.graalvm.word", "jdk.graal.compiler"); + + BigBang bigBang = ((AfterAnalysisAccessImpl) a).getBigBang(); + CallTreePrinter callTreePrinter = new CallTreePrinter(bigBang); + callTreePrinter.buildCallTree(); + + DebugContext debug = bigBang.getDebug(); + List forbiddenReachableTypes = new ArrayList<>(); + try (DebugContext.Scope ignored = debug.scope("GuestGraal")) { + for (AnalysisType analysisType : callTreePrinter.usedAnalysisTypes()) { + Class reachableType = analysisType.getJavaClass(); + if (reachableType.getClassLoader() == loader || reachableType.isArray()) { + continue; + } + Module module = reachableType.getModule(); + if (module.isNamed() && forbiddenHostedModules.contains(module.getName())) { + String fqn = reachableType.getName(); + if (hostedAllowed.stream().anyMatch(pattern -> pattern.matcher(fqn).matches())) { + debug.log("Allowing hosted class %s from %s", fqn, module); + continue; + } + forbiddenReachableTypes.add(String.format("%s/%s", module.getName(), fqn)); + } + } + } + if (!forbiddenReachableTypes.isEmpty()) { + VMError.shouldNotReachHere("GuestGraal build found forbidden hosted types as reachable: %s", String.join(", ", forbiddenReachableTypes)); + } + } + + @Override + public void beforeImageWrite(BeforeImageWriteAccess access) { + if (!Platform.includedIn(Platform.WINDOWS.class)) { + ((FeatureImpl.BeforeImageWriteAccessImpl) access).registerLinkerInvocationTransformer(linkerInvocation -> { + Path imageFilePath = linkerInvocation.getOutputFile(); + String imageName = imageFilePath.getFileName().toString(); + String posixLibraryPrefix = "lib"; + assert !imageName.startsWith(posixLibraryPrefix); + String posixImageName = posixLibraryPrefix + imageName; + linkerInvocation.setOutputFile(imageFilePath.getParent().resolve(posixImageName)); + return linkerInvocation; + }); + } + } + + private static Pattern classesPattern(String packageName, String... regexes) { + return Pattern.compile("%s(%s)".formatted(Pattern.quote(packageName + '.'), String.join("|", regexes))); + } +} diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalFieldsOffsetsFeature.java b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalFieldsOffsetsFeature.java new file mode 100644 index 000000000000..55468cb220f0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalFieldsOffsetsFeature.java @@ -0,0 +1,500 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.graal.hotspot.guestgraal; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.function.Function; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.core.BuildPhaseProvider; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.CompilationAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; +import com.oracle.svm.hosted.meta.HostedMetaAccess; +import com.oracle.svm.util.ModuleSupport; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.internal.misc.Unsafe; + +/** + * Graal uses unsafe memory accesses to access {@code Node}s and {@code LIRInstruction}s. The + * offsets for these accesses are maintained in {@code Fields}, which are accessible from + * meta-classes such as {@code NodeClass} and {@code LIRInstructionClass}. We do not want to replace + * the whole meta-classes. Instead, we just replace the {@code long[]} arrays that hold the actual + * offsets. + */ +public final class GuestGraalFieldsOffsetsFeature implements InternalFeature { + + private final MethodHandles.Lookup mhl = MethodHandles.lookup(); + private GuestGraalClassLoader loader; + + private Class fieldsClass; + private Class edgesClass; + private Class edgesTypeClass; + private MethodHandle fieldsClassGetOffsetsMethod; + private MethodHandle fieldsClassGetCountMethod; + private MethodHandle fieldsClassGetDeclaringClassMethod; + private MethodHandle fieldsClassGetNameMethod; + + private MethodHandle edgesClassTypeMethod; + private MethodHandle edgesClassGetDirectCountMethod; + + private Class nodeClass; + private Class nodeClassClass; + private Class inputEdgesClass; + private Class successorEdgesClass; + private MethodHandle nodeClassClassGetMethod; + private MethodHandle nodeClassClassGetInputEdgesMethod; + private MethodHandle nodeClassClassGetSuccessorEdgesMethod; + private MethodHandle nodeClassClassGetShortNameMethod; + private MethodHandle nodeClassClassComputeIterationMaskMethod; + + private Class fieldIntrospectionClass; + private MethodHandle fieldIntrospectionClassGetDataMethod; + private MethodHandle fieldIntrospectionClassGetAllFieldsMethod; + private MethodHandle fieldIntrospectionClassGetClazzMethod; + private Class lirInstructionClass; + private Class lirInstructionClassClass; + private MethodHandle lirInstructionClassClassGetMethod; + private Class compositeValueClass; + private Class compositeValueClassClass; + private MethodHandle compositeValueClassClassGetMethod; + + private static class FieldsOffsetsReplacement { + protected final Object fields; + protected boolean newValuesAvailable; + protected long[] newOffsets; + protected long newIterationInitMask; + + protected FieldsOffsetsReplacement(Object fields) { + this.fields = fields; + } + } + + static class FieldsOffsetsReplacements { + protected final Map replacements = new IdentityHashMap<>(); + protected boolean sealed; + } + + private static Map getReplacements() { + return ImageSingletons.lookup(FieldsOffsetsReplacements.class).replacements; + } + + @Override + public void duringSetup(DuringSetupAccess a) { + DuringSetupAccessImpl access = (DuringSetupAccessImpl) a; + loader = ImageSingletons.lookup(GuestGraalFeature.class).loader; + + fieldsClass = loader.loadClassOrFail("jdk.graal.compiler.core.common.Fields"); + edgesClass = loader.loadClassOrFail("jdk.graal.compiler.graph.Edges"); + edgesTypeClass = loader.loadClassOrFail("jdk.graal.compiler.graph.Edges$Type"); + nodeClass = loader.loadClassOrFail("jdk.graal.compiler.graph.Node"); + nodeClassClass = loader.loadClassOrFail("jdk.graal.compiler.graph.NodeClass"); + lirInstructionClass = loader.loadClassOrFail("jdk.graal.compiler.lir.LIRInstruction"); + lirInstructionClassClass = loader.loadClassOrFail("jdk.graal.compiler.lir.LIRInstructionClass"); + compositeValueClass = loader.loadClassOrFail("jdk.graal.compiler.lir.CompositeValue"); + compositeValueClassClass = loader.loadClassOrFail("jdk.graal.compiler.lir.CompositeValueClass"); + fieldIntrospectionClass = loader.loadClassOrFail("jdk.graal.compiler.core.common.FieldIntrospection"); + inputEdgesClass = loader.loadClassOrFail("jdk.graal.compiler.graph.InputEdges"); + successorEdgesClass = loader.loadClassOrFail("jdk.graal.compiler.graph.SuccessorEdges"); + + try { + fieldsClassGetOffsetsMethod = mhl.findVirtual(fieldsClass, "getOffsets", MethodType.methodType(long[].class)); + fieldsClassGetCountMethod = mhl.findVirtual(fieldsClass, "getCount", MethodType.methodType(int.class)); + fieldsClassGetDeclaringClassMethod = mhl.findVirtual(fieldsClass, "getDeclaringClass", MethodType.methodType(Class.class, int.class)); + fieldsClassGetNameMethod = mhl.findVirtual(fieldsClass, "getName", MethodType.methodType(String.class, int.class)); + + edgesClassTypeMethod = mhl.findVirtual(edgesClass, "type", MethodType.methodType(edgesTypeClass)); + edgesClassGetDirectCountMethod = mhl.findVirtual(edgesClass, "getDirectCount", MethodType.methodType(int.class)); + + nodeClassClassGetMethod = mhl.findStatic(nodeClassClass, "get", MethodType.methodType(nodeClassClass, Class.class)); + nodeClassClassGetInputEdgesMethod = mhl.findVirtual(nodeClassClass, "getInputEdges", MethodType.methodType(inputEdgesClass)); + nodeClassClassGetSuccessorEdgesMethod = mhl.findVirtual(nodeClassClass, "getSuccessorEdges", MethodType.methodType(successorEdgesClass)); + nodeClassClassGetShortNameMethod = mhl.findVirtual(nodeClassClass, "shortName", MethodType.methodType(String.class)); + nodeClassClassGetShortNameMethod = mhl.findVirtual(nodeClassClass, "shortName", MethodType.methodType(String.class)); + nodeClassClassComputeIterationMaskMethod = mhl.findStatic(nodeClassClass, "computeIterationMask", MethodType.methodType(long.class, edgesTypeClass, int.class, long[].class)); + + lirInstructionClassClassGetMethod = mhl.findStatic(lirInstructionClassClass, "get", MethodType.methodType(lirInstructionClassClass, Class.class)); + compositeValueClassClassGetMethod = mhl.findStatic(compositeValueClassClass, "get", MethodType.methodType(compositeValueClassClass, Class.class)); + + fieldIntrospectionClassGetDataMethod = mhl.findVirtual(fieldIntrospectionClass, "getData", MethodType.methodType(fieldsClass)); + fieldIntrospectionClassGetAllFieldsMethod = mhl.findVirtual(fieldIntrospectionClass, "getAllFields", MethodType.methodType(fieldsClass.arrayType())); + fieldIntrospectionClassGetClazzMethod = mhl.findVirtual(fieldIntrospectionClass, "getClazz", MethodType.methodType(Class.class)); + + } catch (ReflectiveOperationException e) { + throw VMError.shouldNotReachHere(e); + } + + ModuleSupport.accessModuleByClass(ModuleSupport.Access.EXPORT, GuestGraalFieldsOffsetsFeature.class, InternalFeature.class); + ImageSingletons.add(FieldsOffsetsReplacements.class, new FieldsOffsetsReplacements()); + access.registerObjectReplacer(this::replaceFieldsOffsets); + access.registerClassReachabilityListener(this::classReachabilityListener); + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + MethodHandle getInputEdgesOffsets; + MethodHandle getSuccessorEdgesOffsets; + var buildTimeClass = loader.loadClassOrFail("jdk.graal.compiler.hotspot.guestgraal.BuildTime"); + try { + MethodType offsetAccessorSignature = MethodType.methodType(long[].class, Object.class); + getInputEdgesOffsets = mhl.findStatic(buildTimeClass, "getInputEdgesOffsets", offsetAccessorSignature); + getSuccessorEdgesOffsets = mhl.findStatic(buildTimeClass, "getSuccessorEdgesOffsets", offsetAccessorSignature); + } catch (ReflectiveOperationException e) { + throw VMError.shouldNotReachHere(e); + } + + access.registerFieldValueTransformer(ReflectionUtil.lookupField(nodeClassClass, "inputsIteration"), + new IterationMaskRecomputation(getInputEdgesOffsets)); + access.registerFieldValueTransformer(ReflectionUtil.lookupField(nodeClassClass, "successorIteration"), + new IterationMaskRecomputation(getSuccessorEdgesOffsets)); + } + + private static class IterationMaskRecomputation implements FieldValueTransformerWithAvailability { + + private final MethodHandle offsetsFromReceiver; + + IterationMaskRecomputation(MethodHandle offsetsFromReceiver) { + this.offsetsFromReceiver = offsetsFromReceiver; + } + + @Override + public ValueAvailability valueAvailability() { + return ValueAvailability.AfterAnalysis; + } + + @Override + public Object transform(Object receiver, Object originalValue) { + FieldsOffsetsReplacement replacement; + try { + long[] offsetsFromEdges = (long[]) offsetsFromReceiver.invoke(receiver); + replacement = GuestGraalFieldsOffsetsFeature.getReplacements().get(offsetsFromEdges); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + assert replacement.newValuesAvailable : "Cannot access iteration mask before field offsets are assigned"; + return replacement.newIterationInitMask; + } + } + + private Object replaceFieldsOffsets(Object source) { + if (fieldsClass.isInstance(source)) { + /* + * All instances of Fields must have been registered before, otherwise we miss the + * substitution of its offsets array. + */ + assert !ImageSingletons.lookup(FieldsOffsetsReplacements.class).sealed || getReplacements().containsKey(getOffsetsFromFields(source)) : source; + + } else if (source instanceof long[]) { + FieldsOffsetsReplacement replacement = getReplacements().get(source); + if (replacement != null) { + assert source == getOffsetsFromFields(replacement.fields); + + /* + * We can only compute the new offsets after static analysis, i.e., after the object + * layout is done and run-time field offsets are available. Until then, we return + * the hosted offsets so that we have a return value. The actual offsets do not + * matter at this point. + */ + if (replacement.newValuesAvailable) { + return replacement.newOffsets; + } + } + } + return source; + } + + /* Invoked once for every class that is reachable in the native image. */ + private void classReachabilityListener(DuringAnalysisAccess a, Class newlyReachableClass) { + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + if (BuildPhaseProvider.isAnalysisFinished()) { + throw VMError.shouldNotReachHere("New class reachable after analysis: " + newlyReachableClass); + } + + if (!newlyReachableClass.equals(nodeClass) && nodeClass.isAssignableFrom(newlyReachableClass)) { + registerClass(newlyReachableClass, GuestGraalCompilerSupport.get().nodeClasses, this::getNodeClassFromNode, false, access); + } else if (!newlyReachableClass.equals(lirInstructionClass) && lirInstructionClass.isAssignableFrom(newlyReachableClass)) { + registerClass(newlyReachableClass, GuestGraalCompilerSupport.get().instructionClasses, this::getLIRInstructionClassFromLIRInstruction, true, access); + } else if (!newlyReachableClass.equals(compositeValueClass) && compositeValueClass.isAssignableFrom(newlyReachableClass)) { + registerClass(newlyReachableClass, GuestGraalCompilerSupport.get().compositeValueClasses, this::getCompositeValueClassFromCompositeValue, true, access); + } + } + + private void registerClass(Class clazz, EconomicMap, Object> registry, + Function, Object> lookup, boolean excludeAbstract, DuringAnalysisAccessImpl access) { + assert !registry.containsKey(clazz); + + if (!excludeAbstract || !Modifier.isAbstract(clazz.getModifiers())) { + Object nodeClazz = lookup.apply(clazz); + registry.put(clazz, nodeClazz); + registerFields(nodeClazz, access); + + access.requireAnalysisIteration(); + } + } + + private void registerFields(Object introspection, BeforeAnalysisAccessImpl config) { + if (nodeClassClass.isInstance(introspection)) { + + /* The partial evaluator allocates Node classes via Unsafe. */ + AnalysisType nodeType = config.getMetaAccess().lookupJavaType(getClazzFromFieldIntrospection(introspection)); + nodeType.registerInstantiatedCallback(unused -> nodeType.registerAsUnsafeAllocated("Graal node class")); + + Object dataFields = getDataFromFieldIntrospection(introspection); + registerFields(dataFields, config, "Graal node data field"); + + Object inputEdges = getInputEdgesFromNodeClass(introspection); + registerFields(inputEdges, config, "Graal node input edge"); + + Object successorEdges = getSuccessorEdgesFromNodeClass(introspection); + registerFields(successorEdges, config, "Graal node successor edge"); + + /* Ensure field shortName is initialized, so that the instance is immutable. */ + invokeShortName(introspection); + + } else { + assert fieldIntrospectionClass.isInstance(introspection); + for (Object fields : getAllFieldsFromFieldIntrospection(introspection)) { + registerFields(fields, config, "Graal field"); + } + } + } + + private void registerFields(Object fields, BeforeAnalysisAccessImpl config, Object reason) { + getReplacements().put(getOffsetsFromFields(fields), new FieldsOffsetsReplacement(fields)); + + for (int i = 0; i < getCountFromFields(fields); i++) { + AnalysisField aField = config.getMetaAccess().lookupJavaField(findField(fields, i)); + aField.getType().registerAsReachable(aField); + config.registerAsUnsafeAccessed(aField, reason); + } + } + + private Field findField(Object fields, int index) { + try { + return getDeclaringClassFromFields(fields, index).getDeclaredField(getNameFromFields(fields, index)); + } catch (NoSuchFieldException ex) { + throw VMError.shouldNotReachHere(ex); + } + } + + @Override + public void beforeCompilation(BeforeCompilationAccess a) { + CompilationAccessImpl config = (CompilationAccessImpl) a; + HostedMetaAccess hMetaAccess = config.getMetaAccess(); + + getReplacements().forEach((originalOffsets, replacement) -> { + Object fields = replacement.fields; + long[] newOffsets = new long[getCountFromFields(fields)]; + for (int i = 0; i < newOffsets.length; i++) { + Field field = findField(fields, i); + assert Unsafe.getUnsafe().objectFieldOffset(field) == originalOffsets[i]; + newOffsets[i] = hMetaAccess.lookupJavaField(field).getLocation(); + } + replacement.newOffsets = newOffsets; + + if (edgesClass.isInstance(fields)) { + Object edges = edgesClass.cast(fields); + replacement.newIterationInitMask = nodeClassComputeIterationMask(typeFromEdges(edges), getDirectCountFromEdges(edges), newOffsets); + } + + replacement.newValuesAvailable = true; + }); + + ImageSingletons.lookup(FieldsOffsetsReplacements.class).sealed = true; + } + + @Override + public void afterCompilation(AfterCompilationAccess access) { + access.registerAsImmutable(GuestGraalCompilerSupport.get().nodeClasses.getValues(), o -> true); + access.registerAsImmutable(GuestGraalCompilerSupport.get().instructionClasses.getValues(), o -> true); + } + + private long[] getOffsetsFromFields(Object fields) { + try { + return (long[]) fieldsClassGetOffsetsMethod.invoke(fields); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } + + private int getCountFromFields(Object fields) { + try { + return (int) fieldsClassGetCountMethod.invoke(fields); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } + + private Class getDeclaringClassFromFields(Object fields, int index) { + try { + return (Class) fieldsClassGetDeclaringClassMethod.invoke(fields, index); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } + + private String getNameFromFields(Object fields, int index) { + try { + return (String) fieldsClassGetNameMethod.invoke(fields, index); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } + + private Object typeFromEdges(Object edges) { + try { + assert edgesClass.isInstance(edges); + Object edgesType = edgesClassTypeMethod.invoke(edges); + return edgesTypeClass.cast(edgesType); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } + + private int getDirectCountFromEdges(Object edges) { + try { + assert edgesClass.isInstance(edges); + return (int) edgesClassGetDirectCountMethod.invoke(edges); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } + + private Object getDataFromFieldIntrospection(Object fieldIntrospection) { + try { + assert fieldIntrospectionClass.isInstance(fieldIntrospection); + Object fields = fieldIntrospectionClassGetDataMethod.invoke(fieldIntrospection); + return fieldsClass.cast(fields); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } + + private Class getClazzFromFieldIntrospection(Object fieldIntrospection) { + try { + assert fieldIntrospectionClass.isInstance(fieldIntrospection); + Object clazz = fieldIntrospectionClassGetClazzMethod.invoke(fieldIntrospection); + return (Class) clazz; + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } + + private Object[] getAllFieldsFromFieldIntrospection(Object fieldIntrospection) { + try { + assert fieldIntrospectionClass.isInstance(fieldIntrospection); + Object allFields = fieldIntrospectionClassGetAllFieldsMethod.invoke(fieldIntrospection); + return (Object[]) fieldsClass.arrayType().cast(allFields); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } + + private Object getNodeClassFromNode(Class clazz) { + try { + assert nodeClass.isAssignableFrom(clazz); + Object nodeClassInstance = nodeClassClassGetMethod.invoke(clazz); + assert nodeClassClass.isInstance(nodeClassInstance); + return nodeClassInstance; + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } + + private Object getInputEdgesFromNodeClass(Object nodeClazz) { + try { + assert nodeClassClass.isInstance(nodeClazz); + Object inputEdges = nodeClassClassGetInputEdgesMethod.invoke(nodeClazz); + return inputEdgesClass.cast(inputEdges); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } + + private Object getSuccessorEdgesFromNodeClass(Object nodeClazz) { + try { + assert nodeClassClass.isInstance(nodeClazz); + Object successorEdges = nodeClassClassGetSuccessorEdgesMethod.invoke(nodeClazz); + return successorEdgesClass.cast(successorEdges); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } + + private void invokeShortName(Object nodeClazz) { + try { + assert nodeClassClass.isInstance(nodeClazz); + nodeClassClassGetShortNameMethod.invoke(nodeClazz); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } + + private long nodeClassComputeIterationMask(Object edgesType, int directCount, long[] offsets) { + try { + assert edgesTypeClass.isInstance(edgesType); + return (long) nodeClassClassComputeIterationMaskMethod.invoke(edgesType, directCount, offsets); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } + + private Object getLIRInstructionClassFromLIRInstruction(Class clazz) { + try { + assert lirInstructionClass.isAssignableFrom(clazz); + Object nodeClassInstance = lirInstructionClassClassGetMethod.invoke(clazz); + assert lirInstructionClassClass.isInstance(nodeClassInstance); + return nodeClassInstance; + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } + + private Object getCompositeValueClassFromCompositeValue(Class clazz) { + try { + assert compositeValueClass.isAssignableFrom(clazz); + Object nodeClassInstance = compositeValueClassClassGetMethod.invoke(clazz); + assert compositeValueClassClass.isInstance(nodeClassInstance); + return nodeClassInstance; + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + } +} diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalNativeBridgeSupport.java b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalNativeBridgeSupport.java new file mode 100644 index 000000000000..c6ba753fa08e --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalNativeBridgeSupport.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.graal.hotspot.guestgraal; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.graalvm.jniutils.JNIMethodScope; +import org.graalvm.jniutils.NativeBridgeSupport; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.impl.IsolateSupport; + +public final class GuestGraalNativeBridgeSupport implements NativeBridgeSupport { + + private static final String JNI_LIBGRAAL_TRACE_LEVEL_PROPERTY_NAME = "JNI_LIBGRAAL_TRACE_LEVEL"; + private static final int UNINITIALIZED_TRACE_LEVEL = Integer.MIN_VALUE; + + private final ThreadLocal inTrace = ThreadLocal.withInitial(() -> false); + + private final AtomicInteger traceLevel = new AtomicInteger(UNINITIALIZED_TRACE_LEVEL); + + @Override + public String getFeatureName() { + return "LIBGRAAL"; + } + + @Override + public boolean isTracingEnabled(int level) { + return traceLevel() >= level; + } + + @Override + public void trace(String message) { + // Prevents nested tracing of JNI calls originated from this method. + // The TruffleCompilerImpl redirects the TTY using a TTY.Filter to the + // TruffleCompilerRuntime#log(). In libgraal the HSTruffleCompilerRuntime#log() uses a + // FromLibGraalCalls#callVoid() to do the JNI call to the GraalTruffleRuntime#log(). + // The FromLibGraalCalls#callVoid() also traces the JNI call by calling trace(). + // The nested trace call should be ignored. + if (!inTrace.get()) { + inTrace.set(true); + try { + StringBuilder sb = new StringBuilder(); + long isolateID = ImageSingletons.lookup(IsolateSupport.class).getIsolateID(); + sb.append('[').append(isolateID).append(':').append(Thread.currentThread().getName()).append(']'); + JNIMethodScope scope = JNIMethodScope.scopeOrNull(); + if (scope != null) { + sb.append(" ".repeat(2 + (scope.depth() * 2))); + } + sb.append(message); + GuestGraal.ttyPrintf("%s%n", sb); + } finally { + inTrace.remove(); + } + } + } + + private int traceLevel() { + int res = traceLevel.get(); + if (res == UNINITIALIZED_TRACE_LEVEL) { + String var = GuestGraal.getSavedProperty(JNI_LIBGRAAL_TRACE_LEVEL_PROPERTY_NAME); + if (var != null) { + try { + res = Integer.parseInt(var); + } catch (NumberFormatException e) { + GuestGraal.ttyPrintf("Invalid value for %s: %s%n", JNI_LIBGRAAL_TRACE_LEVEL_PROPERTY_NAME, e); + res = 0; + } + } else { + res = 0; + } + traceLevel.set(res); + } + return res; + } +} diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalSubstitutions.java b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalSubstitutions.java new file mode 100644 index 000000000000..2ce8eb2a4bb3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal.hotspot.guestgraal/src/com/oracle/svm/graal/hotspot/guestgraal/GuestGraalSubstitutions.java @@ -0,0 +1,435 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.graal.hotspot.guestgraal; + +import java.lang.ref.Cleaner; +import java.lang.ref.ReferenceQueue; +import java.util.Map; +import java.util.function.Supplier; + +import com.oracle.svm.core.heap.GCCause; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.jdk.JDKLatest; +import com.oracle.svm.core.log.FunctionPointerLogHandler; +import com.oracle.svm.graal.hotspot.LibGraalJNIMethodScope; +import org.graalvm.jniutils.JNI; +import org.graalvm.jniutils.JNIExceptionWrapper; +import org.graalvm.jniutils.JNIMethodScope; +import org.graalvm.jniutils.JNIUtil; +import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.LogHandler; +import org.graalvm.nativeimage.Platform.HOSTED_ONLY; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.VMRuntime; +import org.graalvm.nativeimage.hosted.FieldValueTransformer; +import org.graalvm.nativeimage.impl.IsolateSupport; +import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Delete; +import com.oracle.svm.core.annotate.Inject; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.annotate.TargetElement; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; +import com.oracle.svm.core.util.VMError; + +class GuestGraalJVMCISubstitutions { + + @TargetClass(className = "jdk.vm.ci.services.Services", classLoader = GuestGraalClassLoaderSupplier.class, onlyWith = GuestGraalFeature.IsEnabled.class) + static final class Target_jdk_vm_ci_services_Services { + /* + * Static final boolean field Services.IS_IN_NATIVE_IMAGE is used in many places in the + * JVMCI codebase to switch between the different implementations needed for regular use (a + * built-in module jdk.graal.compiler in the JVM) or as part of libgraal. + */ + // Checkstyle: stop + @Alias // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias, isFinal = true)// + public static boolean IS_IN_NATIVE_IMAGE = true; + // Checkstyle: resume + + /* + * Reset Services.savedProperties to null so that we cannot get the hosted savedProperties + * into the libgraal image-heap. This also guarantees that at libgraal-runtime the + * savedProperties are initialized with the system properties state of the JVM that uses + * libgraal. + */ + @Alias // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // + private static Map savedProperties; + } + + @TargetClass(className = "jdk.vm.ci.hotspot.Cleaner", classLoader = GuestGraalClassLoaderSupplier.class, onlyWith = GuestGraalFeature.IsEnabled.class) + static final class Target_jdk_vm_ci_hotspot_Cleaner { + /* + * Ensure the ReferenceQueue instance in Cleaner.queue that is in libgraal is not + * tainted by any use of the Cleaner class at image build-time. + */ + @Alias // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, isFinal = true, declClass = ReferenceQueue.class)// + private static ReferenceQueue queue; + + /* + * Make package-private clean() accessible so that it can be called from + * GuestGraal.doReferenceHandling(). + */ + @Alias + static native void clean(); + } + + @TargetClass(className = "jdk.vm.ci.hotspot.CompilerToVM", classLoader = GuestGraalClassLoaderSupplier.class, onlyWith = {GuestGraalFeature.IsEnabled.class, JDKLatest.class}) + static final class Target_jdk_vm_ci_hotspot_CompilerToVM { + /* + * For libgraal the implementation of CompilerToVM.lookupType needs to take into account + * that the passed-in classloader can also be an instance of GuestGraalClassLoader. Checking + * if that classLoader is the same as the one of the HotSpotResolvedJavaType class itself + * (which is the GuestGraalClassLoader) takes care of that. + */ + @Substitute + Target_jdk_vm_ci_hotspot_HotSpotResolvedJavaType lookupType(ClassLoader classLoader, String name) throws NoClassDefFoundError { + int accessingClassLoader; + if (classLoader == null) { + accessingClassLoader = 0; + } else if (classLoader == ClassLoader.getPlatformClassLoader()) { + accessingClassLoader = 1; + } else if (classLoader == ClassLoader.getSystemClassLoader()) { + accessingClassLoader = 2; + } else if (classLoader == getClass().getClassLoader()) { + accessingClassLoader = 2; + } else { + throw new IllegalArgumentException("Unsupported class loader for lookup: " + classLoader); + } + return lookupType(name, null, 0L, accessingClassLoader, true); + } + + @Alias + native Target_jdk_vm_ci_hotspot_HotSpotResolvedJavaType lookupType(String name, Target_jdk_vm_ci_hotspot_HotSpotResolvedObjectTypeImpl accessingClass, + long accessingKlassPointer, int accessingClassLoader, boolean resolve) throws NoClassDefFoundError; + } + + @TargetClass(className = "jdk.vm.ci.hotspot.HotSpotResolvedJavaType", classLoader = GuestGraalClassLoaderSupplier.class, onlyWith = GuestGraalFeature.IsEnabled.class) + static final class Target_jdk_vm_ci_hotspot_HotSpotResolvedJavaType { + } + + @TargetClass(className = "jdk.vm.ci.hotspot.HotSpotResolvedObjectTypeImpl", classLoader = GuestGraalClassLoaderSupplier.class, onlyWith = GuestGraalFeature.IsEnabled.class) + static final class Target_jdk_vm_ci_hotspot_HotSpotResolvedObjectTypeImpl { + } +} + +public class GuestGraalSubstitutions { + + private static final String GLOBAL_ATOMIC_LONG = "jdk.graal.compiler.serviceprovider.GlobalAtomicLong"; + + @TargetClass(className = GLOBAL_ATOMIC_LONG, classLoader = GuestGraalClassLoaderSupplier.class, onlyWith = GuestGraalFeature.IsEnabled.class) + static final class Target_jdk_graal_compiler_serviceprovider_GlobalAtomicLong { + + @Inject// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = GlobalAtomicLongAddressProvider.class) // + private CGlobalData addressSupplier; + + @Delete private long address; + + /* + * Graal class GlobalAtomicLong uses a java.lang.ref.Cleaner in static field + * GlobalAtomicLong.cleaner to ensure all native memory allocated by GlobalAtomicLong + * instances get freed when the instances are garbage collected. + * + * Native image does not support java.lang.ref.Cleaner. Instead, we can use a + * CGlobalData per GlobalAtomicLong instance if we can guarantee that no instances + * are created at libgraal-runtime. I.e. all GlobalAtomicLongs in the libgraal-image are + * image-heap objects. + */ + @Delete private static Cleaner cleaner; + + /** + * Delete the constructor to ensure instances of {@code GlobalAtomicLong} cannot be created + * at runtime. + */ + @Substitute + @TargetElement(name = TargetElement.CONSTRUCTOR_NAME) + @SuppressWarnings({"unused", "static-method"}) + public void constructor(long initialValue) { + throw VMError.unsupportedFeature("Cannot create " + GLOBAL_ATOMIC_LONG + " objects in native image runtime"); + } + + @Substitute + private long getAddress() { + return addressSupplier.get().rawValue(); + } + } + + /* + * The challenge with using CGlobalData for GlobalAtomicLongs is that we do have to + * make sure they get initialized with the correct initialValue for a given GlobalAtomicLong + * instance at image build-time. This FieldValueTransformer ensures that. + */ + @Platforms(HOSTED_ONLY.class) + private static class GlobalAtomicLongAddressProvider implements FieldValueTransformer { + @Override + public Object transform(Object receiver, Object originalValue) { + long initialValue; + try { + initialValue = (long) ImageSingletons.lookup(GuestGraalFeature.class).handleGlobalAtomicLongGetInitialValue.invoke(receiver); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } + return CGlobalDataFactory.createWord(WordFactory.unsigned(initialValue), null, true); + } + } + + /** + * Constant used to mark (and disable) Truffle support code. + */ + private static final boolean INCLUDE_TRUFFLE = Boolean.getBoolean("disable.spotbugs.unwritten.field.check"); + + @TargetClass(className = "jdk.graal.compiler.serviceprovider.VMSupport", classLoader = GuestGraalClassLoaderSupplier.class, onlyWith = GuestGraalFeature.IsEnabled.class) + final class Target_jdk_graal_compiler_serviceprovider_VMSupport { + + @Substitute + public static long getIsolateAddress() { + return CurrentIsolate.getIsolate().rawValue(); + } + + @Substitute + public static long getIsolateID() { + return ImageSingletons.lookup(IsolateSupport.class).getIsolateID(); + } + + /** + * Performs the following actions around a libgraal compilation: + *
    + *
  • before: opens a JNIMethodScope to allow Graal compilations of Truffle host methods to + * call methods on the TruffleCompilerRuntime.
  • + *
  • after: closes the above JNIMethodScope
  • + *
  • after: triggers GC weak reference processing as SVM does not use a separate thread + * for this in libgraal
  • + *
+ */ + static class LibGraalCompilationRequestScope implements AutoCloseable { + final JNIMethodScope scope; + + LibGraalCompilationRequestScope() { + if (INCLUDE_TRUFFLE) { + JNI.JNIEnv env = GuestGraal.getJNIEnv(); + // This scope is required to allow Graal compilations of host methods to call + // methods in the TruffleCompilerRuntime. This is, for example, required to find + // out + // about Truffle-specific method annotations. + scope = LibGraalJNIMethodScope.open("", env, false); + } else { + scope = null; + } + } + + @Override + public void close() { + try { + if (scope != null) { + scope.close(); + } + } finally { + /* + * libgraal doesn't use a dedicated reference handler thread, so we trigger the + * reference handling manually when a compilation finishes. + */ + GuestGraal.doReferenceHandling(); + } + } + } + + @Substitute + public static AutoCloseable getCompilationRequestScope() { + return new LibGraalCompilationRequestScope(); + } + + @Substitute + public static void fatalError(String message, int delayMS) { + LogHandler handler = ImageSingletons.lookup(LogHandler.class); + if (handler instanceof FunctionPointerLogHandler) { + try { + Thread.sleep(delayMS); + } catch (InterruptedException e) { + // ignore + } + VMError.shouldNotReachHere(message); + } + } + + @Substitute + public static void startupLibGraal() { + VMRuntime.initialize(); + } + + @Substitute + public static void shutdownLibGraal() { + VMRuntime.shutdown(); + } + + @Substitute + public static void invokeShutdownCallback(String cbClassName, String cbMethodName) { + JNI.JNIEnv env = GuestGraal.getJNIEnv(); + JNI.JClass cbClass = JNIUtil.findClass(env, JNIUtil.getSystemClassLoader(env), + JNIUtil.getBinaryName(cbClassName), true); + JNI.JMethodID cbMethod = JNIUtil.findMethod(env, cbClass, true, cbMethodName, "()V"); + env.getFunctions().getCallStaticVoidMethodA().call(env, cbClass, cbMethod, StackValue.get(0)); + JNIExceptionWrapper.wrapAndThrowPendingJNIException(env); + } + + @Substitute + public static void notifyLowMemoryPoint(boolean hintFullGC, boolean forceFullGC) { + if (forceFullGC) { + Heap.getHeap().getGC().collectCompletely(GCCause.JavaLangSystemGC); + } else { + Heap.getHeap().getGC().collectionHint(hintFullGC); + } + GuestGraal.doReferenceHandling(); + } + } + + @TargetClass(className = "jdk.graal.compiler.phases.BasePhase", classLoader = GuestGraalClassLoaderSupplier.class, onlyWith = GuestGraalFeature.IsEnabled.class) + static final class Target_jdk_graal_compiler_phases_BasePhase { + + /* + * Redirect method to image build-time pre-computed statistics in GuestGraalCompilerSupport. + */ + @Substitute + static Target_jdk_graal_compiler_phases_BasePhase_BasePhaseStatistics getBasePhaseStatistics(Class clazz) { + Object result = GuestGraalCompilerSupport.get().getBasePhaseStatistics().get(clazz); + if (result == null) { + throw VMError.shouldNotReachHere(String.format("Missing statistics for phase class: %s%n", clazz.getName())); + } + return SubstrateUtil.cast(result, Target_jdk_graal_compiler_phases_BasePhase_BasePhaseStatistics.class); + } + } + + @TargetClass(className = "jdk.graal.compiler.phases.BasePhase", innerClass = "BasePhaseStatistics", classLoader = GuestGraalClassLoaderSupplier.class, onlyWith = GuestGraalFeature.IsEnabled.class) + static final class Target_jdk_graal_compiler_phases_BasePhase_BasePhaseStatistics { + } + + @TargetClass(className = "jdk.graal.compiler.lir.phases.LIRPhase", classLoader = GuestGraalClassLoaderSupplier.class, onlyWith = GuestGraalFeature.IsEnabled.class) + static final class Target_jdk_graal_compiler_lir_phases_LIRPhase { + + /* + * Redirect method to image build-time pre-computed statistics in GuestGraalCompilerSupport. + */ + @Substitute + static Target_jdk_graal_compiler_lir_phases_LIRPhase_LIRPhaseStatistics getLIRPhaseStatistics(Class clazz) { + Object result = GuestGraalCompilerSupport.get().getLirPhaseStatistics().get(clazz); + if (result == null) { + throw VMError.shouldNotReachHere(String.format("Missing statistics for phase class: %s%n", clazz.getName())); + } + return SubstrateUtil.cast(result, Target_jdk_graal_compiler_lir_phases_LIRPhase_LIRPhaseStatistics.class); + } + } + + @TargetClass(className = "jdk.graal.compiler.lir.phases.LIRPhase", innerClass = "LIRPhaseStatistics", classLoader = GuestGraalClassLoaderSupplier.class, onlyWith = GuestGraalFeature.IsEnabled.class) + static final class Target_jdk_graal_compiler_lir_phases_LIRPhase_LIRPhaseStatistics { + } + + @TargetClass(className = "jdk.graal.compiler.graph.NodeClass", classLoader = GuestGraalClassLoaderSupplier.class, onlyWith = GuestGraalFeature.IsEnabled.class) + static final class Target_jdk_graal_compiler_graph_NodeClass { + + /* + * Redirect method to image build-time pre-computed nodeClasses map in + * GuestGraalCompilerSupport. + */ + @Substitute + public static Target_jdk_graal_compiler_graph_NodeClass get(Class clazz) { + Object nodeClass = GuestGraalCompilerSupport.get().nodeClasses.get(clazz); + if (nodeClass == null) { + throw VMError.shouldNotReachHere(String.format("Unknown node class: %s%n", clazz.getName())); + } + return SubstrateUtil.cast(nodeClass, Target_jdk_graal_compiler_graph_NodeClass.class); + } + + @Alias // + private String shortName; + + /* + * All node-classes in libgraal are in the image-heap and were already fully initialized at + * build-time. The shortName accessor method can be reduced to a simple field access. + */ + @Substitute + public String shortName() { + assert shortName != null; + return shortName; + } + } + + @TargetClass(className = "jdk.graal.compiler.lir.LIRInstructionClass", classLoader = GuestGraalClassLoaderSupplier.class, onlyWith = GuestGraalFeature.IsEnabled.class) + static final class Target_jdk_graal_compiler_lir_LIRInstructionClass { + + /* + * Redirect method to image build-time pre-computed instructionClasses map in + * GuestGraalCompilerSupport. + */ + @Substitute + public static Target_jdk_graal_compiler_lir_LIRInstructionClass get(Class clazz) { + Object instructionClass = GuestGraalCompilerSupport.get().instructionClasses.get(clazz); + if (instructionClass == null) { + throw VMError.shouldNotReachHere(String.format("Unknown instruction class: %s%n", clazz.getName())); + } + return SubstrateUtil.cast(instructionClass, Target_jdk_graal_compiler_lir_LIRInstructionClass.class); + } + } + + @TargetClass(className = "jdk.graal.compiler.lir.CompositeValueClass", classLoader = GuestGraalClassLoaderSupplier.class, onlyWith = GuestGraalFeature.IsEnabled.class) + static final class Target_jdk_graal_compiler_lir_CompositeValueClass { + + /* + * Redirect method to image build-time pre-computed compositeValueClasses map in + * GuestGraalCompilerSupport. + */ + @Substitute + public static Target_jdk_graal_compiler_lir_CompositeValueClass get(Class clazz) { + Object compositeValueClass = GuestGraalCompilerSupport.get().compositeValueClasses.get(clazz); + if (compositeValueClass == null) { + throw VMError.shouldNotReachHere(String.format("Unknown composite value class: %s%n", clazz.getName())); + } + return SubstrateUtil.cast(compositeValueClass, Target_jdk_graal_compiler_lir_CompositeValueClass.class); + } + } +} + +/* + * This supplier is used by all GuestGraalSubstitutions and ensures that the substitution target + * classes are classes from the GuestGraalClassLoader (instead of hosted Graal & JVMCI classes). + */ +@Platforms(HOSTED_ONLY.class) +class GuestGraalClassLoaderSupplier implements Supplier { + @Override + public ClassLoader get() { + GuestGraalClassLoader loader = ImageSingletons.lookup(GuestGraalFeature.class).loader; + VMError.guarantee(loader != null); + return loader; + } +} diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalEntryPoints.java b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalEntryPoints.java index 9cfde5a30eb5..a524e3eee666 100644 --- a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalEntryPoints.java +++ b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalEntryPoints.java @@ -30,40 +30,27 @@ import java.io.PrintStream; import java.lang.management.ManagementFactory; import java.util.Arrays; -import java.util.HashMap; import java.util.Map; import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.EconomicSet; import org.graalvm.jniutils.JNI.JNIEnv; import org.graalvm.jniutils.JNIMethodScope; -import org.graalvm.nativeimage.UnmanagedMemory; import org.graalvm.nativeimage.c.function.CEntryPoint; -import org.graalvm.nativeimage.c.struct.RawField; -import org.graalvm.nativeimage.c.struct.RawFieldAddress; -import org.graalvm.nativeimage.c.struct.RawStructure; -import org.graalvm.nativeimage.c.struct.SizeOf; -import org.graalvm.nativeimage.c.type.CIntPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; -import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.WordFactory; -import com.oracle.svm.core.c.CGlobalData; import com.oracle.svm.core.heap.Heap; import com.sun.management.ThreadMXBean; -import jdk.graal.compiler.core.common.spi.ForeignCallSignature; -import jdk.graal.compiler.core.target.Backend; import jdk.graal.compiler.debug.GlobalMetrics; import jdk.graal.compiler.hotspot.CompilationContext; import jdk.graal.compiler.hotspot.CompilationTask; -import jdk.graal.compiler.hotspot.HotSpotForeignCallLinkageImpl.CodeInfo; import jdk.graal.compiler.hotspot.HotSpotGraalCompiler; import jdk.graal.compiler.hotspot.HotSpotGraalRuntime; import jdk.graal.compiler.hotspot.HotSpotGraalServices; +import jdk.graal.compiler.hotspot.ProfileReplaySupport; import jdk.graal.compiler.hotspot.ProfileReplaySupport.Options; -import jdk.graal.compiler.hotspot.stubs.Stub; import jdk.graal.compiler.options.OptionDescriptors; import jdk.graal.compiler.options.OptionKey; import jdk.graal.compiler.options.OptionValues; @@ -71,8 +58,6 @@ import jdk.graal.compiler.util.OptionsEncoder; import jdk.internal.misc.Unsafe; import jdk.vm.ci.code.InstalledCode; -import jdk.vm.ci.code.Register; -import jdk.vm.ci.code.RegisterArray; import jdk.vm.ci.hotspot.HotSpotCompilationRequest; import jdk.vm.ci.hotspot.HotSpotInstalledCode; import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; @@ -91,117 +76,6 @@ */ public final class LibGraalEntryPoints { - /** - * Map from a foreign call signature to a C global word that is the address of a pointer to a - * {@link RuntimeStubInfo} struct. - * - * This map is used to mitigate against libgraal isolates compiling and installing duplicate - * {@code RuntimeStub}s for a foreign call. Completely preventing duplicates requires - * inter-isolate synchronization for which there is currently no support. A small number of - * duplicates is acceptable given how few foreign call runtime stubs there are in practice. - * Duplicates will only result when libgraal isolates race to install code for the same stub. - * Testing shows that this is extremely rare. - */ - static final Map> STUBS = new HashMap<>(); - - /** - * A struct that encapsulates the address of the first instruction of and the registers killed - * by a HotSpot {@code RuntimeStub}. One such struct is allocated for each entry in - * {@link LibGraalEntryPoints#STUBS}. - * - *
-     * struct RuntimeStubInfo {
-     *     long start;
-     *     int killedRegistersLength;
-     *     int[killedRegistersLength] killedRegisters;
-     * }
-     * 
- */ - @RawStructure - public interface RuntimeStubInfo extends PointerBase { - // Checkstyle: stop - // @formatter:off - /** - * Address of first instruction in the stub. - */ - @RawField long getStart(); - @RawField void setStart(long value); - - /** - * Length of the killedRegisters array. - */ - @RawField int getKilledRegistersLength(); - @RawField void setKilledRegistersLength(int value); - - /** - * First element of the killedRegisters array. - */ - @RawField int firstKilledRegister(); - @RawFieldAddress CIntPointer addressOf_firstKilledRegister(); - // @formatter:on - // Checkstyle: resume - - class Util { - - static RuntimeStubInfo allocateRuntimeStubInfo(int killedRegistersLength) { - // The first element of the killedRegister array is part of - // the RuntimeStubInfo size computation - int registersTailSize = Integer.BYTES * (killedRegistersLength - 1); - return UnmanagedMemory.malloc(SizeOf.get(RuntimeStubInfo.class) + registersTailSize); - } - - /** - * Allocates and initializes a {@code RuntimeStubInfo} from {@code stub}. - */ - static RuntimeStubInfo newRuntimeStubInfo(Stub stub, Backend backend) { - long start = stub.getCode(backend).getStart(); - EconomicSet registers = stub.getDestroyedCallerRegisters(); - - RuntimeStubInfo rsi = allocateRuntimeStubInfo(registers.size()); - rsi.setStart(start); - rsi.setKilledRegistersLength(registers.size()); - copyIntoCIntArray(registers, rsi.addressOf_firstKilledRegister()); - return rsi; - } - - /** - * Copies the {@linkplain Register#number register numbers} for the entries in - * {@code register} into a newly allocated C int array and returns it. - */ - static void copyIntoCIntArray(EconomicSet registers, CIntPointer dst) { - int i = 0; - for (Register r : registers) { - dst.write(i, r.number); - i++; - } - } - - /** - * Selects entries from {@code allRegisters} corresponding to - * {@linkplain Register#number register numbers} in the C int array {@code regNums} of - * length {@code len} and returns them in a set. - */ - static EconomicSet toRegisterSet(RegisterArray allRegisters, int len, CIntPointer regNums) { - EconomicSet res = EconomicSet.create(len); - for (int i = 0; i < len; i++) { - res.add(allRegisters.get(regNums.read(i))); - } - return res; - } - - /** - * Creates and returns a {@link CodeInfo} from the data in {@code rsi}. - */ - static CodeInfo newCodeInfo(RuntimeStubInfo rsi, Backend backend) { - RegisterArray allRegisters = backend.getCodeCache().getTarget().arch.getRegisters(); - long start = rsi.getStart(); - return new CodeInfo(start, toRegisterSet(allRegisters, - rsi.getKilledRegistersLength(), - rsi.addressOf_firstKilledRegister())); - } - } - } - static class CachedOptions { final OptionValues options; final long hash; @@ -311,7 +185,7 @@ private static long hashConstantOopFields(JNIEnv jniEnv, /** * The implementation of - * {@code jdk.graal.compiler.hotspot.test.StandaloneBulkCompile.compileMethodInLibgraal()}. + * {@code jdk.graal.compiler.hotspot.test.LibGraalCompilationDriver#compileMethodInLibgraal}. * * @param methodHandle the method to be compiled. This is a handle to a * {@link HotSpotResolvedJavaMethod} in HotSpot's heap. A value of 0L can be passed @@ -377,7 +251,7 @@ private static long compileMethod(JNIEnv jniEnv, CompilationTask task = new CompilationTask(runtime, compiler, request, useProfilingInfo, false, false, eagerResolving, installAsDefault); if (profilePathBufferAddress > 0) { String profileLoadPath = CTypeConversion.toJavaString(WordFactory.pointer(profilePathBufferAddress)); - options = new OptionValues(options, Options.LoadProfiles, profileLoadPath); + options = new OptionValues(options, ProfileReplaySupport.Options.LoadProfiles, profileLoadPath); } long allocatedBytesBefore = 0; ThreadMXBean threadMXBean = null; diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java index 3993cf1d4060..3752625f7336 100644 --- a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java +++ b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java @@ -26,27 +26,13 @@ import static com.oracle.svm.graal.hotspot.libgraal.HotSpotGraalOptionValuesUtil.compilerOptionDescriptors; import static com.oracle.svm.graal.hotspot.libgraal.HotSpotGraalOptionValuesUtil.vmOptionDescriptors; -import static com.oracle.svm.graal.hotspot.libgraal.LibGraalEntryPoints.RuntimeStubInfo.Util.newCodeInfo; -import static com.oracle.svm.graal.hotspot.libgraal.LibGraalEntryPoints.RuntimeStubInfo.Util.newRuntimeStubInfo; -import static jdk.vm.ci.hotspot.HotSpotJVMCIRuntime.runtime; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; import java.io.PrintStream; import java.lang.annotation.Annotation; -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayDeque; -import java.util.Arrays; import java.util.Collections; import java.util.Deque; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -54,23 +40,11 @@ import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BooleanSupplier; -import java.util.stream.Collectors; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.MapCursor; -import org.graalvm.jniutils.JNI; -import org.graalvm.jniutils.JNIExceptionWrapper; -import org.graalvm.jniutils.JNIMethodScope; -import org.graalvm.jniutils.JNIUtil; import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.LogHandler; -import org.graalvm.nativeimage.StackValue; -import org.graalvm.nativeimage.VMRuntime; import org.graalvm.nativeimage.hosted.Feature; -import org.graalvm.nativeimage.hosted.RuntimeJNIAccess; -import org.graalvm.nativeimage.hosted.RuntimeReflection; -import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.ObjectScanner; @@ -78,7 +52,6 @@ import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.meta.InvokeInfo; import com.oracle.graal.pointsto.meta.ObjectReachableCallback; -import com.oracle.svm.core.OS; import com.oracle.svm.core.RuntimeAssertionsSupport; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; @@ -87,56 +60,43 @@ import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; -import com.oracle.svm.core.c.CGlobalData; -import com.oracle.svm.core.c.CGlobalDataFactory; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.graal.meta.RuntimeConfiguration; import com.oracle.svm.core.graal.snippets.NodeLoweringProvider; -import com.oracle.svm.core.heap.GCCause; -import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.heap.UnknownObjectField; -import com.oracle.svm.core.log.FunctionPointerLogHandler; import com.oracle.svm.core.option.RuntimeOptionKey; import com.oracle.svm.core.option.RuntimeOptionValues; import com.oracle.svm.core.option.XOptions; 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.graal.hosted.GraalCompilerFeature; -import com.oracle.svm.graal.hotspot.libgraal.LibGraalEntryPoints.RuntimeStubInfo; +import com.oracle.svm.graal.hotspot.GetJNIConfig; import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.jni.JNIFeature; import com.oracle.svm.hosted.reflect.ReflectionFeature; -import com.oracle.svm.util.LogUtils; import com.oracle.svm.util.ModuleSupport; import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.code.DisassemblerProvider; import jdk.graal.compiler.core.GraalServiceThread; -import jdk.graal.compiler.core.common.spi.ForeignCallSignature; import jdk.graal.compiler.core.target.Backend; -import jdk.graal.compiler.debug.DebugContext; import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.graph.Node; import jdk.graal.compiler.graph.NodeClass; import jdk.graal.compiler.hotspot.EncodedSnippets; import jdk.graal.compiler.hotspot.HotSpotBackend; import jdk.graal.compiler.hotspot.HotSpotCodeCacheListener; -import jdk.graal.compiler.hotspot.HotSpotForeignCallLinkageImpl; -import jdk.graal.compiler.hotspot.HotSpotForeignCallLinkageImpl.CodeInfo; +import jdk.graal.compiler.hotspot.HotSpotForeignCallLinkage; import jdk.graal.compiler.hotspot.HotSpotGraalCompiler; -import jdk.graal.compiler.hotspot.HotSpotGraalRuntime; import jdk.graal.compiler.hotspot.HotSpotReplacementsImpl; import jdk.graal.compiler.hotspot.SnippetObjectConstant; import jdk.graal.compiler.hotspot.meta.HotSpotHostForeignCallsProvider; import jdk.graal.compiler.hotspot.meta.HotSpotInvocationPluginProvider; import jdk.graal.compiler.hotspot.meta.HotSpotProviders; -import jdk.graal.compiler.hotspot.stubs.Stub; import jdk.graal.compiler.nodes.graphbuilderconf.GeneratedPluginFactory; import jdk.graal.compiler.nodes.spi.SnippetParameterInfo; -import jdk.graal.compiler.options.Option; import jdk.graal.compiler.options.OptionDescriptor; import jdk.graal.compiler.options.OptionDescriptors; import jdk.graal.compiler.options.OptionDescriptorsMap; @@ -155,34 +115,13 @@ import jdk.graal.compiler.truffle.substitutions.GraphBuilderInvocationPluginProvider; import jdk.graal.compiler.truffle.substitutions.GraphDecoderInvocationPluginProvider; import jdk.internal.misc.Unsafe; -import jdk.vm.ci.code.CompilationRequest; -import jdk.vm.ci.code.CompilationRequestResult; import jdk.vm.ci.hotspot.HotSpotConstantReflectionProvider; import jdk.vm.ci.hotspot.HotSpotJVMCIBackendFactory; import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; -import jdk.vm.ci.hotspot.HotSpotSignature; import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.services.Services; -class LibGraalOptions { - @Option(help = "If non-zero, converts an exception triggered by the CrashAt option into a fatal error " + - "if a non-null pointer was passed in the _fatal option to JNI_CreateJavaVM. " + - "The value of this option is the number of milliseconds to sleep before calling _fatal. " + - "This option exists for the purpose of testing fatal error handling in libgraal.") // - static final RuntimeOptionKey CrashAtIsFatal = new LibGraalRuntimeOptionKey<>(0); - @Option(help = "The fully qualified name of a no-arg, void, static method to be invoked " + - "in HotSpot from libgraal when the libgraal isolate is being shutdown." + - "This option exists for the purpose of testing callbacks in this context.") // - static final RuntimeOptionKey OnShutdownCallback = new LibGraalRuntimeOptionKey<>(null); - @Option(help = "Replaces first exception thrown by the CrashAt option with an OutOfMemoryError. " + - "Subsequently CrashAt exceptions are suppressed. " + - "This option exists to test HeapDumpOnOutOfMemoryError. " + - "See the MethodFilter option for the pattern syntax.") // - static final RuntimeOptionKey CrashAtThrowsOOME = new LibGraalRuntimeOptionKey<>(false); -} - public class LibGraalFeature implements InternalFeature { private final OptionCollector optionCollector = new OptionCollector(); @@ -202,15 +141,6 @@ public LibGraalFeature() { ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, LibGraalFeature.class, true, "org.graalvm.nativeimage.llvm"); } - @Override - public void afterImageWrite(AfterImageWriteAccess access) { - } - - @Override - public boolean isInConfiguration(IsInConfigurationAccess access) { - return true; - } - @Override public List> getRequiredFeatures() { return List.of(JNIFeature.class, GraalCompilerFeature.class, ReflectionFeature.class); @@ -229,10 +159,12 @@ public void duringSetup(DuringSetupAccess a) { access.registerObjectReachableCallback(OptionKey.class, optionCollector::doCallback); ImageClassLoader imageClassLoader = access.getImageClassLoader(); - registerJNIConfiguration(imageClassLoader); + GetJNIConfig.register(imageClassLoader.getClassLoader()); } - /** Collects all {@link OptionKey}s that are reachable at run time. */ + /** + * Collects all {@link OptionKey}s that are reachable at run time. + */ private static class OptionCollector implements ObjectReachableCallback> { final ConcurrentHashMap, OptionKey> options = new ConcurrentHashMap<>(); private boolean sealed; @@ -251,194 +183,6 @@ public void setSealed() { } } - /** - * Helper for registering the JNI configuration for libgraal by parsing the output of the - * {@code -XX:JVMCILibDumpJNIConfig} VM option. - */ - static class JNIConfigSource implements AutoCloseable { - /** - * VM command executed to read the JNI config. - */ - private final String quotedCommand; - - /** - * JNI config lines. - */ - private final List lines; - - /** - * Loader used to resolve type names in the config. - */ - private final ImageClassLoader loader; - - /** - * Path to intermediate file containing the config. This is deleted unless there is an - * {@link #error(String, Object...)} parsing the config to make diagnosing the error easier. - */ - private Path configFilePath; - - int lineNo; - - JNIConfigSource(ImageClassLoader loader) { - this.loader = loader; - Path javaHomePath = Paths.get(System.getProperty("java.home")); - Path binJava = Paths.get("bin", OS.getCurrent() == OS.WINDOWS ? "java.exe" : "java"); - Path javaExe = javaHomePath.resolve(binJava); - if (!Files.isExecutable(javaExe)) { - throw UserError.abort("Java launcher %s does not exist or is not executable", javaExe); - } - configFilePath = Paths.get("libgraal_jniconfig.txt"); - - String[] command = {javaExe.toFile().getAbsolutePath(), "-XX:+UnlockExperimentalVMOptions", "-XX:+EnableJVMCI", "-XX:JVMCILibDumpJNIConfig=" + configFilePath}; - quotedCommand = Arrays.asList(command).stream().map(e -> e.indexOf(' ') == -1 ? e : '\'' + e + '\'').collect(Collectors.joining(" ")); - ProcessBuilder pb = new ProcessBuilder(command); - pb.redirectErrorStream(true); - Process p; - try { - p = pb.start(); - } catch (IOException e) { - throw UserError.abort("Could not run command: %s%n%s", quotedCommand, e); - } - - String nl = System.getProperty("line.separator"); - String out = new BufferedReader(new InputStreamReader(p.getInputStream())) - .lines().collect(Collectors.joining(nl)); - - int exitValue; - try { - exitValue = p.waitFor(); - } catch (InterruptedException e) { - throw UserError.abort("Interrupted waiting for command: %s%n%s", quotedCommand, out); - } - if (exitValue != 0) { - throw UserError.abort("Command finished with exit value %d: %s%n%s", exitValue, quotedCommand, out); - } - try { - lines = Files.readAllLines(configFilePath); - } catch (IOException e) { - configFilePath = null; - throw UserError.abort("Reading JNI config in %s dumped by command: %s%n%s", configFilePath, quotedCommand, out); - } - } - - @Override - public void close() { - if (configFilePath != null && Files.exists(configFilePath)) { - try { - Files.delete(configFilePath); - configFilePath = null; - } catch (IOException e) { - LogUtils.warning("Could not delete %s: %s", configFilePath, e); - } - } - } - - Class findClass(String name) { - String internalName = name; - if (name.startsWith("L") && name.endsWith(";")) { - internalName = name.substring(1, name.length() - 1); - } - Class c = loader.findClass(internalName).get(); - if (c == null) { - throw error("Class " + internalName + " not found"); - } - return c; - } - - void check(boolean condition, String format, Object... args) { - if (!condition) { - error(format, args); - } - } - - UserException error(String format, Object... args) { - Path path = configFilePath; - configFilePath = null; // prevent deletion - String errorMessage = String.format(format, args); - String errorLine = lines.get(lineNo - 1); - throw UserError.abort("Line %d of %s: %s%n%s%n%s generated by command: %s", - lineNo, path.toAbsolutePath(), errorMessage, errorLine, path, quotedCommand); - - } - } - - private static void registerJNIConfiguration(ImageClassLoader loader) { - try (JNIConfigSource source = new JNIConfigSource(loader)) { - Map> classes = new HashMap<>(); - for (String line : source.lines) { - source.lineNo++; - String[] tokens = line.split(" "); - source.check(tokens.length >= 2, "Expected at least 2 tokens"); - String className = tokens[1].replace('/', '.'); - Class clazz = classes.get(className); - if (clazz == null) { - clazz = source.findClass(className); - RuntimeJNIAccess.register(clazz); - RuntimeJNIAccess.register(Array.newInstance(clazz, 0).getClass()); - classes.put(className, clazz); - } - - switch (tokens[0]) { - case "field": { - source.check(tokens.length == 4, "Expected 4 tokens for a field"); - String fieldName = tokens[2]; - try { - RuntimeJNIAccess.register(clazz.getDeclaredField(fieldName)); - } catch (NoSuchFieldException e) { - throw source.error("Field %s.%s not found", clazz.getTypeName(), fieldName); - } catch (NoClassDefFoundError e) { - throw source.error("Could not register field %s.%s: %s", clazz.getTypeName(), fieldName, e); - } - break; - } - case "method": { - source.check(tokens.length == 4, "Expected 4 tokens for a method"); - String methodName = tokens[2]; - HotSpotSignature descriptor = new HotSpotSignature(runtime(), tokens[3]); - Class[] parameters = Arrays.asList(descriptor.toParameterTypes(null))// - .stream().map(JavaType::toClassName).map(source::findClass)// - .collect(Collectors.toList())// - .toArray(new Class[descriptor.getParameterCount(false)]); - try { - if ("".equals(methodName)) { - Constructor cons = clazz.getDeclaredConstructor(parameters); - RuntimeJNIAccess.register(cons); - if (Throwable.class.isAssignableFrom(clazz) && !Modifier.isAbstract(clazz.getModifiers())) { - if (usedInTranslatedException(parameters)) { - RuntimeReflection.register(clazz); - RuntimeReflection.register(cons); - } - } - } else { - RuntimeJNIAccess.register(clazz.getDeclaredMethod(methodName, parameters)); - } - } catch (NoSuchMethodException e) { - throw source.error("Method %s.%s%s not found: %s", clazz.getTypeName(), methodName, descriptor, e); - } catch (NoClassDefFoundError e) { - throw source.error("Could not register method %s.%s%s: %s", clazz.getTypeName(), methodName, descriptor, e); - } - break; - } - case "class": { - source.check(tokens.length == 2, "Expected 2 tokens for a class"); - break; - } - default: { - throw source.error("Unexpected token: " + tokens[0]); - } - } - } - } - } - - /** - * Determines if a throwable constructor with the signature specified by {@code parameters} is - * potentially called via reflection in {@code jdk.vm.ci.hotspot.TranslatedException}. - */ - private static boolean usedInTranslatedException(Class[] parameters) { - return parameters.length == 0 || (parameters.length == 1 && parameters[0] == String.class); - } - @Override public void registerLowerings(RuntimeConfiguration runtimeConfig, OptionValues options, Providers substrateProviders, Map, NodeLoweringProvider> lowerings, boolean hosted) { @@ -450,7 +194,6 @@ public void registerLowerings(RuntimeConfiguration runtimeConfig, OptionValues o public void beforeAnalysis(BeforeAnalysisAccess access) { FeatureImpl.BeforeAnalysisAccessImpl impl = (FeatureImpl.BeforeAnalysisAccessImpl) access; BigBang bb = impl.getBigBang(); - DebugContext debug = bb.getDebug(); // Services that will not be loaded if native-image is run // with -XX:-UseJVMCICompiler. @@ -463,13 +206,8 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { GraalServices.load(HotSpotInvocationPluginProvider.class); GraalServices.load(TruffleHostEnvironment.Lookup.class); - List truffleBackends; - try (DebugContext.Scope scope = debug.scope("SnippetSupportEncode")) { - // Instantiate the truffle compiler to ensure the backends it uses are initialized. - truffleBackends = HotSpotTruffleCompilerImpl.ensureBackendsInitialized(RuntimeOptionValues.singleton()); - } catch (Throwable t) { - throw debug.handle(t); - } + // Instantiate the truffle compiler to ensure the backends it uses are initialized. + List truffleBackends = HotSpotTruffleCompilerImpl.ensureBackendsInitialized(RuntimeOptionValues.singleton()); // Filter out any cached services which are for a different architecture try { @@ -507,6 +245,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { // Clear the saved names if assertions aren't enabled hotSpotSubstrateReplacements.clearSnippetParameterNames(); } + // Mark all the Node classes as allocated so they are available during graph decoding. EncodedSnippets encodedSnippets = HotSpotReplacementsImpl.getEncodedSnippets(); for (NodeClass nodeClass : encodedSnippets.getSnippetNodeClasses()) { @@ -518,19 +257,10 @@ private static void registerForeignCalls(HotSpotProviders providers) { HotSpotHostForeignCallsProvider foreignCalls = providers.getForeignCalls(); foreignCalls.forEachForeignCall((sig, linkage) -> { if (linkage == null || linkage.isCompiledStub()) { - boolean nonConstant = true; - String symbol = null; - - /* - * We process all foreign calls of all backends including Truffle backends. Some - * stubs may be encountered multiple times with multiple backends. It is enough to - * do this once per stub signature. - */ - if (!LibGraalEntryPoints.STUBS.containsKey(sig)) { - CGlobalData data = CGlobalDataFactory.createWord((Pointer) WordFactory.zero(), symbol, nonConstant); - LibGraalEntryPoints.STUBS.put(sig, data); + if (HotSpotForeignCallLinkage.Stubs.initStub(sig)) { if (linkage != null) { - // Force stub construction + // Construct the stub so that all types it uses are registered in + // SymbolicSnippetEncoder.snippetTypes foreignCalls.lookupForeignCall(sig); } } @@ -644,7 +374,7 @@ static HotSpotReplacementsImpl getReplacements() { } private static boolean isNativeImageOption(OptionKey key) { - return key instanceof RuntimeOptionKey && !(key instanceof LibGraalRuntimeOptionKey); + return key instanceof RuntimeOptionKey; } } @@ -693,7 +423,7 @@ static Object convertUnknownValue(Object object) { } // Annotations are currently unsupported in libgraal. These substitutions will turn their use - // into a image time build error. + // into an image time build error. @Delete static native Annotation[] getClassAnnotations(String className); @@ -746,84 +476,9 @@ void constructor(Object object, boolean compressed) { } } -@TargetClass(className = "jdk.graal.compiler.hotspot.HotSpotGraalCompiler", onlyWith = LibGraalFeature.IsEnabled.class) -final class Target_jdk_graal_compiler_hotspot_HotSpotGraalCompiler { - - @SuppressWarnings({"unused", "try"}) - @Substitute - private static CompilationRequestResult compileMethod(HotSpotGraalCompiler compiler, CompilationRequest request) { - long offset = compiler.getGraalRuntime().getVMConfig().jniEnvironmentOffset; - long javaThreadAddr = HotSpotJVMCIRuntime.runtime().getCurrentJavaThread(); - JNI.JNIEnv env = (JNI.JNIEnv) WordFactory.unsigned(javaThreadAddr).add(WordFactory.unsigned(offset)); - // This scope is required to allow Graal compilations of host methods to call methods - // on the TruffleCompilerRuntime. This is, for example, required to find out about - // Truffle-specific method annotations. - try { - try (JNIMethodScope scope = LibGraalUtil.openScope("", env)) { - return compiler.compileMethod(request, true, compiler.getGraalRuntime().getOptions()); - } - } finally { - /* - * libgraal doesn't use a dedicated reference handler thread, so we trigger the - * reference handling manually when a compilation finishes. - */ - LibGraalEntryPoints.doReferenceHandling(); - } - } -} - -@TargetClass(className = "jdk.graal.compiler.hotspot.HotSpotGraalRuntime", onlyWith = LibGraalFeature.IsEnabled.class) -final class Target_jdk_graal_compiler_hotspot_HotSpotGraalRuntime { - - @SuppressWarnings("unused") - @Substitute - private static void startupLibGraal(HotSpotGraalRuntime runtime) { - VMRuntime.initialize(); - } - - @SuppressWarnings("unused") - @Substitute - private static void shutdownLibGraal(HotSpotGraalRuntime runtime) { - try { - String callback = LibGraalOptions.OnShutdownCallback.getValue(); - if (callback != null) { - long offset = runtime.getVMConfig().jniEnvironmentOffset; - long javaThreadAddr = HotSpotJVMCIRuntime.runtime().getCurrentJavaThread(); - JNI.JNIEnv env = (JNI.JNIEnv) WordFactory.unsigned(javaThreadAddr).add(WordFactory.unsigned(offset)); - int lastDot = callback.lastIndexOf('.'); - if (lastDot < 1 || lastDot == callback.length() - 1) { - throw new IllegalArgumentException(LibGraalOptions.OnShutdownCallback.getName() + " value does not have . format: " + callback); - } - String cbClassName = callback.substring(0, lastDot); - String cbMethodName = callback.substring(lastDot + 1); - JNI.JClass cbClass = JNIUtil.findClass(env, JNIUtil.getSystemClassLoader(env), - JNIUtil.getBinaryName(cbClassName), true); - JNI.JMethodID cbMethod = JNIUtil.findMethod(env, cbClass, true, cbMethodName, "()V"); - env.getFunctions().getCallStaticVoidMethodA().call(env, cbClass, cbMethod, StackValue.get(0)); - JNIExceptionWrapper.wrapAndThrowPendingJNIException(env); - } - } finally { - VMRuntime.shutdown(); - } - } -} - -@TargetClass(className = "jdk.graal.compiler.serviceprovider.GraalServices", onlyWith = LibGraalFeature.IsEnabled.class) -final class Target_jdk_graal_compiler_serviceprovider_GraalServices { - - @Substitute - private static void notifyLowMemoryPoint(boolean hintFullGC, boolean forceFullGC) { - if (forceFullGC) { - Heap.getHeap().getGC().collectCompletely(GCCause.JavaLangSystemGC); - } else { - Heap.getHeap().getGC().collectionHint(hintFullGC); - } - LibGraalEntryPoints.doReferenceHandling(); - } -} - @TargetClass(className = "jdk.graal.compiler.hotspot.HotSpotGraalOptionValues", onlyWith = LibGraalFeature.IsEnabled.class) final class Target_jdk_graal_compiler_hotspot_HotSpotGraalOptionValues { + @Substitute private static void notifyLibgraalOptions(EconomicMap, Object> compilerOptionValues, EconomicMap vmOptionSettings) { HotSpotGraalOptionValuesUtil.initializeOptions(compilerOptionValues, vmOptionSettings); @@ -858,10 +513,6 @@ static void initializeOptions(EconomicMap, Object> compilerOptionVa RuntimeOptionValues vmOptions = RuntimeOptionValues.singleton(); vmOptions.update(compilerOptionValues); - if (LibGraalOptions.CrashAtThrowsOOME.getValue() && LibGraalOptions.CrashAtIsFatal.getValue() != 0) { - throw new IllegalArgumentException("CrashAtThrowsOOME and CrashAtIsFatal cannot both be enabled"); - } - if (!vmOptionSettings.isEmpty()) { MapCursor entries = vmOptionSettings.getEntries(); while (entries.advance()) { @@ -899,62 +550,7 @@ void afterRun() { } } -@TargetClass(className = "jdk.graal.compiler.core.GraalCompiler", onlyWith = LibGraalFeature.IsEnabled.class) -final class Target_jdk_graal_compiler_core_GraalCompiler { - @SuppressWarnings("unused") - @Substitute() - private static boolean notifyCrash(String crashMessage) { - if (LibGraalOptions.CrashAtThrowsOOME.getValue()) { - if (HotSpotGraalOptionValuesUtil.OOME_CRASH_DONE.compareAndSet(0L, 1L)) { - // The -Djdk.graal.internal.Xmx option should also be employed to make - // this allocation fail quicky - String largeString = Arrays.toString(new int[Integer.MAX_VALUE - 1]); - throw new InternalError("Failed to trigger OOME: largeString.length=" + largeString.length()); - } else { - // Remaining compilations should proceed so that test finishes quickly. - return false; - } - } else if (LibGraalOptions.CrashAtIsFatal.getValue() != 0) { - LogHandler handler = ImageSingletons.lookup(LogHandler.class); - if (handler instanceof FunctionPointerLogHandler) { - try { - Thread.sleep(LibGraalOptions.CrashAtIsFatal.getValue()); - } catch (InterruptedException e) { - // ignore - } - VMError.shouldNotReachHere(crashMessage); - } - // If changing this message, update the test for it in mx_vm_gate.py - System.out.println("CrashAtIsFatal: no fatalError function pointer installed"); - } - return true; - } -} - @TargetClass(className = "jdk.graal.compiler.hotspot.SymbolicSnippetEncoder", onlyWith = LibGraalFeature.IsEnabled.class) @Delete("shouldn't appear in libgraal") final class Target_jdk_graal_compiler_hotspot_SymbolicSnippetEncoder { } - -@TargetClass(value = HotSpotForeignCallLinkageImpl.class, onlyWith = LibGraalFeature.IsEnabled.class) -final class Target_jdk_graal_compiler_hotspot_HotSpotForeignCallLinkageImpl { - /** - * Gets the code info for a runtime stub, consulting and updating - * {@link LibGraalEntryPoints#STUBS} in the process to share runtime stub code info between - * libgraal isolates. - */ - @SuppressWarnings("unused") - @Substitute - private static CodeInfo getCodeInfo(Stub stub, Backend backend) { - ForeignCallSignature sig = stub.getLinkage().getDescriptor().getSignature(); - CGlobalData data = LibGraalEntryPoints.STUBS.get(sig); - GraalError.guarantee(data != null, "missing global data for %s", sig); - Pointer rsiPointer = data.get(); - RuntimeStubInfo rsi = rsiPointer.readWord(0); - if (rsi.isNull()) { - rsi = newRuntimeStubInfo(stub, backend); - rsiPointer.writeWord(0, rsi); - } - return newCodeInfo(rsi, backend); - } -} diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalSubstitutions.java b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalSubstitutions.java index 34531e7628c0..e573465489e0 100644 --- a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalSubstitutions.java +++ b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalSubstitutions.java @@ -29,13 +29,46 @@ import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.heap.GCCause; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.log.FunctionPointerLogHandler; +import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.serviceprovider.VMSupport; +import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; +import jdk.vm.ci.hotspot.HotSpotVMConfigAccess; +import jdk.vm.ci.hotspot.HotSpotVMConfigStore; import jdk.vm.ci.services.Services; +import org.graalvm.jniutils.JNI; +import org.graalvm.jniutils.JNIExceptionWrapper; +import org.graalvm.jniutils.JNIMethodScope; +import org.graalvm.jniutils.JNIUtil; +import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.LogHandler; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.VMRuntime; +import org.graalvm.nativeimage.impl.IsolateSupport; +import org.graalvm.word.WordFactory; -/** Dummy class to have a class with the file's name. Do not remove. */ +/** + * Contains support code for the substitutions declared in this file. + */ public final class LibGraalSubstitutions { - // Dummy + + static long jniEnvironmentOffset = Integer.MAX_VALUE; + + static long getJniEnvironmentOffset() { + if (jniEnvironmentOffset == Integer.MAX_VALUE) { + HotSpotJVMCIRuntime jvmciRuntime = HotSpotJVMCIRuntime.runtime(); + HotSpotVMConfigStore store = jvmciRuntime.getConfigStore(); + HotSpotVMConfigAccess config = new HotSpotVMConfigAccess(store); + jniEnvironmentOffset = config.getFieldOffset("JavaThread::_jni_environment", Integer.class, "JNIEnv"); + } + return jniEnvironmentOffset; + } } @TargetClass(value = Services.class, onlyWith = LibGraalFeature.IsEnabled.class) @@ -65,3 +98,105 @@ final class Target_jdk_vm_ci_hotspot_Cleaner { @Alias static native void clean(); } + +@TargetClass(value = VMSupport.class, onlyWith = LibGraalFeature.IsEnabled.class) +final class Target_jdk_graal_compiler_serviceprovider_VMSupport { + + @Substitute + public static long getIsolateAddress() { + return CurrentIsolate.getIsolate().rawValue(); + } + + @Substitute + public static long getIsolateID() { + return ImageSingletons.lookup(IsolateSupport.class).getIsolateID(); + } + + /** + * Performs the following actions around a libgraal compilation: + *
    + *
  • before: opens a JNIMethodScope to allow Graal compilations of Truffle host methods to + * call methods on the TruffleCompilerRuntime.
  • + *
  • after: closes the above JNIMethodScope
  • + *
  • after: triggers GC weak reference processing as SVM does not use a separate thread for + * this in libgraal
  • + *
+ */ + static class LibGraalCompilationRequestScope implements AutoCloseable { + final JNIMethodScope scope; + + LibGraalCompilationRequestScope() { + HotSpotJVMCIRuntime jvmciRuntime = HotSpotJVMCIRuntime.runtime(); + long offset = LibGraalSubstitutions.getJniEnvironmentOffset(); + long javaThreadAddr = jvmciRuntime.getCurrentJavaThread(); + JNI.JNIEnv env = (JNI.JNIEnv) WordFactory.unsigned(javaThreadAddr).add(WordFactory.unsigned(offset)); + // This scope is required to allow Graal compilations of host methods to call methods + // on the TruffleCompilerRuntime. This is, for example, required to find out about + // Truffle-specific method annotations. + scope = LibGraalUtil.openScope("", env); + } + + @Override + public void close() { + try { + scope.close(); + } finally { + /* + * libgraal doesn't use a dedicated reference handler thread, so we trigger the + * reference handling manually when a compilation finishes. + */ + LibGraalEntryPoints.doReferenceHandling(); + } + } + } + + @Substitute + public static AutoCloseable getCompilationRequestScope() { + return new LibGraalCompilationRequestScope(); + } + + @Substitute + public static void fatalError(String message, int delayMS) { + LogHandler handler = ImageSingletons.lookup(LogHandler.class); + if (handler instanceof FunctionPointerLogHandler) { + try { + Thread.sleep(delayMS); + } catch (InterruptedException e) { + // ignore + } + VMError.shouldNotReachHere(message); + } + } + + @Substitute + public static void startupLibGraal() { + VMRuntime.initialize(); + } + + @Substitute + public static void shutdownLibGraal() { + VMRuntime.shutdown(); + } + + @Substitute + public static void invokeShutdownCallback(String cbClassName, String cbMethodName) { + long offset = LibGraalSubstitutions.getJniEnvironmentOffset(); + long javaThreadAddr = HotSpotJVMCIRuntime.runtime().getCurrentJavaThread(); + JNI.JNIEnv env = (JNI.JNIEnv) WordFactory.unsigned(javaThreadAddr).add(WordFactory.unsigned(offset)); + JNI.JClass cbClass = JNIUtil.findClass(env, JNIUtil.getSystemClassLoader(env), + JNIUtil.getBinaryName(cbClassName), true); + JNI.JMethodID cbMethod = JNIUtil.findMethod(env, cbClass, true, cbMethodName, "()V"); + env.getFunctions().getCallStaticVoidMethodA().call(env, cbClass, cbMethod, StackValue.get(0)); + JNIExceptionWrapper.wrapAndThrowPendingJNIException(env); + } + + @Substitute + public static void notifyLowMemoryPoint(boolean hintFullGC, boolean forceFullGC) { + if (forceFullGC) { + Heap.getHeap().getGC().collectCompletely(GCCause.JavaLangSystemGC); + } else { + Heap.getHeap().getGC().collectionHint(hintFullGC); + } + LibGraalEntryPoints.doReferenceHandling(); + } +} diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalUtil.java b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalUtil.java index 56e6ef03a0b5..8288326117a4 100644 --- a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalUtil.java +++ b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalUtil.java @@ -26,8 +26,15 @@ import java.util.Objects; +import com.oracle.svm.graal.hotspot.LibGraalJNIMethodScope; +import com.oracle.svm.util.ClassUtil; +import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; +import jdk.vm.ci.hotspot.HotSpotVMConfigAccess; import org.graalvm.jniutils.JNI.JNIEnv; import org.graalvm.jniutils.JNIMethodScope; +import org.graalvm.nativeimage.c.type.CLongPointer; +import org.graalvm.word.PointerBase; +import org.graalvm.word.WordFactory; public final class LibGraalUtil { @@ -36,11 +43,31 @@ private LibGraalUtil() { public static JNIMethodScope openScope(Class entryPointClass, Enum id, JNIEnv env) { Objects.requireNonNull(id, "Id must be non null."); - return LibGraalJNIMethodScope.open(com.oracle.svm.util.ClassUtil.getUnqualifiedName(entryPointClass) + "::" + id, env); + String scopeName = ClassUtil.getUnqualifiedName(entryPointClass) + "::" + id; + return LibGraalJNIMethodScope.open(scopeName, env, getJavaFrameAnchor().isNonNull()); } public static JNIMethodScope openScope(String scopeName, JNIEnv env) { - return LibGraalJNIMethodScope.open(scopeName, env); + return LibGraalJNIMethodScope.open(scopeName, env, getJavaFrameAnchor().isNonNull()); + } + + private static volatile int lastJavaPCOffset = -1; + + private static PointerBase getJavaFrameAnchor() { + CLongPointer currentThreadLastJavaPCOffset = (CLongPointer) WordFactory.unsigned(HotSpotJVMCIRuntime.runtime().getCurrentJavaThread()).add(getLastJavaPCOffset()); + return WordFactory.pointer(currentThreadLastJavaPCOffset.read()); + } + + private static int getLastJavaPCOffset() { + int res = lastJavaPCOffset; + if (res == -1) { + HotSpotVMConfigAccess configAccess = new HotSpotVMConfigAccess(HotSpotJVMCIRuntime.runtime().getConfigStore()); + int anchor = configAccess.getFieldOffset("JavaThread::_anchor", Integer.class, "JavaFrameAnchor"); + int lastJavaPc = configAccess.getFieldOffset("JavaFrameAnchor::_last_Java_pc", Integer.class, "address"); + res = anchor + lastJavaPc; + lastJavaPCOffset = res; + } + return res; } } diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/truffle/LibGraalNativeBridgeSupport.java b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/truffle/LibGraalNativeBridgeSupport.java index d9014bcdf211..5d8e7ed34a3b 100644 --- a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/truffle/LibGraalNativeBridgeSupport.java +++ b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/truffle/LibGraalNativeBridgeSupport.java @@ -27,12 +27,11 @@ import java.util.concurrent.atomic.AtomicInteger; import jdk.graal.compiler.debug.TTY; +import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.graal.compiler.serviceprovider.IsolateUtil; import org.graalvm.jniutils.JNIMethodScope; import org.graalvm.jniutils.NativeBridgeSupport; -import jdk.vm.ci.services.Services; - public final class LibGraalNativeBridgeSupport implements NativeBridgeSupport { private static final String JNI_LIBGRAAL_TRACE_LEVEL_PROPERTY_NAME = "JNI_LIBGRAAL_TRACE_LEVEL"; @@ -80,7 +79,7 @@ public void trace(String message) { private int traceLevel() { int res = traceLevel.get(); if (res == UNINITIALIZED_TRACE_LEVEL) { - String var = Services.getSavedProperty(JNI_LIBGRAAL_TRACE_LEVEL_PROPERTY_NAME); + String var = GraalServices.getSavedProperty(JNI_LIBGRAAL_TRACE_LEVEL_PROPERTY_NAME); if (var != null) { try { res = Integer.parseInt(var); diff --git a/substratevm/src/com.oracle.svm.graal.hotspot/src/com/oracle/svm/graal/hotspot/GetCompilerConfig.java b/substratevm/src/com.oracle.svm.graal.hotspot/src/com/oracle/svm/graal/hotspot/GetCompilerConfig.java new file mode 100644 index 000000000000..6c275f1edbb9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal.hotspot/src/com/oracle/svm/graal/hotspot/GetCompilerConfig.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.graal.hotspot; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import jdk.graal.compiler.util.ObjectCopier; +import org.graalvm.collections.UnmodifiableEconomicMap; +import org.graalvm.collections.UnmodifiableMapCursor; + +import com.oracle.graal.pointsto.api.PointstoOptions; +import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.option.RuntimeOptionKey; + +import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.hotspot.HotSpotGraalOptionValues; +import jdk.graal.compiler.options.OptionKey; +import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.hotspot.guestgraal.CompilerConfig; + +/** + * Gets the map created in a JVM subprocess by running {@link CompilerConfig}. + */ +public class GetCompilerConfig { + + private static final boolean DEBUG = Boolean.getBoolean("debug." + GetCompilerConfig.class.getName()); + + /** + * Result returned by {@link GetCompilerConfig#from}. + * + * @param encodedConfig the {@linkplain CompilerConfig config} serialized to a string + * @param opens map from a module to the set of packages opened to the module defining + * {@link ObjectCopier}. These packages need to be opened when decoding the returned + * string back to an object. + */ + public record Result(String encodedConfig, Map> opens) { + } + + /** + * Launches the JVM in {@code javaHome} to run {@link CompilerConfig}. + * + * @param javaHome the value of the {@code java.home} system property reported by a Java + * installation directory that includes the Graal classes in its runtime image + * @param options the options passed to native-image + */ + public static Result from(Path javaHome, OptionValues options) { + Path javaExe = GetJNIConfig.getJavaExe(javaHome); + UnmodifiableEconomicMap, Object> optionsMap = options.getMap(); + UnmodifiableMapCursor, Object> entries = optionsMap.getEntries(); + Map> opens = Map.of( + // Needed to reflect fields like + // java.util.ImmutableCollections.EMPTY + "java.base", Set.of("java.util")); + + List command = new ArrayList<>(List.of( + javaExe.toFile().getAbsolutePath(), + "-XX:+UnlockExperimentalVMOptions", + "-XX:+EnableJVMCI", + "-XX:-UseJVMCICompiler", // avoid deadlock with jargraal + "-Djdk.vm.ci.services.aot=true")); + + Module module = ObjectCopier.class.getModule(); + String target = module.isNamed() ? module.getName() : "ALL-UNNAMED"; + for (var e : opens.entrySet()) { + for (String source : e.getValue()) { + command.add("--add-opens=%s/%s=%s".formatted(e.getKey(), source, target)); + } + } + + // Propagate compiler options + while (entries.advance()) { + OptionKey key = entries.getKey(); + if (key instanceof RuntimeOptionKey || key instanceof HostedOptionKey) { + // Ignore Native Image options + continue; + } + if (key.getDescriptor().getDeclaringClass().getModule().equals(PointstoOptions.class.getModule())) { + // Ignore points-to analysis options + continue; + } + command.add("-D%s%s=%s".formatted(HotSpotGraalOptionValues.GRAAL_OPTION_PROPERTY_PREFIX, key.getName(), entries.getValue())); + } + + command.add(CompilerConfig.class.getName()); + Path encodedConfigPath = Path.of(GetCompilerConfig.class.getSimpleName() + "_" + ProcessHandle.current().pid() + ".txt").toAbsolutePath(); + command.add(encodedConfigPath.toString()); + + String quotedCommand = command.stream().map(e -> e.indexOf(' ') == -1 ? e : '\'' + e + '\'').collect(Collectors.joining(" ")); + ProcessBuilder pb = new ProcessBuilder(command); + pb.inheritIO(); + Process p; + try { + p = pb.start(); + } catch (IOException ex) { + throw new GraalError("Error running command: %s%n%s", quotedCommand, ex); + } + + try { + int exitValue = p.waitFor(); + if (exitValue != 0) { + throw new GraalError("Command finished with exit value %d: %s", exitValue, quotedCommand); + } + } catch (InterruptedException e) { + throw new GraalError("Interrupted waiting for command: %s", quotedCommand); + } + try { + String encodedConfig = Files.readString(encodedConfigPath); + if (DEBUG) { + System.out.printf("[%d] Executed: %s%n", p.pid(), quotedCommand); + System.out.printf("[%d] Output saved in %s%n", p.pid(), encodedConfigPath); + } else { + Files.deleteIfExists(encodedConfigPath); + } + return new Result(encodedConfig, opens); + } catch (IOException e) { + throw new GraalError(e); + } + } +} diff --git a/substratevm/src/com.oracle.svm.graal.hotspot/src/com/oracle/svm/graal/hotspot/GetJNIConfig.java b/substratevm/src/com.oracle.svm.graal.hotspot/src/com/oracle/svm/graal/hotspot/GetJNIConfig.java new file mode 100644 index 000000000000..fc3624cf17bd --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal.hotspot/src/com/oracle/svm/graal/hotspot/GetJNIConfig.java @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.graal.hotspot; + +import com.oracle.svm.core.OS; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.util.LogUtils; +import com.oracle.svm.util.ModuleSupport; +import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; +import jdk.vm.ci.hotspot.HotSpotSignature; +import jdk.vm.ci.meta.JavaType; +import org.graalvm.nativeimage.hosted.RuntimeJNIAccess; +import org.graalvm.nativeimage.hosted.RuntimeReflection; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static jdk.vm.ci.hotspot.HotSpotJVMCIRuntime.runtime; + +/** + * Registers the JNI configuration for libgraal by parsing the output of the + * {@code -XX:JVMCILibDumpJNIConfig} VM option. + */ +public final class GetJNIConfig implements AutoCloseable { + /** + * VM command executed to read the JNI config. + */ + private final String quotedCommand; + + /** + * JNI config lines. + */ + private final List lines; + + /** + * Loader used to resolve type names in the config. + */ + private final ClassLoader loader; + + /** + * Path to intermediate file containing the config. This is deleted unless there is an + * {@link #error(String, Object...)} parsing the config to make diagnosing the error easier. + */ + private Path configFilePath; + + int lineNo; + + private GetJNIConfig(ClassLoader loader) { + this.loader = loader; + Path javaExe = getJavaExe(Path.of(System.getProperty("java.home"))); + configFilePath = Path.of("libgraal_jniconfig.txt"); + + String[] command = {javaExe.toFile().getAbsolutePath(), "-XX:+UnlockExperimentalVMOptions", "-XX:+EnableJVMCI", "-XX:JVMCILibDumpJNIConfig=" + configFilePath}; + quotedCommand = Stream.of(command).map(e -> e.indexOf(' ') == -1 ? e : '\'' + e + '\'').collect(Collectors.joining(" ")); + ProcessBuilder pb = new ProcessBuilder(command); + pb.redirectErrorStream(true); + Process p; + try { + p = pb.start(); + } catch (IOException e) { + throw UserError.abort("Could not run command: %s%n%s", quotedCommand, e); + } + + String nl = System.lineSeparator(); + String out = new BufferedReader(new InputStreamReader(p.getInputStream())) + .lines().collect(Collectors.joining(nl)); + + int exitValue; + try { + exitValue = p.waitFor(); + } catch (InterruptedException e) { + throw UserError.abort("Interrupted waiting for command: %s%n%s", quotedCommand, out); + } + if (exitValue != 0) { + throw UserError.abort("Command finished with exit value %d: %s%n%s", exitValue, quotedCommand, out); + } + try { + lines = Files.readAllLines(configFilePath); + } catch (IOException e) { + configFilePath = null; + throw UserError.abort("Reading JNI config in %s dumped by command: %s%n%s", configFilePath, quotedCommand, out); + } + } + + static Path getJavaExe(Path javaHome) { + Path javaExe = javaHome.resolve(Path.of("bin", OS.WINDOWS.isCurrent() ? "java.exe" : "java")); + if (!Files.isExecutable(javaExe)) { + throw UserError.abort("Java launcher %s does not exist or is not executable", javaExe); + } + return javaExe; + } + + @Override + public void close() { + if (configFilePath != null && Files.exists(configFilePath)) { + try { + Files.delete(configFilePath); + configFilePath = null; + } catch (IOException e) { + LogUtils.warning("Could not delete %s: %s", configFilePath, e); + } + } + } + + private Class findClass(String name) { + String internalName = name; + if (name.startsWith("L") && name.endsWith(";")) { + internalName = name.substring(1, name.length() - 1); + } + var primitive = ImageClassLoader.forPrimitive(internalName); + if (primitive != null) { + return primitive; + } + try { + return Class.forName(internalName, false, loader); + } catch (ClassNotFoundException e) { + throw VMError.shouldNotReachHere("Cannot find class GuestGraal JNIConfiguration registration", e); + } + } + + private void check(boolean condition, String format, Object... args) { + if (!condition) { + throw error(format, args); + } + } + + private UserError.UserException error(String format, Object... args) { + Path path = configFilePath; + configFilePath = null; // prevent deletion + String errorMessage = String.format(format, args); + String errorLine = lines.get(lineNo - 1); + throw UserError.abort("Line %d of %s: %s%n%s%n%s generated by command: %s", + lineNo, path.toAbsolutePath(), errorMessage, errorLine, path, quotedCommand); + + } + + /** + * Registers the JNI configuration for libgraal by parsing the output of the + * {@code -XX:JVMCILibDumpJNIConfig} VM option. + * + * @param loader used to resolve type names in the config + */ + @SuppressWarnings("try") + public static void register(ClassLoader loader) { + // Export all JVMCI packages to this class + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, GetJNIConfig.class, false, "jdk.internal.vm.ci"); + + try (GetJNIConfig source = new GetJNIConfig(loader)) { + Map> classes = new HashMap<>(); + for (String line : source.lines) { + source.lineNo++; + String[] tokens = line.split(" "); + source.check(tokens.length >= 2, "Expected at least 2 tokens"); + String className = tokens[1].replace('/', '.'); + Class clazz = classes.get(className); + if (clazz == null) { + clazz = source.findClass(className); + assert clazz.getClassLoader() == null || clazz.getClassLoader() == loader; + RuntimeJNIAccess.register(clazz); + RuntimeJNIAccess.register(Array.newInstance(clazz, 0).getClass()); + classes.put(className, clazz); + } + + switch (tokens[0]) { + case "field": { + source.check(tokens.length == 4, "Expected 4 tokens for a field"); + String fieldName = tokens[2]; + try { + RuntimeJNIAccess.register(clazz.getDeclaredField(fieldName)); + } catch (NoSuchFieldException e) { + throw source.error("Field %s.%s not found", clazz.getTypeName(), fieldName); + } catch (NoClassDefFoundError e) { + throw source.error("Could not register field %s.%s: %s", clazz.getTypeName(), fieldName, e); + } + break; + } + case "method": { + source.check(tokens.length == 4, "Expected 4 tokens for a method"); + String methodName = tokens[2]; + HotSpotJVMCIRuntime runtime = runtime(); + String signature = tokens[3]; + HotSpotSignature descriptor = new HotSpotSignature(runtime, signature); + Class[] parameters = Stream.of(descriptor.toParameterTypes(null))// + .map(JavaType::toClassName).map(source::findClass)// + .toList()// + .toArray(new Class[descriptor.getParameterCount(false)]); + assert Arrays.stream(parameters).allMatch(pclazz -> pclazz.getClassLoader() == null || pclazz.getClassLoader() == loader); + try { + if ("".equals(methodName)) { + Constructor cons = clazz.getDeclaredConstructor(parameters); + RuntimeJNIAccess.register(cons); + if (Throwable.class.isAssignableFrom(clazz) && !Modifier.isAbstract(clazz.getModifiers())) { + if (usedInTranslatedException(parameters)) { + RuntimeReflection.register(clazz); + RuntimeReflection.register(cons); + } + } + } else { + RuntimeJNIAccess.register(clazz.getDeclaredMethod(methodName, parameters)); + } + } catch (NoSuchMethodException e) { + throw source.error("Method %s.%s%s not found: %s", clazz.getTypeName(), methodName, descriptor, e); + } catch (NoClassDefFoundError e) { + throw source.error("Could not register method %s.%s%s: %s", clazz.getTypeName(), methodName, descriptor, e); + } + break; + } + case "class": { + source.check(tokens.length == 2, "Expected 2 tokens for a class"); + break; + } + default: { + throw source.error("Unexpected token: " + tokens[0]); + } + } + } + } + } + + /** + * Determines if a throwable constructor with the signature specified by {@code parameters} is + * potentially called via reflection in {@code jdk.vm.ci.hotspot.TranslatedException}. + */ + private static boolean usedInTranslatedException(Class[] parameters) { + return parameters.length == 0 || (parameters.length == 1 && parameters[0] == String.class); + } +} diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalJNIMethodScope.java b/substratevm/src/com.oracle.svm.graal.hotspot/src/com/oracle/svm/graal/hotspot/LibGraalJNIMethodScope.java similarity index 62% rename from substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalJNIMethodScope.java rename to substratevm/src/com.oracle.svm.graal.hotspot/src/com/oracle/svm/graal/hotspot/LibGraalJNIMethodScope.java index 8c58d658af68..7b9271205fa7 100644 --- a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalJNIMethodScope.java +++ b/substratevm/src/com.oracle.svm.graal.hotspot/src/com/oracle/svm/graal/hotspot/LibGraalJNIMethodScope.java @@ -22,15 +22,10 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.graal.hotspot.libgraal; +package com.oracle.svm.graal.hotspot; -import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; -import jdk.vm.ci.hotspot.HotSpotVMConfigAccess; import org.graalvm.jniutils.JNI.JNIEnv; import org.graalvm.jniutils.JNIMethodScope; -import org.graalvm.nativeimage.c.type.CLongPointer; -import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import static org.graalvm.jniutils.JNIUtil.PopLocalFrame; import static org.graalvm.jniutils.JNIUtil.PushLocalFrame; @@ -43,9 +38,7 @@ * not use such a stub so without explicitly allocating a new JNI locals frame, the JNI references * created by libgraal will never be freed (i.e., a memory leak). */ -final class LibGraalJNIMethodScope extends JNIMethodScope { - - private static volatile int lastJavaPCOffset = -1; +public final class LibGraalJNIMethodScope extends JNIMethodScope { private LibGraalJNIMethodScope(String scopeName, JNIEnv env) { super(scopeName, env); @@ -64,24 +57,7 @@ public void close() { * * @see LibGraalJNIMethodScope */ - static JNIMethodScope open(String scopeName, JNIEnv env) { - return scopeOrNull() == null && getJavaFrameAnchor().isNull() ? new LibGraalJNIMethodScope(scopeName, env) : new JNIMethodScope(scopeName, env); - } - - private static PointerBase getJavaFrameAnchor() { - CLongPointer currentThreadLastJavaPCOffset = (CLongPointer) WordFactory.unsigned(HotSpotJVMCIRuntime.runtime().getCurrentJavaThread()).add(getLastJavaPCOffset()); - return WordFactory.pointer(currentThreadLastJavaPCOffset.read()); - } - - private static int getLastJavaPCOffset() { - int res = lastJavaPCOffset; - if (res == -1) { - HotSpotVMConfigAccess configAccess = new HotSpotVMConfigAccess(HotSpotJVMCIRuntime.runtime().getConfigStore()); - int anchor = configAccess.getFieldOffset("JavaThread::_anchor", Integer.class, "JavaFrameAnchor"); - int lastJavaPc = configAccess.getFieldOffset("JavaFrameAnchor::_last_Java_pc", Integer.class, "address"); - res = anchor + lastJavaPc; - lastJavaPCOffset = res; - } - return res; + public static JNIMethodScope open(String scopeName, JNIEnv env, boolean javaFrameAnchorExists) { + return scopeOrNull() == null && !javaFrameAnchorExists ? new LibGraalJNIMethodScope(scopeName, env) : new JNIMethodScope(scopeName, env); } } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalCompilerSupport.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalCompilerSupport.java index 35d5357417fd..0efe51f9f3c8 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalCompilerSupport.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalCompilerSupport.java @@ -24,8 +24,6 @@ */ package com.oracle.svm.graal; -import static org.graalvm.word.LocationIdentity.ANY_LOCATION; - import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; @@ -36,11 +34,7 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; -import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; -import com.oracle.svm.core.c.CGlobalData; -import com.oracle.svm.core.c.CGlobalDataFactory; import com.oracle.svm.core.util.ImageHeapMap; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; @@ -53,7 +47,6 @@ import jdk.graal.compiler.lir.LIRInstructionClass; import jdk.graal.compiler.lir.phases.LIRPhase; import jdk.graal.compiler.phases.BasePhase; -import jdk.graal.compiler.serviceprovider.GraalServices; /** * Holds data that is pre-computed during native image generation and accessed at run time during a @@ -71,41 +64,9 @@ public class GraalCompilerSupport { protected final List debugHandlersFactories = new ArrayList<>(); - private static final CGlobalData nextIsolateId = CGlobalDataFactory.createWord((Pointer) WordFactory.unsigned(1L)); - - private volatile long isolateId = 0; - - /** - * Gets an identifier for the current isolate that is guaranteed to be unique for the first - * {@code 2^64 - 1} isolates in the process. - * - * @return a non-zero value - */ - public long getIsolateId() { - if (isolateId == 0) { - synchronized (this) { - if (isolateId == 0) { - Pointer p = nextIsolateId.get(); - long value; - long nextValue; - do { - value = p.readLong(0); - nextValue = value + 1; - if (nextValue == 0) { - // Avoid setting id to reserved 0 value after long integer overflow - nextValue = 1; - } - } while (p.compareAndSwapLong(0, value, nextValue, ANY_LOCATION) != value); - isolateId = value; - } - } - } - return isolateId; - } - @Platforms(Platform.HOSTED_ONLY.class) public GraalCompilerSupport() { - for (DebugHandlersFactory c : GraalServices.load(DebugHandlersFactory.class)) { + for (DebugHandlersFactory c : DebugHandlersFactory.LOADER) { debugHandlersFactories.add(c); } } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java index c4dfd8501792..b8f9ea411e1b 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java @@ -31,6 +31,7 @@ import java.util.EnumMap; import java.util.Map; +import jdk.graal.compiler.phases.util.Providers; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; @@ -204,8 +205,19 @@ private static CompilationResult compileGraph(RuntimeConfiguration runtimeConfig try (Indent indent2 = debug.logAndIndent("do compilation")) { SubstrateCompilationResult result = new SubstrateCompilationResult(graph.compilationId(), method.format("%H.%n(%p)")); - GraalCompiler.compileGraph(graph, method, backend.getProviders(), backend, null, optimisticOpts, null, suites, lirSuites, result, - CompilationResultBuilderFactory.Default, false); + Providers providers = backend.getProviders(); + GraalCompiler.compile(new GraalCompiler.Request<>(graph, + method, + providers, + backend, + null, + optimisticOpts, + null, + suites, + lirSuites, + result, + CompilationResultBuilderFactory.Default, + false)); return result; } } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java index fea5a930a1ee..f301319bae19 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java @@ -167,7 +167,7 @@ public Object apply(Object source) { } else if (source instanceof HotSpotBackendFactory) { HotSpotBackendFactory factory = (HotSpotBackendFactory) source; Architecture hostArch = HotSpotJVMCIRuntime.runtime().getHostJVMCIBackend().getTarget().arch; - if (!factory.getArchitecture().equals(hostArch.getClass())) { + if (!factory.getArchitecture().equals(hostArch.getName())) { throw new UnsupportedFeatureException("Non-host architecture HotSpotBackendFactory should not appear in the image: " + source); } } else if (source instanceof GraalRuntime) { diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java index 8df923ecd1c3..5e75313fdf1d 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java @@ -40,6 +40,7 @@ import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.FieldValueTransformer; +import org.graalvm.nativeimage.impl.IsolateSupport; import org.graalvm.word.Pointer; import org.graalvm.word.WordFactory; @@ -230,7 +231,7 @@ public static long getIsolateAddress() { @Substitute public static long getIsolateID() { - return ImageSingletons.lookup(GraalCompilerSupport.class).getIsolateId(); + return ImageSingletons.lookup(IsolateSupport.class).getIsolateID(); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java index 687c45a11e9d..c55efe36d594 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java @@ -228,7 +228,7 @@ private static boolean containsHostedOnly(Platforms platformsAnnotation) { } public Enumeration findResourcesByName(String resource) throws IOException { - return classLoaderSupport.getClassLoader().getResources(resource); + return getClassLoader().getResources(resource); } /** @@ -266,25 +266,9 @@ public TypeResult> findClass(String name) { public TypeResult> findClass(String name, boolean allowPrimitives) { try { if (allowPrimitives && name.indexOf('.') == -1) { - switch (name) { - case "boolean": - return TypeResult.forClass(boolean.class); - case "char": - return TypeResult.forClass(char.class); - case "float": - return TypeResult.forClass(float.class); - case "double": - return TypeResult.forClass(double.class); - case "byte": - return TypeResult.forClass(byte.class); - case "short": - return TypeResult.forClass(short.class); - case "int": - return TypeResult.forClass(int.class); - case "long": - return TypeResult.forClass(long.class); - case "void": - return TypeResult.forClass(void.class); + Class primitive = forPrimitive(name); + if (primitive != null) { + return TypeResult.forClass(primitive); } } return TypeResult.forClass(forName(name)); @@ -293,12 +277,27 @@ public TypeResult> findClass(String name, boolean allowPrimitives) { } } + public static Class forPrimitive(String name) { + return switch (name) { + case "boolean" -> boolean.class; + case "char" -> char.class; + case "float" -> float.class; + case "double" -> double.class; + case "byte" -> byte.class; + case "short" -> short.class; + case "int" -> int.class; + case "long" -> long.class; + case "void" -> void.class; + default -> null; + }; + } + public Class forName(String className) throws ClassNotFoundException { return forName(className, false); } public Class forName(String className, boolean initialize) throws ClassNotFoundException { - return Class.forName(className, initialize, classLoaderSupport.getClassLoader()); + return Class.forName(className, initialize, getClassLoader()); } public Class forName(String className, Module module) throws ClassNotFoundException { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoader.java index 2fcb41dd3309..c2727d932fa8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoader.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoader.java @@ -80,7 +80,7 @@ * in {@code jdk.internal.loader.Loader} and {@code URLClassLoader}. More documentation is available * in the original classes. */ -final class NativeImageClassLoader extends SecureClassLoader { +public final class NativeImageClassLoader extends SecureClassLoader { static { ClassLoader.registerAsParallelCapable(); @@ -172,14 +172,18 @@ CodeSource codeSource() { moduleToReader = new ConcurrentHashMap<>(); /* Initialize URLClassPath that is used to lookup classes from class-path. */ - ucp = new URLClassPath(classpath.stream().map(NativeImageClassLoader::pathToURL).toArray(URL[]::new), null); + ucp = new URLClassPath(classpath.stream().map(NativeImageClassLoader::toURL).toArray(URL[]::new), null); } - private static URL pathToURL(Path p) { + public static URL toURL(Path p) { + return toURL(p.toUri()); + } + + public static URL toURL(URI uri) { try { - return p.toUri().toURL(); + return uri.toURL(); } catch (MalformedURLException e) { - throw UserError.abort(e, "Given path element '%s' cannot be expressed as URL.", p); + throw UserError.abort(e, "Given URI '%s' cannot be expressed as URL.", uri); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java index 9b8992b63fa6..434764e5c638 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java @@ -142,7 +142,7 @@ protected NativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, St imagecp = Arrays.stream(classpath) .map(Path::of) .flatMap(NativeImageClassLoaderSupport::toRealPath) - .collect(Collectors.toUnmodifiableList()); + .toList(); String builderClassPathString = System.getProperty("java.class.path"); String[] builderClassPathEntries = builderClassPathString.isEmpty() ? new String[0] : builderClassPathString.split(File.pathSeparator); @@ -154,19 +154,19 @@ protected NativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, St buildcp = Arrays.stream(builderClassPathEntries) .map(Path::of) .flatMap(NativeImageClassLoaderSupport::toRealPath) - .collect(Collectors.toUnmodifiableList()); + .toList(); buildcp.stream().map(Path::toUri).forEach(builderURILocations::add); imagemp = Arrays.stream(modulePath) .map(Path::of) .flatMap(NativeImageClassLoaderSupport::toRealPath) - .collect(Collectors.toUnmodifiableList()); + .toList(); buildmp = Optional.ofNullable(System.getProperty("jdk.module.path")).stream() .flatMap(s -> Arrays.stream(s.split(File.pathSeparator))) .map(Path::of) .flatMap(NativeImageClassLoaderSupport::toRealPath) - .collect(Collectors.toUnmodifiableList()); + .toList(); upgradeAndSystemModuleFinder = createUpgradeAndSystemModuleFinder(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CompileQueue.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CompileQueue.java index 6268f09ec0c6..37a9332f2853 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CompileQueue.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CompileQueue.java @@ -1285,8 +1285,20 @@ private CompilationResult defaultCompileFunction(DebugContext debug, HostedMetho CompilationResult result = backend.newCompilationResult(compilationIdentifier, method.getQualifiedName()); try (Indent indent = debug.logAndIndent("compile %s", method)) { - GraalCompiler.compileGraph(graph, method, backend.getProviders(), backend, null, getOptimisticOpts(), null, suites, lirSuites, result, - new HostedCompilationResultBuilderFactory(), false); + Providers providers = backend.getProviders(); + OptimisticOptimizations optimisticOpts = getOptimisticOpts(); + GraalCompiler.compile(new GraalCompiler.Request<>(graph, + method, + providers, + backend, + null, + optimisticOpts, + null, + suites, + lirSuites, + result, + new HostedCompilationResultBuilderFactory(), + false)); } graph.getOptimizationLog().emit((m) -> m.format(StableMethodNameFormatter.METHOD_FORMAT)); method.compilationInfo.numNodesAfterCompilation = graph.getNodeCount(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SharedRuntimeConfigurationBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SharedRuntimeConfigurationBuilder.java index 4e9cbfac76ef..3968c8621875 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SharedRuntimeConfigurationBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SharedRuntimeConfigurationBuilder.java @@ -61,7 +61,6 @@ import jdk.graal.compiler.options.OptionValues; import jdk.graal.compiler.phases.util.Providers; import jdk.graal.compiler.printer.GraalDebugHandlersFactory; -import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.graal.compiler.word.WordTypes; import jdk.vm.ci.code.CodeCacheProvider; import jdk.vm.ci.code.RegisterConfig; @@ -145,7 +144,7 @@ public final RuntimeConfiguration build() { } List handlers = new ArrayList<>(); - for (DebugHandlersFactory factory : GraalServices.load(DebugHandlersFactory.class)) { + for (DebugHandlersFactory factory : DebugHandlersFactory.LOADER) { if (factory instanceof GraalDebugHandlersFactory) { handlers.add(new GraalDebugHandlersFactory(snippetReflection)); } else { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java index 10c48a7f62ce..6779ef050649 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java @@ -30,18 +30,10 @@ import java.util.function.Function; import java.util.function.Supplier; -import com.oracle.svm.core.c.CGlobalData; -import com.oracle.svm.core.c.CGlobalDataFactory; -import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.heap.Heap; -import com.oracle.svm.hosted.c.CGlobalDataFeature; -import com.oracle.svm.util.ReflectionUtil; -import jdk.graal.compiler.core.common.CompressEncoding; -import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.printer.GraalDebugHandlersFactory; -import com.oracle.svm.core.graal.meta.RuntimeConfiguration; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; +import org.graalvm.word.PointerBase; +import org.graalvm.word.WordFactory; import com.oracle.graal.pointsto.util.GraalAccess; import com.oracle.graal.pointsto.util.Timer; @@ -52,15 +44,24 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.UniqueShortNameProvider; import com.oracle.svm.core.UniqueShortNameProviderDefaultImpl; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; +import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.option.HostedOptionValues; import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.ProgressReporter; +import com.oracle.svm.hosted.c.CGlobalDataFeature; import com.oracle.svm.hosted.image.sources.SourceManager; import com.oracle.svm.hosted.util.DiagnosticUtils; -import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.graal.compiler.core.common.CompressEncoding; +import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.printer.GraalDebugHandlersFactory; @AutomaticallyRegisteredFeature @SuppressWarnings("unused") diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java index 507ca45ea482..dbbaae0961e2 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java @@ -45,6 +45,7 @@ import java.util.function.BooleanSupplier; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.ImageSingletons; @@ -1000,26 +1001,39 @@ Class findTargetClass(Class annotatedBaseClass, TargetClass target) { protected Class findTargetClass(Class annotatedBaseClass, TargetClass target, boolean checkOnlyWith) { return findTargetClass(TargetClass.class, TargetClass.NoClassNameProvider.class, - annotatedBaseClass, target, target.value(), target.className(), target.classNameProvider(), target.innerClass(), checkOnlyWith ? target.onlyWith() : null); + annotatedBaseClass, target, target.value(), target.className(), target.classNameProvider(), target.innerClass(), target.classLoader(), + checkOnlyWith ? target.onlyWith() : null); } protected Class findTargetClass(Class targetClass, Class noClassNameProviderClass, - Class annotatedBaseClass, T target, Class value, String targetClassName, Class> classNameProvider, String[] innerClasses, Class[] onlyWith) { + Class annotatedBaseClass, T target, Class value, String targetClassName, Class> classNameProvider, String[] innerClasses, + Class> classloaderSupplier, Class[] onlyWith) { String className; + ClassLoader loader = imageClassLoader.getClassLoader(); if (value != targetClass) { guarantee(targetClassName.isEmpty(), "Both class and class name specified for substitution"); guarantee(classNameProvider == noClassNameProviderClass, "Both class and classNameProvider specified for substitution"); + guarantee(classloaderSupplier == TargetClass.NoClassLoaderProvider.class, "Annotation attribute 'classLoader' requires use of 'className' or 'classNameProvider'"); className = value.getName(); - } else if (classNameProvider != noClassNameProviderClass) { - try { - className = ReflectionUtil.newInstance(classNameProvider).apply(target); - } catch (ReflectionUtilError ex) { - throw UserError.abort(ex.getCause(), "Cannot instantiate classNameProvider: %s. The class must have a parameterless constructor.", classNameProvider.getTypeName()); - } } else { - guarantee(!targetClassName.isEmpty(), "Neither class, className, nor classNameProvider specified for substitution"); - className = targetClassName; + if (classNameProvider != noClassNameProviderClass) { + try { + className = ReflectionUtil.newInstance(classNameProvider).apply(target); + } catch (ReflectionUtilError ex) { + throw UserError.abort(ex.getCause(), "Cannot instantiate classNameProvider: %s. The class must have a parameterless constructor.", classNameProvider.getTypeName()); + } + } else { + guarantee(!targetClassName.isEmpty(), "Neither class, className, nor classNameProvider specified for substitution"); + className = targetClassName; + } + if (classloaderSupplier != TargetClass.NoClassLoaderProvider.class) { + try { + loader = ReflectionUtil.newInstance(classloaderSupplier).get(); + } catch (ReflectionUtilError ex) { + throw UserError.abort(ex.getCause(), "Cannot instantiate classloaderSupplier: %s. The class must have a parameterless constructor.", classloaderSupplier.getTypeName()); + } + } } if (onlyWith != null) { for (Class onlyWithClass : onlyWith) { @@ -1048,8 +1062,10 @@ protected Class findTargetClass(Class targetClass, Class noClassNam } } - Class holder = imageClassLoader.findClass(className).get(); - if (holder == null) { + Class holder; + try { + holder = Class.forName(className, false, loader); + } catch (ClassNotFoundException e) { throw UserError.abort("Substitution target for %s is not loaded. Use field `onlyWith` in the `TargetClass` annotation to make substitution only active when needed.", annotatedBaseClass.getName()); } diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java index ae1c68ec590a..12fd70d50c99 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java @@ -57,6 +57,10 @@ private static void openModule(Class declaringClass) { ModuleSupport.accessModuleByClass(ModuleSupport.Access.OPEN, ReflectionUtil.class, declaringClass); } + public static Class lookupClass(String className) { + return lookupClass(false, className); + } + public static Class lookupClass(boolean optional, String className) { try { return Class.forName(className, false, ReflectionUtil.class.getClassLoader()); diff --git a/vm/ci/ci_common/libgraal.jsonnet b/vm/ci/ci_common/libgraal.jsonnet index 2637f078705e..85ef5a373ddd 100644 --- a/vm/ci/ci_common/libgraal.jsonnet +++ b/vm/ci/ci_common/libgraal.jsonnet @@ -78,6 +78,36 @@ local galahad = import '../../../ci/ci_common/galahad-common.libsonnet'; # Use economy mode for coverage testing libgraal_truffle_coverage: self.libgraal_truffle_base(['-Ob'], coverage=true), + # Gate for guestgraal + guestgraal_compiler:: { + local guestgraal_env = std.strReplace(vm.libgraal_env, "libgraal", "guestgraal"), + # LibGraal gate tasks currently expected to work + local tasks = [ + "LibGraal Compiler:Basic", + "LibGraal Compiler:FatalErrorHandling", + "LibGraal Compiler:SystemicFailureDetection", + "LibGraal Compiler:CTW", + "LibGraal Compiler:DaCapo", + "LibGraal Compiler:ScalaDaCapo" + ] + + # Renaissance is missing the msvc redistributable on Windows [GR-50132] + if self.os == "windows" then [] else ["LibGraal Compiler:Renaissance"], + + run+: [ + ['mx', '--env', guestgraal_env, 'build'], + ['mx', '--env', guestgraal_env, 'native-image', '-J-esa', '-J-ea', '-esa', '-ea', + '-p', ['mx', '--env', guestgraal_env, '--quiet', 'path', 'JNIUTILS'], + '-cp', ['mx', '--env', guestgraal_env, '--quiet', 'path', 'GUESTGRAAL_LIBRARY'], + '-H:+UnlockExperimentalVMOptions', '-H:+VerifyGraalGraphs', '-H:+VerifyPhases'], + ['mx', '--env', guestgraal_env, 'gate', '--task', std.join(",", tasks), '--extra-vm-argument=-XX:JVMCILibPath=$PWD/' + vm.vm_dir], + ], + logs+: [ + '*/graal-compiler.log', + '*/graal-compiler-ctw.log' + ], + timelimit: '1:00:00', + }, + # See definition of `gates` local variable in ../../compiler/ci_common/gate.jsonnet local gate_jobs = { "gate-vm-libgraal_compiler-labsjdk-latest-linux-amd64": {}, @@ -87,7 +117,12 @@ local galahad = import '../../../ci/ci_common/galahad-common.libsonnet'; "gate-vm-libgraal_compiler-labsjdk-21-linux-amd64": {}, "gate-vm-libgraal_truffle-labsjdk-21-linux-amd64": {}, - }, + } + if repo_config.graalvm_edition == "ce" then + { + # GuestGraal on EE is still under construction + "gate-vm-guestgraal_compiler-labsjdk-latest-linux-amd64": {} + } else {}, + local gates = g.as_gates(gate_jobs), # See definition of `dailies` local variable in ../../compiler/ci_common/gate.jsonnet @@ -145,7 +180,9 @@ local galahad = import '../../../ci/ci_common/galahad-common.libsonnet'; "libgraal_truffle", "libgraal_compiler_quickbuild", "libgraal_truffle_quickbuild" - ] + ] + + # GuestGraal on EE is still under construction + (if repo_config.graalvm_edition == "ce" then ["guestgraal_compiler"] else []) ], local adjust_windows_version(gate) = ( diff --git a/vm/mx.vm/guestgraal b/vm/mx.vm/guestgraal new file mode 100644 index 000000000000..a8e95e56994f --- /dev/null +++ b/vm/mx.vm/guestgraal @@ -0,0 +1,4 @@ +DYNAMIC_IMPORTS=/substratevm +COMPONENTS=lg,cmp,ni +NATIVE_IMAGES=false +DISABLE_INSTALLABLES=true diff --git a/vm/mx.vm/mx_vm_gate.py b/vm/mx.vm/mx_vm_gate.py index fedfad94c6f3..b8c14dbacc89 100644 --- a/vm/mx.vm/mx_vm_gate.py +++ b/vm/mx.vm/mx_vm_gate.py @@ -139,7 +139,7 @@ def _test_libgraal_check_build_path(libgraal_location): def _test_libgraal_basic(extra_vm_arguments, libgraal_location): """ Tests basic libgraal execution by running CountUppercase, ensuring it has a 0 exit code - and that the output for -DgraalShowConfiguration=info describes a libgraal execution. + and that the output for -Djdk.graal.ShowConfiguration=info describes a libgraal execution. """ graalvm_home = mx_sdk_vm_impl.graalvm_home() @@ -218,7 +218,7 @@ def extra_check(compiler_log): # Verify execution via raw java launcher in `mx graalvm-home`. for jre_name, jre, jre_args in jres: try: - cmd = [join(jre, 'bin', 'java')] + jre_args + args + cmd = [join(jre, 'bin', 'java')] + jre_args + extra_vm_arguments + args mx.log(f'{jre_name}: {" ".join(cmd)}') mx.run(cmd) finally: @@ -232,7 +232,7 @@ def extra_check(compiler_log): finally: _check_compiler_log(compiler_log_file, expect) -def _test_libgraal_fatal_error_handling(): +def _test_libgraal_fatal_error_handling(extra_vm_arguments): """ Tests that fatal errors in libgraal route back to HotSpot fatal error handling. """ @@ -240,7 +240,7 @@ def _test_libgraal_fatal_error_handling(): vmargs = ['-XX:+PrintFlagsFinal', '-Djdk.graal.CrashAt=*', '-Djdk.graal.CrashAtIsFatal=1'] - cmd = [join(graalvm_home, 'bin', 'java')] + vmargs + _get_CountUppercase_vmargs() + cmd = [join(graalvm_home, 'bin', 'java')] + vmargs + extra_vm_arguments + _get_CountUppercase_vmargs() out = mx.OutputCapture() scratch_dir = mkdtemp(dir='.') exitcode = mx.run(cmd, nonZeroIsFatal=False, err=out, out=out, cwd=scratch_dir) @@ -282,7 +282,7 @@ def _test_libgraal_fatal_error_handling(): mx.log(f"Cleaning up scratch dir after gate task completion: {scratch_dir}") mx.rmtree(scratch_dir) -def _test_libgraal_oome_dumping(): +def _test_libgraal_oome_dumping(extra_vm_arguments): """ Tests the HeapDumpOnOutOfMemoryError libgraal option. """ @@ -307,7 +307,7 @@ def _test_libgraal_oome_dumping(): f'-Djdk.graal.internal.HeapDumpPath={n}', '-Djdk.graal.SystemicCompilationFailureRate=0', '-Djdk.graal.CrashAtThrowsOOME=true'] - cmd = [join(graalvm_home, 'bin', 'java')] + vmargs + _get_CountUppercase_vmargs() + cmd = [join(graalvm_home, 'bin', 'java')] + vmargs + extra_vm_arguments + _get_CountUppercase_vmargs() mx.run(cmd, cwd=scratch_dir) heap_dumps = glob.glob(v) if not heap_dumps: @@ -322,7 +322,7 @@ def _test_libgraal_oome_dumping(): mx.log(f"Cleaning up scratch dir after gate task completion: {scratch_dir}") mx.rmtree(scratch_dir) -def _test_libgraal_systemic_failure_detection(): +def _test_libgraal_systemic_failure_detection(extra_vm_arguments): """ Tests that system compilation failures are detected and cause the VM to exit. """ @@ -334,7 +334,7 @@ def _test_libgraal_systemic_failure_detection(): '-Djdk.graal.DumpOnError=false', '-Djdk.graal.CompilationFailureAction=Silent' ] - cmd = [join(graalvm_home, 'bin', 'java')] + vmargs + _get_CountUppercase_vmargs() + cmd = [join(graalvm_home, 'bin', 'java')] + vmargs + extra_vm_arguments + _get_CountUppercase_vmargs() out = mx.OutputCapture() scratch_dir = mkdtemp(dir='.') exitcode = mx.run(cmd, nonZeroIsFatal=False, err=out, out=out, cwd=scratch_dir) @@ -362,7 +362,7 @@ def _jdk_has_ForceTranslateFailure_jvmci_option(jdk): return False mx.abort(sink.data) -def _test_libgraal_CompilationTimeout_JIT(): +def _test_libgraal_CompilationTimeout_JIT(extra_vm_arguments): """ Tests timeout handling of CompileBroker compilations. """ @@ -380,7 +380,7 @@ def _test_libgraal_CompilationTimeout_JIT(): f'{G}LogFile={compiler_log_file}', '-Ddebug.graal.CompilationWatchDog=true'] # helps debug failure - cmd = [join(graalvm_home, 'bin', 'java')] + vmargs + _get_CountUppercase_vmargs() + cmd = [join(graalvm_home, 'bin', 'java')] + vmargs + extra_vm_arguments + _get_CountUppercase_vmargs() exit_code = mx.run(cmd, nonZeroIsFatal=False) expectations = ['detected long running compilation'] + (['a stuck compilation'] if vm_can_exit else []) _check_compiler_log(compiler_log_file, expectations) @@ -422,7 +422,7 @@ def _test_libgraal_CompilationTimeout_Truffle(extra_vm_arguments): delay = abspath(join(dirname(__file__), 'Delay.sl')) cp_args = mx.get_runtime_jvm_args(mx_truffle.resolve_sl_dist_names(use_optimized_runtime=True, use_enterprise=True)) - cmd = [join(graalvm_home, 'bin', 'java')] + vmargs + cp_args + ['--module', 'org.graalvm.sl_launcher/com.oracle.truffle.sl.launcher.SLMain', delay] + cmd = [join(graalvm_home, 'bin', 'java')] + vmargs + extra_vm_arguments + cp_args + ['--module', 'org.graalvm.sl_launcher/com.oracle.truffle.sl.launcher.SLMain', delay] err = mx.OutputCapture() exit_code = mx.run(cmd, nonZeroIsFatal=False, err=err) if err.data: @@ -528,13 +528,13 @@ def gate_body(args, tasks): with Task('LibGraal Compiler:Basic', tasks, tags=[VmGateTasks.libgraal], report='compiler') as t: if t: _test_libgraal_basic(extra_vm_arguments, libgraal_location) with Task('LibGraal Compiler:FatalErrorHandling', tasks, tags=[VmGateTasks.libgraal], report='compiler') as t: - if t: _test_libgraal_fatal_error_handling() + if t: _test_libgraal_fatal_error_handling(extra_vm_arguments) with Task('LibGraal Compiler:OOMEDumping', tasks, tags=[VmGateTasks.libgraal], report='compiler') as t: - if t: _test_libgraal_oome_dumping() + if t: _test_libgraal_oome_dumping(extra_vm_arguments) with Task('LibGraal Compiler:SystemicFailureDetection', tasks, tags=[VmGateTasks.libgraal], report='compiler') as t: - if t: _test_libgraal_systemic_failure_detection() + if t: _test_libgraal_systemic_failure_detection(extra_vm_arguments) with Task('LibGraal Compiler:CompilationTimeout:JIT', tasks, tags=[VmGateTasks.libgraal]) as t: - if t: _test_libgraal_CompilationTimeout_JIT() + if t: _test_libgraal_CompilationTimeout_JIT(extra_vm_arguments) with Task('LibGraal Compiler:CompilationTimeout:Truffle', tasks, tags=[VmGateTasks.libgraal]) as t: if t: _test_libgraal_CompilationTimeout_Truffle(extra_vm_arguments)