diff --git a/.github/workflows/ni-layers.yml b/.github/workflows/ni-layers.yml index 75fdd7f713eb..bdad617019de 100644 --- a/.github/workflows/ni-layers.yml +++ b/.github/workflows/ni-layers.yml @@ -48,7 +48,7 @@ on: paths: - '.github/workflows/ni-layers.yml' schedule: - - cron: "0 0 * * 1" + - cron: "0 0 * * 1" # Once a week, at midnight on Monday (00:00 UTC) workflow_dispatch: env: diff --git a/ci/common.jsonnet b/ci/common.jsonnet index d2ef2c435725..7ffa08c93e5f 100644 --- a/ci/common.jsonnet +++ b/ci/common.jsonnet @@ -203,6 +203,12 @@ local common_json = import "../common.json"; }, }, + gradle:: { + downloads+: { + GRADLE_JAVA_HOME: jdks_data["oraclejdk21"], + } + }, + local code_tools = { downloads+: if 'jdk_version' in self && self.jdk_version > 21 then { TOOLS_JAVA_HOME: jdks_data['oraclejdk21'], @@ -251,7 +257,7 @@ local common_json = import "../common.json"; } else {}, }, - graalpy:: { + graalpy:: self.gradle + { packages+: if (self.os == "linux") then { libffi: '>=3.2.1', bzip2: '>=1.0.6', diff --git a/common.json b/common.json index 7dc968c6515c..500a16f186d7 100644 --- a/common.json +++ b/common.json @@ -4,11 +4,11 @@ "Jsonnet files should not include this file directly but use ci/common.jsonnet instead." ], - "mx_version": "7.35.2", + "mx_version": "7.36.1", "COMMENT.jdks": "When adding or removing JDKs keep in sync with JDKs in ci/common.jsonnet", "jdks": { - "galahad-jdk": {"name": "jpg-jdk", "version": "24", "build_id": "jdk-24+25-3126", "platformspecific": true, "extrabundles": ["static-libs"]}, + "galahad-jdk": {"name": "jpg-jdk", "version": "25", "build_id": "jdk-25+2-128", "platformspecific": true, "extrabundles": ["static-libs"]}, "oraclejdk17": {"name": "jpg-jdk", "version": "17.0.7", "build_id": "jdk-17.0.7+8", "platformspecific": true, "extrabundles": ["static-libs"]}, "labsjdk-ce-17": {"name": "labsjdk", "version": "ce-17.0.7+4-jvmci-23.1-b02", "platformspecific": true }, @@ -45,13 +45,13 @@ "oraclejdk23": {"name": "jpg-jdk", "version": "23", "build_id": "jdk-23+37", "platformspecific": true, "extrabundles": ["static-libs"]}, - "oraclejdk-latest": {"name": "jpg-jdk", "version": "24", "build_id": "jdk-24+25", "platformspecific": true, "extrabundles": ["static-libs"]}, - "labsjdk-ce-latest": {"name": "labsjdk", "version": "ce-24+25-jvmci-b01", "platformspecific": true }, - "labsjdk-ce-latestDebug": {"name": "labsjdk", "version": "ce-24+25-jvmci-b01-debug", "platformspecific": true }, - "labsjdk-ce-latest-llvm": {"name": "labsjdk", "version": "ce-24+25-jvmci-b01-sulong", "platformspecific": true }, - "labsjdk-ee-latest": {"name": "labsjdk", "version": "ee-24+25-jvmci-b01", "platformspecific": true }, - "labsjdk-ee-latestDebug": {"name": "labsjdk", "version": "ee-24+25-jvmci-b01-debug", "platformspecific": true }, - "labsjdk-ee-latest-llvm": {"name": "labsjdk", "version": "ee-24+25-jvmci-b01-sulong", "platformspecific": true } + "oraclejdk-latest": {"name": "jpg-jdk", "version": "25", "build_id": "jdk-25+3", "platformspecific": true, "extrabundles": ["static-libs"]}, + "labsjdk-ce-latest": {"name": "labsjdk", "version": "ce-25+3-jvmci-b01", "platformspecific": true }, + "labsjdk-ce-latestDebug": {"name": "labsjdk", "version": "ce-25+3-jvmci-b01-debug", "platformspecific": true }, + "labsjdk-ce-latest-llvm": {"name": "labsjdk", "version": "ce-25+3-jvmci-b01-sulong", "platformspecific": true }, + "labsjdk-ee-latest": {"name": "labsjdk", "version": "ee-25+3-jvmci-b01", "platformspecific": true }, + "labsjdk-ee-latestDebug": {"name": "labsjdk", "version": "ee-25+3-jvmci-b01-debug", "platformspecific": true }, + "labsjdk-ee-latest-llvm": {"name": "labsjdk", "version": "ee-25+3-jvmci-b01-sulong", "platformspecific": true } }, "eclipse": { diff --git a/compiler/ci/ci_common/benchmark-suites.libsonnet b/compiler/ci/ci_common/benchmark-suites.libsonnet index ca6d85e85f39..5f4d09c6bfd6 100644 --- a/compiler/ci/ci_common/benchmark-suites.libsonnet +++ b/compiler/ci/ci_common/benchmark-suites.libsonnet @@ -70,7 +70,7 @@ run+: [ self.benchmark_cmd + ["scala-dacapo:*", "--"] + self.extra_vm_args ], - timelimit: "01:00:00", + timelimit: "01:30:00", forks_batches:: 2, bench_forks_per_batch:: 3, forks_timelimit:: "02:30:00", @@ -105,7 +105,7 @@ run+: [ self.benchmark_cmd + ["renaissance:*"] + suite_version_args + ["--"] + self.extra_vm_args ], - timelimit: "2:00:00", + timelimit: "2:30:00", forks_batches:: 4, bench_forks_per_batch:: 2, forks_timelimit:: "4:00:00", diff --git a/compiler/ci/ci_common/gate.jsonnet b/compiler/ci/ci_common/gate.jsonnet index 724e1501ae91..b6c4526b9a3e 100644 --- a/compiler/ci/ci_common/gate.jsonnet +++ b/compiler/ci/ci_common/gate.jsonnet @@ -93,7 +93,8 @@ coverage_base(ctw):: s.base(tags="build,%s" % if ctw then "ctw" else "coverage", cmd_suffix=s.jacoco_gate_args, - extra_vm_args=if !ctw then "" else "-DCompileTheWorld.MaxClasses=5000 -Djdk.graal.CompilationFailureAction=Print" /*GR-23372 for MaxClasses*/) + + # Leaving assertions enabled slows down ctw enough to cause timeouts, and libgraal does not include assertions anyway. + extra_vm_args=if !ctw then "" else "-da -DCompileTheWorld.MaxClasses=5000 -Djdk.graal.CompilationFailureAction=Print" /*GR-23372 for MaxClasses*/) + { teardown+: [ s.upload_coverage, diff --git a/compiler/ci/ci_includes/baseline-benchmarks.jsonnet b/compiler/ci/ci_includes/baseline-benchmarks.jsonnet index bb1bf4ba09fd..5c165ef76622 100644 --- a/compiler/ci/ci_includes/baseline-benchmarks.jsonnet +++ b/compiler/ci/ci_includes/baseline-benchmarks.jsonnet @@ -6,20 +6,14 @@ local bench = (import '../ci_common/benchmark-suites.libsonnet'), local hw = bc.bench_hw, - local hotspot_amd64_builds = [ - c.weekly + hw.e3 + jdk + cc.c2 + suite + local hotspot_builds = std.flattenArrays([ + [ + c.weekly + hw.e3 + jdk + cc.c2 + suite, + c.weekly + hw.a12c + jdk + cc.c2 + suite + ] for jdk in cc.jdks_of_interest for suite in bench.groups.all_suites - ], - - local hotspot_aarch64_builds = [ - c.weekly + hw.a12c + jdk + cc.c2 + suite - for jdk in cc.jdks_of_interest - for suite in bench.groups.main_suites - ] + [ - c.monthly + hw.a12c + jdk + cc.c2 + bench.specjbb2015, - for jdk in cc.product_jdks - ], + ]), local hotspot_profiling_builds = std.flattenArrays([ [ @@ -73,7 +67,7 @@ ] for jdk in cc.product_jdks ]), - local all_builds = hotspot_amd64_builds + hotspot_aarch64_builds + hotspot_profiling_builds + + local all_builds = hotspot_builds + hotspot_profiling_builds + weekly_forks_amd64_builds + weekly_forks_aarch64_builds + economy_builds + no_tiered_builds + gc_variants_builds, local filtered_builds = [b for b in all_builds if b.is_jdk_supported(b.jdk_version) && b.is_arch_supported(b.arch)], diff --git a/compiler/mx.compiler/suite.py b/compiler/mx.compiler/suite.py index ff9a9aa85ae8..a85fe56f4aed 100644 --- a/compiler/mx.compiler/suite.py +++ b/compiler/mx.compiler/suite.py @@ -4,7 +4,7 @@ "sourceinprojectwhitelist" : [], "groupId" : "org.graalvm.compiler", - "version" : "24.2.0", + "version" : "25.0.0", "release" : False, "url" : "http://www.graalvm.org/", "developer" : { @@ -85,33 +85,39 @@ "urls" : ["https://lafo.ssw.uni-linz.ac.at/pub/graal-external-deps/batik-all-1.7.jar"], }, - "ASM_9.5" : { - "digest" : "sha512:9e65f2983783725bae196ca939b45246958731246df1c495089c8ea5ce646de77c4e01a5a9ba10642016bb3258e1727e9ebcece6e74d9e3c32f528025d76b955", + "ASM_9.7.1" : { + "digest" : "sha512:4767b01603dad5c79cc1e2b5f3722f72b1059d928f184f446ba11badeb1b381b3a3a9a801cc43d25d396df950b09d19597c73173c411b1da890de808b94f1f50", + "sourceDigest" : "sha512:d7c0de5912d04949a3d06cad366ff35a877da2682d9c74579625d62686032ea9349aff6102b17f92e9ec7eb4e9b1cd906b649c6a3ac798bfb9e31e5425de009d", "maven" : { "groupId" : "org.ow2.asm", "artifactId" : "asm", - "version" : "9.5", + "version" : "9.7.1", }, + "license" : "BSD-new", }, - "ASM_TREE_9.5" : { - "digest" : "sha512:816de8f84c216a7bd97b2458bde64a4b99a039b7b59fbd1ef52edf8bf869edabb93967736fe0c61e8eb3e1520e0cefe69ba59cda12df30f9f85db75fb6c064f3", + "ASM_TREE_9.7.1" : { + "digest" : "sha512:e55008c392fdd35e95d3404766b12dd4b46e13d5c362fcd0ab42a65751a82737eaf0ebc857691d1916190d34407adfde4437615d69c278785416fd911e00978d", + "sourceDigest" : "sha512:3cea80bc7b55679dfa3d2065c6cb6951007cc7817082e9fcf4c5e3cdc073c22eddf7c7899cff60b1092049ec9038e8d3aa9a8828ef731739bda8b5afcec30e86", "maven" : { "groupId" : "org.ow2.asm", "artifactId" : "asm-tree", - "version" : "9.5", + "version" : "9.7.1", }, - "dependencies" : ["ASM_9.5"], + "dependencies" : ["ASM_9.7.1"], + "license" : "BSD-new", }, - "ASM_UTIL_9.5" : { - "digest" : "sha512:f68284d8f8fd029f3f428112225b2035ed3a4216cf3b34e0aacc83c32a6d44ab5e5d128b60a13ef768e3396041a62cf63f7fd3445dc5a05ce0ae03a2b2ed3080", + "ASM_UTIL_9.7.1" : { + "digest" : "sha512:522d793d15a2c5ea6504a50222cf0750f1eab7b881cf289675042539b1aba8b3868197b1bebe729de728dd10020eb028ae16252dcd5d84fdcbf7f925832bc269", + "sourceDigest" : "sha512:387aa887bfec24aec287d9aacebfdc0c2e1ab16a4adce933aecac6fc41545ce43a3eea0ed139db52dd0d0af910cfd2162aa4d6330a81b32b64b36f03b49db66a", "maven" : { "groupId" : "org.ow2.asm", "artifactId" : "asm-util", - "version" : "9.5", + "version" : "9.7.1", }, - "dependencies" : ["ASM_9.5"], + "dependencies" : ["ASM_9.7.1"], + "license" : "BSD-new", }, "HSDIS" : { @@ -244,8 +250,8 @@ "dependencies" : [ "jdk.graal.compiler", "mx:JUNIT", - "ASM_TREE_9.5", - "ASM_UTIL_9.5", + "ASM_TREE_9.7.1", + "ASM_UTIL_9.7.1", "JAVA_ALLOCATION_INSTRUMENTER", "truffle:TRUFFLE_SL_TEST", "truffle:TRUFFLE_TEST", @@ -499,8 +505,8 @@ "truffle:TRUFFLE_COMPILER", "truffle:TRUFFLE_RUNTIME", "regex:TREGEX", - "ASM_TREE_9.5", - "ASM_UTIL_9.5", + "ASM_TREE_9.7.1", + "ASM_UTIL_9.7.1", ], "exclude" : [ "mx:JUNIT", @@ -557,8 +563,8 @@ org.graalvm.nativeimage.foreign, org.graalvm.nativeimage.llvm, com.oracle.svm.svm_enterprise, + com.oracle.svm.jdwp.resident, com.oracle.svm_enterprise.ml_dataset, - com.oracle.svm.enterprise.jdwp.resident, org.graalvm.nativeimage.base, org.graalvm.extraimage.builder, org.graalvm.extraimage.librarysupport, diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/CacheResetTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/CacheResetTest.java new file mode 100644 index 000000000000..8adf989ee4aa --- /dev/null +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/CacheResetTest.java @@ -0,0 +1,108 @@ +/* + * 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.core.test; + +import java.util.HashMap; + +import org.graalvm.collections.Pair; +import org.junit.Assert; +import org.junit.Test; + +import jdk.graal.compiler.core.phases.HighTier; +import jdk.graal.compiler.options.OptionValues; +import jdk.vm.ci.code.InstalledCode; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +/** + * Tests that the cache used by the compiler tests properly handles different options. + */ +public class CacheResetTest extends GraalCompilerTest { + + public static void snippet() { + // do nothing + } + + private static final String SNIPPET_NAME = "snippet"; + + @Test + public void testSameOptionsReuse() { + final ResolvedJavaMethod installedCodeOwner = getResolvedJavaMethod(SNIPPET_NAME); + + OptionValues opt = new OptionValues(getInitialOptions(), HighTier.Options.Inline, true); + + test(opt, SNIPPET_NAME); + InstalledCode firstInstalled = getCachedCode(installedCodeOwner); + + test(opt, SNIPPET_NAME); + InstalledCode secondInstalled = getCachedCode(installedCodeOwner); + + assert firstInstalled.isValid(); + assert secondInstalled.isValid(); + + Assert.assertEquals(firstInstalled.getStart(), secondInstalled.getStart()); + } + + @Test + public void testSameOptionsDeepReuse() { + final ResolvedJavaMethod installedCodeOwner = getResolvedJavaMethod(SNIPPET_NAME); + + OptionValues opt = new OptionValues(getInitialOptions(), HighTier.Options.Inline, true); + test(opt, SNIPPET_NAME); + InstalledCode firstInstalled = getCachedCode(installedCodeOwner); + + OptionValues opt1 = new OptionValues(getInitialOptions(), HighTier.Options.Inline, true); + test(opt1, SNIPPET_NAME); + InstalledCode secondInstalled = getCachedCode(installedCodeOwner); + + assert firstInstalled.isValid(); + assert secondInstalled.isValid(); + + Assert.assertEquals(firstInstalled.getStart(), secondInstalled.getStart()); + } + + private static InstalledCode getCachedCode(ResolvedJavaMethod method) { + HashMap> tlCache = cache.get(); + Pair cached = tlCache.get(method); + return cached.getRight(); + } + + @Test + public void testDifferentOptionsNoReuse() { + final ResolvedJavaMethod installedCodeOwner = getResolvedJavaMethod(SNIPPET_NAME); + + OptionValues opt = new OptionValues(getInitialOptions(), HighTier.Options.Inline, true); + test(opt, SNIPPET_NAME); + InstalledCode firstInstalled = getCachedCode(installedCodeOwner); + + OptionValues opt1 = new OptionValues(getInitialOptions(), HighTier.Options.Inline, false); + test(opt1, SNIPPET_NAME); + InstalledCode secondInstalled = getCachedCode(installedCodeOwner); + + assert firstInstalled.isValid(); + assert secondInstalled.isValid(); + + Assert.assertNotEquals(firstInstalled.getStart(), secondInstalled.getStart()); + } +} diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/CheckGraalInvariants.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/CheckGraalInvariants.java index b83d69580e60..5a689230d34d 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/CheckGraalInvariants.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/CheckGraalInvariants.java @@ -118,6 +118,7 @@ import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.SpeculationLog; import jdk.vm.ci.meta.Value; /** @@ -329,12 +330,14 @@ public static void runTest(InvariantsTool tool) { verifiers.add(new VerifyUsageWithEquals(LIRKind.class)); verifiers.add(new VerifyUsageWithEquals(ArithmeticOpTable.class)); verifiers.add(new VerifyUsageWithEquals(ArithmeticOpTable.Op.class)); + verifiers.add(new VerifyUsageWithEquals(SpeculationLog.Speculation.class, SpeculationLog.NO_SPECULATION)); verifiers.add(new VerifySharedConstantEmptyArray()); verifiers.add(new VerifyDebugUsage()); verifiers.add(new VerifyVirtualizableUsage()); verifiers.add(new VerifyUpdateUsages()); verifiers.add(new VerifyLibGraalContextChecks()); + verifiers.add(new VerifyWordFactoryUsage()); verifiers.add(new VerifyBailoutUsage()); verifiers.add(new VerifySystemPropertyUsage()); verifiers.add(new VerifyInstanceOfUsage()); 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 97db728fcaca..892b460c976e 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 @@ -59,6 +59,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; +import org.graalvm.collections.Pair; +import org.graalvm.collections.UnmodifiableEconomicMap; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -129,6 +131,7 @@ import jdk.graal.compiler.nodes.spi.ProfileProvider; import jdk.graal.compiler.nodes.spi.Replacements; import jdk.graal.compiler.nodes.virtual.VirtualObjectNode; +import jdk.graal.compiler.options.OptionKey; import jdk.graal.compiler.options.OptionValues; import jdk.graal.compiler.phases.BasePhase; import jdk.graal.compiler.phases.OptimisticOptimizations; @@ -395,7 +398,7 @@ protected LIRSuites createLIRSuites(OptionValues opts) { return ret; } - private static final ThreadLocal> cache = ThreadLocal.withInitial(HashMap::new); + protected static final ThreadLocal>> cache = ThreadLocal.withInitial(HashMap::new); /** * Reset the entire {@linkplain #cache} of {@linkplain InstalledCode}. Additionally, invalidate @@ -404,8 +407,8 @@ protected LIRSuites createLIRSuites(OptionValues opts) { */ @BeforeClass public static void resetCodeCache() { - for (InstalledCode code : cache.get().values()) { - code.invalidate(); + for (Pair code : cache.get().values()) { + code.getRight().invalidate(); } cache.get().clear(); } @@ -1133,11 +1136,14 @@ protected final InstalledCode getCode(final ResolvedJavaMethod installedCodeOwne protected InstalledCode getCode(final ResolvedJavaMethod installedCodeOwner, StructuredGraph graph, boolean forceCompile, boolean installAsDefault, OptionValues options) { boolean useCache = !forceCompile && getArgumentToBind() == null; if (useCache && graph == null) { - HashMap tlCache = cache.get(); - InstalledCode cached = tlCache.get(installedCodeOwner); + HashMap> tlCache = cache.get(); + Pair cached = tlCache.get(installedCodeOwner); if (cached != null) { - if (cached.isValid()) { - return cached; + // Reuse the cached code if it is still valid and the same options was used for + // the compilation. We use a deep equals for the option values to catch cases where + // users create new option values but with the same values. + if (cached.getRight().isValid() && (options.getMap().equals(cached.getLeft().getMap()) || optionsMapDeepEquals(options.getMap(), cached.getLeft().getMap()))) { + return cached.getRight(); } else { tlCache.remove(installedCodeOwner); } @@ -1184,13 +1190,35 @@ protected InstalledCode getCode(final ResolvedJavaMethod installedCodeOwner, Str } if (useCache) { - cache.get().put(installedCodeOwner, installedCode); + cache.get().put(installedCodeOwner, Pair.create(options, installedCode)); } return installedCode; } throw GraalError.shouldNotReachHere("Bailout limit reached"); // ExcludeFromJacocoGeneratedReport } + private static boolean optionsMapDeepEquals(UnmodifiableEconomicMap, Object> map1, UnmodifiableEconomicMap, Object> map2) { + if (map1.size() != map2.size()) { + return false; + } + var c1 = map1.getEntries(); + var c2 = map2.getEntries(); + while (c1.advance() && c2.advance()) { + Object c1Key = c1.getKey(); + Object c2Key = c2.getKey(); + if (!c1Key.equals(c2Key)) { + return false; + } + Object c1Val = c1.getValue(); + Object c2Val = c2.getValue(); + if (!c1Val.equals(c2Val)) { + return false; + } + } + + return true; + } + /** * Used to produce a graph for a method about to be compiled by * {@link #compile(ResolvedJavaMethod, StructuredGraph)} if the second parameter to that method diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifyUsageWithEquals.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifyUsageWithEquals.java index 51a97164e9fb..8fb39a777f8c 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifyUsageWithEquals.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifyUsageWithEquals.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 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 @@ -24,6 +24,9 @@ */ package jdk.graal.compiler.core.test; +import java.util.Objects; + +import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; import jdk.graal.compiler.core.common.type.ObjectStamp; import jdk.graal.compiler.nodes.Invoke; import jdk.graal.compiler.nodes.NodeView; @@ -36,7 +39,8 @@ import jdk.graal.compiler.nodes.spi.UncheckedInterfaceProvider; import jdk.graal.compiler.nodes.type.StampTool; import jdk.graal.compiler.phases.VerifyPhase; - +import jdk.vm.ci.meta.ConstantReflectionProvider; +import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaField; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.JavaMethod; @@ -49,7 +53,8 @@ /** * For certain types, object identity should not be used for object equality check. This phase * checks the correct usage of the given type. Equality checks with == or != (except null checks) - * results in an {@link AssertionError}. + * results in an {@link AssertionError}. Optionally, a singleton value with which == and != checks + * are allowed as an exception may be provided. */ public class VerifyUsageWithEquals extends VerifyPhase { @@ -63,9 +68,34 @@ public boolean checkContract() { */ private final Class restrictedClass; + /** + * The value besides {@code null} for which equality checks with == or != are allowed. + */ + private final Object safeSingletonValue; + public VerifyUsageWithEquals(Class restrictedClass) { + checkRestrictedClass(restrictedClass); + this.restrictedClass = restrictedClass; + this.safeSingletonValue = null; + } + + /** + * Constructs a verifier to check that object identity is not used for equality checks except + * with {@code null} and the given singleton value. + * + * @param restrictedClass the class for which equality checks are restricted + * @param singletonValue the non-null value for which equality checks with == or != are allowed + * as an exception + */ + public VerifyUsageWithEquals(Class restrictedClass, T singletonValue) { + checkRestrictedClass(restrictedClass); this.restrictedClass = restrictedClass; - assert !restrictedClass.isInterface() || isTrustedInterface(restrictedClass); + Objects.requireNonNull(singletonValue); + this.safeSingletonValue = singletonValue; + } + + private static void checkRestrictedClass(Class restrictedClass) { + assert !restrictedClass.isInterface() || isTrustedInterface(restrictedClass) : "the restricted class must not be an untrusted interface"; } private static final Class[] trustedInterfaceTypes = {JavaType.class, JavaField.class, JavaMethod.class}; @@ -104,10 +134,28 @@ private boolean isAssignableToRestrictedType(ValueNode node, MetaAccessProvider return false; } + private boolean isSafeConstant(ValueNode node, ConstantReflectionProvider constantReflection, SnippetReflectionProvider snippetReflection) { + return isNullConstant(node) || isSafeSingletonValue(node, constantReflection, snippetReflection); + } + private static boolean isNullConstant(ValueNode node) { return node.isConstant() && node.isNullConstant(); } + private boolean isSafeSingletonValue(ValueNode node, ConstantReflectionProvider constantReflection, SnippetReflectionProvider snippetReflection) { + if (safeSingletonValue == null) { + return false; + } + JavaConstant javaConstant = node.asJavaConstant(); + if (node instanceof LoadFieldNode loadField && loadField.isStatic() && loadField.field().isFinal()) { + javaConstant = constantReflection.readFieldValue(loadField.field(), null); + } + if (javaConstant == null) { + return false; + } + return safeSingletonValue == snippetReflection.asObject(Object.class, javaConstant); + } + private static boolean isEqualsMethod(ResolvedJavaMethod method) { if (method.getName().equals("equals")) { Signature sig = method.getSignature(); @@ -129,12 +177,14 @@ private static boolean isThisParameter(ValueNode node) { } /** - * Checks whether the type of {@code x} is assignable to the restricted type and that {@code y} - * is not a null constant. + * Checks whether the type of {@code x} or {@code y} is assignable to the restricted type and + * that {@code x} and {@code y} are not safe constants. */ - private boolean isIllegalUsage(ResolvedJavaMethod method, ValueNode x, ValueNode y, MetaAccessProvider metaAccess) { - if (isAssignableToRestrictedType(x, metaAccess) && !isNullConstant(y)) { - if (isEqualsMethod(method) && isThisParameter(x) || isThisParameter(y)) { + private boolean isIllegalUsage(ResolvedJavaMethod method, ValueNode x, ValueNode y, CoreProviders context) { + if ((isAssignableToRestrictedType(x, context.getMetaAccess()) || isAssignableToRestrictedType(y, context.getMetaAccess())) && + !isSafeConstant(x, context.getConstantReflection(), context.getSnippetReflection()) && + !isSafeConstant(y, context.getConstantReflection(), context.getSnippetReflection())) { + if (isEqualsMethod(method) && (isThisParameter(x) || isThisParameter(y))) { return false; } return true; @@ -151,7 +201,7 @@ protected void verify(StructuredGraph graph, CoreProviders context) { if (restrictedType.isAssignableFrom(method.getDeclaringClass())) { // Allow violation in methods of the restricted type itself and its subclasses. - } else if (isIllegalUsage(method, cn.getX(), cn.getY(), context.getMetaAccess()) || isIllegalUsage(method, cn.getY(), cn.getX(), context.getMetaAccess())) { + } else if (isIllegalUsage(method, cn.getX(), cn.getY(), context)) { throw new VerificationError("Verification of " + restrictedClass.getName() + " usage failed: Comparing " + cn.getX() + " and " + cn.getY() + " in " + method + " must use .equals() for object equality, not '==' or '!='"); } diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifyWordFactoryUsage.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifyWordFactoryUsage.java new file mode 100644 index 000000000000..e43350e662d8 --- /dev/null +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifyWordFactoryUsage.java @@ -0,0 +1,61 @@ +/* + * 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.core.test; + +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.word.Word; +import jdk.vm.ci.meta.ResolvedJavaType; +import org.graalvm.word.WordFactory; + +/** + * Ensures that Graal compiler code uses factory methods in {@link Word} instead of + * {@link WordFactory} to create word values. + */ +public class VerifyWordFactoryUsage extends VerifyPhase { + + @Override + public boolean checkContract() { + return false; + } + + @Override + protected void verify(StructuredGraph graph, CoreProviders context) { + + ResolvedJavaType wordFactory = context.getMetaAccess().lookupJavaType(WordFactory.class); + for (MethodCallTargetNode t : graph.getNodes(MethodCallTargetNode.TYPE)) { + if (t.targetMethod().getDeclaringClass().equals(wordFactory)) { + throw new VerificationError("accessing %s in %s is prohibited - use %s.%s instead", + wordFactory.toJavaName(), + graph.method().format("%H.%n(%p)"), + Word.class.getName(), + graph.method().format("%n(%p)")); + + } + } + } +} diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/test/JVMCIVersionCheckLTSTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/test/JVMCIVersionCheckLTSTest.java new file mode 100644 index 000000000000..635c7656ddf8 --- /dev/null +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/test/JVMCIVersionCheckLTSTest.java @@ -0,0 +1,48 @@ +/* + * 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.test; + +import jdk.graal.compiler.core.test.GraalCompilerTest; +import jdk.graal.compiler.hotspot.JVMCIVersionCheck; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests that {@link JVMCIVersionCheck} can strip the -LTS suffix. + */ +public class JVMCIVersionCheckLTSTest extends GraalCompilerTest { + + private static void expect(String jdkVersionString, String expected) { + JVMCIVersionCheck.Version version = JVMCIVersionCheck.createLabsJDKVersion(jdkVersionString, 1); + Assert.assertEquals(expected, version.stripLTS().toString()); + } + + @Test + public void test() { + expect("24+17", "24+17-jvmci-b01"); + expect("25+1", "25+1-jvmci-b01"); + expect("25+1-LTS", "25+1-jvmci-b01"); + } +} diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/ObjectAccessTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/ObjectAccessTest.java index 5606d4b6aff7..bfd9a7c20c28 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/ObjectAccessTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/ObjectAccessTest.java @@ -37,9 +37,9 @@ import jdk.graal.compiler.nodes.extended.JavaWriteNode; import jdk.graal.compiler.nodes.memory.address.OffsetAddressNode; import jdk.graal.compiler.word.ObjectAccess; +import jdk.graal.compiler.word.Word; import org.graalvm.word.LocationIdentity; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import org.junit.Assert; import org.junit.Test; @@ -152,7 +152,7 @@ public static byte readByte1(Object o, int offset) { @Snippet public static byte readByte2(Object o, int offset) { - return ObjectAccess.readByte(o, WordFactory.signed(offset), ID); + return ObjectAccess.readByte(o, Word.signed(offset), ID); } @Snippet @@ -167,7 +167,7 @@ public static void writeByte1(Object o, int offset, byte value) { @Snippet public static void writeByte2(Object o, int offset, byte value) { - ObjectAccess.writeByte(o, WordFactory.signed(offset), value, ID); + ObjectAccess.writeByte(o, Word.signed(offset), value, ID); } @Snippet @@ -182,7 +182,7 @@ public static char readChar1(Object o, int offset) { @Snippet public static char readChar2(Object o, int offset) { - return ObjectAccess.readChar(o, WordFactory.signed(offset), ID); + return ObjectAccess.readChar(o, Word.signed(offset), ID); } @Snippet @@ -197,7 +197,7 @@ public static void writeChar1(Object o, int offset, char value) { @Snippet public static void writeChar2(Object o, int offset, char value) { - ObjectAccess.writeChar(o, WordFactory.signed(offset), value, ID); + ObjectAccess.writeChar(o, Word.signed(offset), value, ID); } @Snippet @@ -212,7 +212,7 @@ public static short readShort1(Object o, int offset) { @Snippet public static short readShort2(Object o, int offset) { - return ObjectAccess.readShort(o, WordFactory.signed(offset), ID); + return ObjectAccess.readShort(o, Word.signed(offset), ID); } @Snippet @@ -227,7 +227,7 @@ public static void writeShort1(Object o, int offset, short value) { @Snippet public static void writeShort2(Object o, int offset, short value) { - ObjectAccess.writeShort(o, WordFactory.signed(offset), value, ID); + ObjectAccess.writeShort(o, Word.signed(offset), value, ID); } @Snippet @@ -242,7 +242,7 @@ public static int readInt1(Object o, int offset) { @Snippet public static int readInt2(Object o, int offset) { - return ObjectAccess.readInt(o, WordFactory.signed(offset), ID); + return ObjectAccess.readInt(o, Word.signed(offset), ID); } @Snippet @@ -257,7 +257,7 @@ public static void writeInt1(Object o, int offset, int value) { @Snippet public static void writeInt2(Object o, int offset, int value) { - ObjectAccess.writeInt(o, WordFactory.signed(offset), value, ID); + ObjectAccess.writeInt(o, Word.signed(offset), value, ID); } @Snippet @@ -272,7 +272,7 @@ public static long readLong1(Object o, int offset) { @Snippet public static long readLong2(Object o, int offset) { - return ObjectAccess.readLong(o, WordFactory.signed(offset), ID); + return ObjectAccess.readLong(o, Word.signed(offset), ID); } @Snippet @@ -287,7 +287,7 @@ public static void writeLong1(Object o, int offset, long value) { @Snippet public static void writeLong2(Object o, int offset, long value) { - ObjectAccess.writeLong(o, WordFactory.signed(offset), value, ID); + ObjectAccess.writeLong(o, Word.signed(offset), value, ID); } @Snippet @@ -302,7 +302,7 @@ public static float readFloat1(Object o, int offset) { @Snippet public static float readFloat2(Object o, int offset) { - return ObjectAccess.readFloat(o, WordFactory.signed(offset), ID); + return ObjectAccess.readFloat(o, Word.signed(offset), ID); } @Snippet @@ -317,7 +317,7 @@ public static void writeFloat1(Object o, int offset, float value) { @Snippet public static void writeFloat2(Object o, int offset, float value) { - ObjectAccess.writeFloat(o, WordFactory.signed(offset), value, ID); + ObjectAccess.writeFloat(o, Word.signed(offset), value, ID); } @Snippet @@ -332,7 +332,7 @@ public static double readDouble1(Object o, int offset) { @Snippet public static double readDouble2(Object o, int offset) { - return ObjectAccess.readDouble(o, WordFactory.signed(offset), ID); + return ObjectAccess.readDouble(o, Word.signed(offset), ID); } @Snippet @@ -347,7 +347,7 @@ public static void writeDouble1(Object o, int offset, double value) { @Snippet public static void writeDouble2(Object o, int offset, double value) { - ObjectAccess.writeDouble(o, WordFactory.signed(offset), value, ID); + ObjectAccess.writeDouble(o, Word.signed(offset), value, ID); } @Snippet @@ -362,7 +362,7 @@ public static Object readObject1(Object o, int offset) { @Snippet public static Object readObject2(Object o, int offset) { - return ObjectAccess.readObject(o, WordFactory.signed(offset), ID); + return ObjectAccess.readObject(o, Word.signed(offset), ID); } @Snippet @@ -377,7 +377,7 @@ public static void writeObject1(Object o, int offset, Object value) { @Snippet public static void writeObject2(Object o, int offset, Object value) { - ObjectAccess.writeObject(o, WordFactory.signed(offset), value, ID); + ObjectAccess.writeObject(o, Word.signed(offset), value, ID); } @Snippet diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/PointerTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/PointerTest.java index 998c13d8e5b1..57fbf7b94ce6 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/PointerTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/PointerTest.java @@ -42,7 +42,6 @@ import jdk.graal.compiler.word.WordCastNode; import org.graalvm.word.LocationIdentity; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import org.junit.Assert; import org.junit.Test; @@ -172,7 +171,7 @@ public static byte readByte1(Object o, int offset) { @Snippet public static byte readByte2(Object o, int offset) { - return Word.objectToTrackedPointer(o).readByte(WordFactory.signed(offset), ID); + return Word.objectToTrackedPointer(o).readByte(Word.signed(offset), ID); } @Snippet @@ -187,7 +186,7 @@ public static void writeByte1(Object o, int offset, byte value) { @Snippet public static void writeByte2(Object o, int offset, byte value) { - Word.objectToTrackedPointer(o).writeByte(WordFactory.signed(offset), value, ID); + Word.objectToTrackedPointer(o).writeByte(Word.signed(offset), value, ID); } @Snippet @@ -202,7 +201,7 @@ public static char readChar1(Object o, int offset) { @Snippet public static char readChar2(Object o, int offset) { - return Word.objectToTrackedPointer(o).readChar(WordFactory.signed(offset), ID); + return Word.objectToTrackedPointer(o).readChar(Word.signed(offset), ID); } @Snippet @@ -217,7 +216,7 @@ public static void writeChar1(Object o, int offset, char value) { @Snippet public static void writeChar2(Object o, int offset, char value) { - Word.objectToTrackedPointer(o).writeChar(WordFactory.signed(offset), value, ID); + Word.objectToTrackedPointer(o).writeChar(Word.signed(offset), value, ID); } @Snippet @@ -232,7 +231,7 @@ public static short readShort1(Object o, int offset) { @Snippet public static short readShort2(Object o, int offset) { - return Word.objectToTrackedPointer(o).readShort(WordFactory.signed(offset), ID); + return Word.objectToTrackedPointer(o).readShort(Word.signed(offset), ID); } @Snippet @@ -247,7 +246,7 @@ public static void writeShort1(Object o, int offset, short value) { @Snippet public static void writeShort2(Object o, int offset, short value) { - Word.objectToTrackedPointer(o).writeShort(WordFactory.signed(offset), value, ID); + Word.objectToTrackedPointer(o).writeShort(Word.signed(offset), value, ID); } @Snippet @@ -262,7 +261,7 @@ public static int readInt1(Object o, int offset) { @Snippet public static int readInt2(Object o, int offset) { - return Word.objectToTrackedPointer(o).readInt(WordFactory.signed(offset), ID); + return Word.objectToTrackedPointer(o).readInt(Word.signed(offset), ID); } @Snippet @@ -277,7 +276,7 @@ public static void writeInt1(Object o, int offset, int value) { @Snippet public static void writeInt2(Object o, int offset, int value) { - Word.objectToTrackedPointer(o).writeInt(WordFactory.signed(offset), value, ID); + Word.objectToTrackedPointer(o).writeInt(Word.signed(offset), value, ID); } @Snippet @@ -292,7 +291,7 @@ public static long readLong1(Object o, int offset) { @Snippet public static long readLong2(Object o, int offset) { - return Word.objectToTrackedPointer(o).readLong(WordFactory.signed(offset), ID); + return Word.objectToTrackedPointer(o).readLong(Word.signed(offset), ID); } @Snippet @@ -307,7 +306,7 @@ public static void writeLong1(Object o, int offset, long value) { @Snippet public static void writeLong2(Object o, int offset, long value) { - Word.objectToTrackedPointer(o).writeLong(WordFactory.signed(offset), value, ID); + Word.objectToTrackedPointer(o).writeLong(Word.signed(offset), value, ID); } @Snippet @@ -322,7 +321,7 @@ public static float readFloat1(Object o, int offset) { @Snippet public static float readFloat2(Object o, int offset) { - return Word.objectToTrackedPointer(o).readFloat(WordFactory.signed(offset), ID); + return Word.objectToTrackedPointer(o).readFloat(Word.signed(offset), ID); } @Snippet @@ -337,7 +336,7 @@ public static void writeFloat1(Object o, int offset, float value) { @Snippet public static void writeFloat2(Object o, int offset, float value) { - Word.objectToTrackedPointer(o).writeFloat(WordFactory.signed(offset), value, ID); + Word.objectToTrackedPointer(o).writeFloat(Word.signed(offset), value, ID); } @Snippet @@ -352,7 +351,7 @@ public static double readDouble1(Object o, int offset) { @Snippet public static double readDouble2(Object o, int offset) { - return Word.objectToTrackedPointer(o).readDouble(WordFactory.signed(offset), ID); + return Word.objectToTrackedPointer(o).readDouble(Word.signed(offset), ID); } @Snippet @@ -367,7 +366,7 @@ public static void writeDouble1(Object o, int offset, double value) { @Snippet public static void writeDouble2(Object o, int offset, double value) { - Word.objectToTrackedPointer(o).writeDouble(WordFactory.signed(offset), value, ID); + Word.objectToTrackedPointer(o).writeDouble(Word.signed(offset), value, ID); } @Snippet @@ -382,7 +381,7 @@ public static Object readObject1(Object o, int offset) { @Snippet public static Object readObject2(Object o, int offset) { - return Word.objectToTrackedPointer(o).readObject(WordFactory.signed(offset), ID); + return Word.objectToTrackedPointer(o).readObject(Word.signed(offset), ID); } @Snippet @@ -397,7 +396,7 @@ public static void writeObject1(Object o, int offset, Object value) { @Snippet public static void writeObject2(Object o, int offset, Object value) { - Word.objectToTrackedPointer(o).writeObject(WordFactory.signed(offset), value, ID); + Word.objectToTrackedPointer(o).writeObject(Word.signed(offset), value, ID); } @Snippet diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/WordTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/WordTest.java index 437902773be4..e6ce78c8ab2b 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/WordTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/replacements/test/WordTest.java @@ -34,7 +34,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import org.junit.Test; /** @@ -119,7 +118,7 @@ public void testCast() { @Snippet public static long cast(long input) { - WordBase base = WordFactory.signed(input); + WordBase base = Word.signed(input); UnsignedWord unsigned = (UnsignedWord) base; Pointer pointer = (Pointer) unsigned; Word word = (Word) pointer; @@ -128,111 +127,111 @@ public static long cast(long input) { @Snippet public static long unsignedLong(long word) { - return WordFactory.unsigned(word).rawValue(); + return Word.unsigned(word).rawValue(); } @Snippet public static long unsignedInt(int word) { - return WordFactory.unsigned(word).rawValue(); + return Word.unsigned(word).rawValue(); } @Snippet public static long signedLong(long word) { - return WordFactory.signed(word).rawValue(); + return Word.signed(word).rawValue(); } @Snippet public static long signedInt(int word) { - return WordFactory.signed(word).rawValue(); + return Word.signed(word).rawValue(); } @Snippet public static long unsignedPlusInt(long word, int addend) { - return WordFactory.unsigned(word).add(addend).rawValue(); + return Word.unsigned(word).add(addend).rawValue(); } @Snippet public static long unsignedMinusInt(long word, int addend) { - return WordFactory.unsigned(word).subtract(addend).rawValue(); + return Word.unsigned(word).subtract(addend).rawValue(); } @Snippet public static long unsignedPlusLong(long word, long addend) { - return WordFactory.unsigned(word).add(WordFactory.unsigned(addend)).rawValue(); + return Word.unsigned(word).add(Word.unsigned(addend)).rawValue(); } @Snippet public static long unsignedMinusLong(long word, long addend) { - return WordFactory.unsigned(word).subtract(WordFactory.unsigned(addend)).rawValue(); + return Word.unsigned(word).subtract(Word.unsigned(addend)).rawValue(); } @Snippet public static long signedPlusInt(long word, int addend) { - return WordFactory.signed(word).add(addend).rawValue(); + return Word.signed(word).add(addend).rawValue(); } @Snippet public static long signedMinusInt(long word, int addend) { - return WordFactory.signed(word).subtract(addend).rawValue(); + return Word.signed(word).subtract(addend).rawValue(); } @Snippet public static long signedPlusLong(long word, long addend) { - return WordFactory.signed(word).add(WordFactory.signed(addend)).rawValue(); + return Word.signed(word).add(Word.signed(addend)).rawValue(); } @Snippet public static long signedMinusLong(long word, long addend) { - return WordFactory.signed(word).subtract(WordFactory.signed(addend)).rawValue(); + return Word.signed(word).subtract(Word.signed(addend)).rawValue(); } @Snippet public static long signedNot(long word) { - return WordFactory.signed(word).not().rawValue(); + return Word.signed(word).not().rawValue(); } @Snippet public static long unsignedNot(long word) { - return WordFactory.unsigned(word).not().rawValue(); + return Word.unsigned(word).not().rawValue(); } @Snippet public static boolean aboveOrEqual(long word1, long word2) { - return WordFactory.unsigned(word1).aboveOrEqual(WordFactory.unsigned(word2)); + return Word.unsigned(word1).aboveOrEqual(Word.unsigned(word2)); } @Snippet public static boolean above(long word1, long word2) { - return WordFactory.unsigned(word1).aboveThan(WordFactory.unsigned(word2)); + return Word.unsigned(word1).aboveThan(Word.unsigned(word2)); } @Snippet public static boolean belowOrEqual(long word1, long word2) { - return WordFactory.unsigned(word1).belowOrEqual(WordFactory.unsigned(word2)); + return Word.unsigned(word1).belowOrEqual(Word.unsigned(word2)); } @Snippet public static boolean below(long word1, long word2) { - return WordFactory.unsigned(word1).belowThan(WordFactory.unsigned(word2)); + return Word.unsigned(word1).belowThan(Word.unsigned(word2)); } @Snippet public static long andInt(long word, int addend) { - return WordFactory.unsigned(word).and(addend).rawValue(); + return Word.unsigned(word).and(addend).rawValue(); } @Snippet public static long orInt(long word, int addend) { - return WordFactory.unsigned(word).or(addend).rawValue(); + return Word.unsigned(word).or(addend).rawValue(); } @Snippet public static long andLong(long word, long addend) { - return WordFactory.unsigned(word).and(WordFactory.unsigned(addend)).rawValue(); + return Word.unsigned(word).and(Word.unsigned(addend)).rawValue(); } @Snippet public static long orLong(long word, long addend) { - return WordFactory.unsigned(word).or(WordFactory.unsigned(addend)).rawValue(); + return Word.unsigned(word).or(Word.unsigned(addend)).rawValue(); } } diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/test/WordTests.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/test/WordTests.java new file mode 100644 index 000000000000..e467d054ed41 --- /dev/null +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/test/WordTests.java @@ -0,0 +1,185 @@ +/* + * 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.test; + +import static jdk.graal.compiler.core.common.calc.UnsignedMath.aboveOrEqual; +import static jdk.graal.compiler.core.common.calc.UnsignedMath.aboveThan; +import static jdk.graal.compiler.core.common.calc.UnsignedMath.belowOrEqual; +import static jdk.graal.compiler.core.common.calc.UnsignedMath.belowThan; + +import jdk.graal.compiler.word.Word; +import org.graalvm.word.Pointer; +import org.graalvm.word.SignedWord; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +/** + * Tests word operations on boxed values produced by {@link WordFactory} and {@link Word}. + */ +public class WordTests { + + static long[] words = { + Long.MIN_VALUE, + Long.MIN_VALUE + 1, + -1L, + 0L, + 1L, + Long.MAX_VALUE - 1, + Long.MAX_VALUE, + Integer.MAX_VALUE - 1L, + Integer.MAX_VALUE, + Integer.MAX_VALUE + 1L, + Integer.MIN_VALUE - 1L, + Integer.MIN_VALUE, + Integer.MIN_VALUE + 1L + }; + + static SignedWord graalSigned(long val) { + return Word.signed(val); + } + + static UnsignedWord graalUnsigned(long val) { + return Word.unsigned(val); + } + + static Pointer graalPointer(long val) { + return Word.pointer(val); + } + + static List signedWords = Stream.concat( + LongStream.of(words).mapToObj(org.graalvm.word.WordFactory::signed), + LongStream.of(words).mapToObj(WordTests::graalSigned)).toList(); + static List unsignedWords = Stream.concat( + LongStream.of(words).mapToObj(org.graalvm.word.WordFactory::unsigned), + LongStream.of(words).mapToObj(WordTests::graalUnsigned)).toList(); + static List pointers = Stream.concat( + LongStream.of(words).mapToObj(org.graalvm.word.WordFactory::pointer), + LongStream.of(words).mapToObj(WordTests::graalPointer)).toList(); + + @Test + public void testSigned() { + for (var x : signedWords) { + Assert.assertEquals(x.not().rawValue(), ~x.rawValue()); + + for (var y : signedWords) { + Assert.assertEquals(x.equal(y), x.rawValue() == y.rawValue()); + Assert.assertEquals(x.notEqual(y), x.rawValue() != y.rawValue()); + + Assert.assertEquals(x.add(y).rawValue(), x.rawValue() + y.rawValue()); + Assert.assertEquals(x.add(y).rawValue(), x.rawValue() + y.rawValue()); + Assert.assertEquals(x.subtract(y).rawValue(), x.rawValue() - y.rawValue()); + Assert.assertEquals(x.multiply(y).rawValue(), x.rawValue() * y.rawValue()); + + if (y.rawValue() != 0) { + Assert.assertEquals(x.signedDivide(y).rawValue(), x.rawValue() / y.rawValue()); + Assert.assertEquals(x.signedRemainder(y).rawValue(), x.rawValue() % y.rawValue()); + } + Assert.assertEquals(x.and(y).rawValue(), x.rawValue() & y.rawValue()); + Assert.assertEquals(x.or(y).rawValue(), x.rawValue() | y.rawValue()); + Assert.assertEquals(x.xor(y).rawValue(), x.rawValue() ^ y.rawValue()); + + Assert.assertEquals(x.greaterThan(y), x.rawValue() > y.rawValue()); + Assert.assertEquals(x.greaterOrEqual(y), x.rawValue() >= y.rawValue()); + Assert.assertEquals(x.lessThan(y), x.rawValue() < y.rawValue()); + Assert.assertEquals(x.lessOrEqual(y), x.rawValue() <= y.rawValue()); + + Assert.assertEquals(x.shiftLeft((UnsignedWord) y).rawValue(), x.rawValue() << y.rawValue()); + Assert.assertEquals(x.signedShiftRight((UnsignedWord) y).rawValue(), x.rawValue() >> y.rawValue()); + } + } + } + + @Test + public void testUnsigned() { + for (var x : unsignedWords) { + Assert.assertEquals(x.not().rawValue(), ~x.rawValue()); + + for (var y : unsignedWords) { + Assert.assertEquals(x.equal(y), x.rawValue() == y.rawValue()); + Assert.assertEquals(x.notEqual(y), x.rawValue() != y.rawValue()); + + Assert.assertEquals(x.add(y).rawValue(), x.rawValue() + y.rawValue()); + Assert.assertEquals(x.subtract(y).rawValue(), x.rawValue() - y.rawValue()); + Assert.assertEquals(x.multiply(y).rawValue(), x.rawValue() * y.rawValue()); + + if (y.rawValue() != 0) { + Assert.assertEquals(x.unsignedDivide(y).rawValue(), Long.divideUnsigned(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.unsignedRemainder(y).rawValue(), Long.remainderUnsigned(x.rawValue(), y.rawValue())); + } + Assert.assertEquals(x.and(y).rawValue(), x.rawValue() & y.rawValue()); + Assert.assertEquals(x.or(y).rawValue(), x.rawValue() | y.rawValue()); + Assert.assertEquals(x.xor(y).rawValue(), x.rawValue() ^ y.rawValue()); + + Assert.assertEquals(x.aboveThan(y), aboveThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.aboveOrEqual(y), aboveOrEqual(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowThan(y), belowThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowOrEqual(y), belowOrEqual(x.rawValue(), y.rawValue())); + + Assert.assertEquals(x.shiftLeft(y).rawValue(), x.rawValue() << y.rawValue()); + Assert.assertEquals(x.unsignedShiftRight(y).rawValue(), x.rawValue() >>> y.rawValue()); + } + } + } + + @Test + public void testPointer() { + for (var x : pointers) { + Assert.assertEquals(x.not().rawValue(), ~x.rawValue()); + Assert.assertEquals(x.isNull(), x.rawValue() == 0); + Assert.assertEquals(x.isNonNull(), x.rawValue() != 0); + + for (var y : pointers) { + Assert.assertEquals(x.equal(y), x.rawValue() == y.rawValue()); + Assert.assertEquals(x.notEqual(y), x.rawValue() != y.rawValue()); + + Assert.assertEquals(x.add(y).rawValue(), x.rawValue() + y.rawValue()); + Assert.assertEquals(x.subtract(y).rawValue(), x.rawValue() - y.rawValue()); + Assert.assertEquals(x.multiply(y).rawValue(), x.rawValue() * y.rawValue()); + + if (y.rawValue() != 0) { + Assert.assertEquals(x.unsignedDivide(y).rawValue(), Long.divideUnsigned(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.unsignedRemainder(y).rawValue(), Long.remainderUnsigned(x.rawValue(), y.rawValue())); + } + Assert.assertEquals(x.and(y).rawValue(), x.rawValue() & y.rawValue()); + Assert.assertEquals(x.or(y).rawValue(), x.rawValue() | y.rawValue()); + Assert.assertEquals(x.xor(y).rawValue(), x.rawValue() ^ y.rawValue()); + + Assert.assertEquals(x.aboveThan(y), aboveThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.aboveOrEqual(y), aboveOrEqual(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowThan(y), belowThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowOrEqual(y), belowOrEqual(x.rawValue(), y.rawValue())); + + Assert.assertEquals(x.shiftLeft(y).rawValue(), x.rawValue() << y.rawValue()); + Assert.assertEquals(x.unsignedShiftRight(y).rawValue(), x.rawValue() >>> y.rawValue()); + } + } + } +} diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DynamicObjectPartialEvaluationTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DynamicObjectPartialEvaluationTest.java index c2a5e5be7992..23612ccd0231 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DynamicObjectPartialEvaluationTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DynamicObjectPartialEvaluationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -24,18 +24,8 @@ */ package jdk.graal.compiler.truffle.test; -import jdk.graal.compiler.truffle.test.nodes.AbstractTestNode; -import jdk.graal.compiler.truffle.test.nodes.RootTestNode; -import jdk.graal.compiler.graph.Node; -import jdk.graal.compiler.nodes.NamedLocationIdentity; -import jdk.graal.compiler.nodes.StructuredGraph; -import jdk.graal.compiler.nodes.extended.GuardedUnsafeLoadNode; -import jdk.graal.compiler.nodes.extended.RawLoadNode; -import jdk.graal.compiler.nodes.extended.RawStoreNode; -import jdk.graal.compiler.nodes.extended.UnsafeAccessNode; -import jdk.graal.compiler.nodes.java.LoadFieldNode; -import jdk.graal.compiler.nodes.java.StoreFieldNode; -import jdk.graal.compiler.truffle.nodes.ObjectLocationIdentity; +import java.lang.invoke.MethodHandles; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -50,6 +40,18 @@ import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.runtime.OptimizedCallTarget; +import jdk.graal.compiler.graph.Node; +import jdk.graal.compiler.nodes.NamedLocationIdentity; +import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.nodes.extended.GuardedUnsafeLoadNode; +import jdk.graal.compiler.nodes.extended.RawLoadNode; +import jdk.graal.compiler.nodes.extended.RawStoreNode; +import jdk.graal.compiler.nodes.extended.UnsafeAccessNode; +import jdk.graal.compiler.nodes.java.LoadFieldNode; +import jdk.graal.compiler.nodes.java.StoreFieldNode; +import jdk.graal.compiler.truffle.nodes.ObjectLocationIdentity; +import jdk.graal.compiler.truffle.test.nodes.AbstractTestNode; +import jdk.graal.compiler.truffle.test.nodes.RootTestNode; import jdk.vm.ci.meta.JavaKind; public class DynamicObjectPartialEvaluationTest extends PartialEvaluationTest { @@ -59,8 +61,9 @@ public class DynamicObjectPartialEvaluationTest extends PartialEvaluationTest { @Before public void before() { - rootShapeWithoutFields = Shape.newBuilder().layout(TestDynamicObject.class).build(); - rootShapeWithFields = Shape.newBuilder().layout(TestDynamicObjectWithFields.class).build(); + var lookup = MethodHandles.lookup(); + rootShapeWithoutFields = Shape.newBuilder().layout(TestDynamicObject.class, lookup).build(); + rootShapeWithFields = Shape.newBuilder().layout(TestDynamicObjectWithFields.class, lookup).build(); newInstanceWithFields(); newInstanceWithoutFields(); } diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/EncodedGraphCacheTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/EncodedGraphCacheTest.java index 9e8e55fdaa2d..5a0540b24807 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/EncodedGraphCacheTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/EncodedGraphCacheTest.java @@ -128,8 +128,8 @@ private static RootTestNode rootTestNode() { } private static boolean encodedGraphCacheContains(TruffleCompilerImpl compiler, ResolvedJavaMethod method) { - EconomicMap cache = compiler.getPartialEvaluator().getOrCreateEncodedGraphCache(); - return cache.containsKey(method); + EconomicMap cache1 = compiler.getPartialEvaluator().getOrCreateEncodedGraphCache(); + return cache1.containsKey(method); } @SuppressWarnings("try") @@ -218,8 +218,8 @@ public void testUnboundedCacheCapacity() { boolean encodedGraphCache = true; testHelper(encodedGraphCache, 100_000, compiler -> { runWithBooleanFlag(compiler, EncodedGraphCacheTest::disableEncodedGraphCachePurges, true, () -> { - EconomicMap cache = compiler.getPartialEvaluator().getOrCreateEncodedGraphCache(); - nonEmptyGraphCache[0] = !cache.isEmpty(); + EconomicMap cache1 = compiler.getPartialEvaluator().getOrCreateEncodedGraphCache(); + nonEmptyGraphCache[0] = !cache1.isEmpty(); }); }); } diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/FrameDescriptorTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/FrameDescriptorTest.java index e62835cb563c..0eaabc84d21b 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/FrameDescriptorTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/FrameDescriptorTest.java @@ -24,9 +24,8 @@ */ package jdk.graal.compiler.truffle.test; -import jdk.graal.compiler.nodes.ConstantNode; -import jdk.graal.compiler.nodes.ReturnNode; -import jdk.graal.compiler.nodes.StructuredGraph; +import static org.junit.Assert.assertNull; + import org.junit.Assert; import org.junit.Test; @@ -34,11 +33,16 @@ import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.frame.Frame; import com.oracle.truffle.api.frame.FrameDescriptor; +import com.oracle.truffle.api.frame.FrameSlotTypeException; import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.runtime.OptimizedCallTarget; +import jdk.graal.compiler.nodes.ConstantNode; +import jdk.graal.compiler.nodes.ReturnNode; +import jdk.graal.compiler.nodes.StructuredGraph; + public class FrameDescriptorTest extends PartialEvaluationTest { @Test @@ -129,4 +133,90 @@ public void testInfo() { FrameDescriptor fd = builder.info("foo").info(obj).build(); assertEmptyFrameDescriptor(fd, obj); } + + @Test + public void testIllegalDefaultFails() { + FrameDescriptor.Builder builder = FrameDescriptor.newBuilder().defaultValueIllegal(); + int index0 = builder.addSlots(1); + + FrameDescriptor fd = builder.build(); + FrameSlotTypeException e; + + RootNode root1 = new RootNode(null, fd) { + @Override + public Object execute(VirtualFrame frame) { + return frame.getObject(index0); + } + }; + + // fails in interpreted + e = Assert.assertThrows(FrameSlotTypeException.class, () -> { + root1.getCallTarget().call(); + }); + Assert.assertEquals("Frame slot kind Object expected, but got Illegal at frame slot index 0.", e.getMessage()); + + compile(root1); + + // fails in compiled + e = Assert.assertThrows(FrameSlotTypeException.class, () -> { + root1.getCallTarget().call(); + }); + Assert.assertEquals("Frame slot kind Object expected, but got Illegal at frame slot index 0.", e.getMessage()); + } + + @Test + public void testIllegalDefaultNull() { + FrameDescriptor.Builder builder = FrameDescriptor.newBuilder().defaultValueIllegal(); + int index0 = builder.addSlots(1); + FrameDescriptor fd = builder.build(); + + RootNode root1 = new RootNode(null, fd) { + @Override + public Object execute(VirtualFrame frame) { + frame.setObject(index0, null); + return frame.getObject(index0); + } + }; + + assertNull(root1.getCallTarget().call()); + compile(root1); + assertNull(root1.getCallTarget().call()); + } + + @Test + public void testIllegalDefaultCleared() { + FrameDescriptor.Builder builder = FrameDescriptor.newBuilder().defaultValueIllegal(); + int index0 = builder.addSlots(1); + FrameDescriptor fd = builder.build(); + FrameSlotTypeException e; + + RootNode root1 = new RootNode(null, fd) { + @Override + public Object execute(VirtualFrame frame) { + frame.setObject(index0, null); + frame.clear(index0); + return frame.getObject(index0); + } + }; + + // fails in interpreted + e = Assert.assertThrows(FrameSlotTypeException.class, () -> { + root1.getCallTarget().call(); + }); + Assert.assertEquals("Frame slot kind Object expected, but got Illegal at frame slot index 0.", e.getMessage()); + + compile(root1); + + // fails in compiled + e = Assert.assertThrows(FrameSlotTypeException.class, () -> { + root1.getCallTarget().call(); + }); + Assert.assertEquals("Frame slot kind Object expected, but got Illegal at frame slot index 0.", e.getMessage()); + } + + private static void compile(RootNode root) { + OptimizedCallTarget target = (OptimizedCallTarget) root.getCallTarget(); + target.compile(true); + target.waitForCompilation(); + } } diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/MaterializedFrameTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/MaterializedFrameTest.java index aef03aba4b25..151fd9d44c9d 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/MaterializedFrameTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/MaterializedFrameTest.java @@ -61,7 +61,7 @@ private static RootNode createRootNode() { @Override public Object execute(VirtualFrame frame) { MaterializedFrame mframe = frameClassProfile.profile(GraalDirectives.opaque(frame.materialize())); - if (mframe.getFrameDescriptor().getSlotKind(slot) != FrameSlotKind.Int) { + if (getFrameDescriptor().getSlotKind(slot) != FrameSlotKind.Int) { CompilerDirectives.transferToInterpreterAndInvalidate(); mframe.getFrameDescriptor().setSlotKind(slot, FrameSlotKind.Int); } diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/strings/TStringOpsTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/strings/TStringOpsTest.java index d5526ed28cc6..7fc220151d5c 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/strings/TStringOpsTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/strings/TStringOpsTest.java @@ -176,18 +176,18 @@ protected ResolvedJavaMethod getIndexOf2ConsecutiveWithStrideIntl() { } protected InstalledCode cacheInstalledCodeConstantStride(ResolvedJavaMethod installedCodeOwner, StructuredGraph graph, OptionValues options, ResolvedJavaMethod expectedMethod, - InstalledCode[] cache, int strideA, int strideB) { - return cacheInstalledCodeConstantStrideLength(installedCodeOwner, graph, options, expectedMethod, cache, strideA, strideB, 0); + InstalledCode[] cache1, int strideA, int strideB) { + return cacheInstalledCodeConstantStrideLength(installedCodeOwner, graph, options, expectedMethod, cache1, strideA, strideB, 0); } protected InstalledCode cacheInstalledCodeConstantStrideLength(ResolvedJavaMethod installedCodeOwner, StructuredGraph graph, OptionValues options, ResolvedJavaMethod expectedMethod, - InstalledCode[] cache, int strideA, int strideB, int iLength) { + InstalledCode[] cache1, int strideA, int strideB, int iLength) { Assert.assertEquals(expectedMethod, installedCodeOwner); int index = (iLength * 9) + (strideA * 3) + strideB; - InstalledCode installedCode = cache[index]; + InstalledCode installedCode = cache1[index]; while (installedCode == null || !installedCode.isValid()) { installedCode = super.getCode(installedCodeOwner, graph, true, false, options); - cache[index] = installedCode; + cache1[index] = installedCode; } return installedCode; } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/asm/amd64/AMD64Assembler.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/asm/amd64/AMD64Assembler.java index d782f290c2e2..794bdc3f5f09 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/asm/amd64/AMD64Assembler.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/asm/amd64/AMD64Assembler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2025, 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 @@ -2276,10 +2276,10 @@ public static class VexRVMOp extends VexOp { public static final VexRVMOp VPACKSSWB = new VexRVMOp("VPACKSSWB", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0x63, VEXOpAssertion.AVX1_AVX2_AVX512BW_VL, EVEXTuple.FVM, VEXPrefixConfig.WIG); public static final VexRVMOp VADDSUBPS = new VexRVMOp("VADDSUBPS", VEXPrefixConfig.P_F2, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xD0, VEXOpAssertion.AVX1); public static final VexRVMOp VADDSUBPD = new VexRVMOp("VADDSUBPD", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xD0, VEXOpAssertion.AVX1); - public static final VexRVMOp VPAND = new VexRVMOp("VPAND", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xDB, VEXOpAssertion.AVX1_AVX2_AVX512F_VL, EVEXTuple.FVM, VEXPrefixConfig.W1); - public static final VexRVMOp VPANDN = new VexRVMOp("VPANDN", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xDF, VEXOpAssertion.AVX1_AVX2_AVX512F_VL, EVEXTuple.FVM, VEXPrefixConfig.W1); - public static final VexRVMOp VPOR = new VexRVMOp("VPOR", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xEB, VEXOpAssertion.AVX1_AVX2_AVX512F_VL, EVEXTuple.FVM, VEXPrefixConfig.W1); - public static final VexRVMOp VPXOR = new VexRVMOp("VPXOR", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xEF, VEXOpAssertion.AVX1_AVX2_AVX512F_VL, EVEXTuple.FVM, VEXPrefixConfig.W1); + public static final VexRVMOp VPAND = new VexRVMOp("VPAND", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xDB, VEXOpAssertion.AVX1_AVX2_AVX512F_VL, EVEXTuple.FVM, VEXPrefixConfig.W0); + public static final VexRVMOp VPANDN = new VexRVMOp("VPANDN", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xDF, VEXOpAssertion.AVX1_AVX2_AVX512F_VL, EVEXTuple.FVM, VEXPrefixConfig.W0); + public static final VexRVMOp VPOR = new VexRVMOp("VPOR", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xEB, VEXOpAssertion.AVX1_AVX2_AVX512F_VL, EVEXTuple.FVM, VEXPrefixConfig.W0); + public static final VexRVMOp VPXOR = new VexRVMOp("VPXOR", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xEF, VEXOpAssertion.AVX1_AVX2_AVX512F_VL, EVEXTuple.FVM, VEXPrefixConfig.W0); public static final VexRVMOp VPADDB = new VexRVMOp("VPADDB", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xFC, VEXOpAssertion.AVX1_AVX2_AVX512BW_VL, EVEXTuple.FVM, VEXPrefixConfig.WIG); public static final VexRVMOp VPADDW = new VexRVMOp("VPADDW", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xFD, VEXOpAssertion.AVX1_AVX2_AVX512BW_VL, EVEXTuple.FVM, VEXPrefixConfig.WIG); public static final VexRVMOp VPADDD = new VexRVMOp("VPADDD", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xFE, VEXOpAssertion.AVX1_AVX2_AVX512F_VL, EVEXTuple.FVM, VEXPrefixConfig.W0); @@ -2327,7 +2327,11 @@ public static class VexRVMOp extends VexOp { public static final VexRVMOp VSQRTSD = new VexRVMOp("VSQRTSD", VEXPrefixConfig.P_F2, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0x51, VEXOpAssertion.AVX1_AVX512F_128, EVEXTuple.T1S_64BIT, VEXPrefixConfig.W1); public static final VexRVMOp VSQRTSS = new VexRVMOp("VSQRTSS", VEXPrefixConfig.P_F3, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0x51, VEXOpAssertion.AVX1_AVX512F_128, EVEXTuple.T1S_32BIT, VEXPrefixConfig.W0); + public static final VexRVMOp VPERMILPS = new VexRVMOp("VPERMILPS", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F38, VEXPrefixConfig.W0, 0x0C, VEXOpAssertion.AVX1_AVX512F_VL, EVEXTuple.FVM, VEXPrefixConfig.W0); public static final VexRVMOp VPERMD = new VexRVMOp("VPERMD", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F38, VEXPrefixConfig.W0, 0x36, VEXOpAssertion.AVX2_AVX512F_VL_256_512, EVEXTuple.FVM, VEXPrefixConfig.W0); + public static final VexRVMOp VPERMPS = new VexRVMOp("VPERMPS", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F38, VEXPrefixConfig.W0, 0x16, VEXOpAssertion.AVX2_AVX512F_VL_256_512, EVEXTuple.FVM, VEXPrefixConfig.W0); + public static final VexRVMOp VPERMILPD = new VexRVMOp("VPERMILPD", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F38, VEXPrefixConfig.W0, 0x0D, VEXOpAssertion.AVX1_AVX512F_VL, EVEXTuple.FVM, VEXPrefixConfig.W1); + public static final VexRVMOp VMOVSS = new VexRVMOp("VMOVSS", VEXPrefixConfig.P_F3, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0x10, VEXOpAssertion.AVX1_AVX512F_128, EVEXTuple.T1S_32BIT, VEXPrefixConfig.W0); public static final VexRVMOp VMOVSD = new VexRVMOp("VMOVSD", VEXPrefixConfig.P_F2, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0x10, VEXOpAssertion.AVX1_AVX512F_128, EVEXTuple.T1S_64BIT, VEXPrefixConfig.W1); public static final VexRVMOp VMOVHPD = new VexRVMOp("VMOVHPD", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0x16, VEXOpAssertion.AVX1_AVX512F_128, EVEXTuple.T1S_64BIT, VEXPrefixConfig.W1); @@ -2371,10 +2375,14 @@ public static class VexRVMOp extends VexOp { public static final VexRVMOp EVMAXSD = new VexRVMOp("EVMAXSD", VMAXSD); public static final VexRVMOp EVPACKUSDW = new VexRVMOp("EVPACKUSDW", VPACKUSDW); public static final VexRVMOp EVPACKUSWB = new VexRVMOp("EVPACKUSWB", VPACKUSWB); - public static final VexRVMOp EVPAND = new VexRVMOp("EVPAND", VPAND); - public static final VexRVMOp EVPANDN = new VexRVMOp("EVPANDN", VPANDN); - public static final VexRVMOp EVPOR = new VexRVMOp("EVPOR", VPOR); - public static final VexRVMOp EVPXOR = new VexRVMOp("EVPXOR", VPXOR); + public static final VexRVMOp EVPANDD = new VexRVMOp("EVPANDD", VPAND); + public static final VexRVMOp EVPANDQ = new VexRVMOp("EVPANDQ", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xDB, VEXOpAssertion.AVX512F_VL, EVEXTuple.FVM, VEXPrefixConfig.W1, true); + public static final VexRVMOp EVPANDND = new VexRVMOp("EVPANDND", VPANDN); + public static final VexRVMOp EVPANDNQ = new VexRVMOp("EVPANDNQ", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xDF, VEXOpAssertion.AVX512F_VL, EVEXTuple.FVM, VEXPrefixConfig.W1, true); + public static final VexRVMOp EVPORD = new VexRVMOp("EVPORD", VPOR); + public static final VexRVMOp EVPORQ = new VexRVMOp("EVPORQ", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xEB, VEXOpAssertion.AVX512F_VL, EVEXTuple.FVM, VEXPrefixConfig.W1, true); + public static final VexRVMOp EVPXORD = new VexRVMOp("EVPXORD", VPXOR); + public static final VexRVMOp EVPXORQ = new VexRVMOp("EVPXORQ", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F, VEXPrefixConfig.WIG, 0xEF, VEXOpAssertion.AVX512F_VL, EVEXTuple.FVM, VEXPrefixConfig.W1, true); public static final VexRVMOp EVPADDB = new VexRVMOp("EVPADDB", VPADDB); public static final VexRVMOp EVPADDW = new VexRVMOp("EVPADDW", VPADDW); public static final VexRVMOp EVPADDD = new VexRVMOp("EVPADDD", VPADDD); @@ -2431,8 +2439,14 @@ public static class VexRVMOp extends VexOp { public static final VexRVMOp EVSQRTSD = new VexRVMOp("EVSQRTSD", VSQRTSD); public static final VexRVMOp EVSQRTSS = new VexRVMOp("EVSQRTSS", VSQRTSS); + public static final VexRVMOp EVPERMB = new VexRVMOp("EVPERMB", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F38, VEXPrefixConfig.W0, 0x8D, VEXOpAssertion.AVX512_VBMI_VL, EVEXTuple.FVM, VEXPrefixConfig.W0, true); public static final VexRVMOp EVPERMW = new VexRVMOp("EVPERMW", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F38, VEXPrefixConfig.W1, 0x8D, VEXOpAssertion.AVX512BW_VL, EVEXTuple.FVM, VEXPrefixConfig.W1, true); + public static final VexRVMOp EVPERMILPS = new VexRVMOp("EVPERMILPS", VPERMILPS); public static final VexRVMOp EVPERMD = new VexRVMOp("EVPERMD", VPERMD); + public static final VexRVMOp EVPERMPS = new VexRVMOp("EVPERMPS", VPERMPS); + public static final VexRVMOp EVPERMILPD = new VexRVMOp("EVPERMILPD", VPERMILPD); + public static final VexRVMOp EVPERMQ = new VexRVMOp("EVPERMQ", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F38, VEXPrefixConfig.W1, 0x36, VEXOpAssertion.AVX512F_VL_256_512, EVEXTuple.FVM, VEXPrefixConfig.W1, true); + public static final VexRVMOp EVPERMPD = new VexRVMOp("EVPERMPD", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F38, VEXPrefixConfig.W1, 0x16, VEXOpAssertion.AVX512F_VL_256_512, EVEXTuple.FVM, VEXPrefixConfig.W1, true); public static final VexRVMOp EVPBLENDMB = new VexRVMOp("EVPBLENDMB", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F38, VEXPrefixConfig.W0, 0x66, VEXOpAssertion.AVX512BW_VL, EVEXTuple.FVM, VEXPrefixConfig.W0, true); public static final VexRVMOp EVPBLENDMW = new VexRVMOp("EVPBLENDMW", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F38, VEXPrefixConfig.W1, 0x66, VEXOpAssertion.AVX512BW_VL, EVEXTuple.FVM, VEXPrefixConfig.W1, true); @@ -3135,6 +3149,71 @@ public void emit(AMD64Assembler asm, AVXSize size, Register dst, Register src1, } } + /** + * VEX-encoded comparison operation with an operand order of RVMI. The immediate operand is a + * comparison operator. + */ + public static final class VexIntegerCompareOp extends VexOp { + // @formatter:off + public static final VexIntegerCompareOp EVPCMPB = new VexIntegerCompareOp("EVPCMPB", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F3A, VEXPrefixConfig.W0, 0x3F, VEXOpAssertion.MASK_XMM_XMM_AVX512BW_VL, EVEXTuple.FVM); + public static final VexIntegerCompareOp EVPCMPW = new VexIntegerCompareOp("EVPCMPW", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F3A, VEXPrefixConfig.W1, 0x3F, VEXOpAssertion.MASK_XMM_XMM_AVX512BW_VL, EVEXTuple.FVM); + public static final VexIntegerCompareOp EVPCMPD = new VexIntegerCompareOp("EVPCMPD", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F3A, VEXPrefixConfig.W0, 0x1F, VEXOpAssertion.MASK_XMM_XMM_AVX512F_VL, EVEXTuple.FVM); + public static final VexIntegerCompareOp EVPCMPQ = new VexIntegerCompareOp("EVPCMPQ", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F3A, VEXPrefixConfig.W1, 0x1F, VEXOpAssertion.MASK_XMM_XMM_AVX512F_VL, EVEXTuple.FVM); + + public static final VexIntegerCompareOp EVPCMPUB = new VexIntegerCompareOp("EVPCMPUB", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F3A, VEXPrefixConfig.W0, 0x3E, VEXOpAssertion.MASK_XMM_XMM_AVX512BW_VL, EVEXTuple.FVM); + public static final VexIntegerCompareOp EVPCMPUW = new VexIntegerCompareOp("EVPCMPUW", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F3A, VEXPrefixConfig.W1, 0x3E, VEXOpAssertion.MASK_XMM_XMM_AVX512BW_VL, EVEXTuple.FVM); + public static final VexIntegerCompareOp EVPCMPUD = new VexIntegerCompareOp("EVPCMPUD", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F3A, VEXPrefixConfig.W0, 0x1E, VEXOpAssertion.MASK_XMM_XMM_AVX512F_VL, EVEXTuple.FVM); + public static final VexIntegerCompareOp EVPCMPUQ = new VexIntegerCompareOp("EVPCMPUQ", VEXPrefixConfig.P_66, VEXPrefixConfig.M_0F3A, VEXPrefixConfig.W1, 0x1E, VEXOpAssertion.MASK_XMM_XMM_AVX512F_VL, EVEXTuple.FVM); + // @formatter:on + + public enum Predicate { + EQ(0), + LT(1), + LE(2), + FALSE(3), + NEQ(4), + NLT(5), + NLE(6), + TRUE(7); + + private int imm8; + + Predicate(int imm8) { + this.imm8 = imm8; + } + + public static Predicate getPredicate(Condition condition) { + return switch (condition) { + case EQ -> EQ; + case NE -> NEQ; + case LT, BT -> LT; + case LE, BE -> LE; + case GT, AT -> NLE; + case GE, AE -> NLT; + default -> throw GraalError.shouldNotReachHereUnexpectedValue(condition); + }; + } + } + + private VexIntegerCompareOp(String opcode, int pp, int mmmmm, int wEvex, int op, VEXOpAssertion assertion, EVEXTuple evexTuple) { + super(opcode, pp, mmmmm, wEvex, op, assertion, evexTuple, wEvex, true); + } + + public void emit(AMD64Assembler asm, AVXSize size, Register dst, Register src1, Register src2, Register mask, Predicate p) { + emitVexOrEvex(asm, dst, src1, src2, mask, size, pp, mmmmm, w, wEvex, Z0, B0); + asm.emitByte(op); + asm.emitModRM(dst, src2); + asm.emitByte(p.imm8); + } + + public void emit(AMD64Assembler asm, AVXSize size, Register dst, Register src1, AMD64Address src2, Register mask, Predicate p, int b) { + emitVexOrEvex(asm, dst, src1, src2, mask, size, pp, mmmmm, w, wEvex, Z0, b); + asm.emitByte(op); + asm.emitOperandHelper(dst, src2, 1, getDisp8Scale(isEvex, size)); + asm.emitByte(p.imm8); + } + } + /** * VEX-encoded comparison operation with an operand order of RVMI. The immediate operand is a * comparison operator. @@ -3252,14 +3331,22 @@ public VexFloatCompareOp encoding(AMD64SIMDInstructionEncoding encoding) { } public void emit(AMD64Assembler asm, AVXSize size, Register dst, Register src1, Register src2, Predicate p) { - emitVexOrEvex(asm, dst, src1, src2, size, pp, mmmmm, w, wEvex); + emit(asm, size, dst, src1, src2, Register.None, p); + } + + public void emit(AMD64Assembler asm, AVXSize size, Register dst, Register src1, AMD64Address src2, Predicate p) { + emit(asm, size, dst, src1, src2, Register.None, p, B0); + } + + public void emit(AMD64Assembler asm, AVXSize size, Register dst, Register src1, Register src2, Register mask, Predicate p) { + emitVexOrEvex(asm, dst, src1, src2, mask, size, pp, mmmmm, w, wEvex, Z0, B0); asm.emitByte(op); asm.emitModRM(dst, src2); asm.emitByte(p.imm8); } - public void emit(AMD64Assembler asm, AVXSize size, Register dst, Register src1, AMD64Address src2, Predicate p) { - emitVexOrEvex(asm, dst, src1, src2, size, pp, mmmmm, w, wEvex); + public void emit(AMD64Assembler asm, AVXSize size, Register dst, Register src1, AMD64Address src2, Register mask, Predicate p, int b) { + emitVexOrEvex(asm, dst, src1, src2, mask, size, pp, mmmmm, w, wEvex, Z0, b); asm.emitByte(op); asm.emitOperandHelper(dst, src2, 1, getDisp8Scale(isEvex, size)); asm.emitByte(p.imm8); @@ -4544,6 +4631,26 @@ public final void testl(Register dst, int imm32) { } } + /** + * Emit a UD2 instruction, this signals the processor to stop decoding instructions further in + * the fallthrough path (Intel Optimization Reference Manual Volume 1, section 3.4.1.5, Branch + * Type Selection, Assembly/Compiler coding rule 13). + *

+ * This also helps when we want to emit data in the code section as it prevents mismatched + * instructions when decoding from different paths. E.g. consider this piece of hex code: + *

+ * {@code 01 48 01 c8} + *

+ * With {@code 01} being the data and {@code 48 01 c8} being {@code add rax, rcx}. However, if + * the decoder starts with {@code 01} it will see the next instruction being {@code 01 48 01} + * which is {@code add [rax + 1], ecx}. This mismatch invalidates the uop cache as the CPU + * cannot know which instruction sequence is the correct one. + */ + public void ud2() { + emitByte(0x0F); + emitByte(0x0B); + } + public final void vzeroupper() { emitVEX(VEXPrefixConfig.L128, VEXPrefixConfig.P_, VEXPrefixConfig.M_0F, VEXPrefixConfig.W0, 0, 0); emitByte(0x77); @@ -6169,6 +6276,6 @@ public final void evpternlogq(Register dst, int imm8, Register src1, Register sr } public final void evpxorq(Register dst, Register mask, Register nds, AMD64Address src) { - VexRVMOp.EVPXOR.emit(this, AVXSize.ZMM, dst, nds, src, mask); + VexRVMOp.EVPXORQ.emit(this, AVXSize.ZMM, dst, nds, src, mask); } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/DebugOptions.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/DebugOptions.java index 302c7c2cf970..b162be769343 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/DebugOptions.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/DebugOptions.java @@ -252,7 +252,7 @@ public static String getDumpDirectoryName(OptionValues options) { dumpDir = getPath(DumpPath.getValue(options)); } else { Date date = new Date(GraalServices.getGlobalTimeStamp()); - SimpleDateFormat formatter = new SimpleDateFormat("YYYY.MM.dd.HH.mm.ss.SSS"); + SimpleDateFormat formatter = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss.SSS"); dumpDir = getPath(DumpPath.getValue(options), formatter.format(date)); } dumpDir = getAbsolutePath(dumpDir); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/WallClockTimerKey.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/WallClockTimerKey.java index b1fe7b179802..eacf5b6223cf 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/WallClockTimerKey.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/WallClockTimerKey.java @@ -63,11 +63,11 @@ public TimeUnit getTimeUnit() { @Override public String toHumanReadableFormat(long value) { - return String.format("%d ms", value); + return String.format("%d ns", value); } @Override public Pair toCSVFormat(long value) { - return Pair.create(Long.toString(value), "ms"); + return Pair.create(Long.toString(value), "ns"); } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/GraalHotSpotVMConfig.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/GraalHotSpotVMConfig.java index 555baf10282e..303634396d64 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/GraalHotSpotVMConfig.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/GraalHotSpotVMConfig.java @@ -302,7 +302,7 @@ public final int arrayOopDescLengthOffset() { public final int threadCarrierThreadObjectOffset = getFieldOffset("JavaThread::_threadObj", Integer.class, "OopHandle"); public final int threadScopedValueCacheOffset = getFieldOffset("JavaThread::_scopedValueCache", Integer.class, "OopHandle"); - public final int javaThreadLockIDOffset = getFieldOffset("JavaThread::_lock_id", Integer.class, "int64_t", -1, JDK > 21); + public final int javaThreadMonitorOwnerIDOffset = getFieldOffset("JavaThread::_monitor_owner_id", Integer.class, "int64_t", -1, JDK > 21); public final int threadIsInVTMSTransitionOffset = getFieldOffset("JavaThread::_is_in_VTMS_transition", Integer.class, "bool"); public final int threadIsInTmpVTMSTransitionOffset = getFieldOffset("JavaThread::_is_in_tmp_VTMS_transition", Integer.class, "bool", -1, JDK == 21); 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 e82350d0e00f..1b66967ff3fd 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 @@ -43,7 +43,6 @@ import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.graal.compiler.serviceprovider.IsolateUtil; import jdk.graal.compiler.serviceprovider.ServiceProvider; -import jdk.graal.compiler.word.Word; import jdk.vm.ci.common.NativeImageReinitialize; import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; @@ -69,7 +68,7 @@ public PrintStream getStream() { } static { - Word.ensureInitialized(); + /* Calling this method ensures that the static initializer has been executed. */ } /** diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/JVMCIVersionCheck.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/JVMCIVersionCheck.java index 7e89ce8d16b0..c943ff49459c 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/JVMCIVersionCheck.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/JVMCIVersionCheck.java @@ -28,6 +28,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.OptionalInt; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -54,9 +55,9 @@ public final class JVMCIVersionCheck { */ private static final Map> JVMCI_MIN_VERSIONS = Map.of( "21", Map.of(DEFAULT_VENDOR_ENTRY, createLegacyVersion(23, 1, 33)), - "24", Map.of( - "Oracle Corporation", createLabsJDKVersion("24+25", 1), - DEFAULT_VENDOR_ENTRY, createLabsJDKVersion("24+25", 1))); + "25", Map.of( + "Oracle Corporation", createLabsJDKVersion("25+3", 1), + DEFAULT_VENDOR_ENTRY, createLabsJDKVersion("25+3", 1))); private static final int NA = 0; /** * Minimum Java release supported by Graal. @@ -225,6 +226,17 @@ public String printFormat(PrintFormat format) { case AS_TAG -> toString(); }; } + + public Version stripLTS() { + if ("LTS".equals(jdkVersion.optional().orElse(null))) { + String version = jdkVersion.toString(); + assert version.endsWith("-LTS"); + String stripped = version.substring(0, version.length() - 4); + return new Version(stripped, jvmciMajor, jvmciMinor, jvmciBuild, legacy, isOpenJDK); + } else { + return this; + } + } } public static final String OPEN_LABSJDK_RELEASE_URL_PATTERN = "https://github.com/graalvm/labs-openjdk-*/releases"; @@ -270,7 +282,7 @@ static void check(Map props, boolean exitOnFailure, PrintFormat */ public static void check(Map props, boolean exitOnFailure, PrintFormat format, Map> jvmciMinVersions) { JVMCIVersionCheck checker = newJVMCIVersionCheck(props); - String reason = checker.run(getMinVersion(props, jvmciMinVersions), format); + String reason = checker.run(getMinVersion(props, jvmciMinVersions), format, jvmciMinVersions); if (reason != null) { Formatter errorMessage = new Formatter().format("%s%n", reason); errorMessage.format("Set the JVMCI_VERSION_CHECK environment variable to \"ignore\" to suppress "); @@ -329,7 +341,7 @@ private static void failVersionCheck(boolean exit, String errorMessage) { */ public static String check(Map props) { JVMCIVersionCheck checker = newJVMCIVersionCheck(props); - String reason = checker.run(getMinVersion(props, JVMCI_MIN_VERSIONS), null); + String reason = checker.run(getMinVersion(props, JVMCI_MIN_VERSIONS), null, JVMCI_MIN_VERSIONS); if (reason != null) { Formatter errorMessage = new Formatter().format("%s%n", reason); checker.appendJVMInfo(errorMessage); @@ -343,7 +355,7 @@ public static String check(Map props) { * * @return an error message if the version check fails, or {@code null} if no error is detected */ - private String run(Version minVersion, PrintFormat format) { + private String run(Version minVersion, PrintFormat format, Map> jvmciMinVersions) { if (javaSpecVersion.compareTo(Integer.toString(JAVA_MIN_RELEASE)) < 0) { return "Graal requires JDK " + JAVA_MIN_RELEASE + " or later."; } else { @@ -363,13 +375,21 @@ private String run(Version minVersion, PrintFormat format) { } // A "labsjdk" or a known OpenJDK if (minVersion == null) { + OptionalInt max = jvmciMinVersions.keySet().stream().mapToInt(Integer::parseInt).max(); + if (max.isPresent()) { + int maxVersion = max.getAsInt(); + int specVersion = Integer.parseInt(javaSpecVersion); + if (specVersion < maxVersion) { + return String.format("The VM does not support JDK %s. Please update to JDK %s.", specVersion, maxVersion); + } + } // No minimum JVMCI version specified for JDK version return null; } Version v = Version.parse(vmVersion); if (v != null) { if (format != null) { - System.out.println(v.printFormat(format)); + System.out.println(v.stripLTS().printFormat(format)); } if (v.isLessThan(minVersion)) { return String.format("The VM does not support the minimum JVMCI API version required by Graal: %s < %s.", v, minVersion); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/meta/HotSpotGraphBuilderPlugins.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/meta/HotSpotGraphBuilderPlugins.java index 987b9b7dc055..7946b2260ddc 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/meta/HotSpotGraphBuilderPlugins.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/meta/HotSpotGraphBuilderPlugins.java @@ -45,7 +45,7 @@ import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.HOTSPOT_CURRENT_THREAD_OOP_HANDLE_LOCATION; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.HOTSPOT_JAVA_THREAD_CONT_ENTRY; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.HOTSPOT_JAVA_THREAD_SCOPED_VALUE_CACHE_HANDLE_LOCATION; -import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.JAVA_THREAD_LOCK_ID_LOCATION; +import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.JAVA_THREAD_MONITOR_OWNER_ID_LOCATION; import static jdk.graal.compiler.java.BytecodeParserOptions.InlineDuringParsing; import static jdk.graal.compiler.nodes.ConstantNode.forBoolean; import static jdk.graal.compiler.nodes.ProfileData.BranchProbabilityData.injected; @@ -730,11 +730,11 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec barrierSet.writeBarrierType(HOTSPOT_CURRENT_THREAD_OOP_HANDLE_LOCATION), MemoryOrderMode.PLAIN)); if (JavaVersionUtil.JAVA_SPEC > 21) { - GraalError.guarantee(config.javaThreadLockIDOffset != -1, "JavaThread::_lock_id should have been exported"); + GraalError.guarantee(config.javaThreadMonitorOwnerIDOffset != -1, "JavaThread::_lock_id should have been exported"); // Change the lock_id of the JavaThread - ValueNode tid = helper.loadField(thread, helper.getField(b.getMetaAccess().lookupJavaType(Thread.class), "tid")); - OffsetAddressNode address = b.add(new OffsetAddressNode(javaThread, helper.asWord(config.javaThreadLockIDOffset))); - b.add(new JavaWriteNode(JavaKind.Long, address, JAVA_THREAD_LOCK_ID_LOCATION, tid, BarrierType.NONE, false)); + ValueNode monitorOwnerID = helper.loadField(thread, helper.getField(b.getMetaAccess().lookupJavaType(Thread.class), "tid")); + OffsetAddressNode address = b.add(new OffsetAddressNode(javaThread, helper.asWord(config.javaThreadMonitorOwnerIDOffset))); + b.add(new JavaWriteNode(JavaKind.Long, address, JAVA_THREAD_MONITOR_OWNER_ID_LOCATION, monitorOwnerID, BarrierType.NONE, false)); } if (HotSpotReplacementsUtil.supportsVirtualThreadUpdateJFR(config)) { b.add(new VirtualThreadUpdateJFRNode(thread)); @@ -773,7 +773,7 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec } // @formatter:off - @SyncPort(from = "https://github.com/openjdk/jdk/blob/79345bbbae2564f9f523859d1227a1784293b20f/src/hotspot/share/opto/library_call.cpp#L2920-L2974", + @SyncPort(from = "https://github.com/openjdk/jdk/blob/22845a77a2175202876d0029f75fa32271e07b91/src/hotspot/share/opto/library_call.cpp#L2914-L2968", sha1 = "353e0d45b0f63ac58af86dcab5b19777950da7e2") // @formatter:on private static void inlineNativeNotifyJvmtiFunctions(GraalHotSpotVMConfig config, GraphBuilderContext b, ResolvedJavaMethod targetMethod, ForeignCallDescriptor descriptor, @@ -822,7 +822,7 @@ private static void inlineNativeNotifyJvmtiFunctions(GraalHotSpotVMConfig config } // @formatter:off - @SyncPort(from = "https://github.com/openjdk/jdk/blob/cf158bc6cdadfdfa944b8ec1d3dc7069c8f055a9/src/hotspot/share/opto/library_call.cpp#L3740-L3823", + @SyncPort(from = "https://github.com/openjdk/jdk/blob/22845a77a2175202876d0029f75fa32271e07b91/src/hotspot/share/opto/library_call.cpp#L3734-L3817", sha1 = "f05a07a18ffae50e2a2b20586184a26e9cc8c5f2") // @formatter:on private static class ContinuationPinningPlugin extends InvocationPlugin { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/meta/UnimplementedGraalIntrinsics.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/meta/UnimplementedGraalIntrinsics.java index 81e08ee53a1b..9d974a9f1184 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/meta/UnimplementedGraalIntrinsics.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/meta/UnimplementedGraalIntrinsics.java @@ -200,8 +200,6 @@ public UnimplementedGraalIntrinsics(Architecture arch) { "jdk/internal/vm/vector/VectorSupport.maskReductionCoerced(ILjava/lang/Class;Ljava/lang/Class;ILjdk/internal/vm/vector/VectorSupport$VectorMask;Ljdk/internal/vm/vector/VectorSupport$VectorMaskOp;)J", "jdk/internal/vm/vector/VectorSupport.rearrangeOp(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Class;ILjdk/internal/vm/vector/VectorSupport$Vector;Ljdk/internal/vm/vector/VectorSupport$VectorShuffle;Ljdk/internal/vm/vector/VectorSupport$VectorMask;Ljdk/internal/vm/vector/VectorSupport$VectorRearrangeOp;)Ljdk/internal/vm/vector/VectorSupport$Vector;", "jdk/internal/vm/vector/VectorSupport.reductionCoerced(ILjava/lang/Class;Ljava/lang/Class;Ljava/lang/Class;ILjdk/internal/vm/vector/VectorSupport$Vector;Ljdk/internal/vm/vector/VectorSupport$VectorMask;Ljdk/internal/vm/vector/VectorSupport$ReductionOperation;)J", - "jdk/internal/vm/vector/VectorSupport.shuffleIota(Ljava/lang/Class;Ljava/lang/Class;Ljdk/internal/vm/vector/VectorSupport$VectorSpecies;IIIILjdk/internal/vm/vector/VectorSupport$ShuffleIotaOperation;)Ljdk/internal/vm/vector/VectorSupport$VectorShuffle;", - "jdk/internal/vm/vector/VectorSupport.shuffleToVector(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Class;Ljdk/internal/vm/vector/VectorSupport$VectorShuffle;ILjdk/internal/vm/vector/VectorSupport$ShuffleToVectorOperation;)Ljdk/internal/vm/vector/VectorSupport$Vector;", jdk == 21 ? "jdk/internal/vm/vector/VectorSupport.store(Ljava/lang/Class;Ljava/lang/Class;ILjava/lang/Object;JLjdk/internal/vm/vector/VectorSupport$VectorPayload;Ljava/lang/Object;JLjdk/internal/vm/vector/VectorSupport$StoreVectorOperation;)V": "jdk/internal/vm/vector/VectorSupport.store(Ljava/lang/Class;Ljava/lang/Class;ILjava/lang/Object;JZLjdk/internal/vm/vector/VectorSupport$VectorPayload;Ljava/lang/Object;JLjdk/internal/vm/vector/VectorSupport$StoreVectorOperation;)V", jdk == 21 ? "jdk/internal/vm/vector/VectorSupport.storeMasked(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Class;ILjava/lang/Object;JLjdk/internal/vm/vector/VectorSupport$Vector;Ljdk/internal/vm/vector/VectorSupport$VectorMask;Ljava/lang/Object;JLjdk/internal/vm/vector/VectorSupport$StoreVectorMaskedOperation;)V": @@ -212,10 +210,12 @@ public UnimplementedGraalIntrinsics(Architecture arch) { // @formatter:on ); - if (jdk >= 24) { + if (jdk == 21) { add(enterprise, // @formatter:off - "jdk/internal/vm/vector/VectorSupport.wrapShuffleIndexes(Ljava/lang/Class;Ljava/lang/Class;Ljdk/internal/vm/vector/VectorSupport$VectorShuffle;ILjdk/internal/vm/vector/VectorSupport$WrapShuffleIndexesOperation;)Ljdk/internal/vm/vector/VectorSupport$VectorShuffle;"); + "jdk/internal/vm/vector/VectorSupport.shuffleIota(Ljava/lang/Class;Ljava/lang/Class;Ljdk/internal/vm/vector/VectorSupport$VectorSpecies;IIIILjdk/internal/vm/vector/VectorSupport$ShuffleIotaOperation;)Ljdk/internal/vm/vector/VectorSupport$VectorShuffle;", + "jdk/internal/vm/vector/VectorSupport.shuffleToVector(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Class;Ljdk/internal/vm/vector/VectorSupport$VectorShuffle;ILjdk/internal/vm/vector/VectorSupport$ShuffleToVectorOperation;)Ljdk/internal/vm/vector/VectorSupport$Vector;" // @formatter:on + ); } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/DigestBaseSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/DigestBaseSnippets.java index 9374fe29d951..1070fc8dbb12 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/DigestBaseSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/DigestBaseSnippets.java @@ -44,7 +44,6 @@ import jdk.graal.compiler.replacements.nodes.FallbackInvokeWithExceptionNode; import jdk.graal.compiler.word.Word; import org.graalvm.word.LocationIdentity; -import org.graalvm.word.WordFactory; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.ResolvedJavaType; @@ -73,32 +72,32 @@ static int implCompressMultiBlock0(Object receiver, byte[] buf, int ofs, int lim @Snippet.ConstantParameter ResolvedJavaType sha3type) { Object realReceiver = PiNode.piCast(receiver, receiverType, false, true, SnippetAnchorNode.anchor()); - Word bufAddr = WordFactory.unsigned(ComputeObjectAddressNode.get(buf, ReplacementsUtil.getArrayBaseOffset(INJECTED_METAACCESS, JavaKind.Byte) + ofs)); + Word bufAddr = Word.unsigned(ComputeObjectAddressNode.get(buf, ReplacementsUtil.getArrayBaseOffset(INJECTED_METAACCESS, JavaKind.Byte) + ofs)); if (useMD5Intrinsics(INJECTED_VMCONFIG) && doInstanceof(md5type, realReceiver)) { Object md5obj = PiNode.piCast(realReceiver, md5type, false, true, SnippetAnchorNode.anchor()); Object state = RawLoadNode.load(md5obj, HotSpotReplacementsUtil.getFieldOffset(md5type, "state"), JavaKind.Object, LocationIdentity.any()); - Word stateAddr = WordFactory.unsigned(ComputeObjectAddressNode.get(state, ReplacementsUtil.getArrayBaseOffset(INJECTED_METAACCESS, JavaKind.Int))); + Word stateAddr = Word.unsigned(ComputeObjectAddressNode.get(state, ReplacementsUtil.getArrayBaseOffset(INJECTED_METAACCESS, JavaKind.Int))); return HotSpotBackend.md5ImplCompressMBStub(bufAddr, stateAddr, ofs, limit); } else if (useSHA1Intrinsics(INJECTED_VMCONFIG) && doInstanceof(sha1type, realReceiver)) { Object sha1obj = PiNode.piCast(realReceiver, sha1type, false, true, SnippetAnchorNode.anchor()); Object state = RawLoadNode.load(sha1obj, HotSpotReplacementsUtil.getFieldOffset(sha1type, "state"), JavaKind.Object, LocationIdentity.any()); - Word stateAddr = WordFactory.unsigned(ComputeObjectAddressNode.get(state, ReplacementsUtil.getArrayBaseOffset(INJECTED_METAACCESS, JavaKind.Int))); + Word stateAddr = Word.unsigned(ComputeObjectAddressNode.get(state, ReplacementsUtil.getArrayBaseOffset(INJECTED_METAACCESS, JavaKind.Int))); return HotSpotBackend.shaImplCompressMBStub(bufAddr, stateAddr, ofs, limit); } else if (useSHA256Intrinsics(INJECTED_VMCONFIG) && doInstanceof(sha256type, realReceiver)) { Object sha256obj = PiNode.piCast(realReceiver, sha256type, false, true, SnippetAnchorNode.anchor()); Object state = RawLoadNode.load(sha256obj, HotSpotReplacementsUtil.getFieldOffset(sha256type, "state"), JavaKind.Object, LocationIdentity.any()); - Word stateAddr = WordFactory.unsigned(ComputeObjectAddressNode.get(state, ReplacementsUtil.getArrayBaseOffset(INJECTED_METAACCESS, JavaKind.Int))); + Word stateAddr = Word.unsigned(ComputeObjectAddressNode.get(state, ReplacementsUtil.getArrayBaseOffset(INJECTED_METAACCESS, JavaKind.Int))); return HotSpotBackend.sha2ImplCompressMBStub(bufAddr, stateAddr, ofs, limit); } else if (useSHA512Intrinsics(INJECTED_VMCONFIG) && doInstanceof(sha512type, realReceiver)) { Object sha512obj = PiNode.piCast(realReceiver, sha512type, false, true, SnippetAnchorNode.anchor()); Object state = RawLoadNode.load(sha512obj, HotSpotReplacementsUtil.getFieldOffset(sha512type, "state"), JavaKind.Object, LocationIdentity.any()); - Word stateAddr = WordFactory.unsigned(ComputeObjectAddressNode.get(state, ReplacementsUtil.getArrayBaseOffset(INJECTED_METAACCESS, JavaKind.Long))); + Word stateAddr = Word.unsigned(ComputeObjectAddressNode.get(state, ReplacementsUtil.getArrayBaseOffset(INJECTED_METAACCESS, JavaKind.Long))); return HotSpotBackend.sha5ImplCompressMBStub(bufAddr, stateAddr, ofs, limit); } else if (useSHA3Intrinsics(INJECTED_VMCONFIG) && doInstanceof(sha3type, realReceiver)) { Object sha3obj = PiNode.piCast(realReceiver, sha3type, false, true, SnippetAnchorNode.anchor()); int blockSize = RawLoadNode.loadInt(sha3obj, HotSpotReplacementsUtil.getFieldOffset(sha3type, "blockSize"), JavaKind.Int, LocationIdentity.any()); Object state = RawLoadNode.load(sha3obj, HotSpotReplacementsUtil.getFieldOffset(sha3type, "state"), JavaKind.Object, LocationIdentity.any()); - Word stateAddr = WordFactory.unsigned(ComputeObjectAddressNode.get(state, ReplacementsUtil.getArrayBaseOffset(INJECTED_METAACCESS, JavaKind.Long))); + Word stateAddr = Word.unsigned(ComputeObjectAddressNode.get(state, ReplacementsUtil.getArrayBaseOffset(INJECTED_METAACCESS, JavaKind.Long))); return HotSpotBackend.sha3ImplCompressMBStub(bufAddr, stateAddr, blockSize, ofs, limit); } else { return FallbackInvokeWithExceptionNode.fallbackFunctionCallInt(); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotAllocationSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotAllocationSnippets.java index 6b7dae174129..a900f600f056 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotAllocationSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotAllocationSnippets.java @@ -70,7 +70,6 @@ import static jdk.vm.ci.meta.DeoptimizationReason.RuntimeConstraint; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import jdk.graal.compiler.api.replacements.Fold; import jdk.graal.compiler.api.replacements.Fold.InjectedParameter; @@ -148,7 +147,7 @@ protected Object allocateInstance(KlassPointer hub, @ConstantParameter boolean emitMemoryBarrier, @ConstantParameter HotSpotAllocationProfilingData profilingData, @ConstantParameter boolean withException) { - Object result = allocateInstanceImpl(hub.asWord(), WordFactory.unsigned(size), forceSlowPath, fillContents, emitMemoryBarrier, true, profilingData, withException); + Object result = allocateInstanceImpl(hub.asWord(), Word.unsigned(size), forceSlowPath, fillContents, emitMemoryBarrier, true, profilingData, withException); return piCastToSnippetReplaceeStamp(result); } @@ -193,7 +192,7 @@ public Object allocateInstanceDynamic(@NonNullParameter Class type, * FIXME(je,ds): we should actually pass typeContext instead of "" but late binding * of parameters is not yet supported by the GraphBuilderPlugin system. */ - UnsignedWord size = WordFactory.unsigned(layoutHelper); + UnsignedWord size = Word.unsigned(layoutHelper); return allocateInstanceImpl(nonNullHub.asWord(), size, false, fillContents, emitMemoryBarrier, false, profilingData, withException); } } @@ -286,9 +285,9 @@ protected Object newmultiarray(KlassPointer hub, @ConstantParameter int rank, @C private void verifyHeap() { Word tlabInfo = getTLABInfo(); Word topValue = readTlabTop(tlabInfo); - if (!topValue.equal(WordFactory.zero())) { + if (!topValue.equal(Word.zero())) { Word topValueContents = topValue.readWord(0, MARK_WORD_LOCATION); - if (topValueContents.equal(WordFactory.zero())) { + if (topValueContents.equal(Word.zero())) { AssertionSnippets.vmMessageC(VM_MESSAGE_C, true, cstring("overzeroing of TLAB detected"), 0L, 0L, 0L); } } @@ -430,7 +429,7 @@ protected final int getMinimalBulkZeroingSize() { @Override public final void initializeObjectHeader(Word memory, Word hub, boolean isArray) { KlassPointer klassPtr = KlassPointer.fromWord(hub); - Word markWord = WordFactory.signed(HotSpotReplacementsUtil.defaultPrototypeMarkWord(INJECTED_VMCONFIG)); + Word markWord = Word.signed(HotSpotReplacementsUtil.defaultPrototypeMarkWord(INJECTED_VMCONFIG)); HotSpotReplacementsUtil.initializeObjectHeader(memory, markWord, klassPtr); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotG1WriteBarrierSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotG1WriteBarrierSnippets.java index 9986f7bc1f19..1e2ff3c4d3ea 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotG1WriteBarrierSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotG1WriteBarrierSnippets.java @@ -31,7 +31,6 @@ import static jdk.graal.compiler.hotspot.meta.HotSpotForeignCallsProviderImpl.NO_LOCATIONS; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import jdk.graal.compiler.core.common.CompressEncoding; import jdk.graal.compiler.core.common.spi.ForeignCallDescriptor; @@ -125,7 +124,7 @@ protected byte youngCardValue() { @Override protected Word cardTableAddress(Pointer oop) { - Word cardTable = WordFactory.unsigned(HotSpotReplacementsUtil.cardTableStart(INJECTED_VMCONFIG)); + Word cardTable = Word.unsigned(HotSpotReplacementsUtil.cardTableStart(INJECTED_VMCONFIG)); int cardTableShift = HotSpotReplacementsUtil.cardTableShift(INJECTED_VMCONFIG); return cardTable.add(oop.unsignedShiftRight(cardTableShift)); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotHashCodeSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotHashCodeSnippets.java index f39edf6288e1..53244374f8fe 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotHashCodeSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotHashCodeSnippets.java @@ -40,14 +40,12 @@ import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.FAST_PATH_PROBABILITY; import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.probability; -import org.graalvm.word.WordFactory; - import jdk.graal.compiler.lir.SyncPort; import jdk.graal.compiler.replacements.IdentityHashCodeSnippets; import jdk.graal.compiler.word.Word; // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/cf158bc6cdadfdfa944b8ec1d3dc7069c8f055a9/src/hotspot/share/opto/library_call.cpp#L4659-L4793", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/22845a77a2175202876d0029f75fa32271e07b91/src/hotspot/share/opto/library_call.cpp#L4653-L4787", sha1 = "c212d1dbff26d02d4d749e085263d4104895f1ba") // @formatter:on public class HotSpotHashCodeSnippets extends IdentityHashCodeSnippets { @@ -76,10 +74,10 @@ protected int computeIdentityHashCode(final Object x) { // unlocked, i.e., lock bits equals to 0b01. // // See src/hotspot/share/oops/markWord.hpp for more details. - final Word lockBits = mark.and(WordFactory.unsigned(markWordLockMaskInPlace(INJECTED_VMCONFIG))); + final Word lockBits = mark.and(Word.unsigned(markWordLockMaskInPlace(INJECTED_VMCONFIG))); if (useObjectMonitorTable(INJECTED_VMCONFIG) || probability(FAST_PATH_PROBABILITY, - useLightweightLocking(INJECTED_VMCONFIG) ? lockBits.notEqual(WordFactory.unsigned(monitorValue(INJECTED_VMCONFIG))) - : lockBits.equal(WordFactory.unsigned(unlockedValue(INJECTED_VMCONFIG))))) { + useLightweightLocking(INJECTED_VMCONFIG) ? lockBits.notEqual(Word.unsigned(monitorValue(INJECTED_VMCONFIG))) + : lockBits.equal(Word.unsigned(unlockedValue(INJECTED_VMCONFIG))))) { // `& markWord::hash_mask' is essential with -XX:+UseCompactObjectHeaders, because bit // 42 might be set. int hash = (int) mark.unsignedShiftRight(markWordHashCodeShift(INJECTED_VMCONFIG)).and((int) markWordHashMark(INJECTED_VMCONFIG)).rawValue(); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotReplacementsUtil.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotReplacementsUtil.java index d0a434c52a16..6da8224bc695 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotReplacementsUtil.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotReplacementsUtil.java @@ -32,7 +32,6 @@ import java.lang.ref.Reference; import org.graalvm.word.LocationIdentity; -import org.graalvm.word.WordFactory; import jdk.graal.compiler.api.replacements.Fold; import jdk.graal.compiler.api.replacements.Fold.InjectedParameter; @@ -327,7 +326,7 @@ public static Object getPendingException(Word thread) { public static final LocationIdentity JAVA_THREAD_CARRIER_THREAD_OBJECT_LOCATION = NamedLocationIdentity.mutable("JavaThread::_threadObj"); - public static final LocationIdentity JAVA_THREAD_LOCK_ID_LOCATION = NamedLocationIdentity.mutable("JavaThread::_lock_id"); + public static final LocationIdentity JAVA_THREAD_MONITOR_OWNER_ID_LOCATION = NamedLocationIdentity.mutable("JavaThread::_monitor_owner_id"); public static final LocationIdentity JAVA_THREAD_OSTHREAD_LOCATION = NamedLocationIdentity.mutable("JavaThread::_osthread"); @@ -525,7 +524,7 @@ static CompressEncoding klassEncoding(@InjectedParameter GraalHotSpotVMConfig co public static void initializeObjectHeader(Word memory, Word markWord, KlassPointer hub) { if (useCompactObjectHeaders(INJECTED_VMCONFIG)) { - Word compressedHub = WordFactory.unsigned(compress(Compress, hub, klassEncoding(INJECTED_VMCONFIG)).asInt()); + Word compressedHub = Word.unsigned(compress(Compress, hub, klassEncoding(INJECTED_VMCONFIG)).asInt()); Word hubInPlace = compressedHub.shiftLeft(markWordKlassShift(INJECTED_VMCONFIG)); Word newMarkWord = markWord.or(hubInPlace); memory.writeWord(markOffset(INJECTED_VMCONFIG), newMarkWord, MARK_WORD_LOCATION); @@ -756,8 +755,8 @@ static int heldMonitorCountOffset(@InjectedParameter GraalHotSpotVMConfig config } @Fold - static int javaThreadLockIDOffset(@InjectedParameter GraalHotSpotVMConfig config) { - return config.javaThreadLockIDOffset; + static int javaThreadMonitorOwnerIDOffset(@InjectedParameter GraalHotSpotVMConfig config) { + return config.javaThreadMonitorOwnerIDOffset; } @Fold diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotSerialWriteBarrierSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotSerialWriteBarrierSnippets.java index ceddf639f337..d192cd0ff5d7 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotSerialWriteBarrierSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/HotSpotSerialWriteBarrierSnippets.java @@ -26,8 +26,6 @@ import static jdk.graal.compiler.hotspot.GraalHotSpotVMConfig.INJECTED_VMCONFIG; -import org.graalvm.word.WordFactory; - import jdk.graal.compiler.hotspot.meta.HotSpotProviders; import jdk.graal.compiler.nodes.gc.SerialArrayRangeWriteBarrierNode; import jdk.graal.compiler.nodes.gc.SerialWriteBarrierNode; @@ -47,7 +45,7 @@ public HotSpotSerialWriteBarrierSnippets() { @Override public Word cardTableAddress() { - return WordFactory.unsigned(HotSpotReplacementsUtil.cardTableStart(INJECTED_VMCONFIG)); + return Word.unsigned(HotSpotReplacementsUtil.cardTableStart(INJECTED_VMCONFIG)); } @Override diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/LoadExceptionObjectSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/LoadExceptionObjectSnippets.java index efa1d41c46a2..152cc0152039 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/LoadExceptionObjectSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/LoadExceptionObjectSnippets.java @@ -35,8 +35,6 @@ import static jdk.graal.compiler.nodes.PiNode.piCastToSnippetReplaceeStamp; import static jdk.graal.compiler.replacements.SnippetTemplate.DEFAULT_REPLACER; -import org.graalvm.word.WordFactory; - import jdk.graal.compiler.api.replacements.Snippet; import jdk.graal.compiler.api.replacements.Snippet.ConstantParameter; import jdk.graal.compiler.core.common.type.Stamp; @@ -74,7 +72,7 @@ public static Object loadException(@ConstantParameter Register threadRegister) { Word thread = registerAsWord(threadRegister); Object exception = readExceptionOop(thread); writeExceptionOop(thread, null); - writeExceptionPc(thread, WordFactory.zero()); + writeExceptionPc(thread, Word.zero()); return piCastToSnippetReplaceeStamp(exception); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/MonitorSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/MonitorSnippets.java index 2cbc75590343..3405485e374d 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/MonitorSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/MonitorSnippets.java @@ -34,9 +34,9 @@ import static jdk.graal.compiler.hotspot.nodes.VMErrorNode.vmError; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.BASICLOCK_METADATA_LOCATION; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.JAVA_THREAD_HOLD_MONITOR_COUNT_LOCATION; -import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.JAVA_THREAD_LOCK_ID_LOCATION; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.JAVA_THREAD_LOCK_STACK_LOCATION; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.JAVA_THREAD_LOCK_STACK_TOP_LOCATION; +import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.JAVA_THREAD_MONITOR_OWNER_ID_LOCATION; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.JAVA_THREAD_OM_CACHE_LOCATION; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.JAVA_THREAD_UNLOCKED_INFLATED_MONITOR_LOCATION; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.KLASS_ACCESS_FLAGS_LOCATION; @@ -51,9 +51,9 @@ import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.diagnoseSyncOnValueBasedClasses; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.heldMonitorCountOffset; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.isCAssertEnabled; -import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.javaThreadLockIDOffset; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.javaThreadLockStackEndOffset; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.javaThreadLockStackTopOffset; +import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.javaThreadMonitorOwnerIDOffset; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.javaThreadOomCacheOffset; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.javaThreadUnlockedInflatedMonitorOffset; import static jdk.graal.compiler.hotspot.replacements.HotSpotReplacementsUtil.jvmAccIsValueBasedClass; @@ -94,9 +94,9 @@ import static jdk.graal.compiler.replacements.SnippetTemplate.DEFAULT_REPLACER; import static jdk.graal.compiler.replacements.nodes.CStringConstant.cstring; import static org.graalvm.word.LocationIdentity.any; -import static org.graalvm.word.WordFactory.nullPointer; -import static org.graalvm.word.WordFactory.unsigned; -import static org.graalvm.word.WordFactory.zero; +import static jdk.graal.compiler.word.Word.nullPointer; +import static jdk.graal.compiler.word.Word.unsigned; +import static jdk.graal.compiler.word.Word.zero; import java.util.List; import java.util.Objects; @@ -104,7 +104,6 @@ import org.graalvm.word.LocationIdentity; import org.graalvm.word.Pointer; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import jdk.graal.compiler.api.replacements.Fold; import jdk.graal.compiler.api.replacements.Snippet; @@ -200,8 +199,8 @@ * appropriately to comply with the layouts above. */ // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/78b80150e009745b8f28d36c3836f18ad0ca921f/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp#L175-L499", - sha1 = "dfd1e588f5dc19fdd444094b37f162c5ee89803b") +@SyncPort(from = "https://github.com/openjdk/jdk/blob/c113f82f78c7d9be1ac297aebfeb6051f0f904fb/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp#L175-L499", + sha1 = "7bf54a5cd262e46c33067da8674032974e80af08") // @formatter:on public class MonitorSnippets implements Snippets { @@ -323,7 +322,7 @@ private static boolean tryEnterInflated(Object object, Word lock, Word mark, Wor int ownerOffset = objectMonitorOwnerOffset(INJECTED_VMCONFIG); Word owner = monitor.readWord(ownerOffset, OBJECT_MONITOR_OWNER_LOCATION); - Word newOwner = isJDK21() ? thread : thread.readWord(javaThreadLockIDOffset(INJECTED_VMCONFIG), JAVA_THREAD_LOCK_ID_LOCATION); + Word newOwner = isJDK21() ? thread : thread.readWord(javaThreadMonitorOwnerIDOffset(INJECTED_VMCONFIG), JAVA_THREAD_MONITOR_OWNER_ID_LOCATION); // The following owner null check is essential. In the case where the null check fails, it // avoids the subsequent bound-to-fail CAS operation, which would have caused the @@ -358,7 +357,7 @@ private static boolean tryStackLocking(Object object, Word lock, Word mark, Word if (probability(SLOW_PATH_PROBABILITY, mark.and(monitorValue(INJECTED_VMCONFIG)).notEqual(0))) { // Inflated case // Set the lock slot's displaced mark to unused. Any non-0 value suffices. - lock.writeWord(lockMetadataOffset(INJECTED_VMCONFIG), WordFactory.unsigned(unusedMark(INJECTED_VMCONFIG)), BASICLOCK_METADATA_LOCATION); + lock.writeWord(lockMetadataOffset(INJECTED_VMCONFIG), Word.unsigned(unusedMark(INJECTED_VMCONFIG)), BASICLOCK_METADATA_LOCATION); return tryEnterInflated(object, lock, mark, thread, trace, counters); } @@ -414,16 +413,16 @@ private static boolean tryStackLocking(Object object, Word lock, Word mark, Word } // @formatter:off - @SyncPort(from = "https://github.com/openjdk/jdk/blob/78b80150e009745b8f28d36c3836f18ad0ca921f/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp#L501-L667", - sha1 = "1502c5ef5c336a120cc7414741bb36308e2eb6c5") + @SyncPort(from = "https://github.com/openjdk/jdk/blob/c113f82f78c7d9be1ac297aebfeb6051f0f904fb/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp#L501-L667", + sha1 = "1c396a67926e92a2cbc64c27fe667142e9ec157d") // @formatter:on @SuppressWarnings("unused") private static boolean tryLightweightLocking(Object object, Word lock, Word mark, Word thread, boolean trace, Counters counters, Register stackPointerRegister) { - writeMonitorCache(lock, WordFactory.nullPointer()); + writeMonitorCache(lock, Word.nullPointer()); // Prefetch top // We assume `lockStackTop' is always positive and use `WordFactory.unsigned' to skip a sign // extension. - Word lockStackTop = WordFactory.unsigned(thread.readInt(javaThreadLockStackTopOffset(INJECTED_VMCONFIG), JAVA_THREAD_LOCK_STACK_TOP_LOCATION)); + Word lockStackTop = Word.unsigned(thread.readInt(javaThreadLockStackTopOffset(INJECTED_VMCONFIG), JAVA_THREAD_LOCK_STACK_TOP_LOCATION)); if (probability(SLOW_PATH_PROBABILITY, mark.and(monitorValue(INJECTED_VMCONFIG)).notEqual(0))) { // Inflated case @@ -444,7 +443,7 @@ private static boolean tryLightweightLocking(Object object, Word lock, Word mark if (probability(FAST_PATH_PROBABILITY, tryLightweightLockingHelper(object, objectPointer, mark, thread, trace, counters, lockStackTop))) { if (useObjectMonitorTable(INJECTED_VMCONFIG)) { // Need to reload top, clobbered by CAS. - lockStackTop = WordFactory.unsigned(thread.readInt(javaThreadLockStackTopOffset(INJECTED_VMCONFIG), JAVA_THREAD_LOCK_STACK_TOP_LOCATION)); + lockStackTop = Word.unsigned(thread.readInt(javaThreadLockStackTopOffset(INJECTED_VMCONFIG), JAVA_THREAD_LOCK_STACK_TOP_LOCATION)); } // Push object to lock-stack. // Here we don't re-read LockStack::_top as it is thread-local data. @@ -549,7 +548,7 @@ private static boolean tryStackUnlocking(Object object, Word thread, Word lock, // @formatter:on private static boolean tryLightweightUnlocking(Object object, Word thread, Word lock, boolean trace, Counters counters) { // Load top - Word lockStackTop = WordFactory.unsigned(thread.readInt(javaThreadLockStackTopOffset(INJECTED_VMCONFIG), JAVA_THREAD_LOCK_STACK_TOP_LOCATION)); + Word lockStackTop = Word.unsigned(thread.readInt(javaThreadLockStackTopOffset(INJECTED_VMCONFIG), JAVA_THREAD_LOCK_STACK_TOP_LOCATION)); Word newLockStackTop = lockStackTop.add(-wordSize()); // Prefetch mark @@ -572,7 +571,7 @@ private static boolean tryLightweightUnlocking(Object object, Word thread, Word // Pop lock-stack. if (isCAssertEnabled(INJECTED_VMCONFIG)) { - thread.writeWord(newLockStackTop, WordFactory.zero(), JAVA_THREAD_LOCK_STACK_LOCATION); + thread.writeWord(newLockStackTop, Word.zero(), JAVA_THREAD_LOCK_STACK_LOCATION); } thread.writeInt(javaThreadLockStackTopOffset(INJECTED_VMCONFIG), (int) newLockStackTop.rawValue(), JAVA_THREAD_LOCK_STACK_TOP_LOCATION); @@ -586,7 +585,7 @@ private static boolean tryLightweightUnlocking(Object object, Word thread, Word // We elide the monitor check, let the CAS fail instead. // Try to unlock. Transition lock bits 0b00 => 0b01 - Word markLocked = mark.and(WordFactory.unsigned(~markWordLockMaskInPlace(INJECTED_VMCONFIG))); + Word markLocked = mark.and(Word.unsigned(~markWordLockMaskInPlace(INJECTED_VMCONFIG))); Word markUnlocked = mark.or(unlockedValue(INJECTED_VMCONFIG)); if (probability(FAST_PATH_PROBABILITY, objectPointer.logicCompareAndSwapWord(markOffset(INJECTED_VMCONFIG), markLocked, markUnlocked, MARK_WORD_LOCATION))) { traceObject(trace, "-lock{lightweight:cas}", object, false); @@ -846,7 +845,7 @@ public Templates(OptionValues options, SnippetCounter.Group.Factory factory, Hot JAVA_THREAD_LOCK_STACK_TOP_LOCATION, JAVA_THREAD_OM_CACHE_LOCATION, JAVA_THREAD_HOLD_MONITOR_COUNT_LOCATION, - JAVA_THREAD_LOCK_ID_LOCATION}; + JAVA_THREAD_MONITOR_OWNER_ID_LOCATION}; exitLocations = new LocationIdentity[]{ JAVA_THREAD_LOCK_STACK_LOCATION, JAVA_THREAD_LOCK_STACK_TOP_LOCATION, @@ -862,7 +861,7 @@ public Templates(OptionValues options, SnippetCounter.Group.Factory factory, Hot } else { enterLocations = new LocationIdentity[]{ JAVA_THREAD_HOLD_MONITOR_COUNT_LOCATION, - JAVA_THREAD_LOCK_ID_LOCATION}; + JAVA_THREAD_MONITOR_OWNER_ID_LOCATION}; exitLocations = new LocationIdentity[]{ BASICLOCK_METADATA_LOCATION, OBJECT_MONITOR_OWNER_LOCATION, diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/UnsafeSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/UnsafeSnippets.java index eba48cf73708..618e4c903f37 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/UnsafeSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/UnsafeSnippets.java @@ -28,7 +28,6 @@ import static jdk.graal.compiler.replacements.SnippetTemplate.DEFAULT_REPLACER; import org.graalvm.word.LocationIdentity; -import org.graalvm.word.WordFactory; import jdk.graal.compiler.api.replacements.Snippet; import jdk.graal.compiler.hotspot.HotSpotBackend; @@ -50,9 +49,9 @@ public class UnsafeSnippets implements Snippets { @SuppressWarnings("unused") @Snippet static void copyMemory(Object receiver, Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes) { - Word srcAddr = WordFactory.unsigned(ComputeObjectAddressNode.get(srcBase, srcOffset)); - Word dstAddr = WordFactory.unsigned(ComputeObjectAddressNode.get(destBase, destOffset)); - Word size = WordFactory.signed(bytes); + Word srcAddr = Word.unsigned(ComputeObjectAddressNode.get(srcBase, srcOffset)); + Word dstAddr = Word.unsigned(ComputeObjectAddressNode.get(destBase, destOffset)); + Word size = Word.signed(bytes); Word javaThread = CurrentJavaThreadNode.get(); int offset = HotSpotReplacementsUtil.doingUnsafeAccessOffset(INJECTED_VMCONFIG); LocationIdentity any = LocationIdentity.any(); @@ -67,8 +66,8 @@ static void copyMemory(Object receiver, Object srcBase, long srcOffset, Object d @SuppressWarnings("unused") @Snippet static void setMemory(Object receiver, Object objBase, long objOffset, long bytes, byte value) { - Word objAddr = WordFactory.unsigned(ComputeObjectAddressNode.get(objBase, objOffset)); - Word size = WordFactory.signed(bytes); + Word objAddr = Word.unsigned(ComputeObjectAddressNode.get(objBase, objOffset)); + Word size = Word.signed(bytes); Word javaThread = CurrentJavaThreadNode.get(); int offset = HotSpotReplacementsUtil.doingUnsafeAccessOffset(INJECTED_VMCONFIG); LocationIdentity any = LocationIdentity.any(); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/VirtualThreadUpdateJFRSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/VirtualThreadUpdateJFRSnippets.java index 3ba4c89573f8..41b5f1b9fd8f 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/VirtualThreadUpdateJFRSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/VirtualThreadUpdateJFRSnippets.java @@ -64,7 +64,7 @@ * Snippet for updating JFR thread local data on {@code Thread#setCurrentThread} events. */ // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/9d60300feea12d353fcd6c806b196ace2df02d05/src/hotspot/share/opto/library_call.cpp#L3526-L3654", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/22845a77a2175202876d0029f75fa32271e07b91/src/hotspot/share/opto/library_call.cpp#L3520-L3648", sha1 = "59f07096cdbe1aac79b1248db345e9616b54f4a4") // @formatter:on public class VirtualThreadUpdateJFRSnippets implements Snippets { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/arraycopy/HotSpotArraycopySnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/arraycopy/HotSpotArraycopySnippets.java index 7a165b96a363..12161ee4b3a3 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/arraycopy/HotSpotArraycopySnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replacements/arraycopy/HotSpotArraycopySnippets.java @@ -39,7 +39,6 @@ import jdk.graal.compiler.replacements.arraycopy.ArrayCopySnippets; import jdk.graal.compiler.word.Word; import org.graalvm.word.LocationIdentity; -import org.graalvm.word.WordFactory; import jdk.vm.ci.meta.JavaKind; @@ -53,7 +52,7 @@ public boolean hubsEqual(Object nonNullSrc, Object nonNullDest) { } Word getSuperCheckOffset(KlassPointer destElemKlass) { - return WordFactory.signed(destElemKlass.readInt(HotSpotReplacementsUtil.superCheckOffsetOffset(INJECTED_VMCONFIG), HotSpotReplacementsUtil.KLASS_SUPER_CHECK_OFFSET_LOCATION)); + return Word.signed(destElemKlass.readInt(HotSpotReplacementsUtil.superCheckOffsetOffset(INJECTED_VMCONFIG), HotSpotReplacementsUtil.KLASS_SUPER_CHECK_OFFSET_LOCATION)); } @Override diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/CreateExceptionStub.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/CreateExceptionStub.java index e139d39df03a..3173f5825d64 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/CreateExceptionStub.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/CreateExceptionStub.java @@ -29,8 +29,6 @@ import static jdk.vm.ci.hotspot.HotSpotCallingConventionType.NativeCall; import static org.graalvm.word.LocationIdentity.any; -import org.graalvm.word.WordFactory; - import jdk.graal.compiler.api.replacements.Fold; import jdk.graal.compiler.core.common.spi.ForeignCallDescriptor; import jdk.graal.compiler.graph.Node.ConstantNodeParameter; @@ -86,7 +84,7 @@ private static Word classAsCString(Class cls) { } protected static Object createException(Register threadRegister, Class exception) { - Word message = WordFactory.zero(); + Word message = Word.zero(); return createException(threadRegister, exception, message); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/ExceptionHandlerStub.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/ExceptionHandlerStub.java index a0b208671f86..a063887d86ec 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/ExceptionHandlerStub.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/ExceptionHandlerStub.java @@ -33,8 +33,6 @@ import static jdk.graal.compiler.hotspot.stubs.StubUtil.printf; import static org.graalvm.word.LocationIdentity.any; -import org.graalvm.word.WordFactory; - import jdk.graal.compiler.api.replacements.Fold; import jdk.graal.compiler.api.replacements.Fold.InjectedParameter; import jdk.graal.compiler.api.replacements.Snippet; @@ -120,7 +118,7 @@ static void checkNoExceptionInThread(Word thread, boolean enabled) { // This thread-local is only cleared in DEBUG builds of the VM // (see OptoRuntime::generate_exception_blob) Word currentExceptionPc = HotSpotReplacementsUtil.readExceptionPc(thread); - if (currentExceptionPc.notEqual(WordFactory.zero())) { + if (currentExceptionPc.notEqual(Word.zero())) { fatal("exception PC in thread must be zero, not %p", currentExceptionPc.rawValue()); } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/ForeignCallSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/ForeignCallSnippets.java index 5593cf49e34f..8787bedc509a 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/ForeignCallSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/ForeignCallSnippets.java @@ -29,7 +29,6 @@ import org.graalvm.word.LocationIdentity; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import jdk.graal.compiler.api.replacements.Fold; import jdk.graal.compiler.api.replacements.Fold.InjectedParameter; @@ -85,15 +84,15 @@ public static void handlePendingException(Word thread, boolean shouldClearExcept @Snippet(allowMissingProbabilities = true) public static Object verifyObject(Object object) { if (HotSpotReplacementsUtil.verifyOops(GraalHotSpotVMConfig.INJECTED_VMCONFIG)) { - Word verifyOopCounter = WordFactory.unsigned(HotSpotReplacementsUtil.verifyOopCounterAddress(GraalHotSpotVMConfig.INJECTED_VMCONFIG)); + Word verifyOopCounter = Word.unsigned(HotSpotReplacementsUtil.verifyOopCounterAddress(GraalHotSpotVMConfig.INJECTED_VMCONFIG)); verifyOopCounter.writeInt(0, verifyOopCounter.readInt(0) + 1); Pointer oop = Word.objectToTrackedPointer(object); if (object != null) { GuardingNode anchorNode = SnippetAnchorNode.anchor(); // make sure object is 'reasonable' - if (!oop.and(WordFactory.unsigned(HotSpotReplacementsUtil.verifyOopMask(GraalHotSpotVMConfig.INJECTED_VMCONFIG))).equal( - WordFactory.unsigned(HotSpotReplacementsUtil.verifyOopBits(GraalHotSpotVMConfig.INJECTED_VMCONFIG)))) { + if (!oop.and(Word.unsigned(HotSpotReplacementsUtil.verifyOopMask(GraalHotSpotVMConfig.INJECTED_VMCONFIG))).equal( + Word.unsigned(HotSpotReplacementsUtil.verifyOopBits(GraalHotSpotVMConfig.INJECTED_VMCONFIG)))) { fatal("oop not in heap: %p", oop.rawValue()); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/LookUpSecondarySupersTableStub.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/LookUpSecondarySupersTableStub.java index 1c74945412d0..2dea7c6a620c 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/LookUpSecondarySupersTableStub.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/LookUpSecondarySupersTableStub.java @@ -32,8 +32,6 @@ import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.NOT_LIKELY_PROBABILITY; import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.probability; -import org.graalvm.word.WordFactory; - import jdk.graal.compiler.api.replacements.Snippet; import jdk.graal.compiler.core.common.spi.ForeignCallDescriptor; import jdk.graal.compiler.graph.Node.ConstantNodeParameter; @@ -105,7 +103,7 @@ private static boolean lookupSecondarySupersTableSlowPath(KlassPointer t, Word s } public static KlassPointer loadSecondarySupersElement(Word metaspaceArray, long index) { - return KlassPointer.fromWord(metaspaceArray.readWord(WordFactory.signed(HotSpotReplacementsUtil.metaspaceArrayBaseOffset(INJECTED_VMCONFIG) + index * HotSpotReplacementsUtil.wordSize()), + return KlassPointer.fromWord(metaspaceArray.readWord(Word.signed(HotSpotReplacementsUtil.metaspaceArrayBaseOffset(INJECTED_VMCONFIG) + index * HotSpotReplacementsUtil.wordSize()), HotSpotReplacementsUtil.SECONDARY_SUPERS_ELEMENT_LOCATION)); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/StubUtil.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/StubUtil.java index 157e57725e45..c2d4a0663b1e 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/StubUtil.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/stubs/StubUtil.java @@ -45,7 +45,6 @@ import jdk.graal.compiler.hotspot.replacements.Log; import jdk.graal.compiler.word.Word; import org.graalvm.word.LocationIdentity; -import org.graalvm.word.WordFactory; //JaCoCo Exclude @@ -159,7 +158,7 @@ public static void printf(String format, long v1, long v2, long v3) { * Analyzes a given value and prints information about it to the log stream. */ public static void decipher(long value) { - vmMessageC(VM_MESSAGE_C, false, WordFactory.zero(), value, 0L, 0L); + vmMessageC(VM_MESSAGE_C, false, Word.zero(), value, 0L, 0L); } /** diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/BytecodeParser.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/BytecodeParser.java index 683ec84de240..d0450fc7e55f 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/BytecodeParser.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/BytecodeParser.java @@ -3455,7 +3455,12 @@ private FixedNode createTarget(BciBlock block, FrameStateBuilder state, boolean * loop begin node created before. */ LoopBeginNode loopBegin = (LoopBeginNode) getFirstInstruction(block); - LoopEndNode loopEnd = graph.add(new LoopEndNode(loopBegin)); + LoopEndNode loopEnd; + try (DebugCloseable context2 = openNodeContext()) { + // This is the end up of the current control flow so use a position at source + // location instead of the destination. + loopEnd = graph.add(new LoopEndNode(loopBegin)); + } Target target = checkUnstructuredLocking(checkLoopExit(new Target(loopEnd, state.copy()), block), block, getEntryState(block)); FixedNode result = target.entry; /* @@ -3861,6 +3866,7 @@ private LoopBeginNode appendLoopBegin(FixedWithNextNode fixedWithNext, int start if (disableLoopSafepoint()) { loopBegin.disableSafepoint(SafepointState.MUST_NEVER_SAFEPOINT); loopBegin.disableGuestSafepoint(SafepointState.MUST_NEVER_SAFEPOINT); + loopBegin.disableLoopExitSafepoint(SafepointState.MUST_NEVER_SAFEPOINT); } fixedWithNext.setNext(preLoopEnd); // Add the single non-loop predecessor of the loop header. diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64ControlFlow.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64ControlFlow.java index 01388e8102ee..c4a68ac648a4 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64ControlFlow.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64ControlFlow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 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 @@ -457,11 +457,15 @@ public static void emitJumpTable(CompilationResultBuilder crb, AArch64MacroAssem // jump to target masm.jmp(scratch); - masm.bind(jumpTable); - // emit jump table entries - targets.forEach(label -> masm.emitJumpTableOffset(jumpTable, label)); - JumpTable jt = new JumpTable(jumpTable.position(), lowKey, highKey, EntryFormat.OFFSET_ONLY); - crb.compilationResult.addAnnotation(jt); + crb.getLIR().addSlowPath(null, () -> { + // Insert halt so that static analyzers do not continue decoding past this point + masm.halt(); + masm.bind(jumpTable); + // emit jump table entries + targets.forEach(label -> masm.emitJumpTableOffset(jumpTable, label)); + JumpTable jt = new JumpTable(jumpTable.position(), lowKey, highKey, EntryFormat.OFFSET_ONLY); + crb.compilationResult.addAnnotation(jt); + }); } } @@ -542,18 +546,22 @@ public void emitCode(CompilationResultBuilder crb, AArch64MacroAssembler masm) { masm.jmp(jumpTableBase); } - // ensure jump table is aligned with the entry size - masm.align(format.size); - masm.bind(jumpTable); - // emit jump table entries - for (int i = 0; i < targets.length; i++) { - if (format == EntryFormat.VALUE_AND_OFFSET) { - masm.emitInt(keys[i].asInt()); + crb.getLIR().addSlowPath(this, () -> { + // Insert halt so that static analyzers do not continue decoding past this point + masm.halt(); + // ensure jump table is aligned with the entry size + masm.align(format.size); + masm.bind(jumpTable); + // emit jump table entries + for (int i = 0; i < targets.length; i++) { + if (format == EntryFormat.VALUE_AND_OFFSET) { + masm.emitInt(keys[i].asInt()); + } + masm.emitJumpTableOffset(jumpTable, targets[i].label()); } - masm.emitJumpTableOffset(jumpTable, targets[i].label()); - } - JumpTable jt = new JumpTable(jumpTable.position(), 0, keys.length - 1, format); - crb.compilationResult.addAnnotation(jt); + JumpTable jt = new JumpTable(jumpTable.position(), 0, keys.length - 1, format); + crb.compilationResult.addAnnotation(jt); + }); } } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64CountPositivesOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64CountPositivesOp.java index b550d029ecf9..3421b88d5660 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64CountPositivesOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64CountPositivesOp.java @@ -69,7 +69,7 @@ * Returns the number of positive bytes. */ // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/79345bbbae2564f9f523859d1227a1784293b20f/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp#L5735-L5804", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/22845a77a2175202876d0029f75fa32271e07b91/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp#L5750-L5819", sha1 = "ce54a7cf2fcfe7ccb8f6604c038887fc1c4ebce1") @SyncPort(from = "https://github.com/openjdk/jdk/blob/1d117f65f06456ae571aecc146542c2f79d402cf/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp#L4992-L5158", sha1 = "3b4e6edb4372e8babb009763c2d05961348dd723") diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64EncodeArrayOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64EncodeArrayOp.java index d11380f27eb9..f77943abc1ab 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64EncodeArrayOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64EncodeArrayOp.java @@ -51,7 +51,7 @@ import jdk.vm.ci.meta.Value; // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/79345bbbae2564f9f523859d1227a1784293b20f/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp#L6338-L6451", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/22845a77a2175202876d0029f75fa32271e07b91/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp#L6353-L6466", sha1 = "80e6323172af5e8a33625b4eb14629cdad500a0f") // @formatter:on @Opcode("AArch64_ENCODE_ARRAY") diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64FloatToHalfFloatOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64FloatToHalfFloatOp.java index 53c35ce1c972..dc1f043c82bc 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64FloatToHalfFloatOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64FloatToHalfFloatOp.java @@ -39,7 +39,7 @@ import jdk.vm.ci.meta.Value; // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/aaaa86b57172d45d1126c50efc270c6e49aba7a5/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp#L528-L531", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/22845a77a2175202876d0029f75fa32271e07b91/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp#L539-L542", sha1 = "95116a9e350c16c09e5eed9db1328a39cb909474") // @formatter:on public class AArch64FloatToHalfFloatOp extends AArch64LIRInstruction { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64HalfFloatToFloatOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64HalfFloatToFloatOp.java index 533e013a1493..9c728706c785 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64HalfFloatToFloatOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64HalfFloatToFloatOp.java @@ -38,7 +38,7 @@ import jdk.vm.ci.meta.Value; // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/aaaa86b57172d45d1126c50efc270c6e49aba7a5/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp#L533-L536", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/22845a77a2175202876d0029f75fa32271e07b91/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp#L544-L547", sha1 = "f723de35aeda75e448037a23cd6af82f457c24cf") // @formatter:on public class AArch64HalfFloatToFloatOp extends AArch64LIRInstruction { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64PermuteOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64PermuteOp.java index 4075611d38be..47f25b8ec206 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64PermuteOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64PermuteOp.java @@ -30,13 +30,17 @@ import jdk.graal.compiler.asm.aarch64.AArch64ASIMDAssembler.ASIMDSize; import jdk.graal.compiler.asm.aarch64.AArch64ASIMDAssembler.ElementSize; import jdk.graal.compiler.asm.aarch64.AArch64MacroAssembler; +import jdk.graal.compiler.core.common.LIRKind; import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.lir.asm.CompilationResultBuilder; import jdk.graal.compiler.lir.LIRInstructionClass; import jdk.graal.compiler.lir.Opcode; +import jdk.graal.compiler.lir.gen.LIRGeneratorTool; +import jdk.vm.ci.aarch64.AArch64Kind; import jdk.vm.ci.code.Register; import jdk.vm.ci.meta.AllocatableValue; +import jdk.vm.ci.meta.Value; /** * This enum encapsulates AArch64 instructions which perform permutations. @@ -102,4 +106,61 @@ public void emitCode(CompilationResultBuilder crb, AArch64MacroAssembler masm) { } } + + public static class ASIMDPermuteOp extends AArch64LIRInstruction { + private static final LIRInstructionClass TYPE = LIRInstructionClass.create(ASIMDPermuteOp.class); + + @Def protected AllocatableValue result; + @Alive protected AllocatableValue source; + @Use protected AllocatableValue indices; + @Temp({OperandFlag.REG, OperandFlag.ILLEGAL}) protected AllocatableValue xtmp1; + @Temp({OperandFlag.REG, OperandFlag.ILLEGAL}) protected AllocatableValue xtmp2; + + public ASIMDPermuteOp(LIRGeneratorTool tool, AllocatableValue result, AllocatableValue source, AllocatableValue indices) { + super(TYPE); + this.result = result; + this.source = source; + this.indices = indices; + AArch64Kind eKind = ((AArch64Kind) result.getPlatformKind()).getScalar(); + this.xtmp1 = eKind == AArch64Kind.BYTE ? Value.ILLEGAL : tool.newVariable(LIRKind.value(AArch64Kind.V128_BYTE)); + this.xtmp2 = eKind == AArch64Kind.BYTE ? Value.ILLEGAL : tool.newVariable(LIRKind.value(AArch64Kind.V128_BYTE)); + } + + @Override + public void emitCode(CompilationResultBuilder crb, AArch64MacroAssembler masm) { + AArch64Kind vKind = (AArch64Kind) result.getPlatformKind(); + AArch64Kind eKind = vKind.getScalar(); + ASIMDSize vSize = ASIMDSize.fromVectorKind(vKind); + Register xtmp1Reg = xtmp1.equals(Value.ILLEGAL) ? Register.None : asRegister(xtmp1); + Register xtmp2Reg = xtmp2.equals(Value.ILLEGAL) ? Register.None : asRegister(xtmp2); + Register currentIdxReg = asRegister(indices); + // Since NEON only supports byte look up, we repeatedly convert a 2W-bit look up into + // W-bit look up by transforming a 2W-bit index with value v into a pair of W-bit + // indices v * 2, v * 2 + 1 until we reach the element width equal to Byte.SIZE + if (eKind.getSizeInBytes() == AArch64Kind.QWORD.getSizeInBytes()) { + masm.neon.shlVVI(vSize, ElementSize.DoubleWord, xtmp1Reg, currentIdxReg, 1); + masm.neon.shlVVI(vSize, ElementSize.DoubleWord, xtmp2Reg, xtmp1Reg, Integer.SIZE); + masm.neon.orrVVV(vSize, xtmp1Reg, xtmp1Reg, xtmp2Reg); + masm.neon.orrVI(vSize, ElementSize.DoubleWord, xtmp1Reg, 1L << Integer.SIZE); + currentIdxReg = xtmp1Reg; + eKind = AArch64Kind.DWORD; + } + if (eKind.getSizeInBytes() == AArch64Kind.DWORD.getSizeInBytes()) { + masm.neon.shlVVI(vSize, ElementSize.Word, xtmp1Reg, currentIdxReg, 1); + masm.neon.shlVVI(vSize, ElementSize.Word, xtmp2Reg, xtmp1Reg, Short.SIZE); + masm.neon.orrVVV(vSize, xtmp1Reg, xtmp1Reg, xtmp2Reg); + masm.neon.orrVI(vSize, ElementSize.Word, xtmp1Reg, 1 << Short.SIZE); + currentIdxReg = xtmp1Reg; + eKind = AArch64Kind.WORD; + } + if (eKind.getSizeInBytes() == AArch64Kind.WORD.getSizeInBytes()) { + masm.neon.shlVVI(vSize, ElementSize.HalfWord, xtmp1Reg, currentIdxReg, 1); + masm.neon.shlVVI(vSize, ElementSize.HalfWord, xtmp2Reg, xtmp1Reg, Byte.SIZE); + masm.neon.orrVVV(vSize, xtmp1Reg, xtmp1Reg, xtmp2Reg); + masm.neon.orrVI(vSize, ElementSize.HalfWord, xtmp1Reg, 1 << Byte.SIZE); + currentIdxReg = xtmp1Reg; + } + masm.neon.tblVVV(vSize, asRegister(result), asRegister(source), currentIdxReg); + } + } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64RoundFloatToIntegerOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64RoundFloatToIntegerOp.java index 6cac4905c0c7..3b08f73ed2c0 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64RoundFloatToIntegerOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64RoundFloatToIntegerOp.java @@ -45,7 +45,7 @@ * {@link Math#round} algorithm for details. */ // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/79345bbbae2564f9f523859d1227a1784293b20f/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp#L6571-L6619", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/22845a77a2175202876d0029f75fa32271e07b91/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp#L6586-L6634", sha1 = "76d47473bf8d1408bf6e7bf6b8a3d93c19dab9c6") // @formatter:on @Opcode("AARCH64_ROUND_FLOAT_TO_INTEGER") diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64StringUTF16CompressOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64StringUTF16CompressOp.java index fe2235070981..e07a4f295b5e 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64StringUTF16CompressOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/aarch64/AArch64StringUTF16CompressOp.java @@ -44,7 +44,7 @@ import jdk.vm.ci.meta.Value; // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/79345bbbae2564f9f523859d1227a1784293b20f/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp#L6559-L6569", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/22845a77a2175202876d0029f75fa32271e07b91/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp#L6574-L6584", sha1 = "857dc6f9a492da6c8e20afb2139ae393efd228ac") // @formatter:on @Opcode("AArch64_STRING_COMPRESS") diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BigIntegerMulAddOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BigIntegerMulAddOp.java index 583141eae21c..13241647517f 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BigIntegerMulAddOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BigIntegerMulAddOp.java @@ -59,9 +59,9 @@ // @formatter:off @SyncPort(from = "https://github.com/openjdk/jdk/blob/79345bbbae2564f9f523859d1227a1784293b20f/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp#L3269-L3321", sha1 = "2f3b577fa7f0ced9cc2514af80d2c2833ab7caf2") -@SyncPort(from = "https://github.com/openjdk/jdk/blob/959fa4a1a35a1bb650ec5888efaf3d0fc8cfb025/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L7780-L7814", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/496641955041c5e48359e6256a4a61812653d900/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L7779-L7813", sha1 = "e68b8c7bdb37d4bd1350c7e1219fdcb419d2618a") -@SyncPort(from = "https://github.com/openjdk/jdk/blob/959fa4a1a35a1bb650ec5888efaf3d0fc8cfb025/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L8032-L8209", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/496641955041c5e48359e6256a4a61812653d900/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L8031-L8208", sha1 = "d89ad721deb560178359f86e8c6c96ffc6530878") // @formatter:on public final class AMD64BigIntegerMulAddOp extends AMD64LIRInstruction { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BigIntegerMultiplyToLenOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BigIntegerMultiplyToLenOp.java index dcbaf252aed3..e49697ce57d0 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BigIntegerMultiplyToLenOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BigIntegerMultiplyToLenOp.java @@ -58,7 +58,7 @@ // @formatter:off @SyncPort(from = "https://github.com/openjdk/jdk/blob/79345bbbae2564f9f523859d1227a1784293b20f/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp#L3038-L3093", sha1 = "2bf2eb0a9feca080f99e6932d3750cdf3ce2ef3a") -@SyncPort(from = "https://github.com/openjdk/jdk/blob/959fa4a1a35a1bb650ec5888efaf3d0fc8cfb025/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L7036-L7493", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/496641955041c5e48359e6256a4a61812653d900/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L7035-L7492", sha1 = "0763af542cf9f40a1c542e4834a67fc4b2c74e1c") // @formatter:on public final class AMD64BigIntegerMultiplyToLenOp extends AMD64LIRInstruction { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BigIntegerSquareToLenOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BigIntegerSquareToLenOp.java index d6eab417bbf0..5d55a42bee93 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BigIntegerSquareToLenOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BigIntegerSquareToLenOp.java @@ -57,7 +57,7 @@ // @formatter:off @SyncPort(from = "https://github.com/openjdk/jdk/blob/79345bbbae2564f9f523859d1227a1784293b20f/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp#L3147-L3191", sha1 = "ab70559cefe0dc177a290d417047955fba3ad1fc") -@SyncPort(from = "https://github.com/openjdk/jdk/blob/959fa4a1a35a1bb650ec5888efaf3d0fc8cfb025/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L7717-L8030", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/496641955041c5e48359e6256a4a61812653d900/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L7716-L8029", sha1 = "2e4ea1436904cbd5a933eb8c687296d9bbefe4f0") // @formatter:on public final class AMD64BigIntegerSquareToLenOp extends AMD64LIRInstruction { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BitSwapOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BitSwapOp.java index cabd88487152..028856e9a30c 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BitSwapOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64BitSwapOp.java @@ -40,7 +40,7 @@ import jdk.vm.ci.meta.Value; // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/79345bbbae2564f9f523859d1227a1784293b20f/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp#L6448-L6530", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/22845a77a2175202876d0029f75fa32271e07b91/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp#L6456-L6538", sha1 = "34c6e1ee7916fc7190cbcbc237eaf2b510f7dd0e") // @formatter:on public final class AMD64BitSwapOp extends AMD64LIRInstruction { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64ControlFlow.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64ControlFlow.java index 47f39bed188e..219331eacf88 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64ControlFlow.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64ControlFlow.java @@ -711,32 +711,37 @@ public static void emitJumpTable(CompilationResultBuilder crb, AMD64MacroAssembl masm.addq(scratchReg, idxScratchReg); masm.jmp(scratchReg); - // Inserting padding so that jump table address is 4-byte aligned - masm.align(4); - - // Patch LEA instruction above now that we know the position of the jump table - // this is ugly but there is no better way to do this given the assembler API - final int jumpTablePos = masm.position(); - final int leaDisplacementPosition = afterLea - 4; - masm.emitInt(jumpTablePos - afterLea, leaDisplacementPosition); - - // Emit jump table entries - targets.forEach(label -> { - int offsetToJumpTableBase = masm.position() - jumpTablePos; - if (label.isBound()) { - int imm32 = label.position() - jumpTablePos; - masm.emitInt(imm32); - } else { - label.addPatchAt(masm.position(), masm); - - masm.emitByte(0); // pseudo-opcode for jump table entry - masm.emitShort(offsetToJumpTableBase); - masm.emitByte(0); // padding to make jump table entry 4 bytes wide - } + crb.getLIR().addSlowPath(null, () -> { + // Insert halt so that static analyzers do not continue decoding past this point + masm.hlt(); + // Insert ud2 so the CPU does not continue decoding past this point + masm.ud2(); + // Inserting padding so that jump table address is 4-byte aligned + masm.align(4); + // Patch LEA instruction above now that we know the position of the jump table + // this is ugly but there is no better way to do this given the assembler API + int jumpTablePos = masm.position(); + int leaDisplacementPosition = afterLea - 4; + masm.emitInt(jumpTablePos - afterLea, leaDisplacementPosition); + + // Emit jump table entries + targets.forEach(label -> { + int offsetToJumpTableBase = masm.position() - jumpTablePos; + if (label.isBound()) { + int imm32 = label.position() - jumpTablePos; + masm.emitInt(imm32); + } else { + label.addPatchAt(masm.position(), masm); + + masm.emitByte(0); // pseudo-opcode for jump table entry + masm.emitShort(offsetToJumpTableBase); + masm.emitByte(0); // padding to make jump table entry 4 bytes wide + } + }); + + JumpTable jt = new JumpTable(jumpTablePos, lowKey, highKey, EntryFormat.OFFSET_ONLY); + crb.compilationResult.addAnnotation(jt); }); - - JumpTable jt = new JumpTable(jumpTablePos, lowKey, highKey, EntryFormat.OFFSET_ONLY); - crb.compilationResult.addAnnotation(jt); } } @@ -794,38 +799,43 @@ public void emitCode(CompilationResultBuilder crb, AMD64MacroAssembler masm) { masm.addq(scratchReg, entryScratchReg); masm.jmp(scratchReg); - // Inserting padding so that jump the table address is aligned - EntryFormat entryFormat = defaultTarget == null ? EntryFormat.OFFSET_ONLY : EntryFormat.VALUE_AND_OFFSET; - masm.align(entryFormat.size); - - // Patch LEA instruction above now that we know the position of the jump table - // this is ugly but there is no better way to do this given the assembler API - final int jumpTablePos = masm.position(); - final int leaDisplacementPosition = afterLea - 4; - masm.emitInt(jumpTablePos - afterLea, leaDisplacementPosition); - - // Emit jump table entries - for (int i = 0; i < targets.length; i++) { - - Label label = targets[i].label(); - - if (defaultTarget != null) { - masm.emitInt(keys[i].asInt()); + crb.getLIR().addSlowPath(this, () -> { + // Insert halt so that static analyzers do not continue decoding past this point + masm.hlt(); + // Insert ud2 so the CPU does not continue decoding past this point + masm.ud2(); + // Inserting padding so that jump the table address is aligned + EntryFormat entryFormat = defaultTarget == null ? EntryFormat.OFFSET_ONLY : EntryFormat.VALUE_AND_OFFSET; + masm.align(entryFormat.size); + + // Patch LEA instruction above now that we know the position of the jump table + // this is ugly but there is no better way to do this given the assembler API + final int jumpTablePos = masm.position(); + final int leaDisplacementPosition = afterLea - 4; + masm.emitInt(jumpTablePos - afterLea, leaDisplacementPosition); + + // Emit jump table entries + for (int i = 0; i < targets.length; i++) { + Label label = targets[i].label(); + + if (defaultTarget != null) { + masm.emitInt(keys[i].asInt()); + } + if (label.isBound()) { + int imm32 = label.position() - jumpTablePos; + masm.emitInt(imm32); + } else { + int offsetToJumpTableBase = masm.position() - jumpTablePos; + label.addPatchAt(masm.position(), masm); + masm.emitByte(0); // pseudo-opcode for jump table entry + masm.emitShort(offsetToJumpTableBase); + masm.emitByte(0); // padding to make jump table entry 4 bytes wide + } } - if (label.isBound()) { - int imm32 = label.position() - jumpTablePos; - masm.emitInt(imm32); - } else { - int offsetToJumpTableBase = masm.position() - jumpTablePos; - label.addPatchAt(masm.position(), masm); - masm.emitByte(0); // pseudo-opcode for jump table entry - masm.emitShort(offsetToJumpTableBase); - masm.emitByte(0); // padding to make jump table entry 4 bytes wide - } - } - JumpTable jt = new JumpTable(jumpTablePos, 0, keys.length - 1, entryFormat); - crb.compilationResult.addAnnotation(jt); + JumpTable jt = new JumpTable(jumpTablePos, 0, keys.length - 1, entryFormat); + crb.compilationResult.addAnnotation(jt); + }); } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64CountPositivesOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64CountPositivesOp.java index 87293c2c4424..564ae28fff47 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64CountPositivesOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64CountPositivesOp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, 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 @@ -26,7 +26,7 @@ import static jdk.graal.compiler.asm.amd64.AMD64Assembler.VexGeneralPurposeRMVOp.SHLX; import static jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRMOp.VPBROADCASTD; -import static jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRVMOp.EVPXOR; +import static jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRVMOp.EVPXORD; import static jdk.graal.compiler.asm.amd64.AVXKind.AVXSize.QWORD; import static jdk.graal.compiler.asm.amd64.AVXKind.AVXSize.YMM; import static jdk.graal.compiler.asm.amd64.AVXKind.AVXSize.ZMM; @@ -55,7 +55,7 @@ import jdk.vm.ci.meta.Value; // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/79345bbbae2564f9f523859d1227a1784293b20f/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp#L4075-L4345", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/22845a77a2175202876d0029f75fa32271e07b91/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp#L4083-L4353", sha1 = "684b5353c58bbf92e4403aa985113a78a1f38930") // @formatter:on @Opcode("AMD64_COUNT_POSITIVES") @@ -144,7 +144,7 @@ public void emitCode(CompilationResultBuilder crb, AMD64MacroAssembler masm) { Register mask2 = asRegister(maskValue2); masm.movl(tmp1, len); - masm.emit(EVPXOR, vec2, vec2, vec2, ZMM); + masm.emit(EVPXORD, vec2, vec2, vec2, ZMM); // tail count (in chars) 0x3 masm.andl(tmp1, 0x0000003f); // vector count (in chars) diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64EncodeArrayOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64EncodeArrayOp.java index fc680b127b8c..b1e88ab73ce3 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64EncodeArrayOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64EncodeArrayOp.java @@ -54,7 +54,7 @@ import jdk.vm.ci.meta.Value; // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/959fa4a1a35a1bb650ec5888efaf3d0fc8cfb025/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L6875-L7033", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/496641955041c5e48359e6256a4a61812653d900/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L6874-L7032", sha1 = "90e15d79705bc87ffbefbcaa4bdfa55123c12aba") // @formatter:on @Opcode("AMD64_ENCODE_ARRAY") diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64RoundFloatToIntegerOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64RoundFloatToIntegerOp.java index af4951749b98..efeb15d7e994 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64RoundFloatToIntegerOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64RoundFloatToIntegerOp.java @@ -46,7 +46,7 @@ * {@link Math#round} algorithm for details. */ // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/959fa4a1a35a1bb650ec5888efaf3d0fc8cfb025/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L10470-L10566", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/496641955041c5e48359e6256a4a61812653d900/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L10469-L10565", sha1 = "9e13c7375bbb35809ad79ebd6a9cc19e66f57aa1") @SyncPort(from = "https://github.com/openjdk/jdk/blob/79345bbbae2564f9f523859d1227a1784293b20f/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp#L597-L764", sha1 = "312f16a0551887f78cc567638477bbbcbc3765c5") diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64SHA1Op.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64SHA1Op.java index 001ed10687d9..ad5b41295b3e 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64SHA1Op.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64SHA1Op.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -24,8 +24,16 @@ */ package jdk.graal.compiler.lir.amd64; +import static jdk.graal.compiler.lir.amd64.AMD64LIRHelper.pointerConstant; +import static jdk.graal.compiler.lir.amd64.AMD64LIRHelper.recordExternalAddress; import static jdk.vm.ci.amd64.AMD64.xmm0; import static jdk.vm.ci.amd64.AMD64.xmm1; +import static jdk.vm.ci.amd64.AMD64.xmm10; +import static jdk.vm.ci.amd64.AMD64.xmm11; +import static jdk.vm.ci.amd64.AMD64.xmm12; +import static jdk.vm.ci.amd64.AMD64.xmm13; +import static jdk.vm.ci.amd64.AMD64.xmm14; +import static jdk.vm.ci.amd64.AMD64.xmm15; import static jdk.vm.ci.amd64.AMD64.xmm2; import static jdk.vm.ci.amd64.AMD64.xmm3; import static jdk.vm.ci.amd64.AMD64.xmm4; @@ -35,20 +43,18 @@ import static jdk.vm.ci.amd64.AMD64.xmm8; import static jdk.vm.ci.amd64.AMD64.xmm9; import static jdk.vm.ci.code.ValueUtil.asRegister; -import static jdk.graal.compiler.lir.amd64.AMD64LIRHelper.pointerConstant; -import static jdk.graal.compiler.lir.amd64.AMD64LIRHelper.recordExternalAddress; import jdk.graal.compiler.asm.Label; import jdk.graal.compiler.asm.amd64.AMD64Address; import jdk.graal.compiler.asm.amd64.AMD64Assembler.ConditionFlag; import jdk.graal.compiler.asm.amd64.AMD64MacroAssembler; +import jdk.graal.compiler.core.amd64.AMD64LIRGenerator; import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.lir.LIRInstructionClass; import jdk.graal.compiler.lir.SyncPort; import jdk.graal.compiler.lir.asm.ArrayDataPointerConstant; import jdk.graal.compiler.lir.asm.CompilationResultBuilder; -import jdk.graal.compiler.lir.gen.LIRGeneratorTool; - +import jdk.vm.ci.amd64.AMD64.CPUFeature; import jdk.vm.ci.amd64.AMD64Kind; import jdk.vm.ci.code.Register; import jdk.vm.ci.meta.AllocatableValue; @@ -76,11 +82,11 @@ public final class AMD64SHA1Op extends AMD64LIRInstruction { @Temp({OperandFlag.REG}) private Value[] temps; private final boolean multiBlock; - public AMD64SHA1Op(LIRGeneratorTool tool, AllocatableValue bufValue, AllocatableValue stateValue) { + public AMD64SHA1Op(AMD64LIRGenerator tool, AllocatableValue bufValue, AllocatableValue stateValue) { this(tool, bufValue, stateValue, Value.ILLEGAL, Value.ILLEGAL, Value.ILLEGAL, false); } - public AMD64SHA1Op(LIRGeneratorTool tool, AllocatableValue bufValue, AllocatableValue stateValue, AllocatableValue ofsValue, + public AMD64SHA1Op(AMD64LIRGenerator tool, AllocatableValue bufValue, AllocatableValue stateValue, AllocatableValue ofsValue, AllocatableValue limitValue, AllocatableValue resultValue, boolean multiBlock) { super(TYPE); @@ -92,18 +98,40 @@ public AMD64SHA1Op(LIRGeneratorTool tool, AllocatableValue bufValue, Allocatable this.multiBlock = multiBlock; - this.temps = new Value[]{ - xmm0.asValue(), - xmm1.asValue(), - xmm2.asValue(), - xmm3.asValue(), - xmm4.asValue(), - xmm5.asValue(), - xmm6.asValue(), - xmm7.asValue(), - xmm8.asValue(), - xmm9.asValue(), - }; + if (tool.supportsCPUFeature(CPUFeature.AVX)) { + // vzeroupper clears upper bits of xmm0-xmm15 + this.temps = new Value[]{ + xmm0.asValue(), + xmm1.asValue(), + xmm2.asValue(), + xmm3.asValue(), + xmm4.asValue(), + xmm5.asValue(), + xmm6.asValue(), + xmm7.asValue(), + xmm8.asValue(), + xmm9.asValue(), + xmm10.asValue(), + xmm11.asValue(), + xmm12.asValue(), + xmm13.asValue(), + xmm14.asValue(), + xmm15.asValue(), + }; + } else { + this.temps = new Value[]{ + xmm0.asValue(), + xmm1.asValue(), + xmm2.asValue(), + xmm3.asValue(), + xmm4.asValue(), + xmm5.asValue(), + xmm6.asValue(), + xmm7.asValue(), + xmm8.asValue(), + xmm9.asValue(), + }; + } if (multiBlock) { this.bufTempValue = tool.newVariable(bufValue.getValueKind()); @@ -168,6 +196,12 @@ public void emitCode(CompilationResultBuilder crb, AMD64MacroAssembler masm) { Label labelDoneHash = new Label(); Label labelLoop0 = new Label(); + if (masm.supports(CPUFeature.AVX)) { + // Insert vzeroupper here to avoid performance penalty of SSE-AVX transition between + // previously executed AVX instructions and the following SHA-1 instructions. + masm.vzeroupper(); + } + masm.movdqu(abcd, new AMD64Address(state, 0)); masm.pinsrd(e0, new AMD64Address(state, 16), 3); masm.movdqu(shufMask, recordExternalAddress(crb, upperWordMask)); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64SHA256Op.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64SHA256Op.java index c9679faedb82..ab7b35c320b2 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64SHA256Op.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64SHA256Op.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -24,9 +24,17 @@ */ package jdk.graal.compiler.lir.amd64; +import static jdk.graal.compiler.asm.amd64.AMD64Assembler.ConditionFlag.BelowEqual; +import static jdk.graal.compiler.lir.amd64.AMD64LIRHelper.pointerConstant; +import static jdk.graal.compiler.lir.amd64.AMD64LIRHelper.recordExternalAddress; import static jdk.vm.ci.amd64.AMD64.xmm0; import static jdk.vm.ci.amd64.AMD64.xmm1; import static jdk.vm.ci.amd64.AMD64.xmm10; +import static jdk.vm.ci.amd64.AMD64.xmm11; +import static jdk.vm.ci.amd64.AMD64.xmm12; +import static jdk.vm.ci.amd64.AMD64.xmm13; +import static jdk.vm.ci.amd64.AMD64.xmm14; +import static jdk.vm.ci.amd64.AMD64.xmm15; import static jdk.vm.ci.amd64.AMD64.xmm2; import static jdk.vm.ci.amd64.AMD64.xmm3; import static jdk.vm.ci.amd64.AMD64.xmm4; @@ -36,20 +44,17 @@ import static jdk.vm.ci.amd64.AMD64.xmm8; import static jdk.vm.ci.amd64.AMD64.xmm9; import static jdk.vm.ci.code.ValueUtil.asRegister; -import static jdk.graal.compiler.asm.amd64.AMD64Assembler.ConditionFlag.BelowEqual; -import static jdk.graal.compiler.lir.amd64.AMD64LIRHelper.pointerConstant; -import static jdk.graal.compiler.lir.amd64.AMD64LIRHelper.recordExternalAddress; import jdk.graal.compiler.asm.Label; import jdk.graal.compiler.asm.amd64.AMD64Address; import jdk.graal.compiler.asm.amd64.AMD64MacroAssembler; +import jdk.graal.compiler.core.amd64.AMD64LIRGenerator; import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.lir.LIRInstructionClass; import jdk.graal.compiler.lir.SyncPort; import jdk.graal.compiler.lir.asm.ArrayDataPointerConstant; import jdk.graal.compiler.lir.asm.CompilationResultBuilder; -import jdk.graal.compiler.lir.gen.LIRGeneratorTool; - +import jdk.vm.ci.amd64.AMD64.CPUFeature; import jdk.vm.ci.amd64.AMD64Kind; import jdk.vm.ci.code.Register; import jdk.vm.ci.meta.AllocatableValue; @@ -79,11 +84,11 @@ public final class AMD64SHA256Op extends AMD64LIRInstruction { private final boolean multiBlock; - public AMD64SHA256Op(LIRGeneratorTool tool, AllocatableValue bufValue, AllocatableValue stateValue) { + public AMD64SHA256Op(AMD64LIRGenerator tool, AllocatableValue bufValue, AllocatableValue stateValue) { this(tool, bufValue, stateValue, Value.ILLEGAL, Value.ILLEGAL, Value.ILLEGAL, false); } - public AMD64SHA256Op(LIRGeneratorTool tool, AllocatableValue bufValue, AllocatableValue stateValue, AllocatableValue ofsValue, + public AMD64SHA256Op(AMD64LIRGenerator tool, AllocatableValue bufValue, AllocatableValue stateValue, AllocatableValue ofsValue, AllocatableValue limitValue, AllocatableValue resultValue, boolean multiBlock) { super(TYPE); @@ -97,19 +102,40 @@ public AMD64SHA256Op(LIRGeneratorTool tool, AllocatableValue bufValue, Allocatab this.keyTempValue = tool.newVariable(bufValue.getValueKind()); - this.temps = new Value[]{ - xmm0.asValue(), - xmm1.asValue(), - xmm2.asValue(), - xmm3.asValue(), - xmm4.asValue(), - xmm5.asValue(), - xmm6.asValue(), - xmm7.asValue(), - xmm8.asValue(), - xmm9.asValue(), - xmm10.asValue(), - }; + if (tool.supportsCPUFeature(CPUFeature.AVX)) { + // vzeroupper clears upper bits of xmm0-xmm15 + this.temps = new Value[]{ + xmm0.asValue(), + xmm1.asValue(), + xmm2.asValue(), + xmm3.asValue(), + xmm4.asValue(), + xmm5.asValue(), + xmm6.asValue(), + xmm7.asValue(), + xmm8.asValue(), + xmm9.asValue(), + xmm10.asValue(), + xmm11.asValue(), + xmm12.asValue(), + xmm13.asValue(), + xmm14.asValue(), + xmm15.asValue(), + }; + } else { + this.temps = new Value[]{ + xmm0.asValue(), + xmm1.asValue(), + xmm2.asValue(), + xmm3.asValue(), + xmm4.asValue(), + xmm5.asValue(), + xmm6.asValue(), + xmm7.asValue(), + xmm8.asValue(), + xmm9.asValue(), + }; + } if (multiBlock) { this.bufTempValue = tool.newVariable(bufValue.getValueKind()); @@ -199,6 +225,12 @@ public void emitCode(CompilationResultBuilder crb, AMD64MacroAssembler masm) { // keyTemp replaces the hardcoded rax in the original stub. Register keyTemp = asRegister(keyTempValue); + if (masm.supports(CPUFeature.AVX)) { + // Insert vzeroupper here to avoid performance penalty of SSE-AVX transition between + // previously executed AVX instructions and the following SHA-256 instructions. + masm.vzeroupper(); + } + masm.movdqu(state0, new AMD64Address(state, 0)); masm.movdqu(state1, new AMD64Address(state, 16)); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64StringUTF16CompressOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64StringUTF16CompressOp.java index 257815bb7623..4080cc05ce41 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64StringUTF16CompressOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64StringUTF16CompressOp.java @@ -59,7 +59,7 @@ import jdk.vm.ci.meta.Value; // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/959fa4a1a35a1bb650ec5888efaf3d0fc8cfb025/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L9289-L9497", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/496641955041c5e48359e6256a4a61812653d900/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L9288-L9496", sha1 = "3e365037f473204b3f742ab364bd9ad514e72161") // @formatter:on @Opcode("AMD64_STRING_COMPRESS") diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64VectorizedHashCodeOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64VectorizedHashCodeOp.java index 14da2fcc5a36..5c1cd865e4b2 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64VectorizedHashCodeOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64VectorizedHashCodeOp.java @@ -75,12 +75,12 @@ import jdk.vm.ci.meta.Value; // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/79345bbbae2564f9f523859d1227a1784293b20f/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp#L1829-L1938", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/22845a77a2175202876d0029f75fa32271e07b91/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp#L1837-L1946", sha1 = "1cc5a10b19e7746105493d8f430f628cc7f89c51") -@SyncPort(from = "https://github.com/openjdk/jdk/blob/79345bbbae2564f9f523859d1227a1784293b20f/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp#L2129-L2175", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/22845a77a2175202876d0029f75fa32271e07b91/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp#L2137-L2183", sha1 = "9cbba8bd6c4037427fa46f067abb722b15aca90c") -@SyncPort(from = "https://github.com/openjdk/jdk/blob/79345bbbae2564f9f523859d1227a1784293b20f/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp#L3490-L3677", - sha1 = "2457cf3f9d3ff89c1515fa5d95cc7c8437a5318b") +@SyncPort(from = "https://github.com/openjdk/jdk/blob/2c4567a689091721476b6ef0ef4ad042fd63c3fd/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp#L3498-L3685", + sha1 = "a3fe941a49e0e3f8443b2e16e550d6c94b012b12") // @formatter:on @Opcode("VECTORIZED_HASHCODE") public final class AMD64VectorizedHashCodeOp extends AMD64ComplexVectorOp { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64VectorizedMismatchOp.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64VectorizedMismatchOp.java index a8bd777e3d99..25f6eeb0d666 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64VectorizedMismatchOp.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/AMD64VectorizedMismatchOp.java @@ -61,7 +61,7 @@ * instructions where possible. */ // @formatter:off -@SyncPort(from = "https://github.com/openjdk/jdk/blob/959fa4a1a35a1bb650ec5888efaf3d0fc8cfb025/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L7495-L7713", +@SyncPort(from = "https://github.com/openjdk/jdk/blob/496641955041c5e48359e6256a4a61812653d900/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L7494-L7712", sha1 = "72f9b7a60b75ecabf09fc10cb01a9504be97957a") // @formatter:on @Opcode("VECTORIZED_MISMATCH") diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/vector/AMD64VectorShuffle.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/vector/AMD64VectorShuffle.java index e5c98aad8132..ba57eefc2ddd 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/vector/AMD64VectorShuffle.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/amd64/vector/AMD64VectorShuffle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, 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 @@ -56,13 +56,14 @@ import static jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRVMIOp.VSHUFPS; import static jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRVMOp.EVPERMT2B; import static jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRVMOp.EVPSHUFB; -import static jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRVMOp.EVPXOR; +import static jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRVMOp.EVPXORD; import static jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRVMOp.VMOVHPD; import static jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRVMOp.VMOVLHPS; import static jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRVMOp.VMOVLPD; import static jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRVMOp.VMOVSD; import static jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRVMOp.VPSHUFB; import static jdk.graal.compiler.asm.amd64.AVXKind.AVXSize.XMM; +import static jdk.graal.compiler.asm.amd64.AVXKind.AVXSize.YMM; import static jdk.graal.compiler.asm.amd64.AVXKind.AVXSize.ZMM; import static jdk.vm.ci.code.ValueUtil.asRegister; import static jdk.vm.ci.code.ValueUtil.isRegister; @@ -71,13 +72,19 @@ import jdk.graal.compiler.asm.amd64.AMD64Address; import jdk.graal.compiler.asm.amd64.AMD64Assembler; import jdk.graal.compiler.asm.amd64.AMD64Assembler.AMD64SIMDInstructionEncoding; +import jdk.graal.compiler.asm.amd64.AMD64Assembler.VexMoveMaskOp; import jdk.graal.compiler.asm.amd64.AMD64Assembler.VexMRIOp; import jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRMIOp; +import jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRMOp; import jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRVMIOp; +import jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRVMOp; +import jdk.graal.compiler.asm.amd64.AMD64Assembler.VexRVMROp; +import jdk.graal.compiler.asm.amd64.AMD64Assembler.VexShiftOp; import jdk.graal.compiler.asm.amd64.AMD64BaseAssembler; import jdk.graal.compiler.asm.amd64.AMD64MacroAssembler; import jdk.graal.compiler.asm.amd64.AVXKind; import jdk.graal.compiler.asm.amd64.AVXKind.AVXSize; +import jdk.graal.compiler.core.amd64.AMD64LIRGenerator; import jdk.graal.compiler.core.common.LIRKind; import jdk.graal.compiler.debug.Assertions; import jdk.graal.compiler.debug.GraalError; @@ -89,9 +96,254 @@ import jdk.vm.ci.amd64.AMD64Kind; import jdk.vm.ci.code.Register; import jdk.vm.ci.meta.AllocatableValue; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.Value; public class AMD64VectorShuffle { + /** + * General purpose permutation, this node looks up elements from a source vector using the index + * vector as the selector. + */ + public static final class PermuteOp extends AMD64LIRInstruction { + public static final LIRInstructionClass TYPE = LIRInstructionClass.create(PermuteOp.class); + + @Def protected AllocatableValue result; + @Use protected AllocatableValue source; + @Use protected AllocatableValue indices; + private final AMD64SIMDInstructionEncoding encoding; + + private PermuteOp(AllocatableValue result, AllocatableValue source, AllocatableValue indices, AMD64SIMDInstructionEncoding encoding) { + super(TYPE); + this.result = result; + this.source = source; + this.indices = indices; + this.encoding = encoding; + } + + public static AMD64LIRInstruction create(AMD64LIRGenerator gen, AllocatableValue result, AllocatableValue source, AllocatableValue indices, AMD64SIMDInstructionEncoding encoding) { + AMD64Kind eKind = ((AMD64Kind) result.getPlatformKind()).getScalar(); + AVXSize avxSize = AVXKind.getRegisterSize(result); + return switch (eKind) { + case BYTE -> { + if (gen.supportsCPUFeature(CPUFeature.AVX512_VBMI) || avxSize == XMM) { + yield new PermuteOp(result, source, indices, encoding); + } else { + yield switch (avxSize) { + case YMM -> new PermuteOpWithTemps(gen, result, source, indices, encoding, 3, false); + case ZMM -> new PermuteOpWithTemps(gen, result, source, indices, encoding, 3, true); + default -> throw GraalError.shouldNotReachHereUnexpectedValue(avxSize); + }; + } + } + case WORD -> { + if (encoding == AMD64SIMDInstructionEncoding.EVEX) { + GraalError.guarantee(gen.supportsCPUFeature(CPUFeature.AVX512BW) && gen.supportsCPUFeature(CPUFeature.AVX512VL), "must support basic avx512"); + yield new PermuteOp(result, source, indices, encoding); + } else { + GraalError.guarantee(avxSize.getBytes() < ZMM.getBytes(), "zmm requires evex"); + yield switch (avxSize) { + case XMM, YMM -> new PermuteOpWithTemps(gen, result, source, indices, encoding, 3, false); + default -> throw GraalError.shouldNotReachHereUnexpectedValue(avxSize); + }; + } + } + case DWORD, SINGLE -> new PermuteOp(result, source, indices, encoding); + case QWORD, DOUBLE -> { + if (encoding == AMD64SIMDInstructionEncoding.EVEX || avxSize != YMM) { + yield new PermuteOp(result, source, indices, encoding); + } else { + yield new PermuteOpWithTemps(gen, result, source, indices, encoding, 2, false); + } + } + default -> throw GraalError.shouldNotReachHereUnexpectedValue(eKind); + }; + } + + @Override + public void emitCode(CompilationResultBuilder crb, AMD64MacroAssembler masm) { + AMD64Kind eKind = ((AMD64Kind) result.getPlatformKind()).getScalar(); + AVXSize avxSize = AVXKind.getRegisterSize(result); + switch (eKind) { + case BYTE -> { + if (avxSize == XMM) { + VexRVMOp.VPSHUFB.encoding(encoding).emit(masm, XMM, asRegister(result), asRegister(source), asRegister(indices)); + } else { + VexRVMOp.EVPERMB.encoding(encoding).emit(masm, avxSize, asRegister(result), asRegister(indices), asRegister(source)); + } + } + case WORD -> VexRVMOp.EVPERMW.encoding(encoding).emit(masm, avxSize, asRegister(result), asRegister(indices), asRegister(source)); + case DWORD, SINGLE -> { + if (avxSize.getBytes() <= XMM.getBytes()) { + VexRVMOp.VPERMILPS.encoding(encoding).emit(masm, XMM, asRegister(result), asRegister(source), asRegister(indices)); + } else if (((AMD64Kind) result.getPlatformKind()).getScalar().isInteger()) { + VexRVMOp.VPERMD.encoding(encoding).emit(masm, avxSize, asRegister(result), asRegister(indices), asRegister(source)); + } else { + VexRVMOp.VPERMPS.encoding(encoding).emit(masm, avxSize, asRegister(result), asRegister(indices), asRegister(source)); + } + } + case QWORD, DOUBLE -> { + if (avxSize.getBytes() <= XMM.getBytes()) { + VexRVMOp.VPERMILPD.encoding(encoding).emit(masm, XMM, asRegister(result), asRegister(source), asRegister(indices)); + } else if (((AMD64Kind) result.getPlatformKind()).getScalar().isInteger()) { + VexRVMOp.EVPERMQ.encoding(encoding).emit(masm, avxSize, asRegister(result), asRegister(indices), asRegister(source)); + } else { + VexRVMOp.EVPERMPD.encoding(encoding).emit(masm, avxSize, asRegister(result), asRegister(indices), asRegister(source)); + } + } + default -> throw GraalError.shouldNotReachHereUnexpectedValue(eKind); + } + } + } + + /** + * Similar to {@code PermuteOp}, the difference is that this node may use additional temp + * registers. As a result, it is split out so the inputs of {@code PermuteOp} does not need to + * be {@link jdk.graal.compiler.lir.LIRInstruction.Alive}. + */ + private static final class PermuteOpWithTemps extends AMD64LIRInstruction { + public static final LIRInstructionClass TYPE = LIRInstructionClass.create(PermuteOpWithTemps.class); + + @Def protected AllocatableValue result; + @Alive protected AllocatableValue source; + @Alive protected AllocatableValue indices; + @Temp protected AllocatableValue[] xtmps; + @Temp({OperandFlag.REG, OperandFlag.ILLEGAL}) protected AllocatableValue ktmp; + private final AMD64SIMDInstructionEncoding encoding; + + private PermuteOpWithTemps(AMD64LIRGenerator gen, AllocatableValue result, AllocatableValue source, AllocatableValue indices, AMD64SIMDInstructionEncoding encoding, int xtmpRegs, + boolean ktmpReg) { + super(TYPE); + GraalError.guarantee(xtmpRegs <= 3, "too many temporaries, %d", xtmpRegs); + this.result = result; + this.source = source; + this.indices = indices; + this.xtmps = new AllocatableValue[xtmpRegs]; + for (int i = 0; i < xtmpRegs; i++) { + this.xtmps[i] = gen.newVariable(indices.getValueKind()); + } + this.ktmp = ktmpReg ? gen.newVariable(LIRKind.value(AMD64Kind.MASK64)) : Value.ILLEGAL; + this.encoding = encoding; + } + + @Override + public void emitCode(CompilationResultBuilder crb, AMD64MacroAssembler masm) { + AMD64Kind eKind = ((AMD64Kind) result.getPlatformKind()).getScalar(); + AVXSize avxSize = AVXKind.getRegisterSize(result); + switch (eKind) { + case BYTE -> { + GraalError.guarantee(!masm.supports(CPUFeature.AVX512_VBMI) && avxSize.getBytes() > XMM.getBytes(), "should be a PermuteOp"); + emitBytePermute(crb, masm, asRegister(indices)); + } + case WORD -> { + GraalError.guarantee(!masm.supports(CPUFeature.AVX512BW) && avxSize != ZMM, "should be PermuteOp"); + Register indexReg = asRegister(indices); + Register xtmp1Reg = asRegister(xtmps[0]); + Register xtmp2Reg = asRegister(xtmps[1]); + Register xtmp3Reg = asRegister(xtmps[2]); + + // Transform into a byte permute by transforming a 16-bit index with value v + // into a pair of 8-bit indices v * 2, v * 2 + 1 + VexShiftOp.VPSLLW.encoding(encoding).emit(masm, avxSize, xtmp1Reg, indexReg, Byte.SIZE + 1); + AMD64Address inc = (AMD64Address) crb.recordDataReferenceInCode(JavaConstant.forInt(0x01000100), Integer.BYTES); + VexRMOp broadcastOp = masm.supports(CPUFeature.AVX2) ? VexRMOp.VPBROADCASTD : VexRMOp.VBROADCASTSS; + broadcastOp.encoding(encoding).emit(masm, avxSize, xtmp2Reg, inc); + VexRVMOp.VPOR.encoding(encoding).emit(masm, avxSize, xtmp1Reg, xtmp1Reg, xtmp2Reg); + VexShiftOp.VPSLLW.encoding(encoding).emit(masm, avxSize, xtmp2Reg, indexReg, 1); + VexRVMOp.VPOR.encoding(encoding).emit(masm, avxSize, xtmp3Reg, xtmp1Reg, xtmp2Reg); + emitBytePermute(crb, masm, xtmp3Reg); + } + case DWORD, SINGLE -> throw GraalError.shouldNotReachHere("should be PermuteOp"); + case QWORD, DOUBLE -> { + GraalError.guarantee(encoding == AMD64SIMDInstructionEncoding.VEX && avxSize == YMM, "should be PermuteOp"); + Register indexReg = asRegister(indices); + Register xtmp1Reg = asRegister(xtmps[0]); + Register xtmp2Reg = asRegister(xtmps[1]); + + // Transform into an int permute by transforming a 64-bit index with value v + // into a pair of 32-bit indices v + 2, v * 2 + 1 + VexShiftOp.VPSLLQ.encoding(encoding).emit(masm, YMM, xtmp1Reg, indexReg, Integer.SIZE + 1); + AMD64Address inc = (AMD64Address) crb.asLongConstRef(JavaConstant.forLong(1L << Integer.SIZE)); + VexRMOp.VPBROADCASTQ.encoding(encoding).emit(masm, YMM, xtmp2Reg, inc); + VexRVMOp.VPOR.encoding(encoding).emit(masm, YMM, xtmp2Reg, xtmp1Reg, xtmp2Reg); + VexShiftOp.VPSLLQ.encoding(encoding).emit(masm, YMM, xtmp1Reg, indexReg, 1); + VexRVMOp.VPOR.encoding(encoding).emit(masm, YMM, xtmp1Reg, xtmp1Reg, xtmp2Reg); + VexRVMOp op = eKind == AMD64Kind.QWORD ? VexRVMOp.VPERMD : VexRVMOp.VPERMPS; + op.encoding(encoding).emit(masm, YMM, asRegister(result), xtmp1Reg, asRegister(source)); + } + default -> throw GraalError.shouldNotReachHereUnexpectedValue(eKind); + } + } + + private void emitBytePermute(CompilationResultBuilder crb, AMD64MacroAssembler masm, Register indexReg) { + AVXSize avxSize = AVXKind.getRegisterSize(result); + switch (avxSize) { + case XMM -> VexRVMOp.VPSHUFB.encoding(encoding).emit(masm, XMM, asRegister(result), asRegister(source), indexReg); + case YMM -> { + Register sourceReg = asRegister(source); + Register xtmp1Reg = asRegister(xtmps[0]); + Register xtmp2Reg = asRegister(xtmps[1]); + Register xtmp3Reg = asRegister(xtmps[2]); + GraalError.guarantee(!indexReg.equals(xtmp1Reg) && !indexReg.equals(xtmp2Reg), "cannot alias"); + + // Find the elements that are collected from the first YMM half + VexRVMIOp.VPERM2I128.emit(masm, YMM, xtmp1Reg, sourceReg, sourceReg, 0x00); + VexRVMOp.VPSHUFB.encoding(encoding).emit(masm, YMM, xtmp1Reg, xtmp1Reg, indexReg); + + // Find the elements that are collected from the second YMM half + VexRVMIOp.VPERM2I128.emit(masm, YMM, xtmp2Reg, sourceReg, sourceReg, 0x11); + VexRVMOp.VPSHUFB.encoding(encoding).emit(masm, YMM, xtmp2Reg, xtmp2Reg, indexReg); + + // Blend the results, the 5-th bit of the index vector is the selector (0 - 15 + // has the 5-th bit being 0 while 16 - 31 has the 5-bit being 1) + // Shift the 5-th bit to the position of the sign bit to use vpblendvb + VexShiftOp.VPSLLD.encoding(encoding).emit(masm, YMM, xtmp3Reg, indexReg, 3); + VexRVMROp.VPBLENDVB.emit(masm, YMM, asRegister(result), xtmp3Reg, xtmp1Reg, xtmp2Reg); + } + case ZMM -> { + Register sourceReg = asRegister(source); + Register xtmp1Reg = asRegister(xtmps[0]); + Register xtmp2Reg = asRegister(xtmps[1]); + Register xtmp3Reg = asRegister(xtmps[2]); + Register ktmpReg = asRegister(ktmp); + GraalError.guarantee(!indexReg.equals(xtmp1Reg) && !indexReg.equals(xtmp2Reg) && !indexReg.equals(xtmp3Reg), "cannot alias"); + + // Process the even-index elements + // Find the 2-byte location in the source vector and move to the correct 2-byte + // location in the result + VexShiftOp.EVPSRLD.emit(masm, ZMM, xtmp1Reg, indexReg, 1); + VexRVMOp.EVPERMW.emit(masm, ZMM, xtmp1Reg, xtmp1Reg, sourceReg); + + // Elements with indices end with 0 are at the correct position, while the ones + // that have their indices end with 1 need to shift right by 8 + VexShiftOp.EVPSLLD.emit(masm, ZMM, xtmp3Reg, indexReg, Short.SIZE - 1); + VexRMOp.EVPMOVW2M.emit(masm, ZMM, ktmpReg, xtmp3Reg); + VexShiftOp.EVPSRLD.emit(masm, ZMM, xtmp3Reg, xtmp1Reg, Byte.SIZE); + VexRVMOp.EVPBLENDMW.emit(masm, ZMM, xtmp1Reg, xtmp1Reg, xtmp3Reg, ktmpReg); + + // Process the odd-index elements + // Find the 2-byte location in the source vector and move to the correct 2-byte + // location in the result + VexShiftOp.EVPSRLD.emit(masm, ZMM, xtmp2Reg, indexReg, Byte.SIZE + 1); + VexRVMOp.EVPERMW.emit(masm, ZMM, xtmp2Reg, xtmp2Reg, sourceReg); + + // Elements with indices end with 1 are at the correct position, while the ones + // that have their indices end with 0 need to shift left by 8 + VexShiftOp.EVPSLLD.emit(masm, ZMM, xtmp3Reg, indexReg, Byte.SIZE - 1); + VexRMOp.EVPMOVW2M.emit(masm, ZMM, ktmpReg, xtmp3Reg); + VexShiftOp.EVPSLLD.emit(masm, ZMM, xtmp3Reg, xtmp2Reg, Byte.SIZE); + VexRVMOp.EVPBLENDMW.emit(masm, ZMM, xtmp2Reg, xtmp3Reg, xtmp2Reg, ktmpReg); + + // Blend the odd and even index + AMD64Address mask = (AMD64Address) crb.asLongConstRef(JavaConstant.forLong(0x5555555555555555L)); + VexMoveMaskOp.KMOVQ.emit(masm, XMM, ktmpReg, mask); + VexRVMOp.EVPBLENDMB.emit(masm, ZMM, asRegister(result), xtmp2Reg, xtmp1Reg, ktmpReg); + } + default -> throw GraalError.shouldNotReachHereUnexpectedValue(avxSize); + } + } + } + public static final class IntToVectorOp extends AMD64LIRInstruction { public static final LIRInstructionClass TYPE = LIRInstructionClass.create(IntToVectorOp.class); @@ -194,7 +446,7 @@ public void emitCode(CompilationResultBuilder crb, AMD64MacroAssembler masm) { int alignment = crb.dataBuilder.ensureValidDataAlignment(selectorData.length); AMD64Address address = (AMD64Address) crb.recordDataReferenceInCode(selectorData, alignment); EVMOVDQU64.emit(masm, AVXKind.getRegisterSize(kind), asRegister(selector), address); - EVPXOR.emit(masm, AVXKind.getRegisterSize(kind), asRegister(result), asRegister(result), asRegister(result)); + EVPXORD.emit(masm, AVXKind.getRegisterSize(kind), asRegister(result), asRegister(result), asRegister(result)); if (isRegister(source)) { EVPERMT2B.emit(masm, AVXKind.getRegisterSize(kind), asRegister(result), asRegister(selector), asRegister(source), mask != null ? asRegister(mask) : Register.None, mask != null ? AMD64BaseAssembler.EVEXPrefixConfig.Z1 : AMD64BaseAssembler.EVEXPrefixConfig.Z0, diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/gen/LIRGenerator.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/gen/LIRGenerator.java index 72de36a9ed97..d7344c2736e5 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/gen/LIRGenerator.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/gen/LIRGenerator.java @@ -501,6 +501,8 @@ public Variable emitForeignCall(ForeignCallLinkage linkage, LIRFrameState frameS } } + private static final double JUMP_TABLE_THRESHOLD = 3; + public void emitStrategySwitch(JavaConstant[] keyConstants, double[] keyProbabilities, LabelRef[] keyTargets, LabelRef defaultTarget, AllocatableValue value) { SwitchStrategy strategy = SwitchStrategy.getBestStrategy(keyProbabilities, keyConstants, keyTargets); @@ -513,12 +515,12 @@ public void emitStrategySwitch(JavaConstant[] keyConstants, double[] keyProbabil /* * This heuristic tries to find a compromise between the effort for the best switch strategy - * and the density of a tableswitch. If the effort for the strategy is at least 4, then a - * tableswitch is preferred if better than a certain value that starts at 0.5 and lowers - * gradually with additional effort. + * and the density of a tableswitch. If the effort for the strategy is at least + * JUMP_TABLE_THRESHOLD, then a tableswitch is preferred if the density is larger than a + * certain value that gradually decreases as the aforementioned effort rises. */ double minDensity = 1 / Math.sqrt(strategy.getAverageEffort()); - if (strategy.getAverageEffort() < 4d || (tableSwitchDensity < minDensity && hashTableSwitchDensity < minDensity)) { + if (strategy.getAverageEffort() < JUMP_TABLE_THRESHOLD || (tableSwitchDensity < minDensity && hashTableSwitchDensity < minDensity)) { emitStrategySwitch(strategy, value, keyTargets, defaultTarget); } else { if (hashTableSwitchDensity > tableSwitchDensity) { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/java/FinalFieldBarrierNode.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/java/FinalFieldBarrierNode.java index 2a4c118ade9a..59bdb72bb8d5 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/java/FinalFieldBarrierNode.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/java/FinalFieldBarrierNode.java @@ -76,4 +76,7 @@ public void lower(LoweringTool tool) { */ graph().replaceFixedWithFixed(this, graph().add(new MembarNode(MembarNode.FenceKind.CONSTRUCTOR_FREEZE))); } + + @NodeIntrinsic + public static native void finalFieldBarrier(Object object); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/loop/CountedLoopInfo.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/loop/CountedLoopInfo.java index fabc284f4cd8..a16682123e80 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/loop/CountedLoopInfo.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/loop/CountedLoopInfo.java @@ -55,6 +55,7 @@ import jdk.graal.compiler.nodes.util.IntegerHelper; import jdk.graal.compiler.nodes.util.SignedIntegerHelper; import jdk.graal.compiler.nodes.util.UnsignedIntegerHelper; +import jdk.graal.compiler.phases.common.util.LoopUtility; import jdk.vm.ci.meta.DeoptimizationAction; import jdk.vm.ci.meta.DeoptimizationReason; import jdk.vm.ci.meta.JavaKind; @@ -619,6 +620,30 @@ public boolean counterNeverOverflows() { } public boolean ivCanNeverOverflow(InductionVariable iv) { + if (iv != getLimitCheckedIV()) { + /* + * All non-limit checked IVs: This IV is not compared against limit and thus we cannot + * play the trick comparing against the end stamp. We have to compute (if possible) the + * extremum value and use that. + */ + if (iv.isConstantInit() && isConstantMaxTripCount() && iv.isConstantStride()) { + try { + final int bits = IntegerStamp.getBits(iv.valueNode().stamp(NodeView.DEFAULT)); + long tripCountMinus1 = LoopUtility.subtractExact(bits, LoopUtility.tripCountSignedExact(this), 1); + long stripTimesTripCount = LoopUtility.multiplyExact(bits, iv.constantStride(), tripCountMinus1); + @SuppressWarnings("unused") + long extremum = LoopUtility.addExact(bits, stripTimesTripCount, iv.initNode().asJavaConstant().asLong()); + return true; + } catch (ArithmeticException e) { + // overflow + return false; + } + } + return false; + } + + // BELOW: limitCheckedIV case + if (!isLimitIncluded && iv.isConstantStride() && Loop.absStrideIsOne(iv)) { return true; } @@ -626,55 +651,55 @@ public boolean ivCanNeverOverflow(InductionVariable iv) { return true; } // @formatter:off - /* - * Following comment reasons about the simplest possible loop form: - * - * for(i = 0;i < end;i += stride) - * - * The problem is we want to create an overflow guard for the loop that can be hoisted - * before the loop, i.e., the overflow guard must not have loop variant inputs else it must - * be scheduled inside the loop. This means we cannot refer explicitly to the induction - * variable's phi but must establish a relation between end, stride and max (max integer - * range for a given loop) that is sufficient for most cases. - * - * We know that a head counted loop with a stride > 1 may overflow if the stride is big - * enough that end + stride will be > MAX, i.e. it overflows into negative value range. - * - * It is important that "end" in this context is the checked value of the loop condition: - * i.e., an arbitrary value. There is no relation between end and MAX established except - * that based on the integer representation we know that end <= MAX. - * - * A loop can overflow if the last checked value of the iv allows an overflow in the next - * iteration: the value range for which an overflow can happen is [MAX-(stride-1),MAX] e.g. - * - * MAX=10, stride = 3, overflow if number > 10 - * end = MAX -> 10 -> 10 + 3 = 13 -> overflow - * end = MAX-1 -> 9 -> 9 + 3 = 12 -> overflow - * end = MAX-2 -> 8 -> 8 + 3 = 11 -> overflow - * end = MAX-3 -> 7 -> 7 + 3 = 10 -> No overflow at MAX - stride - * - * Note that this guard is pessimistic, i.e., it marks loops as potentially overflowing that - * are actually not overflowing. Consider the following loop: - * - *

-         *    for(i = MAX-56; i < MAX, i += 8)
-         * 
- * - * where i in last loop body visit = MAX - 8, i after = MAX, no overflow - * - * which is wrongly detected as overflowing since "end" is element of [MAX-(stride-1),MAX] - * which is [MAX-7,MAX] and end is MAX. We handle such cases with a speculation and disable - * counted loop detection on subsequent compilations. We can only avoid such false positive - * detections by actually computing the number of iterations with a division, however we try - * to avoid that since that may be part of the fast path. - * - * And additional backup strategy could be to actually emit the precise guard inside the - * loop if the deopt already failed, but we refrain from this for now for simplicity - * reasons. - */ - // @formatter:on + /* + * Following comment reasons about the simplest possible loop form: + * + * for(i = 0;i < end;i += stride) + * + * The problem is we want to create an overflow guard for the loop that can be hoisted + * before the loop, i.e., the overflow guard must not have loop variant inputs else it must + * be scheduled inside the loop. This means we cannot refer explicitly to the induction + * variable's phi but must establish a relation between end, stride and max (max integer + * range for a given loop) that is sufficient for most cases. + * + * We know that a head counted loop with a stride > 1 may overflow if the stride is big + * enough that end + stride will be > MAX, i.e. it overflows into negative value range. + * + * It is important that "end" in this context is the checked value of the loop condition: + * i.e., an arbitrary value. There is no relation between end and MAX established except + * that based on the integer representation we know that end <= MAX. + * + * A loop can overflow if the last checked value of the iv allows an overflow in the next + * iteration: the value range for which an overflow can happen is [MAX-(stride-1),MAX] e.g. + * + * MAX=10, stride = 3, overflow if number > 10 + * end = MAX -> 10 -> 10 + 3 = 13 -> overflow + * end = MAX-1 -> 9 -> 9 + 3 = 12 -> overflow + * end = MAX-2 -> 8 -> 8 + 3 = 11 -> overflow + * end = MAX-3 -> 7 -> 7 + 3 = 10 -> No overflow at MAX - stride + * + * Note that this guard is pessimistic, i.e., it marks loops as potentially overflowing that + * are actually not overflowing. Consider the following loop: + * + *
+            *    for(i = MAX-56; i < MAX, i += 8)
+            * 
+ * + * where i in last loop body visit = MAX - 8, i after = MAX, no overflow + * + * which is wrongly detected as overflowing since "end" is element of [MAX-(stride-1),MAX] + * which is [MAX-7,MAX] and end is MAX. We handle such cases with a speculation and disable + * counted loop detection on subsequent compilations. We can only avoid such false positive + * detections by actually computing the number of iterations with a division, however we try + * to avoid that since that may be part of the fast path. + * + * And additional backup strategy could be to actually emit the precise guard inside the + * loop if the deopt already failed, but we refrain from this for now for simplicity + * reasons. + */ + // @formatter:on IntegerStamp endStamp = (IntegerStamp) getTripCountLimit().stamp(NodeView.DEFAULT); - ValueNode strideNode = iv.strideNode(); + ValueNode strideNode = getLimitCheckedIV().strideNode(); IntegerStamp strideStamp = (IntegerStamp) strideNode.stamp(NodeView.DEFAULT); IntegerHelper integerHelper = getCounterIntegerHelper(); if (getDirection() == InductionVariable.Direction.Up) { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/loop/LoopFragmentInside.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/loop/LoopFragmentInside.java index b5b24e507d53..eebb7979d93a 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/loop/LoopFragmentInside.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/loop/LoopFragmentInside.java @@ -343,6 +343,11 @@ protected CompareNode placeNewSegmentAndCleanup(Loop loop, EconomicMap !(x instanceof SafepointNode)).count() == 0 : "Must only have safepoint(association) usages left for " + newSegmentBegin + " usages=" + + newSegmentBegin.usages(); + newSegmentBegin.replaceAtUsages(mainLoopBegin, InputType.Association); } lastCodeNode.replaceFirstSuccessor(loopEndNode, newSegmentFirstNode); newSegmentLastNode.replaceFirstSuccessor(newSegmentEnd, loopEndNode); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/ConditionalEliminationPhase.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/ConditionalEliminationPhase.java index d888a1e323eb..cff85435a7c3 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/ConditionalEliminationPhase.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/ConditionalEliminationPhase.java @@ -29,6 +29,7 @@ import java.util.ArrayDeque; import java.util.Deque; import java.util.List; +import java.util.Objects; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.EconomicSet; @@ -355,7 +356,7 @@ public HIRBlock enter(HIRBlock b) { Speculation speculation = trueGuard.getSpeculation(); if (speculation == null) { speculation = falseGuard.getSpeculation(); - } else if (falseGuard.getSpeculation() != null && falseGuard.getSpeculation() != speculation) { + } else if (falseGuard.getSpeculation() != null && !falseGuard.getSpeculation().equals(speculation)) { // Cannot optimize due to different speculations. continue; } @@ -1107,7 +1108,7 @@ private boolean canScheduleAbove(Node n, Node target, ValueNode knownToBeAbove) protected boolean foldGuard(DeoptimizingGuard thisGuard, DeoptimizingGuard otherGuard, boolean outcome, Stamp guardedValueStamp, ConditionalEliminationUtil.GuardRewirer rewireGuardFunction) { DeoptimizationAction action = mergeActions(otherGuard.getAction(), thisGuard.getAction()); - if (action != null && otherGuard.getSpeculation() == thisGuard.getSpeculation()) { + if (action != null && Objects.equals(otherGuard.getSpeculation(), thisGuard.getSpeculation())) { LogicNode condition = (LogicNode) thisGuard.getCondition().copyWithInputs(); /* * We have ...; guard(C1); guard(C2);... diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/util/LoopUtility.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/util/LoopUtility.java index 40e87e20fcfe..9537165c4c37 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/util/LoopUtility.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/common/util/LoopUtility.java @@ -53,6 +53,7 @@ import jdk.graal.compiler.nodes.cfg.HIRBlock; import jdk.graal.compiler.nodes.extended.OpaqueValueNode; import jdk.graal.compiler.nodes.loop.BasicInductionVariable; +import jdk.graal.compiler.nodes.loop.CountedLoopInfo; import jdk.graal.compiler.nodes.loop.InductionVariable; import jdk.graal.compiler.nodes.loop.Loop; import jdk.graal.compiler.nodes.loop.LoopsData; @@ -61,6 +62,15 @@ public class LoopUtility { + public static long tripCountSignedExact(CountedLoopInfo loop) { + ValueNode maxTripCountNode = loop.maxTripCountNode(); + final long maxTripCountAsSigned = maxTripCountNode.asJavaConstant().asLong(); + if (maxTripCountAsSigned < 0) { + throw new ArithmeticException("Unsigned value " + maxTripCountAsSigned + " overflows signed range"); + } + return maxTripCountAsSigned; + } + public static long addExact(int bits, long a, long b) { if (bits == 8) { byte ba = NumUtil.safeToByteAE(a); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/AllocationSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/AllocationSnippets.java index 6431e7f2b034..7c942f9fff61 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/AllocationSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/AllocationSnippets.java @@ -33,7 +33,6 @@ import org.graalvm.word.LocationIdentity; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import jdk.graal.compiler.nodes.PrefetchAllocateNode; import jdk.graal.compiler.nodes.extended.MembarNode; @@ -121,7 +120,7 @@ protected Object newMultiArrayImpl(Word hub, int rank, boolean withException, in protected UnsignedWord arrayAllocationSize(int length, int arrayBaseOffset, int log2ElementSize) { int alignment = objectAlignment(); - return WordFactory.unsigned(arrayAllocationSize(length, arrayBaseOffset, log2ElementSize, alignment)); + return Word.unsigned(arrayAllocationSize(length, arrayBaseOffset, log2ElementSize, alignment)); } public static long arrayAllocationSize(int length, int arrayBaseOffset, int log2ElementSize, int alignment) { @@ -173,7 +172,7 @@ private void fillMemory(long value, boolean supportsOptimizedFilling, AllocationSnippetCounters snippetCounters) { ReplacementsUtil.dynamicAssert(endOffset.and(0x7).equal(0), "unaligned object size"); - UnsignedWord offset = WordFactory.unsigned(startOffset); + UnsignedWord offset = Word.unsigned(startOffset); if (probability(SLOW_PATH_PROBABILITY, offset.and(0x7).notEqual(0))) { memory.writeInt(offset, (int) value, LocationIdentity.init()); offset = offset.add(4); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/BigIntegerSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/BigIntegerSnippets.java index 47602c2714b2..9281fb0f2362 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/BigIntegerSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/BigIntegerSnippets.java @@ -30,7 +30,6 @@ import jdk.graal.compiler.phases.util.Providers; import jdk.graal.compiler.replacements.nodes.BigIntegerMultiplyToLenNode; import jdk.graal.compiler.word.Word; -import org.graalvm.word.WordFactory; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.MetaAccessProvider; @@ -67,6 +66,6 @@ public static int[] implMultiplyToLen(int[] x, int xlen, int[] y, int ylen, int[ } private static Word arrayStart(int[] a) { - return WordFactory.unsigned(ComputeObjectAddressNode.get(a, ReplacementsUtil.getArrayBaseOffset(INJECTED_METAACCESS, JavaKind.Int))); + return Word.unsigned(ComputeObjectAddressNode.get(a, ReplacementsUtil.getArrayBaseOffset(INJECTED_METAACCESS, JavaKind.Int))); } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/StandardGraphBuilderPlugins.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/StandardGraphBuilderPlugins.java index cfd551a4a559..b09ce98fa0fc 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/StandardGraphBuilderPlugins.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/StandardGraphBuilderPlugins.java @@ -226,6 +226,7 @@ import jdk.graal.compiler.replacements.nodes.arithmetic.UnsignedMulHighNode; import jdk.graal.compiler.serviceprovider.JavaVersionUtil; import jdk.graal.compiler.serviceprovider.SpeculationReasonGroup; +import jdk.vm.ci.amd64.AMD64; import jdk.vm.ci.code.Architecture; import jdk.vm.ci.code.BytecodePosition; import jdk.vm.ci.code.CodeUtil; @@ -2615,6 +2616,11 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec return true; } } + + @Override + public boolean isGraalOnly() { + return JavaVersionUtil.JAVA_SPEC == 21 && arch instanceof AMD64; + } }); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/gc/G1WriteBarrierSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/gc/G1WriteBarrierSnippets.java index 1646221d8947..e516ec612c07 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/gc/G1WriteBarrierSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/gc/G1WriteBarrierSnippets.java @@ -31,7 +31,6 @@ import org.graalvm.word.LocationIdentity; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import jdk.graal.compiler.api.directives.GraalDirectives; import jdk.graal.compiler.api.replacements.Snippet; @@ -127,7 +126,7 @@ private void satbBarrier(Address address, Object object, Object expectedObject, boolean trace = isTracingActive(traceStartCycle); int gcCycle = 0; if (trace) { - Pointer gcTotalCollectionsAddress = WordFactory.pointer(gcTotalCollectionsAddress()); + Pointer gcTotalCollectionsAddress = Word.pointer(gcTotalCollectionsAddress()); gcCycle = gcTotalCollectionsAddress.readInt(0, LocationIdentity.any()); log(trace, "[%d] G1-Pre Thread %p Object %p\n", gcCycle, thread.rawValue(), Word.objectToTrackedPointer(object).rawValue()); log(trace, "[%d] G1-Pre Thread %p Expected Object %p\n", gcCycle, thread.rawValue(), Word.objectToTrackedPointer(expectedObject).rawValue()); @@ -195,7 +194,7 @@ public void g1PostWriteBarrier(Address address, Object object, Object value, @Co boolean trace = isTracingActive(traceStartCycle); int gcCycle = 0; if (trace) { - Pointer gcTotalCollectionsAddress = WordFactory.pointer(gcTotalCollectionsAddress()); + Pointer gcTotalCollectionsAddress = Word.pointer(gcTotalCollectionsAddress()); gcCycle = gcTotalCollectionsAddress.readInt(0, LocationIdentity.any()); log(trace, "[%d] G1-Post Thread: %p Object: %p\n", gcCycle, thread.rawValue(), Word.objectToTrackedPointer(object).rawValue()); log(trace, "[%d] G1-Post Thread: %p Field: %p\n", gcCycle, thread.rawValue(), oop.rawValue()); @@ -223,7 +222,7 @@ public void g1PostWriteBarrier(Address address, Object object, Object value, @Co MembarNode.memoryBarrier(MembarNode.FenceKind.STORE_LOAD, GC_CARD_LOCATION); byte cardByteReload = cardAddress.readByte(0, GC_CARD_LOCATION); if (probability(NOT_FREQUENT_PROBABILITY, cardByteReload != dirtyCardValue())) { - log(trace, "[%d] G1-Post Thread: %p Card: %p \n", gcCycle, thread.rawValue(), WordFactory.unsigned((int) cardByte).rawValue()); + log(trace, "[%d] G1-Post Thread: %p Card: %p \n", gcCycle, thread.rawValue(), Word.unsigned((int) cardByte).rawValue()); cardAddress.writeByte(0, dirtyCardValue(), GC_CARD_LOCATION); counters.g1ExecutedPostWriteBarrierCounter.inc(); @@ -263,16 +262,16 @@ public void g1ArrayRangePreWriteBarrier(Address address, long length, @ConstantP Word start = getPointerToFirstArrayElement(address, length, elementStride); for (int i = 0; GraalDirectives.injectIterationCount(10, i < length); i++) { - Word arrElemPtr = start.add(WordFactory.unsigned(i * scale)); + Word arrElemPtr = start.add(Word.unsigned(i * scale)); Object previousObject = arrElemPtr.readObject(0, BarrierType.NONE, LocationIdentity.any()); verifyOop(previousObject); if (probability(FREQUENT_PROBABILITY, previousObject != null)) { if (probability(FREQUENT_PROBABILITY, indexValue != 0)) { indexValue = indexValue - wordSize(); - Word logAddress = bufferAddress.add(WordFactory.unsigned(indexValue)); + Word logAddress = bufferAddress.add(Word.unsigned(indexValue)); // Log the object to be marked and update the SATB's buffer next index. logAddress.writeWord(0, Word.objectToTrackedPointer(previousObject), SATB_QUEUE_LOG_LOCATION); - indexAddress.writeWord(0, WordFactory.unsigned(indexValue), SATB_QUEUE_INDEX_LOCATION); + indexAddress.writeWord(0, Word.unsigned(indexValue), SATB_QUEUE_INDEX_LOCATION); } else { g1PreBarrierStub(previousObject); } @@ -307,11 +306,11 @@ public void g1ArrayRangePostWriteBarrier(Address address, long length, @Constant // initialize a new one and add the card entry. if (probability(FREQUENT_PROBABILITY, indexValue != 0)) { indexValue = indexValue - wordSize(); - Word logAddress = bufferAddress.add(WordFactory.unsigned(indexValue)); + Word logAddress = bufferAddress.add(Word.unsigned(indexValue)); // Log the object to be scanned as well as update // the card queue's next index. logAddress.writeWord(0, cur, CARD_QUEUE_LOG_LOCATION); - indexAddress.writeWord(0, WordFactory.unsigned(indexValue), CARD_QUEUE_INDEX_LOCATION); + indexAddress.writeWord(0, Word.unsigned(indexValue), CARD_QUEUE_INDEX_LOCATION); } else { g1PostBarrierStub(cur); } @@ -366,7 +365,7 @@ public void g1ArrayRangePostWriteBarrier(Address address, long length, @Constant protected abstract ForeignCallDescriptor printfCallDescriptor(); protected boolean isTracingActive(int traceStartCycle) { - return traceStartCycle > 0 && ((Pointer) WordFactory.pointer(gcTotalCollectionsAddress())).readInt(0) > traceStartCycle; + return traceStartCycle > 0 && ((Pointer) Word.pointer(gcTotalCollectionsAddress())).readInt(0) > traceStartCycle; } private void log(boolean enabled, String format, long value1, long value2, long value3) { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/gc/WriteBarrierSnippets.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/gc/WriteBarrierSnippets.java index 530a2857e7b8..a227e09e48b4 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/gc/WriteBarrierSnippets.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/gc/WriteBarrierSnippets.java @@ -35,7 +35,6 @@ import jdk.graal.compiler.replacements.nodes.AssertionNode; import jdk.graal.compiler.word.Word; import org.graalvm.word.LocationIdentity; -import org.graalvm.word.WordFactory; public abstract class WriteBarrierSnippets { public static final LocationIdentity GC_CARD_LOCATION = NamedLocationIdentity.mutable("GC-Card"); @@ -53,7 +52,7 @@ protected static Word getPointerToFirstArrayElement(Address address, long length // the address points to the place after the last array element result = result + elementStride * length; } - return WordFactory.unsigned(result); + return Word.unsigned(result); } protected static Word getPointerToLastArrayElement(Address address, long length, int elementStride) { @@ -64,6 +63,6 @@ protected static Word getPointerToLastArrayElement(Address address, long length, } else { result = result + (length - 1) * elementStride; } - return WordFactory.unsigned(result); + return Word.unsigned(result); } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/AbstractKnownTruffleTypes.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/AbstractKnownTruffleTypes.java index 0ed5433248c1..163269812787 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/AbstractKnownTruffleTypes.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/AbstractKnownTruffleTypes.java @@ -137,15 +137,19 @@ protected ResolvedJavaField[] findInstanceFields(ResolvedJavaType declaringClass return getTypeCache(declaringClass).instanceFields; } - protected final ResolvedJavaField findField(ResolvedJavaType declaringClass, String name) { + protected final ResolvedJavaField findField(ResolvedJavaType declaringClass, String name, boolean required) { TypeCache fc = getTypeCache(declaringClass); ResolvedJavaField field = fc.fields.get(name); - if (field == null) { + if (field == null && required) { throw new GraalError("Could not find required field %s.%s", declaringClass.getName(), name); } return field; } + protected final ResolvedJavaField findField(ResolvedJavaType declaringClass, String name) { + return findField(declaringClass, name, true); + } + private TypeCache getTypeCache(ResolvedJavaType declaringClass) { if (typeCache == null || !typeCache.declaringClass.equals(declaringClass)) { GraalError.shouldNotReachHere("Use lookupTypeCached instead to lookup methods or fields."); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/KnownTruffleTypes.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/KnownTruffleTypes.java index 2a27d2085a8d..280ba66ea823 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/KnownTruffleTypes.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/KnownTruffleTypes.java @@ -104,7 +104,9 @@ public class KnownTruffleTypes extends AbstractKnownTruffleTypes { public final ResolvedJavaField FrameDescriptor_defaultValue = findField(FrameDescriptor, "defaultValue"); public final ResolvedJavaField FrameDescriptor_materializeCalled = findField(FrameDescriptor, "materializeCalled"); public final ResolvedJavaField FrameDescriptor_indexedSlotTags = findField(FrameDescriptor, "indexedSlotTags"); + public final ResolvedJavaField FrameDescriptor_indexedSlotCount = findField(FrameDescriptor, "indexedSlotCount", false); public final ResolvedJavaField FrameDescriptor_auxiliarySlotCount = findField(FrameDescriptor, "auxiliarySlotCount"); + public final ResolvedJavaField FrameDescriptor_illegalDefaultValue = findField(FrameDescriptor, "ILLEGAL_DEFAULT_VALUE", false); public final ResolvedJavaType FrameSlotKind = lookupTypeCached("com.oracle.truffle.api.frame.FrameSlotKind"); public final ResolvedJavaField FrameSlotKind_Object = findField(FrameSlotKind, "Object"); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/nodes/frame/NewFrameNode.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/nodes/frame/NewFrameNode.java index b004624cec31..79a9776e0cb9 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/nodes/frame/NewFrameNode.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/nodes/frame/NewFrameNode.java @@ -188,16 +188,24 @@ public NewFrameNode(GraphBuilderContext b, ValueNode frameDescriptorNode, ValueN this.frameDefaultValue = ConstantNode.forConstant(defaultValue, metaAccess, graph); JavaConstant indexedTagsArray = constantReflection.readFieldValue(types.FrameDescriptor_indexedSlotTags, frameDescriptor); - this.indexedFrameSize = constantReflection.readArrayLength(indexedTagsArray); + if (types.FrameDescriptor_indexedSlotCount == null) { + this.indexedFrameSize = constantReflection.readArrayLength(indexedTagsArray); + } else { + this.indexedFrameSize = constantReflection.readFieldValue(types.FrameDescriptor_indexedSlotCount, frameDescriptor).asInt(); + } byte[] indexedFrameSlotKindsCandidate = new byte[indexedFrameSize]; - final int indexedTagsArrayLength = constantReflection.readArrayLength(indexedTagsArray); - for (int i = 0; i < indexedTagsArrayLength; i++) { - final int slot = constantReflection.readArrayElement(indexedTagsArray, i).asInt(); - if (slot == FrameSlotKindStaticTag) { - indexedFrameSlotKindsCandidate[i] = FrameSlotKindStaticTag; - } else { - indexedFrameSlotKindsCandidate[i] = FrameSlotKindLongTag; + if (indexedTagsArray.isNull()) { + Arrays.fill(indexedFrameSlotKindsCandidate, FrameSlotKindLongTag); + } else { + final int indexedTagsArrayLength = constantReflection.readArrayLength(indexedTagsArray); + for (int i = 0; i < indexedTagsArrayLength; i++) { + final int slot = constantReflection.readArrayElement(indexedTagsArray, i).asInt(); + if (slot == FrameSlotKindStaticTag) { + indexedFrameSlotKindsCandidate[i] = FrameSlotKindStaticTag; + } else { + indexedFrameSlotKindsCandidate[i] = FrameSlotKindLongTag; + } } } this.indexedFrameSlotKinds = indexedFrameSlotKindsCandidate; @@ -298,8 +306,20 @@ public void virtualize(VirtualizerTool tool) { ValueNode[] indexedPrimitiveArrayEntryState = new ValueNode[indexedFrameSize]; ValueNode[] indexedTagArrayEntryState = new ValueNode[indexedFrameSize]; - Arrays.fill(indexedObjectArrayEntryState, frameDefaultValue); - Arrays.fill(indexedTagArrayEntryState, smallIntConstants.get(0)); + JavaConstant illegalDefaultValue = null; + // the field may not be defined in older Truffle versions. + if (types.FrameDescriptor_illegalDefaultValue != null) { + illegalDefaultValue = tool.getConstantReflection().readFieldValue(types.FrameDescriptor_illegalDefaultValue, null); + } + + if (illegalDefaultValue != null && tool.getConstantReflection().constantEquals(frameDefaultValue.asJavaConstant(), illegalDefaultValue)) { + Arrays.fill(indexedObjectArrayEntryState, ConstantNode.defaultForKind(JavaKind.Object, graph())); + Arrays.fill(indexedTagArrayEntryState, smallIntConstants.get(FrameSlotKindIllegalTag)); + } else { + Arrays.fill(indexedObjectArrayEntryState, frameDefaultValue); + Arrays.fill(indexedTagArrayEntryState, smallIntConstants.get(0)); + } + Arrays.fill(indexedPrimitiveArrayEntryState, defaultLong); tool.createVirtualObject((VirtualObjectNode) virtualFrameArrays.get(INDEXED_OBJECT_ARRAY), indexedObjectArrayEntryState, Collections. emptyList(), sourcePosition, false); tool.createVirtualObject((VirtualObjectNode) virtualFrameArrays.get(INDEXED_PRIMITIVE_ARRAY), indexedPrimitiveArrayEntryState, Collections. emptyList(), sourcePosition, diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleGraphBuilderPlugins.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleGraphBuilderPlugins.java index 4d630932e245..e1241a74112f 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleGraphBuilderPlugins.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleGraphBuilderPlugins.java @@ -590,7 +590,7 @@ public static void registerFrameWithoutBoxingPlugins(InvocationPlugins plugins, registerFrameAccessors(r, types, JavaKind.Byte); int accessTag = types.FrameSlotKind_javaKindToTagIndex.get(JavaKind.Object); - registerGet(r, JavaKind.Object, accessTag, "unsafeUncheckedGet", JavaKind.Object.name()); + registerGet(r, JavaKind.Object, accessTag, "unsafeUncheckedGet" + JavaKind.Object.name(), true); registerOSRFrameTransferMethods(r); @@ -621,11 +621,10 @@ private static void registerFrameAccessors(Registration r, KnownTruffleTypes typ int accessTag = types.FrameSlotKind_javaKindToTagIndex.get(accessKind); String nameSuffix = accessKind.name(); boolean isPrimitiveAccess = accessKind.isPrimitive(); - String[] indexedGetPrefixes = new String[]{"get", "unsafeGet", "expect", "unsafeExpect"}; - for (String prefix : indexedGetPrefixes) { - registerGet(r, accessKind, accessTag, prefix, nameSuffix); + registerGet(r, accessKind, accessTag, "get" + nameSuffix, false); + for (String prefix : new String[]{"unsafeGet", "expect", "unsafeExpect"}) { + registerGet(r, accessKind, accessTag, prefix + nameSuffix, true); } - r.register(new RequiredInvocationPlugin("get" + nameSuffix + "Static", Receiver.class, int.class) { @Override public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver frameNode, ValueNode frameSlotNode) { @@ -639,20 +638,8 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec } }); - String[] indexedSetPrefixes = new String[]{"set", "unsafeSet"}; - for (String prefix : indexedSetPrefixes) { - r.register(new RequiredInvocationPlugin(prefix + nameSuffix, Receiver.class, int.class, getJavaClass(accessKind)) { - @Override - public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver frameNode, ValueNode frameSlotNode, ValueNode value) { - int frameSlotIndex = maybeGetConstantNumberedFrameSlotIndex(frameNode, frameSlotNode); - if (frameSlotIndex >= 0) { - b.add(new VirtualFrameSetNode(frameNode, frameSlotIndex, accessTag, value, VirtualFrameAccessType.Indexed, VirtualFrameAccessFlags.NON_STATIC_UPDATE)); - return true; - } - return false; - } - }); - } + registerSet(r, accessKind, accessTag, "set" + nameSuffix, false); + registerSet(r, accessKind, accessTag, "unsafeSet" + nameSuffix, true); r.register(new RequiredInvocationPlugin("set" + nameSuffix + "Static", Receiver.class, int.class, getJavaClass(accessKind)) { @Override public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver frameNode, ValueNode frameSlotNode, ValueNode value) { @@ -678,8 +665,8 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec }); } - private static void registerGet(Registration r, JavaKind accessKind, int accessTag, String prefix, String nameSuffix) { - r.register(new RequiredInvocationPlugin(prefix + nameSuffix, Receiver.class, int.class) { + private static void registerGet(Registration r, JavaKind accessKind, int accessTag, String name, boolean optional) { + r.register(new InvocationPlugin(name, Receiver.class, int.class) { @Override public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver frameNode, ValueNode frameSlotNode) { int frameSlotIndex = maybeGetConstantNumberedFrameSlotIndex(frameNode, frameSlotNode); @@ -689,6 +676,70 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec } return false; } + + @Override + public boolean isOptional() { + return optional; + } + }); + } + + private static void registerSet(Registration r, JavaKind accessKind, int accessTag, String name, boolean optional) { + r.register(new InvocationPlugin(name, Receiver.class, int.class, getJavaClass(accessKind)) { + @Override + public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver frameNode, ValueNode frameSlotNode, ValueNode value) { + int frameSlotIndex = maybeGetConstantNumberedFrameSlotIndex(frameNode, frameSlotNode); + if (frameSlotIndex >= 0) { + b.add(new VirtualFrameSetNode(frameNode, frameSlotIndex, accessTag, value, VirtualFrameAccessType.Indexed, VirtualFrameAccessFlags.NON_STATIC_UPDATE)); + return true; + } + return false; + } + + @Override + public boolean isOptional() { + return optional; + } + }); + } + + private static void registerCopy(Registration r, String name, boolean optional) { + r.register(new InvocationPlugin(name, Receiver.class, int.class, int.class) { + @Override + public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode frameSlot1, ValueNode frameSlot2) { + int frameSlot1Index = maybeGetConstantNumberedFrameSlotIndex(receiver, frameSlot1); + int frameSlot2Index = maybeGetConstantNumberedFrameSlotIndex(receiver, frameSlot2); + if (frameSlot1Index >= 0 && frameSlot2Index >= 0) { + b.add(new VirtualFrameCopyNode(receiver, frameSlot1Index, frameSlot2Index, VirtualFrameAccessType.Indexed, VirtualFrameAccessFlags.NON_STATIC_UPDATE)); + return true; + } + return false; + } + + @Override + public boolean isOptional() { + return optional; + } + }); + } + + private static void registerClear(Registration r, String name, int illegalTag, boolean optional) { + r.register(new InvocationPlugin(name, Receiver.class, int.class) { + @Override + public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode frameSlot) { + int frameSlotIndex = maybeGetConstantNumberedFrameSlotIndex(receiver, frameSlot); + if (frameSlotIndex >= 0) { + b.add(new VirtualFrameClearNode(receiver, frameSlotIndex, illegalTag, VirtualFrameAccessType.Indexed, + VirtualFrameAccessFlags.NON_STATIC_UPDATE)); + return true; + } + return false; + } + + @Override + public boolean isOptional() { + return optional; + } }); } @@ -814,34 +865,10 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec } }); - for (String prefix : new String[]{"", "unsafe"}) { - r.register(new RequiredInvocationPlugin(buildCamelCaseName(prefix, "copy"), Receiver.class, int.class, int.class) { - @Override - public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode frameSlot1, ValueNode frameSlot2) { - int frameSlot1Index = maybeGetConstantNumberedFrameSlotIndex(receiver, frameSlot1); - int frameSlot2Index = maybeGetConstantNumberedFrameSlotIndex(receiver, frameSlot2); - if (frameSlot1Index >= 0 && frameSlot2Index >= 0) { - b.add(new VirtualFrameCopyNode(receiver, frameSlot1Index, frameSlot2Index, VirtualFrameAccessType.Indexed, VirtualFrameAccessFlags.NON_STATIC_UPDATE)); - return true; - } - return false; - } - }); - - r.register(new RequiredInvocationPlugin(buildCamelCaseName(prefix, "clear"), Receiver.class, int.class) { - @Override - public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode frameSlot) { - int frameSlotIndex = maybeGetConstantNumberedFrameSlotIndex(receiver, frameSlot); - if (frameSlotIndex >= 0) { - b.add(new VirtualFrameClearNode(receiver, frameSlotIndex, illegalTag, VirtualFrameAccessType.Indexed, - VirtualFrameAccessFlags.NON_STATIC_UPDATE)); - return true; - } - return false; - } - }); - - } + registerCopy(r, "copy", false); + registerCopy(r, "unsafeCopy", true); + registerClear(r, "clear", illegalTag, false); + registerClear(r, "unsafeClear", illegalTag, true); r.register(new RequiredInvocationPlugin("clearPrimitiveStatic", Receiver.class, int.class) { @Override @@ -968,21 +995,6 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec }); } - private static String buildCamelCaseName(String prefix, String name) { - if (prefix.isEmpty()) { - return name; - } else { - return prefix + firstLetterUpperCase(name); - } - } - - private static String firstLetterUpperCase(String name) { - if (name == null || name.isEmpty()) { - return name; - } - return Character.toUpperCase(name.charAt(0)) + name.substring(1, name.length()); - } - public static void registerNodePlugins(InvocationPlugins plugins, KnownTruffleTypes types, MetaAccessProvider metaAccess, boolean canDelayIntrinsification, ConstantReflectionProvider constantReflection) { Registration r = new Registration(plugins, new ResolvedJavaSymbol(types.Node)); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleInvocationPlugins.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleInvocationPlugins.java index 74cfba77807d..fc315a6593ad 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleInvocationPlugins.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/substitutions/TruffleInvocationPlugins.java @@ -90,8 +90,7 @@ private static void registerBytecodePlugins(InvocationPlugins plugins, Replaceme plugins.registerIntrinsificationPredicate(t -> t.getName().equals("Lcom/oracle/truffle/api/bytecode/BytecodeDSLUncheckedAccess;")); InvocationPlugins.Registration r = new InvocationPlugins.Registration(plugins, "com.oracle.truffle.api.bytecode.BytecodeDSLUncheckedAccess", replacements); - r.register(new InlineOnlyInvocationPlugin("uncheckedCast", Receiver.class, Object.class, - Class.class) { + r.register(new InvocationPlugin("uncheckedCast", Receiver.class, Object.class, Class.class) { @Override public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode object, ValueNode clazz) { @@ -111,6 +110,16 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec return true; } } + + @Override + public boolean inlineOnly() { + return true; + } + + @Override + public boolean isOptional() { + return true; + } }); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/word/Word.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/word/Word.java index d4bf78510df2..0436883335e7 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/word/Word.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/word/Word.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, 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 @@ -32,11 +32,10 @@ import org.graalvm.word.ComparableWord; import org.graalvm.word.LocationIdentity; import org.graalvm.word.Pointer; +import org.graalvm.word.PointerBase; import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; -import org.graalvm.word.impl.WordBoxFactory; import jdk.graal.compiler.core.common.calc.Condition; import jdk.graal.compiler.core.common.calc.UnsignedMath; @@ -58,17 +57,77 @@ import jdk.graal.compiler.nodes.calc.XorNode; import jdk.graal.compiler.nodes.memory.address.AddressNode.Address; import jdk.internal.misc.Unsafe; +import org.graalvm.word.WordFactory; +import org.graalvm.word.impl.WordFactoryOpcode; +import org.graalvm.word.impl.WordFactoryOperation; +/** + * This is the compiler-specific implementation and extension of the word types declared in + * {@code org.graalvm.word}. It includes factory methods to create boxed word values that should be + * used instead of the methods with the same signature in {@link WordFactory}. + */ public abstract class Word implements SignedWord, UnsignedWord, Pointer { private static final Unsafe UNSAFE = Unsafe.getUnsafe(); - static { - BoxFactoryImpl.initialize(); + /** + * @see org.graalvm.word.WordFactory#zero() + */ + @WordFactoryOperation(opcode = WordFactoryOpcode.ZERO) + public static T zero() { + return box(0L); } - public static void ensureInitialized() { - /* Calling this method ensures that the static initializer has been executed. */ + /** + * @see org.graalvm.word.WordFactory#nullPointer() + */ + @WordFactoryOperation(opcode = WordFactoryOpcode.ZERO) + public static T nullPointer() { + return box(0L); + } + + /** + * In an execution environment where this method returns a boxed value (e.g. not in Native + * Image), the returned value supports all of the {@link Pointer} memory access operations + * (i.e., read, write, compare-and-swap etc.) through use of {@link Unsafe}. + * + * @see org.graalvm.word.WordFactory#pointer(long) + */ + @WordFactoryOperation(opcode = WordFactoryOpcode.FROM_UNSIGNED) + public static T pointer(long val) { + return box(val); + } + + /** + * @see org.graalvm.word.WordFactory#unsigned(int) + */ + @WordFactoryOperation(opcode = WordFactoryOpcode.FROM_UNSIGNED) + public static T unsigned(int val) { + return box(val & 0xffffffffL); + } + + /** + * @see org.graalvm.word.WordFactory#unsigned(long) + */ + @WordFactoryOperation(opcode = WordFactoryOpcode.FROM_UNSIGNED) + public static T unsigned(long val) { + return box(val); + } + + /** + * @see org.graalvm.word.WordFactory#signed(int) + */ + @WordFactoryOperation(opcode = WordFactoryOpcode.FROM_SIGNED) + public static T signed(int val) { + return box(val); + } + + /** + * @see org.graalvm.word.WordFactory#signed(long) + */ + @WordFactoryOperation(opcode = WordFactoryOpcode.FROM_SIGNED) + public static T signed(long val) { + return box(val); } /** @@ -119,19 +178,6 @@ public enum Opcode { TO_RAW_VALUE, } - static class BoxFactoryImpl extends WordBoxFactory { - static void initialize() { - assert boxFactory == null : "BoxFactory must be initialized only once."; - boxFactory = new BoxFactoryImpl(); - } - - @SuppressWarnings("unchecked") - @Override - public T boxImpl(long val) { - return (T) HostedWord.boxLong(val); - } - } - /* * Outside users must use the different signed() and unsigned() methods to ensure proper * expansion of 32-bit values on 64-bit systems. @@ -141,6 +187,13 @@ private static T box(long val) { return (T) HostedWord.boxLong(val); } + private static Word cast(WordBase val) { + if (val instanceof Word word) { + return word; + } + return HostedWord.boxLong(val.rawValue()); + } + protected abstract long unbox(); private static Word intParam(int val) { @@ -195,13 +248,13 @@ public long rawValue() { @Override @Operation(node = AddNode.class) public Word add(SignedWord val) { - return add((Word) val); + return add(cast(val)); } @Override @Operation(node = AddNode.class) public Word add(UnsignedWord val) { - return add((Word) val); + return add(cast(val)); } @Override @@ -218,13 +271,13 @@ public Word add(Word val) { @Override @Operation(node = SubNode.class) public Word subtract(SignedWord val) { - return subtract((Word) val); + return subtract(cast(val)); } @Override @Operation(node = SubNode.class) public Word subtract(UnsignedWord val) { - return subtract((Word) val); + return subtract(cast(val)); } @Override @@ -241,13 +294,13 @@ public Word subtract(Word val) { @Override @Operation(node = MulNode.class) public Word multiply(SignedWord val) { - return multiply((Word) val); + return multiply(cast(val)); } @Override @Operation(node = MulNode.class) public Word multiply(UnsignedWord val) { - return multiply((Word) val); + return multiply(cast(val)); } @Override @@ -264,7 +317,7 @@ public Word multiply(Word val) { @Override @Operation(opcode = Opcode.INTEGER_DIVISION_NODE_CLASS, node = SignedDivNode.class) public Word signedDivide(SignedWord val) { - return signedDivide((Word) val); + return signedDivide(cast(val)); } @Override @@ -281,13 +334,13 @@ public Word signedDivide(Word val) { @Override @Operation(opcode = Opcode.INTEGER_DIVISION_NODE_CLASS, node = UnsignedDivNode.class) public Word unsignedDivide(UnsignedWord val) { - return unsignedDivide((Word) val); + return unsignedDivide(cast(val)); } @Override @Operation(opcode = Opcode.INTEGER_DIVISION_NODE_CLASS, node = UnsignedDivNode.class) public Word unsignedDivide(int val) { - return signedDivide(intParam(val)); + return unsignedDivide(intParam(val)); } @Operation(opcode = Opcode.INTEGER_DIVISION_NODE_CLASS, node = UnsignedDivNode.class) @@ -298,7 +351,7 @@ public Word unsignedDivide(Word val) { @Override @Operation(opcode = Opcode.INTEGER_DIVISION_NODE_CLASS, node = SignedRemNode.class) public Word signedRemainder(SignedWord val) { - return signedRemainder((Word) val); + return signedRemainder(cast(val)); } @Override @@ -315,7 +368,7 @@ public Word signedRemainder(Word val) { @Override @Operation(opcode = Opcode.INTEGER_DIVISION_NODE_CLASS, node = UnsignedRemNode.class) public Word unsignedRemainder(UnsignedWord val) { - return unsignedRemainder((Word) val); + return unsignedRemainder(cast(val)); } @Override @@ -332,7 +385,7 @@ public Word unsignedRemainder(Word val) { @Override @Operation(node = LeftShiftNode.class, rightOperandIsInt = true) public Word shiftLeft(UnsignedWord val) { - return shiftLeft((Word) val); + return shiftLeft(cast(val)); } @Override @@ -349,7 +402,7 @@ public Word shiftLeft(Word val) { @Override @Operation(node = RightShiftNode.class, rightOperandIsInt = true) public Word signedShiftRight(UnsignedWord val) { - return signedShiftRight((Word) val); + return signedShiftRight(cast(val)); } @Override @@ -366,7 +419,7 @@ public Word signedShiftRight(Word val) { @Override @Operation(node = UnsignedRightShiftNode.class, rightOperandIsInt = true) public Word unsignedShiftRight(UnsignedWord val) { - return unsignedShiftRight((Word) val); + return unsignedShiftRight(cast(val)); } @Override @@ -383,13 +436,13 @@ public Word unsignedShiftRight(Word val) { @Override @Operation(node = AndNode.class) public Word and(SignedWord val) { - return and((Word) val); + return and(cast(val)); } @Override @Operation(node = AndNode.class) public Word and(UnsignedWord val) { - return and((Word) val); + return and(cast(val)); } @Override @@ -406,13 +459,13 @@ public Word and(Word val) { @Override @Operation(node = OrNode.class) public Word or(SignedWord val) { - return or((Word) val); + return or(cast(val)); } @Override @Operation(node = OrNode.class) public Word or(UnsignedWord val) { - return or((Word) val); + return or(cast(val)); } @Override @@ -429,13 +482,13 @@ public Word or(Word val) { @Override @Operation(node = XorNode.class) public Word xor(SignedWord val) { - return xor((Word) val); + return xor(cast(val)); } @Override @Operation(node = XorNode.class) public Word xor(UnsignedWord val) { - return xor((Word) val); + return xor(cast(val)); } @Override @@ -458,31 +511,31 @@ public Word not() { @Override @Operation(opcode = Opcode.IS_NULL) public boolean isNull() { - return equal(WordFactory.zero()); + return equal(zero()); } @Override @Operation(opcode = Opcode.IS_NON_NULL) public boolean isNonNull() { - return notEqual(WordFactory.zero()); + return notEqual(zero()); } @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.EQ) public boolean equal(ComparableWord val) { - return equal((Word) val); + return equal(cast(val)); } @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.EQ) public boolean equal(SignedWord val) { - return equal((Word) val); + return rawValue() == val.rawValue(); } @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.EQ) public boolean equal(UnsignedWord val) { - return equal((Word) val); + return equal(cast(val)); } @Override @@ -493,25 +546,25 @@ public boolean equal(int val) { @Operation(opcode = Opcode.COMPARISON, condition = Condition.EQ) public boolean equal(Word val) { - return unbox() == val.unbox(); + return rawValue() == val.unbox(); } @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.NE) public boolean notEqual(ComparableWord val) { - return notEqual((Word) val); + return notEqual(cast(val)); } @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.NE) public boolean notEqual(SignedWord val) { - return notEqual((Word) val); + return notEqual(cast(val)); } @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.NE) public boolean notEqual(UnsignedWord val) { - return notEqual((Word) val); + return notEqual(cast(val)); } @Override @@ -528,7 +581,7 @@ public boolean notEqual(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.LT) public boolean lessThan(SignedWord val) { - return lessThan((Word) val); + return lessThan(cast(val)); } @Override @@ -545,7 +598,7 @@ public boolean lessThan(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.LE) public boolean lessOrEqual(SignedWord val) { - return lessOrEqual((Word) val); + return lessOrEqual(cast(val)); } @Override @@ -562,7 +615,7 @@ public boolean lessOrEqual(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.GT) public boolean greaterThan(SignedWord val) { - return greaterThan((Word) val); + return greaterThan(cast(val)); } @Override @@ -579,7 +632,7 @@ public boolean greaterThan(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.GE) public boolean greaterOrEqual(SignedWord val) { - return greaterOrEqual((Word) val); + return greaterOrEqual(cast(val)); } @Override @@ -596,7 +649,7 @@ public boolean greaterOrEqual(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.BT) public boolean belowThan(UnsignedWord val) { - return belowThan((Word) val); + return belowThan(cast(val)); } @Override @@ -613,7 +666,7 @@ public boolean belowThan(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.BE) public boolean belowOrEqual(UnsignedWord val) { - return belowOrEqual((Word) val); + return belowOrEqual(cast(val)); } @Override @@ -630,7 +683,7 @@ public boolean belowOrEqual(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.AT) public boolean aboveThan(UnsignedWord val) { - return aboveThan((Word) val); + return aboveThan(cast(val)); } @Override @@ -647,7 +700,7 @@ public boolean aboveThan(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.AE) public boolean aboveOrEqual(UnsignedWord val) { - return aboveOrEqual((Word) val); + return aboveOrEqual(cast(val)); } @Override @@ -664,49 +717,49 @@ public boolean aboveOrEqual(Word val) { @Override @Operation(opcode = Opcode.READ_POINTER) public byte readByte(WordBase offset, LocationIdentity locationIdentity) { - return UNSAFE.getByte(add((Word) offset).unbox()); + return UNSAFE.getByte(add(cast(offset)).unbox()); } @Override @Operation(opcode = Opcode.READ_POINTER) public char readChar(WordBase offset, LocationIdentity locationIdentity) { - return UNSAFE.getChar(add((Word) offset).unbox()); + return UNSAFE.getChar(add(cast(offset)).unbox()); } @Override @Operation(opcode = Opcode.READ_POINTER) public short readShort(WordBase offset, LocationIdentity locationIdentity) { - return UNSAFE.getShort(add((Word) offset).unbox()); + return UNSAFE.getShort(add(cast(offset)).unbox()); } @Override @Operation(opcode = Opcode.READ_POINTER) public int readInt(WordBase offset, LocationIdentity locationIdentity) { - return UNSAFE.getInt(add((Word) offset).unbox()); + return UNSAFE.getInt(add(cast(offset)).unbox()); } @Override @Operation(opcode = Opcode.READ_POINTER) public long readLong(WordBase offset, LocationIdentity locationIdentity) { - return UNSAFE.getLong(add((Word) offset).unbox()); + return UNSAFE.getLong(add(cast(offset)).unbox()); } @Override @Operation(opcode = Opcode.READ_POINTER) public float readFloat(WordBase offset, LocationIdentity locationIdentity) { - return UNSAFE.getFloat(add((Word) offset).unbox()); + return UNSAFE.getFloat(add(cast(offset)).unbox()); } @Override @Operation(opcode = Opcode.READ_POINTER) public double readDouble(WordBase offset, LocationIdentity locationIdentity) { - return UNSAFE.getDouble(add((Word) offset).unbox()); + return UNSAFE.getDouble(add(cast(offset)).unbox()); } @Override @Operation(opcode = Opcode.READ_POINTER) public T readWord(WordBase offset, LocationIdentity locationIdentity) { - return box(UNSAFE.getAddress(add((Word) offset).unbox())); + return box(UNSAFE.getAddress(add(cast(offset)).unbox())); } @Override @@ -716,55 +769,55 @@ public T readWord(WordBase offset, LocationIdentity locatio @Override @Operation(opcode = Opcode.READ_POINTER) public byte readByte(int offset, LocationIdentity locationIdentity) { - return readByte(WordFactory.signed(offset), locationIdentity); + return readByte(signed(offset), locationIdentity); } @Override @Operation(opcode = Opcode.READ_POINTER) public char readChar(int offset, LocationIdentity locationIdentity) { - return readChar(WordFactory.signed(offset), locationIdentity); + return readChar(signed(offset), locationIdentity); } @Override @Operation(opcode = Opcode.READ_POINTER) public short readShort(int offset, LocationIdentity locationIdentity) { - return readShort(WordFactory.signed(offset), locationIdentity); + return readShort(signed(offset), locationIdentity); } @Override @Operation(opcode = Opcode.READ_POINTER) public int readInt(int offset, LocationIdentity locationIdentity) { - return readInt(WordFactory.signed(offset), locationIdentity); + return readInt(signed(offset), locationIdentity); } @Override @Operation(opcode = Opcode.READ_POINTER) public long readLong(int offset, LocationIdentity locationIdentity) { - return readLong(WordFactory.signed(offset), locationIdentity); + return readLong(signed(offset), locationIdentity); } @Override @Operation(opcode = Opcode.READ_POINTER) public float readFloat(int offset, LocationIdentity locationIdentity) { - return readFloat(WordFactory.signed(offset), locationIdentity); + return readFloat(signed(offset), locationIdentity); } @Override @Operation(opcode = Opcode.READ_POINTER) public double readDouble(int offset, LocationIdentity locationIdentity) { - return readDouble(WordFactory.signed(offset), locationIdentity); + return readDouble(signed(offset), locationIdentity); } @Override @Operation(opcode = Opcode.READ_POINTER) public T readWord(int offset, LocationIdentity locationIdentity) { - return readWord(WordFactory.signed(offset), locationIdentity); + return readWord(signed(offset), locationIdentity); } @Override @Operation(opcode = Opcode.READ_POINTER) public Object readObject(int offset, LocationIdentity locationIdentity) { - return readObject(WordFactory.signed(offset), locationIdentity); + return readObject(signed(offset), locationIdentity); } @Override @@ -774,55 +827,55 @@ public Object readObject(int offset, LocationIdentity locationIdentity) { @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeByte(WordBase offset, byte val, LocationIdentity locationIdentity) { - UNSAFE.putByte(add((Word) offset).unbox(), val); + UNSAFE.putByte(add(cast(offset)).unbox(), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeChar(WordBase offset, char val, LocationIdentity locationIdentity) { - UNSAFE.putChar(add((Word) offset).unbox(), val); + UNSAFE.putChar(add(cast(offset)).unbox(), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeShort(WordBase offset, short val, LocationIdentity locationIdentity) { - UNSAFE.putShort(add((Word) offset).unbox(), val); + UNSAFE.putShort(add(cast(offset)).unbox(), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeInt(WordBase offset, int val, LocationIdentity locationIdentity) { - UNSAFE.putInt(add((Word) offset).unbox(), val); + UNSAFE.putInt(add(cast(offset)).unbox(), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeLong(WordBase offset, long val, LocationIdentity locationIdentity) { - UNSAFE.putLong(add((Word) offset).unbox(), val); + UNSAFE.putLong(add(cast(offset)).unbox(), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeFloat(WordBase offset, float val, LocationIdentity locationIdentity) { - UNSAFE.putFloat(add((Word) offset).unbox(), val); + UNSAFE.putFloat(add(cast(offset)).unbox(), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeDouble(WordBase offset, double val, LocationIdentity locationIdentity) { - UNSAFE.putDouble(add((Word) offset).unbox(), val); + UNSAFE.putDouble(add(cast(offset)).unbox(), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeWord(WordBase offset, WordBase val, LocationIdentity locationIdentity) { - UNSAFE.putAddress(add((Word) offset).unbox(), ((Word) val).unbox()); + UNSAFE.putAddress(add(cast(offset)).unbox(), (cast(val)).unbox()); } @Override @Operation(opcode = Opcode.INITIALIZE) public void initializeLong(WordBase offset, long val, LocationIdentity locationIdentity) { - UNSAFE.putLong(add((Word) offset).unbox(), val); + UNSAFE.putLong(add(cast(offset)).unbox(), val); } @Override @@ -832,109 +885,109 @@ public void initializeLong(WordBase offset, long val, LocationIdentity locationI @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeByte(int offset, byte val, LocationIdentity locationIdentity) { - writeByte(WordFactory.signed(offset), val, locationIdentity); + writeByte(signed(offset), val, locationIdentity); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeChar(int offset, char val, LocationIdentity locationIdentity) { - writeChar(WordFactory.signed(offset), val, locationIdentity); + writeChar(signed(offset), val, locationIdentity); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeShort(int offset, short val, LocationIdentity locationIdentity) { - writeShort(WordFactory.signed(offset), val, locationIdentity); + writeShort(signed(offset), val, locationIdentity); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeInt(int offset, int val, LocationIdentity locationIdentity) { - writeInt(WordFactory.signed(offset), val, locationIdentity); + writeInt(signed(offset), val, locationIdentity); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeLong(int offset, long val, LocationIdentity locationIdentity) { - writeLong(WordFactory.signed(offset), val, locationIdentity); + writeLong(signed(offset), val, locationIdentity); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeFloat(int offset, float val, LocationIdentity locationIdentity) { - writeFloat(WordFactory.signed(offset), val, locationIdentity); + writeFloat(signed(offset), val, locationIdentity); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeDouble(int offset, double val, LocationIdentity locationIdentity) { - writeDouble(WordFactory.signed(offset), val, locationIdentity); + writeDouble(signed(offset), val, locationIdentity); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeWord(int offset, WordBase val, LocationIdentity locationIdentity) { - writeWord(WordFactory.signed(offset), val, locationIdentity); + writeWord(signed(offset), val, locationIdentity); } @Override @Operation(opcode = Opcode.INITIALIZE) public void initializeLong(int offset, long val, LocationIdentity locationIdentity) { - initializeLong(WordFactory.signed(offset), val, locationIdentity); + initializeLong(signed(offset), val, locationIdentity); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeObject(int offset, Object val, LocationIdentity locationIdentity) { - writeObject(WordFactory.signed(offset), val, locationIdentity); + writeObject(signed(offset), val, locationIdentity); } @Override @Operation(opcode = Opcode.READ_POINTER) public byte readByte(WordBase offset) { - return UNSAFE.getByte(add((Word) offset).unbox()); + return UNSAFE.getByte(add(cast(offset)).unbox()); } @Override @Operation(opcode = Opcode.READ_POINTER) public char readChar(WordBase offset) { - return UNSAFE.getChar(add((Word) offset).unbox()); + return UNSAFE.getChar(add(cast(offset)).unbox()); } @Override @Operation(opcode = Opcode.READ_POINTER) public short readShort(WordBase offset) { - return UNSAFE.getShort(add((Word) offset).unbox()); + return UNSAFE.getShort(add(cast(offset)).unbox()); } @Override @Operation(opcode = Opcode.READ_POINTER) public int readInt(WordBase offset) { - return UNSAFE.getInt(add((Word) offset).unbox()); + return UNSAFE.getInt(add(cast(offset)).unbox()); } @Override @Operation(opcode = Opcode.READ_POINTER) public long readLong(WordBase offset) { - return UNSAFE.getLong(add((Word) offset).unbox()); + return UNSAFE.getLong(add(cast(offset)).unbox()); } @Override @Operation(opcode = Opcode.READ_POINTER) public float readFloat(WordBase offset) { - return UNSAFE.getFloat(add((Word) offset).unbox()); + return UNSAFE.getFloat(add(cast(offset)).unbox()); } @Override @Operation(opcode = Opcode.READ_POINTER) public double readDouble(WordBase offset) { - return UNSAFE.getDouble(add((Word) offset).unbox()); + return UNSAFE.getDouble(add(cast(offset)).unbox()); } @Override @Operation(opcode = Opcode.READ_POINTER) public T readWord(WordBase offset) { - return box(UNSAFE.getAddress(add((Word) offset).unbox())); + return box(UNSAFE.getAddress(add(cast(offset)).unbox())); } @Override @@ -950,107 +1003,107 @@ public T readWord(WordBase offset) { @Override @Operation(opcode = Opcode.READ_POINTER) public byte readByte(int offset) { - return readByte(WordFactory.signed(offset)); + return readByte(signed(offset)); } @Override @Operation(opcode = Opcode.READ_POINTER) public char readChar(int offset) { - return readChar(WordFactory.signed(offset)); + return readChar(signed(offset)); } @Override @Operation(opcode = Opcode.READ_POINTER) public short readShort(int offset) { - return readShort(WordFactory.signed(offset)); + return readShort(signed(offset)); } @Override @Operation(opcode = Opcode.READ_POINTER) public int readInt(int offset) { - return readInt(WordFactory.signed(offset)); + return readInt(signed(offset)); } @Override @Operation(opcode = Opcode.READ_POINTER) public long readLong(int offset) { - return readLong(WordFactory.signed(offset)); + return readLong(signed(offset)); } @Override @Operation(opcode = Opcode.READ_POINTER) public float readFloat(int offset) { - return readFloat(WordFactory.signed(offset)); + return readFloat(signed(offset)); } @Override @Operation(opcode = Opcode.READ_POINTER) public double readDouble(int offset) { - return readDouble(WordFactory.signed(offset)); + return readDouble(signed(offset)); } @Override @Operation(opcode = Opcode.READ_POINTER) public T readWord(int offset) { - return readWord(WordFactory.signed(offset)); + return readWord(signed(offset)); } @Override @Operation(opcode = Opcode.READ_POINTER) public Object readObject(int offset) { - return readObject(WordFactory.signed(offset)); + return readObject(signed(offset)); } @Operation(opcode = Opcode.READ_HEAP) public Object readObject(int offset, BarrierType barrierType) { - return readObject(WordFactory.signed(offset), barrierType); + return readObject(signed(offset), barrierType); } @Operation(opcode = Opcode.READ_HEAP) public Object readObject(int offset, BarrierType barrierType, LocationIdentity locationIdentity) { - return readObject(WordFactory.signed(offset), barrierType, locationIdentity); + return readObject(signed(offset), barrierType, locationIdentity); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeByte(WordBase offset, byte val) { - UNSAFE.putByte(add((Word) offset).unbox(), val); + UNSAFE.putByte(add(cast(offset)).unbox(), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeChar(WordBase offset, char val) { - UNSAFE.putChar(add((Word) offset).unbox(), val); + UNSAFE.putChar(add(cast(offset)).unbox(), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeShort(WordBase offset, short val) { - UNSAFE.putShort(add((Word) offset).unbox(), val); + UNSAFE.putShort(add(cast(offset)).unbox(), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeInt(WordBase offset, int val) { - UNSAFE.putInt(add((Word) offset).unbox(), val); + UNSAFE.putInt(add(cast(offset)).unbox(), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeLong(WordBase offset, long val) { - UNSAFE.putLong(add((Word) offset).unbox(), val); + UNSAFE.putLong(add(cast(offset)).unbox(), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeFloat(WordBase offset, float val) { - UNSAFE.putFloat(add((Word) offset).unbox(), val); + UNSAFE.putFloat(add(cast(offset)).unbox(), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeDouble(WordBase offset, double val) { - UNSAFE.putDouble(add((Word) offset).unbox(), val); + UNSAFE.putDouble(add(cast(offset)).unbox(), val); } @Override @@ -1072,13 +1125,13 @@ public void writeDouble(WordBase offset, double val) { @Override @Operation(opcode = Opcode.CAS_POINTER) public boolean logicCompareAndSwapInt(WordBase offset, int expectedValue, int newValue, LocationIdentity locationIdentity) { - return UNSAFE.compareAndSetInt(this.toObject(), ((Word) offset).unbox(), expectedValue, newValue); + return UNSAFE.compareAndSetInt(this.toObject(), (cast(offset)).unbox(), expectedValue, newValue); } @Override @Operation(opcode = Opcode.CAS_POINTER) public boolean logicCompareAndSwapLong(WordBase offset, long expectedValue, long newValue, LocationIdentity locationIdentity) { - return UNSAFE.compareAndSetLong(this.toObject(), ((Word) offset).unbox(), expectedValue, newValue); + return UNSAFE.compareAndSetLong(this.toObject(), (cast(offset)).unbox(), expectedValue, newValue); } @Override @@ -1088,13 +1141,13 @@ public boolean logicCompareAndSwapLong(WordBase offset, long expectedValue, long @Override @Operation(opcode = Opcode.CAS_POINTER) public boolean logicCompareAndSwapObject(WordBase offset, Object expectedValue, Object newValue, LocationIdentity locationIdentity) { - return UNSAFE.compareAndSetReference(this.toObject(), ((Word) offset).unbox(), expectedValue, newValue); + return UNSAFE.compareAndSetReference(this.toObject(), (cast(offset)).unbox(), expectedValue, newValue); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeWord(WordBase offset, WordBase val) { - UNSAFE.putAddress(add((Word) offset).unbox(), ((Word) val).unbox()); + UNSAFE.putAddress(add(cast(offset)).unbox(), (cast(val)).unbox()); } @Override @@ -1104,55 +1157,55 @@ public void writeWord(WordBase offset, WordBase val) { @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeByte(int offset, byte val) { - writeByte(WordFactory.signed(offset), val); + writeByte(signed(offset), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeChar(int offset, char val) { - writeChar(WordFactory.signed(offset), val); + writeChar(signed(offset), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeShort(int offset, short val) { - writeShort(WordFactory.signed(offset), val); + writeShort(signed(offset), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeInt(int offset, int val) { - writeInt(WordFactory.signed(offset), val); + writeInt(signed(offset), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeLong(int offset, long val) { - writeLong(WordFactory.signed(offset), val); + writeLong(signed(offset), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeFloat(int offset, float val) { - writeFloat(WordFactory.signed(offset), val); + writeFloat(signed(offset), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeDouble(int offset, double val) { - writeDouble(WordFactory.signed(offset), val); + writeDouble(signed(offset), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeWord(int offset, WordBase val) { - writeWord(WordFactory.signed(offset), val); + writeWord(signed(offset), val); } @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeObject(int offset, Object val) { - writeObject(WordFactory.signed(offset), val); + writeObject(signed(offset), val); } @Override @@ -1162,54 +1215,54 @@ public void writeObject(int offset, Object val) { @Override @Operation(opcode = Opcode.CAS_POINTER) public int compareAndSwapInt(int offset, int expectedValue, int newValue, LocationIdentity locationIdentity) { - return compareAndSwapInt(WordFactory.signed(offset), expectedValue, newValue, locationIdentity); + return compareAndSwapInt(signed(offset), expectedValue, newValue, locationIdentity); } @Override @Operation(opcode = Opcode.CAS_POINTER) public long compareAndSwapLong(int offset, long expectedValue, long newValue, LocationIdentity locationIdentity) { - return compareAndSwapLong(WordFactory.signed(offset), expectedValue, newValue, locationIdentity); + return compareAndSwapLong(signed(offset), expectedValue, newValue, locationIdentity); } @Override @Operation(opcode = Opcode.CAS_POINTER) public T compareAndSwapWord(int offset, T expectedValue, T newValue, LocationIdentity locationIdentity) { - return compareAndSwapWord(WordFactory.signed(offset), expectedValue, newValue, locationIdentity); + return compareAndSwapWord(signed(offset), expectedValue, newValue, locationIdentity); } @Override @Operation(opcode = Opcode.CAS_POINTER) public Object compareAndSwapObject(int offset, Object expectedValue, Object newValue, LocationIdentity locationIdentity) { - return compareAndSwapObject(WordFactory.signed(offset), expectedValue, newValue, locationIdentity); + return compareAndSwapObject(signed(offset), expectedValue, newValue, locationIdentity); } @Override @Operation(opcode = Opcode.CAS_POINTER) public boolean logicCompareAndSwapInt(int offset, int expectedValue, int newValue, LocationIdentity locationIdentity) { - return logicCompareAndSwapInt(WordFactory.signed(offset), expectedValue, newValue, locationIdentity); + return logicCompareAndSwapInt(signed(offset), expectedValue, newValue, locationIdentity); } @Override @Operation(opcode = Opcode.CAS_POINTER) public boolean logicCompareAndSwapLong(int offset, long expectedValue, long newValue, LocationIdentity locationIdentity) { - return logicCompareAndSwapLong(WordFactory.signed(offset), expectedValue, newValue, locationIdentity); + return logicCompareAndSwapLong(signed(offset), expectedValue, newValue, locationIdentity); } @Override @Operation(opcode = Opcode.CAS_POINTER) public boolean logicCompareAndSwapWord(int offset, WordBase expectedValue, WordBase newValue, LocationIdentity locationIdentity) { - return logicCompareAndSwapWord(WordFactory.signed(offset), expectedValue, newValue, locationIdentity); + return logicCompareAndSwapWord(signed(offset), expectedValue, newValue, locationIdentity); } @Override @Operation(opcode = Opcode.CAS_POINTER) public boolean logicCompareAndSwapObject(int offset, Object expectedValue, Object newValue, LocationIdentity locationIdentity) { - return logicCompareAndSwapObject(WordFactory.signed(offset), expectedValue, newValue, locationIdentity); + return logicCompareAndSwapObject(signed(offset), expectedValue, newValue, locationIdentity); } /** - * This is deprecated because of the easy to mistype name collision between {@link #equals} and - * the other equals routines like {@link #equal(Word)}. In general you should never be + * This is deprecated because of the easy to mistype name collision between {@code equals} and + * the other equals routines like {@link #equal(Word)}. In general, you should never be * statically calling this method for Word types. */ @SuppressWarnings("deprecation") @@ -1249,7 +1302,7 @@ private HostedWord(long rawValue) { this.rawValue = rawValue; } - protected static Word boxLong(long val) { + static Word boxLong(long val) { if (val >= SMALL_FROM && val <= SMALL_TO) { return smallCache[(int) val - SMALL_FROM]; } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/word/WordTypes.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/word/WordTypes.java index f28ffb2fea22..6e9e4676b336 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/word/WordTypes.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/word/WordTypes.java @@ -24,8 +24,6 @@ */ package jdk.graal.compiler.word; -import static org.graalvm.nativeimage.ImageInfo.inImageBuildtimeCode; - import jdk.graal.compiler.core.common.Fields; import jdk.graal.compiler.core.common.type.AbstractObjectStamp; import jdk.graal.compiler.core.common.type.Stamp; @@ -35,13 +33,13 @@ import jdk.graal.compiler.nodes.ValueNode; import jdk.graal.compiler.nodes.type.StampTool; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; +import org.graalvm.word.impl.WordFactoryOperation; /** * Encapsulates information for Java types representing raw words (as opposed to Objects). @@ -52,18 +50,12 @@ public class WordTypes { * Resolved type for {@link WordBase}. */ private final ResolvedJavaType wordBaseType; - private final Class wordBaseClass; /** * Resolved type for {@link Word}. */ private final ResolvedJavaType wordImplType; - /** - * Resolved type for {@link WordFactory}. - */ - private final ResolvedJavaType wordFactoryType; - /** * Resolved type for {@link ObjectAccess}. */ @@ -79,15 +71,10 @@ public class WordTypes { public WordTypes(MetaAccessProvider metaAccess, JavaKind wordKind) { this.wordKind = wordKind; this.wordBaseType = metaAccess.lookupJavaType(WordBase.class); - this.wordBaseClass = WordBase.class; this.wordImplType = metaAccess.lookupJavaType(Word.class); - this.wordFactoryType = metaAccess.lookupJavaType(WordFactory.class); this.objectAccessType = metaAccess.lookupJavaType(ObjectAccess.class); this.barrieredAccessType = metaAccess.lookupJavaType(BarrieredAccess.class); - if (!inImageBuildtimeCode()) { - Word.ensureInitialized(); - } this.wordImplType.initialize(); } @@ -95,10 +82,10 @@ public WordTypes(MetaAccessProvider metaAccess, JavaKind wordKind) { * Determines if a given method denotes a word operation. */ public boolean isWordOperation(ResolvedJavaMethod targetMethod) { - final boolean isWordFactory = wordFactoryType.equals(targetMethod.getDeclaringClass()); - if (isWordFactory) { - return !targetMethod.isConstructor(); + if (targetMethod.getAnnotation(WordFactoryOperation.class) != null) { + return true; } + final boolean isObjectAccess = objectAccessType.equals(targetMethod.getDeclaringClass()); final boolean isBarrieredAccess = barrieredAccessType.equals(targetMethod.getDeclaringClass()); if (isObjectAccess || isBarrieredAccess) { @@ -142,7 +129,7 @@ public boolean isWord(JavaType type) { } public boolean isWord(Class clazz) { - return wordBaseClass.isAssignableFrom(clazz); + return WordBase.class.isAssignableFrom(clazz); } /** diff --git a/docs/getting-started/oci/cloud-shell.md b/docs/getting-started/oci/cloud-shell.md index e8eedc0d1980..7bc2e70d0f08 100644 --- a/docs/getting-started/oci/cloud-shell.md +++ b/docs/getting-started/oci/cloud-shell.md @@ -50,18 +50,18 @@ You are all set to run Java applications using Oracle GraalVM JDK in Cloud Shell ## Run a Java Application -The example is a minimal REST-based application, built on top of Spring Boot using Maven. +The example is a minimal REST-based application, built on top of Spring Boot 3 using Maven. The _pom.xml_ file was generated using [Spring Initializr](https://start.spring.io/) with Spring Native Tools added as a feature. The [Spring AOT plugin](https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/#spring-aot) performs ahead-of-time transformations of a Spring application into a native executable. 1. Clone the _demos_ repository and change to the application root directory: ```shell git clone https://github.com/graalvm/graalvm-demos.git - cd graalvm-demos/spring-native-image + cd graalvm-demos/native-image/containerize ``` 2. Build the application with Maven (Apache Maven is also preinstalled in Cloud Shell): ```shell - mvn clean package + ./mvnw clean package ``` This will generate a runnable JAR file that contains all of the application’s dependencies as well as a correctly configured `MANIFEST` file. @@ -85,7 +85,7 @@ The [Spring AOT plugin](https://docs.spring.io/spring-native/docs/current/refere 4. Next, build a native executable for this Spring Boot application using the [`native` Maven profile](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html#quickstart). ```shell - mvn -Pnative native:compile + ./mvnw -Pnative native:compile ``` This will generate a native executable for Linux in the _target_ directory, named _benchmark-jibber_. @@ -115,5 +115,4 @@ Thus, you can use Oracle GraalVM in OCI Cloud Shell to build and test simple Jav - [Java Hello World with Oracle GraalVM in OCI Cloud Shell](https://github.com/graalvm/graalvm-demos/blob/master/java-hello-world-maven/README-Cloud-Shell.md) - [Micronaut Hello World REST App with Oracle GraalVM in OCI Cloud Shell](https://github.com/graalvm/graalvm-demos/blob/master/micronaut-hello-rest-maven/README-Cloud-Shell.md) - [Spring Boot Microservice with Oracle GraalVM in OCI Cloud Shell](https://github.com/graalvm/graalvm-demos/blob/master/spring-native-image/README-Cloud-Shell.md) -- [Oracle GraalVM in OCI Code Editor](code-editor.md) - +- [Oracle GraalVM in OCI Code Editor](code-editor.md) \ No newline at end of file diff --git a/docs/getting-started/oci/code-editor.md b/docs/getting-started/oci/code-editor.md index 287babf616c7..61c3ae130ec5 100644 --- a/docs/getting-started/oci/code-editor.md +++ b/docs/getting-started/oci/code-editor.md @@ -133,6 +133,5 @@ The Code Editor allows you to accomplish quick coding tasks and run applications - [Java Hello World with Oracle GraalVM in OCI Code Editor](https://github.com/oracle-devrel/oci-code-editor-samples/tree/main/java-samples/graalvmee-java-hello-world) - [Micronaut Hello World REST App with Oracle GraalVM in OCI Code Editor](https://github.com/oracle-devrel/oci-code-editor-samples/tree/main/java-samples/graalvmee-java-micronaut-hello-rest) -- [Spring Boot Microservice with Oracle GraalVM in OCI Code Editor](https://github.com/graalvm/graalvm-demos/blob/master/spring-native-image/README-Code-Editor.md) - [Working with Code Editor](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/code_editor_intro.htm) - [Oracle GraalVM in OCI Cloud Shell](cloud-shell.md) diff --git a/docs/reference-manual/embedding/embed-languages.md b/docs/reference-manual/embedding/embed-languages.md index d4374e27be16..7a1c443edfac 100644 --- a/docs/reference-manual/embedding/embed-languages.md +++ b/docs/reference-manual/embedding/embed-languages.md @@ -538,13 +538,17 @@ To build a native executable with the above configuration, run: mvn -Pnative package ``` -To build a native executable from a polyglot application, for example, a Java-host application embedding Python, a `./resources` directory containing all the required files is created by default. -By default, the language runtime will look for the resources directory relative to the native executable or library image that was built. +Building a native executable from a polyglot application, for example, a Java-host application embedding Python, automatically captures all the internal resources required by the included languages and tools. +By default, the resources are included in the native executable itself. +The inclusion of resources in the native executable can be disabled by `-H:-IncludeLanguageResources`. +Another option is a separate _resources_ directory containing all the required files. +To switch to this option, use `-H:+CopyLanguageResources`. This is the default behavior when `-H:+IncludeLanguageResources` is not supported, i.e., with Graal Languages earlier than 24.2.x (see the [versions roadmap](https://www.graalvm.org/release-calendar/)). +When `-H:+CopyLanguageResources` is used, the language runtime will look for the resources directory relative to the native executable or the shared library. At run time, the lookup location may be customized using the `-Dpolyglot.engine.resourcePath=path/to/resources` option. -To disable the resource creation, the `-H:-CopyLanguageResources` build-time option may be used. -Note that some languages may not support running without a resources directory. +To disable the capturing of resources altogether, add both `-H:-IncludeLanguageResources` and `-H:-CopyLanguageResources` to build-time options. +Note that some languages may not support running without their resources. -With Polyglot version 23.1 the language home options like `-Dorg.graalvm.home` should no longer be used and were replaced with the resource directory option. +With Graal Languages version 23.1 and newer the language home options like `-Dorg.graalvm.home` should no longer be used and were replaced with the resource directory option. The language home options remain functional for compatibility reasons but may be removed in future releases. ### Configuring Native Host Reflection diff --git a/docs/reference-manual/native-image/Compatibility.md b/docs/reference-manual/native-image/Compatibility.md index 5ad9d0307431..8cd7aae48a57 100644 --- a/docs/reference-manual/native-image/Compatibility.md +++ b/docs/reference-manual/native-image/Compatibility.md @@ -111,7 +111,6 @@ Mostly all Native Image features are supported on Linux AArch64 architecture, ex * `-R:[+|-]WriteableCodeCache`: must be disabled. * `--libc=`: `musl` is not supported. -* `--gc=`: The G1 garbage collector (`G1`) is not supported. Find a list of options for the `native-image` builder [here](BuildOptions.md). diff --git a/docs/reference-manual/native-image/ForeignInterface.md b/docs/reference-manual/native-image/ForeignInterface.md index e4cbdd514943..98b09e73c975 100644 --- a/docs/reference-manual/native-image/ForeignInterface.md +++ b/docs/reference-manual/native-image/ForeignInterface.md @@ -30,14 +30,18 @@ These two kinds of calls are referred to as "downcalls" and "upcalls" respective ### Looking Up Native Functions The FFM API provides the `SymbolLookup` interface to find functions in native libraries by name. -`SymbolLookup.loaderLookup()` is currently the only supported kind of `SymbolLookup`. +Native image supports all available symbol lookup methods, i.e., `SymbolLookup.loaderLookup()`, `SymbolLookup.libraryLookup()`, and `Linker.defaultLookup()`. ### Registering Foreign Calls In order to perform calls to native code at run time, supporting code must be generated at image build time. -Therefore, the `native-image` tool must be provided with descriptors that characterize the functions to which downcalls may be performed at run time. +Therefore, the `native-image` tool must be provided with descriptors that characterize the functions with which downcalls or upcalls can be performed at runtime. -These descriptors can be registered using a custom `Feature`, for example: +For upcalls, it is recommended to register a specific static method as an upcall target by providing its declaring class and the method name. +This allows `native-image` to create specialized upcall code that can be orders of magnitude faster than a upcall registered only by function descriptor. +Whenever possible, this should be the preferred way to register upcalls. + +Descriptors and target methods can be registered using a custom `Feature`, for example: ```java import static java.lang.foreign.ValueLayout.*; @@ -50,6 +54,9 @@ class ForeignRegistrationFeature implements Feature { RuntimeForeignAccess.registerForUpcall(FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT)); RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.of(ADDRESS, JAVA_INT, JAVA_INT), Linker.Option.firstVariadicArg(1)); RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.ofVoid(JAVA_INT), Linker.Option.captureCallState("errno")); + + MethodHandle target = MethodHandles.lookup().findStatic(UserClass.class, "aStaticMethod", MethodType.of(int.class, int.class, int.class)); + RuntimeForeignAccess.registerForUpcall(target, FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT)); } } ``` diff --git a/docs/reference-manual/native-image/InspectTool.md b/docs/reference-manual/native-image/InspectTool.md index 437da9405c28..2f3bc6967f4f 100644 --- a/docs/reference-manual/native-image/InspectTool.md +++ b/docs/reference-manual/native-image/InspectTool.md @@ -19,7 +19,7 @@ The Native Image Inspect Tool can extract the compressed SBOM using the optional ## Extracting Class-Level Metadata (Deprecated) -> The extraction of class-level metadata using `native-image-inspect` is deprecated. In GraalVM for JDK 24, a deprecation warning is printed to `stderr`, and this functionality will be removed in GraalVM for JDK 25. Please migrate to using [class-level SBOMs](../../security/native-image.md#including-class-level-metadata-in-the-sbom) instead by passing `--enable-sbom=class-level,export` to the `native-image` builder, which generates an SBOM containing the same kind of class-level metadata information. +> The extraction of class-level metadata using `native-image-inspect` is deprecated. In GraalVM for JDK 24, a deprecation warning is printed to `stderr`, and this functionality will be removed in GraalVM for JDK 25. Please migrate to using [class-level SBOMs](../../security/SBOM.md#including-class-level-metadata-in-the-sbom) instead by passing `--enable-sbom=class-level,export` to the `native-image` builder, which generates an SBOM containing the same kind of class-level metadata information. Native Image provides the Inspect Tool to list all classes, fields, and methods included in a native executable or a native shared library. Run the command `$JAVA_HOME/bin/native-image-inspect ` to list classes, methods, fields, and constructors in the JSON format that validates against the JSON schema defined in [`native-image-inspect-schema-v0.2.0.json`](assets/native-image-inspect-schema-v0.2.0.json) (only available in Oracle GraalVM). diff --git a/docs/reference-manual/native-image/ReachabilityMetadata.md b/docs/reference-manual/native-image/ReachabilityMetadata.md index 3263fef98384..6c38282498a0 100644 --- a/docs/reference-manual/native-image/ReachabilityMetadata.md +++ b/docs/reference-manual/native-image/ReachabilityMetadata.md @@ -123,7 +123,7 @@ Computing metadata in code can be achieved in two ways: ## Specifying Metadata with JSON All metadata specified in the _reachability-metadata.json_ file that is located in any of the classpath entries at _META-INF/native-image/\\/\\/_. -The JSON schema for the reachability metadata is defined in [reachability-metadata-schema-v1.0.0.json](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json). +The JSON schema for the reachability metadata is defined in [reachability-metadata-schema-v1.1.0.json](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json). A sample _reachability-metadata.json_ file can be found [in the sample section](#sample-reachability-metadata). The _reachability-metadata.json_ configuration contains a single object with one field for each type of metadata. Each field in the top-level object contains an array of *metadata entries*: @@ -132,7 +132,6 @@ The _reachability-metadata.json_ configuration contains a single object with one "reflection":[], "resources":[], "bundles":[], - "serialization":[], "jni":[] } ``` @@ -640,14 +639,15 @@ To create a custom constructor for serialization use: Proxy classes can only be registered for serialization via the JSON files. ### Serialization Metadata in JSON -Serialization metadata is specified in the `serialization` section of _reachability-metadata.json_. +Serialization metadata is specified in the `reflection` section of _reachability-metadata.json_. To specify a regular `serialized.Type` use ```json { - "serialization": [ + "reflection": [ { - "type": "serialized.Type" + "type": "serialized.Type", + "serializable": true } ] } @@ -656,10 +656,11 @@ To specify a regular `serialized.Type` use To specify a proxy class for serialization, use the following entry: ```json { - "serialization": [ + "reflection": [ { "type": { - "proxy": ["FullyQualifiedInterface1", "...", "FullyQualifiedInterfaceN"] + "proxy": ["FullyQualifiedInterface1", "...", "FullyQualifiedInterfaceN"], + "serializable": true } } ] @@ -670,21 +671,9 @@ In rare cases an application might explicitly make calls to: ```java ReflectionFactory.newConstructorForSerialization(Class cl, Constructor constructorToCall); ``` -In which the passed `constructorToCall` differs from what would automatically be used if regular serialization of `cl`. - -To also support such serialization use cases, it is possible to register serialization for a class with a -custom `constructorToCall`. -For example, to allow serialization of `org.apache.spark.SparkContext$$anonfun$hadoopFile$1`, use the declared constructor of `java.lang.Object` as a custom `targetConstructor`, use: -```json -{ - "serialization": [ - { - "type": "", - "customTargetConstructorClass": "" - } - ] -} -``` +The specified `constructorToCall` differs from the one that would be automatically used during regular serialization of `cl`. +When a class is registered for run-time serialization, all potential custom constructors are automatically registered. +As a result, this use case does not require any additional metadata. ## Sample Reachability Metadata @@ -712,7 +701,8 @@ See below is a sample reachability metadata configuration that you can use in _r "allPublicFields": true, "allDeclaredMethods": true, "allPublicMethods": true, - "unsafeAllocated": true + "unsafeAllocated": true, + "serializable": true } ], "jni": [ @@ -748,12 +738,6 @@ See below is a sample reachability metadata configuration that you can use in _r "name": "fully.qualified.bundle.name", "locales": ["en", "de", "other_optional_locales"] } - ], - "serialization": [ - { - "type": "serialized.Type", - "customTargetConstructorClass": "optional.serialized.super.Type" - } ] } ``` diff --git a/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json b/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json similarity index 98% rename from docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json rename to docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json index a3a75dd1d5e1..065247bf3a09 100644 --- a/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json +++ b/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json", + "$id": "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json", "title": "JSON schema for the reachability metadata used by GraalVM Native Image", "type": "object", "default": {}, @@ -202,6 +202,11 @@ "title": "Allow objects of this class to be instantiated with a call to jdk.internal.misc.Unsafe#allocateInstance or JNI's AllocObject", "type": "boolean", "default": false + }, + "serializable": { + "title": "Allow objects of this class to be serialized and deserialized", + "type": "boolean", + "default": false } }, "additionalProperties": false diff --git a/docs/reference-manual/native-image/guides/containerise-native-executable-with-docker.md b/docs/reference-manual/native-image/guides/containerise-native-executable-with-docker.md index ec8d604f707e..1965898d9e7c 100644 --- a/docs/reference-manual/native-image/guides/containerise-native-executable-with-docker.md +++ b/docs/reference-manual/native-image/guides/containerise-native-executable-with-docker.md @@ -20,7 +20,7 @@ You will use a GraalVM container image with Native Image to compile a Java appli ## Download a Sample Application -This guide uses the [Spring Boot 3 Native Image Microservice example](https://github.com/graalvm/graalvm-demos/blob/master/spring-native-image/README.md). +This guide uses the [Spring Boot 3 Native Image Microservice example](https://github.com/graalvm/graalvm-demos/tree/master/native-image/containerize/spring-boot-microservice-jibber/src/main/java/com/example/benchmarks/jibber). The example is a minimal REST-based API application, built on top of Spring Boot 3. If you call the HTTP endpoint `/jibber`, it will return some nonsense verse generated in the style of the Jabberwocky poem, by Lewis Carroll. @@ -33,12 +33,12 @@ For other installation options, visit the [Downloads section](https://www.graalv 2. Clone the GraalVM Demos repository: ```shell - git clone https://github.com/graalvm/graalvm-demos + git clone https://github.com/graalvm/graalvm-demos.git ``` -3. Change directory to _spring-native-image/_: +3. Change directory to _native-image/containerize/_: ```shell - cd spring-native-image + cd graalvm-demos/native-image/containerize/spring-boot-microservice-jibber ``` ## Build and Run as a Native Executable @@ -71,9 +71,9 @@ With the built-in support for GraalVM Native Image in Spring Boot 3, it has beco The generated native executable is platform-dependent. -1. Containerize the native executable using the following command: +1. Containerize the native executable using the following commands. - - On Linux, containerize the native executable generated in the previous step using the following command: + - On Linux, containerize the native executable generated in the previous step: ```shell docker build -f Dockerfiles/Dockerfile.native --build-arg APP_FILE=benchmark-jibber -t jibber-benchmark:native.0.0.1-SNAPSHOT . ``` @@ -109,10 +109,10 @@ The generated native executable is platform-dependent. In this guide, you saw how to use GraalVM container images to containerize a native executable for your Java application. -With GraalVM Native Image you can build a statically linked native executable by packaging the native executable directly into tiny containers such as scratch or distroless images. +With GraalVM Native Image you can also [build fully static native executables](build-static-and-mostly-static-executable.md) and package them directly into tiny containers such as scratch or distroless containers. ### Related Documentation -* [Build a Static or Mostly-Static Native Executable](build-static-and-mostly-static-executable.md) -* Oracle GraalVM Container Images +* [Build a Native Executable from a Spring Boot Application](build-spring-boot-application-aot.md) +* Oracle GraalVM Container Images * Hands-on Lab: GraalVM Native Image, Spring and Containerisation diff --git a/docs/reference-manual/native-image/guides/create-heap-dump-from-native-executable.md b/docs/reference-manual/native-image/guides/create-heap-dump-from-native-executable.md index f982d57e87b9..2821b83bb032 100644 --- a/docs/reference-manual/native-image/guides/create-heap-dump-from-native-executable.md +++ b/docs/reference-manual/native-image/guides/create-heap-dump-from-native-executable.md @@ -25,7 +25,7 @@ All approaches are described below. > Note: By default, a heap dump is created in the current working directory. The `-XX:HeapDumpPath` option can be used to specify an alternative filename or directory. For example: > `./helloworld -XX:HeapDumpPath=$HOME/helloworld.hprof` -> Also note: It is not possible to create a heap dump on the Microsoft Windows platform. +> Creating a heap dump on the Microsoft Windows platform is not supported. ## Create a Heap Dump with VisualVM @@ -42,6 +42,9 @@ For example: ```shell ./mem-leak-example -XX:+HeapDumpOnOutOfMemoryError +``` +You should see a similar output: +``` Dumping heap to svm-heapdump-67799-OOME.hprof ... Heap dump file created [10046752 bytes in 0.49 secs] Exception in thread "main" java.lang.OutOfMemoryError: Garbage-collected heap size exceeded. @@ -54,11 +57,14 @@ This can be useful to identify which objects the Native Image build process allo For a HelloWorld example, use the option as follows: ```shell -$JAVA_HOME/bin/native-image HelloWorld --enable-monitoring=heapdump +native-image HelloWorld --enable-monitoring=heapdump +``` +```shell ./helloworld -XX:+DumpHeapAndExit -Heap dump created at '/path/to/helloworld.hprof'. ``` +The heap dump is created at _path/to/helloworld.hprof_. + ## Create a Heap Dump with SIGUSR1 (Linux/macOS only) > Note: This requires the `Signal` API, which is enabled by default except when building shared libraries. @@ -168,14 +174,17 @@ For other installation options, visit the [Downloads section](https://www.graalv native-image SVMHeapDump --enable-monitoring=heapdump ``` - (The `native-image` builder creates a native executable from the file _SVMHeapDump.class_. - When the command completes, the native executable _svmheapdump_ is created in the current directory.) + The `native-image` builder creates a native executable from the file _SVMHeapDump.class_. + When the command completes, the native executable _svmheapdump_ is created in the current directory. 3. Run the application, send it a signal, and check the heap dump: Run the application: ```shell ./svmheapdump + ``` + You should see a similar output: + ``` 17 May 2022, 16:38:13: Hello GraalVM native image developer! The PID of this process is: 57509 Send it a signal: 'kill -SIGUSR1 57509' @@ -285,11 +294,12 @@ The condition to create a heap dump is provided as an option on the command line ``` When the command completes, the _svmheapdumpapi_ native executable is created in the current directory. -3. Run the application and check the heap dump - - Now you can run your native executable and create a heap dump from it with output similar to the following: +3. Run the application and create a heap dump: ```shell ./svmheapdumpapi --heapdump + ``` + You should see a similar output: + ``` Sep 15, 2020, 4:06:36 PM: Hello GraalVM native image developer. Your command line options are: --heapdump Heap dump created /var/folders/hw/s9d78jts67gdc8cfyq5fjcdm0000gp/T/SVMHeapDump-6437252222863577987.hprof, size: 8051959 diff --git a/docs/reference-manual/native-image/guides/include-reachability-metadata-maven.md b/docs/reference-manual/native-image/guides/include-reachability-metadata-maven.md index 513905c795ff..979a52d7b586 100644 --- a/docs/reference-manual/native-image/guides/include-reachability-metadata-maven.md +++ b/docs/reference-manual/native-image/guides/include-reachability-metadata-maven.md @@ -126,10 +126,12 @@ For other installation options, visit the [Downloads section](https://www.graalv 1.0.0-SNAPSHOT - UTF-8 2.2.220 - h2example + 21 + 21 + 0.10.3 org.graalvm.example.H2Example + h2example @@ -184,10 +186,6 @@ For other installation options, visit the [Downloads section](https://www.graalv org.apache.maven.plugins maven-compiler-plugin 3.11.0 - - ${java.version} - 22 - @@ -327,15 +325,15 @@ In the `native` Maven profile section, add the `exec-maven-plugin` plugin: ``` -3. Run your application with the agent enabled, on the JVM: +3. Run your application with the agent on the JVM: ```shell - mvn -Pnative -Dagent=true -DskipTests -DskipNativeBuild=true package exec:exec@java-agent + mvn -Pnative -DskipTests -DskipNativeBuild=true package exec:exec@java-agent ``` The agent captures and records calls to the H2 Database and all the dynamic features encountered during a test run into the _reachability-metadata.json_ file in the _target/native/agent-output/main/_ directory. 4. Build a native executable using configuration collected by the agent: ```shell - mvn -Pnative -Dagent=true -DskipTests package exec:exec@native + mvn -Pnative -DskipTests package exec:exec@native ``` It generates a native executable for the platform in the _target/_ directory, called _h2example_. @@ -346,7 +344,8 @@ In the `native` Maven profile section, add the `exec-maven-plugin` plugin: ### Summary -This guide demonstrated how to build a native executable using the [GraalVM Reachability Metadata Repository](https://github.com/oracle/graalvm-reachability-metadata) and with the Tracing Agent. The goal was to show the difference, and prove how using the reachability metadata can simplify the work. +This guide demonstrated how to build a native executable using the [GraalVM Reachability Metadata Repository](https://github.com/oracle/graalvm-reachability-metadata) and with the Tracing Agent. +The goal was to show the difference, and prove how using the reachability metadata can simplify the work. Using the GraalVM Reachability Metadata Repository enhances the usability of Native Image for Java applications depending on 3rd party libraries. ### Related Documentation diff --git a/docs/reference-manual/native-image/guides/include-resources.md b/docs/reference-manual/native-image/guides/include-resources.md index 333b15e66c77..e2bdc4d46dab 100644 --- a/docs/reference-manual/native-image/guides/include-resources.md +++ b/docs/reference-manual/native-image/guides/include-resources.md @@ -7,8 +7,8 @@ permalink: /reference-manual/native-image/guides/include-resources/ # Include Resources in a Native Executable -By default, the `native-image` tool does not integrate any Java resource files into a native executable. -You must specify resources that should be accessible by your application at runtime. +By default, the `native-image` tool does not integrate any resource files into a native executable. +You must specify resources that should be accessible by your application at run time. This guide demonstrates how to register resources to be included in a native executable by providing a resource configuration file. See [Accessing Resources in Native Image](../ReachabilityMetadata.md#resources) for more ways to include resources. @@ -47,8 +47,8 @@ In the following example, you run a "fortune teller" application that simulates } private void printRandomFortune() throws InterruptedException { - int r = RANDOM.nextInt(fortunes.size()); //Pick a random number - String f = fortunes.get(r); //Use the random number to pick a random fortune + int r = RANDOM.nextInt(fortunes.size()); // Pick a random number + String f = fortunes.get(r); // Use the random number to pick a random fortune for (char c: f.toCharArray()) { // Print out the fortune System.out.print(c); Thread.sleep(100); @@ -93,13 +93,16 @@ In the following example, you run a "fortune teller" application that simulates To see which resources were included in your native executable, pass the option `--emit build-report` to the `native-image` tool at build time. It generates an HTML file that can be examined with a regular web browser. -The information about all included resources will be under the `Resources` tab. +The information about all included resources will be under the **Resources** tab. In this demo the path to the resource file is straightforward, but it may be more complex in a real-world use case. -Resources are specified via globs. For more advanced use-cases, you can register resources using the API methods (see [class RuntimeResourceAccess](https://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/hosted/RuntimeResourceAccess.html)). +A resource or resource bundle may come from a module. +Learn more how to specify it in [Resources in Java Modules](../ReachabilityMetadata.md#resources-in-java-modules). + +For more advanced use-cases, you can register resources using the API methods (see [class RuntimeResourceAccess](https://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/hosted/RuntimeResourceAccess.html)). Learn more about specifying a resource path using a glob and some syntax rules to be observed from [Accessing Resources in Native Image](../ReachabilityMetadata.md#resources). ### Related Documentation * [Accessing Resources in Native Image](../ReachabilityMetadata.md#resources) -* [Resource Metadata in JSON](../ReachabilityMetadata.md#resource-metadata-in-json) +* [Resource Metadata in JSON](../ReachabilityMetadata.md#resource-metadata-in-json) \ No newline at end of file diff --git a/docs/reference-manual/native-image/guides/optimize-file-size.md b/docs/reference-manual/native-image/guides/optimize-file-size.md index f7bc387d1456..1c05fc0892ec 100644 --- a/docs/reference-manual/native-image/guides/optimize-file-size.md +++ b/docs/reference-manual/native-image/guides/optimize-file-size.md @@ -27,14 +27,14 @@ For the demo, run a "fortune teller" application that simulates the traditional 2. Change directory to _fortune-demo/fortune-maven_: ```bash - cd fortune-demo/fortune-maven + cd native-image/native-build-tools/maven-plugin ``` ## Build a Native Executable with Default Configuration -1. Create a native executable using the [Maven plugin for Native Image](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html){:target="_blank"}: +1. Create a native executable using the [Maven plugin for Native Image building](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html){:target="_blank"}: ```bash - mvn -Pnative package + ./mvnw -Pnative package ``` The command compiles the project, creates a JAR file with all dependencies, and then generates a native executable, `fortune`, in the _target_ directory. @@ -53,7 +53,7 @@ For the demo, run a "fortune teller" application that simulates the traditional Next create a native executable with the size optimization on, giving a different name for the output file to differentiate it from the previous build. -1. Open the _pom.xml_ file. Find the `native-maven-plugin` declaration, and add the following build arguments within the `` element. The configuration should look like this: +1. Open the _pom.xml_ file. Find the `native-maven-plugin` declaration, and notice the following build arguments within the `` element: ```xml fortune-optimized @@ -68,7 +68,7 @@ Next create a native executable with the size optimization on, giving a differen 2. Create the second native executable: ```bash - mvn -Pnative package + ./mvnw -Pnative package ``` The command generates an executable file, `fortune-optimized`, in the _target_ directory. diff --git a/docs/reference-manual/native-image/guides/optimize-native-executable-size-using-build-report.md b/docs/reference-manual/native-image/guides/optimize-native-executable-size-using-build-report.md index fdef568c59ca..d2a99b4d3db2 100644 --- a/docs/reference-manual/native-image/guides/optimize-native-executable-size-using-build-report.md +++ b/docs/reference-manual/native-image/guides/optimize-native-executable-size-using-build-report.md @@ -65,10 +65,10 @@ The words are delimited by commas and may be enclosed by an arbitrary number of native-image IthWord --emit build-report ``` - The command generates an executable file, `_ithword_`, in the current working directory. + The command generates an executable file, `ithword`, in the current working directory. The Build Report file, _ithword-build-report.html_, is automatically created alongside the native executable. A link to the report is also listed in the _Build artifacts_ section at the end of the build output. - You can specify a different filename or path for the report by appending it to the `build-report` argument, for example, `--emit build-report=/tmp/custom-name-build-report.html`. + You can specify a different filename or path for the report by appending it to the `build-report` option, for example, `--emit build-report=/tmp/custom-name-build-report.html`. (Optional) Run this executable with the same argument: ```shell diff --git a/docs/reference-manual/native-image/guides/specify-class-initialization.md b/docs/reference-manual/native-image/guides/specify-class-initialization.md index 4d8e67f91582..2dba798c61c1 100644 --- a/docs/reference-manual/native-image/guides/specify-class-initialization.md +++ b/docs/reference-manual/native-image/guides/specify-class-initialization.md @@ -7,22 +7,22 @@ permalink: /reference-manual/native-image/guides/specify-class-initialization/ # Specify Class Initialization Explicitly -By default, Native Image initializes application classes at runtime, except for the classes that Native Image proves "safe" for initialization at build time. -However, you can influence the default behavior by explicitly specifying the classes to be initialized at build-time or runtime. +By default, Native Image initializes application classes at run time, except for the classes that Native Image proves "safe" for initialization at build time. +However, you can influence the default behavior by explicitly specifying the classes to be initialized at build-time or run time. For that, there are two command-line options: `--initialize-at-build-time` and `--initialize-at-run-time`. You can use these options to specify whole packages or individual classes. For example, if you have the classes `p.C1`, `p.C2`, … ,`p.Cn`, you can specify that all the classes in the package `p` are to be initialized at build time by passing the following option to `native-image`: ```shell --initialize-at-build-time=p ``` -If you want only class `C1` in package `p` to be initialized at runtime, use: +If you want only class `C1` in package `p` to be initialized at run time, use: ```shell --initialize-at-run-time=p.C1 ``` You can also programmatically specify class initialization using the [`RuntimeClassInitialization`] class (https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeClassInitialization.java) from the [Native Image Feature interface](https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/Feature.java). -This guide demonstrates how to build a native executable by running the class initializer at runtime (default behavior), and then at build time, and compares the two approaches. +This guide demonstrates how to build a native executable by running the class initializer at run time (default behavior), and then at build time, and compares the two approaches. ### Prerequisite Make sure you have installed a GraalVM JDK. @@ -71,7 +71,7 @@ The parser creates records and adds them to a `List` collection. javac TalkParser.java ``` -3. Build a native executable, explicitly running the class initializer at runtime: +3. Build a native executable, explicitly running the class initializer at run time: ```bash native-image --initialize-at-run-time=TalkParser,Talk -o runtime-parser TalkParser ``` @@ -91,14 +91,14 @@ The parser creates records and adds them to a `List` collection. - Bootiful Spring Boot 3 by Josh Long ./runtime-parser 0.00s user 0.00s system 52% cpu 0.010 total ``` - The application parses the text block at runtime. + The application parses the text block at run time. Check the file size which should be around 13M: ``` du -sh runtime-parser ``` -5. Next, build a native executable initializing `TalkParser` at build time, and providing a different name for the output file to differentiate it from the previous build. The `Talk` record has to be initialized explicitly too, so the objects of this type will be persisted in the executable heap. +5. Next, build a native executable initializing `TalkParser` at build time, and providing a different name for the output file to differentiate it from the previous build. The `Talk` record has to be initialized explicitly too, so the objects of this type will be persisted in the image heap. ```bash native-image --initialize-at-build-time=TalkParser,Talk -o buildtime-parser TalkParser ``` @@ -165,11 +165,11 @@ Talks loaded using scanner: 0.002226000 seconds sys ``` -This demonstrates how Native Image can shift work from runtime to build time: when the class is initialized at build time, the text block is parsed when the executable is being built and only the parsed objects are included. +This demonstrates how Native Image can shift work from run time to build time: when the class is initialized at build time, the text block is parsed when the executable is being built and only the parsed objects are included. This not only makes the executable smaller in file size, but also faster to run: when the executable runs, the `Talk` records already exist and only need to be printed.
-To ensure native executables built with Native Image are as compatible as possible with the HotSpot behavior, application classes that cannot be safely initialized at build time, are initialized at runtime. +To ensure native executables built with Native Image are as compatible as possible with the HotSpot behavior, application classes that cannot be safely initialized at build time, are initialized at run time. You as a user, or a framework that you use, must explicitly request build-time initialization for certain classes to benefit from smaller file sizes and faster times to run. Include the right data structures to avoid the image size blowing up instead. We also recommend using `--initialize-at-build-time` with single classes only. diff --git a/docs/reference-manual/native-image/guides/use-system-properties.md b/docs/reference-manual/native-image/guides/use-system-properties.md index 91a6fb33dd7e..78acfb6e7db8 100644 --- a/docs/reference-manual/native-image/guides/use-system-properties.md +++ b/docs/reference-manual/native-image/guides/use-system-properties.md @@ -21,11 +21,11 @@ If you build a native executable using `native-image -Dfoo=bar App`, the system This means it is available to the [code in your application that is run at build time](http://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/ImageInfo.html#inImageBuildtimeCode--) (usually static field initializations and static initializers). Thus, if you run the resulting executable, it will not contain `foo` in the printed list of properties. -If, on the other hand, you run the executable with `app -Dfoo=bar`, it will display `foo` in the list of properties because you specified property at *executable runtime*. +If, on the other hand, you run the executable with `app -Dfoo=bar`, it will display `foo` in the list of properties because you specified property at *executable run time*. In other words: -* Pass `-D=` as an argument to `native-image` to control the properties seen at build time. -* Pass `-D=` as an argument to a native executable to control the properties seen at runtime. +* Pass `-D=` as an option to `native-image` to control the properties seen at build time. +* Pass `-D=` as an option to a native executable to control the properties seen at run time. ## Read System Properties at Build Time @@ -36,7 +36,7 @@ Make sure you have installed a GraalVM JDK. The easiest way to get started is with [SDKMAN!](https://sdkman.io/jdks#graal). For other installation options, visit the [Downloads section](https://www.graalvm.org/downloads/). -1. Save the following Java code into a file named _ReadProperties.java_, then compile it using `javac`: +1. Save the following Java code into a file named _ReadProperties.java_: ```java public class ReadProperties { private static final String STATIC_PROPERTY_KEY = "static_key"; @@ -65,7 +65,12 @@ For other installation options, visit the [Downloads section](https://www.graalv } ``` -2. Build the native executable, passing a system property as a command-line argument. Then run the native executable, passing a different system property on the command line. +2. Compile the application: + ```shell + javac ReadProperties.java + ``` + +3. Build the native executable, passing a system property as a command-line option. Then run the native executable, passing a different system property on the command line. ```shell native-image -Dstatic_key=STATIC_VALUE ReadProperties ``` @@ -81,26 +86,27 @@ For other installation options, visit the [Downloads section](https://www.graalv Value of instance property: INSTANCE_VALUE ``` - This indicates that the class static initializer was not run at build time, but at **runtime**. + This indicates that the class static initializer was not run at build time, but at **run time**. -3. To force the class static initializer to run at build time, use the `--initialize-at-build-time` flag, as follows: +4. To force the class static initializer to run at build time, use the `--initialize-at-build-time` option, as follows: ```shell native-image --initialize-at-build-time=ReadProperties -Dstatic_key=STATIC_VALUE ReadProperties ``` - In the output from the `native-image` tool you should see output similar to the following: - ```shell - ... - [1/7] Initializing... (7.7s @ 0.07GB) + In the output from the `native-image` tool you should see the message like this: + ``` + GraalVM Native Image: Generating 'readproperties' (executable)... + ========================================================================== Getting value of static property with key: static_key + [1/8] Initializing... (4.0s @ 0.13GB) ... ``` - 4. Run the executable again, as follows: +5. Run the executable again, as follows: ```shell ./readproperties -Dinstance_key=INSTANCE_VALUE ``` - This time you should see the following output, confirming that the static initializer was run at **build time**, not at runtime. + This time you should see the following output, confirming that the static initializer was run at **build time**, not at run time. ```shell Value of static property: STATIC_VALUE diff --git a/docs/security/SBOM.md b/docs/security/SBOM.md index 540f9fc3e0e9..a09d60b9a49b 100644 --- a/docs/security/SBOM.md +++ b/docs/security/SBOM.md @@ -71,8 +71,20 @@ It outputs the SBOM in the following format: ## Vulnerability Scanning -To scan for any vulnerable libraries, submit the SBOM to a vulnerability scanner. -For example, the popular [Anchore software supply chain management platform](https://anchore.com/) makes the `grype` scanner freely available. +To scan for vulnerable libraries, use either the [GraalVM GitHub Action](https://github.com/marketplace/actions/github-action-for-graalvm) for automated scanning, or submit the SBOM to a vulnerability scanner directly. + +### GitHub Integration + +When using the [GraalVM GitHub Action](https://github.com/marketplace/actions/github-action-for-graalvm), the SBOM can be automatically generated and submitted to GitHub's dependency submission API for continuous scanning. +This enables: +- Vulnerability tracking with GitHub's Dependabot. +- Dependency tracking with GitHub's Dependency Graph. + +If you use GitHub, this integration offers you the simplest setup and powerful security tooling. + +### Command-Line Scanning + +The popular [Anchore software supply chain management platform](https://anchore.com/) makes the `grype` scanner available for free. You can check whether the libraries given in your SBOMs have known vulnerabilities documented in Anchore's database. For this purpose, the output of the tool can be fed directly to the `grype` scanner to check for vulnerable libraries, using the command `$JAVA_HOME/bin/native-image-inspect --sbom | grype` which produces the following output: ```shell @@ -91,6 +103,8 @@ This dependency information is derived from Native Image's static analysis call Analyzing the dependency graph can help you understand why specific components are included in your application. For example, discovering an unexpected component in the SBOM allows for tracing its inclusion through the dependency graph to identify which parts of the application are using it. +With the GraalVM GitHub Action, you get access to GitHub's Dependency Graph feature. + ## Enhanced SBOMs with Maven Plugin for Native Image To generate more accurate SBOMs with richer component metadata, consider using the [Maven plugin for GraalVM Native Image](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html). diff --git a/docs/security/polyglot-sandbox.md b/docs/security/polyglot-sandbox.md index adada38489ba..08488fa6e6b8 100644 --- a/docs/security/polyglot-sandbox.md +++ b/docs/security/polyglot-sandbox.md @@ -58,7 +58,7 @@ The CONSTRAINED policy: * Disallows [environment access](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Context.Builder.html#allowEnvironmentAccess-org.graalvm.polyglot.EnvironmentAccess-). * Restricts host access: * Disallows [host class loading](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Context.Builder.html#allowHostClassLoading-boolean-). - * Disallows [to all public host classes and methods by default](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/HostAccess.Builder.html#allowPublicAccess-boolean-). + * Disallows [access to all public host classes and methods by default](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/HostAccess.Builder.html#allowPublicAccess-boolean-). * Disallows [access inheritance](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/HostAccess.Builder.html#allowAccessInheritance-boolean-). * Disallows implementation of arbitrary host [classes](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/HostAccess.Builder.html#allowAllClassImplementations-boolean-) and [interfaces](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/HostAccess.Builder.html#allowAllImplementations-boolean-). * Disallows implementation of `java.lang.FunctionalInterface`. @@ -109,7 +109,7 @@ Isolated versions of the languages can be downloaded from Maven using the follow org.graalvm.polyglot js-isolate - ${graalvm.version} + ${graalvm.polyglot.version} pom ``` @@ -233,7 +233,7 @@ Certain limits can be [reset](#resetting-resource-limits) at any point of time d The `sandbox.MaxCPUTime` option allows you to specify the maximum CPU time spent running guest code. CPU time spent depends on the underlying hardware. -The maximum [CPU time](https://docs.oracle.com/en/java/javase/22/docs/api/java.management/java/lang/management/ThreadMXBean.html#getThreadCpuTime\(long\)) specifies how long a context can be active until it is automatically cancelled and the context is closed. +The maximum [CPU time](https://docs.oracle.com/en/java/javase/23/docs/api/java.management/java/lang/management/ThreadMXBean.html#getThreadCpuTime\(long\)) specifies how long a context can be active until it is automatically cancelled and the context is closed. By default the time limit is checked every 10 milliseconds. This can be customized using the `sandbox.MaxCPUTimeCheckInterval` option. @@ -343,7 +343,7 @@ try (Context context = Context.newBuilder("js") } ``` -The limit is checked by retained size computation triggered either based on [allocated](https://docs.oracle.com/en/java/javase/22/docs/api/jdk.management/com/sun/management/ThreadMXBean.html#getThreadAllocatedBytes\(long\)) bytes or on [low memory notification](https://docs.oracle.com/en/java/javase/22/docs/api/java.management/java/lang/management/MemoryMXBean.html). +The limit is checked by retained size computation triggered either based on [allocated](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.management/com/sun/management/ThreadMXBean.html#getThreadAllocatedBytes\(long\)) bytes or on [low memory notification](https://docs.oracle.com/en/java/javase/23/docs/api/java.management/java/lang/management/MemoryMXBean.html). The allocated bytes are checked by a separate high-priority thread that will be woken regularly. There is one such thread for each memory-limited context (one with `sandbox.MaxHeapMemory` set). @@ -360,7 +360,7 @@ Retained size computation for a context can be customized using the expert optio Retained size computation for a context is triggered when a retained bytes estimate exceeds a certain factor of specified `sandbox.MaxHeapMemory`. The estimate is based on heap memory -[allocated](https://docs.oracle.com/en/java/javase/22/docs/api/jdk.management/com/sun/management/ThreadMXBean.html#getThreadAllocatedBytes\(long\)) by threads where the context has been active. +[allocated](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.management/com/sun/management/ThreadMXBean.html#getThreadAllocatedBytes\(long\)) by threads where the context has been active. More precisely, the estimate is the result of previous retained bytes computation, if available, plus bytes allocated since the start of the previous computation. By default the factor of `sandbox.MaxHeapMemory` is 1.0 and it can be customized by the `sandbox.AllocatedBytesCheckFactor` option. The factor must be positive. @@ -377,7 +377,7 @@ This can be configured by the `sandbox.RetainedBytesCheckInterval` option. The i The allocated bytes checking for a context can be disabled by the `sandbox.AllocatedBytesCheckEnabled` option. By default it is enabled ("true"). If disabled ("false"), retained size checking for the context can be triggered only by the low memory trigger. -When the total number of bytes allocated in the heap for the whole host VM exceeds a certain factor of the total heap memory of the VM, [low memory notification](https://docs.oracle.com/en/java/javase/22/docs/api/java.management/java/lang/management/MemoryMXBean.html) is invoked and initiates the following process. +When the total number of bytes allocated in the heap for the whole host VM exceeds a certain factor of the total heap memory of the VM, [low memory notification](https://docs.oracle.com/en/java/javase/23/docs/api/java.management/java/lang/management/MemoryMXBean.html) is invoked and initiates the following process. The execution pauses for all execution contexts where the `sandbox.MaxHeapMemory` option is set. The execution is resumed only when retained bytes in the heap for each memory-limited context are computed and contexts exceeding their limits are cancelled. The default factor is 0.7. This can be configured by the `sandbox.RetainedBytesCheckFactor` option. The factor must be between 0.0 and 1.0. All contexts using the `sandbox.MaxHeapMemory` option must use the same value for `sandbox.RetainedBytesCheckFactor`. diff --git a/docs/security/security-guide.md b/docs/security/security-guide.md index 8aa1eb37b6f6..11eeeb2d40b4 100644 --- a/docs/security/security-guide.md +++ b/docs/security/security-guide.md @@ -58,7 +58,7 @@ In managed mode, all access to unmanaged code including the operating system is * Regarding type safety, it is not possible to reinterpret a data pointer into a function pointer and execute arbitrary instructions (since these are distinct pointer types for LLVM runtime). * System calls are intercepted and routed to the corresponding Truffle APIs. For example, file IO is mapped to the Truffle `FileSystem` API. The set of currently supported system calls is very limited—only syscalls that can safely be mapped to the Truffle API level are available. Since LLVM Runtime in managed mode always runs bitcode compiled for Linux/x86, it only needs to implement system calls for this platform. -* All dependent libraries are executed in managed mode as well, removing all references to natively executed system libraries. This includes libraries that are provided by the LLVM Runtime, such as muslibc. +* All dependent libraries are executed in managed mode as well, removing all references to natively executed system libraries. This includes libraries that are provided by the LLVM Runtime, such as `muslibc`. Managed mode can be selected when creating a context `(Context.create())` or when calling the `bin/lli` binary by specifying the `--llvm.managed` option. A "managed" context will adhere to any restrictions (for example, `allowIO`) passed during context creation and does not need the `allowNativeAccess` privilege. diff --git a/espresso/ci/ci_common/common.jsonnet b/espresso/ci/ci_common/common.jsonnet index 37295ed2b3a9..b0851c81c4bb 100644 --- a/espresso/ci/ci_common/common.jsonnet +++ b/espresso/ci/ci_common/common.jsonnet @@ -277,7 +277,7 @@ local benchmark_suites = ['dacapo', 'renaissance', 'scala-dacapo']; local _builds = [ // Gates - that.jdk21_gate_linux_amd64 + that.eclipse + that.jdt + that.predicates(false, false, false) + that.espresso_gate(allow_warnings=false, tags='style,fullbuild', timelimit='35:00', name='gate-espresso-style-jdk21-linux-amd64'), + that.jdk21_gate_linux_amd64 + that.eclipse + that.jdt + that.predicates(false, false, false) + that.espresso_gate(allow_warnings=false, tags='style,fullbuild,imports', timelimit='35:00', name='gate-espresso-style-jdk21-linux-amd64'), ], builds: utils.add_defined_in(_builds, std.thisFile), diff --git a/espresso/mx.espresso/import_order.py b/espresso/mx.espresso/import_order.py new file mode 100644 index 000000000000..5446d3f55804 --- /dev/null +++ b/espresso/mx.espresso/import_order.py @@ -0,0 +1,190 @@ +# +# 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. +# +# 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. +# + +"""Provides import ordering capabilities for .java files.""" + +from glob import iglob + +# If a given line is an import, it will end with this suffix. +# Used to strip this suffix for faster string comparison. +SUFFIX = ";\n" + +STATIC_PREFIX = "import static " +REGULAR_PREFIX = "import " + +def verify_order(path, prefix_order): + """ + Verifies import order of java files under the given path. + + Iterates over all '.java' files in the given path, recursively over its subfolders. + It then checks that imports in these files are ordered. + + Here are the rules: + 1. All imports that starts with a suffix that appears in this list must + be found before any other import with a suffix that appears later in + this list. + 2. All imports with a given suffix must be in lexicographic order within + all other imports with the same prefix. + 3. Static imports must appear before regular imports. + + :param path: Where to look for the java files. + :param prefix_order: An ordered list of expected suffixes. + :return: The list of files violating the specified order. + """ + + # Validate the prefixes + err = validate_format(prefix_order) + if err: + # Failure is represented with a non-empty list + return [err] + + # Start building definitive list of import prefixes + static_prefixes = [] + regular_prefixes = [] + + for prefix in prefix_order: + if prefix: + # If prefix is "abc", add "import abc" + regular_prefixes.append(REGULAR_PREFIX + prefix + '.') + # Eclipse formatting does not enforce prefix order for static imports. + else: + # Empty prefix means everything will match. + # Empty prefix is added manually below. + break + + # Ensure we have the empty prefix + # Add "import static " + static_prefixes.append(STATIC_PREFIX) + # Add "import " + regular_prefixes.append(REGULAR_PREFIX) + + # Ensures static imports are before regular imports. + prefix_format = static_prefixes + regular_prefixes + + invalid_files = [] + + def is_sorted(li): + if len(li) <= 1: + return True + return all(li[i] <= li[i + 1] for i in range(len(li) - 1)) + + def check_file(to_check, prefix_format): + imports, prefix_ordered = get_imports(to_check, prefix_format) + + if not prefix_ordered: + return False + + for import_list in imports: + if not is_sorted(import_list): + return False + + return True + + for file in iglob(path + '/**/*.java', recursive=True): + if not check_file(file, prefix_format): + invalid_files.append(file) + + return invalid_files + +def validate_format(prefix_order): + """ + Validates a given ordered list of prefix for import order verification. + + Returns the reason for failure of validation if any, or an empty string + if the prefixes are well-formed. + """ + for prefix in prefix_order: + if prefix.endswith('.'): + return "Invalid format for the ordered prefixes: \n'" + prefix + "' must not end with a '.'" + return "" + +def get_imports(file, prefix_format): + """ + Obtains list of imports list, each corresponding to each specified prefix. + Also returns whether the found prefixes were ordered. + + In case the prefixes where not ordered, the last element of the returned list will contain + every import after the violating line + """ + def add_import(li, value, prefix, suf): + to_add = value[len(prefix):] + if to_add.endswith(suf): + to_add = to_add[:len(to_add) - len(suf)] + li.append(to_add) + + def enter_fail_state(imports, prefix_format, cur_prefix_imports): + if cur_prefix_imports: + imports.append(cur_prefix_imports) + return False, len(prefix_format), "" + + with open(file) as f: + imports = [] + prefix_ordered = True + + cur_prefix_idx = 0 + cur_prefix = prefix_format[cur_prefix_idx] + + cur_prefix_imports = [] + + for line in f.readlines(): + ignore = not line.startswith("import") + if ignore: + # start of class declaration, we can stop looking for imports. + end = 'class ' in line or 'interface ' in line or 'enum ' in line or 'record ' in line + if end: + break + continue + + if line.startswith(cur_prefix): + # If we are still ensuring prefix ordering, ensure that this line does not belong + # to a previous prefix. + if prefix_ordered: + for i in range(cur_prefix_idx): + if line.startswith(prefix_format[i]): + # A match for a previous prefix was found: enter fail state + prefix_ordered, cur_prefix_idx, cur_prefix = enter_fail_state(imports, prefix_format, cur_prefix_imports) + cur_prefix_imports = [] + add_import(cur_prefix_imports, line, cur_prefix, SUFFIX) + else: + # cur_prefix not found, advance to next prefix if found, report failure if not. + for i in range(cur_prefix_idx + 1, len(prefix_format)): + if line.startswith(prefix_format[i]): + # Report imports for current prefix, + if cur_prefix_imports: + imports.append(cur_prefix_imports) + # Set state to next prefix. + cur_prefix = prefix_format[i] + cur_prefix_idx = i + cur_prefix_imports = [] + add_import(cur_prefix_imports, line, cur_prefix, SUFFIX) + break + else: + # On failure, dump remaining lines into the last cur_prefix_imports. + prefix_ordered, cur_prefix_idx, cur_prefix = enter_fail_state(imports, prefix_format, cur_prefix_imports) + cur_prefix_imports = [] + add_import(cur_prefix_imports, line, cur_prefix, SUFFIX) + + if cur_prefix_imports: + imports.append(cur_prefix_imports) + + return imports, prefix_ordered diff --git a/espresso/mx.espresso/mx_espresso.py b/espresso/mx.espresso/mx_espresso.py index 6d7b381bab52..80a24e3d35bd 100644 --- a/espresso/mx.espresso/mx_espresso.py +++ b/espresso/mx.espresso/mx_espresso.py @@ -41,11 +41,12 @@ from mx_gate import Task, add_gate_runner from mx_jackpot import jackpot from os.path import join, isabs, exists, dirname, relpath, basename +from import_order import verify_order, validate_format _suite = mx.suite('espresso') # re-export custom mx project classes, so they can be used from suite.py -from mx_sdk_shaded import ShadedLibraryProject # pylint: disable=unused-import +from mx_sdk_shaded import ShadedLibraryProject # JDK compiled with the Sulong toolchain. espresso_llvm_java_home = mx.get_env('ESPRESSO_LLVM_JAVA_HOME') or mx.get_env('LLVM_JAVA_HOME') @@ -129,14 +130,53 @@ def _run_espresso(args=None, cwd=None, nonZeroIsFatal=True, out=None, err=None, def _run_espresso_meta(args, nonZeroIsFatal=True, timeout=None): """Run Espresso (standalone) on Espresso (launcher)""" - return _run_espresso_launcher([ + return _run_espresso([ '--vm.Xss4m', ] + _espresso_standalone_command(args, allow_jacoco=False), nonZeroIsFatal=nonZeroIsFatal, timeout=timeout) +def _run_verify_imports(s): + # Look for the format specification in the suite + prefs = s.eclipse_settings_sources().get('org.eclipse.jdt.ui.prefs') + prefix_order = [] + if prefs: + for pref in prefs: + with open(pref) as f: + for line in f.readlines(): + if line.startswith('org.eclipse.jdt.ui.importorder'): + key_value_sep_index = line.find('=') + if key_value_sep_index != -1: + value = line.strip()[key_value_sep_index + 1:] + prefix_order = value.split(';') + + # Validate import order format + err = validate_format(prefix_order) + if err: + mx.abort(err) + + # Find invalid files + invalid_files = [] + for project in s.projects: + if isinstance(project, ShadedLibraryProject): + # Ignore shaded libraries + continue + for src_dir in project.source_dirs(): + invalid_files += verify_order(src_dir, prefix_order) + + if invalid_files: + mx.abort("The following files have wrong imports order:\n" + '\n'.join(invalid_files)) + + print("All imports correctly ordered!") + +def _run_verify_imports_espresso(args): + if args: + mx.abort("No arguments expected for verify-imports") + _run_verify_imports(_suite) + class EspressoTags: jackpot = 'jackpot' verify = 'verify' + imports = 'imports' def _espresso_gate_runner(args, tasks): @@ -149,6 +189,10 @@ def _espresso_gate_runner(args, tasks): if t: mx_sdk_vm.verify_graalvm_configs(suites=['espresso']) + with Task('Espresso: verify import order', tasks, tags=[EspressoTags.imports]) as t: + if t: + _run_verify_imports(_suite) + mokapot_header_gate_name = 'Verify consistency of mokapot headers' with Task(mokapot_header_gate_name, tasks, tags=[EspressoTags.verify]) as t: if t: @@ -831,6 +875,7 @@ def gen_gc_option_check(args): 'java-truffle': [_run_java_truffle, '[args]'], 'espresso-meta': [_run_espresso_meta, '[args]'], 'gen-gc-option-check': [gen_gc_option_check, '[path to isolate-creation-only-options.txt]'], + 'verify-imports': [_run_verify_imports_espresso, ''], }) diff --git a/espresso/mx.espresso/suite.py b/espresso/mx.espresso/suite.py index 2c597c4409e9..4a1b83732008 100644 --- a/espresso/mx.espresso/suite.py +++ b/espresso/mx.espresso/suite.py @@ -24,7 +24,7 @@ suite = { "mxversion": "7.33.0", "name": "espresso", - "version" : "24.2.0", + "version" : "25.0.0", "release" : False, "groupId" : "org.graalvm.espresso", "url" : "https://www.graalvm.org/reference-manual/java-on-truffle/", @@ -129,6 +129,19 @@ "checkstyle": "com.oracle.truffle.espresso", }, + # Shared link resolver + "com.oracle.truffle.espresso.shared": { + "subDir": "src", + "sourceDirs": ["src"], + "dependencies": [ + "com.oracle.truffle.espresso.classfile", + ], + "requires": [ + ], + "javaCompliance" : "17+", + "checkstyle": "com.oracle.truffle.espresso", + }, + "com.oracle.truffle.espresso": { "subDir": "src", "sourceDirs": ["src"], @@ -137,6 +150,7 @@ "truffle:TRUFFLE_NFI", "com.oracle.truffle.espresso.jdwp", "com.oracle.truffle.espresso.shadowed.asm", + "com.oracle.truffle.espresso.shared", ], "requires": [ "java.logging", @@ -346,7 +360,7 @@ "javaCompliance" : "17+", "spotbugs" : "false", "shadedDependencies" : [ - "truffle:ASM_9.5", + "truffle:ASM_9.7.1", ], "class" : "ShadedLibraryProject", "shade" : { diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClassfileParser.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClassfileParser.java index 25f128f1aa57..4167fc3a8c0b 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClassfileParser.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClassfileParser.java @@ -1821,6 +1821,18 @@ private ParserField parseField(boolean isInterface) throws ValidationException { if (constantValue.getConstantValueIndex() == 0) { throw classFormatError("Invalid ConstantValue index"); } + PoolConstant entry = pool.at(constantValue.getConstantValueIndex(), "constant value index"); + boolean isValid = switch (entry.tag()) { + case INTEGER -> Types.getJavaKind(descriptor).isStackInt(); + case FLOAT -> descriptor == Type._float; + case LONG -> descriptor == Type._long; + case DOUBLE -> descriptor == Type._double; + case STRING -> descriptor == Type.java_lang_String; + default -> false; + }; + if (!isValid) { + throw classFormatError("Invalid ConstantValue index entry type"); + } } else if (attributeName.equals(Name.Synthetic)) { fieldFlags |= ACC_SYNTHETIC; fieldAttributes[i] = new Attribute(attributeName, null); diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClassfileStream.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClassfileStream.java index cc2015f0e15e..9fba36971d65 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClassfileStream.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClassfileStream.java @@ -22,13 +22,13 @@ */ package com.oracle.truffle.espresso.classfile; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; - import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; + /** * Operations for sequentially scanning data items in a class file. Any IO exceptions that occur * during scanning are converted to {@link ParserException.ClassFormatError}s. diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClasspathEntry.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClasspathEntry.java index 283c184b0a8d..27eaab3f2aa8 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClasspathEntry.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClasspathEntry.java @@ -22,10 +22,10 @@ */ package com.oracle.truffle.espresso.classfile; -import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; - import java.io.File; +import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; + /** * An entry in a classpath is a file system path that denotes an existing directory, an existing * zip/jar file or a file. diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClasspathFile.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClasspathFile.java index 64d72520a66b..aba19859bcc8 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClasspathFile.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClasspathFile.java @@ -22,10 +22,10 @@ */ package com.oracle.truffle.espresso.classfile; -import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; - import java.io.File; +import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; + /** * Encapsulates the contents of a file loaded from an {@linkplain ClasspathEntry entry} on a * classpath. diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ConstantPool.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ConstantPool.java index 9ae53512c30f..5d430fe088c6 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ConstantPool.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ConstantPool.java @@ -22,9 +22,23 @@ */ package com.oracle.truffle.espresso.classfile; +import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.CLASS; +import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.DOUBLE; +import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.FIELD_REF; +import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.FLOAT; +import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INTEGER; +import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INTERFACE_METHOD_REF; +import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INVOKEDYNAMIC; +import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.LONG; +import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.METHOD_REF; +import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.NAME_AND_TYPE; +import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.STRING; +import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.UTF8; + +import java.util.Arrays; +import java.util.Formatter; + import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol.ModifiedUTF8; import com.oracle.truffle.espresso.classfile.constantpool.ClassConstant; import com.oracle.truffle.espresso.classfile.constantpool.ClassMethodRefConstant; import com.oracle.truffle.espresso.classfile.constantpool.DoubleConstant; @@ -40,22 +54,8 @@ import com.oracle.truffle.espresso.classfile.constantpool.PoolConstant; import com.oracle.truffle.espresso.classfile.constantpool.StringConstant; import com.oracle.truffle.espresso.classfile.constantpool.Utf8Constant; - -import java.util.Arrays; -import java.util.Formatter; - -import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.CLASS; -import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.DOUBLE; -import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.FIELD_REF; -import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.FLOAT; -import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INTEGER; -import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INTERFACE_METHOD_REF; -import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INVOKEDYNAMIC; -import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.LONG; -import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.METHOD_REF; -import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.NAME_AND_TYPE; -import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.STRING; -import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.UTF8; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.ModifiedUTF8; /** * Immutable, shareable constant-pool representation. diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ImmutableConstantPool.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ImmutableConstantPool.java index d34e707718cf..c5f4c392557f 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ImmutableConstantPool.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ImmutableConstantPool.java @@ -22,16 +22,16 @@ */ package com.oracle.truffle.espresso.classfile; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Objects; + import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.constantpool.ClassConstant; import com.oracle.truffle.espresso.classfile.constantpool.PoolConstant; import com.oracle.truffle.espresso.classfile.constantpool.Utf8Constant; - -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Objects; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; /** * Immutable constant pool implementation backed by an array of constants. diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserField.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserField.java index a2f039c7b8a8..0a96a92726c9 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserField.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserField.java @@ -22,16 +22,16 @@ */ package com.oracle.truffle.espresso.classfile; +import static com.oracle.truffle.espresso.classfile.Constants.ACC_HIDDEN; + import java.lang.reflect.Modifier; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; +import com.oracle.truffle.espresso.classfile.attributes.Attribute; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; -import com.oracle.truffle.espresso.classfile.attributes.Attribute; - -import static com.oracle.truffle.espresso.classfile.Constants.ACC_HIDDEN; public final class ParserField { public static final ParserField[] EMPTY_ARRAY = new ParserField[0]; diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserKlass.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserKlass.java index a3108e3a8e45..4373e3e723a9 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserKlass.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserKlass.java @@ -23,10 +23,10 @@ package com.oracle.truffle.espresso.classfile; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; +import com.oracle.truffle.espresso.classfile.attributes.Attribute; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; -import com.oracle.truffle.espresso.classfile.attributes.Attribute; /** * Immutable raw representation of classes in Espresso, this is the output of the parser, super diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserMethod.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserMethod.java index 5ff6c818ca62..c6cb51712f5c 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserMethod.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserMethod.java @@ -23,10 +23,10 @@ package com.oracle.truffle.espresso.classfile; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; +import com.oracle.truffle.espresso.classfile.attributes.Attribute; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; -import com.oracle.truffle.espresso.classfile.attributes.Attribute; /** * Immutable raw representation of methods in Espresso, this is the output of the parser. diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParsingContext.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParsingContext.java index fb7ce43dd9cf..9400d555b6c7 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParsingContext.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParsingContext.java @@ -22,15 +22,15 @@ */ package com.oracle.truffle.espresso.classfile; +import java.util.function.Supplier; + +import com.oracle.truffle.espresso.classfile.constantpool.Utf8Constant; import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; -import com.oracle.truffle.espresso.classfile.constantpool.Utf8Constant; import com.oracle.truffle.espresso.classfile.perf.TimerCollection; -import java.util.function.Supplier; - public interface ParsingContext { JavaVersion getJavaVersion(); diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/attributes/CodeAttribute.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/attributes/CodeAttribute.java index 094613b8727e..58ff416efeff 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/attributes/CodeAttribute.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/attributes/CodeAttribute.java @@ -23,20 +23,20 @@ package com.oracle.truffle.espresso.classfile.attributes; -import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; -import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; -import com.oracle.truffle.espresso.classfile.ClassfileParser; -import com.oracle.truffle.espresso.classfile.ExceptionHandler; - import static com.oracle.truffle.espresso.classfile.bytecode.Bytecodes.JSR; import static com.oracle.truffle.espresso.classfile.bytecode.Bytecodes.JSR_W; import static com.oracle.truffle.espresso.classfile.bytecode.Bytecodes.MONITORENTER; import static com.oracle.truffle.espresso.classfile.bytecode.Bytecodes.MONITOREXIT; import static com.oracle.truffle.espresso.classfile.bytecode.Bytecodes.RET; +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; +import com.oracle.truffle.espresso.classfile.ClassfileParser; +import com.oracle.truffle.espresso.classfile.ExceptionHandler; +import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; + public final class CodeAttribute extends Attribute { public static final Symbol NAME = Name.Code; diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/attributes/Local.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/attributes/Local.java index a21f881dde7a..f9ca462f2d66 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/attributes/Local.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/attributes/Local.java @@ -22,14 +22,15 @@ */ package com.oracle.truffle.espresso.classfile.attributes; +import java.util.Objects; + +import org.graalvm.collections.Equivalence; + +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; -import com.oracle.truffle.espresso.classfile.JavaKind; -import org.graalvm.collections.Equivalence; - -import java.util.Objects; /** * Describes the type and bytecode index range in which a local variable is live. diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/ClassConstant.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/ClassConstant.java index 9ec794128b54..7bffbcfc7916 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/ClassConstant.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/ClassConstant.java @@ -24,12 +24,12 @@ import java.nio.ByteBuffer; -import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.ConstantPool.Tag; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Validation; +import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; /** * Interface denoting a class entry in a constant pool. diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/ClassMethodRefConstant.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/ClassMethodRefConstant.java index a647a9d83c67..edd4d627ea60 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/ClassMethodRefConstant.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/ClassMethodRefConstant.java @@ -22,12 +22,12 @@ */ package com.oracle.truffle.espresso.classfile.constantpool; -import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.ConstantPool.Tag; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Descriptor; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; +import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; public interface ClassMethodRefConstant extends MethodRefConstant { diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/FieldRefConstant.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/FieldRefConstant.java index a5d8ae5c1c43..bd639bc8790a 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/FieldRefConstant.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/FieldRefConstant.java @@ -22,12 +22,12 @@ */ package com.oracle.truffle.espresso.classfile.constantpool; +import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.ConstantPool.Tag; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; -import com.oracle.truffle.espresso.classfile.ConstantPool; -import com.oracle.truffle.espresso.classfile.ConstantPool.Tag; public interface FieldRefConstant extends MemberRefConstant { diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/InvokeDynamicConstant.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/InvokeDynamicConstant.java index 77893446521a..c4372a98e613 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/InvokeDynamicConstant.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/InvokeDynamicConstant.java @@ -24,13 +24,13 @@ import java.nio.ByteBuffer; +import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.ConstantPool.Tag; import com.oracle.truffle.espresso.classfile.descriptors.Signatures; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; -import com.oracle.truffle.espresso.classfile.ConstantPool; -import com.oracle.truffle.espresso.classfile.ConstantPool.Tag; public interface InvokeDynamicConstant extends BootstrapMethodConstant { diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MemberRefConstant.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MemberRefConstant.java index cc3df961371f..5819ae00d8e7 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MemberRefConstant.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MemberRefConstant.java @@ -22,13 +22,13 @@ */ package com.oracle.truffle.espresso.classfile.constantpool; +import java.nio.ByteBuffer; + +import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Descriptor; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; -import com.oracle.truffle.espresso.classfile.ConstantPool; - -import java.nio.ByteBuffer; /** * Interface denoting a field or method entry in a constant pool. diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MethodHandleConstant.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MethodHandleConstant.java index 65cfc78c24fb..c14a724704d4 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MethodHandleConstant.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MethodHandleConstant.java @@ -24,12 +24,12 @@ import java.nio.ByteBuffer; -import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; import com.oracle.truffle.espresso.classfile.ClassfileParser; import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.ConstantPool.Tag; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; +import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; public interface MethodHandleConstant extends PoolConstant { diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MethodRefConstant.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MethodRefConstant.java index 2f09edc282ab..fdacba7754f7 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MethodRefConstant.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MethodRefConstant.java @@ -22,10 +22,10 @@ */ package com.oracle.truffle.espresso.classfile.constantpool; +import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; -import com.oracle.truffle.espresso.classfile.ConstantPool; public interface MethodRefConstant extends MemberRefConstant { diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MethodTypeConstant.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MethodTypeConstant.java index c07850d3963b..96f23fffc4db 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MethodTypeConstant.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/MethodTypeConstant.java @@ -22,14 +22,14 @@ */ package com.oracle.truffle.espresso.classfile.constantpool; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol; -import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; -import com.oracle.truffle.espresso.classfile.ConstantPool; -import com.oracle.truffle.espresso.classfile.ConstantPool.Tag; +import static com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; import java.nio.ByteBuffer; -import static com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; +import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.ConstantPool.Tag; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; public interface MethodTypeConstant extends PoolConstant { diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/NameAndTypeConstant.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/NameAndTypeConstant.java index d35eea5d998d..902b14667abc 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/NameAndTypeConstant.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/NameAndTypeConstant.java @@ -24,12 +24,12 @@ import java.nio.ByteBuffer; -import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.ConstantPool.Tag; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Descriptor; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; +import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; public interface NameAndTypeConstant extends PoolConstant { diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/PoolConstant.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/PoolConstant.java index 50ddef4d8dff..e1c4c9beb985 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/PoolConstant.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/PoolConstant.java @@ -22,11 +22,11 @@ */ package com.oracle.truffle.espresso.classfile.constantpool; -import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; +import java.nio.ByteBuffer; + import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.ConstantPool.Tag; - -import java.nio.ByteBuffer; +import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; /** * Base interface for entries in a constant pool. diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/StringConstant.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/StringConstant.java index 2f2bf693df4e..d2a47caab329 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/StringConstant.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/StringConstant.java @@ -24,11 +24,11 @@ import java.nio.ByteBuffer; -import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.ConstantPool.Tag; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.ModifiedUTF8; +import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; public interface StringConstant extends PoolConstant { diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/Utf8Constant.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/Utf8Constant.java index aa9a7db76bbb..95543a5795de 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/Utf8Constant.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/constantpool/Utf8Constant.java @@ -24,15 +24,15 @@ import java.nio.ByteBuffer; +import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.ConstantPool.Tag; import com.oracle.truffle.espresso.classfile.descriptors.ModifiedUtf8; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; -import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; -import com.oracle.truffle.espresso.classfile.ConstantPool; -import com.oracle.truffle.espresso.classfile.ConstantPool.Tag; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Validation; +import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; public final class Utf8Constant implements PoolConstant { diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Signatures.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Signatures.java index 62ab7072e6cd..b290e34c06a1 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Signatures.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Signatures.java @@ -32,11 +32,11 @@ import java.util.function.Function; import com.oracle.truffle.api.nodes.ExplodeLoop; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.ParserException; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Descriptor; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; -import com.oracle.truffle.espresso.classfile.JavaKind; /** * Represents a method signature provided by the runtime. diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Symbol.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Symbol.java index 2d4d05c86f31..5e7e38145dba 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Symbol.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Symbol.java @@ -617,6 +617,8 @@ public static void ensureInitialized() { public static final Symbol HIDDEN_INTERNAL_TYPE = StaticSymbols.putName("0HIDDEN_INTERNAL_TYPE"); public static final Symbol rawType = StaticSymbols.putName("rawType"); + public static final Symbol espresso_polyglot = StaticSymbols.putName("espresso.polyglot"); + // Class redefinition plugin helpers public static final Symbol flushFromCaches = StaticSymbols.putName("flushFromCaches"); public static final Symbol generateProxyClass = StaticSymbols.putName("generateProxyClass"); diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Types.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Types.java index 2146c6588772..c400d17e33aa 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Types.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Types.java @@ -28,11 +28,11 @@ import java.util.Arrays; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.ParserException; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Descriptor; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; -import com.oracle.truffle.espresso.classfile.JavaKind; /** * Manages creation and parsing of type descriptors ("field descriptors" in the JVMS). diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Utf8ConstantTable.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Utf8ConstantTable.java index bd33f42a0f01..fa678e2e2f82 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Utf8ConstantTable.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Utf8ConstantTable.java @@ -22,12 +22,12 @@ */ package com.oracle.truffle.espresso.classfile.descriptors; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.espresso.classfile.constantpool.Utf8Constant; - import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.espresso.classfile.constantpool.Utf8Constant; + /** * Global Utf8Constant table. */ diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/ValidationException.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/ValidationException.java index 9eee88a53917..17ea32b19c29 100644 --- a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/ValidationException.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/ValidationException.java @@ -22,10 +22,10 @@ */ package com.oracle.truffle.espresso.classfile.descriptors; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; - import java.io.Serial; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; + public final class ValidationException extends Exception { @Serial private static final long serialVersionUID = 3253956176818294799L; diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/tables/AbstractModuleTable.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/tables/AbstractModuleTable.java new file mode 100644 index 000000000000..eaa7cf640c06 --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/tables/AbstractModuleTable.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2018, 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. + * + * 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.truffle.espresso.classfile.tables; + +import java.util.ArrayList; +import java.util.concurrent.locks.ReadWriteLock; + +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; + +public abstract class AbstractModuleTable> extends EntryTable> { + public AbstractModuleTable(ReadWriteLock lock) { + super(lock); + } + + public ME createAndAddEntry(Symbol name, String version, String location, boolean isOpen, M module) { + return createAndAddEntry(name, new ModuleData<>(version, location, module, isOpen)); + } + + public ME createUnnamedModuleEntry(M module) { + ME result = createEntry(null, new ModuleData<>(null, null, module, true)); + result.setCanReadAllUnnamed(); + return result; + } + + public static final class ModuleData { + private final String version; + private final String location; + private final boolean isOpen; + private final M module; + + public ModuleData(String version, String location, M module, boolean isOpen) { + this.version = version; + this.location = location; + this.isOpen = isOpen; + this.module = module; + } + } + + public abstract static class AbstractModuleEntry extends EntryTable.NamedEntry { + private final boolean isOpen; + private M module; + private String version; + private String location; + private boolean canReadAllUnnamed; + private ArrayList> reads; + + protected AbstractModuleEntry(Symbol name, ModuleData data) { + super(name); + this.version = data.version; + this.location = data.location; + this.isOpen = data.isOpen; + this.module = data.module; + } + + public void addReads(AbstractModuleEntry from) { + if (!isNamed()) { + return; + } + synchronized (this) { + if (from == null) { + setCanReadAllUnnamed(); + return; + } + if (reads == null) { + reads = new ArrayList<>(); + } + if (!contains(from)) { + reads.add(from); + } + } + } + + public boolean canRead(AbstractModuleEntry m, boolean mIsJavaBase) { + if (!isNamed() || mIsJavaBase) { + return true; + } + /* + * Acceptable access to a type in an unnamed module. Note that since unnamed modules can + * read all unnamed modules, this also handles the case where module_from is also + * unnamed but in a different class loader. + */ + if (!m.isNamed() && canReadAllUnnamed) { + return true; + } + synchronized (this) { + if (hasReads()) { + return contains(m); + } else { + return false; + } + } + } + + private boolean contains(AbstractModuleEntry from) { + return reads.contains(from); + } + + public void setModule(M module) { + assert this.module == null; + this.module = module; + } + + public M module() { + return module; + } + + public String version() { + return version; + } + + public String location() { + return location; + } + + public void setCanReadAllUnnamed() { + canReadAllUnnamed = true; + } + + public boolean isOpen() { + return isOpen; + } + + public boolean isNamed() { + return getName() != null; + } + + public boolean hasReads() { + return reads != null && !reads.isEmpty(); + } + + public void setVersionAndLocation(String moduleVersion, String moduleLocation) { + assert version == null && location == null; + this.version = moduleVersion; + this.location = moduleLocation; + } + } +} diff --git a/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/tables/AbstractPackageTable.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/tables/AbstractPackageTable.java new file mode 100644 index 000000000000..35b57d7b2031 --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/tables/AbstractPackageTable.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018, 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. + * + * 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.truffle.espresso.classfile.tables; + +import java.util.ArrayList; +import java.util.concurrent.locks.ReadWriteLock; + +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; + +public abstract class AbstractPackageTable, ME extends AbstractModuleTable.AbstractModuleEntry> extends EntryTable { + public AbstractPackageTable(ReadWriteLock lock) { + super(lock); + } + + public abstract static class AbstractPackageEntry> extends EntryTable.NamedEntry { + protected AbstractPackageEntry(Symbol name, ME module) { + super(name); + this.module = module; + } + + private final ME module; + private ArrayList exports; + private boolean isUnqualifiedExported; + private boolean isExportedAllUnnamed; + private String bootClasspathLocation; + + public void addExports(ME m) { + if (isUnqualifiedExported()) { + return; + } + synchronized (this) { + if (m == null) { + setUnqualifiedExports(); + } + if (exports == null) { + exports = new ArrayList<>(); + } + if (!contains(m)) { + exports.add(m); + } + } + } + + public boolean isQualifiedExportTo(ME m) { + if (isExportedAllUnnamed() && !m.isNamed()) { + return true; + } + if (isUnqualifiedExported() || exports == null) { + return false; + } + return contains(m); + } + + public boolean isUnqualifiedExported() { + return module().isOpen() || isUnqualifiedExported; + } + + public void setUnqualifiedExports() { + if (isUnqualifiedExported()) { + return; + } + isUnqualifiedExported = true; + isExportedAllUnnamed = true; + exports = null; + } + + public boolean isExportedAllUnnamed() { + return module().isOpen() || isExportedAllUnnamed; + } + + public void setExportedAllUnnamed() { + if (isExportedAllUnnamed()) { + return; + } + synchronized (this) { + isExportedAllUnnamed = true; + } + } + + public boolean contains(ME m) { + return exports.contains(m); + } + + public ME module() { + return module; + } + + public void setBootClasspathLocation(String bootClasspathLocation) { + this.bootClasspathLocation = bootClasspathLocation; + } + + public String getBootClasspathLocation() { + return bootClasspathLocation; + } + } +} diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/EntryTable.java b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/tables/EntryTable.java similarity index 88% rename from espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/EntryTable.java rename to espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/tables/EntryTable.java index dd21f68dd191..a8235679cefd 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/EntryTable.java +++ b/espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/tables/EntryTable.java @@ -20,15 +20,13 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ +package com.oracle.truffle.espresso.classfile.tables; -package com.oracle.truffle.espresso.impl; - -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.Objects; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; +import java.util.function.Consumer; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; @@ -45,9 +43,16 @@ protected EntryTable(ReadWriteLock lock) { } @SuppressWarnings("try") - public Collection values() { + public void collectValues(Consumer consumer) { + try (BlockLock block = read()) { + entries.values().forEach(consumer); + } + } + + @SuppressWarnings({"try", "unchecked", "rawtypes"}) + public Symbol[] getKeys() { try (BlockLock block = read()) { - return Collections.unmodifiableCollection(entries.values()); + return entries.keySet().toArray(new Symbol[entries.size()]); } } @@ -88,11 +93,11 @@ protected NamedEntry(Symbol name) { protected final Symbol name; - public Symbol getName() { + public final Symbol getName() { return name; } - public String getNameAsString() { + public final String getNameAsString() { if (name == null) { return "unnamed"; } @@ -100,7 +105,7 @@ public String getNameAsString() { } @Override - public int hashCode() { + public final int hashCode() { if (name == null) { return 0; } @@ -108,7 +113,7 @@ public int hashCode() { } @Override - public boolean equals(Object obj) { + public final boolean equals(Object obj) { if (obj instanceof NamedEntry) { return Objects.equals(((NamedEntry) obj).getName(), this.getName()); } diff --git a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/CallFrame.java b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/CallFrame.java index 3ea1882b70ab..0ab3768c7eda 100644 --- a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/CallFrame.java +++ b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/CallFrame.java @@ -22,6 +22,7 @@ */ package com.oracle.truffle.espresso.jdwp.api; +import com.oracle.truffle.api.TruffleLogger; import com.oracle.truffle.api.debug.DebugScope; import com.oracle.truffle.api.debug.DebugStackFrame; import com.oracle.truffle.api.debug.DebugValue; @@ -34,10 +35,8 @@ import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; -import com.oracle.truffle.espresso.jdwp.impl.DebuggerController; public final class CallFrame { - private static final InteropLibrary INTEROP = InteropLibrary.getFactory().getUncached(); public static final Object INVALID_VALUE = new Object(); @@ -53,11 +52,11 @@ public final class CallFrame { private final DebugStackFrame debugStackFrame; private final DebugScope debugScope; private final JDWPContext context; - private final DebuggerController controller; private Object scope; + private final TruffleLogger logger; public CallFrame(long threadId, byte typeTag, long classId, MethodRef method, long methodId, long codeIndex, Frame frame, Node currentNode, RootNode rootNode, - DebugStackFrame debugStackFrame, JDWPContext context, DebuggerController controller) { + DebugStackFrame debugStackFrame, JDWPContext context, TruffleLogger logger) { this.threadId = threadId; this.typeTag = typeTag; this.classId = classId; @@ -70,11 +69,12 @@ public CallFrame(long threadId, byte typeTag, long classId, MethodRef method, lo this.debugStackFrame = debugStackFrame; this.debugScope = debugStackFrame != null ? debugStackFrame.getScope() : null; this.context = context; - this.controller = controller; + this.logger = logger; } - public CallFrame(long threadId, byte typeTag, long classId, long methodId, long codeIndex) { - this(threadId, typeTag, classId, null, methodId, codeIndex, null, null, null, null, null, null); + // used for tests in comparisons only + public CallFrame(long threadId, byte typeTag, long classId, long methodId, long codeIndex, TruffleLogger logger) { + this(threadId, typeTag, classId, null, methodId, codeIndex, null, null, null, null, null, logger); } public byte getTypeTag() { @@ -124,9 +124,7 @@ public Object getThisValue() { } return INTEROP.readMember(theScope, "0"); } catch (UnsupportedMessageException | UnknownIdentifierException e) { - if (controller != null) { - controller.warning(() -> "Unable to read 'this' value from method: " + getMethod() + " with currentNode: " + currentNode.getClass()); - } + logger.warning(() -> "Unable to read 'this' value from method: " + getMethod() + " with currentNode: " + currentNode.getClass()); return INVALID_VALUE; } } @@ -144,9 +142,7 @@ public void setVariable(Object value, String identifier) { try { INTEROP.writeMember(theScope, identifier, value); } catch (Exception e) { - if (controller != null) { - controller.warning(() -> "Unable to write member " + identifier + " from variables"); - } + logger.warning(() -> "Unable to write member " + identifier + " from variables"); } } @@ -164,14 +160,10 @@ private Object getScope() { try { scope = NodeLibrary.getUncached().getScope(node, frame, true); } catch (UnsupportedMessageException e) { - if (controller != null) { - controller.warning(() -> "Unable to get scope for " + currentNode.getClass()); - } + logger.warning(() -> "Unable to get scope for " + currentNode.getClass()); } } else { - if (controller != null) { - controller.warning(() -> "Unable to get scope for " + currentNode.getClass()); - } + logger.warning(() -> "Unable to get scope for " + currentNode.getClass()); } return scope; } diff --git a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/ErrorCodes.java b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/ErrorCodes.java index 4b1e417b2650..f6457edd9868 100644 --- a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/ErrorCodes.java +++ b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/ErrorCodes.java @@ -50,6 +50,7 @@ public final class ErrorCodes { public static final int ABSENT_INFORMATION = 101; public static final int INVALID_EVENT_TYPE = 102; public static final int INTERNAL = 113; + public static final int INVALID_INDEX = 503; public static final int INVALID_LENGTH = 504; public static final int INVALID_STRING = 506; public static final int INVALID_CLASS_LOADER = 507; diff --git a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/Ids.java b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/Ids.java index 464b88edf438..664cd2f9e9da 100644 --- a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/Ids.java +++ b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/Ids.java @@ -31,7 +31,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.oracle.truffle.espresso.jdwp.impl.DebuggerController; +import com.oracle.truffle.api.TruffleLogger; /** * Class that keeps an ID representation of all entities when communicating with a debugger through @@ -57,11 +57,10 @@ public final class Ids { private HashMap innerClassIDMap = new HashMap<>(16); - private DebuggerController controller; - private List pinnedObjects = new ArrayList<>(); private volatile boolean pinningState = false; + private TruffleLogger logger; @SuppressWarnings({"unchecked", "rawtypes"}) public Ids(T nullObject) { @@ -202,14 +201,8 @@ public boolean checkRemoved(long refTypeId) { return innerClassIDMap.containsValue(refTypeId); } - public void injectController(DebuggerController control) { - this.controller = control; - } - private void log(Supplier supplier) { - if (controller != null) { - controller.finest(supplier); - } + logger.finest(supplier); } public synchronized void pinAll() { @@ -226,4 +219,8 @@ public synchronized void unpinAll() { pinningState = false; pinnedObjects.clear(); } + + public void injectLogger(TruffleLogger truffleLogger) { + this.logger = truffleLogger; + } } \ No newline at end of file diff --git a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/DebuggerConnection.java b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/DebuggerConnection.java index d360bc537022..fb47190a3d89 100644 --- a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/DebuggerConnection.java +++ b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/DebuggerConnection.java @@ -201,7 +201,7 @@ public void run() { } // Now, begin processing packets when they start to flow from the debugger try { - while (!Thread.currentThread().isInterrupted()) { + while (!Thread.currentThread().isInterrupted() && !controller.isClosing()) { try { processPacket(Packet.fromByteArray(debuggerConnection.connection.readPacket())); } catch (IOException e) { @@ -242,7 +242,7 @@ private void processPacket(Packet packet) { result = JDWP.VirtualMachine.CLASSES_BY_SIGNATURE.createReply(packet, controller, context); break; case JDWP.VirtualMachine.ALL_CLASSES.ID: - result = JDWP.VirtualMachine.ALL_CLASSES.createReply(packet, context); + result = JDWP.VirtualMachine.ALL_CLASSES.createReply(packet, context, controller); break; case JDWP.VirtualMachine.ALL_THREADS.ID: result = JDWP.VirtualMachine.ALL_THREADS.createReply(packet, context, controller); diff --git a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/DebuggerController.java b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/DebuggerController.java index 743066a5f6ba..4882b7d1520d 100644 --- a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/DebuggerController.java +++ b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/DebuggerController.java @@ -22,6 +22,7 @@ */ package com.oracle.truffle.espresso.jdwp.impl; +import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; @@ -34,12 +35,13 @@ import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; import java.util.logging.Level; import java.util.regex.Pattern; import com.oracle.truffle.api.CallTarget; -import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.TruffleContext; @@ -81,11 +83,15 @@ public final class DebuggerController implements ContextsListener { private final Map methodBreakpointExpected = new HashMap<>(); private final Map breakpointInfos = new HashMap<>(); + private JDWPContext context; + private Thread senderThread; + private Thread receiverThread; + private volatile HandshakeController hsController = null; + private final Lock resetting = new ReentrantLock(); + private volatile boolean isClosing; private JDWPOptions options; private DebuggerSession debuggerSession; - private final JDWPInstrument instrument; private Ids ids; - private JDWPContext context; private final VirtualMachine vm; private Debugger debugger; private final GCPrevention gcPrevention; @@ -103,8 +109,7 @@ public final class DebuggerController implements ContextsListener { // itself, it must check this field and exit the context if set. private volatile Throwable lateStartupError; - public DebuggerController(JDWPInstrument instrument, TruffleLogger logger) { - this.instrument = instrument; + public DebuggerController(TruffleLogger logger) { this.vm = new VirtualMachineImpl(); this.gcPrevention = new GCPrevention(); this.threadSuspension = new ThreadSuspension(); @@ -120,25 +125,130 @@ public void initialize(Debugger debug, JDWPOptions jdwpOptions, JDWPContext jdwp this.eventListener = vmEventListener; this.initialThread = thread; + ids.injectLogger(jdwpLogger); + // set up the debug session object early to make sure instrumentable nodes are materialized debuggerSession = debug.startSession(new SuspendedCallbackImpl(), SourceElement.ROOT, SourceElement.STATEMENT); debuggerSession.setSteppingFilter(SuspensionFilter.newBuilder().ignoreLanguageContextInitialization(true).build()); - instrument.init(jdwpContext); + init(jdwpContext); + } + + @TruffleBoundary + public void init(JDWPContext jdwpContext) { + this.context = jdwpContext; + + // Do all the non-blocking connection setup on the main thread. + // If we need to suspend on startup, or we need to exit the context due to fatal connection + // errors, we do this later when the context initialization is finalizing. + try { + hsController = new HandshakeController(); + hsController.setupInitialConnection(this); + } catch (IOException e) { + System.err.println("ERROR: transport error 202: connect failed: " + e.getMessage()); + System.err.println("ERROR: JDWP Transport dt_socket failed to initialize, TRANSPORT_INIT(510)"); + + setSetupState(new DebuggerController.SetupState(null, null, true)); + } } public void reInitialize() { - initialize(debugger, options, context, initialThread, eventListener); - assert setupState != null; + // create a new DebuggerController instance + DebuggerController newController = new DebuggerController(jdwpLogger); + newController.truffleContext = truffleContext; + newController.initialize(debugger, options, context, initialThread, eventListener); + assert newController.setupState != null; - if (setupState.fatalConnectionError) { + if (newController.setupState.fatalConnectionError) { fine(() -> "Failed debugger setup due to initial connection issue."); // OK, give up on trying to reconnect return; } // On reconnect, we just pass a placeholder CountDownLatch object which we don't ever wait // for. This avoids tedious null checks in the connection method. - DebuggerConnection.establishDebuggerConnection(this, setupState, true, new CountDownLatch(1)); + DebuggerConnection.establishDebuggerConnection(newController, newController.setupState, true, new CountDownLatch(1)); + } + + public void reset(boolean prepareForReconnect) { + if (isClosing) { + // already done closing, so don't attempt anything further + return; + } + if (!prepareForReconnect) { + // mark that we're closing down the whole context + isClosing = true; + } + Thread currentReceiverThread = null; + try { + // begin section that needs to be synchronized with establishing a new connection and + // starting the threads. The logic within the locked part, must be written in a way that + // it can run on any current state in the debugger connection and in any debugger thread + // existence state. + resetting.lockInterruptibly(); + + currentReceiverThread = receiverThread; + + // Close the server socket used to listen for transport dt_socket. + // This will unblock the accept call on a server socket. + HandshakeController hsc = hsController; + if (hsc != null) { + hsc.close(); + } + // Tell the controller to dispose the underlying connection by adding a special dispose + // packet to the sender thread queue. This will force the sender to complete work. + dispose(); + + // we know the sender can finish work, so wait for it to complete + joinThread(senderThread); + + // clear our current state of the threads + senderThread = null; + + // re-enable GC for all objects + getGCPrevention().clearAll(); + + // end the current debugger session to avoid hitting any further breakpoints + // when resuming all threads + endSession(); + + // resume all threads + forceResumeAll(); + + // Now, close the socket, which will force the receiver thread to complete eventually. + // Note that we might run this code in the receiver thread, so we can't simply join. + closeSocket(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + resetting.unlock(); + } + + // If we're not running in the receiver thread we should join + if (Thread.currentThread() != currentReceiverThread) { + joinThread(currentReceiverThread); + } + + if (prepareForReconnect && !isClosing && isServer()) { + reInitialize(); + } + // At this point the receiver thread field has either been replaced with a fresh thread from + // the above reInitialize call, or we're closing down. Either way, we don't need to worry + // about leaking the receiverThread field. + } + + public int identity() { + return System.identityHashCode(this); + } + + private void joinThread(Thread thread) { + if (thread != null) { + try { + thread.join(); + } catch (InterruptedException e) { + warning(() -> "jdwp thread " + thread.getName() + " didn't finish naturally"); + Thread.currentThread().interrupt(); + } + } } void setDebuggerConnection(DebuggerConnection connection) { @@ -162,11 +272,16 @@ public void closeSocket() { } public void addDebuggerReceiverThread(Thread thread) { - instrument.addDebuggerReceiverThread(thread); + receiverThread = thread; } public void addDebuggerSenderThread(Thread thread) { - instrument.addDebuggerSenderThread(thread); + senderThread = thread; + } + + public boolean isDebuggerThread(Thread hostThread) { + // only the receiver thread enters the context + return hostThread == receiverThread; } public void markLateStartupError(Throwable t) { @@ -174,11 +289,11 @@ public void markLateStartupError(Throwable t) { } public boolean isClosing() { - return instrument.isClosing(); + return isClosing; } public Lock getResettingLock() { - return instrument.getResettingLock(); + return resetting; } static final class SetupState { @@ -266,7 +381,7 @@ void submitExceptionBreakpoint(DebuggerCommand command) { fine(() -> "exception breakpoint submitted"); } - @CompilerDirectives.TruffleBoundary + @TruffleBoundary private void mapBreakpoint(Breakpoint bp, BreakpointInfo info) { breakpointInfos.put(bp, info); info.addBreakpoint(bp); @@ -404,7 +519,7 @@ public Object[] getVisibleGuestThreads() { Object[] allThreads = context.getAllGuestThreads(); ArrayList visibleThreads = new ArrayList<>(allThreads.length); for (Object thread : allThreads) { - if (!instrument.isDebuggerThread(context.asHostThread(thread))) { + if (!isDebuggerThread(context.asHostThread(thread))) { visibleThreads.add(thread); } } @@ -526,7 +641,7 @@ public void disposeDebugger(boolean prepareReconnect) { suspend(context.asGuestThread(Thread.currentThread()), SuspendStrategy.EVENT_THREAD, Collections.emptyList(), true); } } - instrument.reset(prepareReconnect); + reset(prepareReconnect); } public void endSession() { @@ -585,6 +700,9 @@ public void leaveTruffleContext(Object previous) { @Override public void onLanguageContextInitialized(TruffleContext con, @SuppressWarnings("unused") LanguageInfo language) { + if (!"java".equals(language.getId())) { + return; + } truffleContext = con; // With the Espresso context initialized, we can now complete the JDWP setup and establish @@ -792,8 +910,7 @@ public CallFrame[] getCallFrames(Object guestThread) { if (currentNode instanceof RootNode) { currentNode = context.getInstrumentableNode((RootNode) currentNode); } - callFrames.add(new CallFrame(context.getIds().getIdAsLong(guestThread), typeTag, klassId, method, methodId, codeIndex, frame, currentNode, root, null, context, - DebuggerController.this)); + callFrames.add(new CallFrame(context.getIds().getIdAsLong(guestThread), typeTag, klassId, method, methodId, codeIndex, frame, currentNode, root, null, context, jdwpLogger)); return null; }); return callFrames.toArray(new CallFrame[0]); @@ -823,7 +940,7 @@ private class SuspendedCallbackImpl implements SuspendedCallback { @Override public void onSuspend(SuspendedEvent event) { Thread hostThread = Thread.currentThread(); - if (instrument.isDebuggerThread(hostThread)) { + if (isDebuggerThread(hostThread)) { // always allow VM threads to run guest code without // the risk of being suspended return; @@ -1120,7 +1237,7 @@ private CallFrame[] createCallFrames(long threadId, Iterable st codeIndex = context.getBCI(rawNode, rawFrame); } - list.addLast(new CallFrame(threadId, typeTag, klassId, method, methodId, codeIndex, rawFrame, rawNode, root, frame, context, DebuggerController.this)); + list.addLast(new CallFrame(threadId, typeTag, klassId, method, methodId, codeIndex, rawFrame, rawNode, root, frame, context, jdwpLogger)); frameCount++; if (frameLimit != -1 && frameCount >= frameLimit) { return list.toArray(new CallFrame[0]); diff --git a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/FieldBreakpointInfo.java b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/FieldBreakpointInfo.java index 59b2e673966a..f93c2c177ea9 100644 --- a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/FieldBreakpointInfo.java +++ b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/FieldBreakpointInfo.java @@ -22,8 +22,8 @@ */ package com.oracle.truffle.espresso.jdwp.impl; -import com.oracle.truffle.espresso.jdwp.api.FieldRef; import com.oracle.truffle.espresso.jdwp.api.FieldBreakpoint; +import com.oracle.truffle.espresso.jdwp.api.FieldRef; import com.oracle.truffle.espresso.jdwp.api.KlassRef; public final class FieldBreakpointInfo extends AbstractBreakpointInfo implements FieldBreakpoint { diff --git a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/HandshakeController.java b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/HandshakeController.java index a731a24ae662..f1755c028f22 100644 --- a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/HandshakeController.java +++ b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/HandshakeController.java @@ -44,17 +44,16 @@ void setupInitialConnection(DebuggerController controller) throws IOException { DebuggerController.SetupState result; String connectionHost = controller.getHost(); int port = controller.getListeningPort(); - if (connectionHost == null) { - // only allow local host if nothing specified - connectionHost = "localhost"; - } if (controller.isServer()) { ServerSocket serverSocket = new ServerSocket(); serverSocket.setSoTimeout(0); // no timeout if (port != 0) { serverSocket.setReuseAddress(true); } - if ("*".equals(controller.getHost())) { + if (connectionHost == null) { + // localhost only + serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), port)); + } else if ("*".equals(connectionHost)) { // allow any host to bind serverSocket.bind(new InetSocketAddress((InetAddress) null, port)); } else { @@ -62,7 +61,7 @@ void setupInitialConnection(DebuggerController controller) throws IOException { serverSocket.bind(new InetSocketAddress(connectionHost, port)); } // print to console that we're listening - String address = controller.getHost() != null ? controller.getHost() + ":" + port : "" + port; + String address = controller.getHost() != null ? controller.getHost() + ":" + serverSocket.getLocalPort() : "" + serverSocket.getLocalPort(); System.out.println("Listening for transport dt_socket at address: " + address); synchronized (this) { @@ -71,7 +70,19 @@ void setupInitialConnection(DebuggerController controller) throws IOException { } result = new DebuggerController.SetupState(null, serverSocket, false); } else { - result = new DebuggerController.SetupState(new Socket(connectionHost, port), null, false); + try { + controller.getResettingLock().lockInterruptibly(); + if (!controller.isClosing()) { + result = new DebuggerController.SetupState(new Socket(connectionHost, port), null, false); + } else { + // if we're already closing down, simply mark as fatal connection error + result = new DebuggerController.SetupState(null, null, true); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + controller.getResettingLock().unlock(); + } } controller.setSetupState(result); } diff --git a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/JDWP.java b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/JDWP.java index 8d0b9f341ccb..e220b353db6b 100644 --- a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/JDWP.java +++ b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/JDWP.java @@ -109,7 +109,7 @@ static CommandResult createReply(Packet packet, DebuggerController controller, J static class ALL_CLASSES { public static final int ID = 3; - static CommandResult createReply(Packet packet, JDWPContext context) { + static CommandResult createReply(Packet packet, JDWPContext context, DebuggerController controller) { PacketStream reply = new PacketStream().replyPacket().id(packet.id); KlassRef[] allLoadedClasses = context.getAllLoadedClasses(); @@ -121,6 +121,7 @@ static CommandResult createReply(Packet packet, JDWPContext context) { reply.writeString(klass.getTypeAsString()); reply.writeInt(klass.getStatus()); } + controller.fine(() -> "Loaded classes: " + allLoadedClasses.length); return new CommandResult(reply); } @@ -146,7 +147,6 @@ static class TOP_LEVEL_THREAD_GROUPS { static CommandResult createReply(Packet packet, JDWPContext context) { PacketStream reply = new PacketStream().replyPacket().id(packet.id); - Object[] threadGroups = context.getTopLevelThreadGroups(); reply.writeInt(threadGroups.length); for (Object threadGroup : threadGroups) { @@ -2111,8 +2111,7 @@ static CommandResult createReply(Packet packet, DebuggerController controller) { } int startFrame = input.readInt(); - int length = input.readInt(); - final int requestedLength = length; + int requestedLength = input.readInt(); controller.fine(() -> "requesting frames for thread: " + controller.getContext().getThreadName(thread)); controller.fine(() -> "startFrame requested: " + startFrame); @@ -2127,13 +2126,21 @@ static CommandResult createReply(Packet packet, DebuggerController controller) { } CallFrame[] frames = suspendedInfo.getStackFrames(); - - if (length == -1 || length > frames.length) { - length = frames.length; + if (startFrame < 0 || startFrame >= frames.length) { + reply.errorCode(ErrorCodes.INVALID_INDEX); + return new CommandResult(reply); + } + int length; + if (requestedLength == -1) { + length = frames.length - startFrame; + } else if (requestedLength < 0 || startFrame + requestedLength > frames.length) { + reply.errorCode(ErrorCodes.INVALID_LENGTH); + return new CommandResult(reply); + } else { + length = requestedLength; } reply.writeInt(length); - final int finalLength = length; - controller.fine(() -> "returning " + finalLength + " frames for thread: " + controller.getContext().getThreadName(thread)); + controller.fine(() -> "returning " + length + " frames for thread: " + controller.getContext().getThreadName(thread)); for (int i = startFrame; i < startFrame + length; i++) { CallFrame frame = frames[i]; diff --git a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/JDWPInstrument.java b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/JDWPInstrument.java index d3380949bfb1..1e428d3bd83b 100644 --- a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/JDWPInstrument.java +++ b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/JDWPInstrument.java @@ -22,154 +22,21 @@ */ package com.oracle.truffle.espresso.jdwp.impl; -import java.io.IOException; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.instrumentation.TruffleInstrument; import com.oracle.truffle.api.instrumentation.TruffleInstrument.Registration; -import com.oracle.truffle.espresso.jdwp.api.JDWPContext; @Registration(id = JDWPInstrument.ID, name = "Java debug wire protocol", services = DebuggerController.class) public final class JDWPInstrument extends TruffleInstrument { public static final String ID = "jdwp"; - private DebuggerController controller; - private JDWPContext context; - private Thread senderThread; - private Thread receiverThread; - private volatile HandshakeController hsController = null; - private final Lock resetting = new ReentrantLock(); - private volatile boolean isClosing; - @Override protected void onCreate(Env instrumentEnv) { - assert controller == null; - controller = new DebuggerController(this, instrumentEnv.getLogger(ID)); + // It is the DebuggerController that handles the complete lifecycle of a JDWP session. Here + // we simply create a new controller instance, provide it as a service for lookup and + // attaches a context listener to assist in setup and shutdown hooks. + DebuggerController controller = new DebuggerController(instrumentEnv.getLogger(ID)); instrumentEnv.registerService(controller); instrumentEnv.getInstrumenter().attachContextsListener(controller, false); } - - public void reset(boolean prepareForReconnect) { - if (isClosing) { - // already done closing, so don't attempt anything further - return; - } - if (!prepareForReconnect) { - // mark that we're closing down the whole context - isClosing = true; - } - Thread currentReceiverThread = null; - try { - // begin section that needs to be synchronized with establishing a new connection and - // starting the threads. The logic within the locked part, must be written in a way that - // it can run on any current state in the debugger connection and in any debugger thread - // existence state. - resetting.lockInterruptibly(); - - currentReceiverThread = receiverThread; - - // Close the server socket used to listen for transport dt_socket. - // This will unblock the accept call on a server socket. - HandshakeController hsc = hsController; - if (hsc != null) { - hsc.close(); - } - // Tell the controller to dispose the underlying connection by adding a special dispose - // packet to the sender thread queue. This will force the sender to complete work. - controller.dispose(); - - // we know the sender can finish work, so wait for it to complete - joinThread(senderThread); - - // clear our current state of the threads - senderThread = null; - - // re-enable GC for all objects - controller.getGCPrevention().clearAll(); - - // end the current debugger session to avoid hitting any further breakpoints - // when resuming all threads - controller.endSession(); - - // resume all threads - controller.forceResumeAll(); - - // Now, close the socket, which will force the receiver thread to complete eventually. - // Note that we might run this code in the receiver thread, so we can't simply join. - controller.closeSocket(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } finally { - resetting.unlock(); - } - - // If we're not running in the receiver thread we should join - if (Thread.currentThread() != currentReceiverThread) { - joinThread(currentReceiverThread); - } - - if (prepareForReconnect && !isClosing) { - // replace the controller instance - controller.reInitialize(); - } - // At this point the receiver thread field has either been replaced with a fresh thread from - // the above reInitialize call, or we're closing down. Either way, we don't need to worry - // about leaking the receiverThread field. - } - - private void joinThread(Thread thread) { - if (thread != null) { - try { - thread.join(); - } catch (InterruptedException e) { - controller.warning(() -> "jdwp thread " + thread.getName() + " didn't finish naturally"); - Thread.currentThread().interrupt(); - } - } - } - - @TruffleBoundary - public void init(JDWPContext jdwpContext) { - this.context = jdwpContext; - - // Do all the non-blocking connection setup on the main thread. - // If we need to suspend on startup, or we need to exit the context due to fatal connection - // errors, we do this later when the context initialization is finalizing. - try { - hsController = new HandshakeController(); - hsController.setupInitialConnection(controller); - } catch (IOException e) { - System.err.println("ERROR: transport error 202: connect failed: " + e.getMessage()); - System.err.println("ERROR: JDWP Transport dt_socket failed to initialize, TRANSPORT_INIT(510)"); - controller.setSetupState(new DebuggerController.SetupState(null, null, true)); - } - } - - public JDWPContext getContext() { - return context; - } - - public void addDebuggerSenderThread(Thread thread) { - senderThread = thread; - } - - public void addDebuggerReceiverThread(Thread thread) { - receiverThread = thread; - } - - public boolean isDebuggerThread(Thread hostThread) { - // only the receiver thread enters the context - return hostThread == receiverThread; - } - - public Lock getResettingLock() { - return resetting; - } - - public boolean isClosing() { - return isClosing; - } } diff --git a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/RequestFilter.java b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/RequestFilter.java index 404accf9a4a7..b6a117bfa738 100644 --- a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/RequestFilter.java +++ b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/RequestFilter.java @@ -22,11 +22,11 @@ */ package com.oracle.truffle.espresso.jdwp.impl; -import com.oracle.truffle.espresso.jdwp.api.KlassRef; - import java.util.Arrays; import java.util.regex.Pattern; +import com.oracle.truffle.espresso.jdwp.api.KlassRef; + public final class RequestFilter { private final int requestId; diff --git a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/SocketConnection.java b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/SocketConnection.java index 0a5e540c756f..fef7417a19c8 100644 --- a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/SocketConnection.java +++ b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/SocketConnection.java @@ -164,12 +164,4 @@ public void writePacket(byte[] b) throws IOException { */ socketOutput.write(b, 0, len); } - - public boolean isAvailable() { - try { - return socketInput.available() > 0; - } catch (IOException e) { - return false; - } - } } diff --git a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/SourceLocator.java b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/SourceLocator.java index b170531afde2..245fa27515bf 100644 --- a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/SourceLocator.java +++ b/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/SourceLocator.java @@ -24,8 +24,8 @@ import com.oracle.truffle.api.source.Source; import com.oracle.truffle.espresso.jdwp.api.JDWPContext; -import com.oracle.truffle.espresso.jdwp.api.MethodRef; import com.oracle.truffle.espresso.jdwp.api.KlassRef; +import com.oracle.truffle.espresso.jdwp.api.MethodRef; public final class SourceLocator { diff --git a/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/ErrorType.java b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/ErrorType.java new file mode 100644 index 000000000000..1ed92e0471c3 --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/ErrorType.java @@ -0,0 +1,36 @@ +/* + * 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. + * + * 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.truffle.espresso.shared.meta; + +import com.oracle.truffle.espresso.shared.resolver.LinkResolver; + +/** + * The errors that can happen during {@link LinkResolver resolution}. + */ +public enum ErrorType { + IllegalAccessError, + NoSuchFieldError, + NoSuchMethodError, + IncompatibleClassChangeError; +} diff --git a/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/FieldAccess.java b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/FieldAccess.java new file mode 100644 index 000000000000..56034884945d --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/FieldAccess.java @@ -0,0 +1,42 @@ +/* + * 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. + * + * 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.truffle.espresso.shared.meta; + +/** + * Represents a {@link java.lang.reflect.Field}, and provides access to various runtime metadata. + * + * @param The class providing access to the VM-side java {@link Class}. + * @param The class providing access to the VM-side java {@link java.lang.reflect.Method}. + * @param The class providing access to the VM-side java {@link java.lang.reflect.Field}. + */ +public interface FieldAccess, M extends MethodAccess, F extends FieldAccess> extends MemberAccess { + /** + * Whether resolution for this field access site should enforce final field only being written + * in constructors or class initializer, whichever applies (see JVMS-6.5.putfield / + * JVMS-6.5.putstatic). + *

+ * In particular, this check was not enforced in java bytecodes in versions prior to Java 9. + */ + boolean shouldEnforceInitializerCheck(); +} diff --git a/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/MemberAccess.java b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/MemberAccess.java new file mode 100644 index 000000000000..f36dd060a8c5 --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/MemberAccess.java @@ -0,0 +1,56 @@ +/* + * 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. + * + * 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.truffle.espresso.shared.meta; + +/** + * Represents a {@link java.lang.reflect.Member}, and provides access to various runtime + * capabilities such as {@link #accessChecks(TypeAccess, TypeAccess) access control} and + * {@link #loadingConstraints(TypeAccess) enforcing loading constraints}. + * + * @param The class providing access to the VM-side java {@link Class}. + * @param The class providing access to the VM-side java {@link java.lang.reflect.Method}. + * @param The class providing access to the VM-side java {@link java.lang.reflect.Field}. + */ +public interface MemberAccess, M extends MethodAccess, F extends FieldAccess> extends ModifiersProvider, Named { + /** + * @return The class in which the declaration if this member is present. + */ + C getDeclaringClass(); + + /** + * Performs access checks for this member. + * + * @param accessingClass The class from which resolution is being performed. + * @param holderClass The class referenced in the symbolic representation of the method, as seen + * in the constant pool. May be different from {@link #getDeclaringClass()}. + */ + void accessChecks(C accessingClass, C holderClass); + + /** + * Performs loading constraints checks for this member. + * + * @param accessingClass The class from which resolution is being performed. + */ + void loadingConstraints(C accessingClass); +} diff --git a/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/MethodAccess.java b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/MethodAccess.java new file mode 100644 index 000000000000..ce50bea95d55 --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/MethodAccess.java @@ -0,0 +1,60 @@ +/* + * 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. + * + * 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.truffle.espresso.shared.meta; + +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; + +/** + * Represents a {@link java.lang.reflect.Method}, and provides access to various runtime metadata. + * + * @param The class providing access to the VM-side java {@link Class}. + * @param The class providing access to the VM-side java {@link java.lang.reflect.Method}. + * @param The class providing access to the VM-side java {@link java.lang.reflect.Field}. + */ +public interface MethodAccess, M extends MethodAccess, F extends FieldAccess> extends MemberAccess { + /** + * @return The symbolic signature of this method. + */ + Symbol getSymbolicSignature(); + + /** + * @return {@code true} if this method represents an instance initialization method (its + * {@link #getSymbolicName() name} is {@code ""}), {@code false} otherwise. + */ + boolean isConstructor(); + + /** + * @return {@code true} if this method represents a class initialization method (its + * {@link #getSymbolicName() name} is {@code ""}, and it is {@link #isStatic() + * static}), {@code false} otherwise. + */ + boolean isClassInitializer(); + + /** + * Whether loading constraints checks should be skipped for this method. An example of method + * which should skip loading constraints are the polymorphic signature methods. + */ + boolean shouldSkipLoadingConstraints(); +} diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/ModifiersProvider.java b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/ModifiersProvider.java similarity index 97% rename from espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/ModifiersProvider.java rename to espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/ModifiersProvider.java index 48c44958fb22..652b68f487c2 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/ModifiersProvider.java +++ b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/ModifiersProvider.java @@ -20,7 +20,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.truffle.espresso.meta; +package com.oracle.truffle.espresso.shared.meta; import static java.lang.reflect.Modifier.PRIVATE; import static java.lang.reflect.Modifier.PROTECTED; @@ -28,8 +28,6 @@ import java.lang.reflect.Modifier; -import com.oracle.truffle.api.dsl.Idempotent; - /** * A Java element (i.e., a class, interface, field or method) that is described by a set of Java * language {@linkplain #getModifiers() modifiers}. @@ -140,7 +138,6 @@ default boolean isNative() { /** * @see Modifier#isAbstract(int) */ - @Idempotent default boolean isAbstract() { return Modifier.isAbstract(getModifiers()); } diff --git a/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/Named.java b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/Named.java new file mode 100644 index 000000000000..88f77c897c2b --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/Named.java @@ -0,0 +1,37 @@ +/* + * 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. + * + * 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.truffle.espresso.shared.meta; + +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; + +/** + * A {@link Named} object must provide a {@link #getSymbolicName() symbolic name}. + */ +public interface Named { + /** + * @return The symbolic name of this object. + */ + Symbol getSymbolicName(); +} diff --git a/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/RuntimeAccess.java b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/RuntimeAccess.java new file mode 100644 index 000000000000..9b0152115776 --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/RuntimeAccess.java @@ -0,0 +1,59 @@ +/* + * 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. + * + * 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.truffle.espresso.shared.meta; + +import com.oracle.truffle.espresso.classfile.JavaVersion; + +/** + * Provides access to some VM-specific capabilities, such as throwing exceptions, or obtaining the + * implementor's supported {@link JavaVersion}. + * + * @param The class providing access to the VM-side java {@link Class}. + * @param The class providing access to the VM-side java {@link java.lang.reflect.Method}. + * @param The class providing access to the VM-side java {@link java.lang.reflect.Field}. + */ +public interface RuntimeAccess, M extends MethodAccess, F extends FieldAccess> { + /** + * This runtime's supported {@link JavaVersion}. + */ + JavaVersion getJavaVersion(); + + /** + * Signals to the runtime that a Java error should be thrown. The type of the error to be thrown + * is given by the passed {@link ErrorType}. + *

+ * The caller provides an error message that can be constructed using + * {@code String.format(Locale.ENGLISH, messageFormat, args)}. + */ + RuntimeException throwError(ErrorType error, String messageFormat, Object... args); + + /** + * Signals that an unexpected state has been reached and that the current operation must be + * aborted. + *

+ * The caller provides an error message that can be constructed using + * {@code String.format(Locale.ENGLISH, messageFormat, args)}. + */ + RuntimeException fatal(String messageFormat, Object... args); +} diff --git a/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/TypeAccess.java b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/TypeAccess.java new file mode 100644 index 000000000000..133a9754b18d --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/TypeAccess.java @@ -0,0 +1,148 @@ +/* + * 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. + * + * 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.truffle.espresso.shared.meta; + +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; +import com.oracle.truffle.espresso.shared.resolver.LinkResolver; + +/** + * Represents a {@link Class}, and provides access to various lookups and runtime metadata. + * + * @param The class providing access to the VM-side java {@link Class}. + * @param The class providing access to the VM-side java {@link java.lang.reflect.Method}. + * @param The class providing access to the VM-side java {@link java.lang.reflect.Field}. + */ +public interface TypeAccess, M extends MethodAccess, F extends FieldAccess> extends ModifiersProvider, Named { + /** + * Returns the name of this class, as if obtained from {@link Class#getName()}. + */ + String getJavaName(); + + /** + * Returns the superclass of this class, or {@code null} if this class is {@link Object}. + */ + C getSuperClass(); + + /** + * Performs field lookup on this class for the given field name and field type, according to + * JVMS-5.4.3.2. + *

+ * Field lookup is specified as follows, in order of operations: + *

    + *
  • If {@link TypeAccess C} declares a field with the name and type specified, field lookup + * succeeds. The declared field is the result of the field lookup.
  • + *
  • Otherwise, field lookup is applied recursively to the direct superinterfaces of the + * specified class or interface {@link TypeAccess C}.
  • + *
  • Otherwise, if {@link TypeAccess C} has a {@link #getSuperClass() superclass S}, field + * lookup is applied recursively to {@code S}.
  • + *
  • Otherwise, field lookup fails.
  • + *
+ */ + F lookupField(Symbol name, Symbol type); + + /** + * Performs method lookup on this class for the given method name and method signature, + * according to JVMS-5.4.3.3. + *

+ *

    + *
  • This lookup does not need to throw {@link ErrorType#IncompatibleClassChangeError} if this + * class is an interface. It is handled in {@link LinkResolver#resolveMethodSymbol}
  • + *
  • {@link MethodAccess Method} resolution attempts to locate the referenced method in + * {@link TypeAccess C} and its superclasses: + *
      + *
    • If {@link TypeAccess C} declares exactly one {@link MethodAccess method} with the name + * specified, and the declaration is a signature polymorphic method, then method lookup + * succeeds. All the class names mentioned in the descriptor are resolved. The resolved method + * is the signature polymorphic method declaration. It is not necessary for {@link TypeAccess C} + * to declare a method with the signature specified by the method reference.
    • + *
    • Otherwise, if {@link TypeAccess C} declares a method with the name and signature + * specified by the method reference, method lookup succeeds.
    • + *
    • Otherwise, if {@link TypeAccess C} has a {@link #getSuperClass() superclass}, step 2 of + * method resolution is recursively invoked on the direct superclass of {@link TypeAccess + * C}.
    • + *
    + *
  • + *
  • Otherwise, method resolution attempts to locate the referenced method in the + * superinterfaces of the specified class {@link TypeAccess C}: + *
      + *
    • If the maximally-specific superinterface methods of {@link TypeAccess C} for the name and + * signature specified by the method reference include exactly one method that is not + * {@link ModifiersProvider#isAbstract() abstract} , then this method is chosen and method + * lookup succeeds.
    • + *
    • Otherwise, if any superinterface of {@link TypeAccess C} declares a method with the name + * and signature specified that is neither {@link ModifiersProvider#isPrivate() private} nor + * {@link ModifiersProvider#isStatic() static}, one of these is arbitrarily chosen and method + * lookup succeeds.
    • + *
    • Otherwise, method lookup fails and returns {@code null}.
    • + *
    + *
+ */ + M lookupMethod(Symbol name, Symbol signature); + + /** + * Same as {@link #lookupMethod(Symbol, Symbol)}, but ignores + * {@link ModifiersProvider#isStatic() static} methods. + */ + M lookupInstanceMethod(Symbol name, Symbol signature); + + /** + * Performs interface method lookup on this class for the given method name and method + * signature, according to JVMS-5.4.3.3. + *

+ *

    + *
  • This lookup does not need to throw {@link ErrorType#IncompatibleClassChangeError} if this + * class is not an interface. It is handled in {@link LinkResolver#resolveMethodSymbol}.
  • + *
  • Otherwise, if {@link TypeAccess C} declares a method with the given name and signature, + * this method is returned.
  • + *
  • Otherwise, if the class {@link Object} declares a method with the given name and + * signature, which is {@link ModifiersProvider#isPublic() public} and + * non-{@link ModifiersProvider#isStatic() static}, this method is returned.
  • + *
  • Otherwise, if the maximally-specific superinterface methods of {@link TypeAccess C} for + * the given name and signature include exactly one method that is not + * {@link ModifiersProvider#isAbstract() abstract}, then this method is returned.
  • + *
  • Otherwise, if any superinterface of {@link TypeAccess C} declares a method with the name + * and signature specified that is neither {@link ModifiersProvider#isPrivate() private} nor + * {@link ModifiersProvider#isStatic() static}, one of these is arbitrarily chosen + * returned.
  • + *
  • Otherwise, method lookup fails and returns {@code null}.
  • + *
+ */ + M lookupInterfaceMethod(Symbol name, Symbol signature); + + /** + * @return {@code true} if {@code other} is a subtype of {@code this}, {@code false} otherwise. + */ + boolean isAssignableFrom(C other); + + /** + * @return {@code true} if this class represents the {@link Object} class, {@code false} + * otherwise. + */ + default boolean isJavaLangObject() { + return getSuperClass() == null; + } +} diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/CallKind.java b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/resolver/CallKind.java similarity index 97% rename from espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/CallKind.java rename to espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/resolver/CallKind.java index 2282ece60b4f..404440107c8d 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/CallKind.java +++ b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/resolver/CallKind.java @@ -21,7 +21,7 @@ * questions. */ -package com.oracle.truffle.espresso.resolver; +package com.oracle.truffle.espresso.shared.resolver; /** * Indicates what dispatch behavior should be performed for a call-site. diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/CallSiteType.java b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/resolver/CallSiteType.java similarity index 51% rename from espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/CallSiteType.java rename to espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/resolver/CallSiteType.java index fa44c51af5a0..f5f8d75191d5 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/CallSiteType.java +++ b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/resolver/CallSiteType.java @@ -21,12 +21,9 @@ * questions. */ -package com.oracle.truffle.espresso.resolver; +package com.oracle.truffle.espresso.shared.resolver; -import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.espresso.classfile.bytecode.Bytecodes; -import com.oracle.truffle.espresso.classfile.Constants; -import com.oracle.truffle.espresso.meta.EspressoError; /** * Describes the type of call-site resolution that should happen for a given call-site. For @@ -39,37 +36,4 @@ public enum CallSiteType { Special, Virtual, Interface; - - public static CallSiteType fromOpCode(int opcode) { - switch (opcode) { - case Bytecodes.INVOKESTATIC: - return Static; - case Bytecodes.INVOKESPECIAL: - return Special; - case Bytecodes.INVOKEVIRTUAL: - return Virtual; - case Bytecodes.INVOKEINTERFACE: - return Interface; - default: - CompilerDirectives.transferToInterpreterAndInvalidate(); - throw EspressoError.shouldNotReachHere(Bytecodes.nameOf(opcode)); - } - } - - public static CallSiteType fromRefKind(int refKind) { - switch (refKind) { - case Constants.REF_invokeVirtual: - return Virtual; - case Constants.REF_invokeStatic: - return Static; - case Constants.REF_invokeSpecial: // fallthrough - case Constants.REF_newInvokeSpecial: - return Special; - case Constants.REF_invokeInterface: - return Interface; - default: - CompilerDirectives.transferToInterpreterAndInvalidate(); - throw EspressoError.shouldNotReachHere("refKind: " + refKind); - } - } } diff --git a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/JDWPSetup.java b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/resolver/FieldAccessType.java similarity index 55% rename from espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/JDWPSetup.java rename to espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/resolver/FieldAccessType.java index 9274ef0be412..1458a9c1040e 100644 --- a/espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/JDWPSetup.java +++ b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/resolver/FieldAccessType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, 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 @@ -20,25 +20,28 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.truffle.espresso.jdwp.api; -import com.oracle.truffle.api.debug.Debugger; -import com.oracle.truffle.espresso.jdwp.impl.DebuggerController; +package com.oracle.truffle.espresso.shared.resolver; -/** - * Main entry point for setting up JDWP. The class looks up the JDWP instrument and initializes - * communication with the debugger. - */ -public final class JDWPSetup { +public enum FieldAccessType { + GetStatic(true, false), + PutStatic(true, true), + GetInstance(false, false), + PutInstance(false, true); + + private final boolean isStatic; + private final boolean isPut; - private DebuggerController controller; + FieldAccessType(boolean isStatic, boolean isPut) { + this.isStatic = isStatic; + this.isPut = isPut; + } - public void setup(Debugger debugger, DebuggerController control, JDWPOptions options, JDWPContext context, Object mainThread, VMEventListener vmEventListener) { - this.controller = control; - control.initialize(debugger, options, context, mainThread, vmEventListener); + public boolean isStatic() { + return isStatic; } - public void finalizeSession() { - controller.disposeDebugger(false); + public boolean isPut() { + return isPut; } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/LinkResolver.java b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/resolver/LinkResolver.java similarity index 51% rename from espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/LinkResolver.java rename to espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/resolver/LinkResolver.java index 3943f4f8c687..78f24256657b 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/LinkResolver.java +++ b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/resolver/LinkResolver.java @@ -21,29 +21,23 @@ * questions. */ -package com.oracle.truffle.espresso.resolver; +package com.oracle.truffle.espresso.shared.resolver; -import static com.oracle.truffle.espresso.EspressoOptions.SpecComplianceMode.STRICT; -import static com.oracle.truffle.espresso.meta.EspressoError.cat; - -import java.util.Locale; - -import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.espresso.classfile.ClassfileParser; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; -import com.oracle.truffle.espresso.constantpool.Resolution; -import com.oracle.truffle.espresso.impl.Field; -import com.oracle.truffle.espresso.impl.Klass; -import com.oracle.truffle.espresso.impl.Method; -import com.oracle.truffle.espresso.impl.ObjectKlass; -import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.meta.Meta; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; +import com.oracle.truffle.espresso.shared.meta.ErrorType; +import com.oracle.truffle.espresso.shared.meta.FieldAccess; +import com.oracle.truffle.espresso.shared.meta.MethodAccess; +import com.oracle.truffle.espresso.shared.meta.RuntimeAccess; +import com.oracle.truffle.espresso.shared.meta.TypeAccess; +/** + * Provides resolution capabilities according to the Java Virtual Machine Specification on behalf of + * a VM. + */ public final class LinkResolver { - /** * Symbolically resolves a field. * @@ -53,11 +47,17 @@ public final class LinkResolver { * @param symbolicHolder The holder of the field, as described in the constant pool. * @param accessCheck Whether to perform access checks on the resolved field. * @param loadingConstraints Whether to check loading constraints on the resolved field. + * + * @param The class providing VM access. + * @param The class representing the VM-side java {@link Class}. + * @param The class representing the VM-side java {@link java.lang.reflect.Method}. + * @param The class representing the VM-side java {@link java.lang.reflect.Field}. */ - public static Field resolveFieldSymbol(Meta meta, ObjectKlass accessingKlass, - Symbol name, Symbol type, Klass symbolicHolder, + public static , C extends TypeAccess, M extends MethodAccess, F extends FieldAccess> F resolveFieldSymbol( + R runtime, C accessingKlass, + Symbol name, Symbol type, C symbolicHolder, boolean accessCheck, boolean loadingConstraints) { - return LinkResolverImpl.resolveFieldSymbol(meta, accessingKlass, name, type, symbolicHolder, accessCheck, loadingConstraints); + return resolveFieldSymbolImpl(runtime, accessingKlass, name, type, symbolicHolder, accessCheck, loadingConstraints); } /** @@ -66,14 +66,23 @@ public static Field resolveFieldSymbol(Meta meta, ObjectKlass accessingKlass, * accessed with static accesses, and that field writes to final fields are done only in the * constructor or class initializer. * - * @param currentKlass The class in which the field access appears. - * @param currentMethod The method in which the field access appears. * @param symbolicResolution The result of symbolic resolution of the field declared in the * access site. * @param fieldAccessType The {@link FieldAccessType} representing the access site to resolve. + * @param currentKlass The class in which the field access appears. + * @param currentMethod The method in which the field access appears. + * + * @param The class providing VM access. + * @param The class representing the VM-side java {@link Class}. + * @param The class representing the VM-side java {@link java.lang.reflect.Method}. + * @param The class representing the VM-side java {@link java.lang.reflect.Field}. + * + * @see FieldAccessType */ - public static Field resolveFieldAccess(Meta meta, Klass currentKlass, Method currentMethod, Field symbolicResolution, FieldAccessType fieldAccessType) { - return LinkResolverImpl.resolveFieldAccess(meta, symbolicResolution, fieldAccessType, currentKlass, currentMethod); + public static , C extends TypeAccess, M extends MethodAccess, F extends FieldAccess> F resolveFieldAccess( + R runtime, F symbolicResolution, FieldAccessType fieldAccessType, + C currentKlass, M currentMethod) { + return resolveFieldAccessImpl(runtime, symbolicResolution, fieldAccessType, currentKlass, currentMethod); } /** @@ -85,34 +94,52 @@ public static Field resolveFieldAccess(Meta meta, Klass currentKlass, Method cur * @param symbolicHolder The holder of the method, as described in the constant pool. * @param accessCheck Whether to perform access checks on the resolved method. * @param loadingConstraints Whether to check loading constraints on the resolved method. + * + * @param The class providing VM access. + * @param The class representing the VM-side java {@link Class}. + * @param The class representing the VM-side java {@link java.lang.reflect.Method}. + * @param The class representing the VM-side java {@link java.lang.reflect.Field}. */ - public static Method resolveMethodSymbol(Meta meta, ObjectKlass accessingKlass, - Symbol name, Symbol signature, Klass symbolicHolder, + public static , C extends TypeAccess, M extends MethodAccess, F extends FieldAccess> M resolveMethodSymbol( + R runtime, C accessingKlass, + Symbol name, Symbol signature, C symbolicHolder, boolean interfaceLookup, boolean accessCheck, boolean loadingConstraints) { - return LinkResolverImpl.resolveMethodSymbol(meta, accessingKlass, name, signature, symbolicHolder, interfaceLookup, accessCheck, loadingConstraints); + return resolveMethodSymbolImpl(runtime, accessingKlass, name, signature, symbolicHolder, interfaceLookup, accessCheck, loadingConstraints); } /** * Resolve a call-site given the symbolic resolution of the method in the constant pool. + *

+ * The returned {@link ResolvedCall} may be used to accurately dispatch at a call-site. * * @param currentKlass The class in which the call site to resolve appears. * @param symbolicResolution The result of the symbolic resolution of the method declared in the * call site. * @param callSiteType The {@link CallSiteType} representing the call site to resolve. - * @param symbolicHolder The declared holder for symbolic resolution. May differ from - * {@code symbolicResolution.getDeclaringKlass()}. + * @param symbolicHolder The declared holder for symbolic resolution, as seen in the constant + * pool. May differ from {@link MethodAccess#getDeclaringClass() + * symbolicResolution.getDeclaringClass()}. + * + * @param The class providing VM access. + * @param The class representing the VM-side java {@link Class}. + * @param The class representing the VM-side java {@link java.lang.reflect.Method}. + * @param The class representing the VM-side java {@link java.lang.reflect.Field}. + * + * @see CallSiteType + * @see CallKind */ - public static ResolvedCall resolveCallSite(Meta meta, Klass currentKlass, Method symbolicResolution, CallSiteType callSiteType, Klass symbolicHolder) { - return LinkResolverImpl.resolveCallSite(meta, currentKlass, symbolicResolution, callSiteType, symbolicHolder); + public static , C extends TypeAccess, M extends MethodAccess, F extends FieldAccess> ResolvedCall resolveCallSite( + R runtime, C currentKlass, + M symbolicResolution, CallSiteType callSiteType, + C symbolicHolder) { + return resolveCallSiteImpl(runtime, currentKlass, symbolicResolution, callSiteType, symbolicHolder); } - // Only static + // Implementation + private LinkResolver() { } -} - -final class LinkResolverImpl { private static final String AN_INTERFACE = "an interface"; private static final String A_CLASS = "a class"; @@ -121,25 +148,26 @@ final class LinkResolverImpl { private static final String INIT = ""; private static final String CLINIT = ""; - @TruffleBoundary - public static Field resolveFieldSymbol(Meta meta, ObjectKlass accessingKlass, - Symbol name, Symbol type, Klass symbolicHolder, + private static , C extends TypeAccess, M extends MethodAccess, F extends FieldAccess> F resolveFieldSymbolImpl( + R runtime, C accessingKlass, + Symbol name, Symbol type, C symbolicHolder, boolean accessCheck, boolean loadingConstraints) { - Field f = symbolicHolder.lookupField(name, type); + F f = symbolicHolder.lookupField(name, type); if (f == null) { - throw meta.throwExceptionWithMessage(meta.java_lang_NoSuchFieldError, name.toString()); + throw runtime.throwError(ErrorType.NoSuchFieldError, "%s", name); } if (accessCheck) { - Resolution.memberDoAccessCheck(accessingKlass, symbolicHolder, f, meta); + f.accessChecks(accessingKlass, symbolicHolder); } if (loadingConstraints) { - f.checkLoadingConstraints(accessingKlass.getDefiningClassLoader(), f.getDeclaringKlass().getDefiningClassLoader()); + f.loadingConstraints(accessingKlass); } return f; } - public static Field resolveFieldAccess(Meta meta, Field field, FieldAccessType fieldAccessType, - Klass currentKlass, Method currentMethod) { + private static , C extends TypeAccess, M extends MethodAccess, F extends FieldAccess> F resolveFieldAccessImpl( + R runtime, F field, FieldAccessType fieldAccessType, + C currentKlass, M currentMethod) { /* * PUTFIELD/GETFIELD: Otherwise, if the resolved field is a static field, putfield throws an * IncompatibleClassChangeError. @@ -149,11 +177,11 @@ public static Field resolveFieldAccess(Meta meta, Field field, FieldAccessType f */ if (fieldAccessType.isStatic() != field.isStatic()) { - throw throwBoundary(meta, meta.java_lang_IncompatibleClassChangeError, + throw runtime.throwError(ErrorType.IncompatibleClassChangeError, "Expected %s field %s.%s", (fieldAccessType.isStatic()) ? STATIC : NON_STATIC, - field.getDeclaringKlass().getName(), - field.getName()); + field.getDeclaringClass().getJavaName(), + field.getSymbolicName()); } if (fieldAccessType.isPut()) { /* @@ -166,26 +194,24 @@ public static Field resolveFieldAccess(Meta meta, Field field, FieldAccessType f * Otherwise, an IllegalAccessError is thrown. */ if (field.isFinalFlagSet()) { - if (field.getDeclaringKlass() != currentKlass) { - throw throwBoundary(meta, meta.java_lang_IllegalAccessError, + if (field.getDeclaringClass() != currentKlass) { + throw runtime.throwError(ErrorType.IllegalAccessError, "Update to %s final field %s.%s attempted from a different class (%s) than the field's declaring class", (fieldAccessType.isStatic()) ? STATIC : NON_STATIC, - field.getDeclaringKlass().getName(), - field.getName(), - currentKlass.getName()); + field.getDeclaringClass().getJavaName(), + field.getSymbolicName(), + currentKlass.getJavaName()); } - boolean enforceInitializerCheck = (meta.getLanguage().getSpecComplianceMode() == STRICT) || - // HotSpot enforces this only for >= Java 9 (v53) .class files. - field.getDeclaringKlass().getMajorVersion() >= ClassfileParser.JAVA_9_VERSION; + boolean enforceInitializerCheck = field.shouldEnforceInitializerCheck(); if (enforceInitializerCheck) { if (!((fieldAccessType.isStatic() && currentMethod.isClassInitializer()) || (!fieldAccessType.isStatic() && currentMethod.isConstructor()))) { - throw throwBoundary(meta, meta.java_lang_IllegalAccessError, + throw runtime.throwError(ErrorType.IllegalAccessError, "Update to %s final field %s.%s attempted from a different method (%s) than the initializer method %s ", (fieldAccessType.isStatic()) ? STATIC : NON_STATIC, - field.getDeclaringKlass().getName(), - field.getName(), - currentMethod.getName(), + field.getDeclaringClass().getJavaName(), + field.getSymbolicName(), + currentMethod.getSymbolicName(), (fieldAccessType.isStatic()) ? CLINIT : INIT); } } @@ -194,47 +220,51 @@ public static Field resolveFieldAccess(Meta meta, Field field, FieldAccessType f return field; } - @TruffleBoundary - public static Method resolveMethodSymbol(Meta meta, ObjectKlass accessingKlass, Symbol name, Symbol signature, Klass symbolicHolder, + private static , C extends TypeAccess, M extends MethodAccess, F extends FieldAccess> M resolveMethodSymbolImpl(R runtime, + C accessingKlass, Symbol name, + Symbol signature, C symbolicHolder, boolean interfaceLookup, boolean accessCheck, boolean loadingConstraints) { - Method resolved; + M resolved; if (interfaceLookup != symbolicHolder.isInterface()) { String expected = interfaceLookup ? AN_INTERFACE : A_CLASS; String found = interfaceLookup ? A_CLASS : AN_INTERFACE; - meta.throwExceptionWithMessage(meta.java_lang_IncompatibleClassChangeError, cat("Resolution failure for ", symbolicHolder.getExternalName(), ".\n", - "Is ", found, ", but ", expected, " was expected.")); + throw runtime.throwError(ErrorType.IncompatibleClassChangeError, "Resolution failure for %s.\nIs %s, but %s was expected", + symbolicHolder.getJavaName(), found, expected); } if (symbolicHolder.isInterface()) { - resolved = ((ObjectKlass) symbolicHolder).resolveInterfaceMethod(name, signature); + resolved = symbolicHolder.lookupInterfaceMethod(name, signature); } else { resolved = symbolicHolder.lookupMethod(name, signature); } if (resolved == null) { - throw meta.throwExceptionWithMessage(meta.java_lang_NoSuchMethodError, symbolicHolder.getNameAsString() + "." + name + signature); + throw runtime.throwError(ErrorType.NoSuchMethodError, "%s.%s%s", symbolicHolder.getJavaName(), name, signature); } if (accessCheck) { - Resolution.memberDoAccessCheck(accessingKlass, symbolicHolder, resolved, meta); + resolved.accessChecks(accessingKlass, symbolicHolder); } - if (loadingConstraints && !resolved.isPolySignatureIntrinsic()) { - resolved.checkLoadingConstraints(accessingKlass.getDefiningClassLoader(), resolved.getDeclaringKlass().getDefiningClassLoader()); + if (loadingConstraints && !resolved.shouldSkipLoadingConstraints()) { + resolved.loadingConstraints(accessingKlass); } return resolved; } - public static ResolvedCall resolveCallSite(Meta meta, Klass currentKlass, Method symbolicResolution, CallSiteType callSiteType, Klass symbolicHolder) { - Method resolved = symbolicResolution; + private static , C extends TypeAccess, M extends MethodAccess, F extends FieldAccess> ResolvedCall resolveCallSiteImpl( + R runtime, + C currentKlass, M symbolicResolution, + CallSiteType callSiteType, C symbolicHolder) { + M resolved = symbolicResolution; CallKind callKind; switch (callSiteType) { case Static: // Otherwise, if the resolved method is an instance method, the invokestatic // instruction throws an IncompatibleClassChangeError. if (!resolved.isStatic()) { - throw throwBoundary(meta, meta.java_lang_IncompatibleClassChangeError, "Expected static method '%s.%s%s'", - resolved.getDeclaringKlass().getName(), - resolved.getName(), - resolved.getRawSignature()); + throw runtime.throwError(ErrorType.IncompatibleClassChangeError, "Expected static method '%s.%s%s'", + resolved.getDeclaringClass().getJavaName(), + resolved.getSymbolicName(), + resolved.getSymbolicSignature()); } callKind = CallKind.STATIC; break; @@ -242,22 +272,19 @@ public static ResolvedCall resolveCallSite(Meta meta, Klass currentKlass, Method // Otherwise, if the resolved method is static or (jdk8 or earlier) private, the // invokeinterface instruction throws an IncompatibleClassChangeError. if (resolved.isStatic() || - (meta.getJavaVersion().java8OrEarlier() && resolved.isPrivate())) { - throw throwBoundary(meta, meta.java_lang_IncompatibleClassChangeError, "Expected instance not static method '%s.%s%s'", - resolved.getDeclaringKlass().getName(), - resolved.getName(), - resolved.getRawSignature()); + (runtime.getJavaVersion().java8OrEarlier() && resolved.isPrivate())) { + throw runtime.throwError(ErrorType.IncompatibleClassChangeError, "Expected instance not static method '%s.%s%s'", + resolved.getDeclaringClass().getJavaName(), + resolved.getSymbolicName(), + resolved.getSymbolicSignature()); } - if (resolved.getITableIndex() < 0) { - if (resolved.isPrivate()) { - assert meta.getJavaVersion().java9OrLater(); - // Interface private methods do not appear in itables. - callKind = CallKind.DIRECT; - } else { - assert resolved.getVTableIndex() >= 0; - // Can happen in old classfiles that calls j.l.Object on interfaces. - callKind = CallKind.VTABLE_LOOKUP; - } + if (resolved.isPrivate()) { + assert runtime.getJavaVersion().java9OrLater() : "Should have thrown in previous check."; + // Interface private methods do not appear in itables. + callKind = CallKind.DIRECT; + } else if (resolved.getDeclaringClass().isJavaLangObject()) { + // Can happen in old classfiles that calls j.l.Object methods on interfaces. + callKind = CallKind.VTABLE_LOOKUP; } else { callKind = CallKind.ITABLE_LOOKUP; } @@ -266,12 +293,12 @@ public static ResolvedCall resolveCallSite(Meta meta, Klass currentKlass, Method // Otherwise, if the resolved method is a class (static) method, the invokevirtual // instruction throws an IncompatibleClassChangeError. if (resolved.isStatic()) { - throw throwBoundary(meta, meta.java_lang_IncompatibleClassChangeError, "Expected instance method '%s.%s%s'", - resolved.getDeclaringKlass().getName(), - resolved.getName(), - resolved.getRawSignature()); + throw runtime.throwError(ErrorType.IncompatibleClassChangeError, "Expected instance method '%s.%s%s'", + resolved.getDeclaringClass().getJavaName(), + resolved.getSymbolicName(), + resolved.getSymbolicSignature()); } - if (resolved.isFinalFlagSet() || resolved.getDeclaringKlass().isFinalFlagSet() || resolved.isPrivate()) { + if (resolved.isFinalFlagSet() || resolved.getDeclaringClass().isFinalFlagSet() || resolved.isPrivate()) { callKind = CallKind.DIRECT; } else { callKind = CallKind.VTABLE_LOOKUP; @@ -282,21 +309,21 @@ public static ResolvedCall resolveCallSite(Meta meta, Klass currentKlass, Method // class in which it is declared is not the class symbolically referenced by the // instruction, a NoSuchMethodError is thrown. if (resolved.isConstructor()) { - if (resolved.getDeclaringKlass().getName() != symbolicHolder.getName()) { - throw throwBoundary(meta, meta.java_lang_NoSuchMethodError, + if (resolved.getDeclaringClass().getSymbolicName() != symbolicHolder.getSymbolicName()) { + throw runtime.throwError(ErrorType.NoSuchMethodError, "%s.%s%s", - resolved.getDeclaringKlass().getName(), - resolved.getName(), - resolved.getRawSignature()); + resolved.getDeclaringClass().getJavaName(), + resolved.getSymbolicName(), + resolved.getSymbolicSignature()); } } // Otherwise, if the resolved method is a class (static) method, the invokespecial // instruction throws an IncompatibleClassChangeError. if (resolved.isStatic()) { - throw throwBoundary(meta, meta.java_lang_IncompatibleClassChangeError, "Expected instance not static method '%s.%s%s'", - resolved.getDeclaringKlass().getName(), - resolved.getName(), - resolved.getRawSignature()); + throw runtime.throwError(ErrorType.IncompatibleClassChangeError, "Expected instance not static method '%s.%s%s'", + resolved.getDeclaringClass().getJavaName(), + resolved.getSymbolicName(), + resolved.getSymbolicSignature()); } // If all of the following are true, let C be the direct superclass of the current // class: @@ -313,23 +340,17 @@ public static ResolvedCall resolveCallSite(Meta meta, Klass currentKlass, Method if (!resolved.isConstructor()) { if (!symbolicHolder.isInterface() && symbolicHolder != currentKlass && - currentKlass.getSuperKlass() != null && - symbolicHolder != currentKlass.getSuperKlass() && + currentKlass.getSuperClass() != null && + symbolicHolder != currentKlass.getSuperClass() && symbolicHolder.isAssignableFrom(currentKlass)) { - resolved = currentKlass.getSuperKlass().lookupMethod(resolved.getName(), resolved.getRawSignature(), Klass.LookupMode.INSTANCE_ONLY); + resolved = currentKlass.getSuperClass().lookupInstanceMethod(resolved.getSymbolicName(), resolved.getSymbolicSignature()); } } callKind = CallKind.DIRECT; break; default: - CompilerDirectives.transferToInterpreterAndInvalidate(); - throw EspressoError.unimplemented("Resolution for " + callSiteType); + throw runtime.fatal("Resolution for %s", callSiteType); } - return new ResolvedCall(callKind, resolved); - } - - @TruffleBoundary - private static RuntimeException throwBoundary(Meta meta, ObjectKlass exceptionKlass, String messageFormat, Object... args) { - throw meta.throwExceptionWithMessage(exceptionKlass, String.format(Locale.ENGLISH, messageFormat, args)); + return new ResolvedCall<>(callKind, resolved); } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/ResolvedCall.java b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/resolver/ResolvedCall.java similarity index 66% rename from espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/ResolvedCall.java rename to espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/resolver/ResolvedCall.java index 11a037b3cdb8..09bcbf631446 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/ResolvedCall.java +++ b/espresso/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/resolver/ResolvedCall.java @@ -21,38 +21,36 @@ * questions. */ -package com.oracle.truffle.espresso.resolver; +package com.oracle.truffle.espresso.shared.resolver; import java.util.Objects; -import com.oracle.truffle.espresso.impl.Method; +import com.oracle.truffle.espresso.shared.meta.FieldAccess; +import com.oracle.truffle.espresso.shared.meta.MethodAccess; +import com.oracle.truffle.espresso.shared.meta.TypeAccess; -public final class ResolvedCall { +/** + * Represents a resolved call-site. + */ +public final class ResolvedCall, M extends MethodAccess, F extends FieldAccess> { private final CallKind callKind; - private final Method resolved; + private final M resolved; - public ResolvedCall(CallKind callKind, Method resolved) { + public ResolvedCall(CallKind callKind, M resolved) { this.callKind = Objects.requireNonNull(callKind); this.resolved = Objects.requireNonNull(resolved); } - public Object call(Object... args) { - return switch (callKind) { - case STATIC -> - resolved.invokeDirectStatic(args); - case DIRECT -> - resolved.invokeDirect(args); - case VTABLE_LOOKUP -> - resolved.invokeDirectVirtual(args); - case ITABLE_LOOKUP -> - resolved.invokeDirectInterface(args); - }; - } - - public Method getResolvedMethod() { + /** + * Returns the resolved method. + */ + public M getResolvedMethod() { return resolved; } + /** + * Returns the {@link CallKind kind} of call to perform at the call-site. + */ public CallKind getCallKind() { return callKind; } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/EspressoScope.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/EspressoScope.java index b26892cdde9f..d20b6afc5baa 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/EspressoScope.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/EspressoScope.java @@ -41,10 +41,10 @@ import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.attributes.Local; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.nodes.EspressoFrame; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/GraphBuilder.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/GraphBuilder.java index fb6e1bc806a8..c8a0ce90e46a 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/GraphBuilder.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/GraphBuilder.java @@ -37,13 +37,13 @@ import com.oracle.truffle.espresso.analysis.graph.EspressoExecutionGraph; import com.oracle.truffle.espresso.analysis.graph.Graph; import com.oracle.truffle.espresso.analysis.graph.LinkedBlock; +import com.oracle.truffle.espresso.classfile.ExceptionHandler; import com.oracle.truffle.espresso.classfile.bytecode.BytecodeLookupSwitch; import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; import com.oracle.truffle.espresso.classfile.bytecode.BytecodeSwitch; import com.oracle.truffle.espresso.classfile.bytecode.BytecodeTableSwitch; import com.oracle.truffle.espresso.classfile.bytecode.Bytecodes; import com.oracle.truffle.espresso.impl.Method; -import com.oracle.truffle.espresso.classfile.ExceptionHandler; public final class GraphBuilder { public static Graph build(Method method) { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/frame/EspressoFrameDescriptor.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/frame/EspressoFrameDescriptor.java index ab329a070817..128240839133 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/frame/EspressoFrameDescriptor.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/frame/EspressoFrameDescriptor.java @@ -34,12 +34,12 @@ import com.oracle.truffle.api.frame.Frame; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.ExplodeLoop; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.verifier.StackMapFrameParser; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/frame/FrameAnalysis.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/frame/FrameAnalysis.java index 667d68ff3c62..d11cb07ae202 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/frame/FrameAnalysis.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/frame/FrameAnalysis.java @@ -233,25 +233,25 @@ import com.oracle.truffle.espresso.EspressoLanguage; import com.oracle.truffle.espresso.analysis.frame.EspressoFrameDescriptor.Builder; import com.oracle.truffle.espresso.analysis.liveness.LivenessAnalysis; -import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; -import com.oracle.truffle.espresso.classfile.bytecode.BytecodeSwitch; -import com.oracle.truffle.espresso.classfile.bytecode.Bytecodes; import com.oracle.truffle.espresso.classfile.ClassfileParser; import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.Constants; -import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; +import com.oracle.truffle.espresso.classfile.ExceptionHandler; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.attributes.StackMapTableAttribute; +import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; +import com.oracle.truffle.espresso.classfile.bytecode.BytecodeSwitch; +import com.oracle.truffle.espresso.classfile.bytecode.Bytecodes; import com.oracle.truffle.espresso.classfile.constantpool.DynamicConstant; import com.oracle.truffle.espresso.classfile.descriptors.Signatures; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; +import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.ExceptionHandler; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.nodes.EspressoFrame; import com.oracle.truffle.espresso.verifier.StackMapFrameParser; import com.oracle.truffle.espresso.verifier.VerificationTypeInfo; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/frame/FrameType.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/frame/FrameType.java index 1afa70dd64d4..581456eb6ad0 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/frame/FrameType.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/frame/FrameType.java @@ -23,11 +23,11 @@ package com.oracle.truffle.espresso.analysis.frame; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; public abstract class FrameType { public static final FrameType INT = new PrimitiveFrameType(JavaKind.Int); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/liveness/LivenessAnalysis.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/liveness/LivenessAnalysis.java index b767a77ad6a9..e0a264ce69fe 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/liveness/LivenessAnalysis.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/analysis/liveness/LivenessAnalysis.java @@ -47,12 +47,12 @@ import com.oracle.truffle.espresso.analysis.liveness.actions.NullOutAction; import com.oracle.truffle.espresso.analysis.liveness.actions.SelectEdgeAction; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; -import com.oracle.truffle.espresso.impl.Klass; -import com.oracle.truffle.espresso.impl.Method; -import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.classfile.perf.DebugCloseable; import com.oracle.truffle.espresso.classfile.perf.DebugTimer; import com.oracle.truffle.espresso.classfile.perf.TimerCollection; +import com.oracle.truffle.espresso.impl.Klass; +import com.oracle.truffle.espresso.impl.Method; +import com.oracle.truffle.espresso.meta.EspressoError; public final class LivenessAnalysis { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/bytecode/BytecodePrinter.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/bytecode/BytecodePrinter.java index 72f96b28a4bf..a37b7e6ab6ad 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/bytecode/BytecodePrinter.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/bytecode/BytecodePrinter.java @@ -22,8 +22,9 @@ */ package com.oracle.truffle.espresso.bytecode; -import com.oracle.truffle.espresso.impl.Klass; -import com.oracle.truffle.espresso.meta.EspressoError; +import java.io.PrintStream; +import java.util.Arrays; + import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.bytecode.BytecodeLookupSwitch; import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; @@ -32,9 +33,8 @@ import com.oracle.truffle.espresso.classfile.constantpool.ClassConstant; import com.oracle.truffle.espresso.classfile.constantpool.InvokeDynamicConstant; import com.oracle.truffle.espresso.classfile.constantpool.MethodRefConstant; - -import java.io.PrintStream; -import java.util.Arrays; +import com.oracle.truffle.espresso.impl.Klass; +import com.oracle.truffle.espresso.meta.EspressoError; public class BytecodePrinter { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/bytecode/MapperBCI.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/bytecode/MapperBCI.java index b7e1c7b4f5c2..771a570dfb6e 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/bytecode/MapperBCI.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/bytecode/MapperBCI.java @@ -28,9 +28,9 @@ import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.espresso.classfile.attributes.LineNumberTableAttribute; +import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.nodes.EspressoNode; -import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; /** * lightweight map from BCI to array index. The contract is easy, upon lookup, returns the index of @@ -102,6 +102,17 @@ private int lookup(int targetBCI) { throw EspressoError.shouldNotReachHere(); } + /** + * Lookup the index of the largest element which is smaller or equal to {@code targetBCI}. + */ + public int lookupBucket(int targetBCI) { + int res = slowLookup(targetBCI, 0, length); + if (res >= 0) { + return res; + } + return -res - 1; + } + public int lookup(int curIndex, int curBCI, int targetBCI) { int res; int start; @@ -117,7 +128,7 @@ public int lookup(int curIndex, int curBCI, int targetBCI) { if (res >= 0) { return res; } - return -res - 2; + return -res - 1; } public int checkNext(int curIndex, int targetBCI) { @@ -128,20 +139,20 @@ public int checkNext(int curIndex, int targetBCI) { } /** - * inlined binary search. No bounds checks. + * Inlined binary search. No bounds checks. * * @see Arrays#binarySearch(int[], int, int, int) + * @return either the index of the element that is equal to {@code targetBCI} or a negative + * number {@code -(i + 1)} where i is the index of the largest element which is smaller + * than {@code targetBCI}. */ @ExplodeLoop(kind = ExplodeLoop.LoopExplosionKind.FULL_EXPLODE) private int slowLookup(int targetBCI, int start, int end) { - // Our usage should not see an out of bounds int low = start; int high = end - 1; - while (low <= high) { int mid = (low + high) >>> 1; int midVal = bcis[mid]; - if (midVal < targetBCI) { low = mid + 1; } else if (midVal > targetBCI) { @@ -150,7 +161,7 @@ private int slowLookup(int targetBCI, int start, int end) { return mid; } } - return -(low + 1); // key not found. + return -low; } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/FailInvokeDynamicConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/FailInvokeDynamicConstant.java index 3d81be70393c..4ece13b74b6d 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/FailInvokeDynamicConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/FailInvokeDynamicConstant.java @@ -22,19 +22,19 @@ */ package com.oracle.truffle.espresso.constantpool; +import java.nio.ByteBuffer; + import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.constantpool.NameAndTypeConstant; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.constantpool.NameAndTypeConstant; import com.oracle.truffle.espresso.runtime.EspressoException; -import java.nio.ByteBuffer; - public final class FailInvokeDynamicConstant implements LinkableInvokeDynamicConstant { private final EspressoException failure; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/LinkableInvokeDynamicConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/LinkableInvokeDynamicConstant.java index f5fe7a1ed6b2..f6eb31ac2b51 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/LinkableInvokeDynamicConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/LinkableInvokeDynamicConstant.java @@ -22,10 +22,10 @@ */ package com.oracle.truffle.espresso.constantpool; -import com.oracle.truffle.espresso.impl.Method; -import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.classfile.constantpool.InvokeDynamicConstant; import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; +import com.oracle.truffle.espresso.impl.Method; +import com.oracle.truffle.espresso.impl.ObjectKlass; interface LinkableInvokeDynamicConstant extends InvokeDynamicConstant, Resolvable.ResolvedConstant { CallSiteLink link(RuntimeConstantPool pool, ObjectKlass accessingKlass, int thisIndex, Method method, int bci); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/MissingFieldRefConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/MissingFieldRefConstant.java index 8f9d4c267bb4..cdadcc0472e6 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/MissingFieldRefConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/MissingFieldRefConstant.java @@ -25,13 +25,13 @@ import com.oracle.truffle.api.Assumption; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.constantpool.FieldRefConstant; +import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.impl.Field; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.constantpool.FieldRefConstant; -import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; import com.oracle.truffle.espresso.runtime.EspressoException; public final class MissingFieldRefConstant implements FieldRefConstant, Resolvable.ResolvedConstant { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/PreResolvedClassConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/PreResolvedClassConstant.java index 06c617012eb5..7188e7a382d5 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/PreResolvedClassConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/PreResolvedClassConstant.java @@ -22,15 +22,15 @@ */ package com.oracle.truffle.espresso.constantpool; +import java.nio.ByteBuffer; +import java.util.Objects; + import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.constantpool.ClassConstant; +import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.impl.Klass; -import com.oracle.truffle.espresso.classfile.constantpool.ClassConstant; -import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; - -import java.nio.ByteBuffer; -import java.util.Objects; /** * Constant Pool patching inserts already resolved constants in the constant pool. However, at the diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/Resolution.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/Resolution.java index 143a67760b55..680d8745533d 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/Resolution.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/Resolution.java @@ -63,9 +63,9 @@ import com.oracle.truffle.espresso.nodes.methodhandle.MHInvokeGenericNode; import com.oracle.truffle.espresso.nodes.methodhandle.MHLinkToNode; import com.oracle.truffle.espresso.redefinition.ClassRedefinition; -import com.oracle.truffle.espresso.resolver.LinkResolver; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.EspressoException; +import com.oracle.truffle.espresso.runtime.EspressoLinkResolver; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.substitutions.JavaType; @@ -202,18 +202,18 @@ public static Resolvable.ResolvedConstant resolveFieldRefConstant(FieldRefConsta Symbol name = thiz.getName(pool); Symbol type = thiz.getType(pool); - Meta meta = pool.getContext().getMeta(); + EspressoContext context = pool.getContext(); Field field; ClassRedefinition classRedefinition = null; try { try { - field = LinkResolver.resolveFieldSymbol(meta, accessingKlass, name, type, holderKlass, true, true); + field = EspressoLinkResolver.resolveFieldSymbol(context, accessingKlass, name, type, holderKlass, true, true); } catch (EspressoException e) { - classRedefinition = pool.getContext().getClassRedefinition(); + classRedefinition = context.getClassRedefinition(); if (classRedefinition != null) { // could be due to ongoing redefinition classRedefinition.check(); - field = LinkResolver.resolveFieldSymbol(meta, accessingKlass, name, type, holderKlass, true, true); + field = EspressoLinkResolver.resolveFieldSymbol(context, accessingKlass, name, type, holderKlass, true, true); } else { throw e; } @@ -230,7 +230,8 @@ public static Klass getResolvedHolderKlass(MemberRefConstant.Indexes thiz, Runti } @TruffleBoundary - public static void memberDoAccessCheck(ObjectKlass accessingKlass, Klass resolvedKlass, Member member, Meta meta) { + public static void memberDoAccessCheck(Klass accessingKlass, Klass resolvedKlass, Member member, Meta meta) { + assert accessingKlass != null && resolvedKlass != null && member != null : "pre-conditions failed."; if (!memberCheckAccess(accessingKlass, resolvedKlass, member)) { String message = "Class " + accessingKlass.getExternalName() + " cannot access method " + resolvedKlass.getExternalName() + "#" + member.getName(); throw meta.throwExceptionWithMessage(meta.java_lang_IllegalAccessError, meta.toGuestString(message)); @@ -253,7 +254,7 @@ public static void memberDoAccessCheck(ObjectKlass accessingKlass, Klass resolve *

  • R is private and is declared in D. * */ - static boolean memberCheckAccess(ObjectKlass accessingKlass, Klass resolvedKlass, Member member) { + static boolean memberCheckAccess(Klass accessingKlass, Klass resolvedKlass, Member member) { if (member.isPublic()) { return true; } @@ -354,10 +355,11 @@ public static Resolvable.ResolvedConstant resolveInterfaceMethodRefConstant(Inte METHODREF_RESOLVE_COUNT.inc(); Klass holderInterface = getResolvedHolderKlass(thiz, pool, accessingKlass); + EspressoContext context = pool.getContext(); Symbol name = thiz.getName(pool); Symbol signature = thiz.getSignature(pool); - Method method = LinkResolver.resolveMethodSymbol(pool.getContext().getMeta(), accessingKlass, name, signature, holderInterface, true, true, true); + Method method = EspressoLinkResolver.resolveMethodSymbol(context, accessingKlass, name, signature, holderInterface, true, true, true); return new ResolvedInterfaceMethodRefConstant(method); } @@ -472,17 +474,17 @@ public static Resolvable.ResolvedConstant resolveInterfaceMethodRefConstant(Inte public static Resolvable.ResolvedConstant resolveClassMethodRefConstant(ClassMethodRefConstant.Indexes thiz, RuntimeConstantPool pool, @SuppressWarnings("unused") int thisIndex, ObjectKlass accessingKlass) { METHODREF_RESOLVE_COUNT.inc(); + EspressoContext context = pool.getContext(); - Meta meta = context.getMeta(); Klass holderKlass = getResolvedHolderKlass(thiz, pool, accessingKlass); Symbol name = thiz.getName(pool); Symbol signature = thiz.getSignature(pool); - Method method = LinkResolver.resolveMethodSymbol(meta, accessingKlass, name, signature, holderKlass, false, true, true); + Method method = EspressoLinkResolver.resolveMethodSymbol(context, accessingKlass, name, signature, holderKlass, false, true, true); if (method.isInvokeIntrinsic()) { - MHInvokeGenericNode.MethodHandleInvoker invoker = MHInvokeGenericNode.linkMethod(meta.getLanguage(), meta, accessingKlass, method, name, signature); + MHInvokeGenericNode.MethodHandleInvoker invoker = MHInvokeGenericNode.linkMethod(context.getMeta().getLanguage(), context.getMeta(), accessingKlass, method, name, signature); return new ResolvedWithInvokerClassMethodRefConstant(method, invoker); } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedClassConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedClassConstant.java index a56ce37b0505..1b17af502b88 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedClassConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedClassConstant.java @@ -22,14 +22,14 @@ */ package com.oracle.truffle.espresso.constantpool; +import java.util.Objects; + import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.constantpool.ClassConstant; +import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.impl.Klass; -import com.oracle.truffle.espresso.classfile.constantpool.ClassConstant; -import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; - -import java.util.Objects; public final class ResolvedClassConstant implements ClassConstant, Resolvable.ResolvedConstant { private final Klass resolved; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedClassMethodRefConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedClassMethodRefConstant.java index db75a45ab3bc..63c3949ba3f6 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedClassMethodRefConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedClassMethodRefConstant.java @@ -22,16 +22,16 @@ */ package com.oracle.truffle.espresso.constantpool; +import java.util.Objects; + import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.constantpool.ClassMethodRefConstant; +import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.constantpool.ClassMethodRefConstant; -import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; - -import java.util.Objects; public class ResolvedClassMethodRefConstant implements ClassMethodRefConstant, Resolvable.ResolvedConstant { private final Method resolved; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedFailDynamicConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedFailDynamicConstant.java index 315e73baf581..1d2991522e77 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedFailDynamicConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedFailDynamicConstant.java @@ -26,8 +26,8 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.JavaKind; -import com.oracle.truffle.espresso.nodes.BytecodeNode; import com.oracle.truffle.espresso.meta.EspressoError; +import com.oracle.truffle.espresso.nodes.BytecodeNode; import com.oracle.truffle.espresso.runtime.EspressoException; public final class ResolvedFailDynamicConstant implements ResolvedDynamicConstant { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedFieldRefConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedFieldRefConstant.java index bf7894a8ce86..7c5629cb2105 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedFieldRefConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedFieldRefConstant.java @@ -22,16 +22,16 @@ */ package com.oracle.truffle.espresso.constantpool; +import java.util.Objects; + import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.constantpool.FieldRefConstant; +import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.impl.Field; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.constantpool.FieldRefConstant; -import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; - -import java.util.Objects; public final class ResolvedFieldRefConstant implements FieldRefConstant, Resolvable.ResolvedConstant { private final Field resolved; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedInterfaceMethodRefConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedInterfaceMethodRefConstant.java index 0e3be2b4e600..68433eb35227 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedInterfaceMethodRefConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedInterfaceMethodRefConstant.java @@ -22,16 +22,16 @@ */ package com.oracle.truffle.espresso.constantpool; +import java.util.Objects; + import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.constantpool.InterfaceMethodRefConstant; +import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.constantpool.InterfaceMethodRefConstant; -import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; - -import java.util.Objects; public final class ResolvedInterfaceMethodRefConstant implements InterfaceMethodRefConstant, Resolvable.ResolvedConstant { private final Method resolved; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedInvokeDynamicConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedInvokeDynamicConstant.java index 9e195fc89fd7..a162e8dd05aa 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedInvokeDynamicConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedInvokeDynamicConstant.java @@ -22,24 +22,24 @@ */ package com.oracle.truffle.espresso.constantpool; +import java.util.Arrays; + import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.attributes.BootstrapMethodsAttribute; +import com.oracle.truffle.espresso.classfile.constantpool.NameAndTypeConstant; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.impl.ObjectKlass; -import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.attributes.BootstrapMethodsAttribute; -import com.oracle.truffle.espresso.classfile.constantpool.NameAndTypeConstant; +import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.runtime.EspressoException; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; -import java.util.Arrays; - public final class ResolvedInvokeDynamicConstant implements LinkableInvokeDynamicConstant { private final BootstrapMethodsAttribute.Entry bootstrapMethod; private final Symbol[] parsedInvokeSignature; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedMethodHandleConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedMethodHandleConstant.java index a5f0e05fac5a..64f3dc05fc48 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedMethodHandleConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedMethodHandleConstant.java @@ -22,6 +22,8 @@ */ package com.oracle.truffle.espresso.constantpool; +import java.lang.invoke.MethodHandle; + import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.ConstantPool.Tag; import com.oracle.truffle.espresso.classfile.constantpool.MethodHandleConstant; @@ -30,8 +32,6 @@ import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.substitutions.JavaType; -import java.lang.invoke.MethodHandle; - public final class ResolvedMethodHandleConstant implements MethodHandleConstant, Resolvable.ResolvedConstant { private final @JavaType(MethodHandle.class) StaticObject payload; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedMethodTypeConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedMethodTypeConstant.java index 82d6a26d2482..666cd2361e78 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedMethodTypeConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedMethodTypeConstant.java @@ -22,17 +22,17 @@ */ package com.oracle.truffle.espresso.constantpool; +import java.lang.invoke.MethodType; + import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.constantpool.MethodTypeConstant; +import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.constantpool.MethodTypeConstant; -import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.substitutions.JavaType; -import java.lang.invoke.MethodType; - public final class ResolvedMethodTypeConstant implements MethodTypeConstant, Resolvable.ResolvedConstant { private final @JavaType(MethodType.class) StaticObject resolved; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedStringConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedStringConstant.java index fc5522ed6223..3d7985f4881d 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedStringConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedStringConstant.java @@ -23,12 +23,12 @@ package com.oracle.truffle.espresso.constantpool; import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; +import com.oracle.truffle.espresso.classfile.constantpool.StringConstant; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.ModifiedUTF8; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; -import com.oracle.truffle.espresso.classfile.constantpool.StringConstant; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.substitutions.JavaType; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedWithInvokerClassMethodRefConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedWithInvokerClassMethodRefConstant.java index 76fac543f4f4..6f74159c2897 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedWithInvokerClassMethodRefConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/ResolvedWithInvokerClassMethodRefConstant.java @@ -22,11 +22,11 @@ */ package com.oracle.truffle.espresso.constantpool; +import java.util.Objects; + import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.nodes.methodhandle.MHInvokeGenericNode; -import java.util.Objects; - /** * This is used for * {@link com.oracle.truffle.espresso.runtime.MethodHandleIntrinsics.PolySigIntrinsics#InvokeGeneric} diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/RuntimeConstantPool.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/RuntimeConstantPool.java index 0ef687c9a5bb..24c698d5b99f 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/RuntimeConstantPool.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/constantpool/RuntimeConstantPool.java @@ -199,10 +199,13 @@ public MethodRefConstant resolvedMethodRefAt(ObjectKlass accessingKlass, int ind return (MethodRefConstant) resolved; } - public Method resolvedMethodAtNoCache(ObjectKlass accessingKlass, int index) { + public Method resolveMethodAndUpdate(ObjectKlass accessingKlass, int index) { CompilerAsserts.neverPartOfCompilation(); Resolvable.ResolvedConstant resolved = resolvedAtNoCache(accessingKlass, index, "method"); - return (Method) resolved.value(); + synchronized (this) { + resolvedConstants[index] = resolved; + } + return ((Method) resolved.value()); } public StaticObject resolvedMethodHandleAt(ObjectKlass accessingKlass, int index) { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ffi/TruffleByteBuffer.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ffi/TruffleByteBuffer.java index 2326387fa9dd..e09ec96e39e8 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ffi/TruffleByteBuffer.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ffi/TruffleByteBuffer.java @@ -39,8 +39,8 @@ import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.profiles.BranchProfile; -import com.oracle.truffle.espresso.ffi.nfi.NativeUtils; import com.oracle.truffle.espresso.classfile.JavaKind; +import com.oracle.truffle.espresso.ffi.nfi.NativeUtils; @ExportLibrary(InteropLibrary.class) public final class TruffleByteBuffer implements TruffleObject { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ffi/nfi/NFINativeAccess.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ffi/nfi/NFINativeAccess.java index f6d0f4922069..ef882dc0f10b 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ffi/nfi/NFINativeAccess.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ffi/nfi/NFINativeAccess.java @@ -50,6 +50,7 @@ import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.classfile.perf.DebugCounter; import com.oracle.truffle.espresso.ffi.Buffer; import com.oracle.truffle.espresso.ffi.NativeAccess; import com.oracle.truffle.espresso.ffi.NativeSignature; @@ -59,7 +60,6 @@ import com.oracle.truffle.espresso.ffi.SignatureCallNode; import com.oracle.truffle.espresso.ffi.TruffleByteBuffer; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.perf.DebugCounter; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.substitutions.Collect; import com.oracle.truffle.espresso.vm.UnsafeAccess; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ffi/nfi/NativeUtils.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ffi/nfi/NativeUtils.java index 9e2f7004becf..dbbe8d41dcd3 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ffi/nfi/NativeUtils.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ffi/nfi/NativeUtils.java @@ -33,11 +33,11 @@ import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.espresso.classfile.JavaKind; +import com.oracle.truffle.espresso.classfile.descriptors.ModifiedUtf8; import com.oracle.truffle.espresso.ffi.Pointer; import com.oracle.truffle.espresso.ffi.RawPointer; -import com.oracle.truffle.espresso.classfile.descriptors.ModifiedUtf8; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.vm.UnsafeAccess; import sun.misc.Unsafe; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ArrayKlass.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ArrayKlass.java index 20b99ddad302..d35ebf5e9134 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ArrayKlass.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ArrayKlass.java @@ -35,6 +35,7 @@ import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; @@ -43,7 +44,6 @@ import com.oracle.truffle.espresso.impl.PackageTable.PackageEntry; import com.oracle.truffle.espresso.jdwp.api.MethodRef; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.substitutions.JavaType; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/BootClassRegistry.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/BootClassRegistry.java index 3996813e87e2..2f47425a7ac6 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/BootClassRegistry.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/BootClassRegistry.java @@ -23,17 +23,15 @@ package com.oracle.truffle.espresso.impl; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.espresso.classfile.ClasspathFile; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; import com.oracle.truffle.espresso.classfile.perf.DebugCloseable; import com.oracle.truffle.espresso.classfile.perf.DebugCounter; import com.oracle.truffle.espresso.classfile.perf.DebugTimer; -import com.oracle.truffle.espresso.classfile.ClasspathFile; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.substitutions.JavaType; @@ -58,8 +56,6 @@ protected void loadKlassCacheHitsInc() { loadKlassCacheHits.inc(); } - private final Map packageMap = new ConcurrentHashMap<>(); - public BootClassRegistry(long loaderID) { super(loaderID); } @@ -82,19 +78,13 @@ public Klass loadKlassImpl(EspressoContext context, Symbol type) throws Es // use of computeIfAbsent to insert the class since the map is modified. ObjectKlass result = defineKlass(context, type, classpathFile.contents); context.getRegistries().recordConstraint(type, result, getClassLoader()); - packageMap.put(result.getRuntimePackage().toString(), classpathFile.classpathEntry.path()); - return result; - } - - @TruffleBoundary - public String getPackagePath(String pkgName) { - String result = packageMap.get(pkgName); + result.packageEntry().setBootClasspathLocation(classpathFile.classpathEntry.path()); return result; } @TruffleBoundary - public String[] getPackages() { - return packageMap.keySet().toArray(new String[0]); + public Symbol[] getPackages() { + return packages().getKeys(); } @Override diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ClassLoadingEnv.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ClassLoadingEnv.java index 5891397886b7..f0016b5826b1 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ClassLoadingEnv.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ClassLoadingEnv.java @@ -22,27 +22,27 @@ */ package com.oracle.truffle.espresso.impl; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.util.logging.Level; + import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.TruffleLogger; import com.oracle.truffle.espresso.EspressoLanguage; import com.oracle.truffle.espresso.EspressoOptions; +import com.oracle.truffle.espresso.classfile.JavaVersion; +import com.oracle.truffle.espresso.classfile.ParsingContext; +import com.oracle.truffle.espresso.classfile.constantpool.Utf8Constant; import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; -import com.oracle.truffle.espresso.meta.Meta; -import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaVersion; -import com.oracle.truffle.espresso.classfile.ParsingContext; -import com.oracle.truffle.espresso.classfile.constantpool.Utf8Constant; import com.oracle.truffle.espresso.classfile.perf.TimerCollection; +import com.oracle.truffle.espresso.meta.EspressoError; +import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Supplier; -import java.util.logging.Level; - public class ClassLoadingEnv implements LanguageAccess { private final AtomicLong klassIdProvider = new AtomicLong(); private final AtomicLong loaderIdProvider = new AtomicLong(); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ClassRegistries.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ClassRegistries.java index 8901af49cd0f..05d7ccdc4682 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ClassRegistries.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ClassRegistries.java @@ -35,6 +35,7 @@ import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; import com.oracle.truffle.espresso.jdwp.api.ModuleRef; @@ -72,7 +73,7 @@ public ClassRegistries(EspressoContext context) { } public void initJavaBaseModule() { - this.javaBaseModule = bootClassRegistry.modules().createAndAddEntry(Symbol.Name.java_base, bootClassRegistry); + this.javaBaseModule = bootClassRegistry.modules().createAndAddEntry(Name.java_base, null, null, false, null); } public ClassRegistry getClassRegistry(@JavaType(ClassLoader.class) StaticObject classLoader) { @@ -124,19 +125,39 @@ public ModuleTable.ModuleEntry getJavaBaseModule() { public ModuleTable.ModuleEntry getPolyglotAPIModule() { if (polyglotAPIModule == null) { - ModuleRef[] allModuleRefs = getAllModuleRefs(); - for (ModuleRef module : allModuleRefs) { - if ("espresso.polyglot".equals(module.jdwpName())) { - polyglotAPIModule = (ModuleTable.ModuleEntry) module; - break; + polyglotAPIModule = findPlatformOrBootModule(Name.espresso_polyglot); + } + return polyglotAPIModule; + } + + private ModuleTable.ModuleEntry findPlatformOrBootModule(Symbol name) { + ModuleTable.ModuleEntry m = bootClassRegistry.modules().lookup(name); + if (m != null) { + return m; + } + ObjectKlass platformClassLoaderKlass = context.getMeta().jdk_internal_loader_ClassLoaders$PlatformClassLoader; + if (platformClassLoaderKlass == null) { + return null; + } + synchronized (weakClassLoaderSet) { + for (StaticObject loader : weakClassLoaderSet) { + if (loader != null) { + if (platformClassLoaderKlass.isAssignableFrom(loader.getKlass())) { + m = getClassRegistry(loader).modules().lookup(name); + if (m != null) { + return m; + } + } } } } - return polyglotAPIModule; + return null; } public boolean javaBaseDefined() { - return javaBaseModule != null && !StaticObject.isNull(javaBaseModule.module()); + boolean result = javaBaseModule != null && javaBaseModule.module() != null; + assert !result || StaticObject.notNull(javaBaseModule.module()); + return result; } @TruffleBoundary @@ -212,18 +233,18 @@ public Klass[] getAllLoadedClasses() { public ModuleRef[] getAllModuleRefs() { ArrayList list = new ArrayList<>(); // add modules from boot registry - list.addAll(bootClassRegistry.modules().values()); + bootClassRegistry.modules().collectValues(list::add); // add modules from all other registries synchronized (weakClassLoaderSet) { for (StaticObject classLoader : weakClassLoaderSet) { - list.addAll(getClassRegistry(classLoader).modules().values()); + getClassRegistry(classLoader).modules().collectValues(list::add); } } return list.toArray(ModuleRef.EMPTY_ARRAY); } - public ModuleTable.ModuleEntry findUniqueModule(Symbol name) { + public ModuleTable.ModuleEntry findUniqueModule(Symbol name) { ModuleTable.ModuleEntry result = bootClassRegistry.modules().lookup(name); synchronized (weakClassLoaderSet) { for (StaticObject classLoader : weakClassLoaderSet) { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ClassRegistry.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ClassRegistry.java index acc6c42bc96e..7641794b3fbb 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ClassRegistry.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ClassRegistry.java @@ -32,20 +32,20 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import com.oracle.truffle.api.CompilerAsserts; -import com.oracle.truffle.espresso.constantpool.Resolution; -import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; -import com.oracle.truffle.espresso.classfile.descriptors.Types; -import com.oracle.truffle.espresso.impl.ModuleTable.ModuleEntry; -import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.Constants; import com.oracle.truffle.espresso.classfile.ParserKlass; import com.oracle.truffle.espresso.classfile.constantpool.PoolConstant; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; +import com.oracle.truffle.espresso.classfile.descriptors.Types; import com.oracle.truffle.espresso.classfile.perf.DebugCloseable; import com.oracle.truffle.espresso.classfile.perf.DebugTimer; +import com.oracle.truffle.espresso.constantpool.Resolution; +import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; +import com.oracle.truffle.espresso.impl.ModuleTable.ModuleEntry; +import com.oracle.truffle.espresso.meta.EspressoError; +import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.redefinition.DefineKlassListener; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.EspressoException; @@ -123,7 +123,6 @@ private ClassDefinitionInfo(StaticObject protectionDomain, public final boolean isHidden; public final boolean isStrongHidden; public final boolean forceAllowVMAnnotations; - public long klassID = -1; public boolean addedToRegistry() { return !isAnonymousClass() && !isHidden(); @@ -276,7 +275,7 @@ protected ClassRegistry(long loaderID) { } public void initUnnamedModule(StaticObject unnamedModule) { - this.unnamed = ModuleEntry.createUnnamedModuleEntry(unnamedModule, this); + this.unnamed = modules.createUnnamedModuleEntry(unnamedModule); } /** @@ -619,7 +618,7 @@ public final DynamicModuleWrapper getProxyDynamicModuleWrapper() { return dynamicModuleWrapper; } - public final class DynamicModuleWrapper { + public static final class DynamicModuleWrapper { private ModuleEntry dynamicProxyModule; public ModuleEntry getDynamicProxyModule() { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/EnumConstantField.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/EnumConstantField.java index 8cdc59619c1b..0d2eeefe8232 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/EnumConstantField.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/EnumConstantField.java @@ -24,8 +24,8 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; -import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; import com.oracle.truffle.espresso.classfile.descriptors.Types; +import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; public final class EnumConstantField extends Field { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ExtensionFieldsMetadata.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ExtensionFieldsMetadata.java index f37d01ff29d5..3bfa05f6807d 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ExtensionFieldsMetadata.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ExtensionFieldsMetadata.java @@ -30,9 +30,9 @@ import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; -import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.ParserField; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; import com.oracle.truffle.espresso.redefinition.ClassRedefinition; final class ExtensionFieldsMetadata { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Field.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Field.java index ded453b7d10a..dcdab337238a 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Field.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Field.java @@ -27,23 +27,26 @@ import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.Truffle; -import com.oracle.truffle.espresso.jdwp.api.TagConstants; +import com.oracle.truffle.espresso.EspressoOptions; +import com.oracle.truffle.espresso.classfile.ClassfileParser; import com.oracle.truffle.espresso.classfile.Constants; -import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; +import com.oracle.truffle.espresso.classfile.JavaKind; +import com.oracle.truffle.espresso.classfile.attributes.Attribute; import com.oracle.truffle.espresso.classfile.attributes.SignatureAttribute; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.ModifiedUTF8; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; +import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; import com.oracle.truffle.espresso.jdwp.api.FieldBreakpoint; import com.oracle.truffle.espresso.jdwp.api.FieldRef; +import com.oracle.truffle.espresso.jdwp.api.TagConstants; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.meta.Meta; -import com.oracle.truffle.espresso.classfile.attributes.Attribute; import com.oracle.truffle.espresso.runtime.EspressoException; import com.oracle.truffle.espresso.runtime.staticobject.FieldStorageObject; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; +import com.oracle.truffle.espresso.shared.meta.FieldAccess; /** * Represents a resolved Espresso field. @@ -74,7 +77,7 @@ * value (this could be either an Original Field or a Redefine Added Field) a Delegation field is * assigned the underlying field as a Compatible Field. */ -public class Field extends Member implements FieldRef { +public class Field extends Member implements FieldRef, FieldAccess { public static final Field[] EMPTY_ARRAY = new Field[0]; @@ -211,6 +214,22 @@ public final void checkLoadingConstraints(StaticObject loader1, StaticObject loa getDeclaringKlass().getContext().getRegistries().checkLoadingConstraint(getType(), loader1, loader2); } + // region FieldAccess impl + + @Override + public final void loadingConstraints(Klass accessingClass) { + checkLoadingConstraints(accessingClass.getDefiningClassLoader(), getDeclaringKlass().getDefiningClassLoader()); + } + + @Override + public final boolean shouldEnforceInitializerCheck() { + return (getDeclaringKlass().getMeta().getLanguage().getSpecComplianceMode() == EspressoOptions.SpecComplianceMode.STRICT) || + // HotSpot enforces this only for >= Java 9 (v53) .class files. + getDeclaringClass().getMajorVersion() >= ClassfileParser.JAVA_9_VERSION; + } + + // endregion FieldAccess impl + // region Field accesses // region Generic diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/GuestClassRegistry.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/GuestClassRegistry.java index 0e4fb9fd238c..74f418ce8d82 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/GuestClassRegistry.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/GuestClassRegistry.java @@ -71,6 +71,7 @@ public GuestClassRegistry(ClassLoadingEnv env, @JavaType(ClassLoader.class) Stat this.addClass = classLoader.getKlass().lookupMethod(Name.addClass, Signature._void_Class); if (env.getJavaVersion().modulesEnabled()) { StaticObject unnamedModule = env.getMeta().java_lang_ClassLoader_unnamedModule.getObject(classLoader); + assert StaticObject.notNull(unnamedModule); initUnnamedModule(unnamedModule); env.getMeta().HIDDEN_MODULE_ENTRY.setHiddenObject(unnamedModule, getUnnamedModule()); } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Klass.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Klass.java index c08a03047da0..392aa5819e65 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Klass.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Klass.java @@ -30,7 +30,6 @@ import java.util.Comparator; import java.util.function.IntFunction; -import com.oracle.truffle.espresso.jdwp.api.TagConstants; import org.graalvm.collections.EconomicSet; import com.oracle.truffle.api.Assumption; @@ -63,12 +62,14 @@ import com.oracle.truffle.espresso.EspressoLanguage; import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.Constants; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; +import com.oracle.truffle.espresso.classfile.perf.DebugCounter; import com.oracle.truffle.espresso.impl.ModuleTable.ModuleEntry; import com.oracle.truffle.espresso.impl.PackageTable.PackageEntry; import com.oracle.truffle.espresso.jdwp.api.ClassStatusConstants; @@ -76,12 +77,11 @@ import com.oracle.truffle.espresso.jdwp.api.KlassRef; import com.oracle.truffle.espresso.jdwp.api.MethodRef; import com.oracle.truffle.espresso.jdwp.api.ModuleRef; +import com.oracle.truffle.espresso.jdwp.api.TagConstants; import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.meta.InteropKlassesDispatch; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.meta.MetaUtil; -import com.oracle.truffle.espresso.meta.ModifiersProvider; import com.oracle.truffle.espresso.nodes.interop.LookupDeclaredMethod; import com.oracle.truffle.espresso.nodes.interop.LookupDeclaredMethodNodeGen; import com.oracle.truffle.espresso.nodes.interop.LookupFieldNode; @@ -90,7 +90,6 @@ import com.oracle.truffle.espresso.nodes.interop.ToEspressoNodeFactory; import com.oracle.truffle.espresso.nodes.interop.ToPrimitive; import com.oracle.truffle.espresso.nodes.interop.ToPrimitiveFactory; -import com.oracle.truffle.espresso.classfile.perf.DebugCounter; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.EspressoException; import com.oracle.truffle.espresso.runtime.EspressoFunction; @@ -102,12 +101,13 @@ import com.oracle.truffle.espresso.runtime.dispatch.staticobject.InteropLookupAndInvoke; import com.oracle.truffle.espresso.runtime.dispatch.staticobject.InteropLookupAndInvokeFactory; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; +import com.oracle.truffle.espresso.shared.meta.TypeAccess; import com.oracle.truffle.espresso.substitutions.JavaType; import com.oracle.truffle.espresso.vm.InterpreterToVM; import com.oracle.truffle.espresso.vm.VM; @ExportLibrary(InteropLibrary.class) -public abstract class Klass extends ContextAccessImpl implements ModifiersProvider, KlassRef, TruffleObject, EspressoType { +public abstract class Klass extends ContextAccessImpl implements KlassRef, TruffleObject, EspressoType, TypeAccess { // region Interop @@ -816,16 +816,8 @@ public static boolean doModuleAccessChecks(Klass klass, ObjectKlass accessingKla if (moduleFrom == moduleTo) { return true; } - /* - * Acceptable access to a type in an unnamed module. Note that since unnamed modules can - * read all unnamed modules, this also handles the case where module_from is also unnamed - * but in a different class loader. - */ - if (!moduleTo.isNamed() && (moduleFrom.canReadAllUnnamed() || moduleFrom.canRead(moduleTo, context))) { - return true; - } // Establish readability, check if moduleFrom is allowed to read moduleTo. - if (!moduleFrom.canRead(moduleTo, context)) { + if (!moduleFrom.canRead(moduleTo, context.isJavaBase(moduleTo))) { return false; } // Access is allowed if moduleTo is open, i.e. all its packages are unqualifiedly @@ -894,7 +886,7 @@ public final boolean isArray() { public final boolean isInterface() { // conflict between ModifiersProvider and KlassRef interfaces, // so chose the default implementation in ModifiersProvider. - return ModifiersProvider.super.isInterface(); + return TypeAccess.super.isInterface(); } /** @@ -1065,7 +1057,7 @@ public final boolean isFinalFlagSet() { * never overriden default interface methods. We cirumvent this CHA limitation here by using * an invokespecial, which is inlinable. */ - return ModifiersProvider.super.isFinalFlagSet() /* || isLeafAssumption() */; + return TypeAccess.super.isFinalFlagSet() /* || isLeafAssumption() */; } /** @@ -1681,6 +1673,11 @@ public Symbol getName() { return name; } + @Override + public Symbol getSymbolicName() { + return getName(); + } + public String getExternalName() { // Conversion from internal form. String externalName = MetaUtil.internalNameToJava(type.toString(), true, true); @@ -1799,11 +1796,6 @@ public int getStatus() { } } - @Override - public KlassRef getSuperClass() { - return getSuperKlass(); - } - @Override public byte getTagConstant() { return TagConstants.toTagConstant(getJavaKind()); @@ -1881,4 +1873,31 @@ public Assumption getRedefineAssumption() { } // endregion jdwp-specific + + // region TypeAccess impl + + @Override + public String getJavaName() { + return getExternalName(); + } + + @Override + public Klass getSuperClass() { + return getSuperKlass(); + } + + @Override + public Method lookupInterfaceMethod(Symbol methodName, Symbol methodSignature) { + if (this instanceof ObjectKlass) { + return ((ObjectKlass) this).resolveInterfaceMethod(methodName, methodSignature); + } + return null; + } + + @Override + public Method lookupInstanceMethod(Symbol methodName, Symbol methodSignature) { + return lookupMethod(methodName, methodSignature, LookupMode.INSTANCE_ONLY); + } + + // endregion TypeAccess impl } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LanguageAccess.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LanguageAccess.java index 8ca819d04470..0ec1df887228 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LanguageAccess.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LanguageAccess.java @@ -24,10 +24,10 @@ import com.oracle.truffle.espresso.EspressoLanguage; import com.oracle.truffle.espresso.EspressoOptions; +import com.oracle.truffle.espresso.classfile.JavaVersion; import com.oracle.truffle.espresso.classfile.descriptors.Names; import com.oracle.truffle.espresso.classfile.descriptors.Signatures; import com.oracle.truffle.espresso.classfile.descriptors.Types; -import com.oracle.truffle.espresso.classfile.JavaVersion; public interface LanguageAccess { EspressoLanguage getLanguage(); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedField.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedField.java index 7257f170edf1..ec28b84908f3 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedField.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedField.java @@ -22,21 +22,21 @@ */ package com.oracle.truffle.espresso.impl; +import static com.oracle.truffle.espresso.classfile.Constants.FIELD_ID_OBFUSCATE; +import static com.oracle.truffle.espresso.classfile.Constants.FIELD_ID_TYPE; + import com.oracle.truffle.api.staticobject.StaticProperty; +import com.oracle.truffle.espresso.classfile.JavaKind; +import com.oracle.truffle.espresso.classfile.ParserField; +import com.oracle.truffle.espresso.classfile.attributes.Attribute; import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; -import com.oracle.truffle.espresso.classfile.ParserField; -import com.oracle.truffle.espresso.classfile.attributes.Attribute; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; -import static com.oracle.truffle.espresso.classfile.Constants.FIELD_ID_OBFUSCATE; -import static com.oracle.truffle.espresso.classfile.Constants.FIELD_ID_TYPE; - final class LinkedField extends StaticProperty { enum IdMode { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedKlass.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedKlass.java index 575eddc5d2a2..2f3e823b246f 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedKlass.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedKlass.java @@ -22,21 +22,21 @@ */ package com.oracle.truffle.espresso.impl; +import static com.oracle.truffle.espresso.classfile.Constants.ACC_FINALIZER; + +import java.lang.reflect.Modifier; + import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.staticobject.StaticShape; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.ImmutableConstantPool; import com.oracle.truffle.espresso.classfile.ParserKlass; import com.oracle.truffle.espresso.classfile.ParserMethod; import com.oracle.truffle.espresso.classfile.attributes.Attribute; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject.StaticObjectFactory; -import java.lang.reflect.Modifier; - -import static com.oracle.truffle.espresso.classfile.Constants.ACC_FINALIZER; - // Structural shareable klass (superklass in superinterfaces resolved and linked) // contains shape, field locations. // Klass shape, vtable and field locations can be computed at the structural level. diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedKlassFieldLayout.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedKlassFieldLayout.java index 706a1984325b..c5b3f8afcb42 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedKlassFieldLayout.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedKlassFieldLayout.java @@ -33,13 +33,13 @@ import com.oracle.truffle.api.staticobject.StaticShape; import com.oracle.truffle.api.staticobject.StaticShape.Builder; import com.oracle.truffle.espresso.classfile.Constants; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.JavaVersion; import com.oracle.truffle.espresso.classfile.JavaVersion.VersionRange; import com.oracle.truffle.espresso.classfile.ParserField; import com.oracle.truffle.espresso.classfile.ParserKlass; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject.StaticObjectFactory; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedMethod.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedMethod.java index 9ab52d175ba2..208baa47cfe2 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedMethod.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedMethod.java @@ -22,11 +22,11 @@ */ package com.oracle.truffle.espresso.impl; +import com.oracle.truffle.espresso.classfile.ParserMethod; +import com.oracle.truffle.espresso.classfile.attributes.Attribute; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; -import com.oracle.truffle.espresso.classfile.ParserMethod; -import com.oracle.truffle.espresso.classfile.attributes.Attribute; public final class LinkedMethod { private final ParserMethod parserMethod; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LoadingConstraints.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LoadingConstraints.java index 27b95dd4cb6a..6c7c67f67056 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LoadingConstraints.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LoadingConstraints.java @@ -29,10 +29,10 @@ import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; -import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.classfile.perf.DebugCloseable; import com.oracle.truffle.espresso.classfile.perf.DebugTimer; +import com.oracle.truffle.espresso.meta.EspressoError; +import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Member.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Member.java index 19dabdd3af5f..2cfbb6c3b5d4 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Member.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Member.java @@ -22,14 +22,38 @@ */ package com.oracle.truffle.espresso.impl; +import com.oracle.truffle.api.dsl.Idempotent; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Descriptor; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; -import com.oracle.truffle.espresso.meta.ModifiersProvider; +import com.oracle.truffle.espresso.constantpool.Resolution; +import com.oracle.truffle.espresso.shared.meta.MemberAccess; -public abstract class Member implements ModifiersProvider { +public abstract class Member implements MemberAccess { public abstract Symbol getName(); + @Override + public final Symbol getSymbolicName() { + return getName(); + } + public abstract ObjectKlass getDeclaringKlass(); + + @Override + public final ObjectKlass getDeclaringClass() { + return getDeclaringKlass(); + } + + @Override + public final void accessChecks(Klass accessingClass, Klass holderClass) { + Resolution.memberDoAccessCheck(accessingClass, holderClass, this, holderClass.getMeta()); + } + + @Override + @Idempotent + // Re-implement here for indempotent annotation. Some of our nodes benefit from it. + public boolean isAbstract() { + return MemberAccess.super.isAbstract(); + } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Method.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Method.java index 7b2f071ead58..a36ba2d92d4b 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Method.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Method.java @@ -111,7 +111,6 @@ import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.meta.MetaUtil; -import com.oracle.truffle.espresso.meta.ModifiersProvider; import com.oracle.truffle.espresso.nodes.EspressoRootNode; import com.oracle.truffle.espresso.nodes.interop.AbstractLookupNode; import com.oracle.truffle.espresso.nodes.methodhandle.MHInvokeGenericNode.MethodHandleInvoker; @@ -120,11 +119,15 @@ import com.oracle.truffle.espresso.runtime.EspressoThreadLocalState; import com.oracle.truffle.espresso.runtime.MethodHandleIntrinsics; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; +import com.oracle.truffle.espresso.shared.meta.MethodAccess; +import com.oracle.truffle.espresso.shared.meta.ModifiersProvider; +import com.oracle.truffle.espresso.shared.resolver.ResolvedCall; import com.oracle.truffle.espresso.substitutions.JavaType; import com.oracle.truffle.espresso.vm.InterpreterToVM; import com.oracle.truffle.espresso.vm.VM.EspressoStackElement; -public final class Method extends Member implements TruffleObject, ContextAccess { +public final class Method extends Member implements TruffleObject, ContextAccess, + MethodAccess { public static final Method[] EMPTY_ARRAY = new Method[0]; public static final MethodVersion[] EMPTY_VERSION_ARRAY = new MethodVersion[0]; @@ -525,6 +528,19 @@ public Object invokeDirect(Object... args) { } } + public static Object call(ResolvedCall resolved, Object... args) { + return switch (resolved.getCallKind()) { + case STATIC -> + resolved.getResolvedMethod().invokeDirectStatic(args); + case DIRECT -> + resolved.getResolvedMethod().invokeDirect(args); + case VTABLE_LOOKUP -> + resolved.getResolvedMethod().invokeDirectVirtual(args); + case ITABLE_LOOKUP -> + resolved.getResolvedMethod().invokeDirectInterface(args); + }; + } + @TruffleBoundary public Object invokeDirectStatic(Object... args) { EspressoThreadLocalState tls = getLanguage().getThreadLocalState(); @@ -1299,6 +1315,32 @@ public StaticObject apply(int j) { return instance; } + // region MethodAccess impl + + @Override + public Symbol getSymbolicSignature() { + return getRawSignature(); + } + + @Override + @Idempotent + // Re-implement here for indempotent annotation. Some of our nodes benefit from it. + public boolean isAbstract() { + return super.isAbstract(); + } + + @Override + public boolean shouldSkipLoadingConstraints() { + return isPolySignatureIntrinsic(); + } + + @Override + public void loadingConstraints(Klass accessingClass) { + checkLoadingConstraints(accessingClass.getDefiningClassLoader(), getDeclaringKlass().getDefiningClassLoader()); + } + + // endregion MethodAccess impl + private static final class Continuum { Continuum(LivenessAnalysis livenessAnalysis) { this.livenessAnalysis = livenessAnalysis; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ModuleTable.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ModuleTable.java index 6ab872303cc4..2c34d002a706 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ModuleTable.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ModuleTable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -20,47 +20,33 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ - package com.oracle.truffle.espresso.impl; -import java.util.ArrayList; import java.util.concurrent.locks.ReadWriteLock; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; +import com.oracle.truffle.espresso.classfile.tables.AbstractModuleTable; import com.oracle.truffle.espresso.jdwp.api.ModuleRef; -import com.oracle.truffle.espresso.runtime.EspressoContext; +import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; -public class ModuleTable extends EntryTable { - +public final class ModuleTable extends AbstractModuleTable { public ModuleTable(ReadWriteLock lock) { super(lock); } @Override - protected ModuleEntry createEntry(Symbol name, ClassRegistry registry) { - return new ModuleEntry(name, registry); - } - - public ModuleEntry createAndAddEntry(Symbol name, ClassRegistry registry, boolean isOpen, StaticObject module) { - ModuleEntry moduleEntry = createAndAddEntry(name, registry); - if (moduleEntry == null) { - return null; - } - moduleEntry.setModule(module); - moduleEntry.isOpen = isOpen; - return moduleEntry; + protected ModuleEntry createEntry(Symbol name, ModuleData data) { + return new ModuleEntry(name, data); } - public static class ModuleEntry extends EntryTable.NamedEntry implements ModuleRef { - - // TODO: module versions. - ModuleEntry(Symbol name, ClassRegistry data) { - super(name); - this.registry = data; + public static final class ModuleEntry extends AbstractModuleTable.AbstractModuleEntry implements ModuleRef { + ModuleEntry(Symbol name, ModuleData data) { + super(name, data); } + @Override public String jdwpName() { if (name == null) { // JDWP expects the unnamed module to return empty string @@ -70,99 +56,32 @@ public String jdwpName() { } } + @Override public Object classLoader() { - return registry.getClassLoader(); - } - - public static ModuleEntry createUnnamedModuleEntry(StaticObject module, ClassRegistry registry) { - ModuleEntry result = new ModuleEntry(null, registry); - result.setCanReadAllUnnamed(); - if (!StaticObject.isNull(module)) { - result.setModule(module); - } - result.isOpen = true; - return result; - } - - private final ClassRegistry registry; - private StaticObject module = StaticObject.NULL; - private boolean isOpen = false; - - private boolean canReadAllUnnamed = false; - - private ArrayList reads; - - public ClassRegistry registry() { - return registry; - } - - public void addReads(ModuleEntry from) { - if (!isNamed()) { - return; - } - synchronized (this) { - if (from == null) { - setCanReadAllUnnamed(); - return; - } - if (reads == null) { - reads = new ArrayList<>(); - } - if (!contains(from)) { - reads.add(from); - } + StaticObject module = module(); + if (module != null) { + assert StaticObject.notNull(module); + Meta meta = module.getKlass().getMeta(); + return meta.java_lang_Module_loader.getObject(module); + } else { + // this must be the early java.base module + assert name.equals(Name.java_base); + return StaticObject.NULL; } } - public boolean canRead(ModuleEntry m, EspressoContext context) { - if (!isNamed() || m.isJavaBase(context)) { - return true; - } - synchronized (this) { - if (!hasReads()) { - return false; - } else { - return contains(m); - } + public ClassRegistry registry(Meta meta) { + StaticObject module = module(); + ClassRegistries registries = meta.getContext().getRegistries(); + if (module != null) { + assert StaticObject.notNull(module); + StaticObject loader = meta.java_lang_Module_loader.getObject(module); + return registries.getClassRegistry(loader); + } else { + // this must be the early java.base module + assert name.equals(Name.java_base); + return registries.getBootClassRegistry(); } } - - private boolean contains(ModuleEntry from) { - return reads.contains(from); - } - - public void setModule(StaticObject module) { - assert this.module == StaticObject.NULL; - this.module = module; - } - - public StaticObject module() { - return module; - } - - public void setCanReadAllUnnamed() { - canReadAllUnnamed = true; - } - - public boolean canReadAllUnnamed() { - return canReadAllUnnamed; - } - - public boolean isOpen() { - return isOpen; - } - - public boolean isNamed() { - return getName() != null; - } - - public boolean isJavaBase(EspressoContext context) { - return this == context.getRegistries().getJavaBaseModule(); - } - - public boolean hasReads() { - return reads != null && !reads.isEmpty(); - } - } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ObjectKlass.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ObjectKlass.java index 18132ff8f0d1..c8308ee60b21 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ObjectKlass.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/ObjectKlass.java @@ -58,10 +58,11 @@ import com.oracle.truffle.espresso.analysis.hierarchy.ClassHierarchyOracle.ClassHierarchyAccessor; import com.oracle.truffle.espresso.analysis.hierarchy.SingleImplementor; import com.oracle.truffle.espresso.blocking.EspressoLock; -import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; -import com.oracle.truffle.espresso.classfile.bytecode.Bytecodes; import com.oracle.truffle.espresso.classfile.ConstantPool; -import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; +import com.oracle.truffle.espresso.classfile.ParserField; +import com.oracle.truffle.espresso.classfile.ParserKlass; +import com.oracle.truffle.espresso.classfile.ParserMethod; +import com.oracle.truffle.espresso.classfile.attributes.Attribute; import com.oracle.truffle.espresso.classfile.attributes.ConstantValueAttribute; import com.oracle.truffle.espresso.classfile.attributes.EnclosingMethodAttribute; import com.oracle.truffle.espresso.classfile.attributes.InnerClassesAttribute; @@ -72,25 +73,24 @@ import com.oracle.truffle.espresso.classfile.attributes.SignatureAttribute; import com.oracle.truffle.espresso.classfile.attributes.SourceDebugExtensionAttribute; import com.oracle.truffle.espresso.classfile.attributes.SourceFileAttribute; +import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; +import com.oracle.truffle.espresso.classfile.bytecode.Bytecodes; import com.oracle.truffle.espresso.classfile.descriptors.Names; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; +import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; import com.oracle.truffle.espresso.impl.ModuleTable.ModuleEntry; import com.oracle.truffle.espresso.impl.PackageTable.PackageEntry; import com.oracle.truffle.espresso.jdwp.api.Ids; import com.oracle.truffle.espresso.jdwp.api.MethodRef; import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.meta.Meta; -import com.oracle.truffle.espresso.classfile.ParserField; -import com.oracle.truffle.espresso.classfile.ParserKlass; -import com.oracle.truffle.espresso.classfile.ParserMethod; import com.oracle.truffle.espresso.redefinition.ChangePacket; import com.oracle.truffle.espresso.redefinition.ClassRedefinition; import com.oracle.truffle.espresso.redefinition.DetectedChange; -import com.oracle.truffle.espresso.classfile.attributes.Attribute; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.EspressoException; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; @@ -133,6 +133,9 @@ public final class ObjectKlass extends Klass { @CompilationFinal // private volatile int initState = LOADED; + @CompilationFinal // + private EspressoException linkError; + @CompilationFinal volatile KlassVersion klassVersion; // instance and hidden fields declared in this class and in its super classes @@ -156,12 +159,15 @@ public final class ObjectKlass extends Klass { public static final int LOADED = 0; public static final int LINKING = 1; - public static final int PREPARED = 2; - public static final int LINKED = 3; + public static final int VERIFYING = 2; + public static final int FAILED_LINK = 3; + public static final int VERIFIED = 4; + public static final int PREPARED = 5; + public static final int LINKED = 6; + public static final int INITIALIZING = 7; // Can be erroneous only if initialization triggered ! - public static final int ERRONEOUS = 4; - public static final int INITIALIZING = 5; - public static final int INITIALIZED = 6; + public static final int ERRONEOUS = 8; + public static final int INITIALIZED = 9; private final StaticObject definingClassLoader; @@ -187,7 +193,7 @@ public ObjectKlass(EspressoContext context, LinkedKlass linkedKlass, ObjectKlass this.enclosingMethod = (EnclosingMethodAttribute) linkedKlass.getAttribute(EnclosingMethodAttribute.NAME); this.klassVersion = new KlassVersion(pool, linkedKlass, superKlass, superInterfaces); - Field[] skFieldTable = superKlass != null ? superKlass.getInitialFieldTable() : new Field[0]; + Field[] skFieldTable = superKlass != null ? superKlass.getInitialFieldTable() : Field.EMPTY_ARRAY; LinkedField[] lkInstanceFields = linkedKlass.getInstanceFields(); LinkedField[] lkStaticFields = linkedKlass.getStaticFields(); @@ -378,7 +384,7 @@ boolean isInitializingOrInitializedImpl() { * case, if the state is INITIALIZING we cannot really check the lock because an object * might have been leaked to another thread by the clinit. */ - return initState >= ERRONEOUS; + return initState >= INITIALIZING; } boolean isInitializedImpl() { @@ -418,6 +424,12 @@ private void actualInit() { initState = INITIALIZING; getContext().getLogger().log(Level.FINEST, "Initializing: {0}", this.getNameAsString()); + for (Field f : getInitialStaticFields()) { + if (!f.isRemoved()) { + initField(f); + } + } + var tls = getContext().getLanguage().getThreadLocalState(); tls.blockContinuationSuspension(); try { @@ -477,11 +489,6 @@ private void prepare() { try { if (!isPrepared()) { checkLoadingConstraints(); - for (Field f : getInitialStaticFields()) { - if (!f.isRemoved()) { - initField(f); - } - } initState = PREPARED; if (getContext().isMainThreadCreated()) { if (getContext().shouldReportVMEvents()) { @@ -593,7 +600,7 @@ private void checkLoadingConstraints() { @Override public void ensureLinked() { if (!isLinked()) { - checkErroneousVerification(); + checkErroneousLink(); if (CompilerDirectives.isCompilationConstant(this)) { CompilerDirectives.transferToInterpreterAndInvalidate(); } @@ -607,20 +614,30 @@ private void doLink() { try { if (!isLinkingOrLinked()) { initState = LINKING; - if (getSuperKlass() != null) { - getSuperKlass().ensureLinked(); - } - for (ObjectKlass interf : getSuperInterfaces()) { - interf.ensureLinked(); + try { + if (getSuperKlass() != null) { + getSuperKlass().ensureLinked(); + } + for (ObjectKlass interf : getSuperInterfaces()) { + interf.ensureLinked(); + } + } catch (EspressoException e) { + setErroneousLink(e); + throw e; } - prepare(); verify(); + try { + prepare(); + } catch (EspressoException e) { + setErroneousLink(e); + throw e; + } initState = LINKED; } } finally { getInitLock().unlock(); } - checkErroneousVerification(); + checkErroneousLink(); } void initializeImpl() { @@ -632,7 +649,7 @@ void initializeImpl() { @HostCompilerDirectives.InliningCutoff private void doInitialize() { - checkErroneousVerification(); + checkErroneousLink(); checkErroneousInitialization(); if (CompilerDirectives.isCompilationConstant(this)) { CompilerDirectives.transferToInterpreterAndInvalidate(); @@ -656,60 +673,45 @@ private void recursiveInitialize() { // region Verification - @CompilationFinal // - private volatile int verificationStatus = UNVERIFIED; - - @CompilationFinal // - private EspressoException verificationError = null; - - private static final int FAILED_VERIFICATION = -1; - private static final int UNVERIFIED = 0; - private static final int VERIFYING = 1; - private static final int VERIFIED = 2; - - private void setVerificationStatus(int status) { - verificationStatus = status; - } - private boolean isVerifyingOrVerified() { - return verificationStatus >= VERIFYING; + return initState >= VERIFYING; } boolean isVerified() { - return verificationStatus >= VERIFIED; + return initState >= VERIFIED; } - private void checkErroneousVerification() { - if (verificationStatus == FAILED_VERIFICATION) { - throw verificationError; + private void checkErroneousLink() { + if (initState == FAILED_LINK) { + throw linkError; } } - private void setErroneousVerification(EspressoException e) { - verificationStatus = FAILED_VERIFICATION; - verificationError = e; + private void setErroneousLink(EspressoException e) { + initState = FAILED_LINK; + linkError = e; } private void verify() { if (!isVerified()) { - checkErroneousVerification(); + checkErroneousLink(); getInitLock().lock(); try { if (!isVerifyingOrVerified()) { CompilerDirectives.transferToInterpreterAndInvalidate(); - setVerificationStatus(VERIFYING); + initState = VERIFYING; try { verifyImpl(); } catch (EspressoException e) { - setErroneousVerification(e); + setErroneousLink(e); throw e; } - setVerificationStatus(VERIFIED); + initState = VERIFIED; } } finally { getInitLock().unlock(); } - checkErroneousVerification(); + checkErroneousLink(); } } @@ -1090,7 +1092,7 @@ int findVirtualMethodIndex(Symbol methodName, Symbol signature, return -1; } - public void lookupVirtualMethodOverrides(Method current, Klass subKlass, List result) { + void lookupVirtualMethodOverrides(Method current, Klass subKlass, List result) { Symbol methodName = current.getName(); Symbol signature = current.getRawSignature(); for (Method.MethodVersion m : getVTable()) { @@ -1120,6 +1122,7 @@ public void lookupVirtualMethodOverrides(Method current, Klass subKlass, List methodName, Symbol signature) { assert isInterface(); /* @@ -1302,19 +1305,20 @@ public List> getNestedTypeNames() { private void initPackage(@JavaType(ClassLoader.class) StaticObject classLoader) { if (!Names.isUnnamedPackage(getRuntimePackage())) { ClassRegistry registry = getRegistries().getClassRegistry(classLoader); - packageEntry = registry.packages().lookup(getRuntimePackage()); + PackageEntry entry = registry.packages().lookup(getRuntimePackage()); // If the package name is not found in the loader's package // entry table, it is an indication that the package has not // been defined. Consider it defined within the unnamed module. - if (packageEntry == null) { + if (entry == null) { if (!getRegistries().javaBaseDefined()) { // Before java.base is defined during bootstrapping, define all packages in // the java.base module. - packageEntry = registry.packages().lookupOrCreate(getRuntimePackage(), getRegistries().getJavaBaseModule()); + entry = registry.packages().lookupOrCreate(getRuntimePackage(), getRegistries().getJavaBaseModule()); } else { - packageEntry = registry.packages().lookupOrCreate(getRuntimePackage(), registry.getUnnamedModule()); + entry = registry.packages().lookupOrCreate(getRuntimePackage(), registry.getUnnamedModule()); } } + packageEntry = entry; } } @@ -1323,10 +1327,13 @@ public ModuleEntry module() { if (!inUnnamedPackage()) { return packageEntry.module(); } + StaticObject classLoader; if (getHostClass() != null) { - return getRegistries().getClassRegistry(getHostClass().getDefiningClassLoader()).getUnnamedModule(); + classLoader = getHostClass().getDefiningClassLoader(); + } else { + classLoader = getDefiningClassLoader(); } - return getRegistries().getClassRegistry(getDefiningClassLoader()).getUnnamedModule(); + return getRegistries().getClassRegistry(classLoader).getUnnamedModule(); } @Override @@ -1770,7 +1777,7 @@ private KlassVersion(KlassVersion oldVersion, RuntimeConstantPool pool, LinkedKl ParserMethod parserMethod = removedMethod.getLinkedMethod().getParserMethod(); checkSuperMethods(superKlass, parserMethod.getFlags(), parserMethod.getName(), parserMethod.getSignature(), invalidatedClasses); removedMethod.getMethod().removedByRedefinition(); - getContext().getClassRedefinition().getController().fine( + ClassRedefinition.LOGGER.fine( () -> "Removed method " + removedMethod.getMethod().getDeclaringKlass().getName() + "." + removedMethod.getLinkedMethod().getName()); } @@ -1780,7 +1787,7 @@ private KlassVersion(KlassVersion oldVersion, RuntimeConstantPool pool, LinkedKl newDeclaredMethods.addLast(added); virtualMethodsModified |= isVirtual(addedMethod); checkSuperMethods(superKlass, addedMethod.getFlags(), addedMethod.getName(), addedMethod.getSignature(), invalidatedClasses); - getContext().getClassRedefinition().getController().fine(() -> "Added method " + added.getMethod().getDeclaringKlass().getName() + "." + added.getName()); + ClassRedefinition.LOGGER.fine(() -> "Added method " + added.getMethod().getDeclaringKlass().getName() + "." + added.getName()); } if (virtualMethodsModified) { @@ -1797,7 +1804,7 @@ private KlassVersion(KlassVersion oldVersion, RuntimeConstantPool pool, LinkedKl if (changedMethodBodies.containsKey(declMethod)) { ParserMethod newMethod = changedMethodBodies.get(declMethod); Method.SharedRedefinitionContent redefineContent = declMethod.redefine(this, newMethod, packet.parserKlass, ids); - getContext().getClassRedefinition().getController().fine(() -> "Redefining method " + declMethod.getDeclaringKlass().getName() + "." + declMethod.getName()); + ClassRedefinition.LOGGER.fine(() -> "Redefining method " + declMethod.getDeclaringKlass().getName() + "." + declMethod.getName()); methods[i] = redefineContent.getMethodVersion(); int flags = newMethod.getFlags(); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/PackageTable.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/PackageTable.java index de008afdbc2d..297cc3dda6c3 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/PackageTable.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/PackageTable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -20,102 +20,29 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ - package com.oracle.truffle.espresso.impl; -import java.util.ArrayList; import java.util.concurrent.locks.ReadWriteLock; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; +import com.oracle.truffle.espresso.classfile.tables.AbstractPackageTable; import com.oracle.truffle.espresso.impl.ModuleTable.ModuleEntry; +import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; -public class PackageTable extends EntryTable { +public final class PackageTable extends AbstractPackageTable { public PackageTable(ReadWriteLock lock) { super(lock); } @Override - protected PackageEntry createEntry(Symbol name, ModuleEntry appendix) { - return new PackageEntry(name, appendix); + protected PackageEntry createEntry(Symbol name, ModuleEntry data) { + return new PackageEntry(name, data); } - public static class PackageEntry extends EntryTable.NamedEntry { - - @Override - public Symbol getName() { - return name; - } - + public static final class PackageEntry extends AbstractPackageTable.AbstractPackageEntry { public PackageEntry(Symbol name, ModuleEntry module) { - super(name); - this.module = module; - } - - private final ModuleEntry module; - private ArrayList exports = null; - private boolean isUnqualifiedExported = false; - private boolean isExportedAllUnnamed = false; - - public void addExports(ModuleEntry m) { - if (isUnqualifiedExported()) { - return; - } - synchronized (this) { - if (m == null) { - setUnqualifiedExports(); - } - if (exports == null) { - exports = new ArrayList<>(); - } - if (!contains(m)) { - exports.add(m); - } - } - } - - public boolean isQualifiedExportTo(ModuleEntry m) { - if (isExportedAllUnnamed() && !m.isNamed()) { - return true; - } - if (isUnqualifiedExported() || exports == null) { - return false; - } - return contains(m); - } - - public boolean isUnqualifiedExported() { - return module().isOpen() || isUnqualifiedExported; - } - - public void setUnqualifiedExports() { - if (isUnqualifiedExported()) { - return; - } - isUnqualifiedExported = true; - isExportedAllUnnamed = true; - exports = null; - } - - public boolean isExportedAllUnnamed() { - return module().isOpen() || isExportedAllUnnamed; - } - - public void setExportedAllUnnamed() { - if (isExportedAllUnnamed()) { - return; - } - synchronized (this) { - isExportedAllUnnamed = true; - } - } - - public boolean contains(ModuleEntry m) { - return exports.contains(m); - } - - public ModuleEntry module() { - return module; + super(name, module); } } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/PrimitiveKlass.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/PrimitiveKlass.java index acce598fcbb2..d42699688235 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/PrimitiveKlass.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/PrimitiveKlass.java @@ -27,6 +27,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; @@ -34,7 +35,6 @@ import com.oracle.truffle.espresso.impl.ObjectKlass.KlassVersion; import com.oracle.truffle.espresso.impl.PackageTable.PackageEntry; import com.oracle.truffle.espresso.jdwp.api.MethodRef; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.GuestAllocator; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/generics/reflectiveObjects/GenericArrayTypeImpl.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/generics/reflectiveObjects/GenericArrayTypeImpl.java index 58ff61d5c3fc..cb63660a5f11 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/generics/reflectiveObjects/GenericArrayTypeImpl.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/generics/reflectiveObjects/GenericArrayTypeImpl.java @@ -23,12 +23,12 @@ package com.oracle.truffle.espresso.impl.generics.reflectiveObjects; +import java.util.Objects; + import com.oracle.truffle.espresso.impl.EspressoType; import com.oracle.truffle.espresso.impl.GenericArrayEspressoType; import com.oracle.truffle.espresso.impl.Klass; -import java.util.Objects; - /** * Implementation of GenericArrayType interface. */ diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/generics/visitor/TypeTreeVisitor.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/generics/visitor/TypeTreeVisitor.java index d3c5c07ceba4..4a9d43763f61 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/generics/visitor/TypeTreeVisitor.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/generics/visitor/TypeTreeVisitor.java @@ -35,8 +35,8 @@ import com.oracle.truffle.espresso.impl.generics.tree.LongSignature; import com.oracle.truffle.espresso.impl.generics.tree.ShortSignature; import com.oracle.truffle.espresso.impl.generics.tree.SimpleClassTypeSignature; -import com.oracle.truffle.espresso.impl.generics.tree.VoidDescriptor; import com.oracle.truffle.espresso.impl.generics.tree.TypeVariableSignature; +import com.oracle.truffle.espresso.impl.generics.tree.VoidDescriptor; /** * Visit a TypeTree and produce a result of type T. diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/jni/JniEnv.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/jni/JniEnv.java index 76d835909ecb..c85f2027c17f 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/jni/JniEnv.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/jni/JniEnv.java @@ -2954,7 +2954,8 @@ public boolean IsInstanceOf(@JavaType(Object.class) StaticObject obj, @JavaType( if (!meta.java_lang_Class.isAssignableFrom(clazz.getKlass())) { throw meta.throwExceptionWithMessage(meta.java_lang_IllegalArgumentException, "Invalid Class"); } - return clazz.getMirrorKlass(getMeta()).module().module(); + StaticObject module = clazz.getMirrorKlass(getMeta()).module().module(); + return module == null ? StaticObject.NULL : module; } @JniImpl diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/DiffVersionLoadHelper.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/DiffVersionLoadHelper.java index 7bd8cbbb516f..1517fed19a7b 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/DiffVersionLoadHelper.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/DiffVersionLoadHelper.java @@ -23,6 +23,7 @@ package com.oracle.truffle.espresso.meta; +import com.oracle.truffle.espresso.classfile.JavaVersion.VersionRange; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; @@ -30,7 +31,6 @@ import com.oracle.truffle.espresso.impl.Field; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.impl.ObjectKlass; -import com.oracle.truffle.espresso.classfile.JavaVersion.VersionRange; final class DiffVersionLoadHelper { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/InteropKlassesDispatch.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/InteropKlassesDispatch.java index 2eae4f1fb91c..9fe9f1375cc7 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/InteropKlassesDispatch.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/InteropKlassesDispatch.java @@ -23,13 +23,13 @@ package com.oracle.truffle.espresso.meta; -import com.oracle.truffle.espresso.runtime.dispatch.staticobject.ByteBufferInterop; import org.graalvm.collections.Pair; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.runtime.dispatch.staticobject.BaseInterop; +import com.oracle.truffle.espresso.runtime.dispatch.staticobject.ByteBufferInterop; import com.oracle.truffle.espresso.runtime.dispatch.staticobject.EspressoInterop; import com.oracle.truffle.espresso.runtime.dispatch.staticobject.ForeignExceptionInterop; import com.oracle.truffle.espresso.runtime.dispatch.staticobject.InterruptedExceptionInterop; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/Meta.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/Meta.java index 4dd86e835092..d7c91b1bb509 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/Meta.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/Meta.java @@ -45,6 +45,8 @@ import com.oracle.truffle.api.HostCompilerDirectives; import com.oracle.truffle.espresso.EspressoOptions; import com.oracle.truffle.espresso.EspressoOptions.SpecComplianceMode; +import com.oracle.truffle.espresso.classfile.JavaKind; +import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; @@ -59,7 +61,6 @@ import com.oracle.truffle.espresso.impl.ModuleTable; import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.impl.PrimitiveKlass; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.EspressoException; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; @@ -2559,12 +2560,18 @@ public static String toHostStringStatic(StaticObject str) { return str.getKlass().getMeta().toHostString(str); } - @TruffleBoundary - public StaticObject toGuestString(Symbol hostString) { - if (hostString == null) { + public ByteSequence toByteSequence(@JavaType(String.class) StaticObject str) { + if (StaticObject.isNull(str)) { + return null; + } + return ByteSequence.create(toHostString(str)); + } + + public StaticObject toGuestString(Symbol symbol) { + if (symbol == null) { return StaticObject.NULL; } - return toGuestString(hostString.toString()); + return toGuestString(symbol.toString()); } public static boolean isString(Object string) { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/BytecodeNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/BytecodeNode.java index b704ed48bf50..b320539dff9e 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/BytecodeNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/BytecodeNode.java @@ -275,7 +275,6 @@ import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Supplier; import com.oracle.truffle.api.Assumption; import com.oracle.truffle.api.CompilerAsserts; @@ -324,12 +323,12 @@ import com.oracle.truffle.espresso.classfile.constantpool.MethodRefConstant; import com.oracle.truffle.espresso.classfile.constantpool.MethodTypeConstant; import com.oracle.truffle.espresso.classfile.constantpool.PoolConstant; +import com.oracle.truffle.espresso.classfile.constantpool.Resolvable; import com.oracle.truffle.espresso.classfile.constantpool.StringConstant; import com.oracle.truffle.espresso.classfile.descriptors.Signatures; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.perf.DebugCounter; -import com.oracle.truffle.espresso.constantpool.CallSiteLink; import com.oracle.truffle.espresso.constantpool.Resolution; import com.oracle.truffle.espresso.constantpool.ResolvedDynamicConstant; import com.oracle.truffle.espresso.constantpool.ResolvedWithInvokerClassMethodRefConstant; @@ -376,16 +375,17 @@ import com.oracle.truffle.espresso.nodes.quick.invoke.InvokeStaticQuickNode; import com.oracle.truffle.espresso.nodes.quick.invoke.InvokeVirtualQuickNode; import com.oracle.truffle.espresso.nodes.quick.invoke.inline.InlinedMethodNode; -import com.oracle.truffle.espresso.resolver.CallKind; -import com.oracle.truffle.espresso.resolver.CallSiteType; -import com.oracle.truffle.espresso.resolver.FieldAccessType; -import com.oracle.truffle.espresso.resolver.LinkResolver; -import com.oracle.truffle.espresso.resolver.ResolvedCall; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.EspressoException; import com.oracle.truffle.espresso.runtime.EspressoExitException; +import com.oracle.truffle.espresso.runtime.EspressoLinkResolver; import com.oracle.truffle.espresso.runtime.GuestAllocator; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; +import com.oracle.truffle.espresso.shared.resolver.CallKind; +import com.oracle.truffle.espresso.shared.resolver.CallSiteType; +import com.oracle.truffle.espresso.shared.resolver.FieldAccessType; +import com.oracle.truffle.espresso.shared.resolver.ResolvedCall; +import com.oracle.truffle.espresso.substitutions.Target_java_lang_invoke_MethodHandleNatives.SiteTypes; import com.oracle.truffle.espresso.vm.InterpreterToVM; import com.oracle.truffle.espresso.vm.continuation.HostFrameRecord; import com.oracle.truffle.espresso.vm.continuation.UnwindContinuationException; @@ -574,7 +574,7 @@ public void createContinuableNode(int bci, int top) { return; } else { InstrumentationSupport instrument = instrumentation; - int statementIndex = instrument == null ? 0 : instrument.getStatementIndexAfterJump(0, 0, bs.endBCI()); + int statementIndex = instrument == null ? InstrumentationSupport.NO_STATEMENT : instrument.getStartStatementIndex(bci); quickenInvoke(top, bci, opcode, statementIndex); // continue loop, will execute at most once more. } @@ -598,7 +598,7 @@ public Object resumeContinuation(VirtualFrame frame, int bci, int top) { // set up local state. InstrumentationSupport instrument = instrumentation; - int statementIndex = instrument == null ? 0 : instrument.getStatementIndexAfterJump(0, 0, bs.endBCI()); + int statementIndex = instrument == null ? InstrumentationSupport.NO_STATEMENT : instrument.getStartStatementIndex(bci); assert bs.opcode(bci) == QUICK && nodes[bs.readCPI2(bci)] instanceof InvokeContinuableNode; return executeBodyFromBCI(frame, bci, top, statementIndex, true, true); @@ -631,11 +631,11 @@ private static final class EspressoOSRInterpreterState { // does not guarantee this. final int top; // The statement index of the next instruction (if instrumentation is enabled). - final int nextStatementIndex; + final int statementIndex; - EspressoOSRInterpreterState(int top, int nextStatementIndex) { + EspressoOSRInterpreterState(int top, int statementIndex) { this.top = top; - this.nextStatementIndex = nextStatementIndex; + this.statementIndex = statementIndex; } } @@ -675,7 +675,7 @@ public Object getYieldValue() { @Override public Object executeOSR(VirtualFrame osrFrame, int target, Object interpreterState) { EspressoOSRInterpreterState state = (EspressoOSRInterpreterState) interpreterState; - return executeBodyFromBCI(osrFrame, target, state.top, state.nextStatementIndex, true, false); + return executeBodyFromBCI(osrFrame, target, state.top, state.statementIndex, true, false); } @Override @@ -721,7 +721,8 @@ public Object execute(VirtualFrame frame) { getLanguage().getThreadLocalState().blockContinuationSuspension(); } try { - return executeBodyFromBCI(frame, 0, startTop, 0, false, false); + int statementIndex = instrumentation == null ? InstrumentationSupport.NO_STATEMENT : instrumentation.getStartStatementIndex(0); + return executeBodyFromBCI(frame, 0, startTop, statementIndex, false, false); } finally { if (methodVersion.hasJsr()) { getLanguage().getThreadLocalState().unblockContinuationSuspension(); @@ -736,9 +737,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop boolean isOSR, boolean resumeContinuation) { CompilerAsserts.partialEvaluationConstant(startBCI); final InstrumentationSupport instrument = this.instrumentation; - int statementIndex = InstrumentationSupport.NO_STATEMENT; - int nextStatementIndex = startStatementIndex; - boolean skipEntryInstrumentation = isOSR; + int statementIndex = startStatementIndex; boolean skipLivenessActions = instrument != null; boolean shouldResumeContinuation = resumeContinuation; @@ -753,9 +752,11 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop if (instrument != null) { if (resumeContinuation) { - instrument.notifyResume(frame, this, startStatementIndex); - } else if (!skipEntryInstrumentation) { + instrument.notifyResume(frame, this); + instrument.notifyStatementResume(frame, statementIndex); + } else if (!isOSR) { instrument.notifyEntry(frame, this); + instrument.notifyStatementEnter(frame, statementIndex); } } @@ -771,11 +772,11 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop try { CompilerAsserts.partialEvaluationConstant(top); CompilerAsserts.partialEvaluationConstant(curBCI); - CompilerAsserts.partialEvaluationConstant(skipEntryInstrumentation); CompilerAsserts.partialEvaluationConstant(curOpcode); CompilerAsserts.partialEvaluationConstant(statementIndex); - CompilerAsserts.partialEvaluationConstant(nextStatementIndex); + assert statementIndex == InstrumentationSupport.NO_STATEMENT || curBCI == returnValueBci || curBCI == throwValueBci || + statementIndex == instrumentation.hookBCIToNodeIndex.lookupBucket(curBCI); if (instrument != null || Bytecodes.canTrap(curOpcode)) { /* @@ -784,13 +785,6 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop */ setBCI(frame, curBCI); } - if (instrument != null) { - if (!skipEntryInstrumentation) { - instrument.notifyStatement(frame, statementIndex, nextStatementIndex); - } - skipEntryInstrumentation = false; - statementIndex = nextStatementIndex; - } // @formatter:off switch (curOpcode) { @@ -1064,7 +1058,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop if (takeBranchPrimitive1(popInt(frame, top - 1), curOpcode)) { int targetBCI = bs.readBranchDest2(curBCI); top += Bytecodes.stackEffectOf(IFLE); - nextStatementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; } @@ -1078,7 +1072,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop case IF_ICMPLE: if (takeBranchPrimitive2(popInt(frame, top - 1), popInt(frame, top - 2), curOpcode)) { top += Bytecodes.stackEffectOf(IF_ICMPLE); - nextStatementIndex = beforeJumpChecks(frame, curBCI, bs.readBranchDest2(curBCI), top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, bs.readBranchDest2(curBCI), top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = bs.readBranchDest2(curBCI); continue loop; } @@ -1089,7 +1083,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop if (takeBranchRef2(popObject(frame, top - 1), popObject(frame, top - 2), curOpcode)) { int targetBCI = bs.readBranchDest2(curBCI); top += Bytecodes.stackEffectOf(IF_ACMPNE); - nextStatementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; } @@ -1100,7 +1094,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop if (takeBranchRef1(popObject(frame, top - 1), curOpcode)) { int targetBCI = bs.readBranchDest2(curBCI); top += Bytecodes.stackEffectOf(IFNONNULL); - nextStatementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; } @@ -1108,13 +1102,13 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop case GOTO: { int targetBCI = bs.readBranchDest2(curBCI); - nextStatementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; } case GOTO_W: { int targetBCI = bs.readBranchDest4(curBCI); - nextStatementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; } @@ -1122,7 +1116,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop putReturnAddress(frame, top, bs.nextBCI(curBCI)); int targetBCI = bs.readBranchDest2(curBCI); top += Bytecodes.stackEffectOf(JSR); - nextStatementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; } @@ -1130,7 +1124,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop putReturnAddress(frame, top, bs.nextBCI(curBCI)); int targetBCI = bs.readBranchDest4(curBCI); top += Bytecodes.stackEffectOf(JSR_W); - nextStatementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; } @@ -1181,7 +1175,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop if (jsr == targetBCI) { CompilerAsserts.partialEvaluationConstant(jsr); top += Bytecodes.stackEffectOf(RET); - nextStatementIndex = beforeJumpChecks(frame, curBCI, jsr, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, jsr, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = jsr; continue loop; } @@ -1209,7 +1203,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop VolatileArrayAccess.volatileWrite(jsrBci, retOpBci, updatedTargets); }); top += Bytecodes.stackEffectOf(RET); - nextStatementIndex = beforeJumpChecks(frame, retOpBci, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, retOpBci, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; } @@ -1230,7 +1224,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop targetBCI = switchHelper.defaultTarget(bs, curBCI); } top += Bytecodes.stackEffectOf(TABLESWITCH); - nextStatementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; } @@ -1242,7 +1236,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop // Key found. int targetBCI = switchHelper.targetAt(bs, curBCI, i - low); top += Bytecodes.stackEffectOf(TABLESWITCH); - nextStatementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; } @@ -1251,7 +1245,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop // Key not found. int targetBCI = switchHelper.defaultTarget(bs, curBCI); top += Bytecodes.stackEffectOf(TABLESWITCH); - nextStatementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; } @@ -1271,7 +1265,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop // Key found. int targetBCI = curBCI + switchHelper.offsetAt(bs, curBCI, mid); top += Bytecodes.stackEffectOf(LOOKUPSWITCH); - nextStatementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; } @@ -1280,7 +1274,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop // Key not found. int targetBCI = switchHelper.defaultTarget(bs, curBCI); top += Bytecodes.stackEffectOf(LOOKUPSWITCH); - nextStatementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; } @@ -1441,7 +1435,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop if (jsr == targetBCI) { CompilerAsserts.partialEvaluationConstant(jsr); top += Bytecodes.stackEffectOf(RET); - nextStatementIndex = beforeJumpChecks(frame, curBCI, jsr, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, jsr, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = jsr; continue loop; } @@ -1469,7 +1463,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop VolatileArrayAccess.volatileWrite(jsrBci, retOpBci, updatedTargets); }); top += Bytecodes.stackEffectOf(RET); - nextStatementIndex = beforeJumpChecks(frame, retOpBci, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, retOpBci, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; } @@ -1583,7 +1577,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop putObject(frame, top, wrappedStackOverflowError.getGuestException()); top++; int targetBCI = stackOverflowErrorInfo[i + 2]; - nextStatementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; // skip bs.next() } @@ -1651,7 +1645,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop putObject(frame, top, wrappedException.getGuestException()); top++; int targetBCI = handler.getHandlerBCI(); - nextStatementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); + statementIndex = beforeJumpChecks(frame, curBCI, targetBCI, top, statementIndex, instrument, loopCount, skipLivenessActions); curBCI = targetBCI; continue loop; // skip bs.next() } else { @@ -1694,7 +1688,11 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop int targetBCI = curBCI + Bytecodes.lengthOf(curOpcode); livenessAnalysis.performOnEdge(frame, curBCI, targetBCI, skipLivenessActions); if (instrument != null) { - nextStatementIndex = instrument.getNextStatementIndex(statementIndex, targetBCI); + int nextStatementIndex = instrument.getNextStatementIndex(statementIndex, targetBCI); + if (nextStatementIndex != statementIndex) { + instrument.notifyStatementChange(frame, statementIndex, nextStatementIndex, targetBCI); + statementIndex = nextStatementIndex; + } } top += Bytecodes.stackEffectOf(curOpcode); curBCI = targetBCI; @@ -1754,25 +1752,26 @@ private StaticObject newReferenceArray(Klass componentType, int length) { private BaseQuickNode getBaseQuickNode(int curBCI, int top, int statementIndex, BaseQuickNode quickNode) { // block while class redefinition is ongoing getMethod().getContext().getClassRedefinition().check(); - BaseQuickNode result = quickNode; - result = atomic(() -> { - // re-check if node was already replaced by another thread - if (quickNode != nodes[readCPI(curBCI)]) { + // re-check if node was already replaced by another thread + if (quickNode != nodes[readCPI(curBCI)]) { + // another thread beat us + return nodes[readCPI(curBCI)]; + } + BytecodeStream original = new BytecodeStream(getMethodVersion().getCodeAttribute().getOriginalCode()); + char originalCpi = original.readCPI(curBCI); + int originalOpcode = original.currentBC(curBCI); + ResolvedInvoke resolvedInvoke = reResolvedInvoke(originalOpcode, originalCpi); + return atomic(() -> { + char cpi = readCPI(curBCI); + if (quickNode != nodes[cpi]) { // another thread beat us - return nodes[readCPI(curBCI)]; + return nodes[cpi]; } else { - // other threads might still have beat us but if - // so, the resolution failed and so will we below - BytecodeStream original = new BytecodeStream(getMethodVersion().getCodeAttribute().getOriginalCode()); - char cpi = original.readCPI(curBCI); - int nodeOpcode = original.currentBC(curBCI); - Method resolutionSeed = resolveMethodNoCache(nodeOpcode, cpi); - BaseQuickNode toInsert = insert(dispatchQuickened(top, curBCI, cpi, nodeOpcode, statementIndex, resolutionSeed, getMethod().getContext().getEspressoEnv().bytecodeLevelInlining)); - nodes[readCPI(curBCI)] = toInsert; - return toInsert; + BaseQuickNode newNode = insert(dispatchQuickened(top, curBCI, originalOpcode, statementIndex, resolvedInvoke, getMethod().getContext().getEspressoEnv().bytecodeLevelInlining)); + nodes[cpi] = newNode; + return newNode; } }); - return result; } private Object getReturnValueAsObject(VirtualFrame frame, int top) { @@ -2019,7 +2018,10 @@ private void referenceArrayStore(VirtualFrame frame, int top, int index, StaticO private int beforeJumpChecks(VirtualFrame frame, int curBCI, int targetBCI, int top, int statementIndex, InstrumentationSupport instrument, Counter loopCount, boolean skipLivenessActions) { CompilerAsserts.partialEvaluationConstant(targetBCI); - int nextStatementIndex = (instrument == null) ? 0 : instrument.getStatementIndexAfterJump(statementIndex, curBCI, targetBCI); + int nextStatementIndex = (instrument == null) ? InstrumentationSupport.NO_STATEMENT : instrument.getStatementIndexAfterJump(statementIndex, curBCI, targetBCI); + if (nextStatementIndex != statementIndex) { + instrument.notifyStatementChange(frame, statementIndex, nextStatementIndex, targetBCI); + } if (targetBCI <= curBCI) { TruffleSafepoint.poll(this); if (CompilerDirectives.hasNextTier() && ++loopCount.value >= REPORT_LOOP_STRIDE) { @@ -2027,24 +2029,10 @@ private int beforeJumpChecks(VirtualFrame frame, int curBCI, int targetBCI, int loopCount.value = 0; } if (CompilerDirectives.inInterpreter() && BytecodeOSRNode.pollOSRBackEdge(this)) { - Runnable beforeTransfer; - if (instrument != null && statementIndex != nextStatementIndex) { - // Since multiple code locations can jump back to the same bci, we can't - // compile a "statement exit" event inside the OSR code (we don't know - // which statement we came from). Instead, we notify statement exit and entry - // before calling OSR code, and skip the first notify event inside OSR code. - beforeTransfer = () -> { - instrument.notifyStatement(frame, statementIndex, nextStatementIndex); - }; - } else { - // If there's no instrumentation, or we're jumping within the same statement, - // don't send an instrumentation event. - beforeTransfer = null; - } livenessAnalysis.catchUpOSR(frame, targetBCI, skipLivenessActions); Object osrResult; try { - osrResult = BytecodeOSRNode.tryOSR(this, targetBCI, new EspressoOSRInterpreterState(top, nextStatementIndex), beforeTransfer, frame); + osrResult = BytecodeOSRNode.tryOSR(this, targetBCI, new EspressoOSRInterpreterState(top, nextStatementIndex), null, frame); } catch (Throwable any) { // Has already been guest-handled in OSR. Shortcut out of the method. throw new EspressoOSRReturnException(any); @@ -2192,20 +2180,52 @@ private BaseQuickNode injectQuick(int curBCI, BaseQuickNode quick, int opcode) { return quick; } - private BaseQuickNode tryPatchQuick(int curBCI, Supplier newQuickNode) { + @FunctionalInterface + private interface QuickNodeFactory { + BaseQuickNode get(T t); + } + + @FunctionalInterface + private interface QuickNodeResolver { + T get(char cpi); + } + + private BaseQuickNode tryPatchQuick(int curBCI, QuickNodeResolver resolver, QuickNodeFactory newQuickNode) { + Object found = atomic(() -> { + if (bs.currentVolatileBC(curBCI) == QUICK) { + return nodes[readCPI(curBCI)]; + } else { + return readCPI(curBCI); + } + }); + if (found instanceof BaseQuickNode) { + return (BaseQuickNode) found; + } + char cpi = (char) found; + // Perform resolution outside the lock: it can call arbitrary guest code. + T resolved = resolver.get(cpi); return atomic(() -> { if (bs.currentVolatileBC(curBCI) == QUICK) { return nodes[readCPI(curBCI)]; } else { - return injectQuick(curBCI, newQuickNode.get(), QUICK); + return injectQuick(curBCI, newQuickNode.get(resolved), QUICK); } }); } + @FunctionalInterface + private interface QuickNodeSupplier { + BaseQuickNode get(); + } + + private BaseQuickNode tryPatchQuick(int curBCI, QuickNodeSupplier newQuickNode) { + return tryPatchQuick(curBCI, cpi -> null, unused -> newQuickNode.get()); + } + private int quickenCheckCast(VirtualFrame frame, int top, int curBCI, int opcode) { CompilerAsserts.neverPartOfCompilation(); assert opcode == CHECKCAST; - BaseQuickNode quick = tryPatchQuick(curBCI, () -> new CheckCastQuickNode(resolveType(CHECKCAST, readCPI(curBCI)), top, curBCI)); + BaseQuickNode quick = tryPatchQuick(curBCI, cpi -> resolveType(CHECKCAST, cpi), k -> new CheckCastQuickNode(k, top, curBCI)); quick.execute(frame, false); assert Bytecodes.stackEffectOf(opcode) == 0; return 0; // Bytecodes.stackEffectOf(opcode); @@ -2214,7 +2234,7 @@ private int quickenCheckCast(VirtualFrame frame, int top, int curBCI, int opcode private int quickenInstanceOf(VirtualFrame frame, int top, int curBCI, int opcode) { CompilerAsserts.neverPartOfCompilation(); assert opcode == INSTANCEOF; - BaseQuickNode quick = tryPatchQuick(curBCI, () -> new InstanceOfQuickNode(resolveType(INSTANCEOF, readCPI(curBCI)), top, curBCI)); + BaseQuickNode quick = tryPatchQuick(curBCI, cpi -> resolveType(INSTANCEOF, cpi), k -> new InstanceOfQuickNode(k, top, curBCI)); quick.execute(frame, false); assert Bytecodes.stackEffectOf(opcode) == 0; return 0; // Bytecodes.stackEffectOf(opcode); @@ -2240,13 +2260,8 @@ private InvokeQuickNode quickenInvoke(int top, int curBCI, int opcode, int state QUICKENED_INVOKES.inc(); CompilerDirectives.transferToInterpreterAndInvalidate(); assert Bytecodes.isInvoke(opcode); - InvokeQuickNode quick = (InvokeQuickNode) tryPatchQuick(curBCI, () -> { - // During resolution of the symbolic reference to the method, any of the exceptions - // pertaining to method resolution (§5.4.3.3) can be thrown. - char cpi = readCPI(curBCI); - Method resolutionSeed = resolveMethod(opcode, cpi); - return dispatchQuickened(top, curBCI, cpi, opcode, statementIndex, resolutionSeed, getMethod().getContext().getEspressoEnv().bytecodeLevelInlining); - }); + InvokeQuickNode quick = (InvokeQuickNode) tryPatchQuick(curBCI, cpi -> getResolvedInvoke(opcode, cpi), + resolvedInvoke -> dispatchQuickened(top, curBCI, opcode, statementIndex, resolvedInvoke, getMethod().getContext().getEspressoEnv().bytecodeLevelInlining)); return quick; } @@ -2254,10 +2269,10 @@ private InvokeQuickNode quickenInvoke(int top, int curBCI, int opcode, int state * Revert speculative quickening e.g. revert inlined fields accessors to a normal invoke. * INVOKEVIRTUAL -> QUICK (InlinedGetter/SetterNode) -> QUICK (InvokeVirtualNode) */ - public int reQuickenInvoke(VirtualFrame frame, int top, int opcode, int curBCI, int statementIndex, Method resolutionSeed) { + public int reQuickenInvoke(VirtualFrame frame, int top, int opcode, int curBCI, int statementIndex) { CompilerAsserts.neverPartOfCompilation(); assert Bytecodes.isInvoke(opcode); - BaseQuickNode invoke = generifyInlinedMethodNode(top, opcode, curBCI, statementIndex, resolutionSeed); + BaseQuickNode invoke = generifyInlinedMethodNode(top, opcode, curBCI, statementIndex); // Perform the call outside of the lock. return invoke.execute(frame, false); } @@ -2292,8 +2307,9 @@ private BaseQuickNode replaceQuickAt(int opcode, int curBCI, BaseQuickNode old, * Reverts Bytecode-level method inlining at the current bci, in case instrumentation starts * happening on this node. */ - public BaseQuickNode generifyInlinedMethodNode(int top, int opcode, int curBCI, int statementIndex, Method resolutionSeed) { + public BaseQuickNode generifyInlinedMethodNode(int top, int opcode, int curBCI, int statementIndex) { CompilerAsserts.neverPartOfCompilation(); + ResolvedInvoke resolvedInvoke = getResolvedInvoke(opcode, readOriginalCPI(curBCI)); return atomic(() -> { assert bs.currentBC(curBCI) == QUICK; char nodeIndex = readCPI(curBCI); @@ -2303,7 +2319,7 @@ public BaseQuickNode generifyInlinedMethodNode(int top, int opcode, int curBCI, // Might be racy, as read is not volatile, but redoing the work should be OK. return currentQuick; } - BaseQuickNode invoke = dispatchQuickened(top, curBCI, readOriginalCPI(curBCI), opcode, statementIndex, resolutionSeed, false); + BaseQuickNode invoke = dispatchQuickened(top, curBCI, opcode, statementIndex, resolvedInvoke, false); nodes[nodeIndex] = currentQuick.replace(invoke); return invoke; }); @@ -2409,11 +2425,8 @@ private int quickenArrayStore(final VirtualFrame frame, int top, int curBCI, int // endregion quickenForeign - private InvokeQuickNode dispatchQuickened(int top, int curBCI, char cpi, int opcode, int statementIndex, Method resolutionSeed, boolean allowBytecodeInlining) { - - Klass symbolicRef = Resolution.getResolvedHolderKlass((MethodRefConstant.Indexes) getConstantPool().methodAt(cpi), getConstantPool(), getDeclaringKlass()); - ResolvedCall resolvedCall = LinkResolver.resolveCallSite(getMeta(), getDeclaringKlass(), resolutionSeed, CallSiteType.fromOpCode(opcode), symbolicRef); - + private InvokeQuickNode dispatchQuickened(int top, int curBCI, int opcode, int statementIndex, ResolvedInvoke resolvedInvoke, boolean allowBytecodeInlining) { + ResolvedCall resolvedCall = resolvedInvoke.resolvedCall(); Method resolved = resolvedCall.getResolvedMethod(); CallKind callKind = resolvedCall.getCallKind(); @@ -2429,13 +2442,7 @@ private InvokeQuickNode dispatchQuickened(int top, int curBCI, char cpi, int opc } if (resolved.isPolySignatureIntrinsic()) { - MethodHandleInvoker invoker = null; - // There might be an invoker if it's an InvokeGeneric - if (getConstantPool().resolvedMethodRefAt(getDeclaringKlass(), cpi) instanceof ResolvedWithInvokerClassMethodRefConstant withInvoker) { - invoker = withInvoker.invoker(); - assert invoker == null || ((opcode == INVOKEVIRTUAL || opcode == INVOKESPECIAL) && resolved.isInvokeIntrinsic()); - } - return new InvokeHandleNode(resolved, invoker, top, curBCI); + return new InvokeHandleNode(resolved, resolvedInvoke.invoker(), top, curBCI); } else { // @formatter:off return switch (callKind) { @@ -2465,39 +2472,10 @@ private RuntimeException throwBoundary(ObjectKlass exceptionKlass, String messag private int quickenInvokeDynamic(final VirtualFrame frame, int top, int curBCI, int opcode) { CompilerDirectives.transferToInterpreterAndInvalidate(); - assert (Bytecodes.INVOKEDYNAMIC == opcode); - RuntimeConstantPool pool = getConstantPool(); - BaseQuickNode quick = null; - int indyIndex = -1; - Lock lock = getLock(); - try { - lock.lock(); - if (bs.currentVolatileBC(curBCI) == QUICK) { - // Check if someone did the job for us. Defer the call until we are out of the lock. - quick = nodes[readCPI(curBCI)]; - } else { - // fetch indy under lock. - indyIndex = readCPI(curBCI); - } - } finally { - lock.unlock(); - } - if (quick != null) { - // Do invocation outside of the lock. - return quick.execute(frame, false) - Bytecodes.stackEffectOf(opcode); - } - // Resolution should happen outside of the bytecode patching lock. - CallSiteLink link = pool.linkInvokeDynamic(getMethod().getDeclaringKlass(), indyIndex, curBCI, getMethod()); - - // re-lock to check if someone did the job for us, since this was a heavy operation. - quick = atomic(() -> { - if (bs.currentVolatileBC(curBCI) == QUICK) { - // someone beat us to it, just trust him. - return nodes[readCPI(curBCI)]; - } else { - return injectQuick(curBCI, new InvokeDynamicCallSiteNode(link.getMemberName(), link.getUnboxedAppendix(), link.getParsedSignature(), getMethod().getMeta(), top, curBCI), QUICK); - } - }); + assert opcode == Bytecodes.INVOKEDYNAMIC; + BaseQuickNode quick = tryPatchQuick(curBCI, + cpi -> getConstantPool().linkInvokeDynamic(getMethod().getDeclaringKlass(), cpi, curBCI, getMethod()), + link -> new InvokeDynamicCallSiteNode(link.getMemberName(), link.getUnboxedAppendix(), link.getParsedSignature(), getMethod().getMeta(), top, curBCI)); return quick.execute(frame, false) - Bytecodes.stackEffectOf(opcode); } @@ -2511,17 +2489,6 @@ public Klass resolveType(int opcode, char cpi) { return getConstantPool().resolvedKlassAt(getDeclaringKlass(), cpi); } - public Method resolveMethod(int opcode, char cpi) { - assert Bytecodes.isInvoke(opcode); - return getConstantPool().resolvedMethodAt(getDeclaringKlass(), cpi); - } - - private Method resolveMethodNoCache(int opcode, char cpi) { - CompilerAsserts.neverPartOfCompilation(); - assert Bytecodes.isInvoke(opcode); - return getConstantPool().resolvedMethodAtNoCache(getDeclaringKlass(), cpi); - } - private Field resolveField(int opcode, char cpi) { assert opcode == GETFIELD || opcode == GETSTATIC || opcode == PUTFIELD || opcode == PUTSTATIC; Field field = getConstantPool().resolvedFieldAt(getMethod().getDeclaringKlass(), cpi); @@ -2533,6 +2500,34 @@ private Field resolveField(int opcode, char cpi) { return field; } + private record ResolvedInvoke(ResolvedCall resolvedCall, MethodHandleInvoker invoker) { + } + + private ResolvedInvoke reResolvedInvoke(int opcode, char cpi) { + getConstantPool().resolveMethodAndUpdate(getDeclaringKlass(), cpi); + return getResolvedInvoke(opcode, cpi); + } + + private ResolvedInvoke getResolvedInvoke(int opcode, char cpi) { + assert !lockIsHeld(); + // During resolution of the symbolic reference to the method, any of the exceptions + // pertaining to method resolution (§5.4.3.3) can be thrown. + MethodRefConstant methodRefConstant = getConstantPool().resolvedMethodRefAt(getDeclaringKlass(), cpi); + Method resolutionSeed = (Method) ((Resolvable.ResolvedConstant) methodRefConstant).value(); + + Klass symbolicRef = Resolution.getResolvedHolderKlass((MethodRefConstant.Indexes) getConstantPool().methodAt(cpi), getConstantPool(), getDeclaringKlass()); + CallSiteType callSiteType = SiteTypes.callSiteFromOpCode(opcode); + ResolvedCall resolvedCall = EspressoLinkResolver.resolveCallSite(getContext(), getDeclaringKlass(), resolutionSeed, callSiteType, symbolicRef); + MethodHandleInvoker invoker = null; + // There might be an invoker if it's an InvokeGeneric + if (methodRefConstant instanceof ResolvedWithInvokerClassMethodRefConstant withInvoker) { + invoker = withInvoker.invoker(); + assert invoker == null || ((opcode == INVOKEVIRTUAL || opcode == INVOKESPECIAL) && resolvedCall.getResolvedMethod().isInvokeIntrinsic()); + } + + return new ResolvedInvoke(resolvedCall, invoker); + } + // endregion Class/Method/Field resolution // region Instance/array allocation @@ -2706,7 +2701,7 @@ private int putField(VirtualFrame frame, int top, Field field, int curBCI, int o CompilerAsserts.partialEvaluationConstant(field); CompilerAsserts.partialEvaluationConstant(mode); - LinkResolver.resolveFieldAccess(getMeta(), getDeclaringKlass(), getMethod(), field, mode); + EspressoLinkResolver.resolveFieldAccess(getContext(), field, mode, getDeclaringKlass(), getMethod()); byte typeHeader = field.getType().byteAt(0); int slotCount = (typeHeader == 'J' || typeHeader == 'D') ? 2 : 1; @@ -2819,7 +2814,7 @@ private int getField(VirtualFrame frame, int top, Field field, int curBCI, int o CompilerAsserts.partialEvaluationConstant(field); - LinkResolver.resolveFieldAccess(getMeta(), getDeclaringKlass(), getMethod(), field, mode); + EspressoLinkResolver.resolveFieldAccess(getContext(), field, mode, getDeclaringKlass(), getMethod()); int slot = top - 1; StaticObject receiver; @@ -2965,29 +2960,37 @@ static final class InstrumentationSupport extends EspressoNode { /** * If transitioning between two statements, exits the current one, and enter the new one. */ - void notifyStatement(VirtualFrame frame, int statementIndex, int nextStatementIndex) { + void notifyStatementChange(VirtualFrame frame, int statementIndex, int nextStatementIndex, int targetBci) { + assert statementIndex != nextStatementIndex; + notifyStatementExit(frame, statementIndex); + setBCI(frame, targetBci); + notifyStatementEnter(frame, nextStatementIndex); + } + + void notifyStatementEnter(VirtualFrame frame, int statementIndex) { + CompilerAsserts.partialEvaluationConstant(statementIndex); + enterAt(frame, statementIndex); + } + + void notifyStatementResume(VirtualFrame frame, int statementIndex) { + CompilerAsserts.partialEvaluationConstant(statementIndex); + resumeAt(frame, statementIndex); + } + + void notifyStatementExit(VirtualFrame frame, int statementIndex) { CompilerAsserts.partialEvaluationConstant(statementIndex); - CompilerAsserts.partialEvaluationConstant(nextStatementIndex); - if (statementIndex == nextStatementIndex) { - return; - } exitAt(frame, statementIndex, StaticObject.NULL); - enterAt(frame, nextStatementIndex); } - public void notifyEntry(@SuppressWarnings("unused") VirtualFrame frame, AbstractInstrumentableBytecodeNode instrumentableNode) { + public void notifyEntry(VirtualFrame frame, AbstractInstrumentableBytecodeNode instrumentableNode) { if (context.shouldReportVMEvents() && method.hasActiveHook()) { - if (context.reportOnMethodEntry(method, instrumentableNode.getScope(frame, true))) { - enterAt(frame, 0); - } + context.reportOnMethodEntry(method, instrumentableNode.getScope(frame, true)); } } - public void notifyResume(@SuppressWarnings("unused") VirtualFrame frame, AbstractInstrumentableBytecodeNode instrumentableNode, int statementIndex) { + public void notifyResume(VirtualFrame frame, AbstractInstrumentableBytecodeNode instrumentableNode) { if (context.shouldReportVMEvents() && method.hasActiveHook()) { - if (context.reportOnMethodEntry(method, instrumentableNode.getScope(frame, true))) { - resumeAt(frame, statementIndex); - } + context.reportOnMethodEntry(method, instrumentableNode.getScope(frame, true)); } } @@ -3104,18 +3107,29 @@ private void exitAt(VirtualFrame frame, int index, Object returnValue) { int getStatementIndexAfterJump(int statementIndex, int curBCI, int targetBCI) { if (hookBCIToNodeIndex == null) { - return 0; + return NO_STATEMENT; } return hookBCIToNodeIndex.lookup(statementIndex, curBCI, targetBCI); } int getNextStatementIndex(int statementIndex, int nextBCI) { if (hookBCIToNodeIndex == null) { - return 0; + return NO_STATEMENT; } return hookBCIToNodeIndex.checkNext(statementIndex, nextBCI); } + int getStartStatementIndex(int startBci) { + if (hookBCIToNodeIndex == null) { + return NO_STATEMENT; + } + if (startBci == 0) { + assert hookBCIToNodeIndex.lookupBucket(0) == 0; + return 0; + } + return hookBCIToNodeIndex.lookupBucket(startBci); + } + private WrapperNode getWrapperAt(int index) { if (statementNodes == null || index < 0) { return null; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/EspressoFrame.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/EspressoFrame.java index b697fc135fe0..da54b2b2afc0 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/EspressoFrame.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/EspressoFrame.java @@ -29,13 +29,13 @@ import com.oracle.truffle.api.frame.FrameSlotKind; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.ExplodeLoop; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.Signatures; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.runtime.ReturnAddress; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.vm.continuation.HostFrameRecord; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/EspressoNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/EspressoNode.java index e483dac52aa0..35e14a484a52 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/EspressoNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/EspressoNode.java @@ -26,6 +26,7 @@ import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.classfile.JavaVersion; import com.oracle.truffle.espresso.classfile.descriptors.Names; import com.oracle.truffle.espresso.classfile.descriptors.Signatures; import com.oracle.truffle.espresso.classfile.descriptors.Types; @@ -34,7 +35,6 @@ import com.oracle.truffle.espresso.impl.ContextAccess; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.runtime.EspressoContext; -import com.oracle.truffle.espresso.classfile.JavaVersion; import com.oracle.truffle.espresso.runtime.StringTable; import com.oracle.truffle.espresso.substitutions.Substitutions; import com.oracle.truffle.espresso.threads.ThreadsAccess; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/IntrinsicSubstitutorNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/IntrinsicSubstitutorNode.java index 498a1a744f20..79da92d0f6a3 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/IntrinsicSubstitutorNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/IntrinsicSubstitutorNode.java @@ -31,9 +31,9 @@ import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.espresso.classfile.perf.DebugCounter; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.perf.DebugCounter; import com.oracle.truffle.espresso.runtime.EspressoThreadLocalState; import com.oracle.truffle.espresso.substitutions.JavaSubstitution; import com.oracle.truffle.espresso.vm.VM; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/NativeMethodNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/NativeMethodNode.java index adc5f551bf91..dfb98d336539 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/NativeMethodNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/NativeMethodNode.java @@ -41,12 +41,12 @@ import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; +import com.oracle.truffle.espresso.classfile.perf.DebugCounter; import com.oracle.truffle.espresso.ffi.nfi.NativeUtils; import com.oracle.truffle.espresso.impl.Method.MethodVersion; import com.oracle.truffle.espresso.jni.JNIHandles; import com.oracle.truffle.espresso.jni.JniEnv; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.perf.DebugCounter; import com.oracle.truffle.espresso.runtime.EspressoException; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.vm.VM; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/SubstitutionScope.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/SubstitutionScope.java index 3f23f7eeba44..a6ee9cb9043a 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/SubstitutionScope.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/SubstitutionScope.java @@ -32,10 +32,10 @@ import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.espresso.EspressoLanguage; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol; -import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.attributes.MethodParametersAttribute; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.impl.Method; @ExportLibrary(InteropLibrary.class) final class SubstitutionScope implements TruffleObject { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/helper/AbstractGetFieldNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/helper/AbstractGetFieldNode.java index f33ed744b6be..6a11f6105277 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/helper/AbstractGetFieldNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/helper/AbstractGetFieldNode.java @@ -34,10 +34,10 @@ import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.impl.Field; import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.nodes.BytecodeNode; import com.oracle.truffle.espresso.nodes.EspressoFrame; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/helper/AbstractSetFieldNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/helper/AbstractSetFieldNode.java index d37155d7c5b7..3dde77e76fad 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/helper/AbstractSetFieldNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/helper/AbstractSetFieldNode.java @@ -33,9 +33,9 @@ import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.impl.Field; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.nodes.BytecodeNode; import com.oracle.truffle.espresso.nodes.EspressoFrame; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/helper/EspressoReferenceArrayStoreNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/helper/EspressoReferenceArrayStoreNode.java index 202ad00e359e..10dbfa86fcc0 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/helper/EspressoReferenceArrayStoreNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/helper/EspressoReferenceArrayStoreNode.java @@ -23,6 +23,8 @@ package com.oracle.truffle.espresso.nodes.helper; +import static com.oracle.truffle.espresso.vm.InterpreterToVM.outOfBoundsMessage; + import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.espresso.EspressoLanguage; @@ -33,8 +35,6 @@ import com.oracle.truffle.espresso.nodes.bytecodes.InstanceOfFactory; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; -import static com.oracle.truffle.espresso.vm.InterpreterToVM.outOfBoundsMessage; - public final class EspressoReferenceArrayStoreNode extends EspressoNode { @Child InstanceOf.Dynamic instanceOfDynamic; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/EspressoForeignProxyGenerator.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/EspressoForeignProxyGenerator.java index b373a85ce06f..56aaf03a9bfa 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/EspressoForeignProxyGenerator.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/EspressoForeignProxyGenerator.java @@ -68,6 +68,7 @@ import java.util.concurrent.atomic.AtomicLong; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.impl.ArrayKlass; import com.oracle.truffle.espresso.impl.ClassRegistry; @@ -84,7 +85,6 @@ import com.oracle.truffle.espresso.impl.generics.tree.FormalTypeParameter; import com.oracle.truffle.espresso.jni.Mangle; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.EspressoException; @@ -338,7 +338,7 @@ private ProxyClassContext proxyClassContext(Set refTypes) { } } - if (packagePrivateTypes.size() > 0) { + if (!packagePrivateTypes.isEmpty()) { // all package-private types must be in the same runtime package // i.e. same package name and same module (named or unnamed) // @@ -372,7 +372,7 @@ private ProxyClassContext proxyClassContext(Set refTypes) { continue; } - if (!targetModule.canRead(m, context) || (!m.isOpen() && !intf.packageEntry().isUnqualifiedExported())) { + if (!targetModule.canRead(m, context.isJavaBase(m)) || (!m.isOpen() && !intf.packageEntry().isUnqualifiedExported())) { throw new IllegalArgumentException(targetModule + " can't access " + intf.getName()); } } @@ -443,7 +443,7 @@ private ModuleTable.ModuleEntry getDynamicModule(StaticObject loader) { private void ensureAccess(ModuleTable.ModuleEntry target, Klass c) { ModuleTable.ModuleEntry m = c.module(); // add read edge and qualified export for the target module to access - if (!target.canRead(m, context)) { + if (!target.canRead(m, context.isJavaBase(m))) { target.addReads(m); } PackageTable.PackageEntry pe = c.packageEntry(); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/InvokeEspressoNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/InvokeEspressoNode.java index 9a81756d3955..4294f07ed3a1 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/InvokeEspressoNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/InvokeEspressoNode.java @@ -33,8 +33,8 @@ import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.IndirectCallNode; import com.oracle.truffle.api.profiles.BranchProfile; -import com.oracle.truffle.espresso.impl.EspressoType; import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.impl.EspressoType; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.meta.Meta; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/LookupAndInvokeKnownMethodNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/LookupAndInvokeKnownMethodNode.java index 6d9b68011daa..2b7a9fc03a86 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/LookupAndInvokeKnownMethodNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/LookupAndInvokeKnownMethodNode.java @@ -34,8 +34,8 @@ import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.nodes.EspressoNode; -import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.runtime.dispatch.staticobject.EspressoInterop; +import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; /** * This node is a shortcut for implementing behaviors that require doing a virtual/interface lookup diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/ToPrimitive.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/ToPrimitive.java index b20ff88d6831..9cd769d79df1 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/ToPrimitive.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/ToPrimitive.java @@ -34,6 +34,7 @@ import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.espresso.meta.EspressoError; +import com.oracle.truffle.espresso.runtime.dispatch.staticobject.EspressoInterop; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; /** @@ -102,8 +103,13 @@ int doHost(Integer value) { @Specialization int doEspresso(StaticObject value, @Cached BranchProfile exceptionProfile) throws UnsupportedTypeException { - if (value != null && !StaticObject.isNull(value) && value.getKlass() == getMeta().java_lang_Integer) { - return (int) getMeta().java_lang_Integer_value.get(value); + if (value != null && !StaticObject.isNull(value) && EspressoInterop.fitsInInt(value)) { + try { + return EspressoInterop.asInt(value); + } catch (UnsupportedMessageException e) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere("Contract violation: if fitsInInt returns true, asInt must succeed."); + } } exceptionProfile.enter(); throw UnsupportedTypeException.create(new Object[]{value}, EspressoError.cat("Cannot cast ", value, " to int")); @@ -148,8 +154,13 @@ byte doHost(Byte value) { @Specialization byte doEspresso(StaticObject value, @Cached BranchProfile exceptionProfile) throws UnsupportedTypeException { - if (value != null && !StaticObject.isNull(value) && value.getKlass() == getMeta().java_lang_Byte) { - return (byte) getMeta().java_lang_Byte_value.get(value); + if (value != null && !StaticObject.isNull(value) && EspressoInterop.fitsInByte(value)) { + try { + return EspressoInterop.asByte(value); + } catch (UnsupportedMessageException e) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere("Contract violation: if fitsInByte returns true, asByte must succeed."); + } } exceptionProfile.enter(); throw UnsupportedTypeException.create(new Object[]{value}, EspressoError.cat("Cannot cast ", value, " to byte")); @@ -194,8 +205,13 @@ short doHost(Short value) { @Specialization short doEspresso(StaticObject value, @Cached BranchProfile exceptionProfile) throws UnsupportedTypeException { - if (value != null && !StaticObject.isNull(value) && value.getKlass() == getMeta().java_lang_Short) { - return (short) getMeta().java_lang_Short_value.get(value); + if (value != null && !StaticObject.isNull(value) && EspressoInterop.fitsInShort(value)) { + try { + return EspressoInterop.asShort(value); + } catch (UnsupportedMessageException e) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere("Contract violation: if fitsInShort returns true, asShort must succeed."); + } } exceptionProfile.enter(); throw UnsupportedTypeException.create(new Object[]{value}, EspressoError.cat("Cannot cast ", value, " to short")); @@ -264,7 +280,7 @@ char doForeign(Object value, throw UnsupportedTypeException.create(new Object[]{value}, EspressoError.cat("Cannot cast ", s, " to char")); } catch (UnsupportedMessageException e) { CompilerDirectives.transferToInterpreterAndInvalidate(); - throw EspressoError.shouldNotReachHere("Contract violation: if fitsInInt returns true, asInt must succeed."); + throw EspressoError.shouldNotReachHere("Contract violation: if isString returns true, asString must succeed."); } } @@ -292,8 +308,13 @@ long doHost(Long value) { @Specialization long doEspresso(StaticObject value, @Cached BranchProfile exceptionProfile) throws UnsupportedTypeException { - if (value != null && !StaticObject.isNull(value) && value.getKlass() == getMeta().java_lang_Long) { - return (long) getMeta().java_lang_Long_value.get(value); + if (value != null && !StaticObject.isNull(value) && EspressoInterop.fitsInLong(value)) { + try { + return EspressoInterop.asLong(value); + } catch (UnsupportedMessageException e) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere("Contract violation: if fitsInLong returns true, asLong must succeed."); + } } exceptionProfile.enter(); throw UnsupportedTypeException.create(new Object[]{value}, EspressoError.cat("Cannot cast ", value, " to long")); @@ -338,8 +359,13 @@ float doHost(Float value) { @Specialization float doEspresso(StaticObject value, @Cached BranchProfile exceptionProfile) throws UnsupportedTypeException { - if (value != null && !StaticObject.isNull(value) && value.getKlass() == getMeta().java_lang_Float) { - return (float) getMeta().java_lang_Float_value.get(value); + if (value != null && !StaticObject.isNull(value) && EspressoInterop.fitsInFloat(value)) { + try { + return EspressoInterop.asFloat(value); + } catch (UnsupportedMessageException e) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere("Contract violation: if fitsInFloat returns true, asFloat must succeed."); + } } exceptionProfile.enter(); throw UnsupportedTypeException.create(new Object[]{value}, EspressoError.cat("Cannot cast ", value, " to float")); @@ -384,8 +410,13 @@ public abstract static class ToDouble extends ToPrimitive { @Specialization double doEspresso(StaticObject value, @Cached BranchProfile exceptionProfile) throws UnsupportedTypeException { - if (value != null && !StaticObject.isNull(value) && value.getKlass() == getMeta().java_lang_Double) { - return (double) getMeta().java_lang_Double_value.get(value); + if (value != null && !StaticObject.isNull(value) && EspressoInterop.fitsInDouble(value)) { + try { + return EspressoInterop.asDouble(value); + } catch (UnsupportedMessageException e) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere("Contract violation: if fitsInDouble returns true, asDouble must succeed."); + } } exceptionProfile.enter(); throw UnsupportedTypeException.create(new Object[]{value}, EspressoError.cat("Cannot cast ", value, " to double")); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/ToReference.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/ToReference.java index 29f696fb1dc8..7b1797069296 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/ToReference.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/ToReference.java @@ -61,6 +61,7 @@ import com.oracle.truffle.espresso.nodes.bytecodes.InstanceOf; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.EspressoException; +import com.oracle.truffle.espresso.runtime.dispatch.staticobject.EspressoInterop; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; @NodeInfo(shortName = "Convert to Espresso StaticObject") @@ -325,8 +326,33 @@ public abstract static class GenericToReference extends EspressoNode { @Specialization public StaticObject doStaticObject(StaticObject value, EspressoType targetType, @Cached InstanceOf.Dynamic instanceOf) throws UnsupportedTypeException { - if (StaticObject.isNull(value) || instanceOf.execute(value.getKlass(), targetType.getRawType())) { - return value; // pass through, NULL coercion not needed. + Klass rawType = targetType.getRawType(); + if (StaticObject.isNull(value) || instanceOf.execute(value.getKlass(), rawType)) { + return value; + } + try { + Meta meta = getMeta(); + if (rawType == meta.java_lang_Double && EspressoInterop.fitsInDouble(value)) { + return meta.boxDouble(EspressoInterop.asDouble(value)); + } + if (rawType == meta.java_lang_Float && EspressoInterop.fitsInFloat(value)) { + return meta.boxFloat(EspressoInterop.asFloat(value)); + } + if (rawType == meta.java_lang_Long && EspressoInterop.fitsInLong(value)) { + return meta.boxLong(EspressoInterop.asLong(value)); + } + if (rawType == meta.java_lang_Integer && EspressoInterop.fitsInInt(value)) { + return meta.boxInteger(EspressoInterop.asInt(value)); + } + if (rawType == meta.java_lang_Short && EspressoInterop.fitsInShort(value)) { + return meta.boxShort(EspressoInterop.asShort(value)); + } + if (rawType == meta.java_lang_Byte && EspressoInterop.fitsInByte(value)) { + return meta.boxByte(EspressoInterop.asByte(value)); + } + } catch (UnsupportedMessageException ex) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere("Contract violation: if fitsIn* returns true, as* must succeed."); } throw UnsupportedTypeException.create(new Object[]{value}, EspressoError.cat("Cannot cast ", value, " to ", targetType.getRawType().getTypeAsString())); } @@ -498,6 +524,10 @@ public static StaticObject doGeneric(Object value, EspressoType targetType, if (result != null) { return result; } + // no generic conversion to abstract target types allowed + if (targetType.getRawType().isAbstract()) { + throw UnsupportedTypeException.create(new Object[]{value}, targetType.getRawType().getTypeAsString()); + } if (targetType instanceof ObjectKlass rawType) { noConverterProfile.enter(node); checkHasAllFieldsOrThrow(value, rawType, interop, meta); @@ -2609,6 +2639,10 @@ static StaticObject doForeignWrapper(Object value, if (result != null) { return result; } + // no generic conversion to abstract target types allowed + if (target.getRawType().isAbstract()) { + throw UnsupportedTypeException.create(new Object[]{value}, target.getRawType().getTypeAsString()); + } noConverterProfile.enter(node); checkHasAllFieldsOrThrow(value, (ObjectKlass) target.getRawType(), interop, context.getMeta()); return StaticObject.createForeign(EspressoLanguage.get(node), target.getRawType(), value, interop); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MHInvokeGenericNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MHInvokeGenericNode.java index d5e0a938d24a..b134057450ba 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MHInvokeGenericNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MHInvokeGenericNode.java @@ -26,12 +26,12 @@ import com.oracle.truffle.api.nodes.DirectCallNode; import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Signature; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.impl.ObjectKlass; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MHLinkToNativeNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MHLinkToNativeNode.java index 5189c2ee333b..fcf6c4df2d1a 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MHLinkToNativeNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MHLinkToNativeNode.java @@ -29,9 +29,9 @@ import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.runtime.EspressoContext; -import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.runtime.panama.DowncallStubNode; import com.oracle.truffle.espresso.runtime.panama.DowncallStubs; +import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; public abstract class MHLinkToNativeNode extends MethodHandleIntrinsicNode { protected static final int LIMIT = 3; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MHLinkToNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MHLinkToNode.java index 0e9a060fb860..93216b497cec 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MHLinkToNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MHLinkToNode.java @@ -32,6 +32,7 @@ import com.oracle.truffle.api.nodes.DirectCallNode; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.IndirectCallNode; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.Signatures; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; @@ -39,7 +40,6 @@ import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.nodes.quick.invoke.InvokeDynamicCallSiteNode; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.MethodHandleIntrinsics; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MethodHandleIntrinsicNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MethodHandleIntrinsicNode.java index 844a5416c414..f62fd7ae869c 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MethodHandleIntrinsicNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/methodhandle/MethodHandleIntrinsicNode.java @@ -24,10 +24,10 @@ package com.oracle.truffle.espresso.nodes.methodhandle; import com.oracle.truffle.api.dsl.Idempotent; -import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.classfile.JavaKind; -import com.oracle.truffle.espresso.nodes.EspressoNode; import com.oracle.truffle.espresso.classfile.perf.DebugCounter; +import com.oracle.truffle.espresso.impl.Method; +import com.oracle.truffle.espresso.nodes.EspressoNode; /** * Top of the method handle intrinsic behavior implementation hierarchy. diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/InvokeDynamicCallSiteNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/InvokeDynamicCallSiteNode.java index 76cc78fdf36d..70450e382a6b 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/InvokeDynamicCallSiteNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/InvokeDynamicCallSiteNode.java @@ -25,12 +25,12 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.DirectCallNode; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.Signatures; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; import com.oracle.truffle.espresso.impl.Method; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.nodes.EspressoFrame; import com.oracle.truffle.espresso.nodes.quick.QuickNode; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/ConditionalInlinedMethodNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/ConditionalInlinedMethodNode.java index e3839b9e168a..56f996a62229 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/ConditionalInlinedMethodNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/ConditionalInlinedMethodNode.java @@ -25,13 +25,15 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.espresso.impl.Field; +import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.nodes.quick.invoke.InvokeQuickNode; import com.oracle.truffle.espresso.nodes.quick.invoke.InvokeSpecialQuickNode; import com.oracle.truffle.espresso.nodes.quick.invoke.InvokeStaticQuickNode; import com.oracle.truffle.espresso.nodes.quick.invoke.InvokeVirtualQuickNode; -import com.oracle.truffle.espresso.resolver.ResolvedCall; +import com.oracle.truffle.espresso.shared.resolver.ResolvedCall; public class ConditionalInlinedMethodNode extends InlinedMethodNode { @@ -46,7 +48,7 @@ public interface Recipes { @Child protected InvokeQuickNode fallbackNode; private final InlinedMethodPredicate condition; - public ConditionalInlinedMethodNode(ResolvedCall resolvedCall, int top, int opcode, int callerBCI, int statementIndex, Recipes recipes, InlinedMethodPredicate condition) { + public ConditionalInlinedMethodNode(ResolvedCall resolvedCall, int top, int opcode, int callerBCI, int statementIndex, Recipes recipes, InlinedMethodPredicate condition) { super(resolvedCall.getResolvedMethod().getMethodVersion(), top, opcode, callerBCI, statementIndex, null); this.fallbackNode = getFallback(resolvedCall, top, callerBCI); this.condition = condition; @@ -79,7 +81,7 @@ public static InlinedMethodNode getDefinitiveNode(Recipes recipes, return replacement; } - static InvokeQuickNode getFallback(ResolvedCall resolvedCall, int top, int callerBCI) { + static InvokeQuickNode getFallback(ResolvedCall resolvedCall, int top, int callerBCI) { // @formatter:off switch (resolvedCall.getCallKind()) { case STATIC : return new InvokeStaticQuickNode(resolvedCall.getResolvedMethod(), top, callerBCI); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/GuardedConditionalInlinedMethodNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/GuardedConditionalInlinedMethodNode.java index 19fcbd7c598c..bce20367b546 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/GuardedConditionalInlinedMethodNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/GuardedConditionalInlinedMethodNode.java @@ -27,9 +27,11 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.espresso.impl.Field; +import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.nodes.quick.invoke.InvokeQuickNode; -import com.oracle.truffle.espresso.resolver.ResolvedCall; +import com.oracle.truffle.espresso.shared.resolver.ResolvedCall; public final class GuardedConditionalInlinedMethodNode extends InlinedMethodNode { private final ConditionalInlinedMethodNode.Recipes recipes; @@ -38,7 +40,7 @@ public final class GuardedConditionalInlinedMethodNode extends InlinedMethodNode private final InlinedMethodPredicate condition; private final InlinedMethodPredicate guard; - public GuardedConditionalInlinedMethodNode(ResolvedCall resolvedCall, int top, int opcode, int callerBCI, int statementIndex, + public GuardedConditionalInlinedMethodNode(ResolvedCall resolvedCall, int top, int opcode, int callerBCI, int statementIndex, ConditionalInlinedMethodNode.Recipes recipes, InlinedMethodPredicate condition, InlinedMethodPredicate guard) { super(resolvedCall.getResolvedMethod().getMethodVersion(), top, opcode, callerBCI, statementIndex, null); @@ -62,7 +64,7 @@ public int execute(VirtualFrame frame, boolean isContinuationResume) { } } else { CompilerDirectives.transferToInterpreterAndInvalidate(); - return getBytecodeNode().reQuickenInvoke(frame, top, opcode, getCallerBCI(), statementIndex, method.getMethod()); + return getBytecodeNode().reQuickenInvoke(frame, top, opcode, getCallerBCI(), statementIndex); } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/GuardedInlinedMethodNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/GuardedInlinedMethodNode.java index 6461e3633bac..3193bcaa7dd3 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/GuardedInlinedMethodNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/GuardedInlinedMethodNode.java @@ -46,7 +46,7 @@ public int execute(VirtualFrame frame, boolean isContinuationResume) { return executeBody(frame); } else { CompilerDirectives.transferToInterpreterAndInvalidate(); - return getBytecodeNode().reQuickenInvoke(frame, top, opcode, getCallerBCI(), statementIndex, method.getMethod()); + return getBytecodeNode().reQuickenInvoke(frame, top, opcode, getCallerBCI(), statementIndex); } } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/InlinedMethodNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/InlinedMethodNode.java index 92352673cfc9..e8cfe4faaaa3 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/InlinedMethodNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/InlinedMethodNode.java @@ -28,6 +28,8 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; +import com.oracle.truffle.espresso.impl.Field; +import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.nodes.BytecodeNode; @@ -36,8 +38,8 @@ import com.oracle.truffle.espresso.nodes.quick.invoke.InvokeQuickNode; import com.oracle.truffle.espresso.nodes.quick.invoke.inline.bodies.InlinedFieldAccessNode; import com.oracle.truffle.espresso.nodes.quick.invoke.inline.bodies.InlinedSubstitutionBodyNode; -import com.oracle.truffle.espresso.resolver.CallKind; -import com.oracle.truffle.espresso.resolver.ResolvedCall; +import com.oracle.truffle.espresso.shared.resolver.CallKind; +import com.oracle.truffle.espresso.shared.resolver.ResolvedCall; import com.oracle.truffle.espresso.substitutions.JavaSubstitution; import com.oracle.truffle.espresso.substitutions.Substitutions; @@ -108,7 +110,7 @@ public SourceSection getSourceSection() { @Child BodyNode body; - public static InlinedMethodNode createFor(ResolvedCall resolvedCall, int top, int opcode, int curBCI, int statementIndex) { + public static InlinedMethodNode createFor(ResolvedCall resolvedCall, int top, int opcode, int curBCI, int statementIndex) { if (!isInlineCandidate(resolvedCall)) { return null; } @@ -187,10 +189,10 @@ private void initCheck() { } public final BaseQuickNode revertToGeneric(BytecodeNode parent) { - return parent.generifyInlinedMethodNode(top, opcode, getCallerBCI(), statementIndex, method.getMethod()); + return parent.generifyInlinedMethodNode(top, opcode, getCallerBCI(), statementIndex); } - public static boolean isInlineCandidate(ResolvedCall resolvedCall) { + public static boolean isInlineCandidate(ResolvedCall resolvedCall) { Method resolutionSeed = resolvedCall.getResolvedMethod(); if (resolutionSeed.isSynchronized()) { return false; @@ -209,7 +211,7 @@ public static boolean isInlineCandidate(ResolvedCall resolvedCall) { return false; } - public static boolean isUnconditionalInlineCandidate(ResolvedCall resolvedCall) { + public static boolean isUnconditionalInlineCandidate(ResolvedCall resolvedCall) { return resolvedCall.getCallKind().isDirectCall(); } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/bodies/InlinedFieldAccessNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/bodies/InlinedFieldAccessNode.java index f4a5de00c443..36fc167b60f6 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/bodies/InlinedFieldAccessNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/bodies/InlinedFieldAccessNode.java @@ -32,14 +32,15 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; import com.oracle.truffle.espresso.impl.Field; +import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.nodes.quick.invoke.inline.ConditionalInlinedMethodNode; import com.oracle.truffle.espresso.nodes.quick.invoke.inline.GuardedConditionalInlinedMethodNode; import com.oracle.truffle.espresso.nodes.quick.invoke.inline.InlinedMethodNode; import com.oracle.truffle.espresso.nodes.quick.invoke.inline.InlinedMethodPredicate; -import com.oracle.truffle.espresso.resolver.ResolvedCall; import com.oracle.truffle.espresso.runtime.EspressoContext; +import com.oracle.truffle.espresso.shared.resolver.ResolvedCall; public abstract class InlinedFieldAccessNode extends InlinedMethodNode.BodyNode implements InlinedMethodPredicate { @@ -95,14 +96,14 @@ protected InlinedFieldAccessNode(Method.MethodVersion method, char fieldCpi) { this.field = getInlinedField(method, fieldCpi); } - public static InlinedMethodNode createGetter(ResolvedCall resolvedCall, int top, int opCode, int curBCI, int statementIndex) { + public static InlinedMethodNode createGetter(ResolvedCall resolvedCall, int top, int opCode, int curBCI, int statementIndex) { Method.MethodVersion methodVersion = resolvedCall.getResolvedMethod().getMethodVersion(); char fieldCpi = InlinedFieldAccessNode.getFieldCpi(false, methodVersion); ConditionalInlinedMethodNode.Recipes recipes = new FieldAccessRecipes(() -> new InlinedGetterNode(methodVersion, fieldCpi)); return create(resolvedCall, top, opCode, curBCI, statementIndex, recipes, fieldCpi); } - public static InlinedMethodNode createSetter(ResolvedCall resolvedCall, int top, int opCode, int curBCI, int statementIndex) { + public static InlinedMethodNode createSetter(ResolvedCall resolvedCall, int top, int opCode, int curBCI, int statementIndex) { Method.MethodVersion methodVersion = resolvedCall.getResolvedMethod().getMethodVersion(); char fieldCpi = InlinedFieldAccessNode.getFieldCpi(true, methodVersion); ConditionalInlinedMethodNode.Recipes recipes = new FieldAccessRecipes(() -> new InlinedSetterNode(methodVersion, fieldCpi)); @@ -123,7 +124,7 @@ public static InlinedMethodNode createSetter(ResolvedCall resolvedCall, int top, * to a generic invoke if the method is no longer a leaf.
  • * */ - private static InlinedMethodNode create(ResolvedCall resolvedCall, int top, int opCode, int curBCI, int statementIndex, + private static InlinedMethodNode create(ResolvedCall resolvedCall, int top, int opCode, int curBCI, int statementIndex, ConditionalInlinedMethodNode.Recipes recipes, char fieldCpi) { assert isInlineCandidate(resolvedCall); Method.MethodVersion inlinedMethod = resolvedCall.getResolvedMethod().getMethodVersion(); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/CachedLinkedKlassProvider.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/CachedLinkedKlassProvider.java index c2fe7943eda6..63ea33e38e63 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/CachedLinkedKlassProvider.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/CachedLinkedKlassProvider.java @@ -28,11 +28,11 @@ import java.util.concurrent.ConcurrentHashMap; import com.oracle.truffle.api.TruffleLogger; +import com.oracle.truffle.espresso.classfile.ParserKlass; import com.oracle.truffle.espresso.impl.ClassLoadingEnv; import com.oracle.truffle.espresso.impl.ClassRegistry; import com.oracle.truffle.espresso.impl.ContextDescription; import com.oracle.truffle.espresso.impl.LinkedKlass; -import com.oracle.truffle.espresso.classfile.ParserKlass; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; public final class CachedLinkedKlassProvider extends AbstractCachedKlassProvider implements LinkedKlassProvider { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/CachedParserKlassProvider.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/CachedParserKlassProvider.java index 8ca6ba2e2a5d..9838acce84c9 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/CachedParserKlassProvider.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/CachedParserKlassProvider.java @@ -27,10 +27,10 @@ import java.util.concurrent.ConcurrentHashMap; import com.oracle.truffle.api.TruffleLogger; +import com.oracle.truffle.espresso.classfile.ParserKlass; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.impl.ClassLoadingEnv; import com.oracle.truffle.espresso.impl.ClassRegistry; -import com.oracle.truffle.espresso.classfile.ParserKlass; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.verifier.MethodVerifier; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/DefaultLinkedKlassProvider.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/DefaultLinkedKlassProvider.java index 422e5c5f9f61..b588ebc53cde 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/DefaultLinkedKlassProvider.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/DefaultLinkedKlassProvider.java @@ -22,11 +22,11 @@ */ package com.oracle.truffle.espresso.preinit; +import com.oracle.truffle.espresso.classfile.ParserKlass; import com.oracle.truffle.espresso.impl.ClassLoadingEnv; import com.oracle.truffle.espresso.impl.ClassRegistry; import com.oracle.truffle.espresso.impl.ContextDescription; import com.oracle.truffle.espresso.impl.LinkedKlass; -import com.oracle.truffle.espresso.classfile.ParserKlass; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; public final class DefaultLinkedKlassProvider implements LinkedKlassProvider { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/DefaultParserKlassProvider.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/DefaultParserKlassProvider.java index cfe926fe04fe..46470f54a98c 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/DefaultParserKlassProvider.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/DefaultParserKlassProvider.java @@ -22,11 +22,11 @@ */ package com.oracle.truffle.espresso.preinit; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; +import com.oracle.truffle.espresso.classfile.ParserKlass; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.impl.ClassLoadingEnv; import com.oracle.truffle.espresso.impl.ClassRegistry; -import com.oracle.truffle.espresso.classfile.ParserKlass; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; public final class DefaultParserKlassProvider implements ParserKlassProvider { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/EspressoLanguageCache.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/EspressoLanguageCache.java index fb6bb30f0395..f4f9f91d2ab8 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/EspressoLanguageCache.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/EspressoLanguageCache.java @@ -25,12 +25,12 @@ import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.TruffleLogger; import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.classfile.ParserKlass; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.impl.ClassLoadingEnv; import com.oracle.truffle.espresso.impl.ClassRegistry; import com.oracle.truffle.espresso.impl.ContextDescription; import com.oracle.truffle.espresso.impl.LinkedKlass; -import com.oracle.truffle.espresso.classfile.ParserKlass; import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/LinkedKlassProvider.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/LinkedKlassProvider.java index ce9db65d8128..3d1aa221a1da 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/LinkedKlassProvider.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/preinit/LinkedKlassProvider.java @@ -22,11 +22,11 @@ */ package com.oracle.truffle.espresso.preinit; +import com.oracle.truffle.espresso.classfile.ParserKlass; import com.oracle.truffle.espresso.impl.ClassLoadingEnv; import com.oracle.truffle.espresso.impl.ClassRegistry; import com.oracle.truffle.espresso.impl.ContextDescription; import com.oracle.truffle.espresso.impl.LinkedKlass; -import com.oracle.truffle.espresso.classfile.ParserKlass; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; public interface LinkedKlassProvider { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassInfo.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassInfo.java index 79d18c2bd081..10c71ec2df1a 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassInfo.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassInfo.java @@ -22,6 +22,16 @@ */ package com.oracle.truffle.espresso.redefinition; +import java.util.ArrayList; +import java.util.Set; +import java.util.regex.Matcher; + +import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.ParserField; +import com.oracle.truffle.espresso.classfile.ParserKlass; +import com.oracle.truffle.espresso.classfile.ParserMethod; +import com.oracle.truffle.espresso.classfile.attributes.EnclosingMethodAttribute; +import com.oracle.truffle.espresso.classfile.constantpool.NameAndTypeConstant; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; @@ -31,20 +41,10 @@ import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.jdwp.api.RedefineInfo; -import com.oracle.truffle.espresso.classfile.ConstantPool; -import com.oracle.truffle.espresso.classfile.ParserField; -import com.oracle.truffle.espresso.classfile.ParserKlass; -import com.oracle.truffle.espresso.classfile.ParserMethod; -import com.oracle.truffle.espresso.classfile.attributes.EnclosingMethodAttribute; -import com.oracle.truffle.espresso.classfile.constantpool.NameAndTypeConstant; import com.oracle.truffle.espresso.preinit.ParserKlassProvider; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; -import java.util.ArrayList; -import java.util.Set; -import java.util.regex.Matcher; - public abstract class ClassInfo { private final boolean isNewInnerTestKlass; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassRedefinition.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassRedefinition.java index 60e122be3262..ce9503a0d6ac 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassRedefinition.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassRedefinition.java @@ -22,12 +22,39 @@ */ package com.oracle.truffle.espresso.redefinition; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.EconomicSet; + import com.oracle.truffle.api.Assumption; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.Truffle; +import com.oracle.truffle.api.TruffleLogger; +import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.Constants; +import com.oracle.truffle.espresso.classfile.ParserField; +import com.oracle.truffle.espresso.classfile.ParserKlass; +import com.oracle.truffle.espresso.classfile.ParserMethod; +import com.oracle.truffle.espresso.classfile.attributes.Attribute; +import com.oracle.truffle.espresso.classfile.attributes.CodeAttribute; +import com.oracle.truffle.espresso.classfile.attributes.LineNumberTableAttribute; +import com.oracle.truffle.espresso.classfile.attributes.Local; +import com.oracle.truffle.espresso.classfile.attributes.LocalVariableTable; import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; import com.oracle.truffle.espresso.classfile.bytecode.Bytecodes; +import com.oracle.truffle.espresso.classfile.constantpool.PoolConstant; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Types; import com.oracle.truffle.espresso.impl.ClassRegistry; @@ -40,48 +67,21 @@ import com.oracle.truffle.espresso.jdwp.api.ErrorCodes; import com.oracle.truffle.espresso.jdwp.api.Ids; import com.oracle.truffle.espresso.jdwp.api.RedefineInfo; -import com.oracle.truffle.espresso.jdwp.impl.DebuggerController; import com.oracle.truffle.espresso.meta.Meta; -import com.oracle.truffle.espresso.classfile.ConstantPool; -import com.oracle.truffle.espresso.classfile.Constants; -import com.oracle.truffle.espresso.classfile.ParserField; -import com.oracle.truffle.espresso.classfile.ParserKlass; -import com.oracle.truffle.espresso.classfile.ParserMethod; -import com.oracle.truffle.espresso.classfile.attributes.Attribute; -import com.oracle.truffle.espresso.classfile.attributes.CodeAttribute; -import com.oracle.truffle.espresso.classfile.attributes.LineNumberTableAttribute; -import com.oracle.truffle.espresso.classfile.attributes.Local; -import com.oracle.truffle.espresso.classfile.attributes.LocalVariableTable; -import com.oracle.truffle.espresso.classfile.constantpool.PoolConstant; import com.oracle.truffle.espresso.preinit.ParserKlassProvider; import com.oracle.truffle.espresso.redefinition.plugins.impl.RedefineListener; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.EspressoException; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.vm.InterpreterToVM; -import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.EconomicSet; - -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.regex.Matcher; public final class ClassRedefinition { - + public static final TruffleLogger LOGGER = TruffleLogger.getLogger(EspressoLanguage.ID, ClassRedefinition.class); private final Object redefineLock = new Object(); private volatile boolean locked = false; private Thread redefineThread = null; - private final EspressoContext context; private final Ids ids; - private final DebuggerController controller; private final RedefineListener redefineListener; private volatile Assumption missingFieldAssumption = Truffle.getRuntime().createAssumption(); private ArrayList currentDelegationFields; @@ -107,14 +107,13 @@ public enum ClassChange { NEW_CLASS, SCHEMA_CHANGE, CLASS_HIERARCHY_CHANGED, - INVALID; + INVALID } - public ClassRedefinition(EspressoContext context, Ids ids, RedefineListener listener, DebuggerController controller) { + public ClassRedefinition(EspressoContext context, Ids ids, RedefineListener listener) { this.context = context; this.ids = ids; this.redefineListener = listener; - this.controller = controller; } public void begin() { @@ -149,7 +148,7 @@ public void addExtraReloadClasses(List redefineInfos, List bodyChanges = new HashMap<>(); List newSpecialMethods = new ArrayList<>(1); - boolean constantPoolChanged = false; - if (!Arrays.equals(oldParserKlass.getConstantPool().getRawBytes(), newParserKlass.getConstantPool().getRawBytes())) { - constantPoolChanged = true; - } + boolean constantPoolChanged = !Arrays.equals(oldParserKlass.getConstantPool().getRawBytes(), newParserKlass.getConstantPool().getRawBytes()); Iterator oldIt = oldMethods.iterator(); Iterator newIt; while (oldIt.hasNext()) { @@ -674,13 +670,11 @@ private static boolean attrChanged(ParserMethod oldMethod, ParserMethod newMetho Attribute oldAttribute = oldMethod.getAttribute(name); Attribute newAttribute = newMethod.getAttribute(name); if ((oldAttribute == null || newAttribute == null)) { - if (oldAttribute != null || newAttribute != null) { - return true; - } // else both null, so no change. Move on! - } else if (!oldAttribute.sameAs(newAttribute)) { - return true; + // else both null, so no change. Move on! + return oldAttribute != null || newAttribute != null; + } else { + return !oldAttribute.sameAs(newAttribute); } - return false; } private static boolean isSameMethod(ParserMethod oldMethod, ParserMethod newMethod) { @@ -797,7 +791,7 @@ private void doRedefineClass(ChangePacket packet, List invalidatedC } oldKlass.redefineClass(packet, invalidatedClasses, ids); redefinedClasses.add(oldKlass); - if (redefineListener.shouldRerunClassInitializer(oldKlass, packet.detectedChange.clinitChanged(), controller)) { + if (redefineListener.shouldRerunClassInitializer(oldKlass, packet.detectedChange.clinitChanged())) { context.rerunclinit(oldKlass); } } @@ -828,8 +822,4 @@ public Method handleRemovedMethod(Method resolutionSeed, Klass receiverKlass) { public int getNextAvailableFieldSlot() { return nextAvailableFieldSlot.getAndDecrement(); } - - public DebuggerController getController() { - return controller; - } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/DetectedChange.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/DetectedChange.java index a22343293df8..b00ff2fa365b 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/DetectedChange.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/DetectedChange.java @@ -30,12 +30,12 @@ import java.util.Map; import java.util.Set; +import com.oracle.truffle.espresso.classfile.ParserField; +import com.oracle.truffle.espresso.classfile.ParserMethod; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.impl.Field; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.impl.ObjectKlass; -import com.oracle.truffle.espresso.classfile.ParserField; -import com.oracle.truffle.espresso.classfile.ParserMethod; public final class DetectedChange { private final List addedStaticFields = new ArrayList<>(); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ImmutableClassInfo.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ImmutableClassInfo.java index 1a63bab0c3a7..a2ddd05662a6 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ImmutableClassInfo.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ImmutableClassInfo.java @@ -22,13 +22,13 @@ */ package com.oracle.truffle.espresso.redefinition; +import java.lang.ref.WeakReference; +import java.util.ArrayList; + import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; -import java.lang.ref.WeakReference; -import java.util.ArrayList; - // Represents ClassInfo instances that are cached in the global // cache for all classes having been involved in a redefinition public final class ImmutableClassInfo extends ClassInfo { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/InnerClassRedefiner.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/InnerClassRedefiner.java index 0dd991ccdf37..b56f4c8743cd 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/InnerClassRedefiner.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/InnerClassRedefiner.java @@ -22,21 +22,6 @@ */ package com.oracle.truffle.espresso.redefinition; -import com.oracle.truffle.api.TruffleLogger; -import com.oracle.truffle.espresso.EspressoLanguage; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol; -import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; -import com.oracle.truffle.espresso.impl.ClassRegistry; -import com.oracle.truffle.espresso.impl.ConstantPoolPatcher; -import com.oracle.truffle.espresso.impl.Klass; -import com.oracle.truffle.espresso.impl.ObjectKlass; -import com.oracle.truffle.espresso.jdwp.api.ErrorCodes; -import com.oracle.truffle.espresso.jdwp.api.RedefineInfo; -import com.oracle.truffle.espresso.classfile.ParserException; -import com.oracle.truffle.espresso.preinit.ParserKlassProvider; -import com.oracle.truffle.espresso.runtime.EspressoContext; -import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; @@ -51,8 +36,20 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.oracle.truffle.espresso.classfile.ParserException; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; +import com.oracle.truffle.espresso.impl.ClassRegistry; +import com.oracle.truffle.espresso.impl.ConstantPoolPatcher; +import com.oracle.truffle.espresso.impl.Klass; +import com.oracle.truffle.espresso.impl.ObjectKlass; +import com.oracle.truffle.espresso.jdwp.api.ErrorCodes; +import com.oracle.truffle.espresso.jdwp.api.RedefineInfo; +import com.oracle.truffle.espresso.preinit.ParserKlassProvider; +import com.oracle.truffle.espresso.runtime.EspressoContext; +import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; + public final class InnerClassRedefiner { - private static final TruffleLogger LOGGER = TruffleLogger.getLogger(EspressoLanguage.ID, InnerClassRedefiner.class); public static final Pattern ANON_INNER_CLASS_PATTERN = Pattern.compile(".*\\$\\d+.*"); public static final int METHOD_FINGERPRINT_EQUALS = 8; @@ -94,7 +91,7 @@ public HotSwapClassInfo[] matchAnonymousInnerClasses(List redefine RedefineInfo redefineInfo = it.next(); Symbol klassName = ParserKlassProvider.getClassName(context.getMeta(), context.getClassLoadingEnv().getParsingContext(), redefineInfo.getClassBytes()); if (handled.containsKey(klassName)) { - LOGGER.warning(() -> "Ignoring duplicate redefinition requests for name " + klassName); + ClassRedefinition.LOGGER.warning(() -> "Ignoring duplicate redefinition requests for name " + klassName); it.remove(); continue; } @@ -298,7 +295,7 @@ private void matchClassInfo(HotSwapClassInfo hotSwapInfo, List remo private static void addRenamingRule(Map, Symbol>> renamingRules, StaticObject classLoader, Symbol originalName, Symbol newName, EspressoContext context) { Map, Symbol> classLoaderRules = renamingRules.computeIfAbsent(classLoader, k -> new HashMap<>(4)); - context.getClassRedefinition().getController().fine(() -> "Renaming inner class: " + originalName + " to: " + newName); + ClassRedefinition.LOGGER.fine(() -> "Renaming inner class: " + originalName + " to: " + newName); assert classLoaderRules.getOrDefault(originalName, newName).equals(newName) : "rules already contain " + originalName + " -> " + classLoaderRules.get(originalName) + ", cannot map to " + newName; // add simple class names @@ -354,8 +351,7 @@ public void onKlassDefined(ObjectKlass objectKlass) { // classes for this loader and fill in the map List loadedKlasses = classRegistry.getLoadedKlasses(); for (Klass loadedKlass : loadedKlasses) { - if (loadedKlass instanceof ObjectKlass) { - ObjectKlass objectKlass = (ObjectKlass) loadedKlass; + if (loadedKlass instanceof ObjectKlass objectKlass) { Matcher matcher = ANON_INNER_CLASS_PATTERN.matcher(loadedKlass.getNameAsString()); if (matcher.matches()) { Symbol outerClassName = getOuterClassName(loadedKlass.getName()); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/plugins/impl/ExternalPluginHandler.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/plugins/impl/ExternalPluginHandler.java index c755081543b3..38898a1fb1ad 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/plugins/impl/ExternalPluginHandler.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/plugins/impl/ExternalPluginHandler.java @@ -28,8 +28,8 @@ import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.interop.UnsupportedTypeException; import com.oracle.truffle.espresso.impl.Klass; -import com.oracle.truffle.espresso.jdwp.impl.DebuggerController; import com.oracle.truffle.espresso.meta.Meta; +import com.oracle.truffle.espresso.redefinition.ClassRedefinition; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; final class ExternalPluginHandler { @@ -57,26 +57,26 @@ public static ExternalPluginHandler create(StaticObject guestHandler) throws Ill return new ExternalPluginHandler(guestHandler, library); } - public boolean shouldRerunClassInitializer(Klass klass, boolean changed, DebuggerController controller) { + public boolean shouldRerunClassInitializer(Klass klass, boolean changed) { try { return (boolean) interopLibrary.invokeMember(guestHandler, RERUN_CLINIT, klass.mirror(), changed); } catch (UnsupportedMessageException | UnknownIdentifierException | UnsupportedTypeException | ArityException e) { - controller.severe(() -> ExternalPluginHandler.class.getName() + ": shouldRerunClassInitializer: " + e.getMessage()); + klass.getContext().getLogger().severe(() -> ExternalPluginHandler.class.getName() + ": shouldRerunClassInitializer: " + e.getMessage()); } return false; } - public void postHotSwap(Klass[] changedKlasses, DebuggerController controller) { + public void postHotSwap(Klass[] changedKlasses) { + Meta meta = changedKlasses[0].getMeta(); try { StaticObject[] guestClasses = new StaticObject[changedKlasses.length]; for (int i = 0; i < guestClasses.length; i++) { guestClasses[i] = changedKlasses[i].mirror(); } - Meta meta = changedKlasses[0].getMeta(); StaticObject array = StaticObject.createArray(meta.java_lang_Class_array, guestClasses, meta.getContext()); interopLibrary.invokeMember(guestHandler, POST_HOTSWAP, array); } catch (UnsupportedMessageException | UnknownIdentifierException | UnsupportedTypeException | ArityException e) { - controller.severe(() -> ExternalPluginHandler.class.getName() + ": postHotSwap: " + e.getMessage()); + ClassRedefinition.LOGGER.severe(() -> ExternalPluginHandler.class.getName() + ": postHotSwap: " + e.getMessage()); } } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/plugins/impl/RedefineListener.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/plugins/impl/RedefineListener.java index 78f6bba3cd15..ee032a43799a 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/plugins/impl/RedefineListener.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/plugins/impl/RedefineListener.java @@ -26,12 +26,11 @@ import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.jdwp.api.RedefineInfo; -import com.oracle.truffle.espresso.jdwp.impl.DebuggerController; public interface RedefineListener { - boolean shouldRerunClassInitializer(ObjectKlass klass, boolean changed, DebuggerController controller); + boolean shouldRerunClassInitializer(ObjectKlass klass, boolean changed); - void postRedefinition(ObjectKlass[] changedKlasses, DebuggerController controller); + void postRedefinition(ObjectKlass[] changedKlasses); void collectExtraClassesToReload(List redefineInfos, List additional); } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/plugins/impl/RedefinitionPluginHandler.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/plugins/impl/RedefinitionPluginHandler.java index 6bf6d47b9a9a..a884589228e1 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/plugins/impl/RedefinitionPluginHandler.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/plugins/impl/RedefinitionPluginHandler.java @@ -35,7 +35,6 @@ import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.jdwp.api.RedefineInfo; -import com.oracle.truffle.espresso.jdwp.impl.DebuggerController; import com.oracle.truffle.espresso.redefinition.DefineKlassListener; import com.oracle.truffle.espresso.redefinition.plugins.api.ClassLoadAction; import com.oracle.truffle.espresso.redefinition.plugins.api.InternalRedefinitionPlugin; @@ -118,7 +117,7 @@ public void onKlassDefined(ObjectKlass klass) { } @Override - public boolean shouldRerunClassInitializer(ObjectKlass klass, boolean changed, DebuggerController controller) { + public boolean shouldRerunClassInitializer(ObjectKlass klass, boolean changed) { boolean rerun = false; // internal plugins for (InternalRedefinitionPlugin plugin : internalPlugins) { @@ -129,13 +128,13 @@ public boolean shouldRerunClassInitializer(ObjectKlass klass, boolean changed, D } // external plugins if (externalPluginHandler != null) { - rerun |= externalPluginHandler.shouldRerunClassInitializer(klass, changed, controller); + rerun |= externalPluginHandler.shouldRerunClassInitializer(klass, changed); } return rerun; } @Override - public void postRedefinition(ObjectKlass[] changedKlasses, DebuggerController controller) { + public void postRedefinition(ObjectKlass[] changedKlasses) { // internal plugins for (InternalRedefinitionPlugin plugin : internalPlugins) { try { @@ -147,7 +146,7 @@ public void postRedefinition(ObjectKlass[] changedKlasses, DebuggerController co } // external plugins if (externalPluginHandler != null) { - externalPluginHandler.postHotSwap(changedKlasses, controller); + externalPluginHandler.postHotSwap(changedKlasses); } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ref/FinalizationSupport.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ref/FinalizationSupport.java index d78015a0be56..3134b030e461 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ref/FinalizationSupport.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ref/FinalizationSupport.java @@ -33,9 +33,9 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.espresso.EspressoOptions; +import com.oracle.truffle.espresso.classfile.JavaVersion; import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.runtime.EspressoContext; -import com.oracle.truffle.espresso.classfile.JavaVersion; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.vm.UnsafeAccess; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/FieldAccessType.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/FieldAccessType.java deleted file mode 100644 index b8b13fb13886..000000000000 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/FieldAccessType.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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. - * - * 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.truffle.espresso.resolver; - -import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.espresso.classfile.Constants; -import com.oracle.truffle.espresso.classfile.bytecode.Bytecodes; -import com.oracle.truffle.espresso.meta.EspressoError; - -public enum FieldAccessType { - GetStatic(true, false), - PutStatic(true, true), - GetInstance(false, false), - PutInstance(false, true); - - private final boolean isStatic; - private final boolean isPut; - - FieldAccessType(boolean isStatic, boolean isPut) { - this.isStatic = isStatic; - this.isPut = isPut; - } - - public boolean isStatic() { - return isStatic; - } - - public boolean isPut() { - return isPut; - } - - public static FieldAccessType fromOpCode(int opcode) { - switch (opcode) { - case Bytecodes.GETSTATIC: - return GetStatic; - case Bytecodes.PUTSTATIC: - return PutStatic; - case Bytecodes.GETFIELD: - return GetInstance; - case Bytecodes.PUTFIELD: - return PutInstance; - default: - CompilerDirectives.transferToInterpreterAndInvalidate(); - throw EspressoError.shouldNotReachHere(Bytecodes.nameOf(opcode)); - } - } - - public static FieldAccessType fromRefKind(int refKind) { - switch (refKind) { - case Constants.REF_getField: - return GetInstance; - case Constants.REF_getStatic: - return GetStatic; - case Constants.REF_putField: - return PutInstance; - case Constants.REF_putStatic: - return PutStatic; - default: - CompilerDirectives.transferToInterpreterAndInvalidate(); - throw EspressoError.shouldNotReachHere("refkind: " + refKind); - } - } -} diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/Classpath.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/Classpath.java index d1a563bc1768..a4914c394c32 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/Classpath.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/Classpath.java @@ -34,11 +34,11 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import com.oracle.truffle.espresso.classfile.ClasspathEntry; +import com.oracle.truffle.espresso.classfile.ClasspathFile; import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; -import com.oracle.truffle.espresso.classfile.ClasspathEntry; -import com.oracle.truffle.espresso.classfile.ClasspathFile; import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.substitutions.JImageExtensions; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoContext.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoContext.java index 52373154ecff..d06aee56a8b5 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoContext.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoContext.java @@ -37,6 +37,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.WeakHashMap; @@ -87,9 +88,9 @@ import com.oracle.truffle.espresso.impl.Field; import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.Method; +import com.oracle.truffle.espresso.impl.ModuleTable; import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.jdwp.api.Ids; -import com.oracle.truffle.espresso.jdwp.impl.DebuggerController; import com.oracle.truffle.espresso.jni.JNIHandles; import com.oracle.truffle.espresso.jni.JniEnv; import com.oracle.truffle.espresso.meta.EspressoError; @@ -107,6 +108,8 @@ import com.oracle.truffle.espresso.runtime.panama.Platform; import com.oracle.truffle.espresso.runtime.panama.UpcallStubs; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; +import com.oracle.truffle.espresso.shared.meta.ErrorType; +import com.oracle.truffle.espresso.shared.meta.RuntimeAccess; import com.oracle.truffle.espresso.substitutions.Substitutions; import com.oracle.truffle.espresso.threads.ThreadsAccess; import com.oracle.truffle.espresso.vm.InterpreterToVM; @@ -115,7 +118,8 @@ import sun.misc.SignalHandler; -public final class EspressoContext { +public final class EspressoContext + implements RuntimeAccess { // MaxJavaStackTraceDepth is 1024 by default public static final int DEFAULT_STACK_SIZE = 32; @@ -418,7 +422,7 @@ private void spawnVM() throws ContextPatchingException { if (getJavaVersion().modulesEnabled()) { registries.initJavaBaseModule(); - registries.getBootClassRegistry().initUnnamedModule(StaticObject.NULL); + registries.getBootClassRegistry().initUnnamedModule(null); } initializeAgents(); @@ -1158,9 +1162,9 @@ public static EspressoContext get(Node node) { return REFERENCE.get(node); } - public synchronized ClassRedefinition createClassRedefinition(Ids ids, RedefinitionPluginHandler redefinitionPluginHandler, DebuggerController controller) { + public synchronized ClassRedefinition createClassRedefinition(Ids ids, RedefinitionPluginHandler redefinitionPluginHandler) { if (classRedefinition == null) { - classRedefinition = new ClassRedefinition(this, ids, redefinitionPluginHandler, controller); + classRedefinition = new ClassRedefinition(this, ids, redefinitionPluginHandler); } return classRedefinition; } @@ -1245,4 +1249,40 @@ public Path getEspressoLibs() { public Path getEspressoRuntime() { return EspressoLanguage.getEspressoRuntime(getEnv()); } + + public boolean isJavaBase(ModuleTable.ModuleEntry m) { + return m == getRegistries().getJavaBaseModule(); + } + + // RuntimeAccess impl + + @Override + @TruffleBoundary + public RuntimeException throwError(ErrorType error, String messageFormat, Object... args) { + ObjectKlass exType = errorTypeToExceptionKlass(error); + if (exType == null) { + throw fatal(messageFormat, args); + } + throw meta.throwExceptionWithMessage(exType, String.format(Locale.ENGLISH, messageFormat, args)); + } + + @Override + public RuntimeException fatal(String messageFormat, Object... args) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere(String.format(Locale.ENGLISH, messageFormat, args)); + } + + private ObjectKlass errorTypeToExceptionKlass(ErrorType errorType) { + switch (errorType) { + case IllegalAccessError: + return meta.java_lang_IllegalAccessError; + case NoSuchFieldError: + return meta.java_lang_NoSuchFieldError; + case NoSuchMethodError: + return meta.java_lang_NoSuchMethodError; + case IncompatibleClassChangeError: + return meta.java_lang_IncompatibleClassChangeError; + } + return null; + } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoLinkResolver.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoLinkResolver.java new file mode 100644 index 000000000000..4dcabb88de1f --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoLinkResolver.java @@ -0,0 +1,60 @@ +/* + * 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. + * + * 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.truffle.espresso.runtime; + +import com.oracle.truffle.espresso.classfile.descriptors.Symbol; +import com.oracle.truffle.espresso.impl.Field; +import com.oracle.truffle.espresso.impl.Klass; +import com.oracle.truffle.espresso.impl.Method; +import com.oracle.truffle.espresso.shared.resolver.CallSiteType; +import com.oracle.truffle.espresso.shared.resolver.FieldAccessType; +import com.oracle.truffle.espresso.shared.resolver.LinkResolver; +import com.oracle.truffle.espresso.shared.resolver.ResolvedCall; + +public final class EspressoLinkResolver { + public static Field resolveFieldSymbol(EspressoContext ctx, Klass accessingKlass, + Symbol name, Symbol type, Klass symbolicHolder, + boolean accessCheck, boolean loadingConstraints) { + return LinkResolver.resolveFieldSymbol(ctx, accessingKlass, name, type, symbolicHolder, accessCheck, loadingConstraints); + } + + public static Field resolveFieldAccess(EspressoContext ctx, Field symbolicResolution, FieldAccessType fieldAccessType, Klass currentKlass, Method currentMethod) { + return LinkResolver.resolveFieldAccess(ctx, symbolicResolution, fieldAccessType, currentKlass, currentMethod); + } + + public static Method resolveMethodSymbol(EspressoContext ctx, Klass accessingKlass, + Symbol name, Symbol signature, Klass symbolicHolder, + boolean interfaceLookup, + boolean accessCheck, boolean loadingConstraints) { + return LinkResolver.resolveMethodSymbol(ctx, accessingKlass, name, signature, symbolicHolder, interfaceLookup, accessCheck, loadingConstraints); + } + + public static ResolvedCall resolveCallSite(EspressoContext ctx, Klass currentKlass, Method symbolicResolution, CallSiteType callSiteType, Klass symbolicHolder) { + return LinkResolver.resolveCallSite(ctx, currentKlass, symbolicResolution, callSiteType, symbolicHolder); + } + + private EspressoLinkResolver() { + // no instance. + } +} diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoShutdownHandler.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoShutdownHandler.java index 41905d4e3b6d..b2c795a10afe 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoShutdownHandler.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoShutdownHandler.java @@ -32,9 +32,9 @@ import com.oracle.truffle.api.exception.AbstractTruffleException; import com.oracle.truffle.espresso.blocking.EspressoLock; import com.oracle.truffle.espresso.blocking.GuestInterruptedException; -import com.oracle.truffle.espresso.impl.ContextAccessImpl; import com.oracle.truffle.espresso.classfile.perf.DebugCloseable; import com.oracle.truffle.espresso.classfile.perf.DebugTimer; +import com.oracle.truffle.espresso.impl.ContextAccessImpl; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.threads.EspressoThreadRegistry; import com.oracle.truffle.espresso.threads.ThreadsAccess; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/GuestAllocator.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/GuestAllocator.java index ba72ac1dc32f..9b22fe3bb70b 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/GuestAllocator.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/GuestAllocator.java @@ -41,17 +41,17 @@ import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.classfile.JavaKind; +import com.oracle.truffle.espresso.classfile.tables.EntryTable; import com.oracle.truffle.espresso.impl.ArrayKlass; import com.oracle.truffle.espresso.impl.ClassRegistries; import com.oracle.truffle.espresso.impl.ContextAccessImpl; -import com.oracle.truffle.espresso.impl.EntryTable; import com.oracle.truffle.espresso.impl.Field; import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.LanguageAccess; import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.impl.PackageTable; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.substitutions.JavaType; @@ -412,7 +412,7 @@ private static StaticObject doCreateForeign(EspressoLanguage lang, Klass klass, @SuppressWarnings("try") private static void setModule(StaticObject obj, Klass klass) { StaticObject module = klass.module().module(); - if (StaticObject.isNull(module)) { + if (module == null) { // This can happen during initialization, before java.base is defined // This can be concurrent so we check whether java base is indeed defined or not // We use the bootloader's package table lock to deal with races between this code and @@ -435,6 +435,7 @@ private static void setModule(StaticObject obj, Klass klass) { } } else { + assert StaticObject.notNull(module); klass.getContext().getMeta().java_lang_Class_module.setObject(obj, module); } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/JDWPContextImpl.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/JDWPContextImpl.java index 4fc7960837e6..7409c009828e 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/JDWPContextImpl.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/JDWPContextImpl.java @@ -37,6 +37,7 @@ import com.oracle.truffle.api.ThreadLocalAction; import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.TruffleLogger; import com.oracle.truffle.api.debug.Debugger; import com.oracle.truffle.api.frame.Frame; import com.oracle.truffle.api.instrumentation.InstrumentableNode; @@ -57,7 +58,6 @@ import com.oracle.truffle.espresso.jdwp.api.FieldRef; import com.oracle.truffle.espresso.jdwp.api.Ids; import com.oracle.truffle.espresso.jdwp.api.JDWPContext; -import com.oracle.truffle.espresso.jdwp.api.JDWPSetup; import com.oracle.truffle.espresso.jdwp.api.KlassRef; import com.oracle.truffle.espresso.jdwp.api.MethodRef; import com.oracle.truffle.espresso.jdwp.api.ModuleRef; @@ -85,13 +85,14 @@ import com.oracle.truffle.espresso.vm.InterpreterToVM; public final class JDWPContextImpl implements JDWPContext { + + public static final TruffleLogger LOGGER = TruffleLogger.getLogger(EspressoLanguage.ID, JDWPContextImpl.class); private static final InteropLibrary UNCACHED = InteropLibrary.getUncached(); private static final long SUSPEND_TIMEOUT = 100; private final EspressoContext context; private final Ids ids; - private final JDWPSetup setup; private ClassRedefinition classRedefinition; private final InnerClassRedefiner innerClassRedefiner; private RedefinitionPluginHandler redefinitionPluginHandler; @@ -101,23 +102,21 @@ public final class JDWPContextImpl implements JDWPContext { public JDWPContextImpl(EspressoContext context) { this.context = context; this.ids = new Ids<>(StaticObject.NULL); - this.setup = new JDWPSetup(); this.innerClassRedefiner = new InnerClassRedefiner(context); } public void jdwpInit(TruffleLanguage.Env env, Object mainThread, VMEventListenerImpl vmEventListener) { Debugger debugger = env.lookup(env.getInstruments().get("debugger"), Debugger.class); this.controller = env.lookup(env.getInstruments().get(JDWPInstrument.ID), DebuggerController.class); - ids.injectController(controller); vmEventListener.activate(mainThread, controller, this); - setup.setup(debugger, controller, context.getEspressoEnv().JDWPOptions, this, mainThread, vmEventListener); + controller.initialize(debugger, context.getEspressoEnv().JDWPOptions, this, mainThread, vmEventListener); redefinitionPluginHandler = RedefinitionPluginHandler.create(context); - classRedefinition = context.createClassRedefinition(ids, redefinitionPluginHandler, controller); + classRedefinition = context.createClassRedefinition(ids, redefinitionPluginHandler); } public void finalizeContext() { if (context.getEspressoEnv().JDWPOptions != null) { - setup.finalizeSession(); + controller.disposeDebugger(false); } } @@ -671,7 +670,7 @@ public CallFrame locateObjectWaitFrame() { Object currentThread = asGuestThread(Thread.currentThread()); KlassRef klass = context.getMeta().java_lang_Object; MethodRef method = context.getMeta().java_lang_Object_wait.getMethodVersion(); - return new CallFrame(ids.getIdAsLong(currentThread), TypeTag.CLASS, ids.getIdAsLong(klass), method, ids.getIdAsLong(method), 0, null, null, null, null, null, controller); + return new CallFrame(ids.getIdAsLong(currentThread), TypeTag.CLASS, ids.getIdAsLong(klass), method, ids.getIdAsLong(method), 0, null, null, null, null, null, LOGGER); } @Override diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/JImageLibrary.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/JImageLibrary.java index d1939386e82b..fdd45895bc15 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/JImageLibrary.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/JImageLibrary.java @@ -33,6 +33,7 @@ import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.interop.UnsupportedTypeException; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; @@ -43,7 +44,6 @@ import com.oracle.truffle.espresso.impl.PackageTable.PackageEntry; import com.oracle.truffle.espresso.jni.RawBuffer; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; @SuppressWarnings("unused") final class JImageLibrary extends ContextAccessImpl { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/LazyContextCaches.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/LazyContextCaches.java index cba7df04a5e5..8bb2f0528713 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/LazyContextCaches.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/LazyContextCaches.java @@ -28,12 +28,12 @@ import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.RootNode; +import com.oracle.truffle.espresso.classfile.bytecode.VolatileArrayAccess; import com.oracle.truffle.espresso.impl.ContextAccessImpl; import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.meta.InteropKlassesDispatch; import com.oracle.truffle.espresso.nodes.commands.AddPathToBindingsCache; import com.oracle.truffle.espresso.nodes.commands.ReferenceProcessCache; -import com.oracle.truffle.espresso.classfile.bytecode.VolatileArrayAccess; import com.oracle.truffle.espresso.runtime.dispatch.messages.InteropMessage; import com.oracle.truffle.espresso.runtime.dispatch.messages.InteropMessageFactories; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/BaseInterop.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/BaseInterop.java index 313679098b1d..1a58544d8dbd 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/BaseInterop.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/BaseInterop.java @@ -23,6 +23,8 @@ package com.oracle.truffle.espresso.runtime.dispatch.staticobject; +import static com.oracle.truffle.espresso.vm.InterpreterToVM.instanceOf; + import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.dsl.Cached; @@ -38,15 +40,13 @@ import com.oracle.truffle.api.utilities.TriState; import com.oracle.truffle.espresso.EspressoLanguage; import com.oracle.truffle.espresso.impl.Klass; -import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.meta.EspressoError; +import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.runtime.dispatch.messages.GenerateInteropNodes; import com.oracle.truffle.espresso.runtime.dispatch.messages.Shareable; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.vm.VM; -import static com.oracle.truffle.espresso.vm.InterpreterToVM.instanceOf; - /** * BaseInterop (isNull, is/asString, meta-instance, identity, exceptions, toDisplayString) Support * Espresso and foreign objects and null. diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/EspressoInterop.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/EspressoInterop.java index b514ca696519..d49b98e607ad 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/EspressoInterop.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/EspressoInterop.java @@ -100,14 +100,14 @@ public static Meta getMeta() { } @ExportMessage - static boolean isBoolean(StaticObject receiver) { + public static boolean isBoolean(StaticObject receiver) { receiver.checkNotForeign(); assert !isNull(receiver) : "Null espresso object should be dispatched to BaseInterop"; return receiver.getKlass() == receiver.getKlass().getMeta().java_lang_Boolean; } @ExportMessage - static boolean asBoolean(StaticObject receiver) throws UnsupportedMessageException { + public static boolean asBoolean(StaticObject receiver) throws UnsupportedMessageException { receiver.checkNotForeign(); if (!isBoolean(receiver)) { throw UnsupportedMessageException.create(); @@ -128,7 +128,7 @@ static boolean isNumber(StaticObject receiver) { } @ExportMessage - static boolean fitsInByte(StaticObject receiver) { + public static boolean fitsInByte(StaticObject receiver) { receiver.checkNotForeign(); if (isNull(receiver)) { return false; @@ -163,7 +163,7 @@ static boolean fitsInByte(StaticObject receiver) { } @ExportMessage - static boolean fitsInShort(StaticObject receiver) { + public static boolean fitsInShort(StaticObject receiver) { receiver.checkNotForeign(); if (isNull(receiver)) { return false; @@ -194,7 +194,7 @@ static boolean fitsInShort(StaticObject receiver) { } @ExportMessage - static boolean fitsInInt(StaticObject receiver) { + public static boolean fitsInInt(StaticObject receiver) { receiver.checkNotForeign(); if (isNull(receiver)) { return false; @@ -221,7 +221,7 @@ static boolean fitsInInt(StaticObject receiver) { } @ExportMessage - static boolean fitsInLong(StaticObject receiver) { + public static boolean fitsInLong(StaticObject receiver) { receiver.checkNotForeign(); if (isNull(receiver)) { return false; @@ -244,7 +244,7 @@ static boolean fitsInLong(StaticObject receiver) { } @ExportMessage - static boolean fitsInFloat(StaticObject receiver) { + public static boolean fitsInFloat(StaticObject receiver) { receiver.checkNotForeign(); if (isNull(receiver)) { return false; @@ -278,7 +278,7 @@ static boolean fitsInFloat(StaticObject receiver) { } @ExportMessage - static boolean fitsInDouble(StaticObject receiver) { + public static boolean fitsInDouble(StaticObject receiver) { receiver.checkNotForeign(); if (isNull(receiver)) { return false; @@ -349,7 +349,7 @@ private static Number readNumberValue(StaticObject receiver) throws UnsupportedM } @ExportMessage - static byte asByte(StaticObject receiver) throws UnsupportedMessageException { + public static byte asByte(StaticObject receiver) throws UnsupportedMessageException { receiver.checkNotForeign(); if (!fitsInByte(receiver)) { CompilerDirectives.transferToInterpreter(); @@ -359,7 +359,7 @@ static byte asByte(StaticObject receiver) throws UnsupportedMessageException { } @ExportMessage - static short asShort(StaticObject receiver) throws UnsupportedMessageException { + public static short asShort(StaticObject receiver) throws UnsupportedMessageException { receiver.checkNotForeign(); if (!fitsInShort(receiver)) { CompilerDirectives.transferToInterpreter(); @@ -369,7 +369,7 @@ static short asShort(StaticObject receiver) throws UnsupportedMessageException { } @ExportMessage - static int asInt(StaticObject receiver) throws UnsupportedMessageException { + public static int asInt(StaticObject receiver) throws UnsupportedMessageException { receiver.checkNotForeign(); if (!fitsInInt(receiver)) { CompilerDirectives.transferToInterpreter(); @@ -379,7 +379,7 @@ static int asInt(StaticObject receiver) throws UnsupportedMessageException { } @ExportMessage - static long asLong(StaticObject receiver) throws UnsupportedMessageException { + public static long asLong(StaticObject receiver) throws UnsupportedMessageException { receiver.checkNotForeign(); if (!fitsInLong(receiver)) { CompilerDirectives.transferToInterpreter(); @@ -389,7 +389,7 @@ static long asLong(StaticObject receiver) throws UnsupportedMessageException { } @ExportMessage - static float asFloat(StaticObject receiver) throws UnsupportedMessageException { + public static float asFloat(StaticObject receiver) throws UnsupportedMessageException { receiver.checkNotForeign(); if (!fitsInFloat(receiver)) { CompilerDirectives.transferToInterpreter(); @@ -399,7 +399,7 @@ static float asFloat(StaticObject receiver) throws UnsupportedMessageException { } @ExportMessage - static double asDouble(StaticObject receiver) throws UnsupportedMessageException { + public static double asDouble(StaticObject receiver) throws UnsupportedMessageException { receiver.checkNotForeign(); if (!fitsInDouble(receiver)) { CompilerDirectives.transferToInterpreter(); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/InterruptedExceptionInterop.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/InterruptedExceptionInterop.java index 44973106c666..b2fcd2b754ca 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/InterruptedExceptionInterop.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/InterruptedExceptionInterop.java @@ -27,9 +27,9 @@ import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; -import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.runtime.dispatch.messages.GenerateInteropNodes; import com.oracle.truffle.espresso.runtime.dispatch.messages.Shareable; +import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; @GenerateInteropNodes @Shareable diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/IterableInterop.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/IterableInterop.java index b9e7ff6cf111..34e13d4376ec 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/IterableInterop.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/IterableInterop.java @@ -30,9 +30,9 @@ import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.nodes.interop.LookupAndInvokeKnownMethodNode; -import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.runtime.dispatch.messages.GenerateInteropNodes; import com.oracle.truffle.espresso.runtime.dispatch.messages.Shareable; +import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; @GenerateInteropNodes @ExportLibrary(value = InteropLibrary.class, receiverType = StaticObject.class) diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/IteratorInterop.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/IteratorInterop.java index f659ce08f06f..9591a2cacb15 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/IteratorInterop.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/IteratorInterop.java @@ -32,9 +32,9 @@ import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.nodes.interop.LookupAndInvokeKnownMethodNode; import com.oracle.truffle.espresso.runtime.EspressoException; -import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.runtime.dispatch.messages.GenerateInteropNodes; import com.oracle.truffle.espresso.runtime.dispatch.messages.Shareable; +import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.vm.InterpreterToVM; @GenerateInteropNodes diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/MapEntryInterop.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/MapEntryInterop.java index 72f591e4b907..2fb4a2e48509 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/MapEntryInterop.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/MapEntryInterop.java @@ -38,9 +38,9 @@ import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.nodes.interop.InvokeEspressoNode; -import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.runtime.dispatch.messages.GenerateInteropNodes; import com.oracle.truffle.espresso.runtime.dispatch.messages.Shareable; +import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; @GenerateInteropNodes @ExportLibrary(value = InteropLibrary.class, receiverType = StaticObject.class) diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/MapInterop.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/MapInterop.java index 266b10f0fc4d..9efe313b965b 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/MapInterop.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/MapInterop.java @@ -39,9 +39,9 @@ import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.nodes.interop.InvokeEspressoNode; -import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.runtime.dispatch.messages.GenerateInteropNodes; import com.oracle.truffle.espresso.runtime.dispatch.messages.Shareable; +import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.vm.InterpreterToVM; /** diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/ThrowableInterop.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/ThrowableInterop.java index 473bd868db08..2a73b68ea315 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/ThrowableInterop.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/ThrowableInterop.java @@ -31,9 +31,9 @@ import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.meta.Meta; -import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.runtime.dispatch.messages.GenerateInteropNodes; import com.oracle.truffle.espresso.runtime.dispatch.messages.Shareable; +import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; @GenerateInteropNodes @Shareable diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/jimage/ImageStringsReader.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/jimage/ImageStringsReader.java index d08d74cbd650..68c6c1f59991 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/jimage/ImageStringsReader.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/jimage/ImageStringsReader.java @@ -28,8 +28,8 @@ import java.util.Objects; import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; -import com.oracle.truffle.espresso.classfile.descriptors.Validation; import com.oracle.truffle.espresso.classfile.descriptors.ModifiedUtf8; +import com.oracle.truffle.espresso.classfile.descriptors.Validation; public class ImageStringsReader { public static final int HASH_MULTIPLIER = 0x01000193; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Inject.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Inject.java index c6f5e63e913a..24cfedeb566f 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Inject.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Inject.java @@ -23,15 +23,15 @@ package com.oracle.truffle.espresso.substitutions; -import com.oracle.truffle.espresso.EspressoLanguage; -import com.oracle.truffle.espresso.meta.Meta; -import com.oracle.truffle.espresso.runtime.EspressoContext; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.meta.Meta; +import com.oracle.truffle.espresso.runtime.EspressoContext; + /** * Hints that a parameter will be injected and is not part of the the Java method signature. * diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/JavaSubstitution.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/JavaSubstitution.java index 3457e0dc54f7..16f85664835c 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/JavaSubstitution.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/JavaSubstitution.java @@ -25,10 +25,10 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.espresso.classfile.JavaVersion; import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.nodes.quick.invoke.inline.InlinedFrameAccess; import com.oracle.truffle.espresso.nodes.quick.invoke.inline.InlinedMethodPredicate; -import com.oracle.truffle.espresso.classfile.JavaVersion; public abstract class JavaSubstitution extends SubstitutionProfiler { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Substitutions.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Substitutions.java index c698ec9e0c10..1eadf947a181 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Substitutions.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Substitutions.java @@ -30,9 +30,9 @@ import java.util.function.Supplier; import java.util.logging.Level; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import org.graalvm.collections.EconomicMap; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.TruffleLogger; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.nodes.Node; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_com_oracle_truffle_espresso_polyglot_Polyglot.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_com_oracle_truffle_espresso_polyglot_Polyglot.java index 10c596f412e7..5d2f2ec6ae1f 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_com_oracle_truffle_espresso_polyglot_Polyglot.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_com_oracle_truffle_espresso_polyglot_Polyglot.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -28,20 +28,15 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.dsl.Cached.Shared; -import com.oracle.truffle.api.dsl.Fallback; -import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.ReportPolymorphism; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.exception.AbstractTruffleException; import com.oracle.truffle.api.interop.InteropLibrary; -import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.interop.UnsupportedTypeException; -import com.oracle.truffle.api.library.CachedLibrary; -import com.oracle.truffle.api.profiles.BranchProfile; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.profiles.InlinedBranchProfile; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.espresso.EspressoLanguage; -import com.oracle.truffle.espresso.impl.ArrayKlass; import com.oracle.truffle.espresso.impl.EspressoType; import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.ObjectKlass; @@ -74,43 +69,71 @@ protected static InstanceOf createInstanceOf(Klass superType) { return InstanceOf.create(superType, true); } - @Specialization(guards = "targetClass.getMirrorKlass() == cachedTargetKlass", limit = "1") + static ToReference createToEspressoNode(EspressoType type, Meta meta) { + if (type.getRawType() instanceof PrimitiveKlass primitiveKlass) { + Klass boxedKlass = MethodArgsUtils.primitiveTypeToBoxedType(primitiveKlass); + assert boxedKlass != null; + return ToReference.createToReference(boxedKlass, meta); + } + return ToReference.createToReference(type, meta); + } + + @Specialization(guards = "targetClass.getMirrorKlass() == cachedTargetKlass", limit = "2") @JavaType(Object.class) - StaticObject doCached( + static StaticObject doCached( @JavaType(Class.class) StaticObject targetClass, @JavaType(Object.class) StaticObject value, - @Cached("targetClass.getMirrorKlass()") Klass cachedTargetKlass, - @Cached BranchProfile nullTargetClassProfile, - @Cached BranchProfile reWrappingProfile, + @Bind Node node, + @SuppressWarnings("unused") @Cached("targetClass.getMirrorKlass()") Klass cachedTargetKlass, + @Cached InlinedBranchProfile nullTargetClassProfile, + @Cached InlinedBranchProfile reWrappingProfile, + @Cached InlinedBranchProfile errorProfile, + @Bind("getMeta()") Meta meta, @Cached("createInstanceOf(cachedTargetKlass)") InstanceOf instanceOfTarget, - @Cached CastImpl castImpl) { + @Cached("createToEspressoNode(cachedTargetKlass, meta)") ToReference toEspresso) { if (StaticObject.isNull(targetClass)) { - nullTargetClassProfile.enter(); - Meta meta = getMeta(); + nullTargetClassProfile.enter(node); throw meta.throwException(meta.java_lang_NullPointerException); } - if (StaticObject.isNull(value) || (!value.isForeignObject() && instanceOfTarget.execute(value.getKlass()))) { + if (StaticObject.isNull(value) || instanceOfTarget.execute(value.getKlass())) { return value; } - reWrappingProfile.enter(); - return castImpl.execute(getContext(), cachedTargetKlass, value); + reWrappingProfile.enter(node); + try { + Object interopObject = value.isForeignObject() ? value.rawForeignObject(EspressoLanguage.get(node)) : value; + return toEspresso.execute(interopObject); + } catch (UnsupportedTypeException e) { + errorProfile.enter(node); + throw meta.throwExceptionWithMessage(meta.java_lang_ClassCastException, "%s cannot be cast to %s", value, targetClass.getMirrorKlass(meta).getTypeAsString()); + } } @ReportPolymorphism.Megamorphic @Specialization(replaces = "doCached") @JavaType(Object.class) - StaticObject doGeneric(@JavaType(Class.class) StaticObject targetClass, + static StaticObject doGeneric(@JavaType(Class.class) StaticObject targetClass, @JavaType(Object.class) StaticObject value, + @Bind Node node, @Cached InstanceOf.Dynamic instanceOfDynamic, - @Cached CastImpl castImpl) { - Meta meta = getMeta(); + @Cached ToReference.DynamicToReference toEspresso) { + Meta meta = EspressoContext.get(node).getMeta(); if (StaticObject.isNull(targetClass)) { throw meta.throwException(meta.java_lang_NullPointerException); } - if (StaticObject.isNull(value) || (!value.isForeignObject() && instanceOfDynamic.execute(value.getKlass(), targetClass.getMirrorKlass(meta)))) { + if (StaticObject.isNull(value) || instanceOfDynamic.execute(value.getKlass(), targetClass.getMirrorKlass(meta))) { return value; } - return castImpl.execute(getContext(), targetClass.getMirrorKlass(meta), value); + Klass mirrorKlass = targetClass.getMirrorKlass(meta); + try { + if (mirrorKlass instanceof PrimitiveKlass primitiveKlass) { + mirrorKlass = MethodArgsUtils.primitiveTypeToBoxedType(primitiveKlass); + assert mirrorKlass != null; + } + Object interopObject = value.isForeignObject() ? value.rawForeignObject(EspressoLanguage.get(node)) : value; + return toEspresso.execute(interopObject, mirrorKlass); + } catch (UnsupportedTypeException e) { + throw meta.throwExceptionWithMessage(meta.java_lang_ClassCastException, "%s cannot be cast to %s", value, targetClass.getMirrorKlass(meta).getTypeAsString()); + } } } @@ -136,27 +159,45 @@ protected static InstanceOf createInstanceOf(EspressoType superType) { @JavaType(Object.class) StaticObject value, @JavaType(internalName = "Lcom/oracle/truffle/espresso/polyglot/TypeLiteral;") StaticObject targetType); - @Specialization(guards = "getEspressoType(targetType, meta) == cachedTargetType", limit = "1") + @Specialization(guards = "getEspressoType(targetType, context.getMeta()) == cachedTargetType", limit = "2") @JavaType(Object.class) - StaticObject doCached( + static StaticObject doCached( @JavaType(Object.class) StaticObject value, @JavaType(internalName = "Lcom/oracle/truffle/espresso/polyglot/TypeLiteral;") StaticObject targetType, - @Bind("getMeta()") Meta meta, - @SuppressWarnings("unused") @Cached("getEspressoType(targetType, meta)") EspressoType cachedTargetType, + @Bind Node node, + @SuppressWarnings("unused") @Bind("get(node)") EspressoContext context, + @Cached InlinedBranchProfile nullTargetClassProfile, + @Cached InlinedBranchProfile reWrappingProfile, + @Cached InlinedBranchProfile errorProfile, + @SuppressWarnings("unused") @Cached("getEspressoType(targetType, context.getMeta())") EspressoType cachedTargetType, @Cached("createInstanceOf(cachedTargetType.getRawType())") InstanceOf instanceOfTarget, - @Cached("createToEspressoNode(cachedTargetType, meta)") ToReference toEspressoNode) { - assert !StaticObject.isNull(targetType); + @Cached("createToEspressoNode(cachedTargetType, context.getMeta())") ToReference toEspressoNode) { + Meta meta = context.getMeta(); + if (StaticObject.isNull(targetType)) { + nullTargetClassProfile.enter(node); + throw meta.throwException(meta.java_lang_NullPointerException); + } if (StaticObject.isNull(value) || (!value.isForeignObject() && instanceOfTarget.execute(value.getKlass()))) { return value; } + reWrappingProfile.enter(node); try { if (value.isForeignObject()) { - return toEspressoNode.execute(value.rawForeignObject(getLanguage())); + return toEspressoNode.execute(value.rawForeignObject(EspressoLanguage.get(node))); } else { // we know it's not instance of the target type, so throw CCE + errorProfile.enter(node); throw meta.throwExceptionWithMessage(meta.java_lang_ClassCastException, "%s cannot be cast to %s", value, targetType); } } catch (UnsupportedTypeException e) { + if (value.isForeignObject() && cachedTargetType.getRawType().isAbstract() && !cachedTargetType.getRawType().isArray()) { + // allow foreign objects of assignable type to be returned, but only after + // trying to convert to a guest value with ToEspresso + if (instanceOfTarget.execute(value.getKlass())) { + return value; + } + } + errorProfile.enter(node); throw meta.throwExceptionWithMessage(meta.java_lang_ClassCastException, "%s cannot be cast to %s", value, targetType); } } @@ -164,12 +205,13 @@ StaticObject doCached( @ReportPolymorphism.Megamorphic @Specialization(replaces = "doCached") @JavaType(Object.class) - StaticObject doGeneric( + static StaticObject doGeneric( @JavaType(Object.class) StaticObject value, @JavaType(internalName = "Lcom/oracle/truffle/espresso/polyglot/TypeLiteral;") StaticObject targetType, + @Bind Node node, @Cached InstanceOf.Dynamic instanceOfDynamic, @Cached ToReference.DynamicToReference toEspressoNode) { - Meta meta = getMeta(); + Meta meta = EspressoContext.get(node).getMeta(); if (StaticObject.isNull(targetType)) { throw meta.throwException(meta.java_lang_NullPointerException); } @@ -179,8 +221,15 @@ StaticObject doGeneric( EspressoType type = getEspressoType(targetType, meta); if (value.isForeignObject()) { try { - return toEspressoNode.execute(value.rawForeignObject(getLanguage()), type); + return toEspressoNode.execute(value.rawForeignObject(EspressoLanguage.get(node)), type); } catch (UnsupportedTypeException e) { + if (value.isForeignObject() && type.getRawType().isAbstract() && !type.getRawType().isArray()) { + // allow foreign objects of assignable type to be returned, but only after + // trying to convert to a guest value with ToEspresso + if (instanceOfDynamic.execute(value.getKlass(), type.getRawType())) { + return value; + } + } throw meta.throwExceptionWithMessage(meta.java_lang_ClassCastException, "%s cannot be cast to %s", value, type); } } else if (instanceOfDynamic.execute(value.getKlass(), type.getRawType())) { @@ -190,166 +239,6 @@ StaticObject doGeneric( } } } - - @GenerateUncached - abstract static class CastImpl extends SubstitutionNode { - - static final int LIMIT = 3; - - abstract @JavaType(Object.class) StaticObject execute(EspressoContext context, Klass targetKlass, @JavaType(Object.class) StaticObject value); - - static boolean isNull(StaticObject object) { - return StaticObject.isNull(object); - } - - static boolean isStringKlass(EspressoContext context, Klass targetKlass) { - return targetKlass == context.getMeta().java_lang_String; - } - - static boolean isForeignException(EspressoContext context, Klass targetKlass) { - Meta.PolyglotSupport polyglot = context.getMeta().polyglot; - return polyglot != null /* polyglot support is enabled */ - && targetKlass == polyglot.ForeignException; - } - - @Specialization(guards = "value.isEspressoObject()") - @JavaType(Object.class) - StaticObject doEspresso( - EspressoContext context, - Klass targetKlass, - @JavaType(Object.class) StaticObject value, - @Cached InstanceOf.Dynamic instanceOfDynamic, - @Cached BranchProfile exceptionProfile) { - if (isNull(value) || instanceOfDynamic.execute(value.getKlass(), targetKlass)) { - return value; - } - exceptionProfile.enter(); - Meta meta = context.getMeta(); - throw meta.throwException(meta.java_lang_ClassCastException); - } - - @Specialization(guards = "value.isForeignObject()") - @JavaType(Object.class) - StaticObject doPrimitive( - EspressoContext context, - PrimitiveKlass targetKlass, - @JavaType(Object.class) StaticObject value, - @Cached ToReference.DynamicToReference toEspresso, - @Cached BranchProfile exceptionProfile) { - Meta meta = context.getMeta(); - try { - Klass boxedKlass = MethodArgsUtils.primitiveTypeToBoxedType(targetKlass); - return toEspresso.execute(value.rawForeignObject(getLanguage()), boxedKlass); - } catch (UnsupportedTypeException e) { - exceptionProfile.enter(); - throw meta.throwExceptionWithMessage(meta.java_lang_ClassCastException, - "Couldn't read %s value from foreign object", targetKlass.getType()); - } - } - - @Specialization(guards = "value.isForeignObject()") - @JavaType(Object.class) - StaticObject doArray( - EspressoContext context, - ArrayKlass targetKlass, - @JavaType(Object.class) StaticObject value, - @Shared("value") @CachedLibrary(limit = "LIMIT") InteropLibrary interop, - @Cached BranchProfile exceptionProfile) { - Meta meta = context.getMeta(); - Object foreignObject = value.rawForeignObject(getLanguage()); - - // Array-like foreign objects can be wrapped as *[]. - // Buffer-like foreign objects can be wrapped (only) as byte[]. - if (interop.hasArrayElements(foreignObject) || (targetKlass == meta._byte_array && interop.hasBufferElements(foreignObject))) { - return StaticObject.createForeign(meta.getLanguage(), targetKlass, foreignObject, interop); - } - - exceptionProfile.enter(); - throw meta.throwExceptionWithMessage(meta.java_lang_ClassCastException, "Cannot cast non-array value to an array type"); - } - - @Specialization(guards = { - "isStringKlass(context, targetKlass)", - "value.isForeignObject()" - }) - @JavaType(Object.class) - StaticObject doString( - EspressoContext context, - @SuppressWarnings("unused") ObjectKlass targetKlass, - @JavaType(Object.class) StaticObject value, - @Shared("value") @CachedLibrary(limit = "LIMIT") InteropLibrary interop, - @Cached BranchProfile exceptionProfile) { - Meta meta = context.getMeta(); - try { - return meta.toGuestString(interop.asString(value.rawForeignObject(getLanguage()))); - } catch (UnsupportedMessageException e) { - exceptionProfile.enter(); - throw meta.throwExceptionWithMessage(meta.java_lang_ClassCastException, "Cannot cast a non-string foreign object to String"); - } - } - - @Specialization(guards = { - "isForeignException(context, targetKlass)", - "value.isForeignObject()" - }) - @JavaType(Object.class) - StaticObject doForeignException( - EspressoContext context, - @SuppressWarnings("unused") ObjectKlass targetKlass, - @JavaType(Object.class) StaticObject value, - @Shared("value") @CachedLibrary(limit = "LIMIT") InteropLibrary interop, - @Cached BranchProfile exceptionProfile) { - Meta meta = context.getMeta(); - // Casting to ForeignException skip the field checks. - Object foreignObject = value.rawForeignObject(getLanguage()); - if (interop.isException(foreignObject)) { - return StaticObject.createForeignException(context, foreignObject, interop); - } - exceptionProfile.enter(); - throw meta.throwExceptionWithMessage(meta.java_lang_ClassCastException, "Cannot cast a non-exception foreign object to ForeignException"); - } - - @Fallback - @JavaType(Object.class) - StaticObject doDataClassOrThrow( - EspressoContext context, - Klass targetKlass, - @JavaType(Object.class) StaticObject value, - @Shared("value") @CachedLibrary(limit = "LIMIT") InteropLibrary interop, - @Cached InstanceOf.Dynamic instanceOfDynamic, - @Cached ToReference.DynamicToReference toEspresso, - @Cached BranchProfile exceptionProfile) { - Meta meta = context.getMeta(); - if (targetKlass.isAbstract()) { - // allow foreign objects of assignable type to be returned - if (instanceOfDynamic.execute(value.getKlass(), targetKlass)) { - return value; - } - exceptionProfile.enter(); - throw meta.throwExceptionWithMessage(meta.java_lang_ClassCastException, "Invalid cast to non-array abstract class"); - } - assert targetKlass instanceof ObjectKlass; - assert value.isForeignObject(); - ObjectKlass targetObjectKlass = (ObjectKlass) targetKlass; - try { - if (meta.isBoxed(targetObjectKlass)) { - Object foreignObject = value.rawForeignObject(getLanguage()); - return StaticObject.createForeign(meta.getLanguage(), targetKlass, foreignObject, interop); - } else { - return toEspresso.execute(value.rawForeignObject(getLanguage()), targetKlass); - } - } catch (UnsupportedTypeException e) { - // allow foreign objects of assignable type to be returned, but only after trying to - // convert to a guest value with ToEspresso - if (instanceOfDynamic.execute(value.getKlass(), targetKlass)) { - return value; - } - exceptionProfile.enter(); - throw meta.throwExceptionWithMessage(meta.java_lang_ClassCastException, e.getMessage()); - } - } - } - // endregion Polyglot#cast @TruffleBoundary diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_Module.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_Module.java index 56dbc1c8a96e..52aa3d19a675 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_Module.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_Module.java @@ -80,8 +80,8 @@ public final class Target_java_lang_Module { @TruffleBoundary public static void defineModule0(@JavaType(internalName = "Ljava/lang/Module;") StaticObject module, boolean isOpen, - @SuppressWarnings("unused") @JavaType(String.class) StaticObject version, - @SuppressWarnings("unused") @JavaType(String.class) StaticObject location, + @JavaType(String.class) StaticObject version, + @JavaType(String.class) StaticObject location, @JavaType(Object[].class) StaticObject pns, @Inject EspressoLanguage language, @Inject Meta meta, @@ -100,13 +100,15 @@ public final class Target_java_lang_Module { throw meta.throwExceptionWithMessage(meta.java_lang_IllegalArgumentException, "module name cannot be null"); } String hostName = meta.toHostString(guestName); + String hostVersion = meta.toHostString(version); + String hostLocation = meta.toHostString(location); String[] packages = toStringArray(language, pns, meta); if (hostName.equals(JAVA_BASE)) { profiler.profile(5); - meta.getVM().defineJavaBaseModule(module, packages, profiler); + meta.getVM().defineJavaBaseModule(module, hostVersion, hostLocation, packages, profiler); } else { profiler.profile(6); - meta.getVM().defineModule(module, hostName, isOpen, packages, profiler); + meta.getVM().defineModule(module, hostName, hostVersion, hostLocation, isOpen, packages, profiler); } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_System.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_System.java index 05b8de3605fe..e34efc943c4f 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_System.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_System.java @@ -33,6 +33,7 @@ import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.interop.UnsupportedTypeException; import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.classfile.perf.DebugCounter; import com.oracle.truffle.espresso.impl.ArrayKlass; import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.ObjectKlass; @@ -42,7 +43,6 @@ import com.oracle.truffle.espresso.nodes.helper.TypeCheckNodeGen; import com.oracle.truffle.espresso.nodes.interop.ToEspressoNode; import com.oracle.truffle.espresso.nodes.interop.ToEspressoNodeFactory; -import com.oracle.truffle.espresso.classfile.perf.DebugCounter; import com.oracle.truffle.espresso.runtime.EspressoException; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.vm.VM; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_invoke_MethodHandleNatives.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_invoke_MethodHandleNatives.java index 7e0a2d95072c..69c92d0ab12b 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_invoke_MethodHandleNatives.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_invoke_MethodHandleNatives.java @@ -56,11 +56,8 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.dsl.Bind; -import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.classfile.bytecode.Bytecodes; import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; @@ -73,15 +70,15 @@ import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.meta.Meta; -import com.oracle.truffle.espresso.nodes.EspressoNode; -import com.oracle.truffle.espresso.resolver.CallSiteType; -import com.oracle.truffle.espresso.resolver.LinkResolver; -import com.oracle.truffle.espresso.resolver.ResolvedCall; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.EspressoException; +import com.oracle.truffle.espresso.runtime.EspressoLinkResolver; import com.oracle.truffle.espresso.runtime.MethodHandleIntrinsics; import com.oracle.truffle.espresso.runtime.MethodHandleIntrinsics.PolySigIntrinsics; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; +import com.oracle.truffle.espresso.shared.resolver.CallSiteType; +import com.oracle.truffle.espresso.shared.resolver.FieldAccessType; +import com.oracle.truffle.espresso.shared.resolver.ResolvedCall; @EspressoSubstitutions public final class Target_java_lang_invoke_MethodHandleNatives { @@ -321,209 +318,161 @@ public static int getConstant(int which) { return StaticObject.createArray(meta.java_lang_Object_array, result, meta.getContext()); } - @Substitution(methodName = "resolve") - abstract static class ResolveOverload8 extends EspressoNode { - - abstract @JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject execute( - @JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject self, - @JavaType(value = Class.class) StaticObject caller); - - @Specialization - @JavaType(internalName = "Ljava/lang/invoke/MemberName;") - StaticObject doCached( - @JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject self, - @JavaType(value = Class.class) StaticObject caller, - @Cached ResolveOverload11 resolve) { - return resolve.execute(self, caller, false); - } + @Substitution + public static @JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject resolve(@JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject self, + @JavaType(Class.class) StaticObject caller, + @Inject Meta meta) { + return resolve(self, caller, false, meta); } - @Substitution(methodName = "resolve") - abstract static class ResolveOverload11 extends EspressoNode { - - abstract @JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject execute( - @JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject self, - @JavaType(value = Class.class) StaticObject caller, - boolean speculativeResolve); - - @Specialization - @JavaType(internalName = "Ljava/lang/invoke/MemberName;") - StaticObject doCached( - @JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject self, - @JavaType(value = Class.class) StaticObject caller, - boolean speculativeResolve, - @Cached ResolveNode resolve) { - try { - return resolve.execute(self, caller, LM_UNCONDITIONAL); - } catch (EspressoException e) { - if (speculativeResolve) { - return StaticObject.NULL; - } - throw e; + @Substitution + public static @JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject resolve(@JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject self, + @JavaType(Class.class) StaticObject caller, boolean speculativeResolve, + @Inject Meta meta) { + try { + return resolve(self, caller, LM_UNCONDITIONAL, meta); + } catch (EspressoException e) { + if (speculativeResolve) { + return StaticObject.NULL; } + throw e; } } @Substitution(methodName = "resolve") - abstract static class ResolveOverload17 extends SubstitutionNode { - - abstract @JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject execute( - @JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject self, - @JavaType(value = Class.class) StaticObject caller, - int lookupMode, - boolean speculativeResolve); - - @Specialization - @JavaType(internalName = "Ljava/lang/invoke/MemberName;") - StaticObject doCached( - @JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject self, - @JavaType(value = Class.class) StaticObject caller, - int lookupMode, - boolean speculativeResolve, - @Cached ResolveNode resolve) { - EspressoException error = null; - try { - return resolve.execute(self, caller, lookupMode); - } catch (EspressoException e) { - error = e; - } - Meta meta = getMeta(); - int refKind = getRefKind(meta.java_lang_invoke_MemberName_flags.getInt(self)); - if (!isValidRefKind(refKind)) { - throw meta.throwExceptionWithMessage(meta.java_lang_InternalError, "obsolete MemberName format"); - } - if (!speculativeResolve) { - throw error; - } - return StaticObject.NULL; + public static @JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject resolve(@JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject self, + @JavaType(Class.class) StaticObject caller, int lookupMode, boolean speculativeResolve, @Inject Meta meta) { + EspressoException error; + try { + return resolve(self, caller, lookupMode, meta); + } catch (EspressoException e) { + error = e; } + int refKind = getRefKind(meta.java_lang_invoke_MemberName_flags.getInt(self)); + if (!isValidRefKind(refKind)) { + throw meta.throwExceptionWithMessage(meta.java_lang_InternalError, "obsolete MemberName format"); + } + if (!speculativeResolve) { + throw error; + } + return StaticObject.NULL; } - abstract static class ResolveNode extends SubstitutionNode { + @TruffleBoundary + private static StaticObject resolve(StaticObject memberName, @JavaType(Class.class) StaticObject guestCaller, int lookupMode, Meta meta) { + if (StaticObject.isNull(memberName)) { + throw meta.throwExceptionWithMessage(meta.java_lang_InternalError, "Member Name is null."); + } + // JDK code should have already checked that 'caller' has access to 'memberName.clazz'. - /** - * Complete resolution of a memberName, full with method lookup, flags overwriting and - * planting target. - * - * @param memberName The memberName to resolve - * @param caller the class that commands the resolution - * @return The resolved memberName. Note that it should be the same reference as self - */ - abstract @JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject execute( - @JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject memberName, - @JavaType(value = Class.class) StaticObject caller, - int lookupMode); - - @Specialization - @JavaType(internalName = "Ljava/lang/invoke/MemberName;") - StaticObject doCached( - @JavaType(internalName = "Ljava/lang/invoke/MemberName;") StaticObject memberName, - @JavaType(value = Class.class) StaticObject caller, - @SuppressWarnings("unused") int lookupMode, - @Bind("getMeta()") Meta meta, - @Cached BranchProfile isMethodProfile, - @Cached BranchProfile isFieldProfile, - @Cached BranchProfile isConstructorProfile, - @Cached BranchProfile iaeProfile, - @Cached BranchProfile internalErrorProfile, - @Cached BranchProfile linkageErrorProfile, - @Cached BranchProfile isHandleMethodProfile) { - if (StaticObject.isNull(memberName)) { - internalErrorProfile.enter(); - throw meta.throwExceptionWithMessage(meta.java_lang_InternalError, "Member Name is null."); - } - // JDK code should have already checked that 'caller' has access to 'memberName.clazz'. + if (meta.HIDDEN_VMTARGET.getHiddenObject(memberName) != null) { + return memberName; // Already planted + } + StaticObject clazz = meta.java_lang_invoke_MemberName_clazz.getObject(memberName); + StaticObject type = meta.java_lang_invoke_MemberName_type.getObject(memberName); + StaticObject guestName = meta.java_lang_invoke_MemberName_name.getObject(memberName); - if (meta.HIDDEN_VMTARGET.getHiddenObject(memberName) != null) { - return memberName; // Already planted - } - StaticObject clazz = meta.java_lang_invoke_MemberName_clazz.getObject(memberName); - StaticObject type = meta.java_lang_invoke_MemberName_type.getObject(memberName); - StaticObject guestName = meta.java_lang_invoke_MemberName_name.getObject(memberName); + if (StaticObject.isNull(guestName) || StaticObject.isNull(type) || StaticObject.isNull(clazz)) { + throw meta.throwExceptionWithMessage(meta.java_lang_IllegalArgumentException, "Nothing to resolve."); + } + // Extract resolution information from member name. + final int flags = meta.java_lang_invoke_MemberName_flags.getInt(memberName); + if (Integer.bitCount(flags & ALL_KINDS) != 1) { + // Ensure the flags field is not ill-formed. + throw meta.throwExceptionWithMessage(meta.java_lang_IllegalArgumentException, "Invalid MemberName flag format."); + } - if (StaticObject.isNull(guestName) || StaticObject.isNull(type) || StaticObject.isNull(clazz)) { - iaeProfile.enter(); - throw meta.throwExceptionWithMessage(meta.java_lang_IllegalArgumentException, "Nothing to resolve."); - } - // Extract resolution information from member name. - Klass resolutionKlass = clazz.getMirrorKlass(meta); - final int flags = meta.java_lang_invoke_MemberName_flags.getInt(memberName); - if (Integer.bitCount(flags & ALL_KINDS) != 1) { - // Ensure the flags field is not ill-formed. - iaeProfile.enter(); - throw meta.throwExceptionWithMessage(meta.java_lang_IllegalArgumentException, "Invalid MemberName flag format."); - } - ByteSequence desc = asSignature(type, meta); - Symbol name = lookupName(meta, meta.toHostString(guestName), (Constants.flagHas(flags, MN_IS_FIELD)) ? meta.java_lang_NoSuchFieldException : meta.java_lang_NoSuchMethodException); - ObjectKlass callerKlass = StaticObject.isNull(caller) ? null : (ObjectKlass) caller.getMirrorKlass(meta); - - boolean doAccessChecks = callerKlass != null; - boolean doConstraintsChecks = (callerKlass != null && ((lookupMode & LM_UNCONDITIONAL) == 0)); - int refKind = getRefKind(flags); - - if (Constants.flagHas(flags, MN_IS_FIELD)) { - isFieldProfile.enter(); - Symbol t = lookupType(meta, desc); - // Field member name resolution skips several checks: - // - Access checks - // - Static fields are accessed statically - // - Final fields and ref_put* - // These are done when needed by JDK code. - Field f = LinkResolver.resolveFieldSymbol(meta, callerKlass, name, t, resolutionKlass, false, doConstraintsChecks); - plantResolvedField(memberName, f, refKind, meta, meta.getLanguage()); - return memberName; + // Determine the holder klass + Klass resolutionKlass = clazz.getMirrorKlass(meta); + if (!(resolutionKlass instanceof ObjectKlass)) { + // Non-standard behavior: behave as HotSpot. + if (resolutionKlass.isArray()) { + resolutionKlass = meta.java_lang_Object; + } else if (resolutionKlass.isPrimitive()) { + throw defaultResolutionFailure(meta, flags); } + } - isMethodProfile.enter(); - if (Constants.flagHas(flags, MN_IS_CONSTRUCTOR)) { - isConstructorProfile.enter(); - if (name != Name._init_) { - linkageErrorProfile.enter(); - throw meta.throwException(meta.java_lang_LinkageError); - } - // Ignores refKind - refKind = REF_invokeSpecial; - } else if (!Constants.flagHas(flags, MN_IS_METHOD)) { - internalErrorProfile.enter(); - throw meta.throwExceptionWithMessage(meta.java_lang_InternalError, "Unrecognized MemberName format"); - } + // Determine caller klass + Klass callerKlass = StaticObject.isNull(guestCaller) ? null : guestCaller.getMirrorKlass(meta); + if (callerKlass != null && callerKlass.isPrimitive()) { + // HotSpot behavior: primitive caller klass skip checks. + callerKlass = null; + } - // Check if we got a polymorphic signature method, in which case we may need to force - // the creation of a new signature symbol. - PolySigIntrinsics mhMethodId = getPolysignatureIntrinsicID(isHandleMethodProfile, flags, resolutionKlass, refKind, name); + EspressoContext ctx = meta.getContext(); + ByteSequence desc = asSignature(type, meta); + Symbol name = lookupName(meta, meta.toHostString(guestName), (Constants.flagHas(flags, MN_IS_FIELD)) ? meta.java_lang_NoSuchFieldException : meta.java_lang_NoSuchMethodException); + + boolean doAccessChecks = callerKlass != null; + boolean doConstraintsChecks = (callerKlass != null && ((lookupMode & LM_UNCONDITIONAL) == 0)); + int refKind = getRefKind(flags); + + if (Constants.flagHas(flags, MN_IS_FIELD)) { + Symbol t = lookupType(meta, desc); + // Field member name resolution skips several checks: + // - Access checks + // - Static fields are accessed statically + // - Final fields and ref_put* + // These are done when needed by JDK code. + Field f = EspressoLinkResolver.resolveFieldSymbol(ctx, callerKlass, name, t, resolutionKlass, false, doConstraintsChecks); + plantResolvedField(memberName, f, refKind, meta, meta.getLanguage()); + return memberName; + } - if (mhMethodId == InvokeGeneric) { - // Can not resolve InvokeGeneric, as we would miss the invoker and appendix. - internalErrorProfile.enter(); - throw meta.throwException(meta.java_lang_InternalError); + if (Constants.flagHas(flags, MN_IS_CONSTRUCTOR)) { + if (name != Name._init_) { + throw meta.throwException(meta.java_lang_LinkageError); } + // Ignores refKind + refKind = REF_invokeSpecial; + } else if (!Constants.flagHas(flags, MN_IS_METHOD)) { + throw meta.throwExceptionWithMessage(meta.java_lang_InternalError, "Unrecognized MemberName format"); + } + + // Check if we got a polymorphic signature method, in which case we may need to force + // the creation of a new signature symbol. + PolySigIntrinsics mhMethodId = getPolysignatureIntrinsicID(flags, resolutionKlass, refKind, name); - Symbol sig = lookupSignature(meta, desc, mhMethodId); - Method m = LinkResolver.resolveMethodSymbol(meta, callerKlass, name, sig, resolutionKlass, resolutionKlass.isInterface(), doAccessChecks, doConstraintsChecks); - ResolvedCall resolvedCall = LinkResolver.resolveCallSite(meta, callerKlass, m, CallSiteType.fromRefKind(refKind), resolutionKlass); + if (mhMethodId == InvokeGeneric) { + // Can not resolve InvokeGeneric, as we would miss the invoker and appendix. + throw meta.throwException(meta.java_lang_InternalError); + } - plantResolvedMethod(memberName, resolvedCall, meta); + Symbol sig = lookupSignature(meta, desc, mhMethodId); + Method m = EspressoLinkResolver.resolveMethodSymbol(ctx, callerKlass, name, sig, resolutionKlass, resolutionKlass.isInterface(), doAccessChecks, doConstraintsChecks); + ResolvedCall resolvedCall = EspressoLinkResolver.resolveCallSite(ctx, callerKlass, m, SiteTypes.callSiteFromRefKind(refKind), resolutionKlass); - return memberName; + plantResolvedMethod(memberName, resolvedCall, meta); + + return memberName; + } + + private static RuntimeException defaultResolutionFailure(Meta meta, int flags) { + if (Constants.flagHas(flags, MN_IS_FIELD)) { + throw meta.throwExceptionWithMessage(meta.java_lang_NoSuchFieldError, "Field resolution failed"); + } else if (Constants.flagHas(flags, MN_IS_METHOD) || Constants.flagHas(flags, MN_IS_CONSTRUCTOR)) { + throw meta.throwExceptionWithMessage(meta.java_lang_NoSuchMethodError, "Method resolution failed"); + } else { + throw meta.throwExceptionWithMessage(meta.java_lang_LinkageError, "resolution failed"); } + } - private static PolySigIntrinsics getPolysignatureIntrinsicID(BranchProfile isHandleMethodProfile, int flags, Klass resolutionKlass, int refKind, Symbol name) { - PolySigIntrinsics mhMethodId = None; - if (Constants.flagHas(flags, MN_IS_METHOD) && - (resolutionKlass.getType() == Type.java_lang_invoke_MethodHandle || resolutionKlass.getType() == Type.java_lang_invoke_VarHandle)) { - isHandleMethodProfile.enter(); - if (refKind == REF_invokeVirtual || - refKind == REF_invokeSpecial || - refKind == REF_invokeStatic) { - PolySigIntrinsics iid = MethodHandleIntrinsics.getId(name, resolutionKlass); - if (iid != None && - ((refKind == REF_invokeStatic) == (iid.isStaticPolymorphicSignature()))) { - mhMethodId = iid; - } + private static PolySigIntrinsics getPolysignatureIntrinsicID(int flags, Klass resolutionKlass, int refKind, Symbol name) { + PolySigIntrinsics mhMethodId = None; + if (Constants.flagHas(flags, MN_IS_METHOD) && + (resolutionKlass.getType() == Type.java_lang_invoke_MethodHandle || resolutionKlass.getType() == Type.java_lang_invoke_VarHandle)) { + if (refKind == REF_invokeVirtual || + refKind == REF_invokeSpecial || + refKind == REF_invokeStatic) { + PolySigIntrinsics iid = MethodHandleIntrinsics.getId(name, resolutionKlass); + if (iid != None && + ((refKind == REF_invokeStatic) == (iid.isStaticPolymorphicSignature()))) { + mhMethodId = iid; } } - return mhMethodId; } + return mhMethodId; } @TruffleBoundary @@ -607,7 +556,7 @@ public static void plantResolvedMethod(StaticObject memberName, Method target, i plant(memberName, target, meta, methodFlags); } - public static void plantResolvedMethod(StaticObject memberName, ResolvedCall resolvedCall, Meta meta) { + public static void plantResolvedMethod(StaticObject memberName, ResolvedCall resolvedCall, Meta meta) { int methodFlags = getMethodFlags(resolvedCall); plant(memberName, resolvedCall.getResolvedMethod(), meta, methodFlags); } @@ -626,7 +575,7 @@ private static void plantResolvedField(StaticObject memberName, Field field, int meta.java_lang_invoke_MemberName_clazz.setObject(memberName, field.getDeclaringKlass().mirror()); } - private static int getMethodFlags(ResolvedCall call) { + private static int getMethodFlags(ResolvedCall call) { int flags = call.getResolvedMethod().getMethodModifiers(); if (call.getResolvedMethod().isCallerSensitive()) { flags |= MN_CALLER_SENSITIVE; @@ -712,6 +661,73 @@ public static boolean isValidRefKind(int flags) { // endregion Helper methods + public static final class SiteTypes { + public static CallSiteType callSiteFromOpCode(int opcode) { + switch (opcode) { + case Bytecodes.INVOKESTATIC: + return CallSiteType.Static; + case Bytecodes.INVOKESPECIAL: + return CallSiteType.Special; + case Bytecodes.INVOKEVIRTUAL: + return CallSiteType.Virtual; + case Bytecodes.INVOKEINTERFACE: + return CallSiteType.Interface; + default: + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere(Bytecodes.nameOf(opcode)); + } + } + + public static CallSiteType callSiteFromRefKind(int refKind) { + switch (refKind) { + case com.oracle.truffle.espresso.classfile.Constants.REF_invokeVirtual: + return CallSiteType.Virtual; + case com.oracle.truffle.espresso.classfile.Constants.REF_invokeStatic: + return CallSiteType.Static; + case com.oracle.truffle.espresso.classfile.Constants.REF_invokeSpecial: // fallthrough + case com.oracle.truffle.espresso.classfile.Constants.REF_newInvokeSpecial: + return CallSiteType.Special; + case com.oracle.truffle.espresso.classfile.Constants.REF_invokeInterface: + return CallSiteType.Interface; + default: + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere("refKind: " + refKind); + } + } + + public static FieldAccessType fieldAccessFromOpCode(int opcode) { + switch (opcode) { + case Bytecodes.GETSTATIC: + return FieldAccessType.GetStatic; + case Bytecodes.PUTSTATIC: + return FieldAccessType.PutStatic; + case Bytecodes.GETFIELD: + return FieldAccessType.GetInstance; + case Bytecodes.PUTFIELD: + return FieldAccessType.PutInstance; + default: + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere(Bytecodes.nameOf(opcode)); + } + } + + public static FieldAccessType fieldAccessFromRefKind(int refKind) { + switch (refKind) { + case com.oracle.truffle.espresso.classfile.Constants.REF_getField: + return FieldAccessType.GetInstance; + case com.oracle.truffle.espresso.classfile.Constants.REF_getStatic: + return FieldAccessType.GetStatic; + case com.oracle.truffle.espresso.classfile.Constants.REF_putField: + return FieldAccessType.PutInstance; + case com.oracle.truffle.espresso.classfile.Constants.REF_putStatic: + return FieldAccessType.PutStatic; + default: + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere("refkind: " + refKind); + } + } + } + /** * Compile-time constants go here. This collection exists not only for reference from clients, * but also for ensuring the VM and JDK agree on the values of these constants. JDK verifies diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_ref_Reference.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_ref_Reference.java index 569fc42f0d6e..511c507c750d 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_ref_Reference.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_ref_Reference.java @@ -29,9 +29,9 @@ import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.nodes.DirectCallNode; -import com.oracle.truffle.espresso.ref.FinalizationSupport; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.ref.EspressoReference; +import com.oracle.truffle.espresso.ref.FinalizationSupport; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.vm.InterpreterToVM; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_util_regex_Matcher.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_util_regex_Matcher.java index 45dc2d2dc408..0273744b70ca 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_util_regex_Matcher.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_util_regex_Matcher.java @@ -51,6 +51,7 @@ import com.oracle.truffle.api.profiles.InlinedBranchProfile; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.strings.TruffleString; +import com.oracle.truffle.espresso.classfile.JavaVersion; import com.oracle.truffle.espresso.impl.Field; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.impl.ObjectKlass; @@ -59,7 +60,6 @@ import com.oracle.truffle.espresso.nodes.EspressoInlineNode; import com.oracle.truffle.espresso.nodes.bytecodes.InvokeInterface; import com.oracle.truffle.espresso.runtime.EspressoContext; -import com.oracle.truffle.espresso.classfile.JavaVersion; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; @EspressoSubstitutions diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_jdk_internal_foreign_abi_NativeEntryPoint.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_jdk_internal_foreign_abi_NativeEntryPoint.java index 8982c3fa7938..93a5a7d8de2e 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_jdk_internal_foreign_abi_NativeEntryPoint.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_jdk_internal_foreign_abi_NativeEntryPoint.java @@ -28,8 +28,8 @@ import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.runtime.EspressoContext; -import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.runtime.panama.VMStorage; +import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; @EspressoSubstitutions public final class Target_jdk_internal_foreign_abi_NativeEntryPoint { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_jdk_internal_foreign_abi_UpcallLinker.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_jdk_internal_foreign_abi_UpcallLinker.java index 3983af2db29d..a529b4e7c2b8 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_jdk_internal_foreign_abi_UpcallLinker.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_jdk_internal_foreign_abi_UpcallLinker.java @@ -27,8 +27,8 @@ import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.runtime.EspressoContext; -import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.runtime.panama.VMStorage; +import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; @EspressoSubstitutions public final class Target_jdk_internal_foreign_abi_UpcallLinker { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_sun_misc_Unsafe.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_sun_misc_Unsafe.java index 99c4ec8c76ee..765e8050ebd2 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_sun_misc_Unsafe.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_sun_misc_Unsafe.java @@ -45,6 +45,8 @@ import com.oracle.truffle.espresso.EspressoLanguage; import com.oracle.truffle.espresso.blocking.EspressoLock; import com.oracle.truffle.espresso.blocking.GuestInterruptedException; +import com.oracle.truffle.espresso.classfile.JavaKind; +import com.oracle.truffle.espresso.classfile.JavaVersion; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.ffi.Buffer; @@ -56,14 +58,12 @@ import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.meta.MetaUtil; import com.oracle.truffle.espresso.nodes.EspressoInlineNode; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.EspressoException; import com.oracle.truffle.espresso.runtime.GuestAllocator; -import com.oracle.truffle.espresso.classfile.JavaVersion; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; import com.oracle.truffle.espresso.threads.State; import com.oracle.truffle.espresso.threads.Transition; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_sun_reflect_NativeMethodAccessorImpl.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_sun_reflect_NativeMethodAccessorImpl.java index 5055ce17a767..c19f8a2319af 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_sun_reflect_NativeMethodAccessorImpl.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_sun_reflect_NativeMethodAccessorImpl.java @@ -30,21 +30,22 @@ import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.interop.UnsupportedTypeException; import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.Signatures; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; +import com.oracle.truffle.espresso.impl.Field; import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.Method; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.nodes.interop.ToEspressoNode; -import com.oracle.truffle.espresso.resolver.CallKind; -import com.oracle.truffle.espresso.resolver.CallSiteType; -import com.oracle.truffle.espresso.resolver.LinkResolver; -import com.oracle.truffle.espresso.resolver.ResolvedCall; import com.oracle.truffle.espresso.runtime.EspressoException; +import com.oracle.truffle.espresso.runtime.EspressoLinkResolver; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; +import com.oracle.truffle.espresso.shared.resolver.CallKind; +import com.oracle.truffle.espresso.shared.resolver.CallSiteType; +import com.oracle.truffle.espresso.shared.resolver.ResolvedCall; /** * This substitution is merely for performance reasons, to avoid the deep-dive to native. libjava @@ -306,8 +307,8 @@ abstract static class Invoke0 extends SubstitutionNode { } else { callSiteType = CallSiteType.Virtual; } - ResolvedCall resolvedCall = LinkResolver.resolveCallSite( - meta, + ResolvedCall resolvedCall = EspressoLinkResolver.resolveCallSite( + meta.getContext(), null, // No current class. reflectedMethod, callSiteType, klass); @@ -326,7 +327,7 @@ abstract static class Invoke0 extends SubstitutionNode { Object result; try { - result = resolvedCall.call(adjustedArgs); + result = Method.call(resolvedCall, adjustedArgs); } catch (EspressoException e) { throw meta.throwExceptionWithCause(meta.java_lang_reflect_InvocationTargetException, e.getGuestException()); } @@ -342,7 +343,7 @@ abstract static class Invoke0 extends SubstitutionNode { return (StaticObject) result; } - private static Object[] makeArgs(ResolvedCall resolvedCall, StaticObject parameterTypes, + private static Object[] makeArgs(ResolvedCall resolvedCall, StaticObject parameterTypes, StaticObject receiver, StaticObject args, EspressoLanguage language, Meta meta, ToEspressoNode.DynamicToEspresso toEspressoNode) { boolean isForeignArray = args.isForeignObject(); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/MethodVerifier.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/MethodVerifier.java index 7102a500debe..bd5195fbfa86 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/MethodVerifier.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/MethodVerifier.java @@ -22,6 +22,17 @@ */ package com.oracle.truffle.espresso.verifier; +import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.CLASS; +import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INTERFACE_METHOD_REF; +import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Bogus; +import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Double; +import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Float; +import static com.oracle.truffle.espresso.classfile.Constants.ITEM_InitObject; +import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Integer; +import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Long; +import static com.oracle.truffle.espresso.classfile.Constants.ITEM_NewObject; +import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Null; +import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Object; import static com.oracle.truffle.espresso.classfile.bytecode.Bytecodes.AALOAD; import static com.oracle.truffle.espresso.classfile.bytecode.Bytecodes.AASTORE; import static com.oracle.truffle.espresso.classfile.bytecode.Bytecodes.ACONST_NULL; @@ -227,35 +238,24 @@ import static com.oracle.truffle.espresso.classfile.bytecode.Bytecodes.SWAP; import static com.oracle.truffle.espresso.classfile.bytecode.Bytecodes.TABLESWITCH; import static com.oracle.truffle.espresso.classfile.bytecode.Bytecodes.WIDE; -import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.CLASS; -import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INTERFACE_METHOD_REF; -import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Bogus; -import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Double; -import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Float; -import static com.oracle.truffle.espresso.classfile.Constants.ITEM_InitObject; -import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Integer; -import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Long; -import static com.oracle.truffle.espresso.classfile.Constants.ITEM_NewObject; -import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Null; -import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Object; import java.util.Arrays; import com.oracle.truffle.espresso.EspressoLanguage; import com.oracle.truffle.espresso.analysis.frame.EspressoFrameDescriptor; import com.oracle.truffle.espresso.analysis.frame.FrameType; +import com.oracle.truffle.espresso.classfile.ClassfileParser; +import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.ExceptionHandler; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.ParserException; +import com.oracle.truffle.espresso.classfile.attributes.CodeAttribute; +import com.oracle.truffle.espresso.classfile.attributes.StackMapTableAttribute; import com.oracle.truffle.espresso.classfile.bytecode.BytecodeLookupSwitch; import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; import com.oracle.truffle.espresso.classfile.bytecode.BytecodeSwitch; import com.oracle.truffle.espresso.classfile.bytecode.BytecodeTableSwitch; import com.oracle.truffle.espresso.classfile.bytecode.Bytecodes; -import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; -import com.oracle.truffle.espresso.classfile.ClassfileParser; -import com.oracle.truffle.espresso.classfile.ConstantPool; -import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; -import com.oracle.truffle.espresso.classfile.attributes.CodeAttribute; -import com.oracle.truffle.espresso.classfile.attributes.StackMapTableAttribute; import com.oracle.truffle.espresso.classfile.constantpool.ClassConstant; import com.oracle.truffle.espresso.classfile.constantpool.DynamicConstant; import com.oracle.truffle.espresso.classfile.constantpool.FieldRefConstant; @@ -270,16 +270,16 @@ import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; import com.oracle.truffle.espresso.classfile.descriptors.Validation; +import com.oracle.truffle.espresso.classfile.descriptors.ValidationException; +import com.oracle.truffle.espresso.classfile.perf.DebugCloseable; +import com.oracle.truffle.espresso.classfile.perf.DebugTimer; +import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; import com.oracle.truffle.espresso.impl.ContextAccess; import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.Member; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.ExceptionHandler; -import com.oracle.truffle.espresso.classfile.JavaKind; -import com.oracle.truffle.espresso.classfile.perf.DebugCloseable; -import com.oracle.truffle.espresso.classfile.perf.DebugTimer; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/Operand.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/Operand.java index 06dd7466fe71..62264df9b9a7 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/Operand.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/Operand.java @@ -30,11 +30,11 @@ import java.util.ArrayList; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.runtime.EspressoException; abstract class Operand { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/StackMapFrameParser.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/StackMapFrameParser.java index 1dd9ba23d833..85c8e738a38f 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/StackMapFrameParser.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/StackMapFrameParser.java @@ -37,10 +37,10 @@ import static com.oracle.truffle.espresso.verifier.MethodVerifier.failFormatNoFallback; import com.oracle.truffle.espresso.classfile.ClassfileStream; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.ParserException; import com.oracle.truffle.espresso.classfile.attributes.StackMapTableAttribute; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; public final class StackMapFrameParser { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/VerificationTypeInfo.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/VerificationTypeInfo.java index f170330552db..3dc2548ea5ab 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/VerificationTypeInfo.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/verifier/VerificationTypeInfo.java @@ -33,8 +33,8 @@ import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Null; import static com.oracle.truffle.espresso.classfile.Constants.ITEM_Object; -import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/Management.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/Management.java index bf1a0a95fd4e..a2a17cff9d85 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/Management.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/Management.java @@ -54,6 +54,7 @@ import com.oracle.truffle.api.interop.UnsupportedTypeException; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.ffi.NativeSignature; import com.oracle.truffle.espresso.ffi.NativeType; @@ -65,7 +66,6 @@ import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.jni.NativeEnv; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/ModulesHelperVM.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/ModulesHelperVM.java index 7145dd32ef01..4c891d68f732 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/ModulesHelperVM.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/ModulesHelperVM.java @@ -44,8 +44,8 @@ private static ModuleTable.ModuleEntry getModuleEntry(@JavaType(internalName = " return (ModuleTable.ModuleEntry) meta.HIDDEN_MODULE_ENTRY.getHiddenObject(module); } - private static PackageTable.PackageEntry getPackageEntry(ModuleTable.ModuleEntry fromModuleEntry, Symbol nameSymbol) { - return fromModuleEntry.registry().packages().lookup(nameSymbol); + private static PackageTable.PackageEntry getPackageEntry(ModuleTable.ModuleEntry fromModuleEntry, Symbol nameSymbol, Meta meta) { + return fromModuleEntry.registry(meta).packages().lookup(nameSymbol); } public static ModuleTable.ModuleEntry extractToModuleEntry(@JavaType(internalName = "Ljava/lang/Module") StaticObject toModule, Meta meta, @@ -85,7 +85,7 @@ public static PackageTable.PackageEntry extractPackageEntry(String pkg, ModuleTa PackageTable.PackageEntry packageEntry = null; Symbol nameSymbol = meta.getContext().getNames().lookup(pkg); if (nameSymbol != null) { - packageEntry = getPackageEntry(fromModuleEntry, nameSymbol); + packageEntry = getPackageEntry(fromModuleEntry, nameSymbol, meta); } if (packageEntry == null) { if (profiler != null) { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/VM.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/VM.java index c15063ff2023..510891b31057 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/VM.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/VM.java @@ -22,6 +22,48 @@ */ package com.oracle.truffle.espresso.vm; +import static com.oracle.truffle.espresso.classfile.Constants.ACC_ABSTRACT; +import static com.oracle.truffle.espresso.classfile.Constants.ACC_FINAL; +import static com.oracle.truffle.espresso.classfile.Constants.ACC_LAMBDA_FORM_COMPILED; +import static com.oracle.truffle.espresso.classfile.Constants.ACC_PUBLIC; +import static com.oracle.truffle.espresso.jni.JniEnv.JNI_EDETACHED; +import static com.oracle.truffle.espresso.jni.JniEnv.JNI_ERR; +import static com.oracle.truffle.espresso.jni.JniEnv.JNI_EVERSION; +import static com.oracle.truffle.espresso.jni.JniEnv.JNI_OK; +import static com.oracle.truffle.espresso.meta.EspressoError.cat; +import static com.oracle.truffle.espresso.runtime.Classpath.JAVA_BASE; +import static com.oracle.truffle.espresso.runtime.EspressoContext.DEFAULT_STACK_SIZE; +import static com.oracle.truffle.espresso.substitutions.Target_java_lang_invoke_MethodHandleNatives.Constants.ACCESS_VM_ANNOTATIONS; +import static com.oracle.truffle.espresso.substitutions.Target_java_lang_invoke_MethodHandleNatives.Constants.HIDDEN_CLASS; +import static com.oracle.truffle.espresso.substitutions.Target_java_lang_invoke_MethodHandleNatives.Constants.NESTMATE_CLASS; +import static com.oracle.truffle.espresso.substitutions.Target_java_lang_invoke_MethodHandleNatives.Constants.STRONG_LOADER_LINK; + +import java.io.File; +import java.lang.ref.Reference; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Parameter; +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.StringJoiner; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.IntFunction; +import java.util.logging.Level; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.options.OptionValues; + import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.RootCallTarget; @@ -43,7 +85,18 @@ import com.oracle.truffle.espresso.EspressoLanguage; import com.oracle.truffle.espresso.EspressoOptions; import com.oracle.truffle.espresso.blocking.GuestInterruptedException; -import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; +import com.oracle.truffle.espresso.classfile.ClasspathEntry; +import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.Constants; +import com.oracle.truffle.espresso.classfile.JavaKind; +import com.oracle.truffle.espresso.classfile.attributes.Attribute; +import com.oracle.truffle.espresso.classfile.attributes.EnclosingMethodAttribute; +import com.oracle.truffle.espresso.classfile.attributes.InnerClassesAttribute; +import com.oracle.truffle.espresso.classfile.attributes.MethodParametersAttribute; +import com.oracle.truffle.espresso.classfile.attributes.PermittedSubclassesAttribute; +import com.oracle.truffle.espresso.classfile.attributes.RecordAttribute; +import com.oracle.truffle.espresso.classfile.attributes.SignatureAttribute; +import com.oracle.truffle.espresso.classfile.constantpool.NameAndTypeConstant; import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; @@ -51,14 +104,16 @@ import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; import com.oracle.truffle.espresso.classfile.descriptors.Validation; +import com.oracle.truffle.espresso.classfile.tables.EntryTable; +import com.oracle.truffle.espresso.constantpool.RuntimeConstantPool; import com.oracle.truffle.espresso.ffi.NativeSignature; import com.oracle.truffle.espresso.ffi.NativeType; import com.oracle.truffle.espresso.ffi.Pointer; import com.oracle.truffle.espresso.ffi.RawPointer; import com.oracle.truffle.espresso.ffi.nfi.NativeUtils; import com.oracle.truffle.espresso.impl.ArrayKlass; +import com.oracle.truffle.espresso.impl.BootClassRegistry; import com.oracle.truffle.espresso.impl.ClassRegistry; -import com.oracle.truffle.espresso.impl.EntryTable; import com.oracle.truffle.espresso.impl.EspressoClassLoadingException; import com.oracle.truffle.espresso.impl.Field; import com.oracle.truffle.espresso.impl.Klass; @@ -74,25 +129,13 @@ import com.oracle.truffle.espresso.jni.NativeEnv; import com.oracle.truffle.espresso.jni.NoSafepoint; import com.oracle.truffle.espresso.jvmti.JVMTI; +import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.meta.MetaUtil; import com.oracle.truffle.espresso.nodes.EspressoFrame; import com.oracle.truffle.espresso.nodes.EspressoRootNode; import com.oracle.truffle.espresso.nodes.interop.ToEspressoNode; import com.oracle.truffle.espresso.nodes.interop.ToEspressoNodeFactory; -import com.oracle.truffle.espresso.classfile.ClasspathEntry; -import com.oracle.truffle.espresso.classfile.ConstantPool; -import com.oracle.truffle.espresso.classfile.Constants; -import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; -import com.oracle.truffle.espresso.classfile.attributes.Attribute; -import com.oracle.truffle.espresso.classfile.attributes.EnclosingMethodAttribute; -import com.oracle.truffle.espresso.classfile.attributes.InnerClassesAttribute; -import com.oracle.truffle.espresso.classfile.attributes.MethodParametersAttribute; -import com.oracle.truffle.espresso.classfile.attributes.PermittedSubclassesAttribute; -import com.oracle.truffle.espresso.classfile.attributes.RecordAttribute; -import com.oracle.truffle.espresso.classfile.attributes.SignatureAttribute; -import com.oracle.truffle.espresso.classfile.constantpool.NameAndTypeConstant; import com.oracle.truffle.espresso.ref.EspressoReference; import com.oracle.truffle.espresso.runtime.Classpath; import com.oracle.truffle.espresso.runtime.EspressoContext; @@ -118,47 +161,6 @@ import com.oracle.truffle.espresso.vm.structs.JavaVMAttachArgs; import com.oracle.truffle.espresso.vm.structs.Structs; import com.oracle.truffle.espresso.vm.structs.StructsAccess; -import org.graalvm.collections.EconomicMap; -import org.graalvm.options.OptionValues; - -import java.io.File; -import java.lang.ref.Reference; -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Parameter; -import java.nio.ByteBuffer; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.StringJoiner; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicLongFieldUpdater; -import java.util.function.IntFunction; -import java.util.logging.Level; - -import static com.oracle.truffle.espresso.jni.JniEnv.JNI_EDETACHED; -import static com.oracle.truffle.espresso.jni.JniEnv.JNI_ERR; -import static com.oracle.truffle.espresso.jni.JniEnv.JNI_EVERSION; -import static com.oracle.truffle.espresso.jni.JniEnv.JNI_OK; -import static com.oracle.truffle.espresso.classfile.Constants.ACC_ABSTRACT; -import static com.oracle.truffle.espresso.classfile.Constants.ACC_FINAL; -import static com.oracle.truffle.espresso.classfile.Constants.ACC_LAMBDA_FORM_COMPILED; -import static com.oracle.truffle.espresso.classfile.Constants.ACC_PUBLIC; -import static com.oracle.truffle.espresso.meta.EspressoError.cat; -import static com.oracle.truffle.espresso.runtime.Classpath.JAVA_BASE; -import static com.oracle.truffle.espresso.runtime.EspressoContext.DEFAULT_STACK_SIZE; -import static com.oracle.truffle.espresso.substitutions.Target_java_lang_invoke_MethodHandleNatives.Constants.ACCESS_VM_ANNOTATIONS; -import static com.oracle.truffle.espresso.substitutions.Target_java_lang_invoke_MethodHandleNatives.Constants.HIDDEN_CLASS; -import static com.oracle.truffle.espresso.substitutions.Target_java_lang_invoke_MethodHandleNatives.Constants.NESTMATE_CLASS; -import static com.oracle.truffle.espresso.substitutions.Target_java_lang_invoke_MethodHandleNatives.Constants.STRONG_LOADER_LINK; /** * Espresso implementation of the VM interface (libjvm). @@ -530,23 +532,38 @@ public static void JVM_GC() { @VmImpl(isJni = true) @TruffleBoundary - public @JavaType(String.class) StaticObject JVM_GetSystemPackage(@JavaType(String.class) StaticObject name) { - String hostPkgName = getMeta().toHostString(name); - if (hostPkgName.endsWith("/")) { - hostPkgName = hostPkgName.substring(0, hostPkgName.length() - 1); + public @JavaType(String.class) StaticObject JVM_GetSystemPackage(@JavaType(String.class) StaticObject name, @Inject Meta meta) { + if (StaticObject.isNull(name)) { + throw meta.throwNullPointerException(); } - String fileName = getRegistries().getBootClassRegistry().getPackagePath(hostPkgName); - return getMeta().toGuestString(fileName); + ByteSequence pkg = meta.toByteSequence(name); + if (pkg.length() > 0 && pkg.byteAt(pkg.length() - 1) == '/') { + pkg = pkg.subSequence(0, pkg.length() - 1); + } + Symbol pkgName = getNames().lookup(pkg); + if (pkgName == null) { + return StaticObject.NULL; + } + BootClassRegistry bootClassRegistry = getRegistries().getBootClassRegistry(); + PackageEntry packageEntry = bootClassRegistry.packages().lookup(pkgName); + ModuleEntry moduleEntry = packageEntry.module(); + if (moduleEntry != null) { + String location = moduleEntry.location(); + if (location != null) { + return meta.toGuestString(location); + } + } + return meta.toGuestString(packageEntry.getBootClasspathLocation()); } @VmImpl(isJni = true) - public @JavaType(String[].class) StaticObject JVM_GetSystemPackages() { - String[] packages = getRegistries().getBootClassRegistry().getPackages(); - StaticObject[] array = new StaticObject[packages.length]; - for (int i = 0; i < packages.length; i++) { - array[i] = getMeta().toGuestString(packages[i]); + public @JavaType(String[].class) StaticObject JVM_GetSystemPackages(@Inject Meta meta) { + Symbol[] packageSymbols = getRegistries().getBootClassRegistry().getPackages(); + StaticObject[] array = new StaticObject[packageSymbols.length]; + for (int i = 0; i < packageSymbols.length; i++) { + array[i] = meta.toGuestString(packageSymbols[i]); } - return StaticObject.createArray(getMeta().java_lang_String.getArrayClass(), array, getContext()); + return StaticObject.createArray(meta.java_lang_String.getArrayClass(), array, getContext()); } @VmImpl @@ -615,7 +632,7 @@ private static Object readForeignArrayElement(StaticObject array, int index, Int return interop.readArrayElement(array.rawForeignObject(language), index); } catch (UnsupportedMessageException e) { profiler.profile(exceptionBranch); - throw meta.throwExceptionWithMessage(meta.getMeta().java_lang_ClassCastException, "The foreign object is not a readable array"); + throw meta.throwExceptionWithMessage(meta.java_lang_ClassCastException, "The foreign object is not a readable array"); } catch (InvalidArrayIndexException e) { profiler.profile(exceptionBranch); throw meta.throwExceptionWithMessage(meta.java_lang_CloneNotSupportedException, "Foreign array length changed during clone"); @@ -1065,35 +1082,30 @@ private static Klass computeEnclosingClass(ObjectKlass klass) { if (innerClasses == null) { return null; } - RuntimeConstantPool pool = klass.getConstantPool(); - boolean found = false; - Klass outerKlass = null; + // TODO(peterssen): Follow HotSpot implementation described below. + // Throws an exception if outer klass has not declared k as an inner klass + // We need evidence that each klass knows about the other, or else + // the system could allow a spoof of an inner class to gain access rights. for (InnerClassesAttribute.Entry entry : innerClasses.entries()) { if (entry.innerClassIndex != 0) { Symbol innerDescriptor = pool.classAt(entry.innerClassIndex).getName(pool); - // Check decriptors/names before resolving. if (innerDescriptor.equals(klass.getName())) { Klass innerKlass = pool.resolvedKlassAt(klass, entry.innerClassIndex); - found = (innerKlass == klass); - if (found && entry.outerClassIndex != 0) { - outerKlass = pool.resolvedKlassAt(klass, entry.outerClassIndex); + if (innerKlass == klass) { + if (entry.outerClassIndex != 0) { + return pool.resolvedKlassAt(klass, entry.outerClassIndex); + } else { + return null; + } } } } - if (found) { - break; - } } - - // TODO(peterssen): Follow HotSpot implementation described below. - // Throws an exception if outer klass has not declared k as an inner klass - // We need evidence that each klass knows about the other, or else - // the system could allow a spoof of an inner class to gain access rights. - return outerKlass; + return null; } @VmImpl(isJni = true) @@ -3438,9 +3450,9 @@ public int JNI_GetCreatedJavaVMs(@Pointer TruffleObject vmBufPtr, int bufLen, @P @VmImpl(isJni = true) @TruffleBoundary public void JVM_DefineModule(@JavaType(internalName = "Ljava/lang/Module;") StaticObject module, - boolean is_open, - @SuppressWarnings("unused") @JavaType(String.class) StaticObject version, - @SuppressWarnings("unused") @JavaType(String.class) StaticObject location, + boolean isOpen, + @JavaType(String.class) StaticObject version, + @JavaType(String.class) StaticObject location, @Pointer TruffleObject pkgs, int num_package, @Inject Meta meta, @@ -3469,19 +3481,23 @@ public int JNI_GetCreatedJavaVMs(@Pointer TruffleObject vmBufPtr, int bufLen, @P } String hostName = meta.toHostString(guestName); + String hostVersion = meta.toHostString(version); + String hostLocation = meta.toHostString(location); if (hostName.equals(JAVA_BASE)) { profiler.profile(5); - defineJavaBaseModule(module, extractNativePackages(pkgs, num_package, profiler), profiler); + defineJavaBaseModule(module, hostVersion, hostLocation, extractNativePackages(pkgs, num_package, profiler), profiler); return; } profiler.profile(6); - defineModule(module, hostName, is_open, extractNativePackages(pkgs, num_package, profiler), profiler); + defineModule(module, hostName, hostVersion, hostLocation, isOpen, extractNativePackages(pkgs, num_package, profiler), profiler); } @SuppressWarnings("try") public void defineModule(StaticObject module, String moduleName, - boolean is_open, + String moduleVersion, + String moduleLocation, + boolean isOpen, String[] packages, SubstitutionProfiler profiler) { Meta meta = getMeta(); @@ -3540,7 +3556,7 @@ public void defineModule(StaticObject module, } Symbol moduleSymbol = getNames().getOrCreate(moduleName); // Try define module - ModuleEntry moduleEntry = moduleTable.createAndAddEntry(moduleSymbol, registry, is_open, module); + ModuleEntry moduleEntry = moduleTable.createAndAddEntry(moduleSymbol, moduleVersion, moduleLocation, isOpen, module); if (moduleEntry == null) { // Module already defined profiler.profile(12); @@ -3592,7 +3608,7 @@ private String[] extractNativePackages(TruffleObject pkgs, int numPackages, Subs } @SuppressWarnings("try") - public void defineJavaBaseModule(StaticObject module, String[] packages, SubstitutionProfiler profiler) { + public void defineJavaBaseModule(StaticObject module, String moduleVersion, String moduleLocation, String[] packages, SubstitutionProfiler profiler) { Meta meta = getMeta(); StaticObject loader = meta.java_lang_Module_loader.getObject(module); if (!StaticObject.isNull(loader)) { @@ -3614,6 +3630,7 @@ public void defineJavaBaseModule(StaticObject module, String[] packages, Substit } } javaBaseEntry.setModule(module); + javaBaseEntry.setVersionAndLocation(moduleVersion, moduleLocation); meta.HIDDEN_MODULE_ENTRY.setHiddenObject(module, javaBaseEntry); getRegistries().processFixupList(module); } @@ -3945,7 +3962,9 @@ public static void fillInElement(@JavaType(StackTraceElement.class) StaticObject // Fill in module if (module.isNamed()) { meta.java_lang_StackTraceElement_moduleName.setObject(ste, meta.toGuestString(module.getName())); - // TODO: module version + if (module.version() != null) { + meta.java_lang_StackTraceElement_moduleVersion.setObject(ste, meta.toGuestString(module.version())); + } } } } else { // foreign frame diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/continuation/HostFrameRecord.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/continuation/HostFrameRecord.java index b7c5e888d307..34e0517a5743 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/continuation/HostFrameRecord.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/continuation/HostFrameRecord.java @@ -31,9 +31,9 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.espresso.EspressoLanguage; import com.oracle.truffle.espresso.analysis.frame.EspressoFrameDescriptor; +import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; import com.oracle.truffle.espresso.classfile.bytecode.Bytecodes; -import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.constantpool.MethodRefConstant; import com.oracle.truffle.espresso.classfile.descriptors.Signatures; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/npe/Analysis.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/npe/Analysis.java index a925a10ad418..fb294d58c657 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/npe/Analysis.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/npe/Analysis.java @@ -230,10 +230,11 @@ import java.util.ArrayList; import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.ExceptionHandler; import com.oracle.truffle.espresso.classfile.bytecode.BytecodeStream; import com.oracle.truffle.espresso.classfile.bytecode.BytecodeSwitch; import com.oracle.truffle.espresso.classfile.bytecode.Bytecodes; -import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.descriptors.Signatures; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Name; @@ -241,7 +242,6 @@ import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.impl.LanguageAccess; import com.oracle.truffle.espresso.impl.Method; -import com.oracle.truffle.espresso.classfile.ExceptionHandler; final class Analysis implements LanguageAccess { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/npe/MessageBuildHelper.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/npe/MessageBuildHelper.java index 3ea869955d41..2c1aa17fdda1 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/npe/MessageBuildHelper.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/npe/MessageBuildHelper.java @@ -71,10 +71,10 @@ import static com.oracle.truffle.espresso.classfile.bytecode.Bytecodes.SASTORE; import static com.oracle.truffle.espresso.classfile.bytecode.Bytecodes.SIPUSH; -import com.oracle.truffle.espresso.classfile.bytecode.Bytecodes; import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.classfile.attributes.Local; import com.oracle.truffle.espresso.classfile.attributes.LocalVariableTable; +import com.oracle.truffle.espresso.classfile.bytecode.Bytecodes; import com.oracle.truffle.espresso.classfile.constantpool.FieldRefConstant; import com.oracle.truffle.espresso.classfile.constantpool.MethodRefConstant; import com.oracle.truffle.espresso.classfile.descriptors.Signatures; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/npe/StackType.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/npe/StackType.java index d75c7add9c61..c38982ed8b01 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/npe/StackType.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/npe/StackType.java @@ -137,11 +137,11 @@ import static com.oracle.truffle.espresso.classfile.bytecode.Bytecodes.SIPUSH; import com.oracle.truffle.espresso.classfile.ConstantPool; +import com.oracle.truffle.espresso.classfile.JavaKind; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; import com.oracle.truffle.espresso.classfile.descriptors.Symbol.Type; import com.oracle.truffle.espresso.classfile.descriptors.Types; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.JavaKind; enum StackType { VOID(0), diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/structs/StructsAccess.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/structs/StructsAccess.java index 6484084b444a..764291de04ae 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/structs/StructsAccess.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/structs/StructsAccess.java @@ -28,14 +28,14 @@ import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.interop.UnsupportedTypeException; +import com.oracle.truffle.espresso.classfile.perf.DebugCloseable; +import com.oracle.truffle.espresso.classfile.perf.DebugTimer; import com.oracle.truffle.espresso.ffi.Callback; import com.oracle.truffle.espresso.ffi.NativeSignature; import com.oracle.truffle.espresso.ffi.NativeType; import com.oracle.truffle.espresso.ffi.Pointer; import com.oracle.truffle.espresso.ffi.RawPointer; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.classfile.perf.DebugCloseable; -import com.oracle.truffle.espresso.classfile.perf.DebugTimer; import com.oracle.truffle.espresso.runtime.EspressoContext; public final class StructsAccess { diff --git a/graal-common.json b/graal-common.json index c3c8b3ac2468..ade540aa0fd7 100644 --- a/graal-common.json +++ b/graal-common.json @@ -1,6 +1,6 @@ { "README": "This file contains definitions that are useful for the jsonnet CI files of the graal and graal-enterprise repositories.", "ci": { - "overlay": "60f18370ddbc4715fcdbd6cd917b7d80f78ad9d0" + "overlay": "1ccfff0314b5dded71da964c0db7d512f33dfa39" } } diff --git a/regex/CHANGELOG.md b/regex/CHANGELOG.md index ebee071496d2..33460fc5f57a 100644 --- a/regex/CHANGELOG.md +++ b/regex/CHANGELOG.md @@ -2,6 +2,10 @@ This changelog summarizes major changes between TRegex versions relevant to language implementors integrating TRegex into their language. This document will focus on API changes relevant to integrators of TRegex. +## Version 24.2.0 + +* Implemented the [Regular Expression Pattern Modifiers](https://github.com/tc39/proposal-regexp-modifiers) proposal for ECMAScript regular expressions. + ## Version 24.0.0 * Added support for atomic groups and possessive quantifiers in Python regular expressions. diff --git a/regex/mx.regex/suite.py b/regex/mx.regex/suite.py index 0892ea2d5b35..6d10d646e0af 100644 --- a/regex/mx.regex/suite.py +++ b/regex/mx.regex/suite.py @@ -43,7 +43,7 @@ "name" : "regex", - "version" : "24.2.0", + "version" : "25.0.0", "release" : False, "groupId" : "org.graalvm.regex", "url" : "http://www.graalvm.org/", @@ -92,9 +92,6 @@ "jdk.unsupported", # sun.misc.Unsafe ], "annotationProcessors" : ["truffle:TRUFFLE_DSL_PROCESSOR"], - "exports" : [ - "com.oracle.truffle.regex.chardata", - ], "checkstyleVersion" : "10.7.0", "javaCompliance" : "17+", "workingSets" : "Truffle,Regex", @@ -137,6 +134,9 @@ "TREGEX" : { "moduleInfo" : { "name" : "com.oracle.truffle.regex", + "exports": [ + "com.oracle.truffle.regex.chardata", + ], "requires" : [ "java.logging", "jdk.unsupported", # sun.misc.Unsafe diff --git a/regex/src/com.oracle.truffle.regex.test.dummylang/src/com/oracle/truffle/regex/test/dummylang/TRegexTestDummyLanguage.java b/regex/src/com.oracle.truffle.regex.test.dummylang/src/com/oracle/truffle/regex/test/dummylang/TRegexTestDummyLanguage.java index a707a003ee7f..ae8a2434b807 100644 --- a/regex/src/com.oracle.truffle.regex.test.dummylang/src/com/oracle/truffle/regex/test/dummylang/TRegexTestDummyLanguage.java +++ b/regex/src/com.oracle.truffle.regex.test.dummylang/src/com/oracle/truffle/regex/test/dummylang/TRegexTestDummyLanguage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -59,6 +59,7 @@ import com.oracle.truffle.api.strings.TruffleString; import com.oracle.truffle.regex.RegexLanguage; import com.oracle.truffle.regex.RegexObject; +import com.oracle.truffle.regex.RegexSyntaxException; @TruffleLanguage.Registration(name = TRegexTestDummyLanguage.NAME, id = TRegexTestDummyLanguage.ID, characterMimeTypes = TRegexTestDummyLanguage.MIME_TYPE, version = "0.1", dependentLanguages = RegexLanguage.ID) public class TRegexTestDummyLanguage extends TruffleLanguage { @@ -111,8 +112,12 @@ public Object execute(VirtualFrame frame) { } }.getCallTarget(); } - return DummyLanguageContext.get(null).getEnv().parseInternal( - Source.newBuilder(RegexLanguage.ID, src, parsingRequest.getSource().getName()).internal(true).build()); + try { + return DummyLanguageContext.get(null).getEnv().parseInternal( + Source.newBuilder(RegexLanguage.ID, src, parsingRequest.getSource().getName()).internal(true).build()); + } catch (RegexSyntaxException e) { + throw e.withErrorCodeInMessage(); + } } @GenerateInline diff --git a/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonFlagsTest.java b/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonFlagsTest.java index 08e98bd50142..baf33a7c89d0 100644 --- a/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonFlagsTest.java +++ b/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonFlagsTest.java @@ -57,7 +57,6 @@ public void testParseFlags() { assertTrue(parse("i").isIgnoreCase()); assertTrue(parse("m").isMultiLine()); assertTrue(parse("s").isDotAll()); - assertTrue(parse("t").isTemplate()); assertTrue(parse("u").isUnicodeExplicitlySet()); assertTrue(parse("x").isVerbose()); } diff --git a/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/InputStringGeneratorTests.java b/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/InputStringGeneratorTests.java index 67650b767f0c..eff8ed3cd0ed 100644 --- a/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/InputStringGeneratorTests.java +++ b/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/InputStringGeneratorTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -83,28 +83,29 @@ public void testBenchmarkRegexes() { testInputStringGenerator( "([-!#-''*+/-9=?A-Z^-~]+(\\.[-!#-''*+/-9=?A-Z^-~]+)*|\"([ ]!#-[^-~ ]|(\\\\[-~ ]))+\")@[0-9A-Za-z]([0-9A-Za-z-]*[0-9A-Za-z])?(\\.[0-9A-Za-z]([0-9A-Za-z-]*[0-9A-Za-z])?)+"); testInputStringGenerator("(\\S+) (\\S+) (\\S+) \\[([A-Za-z0-9_:/]+\\s[-+]\\d{4})\\] \"(\\S+)\\s?(\\S+)?\\s?(\\S+)?\" (\\d{3}|-) (\\d+|-)\\s?\"?([^\"]*)\"?\\s?\"?([^\"]*)?\"?"); + testInputStringGenerator("(?<=(a))\\1"); } - private TruffleString generateInputString(String pattern, String flags, String options, Encodings.Encoding encoding) { + private TruffleString generateInputString(String pattern, String flags, String options, Encodings.Encoding encoding, long rngSeed) { String sourceString = createSourceString(pattern, flags, options, encoding); Source source = Source.newBuilder("regex", sourceString, "regexSource").build(); RegexSource regexSource = RegexLanguage.createRegexSource(source); RegexAST ast = regexSource.getOptions().getFlavor().createParser(language, regexSource, new CompilationBuffer(regexSource.getEncoding())).parse(); - return InputStringGenerator.generate(ast, rng.nextLong()); + return InputStringGenerator.generate(ast, rngSeed); } void testInputStringGenerator(String pattern) { - testInputStringGenerator(pattern, "", getEngineOptions(), getTRegexEncoding()); + testInputStringGenerator(pattern, "", getEngineOptions(), getTRegexEncoding(), rng.nextLong()); } - void testInputStringGenerator(String pattern, String flags, String options, Encodings.Encoding encoding) { + void testInputStringGenerator(String pattern, String flags, String options, Encodings.Encoding encoding, long rngSeed) { Value compiledRegex = compileRegex(pattern, flags); - testInputStringGenerator(pattern, flags, options, encoding, compiledRegex); + testInputStringGenerator(pattern, flags, options, encoding, rngSeed, compiledRegex); } - private void testInputStringGenerator(String pattern, String flags, String options, Encodings.Encoding encoding, Value compiledRegex) { + private void testInputStringGenerator(String pattern, String flags, String options, Encodings.Encoding encoding, long rngSeed, Value compiledRegex) { for (int i = 0; i < 20; i++) { - TruffleString input = generateInputString(pattern, flags, options, encoding); + TruffleString input = generateInputString(pattern, flags, options, encoding, rngSeed); Assert.assertNotNull(input); Value result = execRegex(compiledRegex, encoding, input, 0); Assert.assertTrue(result.getMember("isMatch").asBoolean()); diff --git a/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/JavaUtilPatternTests.java b/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/JavaUtilPatternTests.java index 34d18193ba48..c306f5bd09e0 100644 --- a/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/JavaUtilPatternTests.java +++ b/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/JavaUtilPatternTests.java @@ -40,11 +40,14 @@ */ package com.oracle.truffle.regex.tregex.test; -import com.oracle.truffle.regex.charset.Range; -import com.oracle.truffle.regex.tregex.parser.CaseFoldData; -import com.oracle.truffle.regex.tregex.parser.flavors.java.JavaFlags; -import com.oracle.truffle.regex.tregex.string.Encodings; -import com.oracle.truffle.regex.util.EmptyArrays; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import java.util.stream.Stream; + import org.graalvm.collections.Pair; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.PolyglotException; @@ -53,13 +56,12 @@ import org.junit.Ignore; import org.junit.Test; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; -import java.util.stream.Stream; +import com.oracle.truffle.regex.RegexSyntaxException.ErrorCode; +import com.oracle.truffle.regex.charset.Range; +import com.oracle.truffle.regex.tregex.parser.CaseFoldData; +import com.oracle.truffle.regex.tregex.parser.flavors.java.JavaFlags; +import com.oracle.truffle.regex.tregex.string.Encodings; +import com.oracle.truffle.regex.util.EmptyArrays; public class JavaUtilPatternTests extends RegexTestBase { @@ -163,6 +165,8 @@ public void documentationSummary() { // Boundary matchers test("^", 0, ""); test("$", 0, ""); + test("$", 0, "empty"); + test("\\Z", 0, "\r\n"); test("\\b", 0, " a", 1); // test("\\b{g}", 0, ""); test("\\B", 0, "b"); @@ -1263,6 +1267,112 @@ public void caseFolding() { }); } + @Test + public void generatedTests() { + /* GENERATED CODE BEGIN - KEEP THIS MARKER FOR AUTOMATIC UPDATES */ + + // Generated using Java version 24 + test("((A|){7,10}?){10,17}", "", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 0, true, 0, 86, 86, 86, 86, 86); + test("(a{1,30}){1,4}", "", "a", 0, true, 0, 1, 0, 1); + test("((a|){4,6}){4,6}", "", "aaaaaaa", 0, true, 0, 7, 7, 7, 7, 7); + test("((a?){4,6}){4,6}", "", "aaaaaaa", 0, true, 0, 7, 7, 7, 7, 7); + test("((|a){4,6}){4,6}", "", "aaaaaaa", 0, true, 0, 0, 0, 0, 0, 0); + test("((a??){4,6}){4,6}", "", "aaaaaaa", 0, true, 0, 0, 0, 0, 0, 0); + test("((a?){4,6}){4,6}", "", "aaaaaa", 0, true, 0, 6, 6, 6, 6, 6); + test("(a|^){100}", "", "a", 0, true, 0, 0, 0, 0); + test("(a|^){100}", "", "aa", 0, true, 0, 0, 0, 0); + test("(a|^){100}", "", "aa", 1, false); + test("(a|^){100}", "", "ab", 1, false); + test("(.)\\1{2,}", "", "billiam", 0, false); + test("(^_(a{1,2}[:])*a{1,2}[:]a{1,2}([.]a{1,4})?_)+", "", "_a:a:a.aaa_", 0, true, 0, 11, 0, 11, 1, 3, 6, 10); + test("(a{2}|())+$", "", "aaaa", 0, true, 0, 4, 4, 4, 4, 4); + test("^a(b*)\\1{4,6}?", "", "abbbb", 0, true, 0, 1, 1, 1); + test("^a(b*)\\1{4,6}?", "", "abbbbb", 0, true, 0, 6, 1, 2); + test("(?<=|$)", "", "a", 0, true, 0, 0); + test("(?=ab)a", "", "ab", 0, true, 0, 1); + test("(?=()|^)|x", "", "empty", 0, true, 0, 0, 0, 0); + test("a(?<=ba)", "", "ba", 0, true, 1, 2); + test("(?<=(?=|()))", "", "aa", 0, true, 0, 0, -1, -1); + test("\\d\\W", "iv", "4\u017f", 0, true, 0, 2); + test("[\u08bc-\ucf3a]", "iv", "\u03b0", 0, false); + test("a(?:|()\\1){1,2}", "", "a", 0, true, 0, 1, -1, -1); + expectSyntaxError("|(?<\\d\\1)\ub7e4", "", "", getTRegexEncoding(), "error", 0, ErrorCode.InvalidNamedGroup); + test("[a-z][a-z\u2028\u2029].|ab(?<=[a-z]w.)", "", "aac", 0, true, 0, 3); + test("(animation|animation-name)", "", "animation", 0, true, 0, 9, 0, 9); + test("(a|){7,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(a|){7,7}?b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(|a){7,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(|a){7,7}?b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(a||b){7,7}c", "", "aaabc", 0, true, 0, 5, 4, 4); + test("(a||b){7,7}c", "", "aaac", 0, true, 0, 4, 3, 3); + test("(a||b){7,7}c", "", "aaabac", 0, true, 0, 6, 5, 5); + test("($|a){7,7}", "", "aaa", 0, true, 0, 3, 3, 3); + test("($|a){7,7}?", "", "aaa", 0, true, 0, 3, 3, 3); + test("(a|$){7,7}", "", "aaa", 0, true, 0, 3, 3, 3); + test("(a|$){7,7}?", "", "aaa", 0, true, 0, 3, 3, 3); + test("(a|$|b){7,7}", "", "aaab", 0, true, 0, 4, 4, 4); + test("(a|$|b){7,7}", "", "aaa", 0, true, 0, 3, 3, 3); + test("(a|$|b){7,7}", "", "aaaba", 0, true, 0, 5, 5, 5); + test("((?=a)|a){7,7}b", "", "aaa", 0, false); + test("((?=[ab])|a){7,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("((?<=a)|a){7,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("a((?<=a)|a){7,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(a|){0,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(a|){0,7}?b", "", "aaab", 0, true, 0, 4, 2, 3); + test("(|a){0,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(|a){0,7}?b", "", "aaab", 0, true, 0, 4, 2, 3); + test("(a||b){0,7}c", "", "aaabc", 0, true, 0, 5, 4, 4); + test("(a||b){0,7}c", "", "aaac", 0, true, 0, 4, 3, 3); + test("(a||b){0,7}c", "", "aaabac", 0, true, 0, 6, 5, 5); + test("((?=a)|a){0,7}b", "", "aaab", 0, true, 0, 4, 2, 3); + test("((?=[ab])|a){0,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("((?<=a)|a){0,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("a((?<=a)|a){0,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(a*?){11,11}?b", "", "aaaaaaaaaaaaaaaaaaaaaaaaab", 0, true, 0, 26, 10, 25); + test("(?:a(b{0,19})c)", "", "abbbbbbbcdebbbbbbbf", 0, true, 0, 9, 1, 8); + test("(?:a(b{0,19})c)de", "", "abbbbbbbcdebbbbbbbf", 0, true, 0, 11, 1, 8); + test("(?<=a(b{0,19})c)de", "", "abbbbbbbcdebbbbbbbf", 0, true, 9, 11, 1, 8); + test("[\ud0d9](?<=\\S)", "", "\ud0d9", 0, true, 0, 1); + test("[\ud0d9](?<=\\W)", "", "\ud0d9", 0, true, 0, 1); + test("\u0895(?<=\\S)", "", "\u0895", 0, true, 0, 1); + test("\u0895(?<=\\W)", "", "\u0895", 0, true, 0, 1); + test("[\u8053](?<=\\S)", "", "\u8053", 0, true, 0, 1); + test("[\u8053](?<=\\W)", "", "\u8053", 0, true, 0, 1); + test("\u0895(?<=\\S)", "", "\u0895", 0, true, 0, 1); + test("\u0895(?<=\\W)", "", "\u0895", 0, true, 0, 1); + test("\u0895|[\u8053\ud0d9]+(?<=\\S\\W\\S)", "", "\ud0d9\ud0d9\ud0d9\ud0d9", 0, true, 0, 4); + test("a|[bc]+(?<=[abc][abcd][abc])", "", "bbbb", 0, true, 0, 4); + test("a(b*)*c\\1d", "", "abbbbcbbd", 0, true, 0, 9, 3, 5); + test("(|a)||b(?<=cde)|", "", "a", 0, true, 0, 0, 0, 0); + test("^(\\1)?\\D*", "s", "empty", 0, true, 0, 5, -1, -1); + test("abcd(?<=d|c()d)", "", "_abcd", 0, true, 1, 5, -1, -1); + test("\\Dw\u3aa7\\A\\S(?<=\ue3b3|\\A()\\S)", "", "\udad1\udcfaw\u3aa7A\ue3b3", 0, false); + test("a(?:c|b(?=()))*", "", "abc", 0, true, 0, 3, 2, 2); + test("a(?:c|b(?=(c)))*", "", "abc", 0, true, 0, 3, 2, 3); + test("a(?:c|(?<=(a))b)*", "", "abc", 0, true, 0, 3, 0, 1); + test("(a||b){15,18}c", "", "ababaabbaaac", 0, true, 0, 12, 11, 11); + test("(a||b){15,18}?c", "", "ababaabbaaac", 0, true, 0, 12, 11, 11); + test("(?:ab|c|^){103,104}", "", "abcababccabccabababccabcababcccccabcababababccccabcabcabccabcabcccabababccabababcababababccababccabcababcabcabccabababccccabcab", 0, true, 0, 0); + test("((?<=a)bec)*d", "", "abecd", 0, true, 1, 5, 1, 4); + test("(|(^|\\z){2,77}?)?", "", "empty", 0, true, 0, 0, 0, 0, -1, -1); + test("a(|a{15,36}){10,11}", "", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 0, true, 0, 1, 1, 1); + test("a(|a{15,36}?){10,11}", "", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 0, true, 0, 1, 1, 1); + test("a(|a{15,36}){10,11}$", "", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 0, true, 0, 66, 66, 66); + test("a(|a{15,36}?){10,11}b$", "", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab", 0, true, 0, 67, 66, 66); + test("(?:a()|b??){22,26}c", "", "aabbbaabaaaaaabaaaac", 0, true, 0, 20, 19, 19); + test("b()(a\\1|){4,4}\\2c", "", "baaaac", 0, false); + test("a((?=b()|)[a-d])+", "", "abbbcbd", 0, true, 0, 7, 6, 7, 6, 6); + test("a(?=b(?<=ab)()|)", "", "ab", 0, true, 0, 1, 2, 2); + test("[ab]*?$(?<=[^b][ab][^b])", "", "aaaaaa", 0, true, 0, 6); + test("([ab]+){0,5}", "", "bbbba", 0, true, 0, 5, 0, 5); + test("[--a]", "v", "empty", 0, false); + test("(?:^\\1|$){10,11}bc", "", "aaaaaabc", 0, false); + test("a(?:|[0-9]+?a|[0-9a]){11,13}?[ab]", "", "a372a466a109585878b", 0, true, 0, 19); + test("\\Z", "", "\r\n", 0, true, 0, 0); + + /* GENERATED CODE END - KEEP THIS MARKER FOR AUTOMATIC UPDATES */ + } + void test(String pattern, int flags, String input) { test(pattern, flags, input, 0); } diff --git a/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/JsTests.java b/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/JsTests.java index 5fa534eec166..d43528c60632 100644 --- a/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/JsTests.java +++ b/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/JsTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -44,6 +44,7 @@ import org.junit.Assert; import org.junit.Test; +import com.oracle.truffle.regex.RegexSyntaxException.ErrorCode; import com.oracle.truffle.regex.errors.JsErrorMessages; import com.oracle.truffle.regex.tregex.TRegexOptions; import com.oracle.truffle.regex.tregex.string.Encodings; @@ -293,9 +294,11 @@ public void innerLiteralSurrogates() { @Test public void gr52906() { // Original test case - test("\\b(((.*?)){67108860})\\b|(?=(?=(?!.).\\b(\\d))){0,4}", "yi", "L1O\n\n\n11\n \n\n11\n \uD091 1aa\uFCDB=\n ", 0, true, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1); + // note: original counter value is 67108860, reduced to let the test finish in reasonable + // time. + test("\\b(((.*?)){9999})\\b|(?=(?=(?!.).\\b(\\d))){0,4}", "yi", "L1O\n\n\n11\n \n\n11\n \uD091 1aa\uFCDB=\n ", 0, true, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1); // Minimized version - test("(.*?){67108863}", "", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 0, true, 0, 0, 0, 0); + test("(.*?){9999}", "", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 0, true, 0, 0, 0, 0); // Linked issue test("(?=(?=(\\W)\u008e+|\\uC47A|(\\s)))+?|((((?:(\\\u0015)))+?))|(?:\\r|[^]+?[^])|\\3{3,}", "gyim", "", 0, true, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); @@ -307,4 +310,239 @@ public void gr52906() { public void gr56676() { test("(?> encoding.getStride(); + return execRegexBoolean(compiledRegex, encoding, converted, fromIndex, length, 0, length); + } + + Value execRegexBoolean(Value compiledRegex, Encodings.Encoding encoding, TruffleString input, int fromIndex, int toIndex, int regionFrom, int regionTo) { + return compiledRegex.invokeMember("execBoolean", input.switchEncodingUncached(encoding.getTStringEncoding()), fromIndex, toIndex, regionFrom, regionTo); + } + + void testBoolean(String pattern, String flags, String options, String input, int fromIndex, boolean isMatch) { + String expectedResult = isMatch ? "Match" : "NoMatch"; + try { + Value compiledRegex = compileRegex(pattern, flags, options, getTRegexEncoding()); + Value result = execRegexBoolean(compiledRegex, getTRegexEncoding(), input, fromIndex); + if (result.asBoolean() != isMatch) { + String actualResult = result.asBoolean() ? "Match" : "NoMatch"; + printTable(pattern, flags, input, fromIndex, expectedResult, actualResult); + if (ASSERTS) { + Assert.fail(options + regexSlashes(pattern, flags) + ' ' + quote(input) + " expected: " + expectedResult + ", actual: " + actualResult); + } + } + } catch (PolyglotException e) { + if (!ASSERTS && e.isSyntaxError()) { + printTable(pattern, flags, input, fromIndex, expectedResult, syntaxErrorToString(e.getMessage())); + } else { + throw e; + } + } + } + + void testBoolean(String pattern, String flags, String input, int fromIndex, boolean isMatch) { + testBoolean(pattern, flags, "BooleanMatch=true", input, fromIndex, isMatch); + } + void test(String pattern, String flags, String input, int fromIndex, boolean isMatch, int... captureGroupBoundsAndLastGroup) { test(pattern, flags, "", input, fromIndex, isMatch, captureGroupBoundsAndLastGroup); } @@ -155,6 +199,7 @@ void test(String pattern, String flags, String options, Encodings.Encoding encod throw e; } } + testBoolean(pattern, flags, "BooleanMatch=true" + (options.isEmpty() ? "" : "," + options), input, fromIndex, isMatch); } void test(Value compiledRegex, String pattern, String flags, String options, Encodings.Encoding encoding, String input, int fromIndex, boolean isMatch, int... captureGroupBoundsAndLastGroup) { @@ -235,12 +280,12 @@ void expectSyntaxError(String pattern, String flags, String expectedMessage, int expectSyntaxError(pattern, flags, "", getTRegexEncoding(), "", 0, expectedMessage, expectedPosition); } - void expectSyntaxError(String pattern, String flags, String options, String expectedMessage, int expectedPosition) { - expectSyntaxError(pattern, flags, options, getTRegexEncoding(), "", 0, expectedMessage, expectedPosition); + void expectSyntaxError(String pattern, String flags, String options, Encodings.Encoding encoding, String input, int fromIndex, ErrorCode expectedErrorCode) { + expectSyntaxError(pattern, flags, options, encoding, input, fromIndex, expectedErrorCode, Integer.MIN_VALUE); } - void expectSyntaxError(String pattern, String flags, String options, Encodings.Encoding encoding, String input, int fromIndex, String expectedMessage) { - expectSyntaxError(pattern, flags, options, encoding, input, fromIndex, expectedMessage, Integer.MIN_VALUE); + void expectSyntaxError(String pattern, String flags, String options, Encodings.Encoding encoding, String input, int fromIndex, ErrorCode expectedErrorCode, int expectedPosition) { + expectSyntaxError(pattern, flags, options, encoding, input, fromIndex, expectedErrorCode.name(), expectedPosition); } void expectSyntaxError(String pattern, String flags, String options, Encodings.Encoding encoding, String input, int fromIndex, String expectedMessage, int expectedPosition) { diff --git a/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/RubyUTF8Tests.java b/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/RubyUTF8Tests.java index 6b858f8b25b9..fa14cee72741 100644 --- a/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/RubyUTF8Tests.java +++ b/regex/src/com.oracle.truffle.regex.test/src/com/oracle/truffle/regex/tregex/test/RubyUTF8Tests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -40,9 +40,11 @@ */ package com.oracle.truffle.regex.tregex.test; -import com.oracle.truffle.regex.tregex.string.Encodings; import org.junit.Test; +import com.oracle.truffle.regex.RegexSyntaxException.ErrorCode; +import com.oracle.truffle.regex.tregex.string.Encodings; + public class RubyUTF8Tests extends RegexTestBase { @Override @@ -59,4 +61,327 @@ Encodings.Encoding getTRegexEncoding() { public void ignoreCaseBackReferences() { test("^(\uff21)(a)\\1\\2$", "i", "\uff21a\uff41A", 0, true, 0, 8, 0, 3, 3, 4); } + + @Test + public void generatedTests() { + /* GENERATED CODE BEGIN - KEEP THIS MARKER FOR AUTOMATIC UPDATES */ + + // Generated using Ruby version 3.3.5 + test("(?=.*c)a(()b?)?c", "", "ac", 0, true, 0, 2, 1, 1, 1, 1); + test("(?=.*c)a(b*)?c", "", "ac", 0, true, 0, 2, 1, 1); + test("(?=.*c)a(()b*)?c", "", "ac", 0, true, 0, 2, 1, 1, 1, 1); + test("(?=.*b)a{2}", "", "aaab", 0, true, 0, 2); + test("a{2}?", "", "c", 0, true, 0, 0); + test("a{2,4}?", "", "c", 0, false); + test("a+?", "", "c", 0, false); + test("a{2}?(b)?c", "", "c", 0, true, 0, 1, -1, -1); + test("(?>(aa)?)+", "", "a", 0, true, 0, 0, -1, -1); + test("(|a+?){0,4}b", "", "aaab", 0, true, 0, 4, 1, 3); + test("(a{2}|())+$", "", "aaaa", 0, true, 0, 4, 4, 4, 4, 4); + test("^a(b*)\\1{4,6}?", "", "abbbb", 0, true, 0, 1, 1, 1); + test("^a(b*)\\1{4,6}?", "", "abbbbb", 0, true, 0, 6, 1, 2); + test("a(?:c|b(?=()))*", "", "abc", 0, true, 0, 3, 2, 2); + test("a(?:c|b(?=(c)))*", "", "abc", 0, true, 0, 3, 2, 3); + test("a(?:c|(?<=(a))b)*", "", "abc", 0, true, 0, 3, 0, 1); + test("\\Z", "", "\r", 0, true, 1, 1); + test("(?<=\\A)", "", "\r", 0, true, 0, 0); + test("(?<=\\b)", "", "\r", 0, false); + test("(?<=\\B)", "", "\r", 0, true, 0, 0); + expectSyntaxError("(?<=+?)", "", "", getTRegexEncoding(), "error", 0, ErrorCode.InvalidQuantifier); + test("(?<=)", "", "empty", 0, true, 0, 0); + test("()?", "", "", 0, true, 0, 0, 0, 0); + test("(a*)?", "", "", 0, true, 0, 0, 0, 0); + test("(a*)*", "", "", 0, true, 0, 0, 0, 0); + test("(?:a|()){50,100}", "", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 0, true, 0, 50, 50, 50); + test("()??", "", "", 0, true, 0, 0, -1, -1); + test("(a*?)?", "", "", 0, true, 0, 0, 0, 0); + test("(a*)??", "", "", 0, true, 0, 0, -1, -1); + test("(a*?)??", "", "", 0, true, 0, 0, -1, -1); + test("(a*?)*", "", "", 0, true, 0, 0, 0, 0); + test("(a*)*?", "", "", 0, true, 0, 0, -1, -1); + test("(a*?)*?", "", "", 0, true, 0, 0, -1, -1); + test("(a|\\2b|())*", "", "aaabbb", 0, true, 0, 6, 6, 6, 6, 6); + test("(a|\\2b|()){2,4}", "", "aaabbb", 0, true, 0, 3, 3, 3, 3, 3); + test("(a|\\2b|\\3()|())*", "", "aaabbb", 0, true, 0, 6, 6, 6, 6, 6, 3, 3); + test("(a|\\2b|\\3()|()){2,4}", "", "aaabbb", 0, true, 0, 3, 3, 3, -1, -1, 3, 3); + test("(a|\\2b|()){20,24}", "", "aaaaaaaaaaaaaaaaaaaabbbbb", 0, true, 0, 23, 22, 23, 20, 20); + test("(a|\\2b|())*?", "", "aaabbb", 0, true, 0, 0, -1, -1, -1, -1); + test("(a|\\2b|()){2,4}", "", "aaabbb", 0, true, 0, 3, 3, 3, 3, 3); + test("(a|\\2b|\\3()|())*?", "", "aaabbb", 0, true, 0, 0, -1, -1, -1, -1, -1, -1); + test("(a|\\2b|\\3()|()){2,4}", "", "aaabbb", 0, true, 0, 3, 3, 3, -1, -1, 3, 3); + test("(a|\\2b|()){20,24}", "", "aaaaaaaaaaaaaaaaaaaabbbbb", 0, true, 0, 23, 22, 23, 20, 20); + test("(?:|a)*", "", "aaa", 0, true, 0, 0); + test("(?:()|a)*", "", "aaa", 0, true, 0, 0, 0, 0); + test("(|a)*", "", "aaa", 0, true, 0, 0, 0, 0); + test("(()|a)*", "", "aaa", 0, true, 0, 0, 0, 0, 0, 0); + test("()\\1(?:|a)*", "", "aaa", 0, true, 0, 0, 0, 0); + test("()\\1(?:()|a)*", "", "aaa", 0, true, 0, 0, 0, 0, 0, 0); + test("()\\1(|a)*", "", "aaa", 0, true, 0, 0, 0, 0, 0, 0); + test("()\\1(()|a)*", "", "aaa", 0, true, 0, 0, 0, 0, 0, 0, 0, 0); + test("()(?:\\1|a)*", "", "aaa", 0, true, 0, 0, 0, 0); + test("()(?:()\\1|a)*", "", "aaa", 0, true, 0, 0, 0, 0, 0, 0); + test("()(?:(\\1)|a)*", "", "aaa", 0, true, 0, 0, 0, 0, 0, 0); + test("()(?:\\1()|a)*", "", "aaa", 0, true, 0, 0, 0, 0, 0, 0); + test("()(\\1|a)*", "", "aaa", 0, true, 0, 0, 0, 0, 0, 0); + test("()(()\\1|a)*", "", "aaa", 0, true, 0, 0, 0, 0, 0, 0, 0, 0); + test("()((\\1)|a)*", "", "aaa", 0, true, 0, 0, 0, 0, 0, 0, 0, 0); + test("()(\\1()|a)*", "", "aaa", 0, true, 0, 0, 0, 0, 0, 0, 0, 0); + test("(?:(?=a)|a)*", "", "aaa", 0, true, 0, 0); + test("(?:(?=a)()|a)*", "", "aaa", 0, true, 0, 0, 0, 0); + test("(?:()(?=a)|a)*", "", "aaa", 0, true, 0, 0, 0, 0); + test("(?:((?=a))|a)*", "", "aaa", 0, true, 0, 0, 0, 0); + test("()\\1(?:(?=a)|a)*", "", "aaa", 0, true, 0, 0, 0, 0); + test("()\\1(?:(?=a)()|a)*", "", "aaa", 0, true, 0, 0, 0, 0, 0, 0); + test("()\\1(?:()(?=a)|a)*", "", "aaa", 0, true, 0, 0, 0, 0, 0, 0); + test("()\\1(?:((?=a))|a)*", "", "aaa", 0, true, 0, 0, 0, 0, 0, 0); + test("(?:|a)*?", "", "aaa", 0, true, 0, 0); + test("(?:()|a)*?", "", "aaa", 0, true, 0, 0, -1, -1); + test("(|a)*?", "", "aaa", 0, true, 0, 0, -1, -1); + test("(()|a)*?", "", "aaa", 0, true, 0, 0, -1, -1, -1, -1); + test("()\\1(?:|a)*?", "", "aaa", 0, true, 0, 0, 0, 0); + test("()\\1(?:()|a)*?", "", "aaa", 0, true, 0, 0, 0, 0, -1, -1); + test("()\\1(|a)*?", "", "aaa", 0, true, 0, 0, 0, 0, -1, -1); + test("()\\1(()|a)*?", "", "aaa", 0, true, 0, 0, 0, 0, -1, -1, -1, -1); + test("()(?:\\1|a)*?", "", "aaa", 0, true, 0, 0, 0, 0); + test("()(?:()\\1|a)*?", "", "aaa", 0, true, 0, 0, 0, 0, -1, -1); + test("()(?:(\\1)|a)*?", "", "aaa", 0, true, 0, 0, 0, 0, -1, -1); + test("()(?:\\1()|a)*?", "", "aaa", 0, true, 0, 0, 0, 0, -1, -1); + test("()(\\1|a)*?", "", "aaa", 0, true, 0, 0, 0, 0, -1, -1); + test("()(()\\1|a)*?", "", "aaa", 0, true, 0, 0, 0, 0, -1, -1, -1, -1); + test("()((\\1)|a)*?", "", "aaa", 0, true, 0, 0, 0, 0, -1, -1, -1, -1); + test("()(\\1()|a)*?", "", "aaa", 0, true, 0, 0, 0, 0, -1, -1, -1, -1); + test("(?:(?=a)|a)*?", "", "aaa", 0, true, 0, 0); + test("(?:(?=a)()|a)*?", "", "aaa", 0, true, 0, 0, -1, -1); + test("(?:()(?=a)|a)*?", "", "aaa", 0, true, 0, 0, -1, -1); + test("(?:((?=a))|a)*?", "", "aaa", 0, true, 0, 0, -1, -1); + test("()\\1(?:(?=a)|a)*?", "", "aaa", 0, true, 0, 0, 0, 0); + test("()\\1(?:(?=a)()|a)*?", "", "aaa", 0, true, 0, 0, 0, 0, -1, -1); + test("()\\1(?:()(?=a)|a)*?", "", "aaa", 0, true, 0, 0, 0, 0, -1, -1); + test("()\\1(?:((?=a))|a)*?", "", "aaa", 0, true, 0, 0, 0, 0, -1, -1); + test("(|a|\\2b|())*", "", "aaabbb", 0, true, 0, 0, 0, 0, -1, -1); + test("(a||\\2b|())*", "", "aaabbb", 0, true, 0, 3, 3, 3, -1, -1); + test("(a|\\2b||())*", "", "aaabbb", 0, true, 0, 3, 3, 3, -1, -1); + test("(a|\\2b|()|)*", "", "aaabbb", 0, true, 0, 6, 6, 6, 6, 6); + test("(()|a|\\3b|())*", "", "aaabbb", 0, true, 0, 0, 0, 0, 0, 0, -1, -1); + test("(a|()|\\3b|())*", "", "aaabbb", 0, true, 0, 3, 3, 3, 3, 3, -1, -1); + test("(a|\\2b|()|())*", "", "aaabbb", 0, true, 0, 6, 6, 6, 6, 6, -1, -1); + test("(a|\\3b|()|())*", "", "aaabbb", 0, true, 0, 3, 3, 3, 3, 3, -1, -1); + test("(a|()|())*", "", "aaa", 0, true, 0, 3, 3, 3, 3, 3, -1, -1); + test("^(()|a|())*$", "", "aaa", 0, true, 0, 3, 3, 3, 3, 3, -1, -1); + test("(|a|\\2b|())*?", "", "aaabbb", 0, true, 0, 0, -1, -1, -1, -1); + test("(a||\\2b|())*?", "", "aaabbb", 0, true, 0, 0, -1, -1, -1, -1); + test("(a|\\2b||())*?", "", "aaabbb", 0, true, 0, 0, -1, -1, -1, -1); + test("(a|\\2b|()|)*?", "", "aaabbb", 0, true, 0, 0, -1, -1, -1, -1); + test("(()|a|\\3b|())*?", "", "aaabbb", 0, true, 0, 0, -1, -1, -1, -1, -1, -1); + test("(a|()|\\3b|())*?", "", "aaabbb", 0, true, 0, 0, -1, -1, -1, -1, -1, -1); + test("(a|\\2b|()|())*?", "", "aaabbb", 0, true, 0, 0, -1, -1, -1, -1, -1, -1); + test("(a|\\3b|()|())*?", "", "aaabbb", 0, true, 0, 0, -1, -1, -1, -1, -1, -1); + test("(a|()|())*?", "", "aaa", 0, true, 0, 0, -1, -1, -1, -1, -1, -1); + test("^(()|a|())*?$", "", "aaa", 0, true, 0, 3, 2, 3, 2, 2, -1, -1); + test("((A|){7,10}?){10,17}", "", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 0, true, 0, 86, 86, 86, 86, 86); + test("(a{1,30}){1,4}", "", "a", 0, true, 0, 1, 0, 1); + test("((a|){4,6}){4,6}", "", "aaaaaaa", 0, true, 0, 7, 7, 7, 7, 7); + test("((a?){4,6}){4,6}", "", "aaaaaaa", 0, true, 0, 7, 7, 7, 7, 7); + test("((|a){4,6}){4,6}", "", "aaaaaaa", 0, true, 0, 0, 0, 0, 0, 0); + test("((a??){4,6}){4,6}", "", "aaaaaaa", 0, true, 0, 0, 0, 0, 0, 0); + test("((a?){4,6}){4,6}", "", "aaaaaa", 0, true, 0, 6, 6, 6, 6, 6); + test("(a|){4,6}", "", "a", 0, true, 0, 1, 1, 1); + test("(a|){4,6}", "", "aa", 0, true, 0, 2, 2, 2); + test("(a|){4,6}", "", "aaa", 0, true, 0, 3, 3, 3); + test("(a|){4,6}", "", "aaaa", 0, true, 0, 4, 4, 4); + test("(a|){4,6}", "", "aaaaa", 0, true, 0, 5, 5, 5); + test("(a|){4,6}", "", "aaaaaa", 0, true, 0, 6, 5, 6); + test("(a|){4,6}", "", "aaaaaaa", 0, true, 0, 6, 5, 6); + test("(a|){4,6}?", "", "a", 0, true, 0, 1, 1, 1); + test("(a|){4,6}?", "", "aa", 0, true, 0, 2, 2, 2); + test("(a|){4,6}?", "", "aaa", 0, true, 0, 3, 3, 3); + test("(a|){4,6}?", "", "aaaa", 0, true, 0, 4, 3, 4); + test("(a|){4,6}?", "", "aaaaa", 0, true, 0, 4, 3, 4); + test("(a|){4,6}?", "", "aaaaaa", 0, true, 0, 4, 3, 4); + test("(a|){4,6}?", "", "aaaaaaa", 0, true, 0, 4, 3, 4); + test("(a|){4,6}?a", "", "a", 0, true, 0, 1, 0, 0); + test("(a|){4,6}?a", "", "aa", 0, true, 0, 2, 1, 1); + test("(a|){4,6}?a", "", "aaa", 0, true, 0, 3, 2, 2); + test("(a|){4,6}?a", "", "aaaa", 0, true, 0, 4, 3, 3); + test("(a|){4,6}?a", "", "aaaaa", 0, true, 0, 5, 3, 4); + test("(a|){4,6}?a", "", "aaaaaa", 0, true, 0, 5, 3, 4); + test("(a|){4,6}?a", "", "aaaaaaa", 0, true, 0, 5, 3, 4); + test("(a|){4,6}?a", "", "aaaaaaaa", 0, true, 0, 5, 3, 4); + test("(|a){4,6}a", "", "a", 0, true, 0, 1, 0, 0); + test("(|a){4,6}a", "", "aa", 0, true, 0, 1, 0, 0); + test("(|a){4,6}a", "", "aaa", 0, true, 0, 1, 0, 0); + test("(|a){4,6}a", "", "aaaa", 0, true, 0, 1, 0, 0); + test("(|a){4,6}a", "", "aaaaa", 0, true, 0, 1, 0, 0); + test("(|a){4,6}a", "", "aaaaaa", 0, true, 0, 1, 0, 0); + test("(|a){4,6}a", "", "aaaaaaa", 0, true, 0, 1, 0, 0); + test("((a|){4,6}){4,6}", "", "a", 0, true, 0, 1, 1, 1, 1, 1); + test("((a|){4,6}){4,6}", "", "aa", 0, true, 0, 2, 2, 2, 2, 2); + test("((a|){4,6}){4,6}", "", "aaa", 0, true, 0, 3, 3, 3, 3, 3); + test("((a|){4,6}){4,6}", "", "aaaa", 0, true, 0, 4, 4, 4, 4, 4); + test("((a|){4,6}){4,6}", "", "aaaaa", 0, true, 0, 5, 5, 5, 5, 5); + test("((a|){4,6}){4,6}", "", "aaaaaa", 0, true, 0, 6, 6, 6, 6, 6); + test("((a|){4,6}){4,6}", "", "aaaaaaa", 0, true, 0, 7, 7, 7, 7, 7); + test("((a|){4,6}){4,6}", "", "aaaaaaaa", 0, true, 0, 8, 8, 8, 8, 8); + test("((a|){4,6}){4,6}", "", "aaaaaaaaa", 0, true, 0, 9, 9, 9, 9, 9); + test("((a|){4,6}){4,6}", "", "aaaaaaaaaa", 0, true, 0, 10, 10, 10, 10, 10); + test("((a|){4,6}){4,6}", "", "aaaaaaaaaaa", 0, true, 0, 11, 11, 11, 11, 11); + test("((a|){4,6}){4,6}", "", "aaaaaaaaaaaa", 0, true, 0, 12, 12, 12, 12, 12); + test("((a|){4,6}){4,6}", "", "aaaaaaaaaaaaa", 0, true, 0, 13, 13, 13, 13, 13); + test("((|a){4,6}){4,6}", "", "a", 0, true, 0, 0, 0, 0, 0, 0); + test("((|a){4,6}){4,6}", "", "aa", 0, true, 0, 0, 0, 0, 0, 0); + test("((|a){4,6}){4,6}", "", "aaa", 0, true, 0, 0, 0, 0, 0, 0); + test("((|a){4,6}){4,6}", "", "aaaa", 0, true, 0, 0, 0, 0, 0, 0); + test("((|a){4,6}){4,6}", "", "aaaaa", 0, true, 0, 0, 0, 0, 0, 0); + test("((|a){4,6}){4,6}", "", "aaaaaa", 0, true, 0, 0, 0, 0, 0, 0); + test("((|a){4,6}){4,6}", "", "aaaaaaaa", 0, true, 0, 0, 0, 0, 0, 0); + test("((|a){4,6}){4,6}", "", "aaaaaaaaa", 0, true, 0, 0, 0, 0, 0, 0); + test("((|a){4,6}){4,6}", "", "aaaaaaaaaa", 0, true, 0, 0, 0, 0, 0, 0); + test("((|a){4,6}){4,6}", "", "aaaaaaaaaaa", 0, true, 0, 0, 0, 0, 0, 0); + test("((|a){4,6}){4,6}", "", "aaaaaaaaaaaa", 0, true, 0, 0, 0, 0, 0, 0); + test("((|a){4,6}){4,6}", "", "aaaaaaaaaaaaa", 0, true, 0, 0, 0, 0, 0, 0); + test("((a|){4,6}?){4,6}", "", "a", 0, true, 0, 1, 1, 1, 1, 1); + test("((a|){4,6}?){4,6}", "", "aa", 0, true, 0, 2, 2, 2, 2, 2); + test("((a|){4,6}?){4,6}", "", "aaa", 0, true, 0, 3, 3, 3, 3, 3); + test("((a|){4,6}?){4,6}", "", "aaaa", 0, true, 0, 4, 4, 4, 4, 4); + test("((a|){4,6}?){4,6}", "", "aaaaa", 0, true, 0, 5, 5, 5, 5, 5); + test("((a|){4,6}?){4,6}", "", "aaaaaa", 0, true, 0, 6, 6, 6, 6, 6); + test("((a|){4,6}?){4,6}", "", "aaaaaaaa", 0, true, 0, 8, 8, 8, 8, 8); + test("((a|){4,6}?){4,6}", "", "aaaaaaaaa", 0, true, 0, 9, 9, 9, 9, 9); + test("((a|){4,6}?){4,6}", "", "aaaaaaaaaa", 0, true, 0, 10, 10, 10, 10, 10); + test("((a|){4,6}?){4,6}", "", "aaaaaaaaaaa", 0, true, 0, 11, 11, 11, 11, 11); + test("((a|){4,6}?){4,6}", "", "aaaaaaaaaaaa", 0, true, 0, 12, 12, 12, 12, 12); + test("((a|){4,6}?){4,6}", "", "aaaaaaaaaaaaa", 0, true, 0, 13, 13, 13, 13, 13); + test("((a|){4,6}?){4,6}", "", "aaaaaaaaaaaaaa", 0, true, 0, 14, 14, 14, 14, 14); + test("((a|){4,6}?){4,6}", "", "aaaaaaaaaaaaaaa", 0, true, 0, 15, 15, 15, 15, 15); + test("((a|){4,6}?){4,6}", "", "aaaaaaaaaaaaaaaa", 0, true, 0, 16, 16, 16, 16, 16); + test("((a|){4,6}?){4,6}", "", "aaaaaaaaaaaaaaaaa", 0, true, 0, 17, 17, 17, 17, 17); + test("((a|){4,6}?){4,6}", "", "aaaaaaaaaaaaaaaaaa", 0, true, 0, 18, 18, 18, 18, 18); + test("((a){4,6}?){4,6}", "", "a", 0, false); + test("((a){4,6}?){4,6}", "", "aa", 0, false); + test("((a){4,6}?){4,6}", "", "aaa", 0, false); + test("((a){4,6}?){4,6}", "", "aaaa", 0, false); + test("((a){4,6}?){4,6}", "", "aaaaa", 0, false); + test("((a){4,6}?){4,6}", "", "aaaaaa", 0, false); + test("((a){4,6}?){4,6}", "", "aaaaaaaaaaaaaaaa", 0, true, 0, 16, 12, 16, 15, 16); + test("((a){4,6}?){4,6}", "", "aaaaaaaaaaaaaaaaa", 0, true, 0, 16, 12, 16, 15, 16); + test("((a){4,6}?){4,6}", "", "aaaaaaaaaaaaaaaaaaaa", 0, true, 0, 20, 16, 20, 19, 20); + test("((a){4,6}?){4,6}", "", "aaaaaaaaaaaaaaaaaaaaaaaa", 0, true, 0, 24, 20, 24, 23, 24); + test("((a){4,6}?){4,6}", "", "aaaaaaaaaaaaaaaaaaaaaaaaa", 0, true, 0, 24, 20, 24, 23, 24); + test("((a){4,6}){4,6}", "", "a", 0, false); + test("((a){4,6}){4,6}", "", "aa", 0, false); + test("((a){4,6}){4,6}", "", "aaa", 0, false); + test("((a){4,6}){4,6}", "", "aaaa", 0, false); + test("((a){4,6}){4,6}", "", "aaaaa", 0, false); + test("((a){4,6}){4,6}", "", "aaaaaa", 0, false); + test("((a){4,6}){4,6}", "", "aaaaaaaaaaaaaaaa", 0, true, 0, 16, 12, 16, 15, 16); + test("((a){4,6}){4,6}", "", "aaaaaaaaaaaaaaaaa", 0, true, 0, 17, 13, 17, 16, 17); + test("((a){4,6}){4,6}", "", "aaaaaaaaaaaaaaaaaaaa", 0, true, 0, 20, 16, 20, 19, 20); + test("((a){4,6}){4,6}", "", "aaaaaaaaaaaaaaaaaaaaaaaa", 0, true, 0, 24, 18, 24, 23, 24); + test("((a){4,6}){4,6}", "", "aaaaaaaaaaaaaaaaaaaaaaaaa", 0, true, 0, 24, 18, 24, 23, 24); + test("((a){4,}){4,6}", "", "a", 0, false); + test("((a){4,}){4,6}", "", "aa", 0, false); + test("((a){4,}){4,6}", "", "aaa", 0, false); + test("((a){4,}){4,6}", "", "aaaa", 0, false); + test("((a){4,}){4,6}", "", "aaaaa", 0, false); + test("((a){4,}){4,6}", "", "aaaaaa", 0, false); + test("((a){4,}){4,6}", "", "aaaaaaaaaaaaaaaa", 0, true, 0, 16, 12, 16, 15, 16); + test("((a){4,}){4,6}", "", "aaaaaaaaaaaaaaaaa", 0, true, 0, 17, 13, 17, 16, 17); + test("((a){4,}){4,6}", "", "aaaaaaaaaaaaaaaaaaaa", 0, true, 0, 20, 16, 20, 19, 20); + test("((a){4,}){4,6}", "", "aaaaaaaaaaaaaaaaaaaaaaaa", 0, true, 0, 24, 20, 24, 23, 24); + test("((a){4,}){4,6}", "", "aaaaaaaaaaaaaaaaaaaaaaaaa", 0, true, 0, 25, 21, 25, 24, 25); + test("(.)\\1{2,}", "", "billiam", 0, false); + test("(^_(a{1,2}[:])*a{1,2}[:]a{1,2}([.]a{1,4})?_)+", "", "_a:a:a.aaa_", 0, true, 0, 11, 0, 11, 1, 3, 6, 10); + test("(a{2}|())+$", "", "aaaa", 0, true, 0, 4, 4, 4, 4, 4); + test("^a(b*)\\1{4,6}?", "", "abbbb", 0, true, 0, 1, 1, 1); + test("^a(b*)\\1{4,6}?", "", "abbbbb", 0, true, 0, 6, 1, 2); + test("(?<=|$)", "", "a", 0, true, 0, 0); + test("(?=ab)a", "", "ab", 0, true, 0, 1); + test("(?=()|^)|x", "", "empty", 0, true, 0, 0, 0, 0); + test("a(?<=ba)", "", "ba", 0, true, 1, 2); + expectSyntaxError("(?<=(?<=a)[])", "i", "", getTRegexEncoding(), "empty", 0, ErrorCode.InvalidCharacterClass); + test("\\d\\W", "i", "4\u017f", 0, true, 0, 3); + test("[\u08bc-\ucf3a]", "i", "\u03b0", 0, true, 0, 2); + test("[\u0450-\u6c50]\u7e57\u55ad()\u64e7\\d|", "i", "\u03b0\u7e57\u55ad\u64e79", 0, true, 0, 12, 8, 8); + test("(?<=(?<=a)b^c)c", "", "abcc", 0, false); + test("a(?:|()\\1){1,2}", "", "a", 0, true, 0, 1, -1, -1); + test("[a-z][a-z\u2028\u2029].|ab(?<=[a-z]w.)", "", "aac", 0, true, 0, 3); + test("(animation|animation-name)", "", "animation", 0, true, 0, 9, 0, 9); + test("(a|){7,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(a|){7,7}?b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(|a){7,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(|a){7,7}?b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(a||b){7,7}c", "", "aaabc", 0, true, 0, 5, 4, 4); + test("(a||b){7,7}c", "", "aaac", 0, true, 0, 4, 3, 3); + test("(a||b){7,7}c", "", "aaabac", 0, true, 0, 6, 5, 5); + test("($|a){7,7}", "", "aaa", 0, true, 0, 3, 3, 3); + test("($|a){7,7}?", "", "aaa", 0, true, 0, 3, 3, 3); + test("(a|$){7,7}", "", "aaa", 0, true, 0, 3, 3, 3); + test("(a|$){7,7}?", "", "aaa", 0, true, 0, 3, 3, 3); + test("(a|$|b){7,7}", "", "aaab", 0, true, 0, 4, 4, 4); + test("(a|$|b){7,7}", "", "aaa", 0, true, 0, 3, 3, 3); + test("(a|$|b){7,7}", "", "aaaba", 0, true, 0, 5, 5, 5); + test("((?=a)|a){7,7}b", "", "aaa", 0, false); + test("((?=[ab])|a){7,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("((?<=a)|a){7,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("a((?<=a)|a){7,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(a|){0,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(a|){0,7}?b", "", "aaab", 0, true, 0, 4, 2, 3); + test("(|a){0,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(|a){0,7}?b", "", "aaab", 0, true, 0, 4, 2, 3); + test("(a||b){0,7}c", "", "aaabc", 0, true, 0, 5, 4, 4); + test("(a||b){0,7}c", "", "aaac", 0, true, 0, 4, 3, 3); + test("(a||b){0,7}c", "", "aaabac", 0, true, 0, 6, 5, 5); + test("((?=a)|a){0,7}b", "", "aaab", 0, true, 0, 4, 2, 3); + test("((?=[ab])|a){0,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("((?<=a)|a){0,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("a((?<=a)|a){0,7}b", "", "aaab", 0, true, 0, 4, 3, 3); + test("(a*){11,11}b", "", "aaaaaaaaaaaaaaaaaaaaaaaaab", 0, true, 0, 26, 25, 25); + test("(?:a(b{0,19})c)", "", "abbbbbbbcdebbbbbbbf", 0, true, 0, 9, 1, 8); + test("(?:a(b{0,19})c)de", "", "abbbbbbbcdebbbbbbbf", 0, true, 0, 11, 1, 8); + test("[\ud0d9](?<=\\S)", "", "\ud0d9", 0, true, 0, 3); + test("[\ud0d9](?<=\\W)", "", "\ud0d9", 0, true, 0, 3); + test("\u0895(?<=\\S)", "", "\u0895", 0, true, 0, 3); + test("\u0895(?<=\\W)", "", "\u0895", 0, true, 0, 3); + test("[\u8053](?<=\\S)", "", "\u8053", 0, true, 0, 3); + test("[\u8053](?<=\\W)", "", "\u8053", 0, true, 0, 3); + test("\u0895(?<=\\S)", "", "\u0895", 0, true, 0, 3); + test("\u0895(?<=\\W)", "", "\u0895", 0, true, 0, 3); + test("\u0895|[\u8053\ud0d9]+(?<=\\S\\W\\S)", "", "\ud0d9\ud0d9\ud0d9\ud0d9", 0, true, 0, 12); + test("\u0895|[\u8053\ud0d9]+(?<=\\S\\W\\S)", "", "\ud0d9\ud0d9\ud0d9\ud0d9", 0, true, 0, 12); + test("\u0895|[\u8053\ud0d9]+(?<=\\S\\W\\S)", "", "\ud0d9\ud0d9\ud0d9\ud0d9", 0, true, 0, 12); + test("a|[bc]+(?<=[abc][abcd][abc])", "", "bbbb", 0, true, 0, 4); + test("a(b*)*c\\1d", "", "abbbbcbbd", 0, true, 0, 9, 3, 5); + test("(|a)||b(?<=cde)|", "", "a", 0, true, 0, 0, 0, 0); + test("^(\\1)?\\D*", "", "empty", 0, true, 0, 5, -1, -1); + test("abcd(?<=d|c()d)", "", "_abcd", 0, true, 1, 5, -1, -1); + test("\\Dw\u3aa7\\A\\S(?<=\ue3b3|\\A()\\S)", "", "\udad1\udcfaw\u3aa7A\ue3b3", 0, false); + test("a(?:c|b(?=()))*", "", "abc", 0, true, 0, 3, 2, 2); + test("a(?:c|b(?=(c)))*", "", "abc", 0, true, 0, 3, 2, 3); + test("a(?:c|(?<=(a))b)*", "", "abc", 0, true, 0, 3, 0, 1); + test("(a||b){15,18}c", "", "ababaabbaaac", 0, true, 0, 12, 11, 11); + test("(a||b){15,18}?c", "", "ababaabbaaac", 0, true, 0, 12, 10, 11); + test("(?:ab|c|^){103,104}", "", "abcababccabccabababccabcababcccccabcababababccccabcabcabccabcabcccabababccabababcababababccababccabcababcabcabccabababccccabcab", 0, true, 0, 0); + test("((?<=a)bec)*d", "", "abecd", 0, true, 1, 5, 1, 4); + test("(|(^|\\z){2,77}?)?", "", "empty", 0, true, 0, 0, 0, 0, -1, -1); + test("a(|a{15,36}){10,11}", "", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 0, true, 0, 1, 1, 1); + test("a(|a{15,36}?){10,11}", "", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 0, true, 0, 1, 1, 1); + test("a(|a{15,36}){10,11}$", "", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 0, true, 0, 66, 66, 66); + test("a(|a{15,36}?){10,11}b$", "", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab", 0, true, 0, 67, 66, 66); + test("(?:a()|b??){22,26}c", "", "aabbbaabaaaaaabaaaac", 0, true, 0, 20, 19, 19); + test("b()(a\\1|){4,4}\\2c", "", "baaaac", 0, true, 0, 6, 1, 1, 3, 4); + test("a((?=b()|)[a-d])+", "", "abbbcbd", 0, true, 0, 7, 6, 7, 6, 6); + test("a(|b){5,7}c", "", "abbbc", 0, true, 0, 5, 4, 4); + test("a(|b){5,8}c", "", "abbbc", 0, true, 0, 5, 4, 4); + test("a(|b){5,9}c", "", "abbbc", 0, true, 0, 5, 4, 4); + test("a(|b){5,}c", "", "abbbc", 0, true, 0, 5, 4, 4); + test("a((?<=a)|b){5,7}c", "", "abbbc", 0, false); + test("a((?<=a)|b){5,8}c", "", "abbbc", 0, false); + test("a((?<=a)|b){5,9}c", "", "abbbc", 0, false); + test("a((?<=a)|b){5,}c", "", "abbbc", 0, false); + test("[ab]*?\\Z(?<=[^b][ab][^b])", "", "aaaaaa", 0, true, 0, 6); + test("(?<=a(b){3,3}?)", "", "abbb", 0, true, 4, 4, 3, 4); + test("\\A(?(?:%\\h\\h|[!$&-.0-9:;=@A-Z_a-z~/])){0}((?!/)\\g++)\\z", "x", "ftp://example.com/%2Ffoo", 0, true, 0, 24, 23, 24); + + /* GENERATED CODE END - KEEP THIS MARKER FOR AUTOMATIC UPDATES */ + } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexFlags.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexFlags.java index dd0ed1625b32..7f42e2f40da9 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexFlags.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexFlags.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -50,6 +50,7 @@ import com.oracle.truffle.regex.tregex.util.json.Json; import com.oracle.truffle.regex.tregex.util.json.JsonConvertible; import com.oracle.truffle.regex.tregex.util.json.JsonValue; +import com.oracle.truffle.regex.util.TBitSet; import com.oracle.truffle.regex.util.TruffleReadOnlyKeysArray; @ExportLibrary(InteropLibrary.class) @@ -76,6 +77,9 @@ public final class RegexFlags extends AbstractConstantKeysObject implements Json PROP_HAS_INDICES, PROP_UNICODE_SETS); + private static final TBitSet ALL_FLAG_CHARS = TBitSet.valueOf('d', 'g', 'i', 'm', 's', 'u', 'v', 'y'); + private static final TBitSet LOCAL_FLAG_CHARS = TBitSet.valueOf('i', 'm', 's'); + private static final int NONE = 0; private static final int IGNORE_CASE = 1; private static final int MULTILINE = 1 << 1; @@ -86,6 +90,10 @@ public final class RegexFlags extends AbstractConstantKeysObject implements Json private static final int HAS_INDICES = 1 << 6; private static final int UNICODE_SETS = 1 << 7; + private static final int[] FLAG_LOOKUP = { + HAS_INDICES, 0, 0, GLOBAL, 0, IGNORE_CASE, 0, 0, 0, MULTILINE, 0, 0, 0, 0, 0, DOT_ALL, 0, UNICODE, UNICODE_SETS, 0, 0, STICKY + }; + public static final RegexFlags DEFAULT = new RegexFlags("", NONE); private final String source; @@ -96,6 +104,17 @@ private RegexFlags(String source, int value) { this.value = value; } + private RegexFlags(int value) { + this.source = generateSource(value); + this.value = value; + } + + private static int maskForFlag(char flagChar) { + assert ALL_FLAG_CHARS.get(flagChar); + // flagChar must be one of [d-y]. + return FLAG_LOOKUP[flagChar - 'd']; + } + public static Builder builder() { return new Builder(); } @@ -109,51 +128,21 @@ public static RegexFlags parseFlags(RegexSource source) throws RegexSyntaxExcept int flags = NONE; for (int i = 0; i < flagsStr.length(); i++) { char ch = flagsStr.charAt(i); - switch (ch) { - case 'i': - flags = addFlag(source, flags, i, IGNORE_CASE); - break; - case 'm': - flags = addFlag(source, flags, i, MULTILINE); - break; - case 'g': - flags = addFlag(source, flags, i, GLOBAL); - break; - case 'y': - flags = addFlag(source, flags, i, STICKY); - break; - case 'u': - if ((flags & UNICODE_SETS) != 0) { - throw RegexSyntaxException.createFlags(source, JsErrorMessages.BOTH_FLAGS_SET_U_V, i); - } - flags = addFlag(source, flags, i, UNICODE); - break; - case 's': - flags = addFlag(source, flags, i, DOT_ALL); - break; - case 'd': - flags = addFlag(source, flags, i, HAS_INDICES); - break; - case 'v': - if ((flags & UNICODE) != 0) { - throw RegexSyntaxException.createFlags(source, JsErrorMessages.BOTH_FLAGS_SET_U_V, i); - } - flags = addFlag(source, flags, i, UNICODE_SETS); - break; - default: - throw RegexSyntaxException.createFlags(source, JsErrorMessages.UNSUPPORTED_FLAG, i); + if (!isValidFlagChar(ch)) { + throw RegexSyntaxException.createFlags(source, JsErrorMessages.UNSUPPORTED_FLAG, i); + } + int flag = maskForFlag(ch); + if ((flags & flag) != 0) { + throw RegexSyntaxException.createFlags(source, JsErrorMessages.REPEATED_FLAG, i); + } + flags |= flag; + if ((flags & (UNICODE | UNICODE_SETS)) == (UNICODE | UNICODE_SETS)) { + throw RegexSyntaxException.createFlags(source, JsErrorMessages.BOTH_FLAGS_SET_U_V, i); } } return new RegexFlags(flagsStr, flags); } - private static int addFlag(RegexSource source, int flags, int i, int flag) { - if ((flags & flag) != 0) { - throw RegexSyntaxException.createFlags(source, JsErrorMessages.REPEATED_FLAG, i); - } - return flags | flag; - } - public String getSource() { return source; } @@ -202,6 +191,34 @@ private boolean isSet(int flag) { return (value & flag) != NONE; } + public static boolean isValidFlagChar(char candidateChar) { + return ALL_FLAG_CHARS.get(candidateChar); + } + + public static boolean isValidLocalFlagChar(char candidateChar) { + return LOCAL_FLAG_CHARS.get(candidateChar); + } + + public RegexFlags addNewFlagModifier(RegexSource regexSource, char flagChar) { + int flag = maskForFlag(flagChar); + if (isSet(flag)) { + throw RegexSyntaxException.createFlags(regexSource, JsErrorMessages.REPEATED_FLAG_IN_MODIFIER); + } + return new RegexFlags(this.value | flag); + } + + public RegexFlags addFlags(RegexFlags otherFlags) { + return new RegexFlags(this.value | otherFlags.value); + } + + public RegexFlags delFlags(RegexFlags otherFlags) { + return new RegexFlags(this.value & ~otherFlags.value); + } + + public boolean overlaps(RegexFlags otherFlags) { + return (this.value & otherFlags.value) != 0; + } + @Override public String toString() { return source; @@ -287,6 +304,35 @@ public Object toDisplayString(@SuppressWarnings("unused") boolean allowSideEffec return "TRegexJSFlags{flags=" + toString() + '}'; } + private static String generateSource(int value) { + StringBuilder sb = new StringBuilder(8); + if ((value & IGNORE_CASE) != 0) { + sb.append("i"); + } + if ((value & MULTILINE) != 0) { + sb.append("m"); + } + if ((value & STICKY) != 0) { + sb.append("y"); + } + if ((value & GLOBAL) != 0) { + sb.append("g"); + } + if ((value & UNICODE) != 0) { + sb.append("u"); + } + if ((value & DOT_ALL) != 0) { + sb.append("s"); + } + if ((value & HAS_INDICES) != 0) { + sb.append("d"); + } + if ((value & UNICODE_SETS) != 0) { + sb.append("v"); + } + return sb.toString(); + } + public static final class Builder { private int value; @@ -342,7 +388,7 @@ public Builder unicodeSets(boolean enabled) { @TruffleBoundary public RegexFlags build() { - return new RegexFlags(generateSource(), this.value); + return new RegexFlags(generateSource(this.value), this.value); } private void updateFlag(boolean enabled, int bitMask) { @@ -352,38 +398,5 @@ private void updateFlag(boolean enabled, int bitMask) { this.value &= ~bitMask; } } - - private boolean isSet(int flag) { - return (value & flag) != NONE; - } - - private String generateSource() { - StringBuilder sb = new StringBuilder(7); - if (isSet(IGNORE_CASE)) { - sb.append("i"); - } - if (isSet(MULTILINE)) { - sb.append("m"); - } - if (isSet(STICKY)) { - sb.append("y"); - } - if (isSet(GLOBAL)) { - sb.append("g"); - } - if (isSet(UNICODE)) { - sb.append("u"); - } - if (isSet(DOT_ALL)) { - sb.append("s"); - } - if (isSet(HAS_INDICES)) { - sb.append("d"); - } - if (isSet(UNICODE_SETS)) { - sb.append("v"); - } - return sb.toString(); - } } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexLanguage.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexLanguage.java index 30c2e15a039d..e6e316671801 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexLanguage.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexLanguage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -182,9 +182,16 @@ public static RegexSource createRegexSource(Source source) { String pattern = srcStr.substring(firstSlash + 1, lastSlash); String flags = srcStr.substring(lastSlash + 1); // ECMAScript-specific: the 'u' and 'v' flags change the encoding - if (optBuilder.getFlavor() == ECMAScriptFlavor.INSTANCE && !optBuilder.isUtf16ExplodeAstralSymbols() && optBuilder.getEncoding() == Encodings.UTF_16_RAW && - (flags.indexOf('u') >= 0 || flags.indexOf('v') >= 0)) { - optBuilder.encoding(Encodings.UTF_16); + if (optBuilder.getFlavor() == ECMAScriptFlavor.INSTANCE) { + if (flags.indexOf('u') >= 0 || flags.indexOf('v') >= 0) { + if (!optBuilder.isUtf16ExplodeAstralSymbols() && optBuilder.getEncoding() == Encodings.UTF_16_RAW) { + optBuilder.encoding(Encodings.UTF_16); + } + } else { + if (optBuilder.getEncoding() == Encodings.UTF_16) { + optBuilder.encoding(Encodings.UTF_16_RAW); + } + } } return new RegexSource(pattern, flags, optBuilder.build(), source); } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexSyntaxException.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexSyntaxException.java index a073c74fc576..ba5f62233b2d 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexSyntaxException.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/RegexSyntaxException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -52,22 +52,45 @@ @ExportLibrary(InteropLibrary.class) public final class RegexSyntaxException extends AbstractTruffleException { + public enum ErrorCode { + InvalidBackReference, + InvalidCharacterClass, + InvalidEscape, + InvalidFlag, + InvalidGroup, + InvalidInlineFlag, + InvalidLookbehind, + InvalidNamedGroup, + InvalidOption, + InvalidQuantifier, + InvalidSubexpressionCall, + UnfinishedSequence, + UnmatchedBracket, + UnmatchedParenthesis, + TRegexBailout; + + public int intValue() { + return -(ordinal() + 3); + } + } + private final SourceSection sourceSection; + private final ErrorCode errorCode; public static RegexSyntaxException createOptions(Source source, String msg, int position) { - return new RegexSyntaxException(msg, source, position); + return new RegexSyntaxException(msg, source, position, ErrorCode.InvalidOption); } - public static RegexSyntaxException createPattern(RegexSource source, String msg, int position) { - return new RegexSyntaxException(msg, patternSource(source), position); + public static RegexSyntaxException createPattern(RegexSource source, String msg, int position, ErrorCode errorCode) { + return new RegexSyntaxException(msg, patternSource(source), position, errorCode); } public static RegexSyntaxException createFlags(RegexSource source, String msg) { - return new RegexSyntaxException(msg, flagsSource(source), 0); + return new RegexSyntaxException(msg, flagsSource(source), 0, ErrorCode.InvalidFlag); } public static RegexSyntaxException createFlags(RegexSource source, String msg, int position) { - return new RegexSyntaxException(msg, flagsSource(source), position); + return new RegexSyntaxException(msg, flagsSource(source), position, ErrorCode.InvalidFlag); } @TruffleBoundary @@ -89,10 +112,22 @@ private static Source flagsSource(RegexSource regexSource) { } @TruffleBoundary - private RegexSyntaxException(String reason, Source src, int position) { + private RegexSyntaxException(String reason, Source src, int position, ErrorCode errorCode) { super(reason); assert position <= src.getLength(); this.sourceSection = src.createSection(position, src.getLength() - position); + this.errorCode = errorCode; + } + + @TruffleBoundary + private RegexSyntaxException(String reason, SourceSection sourceSection, ErrorCode errorCode) { + super(reason); + this.sourceSection = sourceSection; + this.errorCode = errorCode; + } + + public RegexSyntaxException withErrorCodeInMessage() { + return new RegexSyntaxException(errorCode.name() + ' ' + getMessage(), sourceSection, errorCode); } @ExportMessage @@ -112,6 +147,10 @@ SourceSection getSourceSection() { return sourceSection; } + public ErrorCode getErrorCode() { + return errorCode; + } + private static final long serialVersionUID = 1L; } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/analysis/InputStringGenerator.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/analysis/InputStringGenerator.java index e233431839d3..e60bbb26416b 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/analysis/InputStringGenerator.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/analysis/InputStringGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -143,7 +143,7 @@ public TruffleString toTString(Random rng, TruffleString.Encoding encoding) { int iRange = rng.nextInt(codePointSet.size()); codepoints[i] = rng.nextInt(codePointSet.getLo(iRange), codePointSet.getHi(iRange) + 1); } else if (e instanceof BackRefElement backRef) { - codepoints[i] = codepoints[backRef.ref]; + codepoints[i] = codepoints[backRef.ref + nPrepended]; } } return TruffleString.fromIntArrayUTF32Uncached(codepoints).switchEncodingUncached(encoding); @@ -642,7 +642,7 @@ private void processTerm(Term term) { } afterTerm(term); } else if (term.isSubexpressionCall()) { - processGroup(ast.getGroup(term.asSubexpressionCall().getGroupNr())); + processGroup(ast.getGroup(term.asSubexpressionCall().getGroupNr()).get(0)); } else { throw CompilerDirectives.shouldNotReachHere(); } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/charset/ClassSetContents.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/charset/ClassSetContents.java index ab5b8dc87f02..4d8fed4d4b18 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/charset/ClassSetContents.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/charset/ClassSetContents.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -128,6 +128,10 @@ public ClassSetContents caseFold(CodePointSetAccumulator tmp) { return new ClassSetContents(kind, CaseFoldData.simpleCaseFold(codePointSet, tmp), foldedStrings, mayContainStrings); } + public boolean isEmpty() { + return codePointSet.isEmpty() && strings.isEmpty(); + } + public EconomicSet getStrings() { return strings; } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/charset/Constants.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/charset/Constants.java index 5e116bed6b1c..2c5cd68d0018 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/charset/Constants.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/charset/Constants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -258,7 +258,7 @@ public final class Constants { public static final CodePointSet WORD_CHARS_UNICODE_SETS_IGNORE_CASE = CaseFoldData.simpleCaseFold(WORD_CHARS, new CodePointSetAccumulator()); - public static final CodePointSet NON_WORD_CHARS_UNICODE_SETS_IGNORE_CASE = WORD_CHARS_UNICODE_SETS_IGNORE_CASE.createInverse(CaseFoldData.FOLDABLE_CHARACTERS, + public static final CodePointSet NON_WORD_CHARS_UNICODE_SETS_IGNORE_CASE = WORD_CHARS_UNICODE_SETS_IGNORE_CASE.createInverse(CaseFoldData.FOLDED_CHARACTERS, new CompilationBuffer(Encodings.UTF_16)); } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/errors/JsErrorMessages.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/errors/JsErrorMessages.java index d72759c93257..fd352766868d 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/errors/JsErrorMessages.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/errors/JsErrorMessages.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -50,9 +50,11 @@ public class JsErrorMessages { public static final String CHAR_CLASS_RANGE_OUT_OF_ORDER = "Range out of order in character class"; public static final String COMPLEMENT_OF_STRING_SET = "Negated character class may contain strings"; public static final String EMPTY_GROUP_NAME = "Empty named capture group name"; + public static final String EMPTY_MODIFIER = "No flags in modifier"; public static final String ENDS_WITH_UNFINISHED_ESCAPE_SEQUENCE = "Ends with an unfinished escape sequence"; public static final String ENDS_WITH_UNFINISHED_UNICODE_PROPERTY = "Ends with an unfinished Unicode property escape"; public static final String INCOMPLETE_QUANTIFIER = "Incomplete quantifier"; + public static final String INCOMPLETE_MODIFIER = "Incomplete modifier"; public static final String INVALID_CHARACTER_CLASS = "Invalid character class"; public static final String INVALID_CHARACTER_IN_CHARACTER_CLASS = "Invalid character in character class"; public static final String INVALID_CONTROL_CHAR_ESCAPE = "Invalid control char escape"; @@ -60,20 +62,24 @@ public class JsErrorMessages { public static final String INVALID_GROUP = "Invalid group"; public static final String INVALID_GROUP_NAME_PART = "Invalid character in group name"; public static final String INVALID_GROUP_NAME_START = "Invalid character at start of group name"; + public static final String INVALID_MODIFIER = "Invalid modifier"; public static final String INVALID_UNICODE_ESCAPE = "Invalid Unicode escape"; public static final String INVALID_UNICODE_PROPERTY = "Invalid Unicode property escape"; public static final String MISSING_GROUP_FOR_BACKREFERENCE = "Missing capture group for backreference"; public static final String MISSING_GROUP_NAME = "Missing group name in named capture group reference"; + public static final String MODIFIER_BOTH_ADDING_AND_REMOVING_FLAG = "Modifier is both adding and removing the same flag"; public static final String MULTIPLE_GROUPS_SAME_NAME = "Multiple named capture groups with the same name"; public static final String QUANTIFIER_ON_LOOKAHEAD_ASSERTION = "Quantifier on lookahead assertion"; public static final String QUANTIFIER_ON_LOOKBEHIND_ASSERTION = "Quantifier on lookbehind assertion"; public static final String QUANTIFIER_ON_QUANTIFIER = "Quantifier on quantifier"; public static final String QUANTIFIER_OUT_OF_ORDER = "Numbers out of order in {} quantifier"; public static final String QUANTIFIER_WITHOUT_TARGET = "Quantifier without target"; + public static final String REPEATED_FLAG_IN_MODIFIER = "Repeated regex flag in modifier"; public static final String UNMATCHED_LEFT_BRACKET = "Unterminated character class"; public static final String UNMATCHED_RIGHT_BRACKET = "Unmatched ']'"; public static final String UNMATCHED_RIGHT_PARENTHESIS = "Unmatched ')'"; public static final String UNMATCHED_RIGHT_BRACE = "Unmatched '}'"; + public static final String UNSUPPORTED_FLAG_IN_MODIFIER = "Invalid regular expression flag in modifier"; public static final String UNTERMINATED_GROUP = "Unterminated group"; public static final String UNTERMINATED_GROUP_NAME = "Unterminated group name"; public static final String UNTERMINATED_STRING_SET = "Unterminated string set"; @@ -103,6 +109,10 @@ public static String invalidRegularExpression(RegexSource source, String message return String.format("Invalid regular expression: %s: %s", source, message); } + public static String flagNotAllowedInModifier(char flagChar) { + return String.format("Flag '%s' not allowed in modifier", flagChar); + } + /* flag related errors */ public static final String REPEATED_FLAG = "Repeated regex flag"; diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/errors/PyErrorMessages.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/errors/PyErrorMessages.java index 73948c4e5182..02e3005a91f2 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/errors/PyErrorMessages.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/errors/PyErrorMessages.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -59,6 +59,7 @@ public interface PyErrorMessages { String INLINE_FLAGS_CANNOT_USE_U_FLAG_WITH_A_BYTES_PATTERN = "bad inline flags: cannot use 'u' flag with a bytes pattern"; String INLINE_FLAGS_FLAGS_A_U_AND_L_ARE_INCOMPATIBLE = "bad inline flags: flags 'a', 'u' and 'L' are incompatible"; String INLINE_FLAGS_FLAG_TURNED_ON_AND_OFF = "bad inline flags: flag turned on and off"; + String LOOK_BEHIND_REQUIRES_FIXED_WIDTH_PATTERN = "look-behind requires fixed-width pattern"; String MIN_REPEAT_GREATER_THAN_MAX_REPEAT = "min repeat greater than max repeat"; String MISSING_COLON = "missing :"; String MISSING_DASH_COLON_PAREN = "missing -, : or )"; diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/TRegexCompilationRequest.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/TRegexCompilationRequest.java index b54daf3f7154..d6e5d2888b84 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/TRegexCompilationRequest.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/TRegexCompilationRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -278,16 +278,16 @@ TRegexExecNode.LazyCaptureGroupRegexSearchNode compileLazyDFAExecutor(TRegexExec boolean traceFinder = preCalculatedResults != null && preCalculatedResults.length > 0; final boolean trackLastGroup = ast.getOptions().getFlavor().usesLastGroupResultField(); executorNodeForward = createDFAExecutor(nfa, true, true, false, allowSimpleCG && !traceFinder && !(ast.getRoot().startsWithCaret() && !properties.hasCaptureGroups()), trackLastGroup); - final boolean createCaptureGroupTracker = !executorNodeForward.isSimpleCG() && (properties.hasCaptureGroups() || properties.hasLookAroundAssertions() || ast.getOptions().isMustAdvance()) && - !traceFinder; - if (createCaptureGroupTracker) { - executorNodeCaptureGroups = createDFAExecutor(nfa, true, false, true, false, trackLastGroup); - } if (traceFinder && preCalculatedResults.length > 1) { executorNodeBackward = createDFAExecutor(traceFinderNFA, false, false, false, false, false); } else if (!executorNodeForward.isAnchored() && !executorNodeForward.isSimpleCG() && (!traceFinder || !nfa.hasReverseUnAnchoredEntry()) && !source.getOptions().isBooleanMatch()) { executorNodeBackward = createDFAExecutor(nfa, false, false, false, allowSimpleCG && !(ast.getRoot().endsWithDollar() && !properties.hasCaptureGroups()), trackLastGroup); } + final boolean createCaptureGroupTracker = !executorNodeForward.isSimpleCG() && (executorNodeBackward == null || !executorNodeBackward.isSimpleCG()) && + (properties.hasCaptureGroups() || properties.hasLookAroundAssertions() || ast.getOptions().isMustAdvance()) && !traceFinder; + if (createCaptureGroupTracker) { + executorNodeCaptureGroups = createDFAExecutor(nfa, true, false, true, false, trackLastGroup); + } logAutomatonSizes(rootNode); return new TRegexExecNode.LazyCaptureGroupRegexSearchNode( language, source, ast.getFlags(), preCalculatedResults, @@ -367,58 +367,91 @@ public TRegexDFAExecutorNode createDFAExecutor(NFA nfaArg, TRegexDFAExecutorProp private void debugAST() { if (source.getOptions().isDumpAutomata()) { - Env env = RegexContext.get(null).getEnv(); - TruffleFile file = env.getPublicTruffleFile("./ast.tex"); - ASTLaTexExportVisitor.exportLatex(ast, file); - file = env.getPublicTruffleFile("ast.json"); - ast.getWrappedRoot().toJson().dump(file); + dumpAST(); } } + private void dumpAST() { + Env env = RegexContext.get(null).getEnv(); + TruffleFile file = env.getPublicTruffleFile("./ast.tex"); + ASTLaTexExportVisitor.exportLatex(ast, file); + file = env.getPublicTruffleFile("ast.json"); + ast.getWrappedRoot().toJson().dump(file); + } + private void debugPureNFA() { if (source.getOptions().isDumpAutomata()) { - Env env = RegexContext.get(null).getEnv(); - TruffleFile file = env.getPublicTruffleFile("pure_nfa.json"); - Json.obj(Json.prop("dfa", Json.obj( - Json.prop("pattern", source.toString()), - Json.prop("pureNfa", pureNFA.toJson(ast))))).dump(file); + dumpPureNFA(); + } + } + + private void dumpPureNFA() { + dumpPureNFA(pureNFA, "pure_nfa.json"); + PureNFA[] subtrees = pureNFA.getSubtrees(); + for (int i = 0; i < subtrees.length; i++) { + PureNFA subtree = subtrees[i]; + dumpPureNFA(subtree, String.format("pure_nfa_%d.json", i)); } } + private void dumpPureNFA(PureNFA subtree, String fileName) { + Env env = RegexContext.get(null).getEnv(); + TruffleFile file = env.getPublicTruffleFile(fileName); + Json.obj(Json.prop("dfa", Json.obj( + Json.prop("pattern", source.toString()), + Json.prop("pureNfa", subtree.toJson(ast))))).dump(file); + } + private void debugNFA() { if (source.getOptions().isDumpAutomata()) { - Env env = RegexContext.get(null).getEnv(); - TruffleFile file = env.getPublicTruffleFile("./nfa.gv"); - NFAExport.exportDot(nfa, file, true, false); - file = env.getPublicTruffleFile("./nfa.tex"); - NFAExport.exportLaTex(nfa, file, false, true); - file = env.getPublicTruffleFile("./nfa_reverse.gv"); - NFAExport.exportDotReverse(nfa, file, true, false); - file = env.getPublicTruffleFile("nfa.json"); - Json.obj(Json.prop("dfa", Json.obj(Json.prop("pattern", source.toString()), Json.prop("nfa", nfa.toJson(true))))).dump(file); + dumpNFA(); } } + private void dumpNFA() { + Env env = RegexContext.get(null).getEnv(); + TruffleFile file = env.getPublicTruffleFile("./nfa.gv"); + NFAExport.exportDot(nfa, file, true, false); + file = env.getPublicTruffleFile("./nfa.tex"); + NFAExport.exportLaTex(nfa, file, false, true); + file = env.getPublicTruffleFile("./nfa_reverse.gv"); + NFAExport.exportDotReverse(nfa, file, true, false); + file = env.getPublicTruffleFile("nfa.json"); + dumpNFAJson(file, nfa, true); + } + + private void dumpNFAJson(TruffleFile file, NFA dumpNFA, boolean forward) { + Json.obj(Json.prop("dfa", Json.obj(Json.prop("pattern", source.toString()), Json.prop("nfa", dumpNFA.toJson(forward))))).dump(file); + } + private void debugTraceFinder() { if (source.getOptions().isDumpAutomata()) { - Env env = RegexContext.get(null).getEnv(); - TruffleFile file = env.getPublicTruffleFile("./trace_finder.gv"); - NFAExport.exportDotReverse(traceFinderNFA, file, true, false); - file = env.getPublicTruffleFile("nfa_trace_finder.json"); - traceFinderNFA.toJson().dump(file); + dumpTraceFinder(); } } + private void dumpTraceFinder() { + Env env = RegexContext.get(null).getEnv(); + TruffleFile file = env.getPublicTruffleFile("./trace_finder.gv"); + NFAExport.exportDotReverse(traceFinderNFA, file, true, false); + file = env.getPublicTruffleFile("nfa_trace_finder.json"); + dumpNFAJson(file, traceFinderNFA, false); + } + private void debugDFA(DFAGenerator dfa, String debugDumpName) { if (source.getOptions().isDumpAutomata()) { - Env env = RegexContext.get(null).getEnv(); - TruffleFile file = env.getPublicTruffleFile("dfa_" + dfa.getDebugDumpName(debugDumpName) + ".gv"); - DFAExport.exportDot(dfa, file, false); - file = env.getPublicTruffleFile("dfa_" + dfa.getDebugDumpName(debugDumpName) + ".json"); - Json.obj(Json.prop("dfa", dfa.toJson())).dump(file); + dumpDFA(dfa, debugDumpName); } } + private static void dumpDFA(DFAGenerator dfa, String debugDumpName) { + Env env = RegexContext.get(null).getEnv(); + TruffleFile file = env.getPublicTruffleFile("dfa_" + dfa.getDebugDumpName(debugDumpName) + ".gv"); + DFAExport.exportDot(dfa, file, false); + file = env.getPublicTruffleFile("dfa_" + dfa.getDebugDumpName(debugDumpName) + ".json"); + Json.obj(Json.prop("dfa", dfa.toJson())).dump(file); + } + private static boolean shouldLogPhases() { return Loggers.LOG_PHASES.isLoggable(Level.FINER); } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/buffer/LongArrayBuffer.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/buffer/LongArrayBuffer.java index c86eb3951081..e2a61d2067ab 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/buffer/LongArrayBuffer.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/buffer/LongArrayBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -120,6 +120,15 @@ public LongArrayBuffer asFixedSizeArray(int size, int initialValue) { return this; } + public boolean contains(long value) { + for (long v : this) { + if (v == value) { + return true; + } + } + return false; + } + public long[] toArray() { return isEmpty() ? EmptyArrays.LONG : Arrays.copyOf(buf, length); } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/buffer/ObjectArrayBuffer.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/buffer/ObjectArrayBuffer.java index eb3a5a1eaa7d..71001dc12f20 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/buffer/ObjectArrayBuffer.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/buffer/ObjectArrayBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -44,6 +44,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; +import java.util.function.IntFunction; /** * This class is designed as a "scratchpad" for generating many Object arrays of unknown size. It @@ -151,6 +152,10 @@ public ST[] toArray(ST[] a) { return a; } + public ST[] toArray(IntFunction generator) { + return toArray(generator.apply(length)); + } + @Override public Iterator iterator() { return new ObjectBufferIterator<>(buf, length); diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/dfa/DFAGenerator.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/dfa/DFAGenerator.java index 9e3e3ce98bbf..c3d98308e353 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/dfa/DFAGenerator.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/dfa/DFAGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -183,6 +183,10 @@ public DFAStateNodeBuilder[] getEntryStates() { return entryStates; } + private DFAStateNodeBuilder getMaxOffsetAnchoredInitialState() { + return entryStates[nfa.getAnchoredEntry().length - 1]; + } + private DFAStateNodeBuilder getUnanchoredInitialState() { return entryStates[nfa.getAnchoredEntry().length]; } @@ -335,6 +339,7 @@ public TRegexDFAExecutorNode createDFAExecutor() { assert states[0] == null; short[] entryStateIDs = new short[entryStates.length]; short[] cgLastTransition = isGenericCG() ? new short[entryStates.length] : null; + DFASimpleCGTransition[] initialStateSimpleCGTransitions = !isForward() && doSimpleCG ? new DFASimpleCGTransition[entryStates.length] : null; for (int i = 0; i < entryStates.length; i++) { if (entryStates[i] == null) { entryStateIDs[i] = -1; @@ -343,6 +348,8 @@ public TRegexDFAExecutorNode createDFAExecutor() { if (isGenericCG()) { DFACaptureGroupLazyTransitionBuilder lt = getLazyTransition(initialCGTransitions[i]); cgLastTransition[i] = lt.getLastTransitionIndex(); + } else if (!isForward() && doSimpleCG) { + initialStateSimpleCGTransitions[i] = DFASimpleCGTransition.create(entryStates[i].getNfaTransitionSet().getTransition(0), false); } } } @@ -350,7 +357,9 @@ public TRegexDFAExecutorNode createDFAExecutor() { assert getReplacement(getUnanchoredInitialState().getId()) instanceof DFAFindInnerLiteralStateNode; entryStateIDs = new short[]{(short) getUnanchoredInitialState().getId(), (short) getUnanchoredInitialState().getId()}; } - states[0] = new DFAInitialStateNode(entryStateIDs, cgLastTransition); + DFASimpleCG initialStateSimpleCG = initialStateSimpleCGTransitions == null ? null + : DFASimpleCG.create(initialStateSimpleCGTransitions, DFASimpleCGTransition.getEmptyInstance(), DFASimpleCGTransition.getEmptyInstance()); + states[0] = new DFAInitialStateNode(entryStateIDs, cgLastTransition, initialStateSimpleCG); if (TRegexOptions.TRegexEnableNodeSplitter) { states = tryMakeReducible(states); } @@ -384,14 +393,47 @@ private void createInitialStatesForward() { private void createInitialStatesBackward() { entryStates = new DFAStateNodeBuilder[]{null, null}; if (nfa.hasReverseUnAnchoredEntry()) { - entryStates[0] = createInitialState(createTransitionBuilder(createNFATransitionSet(nfa.getReverseAnchoredEntry(), nfa.getReverseUnAnchoredEntry()))); - entryStates[1] = createInitialState(createTransitionBuilder(createNFATransitionSet(nfa.getReverseUnAnchoredEntry()))); + entryStates[0] = createInitialStateBackward(nfa.getReverseAnchoredEntry(), nfa.getReverseUnAnchoredEntry()); + entryStates[1] = createInitialStateBackward(nfa.getReverseUnAnchoredEntry()); } else { - entryStates[0] = createInitialState(createTransitionBuilder(createNFATransitionSet(nfa.getReverseAnchoredEntry()))); + entryStates[0] = createInitialStateBackward(nfa.getReverseAnchoredEntry()); entryStates[1] = null; } } + private DFAStateNodeBuilder createInitialStateBackward(NFAStateTransition... entries) { + /* + * In forward mode, a DFA state consists of a set of NFA transitions, and a DFA state is + * considered final if one of its NFA transitions' targets _contains a subsequent + * transition_ to a NFA final state. + * + * In backward mode, we skip the NFA's final state transitions, so the backward DFA states + * consist of the same NFA transitions as the forward DFA states. As a consequence of this, + * backward DFA states are considered final if one of their NFA transition's targets _is_ a + * backward NFA final state. + */ + ObjectArrayBuffer transitionSet = compilationBuffer.getObjectBuffer1(); + StateSet stateSet = StateSet.create(nfa); + CodePointSet cps = null; + GroupBoundaries groupBoundaries = null; + for (NFAStateTransition entry : entries) { + // skip past reverse entry transitions (i.e. forward final transitions) + for (NFAStateTransition predecessor : entry.getTarget(false).getPredecessors()) { + if (groupBoundaries == null) { + cps = predecessor.getCodePointSet(); + groupBoundaries = predecessor.getGroupBoundaries(); + } else { + if (!predecessor.getGroupBoundaries().equals(groupBoundaries) || !predecessor.getCodePointSet().equals(cps)) { + hasAmbiguousStates = true; + } + } + stateSet.add(predecessor.getTarget(false)); + transitionSet.add(predecessor); + } + } + return createInitialState(createTransitionBuilder(new TransitionSet<>(transitionSet.toArray(NFAStateTransition[]::new), stateSet))); + } + private DFAStateNodeBuilder createInitialState(DFAStateTransitionBuilder transition) { DFAStateNodeBuilder lookup = lookupState(transition.getTransitionSet(), false); if (lookup == null) { @@ -413,15 +455,21 @@ private void expandState(DFAStateNodeBuilder state) { boolean allPrefixStateSuccessors = true; outer: for (NFAStateTransition transition : state.getNfaTransitionSet().getTransitions()) { NFAState nfaState = transition.getTarget(isForward()); - for (NFAStateTransition nfaTransition : nfaState.getSuccessors(isForward())) { - NFAState target = nfaTransition.getTarget(isForward()); - if (!target.isFinalState(isForward()) && (!state.isBackwardPrefixState() || target.hasPrefixStates())) { - anyPrefixStateSuccessors |= target.hasPrefixStates(); - allPrefixStateSuccessors &= target.hasPrefixStates(); - canonicalizer.addArgument(nfaTransition, isForward() ? nfaTransition.getCodePointSet() : target.getCharSet()); - } else if (isForward() && target.isUnAnchoredFinalState()) { - assert target == nfa.getReverseUnAnchoredEntry().getSource(); - break outer; + if (isForward()) { + for (NFAStateTransition nfaTransition : nfaState.getSuccessors(true)) { + NFAState target = nfaTransition.getTarget(true); + if (!target.isFinalState(true)) { + canonicalizer.addArgument(nfaTransition, nfaTransition.getCodePointSet()); + } else if (target.isUnAnchoredFinalState()) { + assert target == nfa.getReverseUnAnchoredEntry().getSource(); + break outer; + } + } + } else if (!nfaState.isFinalState(false) && (!state.isBackwardPrefixState() || nfaState.hasPrefixStates())) { + for (NFAStateTransition nfaTransition : nfaState.getSuccessors(false)) { + anyPrefixStateSuccessors |= nfaState.hasPrefixStates(); + allPrefixStateSuccessors &= nfaState.hasPrefixStates(); + canonicalizer.addArgument(nfaTransition, nfaTransition.getCodePointSet()); } } } @@ -619,19 +667,26 @@ private void tryInnerLiteralOptimization() { int literalStart = props.getInnerLiteralStart(); Sequence rootSeq = nfa.getAst().getRoot().getFirstAlternative(); - boolean prefixHasLookAhead = false; + boolean maybeOverlappingLookArounds = false; // find all parser tree nodes of the prefix StateSet prefixAstNodes = StateSet.create(nfa.getAst()); for (int i = 0; i < literalStart; i++) { Term t = rootSeq.getTerms().get(i); - prefixHasLookAhead |= t.hasLookAheads(); + maybeOverlappingLookArounds |= t.hasLookArounds(); AddToSetVisitor.addCharacterClasses(prefixAstNodes, t); } + boolean lookBehindsAfterLiteral = false; + for (int i = literalEnd; i < rootSeq.size(); i++) { + Term t = rootSeq.getTerms().get(i); + lookBehindsAfterLiteral |= t.hasLookBehinds(); + } + maybeOverlappingLookArounds |= lookBehindsAfterLiteral; // find NFA states of the prefix and the beginning and end of the literal StateSet prefixNFAStates = StateSet.create(nfa); - if (nfa.getUnAnchoredInitialState() != null) { - prefixNFAStates.add(nfa.getUnAnchoredInitialState()); + NFAState unAnchoredInitialState = nfa.getMaxOffsetUnAnchoredInitialState(); + if (unAnchoredInitialState != null) { + prefixNFAStates.add(unAnchoredInitialState); } NFAState literalFirstState = null; NFAState literalLastState = null; @@ -639,7 +694,7 @@ private void tryInnerLiteralOptimization() { if (s == null) { continue; } - if (!prefixHasLookAhead && !s.getStateSet().isEmpty() && prefixAstNodes.containsAll(s.getStateSet())) { + if (!maybeOverlappingLookArounds && !s.getStateSet().isEmpty() && prefixAstNodes.containsAll(s.getStateSet())) { prefixNFAStates.add(s); } if (s.getStateSet().contains(rootSeq.getTerms().get(literalStart))) { @@ -657,7 +712,7 @@ private void tryInnerLiteralOptimization() { literalLastState = s; } } - if (prefixHasLookAhead) { + if (maybeOverlappingLookArounds) { // If there are look-ahead assertions in the prefix, we cannot decide whether a // given NFA state belongs to the prefix just by its AST nodes alone, since a // look-ahead may be merged with nodes of the postfix as well. Therefore, we instead @@ -665,8 +720,9 @@ private void tryInnerLiteralOptimization() { // state to the literal's first state. ArrayList bfsCur = new ArrayList<>(prefixNFAStates); ArrayList bfsNext = new ArrayList<>(); - if (nfa.getAnchoredInitialState() != null) { - bfsCur.add(nfa.getAnchoredInitialState()); + NFAState anchoredInitialState = nfa.getMaxOffsetAnchoredInitialState(); + if (anchoredInitialState != null) { + bfsCur.add(anchoredInitialState); } while (!bfsCur.isEmpty()) { for (NFAState s : bfsCur) { @@ -725,7 +781,7 @@ private void tryInnerLiteralOptimization() { return; } - if (literalStart > 0) { + if (literalStart > 0 || lookBehindsAfterLiteral) { /* * Check if it is possible to match the literal beginning from the prefix, and bail out * if that is the case. Otherwise, the resulting DFA would produce wrong results on e.g. @@ -798,8 +854,8 @@ private void tryInnerLiteralOptimization() { nfa.getReverseAnchoredEntry().setSource(literalFirstState); nfa.getReverseUnAnchoredEntry().setSource(literalFirstState); assert innerLiteralPrefixMatcher == null; - innerLiteralPrefixMatcher = compilationRequest.createDFAExecutor(nfa, new TRegexDFAExecutorProperties(false, false, false, doSimpleCG, - false, rootSeq.getTerms().get(literalStart - 1).getMinPath()), "innerLiteralPrefix"); + int minResultLength = rootSeq.getTerms().get(literalStart).getMinPath() - 1; + innerLiteralPrefixMatcher = compilationRequest.createDFAExecutor(nfa, new TRegexDFAExecutorProperties(false, false, false, doSimpleCG, false, minResultLength), "innerLiteralPrefix"); innerLiteralPrefixMatcher.getProperties().setSimpleCGMustCopy(false); doSimpleCG = doSimpleCG && innerLiteralPrefixMatcher.isSimpleCG(); nfa.setInitialLoopBack(true); @@ -809,7 +865,6 @@ private void tryInnerLiteralOptimization() { executorProps.setCanFindStart(innerLiteralCanFindMatchStart(unanchoredInitialState, literalLastDFAState)); registerStateReplacement(unanchoredInitialState.getId(), new DFAFindInnerLiteralStateNode((short) unanchoredInitialState.getId(), new short[]{(short) literalLastDFAState.getId()}, nfa.getAst().extractInnerLiteral())); - } /** @@ -848,7 +903,7 @@ private boolean innerLiteralCanFindMatchStart(DFAStateNodeBuilder unanchoredInit } private boolean innerLiteralMatchesPrefix(StateSet prefixNFAStates) { - if (innerLiteralTryMatchPrefix(prefixNFAStates, entryStates[0].getNfaTransitionSet().getTargetStateSet().copy())) { + if (innerLiteralTryMatchPrefix(prefixNFAStates, getMaxOffsetAnchoredInitialState().getNfaTransitionSet().getTargetStateSet().copy())) { return true; } for (NFAState s : prefixNFAStates) { diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/dfa/DFAStateNodeBuilder.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/dfa/DFAStateNodeBuilder.java index f987a860e062..52741d485c9e 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/dfa/DFAStateNodeBuilder.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/dfa/DFAStateNodeBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -288,53 +288,44 @@ public void clearPreCalculatedResults() { public DFAStateNodeBuilder updateFinalStateData(DFAGenerator dfaGenerator) { boolean forward = dfaGenerator.isForward(); boolean traceFinder = dfaGenerator.getNfa().isTraceFinderNFA(); - for (NFAStateTransition t : nfaTransitionSet.getTransitions()) { - NFAState target = t.getTarget(forward); - if (target.hasTransitionToAnchoredFinalState(forward)) { - if (anchoredFinalStateTransition == null) { - if (traceFinder && isBackwardPrefixState()) { - for (NFAStateTransition t2 : target.getSuccessors(forward)) { - NFAState target2 = t2.getTarget(forward); - if (target2.isAnchoredFinalState(forward) && target2.hasPrefixStates()) { - setAnchoredFinalState(); - setAnchoredFinalStateTransition(t2); - } - } - } else { - setAnchoredFinalState(); - setAnchoredFinalStateTransition(target.getFirstTransitionToFinalState(forward)); - } + if (forward) { + for (NFAStateTransition t : nfaTransitionSet.getTransitions()) { + // In forward mode, a state is final if it contains a NFA transition to a NFA state + // that has a subsequent transition to a final state + NFAState target = t.getTarget(true); + if (target.hasTransitionToAnchoredFinalState(true) && anchoredFinalStateTransition == null) { + setAnchoredFinalState(); + setAnchoredFinalStateTransition(target.getFirstTransitionToFinalState(true)); } - } - if (target.hasTransitionToUnAnchoredFinalState(forward)) { - if (traceFinder && isBackwardPrefixState()) { - for (NFAStateTransition t2 : target.getSuccessors(forward)) { - NFAState target2 = t2.getTarget(forward); - if (target2.isUnAnchoredFinalState(forward) && target2.hasPrefixStates()) { - setUnAnchoredFinalState(); - setUnAnchoredFinalStateTransition(t2); - } - } - } else { + if (target.hasTransitionToUnAnchoredFinalState(true)) { setUnAnchoredFinalState(); - setUnAnchoredFinalStateTransition(target.getTransitionToUnAnchoredFinalState(forward)); - } - if (forward) { + setUnAnchoredFinalStateTransition(target.getTransitionToUnAnchoredFinalState(true)); return this; } } - if (traceFinder) { - for (NFAStateTransition t2 : target.getSuccessors(forward)) { - NFAState target2 = t2.getTarget(forward); - if (!isBackwardPrefixState() || target2.hasPrefixStates()) { - if (target2.isAnchoredFinalState(forward)) { - assert target2.hasPossibleResults() && target2.getPossibleResults().numberOfSetBits() == 1; - updatePreCalcAnchoredResult(target2.getPossibleResults().iterator().nextInt()); + } else { + for (NFAStateTransition t : nfaTransitionSet.getTransitions()) { + // In backward mode, a state is final if it contains a NFA transition to a NFA final + // state + NFAState target = t.getTarget(false); + if (target.isAnchoredFinalState(false)) { + if (!(traceFinder && isBackwardPrefixState()) || target.hasPrefixStates()) { + if (traceFinder) { + assert target.hasPossibleResults() && target.getPossibleResults().numberOfSetBits() == 1; + updatePreCalcAnchoredResult(target.getPossibleResults().iterator().nextInt()); } - if (target2.isUnAnchoredFinalState(forward)) { - assert target2.hasPossibleResults() && target2.getPossibleResults().numberOfSetBits() == 1; - updatePreCalcUnAnchoredResult(target2.getPossibleResults().iterator().nextInt()); + setAnchoredFinalState(); + setAnchoredFinalStateTransition(t); + } + } + if (target.isUnAnchoredFinalState(false)) { + if (!(traceFinder && isBackwardPrefixState()) || target.hasPrefixStates()) { + if (traceFinder) { + assert target.hasPossibleResults() && target.getPossibleResults().numberOfSetBits() == 1; + updatePreCalcUnAnchoredResult(target.getPossibleResults().iterator().nextInt()); } + setUnAnchoredFinalState(); + setUnAnchoredFinalStateTransition(t); } } } @@ -414,7 +405,7 @@ public String toString() { @Override public JsonValue toJson() { return Json.obj(Json.prop("id", getId()), - Json.prop("stateSet", Json.array(Arrays.stream(nfaTransitionSet.getTransitions()).map(x -> Json.val(x.getTarget().getId())))), + Json.prop("stateSet", Json.array(Arrays.stream(nfaTransitionSet.getTransitions()).map(x -> Json.val(x.getTarget(isForward()).getId())))), Json.prop("finalState", isUnAnchoredFinalState()), Json.prop("anchoredFinalState", isAnchoredFinalState()), Json.prop("transitions", Arrays.stream(getSuccessors()).map(x -> Json.val(x.getId())))); diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFA.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFA.java index 321edc1f8044..11ce7de2bb95 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFA.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFA.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -117,10 +117,28 @@ public NFAState getUnAnchoredInitialState() { return unAnchoredEntry[0] == null ? null : unAnchoredEntry[0].getTarget(); } + public NFAState getMaxOffsetUnAnchoredInitialState() { + return getMaxOffsetInitialState(unAnchoredEntry); + } + public NFAState getAnchoredInitialState() { return anchoredEntry[0] == null ? null : anchoredEntry[0].getTarget(); } + public NFAState getMaxOffsetAnchoredInitialState() { + return getMaxOffsetInitialState(anchoredEntry); + } + + private static NFAState getMaxOffsetInitialState(NFAStateTransition[] entries) { + NFAState ret = null; + for (NFAStateTransition t : entries) { + if (t != null) { + ret = t.getTarget(); + } + } + return ret; + } + public boolean hasReverseUnAnchoredEntry() { return reverseUnAnchoredEntry != null && reverseUnAnchoredEntry.getSource().getPredecessors().length > 0; } @@ -264,8 +282,8 @@ public void setInitialLoopBack(boolean enable) { public boolean isFixedCodePointWidth() { boolean fixedCodePointWidth = true; - for (NFAState state : states) { - if (state != null && !ast.getEncoding().isFixedCodePointWidth(state.getCharSet())) { + for (NFAStateTransition transition : transitions) { + if (transition != null && !transition.getTarget().isFinalState() && !ast.getEncoding().isFixedCodePointWidth(transition.getCodePointSet())) { fixedCodePointWidth = false; break; } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFAGenerator.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFAGenerator.java index 985bf8e8c534..63d005ac1839 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFAGenerator.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFAGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -50,6 +50,8 @@ import java.util.Map; import java.util.Objects; +import org.graalvm.collections.EconomicMap; + import com.oracle.truffle.regex.charset.CodePointSet; import com.oracle.truffle.regex.tregex.TRegexOptions; import com.oracle.truffle.regex.tregex.automaton.StateSet; @@ -57,6 +59,7 @@ import com.oracle.truffle.regex.tregex.buffer.CompilationBuffer; import com.oracle.truffle.regex.tregex.parser.Counter; import com.oracle.truffle.regex.tregex.parser.ast.CharacterClass; +import com.oracle.truffle.regex.tregex.parser.ast.GroupBoundaries; import com.oracle.truffle.regex.tregex.parser.ast.LookBehindAssertion; import com.oracle.truffle.regex.tregex.parser.ast.MatchFound; import com.oracle.truffle.regex.tregex.parser.ast.PositionAssertion; @@ -65,7 +68,6 @@ import com.oracle.truffle.regex.tregex.parser.ast.Sequence; import com.oracle.truffle.regex.tregex.parser.ast.Term; import com.oracle.truffle.regex.util.TBitSet; -import org.graalvm.collections.EconomicMap; public final class NFAGenerator { @@ -75,6 +77,7 @@ public final class NFAGenerator { private final NFAState dummyInitialState; private final NFAState[] anchoredInitialStates; private final NFAState[] initialStates; + private NFAState checkFinalTransitionState; /** * These are like {@link #initialStates}, but with {@code mustAdvance} set to {@code false}, * i.e. we have already advanced when we are in these states. In a regular expression with @@ -96,6 +99,7 @@ public final class NFAGenerator { private final ASTTransitionCanonicalizer astTransitionCanonicalizer; private final TBitSet transitionGBUpdateIndices; private final TBitSet transitionGBClearIndices; + private int lastGroup = -1; private final ArrayList transitionsBuffer = new ArrayList<>(); private final CompilationBuffer compilationBuffer; @@ -106,15 +110,15 @@ private NFAGenerator(RegexAST ast, CompilationBuffer compilationBuffer) { this.transitionGBClearIndices = new TBitSet(ast.getNumberOfCaptureGroups() * 2); this.astTransitionCanonicalizer = new ASTTransitionCanonicalizer(ast, true, false); this.compilationBuffer = compilationBuffer; - dummyInitialState = new NFAState((short) stateID.inc(), StateSet.create(ast, ast.getWrappedRoot()), CodePointSet.getEmpty(), Collections.emptySet(), false, ast.getOptions().isMustAdvance()); + dummyInitialState = new NFAState((short) stateID.inc(), StateSet.create(ast, ast.getWrappedRoot()), Collections.emptySet(), false, ast.getOptions().isMustAdvance()); nfaStates.put(NFAStateID.create(dummyInitialState), dummyInitialState); anchoredFinalState = createFinalState(StateSet.create(ast, ast.getRoot().getSubTreeParent().getAnchoredFinalState()), false); anchoredFinalState.setAnchoredFinalState(); finalState = createFinalState(StateSet.create(ast, ast.getRoot().getSubTreeParent().getMatchFound()), false); finalState.setUnAnchoredFinalState(); assert transitionGBUpdateIndices.isEmpty() && transitionGBClearIndices.isEmpty(); - anchoredReverseEntry = createTransition(anchoredFinalState, dummyInitialState, ast.getEncoding().getFullSet(), -1); - unAnchoredReverseEntry = createTransition(finalState, dummyInitialState, ast.getEncoding().getFullSet(), -1); + anchoredReverseEntry = createTransition(anchoredFinalState, dummyInitialState, ast.getEncoding().getFullSet()); + unAnchoredReverseEntry = createTransition(finalState, dummyInitialState, ast.getEncoding().getFullSet()); int nEntries = ast.getWrappedPrefixLength() + 1; initialStates = new NFAState[nEntries]; advancedInitialState = ast.getOptions().isMustAdvance() ? createFinalState(StateSet.create(ast, ast.getNFAUnAnchoredInitialState(0)), false) : null; @@ -122,7 +126,7 @@ private NFAGenerator(RegexAST ast, CompilationBuffer compilationBuffer) { for (int i = 0; i < initialStates.length; i++) { initialStates[i] = createFinalState(StateSet.create(ast, ast.getNFAUnAnchoredInitialState(i)), ast.getOptions().isMustAdvance()); initialStates[i].setUnAnchoredInitialState(true); - unAnchoredEntries[i] = createTransition(dummyInitialState, initialStates[i], ast.getEncoding().getFullSet(), -1); + unAnchoredEntries[i] = createTransition(dummyInitialState, initialStates[i], ast.getEncoding().getFullSet()); if (i > 0) { initialStates[i].setHasPrefixStates(true); } @@ -141,7 +145,7 @@ private NFAGenerator(RegexAST ast, CompilationBuffer compilationBuffer) { if (i > 0) { initialStates[i].setHasPrefixStates(true); } - anchoredEntries[i] = createTransition(dummyInitialState, anchoredInitialStates[i], ast.getEncoding().getFullSet(), -1); + anchoredEntries[i] = createTransition(dummyInitialState, anchoredInitialStates[i], ast.getEncoding().getFullSet()); } NFAStateTransition[] dummyInitNext = Arrays.copyOf(anchoredEntries, nEntries * 2); System.arraycopy(unAnchoredEntries, 0, dummyInitNext, nEntries, nEntries); @@ -151,6 +155,14 @@ private NFAGenerator(RegexAST ast, CompilationBuffer compilationBuffer) { dummyInitialState.setPredecessors(dummyInitPrev); } + private NFAState getFinalCheckedTransitionState() { + if (checkFinalTransitionState == null) { + checkFinalTransitionState = createFinalState(StateSet.create(ast, ast.getRoot().getSubTreeParent().getMatchFoundChecked()), ast.getOptions().isMustAdvance()); + checkFinalTransitionState.setSuccessors(new NFAStateTransition[]{createNoCGTransition(checkFinalTransitionState, finalState, ast.getEncoding().getFullSet())}, true); + } + return checkFinalTransitionState; + } + public static NFA createNFA(RegexAST ast, CompilationBuffer compilationBuffer) { return new NFAGenerator(ast, compilationBuffer).doCreateNFA(); } @@ -174,9 +186,9 @@ private NFA doCreateNFA() { } if (ast.getOptions().isMustAdvance()) { addNewLoopBackTransition(initialStates[0], advancedInitialState); - initialLoopBack = createTransition(advancedInitialState, advancedInitialState, ast.getEncoding().getFullSet(), -1); + initialLoopBack = createTransition(advancedInitialState, advancedInitialState, ast.getEncoding().getFullSet()); } else { - initialLoopBack = createTransition(initialStates[0], initialStates[0], ast.getEncoding().getFullSet(), -1); + initialLoopBack = createTransition(initialStates[0], initialStates[0], ast.getEncoding().getFullSet()); } for (NFAState s : nfaStates.values()) { @@ -262,17 +274,19 @@ private NFAStateTransition[] createNFATransitions(NFAState sourceState, ASTStep boolean containsPositionAssertion = false; boolean containsMatchFound = false; boolean containsPrefixStates = false; - int lastGroup = -1; + boolean allCCInLookBehind = true; EconomicMap matchedConditionGroupsMap = ast.getProperties().hasConditionalBackReferences() ? EconomicMap.create() : null; for (ASTTransition astTransition : mergeBuilder.getTransitionSet().getTransitions()) { Term target = astTransition.getTarget(); + boolean inLookBehindAssertion = target.isInLookBehindAssertion(); if (target instanceof CharacterClass) { if (stateSetCC == null) { stateSetCC = StateSet.create(ast); finishedLookBehinds = StateSet.create(ast); } stateSetCC.add((CharacterClass) target); - if (target.isInLookBehindAssertion() && target == ((Sequence) target.getParent()).getLastTerm()) { + allCCInLookBehind &= inLookBehindAssertion; + if (inLookBehindAssertion && target == ((Sequence) target.getParent()).getLastTerm()) { finishedLookBehinds.add((LookBehindAssertion) target.getSubTreeParent()); } } else if (target instanceof PositionAssertion) { @@ -283,7 +297,7 @@ private NFAStateTransition[] createNFATransitions(NFAState sourceState, ASTStep } containsPrefixStates |= target.isPrefix(); astTransition.getGroupBoundaries().updateBitSets(transitionGBUpdateIndices, transitionGBClearIndices); - if (!target.isInLookAheadAssertion() && !target.isInLookBehindAssertion()) { + if (!target.isInLookAroundAssertion()) { lastGroup = astTransition.getGroupBoundaries().getLastGroup(); } if (ast.getProperties().hasConditionalBackReferences()) { @@ -291,45 +305,70 @@ private NFAStateTransition[] createNFATransitions(NFAState sourceState, ASTStep } } if (!(sourceState.isMustAdvance() && transitionGBUpdateIndices.get(0) && transitionGBUpdateIndices.get(1))) { - if (stateSetCC == null) { - if (containsPositionAssertion) { - transitionsBuffer.add(createTransition(sourceState, anchoredFinalState, ast.getEncoding().getFullSet(), lastGroup)); - } else if (containsMatchFound) { - transitionsBuffer.add(createTransition(sourceState, finalState, ast.getEncoding().getFullSet(), lastGroup)); - // Transitions dominated by a transition to a final state will never end - // up being used and so we can skip generating them and return the - // current list of transitions. - transitionGBUpdateIndices.clear(); - transitionGBClearIndices.clear(); - return transitionsBuffer.toArray(new NFAStateTransition[transitionsBuffer.size()]); + if (containsPositionAssertion) { + if (stateSetCC == null || allCCInLookBehind) { + transitionsBuffer.add(createTransition(sourceState, anchoredFinalState, ast.getEncoding().getFullSet())); + } + } else if (stateSetCC == null) { + if (containsMatchFound) { + if (mergeBuilder.getCodePointSet().matchesEverything(ast.getEncoding())) { + transitionsBuffer.add(createTransition(sourceState, finalState, ast.getEncoding().getFullSet())); + // Transitions dominated by a transition to a final state will never + // end up being used, so we can skip generating them and return the + // current list of transitions. + clearGroupBoundaries(); + return transitionsBuffer.toArray(new NFAStateTransition[transitionsBuffer.size()]); + } + // This case is only reachable when merging a lookbehind with an empty + // transition to the final state. + // The issue is that the priority between transitions after running the + // canonicalizer is lost, but that doesn't matter since + // they have disjoint code point sets. But when the transition reaches + // the final state, then it's cps is not checked. + // In that case we use this special checkFinalTransitionState, which + // will still check that last code point set matches. + transitionsBuffer.add(createTransition(sourceState, getFinalCheckedTransitionState(), mergeBuilder.getCodePointSet())); + } + } else { + if (containsMatchFound && allCCInLookBehind) { + // possible when a not fully matched lookbehind is still being tracked + // when the main expression already reached a final state. + transitionsBuffer.add(createTransition(sourceState, finalState, ast.getEncoding().getFullSet())); } - } else if (!containsPositionAssertion) { assert mergeBuilder.getCodePointSet().matchesSomething(); - NFAState targetState = registerMatcherState(stateSetCC, mergeBuilder.getCodePointSet(), finishedLookBehinds, containsPrefixStates, + NFAState targetState = registerMatcherState(stateSetCC, finishedLookBehinds, containsPrefixStates, sourceState.isMustAdvance() && !ast.getHardPrefixNodes().isDisjoint(stateSetCC), matchedConditionGroupsMap); - transitionsBuffer.add(createTransition(sourceState, targetState, mergeBuilder.getCodePointSet(), lastGroup)); + transitionsBuffer.add(createTransition(sourceState, targetState, mergeBuilder.getCodePointSet())); } } - transitionGBUpdateIndices.clear(); - transitionGBClearIndices.clear(); + clearGroupBoundaries(); } } return transitionsBuffer.toArray(new NFAStateTransition[transitionsBuffer.size()]); } + private void clearGroupBoundaries() { + transitionGBUpdateIndices.clear(); + transitionGBClearIndices.clear(); + lastGroup = -1; + } + private NFAState createFinalState(StateSet stateSet, boolean mustAdvance) { - NFAState state = new NFAState((short) stateID.inc(), stateSet, ast.getEncoding().getFullSet(), Collections.emptySet(), false, mustAdvance); + NFAState state = new NFAState((short) stateID.inc(), stateSet, Collections.emptySet(), false, mustAdvance); assert !nfaStates.containsKey(NFAStateID.create(state)); nfaStates.put(NFAStateID.create(state), state); return state; } - private NFAStateTransition createTransition(NFAState source, NFAState target, CodePointSet codePointSet, int lastGroup) { - return new NFAStateTransition((short) transitionID.inc(), source, target, codePointSet, ast.createGroupBoundaries(transitionGBUpdateIndices, transitionGBClearIndices, lastGroup)); + private NFAStateTransition createTransition(NFAState source, NFAState target, CodePointSet codePointSet) { + return new NFAStateTransition((short) transitionID.inc(), source, target, codePointSet, ast.createGroupBoundaries(transitionGBUpdateIndices, transitionGBClearIndices, -1, lastGroup)); + } + + private NFAStateTransition createNoCGTransition(NFAState source, NFAState target, CodePointSet codePointSet) { + return new NFAStateTransition((short) transitionID.inc(), source, target, codePointSet, GroupBoundaries.getEmptyInstance(ast.getLanguage())); } private NFAState registerMatcherState(StateSet stateSetCC, - CodePointSet matcherBuilder, StateSet finishedLookBehinds, boolean containsPrefixStates, boolean mustAdvance, @@ -338,7 +377,7 @@ private NFAState registerMatcherState(StateSet stateSe if (nfaStates.containsKey(nfaStateID)) { return nfaStates.get(nfaStateID); } else { - NFAState state = new NFAState((short) stateID.inc(), stateSetCC, matcherBuilder, finishedLookBehinds, containsPrefixStates, mustAdvance, matchedConditionGroupsMap); + NFAState state = new NFAState((short) stateID.inc(), stateSetCC, finishedLookBehinds, containsPrefixStates, mustAdvance, matchedConditionGroupsMap); expansionQueue.push(state); nfaStates.put(nfaStateID, state); return state; @@ -346,7 +385,7 @@ private NFAState registerMatcherState(StateSet stateSe } private void addNewLoopBackTransition(NFAState source, NFAState target) { - source.addLoopBackNext(createTransition(source, target, ast.getEncoding().getFullSet(), -1)); + source.addLoopBackNext(createTransition(source, target, ast.getEncoding().getFullSet())); if (ast.getHardPrefixNodes().isDisjoint(source.getStateSet()) || ast.getFlags().isSticky()) { target.incPredecessors(); } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFAState.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFAState.java index 2d3856c8c4d6..2ee28a95ed03 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFAState.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFAState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -46,9 +46,10 @@ import java.util.Set; import java.util.stream.Collectors; +import org.graalvm.collections.EconomicMap; + import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.regex.charset.CodePointSet; import com.oracle.truffle.regex.tregex.TRegexOptions; import com.oracle.truffle.regex.tregex.automaton.BasicState; import com.oracle.truffle.regex.tregex.automaton.StateSet; @@ -60,7 +61,6 @@ import com.oracle.truffle.regex.tregex.util.json.JsonConvertible; import com.oracle.truffle.regex.tregex.util.json.JsonObject; import com.oracle.truffle.regex.util.TBitSet; -import org.graalvm.collections.EconomicMap; /** * Represents a single state in the NFA form of a regular expression. States may either be matcher @@ -85,27 +85,24 @@ public final class NFAState extends BasicState imp @CompilationFinal private short revTransitionToAnchoredFinalState = -1; @CompilationFinal private short revTransitionToUnAnchoredFinalState = -1; private TBitSet possibleResults; - private final CodePointSet matcherBuilder; private final Set finishedLookBehinds; private final EconomicMap matchedConditionGroupsMap; public NFAState(short id, StateSet stateSet, - CodePointSet matcherBuilder, Set finishedLookBehinds, boolean hasPrefixStates, boolean mustAdvance) { - this(id, stateSet, initFlags(hasPrefixStates, mustAdvance), null, matcherBuilder, finishedLookBehinds, initMatchedConditionGroupsMap(stateSet)); + this(id, stateSet, initFlags(hasPrefixStates, mustAdvance), null, finishedLookBehinds, initMatchedConditionGroupsMap(stateSet)); } public NFAState(short id, StateSet stateSet, - CodePointSet matcherBuilder, Set finishedLookBehinds, boolean hasPrefixStates, boolean mustAdvance, EconomicMap matchedConditionGroupsMap) { - this(id, stateSet, initFlags(hasPrefixStates, mustAdvance), null, matcherBuilder, finishedLookBehinds, matchedConditionGroupsMap); + this(id, stateSet, initFlags(hasPrefixStates, mustAdvance), null, finishedLookBehinds, matchedConditionGroupsMap); } private static EconomicMap initMatchedConditionGroupsMap(StateSet stateSet) { @@ -126,34 +123,27 @@ private static byte initFlags(boolean hasPrefixStates, boolean mustAdvance) { private NFAState(short id, StateSet stateSet, short flags, - CodePointSet matcherBuilder, Set finishedLookBehinds, EconomicMap matchedConditionGroupsMap) { - this(id, stateSet, flags, null, matcherBuilder, finishedLookBehinds, matchedConditionGroupsMap); + this(id, stateSet, flags, null, finishedLookBehinds, matchedConditionGroupsMap); } private NFAState(short id, StateSet stateSet, short flags, TBitSet possibleResults, - CodePointSet matcherBuilder, Set finishedLookBehinds, EconomicMap matchedConditionGroupsMap) { super(id, EMPTY_TRANSITIONS); setFlag(flags); this.stateSet = stateSet; this.possibleResults = possibleResults; - this.matcherBuilder = matcherBuilder; this.finishedLookBehinds = finishedLookBehinds; this.matchedConditionGroupsMap = matchedConditionGroupsMap; } public NFAState createTraceFinderCopy(short copyID) { - return new NFAState(copyID, getStateSet(), getFlags(), matcherBuilder, finishedLookBehinds, matchedConditionGroupsMap); - } - - public CodePointSet getCharSet() { - return matcherBuilder; + return new NFAState(copyID, getStateSet(), getFlags(), finishedLookBehinds, matchedConditionGroupsMap); } public Set getFinishedLookBehinds() { @@ -366,7 +356,6 @@ public NFAState(NFAState original) { this.revTransitionToAnchoredFinalState = original.revTransitionToAnchoredFinalState; this.revTransitionToUnAnchoredFinalState = original.revTransitionToUnAnchoredFinalState; this.possibleResults = original.possibleResults; - this.matcherBuilder = original.matcherBuilder; this.finishedLookBehinds = original.finishedLookBehinds; this.matchedConditionGroupsMap = original.matchedConditionGroupsMap; } @@ -409,7 +398,6 @@ public JsonObject toJson() { Json.prop("stateSet", getStateSet().stream().map(x -> Json.val(x.getId()))), Json.prop("mustAdvance", isMustAdvance()), Json.prop("sourceSections", sourceSectionsToJson()), - Json.prop("matcherBuilder", matcherBuilder.toString()), Json.prop("forwardAnchoredFinalState", isAnchoredFinalState()), Json.prop("forwardUnAnchoredFinalState", isUnAnchoredFinalState()), Json.prop("reverseAnchoredFinalState", isAnchoredInitialState()), @@ -422,9 +410,9 @@ public JsonObject toJson() { public JsonObject toJson(boolean forward) { return Json.obj(Json.prop("id", getId()), Json.prop("stateSet", getStateSet().stream().map(x -> Json.val(x.getId()))), + Json.prop("matcherBuilder", Arrays.stream(getPredecessors()).findFirst().map(t -> t.getCodePointSet().toString()).orElse("")), Json.prop("mustAdvance", isMustAdvance()), Json.prop("sourceSections", sourceSectionsToJson()), - Json.prop("matcherBuilder", matcherBuilder.toString()), Json.prop("anchoredFinalState", isAnchoredFinalState(forward)), Json.prop("unAnchoredFinalState", isUnAnchoredFinalState(forward)), Json.prop("transitions", Arrays.stream(getSuccessors(forward)).map(x -> Json.val(x.getId())))); diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFATraceFinderGenerator.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFATraceFinderGenerator.java index 0ede6998d9c6..c1e108ade934 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFATraceFinderGenerator.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/NFATraceFinderGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -241,21 +241,23 @@ private NFA run() { final NFAStateTransition pathTransition = graphPath.get(i).getTransition(); NFAState copy = copy(pathTransition.getTarget(), resultID); createTransition(lastCopied, copy, pathTransition, result, iResult); - iResult += getEncodedSize(copy); + iResult += getEncodedSize(pathTransition); lastCopied = copy; } // link the copied path to the existing tree createTransition(lastCopied, duplicate, curElement.getTransition(), result, iResult); // traverse the existing tree to the root to complete the pre-calculated // result. + NFAStateTransition parentTransition = curElement.getTransition(); NFAState treeNode = duplicate; while (!treeNode.isFinalState()) { - iResult += getEncodedSize(treeNode); + iResult += getEncodedSize(parentTransition); assert treeNode.getSuccessors().length == 1; + parentTransition = treeNode.getSuccessors()[0]; treeNode.addPossibleResult(resultID); - GroupBoundaries groupBoundaries = treeNode.getSuccessors()[0].getGroupBoundaries(); + GroupBoundaries groupBoundaries = parentTransition.getGroupBoundaries(); groupBoundaries.applyToResultFactory(result, iResult, trackLastGroup); - treeNode = treeNode.getSuccessors()[0].getTarget(); + treeNode = parentTransition.getTarget(); } treeNode.addPossibleResult(resultID); result.setLength(iResult); @@ -331,9 +333,9 @@ private void registerCopy(NFAState original, NFAState copy) { assert states.get(copy.getId()) == copy; } - private int getEncodedSize(NFAState s) { + private int getEncodedSize(NFAStateTransition t) { Encoding encoding = originalNFA.getAst().getEncoding(); - assert encoding.isFixedCodePointWidth(s.getCharSet()); - return encoding.getEncodedSize(s.getCharSet().getMin()); + assert encoding.isFixedCodePointWidth(t.getCodePointSet()); + return encoding.getEncodedSize(t.getCodePointSet().getMin()); } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/PureNFA.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/PureNFA.java index 3323ef3c5186..c554ee4851e1 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/PureNFA.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/PureNFA.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -63,6 +63,7 @@ public final class PureNFA implements StateIndex { private static final PureNFA[] NO_SUBTREES = {}; private final int globalSubTreeId; private final int subTreeId; + private final int fixedWidth; @CompilationFinal(dimensions = 1) private final PureNFAState[] states; @CompilationFinal(dimensions = 1) private final PureNFATransition[] transitions; @CompilationFinal(dimensions = 1) private final PureNFA[] subtrees; @@ -73,9 +74,14 @@ public PureNFA(RegexASTSubtreeRootNode astSubRoot, Counter.ThresholdCounter transitionIDCounter) { this.globalSubTreeId = astSubRoot.getGlobalSubTreeId(); this.subTreeId = astSubRoot.getSubTreeId(); + if (astSubRoot.isFixedWidth()) { + this.fixedWidth = astSubRoot.getGroup().getMinPath(); + } else { + this.fixedWidth = -1; + } this.states = new PureNFAState[stateIDCounter.getCount()]; this.transitions = new PureNFATransition[transitionIDCounter.getCount()]; - this.subtrees = astSubRoot.getSubtrees().size() == 0 ? NO_SUBTREES : new PureNFA[astSubRoot.getSubtrees().size()]; + this.subtrees = astSubRoot.getSubtrees().isEmpty() ? NO_SUBTREES : new PureNFA[astSubRoot.getSubtrees().size()]; for (PureNFAState s : states) { if (s == null) { continue; @@ -109,6 +115,14 @@ public RegexASTSubtreeRootNode getASTSubtree(RegexAST ast) { return isRoot() ? ast.getRoot().getSubTreeParent() : ast.getSubtrees().get(globalSubTreeId); } + public boolean isFixedWidth() { + return fixedWidth >= 0; + } + + public int getFixedWidth() { + return fixedWidth; + } + /** * Get this NFA's "dummy initial state". Since {@link DFAGenerator} works on sets of NFA * transitions, we need pseudo-transitions to the NFA's initial states as entry points for the diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/PureNFAGenerator.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/PureNFAGenerator.java index 5b82f1913646..77e0322c32a0 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/PureNFAGenerator.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/PureNFAGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -131,13 +131,14 @@ private PureNFA createNFA(RegexASTSubtreeRootNode root) { Arrays.fill(nfaStates, null); stateID.reset(); transitionID.reset(); - transitionGen.setReverse(root.isLookBehindAssertion()); + boolean createReverseNFA = root.isLookBehindAssertion() && !(ast.getFlavor().lookBehindsRunLeftToRight() && root.isFixedWidth()); + transitionGen.setReverse(createReverseNFA); PureNFAState dummyInitialState = new PureNFAState(stateID.inc(), ast.getWrappedRoot()); nfaStates[ast.getWrappedRoot().getId()] = dummyInitialState; assert dummyInitialState.getId() == 0; - if (root.isLookBehindAssertion()) { + if (createReverseNFA) { if (root.hasCaret()) { anchoredFinalState = createFinalState(root.getAnchoredInitialState(), false); anchoredFinalState.setAnchoredFinalState(); diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/PureNFATransition.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/PureNFATransition.java index ecb488f4ec95..e637490ecabb 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/PureNFATransition.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/PureNFATransition.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -147,6 +147,6 @@ public JsonValue toJson(RegexAST ast) { Json.prop("target", target.getId()), Json.prop("groupBoundaries", groupBoundaries), Json.prop("sourceSections", groupBoundaries.indexUpdateSourceSectionsToJson(ast)), - Json.prop("guards", Arrays.stream(guards).mapToObj(TransitionGuard::toJson))); + Json.prop("guards", guards.length == 0 ? Json.array(Json.val("no guards")) : Json.array(Arrays.stream(guards).mapToObj(TransitionGuard::toJson)))); } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/TransitionGuard.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/TransitionGuard.java index 4cfcb51369c3..c943dde14d58 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/TransitionGuard.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nfa/TransitionGuard.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -59,26 +59,33 @@ public final class TransitionGuard { public enum Kind { /** - * Transition represents a back-edge in the quantifier loop. Check if the loop count is - * below {@link Quantifier#getMax()}, then increase loop count. + * Increment loop count. */ - loop, + countInc, /** - * Transition represents either a first entry into a quantified expression, or a back-edge - * in a quantifier loop without upper bound, i.e. quantifiers where - * {@link Quantifier#isInfiniteLoop()} is {@code true}. Just increase the loop count. + * Set loop count to 1. */ - loopInc, + countSet1, /** - * Transition is leaving a quantified expression. Check if the loop count is above - * {@link Quantifier#getMin()}, then reset the loop count. + * Set loop count to the quantifier's minimum number of iterations + 1. The extra iteration + * is added because this guard is executed when entering the optional part of a split + * quantifier, i.e. this guard represents a counter value initialization to {@code min} with + * an immediate increment, analogous to how {@link #countSet1} represents a counter value + * initialization to 0 followed by an immediate increment. */ - exit, + countSetMin, /** - * Transition is leaving a quantified expression without lower bound, i.e. quantifiers where - * {@link Quantifier#getMin()} {@code == 0}. Just reset the loop count. + * Check if the loop count is less than {@link Quantifier#getMin()}. */ - exitReset, + countLtMin, + /** + * Check if the loop count is greater or equal to {@link Quantifier#getMin()}. + */ + countGeMin, + /** + * Check if the loop count is less than {@link Quantifier#getMax()}. + */ + countLtMax, /** * Transition is entering a quantified expression that may match the empty string. Save the * current index. @@ -122,32 +129,64 @@ public enum Kind { * {@link ConditionalBackReferenceGroup}. The capture group identified by * {@link #getGroupNumber(long)} must be *not* matched in order to proceed. */ - checkGroupNotMatched + checkGroupNotMatched, } @CompilationFinal(dimensions = 1) private static final Kind[] KIND_VALUES = Arrays.copyOf(Kind.values(), Kind.values().length); - private static final EnumSet QUANTIFIER_GUARDS = EnumSet.of(Kind.loop, Kind.loopInc, Kind.exit, Kind.exitReset); + private static final EnumSet QUANTIFIER_GUARDS = EnumSet.of(Kind.countInc, Kind.countSet1, Kind.countSetMin, Kind.countLtMin, Kind.countGeMin, Kind.countLtMax); private static final EnumSet ZERO_WIDTH_QUANTIFIER_GUARDS = EnumSet.of(Kind.enterZeroWidth, Kind.exitZeroWidth, Kind.escapeZeroWidth); private static final EnumSet GROUP_NUMBER_GUARDS = EnumSet.of(Kind.updateRecursiveBackrefPointer, Kind.checkGroupMatched, Kind.checkGroupNotMatched); private static final EnumSet GROUP_BOUNDARY_INDEX_GUARDS = EnumSet.of(Kind.updateCG); public static final long[] NO_GUARDS = {}; - public static long createLoop(Quantifier quantifier) { - return create(Kind.loop, quantifier); + public static long createCountInc(Quantifier quantifier) { + return create(Kind.countInc, quantifier); + } + + public static long createCountInc(int quantifierIndex) { + return create(Kind.countInc, quantifierIndex); + } + + public static long createCountSet1(Quantifier quantifier) { + return create(Kind.countSet1, quantifier); + } + + public static long createCountSet1(int quantifierIndex) { + return create(Kind.countSet1, quantifierIndex); + } + + public static long createCountSetMin(Quantifier quantifier) { + return create(Kind.countSetMin, quantifier); + } + + public static long createCountSetMin(int quantifierIndex) { + return create(Kind.countSetMin, quantifierIndex); } - public static long createLoopInc(Quantifier quantifier) { - return create(Kind.loopInc, quantifier); + public static long createCountLtMin(Quantifier quantifier) { + return create(Kind.countLtMin, quantifier); } - public static long createExit(Quantifier quantifier) { - return create(Kind.exit, quantifier); + public static long createCountLtMin(int quantifierIndex) { + return create(Kind.countLtMin, quantifierIndex); } - public static long createExitReset(Quantifier quantifier) { - return create(Kind.exitReset, quantifier); + public static long createCountGeMin(Quantifier quantifier) { + return create(Kind.countGeMin, quantifier); + } + + public static long createCountGeMin(int quantifierIndex) { + return create(Kind.countGeMin, quantifierIndex); + } + + public static long createCountLtMax(Quantifier quantifier) { + return create(Kind.countLtMax, quantifier); + } + + public static long createCountLtMax(int quantifierIndex) { + return create(Kind.countLtMax, quantifierIndex); } public static long createEnterZeroWidth(Quantifier quantifier) { @@ -167,6 +206,11 @@ public static long createEscapeZeroWidth(Quantifier quantifier) { return createZeroWidth(Kind.escapeZeroWidth, quantifier); } + public static long createEscapeZeroWidthFromEnter(long guard) { + assert is(guard, Kind.enterZeroWidth); + return create(Kind.escapeZeroWidth, getZeroWidthQuantifierIndex(guard)); + } + public static long createUpdateCG(int index) { return create(Kind.updateCG, index); } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/TRegexExecNode.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/TRegexExecNode.java index 3814bd479217..47bb3bfeb56e 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/TRegexExecNode.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/TRegexExecNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -62,6 +62,7 @@ import com.oracle.truffle.regex.tregex.TRegexCompiler; import com.oracle.truffle.regex.tregex.nfa.NFA; import com.oracle.truffle.regex.tregex.nodes.dfa.TRegexDFAExecutorNode; +import com.oracle.truffle.regex.tregex.nodes.dfa.TRegexLazyBackwardSimpleCGRootNode; import com.oracle.truffle.regex.tregex.nodes.dfa.TRegexLazyCaptureGroupsRootNode; import com.oracle.truffle.regex.tregex.nodes.dfa.TRegexLazyFindStartRootNode; import com.oracle.truffle.regex.tregex.nodes.dfa.TRegexTraceFinderRootNode; @@ -428,6 +429,8 @@ public LazyCaptureGroupRegexSearchNode(RegexLanguage language, final RegexBodyNode bodyNode; if (preCalculatedResults != null) { bodyNode = new TRegexTraceFinderRootNode(language, source, preCalculatedResults, backwardNode); + } else if (getBackwardExecutor().isSimpleCG()) { + bodyNode = new TRegexLazyBackwardSimpleCGRootNode(language, source, backwardNode); } else { bodyNode = new TRegexLazyFindStartRootNode(language, source, backwardNode, captureGroupNode == null); } @@ -485,17 +488,18 @@ private RegexResult executeForward(VirtualFrame frame, TruffleString input, int return preCalculatedResults[0].createFromEnd((int) end); } if (preCalculatedResults == null && captureGroupEntryNode == null) { - if (end == fromIndex) { // zero-length match + if ((backwardCallTarget == null || getForwardExecutor().getNumberOfCaptureGroups() == 1) && end == fromIndex) { + // zero-length match return RegexResult.create((int) end, (int) end); } if (getForwardExecutor().isAnchored() || flags.isSticky()) { return RegexResult.create(fromIndex, (int) end); } - if (getForwardExecutor().canFindStart()) { + if (backwardCallTarget == null && getForwardExecutor().canFindStart()) { return RegexResult.create((int) (end >>> 32), (int) end); - } else { - return RegexResult.createLazy(input, fromIndex, regionFrom, regionTo, -1, (int) end, backwardCallTarget); } + assert backwardCallTarget != null; + return RegexResult.createLazy(input, fromIndex, regionFrom, regionTo, -1, (int) end, backwardCallTarget); } else { if (preCalculatedResults != null) { // traceFinder return RegexResult.createLazy(input, fromIndex, regionFrom, regionTo, -1, (int) end, backwardCallTarget); diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/DFAInitialStateNode.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/DFAInitialStateNode.java index 40a6197b1112..464ad3ab05b4 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/DFAInitialStateNode.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/DFAInitialStateNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -59,11 +59,13 @@ public class DFAInitialStateNode extends DFAAbstractStateNode { @CompilationFinal(dimensions = 1) private final short[] cgLastTransition; private final boolean hasUnanchoredEntry; + private final DFASimpleCG simpleCG; - public DFAInitialStateNode(short[] successors, short[] cgLastTransition) { + public DFAInitialStateNode(short[] successors, short[] cgLastTransition, DFASimpleCG simpleCG) { super((short) 0, successors); this.cgLastTransition = cgLastTransition; this.hasUnanchoredEntry = initUnanchoredEntry(successors); + this.simpleCG = simpleCG; } private static boolean initUnanchoredEntry(short[] successors) { @@ -76,7 +78,7 @@ private static boolean initUnanchoredEntry(short[] successors) { } private DFAInitialStateNode(DFAInitialStateNode copy) { - this(Arrays.copyOf(copy.successors, copy.successors.length), copy.cgLastTransition); + this(Arrays.copyOf(copy.successors, copy.successors.length), copy.cgLastTransition, copy.simpleCG); } public short[] getCgLastTransition() { @@ -91,6 +93,10 @@ public boolean hasUnAnchoredEntry() { return hasUnanchoredEntry; } + public DFASimpleCG getSimpleCG() { + return simpleCG; + } + /** * Creates a node split copy of this initial state as described in {@link DFAAbstractStateNode}, * but ignores copyID, since having two initial states in a DFA is not supported. Therefore, diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/DFASimpleCGTransition.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/DFASimpleCGTransition.java index 70f4b8a32c85..d90a108c87a8 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/DFASimpleCGTransition.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/DFASimpleCGTransition.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -85,7 +85,7 @@ public static DFASimpleCGTransition getEmptyInstance() { return EMPTY_INSTANCE; } - public void apply(int[] result, int currentIndex, boolean trackLastGroup) { + public void apply(int[] result, int currentIndex, boolean trackLastGroup, boolean forward) { CompilerAsserts.partialEvaluationConstant(this); if (indexClears == FULL_CLEAR_ARRAY) { Arrays.fill(result, -1); @@ -94,11 +94,11 @@ public void apply(int[] result, int currentIndex, boolean trackLastGroup) { } applyIndexUpdate(result, currentIndex); if (trackLastGroup && lastGroup != -1) { - result[result.length - 1] = lastGroup; + applyLastGroup(result, forward); } } - public void applyFinal(DFACaptureGroupTrackingData cgData, int currentIndex, boolean simpleCGMustCopy, boolean trackLastGroup) { + public void applyFinal(DFACaptureGroupTrackingData cgData, int currentIndex, boolean simpleCGMustCopy, boolean trackLastGroup, boolean forward) { CompilerAsserts.partialEvaluationConstant(this); int[] result = simpleCGMustCopy ? cgData.currentResult : cgData.results; if (indexClears == FULL_CLEAR_ARRAY) { @@ -109,13 +109,19 @@ public void applyFinal(DFACaptureGroupTrackingData cgData, int currentIndex, boo applyIndexUpdate(result, currentIndex); if (trackLastGroup && lastGroup != -1) { if (simpleCGMustCopy) { - cgData.currentResult[cgData.currentResult.length - 1] = lastGroup; + applyLastGroup(cgData.currentResult, forward); } else { - cgData.results[cgData.results.length - 1] = lastGroup; + applyLastGroup(cgData.results, forward); } } } + private void applyLastGroup(int[] result, boolean forward) { + if (forward || result[result.length - 1] == -1) { + result[result.length - 1] = lastGroup; + } + } + @ExplodeLoop private void applyIndexUpdate(int[] result, int currentIndex) { for (int i = 0; i < indexUpdates.length; i++) { diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/DFAStateNode.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/DFAStateNode.java index 94afa97a9aa4..995660e16751 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/DFAStateNode.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/DFAStateNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -263,11 +263,12 @@ void storeResult(TRegexDFAExecutorLocals locals, TRegexDFAExecutorNode executor, } void applySimpleCGTransition(DFASimpleCGTransition transition, TRegexDFAExecutorNode executor, TRegexDFAExecutorLocals locals) { - transition.apply(locals.getCGData().results, locals.getIndex(), executor.getProperties().tracksLastGroup()); + int index = executor.isForward() ? locals.getIndex() : locals.getNextIndex(); + transition.apply(locals.getCGData().results, index, executor.getProperties().tracksLastGroup(), executor.isForward()); } void applySimpleCGFinalTransition(DFASimpleCGTransition transition, TRegexDFAExecutorNode executor, TRegexDFAExecutorLocals locals) { - transition.applyFinal(locals.getCGData(), locals.getIndex(), executor.getProperties().isSimpleCGMustCopy(), executor.getProperties().tracksLastGroup()); + transition.applyFinal(locals.getCGData(), locals.getIndex(), executor.getProperties().isSimpleCGMustCopy(), executor.getProperties().tracksLastGroup(), executor.isForward()); } @TruffleBoundary diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/TRegexDFAExecutorDebugRecorder.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/TRegexDFAExecutorDebugRecorder.java index d6c36f3e83ec..eab65929aa07 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/TRegexDFAExecutorDebugRecorder.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/TRegexDFAExecutorDebugRecorder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -41,6 +41,7 @@ package com.oracle.truffle.regex.tregex.nodes.dfa; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; @@ -51,7 +52,9 @@ import com.oracle.truffle.regex.tregex.dfa.DFAGenerator; import com.oracle.truffle.regex.tregex.nodes.TRegexExecutorBaseNode; import com.oracle.truffle.regex.tregex.nodes.TRegexExecutorLocals; +import com.oracle.truffle.regex.tregex.string.Encodings; import com.oracle.truffle.regex.tregex.util.json.Json; +import com.oracle.truffle.regex.tregex.util.json.JsonArray; import com.oracle.truffle.regex.tregex.util.json.JsonConvertible; import com.oracle.truffle.regex.tregex.util.json.JsonValue; @@ -68,18 +71,27 @@ public final class TRegexDFAExecutorDebugRecorder implements JsonConvertible { private static final class Recording implements JsonConvertible { - private final String input; + private final TruffleString input; + private final Encodings.Encoding encoding; private final int fromIndex; private int initialIndex; private final int maxIndex; - private final List transitions; + private final boolean forward; + private final int[] transitions; + private final int[] cgPartialTransitions; - private Recording(String input, int fromIndex, int initialIndex, int maxIndex) { + private Recording(TruffleString input, Encodings.Encoding encoding, int fromIndex, int initialIndex, int maxIndex, boolean forward) { this.input = input; + this.encoding = encoding; this.fromIndex = fromIndex; this.initialIndex = initialIndex; this.maxIndex = maxIndex; - transitions = new ArrayList<>(); + this.forward = forward; + int codepoints = input.codePointLengthUncached(encoding.getTStringEncoding()); + transitions = new int[codepoints]; + cgPartialTransitions = new int[codepoints]; + Arrays.fill(transitions, -1); + Arrays.fill(cgPartialTransitions, -1); } @TruffleBoundary @@ -87,76 +99,47 @@ public void setInitialIndex(int initialIndex) { this.initialIndex = initialIndex; } - @TruffleBoundary - private int getLowestIndex() { - return initialIndex < maxIndex ? initialIndex : maxIndex; - } - - @TruffleBoundary - private void initUpToIndex(int currentIndex) { - for (int i = transitions.size(); i <= currentIndex - getLowestIndex(); i++) { - transitions.add(new RecordedTransition(getLowestIndex() + i)); - } - } - - @TruffleBoundary - private RecordedTransition getTransition(int currentIndex) { - RecordedTransition transition = transitions.get(currentIndex - getLowestIndex()); - assert transition.currentIndex == currentIndex; - return transition; - } - @TruffleBoundary public void recordTransition(int currentIndex, int transitionID) { - initUpToIndex(currentIndex); - getTransition(currentIndex).setTransitionID(transitionID); + transitions[toCodePointIndex(currentIndex)] = transitionID; } @TruffleBoundary public void recordCGPartialTransition(int currentIndex, int cgPartialTransitionIndex) { - initUpToIndex(currentIndex); - getTransition(currentIndex).setCgPartialTransitionID(cgPartialTransitionIndex); + cgPartialTransitions[toCodePointIndex(currentIndex)] = cgPartialTransitionIndex; + } + + private int toCodePointIndex(int currentIndex) { + return input.byteIndexToCodePointIndexUncached(0, currentIndex << encoding.getStride(), encoding.getTStringEncoding()) - (forward ? 0 : 1); } @TruffleBoundary @Override public JsonValue toJson() { - return Json.obj(Json.prop("input", input), + JsonArray jsonTransitions = Json.array(); + if (forward) { + for (int i = 0; i < transitions.length; i++) { + appendJsonTransition(i, jsonTransitions); + } + } else { + for (int i = transitions.length - 1; i >= 0; i--) { + appendJsonTransition(i, jsonTransitions); + } + } + return Json.obj(Json.prop("input", input.toJavaStringUncached()), Json.prop("fromIndex", fromIndex), Json.prop("initialIndex", initialIndex), Json.prop("maxIndex", maxIndex), - Json.prop("transitions", transitions)); + Json.prop("transitions", jsonTransitions)); } - } - - private static final class RecordedTransition implements JsonConvertible { - private final int currentIndex; - private int transitionID = -1; - private int cgPartialTransitionID = -1; - - @TruffleBoundary - private RecordedTransition(int currentIndex) { - this.currentIndex = currentIndex; - } - - @TruffleBoundary - public void setTransitionID(int transitionID) { - this.transitionID = transitionID; - } - - @TruffleBoundary - public void setCgPartialTransitionID(int cgPartialTransitionID) { - assert this.cgPartialTransitionID == -1 || this.cgPartialTransitionID == 0; - this.cgPartialTransitionID = cgPartialTransitionID; - } - - @TruffleBoundary - @Override - public JsonValue toJson() { - return Json.obj(Json.prop("currentIndex", currentIndex), - Json.prop("transitionID", transitionID), - Json.prop("cgPartialTransitionID", cgPartialTransitionID)); + private void appendJsonTransition(int i, JsonArray jsonTransitions) { + if (transitions[i] >= 0) { + jsonTransitions.append(Json.obj( + Json.prop("currentIndex", i), + Json.prop("transitionID", transitions[i]), + Json.prop("cgPartialTransitionID", cgPartialTransitions[i]))); + } } } @@ -170,11 +153,11 @@ private TRegexDFAExecutorDebugRecorder(DFAGenerator dfa) { } private final DFAGenerator dfa; - private List recordings = new ArrayList<>(); + private final List recordings = new ArrayList<>(); @TruffleBoundary public void startRecording(TRegexDFAExecutorLocals locals) { - recordings.add(new Recording(locals.getInput().toString(), locals.getFromIndex(), locals.getIndex(), locals.getMaxIndex())); + recordings.add(new Recording(locals.getInput(), dfa.getOptions().getEncoding(), locals.getFromIndex(), locals.getIndex(), locals.getMaxIndex(), dfa.isForward())); } @TruffleBoundary diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/TRegexDFAExecutorNode.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/TRegexDFAExecutorNode.java index 9cb70edeaf53..cbe35fcc9175 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/TRegexDFAExecutorNode.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/TRegexDFAExecutorNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -735,6 +735,11 @@ private short initialStateSuccessor(TRegexDFAExecutorLocals locals, DFAAbstractS if (lastTransition >= 0) { locals.setLastTransition(lastTransition); } + } else if (isSimpleCG()) { + DFASimpleCG simpleCG = ((DFAInitialStateNode) curState).getSimpleCG(); + if (simpleCG != null) { + simpleCG.getTransitions()[i].apply(locals.getCGData().results, locals.getIndex(), getProperties().tracksLastGroup(), isForward()); + } } return successors[i]; } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/TRegexLazyBackwardSimpleCGRootNode.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/TRegexLazyBackwardSimpleCGRootNode.java new file mode 100644 index 000000000000..de93b8ff21ea --- /dev/null +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/dfa/TRegexLazyBackwardSimpleCGRootNode.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024, 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 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.regex.tregex.nodes.dfa; + +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.regex.RegexBodyNode; +import com.oracle.truffle.regex.RegexLanguage; +import com.oracle.truffle.regex.RegexSource; +import com.oracle.truffle.regex.result.RegexResult; +import com.oracle.truffle.regex.tregex.nodes.TRegexExecutorEntryNode; + +public class TRegexLazyBackwardSimpleCGRootNode extends RegexBodyNode { + + @Child private TRegexExecutorEntryNode entryNode; + + public TRegexLazyBackwardSimpleCGRootNode(RegexLanguage language, RegexSource source, TRegexExecutorEntryNode backwardNode) { + super(language, source); + this.entryNode = insert(backwardNode); + } + + @Override + public final Object execute(VirtualFrame frame) { + final Object[] args = frame.getArguments(); + assert args.length == 1; + final RegexResult receiver = (RegexResult) args[0]; + int[] result = (int[]) entryNode.execute(frame, receiver.getInput(), receiver.getFromIndex(), receiver.getEnd(), receiver.getRegionFrom(), receiver.getRegionTo(), receiver.getEnd()); + receiver.setResult(result); + return null; + } + + @Override + public String getEngineLabel() { + return "TRegex bck"; + } +} diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/nfa/TRegexBacktrackingNFAExecutorLocals.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/nfa/TRegexBacktrackingNFAExecutorLocals.java index 82531e1998bc..f6b15ba37943 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/nfa/TRegexBacktrackingNFAExecutorLocals.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/nfa/TRegexBacktrackingNFAExecutorLocals.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -500,10 +500,6 @@ public void setQuantifierCount(int quantifierIndex, int count) { stack()[offsetQuantifierCount(quantifierIndex)] = count; } - public void resetQuantifierCount(int quantifierIndex) { - stack()[offsetQuantifierCount(quantifierIndex)] = 0; - } - public void incQuantifierCount(int quantifierIndex) { stack()[offsetQuantifierCount(quantifierIndex)]++; } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/nfa/TRegexBacktrackingNFAExecutorNode.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/nfa/TRegexBacktrackingNFAExecutorNode.java index d672910fb7de..e7173aacc473 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/nfa/TRegexBacktrackingNFAExecutorNode.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/nodes/nfa/TRegexBacktrackingNFAExecutorNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -75,7 +75,6 @@ import com.oracle.truffle.regex.tregex.parser.Token.Quantifier; import com.oracle.truffle.regex.tregex.parser.ast.Group; import com.oracle.truffle.regex.tregex.parser.ast.InnerLiteral; -import com.oracle.truffle.regex.tregex.parser.ast.LookBehindAssertion; import com.oracle.truffle.regex.tregex.parser.ast.QuantifiableTerm; import com.oracle.truffle.regex.tregex.parser.ast.RegexAST; import com.oracle.truffle.regex.tregex.parser.ast.RegexASTSubtreeRootNode; @@ -87,21 +86,22 @@ */ public final class TRegexBacktrackingNFAExecutorNode extends TRegexBacktrackerSubExecutorNode { - private static final int FLAG_WRITES_CAPTURE_GROUPS = 1 << 0; - private static final int FLAG_FORWARD = 1 << 1; - private static final int FLAG_BACKREF_WITH_NULL_TARGET_FAILS = 1 << 2; - private static final int FLAG_MONITOR_CAPTURE_GROUPS_IN_EMPTY_CHECK = 1 << 3; - private static final int FLAG_TRANSITION_MATCHES_STEP_BY_STEP = 1 << 4; - private static final int FLAG_EMPTY_CHECKS_ON_MANDATORY_LOOP_ITERATIONS = 1 << 5; - private static final int FLAG_TRACK_LAST_GROUP = 1 << 6; - private static final int FLAG_RETURNS_FIRST_GROUP = 1 << 7; + private static final int FLAG_BACKREF_IGNORE_CASE_MULTI_CHARACTER_EXPANSION = 1 << 0; + private static final int FLAG_BACKREF_WITH_NULL_TARGET_FAILS = 1 << 1; + private static final int FLAG_EMPTY_CHECKS_ON_MANDATORY_LOOP_ITERATIONS = 1 << 2; + private static final int FLAG_FORWARD = 1 << 3; + private static final int FLAG_LONE_SURROGATES = 1 << 4; + private static final int FLAG_LOOPBACK_INITIAL_STATE = 1 << 5; + private static final int FLAG_MATCH_BOUNDARY_ASSERTIONS = 1 << 6; + private static final int FLAG_MONITOR_CAPTURE_GROUPS_IN_EMPTY_CHECK = 1 << 7; private static final int FLAG_MUST_ADVANCE = 1 << 8; - private static final int FLAG_LONE_SURROGATES = 1 << 9; - private static final int FLAG_LOOPBACK_INITIAL_STATE = 1 << 10; - private static final int FLAG_USE_MERGE_EXPLODE = 1 << 11; - private static final int FLAG_RECURSIVE_BACK_REFERENCES = 1 << 12; - private static final int FLAG_BACKREF_IGNORE_CASE_MULTI_CHARACTER_EXPANSION = 1 << 13; - private static final int FLAG_MATCH_BOUNDARY_ASSERTIONS = 1 << 14; + private static final int FLAG_RECURSIVE_BACK_REFERENCES = 1 << 9; + private static final int FLAG_RETURNS_FIRST_GROUP = 1 << 10; + private static final int FLAG_REWIND_FIXED_WIDTH_LOOK_BEHIND = 1 << 11; + private static final int FLAG_TRACK_LAST_GROUP = 1 << 12; + private static final int FLAG_TRANSITION_MATCHES_STEP_BY_STEP = 1 << 13; + private static final int FLAG_USE_MERGE_EXPLODE = 1 << 14; + private static final int FLAG_WRITES_CAPTURE_GROUPS = 1 << 15; private final PureNFA nfa; private final int numberOfStates; @@ -141,8 +141,8 @@ public TRegexBacktrackingNFAExecutorNode(RegexAST ast, PureNFA nfa, int numberOf QuantifiableTerm quantifiable = zeroWidthQuantifiables.get(i); if (quantifiable.isGroup()) { Group group = quantifiable.asGroup(); - this.zeroWidthTermEnclosedCGLow[i] = group.getCaptureGroupsLow(); - offset += 2 * (group.getCaptureGroupsHigh() - group.getCaptureGroupsLow()); + this.zeroWidthTermEnclosedCGLow[i] = group.getCaptureGroupsLo(); + offset += 2 * (group.getCaptureGroupsHi() - group.getCaptureGroupsLo()); } this.zeroWidthQuantifierCGOffsets[i + 1] = offset; this.zeroWidthQuantifiers[quantifiable.getQuantifier().getZeroWidthIndex()] = quantifiable.getQuantifier(); @@ -152,9 +152,9 @@ public TRegexBacktrackingNFAExecutorNode(RegexAST ast, PureNFA nfa, int numberOf } else { this.innerLiteral = null; } - this.equalsIgnoreCase = ast.getOptions().getFlavor().getEqualsIgnoreCasePredicate(ast); + this.equalsIgnoreCase = ast.getFlavor().getEqualsIgnoreCasePredicate(ast); if (isBackreferenceIgnoreCaseMultiCharExpansion() && ast.getProperties().hasBackReferences()) { - this.multiCharacterExpansionCaseFoldAlgorithm = ast.getOptions().getFlavor().getCaseFoldAlgorithm(ast); + this.multiCharacterExpansionCaseFoldAlgorithm = ast.getFlavor().getCaseFoldAlgorithm(ast); } else { this.multiCharacterExpansionCaseFoldAlgorithm = null; } @@ -213,21 +213,23 @@ public int getNumberOfStates() { } private static int createFlags(RegexAST ast, PureNFA nfa, boolean mustAdvance, RegexASTSubtreeRootNode subtree, int nStates, int nTransitions) { + RegexFlavor flavor = ast.getFlavor(); int flags = 0; flags = setFlag(flags, FLAG_WRITES_CAPTURE_GROUPS, subtree.hasCaptureGroups()); - flags = setFlag(flags, FLAG_FORWARD, !(subtree instanceof LookBehindAssertion)); - flags = setFlag(flags, FLAG_BACKREF_WITH_NULL_TARGET_FAILS, ast.getOptions().getFlavor().backreferencesToUnmatchedGroupsFail()); - flags = setFlag(flags, FLAG_MONITOR_CAPTURE_GROUPS_IN_EMPTY_CHECK, ast.getOptions().getFlavor().emptyChecksMonitorCaptureGroups()); - flags = setFlag(flags, FLAG_TRANSITION_MATCHES_STEP_BY_STEP, ast.getOptions().getFlavor().matchesTransitionsStepByStep()); - flags = setFlag(flags, FLAG_EMPTY_CHECKS_ON_MANDATORY_LOOP_ITERATIONS, ast.getOptions().getFlavor().emptyChecksOnMandatoryLoopIterations()); - flags = setFlag(flags, FLAG_TRACK_LAST_GROUP, ast.getOptions().getFlavor().usesLastGroupResultField()); - flags = setFlag(flags, FLAG_RETURNS_FIRST_GROUP, !isFlagSet(flags, FLAG_FORWARD) && ast.getOptions().getFlavor().lookBehindsRunLeftToRight()); + flags = setFlag(flags, FLAG_REWIND_FIXED_WIDTH_LOOK_BEHIND, subtree.isLookBehindAssertion() && flavor.lookBehindsRunLeftToRight() && nfa.isFixedWidth()); + flags = setFlag(flags, FLAG_FORWARD, !subtree.isLookBehindAssertion() || isFlagSet(flags, FLAG_REWIND_FIXED_WIDTH_LOOK_BEHIND)); + flags = setFlag(flags, FLAG_BACKREF_WITH_NULL_TARGET_FAILS, flavor.backreferencesToUnmatchedGroupsFail()); + flags = setFlag(flags, FLAG_MONITOR_CAPTURE_GROUPS_IN_EMPTY_CHECK, flavor.emptyChecksMonitorCaptureGroups()); + flags = setFlag(flags, FLAG_TRANSITION_MATCHES_STEP_BY_STEP, flavor.matchesTransitionsStepByStep()); + flags = setFlag(flags, FLAG_EMPTY_CHECKS_ON_MANDATORY_LOOP_ITERATIONS, flavor.emptyChecksOnMandatoryLoopIterations()); + flags = setFlag(flags, FLAG_TRACK_LAST_GROUP, flavor.usesLastGroupResultField()); + flags = setFlag(flags, FLAG_RETURNS_FIRST_GROUP, !isFlagSet(flags, FLAG_FORWARD) && flavor.lookBehindsRunLeftToRight()); flags = setFlag(flags, FLAG_MUST_ADVANCE, mustAdvance); flags = setFlag(flags, FLAG_LONE_SURROGATES, ast.getProperties().hasLoneSurrogates()); flags = setFlag(flags, FLAG_LOOPBACK_INITIAL_STATE, nfa.isRoot() && !ast.getFlags().isSticky() && !ast.getRoot().startsWithCaret()); flags = setFlag(flags, FLAG_USE_MERGE_EXPLODE, nStates <= ast.getOptions().getMaxBackTrackerCompileSize() && nTransitions <= ast.getOptions().getMaxBackTrackerCompileSize()); flags = setFlag(flags, FLAG_RECURSIVE_BACK_REFERENCES, ast.getProperties().hasRecursiveBackReferences()); - flags = setFlag(flags, FLAG_BACKREF_IGNORE_CASE_MULTI_CHARACTER_EXPANSION, ast.getOptions().getFlavor().backreferenceIgnoreCaseMultiCharExpansion() && ast.getProperties().hasBackReferences()); + flags = setFlag(flags, FLAG_BACKREF_IGNORE_CASE_MULTI_CHARACTER_EXPANSION, flavor.backreferenceIgnoreCaseMultiCharExpansion() && ast.getProperties().hasBackReferences()); flags = setFlag(flags, FLAG_MATCH_BOUNDARY_ASSERTIONS, ast.getProperties().hasMatchBoundaryAssertions()); return flags; } @@ -309,6 +311,10 @@ public boolean isMatchBoundaryAssertions() { return isFlagSet(FLAG_MATCH_BOUNDARY_ASSERTIONS); } + public boolean isRewindFixedWidthLookBehind() { + return isFlagSet(FLAG_REWIND_FIXED_WIDTH_LOOK_BEHIND); + } + private boolean isFlagSet(int flag) { return isFlagSet(flags, flag); } @@ -346,6 +352,12 @@ public TRegexExecutorLocals createLocals(TruffleString input, int fromIndex, int @Override public Object execute(VirtualFrame frame, TRegexExecutorLocals abstractLocals, TruffleString.CodeRange codeRange) { TRegexBacktrackingNFAExecutorLocals locals = (TRegexBacktrackingNFAExecutorLocals) abstractLocals; + if (isRewindFixedWidthLookBehind()) { + assert isForward(); + if (rewindUpTo(locals, 0, nfa.getFixedWidth(), codeRange) != nfa.getFixedWidth()) { + return null; + } + } if (innerLiteral != null) { locals.setIndex(locals.getFromIndex()); int innerLiteralIndex = findInnerLiteral(locals); @@ -803,26 +815,29 @@ protected boolean transitionMatches(VirtualFrame frame, TRegexBacktrackingNFAExe TransitionGuard.Kind kind = TransitionGuard.getKind(guard); CompilerAsserts.partialEvaluationConstant(kind); switch (kind) { - case loop -> { - // retreat if quantifier count is at maximum - if (locals.getQuantifierCount(TransitionGuard.getQuantifierIndex(guard)) == getQuantifier(guard).getMax()) { + case countLtMin -> { + // retreat if quantifier count is greater or equal to minimum + if (locals.getQuantifierCount(TransitionGuard.getQuantifierIndex(guard)) >= getQuantifier(guard).getMin()) { return false; } } - case exit -> { + case countGeMin -> { // retreat if quantifier count is less than minimum if (locals.getQuantifierCount(TransitionGuard.getQuantifierIndex(guard)) < getQuantifier(guard).getMin()) { return false; } } + case countLtMax -> { + // retreat if quantifier count is at maximum + if (locals.getQuantifierCount(TransitionGuard.getQuantifierIndex(guard)) >= getQuantifier(guard).getMax()) { + return false; + } + } case exitZeroWidth -> { Quantifier q = getZeroWidthQuantifier(guard); CompilerAsserts.partialEvaluationConstant(q); if (locals.getZeroWidthQuantifierGuardIndex(TransitionGuard.getZeroWidthQuantifierIndex(guard)) == index && (!isMonitorCaptureGroupsInEmptyCheck() || locals.isResultUnmodifiedByZeroWidthQuantifier(TransitionGuard.getZeroWidthQuantifierIndex(guard))) && - // In JS, we allow this guard to pass if we are still in the - // optional part of the quantifier. This allows JS to fast- - // forward past all the empty mandatory iterations. (isEmptyChecksOnMandatoryLoopIterations() || !q.hasIndex() || locals.getQuantifierCount(q.getIndex()) > q.getMin())) { return false; } @@ -833,16 +848,20 @@ protected boolean transitionMatches(VirtualFrame frame, TRegexBacktrackingNFAExe return false; } } - case checkGroupMatched -> { - if (getBackRefBoundary(locals, transition, Group.groupNumberToBoundaryIndexStart(TransitionGuard.getGroupNumber(guard)), index) == -1 || - getBackRefBoundary(locals, transition, Group.groupNumberToBoundaryIndexEnd(TransitionGuard.getGroupNumber(guard)), index) == -1) { - return false; - } - } - case checkGroupNotMatched -> { - if (getBackRefBoundary(locals, transition, Group.groupNumberToBoundaryIndexStart(TransitionGuard.getGroupNumber(guard)), index) != -1 && - getBackRefBoundary(locals, transition, Group.groupNumberToBoundaryIndexEnd(TransitionGuard.getGroupNumber(guard)), index) != -1) { - return false; + case checkGroupMatched, checkGroupNotMatched -> { + int start = getBackRefBoundary(locals, transition, Group.groupNumberToBoundaryIndexStart(TransitionGuard.getGroupNumber(guard)), index); + int end = getBackRefBoundary(locals, transition, Group.groupNumberToBoundaryIndexEnd(TransitionGuard.getGroupNumber(guard)), index); + switch (kind) { + case checkGroupMatched -> { + if (start == -1 || end == -1) { + return false; + } + } + case checkGroupNotMatched -> { + if (start != -1 && end != -1) { + return false; + } + } } } default -> { @@ -891,41 +910,19 @@ protected void updateState(TRegexBacktrackingNFAExecutorLocals locals, PureNFATr for (long guard : transition.getGuards()) { CompilerAsserts.partialEvaluationConstant(guard); switch (TransitionGuard.getKind(guard)) { - case loop, loopInc -> { + case countInc -> { locals.incQuantifierCount(TransitionGuard.getQuantifierIndex(guard)); } - case exit, exitReset -> { - locals.resetQuantifierCount(TransitionGuard.getQuantifierIndex(guard)); + case countSet1 -> { + locals.setQuantifierCount(TransitionGuard.getQuantifierIndex(guard), 1); + } + case countSetMin -> { + locals.setQuantifierCount(TransitionGuard.getQuantifierIndex(guard), getQuantifier(guard).getMin() + 1); } case enterZeroWidth -> { locals.setZeroWidthQuantifierGuardIndex(TransitionGuard.getZeroWidthQuantifierIndex(guard)); locals.setZeroWidthQuantifierResults(TransitionGuard.getZeroWidthQuantifierIndex(guard)); } - case exitZeroWidth -> { - Quantifier q = getZeroWidthQuantifier(guard); - CompilerAsserts.partialEvaluationConstant(q); - boolean emptyCheckFailed = locals.getZeroWidthQuantifierGuardIndex(TransitionGuard.getZeroWidthQuantifierIndex(guard)) == index && - (!isMonitorCaptureGroupsInEmptyCheck() || locals.isResultUnmodifiedByZeroWidthQuantifier(TransitionGuard.getZeroWidthQuantifierIndex(guard))); - boolean advancePastOptionalIterations = !isEmptyChecksOnMandatoryLoopIterations() && q.hasIndex() && locals.getQuantifierCount(q.getIndex()) < q.getMin(); - if (emptyCheckFailed && advancePastOptionalIterations && !transition.hasCaretGuard() && !transition.hasDollarGuard()) { - // We advance the counter to min - 1 to skip past all but one mandatory - // iteration. We do not skip the last mandatory iteration and set the - // counter to min, because of the way JavaScript regexes are executed. The - // JavaScript flavor does not set matchesTransitionStepByStep and therefore - // all guards are tested against the same original state. In the case of the - // last mandatory iteration, we would like it to be possible to match the - // exitZeroWidth guard followed by the exit guard, so that it is possible to - // hit the exact minimum number of iterations. However, this relies on first - // updating the state with exitZeroWidth and then testing this new state - // with the exit guard. This would mean having to enable - // matchesTransitionStepByStep for JavaScript and implementing this logic in - // tryUpdateState instead, which would lead to degraded performance for JS - // regexps. Instead, we choose to advance the counter to just before the - // last mandatory iteration so that this fast-forwarding behavior does not - // coincide with an exit guard that should pass. - locals.setQuantifierCount(q.getIndex(), q.getMin() - 1); - } - } default -> { } } @@ -1011,64 +1008,81 @@ protected boolean tryUpdateState(VirtualFrame frame, TRegexBacktrackingNFAExecut locals.setMatchEndAssertionTraversed(); } for (long guard : transition.getGuards()) { - switch (TransitionGuard.getKind(guard)) { - case loopInc: + TransitionGuard.Kind kind = TransitionGuard.getKind(guard); + switch (kind) { + case countInc -> { locals.incQuantifierCount(TransitionGuard.getQuantifierIndex(guard)); - break; - case loop: - // retreat if quantifier count is at maximum - if (locals.getQuantifierCount(TransitionGuard.getQuantifierIndex(guard)) == getQuantifier(guard).getMax()) { + } + case countSet1 -> { + locals.setQuantifierCount(TransitionGuard.getQuantifierIndex(guard), 1); + } + case countSetMin -> { + locals.setQuantifierCount(TransitionGuard.getQuantifierIndex(guard), getQuantifier(guard).getMin() + 1); + } + case countLtMin -> { + // retreat if quantifier count is greater or equal to minimum + if (locals.getQuantifierCount(TransitionGuard.getQuantifierIndex(guard)) >= getQuantifier(guard).getMin()) { return false; } - locals.incQuantifierCount(TransitionGuard.getQuantifierIndex(guard)); - break; - case exit: + } + case countGeMin -> { // retreat if quantifier count is less than minimum if (locals.getQuantifierCount(TransitionGuard.getQuantifierIndex(guard)) < getQuantifier(guard).getMin()) { return false; } - locals.resetQuantifierCount(TransitionGuard.getQuantifierIndex(guard)); - break; - case exitReset: - locals.resetQuantifierCount(TransitionGuard.getQuantifierIndex(guard)); - break; - case updateCG: + } + case countLtMax -> { + // retreat if quantifier count is at maximum + if (locals.getQuantifierCount(TransitionGuard.getQuantifierIndex(guard)) >= getQuantifier(guard).getMax()) { + return false; + } + } + case updateCG -> { locals.setCaptureGroupBoundary(TransitionGuard.getGroupBoundaryIndex(guard), index); if (isTrackLastGroup() && TransitionGuard.getGroupBoundaryIndex(guard) % 2 != 0 && TransitionGuard.getGroupBoundaryIndex(guard) > 1) { locals.setLastGroup(TransitionGuard.getGroupBoundaryIndex(guard) / 2); } - break; - case updateRecursiveBackrefPointer: + } + case updateRecursiveBackrefPointer -> { locals.saveRecursiveBackrefGroupStart(TransitionGuard.getGroupNumber(guard)); - break; - case enterZeroWidth: + } + case enterZeroWidth -> { locals.setZeroWidthQuantifierGuardIndex(TransitionGuard.getZeroWidthQuantifierIndex(guard)); locals.setZeroWidthQuantifierResults(TransitionGuard.getZeroWidthQuantifierIndex(guard)); - break; - case exitZeroWidth: + } + case exitZeroWidth -> { + Quantifier q = getZeroWidthQuantifier(guard); + CompilerAsserts.partialEvaluationConstant(q); if (locals.getZeroWidthQuantifierGuardIndex(TransitionGuard.getZeroWidthQuantifierIndex(guard)) == index && - (!isMonitorCaptureGroupsInEmptyCheck() || locals.isResultUnmodifiedByZeroWidthQuantifier(TransitionGuard.getZeroWidthQuantifierIndex(guard)))) { + (!isMonitorCaptureGroupsInEmptyCheck() || locals.isResultUnmodifiedByZeroWidthQuantifier(TransitionGuard.getZeroWidthQuantifierIndex(guard))) && + (isEmptyChecksOnMandatoryLoopIterations() || !q.hasIndex() || locals.getQuantifierCount(q.getIndex()) > q.getMin())) { return false; } - break; - case escapeZeroWidth: + } + case escapeZeroWidth -> { if (locals.getZeroWidthQuantifierGuardIndex(TransitionGuard.getZeroWidthQuantifierIndex(guard)) != index || (isMonitorCaptureGroupsInEmptyCheck() && !locals.isResultUnmodifiedByZeroWidthQuantifier(TransitionGuard.getZeroWidthQuantifierIndex(guard)))) { return false; } - break; - case checkGroupMatched: - if (locals.getCaptureGroupStart(TransitionGuard.getGroupNumber(guard)) == -1 || locals.getCaptureGroupEnd(TransitionGuard.getGroupNumber(guard)) == -1) { - return false; - } - break; - case checkGroupNotMatched: - if (locals.getCaptureGroupStart(TransitionGuard.getGroupNumber(guard)) != -1 && locals.getCaptureGroupEnd(TransitionGuard.getGroupNumber(guard)) != -1) { - return false; + } + case checkGroupMatched, checkGroupNotMatched -> { + int start = locals.getCaptureGroupStart(TransitionGuard.getGroupNumber(guard)); + int end = locals.getCaptureGroupEnd(TransitionGuard.getGroupNumber(guard)); + switch (kind) { + case checkGroupMatched -> { + if (start == -1 || end == -1) { + return false; + } + } + case checkGroupNotMatched -> { + if (start != -1 && end != -1) { + return false; + } + } } - break; - default: - break; + } + default -> { + } } } locals.saveIndex(getNewIndex(locals, target, index)); @@ -1156,6 +1170,7 @@ private boolean canInlineBackReferenceIntoTransition(PureNFAState backRef) { private boolean matchBackReferenceSimple(TRegexBacktrackingNFAExecutorLocals locals, PureNFAState backReference, PureNFATransition transition, int index) { assert backReference.isBackReference(); assert canInlineBackReferenceIntoTransition(backReference); + assert !isRecursiveBackreferences(); Pair backRefBounds = getBackRefBounds(locals, backReference, transition, index); final int backrefStart = backRefBounds.getLeft(); final int backrefEnd = backRefBounds.getRight(); diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/JSRegexLexer.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/JSRegexLexer.java index 039b14dcf047..e6a2b5539033 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/JSRegexLexer.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/JSRegexLexer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -42,6 +42,8 @@ import static com.oracle.truffle.regex.tregex.parser.flavors.ECMAScriptFlavor.UNICODE; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.List; import java.util.Map; @@ -49,6 +51,7 @@ import com.oracle.truffle.regex.RegexFlags; import com.oracle.truffle.regex.RegexSource; import com.oracle.truffle.regex.RegexSyntaxException; +import com.oracle.truffle.regex.RegexSyntaxException.ErrorCode; import com.oracle.truffle.regex.charset.ClassSetContents; import com.oracle.truffle.regex.charset.CodePointSet; import com.oracle.truffle.regex.charset.CodePointSetAccumulator; @@ -65,16 +68,34 @@ public final class JSRegexLexer extends RegexLexer { private static final TBitSet CLASS_SET_SYNTAX_CHARS = TBitSet.valueOf('(', ')', '-', '/', '[', '\\', ']', '{', '|', '}'); private static final TBitSet CLASS_SET_RESERVED_PUNCTUATORS = TBitSet.valueOf('!', '#', '%', '&', ',', '-', ':', ';', '<', '=', '>', '@', '`', '~'); private static final TBitSet CLASS_SET_RESERVED_DOUBLE_PUNCTUATORS = TBitSet.valueOf('!', '#', '$', '%', '&', '*', '+', ',', '.', ':', ';', '<', '=', '>', '?', '@', '^', '`', '~'); - private final RegexFlags flags; + + private final Deque flagsStack = new ArrayDeque<>(); + private RegexFlags globalFlags; public JSRegexLexer(RegexSource source, RegexFlags flags, CompilationBuffer compilationBuffer) { super(source, compilationBuffer); - this.flags = flags; + this.globalFlags = flags; + } + + public RegexFlags getGlobalFlags() { + return globalFlags; + } + + public RegexFlags getLocalFlags() { + return flagsStack.isEmpty() ? globalFlags : flagsStack.peek(); + } + + public void pushLocalFlags(RegexFlags localFlags) { + flagsStack.push(localFlags); + } + + public void popLocalFlags() { + flagsStack.pop(); } @Override protected boolean featureEnabledIgnoreCase() { - return flags.isIgnoreCase(); + return getLocalFlags().isIgnoreCase(); } @Override @@ -144,7 +165,7 @@ protected TBitSet getWhitespace() { @Override protected boolean featureEnabledOctalEscapes() { - return !flags.isEitherUnicode(); + return !getLocalFlags().isEitherUnicode(); } @Override @@ -154,24 +175,25 @@ protected boolean featureEnabledSpecialGroups() { @Override protected boolean featureEnabledUnicodePropertyEscapes() { - return flags.isEitherUnicode(); + return getLocalFlags().isEitherUnicode(); } @Override protected boolean featureEnabledClassSetExpressions() { - return flags.isUnicodeSets(); + return getLocalFlags().isUnicodeSets(); } @Override protected void caseFoldUnfold(CodePointSetAccumulator charClass) { - CaseFoldData.CaseFoldUnfoldAlgorithm caseFolding = flags.isEitherUnicode() ? CaseFoldData.CaseFoldUnfoldAlgorithm.ECMAScriptUnicode : CaseFoldData.CaseFoldUnfoldAlgorithm.ECMAScriptNonUnicode; + CaseFoldData.CaseFoldUnfoldAlgorithm caseFolding = getLocalFlags().isEitherUnicode() ? CaseFoldData.CaseFoldUnfoldAlgorithm.ECMAScriptUnicode + : CaseFoldData.CaseFoldUnfoldAlgorithm.ECMAScriptNonUnicode; CodePointSetAccumulator tmp = compilationBuffer.getCodePointSetAccumulator1(); CaseFoldData.applyCaseFoldUnfold(charClass, tmp, caseFolding); } @Override protected CodePointSet complementClassSet(CodePointSet codePointSet) { - if (flags.isUnicodeSets() && flags.isIgnoreCase()) { + if (getLocalFlags().isUnicodeSets() && getLocalFlags().isIgnoreCase()) { return codePointSet.createInverse(CaseFoldData.FOLDED_CHARACTERS, compilationBuffer); } else { return codePointSet.createInverse(source.getEncoding()); @@ -180,7 +202,7 @@ protected CodePointSet complementClassSet(CodePointSet codePointSet) { @Override protected ClassSetContents caseFoldClassSetAtom(ClassSetContents classSetContents) { - if (flags.isUnicodeSets() && flags.isIgnoreCase()) { + if (getLocalFlags().isUnicodeSets() && getLocalFlags().isIgnoreCase()) { return classSetContents.caseFold(compilationBuffer.getCodePointSetAccumulator1()); } else { return classSetContents; @@ -189,7 +211,7 @@ protected ClassSetContents caseFoldClassSetAtom(ClassSetContents classSetContent @Override protected CodePointSet getDotCodePointSet() { - return flags.isDotAll() ? Constants.DOT_ALL : Constants.DOT; + return getLocalFlags().isDotAll() ? Constants.DOT_ALL : Constants.DOT; } @Override @@ -209,6 +231,15 @@ protected int getMaxBackReferenceDigits() { @Override protected CodePointSet getPredefinedCharClass(char c) { + CodePointSet predefinedCharClass = getPredefinedCharClassCPS(c); + if (featureEnabledIgnoreCase()) { + return caseFoldUnfold(predefinedCharClass); + } else { + return predefinedCharClass; + } + } + + private CodePointSet getPredefinedCharClassCPS(char c) { switch (c) { case 's': if (source.getOptions().isU180EWhitespace()) { @@ -227,17 +258,17 @@ protected CodePointSet getPredefinedCharClass(char c) { case 'D': return Constants.NON_DIGITS; case 'w': - if (flags.isUnicodeSets() && flags.isIgnoreCase()) { + if (getLocalFlags().isUnicodeSets() && getLocalFlags().isIgnoreCase()) { return Constants.WORD_CHARS_UNICODE_SETS_IGNORE_CASE; - } else if (flags.isUnicode() && flags.isIgnoreCase()) { + } else if (getLocalFlags().isUnicode() && getLocalFlags().isIgnoreCase()) { return Constants.WORD_CHARS_UNICODE_IGNORE_CASE; } else { return Constants.WORD_CHARS; } case 'W': - if (flags.isUnicodeSets() && flags.isIgnoreCase()) { + if (getLocalFlags().isUnicodeSets() && getLocalFlags().isIgnoreCase()) { return Constants.NON_WORD_CHARS_UNICODE_SETS_IGNORE_CASE; - } else if (flags.isUnicode() && flags.isIgnoreCase()) { + } else if (getLocalFlags().isUnicode() && getLocalFlags().isIgnoreCase()) { return Constants.NON_WORD_CHARS_UNICODE_IGNORE_CASE; } else { return Constants.NON_WORD_CHARS; @@ -250,12 +281,12 @@ protected CodePointSet getPredefinedCharClass(char c) { @Override protected void checkClassSetCharacter(int codePoint) throws RegexSyntaxException { if (CLASS_SET_SYNTAX_CHARS.get(codePoint)) { - throw syntaxError(JsErrorMessages.unexpectedCharacterInClassSet(codePoint)); + throw syntaxError(JsErrorMessages.unexpectedCharacterInClassSet(codePoint), ErrorCode.InvalidCharacterClass); } if (CLASS_SET_RESERVED_DOUBLE_PUNCTUATORS.get(codePoint)) { String punctuator = Character.toString(codePoint); if (lookahead(punctuator)) { - throw syntaxError(JsErrorMessages.unexpectedDoublePunctuatorInClassSet(punctuator)); + throw syntaxError(JsErrorMessages.unexpectedDoublePunctuatorInClassSet(punctuator), ErrorCode.InvalidCharacterClass); } } } @@ -267,13 +298,13 @@ protected long boundedQuantifierMaxValue() { @Override protected RegexSyntaxException handleBoundedQuantifierOutOfOrder() { - return syntaxError(JsErrorMessages.QUANTIFIER_OUT_OF_ORDER); + return syntaxError(JsErrorMessages.QUANTIFIER_OUT_OF_ORDER, ErrorCode.InvalidQuantifier); } @Override protected Token handleBoundedQuantifierEmptyOrMissingMin() throws RegexSyntaxException { - if (flags.isEitherUnicode()) { - throw syntaxError(JsErrorMessages.INCOMPLETE_QUANTIFIER); + if (getLocalFlags().isEitherUnicode()) { + throw syntaxError(JsErrorMessages.INCOMPLETE_QUANTIFIER, ErrorCode.InvalidQuantifier); } position = getLastTokenPosition() + 1; return literalChar('{'); @@ -296,13 +327,13 @@ protected Token handleBoundedQuantifierOverflowMin(long min, long max) { @Override protected RegexSyntaxException handleCCRangeOutOfOrder(int startPos) { - return syntaxError(JsErrorMessages.CHAR_CLASS_RANGE_OUT_OF_ORDER); + return syntaxError(JsErrorMessages.CHAR_CLASS_RANGE_OUT_OF_ORDER, ErrorCode.InvalidCharacterClass); } @Override protected void handleCCRangeWithPredefCharClass(int startPos, ClassSetContents firstAtom, ClassSetContents secondAtom) { - if (flags.isEitherUnicode()) { - throw syntaxError(JsErrorMessages.INVALID_CHARACTER_CLASS); + if (getLocalFlags().isEitherUnicode()) { + throw syntaxError(JsErrorMessages.INVALID_CHARACTER_CLASS, ErrorCode.InvalidCharacterClass); } } @@ -323,7 +354,7 @@ protected void validatePOSIXEquivalenceClass(String sequence) { @Override protected RegexSyntaxException handleComplementOfStringSet() { - return syntaxError(JsErrorMessages.invalidRegularExpression(source, JsErrorMessages.COMPLEMENT_OF_STRING_SET)); + return syntaxError(JsErrorMessages.invalidRegularExpression(source, JsErrorMessages.COMPLEMENT_OF_STRING_SET), ErrorCode.InvalidCharacterClass); } @Override @@ -333,44 +364,44 @@ protected void handleGroupRedefinition(String name, int newId, int oldId) { @Override protected void handleIncompleteEscapeX() { - if (flags.isEitherUnicode()) { - throw syntaxError(JsErrorMessages.INVALID_ESCAPE); + if (getLocalFlags().isEitherUnicode()) { + throw syntaxError(JsErrorMessages.INVALID_ESCAPE, ErrorCode.InvalidEscape); } } @Override protected Token handleInvalidBackReference(int reference) { - if (flags.isEitherUnicode()) { - throw syntaxError(JsErrorMessages.MISSING_GROUP_FOR_BACKREFERENCE); + if (getLocalFlags().isEitherUnicode()) { + throw syntaxError(JsErrorMessages.MISSING_GROUP_FOR_BACKREFERENCE, ErrorCode.InvalidBackReference); } return null; } @Override protected RegexSyntaxException handleInvalidCharInCharClass() { - return syntaxError(JsErrorMessages.INVALID_CHARACTER_IN_CHARACTER_CLASS); + return syntaxError(JsErrorMessages.INVALID_CHARACTER_IN_CHARACTER_CLASS, ErrorCode.InvalidCharacterClass); } private int handleInvalidEscape(int c) { - if (flags.isEitherUnicode()) { - throw syntaxError(JsErrorMessages.INVALID_ESCAPE); + if (getLocalFlags().isEitherUnicode()) { + throw syntaxError(JsErrorMessages.INVALID_ESCAPE, ErrorCode.InvalidEscape); } return c; } @Override protected RegexSyntaxException handleInvalidGroupBeginQ() { - return syntaxError(JsErrorMessages.INVALID_GROUP); + return syntaxError(JsErrorMessages.INVALID_GROUP, ErrorCode.InvalidGroup); } @Override protected RegexSyntaxException handleMixedClassSetOperators(ClassSetOperator leftOperator, ClassSetOperator rightOperator) { - return syntaxError(JsErrorMessages.mixedOperatorsInClassSet(leftOperator, rightOperator)); + return syntaxError(JsErrorMessages.mixedOperatorsInClassSet(leftOperator, rightOperator), ErrorCode.InvalidCharacterClass); } @Override protected RegexSyntaxException handleMissingClassSetOperand(ClassSetOperator operator) { - return syntaxError(JsErrorMessages.missingClassSetOperand(operator)); + return syntaxError(JsErrorMessages.missingClassSetOperand(operator), ErrorCode.InvalidCharacterClass); } @Override @@ -379,12 +410,12 @@ protected void handleOctalOutOfRange() { @Override protected RegexSyntaxException handleRangeAsClassSetOperand(ClassSetOperator operator) { - return syntaxError(JsErrorMessages.rangeAsClassSetOperand(operator)); + return syntaxError(JsErrorMessages.rangeAsClassSetOperand(operator), ErrorCode.InvalidCharacterClass); } @Override protected void handleUnfinishedEscape() { - throw syntaxError(JsErrorMessages.ENDS_WITH_UNFINISHED_ESCAPE_SEQUENCE); + throw syntaxError(JsErrorMessages.ENDS_WITH_UNFINISHED_ESCAPE_SEQUENCE, ErrorCode.InvalidEscape); } @Override @@ -393,34 +424,34 @@ protected void handleUnfinishedGroupComment() { @Override protected RegexSyntaxException handleUnfinishedGroupQ() { - return syntaxError(JsErrorMessages.INVALID_GROUP); + return syntaxError(JsErrorMessages.INVALID_GROUP, ErrorCode.InvalidGroup); } @Override protected RegexSyntaxException handleUnfinishedRangeInClassSet() { - return syntaxError(JsErrorMessages.UNTERMINATED_CHARACTER_RANGE); + return syntaxError(JsErrorMessages.UNTERMINATED_CHARACTER_RANGE, ErrorCode.InvalidCharacterClass); } @Override protected void handleUnmatchedRightBrace() { - if (flags.isEitherUnicode()) { + if (getLocalFlags().isEitherUnicode()) { // In ECMAScript regular expressions, syntax characters such as '}' and ']' // cannot be used as atomic patterns. However, Annex B relaxes this condition // and allows the use of unmatched '}' and ']', which then match themselves. // Nevertheless, in Unicode mode, we should still be strict. - throw syntaxError(JsErrorMessages.UNMATCHED_RIGHT_BRACE); + throw syntaxError(JsErrorMessages.UNMATCHED_RIGHT_BRACE, ErrorCode.InvalidQuantifier); } } @Override protected RegexSyntaxException handleUnmatchedLeftBracket() { - return syntaxError(JsErrorMessages.UNMATCHED_LEFT_BRACKET); + return syntaxError(JsErrorMessages.UNMATCHED_LEFT_BRACKET, ErrorCode.UnmatchedBracket); } @Override protected void handleUnmatchedRightBracket() { - if (flags.isEitherUnicode()) { - throw syntaxError(JsErrorMessages.UNMATCHED_RIGHT_BRACKET); + if (getLocalFlags().isEitherUnicode()) { + throw syntaxError(JsErrorMessages.UNMATCHED_RIGHT_BRACKET, ErrorCode.UnmatchedBracket); } } @@ -429,7 +460,7 @@ protected int parseCodePointInGroupName() throws RegexSyntaxException { if (consumingLookahead("\\u")) { final int unicodeEscape = parseUnicodeEscapeChar(true); if (unicodeEscape < 0) { - throw syntaxError(JsErrorMessages.INVALID_UNICODE_ESCAPE); + throw syntaxError(JsErrorMessages.INVALID_UNICODE_ESCAPE, ErrorCode.InvalidEscape); } else { return unicodeEscape; } @@ -442,13 +473,13 @@ private String jsParseGroupName() { ParseGroupNameResult result = parseGroupName('>'); switch (result.state) { case empty: - throw syntaxError(JsErrorMessages.EMPTY_GROUP_NAME); + throw syntaxError(JsErrorMessages.EMPTY_GROUP_NAME, ErrorCode.InvalidNamedGroup); case unterminated: - throw syntaxError(JsErrorMessages.UNTERMINATED_GROUP_NAME); + throw syntaxError(JsErrorMessages.UNTERMINATED_GROUP_NAME, ErrorCode.InvalidNamedGroup); case invalidStart: - throw syntaxError(JsErrorMessages.INVALID_GROUP_NAME_START); + throw syntaxError(JsErrorMessages.INVALID_GROUP_NAME_START, ErrorCode.InvalidNamedGroup); case invalidRest: - throw syntaxError(JsErrorMessages.INVALID_GROUP_NAME_PART); + throw syntaxError(JsErrorMessages.INVALID_GROUP_NAME_PART, ErrorCode.InvalidNamedGroup); case valid: return result.groupName; default: @@ -463,12 +494,12 @@ protected Token parseCustomEscape(char c) { } else if (c == 'B') { return Token.createNonWordBoundary(); } else if (c == 'k') { - if (flags.isEitherUnicode() || hasNamedCaptureGroups()) { + if (getLocalFlags().isEitherUnicode() || hasNamedCaptureGroups()) { if (atEnd()) { handleUnfinishedEscape(); } if (consumeChar() != '<') { - throw syntaxError(JsErrorMessages.MISSING_GROUP_NAME); + throw syntaxError(JsErrorMessages.MISSING_GROUP_NAME, ErrorCode.InvalidNamedGroup); } String groupName = jsParseGroupName(); // backward reference @@ -480,7 +511,7 @@ protected Token parseCustomEscape(char c) { if (allNamedCaptureGroups != null && allNamedCaptureGroups.containsKey(groupName)) { return Token.createBackReference(allNamedCaptureGroups.get(groupName).stream().mapToInt(x -> x).toArray(), false); } - throw syntaxError(JsErrorMessages.MISSING_GROUP_FOR_BACKREFERENCE); + throw syntaxError(JsErrorMessages.MISSING_GROUP_FOR_BACKREFERENCE, ErrorCode.InvalidBackReference); } else { return literalChar(c); } @@ -492,10 +523,10 @@ protected Token parseCustomEscape(char c) { protected int parseCustomEscapeChar(char c, boolean inCharClass) { switch (c) { case '0': - if (flags.isEitherUnicode() && lookahead(RegexLexer::isDecimalDigit, 1)) { - throw syntaxError(JsErrorMessages.INVALID_ESCAPE); + if (getLocalFlags().isEitherUnicode() && lookahead(RegexLexer::isDecimalDigit, 1)) { + throw syntaxError(JsErrorMessages.INVALID_ESCAPE, ErrorCode.InvalidEscape); } - if (!flags.isEitherUnicode() && lookahead(RegexLexer::isOctalDigit, 1)) { + if (!getLocalFlags().isEitherUnicode() && lookahead(RegexLexer::isOctalDigit, 1)) { return parseOctal(0, 2); } return '\0'; @@ -505,7 +536,7 @@ protected int parseCustomEscapeChar(char c, boolean inCharClass) { return handleInvalidControlEscape(); } final char controlLetter = curChar(); - if (!flags.isEitherUnicode() && (isDecimalDigit(controlLetter) || controlLetter == '_') && inCharClass) { + if (!getLocalFlags().isEitherUnicode() && (isDecimalDigit(controlLetter) || controlLetter == '_') && inCharClass) { advance(); return controlLetter % 32; } @@ -516,7 +547,7 @@ protected int parseCustomEscapeChar(char c, boolean inCharClass) { advance(); return Character.toUpperCase(controlLetter) - ('A' - 1); case 'u': - final int unicodeEscape = parseUnicodeEscapeChar(flags.isEitherUnicode()); + final int unicodeEscape = parseUnicodeEscapeChar(getLocalFlags().isEitherUnicode()); return unicodeEscape < 0 ? c : unicodeEscape; default: return -1; @@ -525,7 +556,7 @@ protected int parseCustomEscapeChar(char c, boolean inCharClass) { @Override protected int parseCustomEscapeCharFallback(int c, boolean inCharClass) { - if (inCharClass && flags.isUnicodeSets()) { + if (inCharClass && getLocalFlags().isUnicodeSets()) { // parsing a ClassSetCharacter in ClassSetExpression if (!SYNTAX_CHARS.get(c) && !CLASS_SET_RESERVED_PUNCTUATORS.get(c)) { return handleInvalidEscape(c); @@ -545,15 +576,76 @@ protected int parseCustomEscapeCharFallback(int c, boolean inCharClass) { } private char handleInvalidControlEscape() throws RegexSyntaxException { - if (flags.isEitherUnicode()) { - throw syntaxError(JsErrorMessages.INVALID_CONTROL_CHAR_ESCAPE); + if (getLocalFlags().isEitherUnicode()) { + throw syntaxError(JsErrorMessages.INVALID_CONTROL_CHAR_ESCAPE, ErrorCode.InvalidEscape); } return '\\'; } @Override protected Token parseCustomGroupBeginQ(char charAfterQuestionMark) { - return null; + if (RegexFlags.isValidFlagChar(charAfterQuestionMark) || charAfterQuestionMark == '-') { + return parseFlagModifier(charAfterQuestionMark); + } else { + return null; + } + } + + private RegexFlags parseLocalFlags(char firstChar) { + char ch = firstChar; + RegexFlags flags = RegexFlags.DEFAULT; + while (RegexFlags.isValidFlagChar(ch)) { + if (!RegexFlags.isValidLocalFlagChar(ch)) { + throw syntaxError(JsErrorMessages.flagNotAllowedInModifier(ch), ErrorCode.InvalidInlineFlag); + } + flags = flags.addNewFlagModifier(source, ch); + if (atEnd()) { + throw syntaxError(JsErrorMessages.INCOMPLETE_MODIFIER, ErrorCode.InvalidInlineFlag); + } + ch = consumeChar(); + } + return flags; + } + + private Token parseFlagModifier(char charAfterQuestionMark) { + RegexFlags addFlags = parseLocalFlags(charAfterQuestionMark); + char ch = prevChar(); + switch (ch) { + case ':': + return finishFlagModifier(addFlags, RegexFlags.DEFAULT); + case '-': + if (atEnd()) { + throw syntaxError(JsErrorMessages.INCOMPLETE_MODIFIER, ErrorCode.InvalidInlineFlag); + } + ch = consumeChar(); + RegexFlags removeFlags = parseLocalFlags(ch); + ch = prevChar(); + if (ch != ':') { + if (Character.isAlphabetic(ch)) { + throw syntaxError(JsErrorMessages.UNSUPPORTED_FLAG_IN_MODIFIER, ErrorCode.InvalidInlineFlag); + } else { + throw syntaxError(JsErrorMessages.INVALID_MODIFIER, ErrorCode.InvalidInlineFlag); + } + } + return finishFlagModifier(addFlags, removeFlags); + default: + if (Character.isAlphabetic(ch)) { + throw syntaxError(JsErrorMessages.UNSUPPORTED_FLAG_IN_MODIFIER, ErrorCode.InvalidInlineFlag); + } else { + throw syntaxError(JsErrorMessages.INVALID_MODIFIER, ErrorCode.InvalidInlineFlag); + } + } + } + + private Token finishFlagModifier(RegexFlags addFlags, RegexFlags removeFlags) { + if (addFlags.overlaps(removeFlags)) { + throw syntaxError(JsErrorMessages.MODIFIER_BOTH_ADDING_AND_REMOVING_FLAG, ErrorCode.InvalidInlineFlag); + } + if (addFlags.isNone() && removeFlags.isNone()) { + throw syntaxError(JsErrorMessages.EMPTY_MODIFIER, ErrorCode.InvalidInlineFlag); + } + RegexFlags newFlags = getLocalFlags().addFlags(addFlags).delFlags(removeFlags); + return Token.createInlineFlags(newFlags, false); } @Override @@ -575,7 +667,7 @@ private int parseUnicodeEscapeChar(boolean unicodeMode) throws RegexSyntaxExcept if (unicodeMode && consumingLookahead("{")) { final int value = parseHexUnicode(1, Integer.MAX_VALUE, 0x10ffff); if (!consumingLookahead("}")) { - throw syntaxError(JsErrorMessages.INVALID_UNICODE_ESCAPE); + throw syntaxError(JsErrorMessages.INVALID_UNICODE_ESCAPE, ErrorCode.InvalidEscape); } return value; } else { @@ -600,11 +692,11 @@ private int parseUnicodeEscapeChar(boolean unicodeMode) throws RegexSyntaxExcept private int parseHexUnicode(int minDigits, int maxDigits, int maxValue) { return parseHex(minDigits, maxDigits, maxValue, () -> { - if (flags.isEitherUnicode()) { - throw syntaxError(JsErrorMessages.INVALID_UNICODE_ESCAPE); + if (getLocalFlags().isEitherUnicode()) { + throw syntaxError(JsErrorMessages.INVALID_UNICODE_ESCAPE, ErrorCode.InvalidEscape); } }, () -> { - throw syntaxError(JsErrorMessages.INVALID_UNICODE_ESCAPE); + throw syntaxError(JsErrorMessages.INVALID_UNICODE_ESCAPE, ErrorCode.InvalidEscape); }); } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/JSRegexParser.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/JSRegexParser.java index 552ce68283fa..1c6242a90b23 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/JSRegexParser.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/JSRegexParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -44,6 +44,7 @@ import java.util.List; import java.util.Map; +import com.oracle.truffle.regex.RegexSyntaxException.ErrorCode; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.Equivalence; @@ -73,7 +74,6 @@ public final class JSRegexParser implements RegexParser { Token.Kind.backReference); private final RegexParserGlobals globals; private final RegexSource source; - private final RegexFlags flags; private final JSRegexLexer lexer; private final RegexASTBuilder astBuilder; private final CodePointSetAccumulator curCharClass = new CodePointSetAccumulator(); @@ -82,7 +82,7 @@ public final class JSRegexParser implements RegexParser { public JSRegexParser(RegexLanguage language, RegexSource source, CompilationBuffer compilationBuffer) throws RegexSyntaxException { this.globals = language.parserGlobals; this.source = source; - this.flags = RegexFlags.parseFlags(source); + RegexFlags flags = RegexFlags.parseFlags(source); this.lexer = new JSRegexLexer(source, flags, compilationBuffer); this.astBuilder = new RegexASTBuilder(language, source, flags, flags.isEitherUnicode(), compilationBuffer); } @@ -91,9 +91,13 @@ public static Group parseRootLess(RegexLanguage language, String pattern) throws return new JSRegexParser(language, new RegexSource(pattern, "", RegexOptions.DEFAULT, null), new CompilationBuffer(Encodings.UTF_16_RAW)).parse(false).getRoot(); } + private RegexFlags getLocalFlags() { + return lexer.getLocalFlags(); + } + @Override public RegexFlags getFlags() { - return flags; + return lexer.getGlobalFlags(); } @Override @@ -126,7 +130,7 @@ private RegexAST parse(boolean rootCapture) throws RegexSyntaxException { switch (token.kind) { case caret: if (prevKind != Token.Kind.caret) { - if (flags.isMultiline()) { + if (getLocalFlags().isMultiline()) { astBuilder.addCopy(token, globals.getJsMultiLineCaretSubstitution()); } else { astBuilder.addPositionAssertion(token); @@ -135,7 +139,7 @@ private RegexAST parse(boolean rootCapture) throws RegexSyntaxException { break; case dollar: if (prevKind != Token.Kind.dollar) { - if (flags.isMultiline()) { + if (getLocalFlags().isMultiline()) { astBuilder.addCopy(token, globals.getJsMultiLineDollarSubsitution()); } else { astBuilder.addPositionAssertion(token); @@ -150,7 +154,7 @@ private RegexAST parse(boolean rootCapture) throws RegexSyntaxException { astBuilder.replaceCurTermWithDeadNode(); break; } - if (flags.isEitherUnicode() && flags.isIgnoreCase()) { + if (getLocalFlags().isEitherUnicode() && getLocalFlags().isIgnoreCase()) { astBuilder.addCopy(token, globals.getJsUnicodeIgnoreCaseWordBoundarySubstitution()); } else { astBuilder.addCopy(token, globals.getJsWordBoundarySubstitution()); @@ -164,27 +168,27 @@ private RegexAST parse(boolean rootCapture) throws RegexSyntaxException { astBuilder.replaceCurTermWithDeadNode(); break; } - if (flags.isEitherUnicode() && flags.isIgnoreCase()) { + if (getLocalFlags().isEitherUnicode() && getLocalFlags().isIgnoreCase()) { astBuilder.addCopy(token, globals.getJsUnicodeIgnoreCaseNonWordBoundarySubsitution()); } else { astBuilder.addCopy(token, globals.getJsNonWordBoundarySubstitution()); } break; case backReference: - astBuilder.addBackReference((Token.BackReference) token, flags.isIgnoreCase()); + astBuilder.addBackReference((Token.BackReference) token, getLocalFlags().isIgnoreCase()); break; case quantifier: if (astBuilder.getCurTerm() == null || !QUANTIFIER_PREV.contains(prevKind)) { - throw syntaxError(JsErrorMessages.QUANTIFIER_WITHOUT_TARGET); + throw syntaxError(JsErrorMessages.QUANTIFIER_WITHOUT_TARGET, ErrorCode.InvalidQuantifier); } if (prevKind == Token.Kind.quantifier) { - throw syntaxError(JsErrorMessages.QUANTIFIER_ON_QUANTIFIER); + throw syntaxError(JsErrorMessages.QUANTIFIER_ON_QUANTIFIER, ErrorCode.InvalidQuantifier); } - if (flags.isEitherUnicode() && astBuilder.getCurTerm().isLookAheadAssertion()) { - throw syntaxError(JsErrorMessages.QUANTIFIER_ON_LOOKAHEAD_ASSERTION); + if (getLocalFlags().isEitherUnicode() && astBuilder.getCurTerm().isLookAheadAssertion()) { + throw syntaxError(JsErrorMessages.QUANTIFIER_ON_LOOKAHEAD_ASSERTION, ErrorCode.InvalidQuantifier); } if (astBuilder.getCurTerm().isLookBehindAssertion()) { - throw syntaxError(JsErrorMessages.QUANTIFIER_ON_LOOKBEHIND_ASSERTION); + throw syntaxError(JsErrorMessages.QUANTIFIER_ON_LOOKBEHIND_ASSERTION, ErrorCode.InvalidQuantifier); } astBuilder.addQuantifier((Token.Quantifier) token); break; @@ -197,6 +201,12 @@ private RegexAST parse(boolean rootCapture) throws RegexSyntaxException { case nonCaptureGroupBegin: astBuilder.pushGroup(token); break; + case inlineFlags: + Token.InlineFlags inlineFlags = (Token.InlineFlags) token; + assert !inlineFlags.isGlobal(); + astBuilder.pushGroup(inlineFlags); + lexer.pushLocalFlags((RegexFlags) inlineFlags.getFlags()); + break; case lookAheadAssertionBegin: astBuilder.pushLookAheadAssertion(token, ((Token.LookAheadAssertionBegin) token).isNegated()); break; @@ -205,12 +215,15 @@ private RegexAST parse(boolean rootCapture) throws RegexSyntaxException { break; case groupEnd: if (astBuilder.getCurGroup().getParent() instanceof RegexASTRootNode) { - throw syntaxError(JsErrorMessages.UNMATCHED_RIGHT_PARENTHESIS); + throw syntaxError(JsErrorMessages.UNMATCHED_RIGHT_PARENTHESIS, ErrorCode.UnmatchedParenthesis); + } + if (astBuilder.getCurGroup().isLocalFlags()) { + lexer.popLocalFlags(); } astBuilder.popGroup(token); break; case literalChar: - literalChar(((Token.LiteralCharacter) token).getCodePoint()); + literalChar((Token.LiteralCharacter) token); break; case charClass: astBuilder.addCharClass((Token.CharacterClass) token); @@ -223,35 +236,35 @@ private RegexAST parse(boolean rootCapture) throws RegexSyntaxException { break; case charClassEnd: boolean wasSingleChar = !lexer.isCurCharClassInverted() && curCharClass.matchesSingleChar(); - if (flags.isIgnoreCase()) { + if (getLocalFlags().isIgnoreCase()) { lexer.caseFoldUnfold(curCharClass); } CodePointSet cps = curCharClass.toCodePointSet(); - astBuilder.addCharClass(lexer.isCurCharClassInverted() ? cps.createInverse(source.getEncoding()) : cps, wasSingleChar); + astBuilder.addCharClass(lexer.isCurCharClassInverted() ? cps.createInverse(source.getEncoding()) : cps, wasSingleChar, token.getSourceSection()); break; case classSet: - astBuilder.addClassSet((Token.ClassSet) token, flags.isIgnoreCase() ? CaseFoldData.CaseFoldUnfoldAlgorithm.ECMAScriptUnicode : null); + astBuilder.addClassSet((Token.ClassSet) token, getLocalFlags().isIgnoreCase() ? CaseFoldData.CaseFoldUnfoldAlgorithm.ECMAScriptUnicode : null); break; default: throw CompilerDirectives.shouldNotReachHere(); } } if (!astBuilder.curGroupIsRoot()) { - throw syntaxError(JsErrorMessages.UNTERMINATED_GROUP); + throw syntaxError(JsErrorMessages.UNTERMINATED_GROUP, ErrorCode.UnmatchedParenthesis); } RegexAST ast = astBuilder.popRootGroup(); checkNamedCaptureGroups(ast); return ast; } - private void literalChar(int codePoint) { - if (flags.isIgnoreCase()) { + private void literalChar(Token.LiteralCharacter literalCharacter) { + if (getLocalFlags().isIgnoreCase()) { curCharClass.clear(); - curCharClass.addCodePoint(codePoint); + curCharClass.addCodePoint(literalCharacter.getCodePoint()); lexer.caseFoldUnfold(curCharClass); - astBuilder.addCharClass(curCharClass.toCodePointSet(), true); + astBuilder.addCharClass(curCharClass.toCodePointSet(), true, literalCharacter.getSourceSection()); } else { - astBuilder.addCharClass(CodePointSet.create(codePoint)); + astBuilder.addLiteralChar(literalCharacter); } } @@ -271,8 +284,8 @@ private void checkNamedCaptureGroups(RegexAST ast) { for (Map.Entry> entry : lexer.getNamedCaptureGroups().entrySet()) { for (int i = 0; i < entry.getValue().size() - 1; i++) { for (int j = i + 1; j < entry.getValue().size(); j++) { - if (canBothParticipate(ast.getGroup(entry.getValue().get(i)), ast.getGroup(entry.getValue().get(j)))) { - throw syntaxError(JsErrorMessages.MULTIPLE_GROUPS_SAME_NAME); + if (canBothParticipate(ast.getGroup(entry.getValue().get(i)).get(0), ast.getGroup(entry.getValue().get(j)).get(0))) { + throw syntaxError(JsErrorMessages.MULTIPLE_GROUPS_SAME_NAME, ErrorCode.InvalidNamedGroup); } } } @@ -306,7 +319,7 @@ private static boolean canBothParticipate(Group a, Group b) { throw CompilerDirectives.shouldNotReachHere("no common ancestor found for named capture groups in regexp"); } - private RegexSyntaxException syntaxError(String msg) { - return RegexSyntaxException.createPattern(source, msg, lexer.getLastTokenPosition()); + private RegexSyntaxException syntaxError(String msg, ErrorCode errorCode) { + return RegexSyntaxException.createPattern(source, msg, lexer.getLastTokenPosition(), errorCode); } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/JSRegexValidator.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/JSRegexValidator.java index 99770375d5ab..a75889f3dff3 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/JSRegexValidator.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/JSRegexValidator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -50,6 +50,7 @@ import com.oracle.truffle.regex.RegexLanguage; import com.oracle.truffle.regex.RegexSource; import com.oracle.truffle.regex.RegexSyntaxException; +import com.oracle.truffle.regex.RegexSyntaxException.ErrorCode; import com.oracle.truffle.regex.UnsupportedRegexException; import com.oracle.truffle.regex.errors.JsErrorMessages; import com.oracle.truffle.regex.tregex.buffer.CompilationBuffer; @@ -134,14 +135,14 @@ private void parseDryRun() throws RegexSyntaxException { case quantifier: switch (curTermState) { case Null: - throw syntaxError(JsErrorMessages.QUANTIFIER_WITHOUT_TARGET); + throw syntaxError(JsErrorMessages.QUANTIFIER_WITHOUT_TARGET, ErrorCode.InvalidQuantifier); case LookAheadAssertion: if (flags.isEitherUnicode()) { - throw syntaxError(JsErrorMessages.QUANTIFIER_ON_LOOKAHEAD_ASSERTION); + throw syntaxError(JsErrorMessages.QUANTIFIER_ON_LOOKAHEAD_ASSERTION, ErrorCode.InvalidQuantifier); } break; case LookBehindAssertion: - throw syntaxError(JsErrorMessages.QUANTIFIER_ON_LOOKBEHIND_ASSERTION); + throw syntaxError(JsErrorMessages.QUANTIFIER_ON_LOOKBEHIND_ASSERTION, ErrorCode.InvalidQuantifier); case Other: break; } @@ -152,6 +153,7 @@ private void parseDryRun() throws RegexSyntaxException { break; case captureGroupBegin: case nonCaptureGroupBegin: + case inlineFlags: syntaxStack.add(RegexStackElem.Group); curTermState = CurTermState.Null; break; @@ -165,7 +167,7 @@ private void parseDryRun() throws RegexSyntaxException { break; case groupEnd: if (syntaxStack.isEmpty()) { - throw syntaxError(JsErrorMessages.UNMATCHED_RIGHT_PARENTHESIS); + throw syntaxError(JsErrorMessages.UNMATCHED_RIGHT_PARENTHESIS, ErrorCode.UnmatchedParenthesis); } RegexStackElem poppedElem = syntaxStack.remove(syntaxStack.size() - 1); switch (poppedElem) { @@ -185,10 +187,10 @@ private void parseDryRun() throws RegexSyntaxException { } } if (lexer.inCharacterClass()) { - throw syntaxError(JsErrorMessages.UNMATCHED_LEFT_BRACKET); + throw syntaxError(JsErrorMessages.UNMATCHED_LEFT_BRACKET, ErrorCode.UnmatchedBracket); } if (!syntaxStack.isEmpty()) { - throw syntaxError(JsErrorMessages.UNTERMINATED_GROUP); + throw syntaxError(JsErrorMessages.UNTERMINATED_GROUP, ErrorCode.UnmatchedParenthesis); } checkNamedCaptureGroups(); } @@ -207,7 +209,7 @@ private void checkNamedCaptureGroups() { } } - private RegexSyntaxException syntaxError(String msg) { - return RegexSyntaxException.createPattern(source, msg, lexer.getLastTokenPosition()); + private RegexSyntaxException syntaxError(String msg, ErrorCode errorCode) { + return RegexSyntaxException.createPattern(source, msg, lexer.getLastTokenPosition(), errorCode); } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexASTBuilder.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexASTBuilder.java index 39083fd466eb..a867a315c605 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexASTBuilder.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexASTBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -44,6 +44,7 @@ import java.util.Arrays; import java.util.Comparator; +import com.oracle.truffle.regex.tregex.parser.ast.visitors.MarkAsDeadVisitor; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.Equivalence; @@ -216,7 +217,7 @@ public void pushRootGroup(boolean rootCapture) { */ public RegexAST popRootGroup() { optimizeGroup(curGroup); - ast.getRoot().setEnclosedCaptureGroupsHigh(groupCount.getCount()); + ast.getRoot().setEnclosedCaptureGroupsHi(groupCount.getCount()); return ast; } @@ -229,6 +230,9 @@ public RegexAST popRootGroup() { */ public void pushGroup(Token token) { pushGroup(token, ast.createGroup(), null, true); + if (token != null && token.kind == Token.Kind.inlineFlags) { + curGroup.setLocalFlags(true); + } } public void pushGroup() { @@ -332,7 +336,7 @@ private Group pushGroup(Token token, Group group, RegexASTSubtreeRootNode parent addSourceSection(group, token); setGroupStartPosition(group, token); curGroup = group; - curGroup.setEnclosedCaptureGroupsLow(groupCount.getCount()); + curGroup.setEnclosedCaptureGroupsLo(groupCount.getCount()); if (openFirstSequence) { nextSequence(); } else { @@ -361,7 +365,7 @@ public void popGroup(Token token) { ast.getNodeCount().dec(); } optimizeGroup(curGroup); - curGroup.setEnclosedCaptureGroupsHigh(groupCount.getCount()); + curGroup.setEnclosedCaptureGroupsHi(groupCount.getCount()); addSourceSection(curGroup, token); if (curGroup.getParent().isSubtreeRoot()) { addSourceSection(curGroup.getParent(), token); @@ -422,10 +426,20 @@ public void addCharClass(CodePointSet charSet, boolean wasSingleChar) { addCharClass(Token.createCharClass(charSet, wasSingleChar)); } + public void addCharClass(CodePointSet charSet, boolean wasSingleChar, SourceSection sourceSection) { + Token.CharacterClass charClass = Token.createCharClass(charSet, wasSingleChar); + charClass.setSourceSection(sourceSection); + addCharClass(charClass); + } + public void addCharClass(CodePointSet charSet) { addCharClass(charSet, charSet.matchesSingleChar()); } + public void addLiteralChar(Token.LiteralCharacter literalCharacter) { + addCharClass(CodePointSet.create(literalCharacter.getCodePoint()), true, literalCharacter.getSourceSection()); + } + private CodePointSet pruneCharClass(CodePointSet cps) { return encoding.getFullSet().createIntersection(cps, compilationBuffer); } @@ -448,8 +462,8 @@ private Term translateUnicodeCharClass(CodePointSet codePointSet, Token token, b return createCharClass(codePointSet, token, wasSingleChar); } Group group = ast.createGroup(); - group.setEnclosedCaptureGroupsLow(groupCount.getCount()); - group.setEnclosedCaptureGroupsHigh(groupCount.getCount()); + group.setEnclosedCaptureGroupsLo(groupCount.getCount()); + group.setEnclosedCaptureGroupsHi(groupCount.getCount()); IntRangesBuffer tmp = compilationBuffer.getIntRangesBuffer1(); CodePointSet bmpRanges = codePointSet.createIntersection(Constants.BMP_WITHOUT_SURROGATES, tmp); CodePointSet astralRanges = codePointSet.createIntersection(Constants.ASTRAL_SYMBOLS, tmp); @@ -556,6 +570,10 @@ public void addClassSet(Token.ClassSet token, CaseFoldData.CaseFoldUnfoldAlgorit CodePointSetAccumulator buf = compilationBuffer.getCodePointSetAccumulator1(); ClassSetContents contents = token.getContents(); + if (contents.isEmpty()) { + addCharClass(CodePointSet.getEmpty()); + return; + } pushGroup(false); String[] sortedStrings = new String[contents.getStrings().size()]; @@ -812,11 +830,11 @@ private void setQuantifier(QuantifiableTerm term, Token.Quantifier quantifier) { private Group wrapTermInGroup(Term term) { Group wrapperGroup = ast.createGroup(); if (term.isGroup()) { - wrapperGroup.setEnclosedCaptureGroupsLow(term.asGroup().getCaptureGroupsLow()); - wrapperGroup.setEnclosedCaptureGroupsHigh(term.asGroup().getCaptureGroupsHigh()); + wrapperGroup.setEnclosedCaptureGroupsLo(term.asGroup().getCaptureGroupsLo()); + wrapperGroup.setEnclosedCaptureGroupsHi(term.asGroup().getCaptureGroupsHi()); } else if (term.isAtomicGroup()) { - wrapperGroup.setEnclosedCaptureGroupsLow(term.asAtomicGroup().getEnclosedCaptureGroupsLow()); - wrapperGroup.setEnclosedCaptureGroupsHigh(term.asAtomicGroup().getEnclosedCaptureGroupsHigh()); + wrapperGroup.setEnclosedCaptureGroupsLo(term.asAtomicGroup().getEnclosedCaptureGroupsLow()); + wrapperGroup.setEnclosedCaptureGroupsHi(term.asAtomicGroup().getEnclosedCaptureGroupsHigh()); } Sequence wrapperSequence = wrapperGroup.addSequence(ast); term.getParent().asSequence().replace(term.getSeqIndex(), wrapperGroup); @@ -844,6 +862,7 @@ public void addCopy(Token token, Group sourceGroup) { */ public void removeCurTerm() { ast.getNodeCount().dec(countVisitor.count(curSequence.getLastTerm())); + MarkAsDeadVisitor.markAsDead(curSequence.getLastTerm()); curSequence.removeLastTerm(); curTerm = curSequence.isEmpty() ? null : curSequence.getLastTerm(); } @@ -968,7 +987,7 @@ public void addWordNonBoundaryAssertionPython(CodePointSet wordChars, CodePointS /* optimizations */ private void optimizeGroup(Group group) { - if (group.isConditionalBackReferenceGroup()) { + if (group.isConditionalBackReferenceGroup() || group.isInLookBehindAssertion()) { return; } sortAlternatives(group); @@ -1103,9 +1122,9 @@ private void mergeCommonPrefixes(Group group) { copy.add(t); if (t.isGroup()) { Group g = t.asGroup(); - if (g.getEnclosedCaptureGroupsLow() != g.getEnclosedCaptureGroupsHigh()) { - enclosedCGLo = Math.min(enclosedCGLo, g.getEnclosedCaptureGroupsLow()); - enclosedCGHi = Math.max(enclosedCGHi, g.getEnclosedCaptureGroupsHigh()); + if (g.getEnclosedCaptureGroupsLo() != g.getEnclosedCaptureGroupsHi()) { + enclosedCGLo = Math.min(enclosedCGLo, g.getEnclosedCaptureGroupsLo()); + enclosedCGHi = Math.max(enclosedCGHi, g.getEnclosedCaptureGroupsHi()); } if (g.isCapturing()) { enclosedCGLo = Math.min(enclosedCGLo, g.getGroupNumber()); @@ -1117,8 +1136,8 @@ private void mergeCommonPrefixes(Group group) { } } if (enclosedCGLo != Integer.MAX_VALUE) { - innerGroup.setEnclosedCaptureGroupsLow(enclosedCGLo); - innerGroup.setEnclosedCaptureGroupsHigh(enclosedCGHi); + innerGroup.setEnclosedCaptureGroupsLo(enclosedCGLo); + innerGroup.setEnclosedCaptureGroupsHi(enclosedCGHi); } if (!innerGroup.isEmpty() && !(innerGroup.size() == 1 && innerGroup.getFirstAlternative().isEmpty())) { optimizeGroup(innerGroup); diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexASTPostProcessor.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexASTPostProcessor.java index ddb56803a788..be8605c65b9b 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexASTPostProcessor.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexASTPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -49,6 +49,7 @@ import com.oracle.truffle.regex.tregex.TRegexOptions; import com.oracle.truffle.regex.tregex.buffer.CompilationBuffer; import com.oracle.truffle.regex.tregex.parser.ast.BackReference; +import com.oracle.truffle.regex.tregex.parser.ast.CalcASTFlagsVisitor; import com.oracle.truffle.regex.tregex.parser.ast.CalcASTPropsVisitor; import com.oracle.truffle.regex.tregex.parser.ast.CharacterClass; import com.oracle.truffle.regex.tregex.parser.ast.Group; @@ -56,6 +57,7 @@ import com.oracle.truffle.regex.tregex.parser.ast.PositionAssertion; import com.oracle.truffle.regex.tregex.parser.ast.QuantifiableTerm; import com.oracle.truffle.regex.tregex.parser.ast.RegexAST; +import com.oracle.truffle.regex.tregex.parser.ast.RegexASTNode; import com.oracle.truffle.regex.tregex.parser.ast.Sequence; import com.oracle.truffle.regex.tregex.parser.ast.SubexpressionCall; import com.oracle.truffle.regex.tregex.parser.ast.Term; @@ -64,6 +66,7 @@ import com.oracle.truffle.regex.tregex.parser.ast.visitors.InitIDVisitor; import com.oracle.truffle.regex.tregex.parser.ast.visitors.MarkLookBehindEntriesVisitor; import com.oracle.truffle.regex.tregex.parser.ast.visitors.NodeCountVisitor; +import com.oracle.truffle.regex.tregex.parser.ast.visitors.PropagateDeadFlagVisitor; import com.oracle.truffle.regex.tregex.string.Encodings; public class RegexASTPostProcessor { @@ -81,6 +84,7 @@ public RegexASTPostProcessor(RegexAST ast, CompilationBuffer compilationBuffer) } public void prepareForDFA() { + CalcASTFlagsVisitor.run(ast); if (ast.getOptions().isBooleanMatch()) { DisableCaptureGroupsVisitor.disableCaptureGroups(ast); } @@ -89,6 +93,7 @@ public void prepareForDFA() { UnrollQuantifiersVisitor.unrollQuantifiers(ast); } CalcASTPropsVisitor.run(ast, compilationBuffer); + PropagateDeadFlagVisitor.propagateDeadFlag(ast.getRoot()); ast.createPrefix(); InitIDVisitor.init(ast); if (ast.canTransformToDFA()) { @@ -160,7 +165,7 @@ protected void visit(CharacterClass characterClass) { @Override protected void leave(Group group) { if (group.hasQuantifier()) { - quantifierExpander.expandQuantifier(group, shouldUnroll(group) && shouldUnrollVisitor.shouldUnroll(group)); + quantifierExpander.expandQuantifier(group, group.getQuantifier().isUnrollTrivial() || shouldUnroll(group) && shouldUnrollVisitor.shouldUnroll(group)); } } @@ -204,6 +209,7 @@ private static final class QuantifierExpander { private final RegexAST ast; private final CopyVisitor copyVisitor; + private final ClearRegisteredCaptureGroupsVisitor clearRegisteredCaptureGroupsVisitor; private Group curGroup; private Sequence curSequence; private Term curTerm; @@ -211,6 +217,7 @@ private static final class QuantifierExpander { QuantifierExpander(RegexAST ast) { this.ast = ast; this.copyVisitor = new CopyVisitor(ast); + this.clearRegisteredCaptureGroupsVisitor = new ClearRegisteredCaptureGroupsVisitor(ast); } private void pushGroup() { @@ -249,40 +256,49 @@ private void addTermCopyAsGroup(Term term) { addTerm(copyVisitor.copy(term)); popGroup(); if (term.isGroup()) { - curTerm.asGroup().setEnclosedCaptureGroupsLow(term.asGroup().getCaptureGroupsLow()); - curTerm.asGroup().setEnclosedCaptureGroupsHigh(term.asGroup().getCaptureGroupsHigh()); + curTerm.asGroup().setEnclosedCaptureGroupsLo(term.asGroup().getCaptureGroupsLo()); + curTerm.asGroup().setEnclosedCaptureGroupsHi(term.asGroup().getCaptureGroupsHi()); } } } - private void createOptionalBranch(QuantifiableTerm term, Token.Quantifier quantifier, boolean unroll, int recurse) { + private void createOptionalBranch(QuantifiableTerm term, Token.Quantifier quantifier, boolean unroll, boolean mandatory, boolean optional, int recurse) { // We wrap the quantified term in a group, as NFATraversalRegexASTVisitor is set up // to expect quantifier guards only on group boundaries. + if (term.isInLookBehindAssertion()) { + createOptional(term, quantifier, unroll, mandatory, optional, recurse - 1); + } addTermCopyAsGroup(term); curTerm.asGroup().setQuantifier(quantifier); curTerm.setExpandedQuantifier(unroll); - curTerm.setMandatoryUnrolledQuantifier(false); + curTerm.setMandatoryQuantifier(mandatory); + curTerm.setOptionalQuantifier(optional); curTerm.setEmptyGuard(true); - createOptional(term, quantifier, unroll, recurse - 1); + if (!term.isInLookBehindAssertion()) { + createOptional(term, quantifier, unroll, mandatory, optional, recurse - 1); + } } - private void createOptional(QuantifiableTerm term, Token.Quantifier quantifier, boolean unroll, int recurse) { + private void createOptional(QuantifiableTerm term, Token.Quantifier quantifier, boolean unroll, boolean mandatory, boolean optional, int recurse) { if (recurse < 0) { return; } pushGroup(); if (term.isGroup()) { - curGroup.setEnclosedCaptureGroupsLow(term.asGroup().getCaptureGroupsLow()); - curGroup.setEnclosedCaptureGroupsHigh(term.asGroup().getCaptureGroupsHigh()); + curGroup.setEnclosedCaptureGroupsLo(term.asGroup().getCaptureGroupsLo()); + curGroup.setEnclosedCaptureGroupsHi(term.asGroup().getCaptureGroupsHi()); } - if (quantifier.isGreedy()) { - createOptionalBranch(term, quantifier, unroll, recurse); + if (quantifier.isGreedy() || mandatory) { + createOptionalBranch(term, quantifier, unroll, mandatory, optional, recurse); nextSequence(); curSequence.setQuantifierPassThroughSequence(true); } else { curSequence.setQuantifierPassThroughSequence(true); nextSequence(); - createOptionalBranch(term, quantifier, unroll, recurse); + createOptionalBranch(term, quantifier, unroll, false, optional, recurse); + } + if (!unroll && !mandatory && recurse == 0) { + curGroup.setLoop(true); } popGroup(); } @@ -290,6 +306,7 @@ private void createOptional(QuantifiableTerm term, Token.Quantifier quantifier, private void expandQuantifier(QuantifiableTerm toExpand, boolean unroll) { assert toExpand.hasQuantifier(); assert !unroll || toExpand.isUnrollingCandidate(); + clearRegisteredCaptureGroupsVisitor.clear(toExpand); Token.Quantifier quantifier = toExpand.getQuantifier(); toExpand.setQuantifier(null); @@ -300,17 +317,33 @@ private void expandQuantifier(QuantifiableTerm toExpand, boolean unroll) { // replace the term to expand with a new wrapper group replaceCurTermWithNewGroup(); + boolean mandatoryOptionalSplit = !unroll && !ast.getFlavor().emptyChecksOnMandatoryLoopIterations() && quantifier.getMin() > 0 && toExpand.mayMatchEmptyString(); + + if (toExpand.isInLookBehindAssertion()) { + unrollOptional(toExpand, quantifier, unroll, mandatoryOptionalSplit); + unrollMandatory(toExpand, quantifier, unroll, mandatoryOptionalSplit); + } else { + unrollMandatory(toExpand, quantifier, unroll, mandatoryOptionalSplit); + unrollOptional(toExpand, quantifier, unroll, mandatoryOptionalSplit); + } + } + + private void unrollMandatory(QuantifiableTerm toExpand, Token.Quantifier quantifier, boolean unroll, boolean mandatoryOptionalSplit) { // unroll mandatory part ( x{3} -> xxx ) if (unroll) { - // unroll non-optional part ( x{3} -> xxx ) for (int i = 0; i < quantifier.getMin(); i++) { addTermCopyAsGroup(toExpand); curTerm.asGroup().setQuantifier(quantifier); curTerm.setExpandedQuantifier(true); - curTerm.setMandatoryUnrolledQuantifier(true); + curTerm.setMandatoryQuantifier(true); } + } else if (mandatoryOptionalSplit) { + createOptional(toExpand, quantifier, false, true, false, 0); + ((Group) curTerm).setLoop(true); } + } + private void unrollOptional(QuantifiableTerm toExpand, Token.Quantifier quantifier, boolean unroll, boolean mandatoryOptionalSplit) { // unroll optional part ( x{0,3} -> (x(x(x|)|)|) ) // In flavors like Python or Ruby, loops can be repeated past the point where the // position in the string keeps advancing (i.e. we are matching at least one @@ -319,9 +352,13 @@ private void expandQuantifier(QuantifiableTerm toExpand, boolean unroll) { // iteration is run because there is no backtracking after failing the empty check. // We can emulate this behavior by dropping empty guards in small bounded loops, // such as is the case for unrolled loops. - createOptional(toExpand, quantifier, unroll, !unroll || quantifier.isInfiniteLoop() ? 0 : (quantifier.getMax() - quantifier.getMin()) - 1); - if (!unroll || quantifier.isInfiniteLoop()) { - ((Group) curTerm).setLoop(true); + if (unroll) { + createOptional(toExpand, quantifier, true, false, false, quantifier.isInfiniteLoop() ? 0 : quantifier.getMax() - quantifier.getMin() - 1); + if (quantifier.isInfiniteLoop()) { + ((Group) curTerm).setLoop(true); + } + } else if (quantifier.isInfiniteLoop() || quantifier.getMax() > quantifier.getMin() || !mandatoryOptionalSplit) { + createOptional(toExpand, quantifier, false, false, mandatoryOptionalSplit, 0); } } } @@ -395,25 +432,39 @@ private static LookAroundOptimization replace(Term replacement) { private LookAroundOptimization optimizeLookAround(LookAroundAssertion lookaround) { Group group = lookaround.getGroup(); - // Drop empty lookarounds: - // * (?=) -> NOP - // * (?<=) -> NOP - // * (?!) -> DEAD - // * (? DEAD - if (group.size() == 1 && group.getFirstAlternative().isEmpty()) { - if (lookaround.isNegated()) { - // empty negative lookarounds never match - ast.getNodeCount().dec(countVisitor.count(lookaround)); - return LookAroundOptimization.replace(ast.createCharacterClass(CodePointSet.getEmpty())); - } else { - // empty positive lookarounds are no-ops - ast.getNodeCount().dec(countVisitor.count(lookaround)); - return LookAroundOptimization.NO_OP; + // Simplify lookarounds with empty branches: + boolean hasCaptureGroups = false; + for (int i = 0; i < group.size(); i++) { + Sequence s = group.getAlternatives().get(i); + // we also check for s.isEmpty here, because a previous lookaround optimization may + // already have removed the capture groups, and we don't re-run CalcAstPropsVisitor + // between these optimizations. + // Example: in /(?<=(?=|()))/, we first remove the inner lookahead, so the outer + // lookbehind is empty but still has the hasGroups flag set. + hasCaptureGroups |= s.hasCaptureGroups() && !s.isEmpty(); + if (s.isEmpty()) { + if (lookaround.isNegated()) { + // negative lookarounds with empty branches never match + ast.getNodeCount().dec(countVisitor.count(lookaround)); + return LookAroundOptimization.replace(ast.createCharacterClass(CodePointSet.getEmpty())); + } else { + // positive lookarounds with empty branches are no-ops, but we still have to + // keep higher priority branches if they have capture groups + if (hasCaptureGroups) { + if (group.size() > i + 1) { + group.getAlternatives().subList(i + 1, group.size()).clear(); + } + break; + } else { + ast.getNodeCount().dec(countVisitor.count(lookaround)); + return LookAroundOptimization.NO_OP; + } + } } } // Extract position assertions from positive lookarounds - if (!lookaround.isNegated()) { + if (!lookaround.isNegated() && !lookaround.hasCaptureGroups()) { if (group.size() == 1 && group.getFirstAlternative().size() == 1 && group.getFirstAlternative().getFirstTerm().isPositionAssertion()) { // unwrap positive lookarounds containing only a position assertion // * (?=$) -> $ @@ -436,10 +487,11 @@ private LookAroundOptimization optimizeLookAround(LookAroundAssertion lookaround if (innerPositionAssertion >= 0) { Sequence removed = group.getAlternatives().remove(innerPositionAssertion); Group wrapGroup = ast.createGroup(); - wrapGroup.setEnclosedCaptureGroupsLow(group.getCaptureGroupsLow()); - wrapGroup.setEnclosedCaptureGroupsHigh(group.getCaptureGroupsHigh()); + wrapGroup.setEnclosedCaptureGroupsLo(group.getCaptureGroupsLo()); + wrapGroup.setEnclosedCaptureGroupsHi(group.getCaptureGroupsHi()); wrapGroup.add(removed); Sequence wrapSeq = wrapGroup.addSequence(ast); + assert !group.isEmpty(); wrapSeq.add(lookaround); return LookAroundOptimization.replace(wrapGroup); } @@ -488,9 +540,30 @@ public static void disableCaptureGroups(RegexAST ast) { @Override protected void visit(Group group) { - if (group.isCapturing() && !ast.isGroupReferenced(group.getGroupNumber())) { + if (group.isCapturing() && !ast.isGroupReferenced(group.getGroupNumber()) && + !(group.getGroupNumber() == 0 && (ast.getProperties().hasMatchBoundaryAssertions() || ast.getOptions().isMustAdvance()))) { group.clearGroupNumber(); } } } + + private static final class ClearRegisteredCaptureGroupsVisitor extends DepthFirstTraversalRegexASTVisitor { + + private final RegexAST ast; + + private ClearRegisteredCaptureGroupsVisitor(RegexAST ast) { + this.ast = ast; + } + + public void clear(RegexASTNode root) { + run(root); + } + + @Override + protected void visit(Group group) { + if (group.isCapturing()) { + ast.clearRegisteredCaptureGroups(group.getGroupNumber()); + } + } + } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexLexer.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexLexer.java index 75d82d3219d0..12fb8a8e1fe9 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexLexer.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexLexer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -46,6 +46,7 @@ import java.util.Map; import java.util.function.IntPredicate; +import com.oracle.truffle.regex.RegexSyntaxException.ErrorCode; import org.graalvm.collections.EconomicSet; import com.oracle.truffle.api.ArrayUtils; @@ -540,6 +541,10 @@ protected char curChar() { return pattern.charAt(position); } + protected char prevChar() { + return pattern.charAt(position - 1); + } + protected char consumeChar() { final char c = pattern.charAt(position); advance(); @@ -760,16 +765,19 @@ protected Token literalChar(int codePoint) { private Token charClass(CodePointSet codePointSet) { if (featureEnabledIgnoreCase()) { - curCharClass.clear(); - curCharClass.addSet(codePointSet); - boolean wasSingleChar = curCharClass.matchesSingleChar(); - caseFoldUnfold(curCharClass); - return Token.createCharClass(curCharClass.toCodePointSet(), wasSingleChar); + return Token.createCharClass(caseFoldUnfold(codePointSet), codePointSet.matchesSingleChar()); } else { return Token.createCharClass(codePointSet); } } + protected CodePointSet caseFoldUnfold(CodePointSet codePointSet) { + curCharClass.clear(); + curCharClass.addSet(codePointSet); + caseFoldUnfold(curCharClass); + return curCharClass.toCodePointSet(); + } + /* lexer */ private Token getNext() throws RegexSyntaxException { @@ -1253,8 +1261,9 @@ protected ClassSetContents parseClassSetExpression() throws RegexSyntaxException } } + boolean atStart = position == startPos; ClassSetOperator newOperator = parseClassSetOperator(); - if (position == startPos) { + if (atStart) { if (newOperator != ClassSetOperator.Union) { throw handleMissingClassSetOperand(newOperator); } @@ -1356,11 +1365,11 @@ private ClassSetContents parseClassSetStrings(char c) { strings.add(string); } if (atEnd()) { - throw syntaxError(JsErrorMessages.UNTERMINATED_STRING_SET); + throw syntaxError(JsErrorMessages.UNTERMINATED_STRING_SET, ErrorCode.InvalidCharacterClass); } } while (consumingLookahead('|')); if (atEnd()) { - throw syntaxError(JsErrorMessages.UNTERMINATED_STRING_SET); + throw syntaxError(JsErrorMessages.UNTERMINATED_STRING_SET, ErrorCode.InvalidCharacterClass); } assert curChar() == '}'; advance(); @@ -1387,14 +1396,14 @@ private String parseClassSetString() { protected ClassSetContents parseUnicodeCharacterProperty(boolean invert) throws RegexSyntaxException { if (!consumingLookahead("{")) { - throw syntaxError(JsErrorMessages.INVALID_UNICODE_PROPERTY); + throw syntaxError(JsErrorMessages.INVALID_UNICODE_PROPERTY, ErrorCode.InvalidCharacterClass); } int namePos = position; while (!atEnd() && curChar() != '}') { advance(); } if (!consumingLookahead("}")) { - throw syntaxError(JsErrorMessages.ENDS_WITH_UNFINISHED_UNICODE_PROPERTY); + throw syntaxError(JsErrorMessages.ENDS_WITH_UNFINISHED_UNICODE_PROPERTY, ErrorCode.InvalidCharacterClass); } try { String propertyName = pattern.substring(namePos, position - 1); @@ -1415,7 +1424,7 @@ protected ClassSetContents parseUnicodeCharacterProperty(boolean invert) throws return ClassSetContents.createCharacterClass(invert ? propertySet.createInverse(encoding) : propertySet); } } catch (IllegalArgumentException e) { - throw syntaxError(e.getMessage()); + throw syntaxError(e.getMessage(), ErrorCode.InvalidCharacterClass); } } @@ -1512,8 +1521,8 @@ private boolean isEscapeCharClass(char c) { return isPredefCharClass(c) || (featureEnabledUnicodePropertyEscapes() && (c == 'p' || c == 'P')); } - public RegexSyntaxException syntaxError(String msg) { - return RegexSyntaxException.createPattern(source, msg, getLastAtomPosition()); + public RegexSyntaxException syntaxError(String msg, ErrorCode errorCode) { + return RegexSyntaxException.createPattern(source, msg, getLastAtomPosition(), errorCode); } public static boolean isDecimalDigit(int c) { diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexProperties.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexProperties.java index ab87144fab5d..7f1e47690150 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexProperties.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/RegexProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -63,10 +63,10 @@ public class RegexProperties implements JsonConvertible { private static final int FLAG_FIXED_CODEPOINT_WIDTH = 1 << 12; private static final int FLAG_CAPTURE_GROUPS_IN_LOOK_AROUND_ASSERTIONS = 1 << 13; private static final int FLAG_EMPTY_CAPTURE_GROUPS = 1 << 14; - private static final int FLAG_ATOMIC_GROUPS = 1 << 15; - private static final int FLAG_BACK_REFERENCES = 1 << 16; - private static final int FLAG_RECURSIVE_BACK_REFERENCES = 1 << 17; - private static final int FLAG_NESTED_LOOK_BEHIND_ASSERTIONS = 1 << 18; + private static final int FLAG_BACK_REFERENCES = 1 << 15; + private static final int FLAG_RECURSIVE_BACK_REFERENCES = 1 << 16; + private static final int FLAG_NESTED_LOOK_BEHIND_ASSERTIONS = 1 << 17; + private static final int FLAG_LOOK_AROUND_WITH_CAPTURE_GROUPS_NESTED_IN_QUANTIFIER = 1 << 18; private static final int FLAG_CONDITIONAL_BACKREFERENCES = 1 << 19; private static final int FLAG_CONDITIONAL_REFERENCES_INTO_LOOK_AHEADS = 1 << 20; private static final int FLAG_MATCH_BOUNDARY_ASSERTIONS = 1 << 21; @@ -111,14 +111,6 @@ public void setEmptyCaptureGroups() { setFlag(FLAG_EMPTY_CAPTURE_GROUPS); } - public boolean hasAtomicGroups() { - return getFlag(FLAG_ATOMIC_GROUPS); - } - - public void setAtomicGroups() { - setFlag(FLAG_ATOMIC_GROUPS); - } - public boolean hasCharClasses() { return getFlag(FLAG_CHAR_CLASSES); } @@ -264,6 +256,14 @@ public void setNestedLookBehindAssertions() { setFlag(FLAG_NESTED_LOOK_BEHIND_ASSERTIONS); } + public boolean hasLookAroundWithCaptureGroupsNestedInQuantifier() { + return getFlag(FLAG_LOOK_AROUND_WITH_CAPTURE_GROUPS_NESTED_IN_QUANTIFIER); + } + + public void setLookAroundWithCaptureGroupsNestedInQuantifier() { + setFlag(FLAG_LOOK_AROUND_WITH_CAPTURE_GROUPS_NESTED_IN_QUANTIFIER); + } + public boolean hasConditionalBackReferences() { return getFlag(FLAG_CONDITIONAL_BACKREFERENCES); } @@ -303,6 +303,7 @@ public JsonValue toJson() { Json.prop("captureGroupsInLookAroundAssertions", hasCaptureGroupsInLookAroundAssertions()), Json.prop("backReferences", hasBackReferences()), Json.prop("nestedLookBehindAssertions", hasNestedLookBehindAssertions()), + Json.prop("lookAroundWithCaptureGroupsNestedInQuantifier", hasLookAroundWithCaptureGroupsNestedInQuantifier()), Json.prop("conditionalBackReferences", hasConditionalBackReferences()), Json.prop("conditionalReferencesIntoLookAheads", hasConditionalReferencesIntoLookAheads())); } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/Token.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/Token.java index 0c0fba7ae4e1..7e9318e15606 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/Token.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/Token.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -297,6 +297,10 @@ public boolean isGreedy() { return greedy; } + public boolean isLazy() { + return !greedy; + } + public boolean isPossessive() { return possessive; } @@ -333,6 +337,14 @@ public void setZeroWidthIndex(int zeroWidthIndex) { this.zeroWidthIndex = zeroWidthIndex; } + /** + * Returns {@code true} if {@link #getMax()} is infinite or greater than the given + * threshold. + */ + public boolean isMaxGreaterThan(int threshold) { + return Integer.compareUnsigned(max, threshold) > 0; + } + /** * Returns {@code true} iff both {@link #getMin()} and {@link #getMax()} are less or equal * to the given threshold, or infinite {@link #isInfiniteLoop()}. diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/AtomicGroup.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/AtomicGroup.java index ff5db6105c0c..449359ab683f 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/AtomicGroup.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/AtomicGroup.java @@ -83,14 +83,14 @@ public Term copyRecursive(RegexAST ast, CompilationBuffer compilationBuffer) { * Gets the (inclusive) lower bound of the range of capture groups contained within this group. */ public int getEnclosedCaptureGroupsLow() { - return getGroup().getEnclosedCaptureGroupsLow(); + return getGroup().getEnclosedCaptureGroupsLo(); } /** * Gets the (exclusive) upper bound of the range of capture groups contained within this group. */ public int getEnclosedCaptureGroupsHigh() { - return getGroup().getEnclosedCaptureGroupsHigh(); + return getGroup().getEnclosedCaptureGroupsHi(); } @Override diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/BackReference.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/BackReference.java index 06c7bf95ab8d..e3aa903f0081 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/BackReference.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/BackReference.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -173,6 +173,6 @@ public String toString() { @TruffleBoundary @Override public JsonValue toJson() { - return toJson("BackReference").append(Json.prop("groupNumbers", Arrays.stream(groupNumbers).mapToObj(x -> Json.val(x)))); + return toJson("BackReference").append(Json.prop("groupNumbers", Arrays.stream(groupNumbers).mapToObj(Json::val))); } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/CalcASTFlagsVisitor.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/CalcASTFlagsVisitor.java new file mode 100644 index 000000000000..4285ec01c5d0 --- /dev/null +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/CalcASTFlagsVisitor.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2024, 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 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.regex.tregex.parser.ast; + +import static com.oracle.truffle.regex.tregex.parser.ast.CalcASTPropsVisitor.OR_FLAGS; +import static com.oracle.truffle.regex.tregex.parser.ast.CalcASTPropsVisitor.setFlagsLookAroundAssertion; + +import com.oracle.truffle.regex.tregex.parser.ast.visitors.DepthFirstTraversalRegexASTVisitor; + +/** + * Reduced version of {@link CalcASTPropsVisitor}. This version calculates + * {@link CalcASTPropsVisitor#OR_FLAGS} and {@link RegexASTNode#mayMatchEmptyString()} only. + */ +public class CalcASTFlagsVisitor extends DepthFirstTraversalRegexASTVisitor { + + private static final int OR_FLAGS_GROUP = OR_FLAGS | RegexASTNode.FLAG_MAY_MATCH_EMPTY_STRING; + private final RegexAST ast; + + public CalcASTFlagsVisitor(RegexAST ast) { + this.ast = ast; + } + + public static void run(RegexAST ast) { + CalcASTFlagsVisitor visitor = new CalcASTFlagsVisitor(ast); + visitor.run(ast.getRoot()); + } + + @Override + protected void visit(BackReference backReference) { + backReference.setHasBackReferences(); + backReference.getParent().setHasBackReferences(); + backReference.setMayMatchEmptyString(true); + } + + @Override + protected void visit(Sequence sequence) { + sequence.setMayMatchEmptyString(true); + } + + @Override + protected void leave(Group group) { + int flags = 0; + for (Sequence s : group.getAlternatives()) { + flags |= s.getFlags(OR_FLAGS_GROUP); + } + if (group.isLoop()) { + flags |= RegexASTNode.FLAG_HAS_LOOPS; + } + if (group.isCapturing()) { + flags |= RegexASTNode.FLAG_HAS_CAPTURE_GROUPS; + } + group.setFlags(flags, OR_FLAGS_GROUP); + if (group.getParent() != null) { + if (!group.mayMatchEmptyString() && !group.hasMin0Quantifier()) { + group.getParent().setMayMatchEmptyString(false); + } + group.getParent().setFlags(group.getParent().getFlags(OR_FLAGS) | (flags & ~RegexASTNode.FLAG_MAY_MATCH_EMPTY_STRING), OR_FLAGS); + } + } + + @Override + protected void visit(CharacterClass characterClass) { + if (!characterClass.hasMin0Quantifier()) { + characterClass.getParent().setMayMatchEmptyString(false); + } + } + + @Override + protected void visit(PositionAssertion assertion) { + switch (assertion.type) { + case CARET -> assertion.getParent().setHasCaret(); + case DOLLAR -> assertion.getParent().setHasDollar(); + case MATCH_BEGIN, MATCH_END -> ast.getProperties().setMatchBoundaryAssertions(); + } + } + + @Override + protected void visit(LookBehindAssertion assertion) { + assertion.setHasLookBehinds(); + assertion.getParent().setHasLookBehinds(); + } + + @Override + protected void leave(LookBehindAssertion assertion) { + setFlagsLookAroundAssertion(assertion); + } + + @Override + protected void visit(LookAheadAssertion assertion) { + assertion.setHasLookAheads(); + assertion.getParent().setHasLookAheads(); + } + + @Override + protected void leave(LookAheadAssertion assertion) { + setFlagsLookAroundAssertion(assertion); + } + + @Override + protected void leave(AtomicGroup atomicGroup) { + atomicGroup.setHasAtomicGroups(); + atomicGroup.getParent().setHasAtomicGroups(); + CalcASTPropsVisitor.setFlagsSubtreeRootNode(atomicGroup, OR_FLAGS); + if (!atomicGroup.mayMatchEmptyString()) { + atomicGroup.getParent().setMayMatchEmptyString(false); + } + } +} diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/CalcASTPropsVisitor.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/CalcASTPropsVisitor.java index 0987f51c4b2d..3107f104977a 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/CalcASTPropsVisitor.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/CalcASTPropsVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -41,6 +41,7 @@ package com.oracle.truffle.regex.tregex.parser.ast; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.graalvm.collections.EconomicMap; @@ -48,10 +49,13 @@ import org.graalvm.collections.Equivalence; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.regex.RegexSyntaxException; import com.oracle.truffle.regex.charset.CodePointSet; import com.oracle.truffle.regex.charset.Constants; +import com.oracle.truffle.regex.errors.PyErrorMessages; import com.oracle.truffle.regex.tregex.buffer.CompilationBuffer; import com.oracle.truffle.regex.tregex.parser.ast.visitors.DepthFirstTraversalRegexASTVisitor; +import com.oracle.truffle.regex.tregex.parser.flavors.PythonFlavor; import com.oracle.truffle.regex.tregex.string.Encodings; /** @@ -124,31 +128,38 @@ public class CalcASTPropsVisitor extends DepthFirstTraversalRegexASTVisitor { * When processing a {@link Group}, these flags will be set in the group iff they are set in * all of its alternatives. */ - private static final int AND_FLAGS = RegexASTNode.FLAG_STARTS_WITH_CARET | RegexASTNode.FLAG_ENDS_WITH_DOLLAR | RegexASTNode.FLAG_DEAD; + static final int AND_FLAGS = RegexASTNode.FLAG_STARTS_WITH_CARET | RegexASTNode.FLAG_ENDS_WITH_DOLLAR | RegexASTNode.FLAG_DEAD; /** * When processing a {@link Group}, these flags will be set in the group iff they are set in * any of its alternatives. */ - private static final int OR_FLAGS = RegexASTNode.FLAG_HAS_CARET | + static final int OR_FLAGS = RegexASTNode.FLAG_HAS_CARET | RegexASTNode.FLAG_HAS_DOLLAR | + RegexASTNode.FLAG_HAS_ATOMIC_GROUPS | RegexASTNode.FLAG_HAS_LOOPS | RegexASTNode.FLAG_HAS_QUANTIFIERS | RegexASTNode.FLAG_HAS_CAPTURE_GROUPS | RegexASTNode.FLAG_HAS_LOOK_AHEADS | RegexASTNode.FLAG_HAS_LOOK_BEHINDS | RegexASTNode.FLAG_HAS_BACK_REFERENCES; - private static final int CHANGED_FLAGS = AND_FLAGS | OR_FLAGS; + static final int CHANGED_FLAGS = AND_FLAGS | OR_FLAGS; private final RegexAST ast; + private final int[] captureGroupsMinWidth; + private final int[] captureGroupsMaxWidth; private final CompilationBuffer compilationBuffer; private final EconomicMap> conditionalBackReferences; private final EconomicMap> conditionGroups; public CalcASTPropsVisitor(RegexAST ast, CompilationBuffer compilationBuffer) { this.ast = ast; + this.captureGroupsMinWidth = new int[ast.getNumberOfCaptureGroups()]; + this.captureGroupsMaxWidth = new int[ast.getNumberOfCaptureGroups()]; this.compilationBuffer = compilationBuffer; this.conditionalBackReferences = EconomicMap.create(ast.getConditionGroups().numberOfSetBits()); this.conditionGroups = EconomicMap.create(ast.getConditionGroups().numberOfSetBits()); + Arrays.fill(captureGroupsMinWidth, -1); + Arrays.fill(captureGroupsMaxWidth, -1); } public static void run(RegexAST ast, CompilationBuffer compilationBuffer) { @@ -173,8 +184,46 @@ protected void visit(BackReference backReference) { } backReference.setHasBackReferences(); backReference.getParent().setHasBackReferences(); - if (backReference.hasQuantifier()) { - // TODO: maybe check if the referenced group can produce a zero-width match + + int minWidth = 0; + if (ast.getFlavor().backreferencesToUnmatchedGroupsFail()) { + /* + * Calculate the back-reference's min and max path by checking the referenced group's + * min and max width. This is useful only if + * ast.getFlavor().backreferencesToUnmatchedGroupsFail(), because otherwise + * back-references to groups that have not been matched yet will always match the empty + * string, and we would have to calculate the expression's dominator tree to check + * whether it is possible to reach the back-reference without matching the referenced + * group. + */ + minWidth = Integer.MAX_VALUE; + int maxWidth = 0; + boolean isDead = true; + for (int groupNumber : backReference.getGroupNumbers()) { + if (ast.getGroup(groupNumber).stream().allMatch(RegexASTNode::isDead)) { + continue; + } else { + isDead = false; + } + if (captureGroupsMinWidth[groupNumber] < 0) { + assert isReverse(); + minWidth = 0; + maxWidth = 0; + break; + } + minWidth = Math.min(minWidth, captureGroupsMinWidth[groupNumber]); + maxWidth = Math.max(minWidth, captureGroupsMaxWidth[groupNumber]); + } + if (isDead) { + backReference.markAsDead(); + backReference.getParent().markAsDead(); + return; + } + backReference.getParent().incMinPath(minWidth); + backReference.getParent().incMaxPath(maxWidth); + } + backReference.setMayMatchEmptyString(minWidth == 0); + if (minWidth == 0 && backReference.hasQuantifier()) { setZeroWidthQuantifierIndex(backReference); } if (backReference.hasNotUnrolledQuantifier()) { @@ -187,6 +236,7 @@ protected void visit(BackReference backReference) { @Override protected void visit(Group group) { + clearORFlags(group); if (group.getParent().isSequence() || group.getParent().isAtomicGroup()) { group.setMinPath(group.getParent().getMinPath()); group.setMaxPath(group.getParent().getMaxPath()); @@ -195,6 +245,9 @@ protected void visit(Group group) { group.setMinPath(0); group.setMaxPath(0); } + if (isForward() && group.hasQuantifier()) { + group.setEnclosedZeroWidthGroupsLo(ast.getGroupsWithGuards().size()); + } } @Override @@ -225,12 +278,6 @@ protected void leave(Group group) { conditionalBackReferences.get(referencedGroupNumber).add(group); } } - if (group.isDead()) { - if (group.getParent() != null) { - group.getParent().markAsDead(); - } - return; - } int minPath = Integer.MAX_VALUE; int maxPath = 0; int prefixLengthMin = 0; @@ -240,6 +287,12 @@ protected void leave(Group group) { if (s.isDead()) { continue; } + if (s.isQuantifierPassThroughSequence()) { + QuantifiableTerm term = s.quantifierPassThroughGetQuantifiedTerm(); + if (!term.isExpandedQuantifier() && !term.isOptionalQuantifier() && term.getQuantifier().getMin() > 0) { + continue; + } + } flags = (flags & (s.getFlags(AND_FLAGS) | ~AND_FLAGS)) | s.getFlags(OR_FLAGS); minPath = Math.min(minPath, s.getMinPath()); maxPath = Math.max(maxPath, s.getMaxPath()); @@ -248,11 +301,35 @@ protected void leave(Group group) { prefixLengthMax = Math.max(prefixLengthMax, s.getPrefixLengthMax()); } } + if ((flags & RegexASTNode.FLAG_DEAD) != 0) { + group.markAsDead(); + if (group.getParent() != null) { + group.getParent().markAsDead(); + } + return; + } + if (group.isCapturing()) { + captureGroupsMinWidth[group.getGroupNumber()] = minPath - group.getMinPath(); + captureGroupsMaxWidth[group.getGroupNumber()] = maxPath - group.getMaxPath(); + flags |= RegexASTNode.FLAG_HAS_CAPTURE_GROUPS; + if (group.getMinPath() == minPath && group.getMaxPath() == maxPath) { + ast.getProperties().setEmptyCaptureGroups(); + } + } if (group.hasQuantifier()) { + /* + * If a quantifier can produce a zero-width match, we have to check this in + * back-tracking mode. In flavors more complex than JS (where empty loop iterations can + * be admitted), we have to check this at all times. In JS, we can afford to only do + * this check when the expression contains back-references or lookarounds. + */ + if (minPath - group.getMinPath() == 0 || ast.getOptions().getFlavor().emptyChecksMonitorCaptureGroups()) { + setZeroWidthQuantifierIndex(group); + } if (!group.isExpandedQuantifier()) { flags |= RegexASTNode.FLAG_HAS_QUANTIFIERS; setQuantifierIndex(group); - if (group.getQuantifier().getMin() == 0) { + if (group.getQuantifier().getMin() == 0 || group.isOptionalQuantifier()) { flags &= ~(RegexASTNode.FLAG_STARTS_WITH_CARET | RegexASTNode.FLAG_ENDS_WITH_DOLLAR); } /* @@ -261,28 +338,16 @@ protected void leave(Group group) { * summed up with min and max path of the group, so sequence.minPath - group.minPath * is the sequence's "own" minPath */ - minPath = group.getMinPath() + ((minPath - group.getMinPath()) * group.getQuantifier().getMin()); + minPath = group.getMinPath() + ((minPath - group.getMinPath()) * (group.isOptionalQuantifier() ? 0 : group.getQuantifier().getMin())); if (group.getQuantifier().isInfiniteLoop()) { flags |= RegexASTNode.FLAG_HAS_LOOPS; + // Just increase maxPath by one loop iteration; It's enough to determine + // whether a given sub-expression is fixed-width. + maxPath = group.getMaxPath() + ((maxPath - group.getMaxPath()) * (group.getQuantifier().getMin() + 1)); } else { maxPath = group.getMaxPath() + ((maxPath - group.getMaxPath()) * group.getQuantifier().getMax()); } } - /* - * If a quantifier can produce a zero-width match, we have to check this in - * back-tracking mode. In flavors more complex than JS (where empty loop iterations can - * be admitted), we have to check this at all times. In JS, we can afford to only do - * this check when the expression contains back-references or lookarounds. - */ - if (minPath - group.getMinPath() == 0) { - setZeroWidthQuantifierIndex(group); - } - } - if (group.isCapturing()) { - flags |= RegexASTNode.FLAG_HAS_CAPTURE_GROUPS; - if (group.getMinPath() == minPath && group.getMaxPath() == maxPath) { - ast.getProperties().setEmptyCaptureGroups(); - } } group.setFlags(flags, CHANGED_FLAGS); group.setMinPath(minPath); @@ -304,13 +369,17 @@ protected void leave(Group group) { group.getParent().setPrefixLengthMax(prefixLengthMax); } } - if (isForward() && (group.hasEmptyGuard() || group.isLoop())) { + if (isForward() && group.hasQuantifier()) { + group.setEnclosedZeroWidthGroupsHi(ast.getGroupsWithGuards().size()); + } + if (isForward() && (group.hasEmptyGuard() || group.isLoop() || group.hasQuantifier())) { ast.registerGroupWithGuards(group); } } @Override protected void visit(Sequence sequence) { + clearORFlags(sequence); sequence.setMinPath(sequence.getParent().getMinPath()); sequence.setMaxPath(sequence.getParent().getMaxPath()); } @@ -372,10 +441,6 @@ protected void visit(PositionAssertion assertion) { } } break; - case MATCH_BEGIN: - case MATCH_END: - ast.getProperties().setMatchBoundaryAssertions(); - break; } assertion.setMinPath(assertion.getParent().getMinPath()); assertion.setMaxPath(assertion.getParent().getMaxPath()); @@ -383,6 +448,8 @@ protected void visit(PositionAssertion assertion) { @Override protected void visit(LookBehindAssertion assertion) { + clearORFlags(assertion); + assertion.setHasLookBehinds(); assertion.getParent().setHasLookBehinds(); assertion.setMinPath(assertion.getParent().getMinPath()); assertion.setMaxPath(assertion.getParent().getMaxPath()); @@ -416,10 +483,15 @@ protected void leave(LookBehindAssertion assertion) { } } leaveLookAroundAssertion(assertion); + if (isForward() && !assertion.isDead() && ast.getFlavor() == PythonFlavor.INSTANCE && !assertion.isFixedWidth()) { + throw RegexSyntaxException.createPattern(ast.getSource(), PyErrorMessages.LOOK_BEHIND_REQUIRES_FIXED_WIDTH_PATTERN, 0, RegexSyntaxException.ErrorCode.InvalidLookbehind); + } } @Override protected void visit(LookAheadAssertion assertion) { + clearORFlags(assertion); + assertion.setHasLookAheads(); assertion.getParent().setHasLookAheads(); assertion.setMinPath(assertion.getParent().getMinPath()); assertion.setMaxPath(assertion.getParent().getMaxPath()); @@ -439,6 +511,7 @@ protected void leave(LookAheadAssertion assertion) { @Override protected void visit(AtomicGroup atomicGroup) { + clearORFlags(atomicGroup); atomicGroup.setMinPath(atomicGroup.getParent().getMinPath()); atomicGroup.setMaxPath(atomicGroup.getParent().getMaxPath()); } @@ -446,9 +519,10 @@ protected void visit(AtomicGroup atomicGroup) { @Override protected void leave(AtomicGroup atomicGroup) { if (isForward() && !atomicGroup.isDead()) { - ast.getProperties().setAtomicGroups(); + atomicGroup.setHasAtomicGroups(); + atomicGroup.getParent().setHasAtomicGroups(); } - leaveSubtreeRootNode(atomicGroup, CHANGED_FLAGS); + setFlagsSubtreeRootNode(atomicGroup, CHANGED_FLAGS); atomicGroup.getParent().setMinPath(atomicGroup.getMinPath()); atomicGroup.getParent().setMaxPath(atomicGroup.getMaxPath()); } @@ -456,15 +530,47 @@ protected void leave(AtomicGroup atomicGroup) { private void leaveLookAroundAssertion(LookAroundAssertion assertion) { if (assertion.hasCaptureGroups()) { ast.getProperties().setCaptureGroupsInLookAroundAssertions(); + if (!ast.getOptions().getFlavor().nestedCaptureGroupsKeptOnLoopReentry() && !assertion.isNegated()) { + RegexASTNode parent = assertion.getParent(); + boolean innerGroupMayBeSkipped = assertion.getGroup().size() > 1; + while (parent != null) { + if (parent.isGroup()) { + Group parentGroup = parent.asGroup(); + innerGroupMayBeSkipped |= parentGroup.size() > 1; + if (innerGroupMayBeSkipped && parentGroup.hasQuantifier() && parentGroup.getQuantifier().isMaxGreaterThan(1)) { + /* + * This is a corner case we currently don't support in DFA mode: In + * ECMAScript, nested capture groups are cleared on every loop + * iteration, but merged look-around assertions may "spill" from one + * loop iteration into the next, which would require keeping track of + * loop enter/exit bounds across NFA states. This is probably doable, + * but not worth the effort, since these kinds of expressions are very + * rare. + * + * Example: matching regex a(?:c|b(?=(c)))* against "abc". The inner + * capture group would be set on the first iteration and cleared again + * on the second, but right now we don't keep track of that and set the + * capture group bounds simultaneously with matching 'c'. + */ + ast.getProperties().setLookAroundWithCaptureGroupsNestedInQuantifier(); + } + } + parent = parent.getParent(); + } + } } + setFlagsLookAroundAssertion(assertion); + } + + static void setFlagsLookAroundAssertion(LookAroundAssertion assertion) { // flag propagation to parent sequences: // - LookAhead expressions propagate all flags // - LookBehind expressions omit "startsWithCaret" and "endsWithDollar" // - negated lookarounds additionally don't propagate the "dead" flag - leaveSubtreeRootNode(assertion, assertion.isNegated() ? OR_FLAGS : assertion.isLookBehindAssertion() ? OR_FLAGS | RegexASTNode.FLAG_DEAD : CHANGED_FLAGS); + setFlagsSubtreeRootNode(assertion, assertion.isNegated() ? OR_FLAGS : assertion.isLookBehindAssertion() ? OR_FLAGS | RegexASTNode.FLAG_DEAD : CHANGED_FLAGS); } - private static void leaveSubtreeRootNode(RegexASTSubtreeRootNode subtreeRootNode, int flagMask) { + static void setFlagsSubtreeRootNode(RegexASTSubtreeRootNode subtreeRootNode, int flagMask) { subtreeRootNode.getParent().setFlags(subtreeRootNode.getFlags(flagMask) | subtreeRootNode.getParent().getFlags(flagMask), flagMask); } @@ -487,7 +593,9 @@ protected void visit(CharacterClass characterClass) { if (characterClass.hasNotUnrolledQuantifier()) { characterClass.getParent().setHasQuantifiers(); setQuantifierIndex(characterClass); - characterClass.getParent().incMinPath(characterClass.getQuantifier().getMin()); + if (!characterClass.isOptionalQuantifier()) { + characterClass.getParent().incMinPath(characterClass.getQuantifier().getMin()); + } if (characterClass.getQuantifier().isInfiniteLoop()) { characterClass.setHasLoops(); characterClass.getParent().setHasLoops(); @@ -596,4 +704,12 @@ private void registerConditionGroupsInLookAheadAssertions() { } } } + + private void clearORFlags(RegexASTNode node) { + // unset flags set by previous invocations of CalcASTFlagsVisitor, to account for removed or + // dead nodes + if (isReverse()) { + node.clearFlags(OR_FLAGS); + } + } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/Group.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/Group.java index 980a8c9052b2..83e3ff670faa 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/Group.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/Group.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -77,8 +77,10 @@ public class Group extends QuantifiableTerm implements RegexASTVisitorIterable { private short visitorIterationIndex = 0; private short groupNumber = -1; private short groupsWithGuardsIndex = -1; - private short enclosedCaptureGroupsLow; - private short enclosedCaptureGroupsHigh; + private int enclosedCaptureGroupsLo; + private int enclosedCaptureGroupsHi; + private int enclosedZeroWidthGroupsLo; + private int enclosedZeroWidthGroupsHi; /** * Creates an empty non-capturing group. @@ -98,13 +100,17 @@ public class Group extends QuantifiableTerm implements RegexASTVisitorIterable { protected Group(Group copy) { super(copy); groupNumber = copy.groupNumber; - enclosedCaptureGroupsLow = copy.enclosedCaptureGroupsLow; - enclosedCaptureGroupsHigh = copy.enclosedCaptureGroupsHigh; + enclosedCaptureGroupsLo = copy.enclosedCaptureGroupsLo; + enclosedCaptureGroupsHi = copy.enclosedCaptureGroupsHi; } @Override public Group copy(RegexAST ast) { - return ast.register(new Group(this)); + Group copy = new Group(this); + if (isCapturing()) { + ast.registerCaptureGroupCopy(copy); + } + return ast.register(copy); } @Override @@ -206,8 +212,6 @@ public boolean isCapturing() { /** * Marks this {@link Group} as capturing and sets its group number. - * - * @param groupNumber */ public void setGroupNumber(int groupNumber) { assert groupNumber <= TRegexOptions.TRegexMaxNumberOfCaptureGroups; @@ -237,51 +241,67 @@ public void clearGroupNumber() { /** * Gets the (inclusive) lower bound of the range of capture groups contained within this group. */ - public int getEnclosedCaptureGroupsLow() { - return enclosedCaptureGroupsLow; + public int getEnclosedCaptureGroupsLo() { + return enclosedCaptureGroupsLo; } /** * Gets the (inclusive) lower bound of the range of capture groups in this term. In contrast to - * {@link #getEnclosedCaptureGroupsLow()}, this range contains the group itself if it is a + * {@link #getEnclosedCaptureGroupsLo()}, this range contains the group itself if it is a * capturing group. */ - public int getCaptureGroupsLow() { - return isCapturing() ? getGroupNumber() : enclosedCaptureGroupsLow; + public int getCaptureGroupsLo() { + return isCapturing() ? getGroupNumber() : enclosedCaptureGroupsLo; } /** * Sets the (inclusive) lower bound of the range of capture groups contained within this group. */ - public void setEnclosedCaptureGroupsLow(int enclosedCaptureGroupsLow) { - assert enclosedCaptureGroupsLow <= TRegexOptions.TRegexMaxNumberOfCaptureGroups; - this.enclosedCaptureGroupsLow = (short) enclosedCaptureGroupsLow; + public void setEnclosedCaptureGroupsLo(int enclosedCaptureGroupsLo) { + assert enclosedCaptureGroupsLo <= TRegexOptions.TRegexMaxNumberOfCaptureGroups; + this.enclosedCaptureGroupsLo = (short) enclosedCaptureGroupsLo; } /** * Gets the (exclusive) upper bound of the range of capture groups contained within this group. */ - public int getEnclosedCaptureGroupsHigh() { - return enclosedCaptureGroupsHigh; + public int getEnclosedCaptureGroupsHi() { + return enclosedCaptureGroupsHi; } /** * Gets the (exclusive) upper bound of the range of capture groups in this term. */ - public int getCaptureGroupsHigh() { - return enclosedCaptureGroupsHigh; + public int getCaptureGroupsHi() { + return enclosedCaptureGroupsHi; } /** * Sets the (exclusive) upper bound of the range of capture groups contained within this group. */ - public void setEnclosedCaptureGroupsHigh(int enclosedCaptureGroupsHigh) { - assert enclosedCaptureGroupsHigh <= TRegexOptions.TRegexMaxNumberOfCaptureGroups; - this.enclosedCaptureGroupsHigh = (short) enclosedCaptureGroupsHigh; + public void setEnclosedCaptureGroupsHi(int enclosedCaptureGroupsHi) { + assert enclosedCaptureGroupsHi <= TRegexOptions.TRegexMaxNumberOfCaptureGroups; + this.enclosedCaptureGroupsHi = (short) enclosedCaptureGroupsHi; } public boolean hasEnclosedCaptureGroups() { - return enclosedCaptureGroupsHigh > enclosedCaptureGroupsLow; + return enclosedCaptureGroupsHi > enclosedCaptureGroupsLo; + } + + public int getEnclosedZeroWidthGroupsLo() { + return enclosedZeroWidthGroupsLo; + } + + public void setEnclosedZeroWidthGroupsLo(int enclosedZeroWidthGroupsLo) { + this.enclosedZeroWidthGroupsLo = enclosedZeroWidthGroupsLo; + } + + public int getEnclosedZeroWidthGroupsHi() { + return enclosedZeroWidthGroupsHi; + } + + public void setEnclosedZeroWidthGroupsHi(int enclosedZeroWidthGroupsHi) { + this.enclosedZeroWidthGroupsHi = enclosedZeroWidthGroupsHi; } /** @@ -336,8 +356,6 @@ public boolean isEmpty() { /** * Adds a new alternative to this group. The new alternative will be appended to the * end, meaning it will have the lowest priority among all the alternatives. - * - * @param sequence */ public void add(Sequence sequence) { sequence.setParent(this); @@ -349,8 +367,6 @@ public void add(Sequence sequence) { * Inserts a new alternative to this group. The new alternative will be inserted at the * beginning, meaning it will have the highest priority among all the * alternatives. - * - * @param sequence */ public void insertFirst(Sequence sequence) { sequence.setParent(this); diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/GroupBoundaries.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/GroupBoundaries.java index 33b22a9f20e4..68d56c4b7f6f 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/GroupBoundaries.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/GroupBoundaries.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -76,6 +76,7 @@ public class GroupBoundaries implements JsonConvertible { private final TBitSet updateIndices; private final TBitSet clearIndices; + private final int firstGroup; private final int lastGroup; private final int cachedHash; @CompilationFinal(dimensions = 1) private byte[] updateArrayByte; @@ -83,19 +84,20 @@ public class GroupBoundaries implements JsonConvertible { @CompilationFinal(dimensions = 1) private short[] updateArray; @CompilationFinal(dimensions = 1) private short[] clearArray; - GroupBoundaries(TBitSet updateIndices, TBitSet clearIndices, int lastGroup) { + GroupBoundaries(TBitSet updateIndices, TBitSet clearIndices, int firstGroup, int lastGroup) { this.updateIndices = updateIndices; this.clearIndices = clearIndices; + this.firstGroup = firstGroup; this.lastGroup = lastGroup; // both bit sets are immutable, and the hash is always needed immediately in // RegexAST#createGroupBoundaries() - this.cachedHash = (Objects.hashCode(updateIndices) * 31 + Objects.hashCode(clearIndices)) * 31 + lastGroup; + this.cachedHash = (Objects.hashCode(updateIndices) * 31 + Objects.hashCode(clearIndices)) * 31 + firstGroup * 31 + lastGroup; } public static GroupBoundaries[] createCachedGroupBoundaries() { GroupBoundaries[] instances = new GroupBoundaries[TBitSet.getNumberOfStaticInstances()]; for (int i = 0; i < instances.length; i++) { - instances[i] = new GroupBoundaries(TBitSet.getStaticInstance(i), TBitSet.getEmptyInstance(), -1); + instances[i] = new GroupBoundaries(TBitSet.getStaticInstance(i), TBitSet.getEmptyInstance(), -1, -1); } return instances; } @@ -212,6 +214,10 @@ public void updateBitSets(TBitSet foreignUpdateIndices, TBitSet foreignClearIndi foreignClearIndices.union(clearIndices); } + public int getFirstGroup() { + return firstGroup; + } + public int getLastGroup() { return lastGroup; } @@ -225,7 +231,7 @@ public boolean equals(Object obj) { return false; } GroupBoundaries o = (GroupBoundaries) obj; - return Objects.equals(updateIndices, o.updateIndices) && Objects.equals(clearIndices, o.clearIndices) && lastGroup == o.lastGroup; + return Objects.equals(updateIndices, o.updateIndices) && Objects.equals(clearIndices, o.clearIndices) && firstGroup == o.firstGroup && lastGroup == o.lastGroup; } @Override @@ -352,7 +358,7 @@ public JsonArray indexUpdateSourceSectionsToJson(RegexAST ast) { if (!hasIndexUpdates() || !ast.getOptions().isDumpAutomataWithSourceSections()) { return Json.array(); } - return RegexAST.sourceSectionsToJson(getUpdateIndices().stream().mapToObj(x -> ast.getSourceSections(ast.getGroupByBoundaryIndex(x)).get(x & 1))); + return RegexAST.sourceSectionsToJson(getUpdateIndices().stream().mapToObj(x -> ast.getSourceSections(ast.getGroupByBoundaryIndex(x).get(0)).get(x & 1))); } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/QuantifiableTerm.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/QuantifiableTerm.java index 1daabdfe55a1..b6d1cc932da0 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/QuantifiableTerm.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/QuantifiableTerm.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -69,6 +69,10 @@ public boolean hasQuantifier() { return quantifier != null; } + public boolean hasMin0Quantifier() { + return hasQuantifier() && quantifier.getMin() == 0; + } + /** * Returns {@code true} iff this term has a quantifier that was not unrolled by the parser. */ diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/RegexAST.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/RegexAST.java index caca3b2f4b7b..2d29aadfea21 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/RegexAST.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/RegexAST.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -46,6 +46,7 @@ import java.util.StringJoiner; import java.util.stream.Stream; +import com.oracle.truffle.regex.tregex.parser.flavors.RegexFlavor; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.Equivalence; @@ -98,7 +99,7 @@ public final class RegexAST implements StateIndex, JsonConvertible * Possibly wrapped root for NFA generation (see {@link #createPrefix()}). */ private Group wrappedRoot; - private final List captureGroups = new ArrayList<>(); + private final ArrayList> captureGroups = new ArrayList<>(); private final List quantifiers = new ArrayList<>(); private final List zeroWidthQuantifiables = new ArrayList<>(); private final GlobalSubTreeIndex subtrees = new GlobalSubTreeIndex(); @@ -146,6 +147,10 @@ public RegexOptions getOptions() { return source.getOptions(); } + public RegexFlavor getFlavor() { + return source.getOptions().getFlavor(); + } + public Encoding getEncoding() { return source.getEncoding(); } @@ -198,6 +203,10 @@ public Token.Quantifier[] getQuantifierArray() { return quantifiers.toArray(Token.Quantifier[]::new); } + public Token.Quantifier getQuantifier(int quantifierIndex) { + return quantifiers.get(quantifierIndex); + } + public void registerZeroWidthQuantifiable(QuantifiableTerm zeroWidthQuantifiable) { zeroWidthQuantifiable.getQuantifier().setZeroWidthIndex(zeroWidthQuantifiables.size()); zeroWidthQuantifiables.add(zeroWidthQuantifiable); @@ -207,11 +216,15 @@ public List getZeroWidthQuantifiables() { return zeroWidthQuantifiables; } - public Group getGroup(int index) { - return captureGroups.get(index); + /** + * Get capture group with given capture group number. May return multiple nodes due to + * quantifier unrolling. + */ + public ArrayList getGroup(int groupNumber) { + return captureGroups.get(groupNumber); } - public Group getGroupByBoundaryIndex(int index) { + public ArrayList getGroupByBoundaryIndex(int index) { return captureGroups.get(index / 2); } @@ -274,9 +287,8 @@ public GlobalSubTreeIndex getSubtrees() { } public void registerGroupWithGuards(Group group) { - if (group.getGroupsWithGuardsIndex() < 0) { - groupsWithGuards.add(group); - } + assert group.getGroupsWithGuardsIndex() < 0; + groupsWithGuards.add(group); } public GroupsWithGuardsIndex getGroupsWithGuards() { @@ -340,10 +352,21 @@ public Group createGroup() { public Group createCaptureGroup(int groupNumber) { Group group = register(new Group(groupNumber)); assert captureGroups.size() == groupNumber; - captureGroups.add(group); + ArrayList groupList = new ArrayList<>(); + groupList.add(group); + captureGroups.add(groupList); return group; } + public void registerCaptureGroupCopy(Group groupCopy) { + assert !captureGroups.get(groupCopy.getGroupNumber()).contains(groupCopy); + captureGroups.get(groupCopy.getGroupNumber()).add(groupCopy); + } + + public void clearRegisteredCaptureGroups(int groupNumber) { + captureGroups.get(groupNumber).clear(); + } + public Group createConditionalBackReferenceGroup(int referencedGroupNumber) { referencedGroups.set(referencedGroupNumber); conditionGroups.set(referencedGroupNumber); @@ -373,7 +396,7 @@ public AtomicGroup createAtomicGroup() { } public void createNFAHelperNodes(RegexASTSubtreeRootNode rootNode) { - nodeCount.inc(4); + nodeCount.inc(5); PositionAssertion anchored = new PositionAssertion(PositionAssertion.Type.CARET); rootNode.setAnchoredInitialState(anchored); MatchFound unAnchored = new MatchFound(); @@ -382,6 +405,8 @@ public void createNFAHelperNodes(RegexASTSubtreeRootNode rootNode) { rootNode.setMatchFound(end); PositionAssertion anchoredEnd = new PositionAssertion(PositionAssertion.Type.DOLLAR); rootNode.setAnchoredFinalState(anchoredEnd); + MatchFound endChecked = new MatchFound(); + rootNode.setMatchFoundChecked(endChecked); } public PositionAssertion createPositionAssertion(PositionAssertion.Type type) { @@ -558,18 +583,18 @@ public void unhidePrefix() { } } - public GroupBoundaries createGroupBoundaries(TBitSet updateIndices, TBitSet clearIndices, int lastGroup) { + public GroupBoundaries createGroupBoundaries(TBitSet updateIndices, TBitSet clearIndices, int firstGroup, int lastGroup) { if (!getOptions().getFlavor().usesLastGroupResultField()) { GroupBoundaries staticInstance = GroupBoundaries.getStaticInstance(language, updateIndices, clearIndices); if (staticInstance != null) { return staticInstance; } } - GroupBoundaries lookup = new GroupBoundaries(updateIndices, clearIndices, lastGroup); + GroupBoundaries lookup = new GroupBoundaries(updateIndices, clearIndices, firstGroup, lastGroup); if (groupBoundariesDeduplicationMap.containsKey(lookup)) { return groupBoundariesDeduplicationMap.get(lookup); } else { - GroupBoundaries gb = new GroupBoundaries(updateIndices.copy(), clearIndices.copy(), lastGroup); + GroupBoundaries gb = new GroupBoundaries(updateIndices.copy(), clearIndices.copy(), firstGroup, lastGroup); groupBoundariesDeduplicationMap.put(gb, gb); return gb; } @@ -679,8 +704,9 @@ public boolean canTransformToDFA() { getProperties().hasNonLiteralLookBehindAssertions() || getProperties().hasNegativeLookBehindAssertions() || getRoot().hasQuantifiers() || - getProperties().hasAtomicGroups() || - getProperties().hasConditionalReferencesIntoLookAheads()) && + getRoot().hasAtomicGroups() || + getProperties().hasConditionalReferencesIntoLookAheads() || + getProperties().hasLookAroundWithCaptureGroupsNestedInQuantifier()) && couldCalculateLastGroup; } @@ -715,12 +741,15 @@ public String canTransformToDFAFailureReason() { if (getRoot().hasQuantifiers()) { sb.add("could not unroll all quantifiers"); } - if (getProperties().hasAtomicGroups()) { + if (getRoot().hasAtomicGroups()) { sb.add("regex has atomic groups"); } if (getProperties().hasConditionalReferencesIntoLookAheads()) { sb.add("regex has conditional back-references into look-ahead assertions"); } + if (getProperties().hasLookAroundWithCaptureGroupsNestedInQuantifier()) { + sb.add("regex has look-around assertion with capture groups nested in a quantified group"); + } return sb.toString(); } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/RegexASTNode.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/RegexASTNode.java index 3e3347fb8f69..825407a67280 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/RegexASTNode.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/RegexASTNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -62,20 +62,23 @@ public abstract class RegexASTNode implements JsonConvertible { static final int FLAG_BACK_REFERENCE_IS_NESTED_OR_FORWARD = 1 << 8; static final int FLAG_BACK_REFERENCE_IS_IGNORE_CASE = 1 << 9; static final int FLAG_BACK_REFERENCE_IS_IGNORE_CASE_ALTERNATIVE_MODE = 1 << 10; - static final int FLAG_GROUP_LOOP = 1 << 11; - static final int FLAG_GROUP_EXPANDED_QUANTIFIER = 1 << 12; - static final int FLAG_GROUP_MANDATORY_UNROLLED_QUANTIFIER = 1 << 13; - static final int FLAG_GROUP_QUANTIFIER_PASS_THROUGH_SEQUENCE = 1 << 14; - static final int FLAG_GROUP_LOCAL_FLAGS = 1 << 15; - static final int FLAG_EMPTY_GUARD = 1 << 16; - static final int FLAG_LOOK_AROUND_NEGATED = 1 << 17; - static final int FLAG_HAS_LOOPS = 1 << 18; - static final int FLAG_HAS_CAPTURE_GROUPS = 1 << 19; - static final int FLAG_HAS_QUANTIFIERS = 1 << 20; - static final int FLAG_HAS_LOOK_BEHINDS = 1 << 21; - static final int FLAG_HAS_LOOK_AHEADS = 1 << 22; - static final int FLAG_HAS_BACK_REFERENCES = 1 << 23; - static final int FLAG_CHARACTER_CLASS_WAS_SINGLE_CHAR = 1 << 24; + static final int FLAG_MAY_MATCH_EMPTY_STRING = 1 << 11; + static final int FLAG_GROUP_LOOP = 1 << 12; + static final int FLAG_GROUP_EXPANDED_QUANTIFIER = 1 << 13; + static final int FLAG_GROUP_MANDATORY_QUANTIFIER = 1 << 14; + static final int FLAG_GROUP_OPTIONAL_QUANTIFIER = 1 << 15; + static final int FLAG_GROUP_QUANTIFIER_PASS_THROUGH_SEQUENCE = 1 << 16; + static final int FLAG_GROUP_LOCAL_FLAGS = 1 << 17; + static final int FLAG_EMPTY_GUARD = 1 << 18; + static final int FLAG_LOOK_AROUND_NEGATED = 1 << 19; + static final int FLAG_HAS_ATOMIC_GROUPS = 1 << 20; + static final int FLAG_HAS_LOOPS = 1 << 21; + static final int FLAG_HAS_CAPTURE_GROUPS = 1 << 22; + static final int FLAG_HAS_QUANTIFIERS = 1 << 23; + static final int FLAG_HAS_LOOK_BEHINDS = 1 << 24; + static final int FLAG_HAS_LOOK_AHEADS = 1 << 25; + static final int FLAG_HAS_BACK_REFERENCES = 1 << 26; + static final int FLAG_CHARACTER_CLASS_WAS_SINGLE_CHAR = 1 << 27; private int id = -1; private RegexASTNode parent; @@ -150,6 +153,10 @@ protected boolean isFlagSet(int flag) { return (flags & flag) != 0; } + protected boolean areAllFlagsSet(int multipleFlags) { + return (flags & multipleFlags) == multipleFlags; + } + protected void setFlag(int flag) { setFlag(flag, true); } @@ -166,6 +173,13 @@ protected void setFlags(int newFlags, int mask) { flags = flags & ~mask | newFlags; } + /** + * Clear all flags denoted by {@code mask}. + */ + protected void clearFlags(int mask) { + flags = flags & ~mask; + } + protected void setFlag(int flag, boolean value) { if (value) { flags |= flag; @@ -224,6 +238,14 @@ public void setEmptyGuard(boolean emptyGuard) { setFlag(FLAG_EMPTY_GUARD, emptyGuard); } + public boolean mayMatchEmptyString() { + return isFlagSet(FLAG_MAY_MATCH_EMPTY_STRING); + } + + public void setMayMatchEmptyString(boolean value) { + setFlag(FLAG_MAY_MATCH_EMPTY_STRING, value); + } + /** * Subexpression contains {@link #isCaret() "^"}. */ @@ -284,6 +306,21 @@ public void setEndsWithDollar(boolean endsWithDollar) { setFlag(FLAG_ENDS_WITH_DOLLAR, endsWithDollar); } + /** + * Subexpression contains {@link AtomicGroup atomic groups}. + */ + public boolean hasAtomicGroups() { + return isFlagSet(FLAG_HAS_ATOMIC_GROUPS); + } + + public void setHasAtomicGroups() { + setHasAtomicGroups(true); + } + + public void setHasAtomicGroups(boolean hasAtomicGroups) { + setFlag(FLAG_HAS_ATOMIC_GROUPS, hasAtomicGroups); + } + /** * Subexpression contains {@link Group#isLoop() loops}. */ @@ -344,6 +381,10 @@ public void setHasLookBehinds() { setFlag(FLAG_HAS_LOOK_BEHINDS, true); } + public boolean hasLookArounds() { + return isFlagSet(FLAG_HAS_LOOK_AHEADS | FLAG_HAS_LOOK_BEHINDS); + } + /** * Subexpression contains {@link #isBackReference() back-references}. */ @@ -390,23 +431,48 @@ public void setExpandedQuantifier(boolean expandedQuantifier) { /** * Indicates whether this {@link RegexASTNode} represents a mandatory copy of a quantified term - * after unrolling. + * after unrolling or splitting. * * E.g., in the expansion of A{2,4}, which is AA(A(A|)|), the first two occurrences of A are * marked with this flag. */ - public boolean isMandatoryUnrolledQuantifier() { - return isFlagSet(FLAG_GROUP_MANDATORY_UNROLLED_QUANTIFIER); + public boolean isMandatoryQuantifier() { + return isFlagSet(FLAG_GROUP_MANDATORY_QUANTIFIER); + } + + /** + * Marks this {@link RegexASTNode} as being inserted into the AST as the mandatory part of + * unrolling or splitting a quantified term. + * + * @see #isMandatoryQuantifier() + */ + public void setMandatoryQuantifier(boolean mandatoryQuantifier) { + setFlag(FLAG_GROUP_MANDATORY_QUANTIFIER, mandatoryQuantifier); + } + + /** + * Indicates whether this {@link RegexASTNode} represents an optional copy of a quantified term + * after unrolling or splitting. + * + * E.g., in the expansion of A{2,4}, which is AA(A(A|)|), the groups (A(A|)|) are marked with + * this flag. + */ + public boolean isOptionalQuantifier() { + return isFlagSet(FLAG_GROUP_OPTIONAL_QUANTIFIER); } /** - * Marks this {@link RegexASTNode} as being inserted into the AST as part of unrolling the - * mandatory part of a quantified term. + * Marks this {@link RegexASTNode} as being inserted into the AST as the optional part of + * unrolling or splitting a quantified term. * - * @see #isMandatoryUnrolledQuantifier() + * @see #isOptionalQuantifier() */ - public void setMandatoryUnrolledQuantifier(boolean mandatoryUnrolledQuantifier) { - setFlag(FLAG_GROUP_MANDATORY_UNROLLED_QUANTIFIER, mandatoryUnrolledQuantifier); + public void setOptionalQuantifier(boolean optionalQuantifier) { + setFlag(FLAG_GROUP_OPTIONAL_QUANTIFIER, optionalQuantifier); + } + + public boolean isMandatoryUnrolledQuantifier() { + return areAllFlagsSet(FLAG_GROUP_MANDATORY_QUANTIFIER | FLAG_GROUP_EXPANDED_QUANTIFIER); } /** @@ -500,6 +566,10 @@ public boolean isInLookAheadAssertion() { return getSubTreeParent() instanceof LookAheadAssertion; } + public boolean isInLookAroundAssertion() { + return getSubTreeParent() instanceof LookAroundAssertion; + } + public String toStringWithID() { return String.format("%d (%s)", id, toString()); } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/RegexASTSubtreeRootNode.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/RegexASTSubtreeRootNode.java index cd9b96d754f5..961ff298c384 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/RegexASTSubtreeRootNode.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/RegexASTSubtreeRootNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -61,6 +61,7 @@ public abstract class RegexASTSubtreeRootNode extends Term implements RegexASTVi private MatchFound unAnchoredInitialState; private PositionAssertion anchoredFinalState; private MatchFound matchFound; + private MatchFound matchFoundChecked; private boolean visitorGroupVisited = false; private final SubTreeIndex subtrees = new SubTreeIndex(); @@ -84,6 +85,15 @@ public abstract class RegexASTSubtreeRootNode extends Term implements RegexASTVi setGroup(copy.group.copyRecursive(ast, compilationBuffer)); } + @Override + public void markAsDead() { + super.markAsDead(); + anchoredInitialState.markAsDead(); + unAnchoredInitialState.markAsDead(); + anchoredFinalState.markAsDead(); + matchFound.markAsDead(); + } + public boolean globalSubTreeIdInitialized() { return globalSubTreeId >= 0; } @@ -188,6 +198,10 @@ public void setAnchoredFinalState(PositionAssertion anchoredFinalState) { anchoredFinalState.setNext(group); } + public boolean isFixedWidth() { + return getGroup().getMinPath() == getGroup().getMaxPath(); + } + @Override public boolean visitorHasNext() { return !visitorGroupVisited; @@ -217,4 +231,13 @@ public String toString() { protected JsonObject toJson(String typeName) { return super.toJson(typeName).append(Json.prop("group", astNodeId(group))); } + + public void setMatchFoundChecked(MatchFound matchFoundChecked) { + this.matchFoundChecked = matchFoundChecked; + } + + public MatchFound getMatchFoundChecked() { + assert matchFoundChecked != null; + return matchFoundChecked; + } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/Sequence.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/Sequence.java index 792a1971a619..ed60a2fd2f22 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/Sequence.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/Sequence.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -200,13 +200,23 @@ public boolean isSingleCharClass() { return size() == 1 && isLiteral(); } + public QuantifiableTerm quantifierPassThroughGetQuantifiedTerm() { + assert isQuantifierPassThroughSequence(); + assert getParent().isGroup(); + assert getParent().size() == 2; + Sequence otherSeq = isFirstInGroup() ? getParent().getLastAlternative() : getParent().getFirstAlternative(); + Term quantifiedTerm = isInLookBehindAssertion() ? otherSeq.getLastTerm() : otherSeq.getFirstTerm(); + assert otherSeq.size() <= 2 || quantifiedTerm.isExpandedQuantifier(); + return quantifiedTerm.asQuantifiableTerm(); + } + public int getEnclosedCaptureGroupsLow() { int lo = Integer.MAX_VALUE; for (Term t : terms) { if (t instanceof Group) { Group g = (Group) t; - if (g.getEnclosedCaptureGroupsLow() != g.getEnclosedCaptureGroupsHigh()) { - lo = Math.min(lo, g.getEnclosedCaptureGroupsLow()); + if (g.getEnclosedCaptureGroupsLo() != g.getEnclosedCaptureGroupsHi()) { + lo = Math.min(lo, g.getEnclosedCaptureGroupsLo()); } if (g.isCapturing()) { lo = Math.min(lo, g.getGroupNumber()); @@ -221,8 +231,8 @@ public int getEnclosedCaptureGroupsHigh() { for (Term t : terms) { if (t instanceof Group) { Group g = (Group) t; - if (g.getEnclosedCaptureGroupsLow() != g.getEnclosedCaptureGroupsHigh()) { - hi = Math.max(hi, g.getEnclosedCaptureGroupsHigh()); + if (g.getEnclosedCaptureGroupsLo() != g.getEnclosedCaptureGroupsHi()) { + hi = Math.max(hi, g.getEnclosedCaptureGroupsHi()); } if (g.isCapturing()) { hi = Math.max(hi, g.getGroupNumber() + 1); diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/InitIDVisitor.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/InitIDVisitor.java index 561207db7ae9..ac17287783a4 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/InitIDVisitor.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/InitIDVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -121,6 +121,7 @@ protected void leave(Group group) { if (group.getParent() instanceof RegexASTSubtreeRootNode) { initID(group.getSubTreeParent().getAnchoredFinalState()); initID(group.getSubTreeParent().getMatchFound()); + initID(group.getSubTreeParent().getMatchFoundChecked()); } } diff --git a/sdk/src/org.graalvm.word/src/org/graalvm/word/impl/WordBoxFactory.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/MarkAsAliveVisitor.java similarity index 79% rename from sdk/src/org.graalvm.word/src/org/graalvm/word/impl/WordBoxFactory.java rename to regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/MarkAsAliveVisitor.java index 00a63f98884e..850750be66f0 100644 --- a/sdk/src/org.graalvm.word/src/org/graalvm/word/impl/WordBoxFactory.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/MarkAsAliveVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -38,21 +38,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package org.graalvm.word.impl; +package com.oracle.truffle.regex.tregex.parser.ast.visitors; -import org.graalvm.word.WordBase; +import com.oracle.truffle.regex.tregex.parser.ast.RegexASTNode; -/** - * Base class for a factory to create boxed {@link Word} instances. A concrete subclass must - * initialize {@link #boxFactory}. - */ -public abstract class WordBoxFactory { - - protected static WordBoxFactory boxFactory; +public class MarkAsAliveVisitor extends DepthFirstTraversalRegexASTVisitor { - protected abstract T boxImpl(long val); + public static void markAsAlive(RegexASTNode runRoot) { + new MarkAsAliveVisitor().run(runRoot); + } - public static T box(long val) { - return boxFactory.boxImpl(val); + @Override + protected void doVisit(RegexASTNode cur) { + cur.setDead(false); } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/MarkAsDeadVisitor.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/MarkAsDeadVisitor.java new file mode 100644 index 000000000000..3129c67f1d36 --- /dev/null +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/MarkAsDeadVisitor.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, 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 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.regex.tregex.parser.ast.visitors; + +import com.oracle.truffle.regex.tregex.parser.ast.RegexASTNode; + +public class MarkAsDeadVisitor extends DepthFirstTraversalRegexASTVisitor { + + public static void markAsDead(RegexASTNode runRoot) { + new MarkAsDeadVisitor().run(runRoot); + } + + @Override + protected void doVisit(RegexASTNode cur) { + cur.markAsDead(); + } +} diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/MarkLookBehindEntriesVisitor.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/MarkLookBehindEntriesVisitor.java index 76aaa8cce9a0..1f9b2a4cfe19 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/MarkLookBehindEntriesVisitor.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/MarkLookBehindEntriesVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -86,7 +86,7 @@ public MarkLookBehindEntriesVisitor(RegexAST ast) { public void run() { for (RegexASTSubtreeRootNode subtreeRootNode : ast.getSubtrees()) { - if (!subtreeRootNode.isLookBehindAssertion()) { + if (!subtreeRootNode.isLookBehindAssertion() || subtreeRootNode.isDead()) { continue; } LookBehindAssertion lb = subtreeRootNode.asLookBehindAssertion(); diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/NFATraversalRegexASTVisitor.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/NFATraversalRegexASTVisitor.java index 48b26bddbe8e..fad660277b91 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/NFATraversalRegexASTVisitor.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/NFATraversalRegexASTVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -40,13 +40,14 @@ */ package com.oracle.truffle.regex.tregex.parser.ast.visitors; +import static com.oracle.truffle.regex.tregex.util.MathUtil.saturatingInc; + import java.util.Arrays; import java.util.Set; import org.graalvm.collections.EconomicSet; import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.regex.tregex.automaton.StateSet; import com.oracle.truffle.regex.tregex.buffer.LongArrayBuffer; import com.oracle.truffle.regex.tregex.nfa.ASTStepVisitor; import com.oracle.truffle.regex.tregex.nfa.TransitionGuard; @@ -54,7 +55,6 @@ import com.oracle.truffle.regex.tregex.parser.ast.CharacterClass; import com.oracle.truffle.regex.tregex.parser.ast.Group; import com.oracle.truffle.regex.tregex.parser.ast.GroupBoundaries; -import com.oracle.truffle.regex.tregex.parser.ast.GroupsWithGuardsIndex; import com.oracle.truffle.regex.tregex.parser.ast.LookAheadAssertion; import com.oracle.truffle.regex.tregex.parser.ast.LookAroundAssertion; import com.oracle.truffle.regex.tregex.parser.ast.LookBehindAssertion; @@ -64,6 +64,7 @@ import com.oracle.truffle.regex.tregex.parser.ast.RegexASTNode; import com.oracle.truffle.regex.tregex.parser.ast.Sequence; import com.oracle.truffle.regex.tregex.parser.ast.Term; +import com.oracle.truffle.regex.tregex.parser.flavors.RegexFlavor; import com.oracle.truffle.regex.util.TBitSet; /** @@ -118,21 +119,6 @@ public abstract class NFATraversalRegexASTVisitor { * alternation we should visit when back-tracking to find the next successor. */ private final LongArrayBuffer curPath = new LongArrayBuffer(8); - /** - * insideLoops is the set of looping groups that we are currently inside of. We need to maintain - * this in order to detect infinite loops in the NFA traversal. If we enter a looping group, - * traverse it without encountering a CharacterClass node or a MatchFound node and arrive back - * at the same group, then we are bound to loop like this forever. Using insideLoops, we can - * detect this situation and proceed with the search using another alternative. For example, in - * the RegexAST {@code ((|[a])*|)*}, which corresponds to the regex {@code /(a*?)* /}, we can - * traverse the inner loop, {@code (|[a])*}, without hitting any CharacterClass node by choosing - * the first alternative and we will then arrive back at the outer loop. There, we detect an - * infinite loop, which causes us to backtrack and choose the second alternative in the inner - * loop, leading us to the CharacterClass node {@code [a]}.
    - * NB: For every looping group, this set tells us whether there is an {@code enter} node for it - * on the current path. - */ - private final StateSet insideLoops; /** * This set is needed to make sure that a quantified term cannot match the empty string, as is * specified in step 2a of RepeatMatcher from ECMAScript draft 2018, chapter 21.2.2.5.1. @@ -181,36 +167,54 @@ public abstract class NFATraversalRegexASTVisitor { private int caretsOnPath = 0; private int matchBeginAssertionsOnPath = 0; private int matchEndAssertionsOnPath = 0; - private final int[] lookAroundVisitiedCount; + private final int[] lookAroundVisitedCount; private final TBitSet captureGroupUpdates; private final TBitSet captureGroupClears; private final TBitSet referencedGroupBoundaries; + private int firstGroup = -1; private int lastGroup = -1; - private final TBitSet boundedQuantifiersLoop; - private final TBitSet boundedQuantifiersExited; - /** - * Quantifier guards are stored in an immutable linked list, which allows for cheap sharing of - * snapshots for the purposes of deduplication. + * Per-quantifier position of last quantified group exit or escape in the transition guards + * array. + */ + private final int[] bqLastCounterReset; + /** + * Per-quantifier position of last quantified group zero-width-enter guard in the transition + * guards array. + */ + private final int[] bqLastZeroWidthEnter; + /** + * Tracks whether a given quantifier has been exited "normally" on the current path. */ + private final TBitSet bqExited; + /** + * Tracks whether a given quantifier has been bypassed using either a group passthrough or a + * group escape on the current path. + */ + private final TBitSet bqBypassed; + private final TBitSet referencedCaptureGroupsTmp; + private final LongArrayBuffer transitionGuards = new LongArrayBuffer(8); + private final LongArrayBuffer transitionGuardsCanonicalized = new LongArrayBuffer(8); private long[] transitionGuardsResult = null; protected NFATraversalRegexASTVisitor(RegexAST ast) { this.ast = ast; - this.insideLoops = StateSet.create(ast.getGroupsWithGuards()); this.insideEmptyGuardGroup = new TBitSet(ast.getGroupsWithGuards().size()); this.lookAroundsOnPath = new TBitSet(ast.getSubtrees().size()); - this.lookAroundVisitiedCount = new int[ast.getSubtrees().size()]; + this.lookAroundVisitedCount = new int[ast.getSubtrees().size()]; this.captureGroupUpdates = new TBitSet(ast.getNumberOfCaptureGroups() * 2); this.captureGroupClears = new TBitSet(ast.getNumberOfCaptureGroups() * 2); this.referencedGroupBoundaries = new TBitSet(ast.getNumberOfCaptureGroups() * 2); - this.boundedQuantifiersLoop = new TBitSet(ast.getQuantifierCount()); - this.boundedQuantifiersExited = new TBitSet(ast.getQuantifierCount()); + this.bqLastCounterReset = new int[ast.getQuantifierCount()]; + this.bqLastZeroWidthEnter = new int[ast.getGroupsWithGuards().size()]; + this.bqExited = new TBitSet(ast.getGroupsWithGuards().size()); + this.bqBypassed = new TBitSet(ast.getGroupsWithGuards().size()); for (int i : ast.getReferencedGroups()) { referencedGroupBoundaries.set(Group.groupNumberToBoundaryIndexStart(i)); referencedGroupBoundaries.set(Group.groupNumberToBoundaryIndexEnd(i)); } + this.referencedCaptureGroupsTmp = new TBitSet(ast.getNumberOfCaptureGroups()); } public Set getTraversableLookBehindAssertions() { @@ -267,6 +271,14 @@ public void setReverse(boolean reverse) { this.forward = !reverse; } + private void setShouldRetreat() { + shouldRetreat = true; + } + + protected RegexFlavor getFlavor() { + return ast.getOptions().getFlavor(); + } + protected abstract boolean isBuildingDFA(); protected abstract boolean canPruneAfterUnconditionalFinalState(); @@ -278,7 +290,6 @@ private boolean canTraverseLookArounds() { protected void run(Term runRoot) { clearCaptureGroupData(); recalcTransitionGuards = false; - assert insideLoops.isEmpty(); assert insideEmptyGuardGroup.isEmpty(); assert curPath.isEmpty(); assert dollarsOnPath == 0; @@ -286,11 +297,12 @@ protected void run(Term runRoot) { assert matchBeginAssertionsOnPath == 0; assert matchEndAssertionsOnPath == 0; assert lookAroundsOnPath.isEmpty(); - assert nodeVisitsEmpty() : Arrays.toString(lookAroundVisitiedCount); + assert isEmpty(lookAroundVisitedCount) : Arrays.toString(lookAroundVisitedCount); assert !shouldRetreat; assert transitionGuards.isEmpty(); assert captureGroupUpdates.isEmpty(); assert captureGroupClears.isEmpty(); + assert firstGroup == -1; assert lastGroup == -1; root = runRoot; pathDeduplicationSet.clear(); @@ -316,14 +328,13 @@ protected void run(Term runRoot) { if (done) { break; } - RegexASTNode target = pathGetNode(curPath.peek()); - visit(target); - if (canPruneAfterUnconditionalFinalState() && target.isMatchFound() && !dollarsOnPath() && !caretsOnPath() && lookAroundsOnPath.isEmpty() && !hasTransitionGuards()) { + assert cur == pathGetNode(curPath.peek()); + visit(cur); + if (canPruneAfterUnconditionalFinalState() && cur.isMatchFound() && !dollarsOnPath() && !caretsOnPath() && lookAroundsOnPath.isEmpty() && !hasTransitionGuards()) { /* * Transitions after an unconditional final state transition will never be taken, so * it is safe to prune them. */ - insideLoops.clear(); insideEmptyGuardGroup.clear(); curPath.clear(); clearCaptureGroupData(); @@ -343,13 +354,11 @@ protected void run(Term runRoot) { // If we have back-tracked into an empty-match transition, then we must continue by // advancing past the empty-match group using advanceTerm instead of entering the group // again using doAdvance. - if (cur.isGroup() && cur.hasEmptyGuard()) { + if (cur.isGroup() && cur.hasEmptyGuard() && !done) { foundNextTarget = advanceTerm(cur.asGroup()); } } - if (useTransitionGuards()) { - clearTransitionGuards(); - } + clearTransitionGuards(); done = false; } @@ -390,13 +399,12 @@ protected long[] getTransitionGuardsOnPath() { protected void calcTransitionGuardsResult() { if (transitionGuardsResult == null) { - assert useTransitionGuards() || getTransitionGuards().isEmpty(); transitionGuardsResult = getTransitionGuards().isEmpty() ? TransitionGuard.NO_GUARDS : getTransitionGuards().toArray(); } } protected GroupBoundaries getGroupBoundaries() { - return ast.createGroupBoundaries(getCaptureGroupUpdates(), getCaptureGroupClears(), getLastGroup()); + return ast.createGroupBoundaries(getCaptureGroupUpdates(), getCaptureGroupClears(), getFirstGroup(), getLastGroup()); } /** @@ -406,10 +414,7 @@ protected GroupBoundaries getGroupBoundaries() { * @return {@code true} if a successor was reached in this step */ private boolean doAdvance() { - // We only use the insideLoops optimization when the regex flavor does not allow empty loop - // iterations. Empty loop iterations can occur when a regex flavor monitor capture groups - // in its empty check, or when it doesn't use backtracking when exiting a loop. - if (cur.isDead() || (!ast.getOptions().getFlavor().canHaveEmptyLoopIterations() && cur.isGroupWithGuards() && insideLoops.contains(cur.asGroup()))) { + if (cur.isDead()) { return retreat(); } if (cur.isSequence()) { @@ -419,19 +424,13 @@ private boolean doAdvance() { if (sequence.isQuantifierPassThroughSequence()) { // this empty sequence was inserted during quantifier expansion, so it is // allowed to pass through the parent quantified group. - assert pathGetNode(curPath.peek()) == parent && pathIsGroupEnter(curPath.peek()); + assert pathGetNode(curPath.peek()) == parent && PathElement.isGroupEnter(curPath.peek()); switchEnterToPassThrough(parent); - if (shouldRetreat) { - return retreat(); - } - if (parent.isLoop()) { - unregisterInsideLoop(parent); - } } else { pushGroupExit(parent); - if (shouldRetreat) { - return retreat(); - } + } + if (shouldRetreat) { + return retreat(); } return advanceTerm(parent); } else { @@ -447,9 +446,6 @@ private boolean doAdvance() { if (group.hasEmptyGuard()) { insideEmptyGuardGroup.set(group.getGroupsWithGuardsIndex()); } - if (group.isLoop()) { - registerInsideLoop(group); - } // This path will only be hit when visiting a group for the first time. All groups // must have at least one child sequence, so no check is needed here. // createGroupEnterPathElement initializes the group alternation index with 1, so we @@ -457,77 +453,87 @@ private boolean doAdvance() { cur = group.getFirstAlternative(); return deduplicatePath(true); } else { - curPath.add(createPathElement(cur)); + curPath.add(PathElement.create(cur)); if (cur.isPositionAssertion()) { - final PositionAssertion assertion = (PositionAssertion) cur; - switch (assertion.type) { - case CARET: - caretsOnPath++; - if (canTraverseCaret) { - return advanceTerm(assertion); - } else { - return retreat(); - } - case DOLLAR: - dollarsOnPath++; - return advanceTerm(assertion); - case MATCH_BEGIN: - if (!ignoreMatchBoundaryAssertions) { - matchBeginAssertionsOnPath++; - if (forward && isBuildingDFA() && !isRootEnterOnPath()) { - return retreat(); - } - } - return advanceTerm(assertion); - case MATCH_END: - if (!ignoreMatchBoundaryAssertions) { - matchEndAssertionsOnPath++; - if (!forward && isBuildingDFA() && !isRootEnterOnPath()) { - return retreat(); - } - } - return advanceTerm(assertion); - default: - throw CompilerDirectives.shouldNotReachHere(); - } + return advancePositionAssertion(cur.asPositionAssertion()); } else if (cur.isLookAroundAssertion()) { - LookAroundAssertion lookAround = cur.asLookAroundAssertion(); - if (canTraverseLookArounds()) { - if (lookAround.isLookAheadAssertion()) { - enterLookAhead(lookAround.asLookAheadAssertion()); - addLookAroundToVisitedSet(); - return advanceTerm(lookAround); - } else { - assert lookAround.isLookBehindAssertion(); - addLookAroundToVisitedSet(); - if (traversableLookBehindAssertions == null || traversableLookBehindAssertions.contains(lookAround.asLookBehindAssertion())) { - return advanceTerm(lookAround); - } else { - return retreat(); - } + return advanceLookAround(cur.asLookAroundAssertion()); + } else { + assert cur.isCharacterClass() || cur.isBackReference() || cur.isMatchFound() || cur.isAtomicGroup(); + if ((forward && dollarsOnPath() || !forward && caretsOnPath()) && !canMatchEmptyString(cur)) { + return retreat(); + } + if (!ignoreMatchBoundaryAssertions) { + if ((forward && matchBeginAssertionsOnPath() || !forward && matchEndAssertionsOnPath()) && !isRootEnterOnPath() && !canMatchEmptyString(cur)) { + return retreat(); + } + if (matchEndAssertionsOnPath() && (cur.isCharacterClass() || cur.isBackReference())) { + return retreat(); } } return true; + } + } + } + + private boolean advanceLookAround(LookAroundAssertion lookAround) { + if (canTraverseLookArounds()) { + if (lookAround.isLookAheadAssertion()) { + enterLookAhead(lookAround.asLookAheadAssertion()); + addLookAroundToVisitedSet(); + return advanceTerm(lookAround); } else { - assert cur.isCharacterClass() || cur.isBackReference() || cur.isMatchFound() || cur.isAtomicGroup(); - if ((forward && dollarsOnPath() || !forward && caretsOnPath()) && cur.isCharacterClass()) { - // don't visit CharacterClass nodes if we traversed PositionAssertions already + assert lookAround.isLookBehindAssertion(); + addLookAroundToVisitedSet(); + if (traversableLookBehindAssertions == null || traversableLookBehindAssertions.contains(lookAround.asLookBehindAssertion())) { + return advanceTerm(lookAround); + } else { return retreat(); } - if (!ignoreMatchBoundaryAssertions && matchEndAssertionsOnPath() && (cur.isCharacterClass() || cur.isBackReference())) { + } + } + return true; + } + + private boolean advancePositionAssertion(PositionAssertion assertion) { + switch (assertion.type) { + case CARET: + caretsOnPath++; + if (canTraverseCaret) { + return advanceTerm(assertion); + } else { return retreat(); } - return true; - } + case DOLLAR: + dollarsOnPath++; + return advanceTerm(assertion); + case MATCH_BEGIN: + if (!ignoreMatchBoundaryAssertions) { + matchBeginAssertionsOnPath++; + if (forward && isBuildingDFA() && !isRootEnterOnPath()) { + return retreat(); + } + } + return advanceTerm(assertion); + case MATCH_END: + if (!ignoreMatchBoundaryAssertions) { + matchEndAssertionsOnPath++; + if (!forward && isBuildingDFA() && !isRootEnterOnPath()) { + return retreat(); + } + } + return advanceTerm(assertion); + default: + throw CompilerDirectives.shouldNotReachHere(); } } /** * Advances past the given {@link Term} and updates {@link #cur the current node}. * - * @return {@code true} if a successor was reached in this step (possible if - * {@link #advanceEmptyGuard} returns {@code true} and we have the quantified group as - * the successor) + * @return {@code true} if a successor was reached in this step (possible if we want to generate + * a transition to the special EMPTY_STATE, which by itself doesn't match anything, but + * acts as a helper for simulating the backtracking behavior of the ECMAScript flavor) */ private boolean advanceTerm(Term term) { if (ast.isNFAInitialState(term) || (term.getParent().isSubtreeRoot() && (term.isPositionAssertion() || term.isMatchFound()))) { @@ -541,19 +547,46 @@ private boolean advanceTerm(Term term) { } Term curTerm = term; while (!curTerm.getParent().isSubtreeRoot()) { - // We are leaving curTerm, which is a quantified group that we have already entered - // during this step. - // Unless we are building a DFA in a flavor which can have empty loop iterations, we - // call into advanceEmptyGuard. This is crucial to preserve the termination of the AST - // traversal/NFA generation. In the case of building a DFA in a flavor which can have - // empty loop iterations: - // a) we cannot use advanceEmptyGuard because it might introduce empty transitions, - // which are forbidden in the DFA, - // and b) termination is ensured by resolving exitZeroWidth/escapeZeroWidth guards - // statically. + /* + * We are leaving curTerm, which is a quantified group that we have already entered + * during this step. + * + * We avoid infinite loops on these groups by statically resolving TransitionGuards and + * de-duplicating equivalent transitions, but we have to apply special treatment for + * ECMAScript and Python's behavior on empty loop iterations here. + * + * ECMAScript and Python don't stop quantifier loops on empty matches as long as their + * minimum count has not been reached. Unfortunately, we have to simulate this behavior + * in cases where it is observable via capture groups, back-references or position + * assertions. + */ if (curTerm.isGroupWithGuards() && insideEmptyGuardGroup.get(curTerm.asGroup().getGroupsWithGuardsIndex()) && - !(ast.getOptions().getFlavor().canHaveEmptyLoopIterations() && isBuildingDFA())) { - return advanceEmptyGuard(curTerm); + !getFlavor().emptyChecksMonitorCaptureGroups()) { + Group curGroup = curTerm.asGroup(); + Quantifier quantifier = curGroup.getQuantifier(); + // If we are: + // - in ECMAScript or Python flavor + // - in the mandatory split part of a quantifier + // - that has not been unrolled + // - and capture groups are visible to the caller, or the expression contains + // back-references, or we crossed a caret + if (!getFlavor().emptyChecksOnMandatoryLoopIterations() && + curGroup.isMandatoryQuantifier() && + !curGroup.isExpandedQuantifier() && + (!ast.getOptions().isBooleanMatch() || ast.getProperties().hasBackReferences() || caretsOnPath())) { + // the existence of a mandatory copy of the quantifier loop implies a minimum + // greater than zero + assert !curGroup.isMandatoryQuantifier() || quantifier.getMin() > 0; + popGroupExit(); + cur = curTerm; + // Set the current group node as the path's target to indicate we want to + // generate an EMPTY_STATE for it. The empty state allows the backtracking + // engine to loop without consuming characters. + curPath.add(PathElement.create(cur)); + return true; + } + // otherwise, retreat. + return retreat(); } Sequence parentSeq = (Sequence) curTerm.getParent(); if (curTerm == (forward ? parentSeq.getLastTerm() : parentSeq.getFirstTerm())) { @@ -578,33 +611,6 @@ private boolean advanceTerm(Term term) { return false; } - /** - * Advances past a {@link Group} with an empty-guard. This can produce a transition to the - * special empty-match state that is represented by setting the successor to the quantified - * group. - * - * @return {@code true} if a successor (the quantified group) was reached in this step - */ - private boolean advanceEmptyGuard(Term curTerm) { - // We found a zero-width match group with a quantifier. - // In flavors where we cannot have empty loop iterations (JavaScript), we generate - // transitions to the special empty-match state only for bounded quantifiers which haven't - // been unrolled. In flavors where we can have empty loop iterations, we generate - // transitions to the empty-match state unconditionally. This ensures that we do not try to - // generate NFA transitions that span multiple repetitions of the same quantified group, - // potentially leading to non-terminating NFA generation. - if (ast.getOptions().getFlavor().canHaveEmptyLoopIterations() || - (curTerm.isQuantifiableTerm() && curTerm.asQuantifiableTerm().hasNotUnrolledQuantifier() && curTerm.asQuantifiableTerm().getQuantifier().getMin() > 0)) { - assert curTerm.isGroup(); - // By returning the quantified group itself, we map the transition target to the special - // empty-match state. - cur = curTerm; - return true; - } else { - return retreat(); - } - } - /** * Backtrack through the traversal and find an unexplored alternative. * @@ -613,42 +619,31 @@ private boolean advanceEmptyGuard(Term curTerm) { private boolean retreat() { shouldRetreat = false; while (!curPath.isEmpty()) { - long lastVisited = curPath.peek(); - RegexASTNode node = pathGetNode(lastVisited); - if (pathIsGroup(lastVisited)) { + long lastElement = curPath.peek(); + RegexASTNode node = pathGetNode(lastElement); + if (PathElement.isGroup(lastElement)) { Group group = (Group) node; - if (pathIsGroupEnter(lastVisited) || pathIsGroupPassThrough(lastVisited)) { - if (pathGroupHasNext(lastVisited)) { - if (pathIsGroupPassThrough(lastVisited) && group.isLoop()) { - // a passthrough node was changed to an enter node, - // so we register the loop in insideLoops - registerInsideLoop(group); - } + if (PathElement.isGroupEnter(lastElement) || PathElement.isGroupPassThrough(lastElement)) { + if (pathGroupHasNext(lastElement)) { switchNextGroupAlternative(group); if (shouldRetreat) { return retreat(); } - cur = pathGroupGetNext(lastVisited); + cur = pathGroupGetNext(lastElement); return deduplicatePath(true); } else { - if (pathIsGroupEnter(lastVisited)) { + if (PathElement.isGroupEnter(lastElement)) { popGroupEnter(); } else { - assert pathIsGroupPassThrough(lastVisited); + assert PathElement.isGroupPassThrough(lastElement); popGroupPassThrough(); } - if (pathIsGroupEnter(lastVisited) && group.isLoop()) { - // we only deregister the node from insideLoops if this was an enter - // node, if it was a passthrough node, it was already deregistered when - // it was transformed from an enter node in doAdvance - unregisterInsideLoop(group); - } if (group.hasEmptyGuard()) { insideEmptyGuardGroup.clear(group.getGroupsWithGuardsIndex()); } } - } else if (ast.getOptions().getFlavor().failingEmptyChecksDontBacktrack() && pathIsGroupExit(lastVisited) && group.hasQuantifier() && group.getQuantifier().hasZeroWidthIndex()) { - // In Ruby, Python and OracleDB, when we finish an iteration of a loop, there is + } else if (PathElement.isGroupExit(lastElement) && needsZeroWidthEscape(group)) { + // In Ruby and OracleDB, when we finish an iteration of a loop, there is // an empty check. If we pass the empty check, we return to the beginning of the // loop where we get to make a non-deterministic choice whether we want to start // another iteration of the loop (so far the same as ECMAScript). However, if we @@ -659,6 +654,8 @@ private boolean retreat() { // (exitZeroWidth and escapeZeroWidth, respectively), so that at runtime, only // one of the two transitions will be admissible. The clause below lets us // generate the second transition by replacing the loop exit with a loop escape. + // In ECMAScript, we use the same mechanism to fast-forward mandatory quantifier + // parts when we find a zero-width match for the quantified expression. switchExitToEscape(group); if (shouldRetreat) { return retreat(); @@ -670,39 +667,19 @@ private boolean retreat() { pushGroupExit(parentGroup); return advanceTerm(parentGroup); } else { - if (pathIsGroupExit(lastVisited)) { + if (PathElement.isGroupExit(lastElement)) { popGroupExit(); } else { - assert pathIsGroupEscape(lastVisited); + assert PathElement.isGroupEscape(lastElement); popGroupEscape(group); } } } else { curPath.pop(); if (canTraverseLookArounds() && node.isLookAroundAssertion()) { - if (node.isLookAheadAssertion()) { - leaveLookAhead(node.asLookAheadAssertion()); - } - removeLookAroundFromVisitedSet(lastVisited); + popLookAround(node, lastElement); } else if (node.isPositionAssertion()) { - switch (node.asPositionAssertion().type) { - case CARET -> { - caretsOnPath--; - } - case DOLLAR -> { - dollarsOnPath--; - } - case MATCH_BEGIN -> { - if (!ignoreMatchBoundaryAssertions) { - matchBeginAssertionsOnPath--; - } - } - case MATCH_END -> { - if (!ignoreMatchBoundaryAssertions) { - matchEndAssertionsOnPath--; - } - } - } + popPositionAssertion(node); } } } @@ -710,6 +687,34 @@ private boolean retreat() { return false; } + private void popLookAround(RegexASTNode node, long pathElement) { + if (node.isLookAheadAssertion()) { + leaveLookAhead(node.asLookAheadAssertion()); + } + removeLookAroundFromVisitedSet(pathElement); + } + + private void popPositionAssertion(RegexASTNode node) { + switch (node.asPositionAssertion().type) { + case CARET -> { + caretsOnPath--; + } + case DOLLAR -> { + dollarsOnPath--; + } + case MATCH_BEGIN -> { + if (!ignoreMatchBoundaryAssertions) { + matchBeginAssertionsOnPath--; + } + } + case MATCH_END -> { + if (!ignoreMatchBoundaryAssertions) { + matchEndAssertionsOnPath--; + } + } + } + } + /** * This should be called whenever {@link #cur} is set to some {@link Sequence}. * @@ -720,7 +725,13 @@ private boolean deduplicatePath(boolean internal) { if (shouldRetreat) { return retreat(); } - // interal == true means that this is being called during traversal, before reaching a + if (internal && getFlavor().emptyChecksMonitorCaptureGroups()) { + // in Ruby, we don't deduplicate on intermediate Sequence nodes, because due to the + // "empty checks monitor capture groups" property, we may have to generate transitions + // that represent multiple loop iterations in a quantified expression. + return false; + } + // internal == true means that this is being called during traversal, before reaching a // successor node (these calls are made in regular intervals, whenever a new Sequence is // entered). // This method is also called for every successor we have found (internal == false). In @@ -732,7 +743,7 @@ private boolean deduplicatePath(boolean internal) { // encountered first will dominate the one found later and any empty capture groups that // would have been matched along the way cannot affect future matching. boolean captureGroupsMatter = !cur.isMatchFound() && - ((ast.getOptions().getFlavor().backreferencesToUnmatchedGroupsFail() && ast.getProperties().hasBackReferences()) || + ((getFlavor().backreferencesToUnmatchedGroupsFail() && ast.getProperties().hasBackReferences()) || (isBuildingDFA() && ast.getProperties().hasConditionalBackReferences())); long id = cur.getId(); @@ -777,6 +788,9 @@ private boolean deduplicatePath(boolean internal) { } private void dedupKeyAddGroupBoundaries(TBitSet boundaries) { + // We only care about groups referenced by back-references when de-duplicating transitions. + // Without back-references, the first possible transition to the same target always + // dominates the others. long[] bitset = boundaries.getInternalArray(); long[] referenced = referencedGroupBoundaries.getInternalArray(); assert bitset.length == referenced.length; @@ -785,93 +799,25 @@ private void dedupKeyAddGroupBoundaries(TBitSet boundaries) { } } - /** - * First field: (short) group alternation index. This value is used to iterate the alternations - * of groups referenced in a group-enter path element.
    - * Since the same group can appear multiple times on the path, we cannot reuse {@link Group}'s - * implementation of {@link RegexASTVisitorIterable}. Therefore, every occurrence of a group on - * the path has its own index for iterating and back-tracking over its alternatives. - */ - private static final int PATH_GROUP_ALT_INDEX_OFFSET = 0; - /** - * Second field: (int) id of the path element's {@link RegexASTNode}. - */ - private static final int PATH_NODE_OFFSET = Short.SIZE; - /** - * Third field: group action. Every path element referencing a group must have one of four - * possible group actions: - *
      - *
    • group enter
    • - *
    • group exit
    • - *
    • group pass through
    • - *
    • group escape
    • - *
    - */ - private static final int PATH_GROUP_ACTION_OFFSET = Short.SIZE + Integer.SIZE; - private static final long PATH_GROUP_ACTION_ENTER = 1L << PATH_GROUP_ACTION_OFFSET; - private static final long PATH_GROUP_ACTION_EXIT = 1L << PATH_GROUP_ACTION_OFFSET + 1; - private static final long PATH_GROUP_ACTION_PASS_THROUGH = 1L << PATH_GROUP_ACTION_OFFSET + 2; - private static final long PATH_GROUP_ACTION_ESCAPE = 1L << PATH_GROUP_ACTION_OFFSET + 3; - private static final long PATH_GROUP_ACTION_ANY = PATH_GROUP_ACTION_ENTER | PATH_GROUP_ACTION_EXIT | PATH_GROUP_ACTION_PASS_THROUGH | PATH_GROUP_ACTION_ESCAPE; - - /** - * Create a new path element containing the given node. - */ - private static long createPathElement(RegexASTNode node) { - return (long) node.getId() << PATH_NODE_OFFSET; - } - - private static int pathGetNodeId(long pathElement) { - return (int) (pathElement >>> PATH_NODE_OFFSET); + private static boolean canMatchEmptyString(RegexASTNode node) { + if (node.isBackReference()) { + return node.asBackReference().mayMatchEmptyString(); + } + return !node.isCharacterClass(); } /** * Get the {@link RegexASTNode} contained in the given path element. */ private RegexASTNode pathGetNode(long pathElement) { - return ast.getState(pathGetNodeId(pathElement)); - } - - /** - * Get the group alternation index of the given path element. - */ - private static int pathGetGroupAltIndex(long pathElement) { - return (short) (pathElement >>> PATH_GROUP_ALT_INDEX_OFFSET); - } - - /** - * Returns {@code true} if the given path element has any group action set. Every path element - * containing a group must have one group action. - */ - private static boolean pathIsGroup(long pathElement) { - return (pathElement & PATH_GROUP_ACTION_ANY) != 0; - } - - private static boolean pathIsGroupEnter(long pathElement) { - return (pathElement & PATH_GROUP_ACTION_ENTER) != 0; - } - - private static boolean pathIsGroupExit(long pathElement) { - return (pathElement & PATH_GROUP_ACTION_EXIT) != 0; - } - - private static boolean pathIsGroupPassThrough(long pathElement) { - return (pathElement & PATH_GROUP_ACTION_PASS_THROUGH) != 0; - } - - private static boolean pathIsGroupEscape(long pathElement) { - return (pathElement & PATH_GROUP_ACTION_ESCAPE) != 0; - } - - private static boolean pathIsGroupExitOrEscape(long pathElement) { - return (pathElement & (PATH_GROUP_ACTION_EXIT | PATH_GROUP_ACTION_ESCAPE)) != 0; + return ast.getState(PathElement.getNodeId(pathElement)); } /** * Returns {@code true} if the path element's group alternation index is still in bounds. */ private boolean pathGroupHasNext(long pathElement) { - return pathGetGroupAltIndex(pathElement) < ((Group) pathGetNode(pathElement)).size(); + return PathElement.getGroupAltIndex(pathElement) < ((Group) pathGetNode(pathElement)).size(); } /** @@ -879,16 +825,17 @@ private boolean pathGroupHasNext(long pathElement) { * the group alternation index! */ private Sequence pathGroupGetNext(long pathElement) { - return ((Group) pathGetNode(pathElement)).getAlternatives().get(pathGetGroupAltIndex(pathElement)); + return ((Group) pathGetNode(pathElement)).getAlternatives().get(PathElement.getGroupAltIndex(pathElement)); } protected boolean isRootEnterOnPath() { - return isGroupEnterOnPath(ast.getRoot().getId()); + return isGroupEnterOnPath(ast.getRoot()); } - private boolean isGroupEnterOnPath(int groupNodeId) { + private boolean isGroupEnterOnPath(Group group) { + int groupNodeId = group.getId(); for (long element : curPath) { - if (pathGetNodeId(element) == groupNodeId && pathIsGroupEnter(element)) { + if (PathElement.getNodeId(element) == groupNodeId && PathElement.isGroupEnter(element)) { return true; } } @@ -897,48 +844,48 @@ private boolean isGroupEnterOnPath(int groupNodeId) { /// Pushing and popping group elements to and from the path private void pushGroupEnter(Group group, int groupAltIndex) { - curPath.add(createPathElement(group) | (groupAltIndex << PATH_GROUP_ALT_INDEX_OFFSET) | PATH_GROUP_ACTION_ENTER); + curPath.add(PathElement.createGroupEnter(group, groupAltIndex)); recalcTransitionGuards = true; } private int popGroupEnter() { long pathEntry = curPath.pop(); - assert pathIsGroupEnter(pathEntry); + assert PathElement.isGroupEnter(pathEntry); recalcTransitionGuards = true; - return pathGetGroupAltIndex(pathEntry); + return PathElement.getGroupAltIndex(pathEntry); } private void switchNextGroupAlternative(Group group) { int groupAltIndex; - if (pathIsGroupEnter(curPath.peek())) { + if (PathElement.isGroupEnter(curPath.peek())) { groupAltIndex = popGroupEnter(); } else { - assert pathIsGroupPassThrough(curPath.peek()); + assert PathElement.isGroupPassThrough(curPath.peek()); groupAltIndex = popGroupPassThrough(); } pushGroupEnter(group, groupAltIndex + 1); } private void pushGroupExit(Group group) { - curPath.add(createPathElement(group) | PATH_GROUP_ACTION_EXIT); + curPath.add(PathElement.createGroupExit(group)); recalcTransitionGuards = true; } private void popGroupExit() { long pathEntry = curPath.pop(); - assert pathIsGroupExit(pathEntry); + assert PathElement.isGroupExit(pathEntry); recalcTransitionGuards = true; } private void pushGroupPassThrough(Group group, int groupAltIndex) { - curPath.add(createPathElement(group) | PATH_GROUP_ACTION_PASS_THROUGH | (groupAltIndex << PATH_GROUP_ALT_INDEX_OFFSET)); + curPath.add(PathElement.createGroupPassThrough(group, groupAltIndex)); recalcTransitionGuards = true; } private int popGroupPassThrough() { long pathEntry = curPath.pop(); - int groupAltIndex = pathGetGroupAltIndex(pathEntry); - assert pathIsGroupPassThrough(pathEntry); + int groupAltIndex = PathElement.getGroupAltIndex(pathEntry); + assert PathElement.isGroupPassThrough(pathEntry); recalcTransitionGuards = true; return groupAltIndex; } @@ -954,13 +901,14 @@ private void switchExitToEscape(Group group) { } private void pushGroupEscape(Group group) { - curPath.add(createPathElement(group) | PATH_GROUP_ACTION_ESCAPE); + long groupEscape = PathElement.createGroupEscape(group); + curPath.add(groupEscape); recalcTransitionGuards = true; } private void popGroupEscape(Group group) { long pathEntry = curPath.pop(); - assert pathIsGroupEscape(pathEntry); + assert PathElement.isGroupEscape(pathEntry); assert group == pathGetNode(pathEntry); recalcTransitionGuards = true; } @@ -969,6 +917,7 @@ private void popGroupEscape(Group group) { private void clearCaptureGroupData() { captureGroupUpdates.clear(); captureGroupClears.clear(); + firstGroup = -1; lastGroup = -1; } @@ -982,6 +931,11 @@ private TBitSet getCaptureGroupClears() { return captureGroupClears; } + private int getFirstGroup() { + calcTransitionGuards(); + return firstGroup; + } + private int getLastGroup() { calcTransitionGuards(); return lastGroup; @@ -989,34 +943,16 @@ private int getLastGroup() { private LongArrayBuffer getTransitionGuards() { calcTransitionGuards(); - return transitionGuards; + return transitionGuardsCanonicalized; } private void calcTransitionGuards() { if (recalcTransitionGuards) { - if (useTransitionGuards()) { - calculateTransitionGuards(); - } else { - calculateGroupBoundaries(); - } + calculateTransitionGuards(); recalcTransitionGuards = false; } } - private void calculateGroupBoundaries() { - clearCaptureGroupData(); - for (long element : curPath) { - if (pathIsGroup(element)) { - Group group = (Group) pathGetNode(element); - if (pathIsGroupEnter(element)) { - calcGroupBoundariesEnter(group); - } else if (pathIsGroupExitOrEscape(element)) { - calcGroupBoundariesExit(group); - } - } - } - } - private int getBoundaryIndexStart(Group group) { return forward ? group.getBoundaryIndexStart() : group.getBoundaryIndexEnd(); } @@ -1028,10 +964,13 @@ private int getBoundaryIndexEnd(Group group) { private void calcGroupBoundariesEnter(Group group) { if (group.isCapturing()) { captureGroupUpdate(getBoundaryIndexStart(group)); + if (updatesLastGroupField(group) && firstGroup == -1) { + firstGroup = group.getGroupNumber(); + } } - if (!ast.getOptions().getFlavor().nestedCaptureGroupsKeptOnLoopReentry() && group.hasQuantifier() && group.hasEnclosedCaptureGroups()) { - int lo = Group.groupNumberToBoundaryIndexStart(group.getEnclosedCaptureGroupsLow()); - int hi = Group.groupNumberToBoundaryIndexEnd(group.getEnclosedCaptureGroupsHigh() - 1); + if (clearsEnclosedGroups(group)) { + int lo = Group.groupNumberToBoundaryIndexStart(group.getEnclosedCaptureGroupsLo()); + int hi = Group.groupNumberToBoundaryIndexEnd(group.getEnclosedCaptureGroupsHi() - 1); captureGroupClears.setRange(lo, hi); captureGroupUpdates.clearRange(lo, hi); } @@ -1040,7 +979,7 @@ private void calcGroupBoundariesEnter(Group group) { private void calcGroupBoundariesExit(Group group) { if (group.isCapturing()) { captureGroupUpdate(getBoundaryIndexEnd(group)); - if (ast.getOptions().getFlavor().usesLastGroupResultField() && group.getGroupNumber() != 0) { + if (updatesLastGroupField(group)) { lastGroup = group.getGroupNumber(); } } @@ -1053,51 +992,74 @@ private void captureGroupUpdate(int boundary) { private void calculateTransitionGuards() { clearCaptureGroupData(); - boundedQuantifiersLoop.clear(); - boundedQuantifiersExited.clear(); + bqExited.clear(); + bqBypassed.clear(); + Arrays.fill(bqLastZeroWidthEnter, -1); + Arrays.fill(bqLastCounterReset, -1); transitionGuards.clear(); - for (long element : curPath) { - if (pathIsGroup(element)) { + transitionGuardsCanonicalized.clear(); + for (int i = 0; i < curPath.length(); i++) { + long element = curPath.get(i); + if (PathElement.isGroup(element)) { Group group = (Group) pathGetNode(element); - int groupAltIndex = pathGetGroupAltIndex(element); - if (pathIsGroupEnter(element)) { + int groupAltIndex = PathElement.getGroupAltIndex(element); + if (PathElement.isGroupEnter(element)) { if (group.hasQuantifier()) { Quantifier quantifier = group.getQuantifier(); if (quantifier.hasIndex()) { - if (!quantifier.isInfiniteLoop() && boundedQuantifiersLoop.get(quantifier.getIndex()) && !boundedQuantifiersExited.get(quantifier.getIndex())) { - pushTransitionGuard(TransitionGuard.createLoop(quantifier)); + if (bqExited.get(group.getGroupsWithGuardsIndex()) && !bqBypassed.get(group.getGroupsWithGuardsIndex())) { + if (group.isMandatoryQuantifier()) { + pushTransitionGuard(TransitionGuard.createCountLtMin(quantifier)); + } else if (!quantifier.isInfiniteLoop()) { + pushTransitionGuard(TransitionGuard.createCountLtMax(quantifier)); + } + pushTransitionGuard(TransitionGuard.createCountInc(quantifier)); } else { - pushTransitionGuard(TransitionGuard.createLoopInc(quantifier)); + if (group.isOptionalQuantifier()) { + pushTransitionGuard(TransitionGuard.createCountSetMin(quantifier)); + } else { + pushTransitionGuard(TransitionGuard.createCountSet1(quantifier)); + } } } + if (group.getEnclosedZeroWidthGroupsHi() - group.getEnclosedZeroWidthGroupsLo() > 0) { + bqBypassed.clearRange(group.getEnclosedZeroWidthGroupsLo(), group.getEnclosedZeroWidthGroupsHi() - 1); + bqExited.clearRange(group.getEnclosedZeroWidthGroupsLo(), group.getEnclosedZeroWidthGroupsHi() - 1); + } if (needsEmptyCheck(group)) { pushTransitionGuard(TransitionGuard.createEnterZeroWidth(quantifier)); } } - if (needsUpdateCGStepByStep(group) && !captureGroupUpdates.get(getBoundaryIndexStart(group))) { + if (needsUpdateCGStepByStep(group) && (getFlavor().usesLastGroupResultField() || !captureGroupUpdates.get(getBoundaryIndexStart(group)))) { pushTransitionGuard(TransitionGuard.createUpdateCG(getBoundaryIndexStart(group))); } calcGroupBoundariesEnter(group); if (group.isConditionalBackReferenceGroup()) { pushTransitionGuard(getConditionalBackReferenceGroupTransitionGuard(group, groupAltIndex)); } - } else if (pathIsGroupExitOrEscape(element)) { - if (pathIsGroupExit(element)) { + } else if (PathElement.isGroupExitOrEscape(element)) { + if (PathElement.isGroupExit(element)) { if (group.hasQuantifier()) { Quantifier quantifier = group.getQuantifier(); if (quantifier.hasIndex()) { - boundedQuantifiersLoop.set(quantifier.getIndex()); + if (!root.isGroup()) { + bqLastCounterReset[quantifier.getIndex()] = transitionGuards.length(); + } + bqExited.set(group.getGroupsWithGuardsIndex()); } if (needsEmptyCheck(group)) { pushTransitionGuard(TransitionGuard.createExitZeroWidth(quantifier)); } } - } else if (pathIsGroupEscape(element)) { + } else if (PathElement.isGroupEscape(element)) { if (group.hasQuantifier()) { Quantifier quantifier = group.getQuantifier(); if (quantifier.hasIndex()) { - boundedQuantifiersExited.set(quantifier.getIndex()); - pushTransitionGuard(TransitionGuard.createExitReset(quantifier)); + bqLastCounterReset[quantifier.getIndex()] = transitionGuards.length(); + if (bqBypassed.get(group.getGroupsWithGuardsIndex())) { + setShouldRetreat(); + } + bqBypassed.set(group.getGroupsWithGuardsIndex()); } if (quantifier.hasZeroWidthIndex()) { pushTransitionGuard(TransitionGuard.createEscapeZeroWidth(quantifier)); @@ -1105,50 +1067,122 @@ private void calculateTransitionGuards() { } } pushRecursiveBackrefUpdates(group); - if (needsUpdateCGStepByStep(group) && !captureGroupUpdates.get(getBoundaryIndexEnd(group))) { + if (needsUpdateCGStepByStep(group) && (getFlavor().usesLastGroupResultField() || !captureGroupUpdates.get(getBoundaryIndexEnd(group)))) { pushTransitionGuard(TransitionGuard.createUpdateCG(getBoundaryIndexEnd(group))); } calcGroupBoundariesExit(group); - } else if (pathIsGroupPassThrough(element)) { + } else if (PathElement.isGroupPassThrough(element)) { Group quantifierGroup = getQuantifiedGroupFromPassthrough(group, groupAltIndex); Quantifier quantifier = quantifierGroup.getQuantifier(); if (!quantifierGroup.isExpandedQuantifier()) { - if (quantifier.hasIndex()) { + if (quantifierGroup.isDead()) { if (quantifier.getMin() > 0) { - boundedQuantifiersExited.set(quantifier.getIndex()); - pushTransitionGuard(TransitionGuard.createExit(quantifier)); - } else { - pushTransitionGuard(TransitionGuard.createExitReset(quantifier)); + setShouldRetreat(); } - } else { - assert quantifierGroup.isDead(); - if (quantifier.getMin() > 0) { - shouldRetreat = true; + } else if (quantifier.hasIndex()) { + if (bqBypassed.get(quantifierGroup.getGroupsWithGuardsIndex())) { + setShouldRetreat(); + } + bqBypassed.set(quantifierGroup.getGroupsWithGuardsIndex()); + if (quantifierGroup.isMandatoryQuantifier() || quantifier.getMin() > 0 && !quantifierGroup.isOptionalQuantifier()) { + if (!bqExited.get(quantifierGroup.getGroupsWithGuardsIndex())) { + setShouldRetreat(); + } + if (quantifier.getMin() > 0) { + pushTransitionGuard(TransitionGuard.createCountGeMin(quantifier)); + } } } } } } } + for (int i = 0; i < transitionGuards.length(); i++) { + long guard = transitionGuards.get(i); + if (shouldKeepGuard(guard, i)) { + transitionGuardsCanonicalized.add(guard); + } + } + } + + private boolean shouldKeepGuard(long guard, int guardPosition) { + switch (TransitionGuard.getKind(guard)) { + case countSet1, countInc, countSetMin -> { + return getFlavor().emptyChecksMonitorCaptureGroups() || guardPosition >= bqLastCounterReset[TransitionGuard.getQuantifierIndex(guard)]; + } + case enterZeroWidth -> { + int zeroWidthQuantifierIndex = TransitionGuard.getZeroWidthQuantifierIndex(guard); + Group quantifiedTerm = (Group) ast.getZeroWidthQuantifiables().get(zeroWidthQuantifierIndex); + // we need to keep enterZeroWidth guards if the quantified expression can contain + // NFA states that don't consume any characters, or the expression contains capture + // groups referred to by back-references. In the case of referenced groups, the + // guard is needed just to differentiate transitions in nested quantifiers, + // because these may require additional backtracking, e.g. matching + // /a(b*)*c\\1d/ against "abbbbcbbd" + return bqLastZeroWidthEnter[zeroWidthQuantifierIndex] == guardPosition && (quantifiedTerm.hasCaret() || + quantifiedTerm.hasLookArounds() || + quantifiedTerm.hasBackReferences() || + quantifiedTerm.hasAtomicGroups() || + hasReferencedCaptureGroups(quantifiedTerm) || + (cur.isGroup() && cur.asGroup().getQuantifier().getZeroWidthIndex() != zeroWidthQuantifierIndex)); + } + case updateRecursiveBackrefPointer -> { + for (int i = transitionGuards.length() - 1; i > guardPosition; i--) { + if (transitionGuards.get(i) == guard) { + return false; + } + } + return true; + } + default -> { + return true; + } + } + } + + private boolean hasReferencedCaptureGroups(Group quantifiedTerm) { + if (!ast.getProperties().hasBackReferences() || !quantifiedTerm.hasCaptureGroups()) { + return false; + } + referencedCaptureGroupsTmp.clear(); + referencedCaptureGroupsTmp.setRange(quantifiedTerm.getCaptureGroupsLo(), quantifiedTerm.getCaptureGroupsHi() - 1); + return !ast.getReferencedGroups().isDisjoint(referencedCaptureGroupsTmp); } private static Group getQuantifiedGroupFromPassthrough(Group group, int groupAltIndex) { assert group.size() == 2 && groupAltIndex - 1 >= 0 && groupAltIndex - 1 <= 1; int otherAltIndex = (groupAltIndex - 1) ^ 1; Sequence otherAlternative = group.getAlternatives().get(otherAltIndex); - assert !otherAlternative.isEmpty() && otherAlternative.get(0).isGroup(); - Group quantifierGroup = otherAlternative.get(0).asGroup(); + Term quantifiedTerm = group.isInLookBehindAssertion() ? otherAlternative.getLastTerm() : otherAlternative.getFirstTerm(); + assert !otherAlternative.isEmpty() && quantifiedTerm.isGroup(); + Group quantifierGroup = quantifiedTerm.asGroup(); assert quantifierGroup.hasQuantifier(); return quantifierGroup; } private boolean needsUpdateCGStepByStep(Group group) { - return ast.getOptions().getFlavor().matchesTransitionsStepByStep() && group.isCapturing(); + return getFlavor().matchesTransitionsStepByStep() && group.isCapturing(); } private boolean needsEmptyCheck(Group group) { assert group.hasQuantifier(); - return group.getQuantifier().hasZeroWidthIndex() && (ast.getOptions().getFlavor().emptyChecksOnMandatoryLoopIterations() || !group.isMandatoryUnrolledQuantifier()); + return group.getQuantifier().hasZeroWidthIndex() && (getFlavor().emptyChecksOnMandatoryLoopIterations() || !group.isMandatoryUnrolledQuantifier()); + } + + private boolean needsZeroWidthEscape(Group group) { + if (getFlavor().failingEmptyChecksDontBacktrack()) { + return group.hasQuantifier() && group.getQuantifier().hasZeroWidthIndex(); + } else { + return group.hasNotUnrolledQuantifier() && group.getQuantifier().hasZeroWidthIndex() && group.getQuantifier().getMin() > 0 && group.isMandatoryQuantifier(); + } + } + + private boolean clearsEnclosedGroups(Group group) { + return !getFlavor().nestedCaptureGroupsKeptOnLoopReentry() && group.hasQuantifier() && group.hasEnclosedCaptureGroups(); + } + + private boolean updatesLastGroupField(Group group) { + return getFlavor().usesLastGroupResultField() && group.isCapturing() && group.getGroupNumber() != 0; } private static long getConditionalBackReferenceGroupTransitionGuard(Group group, int groupAltIndex) { @@ -1163,33 +1197,31 @@ private static long getConditionalBackReferenceGroupTransitionGuard(Group group, } private void pushRecursiveBackrefUpdates(Group group) { - if (ast.getOptions().getFlavor().supportsRecursiveBackreferences() && ast.getProperties().hasRecursiveBackReferences()) { + if (getFlavor().supportsRecursiveBackreferences() && ast.getProperties().hasRecursiveBackReferences()) { if (group.isCapturing() && ast.isGroupRecursivelyReferenced(group.getGroupNumber())) { pushTransitionGuard(TransitionGuard.createUpdateRecursiveBackref(group.getGroupNumber())); } } } - /// Quantifier guard data handling - private boolean useTransitionGuards() { - // In some flavors, we need to calculate quantifier guards even when building DFAs, since - // these guards represent critical semantic details. While these guards would be ignored by - // the DFA at runtime, they are all resolved statically during this traversal. This is - // checked by ASTStepVisitor#noPredicatesInGuards. - return !isBuildingDFA() || ast.getOptions().getFlavor().canHaveEmptyLoopIterations(); - } - private void clearTransitionGuards() { transitionGuards.clear(); + transitionGuardsCanonicalized.clear(); } private void pushTransitionGuard(long guard) { - assert useTransitionGuards(); // First, we check whether the guard can be resolved statically. If it is trivially true, // we ignore it (normalization). If it is impossible to satisfy, we backtrack. switch (TransitionGuard.getKind(guard)) { - case exitZeroWidth: - case escapeZeroWidth: { + case countSet1, countSetMin -> { + bqLastCounterReset[TransitionGuard.getQuantifierIndex(guard)] = transitionGuards.length(); + } + case countLtMin, countGeMin, countLtMax -> { + if (canOmitCounterCheck(guard)) { + return; + } + } + case exitZeroWidth, escapeZeroWidth -> { boolean keptAliveByConsumedInput = false; boolean keptAliveByCaptureGroups = false; if (!transitionGuards.isEmpty() && transitionGuards.peek() == guard) { @@ -1204,76 +1236,151 @@ private void pushTransitionGuard(long guard) { enterFound = true; break; } - if (ast.getOptions().getFlavor().emptyChecksMonitorCaptureGroups() && TransitionGuard.is(tg, TransitionGuard.Kind.updateCG)) { + if (getFlavor().emptyChecksMonitorCaptureGroups() && TransitionGuard.is(tg, TransitionGuard.Kind.updateCG)) { keptAliveByCaptureGroups = true; } } if (!enterFound) { // We did not find any corresponding enterZeroWidth, so exitZeroWidth will // pass because of input being consumed. - keptAliveByConsumedInput = isBuildingDFA() || root.isCharacterClass(); + keptAliveByConsumedInput = isBuildingDFA() || !canMatchEmptyString(root); } boolean keptAlive = keptAliveByConsumedInput || keptAliveByCaptureGroups; - if (isBuildingDFA()) { - // TODO: We should be able to eliminate some of these - // exitZeroWidth/escapeZeroWidth guards even - // when not building a DFA. - if ((TransitionGuard.is(guard, TransitionGuard.Kind.exitZeroWidth) && !keptAlive) || (TransitionGuard.is(guard, TransitionGuard.Kind.escapeZeroWidth) && keptAlive)) { - shouldRetreat = true; + boolean isExit = TransitionGuard.is(guard, TransitionGuard.Kind.exitZeroWidth); + boolean isEscape = TransitionGuard.is(guard, TransitionGuard.Kind.escapeZeroWidth); + int zeroWidthQuantifierIndex = TransitionGuard.getZeroWidthQuantifierIndex(guard); + if (isEscape) { + bqLastZeroWidthEnter[zeroWidthQuantifierIndex] = -1; + } + if ((isExit && !keptAlive) || (isEscape && keptAlive)) { + if (isBuildingDFA() || (isExit && enterFound) || !canMatchEmptyString(root) || root.isMatchFound()) { + setShouldRetreat(); } + } + if (isBuildingDFA() || !canMatchEmptyString(root) || root.isMatchFound() || + (root.isGroup() && root.asGroup().getQuantifier().getZeroWidthIndex() == zeroWidthQuantifierIndex) || + (isEscape && enterFound && !keptAliveByCaptureGroups)) { return; } - break; } - case enterZeroWidth: { - // If there is another enterZeroWidth for the same group in the quantifier guards - // and there are no CG updates in between, then this new enterZeroWidth is - // redundant. - for (int i = transitionGuards.length() - 1; i >= 0; i--) { - long tg = transitionGuards.get(i); - if (ast.getOptions().getFlavor().emptyChecksMonitorCaptureGroups() && TransitionGuard.is(tg, TransitionGuard.Kind.updateCG)) { - break; - } - if (tg == guard) { - return; + case enterZeroWidth -> { + int zeroWidthQuantifierIndex = TransitionGuard.getZeroWidthQuantifierIndex(guard); + if (bqLastZeroWidthEnter[zeroWidthQuantifierIndex] < 0) { + bqLastZeroWidthEnter[zeroWidthQuantifierIndex] = transitionGuards.length(); + } else if (getFlavor().emptyChecksMonitorCaptureGroups()) { + // If there is another enterZeroWidth for the same group in the quantifier + // guards and there are no CG updates in between, then this new enterZeroWidth + // is redundant. + for (int i = transitionGuards.length() - 1; i >= bqLastZeroWidthEnter[zeroWidthQuantifierIndex]; i--) { + if (TransitionGuard.is(transitionGuards.get(i), TransitionGuard.Kind.updateCG)) { + bqLastZeroWidthEnter[zeroWidthQuantifierIndex] = transitionGuards.length(); + break; + } } } - break; } - case checkGroupMatched: - case checkGroupNotMatched: { + case checkGroupMatched, checkGroupNotMatched -> { assert (isBuildingDFA() && getMatchedConditionGroups() != null) == this instanceof ASTStepVisitor; if (isBuildingDFA() && getMatchedConditionGroups() != null) { int referencedGroupNumber = TransitionGuard.getGroupNumber(guard); int groupEndIndex = Group.groupNumberToBoundaryIndexEnd(referencedGroupNumber); boolean groupMatched = (getMatchedConditionGroups().get(referencedGroupNumber) && !captureGroupClears.get(groupEndIndex)) || captureGroupUpdates.get(groupEndIndex); if ((TransitionGuard.is(guard, TransitionGuard.Kind.checkGroupMatched)) != groupMatched) { - shouldRetreat = true; + setShouldRetreat(); } return; } - break; } } transitionGuards.add(guard); } + private boolean canOmitCounterCheck(long guard) { + assert TransitionGuard.is(guard, TransitionGuard.Kind.countLtMin) || TransitionGuard.is(guard, TransitionGuard.Kind.countGeMin) || TransitionGuard.is(guard, TransitionGuard.Kind.countLtMax); + int quantifierIndex = TransitionGuard.getQuantifierIndex(guard); + int min = ast.getQuantifier(quantifierIndex).getMin(); + int max = ast.getQuantifier(quantifierIndex).getMax(); + int minPlus1 = saturatingInc(min); + + long countLtMin = TransitionGuard.createCountLtMin(quantifierIndex); + long countGeMin = TransitionGuard.createCountGeMin(quantifierIndex); + long countLtMax = TransitionGuard.createCountLtMax(quantifierIndex); + long countInc = TransitionGuard.createCountInc(quantifierIndex); + long countSetMin = TransitionGuard.createCountSetMin(quantifierIndex); + long countSet1 = TransitionGuard.createCountSet1(quantifierIndex); + + int counterLow = 0; + int counterHigh = Integer.MAX_VALUE; + for (long existingGuard : transitionGuards) { + if (existingGuard == countLtMin) { + counterHigh = Math.min(counterHigh, min - 1); + } else if (existingGuard == countGeMin) { + counterLow = Math.max(counterLow, min); + } else if (existingGuard == countLtMax) { + counterHigh = Math.min(counterHigh, max - 1); + } else if (existingGuard == countSetMin) { + counterLow = minPlus1; + counterHigh = minPlus1; + } else if (existingGuard == countSet1) { + counterLow = 1; + counterHigh = 1; + } else if (existingGuard == countInc) { + counterLow = saturatingInc(counterLow); + counterHigh = saturatingInc(counterHigh); + } + } + + switch (TransitionGuard.getKind(guard)) { + case countLtMin -> { + if (counterHigh < min) { + return true; + } else if (counterLow >= min) { + setShouldRetreat(); + return true; + } else { + return false; + } + } + case countLtMax -> { + if (counterHigh < max) { + return true; + } else if (counterLow >= max) { + setShouldRetreat(); + return true; + } else { + return false; + } + } + case countGeMin -> { + if (counterLow >= min) { + return true; + } else if (counterHigh < min) { + setShouldRetreat(); + return true; + } else { + return false; + } + } + default -> throw CompilerDirectives.shouldNotReachHere(); + } + } + /// Visited set management private void addLookAroundToVisitedSet() { LookAroundAssertion la = (LookAroundAssertion) cur; - lookAroundVisitiedCount[la.getGlobalSubTreeId()]++; + lookAroundVisitedCount[la.getGlobalSubTreeId()]++; lookAroundsOnPath.set(la.getGlobalSubTreeId()); } private void removeLookAroundFromVisitedSet(long pathElement) { LookAroundAssertion la = (LookAroundAssertion) pathGetNode(pathElement); - if (--lookAroundVisitiedCount[la.getGlobalSubTreeId()] == 0) { + if (--lookAroundVisitedCount[la.getGlobalSubTreeId()] == 0) { lookAroundsOnPath.clear(la.getGlobalSubTreeId()); } } - private boolean nodeVisitsEmpty() { - for (int i : lookAroundVisitiedCount) { + private static boolean isEmpty(int[] array) { + for (int i : array) { if (i != 0) { return false; } @@ -1281,37 +1388,24 @@ private boolean nodeVisitsEmpty() { return true; } - /// insideLoops management - private void registerInsideLoop(Group group) { - if (!ast.getOptions().getFlavor().canHaveEmptyLoopIterations()) { - insideLoops.add(group); - } - } - - private void unregisterInsideLoop(Group group) { - if (!ast.getOptions().getFlavor().canHaveEmptyLoopIterations()) { - insideLoops.remove(group); - } - } - @SuppressWarnings("unused") private void dumpPath() { System.out.println("NEW PATH"); for (int i = 0; i < curPath.length(); i++) { long element = curPath.get(i); - if (pathIsGroup(element)) { + if (PathElement.isGroup(element)) { Group group = (Group) pathGetNode(element); - if (pathIsGroupEnter(element)) { - System.out.printf("ENTER (%d) %s%n", pathGetGroupAltIndex(element), group); - } else if (pathIsGroupExit(element)) { - System.out.printf("EXIT %s%n", group); - } else if (pathIsGroupPassThrough(element)) { - System.out.printf("PASSTHROUGH %s%n", group); + if (PathElement.isGroupEnter(element)) { + System.out.printf("ENTER (%2d) %2d %s%n", PathElement.getGroupAltIndex(element), group.getId(), group); + } else if (PathElement.isGroupExit(element)) { + System.out.printf("EXIT %2d %s%n", group.getId(), group); + } else if (PathElement.isGroupPassThrough(element)) { + System.out.printf("PASSTHROUGH %2d %s%n", group.getId(), group); } else { - System.out.printf("ESCAPE %s%n", group); + System.out.printf("ESCAPE %2d %s%n", group.getId(), group); } } else { - System.out.printf("NODE %s%n", pathGetNode(element)); + System.out.printf("NODE %2d %s%n", PathElement.getNodeId(element), pathGetNode(element)); } } } @@ -1345,4 +1439,99 @@ public int hashCode() { return hashCode; } } + + private static final class PathElement { + + /** + * First field: (short) group alternation index. This value is used to iterate the + * alternations of groups referenced in a group-enter path element.
    + * Since the same group can appear multiple times on the path, we cannot reuse + * {@link Group}'s implementation of {@link RegexASTVisitorIterable}. Therefore, every + * occurrence of a group on the path has its own index for iterating and back-tracking over + * its alternatives. + */ + private static final int PATH_GROUP_ALT_INDEX_OFFSET = 0; + /** + * Second field: (int) id of the path element's {@link RegexASTNode}. + */ + private static final int PATH_NODE_OFFSET = Short.SIZE; + /** + * Third field: group action. Every path element referencing a group must have one of four + * possible group actions: + *
      + *
    • group enter
    • + *
    • group exit
    • + *
    • group pass through
    • + *
    • group escape
    • + *
    + */ + private static final int GROUP_ACTION_OFFSET = Short.SIZE + Integer.SIZE; + private static final long GROUP_ACTION_ENTER = 1L << GROUP_ACTION_OFFSET; + private static final long GROUP_ACTION_EXIT = 1L << GROUP_ACTION_OFFSET + 1; + private static final long GROUP_ACTION_PASS_THROUGH = 1L << GROUP_ACTION_OFFSET + 2; + private static final long GROUP_ACTION_ESCAPE = 1L << GROUP_ACTION_OFFSET + 3; + private static final long GROUP_ACTION_ANY = GROUP_ACTION_ENTER | GROUP_ACTION_EXIT | GROUP_ACTION_PASS_THROUGH | GROUP_ACTION_ESCAPE; + + /** + * Create a new path element containing the given node. + */ + private static long create(RegexASTNode node) { + return (long) node.getId() << PATH_NODE_OFFSET; + } + + private static long createGroupEnter(Group group, int groupAltIndex) { + return create(group) | (groupAltIndex << PathElement.PATH_GROUP_ALT_INDEX_OFFSET) | PathElement.GROUP_ACTION_ENTER; + } + + public static long createGroupPassThrough(Group group, int groupAltIndex) { + return create(group) | (groupAltIndex << PathElement.PATH_GROUP_ALT_INDEX_OFFSET) | PathElement.GROUP_ACTION_PASS_THROUGH; + } + + public static long createGroupExit(Group group) { + return create(group) | PathElement.GROUP_ACTION_EXIT; + } + + public static long createGroupEscape(Group group) { + return create(group) | PathElement.GROUP_ACTION_ESCAPE; + } + + private static int getNodeId(long pathElement) { + return (int) (pathElement >>> PATH_NODE_OFFSET); + } + + /** + * Get the group alternation index of the given path element. + */ + private static int getGroupAltIndex(long pathElement) { + return (short) (pathElement >>> PATH_GROUP_ALT_INDEX_OFFSET); + } + + /** + * Returns {@code true} if the given path element has any group action set. Every path + * element containing a group must have one group action. + */ + private static boolean isGroup(long pathElement) { + return (pathElement & GROUP_ACTION_ANY) != 0; + } + + private static boolean isGroupEnter(long pathElement) { + return (pathElement & GROUP_ACTION_ENTER) != 0; + } + + private static boolean isGroupExit(long pathElement) { + return (pathElement & GROUP_ACTION_EXIT) != 0; + } + + private static boolean isGroupPassThrough(long pathElement) { + return (pathElement & GROUP_ACTION_PASS_THROUGH) != 0; + } + + private static boolean isGroupEscape(long pathElement) { + return (pathElement & GROUP_ACTION_ESCAPE) != 0; + } + + private static boolean isGroupExitOrEscape(long pathElement) { + return (pathElement & (GROUP_ACTION_EXIT | GROUP_ACTION_ESCAPE)) != 0; + } + } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/PropagateDeadFlagVisitor.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/PropagateDeadFlagVisitor.java new file mode 100644 index 000000000000..475eeee1db5a --- /dev/null +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/ast/visitors/PropagateDeadFlagVisitor.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2024, 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 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.regex.tregex.parser.ast.visitors; + +import com.oracle.truffle.regex.tregex.parser.ast.AtomicGroup; +import com.oracle.truffle.regex.tregex.parser.ast.BackReference; +import com.oracle.truffle.regex.tregex.parser.ast.CharacterClass; +import com.oracle.truffle.regex.tregex.parser.ast.Group; +import com.oracle.truffle.regex.tregex.parser.ast.LookAheadAssertion; +import com.oracle.truffle.regex.tregex.parser.ast.LookBehindAssertion; +import com.oracle.truffle.regex.tregex.parser.ast.PositionAssertion; +import com.oracle.truffle.regex.tregex.parser.ast.RegexASTNode; +import com.oracle.truffle.regex.tregex.parser.ast.Sequence; +import com.oracle.truffle.regex.tregex.parser.ast.SubexpressionCall; + +public class PropagateDeadFlagVisitor extends DepthFirstTraversalRegexASTVisitor { + + private int deadFlagsSeen = 0; + + public static void propagateDeadFlag(RegexASTNode runRoot) { + new PropagateDeadFlagVisitor().run(runRoot); + } + + private void mark(RegexASTNode node) { + if (deadFlagsSeen > 0) { + node.markAsDead(); + } + } + + private void incFlagsSeen(RegexASTNode node) { + if (node.isDead()) { + deadFlagsSeen++; + } + } + + private void decFlagsSeen(RegexASTNode node) { + if (node.isDead()) { + deadFlagsSeen--; + } + mark(node); + } + + @Override + protected void visit(BackReference backReference) { + mark(backReference); + } + + @Override + protected void visit(Group group) { + incFlagsSeen(group); + } + + @Override + protected void visit(Sequence sequence) { + incFlagsSeen(sequence); + } + + @Override + protected void visit(SubexpressionCall subexpressionCall) { + mark(subexpressionCall); + } + + @Override + protected void visit(PositionAssertion assertion) { + mark(assertion); + } + + @Override + protected void visit(LookBehindAssertion assertion) { + incFlagsSeen(assertion); + } + + @Override + protected void visit(LookAheadAssertion assertion) { + incFlagsSeen(assertion); + } + + @Override + protected void visit(AtomicGroup atomicGroup) { + incFlagsSeen(atomicGroup); + } + + @Override + protected void visit(CharacterClass characterClass) { + mark(characterClass); + } + + @Override + protected void leave(Group group) { + decFlagsSeen(group); + } + + @Override + protected void leave(Sequence sequence) { + decFlagsSeen(sequence); + } + + @Override + protected void leave(LookBehindAssertion assertion) { + decFlagsSeen(assertion); + } + + @Override + protected void leave(LookAheadAssertion assertion) { + decFlagsSeen(assertion); + } + + @Override + protected void leave(AtomicGroup atomicGroup) { + decFlagsSeen(atomicGroup); + } +} diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/OracleDBRegexLexer.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/OracleDBRegexLexer.java index 1643cd4b2a96..8145263caf97 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/OracleDBRegexLexer.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/OracleDBRegexLexer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -46,6 +46,7 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.regex.RegexSource; import com.oracle.truffle.regex.RegexSyntaxException; +import com.oracle.truffle.regex.RegexSyntaxException.ErrorCode; import com.oracle.truffle.regex.charset.ClassSetContents; import com.oracle.truffle.regex.charset.CodePointSet; import com.oracle.truffle.regex.charset.CodePointSetAccumulator; @@ -137,19 +138,19 @@ protected CodePointSet getPOSIXCharClass(String name) { if (cps != null) { return cps; } - throw syntaxError(OracleDBErrorMessages.INVALID_CHARACTER_CLASS); + throw syntaxError(OracleDBErrorMessages.INVALID_CHARACTER_CLASS, ErrorCode.InvalidCharacterClass); } @Override protected void validatePOSIXCollationElement(String sequence) { assert !JavaStringUtil.isSingleCodePoint(sequence); - throw syntaxError(OracleDBErrorMessages.INVALID_COLLATION_ELEMENT); + throw syntaxError(OracleDBErrorMessages.INVALID_COLLATION_ELEMENT, ErrorCode.InvalidCharacterClass); } @Override protected void validatePOSIXEquivalenceClass(String sequence) { assert !JavaStringUtil.isSingleCodePoint(sequence); - throw syntaxError(OracleDBErrorMessages.INVALID_EQUIVALENCE_CLASS); + throw syntaxError(OracleDBErrorMessages.INVALID_EQUIVALENCE_CLASS, ErrorCode.InvalidCharacterClass); } @Override @@ -277,7 +278,7 @@ protected long boundedQuantifierMaxValue() { @Override protected RegexSyntaxException handleBoundedQuantifierOutOfOrder() { - return syntaxError(OracleDBErrorMessages.INVALID_INTERVAL); + return syntaxError(OracleDBErrorMessages.INVALID_INTERVAL, ErrorCode.InvalidQuantifier); } @Override @@ -297,23 +298,23 @@ protected Token handleBoundedQuantifierOverflow(long min, long max) { if (Long.compareUnsigned(min, max) > 0) { throw handleBoundedQuantifierOutOfOrder(); } - throw syntaxError(OracleDBErrorMessages.INVALID_INTERVAL); + throw syntaxError(OracleDBErrorMessages.INVALID_INTERVAL, ErrorCode.InvalidQuantifier); } @Override protected Token handleBoundedQuantifierOverflowMin(long min, long max) { - throw syntaxError(OracleDBErrorMessages.INVALID_INTERVAL); + throw syntaxError(OracleDBErrorMessages.INVALID_INTERVAL, ErrorCode.InvalidQuantifier); } @Override protected RegexSyntaxException handleCCRangeOutOfOrder(int startPos) { - return syntaxError(OracleDBErrorMessages.INVALID_RANGE); + return syntaxError(OracleDBErrorMessages.INVALID_RANGE, ErrorCode.InvalidCharacterClass); } @Override protected void handleCCRangeWithPredefCharClass(int startPos, ClassSetContents firstAtom, ClassSetContents secondAtom) { if ((firstAtom.isAllowedInRange() || !firstAtom.isCodePointSetOnly()) && secondAtom.isCodePointSetOnly()) { - throw syntaxError(OracleDBErrorMessages.INVALID_RANGE); + throw syntaxError(OracleDBErrorMessages.INVALID_RANGE, ErrorCode.InvalidCharacterClass); } } @@ -334,7 +335,7 @@ protected void handleIncompleteEscapeX() { @Override protected Token handleInvalidBackReference(int reference) { - throw syntaxError(OracleDBErrorMessages.MISSING_GROUP_FOR_BACKREFERENCE); + throw syntaxError(OracleDBErrorMessages.MISSING_GROUP_FOR_BACKREFERENCE, ErrorCode.InvalidBackReference); } @Override @@ -393,7 +394,7 @@ protected void handleUnmatchedRightBrace() { @Override protected RegexSyntaxException handleUnmatchedLeftBracket() { - return syntaxError(OracleDBErrorMessages.UNMATCHED_LEFT_BRACKET); + return syntaxError(OracleDBErrorMessages.UNMATCHED_LEFT_BRACKET, ErrorCode.UnmatchedBracket); } @Override diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/OracleDBRegexParser.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/OracleDBRegexParser.java index 6cbd2955e55c..d2917c730921 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/OracleDBRegexParser.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/OracleDBRegexParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 com.oracle.truffle.regex.RegexLanguage; import com.oracle.truffle.regex.RegexSource; import com.oracle.truffle.regex.RegexSyntaxException; +import com.oracle.truffle.regex.RegexSyntaxException.ErrorCode; import com.oracle.truffle.regex.charset.ClassSetContents; import com.oracle.truffle.regex.charset.CodePointSet; import com.oracle.truffle.regex.charset.CodePointSetAccumulator; @@ -168,7 +169,7 @@ public RegexAST parse() throws RegexSyntaxException { break; case quantifier: if (prevKind == Token.Kind.quantifier) { - throw syntaxError(OracleDBErrorMessages.NESTED_QUANTIFIER); + throw syntaxError(OracleDBErrorMessages.NESTED_QUANTIFIER, ErrorCode.InvalidQuantifier); } if (astBuilder.getCurTerm() == null || prevKind == Token.Kind.captureGroupBegin) { // quantifiers without target are ignored @@ -189,7 +190,7 @@ public RegexAST parse() throws RegexSyntaxException { break; case groupEnd: if (astBuilder.getCurGroup().getParent() instanceof RegexASTRootNode) { - throw syntaxError(OracleDBErrorMessages.UNMATCHED_RIGHT_PARENTHESIS); + throw syntaxError(OracleDBErrorMessages.UNMATCHED_RIGHT_PARENTHESIS, ErrorCode.UnmatchedParenthesis); } astBuilder.popGroup(token); break; @@ -218,7 +219,7 @@ public RegexAST parse() throws RegexSyntaxException { } } if (!astBuilder.curGroupIsRoot()) { - throw syntaxError(OracleDBErrorMessages.UNTERMINATED_GROUP); + throw syntaxError(OracleDBErrorMessages.UNTERMINATED_GROUP, ErrorCode.UnmatchedParenthesis); } if (!literalStringBuffer.isEmpty()) { addLiteralString(literalStringBuffer); @@ -355,7 +356,7 @@ private void addLiteralString(IntArrayBuffer literalStringBuffer) { literalStringBuffer.clear(); } - private RegexSyntaxException syntaxError(String msg) { - return RegexSyntaxException.createPattern(source, msg, lexer.getLastTokenPosition()); + private RegexSyntaxException syntaxError(String msg, ErrorCode errorCode) { + return RegexSyntaxException.createPattern(source, msg, lexer.getLastTokenPosition(), errorCode); } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonFlags.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonFlags.java index 02031d2e8779..b970ab8c3a8e 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonFlags.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonFlags.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -63,7 +63,6 @@ public final class PythonFlags extends AbstractConstantKeysObject { private static final String PROP_IGNORECASE = "IGNORECASE"; private static final String PROP_LOCALE = "LOCALE"; private static final String PROP_MULTILINE = "MULTILINE"; - private static final String PROP_TEMPLATE = "TEMPLATE"; private static final String PROP_UNICODE = "UNICODE"; private static final String PROP_VERBOSE = "VERBOSE"; private static final TruffleReadOnlyKeysArray KEYS = new TruffleReadOnlyKeysArray( @@ -72,16 +71,15 @@ public final class PythonFlags extends AbstractConstantKeysObject { PROP_IGNORECASE, PROP_LOCALE, PROP_MULTILINE, - PROP_TEMPLATE, PROP_UNICODE, PROP_VERBOSE); private final int value; - private static final TBitSet ALL_FLAG_CHARS = TBitSet.valueOf('L', 'a', 'i', 'm', 's', 't', 'u', 'x'); + private static final TBitSet ALL_FLAG_CHARS = TBitSet.valueOf('L', 'a', 'i', 'm', 's', 'u', 'x'); private static final TBitSet TYPE_FLAG_CHARS = TBitSet.valueOf('L', 'a', 'u'); - private static final String FLAGS = "iLmsxatu"; + private static final String FLAGS = "iLmsxau"; private static final int FLAG_IGNORE_CASE = 1; private static final int FLAG_LOCALE = 1 << 1; @@ -89,16 +87,14 @@ public final class PythonFlags extends AbstractConstantKeysObject { private static final int FLAG_DOT_ALL = 1 << 3; private static final int FLAG_VERBOSE = 1 << 4; private static final int FLAG_ASCII = 1 << 5; - private static final int FLAG_TEMPLATE = 1 << 6; - private static final int FLAG_UNICODE = 1 << 7; + private static final int FLAG_UNICODE = 1 << 6; private static final int[] FLAG_LOOKUP = { FLAG_ASCII, 0, 0, 0, 0, 0, 0, 0, FLAG_IGNORE_CASE, 0, 0, FLAG_LOCALE, FLAG_MULTILINE, 0, 0, 0, - 0, 0, FLAG_DOT_ALL, FLAG_TEMPLATE, FLAG_UNICODE, 0, 0, FLAG_VERBOSE + 0, 0, FLAG_DOT_ALL, 0, FLAG_UNICODE, 0, 0, FLAG_VERBOSE }; private static final int TYPE_FLAGS = FLAG_LOCALE | FLAG_ASCII | FLAG_UNICODE; - private static final int GLOBAL_FLAGS = FLAG_TEMPLATE; public static final PythonFlags EMPTY_INSTANCE = new PythonFlags(""); public static final PythonFlags TYPE_FLAGS_INSTANCE = new PythonFlags(TYPE_FLAGS); @@ -151,10 +147,6 @@ public boolean isAscii() { return hasFlag(FLAG_ASCII); } - public boolean isTemplate() { - return hasFlag(FLAG_TEMPLATE); - } - public boolean isUnicodeExplicitlySet() { return hasFlag(FLAG_UNICODE); } @@ -229,10 +221,6 @@ public int numberOfTypeFlags() { return Integer.bitCount(value & TYPE_FLAGS); } - public boolean includesGlobalFlags() { - return (value & GLOBAL_FLAGS) != 0; - } - public boolean overlaps(PythonFlags otherFlags) { return (this.value & otherFlags.value) != 0; } @@ -281,7 +269,6 @@ public boolean isMemberReadableImpl(String symbol) { case PROP_IGNORECASE: case PROP_LOCALE: case PROP_MULTILINE: - case PROP_TEMPLATE: case PROP_UNICODE: case PROP_VERBOSE: return true; @@ -303,8 +290,6 @@ public Object readMemberImpl(String symbol) throws UnknownIdentifierException { return isLocale(); case PROP_MULTILINE: return isMultiLine(); - case PROP_TEMPLATE: - return isTemplate(); case PROP_UNICODE: return isUnicodeExplicitlySet(); case PROP_VERBOSE: diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonFlavor.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonFlavor.java index b71a7665a481..be0e3f6631d4 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonFlavor.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonFlavor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -67,7 +67,7 @@ public final class PythonFlavor extends RegexFlavor { private PythonFlavor() { super(BACKREFERENCES_TO_UNMATCHED_GROUPS_FAIL | NESTED_CAPTURE_GROUPS_KEPT_ON_LOOP_REENTRY | FAILING_EMPTY_CHECKS_DONT_BACKTRACK | USES_LAST_GROUP_RESULT_FIELD | - LOOKBEHINDS_RUN_LEFT_TO_RIGHT | NEEDS_GROUP_START_POSITIONS | HAS_CONDITIONAL_BACKREFERENCES | EMPTY_CHECKS_ON_MANDATORY_LOOP_ITERATIONS); + LOOKBEHINDS_RUN_LEFT_TO_RIGHT | NEEDS_GROUP_START_POSITIONS | HAS_CONDITIONAL_BACKREFERENCES); } @Override diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonRegexLexer.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonRegexLexer.java index 9c03959cdc26..086dee97dfe8 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonRegexLexer.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonRegexLexer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -40,6 +40,8 @@ */ package com.oracle.truffle.regex.tregex.parser.flavors; +import static com.oracle.truffle.regex.tregex.parser.flavors.PythonFlavor.UNICODE; + import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; @@ -51,6 +53,7 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.regex.RegexSource; import com.oracle.truffle.regex.RegexSyntaxException; +import com.oracle.truffle.regex.RegexSyntaxException.ErrorCode; import com.oracle.truffle.regex.UnsupportedRegexException; import com.oracle.truffle.regex.chardata.UnicodeCharacterAliases; import com.oracle.truffle.regex.charset.ClassSetContents; @@ -65,8 +68,6 @@ import com.oracle.truffle.regex.tregex.string.Encodings; import com.oracle.truffle.regex.util.TBitSet; -import static com.oracle.truffle.regex.tregex.parser.flavors.PythonFlavor.UNICODE; - public final class PythonRegexLexer extends RegexLexer { private static final CodePointSet ASCII_WHITESPACE = CodePointSet.createNoDedup(0x09, 0x0d, 0x20, 0x20); @@ -137,7 +138,15 @@ public final class PythonRegexLexer extends RegexLexer { // Similarly as for \S, we will not be able to produce a replacement string for \W. // We will need to construct the set ourselves. CodePointSet alpha = UNICODE.getProperty("General_Category=Letter"); - CodePointSet numericExtras = CodePointSet.createNoDedup(0xf96b, 0xf973, 0xf978, 0xf9b2, 0xf9d1, 0xf9d3, 0xf9fd, 0x2f890); + CodePointSet numericExtras = CodePointSet.createNoDedup( + 0xf96b, 0xf96b, + 0xf973, 0xf973, + 0xf978, 0xf978, + 0xf9b2, 0xf9b2, + 0xf9d1, 0xf9d1, + 0xf9d3, 0xf9d3, + 0xf9fd, 0xf9fd, + 0x2f890, 0x2f890); CodePointSet numeric = UNICODE.getProperty("General_Category=Number").union(numericExtras); CodePointSet wordChars = alpha.union(numeric).union(CodePointSet.create('_')); CodePointSet nonWordChars = wordChars.createInverse(Encodings.UTF_32); @@ -400,12 +409,12 @@ protected long boundedQuantifierMaxValue() { } private RegexSyntaxException handleBadCharacterInGroupName(ParseGroupNameResult result) { - return syntaxErrorAtRel(PyErrorMessages.badCharacterInGroupName(result.groupName), result.groupName.length() + 1); + return syntaxErrorAtRel(PyErrorMessages.badCharacterInGroupName(result.groupName), result.groupName.length() + 1, ErrorCode.InvalidNamedGroup); } @Override protected RegexSyntaxException handleBoundedQuantifierOutOfOrder() { - return syntaxErrorAtAbs(PyErrorMessages.MIN_REPEAT_GREATER_THAN_MAX_REPEAT, getLastTokenPosition() + 1); + return syntaxErrorAtAbs(PyErrorMessages.MIN_REPEAT_GREATER_THAN_MAX_REPEAT, getLastTokenPosition() + 1, ErrorCode.InvalidQuantifier); } @Override @@ -431,12 +440,12 @@ protected Token handleBoundedQuantifierOverflowMin(long min, long max) { @Override protected RegexSyntaxException handleCCRangeOutOfOrder(int rangeStart) { - return syntaxErrorAtAbs(PyErrorMessages.badCharacterRange(pattern.substring(rangeStart, position)), rangeStart); + return syntaxErrorAtAbs(PyErrorMessages.badCharacterRange(pattern.substring(rangeStart, position)), rangeStart, ErrorCode.InvalidCharacterClass); } @Override protected void handleCCRangeWithPredefCharClass(int rangeStart, ClassSetContents firstAtom, ClassSetContents secondAtom) { - throw syntaxErrorAtAbs(PyErrorMessages.badCharacterRange(pattern.substring(rangeStart, position)), rangeStart); + throw syntaxErrorAtAbs(PyErrorMessages.badCharacterRange(pattern.substring(rangeStart, position)), rangeStart, ErrorCode.InvalidCharacterClass); } @Override @@ -461,18 +470,18 @@ protected RegexSyntaxException handleComplementOfStringSet() { @Override protected void handleGroupRedefinition(String name, int newId, int oldId) { - throw syntaxErrorAtRel(PyErrorMessages.redefinitionOfGroupName(name, newId, oldId), name.length() + 1); + throw syntaxErrorAtRel(PyErrorMessages.redefinitionOfGroupName(name, newId, oldId), name.length() + 1, ErrorCode.InvalidNamedGroup); } @Override protected void handleIncompleteEscapeX() { - throw syntaxError(PyErrorMessages.incompleteEscape(substring(2 + count(RegexLexer::isHexDigit)))); + throw syntaxError(PyErrorMessages.incompleteEscape(substring(2 + count(RegexLexer::isHexDigit))), ErrorCode.InvalidEscape); } @Override protected Token handleInvalidBackReference(int reference) { String ref = Integer.toString(reference); - throw syntaxErrorAtRel(PyErrorMessages.invalidGroupReference(ref), ref.length()); + throw syntaxErrorAtRel(PyErrorMessages.invalidGroupReference(ref), ref.length(), ErrorCode.InvalidBackReference); } @Override @@ -483,7 +492,7 @@ protected RegexSyntaxException handleInvalidCharInCharClass() { @Override protected RegexSyntaxException handleInvalidGroupBeginQ() { retreat(); - return syntaxErrorAtAbs(PyErrorMessages.unknownExtensionQ(curChar()), getLastTokenPosition() + 1); + return syntaxErrorAtAbs(PyErrorMessages.unknownExtensionQ(curChar()), getLastTokenPosition() + 1, ErrorCode.InvalidGroup); } @Override @@ -498,7 +507,7 @@ protected RegexSyntaxException handleMissingClassSetOperand(ClassSetOperator ope @Override protected void handleOctalOutOfRange() { - throw syntaxError(PyErrorMessages.invalidOctalEscape(substring(4))); + throw syntaxError(PyErrorMessages.invalidOctalEscape(substring(4)), ErrorCode.InvalidEscape); } @Override @@ -508,17 +517,17 @@ protected RegexSyntaxException handleRangeAsClassSetOperand(ClassSetOperator ope @Override protected void handleUnfinishedEscape() { - throw syntaxError(PyErrorMessages.BAD_ESCAPE_END_OF_PATTERN); + throw syntaxError(PyErrorMessages.BAD_ESCAPE_END_OF_PATTERN, ErrorCode.InvalidEscape); } @Override protected void handleUnfinishedGroupComment() { - throw syntaxError(PyErrorMessages.UNTERMINATED_COMMENT); + throw syntaxError(PyErrorMessages.UNTERMINATED_COMMENT, ErrorCode.UnmatchedParenthesis); } @Override protected RegexSyntaxException handleUnfinishedGroupQ() { - return syntaxErrorHere(PyErrorMessages.UNEXPECTED_END_OF_PATTERN); + return syntaxErrorHere(PyErrorMessages.UNEXPECTED_END_OF_PATTERN, ErrorCode.UnmatchedParenthesis); } @Override @@ -533,7 +542,7 @@ protected void handleUnmatchedRightBrace() { @Override protected RegexSyntaxException handleUnmatchedLeftBracket() { - return syntaxErrorAtAbs(PyErrorMessages.UNTERMINATED_CHARACTER_SET, getLastCharacterClassBeginPosition()); + return syntaxErrorAtAbs(PyErrorMessages.UNTERMINATED_CHARACTER_SET, getLastCharacterClassBeginPosition(), ErrorCode.UnmatchedBracket); } @Override @@ -585,42 +594,42 @@ protected int parseCustomEscapeChar(char c, boolean inCharClass) { } int length = countUpTo(RegexLexer::isHexDigit, escapeLength); if (length != escapeLength) { - throw syntaxError(PyErrorMessages.incompleteEscape(substring(2 + length))); + throw syntaxError(PyErrorMessages.incompleteEscape(substring(2 + length)), ErrorCode.InvalidEscape); } advance(length); try { int codePoint = Integer.parseInt(pattern, position - length, position, 16); if (codePoint > 0x10FFFF) { - throw syntaxError(PyErrorMessages.invalidUnicodeEscape(substring(2 + length))); + throw syntaxError(PyErrorMessages.invalidUnicodeEscape(substring(2 + length)), ErrorCode.InvalidEscape); } return codePoint; } catch (NumberFormatException e) { - throw syntaxError(PyErrorMessages.incompleteEscape(substring(2 + length))); + throw syntaxError(PyErrorMessages.incompleteEscape(substring(2 + length)), ErrorCode.InvalidEscape); } } else { // \\u or \\U in 'bytes' patterns - throw syntaxError(PyErrorMessages.badEscape(c)); + throw syntaxError(PyErrorMessages.badEscape(c), ErrorCode.InvalidEscape); } case 'N': { if (mode != PythonREMode.Str) { - throw syntaxError(PyErrorMessages.badEscape(c)); + throw syntaxError(PyErrorMessages.badEscape(c), ErrorCode.InvalidEscape); } if (!consumingLookahead("{")) { - throw syntaxErrorHere(PyErrorMessages.missing("{")); + throw syntaxErrorHere(PyErrorMessages.missing("{"), ErrorCode.InvalidEscape); } int nameStart = position; int nameEnd = pattern.indexOf('}', position); if (atEnd() || nameEnd == position) { - throw syntaxErrorHere(PyErrorMessages.missing("character name")); + throw syntaxErrorHere(PyErrorMessages.missing("character name"), ErrorCode.InvalidEscape); } if (nameEnd < 0) { - throw syntaxErrorHere(PyErrorMessages.missingUnterminatedName('}')); + throw syntaxErrorHere(PyErrorMessages.missingUnterminatedName('}'), ErrorCode.InvalidEscape); } String characterName = pattern.substring(nameStart, nameEnd); position = nameEnd + 1; int codePoint = lookupCharacterByName(characterName); if (codePoint == -1) { - throw syntaxError(PyErrorMessages.undefinedCharacterName(characterName)); + throw syntaxError(PyErrorMessages.undefinedCharacterName(characterName), ErrorCode.InvalidEscape); } return codePoint; } @@ -632,7 +641,7 @@ protected int parseCustomEscapeChar(char c, boolean inCharClass) { @Override protected int parseCustomEscapeCharFallback(int c, boolean inCharClass) { if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9') { - throw syntaxError(PyErrorMessages.badEscape(c)); + throw syntaxError(PyErrorMessages.badEscape(c), ErrorCode.InvalidEscape); } return c; } @@ -649,9 +658,9 @@ protected Token parseCustomGroupBeginQ(char charAfterQuestionMark) { ParseGroupNameResult result = parseGroupName('>'); switch (result.state) { case empty: - throw syntaxErrorHere(PyErrorMessages.MISSING_GROUP_NAME); + throw syntaxErrorHere(PyErrorMessages.MISSING_GROUP_NAME, ErrorCode.InvalidNamedGroup); case unterminated: - throw syntaxErrorAtAbs(PyErrorMessages.UNTERMINATED_NAME_ANGLE_BRACKET, pos); + throw syntaxErrorAtAbs(PyErrorMessages.UNTERMINATED_NAME_ANGLE_BRACKET, pos, ErrorCode.InvalidNamedGroup); case invalidStart: case invalidRest: throw handleBadCharacterInGroupName(result); @@ -667,7 +676,7 @@ protected Token parseCustomGroupBeginQ(char charAfterQuestionMark) { return parseNamedBackReference(); } default: - throw syntaxErrorAtRel(PyErrorMessages.unknownExtensionP(ch2), 3); + throw syntaxErrorAtRel(PyErrorMessages.unknownExtensionP(ch2), 3, ErrorCode.InvalidGroup); } } case '>': @@ -681,7 +690,6 @@ protected Token parseCustomGroupBeginQ(char charAfterQuestionMark) { case 's': case 'x': case 'a': - case 't': case 'u': return parseInlineFlags(charAfterQuestionMark); default: @@ -692,9 +700,9 @@ protected Token parseCustomGroupBeginQ(char charAfterQuestionMark) { @Override protected Token parseGroupLt() { if (atEnd()) { - throw syntaxErrorHere(PyErrorMessages.UNEXPECTED_END_OF_PATTERN); + throw syntaxErrorHere(PyErrorMessages.UNEXPECTED_END_OF_PATTERN, ErrorCode.InvalidGroup); } - throw syntaxErrorAtAbs(PyErrorMessages.unknownExtensionLt(curChar()), getLastTokenPosition() + 1); + throw syntaxErrorAtAbs(PyErrorMessages.unknownExtensionLt(curChar()), getLastTokenPosition() + 1, ErrorCode.InvalidGroup); } /** @@ -706,9 +714,9 @@ private Token parseConditionalBackReference() { ParseGroupNameResult result = parseGroupName(')'); switch (result.state) { case empty: - throw syntaxErrorHere(PyErrorMessages.MISSING_GROUP_NAME); + throw syntaxErrorHere(PyErrorMessages.MISSING_GROUP_NAME, ErrorCode.InvalidNamedGroup); case unterminated: - throw syntaxErrorAtRel(PyErrorMessages.UNTERMINATED_NAME, result.groupName.length()); + throw syntaxErrorAtRel(PyErrorMessages.UNTERMINATED_NAME, result.groupName.length(), ErrorCode.InvalidNamedGroup); case invalidStart: case invalidRest: position -= result.groupName.length() + 1; @@ -723,9 +731,9 @@ private Token parseConditionalBackReference() { assert curChar() == ')'; advance(); if (groupNumber == 0) { - throw syntaxErrorAtRel(PyErrorMessages.BAD_GROUP_NUMBER, result.groupName.length() + 1); + throw syntaxErrorAtRel(PyErrorMessages.BAD_GROUP_NUMBER, result.groupName.length() + 1, ErrorCode.InvalidBackReference); } else if (groupNumber == -1) { - throw syntaxErrorAtRel(PyErrorMessages.invalidGroupReference(result.groupName), result.groupName.length() + 1); + throw syntaxErrorAtRel(PyErrorMessages.invalidGroupReference(result.groupName), result.groupName.length() + 1, ErrorCode.InvalidBackReference); } break; case valid: @@ -734,7 +742,7 @@ private Token parseConditionalBackReference() { groupNumber = getSingleNamedGroupNumber(result.groupName); namedReference = true; } else { - throw syntaxErrorAtRel(PyErrorMessages.unknownGroupName(result.groupName, mode), result.groupName.length() + 1); + throw syntaxErrorAtRel(PyErrorMessages.unknownGroupName(result.groupName, mode), result.groupName.length() + 1, ErrorCode.InvalidBackReference); } break; default: @@ -758,52 +766,43 @@ private Token parseInlineFlags(int ch0) { case ')': return Token.createInlineFlags(positiveFlags, true); case ':': - if (positiveFlags.includesGlobalFlags()) { - throw syntaxErrorAtRel(PyErrorMessages.INLINE_FLAGS_CANNOT_TURN_ON_GLOBAL_FLAG, 1); - } return parseLocalFlags(positiveFlags, PythonFlags.EMPTY_INSTANCE); case '-': - if (positiveFlags.includesGlobalFlags()) { - throw syntaxErrorAtRel(PyErrorMessages.INLINE_FLAGS_CANNOT_TURN_ON_GLOBAL_FLAG, 1); - } if (atEnd()) { - throw syntaxErrorHere(PyErrorMessages.MISSING_FLAG); + throw syntaxErrorHere(PyErrorMessages.MISSING_FLAG, ErrorCode.InvalidInlineFlag); } ch = consumeChar(); if (!PythonFlags.isValidFlagChar(ch)) { if (Character.isAlphabetic(ch)) { - throw syntaxErrorAtRel(PyErrorMessages.UNKNOWN_FLAG, 1); + throw syntaxErrorAtRel(PyErrorMessages.UNKNOWN_FLAG, 1, ErrorCode.InvalidInlineFlag); } else { - throw syntaxErrorAtRel(PyErrorMessages.MISSING_FLAG, 1); + throw syntaxErrorAtRel(PyErrorMessages.MISSING_FLAG, 1, ErrorCode.InvalidInlineFlag); } } PythonFlags negativeFlags = PythonFlags.EMPTY_INSTANCE; while (PythonFlags.isValidFlagChar(ch)) { negativeFlags = negativeFlags.addFlag(ch); if (PythonFlags.isTypeFlagChar(ch)) { - throw syntaxErrorHere(PyErrorMessages.INLINE_FLAGS_CANNOT_TURN_OFF_FLAGS_A_U_AND_L); + throw syntaxErrorHere(PyErrorMessages.INLINE_FLAGS_CANNOT_TURN_OFF_FLAGS_A_U_AND_L, ErrorCode.InvalidInlineFlag); } if (atEnd()) { - throw syntaxErrorHere(PyErrorMessages.MISSING_COLON); + throw syntaxErrorHere(PyErrorMessages.MISSING_COLON, ErrorCode.InvalidInlineFlag); } ch = consumeChar(); } if (ch != ':') { if (Character.isAlphabetic(ch)) { - throw syntaxErrorAtRel(PyErrorMessages.UNKNOWN_FLAG, 1); + throw syntaxErrorAtRel(PyErrorMessages.UNKNOWN_FLAG, 1, ErrorCode.InvalidInlineFlag); } else { - throw syntaxErrorAtRel(PyErrorMessages.MISSING_COLON, 1); + throw syntaxErrorAtRel(PyErrorMessages.MISSING_COLON, 1, ErrorCode.InvalidInlineFlag); } } - if (negativeFlags.includesGlobalFlags()) { - throw syntaxErrorAtRel(PyErrorMessages.INLINE_FLAGS_CANNOT_TURN_OFF_GLOBAL_FLAG, 1); - } return parseLocalFlags(positiveFlags, negativeFlags); default: if (Character.isAlphabetic(ch)) { - throw syntaxErrorAtRel(PyErrorMessages.UNKNOWN_FLAG, 1); + throw syntaxErrorAtRel(PyErrorMessages.UNKNOWN_FLAG, 1, ErrorCode.InvalidInlineFlag); } else { - throw syntaxErrorAtRel(PyErrorMessages.MISSING_DASH_COLON_PAREN, 1); + throw syntaxErrorAtRel(PyErrorMessages.MISSING_DASH_COLON_PAREN, 1, ErrorCode.InvalidInlineFlag); } } } @@ -811,16 +810,16 @@ private Token parseInlineFlags(int ch0) { private PythonFlags addFlag(PythonFlags flagsArg, int ch) { PythonFlags flags = flagsArg.addFlag(ch); if (mode == PythonREMode.Str && ch == 'L') { - throw syntaxErrorHere(PyErrorMessages.INLINE_FLAGS_CANNOT_USE_L_FLAG_WITH_A_STR_PATTERN); + throw syntaxErrorHere(PyErrorMessages.INLINE_FLAGS_CANNOT_USE_L_FLAG_WITH_A_STR_PATTERN, ErrorCode.InvalidInlineFlag); } if (mode == PythonREMode.Bytes && ch == 'u') { - throw syntaxErrorHere(PyErrorMessages.INLINE_FLAGS_CANNOT_USE_U_FLAG_WITH_A_BYTES_PATTERN); + throw syntaxErrorHere(PyErrorMessages.INLINE_FLAGS_CANNOT_USE_U_FLAG_WITH_A_BYTES_PATTERN, ErrorCode.InvalidInlineFlag); } if (flags.numberOfTypeFlags() > 1) { - throw syntaxErrorHere(PyErrorMessages.INLINE_FLAGS_FLAGS_A_U_AND_L_ARE_INCOMPATIBLE); + throw syntaxErrorHere(PyErrorMessages.INLINE_FLAGS_FLAGS_A_U_AND_L_ARE_INCOMPATIBLE, ErrorCode.InvalidInlineFlag); } if (atEnd()) { - throw syntaxErrorHere(PyErrorMessages.MISSING_DASH_COLON_PAREN); + throw syntaxErrorHere(PyErrorMessages.MISSING_DASH_COLON_PAREN, ErrorCode.InvalidInlineFlag); } return flags; } @@ -834,7 +833,7 @@ private PythonFlags addFlag(PythonFlags flagsArg, int ch) { */ private Token parseLocalFlags(PythonFlags positiveFlags, PythonFlags negativeFlags) { if (positiveFlags.overlaps(negativeFlags)) { - throw syntaxErrorAtRel(PyErrorMessages.INLINE_FLAGS_FLAG_TURNED_ON_AND_OFF, 1); + throw syntaxErrorAtRel(PyErrorMessages.INLINE_FLAGS_FLAG_TURNED_ON_AND_OFF, 1, ErrorCode.InvalidInlineFlag); } PythonFlags newFlags = getLocalFlags().addFlags(positiveFlags).delFlags(negativeFlags); if (positiveFlags.numberOfTypeFlags() > 0) { @@ -846,7 +845,7 @@ private Token parseLocalFlags(PythonFlags positiveFlags, PythonFlags negativeFla private void mustHaveMore() { if (atEnd()) { - throw syntaxErrorHere(PyErrorMessages.UNEXPECTED_END_OF_PATTERN); + throw syntaxErrorHere(PyErrorMessages.UNEXPECTED_END_OF_PATTERN, ErrorCode.InvalidGroup); } } @@ -857,9 +856,9 @@ private Token parseNamedBackReference() { ParseGroupNameResult result = parseGroupName(')'); switch (result.state) { case empty: - throw syntaxErrorHere(PyErrorMessages.MISSING_GROUP_NAME); + throw syntaxErrorHere(PyErrorMessages.MISSING_GROUP_NAME, ErrorCode.InvalidBackReference); case unterminated: - throw syntaxErrorAtRel(PyErrorMessages.UNTERMINATED_NAME, result.groupName.length()); + throw syntaxErrorAtRel(PyErrorMessages.UNTERMINATED_NAME, result.groupName.length(), ErrorCode.InvalidBackReference); case invalidStart: case invalidRest: throw handleBadCharacterInGroupName(result); @@ -868,7 +867,7 @@ private Token parseNamedBackReference() { assert namedCaptureGroups.get(result.groupName).size() == 1; return Token.createBackReference(namedCaptureGroups.get(result.groupName).get(0), true); } else { - throw syntaxErrorAtRel(PyErrorMessages.unknownGroupName(result.groupName, mode), result.groupName.length() + 1); + throw syntaxErrorAtRel(PyErrorMessages.unknownGroupName(result.groupName, mode), result.groupName.length() + 1, ErrorCode.InvalidBackReference); } default: throw CompilerDirectives.shouldNotReachHere(); @@ -879,16 +878,16 @@ private String substring(int length) { return pattern.substring(getLastAtomPosition(), getLastAtomPosition() + length); } - public RegexSyntaxException syntaxErrorAtAbs(String msg, int i) { - return RegexSyntaxException.createPattern(source, msg, i); + public RegexSyntaxException syntaxErrorAtAbs(String msg, int i, ErrorCode errorCode) { + return RegexSyntaxException.createPattern(source, msg, i, errorCode); } - private RegexSyntaxException syntaxErrorAtRel(String msg, int i) { - return RegexSyntaxException.createPattern(source, msg, position - i); + private RegexSyntaxException syntaxErrorAtRel(String msg, int i, ErrorCode errorCode) { + return RegexSyntaxException.createPattern(source, msg, position - i, errorCode); } - public RegexSyntaxException syntaxErrorHere(String msg) { - return RegexSyntaxException.createPattern(source, msg, position); + public RegexSyntaxException syntaxErrorHere(String msg, ErrorCode errorCode) { + return RegexSyntaxException.createPattern(source, msg, position, errorCode); } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonRegexParser.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonRegexParser.java index 9bb36b627230..b428365dee2a 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonRegexParser.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/PythonRegexParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -50,6 +50,8 @@ import com.oracle.truffle.regex.RegexLanguage; import com.oracle.truffle.regex.RegexSource; import com.oracle.truffle.regex.RegexSyntaxException; +import com.oracle.truffle.regex.RegexSyntaxException.ErrorCode; +import com.oracle.truffle.regex.charset.ClassSetContents; import com.oracle.truffle.regex.charset.CodePointSet; import com.oracle.truffle.regex.charset.CodePointSetAccumulator; import com.oracle.truffle.regex.charset.Constants; @@ -75,6 +77,7 @@ public final class PythonRegexParser implements RegexParser { private final PythonRegexLexer lexer; private final RegexASTBuilder astBuilder; private final CodePointSetAccumulator curCharClass = new CodePointSetAccumulator(); + private final CodePointSetAccumulator curCharClassCaseClosure = new CodePointSetAccumulator(); public PythonRegexParser(RegexLanguage language, RegexSource source, CompilationBuffer compilationBuffer) throws RegexSyntaxException { this.mode = PythonREMode.fromEncoding(source.getEncoding()); @@ -194,16 +197,16 @@ public RegexAST parse() throws RegexSyntaxException { break; case quantifier: if (prevKind == Token.Kind.quantifier) { - throw syntaxError(PyErrorMessages.MULTIPLE_REPEAT); + throw syntaxError(PyErrorMessages.MULTIPLE_REPEAT, ErrorCode.InvalidQuantifier); } if (astBuilder.getCurTerm() == null || !QUANTIFIER_PREV.contains(prevKind)) { - throw syntaxError(PyErrorMessages.NOTHING_TO_REPEAT); + throw syntaxError(PyErrorMessages.NOTHING_TO_REPEAT, ErrorCode.InvalidQuantifier); } astBuilder.addQuantifier((Token.Quantifier) token); break; case alternation: if (astBuilder.getCurGroup().isConditionalBackReferenceGroup() && astBuilder.getCurGroup().getAlternatives().size() == 2) { - throw syntaxError(PyErrorMessages.CONDITIONAL_BACKREF_WITH_MORE_THAN_TWO_BRANCHES); + throw syntaxError(PyErrorMessages.CONDITIONAL_BACKREF_WITH_MORE_THAN_TWO_BRANCHES, ErrorCode.InvalidBackReference); } astBuilder.nextSequence(); break; @@ -224,7 +227,7 @@ public RegexAST parse() throws RegexSyntaxException { break; case groupEnd: if (astBuilder.getCurGroup().getParent() instanceof RegexASTRootNode) { - throw syntaxError(PyErrorMessages.UNBALANCED_PARENTHESIS); + throw syntaxError(PyErrorMessages.UNBALANCED_PARENTHESIS, ErrorCode.UnmatchedParenthesis); } if (astBuilder.getCurGroup().isLocalFlags()) { lexer.popLocalFlags(); @@ -243,14 +246,22 @@ public RegexAST parse() throws RegexSyntaxException { break; case charClassBegin: curCharClass.clear(); + curCharClassCaseClosure.clear(); break; case charClassAtom: - curCharClass.addSet(((Token.CharacterClassAtom) token).getContents().getCodePointSet()); + ClassSetContents contents = ((Token.CharacterClassAtom) token).getContents(); + if (lexer.featureEnabledIgnoreCase() && !contents.isCharacterClass()) { + curCharClassCaseClosure.addSet(contents.getCodePointSet()); + } else { + curCharClass.addSet(contents.getCodePointSet()); + } break; case charClassEnd: - boolean wasSingleChar = !lexer.isCurCharClassInverted() && curCharClass.matchesSingleChar(); + boolean wasSingleChar = !lexer.isCurCharClassInverted() && + (curCharClass.matchesSingleChar() && curCharClassCaseClosure.isEmpty() || curCharClass.isEmpty() && curCharClassCaseClosure.matchesSingleChar()); if (lexer.featureEnabledIgnoreCase()) { - lexer.caseFoldUnfold(curCharClass); + lexer.caseFoldUnfold(curCharClassCaseClosure); + curCharClass.addSet(curCharClassCaseClosure.get()); } CodePointSet cps = curCharClass.toCodePointSet(); astBuilder.addCharClass(lexer.isCurCharClassInverted() ? cps.createInverse(lexer.source.getEncoding()) : cps, wasSingleChar); @@ -266,12 +277,11 @@ public RegexAST parse() throws RegexSyntaxException { if (inlineFlags.isGlobal()) { boolean first = prev == null || (prevKind == Token.Kind.inlineFlags && ((Token.InlineFlags) prev).isGlobal()); if (!first) { - throw syntaxErrorAtAbs(PyErrorMessages.GLOBAL_FLAGS_NOT_AT_START, inlineFlags.getPosition()); + throw syntaxErrorAtAbs(PyErrorMessages.GLOBAL_FLAGS_NOT_AT_START, inlineFlags.getPosition(), ErrorCode.InvalidInlineFlag); } lexer.addGlobalFlags((PythonFlags) inlineFlags.getFlags()); } else { astBuilder.pushGroup(inlineFlags); - astBuilder.getCurGroup().setLocalFlags(true); lexer.pushLocalFlags((PythonFlags) inlineFlags.getFlags()); } break; @@ -282,13 +292,14 @@ public RegexAST parse() throws RegexSyntaxException { astBuilder.addDollar(); } if (!astBuilder.curGroupIsRoot()) { - throw syntaxErrorAtAbs(PyErrorMessages.UNTERMINATED_SUBPATTERN, astBuilder.getCurGroupStartPosition()); + throw syntaxErrorAtAbs(PyErrorMessages.UNTERMINATED_SUBPATTERN, astBuilder.getCurGroupStartPosition(), ErrorCode.UnmatchedParenthesis); } RegexAST ast = astBuilder.popRootGroup(); for (Token.BackReference conditionalBackReference : conditionalBackReferences) { assert conditionalBackReference.getGroupNumbers().length == 1; if (conditionalBackReference.getGroupNumbers()[0] >= ast.getNumberOfCaptureGroups()) { - throw syntaxErrorAtAbs(PyErrorMessages.invalidGroupReference(Integer.toString(conditionalBackReference.getGroupNumbers()[0])), conditionalBackReference.getPosition() + 3); + throw syntaxErrorAtAbs(PyErrorMessages.invalidGroupReference(Integer.toString(conditionalBackReference.getGroupNumbers()[0])), conditionalBackReference.getPosition() + 3, + ErrorCode.InvalidBackReference); } } lexer.fixFlags(); @@ -323,7 +334,7 @@ private void verifyGroupReference(Token.BackReference backRefToken) throws Regex // references but also when a forward reference is made. if (conditional && insideLookBehind) { if (groupNumber >= lexer.numberOfCaptureGroupsSoFar()) { - throw syntaxErrorHere(PyErrorMessages.CANNOT_REFER_TO_AN_OPEN_GROUP); + throw syntaxErrorHere(PyErrorMessages.CANNOT_REFER_TO_AN_OPEN_GROUP, ErrorCode.InvalidBackReference); } } if (!conditional || insideLookBehind) { @@ -331,7 +342,7 @@ private void verifyGroupReference(Token.BackReference backRefToken) throws Regex while (parent != null) { if (parent instanceof Group && ((Group) parent).getGroupNumber() == groupNumber) { int errorPosition = backRefToken.isNamedReference() ? backRefToken.getPosition() + 4 : backRefToken.getPosition(); - throw syntaxErrorAtAbs(PyErrorMessages.CANNOT_REFER_TO_AN_OPEN_GROUP, errorPosition); + throw syntaxErrorAtAbs(PyErrorMessages.CANNOT_REFER_TO_AN_OPEN_GROUP, errorPosition, ErrorCode.InvalidBackReference); } parent = parent.getParent(); } @@ -346,8 +357,8 @@ private void verifyGroupReference(Token.BackReference backRefToken) throws Regex // other error that appears later in the expression. In such cases, we would not be // compatible with CPython error messages. while (parent != null) { - if (parent instanceof LookBehindAssertion && ((LookBehindAssertion) parent).getGroup().getEnclosedCaptureGroupsLow() <= groupNumber) { - throw syntaxErrorHere(PyErrorMessages.CANNOT_REFER_TO_GROUP_DEFINED_IN_THE_SAME_LOOKBEHIND_SUBPATTERN); + if (parent instanceof LookBehindAssertion && ((LookBehindAssertion) parent).getGroup().getEnclosedCaptureGroupsLo() <= groupNumber) { + throw syntaxErrorHere(PyErrorMessages.CANNOT_REFER_TO_GROUP_DEFINED_IN_THE_SAME_LOOKBEHIND_SUBPATTERN, ErrorCode.InvalidBackReference); } parent = parent.getSubTreeParent(); } @@ -366,15 +377,15 @@ private boolean insideLookBehind() { return insideLookBehind; } - private RegexSyntaxException syntaxError(String msg) { - return lexer.syntaxError(msg); + private RegexSyntaxException syntaxError(String msg, ErrorCode errorCode) { + return lexer.syntaxError(msg, errorCode); } - private RegexSyntaxException syntaxErrorHere(String msg) { - return lexer.syntaxErrorHere(msg); + private RegexSyntaxException syntaxErrorHere(String msg, ErrorCode errorCode) { + return lexer.syntaxErrorHere(msg, errorCode); } - private RegexSyntaxException syntaxErrorAtAbs(String msg, int i) { - return lexer.syntaxErrorAtAbs(msg, i); + private RegexSyntaxException syntaxErrorAtAbs(String msg, int i, ErrorCode errorCode) { + return lexer.syntaxErrorAtAbs(msg, i, errorCode); } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/RubyFlavor.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/RubyFlavor.java index 580e10ba0012..a2f28365b202 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/RubyFlavor.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/RubyFlavor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -228,7 +228,7 @@ public final class RubyFlavor extends RegexFlavor { private RubyFlavor() { super(BACKREFERENCES_TO_UNMATCHED_GROUPS_FAIL | EMPTY_CHECKS_MONITOR_CAPTURE_GROUPS | NESTED_CAPTURE_GROUPS_KEPT_ON_LOOP_REENTRY | FAILING_EMPTY_CHECKS_DONT_BACKTRACK | - HAS_CONDITIONAL_BACKREFERENCES | EMPTY_CHECKS_ON_MANDATORY_LOOP_ITERATIONS); + HAS_CONDITIONAL_BACKREFERENCES | EMPTY_CHECKS_ON_MANDATORY_LOOP_ITERATIONS | LOOKBEHINDS_RUN_LEFT_TO_RIGHT); } @Override diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/RubyRegexParser.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/RubyRegexParser.java index af3a7df40664..2258a58f8c49 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/RubyRegexParser.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/RubyRegexParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -55,6 +55,7 @@ import java.util.Optional; import java.util.function.IntPredicate; +import com.oracle.truffle.regex.RegexSyntaxException.ErrorCode; import org.graalvm.collections.Pair; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; @@ -98,9 +99,6 @@ public final class RubyRegexParser implements RegexValidator, RegexParser { // This is the same as above but restricted to ASCII. private static final Map ASCII_POSIX_CHAR_CLASSES; - // The [\n\r] CodePointSet. - private static final CodePointSet NEWLINE_RETURN = CodePointSet.create('\n', '\n', '\r', '\r'); - // CodePointSet constants used for expanding the \R escape sequence. private static final CodePointSet UNICODE_LINE_BREAKS = CodePointSet.create(0x0a, 0x0c, 0x85, 0x85, 0x2028, 0x2029); private static final CodePointSet ASCII_LINE_BREAKS = CodePointSet.create(0x0a, 0x0c); @@ -477,7 +475,7 @@ private String getUpTo(int count, IntPredicate pred) { private void advance() { if (atEnd()) { - throw syntaxErrorAtEnd(RbErrorMessages.UNEXPECTED_END_OF_PATTERN); + throw syntaxErrorAtEnd(RbErrorMessages.UNEXPECTED_END_OF_PATTERN, ErrorCode.UnfinishedSequence); } advance(1); } @@ -499,10 +497,9 @@ private boolean match(String next) { } } - private void mustMatch(String next) { - assert "}".equals(next) || ")".equals(next); + private void mustMatch(String next, String errorMsg, ErrorCode errorCode) { if (!match(next)) { - throw syntaxErrorHere("}".equals(next) ? RbErrorMessages.EXPECTED_BRACE : RbErrorMessages.EXPECTED_PAREN); + throw syntaxErrorHere(errorMsg, errorCode); } } @@ -618,16 +615,16 @@ private void addDeadNode() { // Error reporting - private RegexSyntaxException syntaxErrorAtEnd(String message) { - return RegexSyntaxException.createPattern(inSource, message, inPattern.length() - 1); + private RegexSyntaxException syntaxErrorAtEnd(String message, ErrorCode errorCode) { + return RegexSyntaxException.createPattern(inSource, message, inPattern.length() - 1, errorCode); } - private RegexSyntaxException syntaxErrorHere(String message) { - return RegexSyntaxException.createPattern(inSource, message, position); + private RegexSyntaxException syntaxErrorHere(String message, ErrorCode errorCode) { + return RegexSyntaxException.createPattern(inSource, message, position, errorCode); } - private RegexSyntaxException syntaxErrorAt(String message, int pos) { - return RegexSyntaxException.createPattern(inSource, message, pos); + private RegexSyntaxException syntaxErrorAt(String message, int pos, ErrorCode errorCode) { + return RegexSyntaxException.createPattern(inSource, message, pos, errorCode); } // First pass - identifying capture groups @@ -742,7 +739,7 @@ private void run() { if (!atEnd()) { assert curChar() == ')'; - throw syntaxErrorHere(RbErrorMessages.UNBALANCED_PARENTHESIS); + throw syntaxErrorHere(RbErrorMessages.UNBALANCED_PARENTHESIS, ErrorCode.UnmatchedParenthesis); } } @@ -778,6 +775,7 @@ private void disjunction(boolean toplevel) { } private void disjunction() { + canHaveQuantifier = false; disjunction(false); } @@ -1151,17 +1149,19 @@ private boolean assertionEscape() { addCaret(); return true; case 'Z': - // (?:$|(?=[\r\n]$)) + notAllowedInLookbehind(restorePosition); + // (?:$|(?=[\n]$)) pushGroup(); // (?: addDollar(); // $ nextSequence(); // | pushLookAheadAssertion(false); // (?= - addCharClass(NEWLINE_RETURN); // [\r\n] + addCharClass(CodePointSet.create('\n')); // [\n] addDollar(); // $ popGroup(); // ) popGroup(); // ) return true; case 'z': + notAllowedInLookbehind(restorePosition); addDollar(); return true; case 'G': @@ -1319,14 +1319,12 @@ private boolean backreference() { return false; } if (containsNamedCaptureGroups()) { - throw syntaxErrorAt(RbErrorMessages.NUMBERED_BACKREF_CALL_IS_NOT_ALLOWED, restorePosition); + throw syntaxErrorAt(RbErrorMessages.NUMBERED_BACKREF_CALL_IS_NOT_ALLOWED, restorePosition, ErrorCode.InvalidBackReference); } if (groupNumber > numberOfCaptureGroups()) { - throw syntaxErrorAt(RbErrorMessages.invalidGroupReference(number), restorePosition); - } - if (lookbehindDepth > 0) { - throw syntaxErrorAt(RbErrorMessages.INVALID_PATTERN_IN_LOOK_BEHIND, restorePosition); + throw syntaxErrorAt(RbErrorMessages.invalidGroupReference(number), restorePosition, ErrorCode.InvalidBackReference); } + notAllowedInLookbehind(restorePosition); if (groupNumber > groupIndex && groupNumber >= 10) { // forward references >= 10 are interpreted as octal escapes instead position = restorePosition; @@ -1339,6 +1337,12 @@ private boolean backreference() { } } + private void notAllowedInLookbehind(int errorPosition) { + if (lookbehindDepth > 0) { + throw syntaxErrorAt(RbErrorMessages.INVALID_PATTERN_IN_LOOK_BEHIND, errorPosition, ErrorCode.InvalidLookbehind); + } + } + /** * Tries to parse a named backreference (e.g. {@code \k}). * @@ -1363,7 +1367,7 @@ private List parseGroupReference(char terminator, boolean allowNumeric, int beginPos = position; if (curChar() == '-' || RegexLexer.isDecimalDigit(curChar())) { if (!allowNumeric) { - throw syntaxErrorHere(RbErrorMessages.INVALID_GROUP_NAME); + throw syntaxErrorHere(RbErrorMessages.INVALID_GROUP_NAME, ErrorCode.InvalidBackReference); } int sign = match("-") ? -1 : 1; groupName = getMany(RegexLexer::isDecimalDigit); @@ -1371,24 +1375,24 @@ private List parseGroupReference(char terminator, boolean allowNumeric, try { groupNumber = sign * Integer.parseInt(groupName); } catch (NumberFormatException e) { - throw syntaxErrorAt(RbErrorMessages.INVALID_GROUP_NAME, beginPos); + throw syntaxErrorAt(RbErrorMessages.INVALID_GROUP_NAME, beginPos, ErrorCode.InvalidBackReference); } if (groupNumber < 0) { groupNumber = numberOfCaptureGroups() + 1 + groupNumber; } if (containsNamedCaptureGroups()) { - throw syntaxErrorAt(RbErrorMessages.NUMBERED_BACKREF_CALL_IS_NOT_ALLOWED, beginPos); + throw syntaxErrorAt(RbErrorMessages.NUMBERED_BACKREF_CALL_IS_NOT_ALLOWED, beginPos, ErrorCode.InvalidBackReference); } if (resolveReference) { if (groupNumber < 0 || groupNumber > numberOfCaptureGroups()) { - throw syntaxErrorAt(RbErrorMessages.invalidGroupReference(groupName), beginPos); + throw syntaxErrorAt(RbErrorMessages.invalidGroupReference(groupName), beginPos, ErrorCode.InvalidBackReference); } groupNumbers = new ArrayList<>(1); groupNumbers.add(groupNumber); } } else { if (!allowNamed) { - throw syntaxErrorAt(RbErrorMessages.INVALID_GROUP_NAME, beginPos); + throw syntaxErrorAt(RbErrorMessages.INVALID_GROUP_NAME, beginPos, ErrorCode.InvalidBackReference); } groupName = getMany(c -> { if (allowLevels) { @@ -1398,11 +1402,11 @@ private List parseGroupReference(char terminator, boolean allowNumeric, } }); if (groupName.isEmpty()) { - throw syntaxErrorAt(RbErrorMessages.MISSING_GROUP_NAME, beginPos); + throw syntaxErrorAt(RbErrorMessages.MISSING_GROUP_NAME, beginPos, ErrorCode.InvalidBackReference); } if (resolveReference) { if (namedCaptureGroups == null || !namedCaptureGroups.containsKey(groupName)) { - throw syntaxErrorAt(RbErrorMessages.unknownGroupName(groupName), beginPos); + throw syntaxErrorAt(RbErrorMessages.unknownGroupName(groupName), beginPos, ErrorCode.InvalidBackReference); } groupNumbers = namedCaptureGroups.get(groupName); } @@ -1411,22 +1415,20 @@ private List parseGroupReference(char terminator, boolean allowNumeric, advance(); // consume sign String level = getMany(RegexLexer::isDecimalDigit); if (level.isEmpty()) { - throw syntaxErrorAt(RbErrorMessages.INVALID_GROUP_NAME, beginPos); + throw syntaxErrorAt(RbErrorMessages.INVALID_GROUP_NAME, beginPos, ErrorCode.InvalidBackReference); } bailOut("backreferences to other levels are not supported"); } if (!match(Character.toString(terminator))) { - throw syntaxErrorAt(RbErrorMessages.INVALID_GROUP_NAME, beginPos); - } - if (lookbehindDepth > 0) { - throw syntaxErrorAt(RbErrorMessages.INVALID_PATTERN_IN_LOOK_BEHIND, beginPos); + throw syntaxErrorAt(RbErrorMessages.INVALID_GROUP_NAME, beginPos, ErrorCode.InvalidBackReference); } + notAllowedInLookbehind(beginPos); return groupNumbers; } private void buildNamedBackreference(Integer[] groupNumbers, String name) { if (groupNumbers.length == 0) { - throw syntaxErrorHere(RbErrorMessages.undefinedReference(name)); + throw syntaxErrorHere(RbErrorMessages.undefinedReference(name), ErrorCode.InvalidBackReference); } else if (groupNumbers.length == 1) { buildBackreference(groupNumbers[0], true); } else { @@ -1534,7 +1536,7 @@ private boolean subexpressionCall() { List targetGroups = parseGroupReference('>', true, true, false, true); int nameEnd = position - 1; if (targetGroups.size() > 1) { - throw syntaxErrorHere(RbErrorMessages.multiplexCall(inPattern.substring(nameStart, nameEnd))); + throw syntaxErrorHere(RbErrorMessages.multiplexCall(inPattern.substring(nameStart, nameEnd)), ErrorCode.InvalidSubexpressionCall); } addSubexpressionCall(targetGroups.get(0)); hasSubexpressionCalls = true; @@ -1560,11 +1562,11 @@ private boolean stringEscape() { try { int codePoint = Integer.parseInt(code, 16); if (codePoint > 0x10FFFF) { - throw syntaxErrorAt(RbErrorMessages.invalidUnicodeEscape(code), beginPos); + throw syntaxErrorAt(RbErrorMessages.invalidUnicodeEscape(code), beginPos, ErrorCode.InvalidEscape); } buildChar(codePoint); } catch (NumberFormatException e) { - throw syntaxErrorAt(RbErrorMessages.badEscape(code), beginPos); + throw syntaxErrorAt(RbErrorMessages.badEscape(code), beginPos, ErrorCode.InvalidEscape); } getMany(WHITESPACE::get); } @@ -1614,10 +1616,10 @@ private int fetchEscapedChar() { case 'c': case 'C': { if (atEnd()) { - throw syntaxErrorAt(RbErrorMessages.END_PATTERN_AT_CONTROL, beginPos); + throw syntaxErrorAt(RbErrorMessages.END_PATTERN_AT_CONTROL, beginPos, ErrorCode.InvalidEscape); } if (ch == 'C' && !match("-")) { - throw syntaxErrorAt(RbErrorMessages.INVALID_CONTROL_CODE_SYNTAX, beginPos); + throw syntaxErrorAt(RbErrorMessages.INVALID_CONTROL_CODE_SYNTAX, beginPos, ErrorCode.InvalidEscape); } int c = consumeChar(); if (c == '?') { @@ -1630,13 +1632,13 @@ private int fetchEscapedChar() { } case 'M': { if (atEnd()) { - throw syntaxErrorAt(RbErrorMessages.END_PATTERN_AT_META, beginPos); + throw syntaxErrorAt(RbErrorMessages.END_PATTERN_AT_META, beginPos, ErrorCode.InvalidEscape); } if (!match("-")) { - throw syntaxErrorAt(RbErrorMessages.INVALID_META_CODE_SYNTAX, beginPos); + throw syntaxErrorAt(RbErrorMessages.INVALID_META_CODE_SYNTAX, beginPos, ErrorCode.InvalidEscape); } if (atEnd()) { - throw syntaxErrorAt(RbErrorMessages.END_PATTERN_AT_META, beginPos); + throw syntaxErrorAt(RbErrorMessages.END_PATTERN_AT_META, beginPos, ErrorCode.InvalidEscape); } int c = consumeChar(); if (c == '\\') { @@ -1682,21 +1684,21 @@ private Optional characterEscape() { String code; if (match("{")) { code = getMany(RegexLexer::isHexDigit); - mustMatch("}"); + mustMatch("}", RbErrorMessages.EXPECTED_BRACE, ErrorCode.InvalidEscape); } else { code = getUpTo(4, RegexLexer::isHexDigit); if (code.length() < 4) { - throw syntaxErrorAt(RbErrorMessages.incompleteEscape(code), beginPos); + throw syntaxErrorAt(RbErrorMessages.incompleteEscape(code), beginPos, ErrorCode.InvalidEscape); } } try { int codePoint = Integer.parseInt(code, 16); if (codePoint > 0x10FFFF) { - throw syntaxErrorAt(RbErrorMessages.invalidUnicodeEscape(code), beginPos); + throw syntaxErrorAt(RbErrorMessages.invalidUnicodeEscape(code), beginPos, ErrorCode.InvalidEscape); } return Optional.of(codePoint); } catch (NumberFormatException e) { - throw syntaxErrorAt(RbErrorMessages.badEscape(code), beginPos); + throw syntaxErrorAt(RbErrorMessages.badEscape(code), beginPos, ErrorCode.InvalidEscape); } } case '0': @@ -1707,10 +1709,10 @@ private Optional characterEscape() { case '5': case '6': case '7': { - String code = getUpTo(3, c -> RegexLexer.isOctalDigit(c)); + String code = getUpTo(3, RegexLexer::isOctalDigit); int codePoint = Integer.parseInt(code, 8); if (codePoint > 0xFF) { - throw syntaxErrorAt(RbErrorMessages.TOO_BIG_NUMBER, beginPos); + throw syntaxErrorAt(RbErrorMessages.TOO_BIG_NUMBER, beginPos, ErrorCode.InvalidEscape); } return Optional.of(codePoint); } @@ -1772,7 +1774,7 @@ private void collectCharClass() { int firstPosInside = position; classBody: while (true) { if (atEnd()) { - throw syntaxErrorAt(RbErrorMessages.UNTERMINATED_CHARACTER_SET, beginPos); + throw syntaxErrorAt(RbErrorMessages.UNTERMINATED_CHARACTER_SET, beginPos, ErrorCode.InvalidCharacterClass); } int rangeStart = position; Optional lowerBound; @@ -1811,7 +1813,7 @@ private void collectCharClass() { // a hyphen following a nested char class is never interpreted as a range operator if (!wasNestedCharClass && match("-")) { if (atEnd()) { - throw syntaxErrorAt(RbErrorMessages.UNTERMINATED_CHARACTER_SET, beginPos); + throw syntaxErrorAt(RbErrorMessages.UNTERMINATED_CHARACTER_SET, beginPos, ErrorCode.InvalidCharacterClass); } Optional upperBound; ch = consumeChar(); @@ -1852,7 +1854,7 @@ private void collectCharClass() { // both the left operand and the range operator if (!wasNestedCharClass) { if (!lowerBound.isPresent() || !upperBound.isPresent() || upperBound.get() < lowerBound.get()) { - throw syntaxErrorAt(RbErrorMessages.badCharacterRange(inPattern.substring(rangeStart, position)), rangeStart); + throw syntaxErrorAt(RbErrorMessages.badCharacterRange(inPattern.substring(rangeStart, position)), rangeStart, ErrorCode.InvalidCharacterClass); } curCharClassAddRange(lowerBound.get(), upperBound.get()); } @@ -1954,7 +1956,7 @@ private PosixClassParseResult collectPosixCharClass() { } if (match(":]")) { if (!UNICODE_POSIX_CHAR_CLASSES.containsKey(className)) { - throw syntaxErrorAt(RbErrorMessages.INVALID_POSIX_BRACKET_TYPE, restorePosition); + throw syntaxErrorAt(RbErrorMessages.INVALID_POSIX_BRACKET_TYPE, restorePosition, ErrorCode.InvalidCharacterClass); } CodePointSet charSet; if (getLocalFlags().isAscii()) { @@ -2015,7 +2017,7 @@ private void quantifier(int ch) { if (canHaveQuantifier) { addQuantifier(Token.createQuantifier(quantifier.lower, quantifier.upper, quantifier.greedy, quantifier.possessive, ch != '{')); } else { - throw syntaxErrorAt(RbErrorMessages.NOTHING_TO_REPEAT, start); + throw syntaxErrorAt(RbErrorMessages.NOTHING_TO_REPEAT, start, ErrorCode.InvalidQuantifier); } } else { string(consumeChar()); @@ -2053,7 +2055,7 @@ private Quantifier parseQuantifier(int ch) { return null; } if (lowerBound.isPresent() && upperBound.isPresent() && lowerBound.get().compareTo(upperBound.get()) > 0) { - throw syntaxErrorAt(RbErrorMessages.MIN_REPEAT_GREATER_THAN_MAX_REPEAT, start); + throw syntaxErrorAt(RbErrorMessages.MIN_REPEAT_GREATER_THAN_MAX_REPEAT, start, ErrorCode.InvalidQuantifier); } boolean greedy = true; if (canBeNonGreedy && match("?")) { @@ -2117,7 +2119,7 @@ private static int quantifierBoundsToIntValue(BigInteger i) { */ private void parens() { if (atEnd()) { - throw syntaxErrorAtEnd(RbErrorMessages.UNTERMINATED_SUBPATTERN); + throw syntaxErrorAtEnd(RbErrorMessages.UNTERMINATED_SUBPATTERN, ErrorCode.UnmatchedParenthesis); } if (match("?")) { final int ch1 = consumeChar(); @@ -2182,7 +2184,7 @@ private void parens() { break; default: - throw syntaxErrorAt(RbErrorMessages.unknownExtension(ch1), position - 1); + throw syntaxErrorAt(RbErrorMessages.unknownExtension(ch1), position - 1, ErrorCode.InvalidGroup); } } else { group(!containsNamedCaptureGroups()); @@ -2197,10 +2199,10 @@ private void parens() { private String parseGroupName(char terminator) { String groupName = getMany(c -> c != terminator); if (!match(Character.toString(terminator))) { - throw syntaxErrorHere(RbErrorMessages.unterminatedName(terminator)); + throw syntaxErrorHere(RbErrorMessages.unterminatedName(terminator), ErrorCode.InvalidNamedGroup); } if (groupName.isEmpty()) { - throw syntaxErrorHere(RbErrorMessages.MISSING_GROUP_NAME); + throw syntaxErrorHere(RbErrorMessages.MISSING_GROUP_NAME, ErrorCode.InvalidNamedGroup); } return groupName; } @@ -2212,7 +2214,7 @@ private void parenComment() { int beginPos = position - 2; while (true) { if (atEnd()) { - throw syntaxErrorAt(RbErrorMessages.UNTERMINATED_COMMENT, beginPos); + throw syntaxErrorAt(RbErrorMessages.UNTERMINATED_COMMENT, beginPos, ErrorCode.UnmatchedParenthesis); } int ch = consumeChar(); if (ch == '\\' && !atEnd()) { @@ -2246,7 +2248,7 @@ private void group(boolean capturing) { } canHaveQuantifier = true; } else { - throw syntaxErrorHere(RbErrorMessages.UNTERMINATED_SUBPATTERN); + throw syntaxErrorHere(RbErrorMessages.UNTERMINATED_SUBPATTERN, ErrorCode.UnmatchedParenthesis); } } @@ -2257,13 +2259,14 @@ private void group(boolean capturing) { * @param negate {@code true} if the assertion to be pushed is a negative lookahead assertion */ private void lookahead(boolean negate) { + notAllowedInLookbehind(position); pushLookAheadAssertion(negate); disjunction(); if (match(")")) { popGroup(); canHaveQuantifier = true; } else { - throw syntaxErrorHere(RbErrorMessages.UNTERMINATED_SUBPATTERN); + throw syntaxErrorHere(RbErrorMessages.UNTERMINATED_SUBPATTERN, ErrorCode.UnmatchedParenthesis); } } @@ -2279,7 +2282,7 @@ private void lookbehind(boolean negate) { popGroup(); canHaveQuantifier = true; } else { - throw syntaxErrorHere(RbErrorMessages.UNTERMINATED_SUBPATTERN); + throw syntaxErrorHere(RbErrorMessages.UNTERMINATED_SUBPATTERN, ErrorCode.UnmatchedParenthesis); } } @@ -2294,7 +2297,7 @@ private void atomicGroup() { popGroup(); canHaveQuantifier = true; } else { - throw syntaxErrorHere(RbErrorMessages.UNTERMINATED_SUBPATTERN); + throw syntaxErrorHere(RbErrorMessages.UNTERMINATED_SUBPATTERN, ErrorCode.UnmatchedParenthesis); } } @@ -2307,16 +2310,16 @@ private void conditionalBackReference() { if (match("<")) { namedReference = curChar() != '-' && !RegexLexer.isDecimalDigit(curChar()); groupNumbers = parseGroupReference('>', true, true, true, true); - mustMatch(")"); + mustMatch(")", RbErrorMessages.EXPECTED_PAREN, ErrorCode.InvalidBackReference); } else if (match("'")) { namedReference = curChar() != '-' && !RegexLexer.isDecimalDigit(curChar()); groupNumbers = parseGroupReference('\'', true, true, true, true); - mustMatch(")"); + mustMatch(")", RbErrorMessages.EXPECTED_PAREN, ErrorCode.InvalidBackReference); } else if (RegexLexer.isDecimalDigit(curChar())) { namedReference = false; groupNumbers = parseGroupReference(')', true, false, true, true); } else { - throw syntaxErrorHere(RbErrorMessages.INVALID_GROUP_NAME); + throw syntaxErrorHere(RbErrorMessages.INVALID_GROUP_NAME, ErrorCode.InvalidBackReference); } pushConditionalBackReferenceGroup(groupNumbers.get(0), namedReference); alternative(); @@ -2325,14 +2328,14 @@ private void conditionalBackReference() { canHaveQuantifier = false; alternative(); if (curChar() == '|') { - throw syntaxErrorHere(RbErrorMessages.CONDITIONAL_BACKREF_WITH_MORE_THAN_TWO_BRANCHES); + throw syntaxErrorHere(RbErrorMessages.CONDITIONAL_BACKREF_WITH_MORE_THAN_TWO_BRANCHES, ErrorCode.InvalidGroup); } } else { // Generate the implicit empty else-branch, if it was not specified. nextSequence(); } if (!match(")")) { - throw syntaxErrorHere(RbErrorMessages.UNTERMINATED_SUBPATTERN); + throw syntaxErrorHere(RbErrorMessages.UNTERMINATED_SUBPATTERN, ErrorCode.UnmatchedParenthesis); } popGroup(); canHaveQuantifier = true; @@ -2347,7 +2350,7 @@ private void absentExpression() { if (match(")")) { bailOut("absent expressions not supported"); } else { - throw syntaxErrorHere(RbErrorMessages.UNTERMINATED_SUBPATTERN); + throw syntaxErrorHere(RbErrorMessages.UNTERMINATED_SUBPATTERN, ErrorCode.UnmatchedParenthesis); } canHaveQuantifier = true; } @@ -2366,20 +2369,20 @@ private void flags(int ch0) { } else if (RubyFlags.isValidFlagChar(ch)) { if (negative) { if (RubyFlags.isTypeFlag(ch)) { - throw syntaxErrorHere(RbErrorMessages.UNDEFINED_GROUP_OPTION); + throw syntaxErrorHere(RbErrorMessages.UNDEFINED_GROUP_OPTION, ErrorCode.InvalidInlineFlag); } newFlags = newFlags.delFlag(ch); } else { newFlags = newFlags.addFlag(ch); } } else if (Character.isAlphabetic(ch)) { - throw syntaxErrorHere(RbErrorMessages.UNDEFINED_GROUP_OPTION); + throw syntaxErrorHere(RbErrorMessages.UNDEFINED_GROUP_OPTION, ErrorCode.InvalidInlineFlag); } else { - throw syntaxErrorHere(RbErrorMessages.MISSING_DASH_COLON_PAREN); + throw syntaxErrorHere(RbErrorMessages.MISSING_DASH_COLON_PAREN, ErrorCode.InvalidInlineFlag); } if (atEnd()) { - throw syntaxErrorAtEnd(RbErrorMessages.MISSING_FLAG_DASH_COLON_PAREN); + throw syntaxErrorAtEnd(RbErrorMessages.MISSING_FLAG_DASH_COLON_PAREN, ErrorCode.InvalidInlineFlag); } ch = consumeChar(); } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/RubySubexpressionCalls.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/RubySubexpressionCalls.java index abf3efb58d23..698dc35ef6fb 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/RubySubexpressionCalls.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/RubySubexpressionCalls.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 com.oracle.truffle.regex.tregex.parser.ast.SubexpressionCall; import com.oracle.truffle.regex.tregex.parser.ast.visitors.CopyVisitor; import com.oracle.truffle.regex.tregex.parser.ast.visitors.DepthFirstTraversalRegexASTVisitor; +import com.oracle.truffle.regex.tregex.parser.ast.visitors.MarkAsAliveVisitor; import java.util.ArrayList; import java.util.HashMap; @@ -74,7 +75,7 @@ public static void expandNonRecursiveSubexpressionCalls(RegexAST ast) { CallGraphNode node = expansionStack.remove(expansionStack.size() - 1); if (node instanceof SubexpressionCallNode) { SubexpressionCall subexpressionCall = ((SubexpressionCallNode) node).subexpressionCall; - replace(subexpressionCall, ast.getGroup(subexpressionCall.getGroupNr()), copyVisitor); + replace(subexpressionCall, ast.getGroup(subexpressionCall.getGroupNr()).get(0), copyVisitor); } if (callGraph.containsKey(node)) { for (CallGraphNode dependent : callGraph.get(node)) { @@ -98,6 +99,7 @@ public static void expandNonRecursiveSubexpressionCalls(RegexAST ast) { private static void replace(SubexpressionCall caller, Group callee, CopyVisitor copyVisitor) { Group copy = (Group) copyVisitor.copy(callee); + MarkAsAliveVisitor.markAsAlive(copy); copy.setQuantifier(caller.getQuantifier()); Sequence callerSeq = caller.getParent(); int callerSeqIndex = caller.getSeqIndex(); diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaFlags.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaFlags.java index 99cb31a85712..24b44362d526 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaFlags.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaFlags.java @@ -179,6 +179,10 @@ public String toString() { return sb.toString(); } + public int getValue() { + return value; + } + public boolean isCanonEq() { return isSet(Pattern.CANON_EQ); } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaRegexLexer.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaRegexLexer.java index 7129aa86d624..47fd08058a90 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaRegexLexer.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaRegexLexer.java @@ -48,6 +48,7 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.regex.RegexSource; import com.oracle.truffle.regex.RegexSyntaxException; +import com.oracle.truffle.regex.RegexSyntaxException.ErrorCode; import com.oracle.truffle.regex.UnsupportedRegexException; import com.oracle.truffle.regex.charset.ClassSetContents; import com.oracle.truffle.regex.charset.CodePointSet; @@ -378,27 +379,27 @@ protected long boundedQuantifierMaxValue() { @Override protected RegexSyntaxException handleBoundedQuantifierOutOfOrder() { - return syntaxError(JavaErrorMessages.ILLEGAL_REPETITION); + return syntaxError(JavaErrorMessages.ILLEGAL_REPETITION, ErrorCode.InvalidQuantifier); } @Override protected Token handleBoundedQuantifierEmptyOrMissingMin() { - throw syntaxError(JavaErrorMessages.ILLEGAL_REPETITION); + throw syntaxError(JavaErrorMessages.ILLEGAL_REPETITION, ErrorCode.InvalidQuantifier); } @Override protected Token handleBoundedQuantifierInvalidCharacter() { - throw syntaxError(JavaErrorMessages.UNCLOSED_COUNTED_CLOSURE); + throw syntaxError(JavaErrorMessages.UNCLOSED_COUNTED_CLOSURE, ErrorCode.InvalidQuantifier); } @Override protected Token handleBoundedQuantifierOverflow(long min, long max) { - throw syntaxError(JavaErrorMessages.ILLEGAL_REPETITION); + throw syntaxError(JavaErrorMessages.ILLEGAL_REPETITION, ErrorCode.InvalidQuantifier); } @Override protected Token handleBoundedQuantifierOverflowMin(long min, long max) { - throw syntaxError(JavaErrorMessages.ILLEGAL_REPETITION); + throw syntaxError(JavaErrorMessages.ILLEGAL_REPETITION, ErrorCode.InvalidQuantifier); } @Override @@ -418,12 +419,12 @@ protected RegexSyntaxException handleComplementOfStringSet() { @Override protected void handleGroupRedefinition(String name, int newId, int oldId) { - throw syntaxError(JavaErrorMessages.groupRedefinition(name)); + throw syntaxError(JavaErrorMessages.groupRedefinition(name), ErrorCode.InvalidNamedGroup); } @Override protected void handleIncompleteEscapeX() { - throw syntaxError(JavaErrorMessages.ILLEGAL_HEX_ESCAPE); + throw syntaxError(JavaErrorMessages.ILLEGAL_HEX_ESCAPE, ErrorCode.InvalidEscape); } @Override @@ -446,7 +447,7 @@ protected RegexSyntaxException handleInvalidCharInCharClass() { @Override protected RegexSyntaxException handleInvalidGroupBeginQ() { - return syntaxError(JavaErrorMessages.UNKNOWN_INLINE_MODIFIER); + return syntaxError(JavaErrorMessages.UNKNOWN_INLINE_MODIFIER, ErrorCode.InvalidGroup); } @Override @@ -470,7 +471,7 @@ protected RegexSyntaxException handleRangeAsClassSetOperand(ClassSetOperator ope @Override protected void handleUnfinishedEscape() { - throw syntaxError(JavaErrorMessages.UNESCAPED_TRAILING_BACKSLASH); + throw syntaxError(JavaErrorMessages.UNESCAPED_TRAILING_BACKSLASH, ErrorCode.InvalidEscape); } @Override @@ -480,7 +481,7 @@ protected void handleUnfinishedGroupComment() { @Override protected RegexSyntaxException handleUnfinishedGroupQ() { - return syntaxError(JavaErrorMessages.UNKNOWN_INLINE_MODIFIER); + return syntaxError(JavaErrorMessages.UNKNOWN_INLINE_MODIFIER, ErrorCode.InvalidGroup); } @Override @@ -494,7 +495,7 @@ protected void handleUnmatchedRightBrace() { @Override protected RegexSyntaxException handleUnmatchedLeftBracket() { - return syntaxError(JavaErrorMessages.UNCLOSED_CHARACTER_CLASS); + return syntaxError(JavaErrorMessages.UNCLOSED_CHARACTER_CLASS, ErrorCode.UnmatchedBracket); } @Override @@ -530,11 +531,11 @@ protected ClassSetContents parseUnicodeCharacterProperty(boolean invert) throws advance(); } if (!consumingLookahead("}")) { - throw syntaxError(JavaErrorMessages.UNCLOSED_CHAR_FAMILY); + throw syntaxError(JavaErrorMessages.UNCLOSED_CHAR_FAMILY, ErrorCode.InvalidCharacterClass); } name = pattern.substring(namePos, position - 1); if (name.isEmpty()) { - throw syntaxError(JavaErrorMessages.EMPTY_CHAR_FAMILY); + throw syntaxError(JavaErrorMessages.EMPTY_CHAR_FAMILY, ErrorCode.InvalidCharacterClass); } } CodePointSet p = null; @@ -563,7 +564,7 @@ protected ClassSetContents parseUnicodeCharacterProperty(boolean invert) throws break; } if (p == null) { - throw syntaxError(JavaErrorMessages.unknownUnicodeProperty(name, value)); + throw syntaxError(JavaErrorMessages.unknownUnicodeProperty(name, value), ErrorCode.InvalidCharacterClass); } } else { if (name.startsWith("In")) { @@ -588,7 +589,7 @@ protected ClassSetContents parseUnicodeCharacterProperty(boolean invert) throws } } if (p == null) { - throw syntaxError(JavaErrorMessages.unknownUnicodeCharacterProperty(name)); + throw syntaxError(JavaErrorMessages.unknownUnicodeCharacterProperty(name), ErrorCode.InvalidCharacterClass); } } if (invert) { @@ -655,13 +656,13 @@ private CodePointSet parseCharClassInternal(boolean consume) throws RegexSyntaxE } if (prev == null) { if (right == null) { - throw syntaxError(JavaErrorMessages.BAD_CLASS_SYNTAX); + throw syntaxError(JavaErrorMessages.BAD_CLASS_SYNTAX, ErrorCode.InvalidCharacterClass); } else { prev = right; } } else { if (curr == null) { - throw syntaxError(JavaErrorMessages.BAD_INTERSECTION_SYNTAX); + throw syntaxError(JavaErrorMessages.BAD_INTERSECTION_SYNTAX, ErrorCode.InvalidCharacterClass); } prev = prev.createIntersection(curr, compilationBuffer); } @@ -722,14 +723,14 @@ private CodePointSet parseCharClassInternal(boolean consume) throws RegexSyntaxE } } } - throw syntaxError(JavaErrorMessages.UNCLOSED_CHARACTER_CLASS); + throw syntaxError(JavaErrorMessages.UNCLOSED_CHARACTER_CLASS, ErrorCode.UnmatchedBracket); } private CodePointSet parseRange(char c) { int ch = parseCharClassAtomCodePoint(c); if (consumingLookahead('-')) { if (atEnd()) { - throw syntaxError(JavaErrorMessages.ILLEGAL_CHARACTER_RANGE); + throw syntaxError(JavaErrorMessages.ILLEGAL_CHARACTER_RANGE, ErrorCode.InvalidCharacterClass); } if (curChar() == ']' || curChar() == '[') { // unmatched '-' is treated as literal @@ -738,7 +739,7 @@ private CodePointSet parseRange(char c) { } int upper = parseCharClassAtomCodePoint(consumeChar()); if (upper < ch) { - throw syntaxError(JavaErrorMessages.ILLEGAL_CHARACTER_RANGE); + throw syntaxError(JavaErrorMessages.ILLEGAL_CHARACTER_RANGE, ErrorCode.InvalidCharacterClass); } return CodePointSet.create(ch, upper); } else { @@ -763,14 +764,14 @@ protected Token parseCustomEscape(char c) { handleUnfinishedEscape(); } if (consumeChar() != '<') { - throw syntaxError(JavaErrorMessages.NAMED_CAPTURE_GROUP_REFERENCE_MISSING_BEGIN); + throw syntaxError(JavaErrorMessages.NAMED_CAPTURE_GROUP_REFERENCE_MISSING_BEGIN, ErrorCode.InvalidBackReference); } String groupName = javaParseGroupName(); // backward reference if (namedCaptureGroups.containsKey(groupName)) { return Token.createBackReference(getSingleNamedGroupNumber(groupName), false); } - throw syntaxError(JavaErrorMessages.unknownGroupReference(groupName)); + throw syntaxError(JavaErrorMessages.unknownGroupReference(groupName), ErrorCode.InvalidBackReference); } case 'R' -> { return Token.createLineBreak(); @@ -797,12 +798,12 @@ protected int parseCustomEscapeChar(char c, boolean inCharClass) { switch (c) { case 'b': // \b is only valid as the boundary matcher and not as a character escape - throw syntaxError(JavaErrorMessages.ILLEGAL_ESCAPE_SEQUENCE); + throw syntaxError(JavaErrorMessages.ILLEGAL_ESCAPE_SEQUENCE, ErrorCode.InvalidEscape); case '0': if (lookahead(RegexLexer::isOctalDigit, 1)) { return parseOctal(0, 3); } - throw syntaxError(JavaErrorMessages.ILLEGAL_OCT_ESCAPE); + throw syntaxError(JavaErrorMessages.ILLEGAL_OCT_ESCAPE, ErrorCode.InvalidEscape); case 'u': int n = parseUnicodeHexEscape(); if (Character.isHighSurrogate((char) n)) { @@ -819,36 +820,36 @@ protected int parseCustomEscapeChar(char c, boolean inCharClass) { case 'x': if (consumingLookahead('{')) { int hex = parseHex(1, 8, Character.MAX_CODE_POINT, () -> { - throw syntaxError(JavaErrorMessages.ILLEGAL_HEX_ESCAPE); + throw syntaxError(JavaErrorMessages.ILLEGAL_HEX_ESCAPE, ErrorCode.InvalidEscape); }, () -> { - throw syntaxError(JavaErrorMessages.HEX_TOO_BIG); + throw syntaxError(JavaErrorMessages.HEX_TOO_BIG, ErrorCode.InvalidEscape); }); if (!consumingLookahead('}')) { - throw syntaxError(JavaErrorMessages.UNCLOSED_HEX); + throw syntaxError(JavaErrorMessages.UNCLOSED_HEX, ErrorCode.InvalidEscape); } return hex; } return -1; case 'c': if (atEnd()) { - throw syntaxError(JavaErrorMessages.ILLEGAL_CTRL_SEQ); + throw syntaxError(JavaErrorMessages.ILLEGAL_CTRL_SEQ, ErrorCode.InvalidEscape); } return consumeChar() ^ 64; case 'N': if (consumingLookahead('{')) { int i = position; if (!findChars('}')) { - throw syntaxError(JavaErrorMessages.UNCLOSED_CHAR_NAME); + throw syntaxError(JavaErrorMessages.UNCLOSED_CHAR_NAME, ErrorCode.InvalidEscape); } advance(); // skip '}' String name = pattern.substring(i, position - 1); try { return Character.codePointOf(name); } catch (IllegalArgumentException x) { - throw syntaxError(JavaErrorMessages.unknownCharacterName(name)); + throw syntaxError(JavaErrorMessages.unknownCharacterName(name), ErrorCode.InvalidEscape); } } - throw syntaxError(JavaErrorMessages.ILLEGAL_CHARACTER_NAME); + throw syntaxError(JavaErrorMessages.ILLEGAL_CHARACTER_NAME, ErrorCode.InvalidEscape); case 'a': return 0x7; case 'e': @@ -864,7 +865,7 @@ private int parseUnicodeHexEscape() { if (consumingLookahead(RegexLexer::isHexDigit, 4)) { return Integer.parseInt(pattern, position - 4, position, 16); } - throw syntaxError(JavaErrorMessages.ILLEGAL_UNICODE_ESC_SEQ); + throw syntaxError(JavaErrorMessages.ILLEGAL_UNICODE_ESC_SEQ, ErrorCode.InvalidEscape); } @Override @@ -873,7 +874,7 @@ protected int parseCustomEscapeCharFallback(int c, boolean inCharClass) { // digits are not accepted here since they should have been parsed as octal sequence or // backreference earlier if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { - throw syntaxError(JavaErrorMessages.ILLEGAL_ESCAPE_SEQUENCE); + throw syntaxError(JavaErrorMessages.ILLEGAL_ESCAPE_SEQUENCE, ErrorCode.InvalidEscape); } return c; } @@ -929,9 +930,9 @@ private String javaParseGroupName() { ParseGroupNameResult result = parseGroupName('>'); switch (result.state) { case empty, invalidStart: - throw syntaxError(JavaErrorMessages.INVALID_GROUP_NAME_START); + throw syntaxError(JavaErrorMessages.INVALID_GROUP_NAME_START, ErrorCode.InvalidNamedGroup); case unterminated, invalidRest: - throw syntaxError(JavaErrorMessages.INVALID_GROUP_NAME_REST); + throw syntaxError(JavaErrorMessages.INVALID_GROUP_NAME_REST, ErrorCode.InvalidNamedGroup); case valid: return result.groupName; default: diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaRegexParser.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaRegexParser.java index 2c39bf6271a3..259d52e532a4 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaRegexParser.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaRegexParser.java @@ -46,6 +46,7 @@ import com.oracle.truffle.regex.RegexLanguage; import com.oracle.truffle.regex.RegexSource; import com.oracle.truffle.regex.RegexSyntaxException; +import com.oracle.truffle.regex.RegexSyntaxException.ErrorCode; import com.oracle.truffle.regex.UnsupportedRegexException; import com.oracle.truffle.regex.charset.CodePointSet; import com.oracle.truffle.regex.charset.Constants; @@ -107,11 +108,23 @@ public RegexAST parse() { addCaret(); break; case Z: - pushGroup(); // (?: - lineTerminators(); + pushGroup(); + pushLookAheadAssertion(); + if (getFlags().isUnixLines()) { + addCharClass(CodePointSet.create('\n')); + addDollar(); + } else { + addCharClass(CodePointSet.create('\r')); + addCharClass(CodePointSet.create('\n')); + addDollar(); + nextSequence(); + addCharClass(CodePointSet.createNoDedup('\n', '\n', '\r', '\r', 0x0085, 0x0085, 0x2028, 0x2029)); + addDollar(); + } + popGroup(); nextSequence(); - popGroup(); // ) addDollar(); + popGroup(); break; case z: addDollar(); @@ -143,7 +156,7 @@ public RegexAST parse() { Token.Quantifier quantifier = (Token.Quantifier) token; // quantifiers of type *, + or ? cannot directly follow another quantifier if (last instanceof Token.Quantifier && quantifier.isSingleChar()) { - throw syntaxErrorHere(JavaErrorMessages.danglingMetaCharacter(quantifier)); + throw syntaxErrorHere(JavaErrorMessages.danglingMetaCharacter(quantifier), ErrorCode.InvalidQuantifier); } if (astBuilder.getCurTerm() != null) { if (quantifier.isPossessive()) { @@ -152,7 +165,7 @@ public RegexAST parse() { addQuantifier((Token.Quantifier) token); } else { if (quantifier.isSingleChar()) { - throw syntaxErrorHere(JavaErrorMessages.danglingMetaCharacter(quantifier)); + throw syntaxErrorHere(JavaErrorMessages.danglingMetaCharacter(quantifier), ErrorCode.InvalidQuantifier); } } break; @@ -163,7 +176,6 @@ public RegexAST parse() { // flagStack push is handled in the lexer if (!((Token.InlineFlags) token).isGlobal()) { astBuilder.pushGroup(token); - astBuilder.getCurGroup().setLocalFlags(true); lexer.pushLocalFlags(); } lexer.setCurrentFlags((JavaFlags) ((Token.InlineFlags) token).getFlags()); @@ -186,7 +198,7 @@ public RegexAST parse() { break; case groupEnd: if (astBuilder.getCurGroup().getParent() instanceof RegexASTRootNode) { - throw syntaxErrorHere(JsErrorMessages.UNMATCHED_RIGHT_PARENTHESIS); + throw syntaxErrorHere(JsErrorMessages.UNMATCHED_RIGHT_PARENTHESIS, ErrorCode.UnmatchedParenthesis); } lexer.popLocalFlags(); astBuilder.popGroup(token); @@ -216,7 +228,7 @@ public RegexAST parse() { astBuilder.addDollar(); } if (!astBuilder.curGroupIsRoot()) { - throw syntaxErrorHere(JavaErrorMessages.UNCLOSED_GROUP); + throw syntaxErrorHere(JavaErrorMessages.UNCLOSED_GROUP, ErrorCode.UnmatchedParenthesis); } return astBuilder.popRootGroup(); } @@ -233,8 +245,8 @@ public AbstractRegexObject getNamedCaptureGroups() { // Error reporting - private RegexSyntaxException syntaxErrorHere(String message) { - return RegexSyntaxException.createPattern(source, message, lexer.getLastTokenPosition()); + private RegexSyntaxException syntaxErrorHere(String message, ErrorCode errorCode) { + return RegexSyntaxException.createPattern(source, message, lexer.getLastTokenPosition(), errorCode); } private void literalString(Token.LiteralString token) { diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaRegexValidator.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaRegexValidator.java index 76a22eb85306..c88316405d2f 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaRegexValidator.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/flavors/java/JavaRegexValidator.java @@ -42,6 +42,7 @@ import com.oracle.truffle.regex.RegexSource; import com.oracle.truffle.regex.RegexSyntaxException; +import com.oracle.truffle.regex.RegexSyntaxException.ErrorCode; import com.oracle.truffle.regex.UnsupportedRegexException; import com.oracle.truffle.regex.errors.JavaErrorMessages; import com.oracle.truffle.regex.errors.JsErrorMessages; @@ -117,10 +118,10 @@ public void validate() throws RegexSyntaxException { Token.Quantifier quantifier = (Token.Quantifier) token; // quantifiers of type *, + or ? cannot directly follow another quantifier if (last instanceof Token.Quantifier && quantifier.isSingleChar()) { - throw syntaxErrorHere(JavaErrorMessages.danglingMetaCharacter(quantifier)); + throw syntaxErrorHere(JavaErrorMessages.danglingMetaCharacter(quantifier), ErrorCode.InvalidQuantifier); } if (curTermState == CurTermState.Null && quantifier.isSingleChar()) { - throw syntaxErrorHere(JavaErrorMessages.danglingMetaCharacter(quantifier)); + throw syntaxErrorHere(JavaErrorMessages.danglingMetaCharacter(quantifier), ErrorCode.InvalidQuantifier); } if (quantifier.isPossessive()) { throw new UnsupportedRegexException("possessive quantifiers are not supported"); @@ -145,7 +146,7 @@ public void validate() throws RegexSyntaxException { break; case groupEnd: if (syntaxStack.isEmpty()) { - throw syntaxErrorHere(JsErrorMessages.UNMATCHED_RIGHT_PARENTHESIS); + throw syntaxErrorHere(JsErrorMessages.UNMATCHED_RIGHT_PARENTHESIS, ErrorCode.UnmatchedParenthesis); } RegexStackElem poppedElem = syntaxStack.remove(syntaxStack.size() - 1); switch (poppedElem) { @@ -164,12 +165,12 @@ public void validate() throws RegexSyntaxException { } if (!syntaxStack.isEmpty()) { - throw syntaxErrorHere(JavaErrorMessages.UNCLOSED_GROUP); + throw syntaxErrorHere(JavaErrorMessages.UNCLOSED_GROUP, ErrorCode.UnmatchedParenthesis); } } // Error reporting - private RegexSyntaxException syntaxErrorHere(String message) { - return RegexSyntaxException.createPattern(source, message, lexer.getLastTokenPosition()); + private RegexSyntaxException syntaxErrorHere(String message, ErrorCode errorCode) { + return RegexSyntaxException.createPattern(source, message, lexer.getLastTokenPosition(), errorCode); } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/util/DebugUtil.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/util/DebugUtil.java index d6608f6c1d34..56496640831a 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/util/DebugUtil.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/util/DebugUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -78,23 +78,24 @@ public static String escapeString(String s) { public static String regexSourceEscape(String pattern, String flags) { StringBuilder sb = new StringBuilder(pattern.length() + 2); sb.append('/'); - int i = 0; - while (i < pattern.length()) { - int c = pattern.codePointAt(i); + javaStringEscape(sb, pattern); + return sb.append('/').append(flags).toString(); + } + + public static String javaStringEscape(String string) { + return javaStringEscape(new StringBuilder(string.length()), string).toString(); + } + + private static StringBuilder javaStringEscape(StringBuilder sb, String string) { + for (int i = 0; i < string.length(); i++) { + int c = string.charAt(i); if (0x20 <= c && c <= 0x7e) { sb.appendCodePoint(c); } else { - sb.append("\\u"); - if (c > 0xffff) { - i++; - sb.append(String.format("{%06x}", c)); - } else { - sb.append(String.format("%04x", c)); - } + sb.append("\\u").append(String.format("%04x", c)); } - i++; } - return sb.append('/').append(flags).toString(); + return sb; } @TruffleBoundary @@ -111,7 +112,7 @@ public static String nodeID(int id) { @TruffleBoundary public static String jsStringEscape(String str) { - StringBuffer escapedString = new StringBuffer(); + StringBuilder escapedString = new StringBuilder(); Matcher m = specialChars.matcher(str); while (m.find()) { String replacement; diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/util/MathUtil.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/util/MathUtil.java index ab87aa515eaf..8ed09130599b 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/util/MathUtil.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/util/MathUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -50,4 +50,11 @@ public static int log2floor(int x) { public static int log2ceil(int x) { return 32 - Integer.numberOfLeadingZeros(x - 1); } + + public static int saturatingInc(int x) { + if (x == Integer.MAX_VALUE) { + return x; + } + return x + 1; + } } diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/util/BitSets.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/util/BitSets.java index 896f8de42f54..3e629d4ea9fb 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/util/BitSets.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/util/BitSets.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -107,6 +107,9 @@ public static boolean add(long[] bs, int index) { return bs[wordIndex(index)] != old; } + /** + * Set all bits from lo (inclusive) to hi (inclusive). + */ public static void setRange(long[] bs, int lo, int hi) { int wordIndexLo = wordIndex(lo); int wordIndexHi = wordIndex(hi); @@ -123,6 +126,9 @@ public static void setRange(long[] bs, int lo, int hi) { bs[wordIndexHi] |= rangeHi; } + /** + * Clear all bits from lo (inclusive) to hi (inclusive). + */ public static void clearRange(long[] bs, int lo, int hi) { int wordIndexLo = wordIndex(lo); int wordIndexHi = wordIndex(hi); diff --git a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/util/TBitSet.java b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/util/TBitSet.java index ff385c1b1372..2994bc1fe2f9 100644 --- a/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/util/TBitSet.java +++ b/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/util/TBitSet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -155,11 +155,17 @@ public void set(int b) { BitSets.set(words, b); } + /** + * Set all bits from lo (inclusive) to hi (inclusive). + */ public void setRange(int lo, int hi) { ensureCapacity(BitSets.wordIndex(hi) + 1); BitSets.setRange(words, lo, hi); } + /** + * Clear all bits from lo (inclusive) to hi (inclusive). + */ public void clearRange(int lo, int hi) { ensureCapacity(BitSets.wordIndex(hi) + 1); BitSets.clearRange(words, lo, hi); diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index ce84758c0422..11a3f3aa849a 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -2,12 +2,18 @@ This changelog summarizes major changes between GraalVM SDK versions. The main focus is on APIs exported by GraalVM SDK. +## Version 25.0.0 +* GR-60636 Truffle now stops compiling when the code cache fills up on HotSpot. A warning is printed when that happens. + ## Version 24.2.0 * GR-54905 When using Truffle NFI with the Panama backend, native access must now be granted to the Truffle module instead of the NFI Panama module. Use the `--enable-native-access=org.graalvm.truffle` Java command line option to enable the native access for the NFI Panama backend. * GR-57681 Added the ability to use `Value#as(byte[].class)` to copy the contents of a guest language byte buffer (`Value#hasBufferElements()`) to a new byte array. The new functionality has precedence over accessing the guest object as array (`Value#hasArrayElements()`) if both ways are available. * GR-57817 Starting with JDK 24 users now need to configure native access privileges to the java executable in order to avoid warnings from being printed by the JDK. For usages of the module-path pass the `--enable-native-access=org.graalvm.truffle` option and for class-path usages pass the `--enable-native-access=ALL-UNNAMED` option to resolve the new warning. Note that Truffle automatically forwards the native access capability to all loaded languages and tools, therefore no further configuration is required. If the native access is denied by the user with `--illegal-native-access=deny` then loading the optimizing runtime will fail and the fallback runtime will be used. More information can be found in the integrity-by-default [JEP-472](https://openjdk.org/jeps/472). * GR-54300 `Context` and `Engine` is now automatically closed when no longer strongly referenced. A reachable `Value` or `PolyglotException` will keep the associated `Context` reachable. Additionally, the `Context` remains reachable when explicitly entered or if there is an active polyglot thread within it. The `Engine` remains reachable when there is a strongly reachable `Language`, `Instrument`, or `Context` instance. However, it is still recommended not to rely on garbage collection for closing. Instead, use the try-with-resources pattern for explicit context and engine management. For more information, refer to the [Automatic Close on GC documentation](https://github.com/oracle/graal/blob/master/truffle/docs/CloseOnGc.md). +* GR-60022 Introduced the [FileSystem.newCompositeFileSystem](https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/io/FileSystem.html#newCompositeFileSystem(org.graalvm.polyglot.io.FileSystem,org.graalvm.polyglot.io.FileSystem.Selector...)) method, which creates a composite `FileSystem` delegating operations to the specified delegate FileSystem instances based on the provided selectors. +* GR-60022 Introduced the [FileSystem.newDenyIOFileSystem](https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/io/FileSystem.html#newDenyIOFileSystem()) method, which creates a `FileSystem` that denies all file operations except for path parsing. +* GR-57838 Added automatic inclusion of language and instrument resources for embedding Truffle languages in native image. We no longer produce a _resources_ folder next to the image by default. Documentation available [here](https://www.graalvm.org/reference-manual/embed-languages/#build-native-executables-from-polyglot-applications). ## Version 24.1.0 * GR-51177 Enable random offsets of runtime compiled function entry points for the UNTRUSTED polyglot sandbox policy. diff --git a/sdk/mx.sdk/mx_sdk_benchmark.py b/sdk/mx.sdk/mx_sdk_benchmark.py index 166bef1d6995..153689cee65c 100644 --- a/sdk/mx.sdk/mx_sdk_benchmark.py +++ b/sdk/mx.sdk/mx_sdk_benchmark.py @@ -1191,13 +1191,7 @@ def rules(self, out, benchmarks, bmSuiteArgs): "micronaut-similarity": {}, "micronaut-pegasus": {}, "quarkus-hello-world": {}, - "quarkus-tika-odt": { - "barista-bench-name": "quarkus-tika", - }, - "quarkus-tika-pdf": { - "barista-bench-name": "quarkus-tika", - "workload": "pdf-workload.barista.json", - }, + "quarkus-tika": {}, "spring-hello-world": {}, "spring-petclinic": {}, }, @@ -1262,7 +1256,7 @@ def subgroup(self): def benchmarkList(self, bmSuiteArgs): exclude = [] - # Barista currently does not support running 'micronaut-pegasus' on the JVM - running it results in a crash + # Barista currently does not support running 'micronaut-pegasus' on the JVM - running it results in a crash (GR-59793) exclude.append("micronaut-pegasus") return [b for b in self.completeBenchmarkList(bmSuiteArgs) if not b in exclude] @@ -1625,6 +1619,12 @@ def renaissanceIterations(self): del benchmarks["log-regression"] del benchmarks["naive-bayes"] + if self.version() in ["0.16.0"]: + del benchmarks["chi-square"] + del benchmarks["gauss-mix"] + del benchmarks["page-rank"] + del benchmarks["movie-lens"] + return benchmarks def completeBenchmarkList(self, bmSuiteArgs): @@ -1687,7 +1687,7 @@ def createCommandLineArgs(self, benchmarks, bmSuiteArgs): if any(benchmark in sparkBenchmarks for benchmark in benchmarks): # Spark benchmarks require a higher stack size than default in some configurations. # [JDK-8303076] [GR-44499] [GR-50671] - vmArgs.append("-Xss1090K") + vmArgs.append("-Xss1500K") runArgs = self.postprocessRunArgs(benchmarks[0], self.runArgs(bmSuiteArgs)) return (vmArgs + ["-jar", self.renaissancePath()] + runArgs + [benchArg]) diff --git a/sdk/mx.sdk/mx_sdk_vm_impl.py b/sdk/mx.sdk/mx_sdk_vm_impl.py index 9a652453d726..2667910f63c4 100644 --- a/sdk/mx.sdk/mx_sdk_vm_impl.py +++ b/sdk/mx.sdk/mx_sdk_vm_impl.py @@ -1238,8 +1238,8 @@ def native_image(self, build_args, output_file, out=None, err=None): ]) # Prefix native-image builds that print straight to stdout or stderr with [:] - out = out or mx.PrefixCapture(sys.stdout.write, basename(output_file)) - err = err or mx.PrefixCapture(sys.stderr.write, basename(output_file)) + out = out or mx.PrefixCapture(lambda l: mx.log(l, end=''), basename(output_file)) + err = err or mx.PrefixCapture(lambda l: mx.log(l, end='', file=sys.stderr), basename(output_file)) mx.run(native_image_command, nonZeroIsFatal=True, out=out, err=err) @@ -2306,6 +2306,9 @@ def _get_extra_jvm_args(): extra_jvm_args = mx.list_to_cmd_line(image_config.extra_jvm_args) if isinstance(self.subject.component, mx_sdk.GraalVmTruffleComponent) or image_config.is_polyglot: extra_jvm_args = ' '.join([extra_jvm_args, '--enable-native-access=org.graalvm.truffle']) + # GR-59703: Migrate sun.misc.* usages. + if mx.VersionSpec("23.0.0") <= mx.get_jdk(tag='default').version: + extra_jvm_args = ' '.join([extra_jvm_args, '--sun-misc-unsafe-memory-access=allow']) if not _jlink_libraries(): if mx.is_windows(): extra_jvm_args = ' '.join([extra_jvm_args, r'--upgrade-module-path "%location%\..\..\jvmci\graal.jar"']) @@ -2513,6 +2516,7 @@ def get_build_args(self): '-H:APIFunctionPrefix=truffle_isolate_', ] + svm_experimental_options([ '-H:+IgnoreMaxHeapSizeWhileInVMOperation', + '-H:+CopyLanguageResources', '-H:+GenerateBuildArtifactsFile', # generate 'build-artifacts.json' ]) + mx.get_runtime_jvm_args(self.subject.native_image_jar_distributions) + \ project.native_image_config.build_args + project.native_image_config.build_args_enterprise diff --git a/sdk/mx.sdk/suite.py b/sdk/mx.sdk/suite.py index 3c0c907bac32..47c3323c02fa 100644 --- a/sdk/mx.sdk/suite.py +++ b/sdk/mx.sdk/suite.py @@ -39,9 +39,9 @@ # SOFTWARE. # suite = { - "mxversion": "7.33.1", + "mxversion": "7.36.0", "name" : "sdk", - "version" : "24.2.0", + "version" : "25.0.0", "release" : False, "sourceinprojectwhitelist" : [], "url" : "https://github.com/oracle/graal", @@ -413,6 +413,19 @@ "workingSets" : "API,SDK", }, + "org.graalvm.word.test" : { + "subDir" : "src", + "sourceDirs" : ["src"], + "dependencies" : [ + "mx:JUNIT", + "org.graalvm.word" + ], + "javaCompliance" : "21+", + "workingSets" : "SDK", + "checkstyle" : "org.graalvm.word", + "graalCompilerSourceEdition": "ignore", + }, + "org.graalvm.nativeimage" : { "subDir" : "src", "sourceDirs" : ["src"], @@ -947,6 +960,7 @@ class UniversalDetector { "SDK_TEST" : { "subDir" : "src", "dependencies" : [ + "org.graalvm.word.test", "org.graalvm.collections.test", "org.graalvm.nativeimage.test", "org.graalvm.launcher.test", @@ -957,6 +971,7 @@ class UniversalDetector { "sdk:POLYGLOT", "sdk:NATIVEIMAGE", "sdk:COLLECTIONS", + "sdk:WORD", "sdk:LAUNCHER_COMMON" ], "maven" : False, diff --git a/sdk/src/org.graalvm.launcher.native/src/launcher.cc b/sdk/src/org.graalvm.launcher.native/src/launcher.cc index 94b5e7c9a72e..34516aab5f9b 100644 --- a/sdk/src/org.graalvm.launcher.native/src/launcher.cc +++ b/sdk/src/org.graalvm.launcher.native/src/launcher.cc @@ -209,6 +209,23 @@ static std::string exe_path() { #elif defined (_WIN32) char *realPath = (char *)malloc(_MAX_PATH); GetModuleFileNameA(NULL, realPath, _MAX_PATH); + /* try to do a realpath equivalent */ + HANDLE handle = CreateFile(realPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (handle != INVALID_HANDLE_VALUE) { + const size_t size = _MAX_PATH + 4; + char *resolvedPath = (char *)malloc(size); + DWORD ret = GetFinalPathNameByHandleA(handle, resolvedPath, size, 0); + /* + * The path returned from GetFinalPathNameByHandleA should always + * use "\\?\" path syntax. We strip the prefix. + * See: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file + */ + if (ret < size && resolvedPath[0] == '\\' && resolvedPath[1] == '\\' && resolvedPath[2] == '?' && resolvedPath[3] == '\\') { + strcpy_s(realPath, _MAX_PATH, resolvedPath + 4); + } + free(resolvedPath); + CloseHandle(handle); + } #endif std::string p(realPath); free(realPath); @@ -558,6 +575,10 @@ static void parse_vm_options(int argc, char **argv, std::string exeDir, JavaVMIn /* Allow Truffle NFI Panama to use Linker#{downcallHandle,upcallStub} without warnings. */ vmArgs.push_back("--enable-native-access=org.graalvm.truffle"); +// GR-59703: Migrate sun.misc.* usages. +#if LAUNCHER_JDK_VERSION >= 23 + vmArgs.push_back("--sun-misc-unsafe-memory-access=allow"); +#endif } jint nOptions = jvmMode ? vmArgs.size() : 1 + vmArgs.size(); diff --git a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest index ef23e446d560..ddb5a9414cd4 100644 --- a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest +++ b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest @@ -1088,6 +1088,7 @@ meth public !varargs static void initializeAtRunTime(java.lang.String[]) supr java.lang.Object CLSS public final org.graalvm.nativeimage.hosted.RuntimeForeignAccess +meth public !varargs static void registerForDirectUpcall(java.lang.invoke.MethodHandle,java.lang.Object,java.lang.Object[]) meth public !varargs static void registerForDowncall(java.lang.Object,java.lang.Object[]) meth public !varargs static void registerForUpcall(java.lang.Object,java.lang.Object[]) supr java.lang.Object @@ -1143,6 +1144,7 @@ meth public !varargs static void registerProxyClass(java.lang.Class[]) meth public static void registerIncludingAssociatedClasses(java.lang.Class) meth public static void registerLambdaCapturingClass(java.lang.Class) meth public static void registerWithTargetConstructorClass(java.lang.Class,java.lang.Class) + anno 0 java.lang.Deprecated(boolean forRemoval=false, java.lang.String since="24.2") supr java.lang.Object CLSS public final org.graalvm.nativeimage.hosted.RuntimeSystemProperties diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeForeignAccess.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeForeignAccess.java index 9d3586017b23..2b5aa887f6ce 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeForeignAccess.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeForeignAccess.java @@ -40,6 +40,9 @@ */ package org.graalvm.nativeimage.hosted; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; + import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -86,6 +89,34 @@ public static void registerForUpcall(Object desc, Object... options) { ImageSingletons.lookup(RuntimeForeignAccessSupport.class).registerForUpcall(ConfigurationCondition.alwaysTrue(), desc, options); } + /** + * Registers a specific static method (denoted by a method handle) as a fast upcall target. This + * will create a specialized upcall stub that will invoke only the specified method, which is + * much faster than using {@link #registerForUpcall(Object, Object...)}). + *

    + * The provided method handle must be a direct method handle. Those are most commonly created + * using {@link java.lang.invoke.MethodHandles.Lookup#findStatic(Class, String, MethodType)}. + * However, a strict requirement is that it must be possible to create a non-empty descriptor + * for the method handle using {@link MethodHandle#describeConstable()}. The denoted static + * method will also be registered for reflective access since run-time code will also create a + * method handle to denoted static method. + *

    + *

    + * Even though this method is weakly typed for compatibility reasons, runtime checks will be + * performed to ensure that the arguments have the expected type. It will be deprecated in favor + * of strongly typed variant as soon as possible. + *

    + * + * @param target A direct method handle denoting a static method. + * @param desc A {@link java.lang.foreign.FunctionDescriptor} to register for upcalls. + * @param options An array of {@link java.lang.foreign.Linker.Option} used for the upcalls. + * + * @since 24.2 + */ + public static void registerForDirectUpcall(MethodHandle target, Object desc, Object... options) { + ImageSingletons.lookup(RuntimeForeignAccessSupport.class).registerForDirectUpcall(ConfigurationCondition.alwaysTrue(), target, desc, options); + } + private RuntimeForeignAccess() { } } diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeSerialization.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeSerialization.java index 09d2c4a42e99..8de8de2ea716 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeSerialization.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeSerialization.java @@ -81,7 +81,9 @@ public static void registerIncludingAssociatedClasses(Class clazz) { * @since 21.3 */ public static void register(Class... classes) { - RuntimeSerializationSupport.singleton().register(ConfigurationCondition.alwaysTrue(), classes); + for (Class clazz : classes) { + RuntimeSerializationSupport.singleton().register(ConfigurationCondition.alwaysTrue(), clazz); + } } /** @@ -92,11 +94,16 @@ public static void register(Class... classes) { * {@code ReflectionFactory.newConstructorForSerialization(Class cl, Constructor constructorToCall)} * where the passed `constructorToCall` differs from what would automatically be used if regular * deserialization of `cl` would happen. This method exists to also support such usecases. + * + * @deprecated Use {@link #register(Class[])} instead. All possible custom constructors will be + * registered automatically. * * @since 21.3 */ + @Deprecated(since = "24.2") + @SuppressWarnings("unused") public static void registerWithTargetConstructorClass(Class clazz, Class customTargetConstructorClazz) { - RuntimeSerializationSupport.singleton().registerWithTargetConstructorClass(ConfigurationCondition.alwaysTrue(), clazz, customTargetConstructorClazz); + RuntimeSerializationSupport.singleton().register(ConfigurationCondition.alwaysTrue(), clazz); } /** diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeForeignAccessSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeForeignAccessSupport.java index 96db98ab24be..dd0537c496fc 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeForeignAccessSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeForeignAccessSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -40,8 +40,12 @@ */ package org.graalvm.nativeimage.impl; +import java.lang.invoke.MethodHandle; + public interface RuntimeForeignAccessSupport { void registerForDowncall(ConfigurationCondition condition, Object desc, Object... options); void registerForUpcall(ConfigurationCondition condition, Object desc, Object... options); + + void registerForDirectUpcall(ConfigurationCondition condition, MethodHandle target, Object desc, Object... options); } diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeSerializationSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeSerializationSupport.java index 6cecab043977..3237047e86d8 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeSerializationSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeSerializationSupport.java @@ -55,11 +55,9 @@ static RuntimeSerializationSupport singleton() { void registerIncludingAssociatedClasses(C condition, Class clazz); - void register(C condition, Class... classes); + void register(C condition, Class clazz); - void registerWithTargetConstructorClass(C condition, Class clazz, Class customTargetConstructorClazz); - - void registerWithTargetConstructorClass(C condition, String className, String customTargetConstructorClassName); + void register(C condition, String clazz); void registerLambdaCapturingClass(C condition, String lambdaCapturingClassName); diff --git a/sdk/src/org.graalvm.polyglot/snapshot.sigtest b/sdk/src/org.graalvm.polyglot/snapshot.sigtest index 9bb9e1c139a1..d083be26386a 100644 --- a/sdk/src/org.graalvm.polyglot/snapshot.sigtest +++ b/sdk/src/org.graalvm.polyglot/snapshot.sigtest @@ -90,7 +90,7 @@ meth public void printStackTrace(java.io.PrintStream) meth public void printStackTrace(java.io.PrintWriter) meth public void setStackTrace(java.lang.StackTraceElement[]) supr java.lang.Object -hfds CAUSE_CAPTION,EMPTY_THROWABLE_ARRAY,NULL_CAUSE_MESSAGE,SELF_SUPPRESSION_MESSAGE,SUPPRESSED_CAPTION,SUPPRESSED_SENTINEL,UNASSIGNED_STACK,backtrace,cause,depth,detailMessage,serialVersionUID,stackTrace,suppressedExceptions +hfds CAUSE_CAPTION,EMPTY_THROWABLE_ARRAY,NULL_CAUSE_MESSAGE,SELF_SUPPRESSION_MESSAGE,SUPPRESSED_CAPTION,SUPPRESSED_SENTINEL,UNASSIGNED_STACK,backtrace,cause,depth,detailMessage,jfrTracing,serialVersionUID,stackTrace,suppressedExceptions hcls PrintStreamOrWriter,SentinelHolder,WrappedPrintStream,WrappedPrintWriter CLSS public abstract interface java.lang.annotation.Annotation @@ -122,6 +122,15 @@ meth public abstract java.lang.annotation.ElementType[] value() CLSS public abstract interface java.lang.constant.Constable meth public abstract java.util.Optional describeConstable() +CLSS public abstract interface java.util.function.Predicate<%0 extends java.lang.Object> + anno 0 java.lang.FunctionalInterface() +meth public abstract boolean test({java.util.function.Predicate%0}) +meth public java.util.function.Predicate<{java.util.function.Predicate%0}> and(java.util.function.Predicate) +meth public java.util.function.Predicate<{java.util.function.Predicate%0}> negate() +meth public java.util.function.Predicate<{java.util.function.Predicate%0}> or(java.util.function.Predicate) +meth public static <%0 extends java.lang.Object> java.util.function.Predicate<{%%0}> isEqual(java.lang.Object) +meth public static <%0 extends java.lang.Object> java.util.function.Predicate<{%%0}> not(java.util.function.Predicate) + CLSS public final org.graalvm.polyglot.Context innr public final Builder intf java.lang.AutoCloseable @@ -147,7 +156,7 @@ meth public void leave() meth public void resetLimits() meth public void safepoint() supr java.lang.Object -hfds ALL_HOST_CLASSES,EMPTY,NO_HOST_CLASSES,UNSET_HOST_LOOKUP,currentAPI,dispatch,engine,receiver +hfds ALL_HOST_CLASSES,EMPTY,NO_HOST_CLASSES,UNSET_HOST_LOOKUP,creatorContext,currentAPI,dispatch,engine,parent,receiver CLSS public final org.graalvm.polyglot.Context$Builder outer org.graalvm.polyglot.Context @@ -213,8 +222,8 @@ meth public static org.graalvm.polyglot.Engine$Builder newBuilder() meth public void close() meth public void close(boolean) supr java.lang.Object -hfds EMPTY,ENGINES,currentAPI,dispatch,initializationException,receiver,shutdownHookInitialized -hcls APIAccessImpl,ClassPathIsolation,EngineShutDownHook,ImplHolder,PolyglotInvalid +hfds EMPTY,ENGINES,creatorEngine,currentAPI,dispatch,initializationException,receiver,shutdownHookInitialized +hcls APIAccessImpl,CleanableReference,ContextReference,EngineReference,EngineShutDownHook,ImplHolder,PolyglotInvalid CLSS public final org.graalvm.polyglot.Engine$Builder outer org.graalvm.polyglot.Engine @@ -336,16 +345,20 @@ supr java.lang.Enum CLSS public final org.graalvm.polyglot.Instrument meth public <%0 extends java.lang.Object> {%%0} lookup(java.lang.Class<{%%0}>) +meth public boolean equals(java.lang.Object) +meth public int hashCode() meth public java.lang.String getId() meth public java.lang.String getName() meth public java.lang.String getVersion() meth public java.lang.String getWebsite() meth public org.graalvm.options.OptionDescriptors getOptions() supr java.lang.Object -hfds dispatch,receiver +hfds dispatch,engine,receiver CLSS public final org.graalvm.polyglot.Language +meth public boolean equals(java.lang.Object) meth public boolean isInteractive() +meth public int hashCode() meth public java.lang.String getDefaultMimeType() meth public java.lang.String getId() meth public java.lang.String getImplementationName() @@ -355,7 +368,7 @@ meth public java.lang.String getWebsite() meth public java.util.Set getMimeTypes() meth public org.graalvm.options.OptionDescriptors getOptions() supr java.lang.Object -hfds dispatch,receiver +hfds dispatch,engine,receiver CLSS public final org.graalvm.polyglot.PolyglotAccess fld public final static org.graalvm.polyglot.PolyglotAccess ALL @@ -403,7 +416,7 @@ meth public void printStackTrace(java.io.PrintStream) meth public void printStackTrace(java.io.PrintWriter) meth public void setStackTrace(java.lang.StackTraceElement[]) supr java.lang.RuntimeException -hfds dispatch,impl +hfds anchor,dispatch,impl CLSS public final org.graalvm.polyglot.PolyglotException$StackFrame outer org.graalvm.polyglot.PolyglotException @@ -540,6 +553,7 @@ supr java.lang.Object hfds rawType,type CLSS public final org.graalvm.polyglot.Value +innr public final static StringEncoding meth public !varargs org.graalvm.polyglot.Value execute(java.lang.Object[]) meth public !varargs org.graalvm.polyglot.Value invokeMember(java.lang.String,java.lang.Object[]) meth public !varargs org.graalvm.polyglot.Value newInstance(java.lang.Object[]) @@ -591,6 +605,7 @@ meth public boolean removeHashEntry(java.lang.Object) meth public boolean removeMember(java.lang.String) meth public byte asByte() meth public byte readBufferByte(long) +meth public byte[] asStringBytes(org.graalvm.polyglot.Value$StringEncoding) meth public double asDouble() meth public double readBufferDouble(java.nio.ByteOrder,long) meth public float asFloat() @@ -632,6 +647,10 @@ meth public org.graalvm.polyglot.Value getMetaParents() meth public short asShort() meth public short readBufferShort(java.nio.ByteOrder,long) meth public static org.graalvm.polyglot.Value asValue(java.lang.Object) +meth public static org.graalvm.polyglot.Value fromByteBasedString(byte[],int,int,org.graalvm.polyglot.Value$StringEncoding,boolean) +meth public static org.graalvm.polyglot.Value fromByteBasedString(byte[],org.graalvm.polyglot.Value$StringEncoding) +meth public static org.graalvm.polyglot.Value fromNativeString(long,int,int,org.graalvm.polyglot.Value$StringEncoding,boolean) +meth public static org.graalvm.polyglot.Value fromNativeString(long,int,org.graalvm.polyglot.Value$StringEncoding) meth public void pin() meth public void putHashEntry(java.lang.Object,java.lang.Object) meth public void putMember(java.lang.String,java.lang.Object) @@ -645,6 +664,18 @@ meth public void writeBufferLong(java.nio.ByteOrder,long,long) meth public void writeBufferShort(java.nio.ByteOrder,long,short) supr java.lang.Object +CLSS public final static org.graalvm.polyglot.Value$StringEncoding + outer org.graalvm.polyglot.Value +fld public final static org.graalvm.polyglot.Value$StringEncoding UTF_16 +fld public final static org.graalvm.polyglot.Value$StringEncoding UTF_16_BIG_ENDIAN +fld public final static org.graalvm.polyglot.Value$StringEncoding UTF_16_LITTLE_ENDIAN +fld public final static org.graalvm.polyglot.Value$StringEncoding UTF_32 +fld public final static org.graalvm.polyglot.Value$StringEncoding UTF_32_BIG_ENDIAN +fld public final static org.graalvm.polyglot.Value$StringEncoding UTF_32_LITTLE_ENDIAN +fld public final static org.graalvm.polyglot.Value$StringEncoding UTF_8 +supr java.lang.Object +hfds value + CLSS public abstract interface org.graalvm.polyglot.io.ByteSequence meth public abstract byte byteAt(int) meth public abstract int length() @@ -654,7 +685,9 @@ meth public org.graalvm.polyglot.io.ByteSequence subSequence(int,int) meth public static org.graalvm.polyglot.io.ByteSequence create(byte[]) CLSS public abstract interface org.graalvm.polyglot.io.FileSystem +innr public abstract static Selector meth public !varargs boolean isSameFile(java.nio.file.Path,java.nio.file.Path,java.nio.file.LinkOption[]) throws java.io.IOException +meth public !varargs static org.graalvm.polyglot.io.FileSystem newCompositeFileSystem(org.graalvm.polyglot.io.FileSystem,org.graalvm.polyglot.io.FileSystem$Selector[]) meth public !varargs void copy(java.nio.file.Path,java.nio.file.Path,java.nio.file.CopyOption[]) throws java.io.IOException meth public !varargs void createSymbolicLink(java.nio.file.Path,java.nio.file.Path,java.nio.file.attribute.FileAttribute[]) throws java.io.IOException meth public !varargs void move(java.nio.file.Path,java.nio.file.Path,java.nio.file.CopyOption[]) throws java.io.IOException @@ -679,11 +712,22 @@ meth public static org.graalvm.polyglot.io.FileSystem allowInternalResourceAcces meth public static org.graalvm.polyglot.io.FileSystem allowLanguageHomeAccess(org.graalvm.polyglot.io.FileSystem) anno 0 java.lang.Deprecated(boolean forRemoval=false, java.lang.String since="") meth public static org.graalvm.polyglot.io.FileSystem newDefaultFileSystem() +meth public static org.graalvm.polyglot.io.FileSystem newDenyIOFileSystem() meth public static org.graalvm.polyglot.io.FileSystem newFileSystem(java.nio.file.FileSystem) meth public static org.graalvm.polyglot.io.FileSystem newReadOnlyFileSystem(org.graalvm.polyglot.io.FileSystem) meth public void createLink(java.nio.file.Path,java.nio.file.Path) throws java.io.IOException meth public void setCurrentWorkingDirectory(java.nio.file.Path) +CLSS public abstract static org.graalvm.polyglot.io.FileSystem$Selector + outer org.graalvm.polyglot.io.FileSystem +cons protected init(org.graalvm.polyglot.io.FileSystem) +intf java.util.function.Predicate +meth public abstract boolean test(java.nio.file.Path) +meth public final org.graalvm.polyglot.io.FileSystem getFileSystem() +meth public static org.graalvm.polyglot.io.FileSystem$Selector of(org.graalvm.polyglot.io.FileSystem,java.util.function.Predicate) +supr java.lang.Object +hfds fileSystem + CLSS public final org.graalvm.polyglot.io.IOAccess fld public final static org.graalvm.polyglot.io.IOAccess ALL fld public final static org.graalvm.polyglot.io.IOAccess NONE @@ -772,7 +816,7 @@ intf java.lang.AutoCloseable meth public static org.graalvm.polyglot.management.ExecutionListener$Builder newBuilder() meth public void close() supr java.lang.Object -hfds EMPTY,dispatch,receiver +hfds EMPTY,creatorEngine,dispatch,receiver CLSS public final org.graalvm.polyglot.management.ExecutionListener$Builder outer org.graalvm.polyglot.management.ExecutionListener diff --git a/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Engine.java b/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Engine.java index cebdc77f4a9a..c77f9ee47d71 100644 --- a/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Engine.java +++ b/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Engine.java @@ -1960,6 +1960,16 @@ public FileSystem newNIOFileSystem(java.nio.file.FileSystem fileSystem) { throw noPolyglotImplementationFound(); } + @Override + public FileSystem newCompositeFileSystem(FileSystem fallbackFileSystem, FileSystem.Selector... delegates) { + throw noPolyglotImplementationFound(); + } + + @Override + public FileSystem newDenyIOFileSystem() { + throw noPolyglotImplementationFound(); + } + @Override public ByteSequence asByteSequence(Object object) { throw noPolyglotImplementationFound(); diff --git a/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/PolyglotException.java b/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/PolyglotException.java index 2cd5da242168..9a72c4a21236 100644 --- a/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/PolyglotException.java +++ b/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/PolyglotException.java @@ -173,7 +173,7 @@ public StackTraceElement[] getStackTrace() { /** * Gets a user readable message for the polyglot exception. In case the exception is * {@link #isInternalError() internal} then the original java class name is included in the - * message. The message never returns null. + * message. The message may return null if no message is available. * * @since 19.0 */ diff --git a/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Value.java b/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Value.java index 8c9367a06d8b..8c2257677b82 100644 --- a/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Value.java +++ b/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Value.java @@ -539,7 +539,7 @@ public byte readBufferByte(long byteOffset) throws UnsupportedOperationException * Invoking this message does not cause any observable side-effects. *

    * Example reading into an output stream using a 4k auxiliary byte array: - * + * *

          * Value val = ...
          * assert val.hasBufferElements();
    @@ -556,11 +556,11 @@ public byte readBufferByte(long byteOffset) throws UnsupportedOperationException
          *
          * In case the goal is to read the whole contents into a single byte array, the easiest way is
          * to do that through {@link ByteSequence}:
    -     * 
    +     *
          * 
          * byte[] byteArray = val.as(ByteSequence.class).toByteArray();
          * 
    - * + * * @param byteOffset offset in the buffer to start reading from. * @param destination byte array to write the read bytes into. * @param destinationOffset offset in the destination array to start writing from. @@ -1195,6 +1195,34 @@ public String asString() { } } + /** + * Returns the bytes of a given string value without converting it to a Java {@link String}. + *

    + * This method retrieves the raw bytes of the string in the specified {@link StringEncoding}, + * avoiding intermediate conversions to a Java {@code String}. This is particularly useful for + * performance-sensitive scenarios where the overhead of creating a Java {@code String} is + * undesirable. + *

    + * If the string is not already encoded in the specified encoding, it will be re-encoded before + * the bytes are returned. Note that re-encoding may involve additional computational overhead + * depending on the size of the string and the differences between its current encoding and the + * target encoding. + * + * Usage Note: The returned byte array represents the raw data of the string in the + * requested encoding. Modifications to the array will not affect the underlying string value. + * + * @param encoding the desired encoding for the string. Must not be null. Supported + * encodings are defined in {@link StringEncoding}. + * @return a byte array containing the string's raw bytes in the specified encoding + * @throws NullPointerException if {@code encoding} is null + * @throws IllegalStateException if the string value is no longer valid (e.g., the associated + * context has been closed) + * @since 24.2 + */ + public byte[] asStringBytes(StringEncoding encoding) { + return dispatch.asStringBytes(this.context, receiver, encoding.value); + } + /** * Returns true if this value represents a {@link #isNumber() number} and the value * fits in int, else false. @@ -2576,6 +2604,182 @@ public void pin() { dispatch.pin(this.context, receiver); Reference.reachabilityFence(creatorContext); } + + /** + * Creates a byte-based string value that can be passed to polyglot languages. + *

    + * The returned value is guaranteed to return true for {@link Value#isString()}. + * The string can later be retrieved as a byte array using + * {@link Value#asStringBytes(StringEncoding)}. This method ensures immutability by + * conservatively copying the byte array before passing it to the underlying implementation. + *

    + * + * Performance Note: Copying the byte array can have a performance impact. Use this + * method when immutability is required, or use the more flexible overloaded method + * {@link #fromByteBasedString(byte[], int, int, StringEncoding, boolean)} to control copying + * behavior. + * + * @param bytes the byte array representing the string + * @param encoding the encoding of the byte array + * @return a polyglot string {@link Value} + * @throws NullPointerException if either {@code bytes} or {@code encoding} is null + * @since 24.2 + */ + public static Value fromByteBasedString(byte[] bytes, StringEncoding encoding) { + Objects.requireNonNull(bytes); + Objects.requireNonNull(encoding); + return Engine.getImpl().fromByteBasedString(bytes, 0, bytes.length, encoding.value, true); + } + + /** + * Creates a byte-based string value with more granular control over the byte array's usage. + *

    + * This method provides additional flexibility by allowing a subset of the byte array to be + * passed and controlling whether the byte array should be copied to ensure immutability. + * + * @param bytes the byte array representing the string + * @param offset the starting offset in the byte array + * @param length the number of bytes to include starting from {@code offset} + * @param encoding the encoding of the byte array + * @param copy whether to copy the byte array to ensure immutability + * @return a polyglot string {@link Value} + * @since 24.2 + */ + public static Value fromByteBasedString(byte[] bytes, int offset, int length, StringEncoding encoding, boolean copy) { + Objects.requireNonNull(bytes); + Objects.requireNonNull(encoding); + if (offset < 0) { + throw new IndexOutOfBoundsException("byteLength must not be negative"); + } + if (length < 0) { + throw new IndexOutOfBoundsException("byteOffset must not be negative"); + } + if (offset + length > bytes.length) { + throw new IndexOutOfBoundsException("byte index is out of bounds"); + } + return Engine.getImpl().fromByteBasedString(bytes, offset, length, encoding.value, copy); + } + + /** + * Creates a native string object that can be passed to polyglot languages. + *

    + * Native strings avoid copying, offering better performance for certain use cases. However, + * clients must guarantee the lifetime of the native string as long as the {@link Value} is + * alive. The returned value is guaranteed to return true for + * {@link Value#isString()}. + *

    + * Usage Warning: The polyglot context or engine does not manage the lifetime of the + * native pointer. Clients must ensure that the pointer remains valid and that the memory is not + * deallocated while the string is in use. Passing a deallocated or invalid pointer can result + * in crashes or undefined behavior. + *

    + * Note: Whenever possible, use {@link #fromByteBasedString(byte[], StringEncoding)} to + * avoid the risks associated with native memory management. + * + *

      + *
    • The native string's memory must remain valid for the lifetime of the context it is passed + * to. + *
    • The native bytes must not be mutated after being passed to this method. + *
    • The bytes must already be encoded with the specified encoding. + *
    + * + * @param basePointer the raw base pointer to the native string in memory + * @param byteLength the length of the string in bytes + * @param encoding the encoding of the native string + * @param copy whether to copy the native string bytes for additional safety + * @return a polyglot string {@link Value} + * @since 24.2 + */ + public static Value fromNativeString(long basePointer, int byteOffset, int byteLength, StringEncoding encoding, boolean copy) { + Objects.requireNonNull(encoding); + if (basePointer == 0L) { + throw new NullPointerException("Null base pointer provided."); + } + if (byteLength < 0) { + throw new IndexOutOfBoundsException("byteLength must not be negative"); + } + if (byteOffset < 0) { + throw new IndexOutOfBoundsException("byteOffset must not be negative"); + } + return Engine.getImpl().fromNativeString(basePointer, byteOffset, byteLength, encoding.value, copy); + } + + /** + * Creates a native string object with default safety settings. + *

    + * This method is equivalent to calling + * {@link #fromNativeString(long, int, int, StringEncoding, boolean)} with {@code copy} set to + * {@code true}. + *

    + * + * @param basePointer the raw base pointer to the native string in memory + * @param byteLength the length of the string in bytes + * @param encoding the encoding of the native string + * @return a polyglot string {@link Value} + * @since 24.2 + */ + public static Value fromNativeString(long basePointer, int byteLength, StringEncoding encoding) { + return fromNativeString(basePointer, 0, byteLength, encoding, true); + } + + /** + * Enum like class representing the supported string encodings. The encodings determine how byte + * arrays or native strings are interpreted when creating or retrieving string values. This + * class is not directly a enum to support compatible evolution. + * + * @since 24.2 + */ + public static final class StringEncoding { + + /** + * @since 24.2 + */ + public static final StringEncoding UTF_8 = new StringEncoding(0); + + /** + * @since 24.2 + */ + public static final StringEncoding UTF_16_LITTLE_ENDIAN = new StringEncoding(1); + /** + * @since 24.2 + */ + public static final StringEncoding UTF_16_BIG_ENDIAN = new StringEncoding(2); + /** + * @since 24.2 + */ + public static final StringEncoding UTF_32_LITTLE_ENDIAN = new StringEncoding(3); + /** + * @since 24.2 + */ + public static final StringEncoding UTF_32_BIG_ENDIAN = new StringEncoding(4); + + /** + * The native UTF 16 encoding for the current platform. + * + * @see ByteOrder#nativeOrder() + * @since 24.2 + */ + public static final StringEncoding UTF_16 = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN ? UTF_16_LITTLE_ENDIAN : UTF_16_BIG_ENDIAN; + + /** + * The native UTF 32 encoding for the current platform. + * + * @see ByteOrder#nativeOrder() + * @since 24.2 + */ + public static final StringEncoding UTF_32 = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN ? UTF_32_LITTLE_ENDIAN : UTF_32_BIG_ENDIAN; + + /* + * Mapping table to PolyglotImpl.LazyEncodings.TABLE. Keep in sync. + */ + final int value; + + private StringEncoding(int value) { + this.value = value; + } + + } + } abstract class AbstractValue { diff --git a/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/impl/AbstractPolyglotImpl.java b/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/impl/AbstractPolyglotImpl.java index af859b24a361..95ebe0ba192b 100644 --- a/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/impl/AbstractPolyglotImpl.java +++ b/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/impl/AbstractPolyglotImpl.java @@ -1402,6 +1402,8 @@ public boolean hasHashEntry(Object context, Object receiver, Object key) { public abstract Object getHashValuesIterator(Object context, Object receiver); public abstract void pin(Object languageContext, Object receiver); + + public abstract byte[] asStringBytes(Object context, Object receiver, int encoding); } public Class loadLanguageClass(String className) { @@ -1416,6 +1418,14 @@ public Object asValue(Object o) { return getNext().asValue(o); } + public Value fromNativeString(long basePointer, int byteOffset, int byteLength, int encoding, boolean copy) { + return getNext().fromNativeString(basePointer, byteOffset, byteLength, encoding, copy); + } + + public Value fromByteBasedString(byte[] bytes, int offset, int length, int encoding, boolean copy) { + return getNext().fromByteBasedString(bytes, offset, length, encoding, copy); + } + public Object newTargetTypeMapping(Class sourceType, Class targetType, Predicate acceptsValue, Function convertValue, TargetMappingPrecedence precedence) { return getNext().newTargetTypeMapping(sourceType, targetType, acceptsValue, convertValue, precedence); } @@ -1440,6 +1450,14 @@ public FileSystem newNIOFileSystem(java.nio.file.FileSystem fileSystem) { return getNext().newNIOFileSystem(fileSystem); } + public FileSystem newCompositeFileSystem(FileSystem fallbackFileSystem, FileSystem.Selector... delegates) { + return getNext().newCompositeFileSystem(fallbackFileSystem, delegates); + } + + public FileSystem newDenyIOFileSystem() { + return getNext().newDenyIOFileSystem(); + } + public ByteSequence asByteSequence(Object object) { return getNext().asByteSequence(object); } diff --git a/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/io/FileSystem.java b/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/io/FileSystem.java index 32ced7489385..a2d350400f97 100644 --- a/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/io/FileSystem.java +++ b/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/io/FileSystem.java @@ -66,6 +66,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Predicate; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Engine; @@ -578,4 +579,119 @@ static FileSystem newReadOnlyFileSystem(FileSystem fileSystem) { static FileSystem newFileSystem(java.nio.file.FileSystem fileSystem) { return IOHelper.ImplHolder.IMPL.newNIOFileSystem(fileSystem); } + + /** + * Creates a {@link FileSystem} that denies all file operations except for path parsing. Any + * attempt to perform file operations such as reading, writing, or deletion will result in a + * {@link SecurityException} being thrown. + *

    + * Typically, this file system does not need to be explicitly installed to restrict access to + * host file systems. Instead, use {@code Context.newBuilder().allowIO(IOAccess.NONE)}. This + * method is intended primarily for use as a fallback file system in a + * {@link #newCompositeFileSystem(FileSystem, Selector...) composite file system}. + * + * @since 24.2 + */ + static FileSystem newDenyIOFileSystem() { + return IOHelper.ImplHolder.IMPL.newDenyIOFileSystem(); + } + + /** + * Creates a composite {@link FileSystem} that delegates operations to the provided + * {@code delegates}. The {@link FileSystem} of the first {@code delegate} whose + * {@link Selector#test(Path)} method accepts the path is used for the file system operation. If + * no {@code delegate} accepts the path, the {@code fallbackFileSystem} is used. + *

    + * The {@code fallbackFileSystem} is responsible for parsing {@link Path} objects. All provided + * file systems must use the same {@link Path} type, {@link #getSeparator() separator}, and + * {@link #getPathSeparator() path separator}. If any file system does not meet this + * requirement, an {@link IllegalArgumentException} is thrown. + *

    + * The composite file system maintains its own notion of the current working directory and + * ensures that the {@link #setCurrentWorkingDirectory(Path)} method is not invoked on any of + * the delegates. When a request to set the current working directory is received, the composite + * file system verifies that the specified path corresponds to an existing directory by + * consulting either the appropriate delegate or the {@code fallbackFileSystem}. If an explicit + * current working directory has been set, the composite file system normalizes and resolves all + * relative paths to absolute paths prior to delegating operations. Conversely, if no explicit + * current working directory is set, the composite file system directly forwards the incoming + * path, whether relative or absolute, to the appropriate delegate. Furthermore, when an + * explicit current working directory is set, the composite file system does not delegate + * {@code toAbsolutePath} operations, as delegates do not maintain an independent notion of the + * current working directory. If the current working directory is unset, {@code toAbsolutePath} + * operations are delegated to the {@code fallbackFileSystem}. + *

    + * Operations that are independent of path context, including {@code getTempDirectory}, + * {@code getSeparator}, and {@code getPathSeparator}, are handled exclusively by the + * {@code fallbackFileSystem}. + * + * @throws IllegalArgumentException if the file systems do not use the same {@link Path} type, + * {@link #getSeparator() separator}, or {@link #getPathSeparator() path separator} + * @since 24.2 + */ + static FileSystem newCompositeFileSystem(FileSystem fallbackFileSystem, Selector... delegates) { + return IOHelper.ImplHolder.IMPL.newCompositeFileSystem(fallbackFileSystem, delegates); + } + + /** + * A selector for determining which {@link FileSystem} should handle operations on a given + * {@link Path}. This class encapsulates a {@link FileSystem} and defines a condition for + * selecting it. + * + * @since 24.2 + */ + abstract class Selector implements Predicate { + + private final FileSystem fileSystem; + + /** + * Creates a {@link Selector} for the specified {@link FileSystem}. + * + * @since 24.2 + */ + protected Selector(FileSystem fileSystem) { + this.fileSystem = Objects.requireNonNull(fileSystem, "FileSystem must be non-null"); + } + + /** + * Returns the {@link FileSystem} associated with this selector. + * + * @since 24.2 + */ + public final FileSystem getFileSystem() { + return fileSystem; + } + + /** + * Tests whether the {@link FileSystem} associated with this selector can handle operations + * on the specified {@link Path}. + * + * @param path the path to test, provided as a normalized absolute path. The given + * {@code path} has no path components equal to {@code "."} or {@code ".."}. + * @return {@code true} if the associated {@link FileSystem} can handle the {@code path}; + * {@code false} otherwise + * @since 24.2 + */ + public abstract boolean test(Path path); + + /** + * Creates a {@link Selector} for the specified {@link FileSystem} using the provided + * {@link Predicate}. + * + * @param fileSystem the {@link FileSystem} to associate with the selector + * @param predicate the condition to determine if the {@link FileSystem} can handle a given + * path + * @return a new {@link Selector} that delegates path testing to the {@code predicate} + * @since 24.2 + */ + public static Selector of(FileSystem fileSystem, Predicate predicate) { + Objects.requireNonNull(predicate, "Predicate must be non-null"); + return new Selector(fileSystem) { + @Override + public boolean test(Path path) { + return predicate.test(path); + } + }; + } + } } diff --git a/sdk/src/org.graalvm.word.test/src/org/graalvm/word/test/WordTests.java b/sdk/src/org.graalvm.word.test/src/org/graalvm/word/test/WordTests.java new file mode 100644 index 000000000000..4c64e3424cd2 --- /dev/null +++ b/sdk/src/org.graalvm.word.test/src/org/graalvm/word/test/WordTests.java @@ -0,0 +1,268 @@ +/* + * 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 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.word.test; + +import static org.graalvm.word.WordFactory.unsigned; +import static org.graalvm.word.WordFactory.signed; +import static org.graalvm.word.WordFactory.pointer; + +import org.graalvm.word.Pointer; +import org.graalvm.word.SignedWord; +import org.graalvm.word.UnsignedWord; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; +import java.util.stream.LongStream; + +public class WordTests { + + static long[] words = { + Long.MIN_VALUE, + Long.MIN_VALUE + 1, + -1L, + 0L, + 1L, + Long.MAX_VALUE - 1, + Long.MAX_VALUE, + Integer.MAX_VALUE - 1L, + Integer.MAX_VALUE, + Integer.MAX_VALUE + 1L, + Integer.MIN_VALUE - 1L, + Integer.MIN_VALUE, + Integer.MIN_VALUE + 1L + }; + + static SignedWord signedWord(long val) { + return signed(val); + } + + static UnsignedWord unsignedWord(long val) { + return unsigned(val); + } + + static Pointer asPointer(long val) { + return pointer(val); + } + + static List signedWords = LongStream.of(words).mapToObj(WordTests::signedWord).toList(); + static List unsignedWords = LongStream.of(words).mapToObj(WordTests::unsignedWord).toList(); + static List pointers = LongStream.of(words).mapToObj(WordTests::asPointer).toList(); + + @Test + public void testSigned() { + for (var x : signedWords) { + Assert.assertEquals(x.not().rawValue(), ~x.rawValue()); + + for (var y : signedWords) { + Assert.assertEquals(x.equal(y), x == y); + Assert.assertEquals(x.notEqual(y), x != y); + + Assert.assertEquals(x.add(y).rawValue(), x.rawValue() + y.rawValue()); + Assert.assertEquals(x.subtract(y).rawValue(), x.rawValue() - y.rawValue()); + Assert.assertEquals(x.multiply(y).rawValue(), x.rawValue() * y.rawValue()); + + if (y.rawValue() != 0) { + Assert.assertEquals(x.signedDivide(y).rawValue(), x.rawValue() / y.rawValue()); + Assert.assertEquals(x.signedRemainder(y).rawValue(), x.rawValue() % y.rawValue()); + } + Assert.assertEquals(x.and(y).rawValue(), x.rawValue() & y.rawValue()); + Assert.assertEquals(x.or(y).rawValue(), x.rawValue() | y.rawValue()); + Assert.assertEquals(x.xor(y).rawValue(), x.rawValue() ^ y.rawValue()); + + Assert.assertEquals(x.equal(y), x == y); + Assert.assertEquals(x.notEqual(y), x != y); + + Assert.assertEquals(x.greaterThan(y), x.rawValue() > y.rawValue()); + Assert.assertEquals(x.greaterOrEqual(y), x.rawValue() >= y.rawValue()); + Assert.assertEquals(x.lessThan(y), x.rawValue() < y.rawValue()); + Assert.assertEquals(x.lessOrEqual(y), x.rawValue() <= y.rawValue()); + + Assert.assertEquals(x.shiftLeft((UnsignedWord) y).rawValue(), x.rawValue() << y.rawValue()); + Assert.assertEquals(x.signedShiftRight((UnsignedWord) y).rawValue(), x.rawValue() >> y.rawValue()); + } + } + } + + @Test + public void testUnsigned() { + for (var x : unsignedWords) { + Assert.assertEquals(x.not().rawValue(), ~x.rawValue()); + + for (var y : unsignedWords) { + Assert.assertEquals(x.equal(y), x == y); + Assert.assertEquals(x.notEqual(y), x != y); + + Assert.assertEquals(x.add(y).rawValue(), x.rawValue() + y.rawValue()); + Assert.assertEquals(x.subtract(y).rawValue(), x.rawValue() - y.rawValue()); + Assert.assertEquals(x.multiply(y).rawValue(), x.rawValue() * y.rawValue()); + + if (y.rawValue() != 0) { + Assert.assertEquals(x.unsignedDivide(y).rawValue(), Long.divideUnsigned(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.unsignedRemainder(y).rawValue(), Long.remainderUnsigned(x.rawValue(), y.rawValue())); + } + Assert.assertEquals(x.and(y).rawValue(), x.rawValue() & y.rawValue()); + Assert.assertEquals(x.or(y).rawValue(), x.rawValue() | y.rawValue()); + Assert.assertEquals(x.xor(y).rawValue(), x.rawValue() ^ y.rawValue()); + + Assert.assertEquals(x.equal(y), x == y); + Assert.assertEquals(x.notEqual(y), x != y); + + Assert.assertEquals(x.aboveThan(y), longAboveThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.aboveOrEqual(y), longAboveOrEqual(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowThan(y), longBelowThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowOrEqual(y), longBelowOrEqual(x.rawValue(), y.rawValue())); + + Assert.assertEquals(x.shiftLeft(y).rawValue(), x.rawValue() << y.rawValue()); + Assert.assertEquals(x.unsignedShiftRight(y).rawValue(), x.rawValue() >>> y.rawValue()); + } + } + } + + @Test + public void testPointer() { + for (var x : pointers) { + Assert.assertEquals(x.not().rawValue(), ~x.rawValue()); + Assert.assertEquals(x.isNull(), x.rawValue() == 0); + Assert.assertEquals(x.isNonNull(), x.rawValue() != 0); + + for (var y : pointers) { + Assert.assertEquals(x.equal(y), x == y); + Assert.assertEquals(x.notEqual(y), x != y); + + Assert.assertEquals(x.add(y).rawValue(), x.rawValue() + y.rawValue()); + Assert.assertEquals(x.subtract(y).rawValue(), x.rawValue() - y.rawValue()); + Assert.assertEquals(x.multiply(y).rawValue(), x.rawValue() * y.rawValue()); + + if (y.rawValue() != 0) { + Assert.assertEquals(x.unsignedDivide(y).rawValue(), Long.divideUnsigned(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.unsignedRemainder(y).rawValue(), Long.remainderUnsigned(x.rawValue(), y.rawValue())); + } + Assert.assertEquals(x.and(y).rawValue(), x.rawValue() & y.rawValue()); + Assert.assertEquals(x.or(y).rawValue(), x.rawValue() | y.rawValue()); + Assert.assertEquals(x.xor(y).rawValue(), x.rawValue() ^ y.rawValue()); + + Assert.assertEquals(x.equal(y), x == y); + Assert.assertEquals(x.notEqual(y), x != y); + + Assert.assertEquals(x.aboveThan(y), longAboveThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.aboveOrEqual(y), longAboveOrEqual(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowThan(y), longBelowThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowOrEqual(y), longBelowOrEqual(x.rawValue(), y.rawValue())); + + Assert.assertEquals(x.shiftLeft(y).rawValue(), x.rawValue() << y.rawValue()); + Assert.assertEquals(x.unsignedShiftRight(y).rawValue(), x.rawValue() >>> y.rawValue()); + } + } + } + + @Test + public void testWords() { + for (long x : words) { + Assert.assertEquals(signed(x).not().rawValue(), ~x); + Assert.assertEquals(pointer(x).isNull(), x == 0); + Assert.assertEquals(pointer(x).isNonNull(), x != 0); + + for (long y : words) { + Assert.assertEquals(signed(x).equal(signed(y)), x == y); + Assert.assertEquals(signed(x).notEqual(signed(y)), x != y); + + Assert.assertEquals(signed(x).add(signed(y)).rawValue(), x + y); + Assert.assertEquals(signed(x).subtract(signed(y)).rawValue(), x - y); + Assert.assertEquals(signed(x).multiply(signed(y)).rawValue(), x * y); + + Assert.assertEquals(unsigned(x).add(unsigned(y)).rawValue(), x + y); + Assert.assertEquals(unsigned(x).subtract(unsigned(y)).rawValue(), x - y); + Assert.assertEquals(unsigned(x).multiply(unsigned(y)).rawValue(), x * y); + + if (y != 0) { + Assert.assertEquals(unsigned(x).unsignedDivide(unsigned(y)).rawValue(), Long.divideUnsigned(x, y)); + Assert.assertEquals(unsigned(x).unsignedRemainder(unsigned(y)).rawValue(), Long.remainderUnsigned(x, y)); + Assert.assertEquals(signed(x).signedDivide(signed(y)).rawValue(), x / y); + Assert.assertEquals(signed(x).signedRemainder(signed(y)).rawValue(), x % y); + } + Assert.assertEquals(signed(x).and(signed(y)).rawValue(), x & y); + Assert.assertEquals(signed(x).or(signed(y)).rawValue(), x | y); + Assert.assertEquals(signed(x).xor(signed(y)).rawValue(), x ^ y); + + Assert.assertEquals(unsigned(x).and(unsigned(y)).rawValue(), x & y); + Assert.assertEquals(unsigned(x).or(unsigned(y)).rawValue(), x | y); + Assert.assertEquals(unsigned(x).xor(unsigned(y)).rawValue(), x ^ y); + + Assert.assertEquals(signed(x).equal(signed(y)), x == y); + Assert.assertEquals(signed(x).notEqual(signed(y)), x != y); + Assert.assertEquals(unsigned(x).equal(unsigned(y)), x == y); + Assert.assertEquals(unsigned(x).notEqual(unsigned(y)), x != y); + + Assert.assertEquals(signed(x).greaterThan(signed(y)), x > y); + Assert.assertEquals(signed(x).greaterOrEqual(signed(y)), x >= y); + Assert.assertEquals(signed(x).lessThan(signed(y)), x < y); + Assert.assertEquals(signed(x).lessOrEqual(signed(y)), x <= y); + + Assert.assertEquals(unsigned(x).aboveThan(unsigned(y)), longAboveThan(x, y)); + Assert.assertEquals(unsigned(x).aboveOrEqual(unsigned(y)), longAboveOrEqual(x, y)); + Assert.assertEquals(unsigned(x).belowThan(unsigned(y)), longBelowThan(x, y)); + Assert.assertEquals(unsigned(x).belowOrEqual(unsigned(y)), longBelowOrEqual(x, y)); + + Assert.assertEquals(signed(x).shiftLeft(signed(y)).rawValue(), x << y); + Assert.assertEquals(signed(x).signedShiftRight(signed(y)).rawValue(), x >> y); + Assert.assertEquals(unsigned(x).shiftLeft(unsigned(y)).rawValue(), x << y); + Assert.assertEquals(unsigned(x).unsignedShiftRight(unsigned(y)).rawValue(), x >>> y); + } + } + } + + static boolean longAboveThan(long a, long b) { + return Long.compareUnsigned(a, b) > 0; + } + + static boolean longAboveOrEqual(long a, long b) { + return Long.compareUnsigned(a, b) >= 0; + } + + static boolean longBelowThan(long a, long b) { + return Long.compareUnsigned(a, b) < 0; + } + + static boolean longBelowOrEqual(long a, long b) { + return Long.compareUnsigned(a, b) <= 0; + } +} diff --git a/sdk/src/org.graalvm.word/src/org/graalvm/word/Word.java b/sdk/src/org.graalvm.word/src/org/graalvm/word/Word.java new file mode 100644 index 000000000000..d62e9b74fd6c --- /dev/null +++ b/sdk/src/org.graalvm.word/src/org/graalvm/word/Word.java @@ -0,0 +1,658 @@ +/* + * 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 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.word; + +import static java.lang.Long.compareUnsigned; + +/** + * A box for words that supports all operations except memory accesses (see + * {@link WordFactory#pointer(long)}). + */ +final class Word implements SignedWord, UnsignedWord, Pointer { + + private final long rawValue; + + Word(long val) { + this.rawValue = val; + } + + @SuppressWarnings("unchecked") + static T box(long val) { + return (T) new Word(val); + } + + @Override + public native Object toObject(); + + @Override + public native T toObject(Class clazz, boolean nonNull); + + @Override + public native Object toObjectNonNull(); + + @Override + public native byte readByte(WordBase offset, LocationIdentity locationIdentity); + + @Override + public native char readChar(WordBase offset, LocationIdentity locationIdentity); + + @Override + public native short readShort(WordBase offset, LocationIdentity locationIdentity); + + @Override + public native int readInt(WordBase offset, LocationIdentity locationIdentity); + + @Override + public native long readLong(WordBase offset, LocationIdentity locationIdentity); + + @Override + public native float readFloat(WordBase offset, LocationIdentity locationIdentity); + + @Override + public native double readDouble(WordBase offset, LocationIdentity locationIdentity); + + @Override + public native T readWord(WordBase offset, LocationIdentity locationIdentity); + + @Override + public native Object readObject(WordBase offset, LocationIdentity locationIdentity); + + @Override + public native byte readByte(int offset, LocationIdentity locationIdentity); + + @Override + public native char readChar(int offset, LocationIdentity locationIdentity); + + @Override + public native short readShort(int offset, LocationIdentity locationIdentity); + + @Override + public native int readInt(int offset, LocationIdentity locationIdentity); + + @Override + public native long readLong(int offset, LocationIdentity locationIdentity); + + @Override + public native float readFloat(int offset, LocationIdentity locationIdentity); + + @Override + public native double readDouble(int offset, LocationIdentity locationIdentity); + + @Override + public native T readWord(int offset, LocationIdentity locationIdentity); + + @Override + public native Object readObject(int offset, LocationIdentity locationIdentity); + + @Override + public native T readWordVolatile(int offset, LocationIdentity locationIdentity); + + @Override + public native void writeByte(WordBase offset, byte val, LocationIdentity locationIdentity); + + @Override + public native void writeChar(WordBase offset, char val, LocationIdentity locationIdentity); + + @Override + public native void writeShort(WordBase offset, short val, LocationIdentity locationIdentity); + + @Override + public native void writeInt(WordBase offset, int val, LocationIdentity locationIdentity); + + @Override + public native void writeLong(WordBase offset, long val, LocationIdentity locationIdentity); + + @Override + public native void writeFloat(WordBase offset, float val, LocationIdentity locationIdentity); + + @Override + public native void writeDouble(WordBase offset, double val, LocationIdentity locationIdentity); + + @Override + public native void writeWord(WordBase offset, WordBase val, LocationIdentity locationIdentity); + + @Override + public void initializeLong(WordBase offset, long val, LocationIdentity locationIdentity) { + + } + + @Override + public native void writeObject(WordBase offset, Object val, LocationIdentity locationIdentity); + + @Override + public native void writeByte(int offset, byte val, LocationIdentity locationIdentity); + + @Override + public native void writeChar(int offset, char val, LocationIdentity locationIdentity); + + @Override + public native void writeShort(int offset, short val, LocationIdentity locationIdentity); + + @Override + public native void writeInt(int offset, int val, LocationIdentity locationIdentity); + + @Override + public native void writeLong(int offset, long val, LocationIdentity locationIdentity); + + @Override + public native void writeFloat(int offset, float val, LocationIdentity locationIdentity); + + @Override + public native void writeDouble(int offset, double val, LocationIdentity locationIdentity); + + @Override + public native void writeWord(int offset, WordBase val, LocationIdentity locationIdentity); + + @Override + public void initializeLong(int offset, long val, LocationIdentity locationIdentity) { + + } + + @Override + public native void writeObject(int offset, Object val, LocationIdentity locationIdentity); + + @Override + public native byte readByte(WordBase offset); + + @Override + public native char readChar(WordBase offset); + + @Override + public native short readShort(WordBase offset); + + @Override + public native int readInt(WordBase offset); + + @Override + public native long readLong(WordBase offset); + + @Override + public native float readFloat(WordBase offset); + + @Override + public native double readDouble(WordBase offset); + + @Override + public native T readWord(WordBase offset); + + @Override + public native Object readObject(WordBase offset); + + @Override + public native byte readByte(int offset); + + @Override + public native char readChar(int offset); + + @Override + public native short readShort(int offset); + + @Override + public native int readInt(int offset); + + @Override + public native long readLong(int offset); + + @Override + public native float readFloat(int offset); + + @Override + public native double readDouble(int offset); + + @Override + public native T readWord(int offset); + + @Override + public native Object readObject(int offset); + + @Override + public native void writeByte(WordBase offset, byte val); + + @Override + public native void writeChar(WordBase offset, char val); + + @Override + public native void writeShort(WordBase offset, short val); + + @Override + public native void writeInt(WordBase offset, int val); + + @Override + public native void writeLong(WordBase offset, long val); + + @Override + public native void writeFloat(WordBase offset, float val); + + @Override + public native void writeDouble(WordBase offset, double val); + + @Override + public native void writeWord(WordBase offset, WordBase val); + + @Override + public native void writeObject(WordBase offset, Object val); + + @Override + public native int compareAndSwapInt(WordBase offset, int expectedValue, int newValue, LocationIdentity locationIdentity); + + @Override + public native long compareAndSwapLong(WordBase offset, long expectedValue, long newValue, LocationIdentity locationIdentity); + + @Override + public native T compareAndSwapWord(WordBase offset, T expectedValue, T newValue, LocationIdentity locationIdentity); + + @Override + public native Object compareAndSwapObject(WordBase offset, Object expectedValue, Object newValue, LocationIdentity locationIdentity); + + @Override + public boolean logicCompareAndSwapInt(WordBase offset, int expectedValue, int newValue, LocationIdentity locationIdentity) { + return false; + } + + @Override + public boolean logicCompareAndSwapLong(WordBase offset, long expectedValue, long newValue, LocationIdentity locationIdentity) { + return false; + } + + @Override + public boolean logicCompareAndSwapWord(WordBase offset, WordBase expectedValue, WordBase newValue, LocationIdentity locationIdentity) { + return false; + } + + @Override + public boolean logicCompareAndSwapObject(WordBase offset, Object expectedValue, Object newValue, LocationIdentity locationIdentity) { + return false; + } + + @Override + public native void writeByte(int offset, byte val); + + @Override + public native void writeChar(int offset, char val); + + @Override + public native void writeShort(int offset, short val); + + @Override + public native void writeInt(int offset, int val); + + @Override + public native void writeLong(int offset, long val); + + @Override + public native void writeFloat(int offset, float val); + + @Override + public native void writeDouble(int offset, double val); + + @Override + public native void writeWord(int offset, WordBase val); + + @Override + public native void writeObject(int offset, Object val); + + @Override + public native void writeWordVolatile(int offset, WordBase val); + + @Override + public native int compareAndSwapInt(int offset, int expectedValue, int newValue, LocationIdentity locationIdentity); + + @Override + public native long compareAndSwapLong(int offset, long expectedValue, long newValue, LocationIdentity locationIdentity); + + @Override + public native T compareAndSwapWord(int offset, T expectedValue, T newValue, LocationIdentity locationIdentity); + + @Override + public native Object compareAndSwapObject(int offset, Object expectedValue, Object newValue, LocationIdentity locationIdentity); + + @Override + public native boolean logicCompareAndSwapInt(int offset, int expectedValue, int newValue, LocationIdentity locationIdentity); + + @Override + public native boolean logicCompareAndSwapLong(int offset, long expectedValue, long newValue, LocationIdentity locationIdentity); + + @Override + public native boolean logicCompareAndSwapWord(int offset, WordBase expectedValue, WordBase newValue, LocationIdentity locationIdentity); + + @Override + public native boolean logicCompareAndSwapObject(int offset, Object expectedValue, Object newValue, LocationIdentity locationIdentity); + + @Override + public Pointer add(UnsignedWord val) { + return box(this.rawValue + val.rawValue()); + } + + @Override + public Pointer subtract(UnsignedWord val) { + return box(this.rawValue - val.rawValue()); + } + + @Override + public UnsignedWord multiply(UnsignedWord val) { + return box(this.rawValue * val.rawValue()); + } + + @Override + public UnsignedWord unsignedDivide(UnsignedWord val) { + return box(Long.divideUnsigned(this.rawValue, val.rawValue())); + } + + @Override + public UnsignedWord unsignedRemainder(UnsignedWord val) { + return box(Long.remainderUnsigned(this.rawValue, val.rawValue())); + } + + @Override + public Pointer and(UnsignedWord val) { + return box(this.rawValue & val.rawValue()); + } + + @Override + public Pointer or(UnsignedWord val) { + return box(this.rawValue | val.rawValue()); + } + + @Override + public UnsignedWord xor(UnsignedWord val) { + return box(this.rawValue ^ val.rawValue()); + } + + @Override + public boolean isNull() { + return this.rawValue == 0; + } + + @Override + public boolean isNonNull() { + return this.rawValue != 0; + } + + @Override + public SignedWord add(SignedWord val) { + return box(this.rawValue + val.rawValue()); + } + + @Override + public SignedWord subtract(SignedWord val) { + return box(this.rawValue - val.rawValue()); + } + + @Override + public SignedWord multiply(SignedWord val) { + return box(this.rawValue * val.rawValue()); + } + + @Override + public SignedWord signedDivide(SignedWord val) { + return box(this.rawValue / val.rawValue()); + } + + @Override + public SignedWord signedRemainder(SignedWord val) { + return box(this.rawValue % val.rawValue()); + } + + @Override + public Word shiftLeft(UnsignedWord n) { + return box(this.rawValue << n.rawValue()); + } + + @Override + public UnsignedWord unsignedShiftRight(UnsignedWord n) { + return box(this.rawValue >>> n.rawValue()); + } + + @Override + public SignedWord signedShiftRight(UnsignedWord n) { + return box(this.rawValue >> n.rawValue()); + } + + @Override + public SignedWord and(SignedWord val) { + return box(this.rawValue & val.rawValue()); + } + + @Override + public SignedWord or(SignedWord val) { + return box(this.rawValue | val.rawValue()); + } + + @Override + public SignedWord xor(SignedWord val) { + return box(this.rawValue ^ val.rawValue()); + } + + @Override + public Word not() { + return box(~this.rawValue); + } + + @Override + public boolean equal(UnsignedWord val) { + return this.rawValue == val.rawValue(); + } + + @Override + public boolean notEqual(UnsignedWord val) { + return this.rawValue != val.rawValue(); + } + + @Override + public boolean belowThan(UnsignedWord val) { + return compareUnsigned(this.rawValue, val.rawValue()) < 0; + } + + @Override + public boolean belowOrEqual(UnsignedWord val) { + return compareUnsigned(this.rawValue, val.rawValue()) <= 0; + } + + @Override + public boolean aboveThan(UnsignedWord val) { + return compareUnsigned(this.rawValue, val.rawValue()) > 0; + } + + @Override + public boolean aboveOrEqual(UnsignedWord val) { + return compareUnsigned(this.rawValue, val.rawValue()) >= 0; + } + + @Override + public boolean equal(SignedWord val) { + return this.rawValue == val.rawValue(); + } + + @Override + public boolean notEqual(SignedWord val) { + return this.rawValue != val.rawValue(); + } + + @Override + public boolean lessThan(SignedWord val) { + return this.rawValue < val.rawValue(); + } + + @Override + public boolean lessOrEqual(SignedWord val) { + return this.rawValue <= val.rawValue(); + } + + @Override + public boolean greaterThan(SignedWord val) { + return this.rawValue > val.rawValue(); + } + + @Override + public boolean greaterOrEqual(SignedWord val) { + return this.rawValue >= val.rawValue(); + } + + @Override + public Word add(int val) { + return box(this.rawValue + val); + } + + @Override + public Word subtract(int val) { + return box(this.rawValue - val); + } + + @Override + public Word multiply(int val) { + return box(this.rawValue * val); + } + + @Override + public UnsignedWord unsignedDivide(int val) { + return box(Long.divideUnsigned(this.rawValue, val)); + } + + @Override + public UnsignedWord unsignedRemainder(int val) { + return box(Long.remainderUnsigned(this.rawValue, val)); + } + + @Override + public SignedWord signedDivide(int val) { + return box(this.rawValue / val); + } + + @Override + public SignedWord signedRemainder(int val) { + return box(this.rawValue % val); + } + + @Override + public Word shiftLeft(int n) { + return box(this.rawValue << n); + } + + @Override + public UnsignedWord unsignedShiftRight(int n) { + return box(this.rawValue >>> n); + } + + @Override + public SignedWord signedShiftRight(int n) { + return box(this.rawValue >> n); + } + + @Override + public Word and(int val) { + return box(this.rawValue & val); + } + + @Override + public Word or(int val) { + return box(this.rawValue | val); + } + + @Override + public Word xor(int val) { + return box(this.rawValue ^ val); + } + + @Override + public boolean equal(int val) { + return this.rawValue == 0; + } + + @Override + public boolean notEqual(int val) { + return this.rawValue != 0; + } + + @Override + public boolean belowThan(int val) { + return compareUnsigned(this.rawValue, val) < 0; + } + + @Override + public boolean belowOrEqual(int val) { + return compareUnsigned(this.rawValue, val) <= 0; + } + + @Override + public boolean aboveThan(int val) { + return compareUnsigned(this.rawValue, val) > 0; + } + + @Override + public boolean aboveOrEqual(int val) { + return compareUnsigned(this.rawValue, val) >= 0; + } + + @Override + public boolean lessThan(int val) { + return this.rawValue < val; + } + + @Override + public boolean lessOrEqual(int val) { + return this.rawValue <= val; + } + + @Override + public boolean greaterThan(int val) { + return this.rawValue > val; + } + + @Override + public boolean greaterOrEqual(int val) { + return this.rawValue >= val; + } + + @Override + public boolean equal(ComparableWord val) { + return this.rawValue == val.rawValue(); + } + + @Override + public boolean notEqual(ComparableWord val) { + return this.rawValue != val.rawValue(); + } + + @Override + public long rawValue() { + return rawValue; + } +} diff --git a/sdk/src/org.graalvm.word/src/org/graalvm/word/WordFactory.java b/sdk/src/org.graalvm.word/src/org/graalvm/word/WordFactory.java index 10e8d73dd928..73c55ae20e34 100644 --- a/sdk/src/org.graalvm.word/src/org/graalvm/word/WordFactory.java +++ b/sdk/src/org.graalvm.word/src/org/graalvm/word/WordFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -40,7 +40,6 @@ */ package org.graalvm.word; -import org.graalvm.word.impl.WordBoxFactory; import org.graalvm.word.impl.WordFactoryOpcode; import org.graalvm.word.impl.WordFactoryOperation; @@ -64,7 +63,7 @@ private WordFactory() { */ @WordFactoryOperation(opcode = WordFactoryOpcode.ZERO) public static T zero() { - return WordBoxFactory.box(0L); + return Word.box(0L); } /** @@ -77,7 +76,7 @@ public static T zero() { */ @WordFactoryOperation(opcode = WordFactoryOpcode.ZERO) public static T nullPointer() { - return WordBoxFactory.box(0L); + return Word.box(0L); } /** @@ -91,13 +90,18 @@ public static T nullPointer() { */ @WordFactoryOperation(opcode = WordFactoryOpcode.FROM_UNSIGNED) public static T unsigned(long val) { - return WordBoxFactory.box(val); + return Word.box(val); } /** * Unsafe conversion from a Java long value to a {@link PointerBase pointer}. The parameter is * treated as an unsigned 64-bit value (in contrast to the semantics of a Java long). * + * In an execution environment where this method returns a boxed value (e.g. not in Native + * Image), the returned value will throw {@link UnsatisfiedLinkError} if any of the + * {@link Pointer} memory access operations (i.e., read, write, compare-and-swap etc.) are + * invoked on it. + * * @param val a 64 bit unsigned value * @return the value cast to PointerBase * @@ -105,7 +109,7 @@ public static T unsigned(long val) { */ @WordFactoryOperation(opcode = WordFactoryOpcode.FROM_UNSIGNED) public static T pointer(long val) { - return WordBoxFactory.box(val); + return Word.box(val); } /** @@ -119,7 +123,7 @@ public static T pointer(long val) { */ @WordFactoryOperation(opcode = WordFactoryOpcode.FROM_UNSIGNED) public static T unsigned(int val) { - return WordBoxFactory.box(val & 0xffffffffL); + return Word.box(val & 0xffffffffL); } /** @@ -133,7 +137,7 @@ public static T unsigned(int val) { */ @WordFactoryOperation(opcode = WordFactoryOpcode.FROM_SIGNED) public static T signed(long val) { - return WordBoxFactory.box(val); + return Word.box(val); } /** @@ -147,6 +151,7 @@ public static T signed(long val) { */ @WordFactoryOperation(opcode = WordFactoryOpcode.FROM_SIGNED) public static T signed(int val) { - return WordBoxFactory.box(val); + return Word.box(val); } + } diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 91615e71923d..ad49fdae0dc5 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -2,6 +2,9 @@ This changelog summarizes major changes to GraalVM Native Image. +## GraalVM for JDK 25 +* (GR-58668) Enabled [Whole-Program Sparse Conditional Constant Propagation (WP-SCCP)](https://github.com/oracle/graal/pull/9821) by default, improving the precision of points-to analysis in Native Image. This optimization enhances static analysis accuracy and scalability, potentially reducing the size of the final native binary. + ## GraalVM for JDK 24 (Internal Version 24.2.0) * (GR-59717) Added `DuringSetupAccess.registerObjectReachabilityHandler` to allow registering a callback that is executed when an object of a specified type is marked as reachable during heap scanning. * (GR-55708) (Alibaba contribution) Support for running premain methods of Java agents at runtime as an experimental feature. At build time, `-H:PremainClasses` is used to set the premain classes. @@ -10,6 +13,8 @@ At runtime, premain runtime options are set along with main class' arguments in The warning is planned to be replaced by an error in GraalVM for JDK 25. * (GR-48384) Added a GDB Python script (`gdb-debughelpers.py`) to improve the Native Image debugging experience. * (GR-49517) Add support for emitting Windows x64 unwind info. This enables stack walking in native tooling such as debuggers and profilers. +* (GR-52576) Optimize FFM API upcalls for specifiable static upcall target methods. +* (GR-56599) Update native image debuginfo from DWARF4 to DWARF5 and store type information for debugging in DWARF type units. * (GR-56601) Together with Red Hat, we added experimental support for `jcmd` on Linux and macOS. Add `--enable-monitoring=jcmd` to your build arguments to try it out. * (GR-57384) Preserve the origin of a resource included in a native image. The information is included in the report produced by -H:+GenerateEmbeddedResourcesFile. * (GR-58000) Support for `GetStringUTFLengthAsLong` added in JNI_VERSION_24 ([JDK-8328877](https://bugs.openjdk.org/browse/JDK-8328877)) @@ -17,6 +22,8 @@ At runtime, premain runtime options are set along with main class' arguments in * (GR-58914) `ActiveProcessorCount` must be set at isolate or VM creation time. * (GR-59326) Ensure builder ForkJoin commonPool parallelism always respects NativeImageOptions.NumberOfThreads. * (GR-60081) Native Image now targets `armv8.1-a` by default on AArch64. Use `-march=compatibility` for best compatibility or `-march=native` for best performance if the native executable is deployed on the same machine or on a machine with the same CPU features. To list all available machine types, use `-march=list`. +* (GR-60234) Remove `"customTargetConstructorClass"` field from the serialization JSON metadata. All possible constructors are now registered by default when registering a type for serialization. `RuntimeSerialization.registerWithTargetConstructorClass` is now deprecated. +* (GR-60237) Include serialization JSON reachability metadata as part of reflection metadata by introducing the `"serializable"` flag for reflection entries. ## GraalVM for JDK 23 (Internal Version 24.1.0) * (GR-51520) The old class initialization strategy, which was deprecated in GraalVM for JDK 22, is removed. The option `StrictImageHeap` no longer has any effect. diff --git a/substratevm/mx.substratevm/macro-junitcp.properties b/substratevm/mx.substratevm/macro-junitcp.properties deleted file mode 100644 index 73c91c7c042e..000000000000 --- a/substratevm/mx.substratevm/macro-junitcp.properties +++ /dev/null @@ -1,13 +0,0 @@ -# This file contains support for building a set of junit tests into a native-image - -ImageName = svmjunit - -ImageClasspath = ${.}/junit-support.jar:${.}/junit-tool.jar:${.}/junit.jar:${.}/hamcrest.jar - -Args = --features=com.oracle.svm.junit.JUnitFeature \ - --initialize-at-build-time=org.junit,com.oracle.mxtool.junit \ - --link-at-build-time=@svm-junit.packages \ - -H:Class=com.oracle.svm.junit.SVMJUnitRunner \ - -H:+UnlockExperimentalVMOptions \ - -H:TestFile=${*} \ - -H:-UnlockExperimentalVMOptions diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 25f70eb1b31a..fd39fe87e4b3 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -334,7 +334,7 @@ def native_image_func(args, **kwargs): yield native_image_func native_image_context.hosted_assertions = ['-J-ea', '-J-esa'] -_native_unittest_features = '--features=com.oracle.svm.test.ImageInfoTest$TestFeature,com.oracle.svm.test.ServiceLoaderTest$TestFeature,com.oracle.svm.test.SecurityServiceTest$TestFeature,com.oracle.svm.test.ReflectionRegistrationTest$TestFeature' +_native_unittest_features = '--features=com.oracle.svm.test.ImageInfoTest$TestFeature,com.oracle.svm.test.services.ServiceLoaderTest$TestFeature,com.oracle.svm.test.services.SecurityServiceTest$TestFeature,com.oracle.svm.test.ReflectionRegistrationTest$TestFeature' IMAGE_ASSERTION_FLAGS = svm_experimental_options(['-H:+VerifyGraalGraphs', '-H:+VerifyPhases']) @@ -546,8 +546,8 @@ def native_unittests_task(extra_build_args=None): additional_build_args = svm_experimental_options([ '-H:-JfrTrimInternalStackTraces', - '-H:AdditionalSecurityProviders=com.oracle.svm.test.SecurityServiceTest$NoOpProvider', - '-H:AdditionalSecurityServiceTypes=com.oracle.svm.test.SecurityServiceTest$JCACompliantNoOpService', + '-H:AdditionalSecurityProviders=com.oracle.svm.test.services.SecurityServiceTest$NoOpProvider', + '-H:AdditionalSecurityServiceTypes=com.oracle.svm.test.services.SecurityServiceTest$JCACompliantNoOpService', '-cp', cp_entry_name ]) if extra_build_args is not None: @@ -646,7 +646,7 @@ def batched(iterable, n): yield batch -def _native_junit(native_image, unittest_args, build_args=None, run_args=None, blacklist=None, whitelist=None, preserve_image=False, force_builder_on_cp=False, test_classes_per_run=None): +def _native_junit(native_image, unittest_args, build_args=None, run_args=None, blacklist=None, whitelist=None, preserve_image=False, test_classes_per_run=None): build_args = build_args or [] javaProperties = {} for dist in suite.dists: @@ -676,13 +676,7 @@ def dummy_harness(test_deps, vm_launcher, vm_args): mx.log('Building junit image for matching: ' + ' '.join(test_classes)) extra_image_args = mx.get_runtime_jvm_args(unittest_deps, jdk=mx_compiler.jdk, exclude_names=mx_sdk_vm_impl.NativePropertiesBuildTask.implicit_excludes) macro_junit = '--macro:junit' - if force_builder_on_cp: - macro_junit += 'cp' - custom_env = os.environ.copy() - custom_env['USE_NATIVE_IMAGE_JAVA_PLATFORM_MODULE_SYSTEM'] = 'false' - else: - custom_env = None - unittest_image = native_image(['-ea', '-esa'] + build_args + extra_image_args + [macro_junit + '=' + unittest_file] + svm_experimental_options(['-H:Path=' + junit_test_dir]), env=custom_env) + unittest_image = native_image(['-ea', '-esa'] + build_args + extra_image_args + [macro_junit + '=' + unittest_file] + svm_experimental_options(['-H:Path=' + junit_test_dir])) image_pattern_replacement = unittest_image + ".exe" if mx.is_windows() else unittest_image run_args = [arg.replace('${unittest.image}', image_pattern_replacement) for arg in run_args] mx.log('Running: ' + ' '.join(map(shlex.quote, [unittest_image] + run_args))) @@ -720,7 +714,7 @@ def unmask(args): def _native_unittest(native_image, cmdline_args): parser = ArgumentParser(prog='mx native-unittest', description='Run unittests as native image.') - all_args = ['--build-args', '--run-args', '--blacklist', '--whitelist', '-p', '--preserve-image', '--force-builder-on-cp', '--test-classes-per-run'] + all_args = ['--build-args', '--run-args', '--blacklist', '--whitelist', '-p', '--preserve-image', '--test-classes-per-run'] cmdline_args = [_mask(arg, all_args) for arg in cmdline_args] parser.add_argument(all_args[0], metavar='ARG', nargs='*', default=[]) parser.add_argument(all_args[1], metavar='ARG', nargs='*', default=[]) @@ -728,7 +722,6 @@ def _native_unittest(native_image, cmdline_args): parser.add_argument('--whitelist', help='run testcases specified in only', metavar='') parser.add_argument('-p', '--preserve-image', help='do not delete the generated native image', action='store_true') parser.add_argument('--test-classes-per-run', help='run N test classes per image run, instead of all tests at once', nargs=1, type=int) - parser.add_argument('--force-builder-on-cp', help='force image builder to run on classpath', action='store_true') parser.add_argument('unittest_args', metavar='TEST_ARG', nargs='*') pargs = parser.parse_args(cmdline_args) @@ -750,7 +743,7 @@ def _native_unittest(native_image, cmdline_args): mx.log('warning: could not read blacklist: ' + blacklist) unittest_args = unmask(pargs.unittest_args) if unmask(pargs.unittest_args) else ['com.oracle.svm.test', 'com.oracle.svm.configure.test'] - _native_junit(native_image, unittest_args, unmask(pargs.build_args), unmask(pargs.run_args), blacklist, whitelist, pargs.preserve_image, pargs.force_builder_on_cp, test_classes_per_run) + _native_junit(native_image, unittest_args, unmask(pargs.build_args), unmask(pargs.run_args), blacklist, whitelist, pargs.preserve_image, test_classes_per_run) def jvm_unittest(args): @@ -1194,6 +1187,19 @@ def mx_post_parse_cmd_line(opts): for dist in suite.dists: if dist.isJARDistribution(): dist.set_archiveparticipant(GraalArchiveParticipant(dist, isTest=dist.name.endswith('_TEST'))) + # Compilation of module-info.java classes need upgrade-module path arguments to + # when javac is invoked. This is in particular needed when the base JDK includes no + # JMODs. + if opts.no_jlinking: + all_jar_dists = set() + for p in suite.projects_recursive(): + if p.isJavaProject(): + jd = p.get_declaring_module_distribution() + if jd: + all_jar_dists.add(jd) + for d in all_jar_dists: + d.add_module_info_compilation_participant(NoJlinkModuleInfoCompilationParticipant(d, "jdk.graal.compiler").__process__) + def native_image_context_run(func, func_args=None, config=None, build_if_missing=False): func_args = [] if func_args is None else func_args @@ -1455,19 +1461,6 @@ def _native_image_launcher_extra_jvm_args(): jlink=False, )) -mx_sdk_vm.register_graalvm_component(mx_sdk_vm.GraalVMSvmMacro( - suite=suite, - name='Native Image JUnit with image-builder on classpath', - short_name='njucp', - dir_name='junitcp', - license_files=[], - third_party_license_files=[], - dependencies=['SubstrateVM'], - jar_distributions=['substratevm:JUNIT_SUPPORT', 'mx:JUNIT_TOOL', 'mx:JUNIT', 'mx:HAMCREST'], - support_distributions=['substratevm:NATIVE_IMAGE_JUNITCP_SUPPORT'], - jlink=False, -)) - libgraal_jar_distributions = [ 'sdk:NATIVEBRIDGE', 'sdk:JNIUTILS', @@ -1614,6 +1607,42 @@ def prevent_build_path_in_libgraal(): ) mx_sdk_vm.register_graalvm_component(libgraal) +libsvmjdwp_build_args = [ + "-H:+UnlockExperimentalVMOptions", + "-H:+IncludeDebugHelperMethods", + "-H:-DeleteLocalSymbols", + "-H:+PreserveFramePointer", +] + +libsvmjdwp_lib_config = mx_sdk_vm.LibraryConfig( + destination="", + jvm_library=True, + use_modules='image', + jar_distributions=['substratevm:SVM_JDWP_SERVER'], + build_args=libsvmjdwp_build_args + [ + '--features=com.oracle.svm.jdwp.server.ServerJDWPFeature', + ], + headers=False, +) + +libsvmjdwp = mx_sdk_vm.GraalVmJreComponent( + suite=suite, + name='SubstrateVM JDWP Debugger', + short_name='svmjdwp', + dir_name="svm", + license_files=[], + third_party_license_files=[], + dependencies=[], + jar_distributions=[], + builder_jar_distributions=['substratevm:SVM_JDWP_COMMON', 'substratevm:SVM_JDWP_RESIDENT'], + support_distributions=[], + priority=1, + library_configs=[libsvmjdwp_lib_config], + stability="experimental", + jlink=False, +) +mx_sdk_vm.register_graalvm_component(libsvmjdwp) + def _native_image_configure_extra_jvm_args(): packages = ['jdk.graal.compiler/jdk.graal.compiler.phases.common', 'jdk.internal.vm.ci/jdk.vm.ci.meta', 'jdk.internal.vm.ci/jdk.vm.ci.services', 'jdk.graal.compiler/jdk.graal.compiler.core.common.util'] args = ['--add-exports=' + packageName + '=ALL-UNNAMED' for packageName in packages] @@ -1878,6 +1907,33 @@ def checkLine(line, marker, init_kind, msg, wrongly_initialized_lines): native_image_context_run(build_and_test_clinittest_image, args) +class NoJlinkModuleInfoCompilationParticipant: + + def __init__(self, dist, module_name): + self.dist = dist + self.module_name = module_name + + # Upgrade module path for compilation of module-info.java files when not using jmods from the JDK + def __process__(self, module_desc): + """ + :param module_desc: The JavaModuleDescriptor for this distribution + :rtype: list of strings with extra javac arguments + """ + def safe_path_arg(p): + r""" + Return `p` with all `\` characters replaced with `\\`, all spaces replaced + with `\ ` and the result enclosed in double quotes. + """ + return '"' + p.replace('\\', '\\\\').replace(' ', '\\ ') + '"' + graal_mod = None + for m in module_desc.modulepath: + if m.name == self.module_name: + graal_mod = m + break + if graal_mod: + return [ '--upgrade-module-path=' + safe_path_arg(graal_mod.jarpath) ] + return [] + class SubstrateJvmFuncsFallbacksBuilder(mx.Project): def __init__(self, suite, name, deps, workingSets, theLicense, **kwArgs): @@ -2416,3 +2472,57 @@ def svm_libcontainer_namespace(args): libcontainer_project = mx.project("com.oracle.svm.native.libcontainer") for src_dir in libcontainer_project.source_dirs(): mx.command_function("svm_namespace")(args + ["--directory", src_dir , "--namespace", "svm_container"]) + +@mx.command(suite, 'capnp-compile', usage_msg="Compile Cap'n Proto schema files to source code.") +def capnp_compile(args): + capnpcjava_home = os.environ.get('CAPNPROTOJAVA_HOME') + if capnpcjava_home is None or not exists(capnpcjava_home + '/capnpc-java'): + mx.abort('Clone and build capnproto/capnproto-java from GitHub and point CAPNPROTOJAVA_HOME to its path.') + srcdir = 'src/com.oracle.svm.hosted/resources/' + outdir = 'src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/' + command = ['capnp', 'compile', + '--import-path=' + capnpcjava_home + '/compiler/src/main/schema/', + '--output=' + capnpcjava_home + '/capnpc-java:' + outdir, + '--src-prefix=' + srcdir, + srcdir + 'SharedLayerSnapshotCapnProtoSchema.capnp'] + mx.run(command) + # Remove huge unused schema chunks from generated code + outpath = outdir + 'SharedLayerSnapshotCapnProtoSchemaHolder.java' # name specified in schema + with open(outpath, 'r') as f: + lines = f.readlines() + with open(outpath, 'w') as f: + f.write( +"""/* + * 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. + */ +//@formatter:off +//Checkstyle: stop +""") + for line in lines: + if line.startswith("public final class "): + f.write('@SuppressWarnings("all")\n') + if 'public static final class Schemas {' in line: + break + f.write(line) + f.write('}\n') diff --git a/substratevm/mx.substratevm/mx_substratevm_benchmark.py b/substratevm/mx.substratevm/mx_substratevm_benchmark.py index f831ccb14efc..a43c5b5f05a5 100644 --- a/substratevm/mx.substratevm/mx_substratevm_benchmark.py +++ b/substratevm/mx.substratevm/mx_substratevm_benchmark.py @@ -31,6 +31,7 @@ import re from glob import glob from pathlib import Path +from typing import List import mx import mx_benchmark @@ -287,13 +288,16 @@ def benchmarkName(self): return self.context.benchmark def benchmarkList(self, bmSuiteArgs): - return self.completeBenchmarkList(bmSuiteArgs) - - def default_stages(self): - if self.context.benchmark == "micronaut-pegasus": - # The 'agent' stage is not supported, as currently we cannot run micronaut-pegasus on the JVM - return ['instrument-image', 'instrument-run', 'image', 'run'] - return super().default_stages() + exclude = [] + return [b for b in self.completeBenchmarkList(bmSuiteArgs) if b not in exclude] + + def stages(self, bm_suite_args: List[str]) -> List[mx_sdk_benchmark.Stage]: + stages = super().stages(bm_suite_args) + if self.context.benchmark == "micronaut-pegasus" and mx_sdk_benchmark.Stage.AGENT in stages: + # The 'agent' stage is not supported, as currently we cannot run micronaut-pegasus on the JVM (GR-59793) + stages.remove(mx_sdk_benchmark.Stage.AGENT) + mx.warn(f"Skipping the 'agent' stage as it is not supported for the 'micronaut-pegasus' benchmark. The stages that will be executed are: {[stage.value for stage in stages]}") + return stages def application_nib(self): if self.benchmarkName() not in self._application_nibs: diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 0bda22881ea3..d527e1f0936b 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -2,7 +2,7 @@ suite = { "mxversion": "7.33.1", "name": "substratevm", - "version" : "24.2.0", + "version" : "25.0.0", "release" : False, "url" : "https://github.com/oracle/graal/tree/master/substratevm", @@ -202,6 +202,15 @@ "path": "mx.substratevm/jar-with-space-in-resource-dir.jar", "digest": "sha512:270bffd158c92b04b16db147f4ef336dcb4d830bf3503cc25be1227b351597a3254544b3c4a5183dcc53f2f3ab10b282722dbf7f1b5e9d9a2741878a7057eb40", }, + + "CAPNPROTO_RUNTIME": { + "digest" : "sha512:94a7776511c344da60a1acdc346c133522a43c239d067d0d5d86c21291e0252a19bd4fa74e4b1d3a93e75dadd41af6557a5d118a1584e39d34c092485ce065b2", + "maven" : { + "groupId" : "org.capnproto", + "artifactId" : "runtime", + "version" : "0.1.16", + }, + }, }, "projects": { @@ -537,6 +546,7 @@ "sourceDirs": ["src"], "dependencies": [ "com.oracle.svm.common", + "CAPNPROTO_RUNTIME" ], "requires" : [ "jdk.internal.vm.ci" @@ -719,13 +729,13 @@ ], "requiresConcealed": { "java.base": [ - "jdk.internal.loader", - "jdk.internal.reflect", "jdk.internal.foreign", "jdk.internal.foreign.abi", "jdk.internal.foreign.abi.x64", "jdk.internal.foreign.abi.x64.sysv", "jdk.internal.foreign.abi.x64.windows", + "jdk.internal.loader", + "jdk.internal.reflect", ], "jdk.internal.vm.ci" : [ "jdk.vm.ci.amd64", @@ -742,7 +752,8 @@ ], "checkstyle": "com.oracle.svm.hosted", "workingSets": "SVM", - "jacoco" : "include", + # disable coverage as long it cannot run on JDK latest [GR-59586] + "jacoco" : "exclude", # disable SpotBugs as long JDK 22 is unsupported [GR-49566] "spotbugs" : "false", }, @@ -758,6 +769,9 @@ "java.base": [ "jdk.internal.foreign", "jdk.internal.foreign.abi", + "jdk.internal.foreign.abi.x64.windows", + "jdk.internal.foreign.abi.x64.sysv", + "jdk.internal.foreign.layout", ], "jdk.internal.vm.ci" : [ "jdk.vm.ci.code", @@ -773,7 +787,8 @@ ], "checkstyle": "com.oracle.svm.hosted", "workingSets": "SVM", - "jacoco" : "include", + # disable coverage as long it cannot run on JDK latest [GR-59586] + "jacoco" : "exclude", # disable SpotBugs as long JDK 22 is unsupported [GR-49566] "spotbugs" : "false", }, @@ -796,10 +811,10 @@ "cflags": ["-Zi", "-O2", "-D_LITTLE_ENDIAN"], }, "linux": { - "cflags": ["-g", "-gdwarf-4", "-fPIC", "-O2", "-D_LITTLE_ENDIAN", "-ffunction-sections", "-fdata-sections", "-fvisibility=hidden", "-D_FORTIFY_SOURCE=0"], + "cflags": ["-g", "-gdwarf-5", "-fPIC", "-O2", "-D_LITTLE_ENDIAN", "-ffunction-sections", "-fdata-sections", "-fvisibility=hidden", "-D_FORTIFY_SOURCE=0"], }, "": { - "cflags": ["-g", "-gdwarf-4", "-fPIC", "-O2", "-D_LITTLE_ENDIAN", "-ffunction-sections", "-fdata-sections", "-fvisibility=hidden", "-D_FORTIFY_SOURCE=0"], + "cflags": ["-g", "-gdwarf-5", "-fPIC", "-O2", "-D_LITTLE_ENDIAN", "-ffunction-sections", "-fdata-sections", "-fvisibility=hidden", "-D_FORTIFY_SOURCE=0"], }, }, "jacoco" : "exclude", @@ -857,7 +872,7 @@ "cflags": ["-g", "-fPIC", "-O2", "-ffunction-sections", "-fdata-sections", "-fvisibility=hidden"], }, "linux": { - "cflags": ["-g", "-gdwarf-4", "-fPIC", "-O2", "-ffunction-sections", "-fdata-sections", "-fvisibility=hidden", "-D_FORTIFY_SOURCE=0", "-D_GNU_SOURCE"], + "cflags": ["-g", "-gdwarf-5", "-fPIC", "-O2", "-ffunction-sections", "-fdata-sections", "-fvisibility=hidden", "-D_FORTIFY_SOURCE=0", "-D_GNU_SOURCE"], }, "": { "ignore": "only darwin and linux are supported", @@ -1524,6 +1539,119 @@ "jacoco" : "exclude", "graalCompilerSourceEdition": "ignore", }, + + "com.oracle.svm.interpreter.metadata": { + "subDir": "src", + "sourceDirs": ["src"], + "dependencies": [ + "substratevm:SVM" + ], + "requiresConcealed" : { + "jdk.internal.vm.ci" : [ + "jdk.vm.ci.meta", + ], + }, + "checkstyle": "com.oracle.svm.hosted", + "javaCompliance": "21+", + "workingSets": "SVM", + }, + + "com.oracle.svm.interpreter": { + "subDir": "src", + "sourceDirs": ["src"], + "dependencies": [ + "com.oracle.svm.interpreter.metadata", + ], + "requires" : [ + "java.base" + ], + "requiresConcealed" : { + "jdk.internal.vm.ci" : [ + "jdk.vm.ci.meta", + "jdk.vm.ci.code", + ], + "java.base" : [ + "jdk.internal.misc", # Unsafe + ], + }, + "checkstyle": "com.oracle.svm.hosted", + "javaCompliance": "21+", + "annotationProcessors": [ + "compiler:GRAAL_PROCESSOR", + "substratevm:SVM_PROCESSOR", + ], + "workingSets": "SVM", + }, + + # Common project both jdwp.server and jdwp.resident. + "com.oracle.svm.jdwp.bridge": { + "subDir": "src", + "sourceDirs": ["src"], + "dependencies": [ + "substratevm:SVM", + ], + "requiresConcealed" : { + "jdk.internal.vm.ci" : [ + "jdk.vm.ci.meta", + ], + }, + "checkstyle": "com.oracle.svm.hosted", + "javaCompliance": "21+", + "annotationProcessors": [ + "compiler:GRAAL_PROCESSOR", + "substratevm:SVM_PROCESSOR", + ], + "workingSets": "SVM", + }, + + # JDWP server, should run on HotSpot and as a shared library e.g. libsvmjdwp.so + "com.oracle.svm.jdwp.server": { + "subDir": "src", + "sourceDirs": ["src"], + "dependencies": [ + "com.oracle.svm.interpreter.metadata", + "com.oracle.svm.jdwp.bridge", + ], + "requiresConcealed" : { + "jdk.internal.vm.ci" : [ + "jdk.vm.ci.meta", + ], + "java.base" : [ + "jdk.internal.misc", # Signal + ], + }, + "checkstyle": "com.oracle.svm.hosted", + "javaCompliance": "21+", + "annotationProcessors": [ + "substratevm:SVM_PROCESSOR", + ], + "workingSets": "SVM", + }, + + # JDWP implementation bits that are included in the application. + "com.oracle.svm.jdwp.resident": { + "subDir": "src", + "sourceDirs": ["src"], + "dependencies": [ + "com.oracle.svm.interpreter", + "com.oracle.svm.jdwp.bridge", + ], + "requiresConcealed" : { + "jdk.internal.vm.ci" : [ + "jdk.vm.ci.meta", + "jdk.vm.ci.code", + ], + "java.base" : [ + "jdk.internal.misc", # Signal + ], + }, + "checkstyle": "com.oracle.svm.hosted", + "javaCompliance": "21+", + "annotationProcessors": [ + "substratevm:SVM_PROCESSOR", + ], + "workingSets": "SVM", + }, }, "distributions": { @@ -1583,9 +1711,9 @@ org.graalvm.extraimage.librarysupport, com.oracle.svm.extraimage_enterprise, org.graalvm.nativeimage.foreign, - com.oracle.svm.enterprise.jdwp.common, - com.oracle.svm.enterprise.jdwp.server, - com.oracle.svm.enterprise.jdwp.resident, + com.oracle.svm.jdwp.common, + com.oracle.svm.jdwp.server, + com.oracle.svm.jdwp.resident, org.graalvm.truffle.runtime.svm, com.oracle.truffle.enterprise.svm""", "com.oracle.svm.hosted.c.libc to com.oracle.graal.sandbox", @@ -1598,7 +1726,7 @@ "com.oracle.svm.hosted.fieldfolding to jdk.graal.compiler", "com.oracle.svm.hosted.phases to jdk.graal.compiler", "com.oracle.svm.hosted.reflect to jdk.graal.compiler", - "com.oracle.svm.core.thread to com.oracle.svm.enterprise.jdwp.resident", + "com.oracle.svm.core.thread to com.oracle.svm.jdwp.resident", ], "requires": [ "java.management", @@ -1653,6 +1781,9 @@ "java.management": [ "sun.management", ], + "org.graalvm.nativeimage.pointsto": [ + "org.capnproto" + ] }, }, "noMavenJavadoc": True, @@ -2063,7 +2194,7 @@ "org.graalvm.collections", ], "exports" : [ - "com.oracle.svm.util to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.llvm,org.graalvm.nativeimage.agent.jvmtibase,org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.agent.diagnostics,org.graalvm.nativeimage.junitsupport,com.oracle.svm.svm_enterprise,com.oracle.svm_enterprise.ml_dataset,com.oracle.svm.enterprise.jdwp.resident,org.graalvm.extraimage.builder,com.oracle.svm.extraimage_enterprise,org.graalvm.extraimage.librarysupport,org.graalvm.nativeimage.foreign,org.graalvm.truffle.runtime.svm,com.oracle.truffle.enterprise.svm", + "com.oracle.svm.util to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.llvm,org.graalvm.nativeimage.agent.jvmtibase,org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.agent.diagnostics,org.graalvm.nativeimage.junitsupport,com.oracle.svm.svm_enterprise,com.oracle.svm_enterprise.ml_dataset,com.oracle.svm.jdwp.resident,org.graalvm.extraimage.builder,com.oracle.svm.extraimage_enterprise,org.graalvm.extraimage.librarysupport,org.graalvm.nativeimage.foreign,org.graalvm.truffle.runtime.svm,com.oracle.truffle.enterprise.svm", "com.oracle.svm.common.meta to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.llvm,org.graalvm.extraimage.builder,org.graalvm.nativeimage.foreign,org.graalvm.truffle.runtime.svm,com.oracle.truffle.enterprise.svm", "com.oracle.svm.common.option to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.foreign,org.graalvm.truffle.runtime.svm,com.oracle.truffle.enterprise.svm", ], @@ -2086,6 +2217,7 @@ "NATIVE_IMAGE_BASE", ], "exclude": [ + # "CAPNPROTO_RUNTIME", ], "moduleInfo" : { "name" : "org.graalvm.nativeimage.pointsto", @@ -2310,15 +2442,6 @@ }, }, - "NATIVE_IMAGE_JUNITCP_SUPPORT" : { - "native" : True, - "description" : "Native-image based junit testing support but with running image-builder on classpath", - "layout" : { - "native-image.properties" : "file:mx.substratevm/macro-junitcp.properties", - "svm-junit.packages" : "file:mx.substratevm/svm-junit.packages", - }, - }, - "SVM_FOREIGN": { "subDir": "src", "description" : "SubstrateVM support for the Foreign API", @@ -2390,5 +2513,62 @@ "tag": ["default", "public"], }, }, + + "SVM_JDWP_COMMON": { + "subDir": "src", + "dependencies": [ + "com.oracle.svm.interpreter.metadata", + "com.oracle.svm.jdwp.bridge", + ], + "distDependencies": [ + "SVM", + ], + "moduleInfo" : { + "name" : "com.oracle.svm.jdwp.common", + "exports" : [ + "com.oracle.svm.jdwp.bridge to com.oracle.svm.jdwp.server,com.oracle.svm.jdwp.resident", + "com.oracle.svm.jdwp.bridge.nativebridge to com.oracle.svm.jdwp.server,com.oracle.svm.jdwp.resident", + "com.oracle.svm.jdwp.bridge.jniutils to com.oracle.svm.jdwp.server,com.oracle.svm.jdwp.resident", + "com.oracle.svm.interpreter.metadata to com.oracle.svm.jdwp.server,com.oracle.svm.jdwp.resident", + "com.oracle.svm.interpreter.metadata.serialization to com.oracle.svm.jdwp.server,com.oracle.svm.jdwp.resident", + ], + "requires" : [ + "org.graalvm.collections", + ], + } + }, + + "SVM_JDWP_RESIDENT": { + "subDir": "src", + "dependencies": [ + "com.oracle.svm.jdwp.resident", + ], + "distDependencies": [ + "SVM_JDWP_COMMON", + "sdk:COLLECTIONS", + "compiler:GRAAL", + ], + "moduleInfo" : { + "name" : "com.oracle.svm.jdwp.resident", + "exports": [ + "com.oracle.svm.interpreter,com.oracle.svm.jdwp.resident to org.graalvm.nativeimage.builder", + ], + } + }, + + "SVM_JDWP_SERVER": { + "subDir": "src", + "dependencies": [ + "com.oracle.svm.jdwp.server", + ], + "distDependencies": [ + "substratevm:SVM", + "SVM_JDWP_COMMON", + ], + "moduleInfo" : { + "name" : "com.oracle.svm.jdwp.server", + } + }, + }, } diff --git a/substratevm/src/com.oracle.graal.pointsto.standalone.test/src/com/oracle/graal/pointsto/standalone/test/PointstoAnalyzerTester.java b/substratevm/src/com.oracle.graal.pointsto.standalone.test/src/com/oracle/graal/pointsto/standalone/test/PointstoAnalyzerTester.java index 4e19cf234b60..bbffe5215c4a 100644 --- a/substratevm/src/com.oracle.graal.pointsto.standalone.test/src/com/oracle/graal/pointsto/standalone/test/PointstoAnalyzerTester.java +++ b/substratevm/src/com.oracle.graal.pointsto.standalone.test/src/com/oracle/graal/pointsto/standalone/test/PointstoAnalyzerTester.java @@ -26,17 +26,11 @@ package com.oracle.graal.pointsto.standalone.test; -import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; -import com.oracle.graal.pointsto.meta.AnalysisElement; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.meta.AnalysisUniverse; -import com.oracle.graal.pointsto.standalone.PointsToAnalyzer; -import com.oracle.graal.pointsto.util.AnalysisError; -import jdk.vm.ci.meta.MetaAccessProvider; -import jdk.vm.ci.meta.ResolvedJavaField; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; -import org.graalvm.nativeimage.hosted.Feature; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; @@ -54,13 +48,20 @@ import java.util.List; import java.util.Set; import java.util.function.Function; -import java.util.stream.Collectors; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; +import com.oracle.graal.pointsto.meta.AnalysisElement; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.graal.pointsto.standalone.PointsToAnalyzer; +import com.oracle.graal.pointsto.util.AnalysisError; + +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; public class PointstoAnalyzerTester { private Set expectedReachableMethods = new HashSet<>(); @@ -307,11 +308,11 @@ private static void assertReachable(String elemen List shouldReachableButNot = expectedReachables.stream().filter(t -> { R element = getAnalysisElement.apply(t); return element != null && !element.isReachable(); - }).collect(Collectors.toList()); + }).toList(); if (!shouldReachableButNot.isEmpty()) { StringBuilder sb = new StringBuilder(elementType + " should be reached but not:"); - shouldReachableButNot.forEach(s -> sb.append(" ").append(getName.apply(s)).append("\n")); + shouldReachableButNot.forEach(s -> sb.append(" ").append(getName.apply(s)).append(System.lineSeparator())); fail(sb.toString()); } } diff --git a/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/MethodConfigReader.java b/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/MethodConfigReader.java index a69052f0bad5..cc294bdbee49 100644 --- a/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/MethodConfigReader.java +++ b/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/MethodConfigReader.java @@ -26,16 +26,6 @@ package com.oracle.graal.pointsto.standalone; -import com.oracle.graal.pointsto.BigBang; -import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.graal.pointsto.util.AnalysisError; -import jdk.vm.ci.meta.MetaAccessProvider; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; -import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.debug.DebugOptions; -import jdk.graal.compiler.debug.MethodFilter; - import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -50,6 +40,17 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.util.AnalysisError; + +import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.debug.DebugOptions; +import jdk.graal.compiler.debug.MethodFilter; +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + /** * This class reads the configuration file that complies to the following rules: *

      @@ -86,10 +87,11 @@ public static void readMethodFromFile(String file, BigBang bigbang, ClassLoader int totalSize = methodNameList.size(); int num = forMethodList(bigbang.getDebug(), methodNameList, bigbang, classLoader, actionForEachMethod); StringBuilder sb = new StringBuilder(); - sb.append("==Reading analysis entry points status==\n"); - sb.append(num).append(" out of ").append(totalSize).append(" methods are read from ").append(file).append("\n"); + sb.append("==Reading analysis entry points status==").append(System.lineSeparator()); + sb.append(num).append(" out of ").append(totalSize).append(" methods are read from ").append(file).append(System.lineSeparator()); if (num < totalSize) { - sb.append("To see the details about the missing methods, please append option -H:").append(DebugOptions.Log.getName()).append("=").append(READ_ENTRY_POINTS).append(":3").append("\n"); + sb.append("To see the details about the missing methods, please append option -H:").append(DebugOptions.Log.getName()).append("=").append(READ_ENTRY_POINTS).append(":3") + .append(System.lineSeparator()); } System.out.println(sb.toString()); } diff --git a/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/PointsToAnalyzer.java b/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/PointsToAnalyzer.java index 18de8ba7cebc..515a90e107fb 100644 --- a/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/PointsToAnalyzer.java +++ b/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/PointsToAnalyzer.java @@ -48,9 +48,6 @@ import com.oracle.graal.pointsto.heap.HeapSnapshotVerifier; import com.oracle.graal.pointsto.heap.HostedValuesProvider; import com.oracle.graal.pointsto.heap.ImageHeap; -import com.oracle.graal.pointsto.heap.ImageLayerLoader; -import com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil; -import com.oracle.graal.pointsto.heap.ImageLayerWriter; import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMetaAccessExtensionProvider; @@ -163,18 +160,9 @@ private PointsToAnalyzer(String mainEntryClass, OptionValues options) { aUniverse.setBigBang(bigbang); ImageHeap heap = new ImageHeap(); HostedValuesProvider hostedValuesProvider = new HostedValuesProvider(aMetaAccess, aUniverse); - ImageLayerSnapshotUtil imageLayerSnapshotUtil = new ImageLayerSnapshotUtil(); - ImageLayerLoader imageLayerLoader = new ImageLayerLoader(); - imageLayerLoader.setImageLayerSnapshotUtil(imageLayerSnapshotUtil); - imageLayerLoader.setUniverse(aUniverse); - aUniverse.setImageLayerLoader(imageLayerLoader); StandaloneImageHeapScanner heapScanner = new StandaloneImageHeapScanner(bigbang, heap, aMetaAccess, snippetReflection, aConstantReflection, new AnalysisObjectScanningObserver(bigbang), analysisClassLoader, hostedValuesProvider); aUniverse.setHeapScanner(heapScanner); - imageLayerLoader.executeHeapScannerTasks(); - ImageLayerWriter imageLayerWriter = new ImageLayerWriter(true); - imageLayerWriter.setImageLayerSnapshotUtil(imageLayerSnapshotUtil); - imageLayerWriter.setImageHeap(heap); HeapSnapshotVerifier heapVerifier = new StandaloneHeapSnapshotVerifier(bigbang, heap, heapScanner); aUniverse.setHeapVerifier(heapVerifier); /* Register already created types as assignable. */ diff --git a/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/heap/StandaloneImageHeapScanner.java b/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/heap/StandaloneImageHeapScanner.java index 4e1674a84469..aa8fc1affaf2 100644 --- a/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/heap/StandaloneImageHeapScanner.java +++ b/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/heap/StandaloneImageHeapScanner.java @@ -68,7 +68,7 @@ protected Class getClass(String className) { } @Override - protected ValueSupplier readHostedFieldValue(AnalysisField field, JavaConstant receiver) { + public ValueSupplier readHostedFieldValue(AnalysisField field, JavaConstant receiver) { ValueSupplier ret = super.readHostedFieldValue(field, receiver); if (ret.get() == null && field.isStatic()) { ResolvedJavaField wrappedField = field.getWrapped(); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java index 93ca08f7f329..e4a6f3ed1741 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java @@ -232,9 +232,6 @@ public void cleanupAfterAnalysis() { universe.getHeapScanner().cleanupAfterAnalysis(); universe.getHeapVerifier().cleanupAfterAnalysis(); - if (universe.getImageLayerLoader() != null) { - universe.getImageLayerLoader().cleanupAfterAnalysis(); - } } @Override diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java index 78b5a4f97f22..a7d288e026bf 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java @@ -83,7 +83,7 @@ public boolean forPrimitiveFieldValue(JavaConstant receiver, AnalysisField field /* Add the constant value object to the field's type flow. */ FieldTypeFlow fieldTypeFlow = getFieldTypeFlow(field, receiver); /* Add the new constant to the field's flow state. */ - return fieldTypeFlow.addState(analysis, TypeState.forPrimitiveConstant(fieldValue.asLong())); + return fieldTypeFlow.addState(analysis, TypeState.forPrimitiveConstant(analysis, fieldValue.asLong())); } /** diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java index a8e014138b1f..d59686d8a49a 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java @@ -32,6 +32,7 @@ import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.hosted.Feature; +import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.svm.core.annotate.TargetClass; @@ -88,6 +89,16 @@ public boolean isFieldIncluded(Field field) { return bb.getHostVM().isFieldIncluded(bb, field); } + /** + * Determine if the given field needs to be included in the image according to the policy. + */ + public boolean isFieldIncluded(AnalysisField field) { + if (!bb.getHostVM().platformSupported(field)) { + return false; + } + return bb.getHostVM().isFieldIncluded(bb, field); + } + /** * Includes the given class in the image. */ @@ -121,6 +132,13 @@ public void includeField(Field field) { bb.postTask(debug -> bb.addRootField(field)); } + /** + * Includes the given field in the image. + */ + public void includeField(AnalysisField field) { + bb.postTask(debug -> bb.addRootField(field)); + } + /** * The analysis for the base layer of a layered image assumes that any method that is reachable * using the base java access rules can be an entry point. An upper layer does not have access diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java index b66e5c8e728a..f1cb90121350 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java @@ -501,12 +501,15 @@ public AnalysisType addRootField(Class clazz, String fieldName) { @Override public AnalysisType addRootField(Field field) { - AnalysisField analysisField = getMetaAccess().lookupJavaField(field); - if (analysisField.isStatic()) { - return addRootStaticField(analysisField); + return addRootField(getMetaAccess().lookupJavaField(field)); + } + + @Override + public AnalysisType addRootField(AnalysisField field) { + if (field.isStatic()) { + return addRootStaticField(field); } else { - AnalysisType analysisType = getMetaAccess().lookupJavaType(field.getDeclaringClass()); - return addRootField(analysisType, analysisField); + return addRootField(field.getDeclaringClass(), field); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ReachabilityAnalysis.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ReachabilityAnalysis.java index a542fbd78d88..61eb2fa1810c 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ReachabilityAnalysis.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ReachabilityAnalysis.java @@ -60,6 +60,8 @@ public interface ReachabilityAnalysis { AnalysisType addRootField(Field field); + AnalysisType addRootField(AnalysisField field); + /** * Registers the method as root. Must be an {@link MultiMethod#ORIGINAL_METHOD}. * diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java index 52089bb43432..4e210c71ae67 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java @@ -42,6 +42,7 @@ import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.PointsToAnalysis; import com.oracle.graal.pointsto.flow.InvokeTypeFlow; +import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; @@ -330,11 +331,23 @@ public HostedProviders getProviders(MultiMethod.MultiMethodKey key) { return providers; } + /** + * This method should only be used by the {@code ClassInclusionPolicy} to determine which fields + * should be included in the shared layer. + */ @SuppressWarnings("unused") public boolean isFieldIncluded(BigBang bb, Field field) { return true; } + /** + * See {@link HostVM#isFieldIncluded(BigBang, Field)}. + */ + @SuppressWarnings("unused") + public boolean isFieldIncluded(BigBang bb, AnalysisField field) { + return true; + } + public boolean isClosedTypeWorld() { return true; } @@ -343,6 +356,10 @@ public boolean enableTrackAcrossLayers() { return false; } + public boolean enableReachableInCurrentLayer() { + return false; + } + /** * Helpers to determine what analysis actions should be taken for a given Multi-Method version. */ diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/ImageLayerLoader.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/ImageLayerLoader.java new file mode 100644 index 000000000000..17b71f5515f9 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/ImageLayerLoader.java @@ -0,0 +1,132 @@ +/* + * 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.graal.pointsto.api; + +import java.util.Set; + +import com.oracle.graal.pointsto.flow.AnalysisParsedGraph; +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.util.AnalysisError; + +import jdk.vm.ci.meta.JavaConstant; + +public class ImageLayerLoader { + /** + * Returns the type id of the given type in the base layer if it exists. This makes the link + * between the base layer and the extension layer as the id is used to determine which constant + * should be linked to this type. + */ + @SuppressWarnings("unused") + public int lookupHostedTypeInBaseLayer(AnalysisType type) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); + } + + @SuppressWarnings("unused") + public void initializeBaseLayerType(AnalysisType type) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); + } + + /** + * Returns the method id of the given method in the base layer if it exists. This makes the link + * between the base layer and the extension layer as the id is used to determine the method used + * in RelocatableConstants. + */ + @SuppressWarnings("unused") + public int lookupHostedMethodInBaseLayer(AnalysisMethod analysisMethod) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); + } + + @SuppressWarnings("unused") + public void addBaseLayerMethod(AnalysisMethod analysisMethod) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); + } + + /** + * We save analysis parsed graphs for methods considered + * {@link AnalysisMethod#isTrackedAcrossLayers()}. + */ + @SuppressWarnings("unused") + public boolean hasAnalysisParsedGraph(AnalysisMethod analysisMethod) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); + } + + @SuppressWarnings("unused") + public AnalysisParsedGraph getAnalysisParsedGraph(AnalysisMethod analysisMethod) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); + } + + @SuppressWarnings("unused") + public void loadPriorStrengthenedGraphAnalysisElements(AnalysisMethod analysisMethod) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); + } + + /** + * Returns the field id of the given field in the base layer if it exists. This makes the link + * between the base layer and the extension image as the id allows to set the flags of the + * fields in the extension image. + */ + @SuppressWarnings("unused") + public int lookupHostedFieldInBaseLayer(AnalysisField analysisField) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); + } + + @SuppressWarnings("unused") + public void addBaseLayerField(AnalysisField analysisField) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); + } + + @SuppressWarnings("unused") + public void initializeBaseLayerField(AnalysisField analysisField) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); + } + + @SuppressWarnings("unused") + public boolean hasValueForConstant(JavaConstant javaConstant) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); + } + + @SuppressWarnings("unused") + public ImageHeapConstant getValueForConstant(JavaConstant javaConstant) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); + } + + @SuppressWarnings("unused") + public Set getRelinkedFields(AnalysisType type) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); + } + + @SuppressWarnings("unused") + public boolean hasDynamicHubIdentityHashCode(int tid) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); + } + + @SuppressWarnings("unused") + public int getDynamicHubIdentityHashCode(int tid) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoaderHelper.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/ImageLayerWriter.java similarity index 69% rename from substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoaderHelper.java rename to substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/ImageLayerWriter.java index 01bd77248601..63898e56abc4 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoaderHelper.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/ImageLayerWriter.java @@ -22,24 +22,15 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.graal.pointsto.heap; +package com.oracle.graal.pointsto.api; -import org.graalvm.collections.EconomicMap; +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.util.AnalysisError; -public class ImageLayerLoaderHelper { - protected ImageLayerLoader imageLayerLoader; - - public ImageLayerLoaderHelper(ImageLayerLoader imageLayerLoader) { - this.imageLayerLoader = imageLayerLoader; - } - - @SuppressWarnings("unused") - protected boolean loadType(EconomicMap typeData, int tid) { - return false; - } +public class ImageLayerWriter { @SuppressWarnings("unused") - protected boolean loadMethod(EconomicMap methodData, int mid) { - return false; + public void onTrackedAcrossLayer(AnalysisMethod method, Object reason) { + throw AnalysisError.shouldNotReachHere("This method should not be called"); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/PointstoOptions.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/PointstoOptions.java index 1967e8aebbd4..0c8c0569a331 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/PointstoOptions.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/PointstoOptions.java @@ -37,10 +37,10 @@ public class PointstoOptions { @Option(help = "Track primitive values using the infrastructure of points-to analysis.")// - public static final OptionKey TrackPrimitiveValues = new OptionKey<>(false); + public static final OptionKey TrackPrimitiveValues = new OptionKey<>(true); @Option(help = "Use predicates in points-to analysis.")// - public static final OptionKey UsePredicates = new OptionKey<>(false); + public static final OptionKey UsePredicates = new OptionKey<>(true); @Option(help = "Use experimental Reachability Analysis instead of points-to.")// public static final OptionKey UseExperimentalReachabilityAnalysis = new OptionKey<>(false); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/constraints/UnsupportedFeatures.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/constraints/UnsupportedFeatures.java index 61a9b54a7310..ce22ad98076a 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/constraints/UnsupportedFeatures.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/constraints/UnsupportedFeatures.java @@ -122,10 +122,11 @@ public void report(BigBang bb) { String unsupportedFeaturesMessage; if (singleEntry) { - unsupportedFeaturesMessage = entries.get(0).message + "\nDetailed message:\n" + outputStream.toString(); - throw new UnsupportedFeatureException(unsupportedFeaturesMessage, entries.get(0).originalException); + Data entry = entries.getFirst(); + unsupportedFeaturesMessage = entry.message + System.lineSeparator() + "Detailed message:" + System.lineSeparator() + outputStream; + throw new UnsupportedFeatureException(unsupportedFeaturesMessage, entry.originalException); } else { - unsupportedFeaturesMessage = "Unsupported features in " + entries.size() + " methods" + "\nDetailed message:\n" + outputStream.toString(); + unsupportedFeaturesMessage = "Unsupported features in " + entries.size() + " methods" + System.lineSeparator() + "Detailed message:" + System.lineSeparator() + outputStream; throw new UnsupportedFeatureException(unsupportedFeaturesMessage); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanCheckTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanCheckTypeFlow.java index e6a0ee790779..9d262c6157c2 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanCheckTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanCheckTypeFlow.java @@ -46,19 +46,19 @@ protected BooleanCheckTypeFlow(BooleanCheckTypeFlow original, MethodFlowsGraph m super(original, methodFlows); } - protected static TypeState convertToBoolean(boolean canBeTrue, boolean canBeFalse) { + protected static TypeState convertToBoolean(PointsToAnalysis bb, boolean canBeTrue, boolean canBeFalse) { if (canBeTrue && canBeFalse) { return TypeState.anyPrimitiveState(); } else if (canBeTrue) { - return TypeState.forBoolean(true); + return TypeState.forBoolean(bb, true); } else if (canBeFalse) { - return TypeState.forBoolean(false); + return TypeState.forBoolean(bb, false); } return TypeState.forEmpty(); } - protected static TypeState convertToBoolean(TypeState trueState, TypeState falseState) { - return convertToBoolean(trueState.isNotEmpty(), falseState.isNotEmpty()); + protected static TypeState convertToBoolean(PointsToAnalysis bb, TypeState trueState, TypeState falseState) { + return convertToBoolean(bb, trueState.isNotEmpty(), falseState.isNotEmpty()); } @Override diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanInstanceOfCheckTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanInstanceOfCheckTypeFlow.java index 0db031d0fb02..0d9e851b34f0 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanInstanceOfCheckTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanInstanceOfCheckTypeFlow.java @@ -68,6 +68,6 @@ public TypeState filter(PointsToAnalysis bb, TypeState update) { canBeTrue = TypeState.forIntersection(bb, update, checkedType.getAssignableTypes(includeNull)); canBeFalse = TypeState.forSubtraction(bb, update, checkedType.getAssignableTypes(includeNull)); } - return convertToBoolean(canBeTrue, canBeFalse); + return convertToBoolean(bb, canBeTrue, canBeFalse); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanNullCheckTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanNullCheckTypeFlow.java index cda0e675cac2..e66fbba961bb 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanNullCheckTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanNullCheckTypeFlow.java @@ -52,6 +52,6 @@ public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph met public TypeState filter(PointsToAnalysis bb, TypeState newState) { var hasNull = newState.canBeNull(); var hasTypes = newState.typesCount() > 0; - return convertToBoolean(hasNull, hasTypes); + return convertToBoolean(bb, hasNull, hasTypes); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanPrimitiveCheckTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanPrimitiveCheckTypeFlow.java index ea2f4bad2a80..d930927cc28e 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanPrimitiveCheckTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanPrimitiveCheckTypeFlow.java @@ -86,6 +86,6 @@ public TypeState eval(PointsToAnalysis bb) { } assert leftState.isPrimitive() : left; assert rightState.isPrimitive() : right; - return convertToBoolean(TypeState.filter(leftState, comparison, rightState), TypeState.filter(leftState, comparison.negate(), rightState)); + return convertToBoolean(bb, TypeState.filter(leftState, comparison, rightState), TypeState.filter(leftState, comparison.negate(), rightState)); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ConstantPrimitiveSourceTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ConstantPrimitiveSourceTypeFlow.java index 3b547a4e89fc..4cb1b30636cd 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ConstantPrimitiveSourceTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ConstantPrimitiveSourceTypeFlow.java @@ -32,8 +32,8 @@ public class ConstantPrimitiveSourceTypeFlow extends TypeFlow implements PrimitiveFlow { - public ConstantPrimitiveSourceTypeFlow(BytecodePosition source, AnalysisType type, long value) { - super(source, type, TypeState.forPrimitiveConstant(value)); + public ConstantPrimitiveSourceTypeFlow(BytecodePosition source, AnalysisType type, TypeState state) { + super(source, type, state); } public ConstantPrimitiveSourceTypeFlow(ConstantPrimitiveSourceTypeFlow original, MethodFlowsGraph methodFlows) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java index 1474204f06f4..25c4528e4fed 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java @@ -87,17 +87,16 @@ public synchronized void setAsStubFlow() { private void throwSealedError() { assert sealedFlowsGraph != null; StringBuilder sb = new StringBuilder(); - sb.append("Sealed problem:\n"); - if (sealedFlowsGraph instanceof StackTraceElement[]) { - StackTraceElement[] trace = (StackTraceElement[]) sealedFlowsGraph; + sb.append("Sealed problem:").append(System.lineSeparator()); + if (sealedFlowsGraph instanceof StackTraceElement[] trace) { sb = new StringBuilder(); - sb.append("stack trace:\n"); + sb.append("stack trace:").append(System.lineSeparator()); for (StackTraceElement elem : trace) { - sb.append(elem.toString()).append("\n"); + sb.append(elem.toString()).append(System.lineSeparator()); } - sb.append("end trace:\n"); + sb.append("end trace:").append(System.lineSeparator()); } else { - sb.append("stack trace is unknown\n"); + sb.append("stack trace is unknown").append(System.lineSeparator()); } throw AnalysisError.shouldNotReachHere(sb.toString()); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java index 73684a329469..161001f3aa9b 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java @@ -294,7 +294,7 @@ public static void registerUsedElements(PointsToAnalysis bb, StructuredGraph gra type.registerAsInstantiated(AbstractAnalysisEngine.sourcePosition(node)); for (var f : type.getInstanceFields(true)) { var field = (AnalysisField) f; - field.getInitialFlow().addState(bb, TypeState.defaultValueForKind(field.getStorageKind())); + field.getInitialFlow().addState(bb, TypeState.defaultValueForKind(bb, field.getStorageKind())); } } @@ -354,7 +354,7 @@ public static void registerUsedElements(PointsToAnalysis bb, StructuredGraph gra JavaConstant root = cn.asJavaConstant(); if (cn.hasUsages() && cn.isJavaConstant() && root.getJavaKind() == JavaKind.Object && root.isNonNull()) { assert StampTool.isExactType(cn) : cn; - if (!ignoreConstant(cn)) { + if (!ignoreConstant(bb, cn)) { AnalysisType type = (AnalysisType) StampTool.typeOrNull(cn, bb.getMetaAccess()); type.registerAsInstantiated(new EmbeddedRootScan(AbstractAnalysisEngine.sourcePosition(cn), root)); registerEmbeddedRoot(bb, cn); @@ -402,20 +402,23 @@ public static void registerUsedElements(PointsToAnalysis bb, StructuredGraph gra * do not want to make the receiver class reachable, because as long as the receiver class is * not reachable for any other "real" reason we know that isAssignableFrom will always return * false. So in {@link StrengthenGraphs} we can then constant-fold the - * {@link ClassIsAssignableFromNode} to false. + * {@link ClassIsAssignableFromNode} to false. We only apply this optimization for + * {@link ClassIsAssignableFromNode} if it's a closed type world, for open world we cannot fold + * the type check since the type may be used later. * * Similarly, a class should not be marked as reachable only so that we can add the class name * to the error message of a {@link ClassCastException}. In {@link StrengthenGraphs} we can * re-write the Class constant to a String constant, i.e., only embed the class name and not the - * full java.lang.Class object in the image. + * full java.lang.Class object in the image. We can apply this optimization optimistically for + * both closed and open type world. * * {@link FrameState} are only used for debugging. We do not want to have larger images just so * that users can see a constant value in the debugger. */ - protected static boolean ignoreConstant(ConstantNode node) { + protected static boolean ignoreConstant(PointsToAnalysis bb, ConstantNode node) { for (var u : node.usages()) { if (u instanceof ClassIsAssignableFromNode usage) { - if (usage.getOtherClass() == node || usage.getThisClass() != node) { + if (!bb.getHostVM().isClosedTypeWorld() || usage.getOtherClass() == node || usage.getThisClass() != node) { return false; } } else if (u instanceof BytecodeExceptionNode usage) { @@ -458,10 +461,23 @@ protected static boolean needsUnsafeRegistration(FieldOffsetProvider node) { return false; } + /** + * In closed type world, just using a type in an instanceof type check doesn't mark the type as + * reachable. Assuming the type is not otherwise made reachable, this allows the graph + * strengthening to eliminate the type check completely by replacing a stamp with an unreachable + * type with an empty stamp (see StrengthenSimplifier#strengthenStamp). + *

      + * However, in open world we cannot make assumptions about types that may become reachable + * later. Therefore, we must mark the instanceof checked type as reachable. Moreover, stamp + * strengthening based on reachability status of types must be disabled. + */ protected static boolean ignoreInstanceOfType(PointsToAnalysis bb, AnalysisType type) { if (bb.getHostVM().ignoreInstanceOfTypeDisallowed()) { return false; } + if (!bb.getHostVM().isClosedTypeWorld()) { + return false; + } if (type == null) { return false; } @@ -682,6 +698,8 @@ protected void apply(boolean forceReparse, Object reason) { assert !processed : "can only call apply once per MethodTypeFlowBuilder"; processed = true; + method.setReachableInCurrentLayer(); + if (method.analyzedInPriorLayer()) { /* * We don't need to analyze this method. We already know its return type state from the @@ -829,7 +847,7 @@ protected TypeFlowBuilder handleIntegerStamp(IntegerStamp stamp, ValueNode no long hi = stamp.upperBound(); if (lo == hi) { return TypeFlowBuilder.create(bb, method, getPredicate(), node, ConstantPrimitiveSourceTypeFlow.class, () -> { - var flow = new ConstantPrimitiveSourceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type, lo); + var flow = new ConstantPrimitiveSourceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type, TypeState.forPrimitiveConstant(bb, lo)); flowsGraph.addMiscEntryFlow(flow); return flow; }); @@ -1947,7 +1965,7 @@ protected void processCommitAllocation(CommitAllocationNode commitAllocationNode } else { if (!type.isArray()) { AnalysisField field = (AnalysisField) ((VirtualInstanceNode) virtualObject).field(i); - field.getInitialFlow().addState(bb, TypeState.defaultValueForKind(field.getStorageKind())); + field.getInitialFlow().addState(bb, TypeState.defaultValueForKind(bb, field.getStorageKind())); } } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/NewInstanceTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/NewInstanceTypeFlow.java index 9d9002e712ab..844ef9c9e2cf 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/NewInstanceTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/NewInstanceTypeFlow.java @@ -77,7 +77,7 @@ protected void onFlowEnabled(PointsToAnalysis bb) { if (insertDefaultFieldValues) { for (var f : declaredType.getInstanceFields(true)) { var field = (AnalysisField) f; - field.getInitialFlow().addState(bb, TypeState.defaultValueForKind(field.getStorageKind())); + field.getInitialFlow().addState(bb, TypeState.defaultValueForKind(bb, field.getStorageKind())); } } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveAnalysisPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveAnalysisPolicy.java index 8ceac63b9ed6..024a1952d1e6 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveAnalysisPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveAnalysisPolicy.java @@ -430,12 +430,12 @@ public TypeState forContextInsensitiveTypeState(PointsToAnalysis bb, TypeState s @Override public SingleTypeState singleTypeState(PointsToAnalysis bb, boolean canBeNull, AnalysisType type, AnalysisObject... objects) { - return new ContextSensitiveSingleTypeState(bb, canBeNull, type, objects); + return PointsToStats.registerTypeState(bb, new ContextSensitiveSingleTypeState(canBeNull, type, objects)); } @Override public MultiTypeState multiTypeState(PointsToAnalysis bb, boolean canBeNull, BitSet typesBitSet, int typesCount, AnalysisObject... objects) { - return new ContextSensitiveMultiTypeState(bb, canBeNull, typesBitSet, typesCount, objects); + return PointsToStats.registerTypeState(bb, new ContextSensitiveMultiTypeState(canBeNull, typesBitSet, typesCount, objects)); } @Override @@ -466,7 +466,7 @@ public TypeState doUnion(PointsToAnalysis bb, SingleTypeState state1, SingleType assert !Arrays.equals(resultObjects, s1.objects) && !Arrays.equals(resultObjects, s2.objects) : resultObjects; /* Create the resulting exact type state. */ - SingleTypeState result = new ContextSensitiveSingleTypeState(bb, resultCanBeNull, s1.exactType(), resultObjects); + SingleTypeState result = singleTypeState(bb, resultCanBeNull, s1.exactType(), resultObjects); assert !s1.equals(result) && !s2.equals(result) : result; PointsToStats.registerUnionOperation(bb, s1, s2, result); return result; @@ -482,7 +482,7 @@ public TypeState doUnion(PointsToAnalysis bb, SingleTypeState state1, SingleType /* We know the types, construct the types bit set without walking the objects. */ BitSet typesBitSet = TypeStateUtils.newBitSet(s1.exactType().getId(), s2.exactType().getId()); assert typesBitSet.cardinality() == 2 : typesBitSet; - TypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, typesBitSet, 2, resultObjects); + TypeState result = multiTypeState(bb, resultCanBeNull, typesBitSet, 2, resultObjects); PointsToStats.registerUnionOperation(bb, s1, s2, result); return result; } @@ -542,7 +542,7 @@ public TypeState doUnion(PointsToAnalysis bb, MultiTypeState state1, SingleTypeS System.arraycopy(so1, typeRange.right(), resultObjects, typeRange.left() + unionObjects.length, so1.length - typeRange.right()); /* The types bit set of the result and s1 are the same. */ - MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, s1.bitSet(), s1.typesCount(), resultObjects); + MultiTypeState result = multiTypeState(bb, resultCanBeNull, s1.bitSet(), s1.typesCount(), resultObjects); assert !result.equals(s1) : result; /* * No need to check the result size against the all-instantiated since the type count @@ -576,7 +576,7 @@ public TypeState doUnion(PointsToAnalysis bb, MultiTypeState state1, SingleTypeS BitSet typesBitSet = TypeStateUtils.set(s1.bitSet(), s2.exactType().getId()); int typesCount = s1.typesCount() + 1; assert typesCount == typesBitSet.cardinality() : typesCount; - MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, typesBitSet, typesCount, resultObjects); + MultiTypeState result = multiTypeState(bb, resultCanBeNull, typesBitSet, typesCount, resultObjects); PointsToStats.registerUnionOperation(bb, s1, s2, result); return result; } @@ -621,7 +621,7 @@ private TypeState doUnion0(PointsToAnalysis bb, ContextSensitiveMultiTypeState s BitSet resultTypesBitSet = TypeStateUtils.or(s1.bitSet(), s2.bitSet()); int typesCount = s1.typesCount() + s2.typesCount(); assert typesCount == resultTypesBitSet.cardinality() : typesCount; - MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, resultTypesBitSet, typesCount, resultObjects); + MultiTypeState result = multiTypeState(bb, resultCanBeNull, resultTypesBitSet, typesCount, resultObjects); PointsToStats.registerUnionOperation(bb, s1, s2, result); return result; @@ -635,7 +635,7 @@ private TypeState doUnion0(PointsToAnalysis bb, ContextSensitiveMultiTypeState s BitSet resultTypesBitSet = TypeStateUtils.or(s1.bitSet(), s2.bitSet()); int typesCount = s1.typesCount() + s2.typesCount(); assert typesCount == resultTypesBitSet.cardinality() : typesCount; - MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, resultTypesBitSet, typesCount, resultObjects); + MultiTypeState result = multiTypeState(bb, resultCanBeNull, resultTypesBitSet, typesCount, resultObjects); PointsToStats.registerUnionOperation(bb, s1, s2, result); return result; } @@ -824,7 +824,7 @@ private TypeState doUnion2(PointsToAnalysis bb, ContextSensitiveMultiTypeState s /* Logical OR the type bit sets. */ BitSet resultTypesBitSet = TypeStateUtils.or(s1.bitSet(), s2.bitSet()); - MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, resultTypesBitSet, resultTypesBitSet.cardinality(), + MultiTypeState result = multiTypeState(bb, resultCanBeNull, resultTypesBitSet, resultTypesBitSet.cardinality(), resultObjects.copyToArray(new AnalysisObject[resultObjects.size()])); assert !result.equals(s1) : "speculation code should prevent this case"; @@ -874,7 +874,7 @@ public TypeState doIntersection(PointsToAnalysis bb, MultiTypeState s1, SingleTy AnalysisObject[] resultObjects = ((ContextSensitiveMultiTypeState) s1).objectsArray(s2.exactType()); /* All objects must have the same type. */ assert TypeStateUtils.holdsSingleTypeState(resultObjects); - return new ContextSensitiveSingleTypeState(bb, resultCanBeNull, s2.exactType(), resultObjects); + return singleTypeState(bb, resultCanBeNull, s2.exactType(), resultObjects); } else { return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); } @@ -900,7 +900,7 @@ public TypeState doIntersection(PointsToAnalysis bb, MultiTypeState state1, Mult return doIntersection0(bb, s1, s2, resultCanBeNull); } - private static TypeState doIntersection0(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { + private TypeState doIntersection0(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { /* Speculate that s1 and s2 have either the same types, or no types in common. */ if (s1.bitSet().equals(s2.bitSet())) { @@ -916,7 +916,7 @@ private static TypeState doIntersection0(PointsToAnalysis bb, ContextSensitiveMu return doIntersection1(bb, s1, s2, resultCanBeNull); } - private static TypeState doIntersection1(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { + private TypeState doIntersection1(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { /* * Speculate that s2 contains all types of s1, i.e., the filter is broader than s1, thus the * result is s1. @@ -964,7 +964,7 @@ private static TypeState doIntersection1(PointsToAnalysis bb, ContextSensitiveMu private static ThreadLocal> intersectionArrayListTL = new ThreadLocal<>(); - private static TypeState doIntersection2(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull, int idx1Param, int idx2Param) { + private TypeState doIntersection2(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull, int idx1Param, int idx2Param) { try (ListUtils.UnsafeArrayListClosable tlArrayClosable = ListUtils.getTLArrayList(intersectionArrayListTL, 256)) { ListUtils.UnsafeArrayList resultObjects = tlArrayClosable.list(); @@ -1007,11 +1007,11 @@ private static TypeState doIntersection2(PointsToAnalysis bb, ContextSensitiveMu if (TypeStateUtils.holdsSingleTypeState(objects, objects.length)) { /* Multiple objects of the same type. */ - return new ContextSensitiveSingleTypeState(bb, resultCanBeNull, objects[0].type(), objects); + return singleTypeState(bb, resultCanBeNull, objects[0].type(), objects); } else { /* Logical AND the type bit sets. */ BitSet resultTypesBitSet = TypeStateUtils.and(s1.bitSet(), s2.bitSet()); - MultiTypeState result = new ContextSensitiveMultiTypeState(bb, resultCanBeNull, resultTypesBitSet, resultTypesBitSet.cardinality(), objects); + MultiTypeState result = multiTypeState(bb, resultCanBeNull, resultTypesBitSet, resultTypesBitSet.cardinality(), objects); /* * The result can be equal to s1 if and only if s1 and s2 have the same type @@ -1077,15 +1077,15 @@ public TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState state1, Singl System.arraycopy(s1.objects, typeRange.right(), resultObjects, typeRange.left(), s1.objects.length - typeRange.right()); if (resultObjects.length == 1) { - return new ContextSensitiveSingleTypeState(bb, resultCanBeNull, resultObjects[0].type(), resultObjects[0]); + return singleTypeState(bb, resultCanBeNull, resultObjects[0].type(), resultObjects[0]); } else if (TypeStateUtils.holdsSingleTypeState(resultObjects)) { /* Multiple objects of the same type. */ - return new ContextSensitiveSingleTypeState(bb, resultCanBeNull, resultObjects[0].type(), resultObjects); + return singleTypeState(bb, resultCanBeNull, resultObjects[0].type(), resultObjects); } else { BitSet resultTypesBitSet = TypeStateUtils.clear(s1.bitSet(), s2.exactType().getId()); int typesCount = s1.typesCount() - 1; assert typesCount == resultTypesBitSet.cardinality() : typesCount; - return new ContextSensitiveMultiTypeState(bb, resultCanBeNull, resultTypesBitSet, typesCount, resultObjects); + return multiTypeState(bb, resultCanBeNull, resultTypesBitSet, typesCount, resultObjects); } } else { @@ -1110,7 +1110,7 @@ public TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState state1, Multi return doSubtraction0(bb, s1, s2, resultCanBeNull); } - private static TypeState doSubtraction0(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { + private TypeState doSubtraction0(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { /* Speculate that s1 and s2 have either the same types, or no types in common. */ if (s1.bitSet().equals(s2.bitSet())) { @@ -1126,7 +1126,7 @@ private static TypeState doSubtraction0(PointsToAnalysis bb, ContextSensitiveMul return doSubtraction1(bb, s1, s2, resultCanBeNull); } - private static TypeState doSubtraction1(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { + private TypeState doSubtraction1(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull) { /* * Speculate that s1 and s2 have no overlap, i.e., they don't have any objects in common. In * that case, the result is just s1. @@ -1162,7 +1162,7 @@ private static TypeState doSubtraction1(PointsToAnalysis bb, ContextSensitiveMul return doSubtraction2(bb, s1, s2, resultCanBeNull, idx1, idx2); } - private static TypeState doSubtraction2(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull, int idx1Param, int idx2Param) { + private TypeState doSubtraction2(PointsToAnalysis bb, ContextSensitiveMultiTypeState s1, ContextSensitiveMultiTypeState s2, boolean resultCanBeNull, int idx1Param, int idx2Param) { try (ListUtils.UnsafeArrayListClosable tlArrayClosable = ListUtils.getTLArrayList(intersectionArrayListTL, 256)) { ListUtils.UnsafeArrayList resultObjects = tlArrayClosable.list(); @@ -1207,14 +1207,14 @@ private static TypeState doSubtraction2(PointsToAnalysis bb, ContextSensitiveMul if (TypeStateUtils.holdsSingleTypeState(objects, totalLength)) { /* Multiple objects of the same type. */ - return new ContextSensitiveSingleTypeState(bb, resultCanBeNull, objects[0].type(), objects); + return singleTypeState(bb, resultCanBeNull, objects[0].type(), objects); } else { BitSet resultTypesBitSet = TypeStateUtils.andNot(s1.bitSet(), s2.bitSet()); /* * Don't need to check if the result is close-to-all-instantiated since result * <= s1. */ - return new ContextSensitiveMultiTypeState(bb, resultCanBeNull, resultTypesBitSet, resultTypesBitSet.cardinality(), objects); + return multiTypeState(bb, resultCanBeNull, resultTypesBitSet, resultTypesBitSet.cardinality(), objects); } } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveMultiTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveMultiTypeState.java index 0d6b637afb28..e6835aca3fec 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveMultiTypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveMultiTypeState.java @@ -33,6 +33,7 @@ import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.typestate.MultiTypeState; +import com.oracle.graal.pointsto.typestate.PointsToStats; import com.oracle.graal.pointsto.typestate.TypeState; public class ContextSensitiveMultiTypeState extends MultiTypeState { @@ -43,16 +44,16 @@ public class ContextSensitiveMultiTypeState extends MultiTypeState { protected int[] objectTypeIds; /** Creates a new type state using the provided types bit set and objects. */ - public ContextSensitiveMultiTypeState(PointsToAnalysis bb, boolean canBeNull, BitSet typesBitSet, int typesCount, AnalysisObject... objects) { - super(bb, canBeNull, typesBitSet, typesCount); + public ContextSensitiveMultiTypeState(boolean canBeNull, BitSet typesBitSet, int typesCount, AnalysisObject... objects) { + super(canBeNull, typesBitSet, typesCount); this.objects = objects; assert objects.length > 1 : "Multi type state with single object."; assert checkObjects(); } /** Create a type state with the same content and a reversed canBeNull value. */ - protected ContextSensitiveMultiTypeState(PointsToAnalysis bb, boolean canBeNull, ContextSensitiveMultiTypeState other) { - super(bb, canBeNull, other); + protected ContextSensitiveMultiTypeState(boolean canBeNull, ContextSensitiveMultiTypeState other) { + super(canBeNull, other); this.objects = other.objects; this.merged = other.merged; } @@ -128,7 +129,7 @@ public TypeState forCanBeNull(PointsToAnalysis bb, boolean resultCanBeNull) { return this; } else { /* Just flip the canBeNull flag and copy the rest of the values from this. */ - return new ContextSensitiveMultiTypeState(bb, resultCanBeNull, this); + return PointsToStats.registerTypeState(bb, new ContextSensitiveMultiTypeState(resultCanBeNull, this)); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveSingleTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveSingleTypeState.java index 850dfbc59711..975a661ee059 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveSingleTypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/ContextSensitiveSingleTypeState.java @@ -31,6 +31,7 @@ import com.oracle.graal.pointsto.PointsToAnalysis; import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.typestate.PointsToStats; import com.oracle.graal.pointsto.typestate.SingleTypeState; import com.oracle.graal.pointsto.typestate.TypeState; @@ -41,20 +42,19 @@ public class ContextSensitiveSingleTypeState extends SingleTypeState { protected final AnalysisObject[] objects; /** Creates a new type state from incoming objects. */ - @SuppressWarnings("this-escape") - public ContextSensitiveSingleTypeState(PointsToAnalysis bb, boolean canBeNull, AnalysisType type, AnalysisObject... objects) { - super(bb, canBeNull, type); + public ContextSensitiveSingleTypeState(boolean canBeNull, AnalysisType type, AnalysisObject... objects) { + super(canBeNull, type); this.objects = objects; - assert checkObjects(); + assert checkObjects(objects); } /** Create a type state with the same content and a reversed canBeNull value. */ - protected ContextSensitiveSingleTypeState(PointsToAnalysis bb, boolean canBeNull, ContextSensitiveSingleTypeState other) { - super(bb, canBeNull, other); + protected ContextSensitiveSingleTypeState(boolean canBeNull, ContextSensitiveSingleTypeState other) { + super(canBeNull, other); this.objects = other.objects; } - protected boolean checkObjects() { + private static boolean checkObjects(AnalysisObject[] objects) { /* Check that the objects array are sorted by type. */ for (int idx = 0; idx < objects.length - 1; idx++) { AnalysisObject o0 = objects[idx]; @@ -107,7 +107,7 @@ public TypeState forCanBeNull(PointsToAnalysis bb, boolean stateCanBeNull) { if (stateCanBeNull == this.canBeNull()) { return this; } else { - return new ContextSensitiveSingleTypeState(bb, stateCanBeNull, this); + return PointsToStats.registerTypeState(bb, new ContextSensitiveSingleTypeState(stateCanBeNull, this)); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapConstant.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapConstant.java index 13448acb5b1c..9151f5162ea5 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapConstant.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapConstant.java @@ -52,7 +52,7 @@ @Platforms(Platform.HOSTED_ONLY.class) public abstract class ImageHeapConstant implements JavaConstant, TypedConstant, CompressibleConstant, VMConstant { - private static final AtomicInteger currentId = new AtomicInteger(0); + private static final AtomicInteger currentId = new AtomicInteger(1); public static final VarHandle isReachableHandle = ReflectionUtil.unreflectField(ConstantData.class, "isReachable", MethodHandles.lookup()); 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 ce2a2eac840f..a4b777b90d17 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 @@ -81,7 +81,7 @@ private InstanceData(AnalysisType type, JavaConstant hostedObject, Object[] fiel this(type, hostedObject, -1, -1); } - ImageHeapInstance(AnalysisType type, JavaConstant hostedObject, int identityHashCode, int id) { + public ImageHeapInstance(AnalysisType type, JavaConstant hostedObject, int identityHashCode, int id) { super(new InstanceData(type, hostedObject, null, identityHashCode, id), false); } @@ -98,7 +98,7 @@ public InstanceData getConstantData() { return (InstanceData) super.getConstantData(); } - void setFieldValues(Object[] fieldValues) { + public void setFieldValues(Object[] fieldValues) { boolean success = valuesHandle.compareAndSet(constantData, null, fieldValues); AnalysisError.guarantee(success, "Unexpected field values reference for constant %s", this); } @@ -149,7 +149,18 @@ public Object getFieldValue(AnalysisField field) { /* Base layer constants that are not relinked might not have field positions computed */ field.getType().getInstanceFields(true); } - return arrayHandle.getVolatile(getFieldValues(), field.getPosition()); + return getFieldValue(field.getPosition()); + } + + public Object getFieldValue(int fieldPosition) { + return arrayHandle.getVolatile(getFieldValues(), fieldPosition); + } + + public int getFieldValuesSize() { + if (isReaderInstalled()) { + return getConstantData().fieldValues.length; + } + return 0; } /** diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapObjectArray.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapObjectArray.java index 108555de0b9f..311085fc1889 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapObjectArray.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapObjectArray.java @@ -71,7 +71,7 @@ private ObjectArrayData(AnalysisType type, JavaConstant hostedObject, Object[] a this(type, hostedObject, length, -1, -1); } - ImageHeapObjectArray(AnalysisType type, JavaConstant hostedObject, int length, int identityHashCode, int id) { + public ImageHeapObjectArray(AnalysisType type, JavaConstant hostedObject, int length, int identityHashCode, int id) { super(new ObjectArrayData(type, hostedObject, null, length, identityHashCode, id), false); } @@ -92,7 +92,7 @@ public ObjectArrayData getConstantData() { return (ObjectArrayData) super.getConstantData(); } - void setElementValues(Object[] elementValues) { + public void setElementValues(Object[] elementValues) { boolean success = elementsHandle.compareAndSet(constantData, null, elementValues); AnalysisError.guarantee(success, "Unexpected field values reference for constant %s", this); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapPrimitiveArray.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapPrimitiveArray.java index e664bb1c10e9..e021ca04fa24 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapPrimitiveArray.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapPrimitiveArray.java @@ -60,7 +60,7 @@ private PrimitiveArrayData(AnalysisType type, JavaConstant hostedObject, Object this(type, hostedObject, array, length, -1, -1); } - ImageHeapPrimitiveArray(AnalysisType type, JavaConstant hostedObject, Object array, int length, int identityHashCode, int id) { + public ImageHeapPrimitiveArray(AnalysisType type, JavaConstant hostedObject, Object array, int length, int identityHashCode, int id) { super(new PrimitiveArrayData(type, hostedObject, /* We need a clone of the hosted array so that we have a stable snapshot. */ getClone(type.getComponentType().getJavaKind(), array), diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java index 2edafcb27ebc..aba8d82bc991 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java @@ -44,6 +44,7 @@ import com.oracle.graal.pointsto.ObjectScanner.ScanReason; import com.oracle.graal.pointsto.ObjectScanningObserver; import com.oracle.graal.pointsto.api.HostVM; +import com.oracle.graal.pointsto.api.ImageLayerLoader; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.heap.HeapSnapshotVerifier.ScanningObserver; import com.oracle.graal.pointsto.heap.value.ValueSupplier; @@ -830,7 +831,7 @@ protected void rescanEconomicMap(EconomicMap object) { } } - void doScan(JavaConstant constant) { + public void doScan(JavaConstant constant) { doScan(constant, OtherReason.RESCAN); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoader.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoader.java deleted file mode 100644 index 7494e0d1ec54..000000000000 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoader.java +++ /dev/null @@ -1,1512 +0,0 @@ -/* - * 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.graal.pointsto.heap; - -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ANALYSIS_PARSED_GRAPH_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ARGUMENTS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ARGUMENT_IDS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ARRAY_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CAN_BE_STATICALLY_BOUND_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CLASS_INIT_NAME; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CLASS_JAVA_NAME_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CLASS_NAME_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CODE_SIZE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CODE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.COMPONENT_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CONSTANTS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CONSTANTS_TO_RELINK_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CONSTANT_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CONSTRUCTOR_NAME; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.DATA_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ENCLOSING_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ENUM_CLASS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ENUM_NAME_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FIELDS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FIELD_ACCESSED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FIELD_FOLDED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FIELD_READ_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FIELD_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FIELD_WRITTEN_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.HUB_IDENTITY_HASH_CODE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IDENTITY_HASH_CODE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IMAGE_HEAP_SIZE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INSTANCE_FIELDS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INSTANCE_FIELDS_WITH_SUPER_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INSTANCE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INTERFACES_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INTRINSIC_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_CONSTRUCTOR_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_DIRECT_ROOT_METHOD; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_ENUM_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_IMPLEMENTATION_INVOKED; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INITIALIZED_AT_BUILD_TIME_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INITIALIZED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INSTANTIATED; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INTERFACE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INTERNAL_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INTRINSIC_METHOD; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INVOKED; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_LINKED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_REACHABLE; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_STATIC_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_SYNTHETIC_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_UNSAFE_ALLOCATED; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_VAR_ARGS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_VIRTUAL_ROOT_METHOD; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.LOCATION_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.METHODS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.METHOD_HANDLE_INTRINSIC_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.MODIFIERS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NAME_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NEXT_CONSTANT_ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NEXT_FIELD_ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NEXT_METHOD_ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NEXT_TYPE_ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NOT_MATERIALIZED_CONSTANT; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NULL_POINTER_CONSTANT; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.OBJECT_OFFSET_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.OBJECT_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.PARENT_CONSTANT_ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.PARENT_CONSTANT_INDEX_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.PERSISTED; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.PRIMITIVE_ARRAY_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.RELOCATED_CONSTANT_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.RETURN_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.SIMULATED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.SOURCE_FILE_NAME_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.STATIC_OBJECT_FIELDS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.STATIC_PRIMITIVE_FIELDS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.STRENGTHENED_GRAPH_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.SUPER_CLASS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.TID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.TYPES_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.VALUE_TAG; -import static com.oracle.graal.pointsto.util.AnalysisError.guarantee; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.annotation.Annotation; -import java.lang.reflect.Executable; -import java.lang.reflect.Field; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.MapCursor; - -import com.oracle.graal.pointsto.flow.AnalysisParsedGraph; -import com.oracle.graal.pointsto.heap.value.ValueSupplier; -import com.oracle.graal.pointsto.infrastructure.ResolvedSignature; -import com.oracle.graal.pointsto.meta.AnalysisField; -import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; -import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.meta.AnalysisUniverse; -import com.oracle.graal.pointsto.meta.BaseLayerField; -import com.oracle.graal.pointsto.meta.BaseLayerMethod; -import com.oracle.graal.pointsto.meta.BaseLayerType; -import com.oracle.graal.pointsto.util.AnalysisError; -import com.oracle.graal.pointsto.util.AnalysisFuture; -import com.oracle.svm.util.ReflectionUtil; - -import jdk.graal.compiler.core.common.NumUtil; -import jdk.graal.compiler.core.common.SuppressFBWarnings; -import jdk.graal.compiler.debug.GraalError; -import jdk.graal.compiler.nodes.EncodedGraph; -import jdk.graal.compiler.util.ObjectCopier; -import jdk.graal.compiler.util.json.JsonParser; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.MethodHandleAccessProvider.IntrinsicMethod; -import jdk.vm.ci.meta.PrimitiveConstant; -import jdk.vm.ci.meta.ResolvedJavaField; -import jdk.vm.ci.meta.ResolvedJavaType; - -/** - * Loads the base layer persisted by {@link ImageLayerWriter}. The format of the json file is the - * following: - * - *

      - * {
      - *      "next type id": nextTypeId,
      - *      "next method id": nextMethodId,
      - *      "next field id": nextFieldId,
      - *      "static primitive fields": staticPrimitiveFields.id,
      - *      "static object fields": staticObjectFields.id,
      - *      "image heap size": imageHeapSize,
      - *      "constants to relink": [ids...],
      - *      "types": {
      - *          typeIdentifier: {
      - *              "id": id,
      - *              "fields": [ids...],
      - *              "hub identityHashCode": System.identityHashCode(hub),
      - *              "class java name": type.toJavaName(),
      - *              "class name": type.getName(),
      - *              "modifiers": modifiers,
      - *              "is interface": isInterface,
      - *              "is enum": isEnum,
      - *              "is initialized": isInitialized,
      - *              "is initialized at build time": isInitializedAtBuildTime,
      - *              "is linked": isLinked,
      - *              "source file name": sourceFileName,
      - *              "enclosing type": enclosingTid,
      - *              "component type": componentTid,
      - *              "super class": superClassTid,
      - *              "is instantiated": isInstantiated,
      - *              "is unsafe allocated": isUnsafeAllocated,
      - *              "is reachable": isReachable,
      - *              "interfaces": [
      - *                  interfaceTid,
      - *                  ...
      - *              ],
      - *              "annotations": [
      - *                  annotationName,
      - *                  ...
      - *              ]
      - *          },
      - *          ...
      - *      },
      - *      "methods": {
      - *          methodIdentifier: {
      - *              "id": id,
      - *              ("arguments": [
      - *                  argumentName,
      - *                  ...
      - *              ],
      - *              "class name": className,)
      - *              "tid": tid,
      - *              "argument ids": [
      - *                  argumentId,
      - *                  ...
      - *              ],
      - *              "id": id,
      - *              "name": name,
      - *              "return type": returnTypeId,
      - *              "is varArg": isVarArg,
      - *              "can be statically bound": canBeStaticallyBound,
      - *              "modifiers": modifiers,
      - *              "is constructor": isConstructor,
      - *              "is synthetic": isSynthetic,
      - *              "code": code,
      - *              "code size": codeSize,
      - *              "method handle intrinsic": methodHandleIntrinsic,
      - *              "compiled": compiled,
      - *              "is virtual root method": isVirtualRootMethod,
      - *              "is direct root method": isDirectRootMethod,
      - *              "is invoked": isInvoked,
      - *              "is implementation invoked": isImplementationInvoked,
      - *              "is intrinsic method": isIntrinsicMethod,
      - *              "annotations": [
      - *                  annotationName,
      - *                  ...
      - *              ]
      - *          },
      - *          ...
      - *      },
      - *      "fields": {
      - *          tid: {
      - *              name: {
      - *                  "id": id,
      - *                  "accessed": accessed,
      - *                  "read": read,
      - *                  "written": written,
      - *                  "folded": folded,
      - *                  "is internal": isInternal,
      - *                  "field type": typeId,
      - *                  "modifiers": modifiers,
      - *                  "position": position,
      - *                  "annotations": [
      - *                      annotationName,
      - *                      ...
      - *                  ]
      - *                  (,"class name": className)
      - *                  (,"location": location)
      - *              },
      - *              ...
      - *          },
      - *          ...
      - *      },
      - *      "constants": {
      - *          id: {
      - *              "tid": tid,
      - *              "identityHashCode": identityHashCode,
      - *              "constant type": constantType,
      - *              "data": [
      - *                  [constantType, value],
      - *                  ...
      - *              ],
      - *              "simulated": simulated
      - *              (,"object offset": offset)
      - *              (,"value": string)
      - *              (,"enum class": enumClass)
      - *              (,"enum name": enumValue)
      - *              (,"class id": cid)
      - *          }
      - *      },
      - *      "image singleton objects" : [
      - *          objectID,
      - *          "class name",
      - *          { (key_value_store) }
      - *      ],
      - *      "image singleton keys" : [
      - *          "key class name",
      - *          persist_flags,
      - *          objectID
      - *      ]
      - * }
      - * 
      - * - * For an {@link ImageHeapInstance} or an {@link ImageHeapObjectArray}, the "data" entry contains - * constant ids, markers from {@link ImageLayerSnapshotUtil} or primitive value, stored in the form - * of a two elements array. The first element is the constant type, which is the string - * representation of the kind of the primitive value or a custom tag. The second element is the - * primitive value, the constant id, the method id or a custom marker. For an - * {@link ImageHeapPrimitiveArray} it contains the array itself. Interned {@link String} constants - * are relinked in the extension image using their base layer "value". {@link Enum} values are - * relinked in the extension image using their "enum class" and "enum name". {@link Class} constants - * (DynamicHub) are relinked in the extension image using their type id. - *

      - * Relinking a base layer {@link ImageHeapConstant} is finding the corresponding hosted object in - * the extension image build process and storing it in the constant. This is only done for object - * that can be created or found using a specific recipe. Those constants are then called parent - * constants. Some fields of their field values can then be relinked using the value of the hosted - * object. The produced constants are called child constants, and they have to be consistent across - * image builds. - *

      - * The "offset object" is the offset of the constant in the heap from the base layer. - */ -public class ImageLayerLoader { - private final Map types = new ConcurrentHashMap<>(); - protected final Map methods = new ConcurrentHashMap<>(); - protected final Map fields = new ConcurrentHashMap<>(); - protected final Map constants = new ConcurrentHashMap<>(); - private final List loadPaths; - private final Map baseLayerTypes = new ConcurrentHashMap<>(); - private final Map typeToHubIdentityHashCode = new ConcurrentHashMap<>(); - private final Map baseLayerMethods = new ConcurrentHashMap<>(); - private final Map baseLayerFields = new ConcurrentHashMap<>(); - - /** Map from the type id to its identifier in the jsonMap. */ - protected final Map typeIdToIdentifier = new HashMap<>(); - - /** Map from the method id to its identifier in the jsonMap. */ - private final Map methodIdToIdentifier = new HashMap<>(); - - /** Map from the field id to the information needed to access it in the jsonMap. */ - private final Map fieldIdToIdentifier = new HashMap<>(); - - record FieldIdentifier(String tid, String name) { - } - - protected final Set> heapScannerTasks = ConcurrentHashMap.newKeySet(); - private ImageLayerSnapshotUtil imageLayerSnapshotUtil; - private ImageLayerLoaderHelper imageLayerLoaderHelper; - protected final Map typeToConstant = new ConcurrentHashMap<>(); - protected final Map stringToConstant = new ConcurrentHashMap<>(); - protected final Map, Integer> enumToConstant = new ConcurrentHashMap<>(); - protected final Map objectOffsets = new ConcurrentHashMap<>(); - protected final Map fieldLocations = new ConcurrentHashMap<>(); - protected AnalysisUniverse universe; - protected AnalysisMetaAccess metaAccess; - protected HostedValuesProvider hostedValuesProvider; - - protected EconomicMap jsonMap; - protected FileChannel graphsChannel; - - private long imageHeapSize; - - public record FilePaths(Path snapshot, Path snapshotGraphs) { - } - - public ImageLayerLoader() { - this(List.of()); - } - - public ImageLayerLoader(List loadPaths) { - this.loadPaths = loadPaths; - } - - public void setImageLayerSnapshotUtil(ImageLayerSnapshotUtil imageLayerSnapshotUtil) { - this.imageLayerSnapshotUtil = imageLayerSnapshotUtil; - } - - public AnalysisUniverse getUniverse() { - return universe; - } - - public void setUniverse(AnalysisUniverse newUniverse) { - this.universe = newUniverse; - } - - public void setImageLayerLoaderHelper(ImageLayerLoaderHelper imageLayerLoaderHelper) { - this.imageLayerLoaderHelper = imageLayerLoaderHelper; - } - - /** This code is not thread safe. */ - protected void openFilesAndLoadJsonMap() { - assert loadPaths.size() == 1 : "Currently only one path is supported for image layer loading " + loadPaths; - if (jsonMap == null) { - for (FilePaths paths : loadPaths) { - try { - graphsChannel = FileChannel.open(paths.snapshotGraphs); - - try (InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(paths.snapshot.toFile()))) { - Object json = new JsonParser(inputStreamReader).parse(); - jsonMap = cast(json); - } - } catch (IOException e) { - throw AnalysisError.shouldNotReachHere("Error during image layer snapshot loading", e); - } - } - } - } - - public void loadLayerAnalysis() { - openFilesAndLoadJsonMap(); - loadLayerAnalysis0(); - } - - public void cleanupAfterAnalysis() { - if (graphsChannel != null) { - try { - graphsChannel.close(); - } catch (IOException e) { - throw AnalysisError.shouldNotReachHere(e); - } - } - } - - /** - * Initializes the {@link ImageLayerLoader}. - */ - private void loadLayerAnalysis0() { - /* - * The new ids of the extension image need to be different from the ones from the base - * layer. The start id is set to the next id of the base layer. - */ - int nextTypeId = get(jsonMap, NEXT_TYPE_ID_TAG); - universe.setStartTypeId(nextTypeId); - - int nextMethodId = get(jsonMap, NEXT_METHOD_ID_TAG); - universe.setStartMethodId(nextMethodId); - - int nextFieldId = get(jsonMap, NEXT_FIELD_ID_TAG); - universe.setStartFieldId(nextFieldId); - - int nextConstantId = get(jsonMap, NEXT_CONSTANT_ID_TAG); - ImageHeapConstant.setCurrentId(nextConstantId); - - imageHeapSize = Long.parseLong(get(jsonMap, IMAGE_HEAP_SIZE_TAG)); - - storeIdToIdentifier(TYPES_TAG, typeIdToIdentifier); - storeIdToIdentifier(METHODS_TAG, methodIdToIdentifier); - - EconomicMap fieldsMap = get(jsonMap, FIELDS_TAG); - MapCursor fieldsCursor = fieldsMap.getEntries(); - while (fieldsCursor.advance()) { - EconomicMap typeData = getValue(fieldsCursor); - MapCursor typeFieldsCursor = typeData.getEntries(); - while (typeFieldsCursor.advance()) { - EconomicMap fieldData = getValue(typeFieldsCursor); - int id = get(fieldData, ID_TAG); - fieldIdToIdentifier.put(id, new FieldIdentifier(fieldsCursor.getKey(), typeFieldsCursor.getKey())); - } - } - - EconomicMap constantsMap = get(jsonMap, CONSTANTS_TAG); - List constantsToRelink = get(jsonMap, CONSTANTS_TO_RELINK_TAG); - for (int id : constantsToRelink) { - EconomicMap constantData = get(constantsMap, String.valueOf(id)); - int identityHashCode = get(constantData, IDENTITY_HASH_CODE_TAG); - prepareConstantRelinking(constantData, identityHashCode, id); - } - } - - private void storeIdToIdentifier(String tag, Map idToIdentifier) { - EconomicMap elementsMap = get(jsonMap, tag); - MapCursor cursor = elementsMap.getEntries(); - while (cursor.advance()) { - EconomicMap data = getValue(cursor); - int id = get(data, ID_TAG); - idToIdentifier.put(id, cursor.getKey()); - } - } - - protected void prepareConstantRelinking(EconomicMap constantData, int identityHashCode, int id) { - String value = get(constantData, VALUE_TAG); - if (value != null) { - injectIdentityHashCode(value.intern(), identityHashCode); - stringToConstant.put(value, id); - } - - String className = get(constantData, ENUM_CLASS_TAG); - if (className != null) { - Enum enumValue = getEnumValue(constantData); - injectIdentityHashCode(enumValue, identityHashCode); - enumToConstant.put(enumValue, id); - } - } - - private void loadType(EconomicMap typeData) { - int tid = get(typeData, ID_TAG); - - if (imageLayerLoaderHelper.loadType(typeData, tid)) { - return; - } - - String name = get(typeData, CLASS_JAVA_NAME_TAG); - Class clazz = lookupBaseLayerTypeInHostVM(name); - - Integer superClassTid = get(typeData, SUPER_CLASS_TAG); - ResolvedJavaType superClass = getResolvedJavaType(superClassTid); - - List interfacesIds = get(typeData, INTERFACES_TAG); - ResolvedJavaType[] interfaces = interfacesIds.stream().map(this::getResolvedJavaType).toList().toArray(new ResolvedJavaType[0]); - - if (clazz != null) { - /* - * When looking up the class by name, the host VM will create the corresponding - * AnalysisType. During this process, the method lookupHostedTypeInBaseLayer will be - * called to see if the type already exists in the base layer. If it is the case, the id - * from the base layer will be reused and the ImageLayerLoader#types map will be - * populated. - */ - metaAccess.lookupJavaType(clazz); - } - - if (!types.containsKey(tid)) { - /* - * If the type cannot be looked up by name, an incomplete AnalysisType, which uses a - * BaseLayerType in its wrapped field, has to be created - */ - BaseLayerType baseLayerType = getBaseLayerType(typeData, tid, superClass, interfaces); - - List instanceFieldIds = get(typeData, INSTANCE_FIELDS_TAG); - ResolvedJavaField[] instanceFields = instanceFieldIds.stream().map(this::getBaseLayerField).toList().toArray(new ResolvedJavaField[0]); - baseLayerType.setInstanceFields(instanceFields); - - List instanceFieldWithSuperIds = get(typeData, INSTANCE_FIELDS_WITH_SUPER_TAG); - ResolvedJavaField[] instanceFieldsWithSuper = instanceFieldWithSuperIds.stream().map(this::getBaseLayerField).toList().toArray(new ResolvedJavaField[0]); - baseLayerType.setInstanceFieldsWithSuper(instanceFieldsWithSuper); - - AnalysisType type = universe.lookup(baseLayerType); - guarantee(getBaseLayerTypeId(type) == tid, "The base layer type %s is not correctly matched to the id %d", type, tid); - } - } - - private BaseLayerType getBaseLayerType(int tid) { - EconomicMap typesMap = get(jsonMap, TYPES_TAG); - EconomicMap typeData = get(typesMap, typeIdToIdentifier.get(tid)); - - Integer superClassTid = get(typeData, SUPER_CLASS_TAG); - ResolvedJavaType superClass = getResolvedJavaType(superClassTid); - - List interfacesIds = get(typeData, INTERFACES_TAG); - ResolvedJavaType[] interfaces = interfacesIds.stream().map(this::getResolvedJavaType).toList().toArray(new ResolvedJavaType[0]); - - return getBaseLayerType(typeData, tid, superClass, interfaces); - } - - private BaseLayerType getBaseLayerType(EconomicMap typeData, int tid, ResolvedJavaType superClass, ResolvedJavaType[] interfaces) { - return baseLayerTypes.computeIfAbsent(tid, typeId -> { - String className = get(typeData, CLASS_NAME_TAG); - int modifiers = get(typeData, MODIFIERS_TAG); - boolean isInterface = get(typeData, IS_INTERFACE_TAG); - boolean isEnum = get(typeData, IS_ENUM_TAG); - boolean isInitialized = get(typeData, IS_INITIALIZED_TAG); - boolean initializedAtBuildTime = get(typeData, IS_INITIALIZED_AT_BUILD_TIME_TAG); - boolean isLinked = get(typeData, IS_LINKED_TAG); - String sourceFileName = get(typeData, SOURCE_FILE_NAME_TAG); - - Integer enclosingTid = get(typeData, ENCLOSING_TYPE_TAG); - ResolvedJavaType enclosingType = getResolvedJavaType(enclosingTid); - - Integer componentTid = get(typeData, COMPONENT_TYPE_TAG); - ResolvedJavaType componentType = getResolvedJavaType(componentTid); - - ResolvedJavaType objectType = universe.getOriginalMetaAccess().lookupJavaType(Object.class); - - Annotation[] annotations = getAnnotations(typeData); - - return new BaseLayerType(className, tid, modifiers, isInterface, isEnum, isInitialized, initializedAtBuildTime, isLinked, sourceFileName, enclosingType, componentType, superClass, - interfaces, objectType, annotations); - }); - } - - protected Annotation[] getAnnotations(@SuppressWarnings("unused") EconomicMap elementData) { - return new Annotation[0]; - } - - private ResolvedJavaType getResolvedJavaType(Integer tid) { - return tid == null ? null : getAnalysisType(tid).getWrapped(); - } - - public AnalysisType getAnalysisType(Integer tid) { - if (!types.containsKey(tid)) { - EconomicMap typesMap = get(jsonMap, TYPES_TAG); - loadType(get(typesMap, typeIdToIdentifier.get(tid))); - } - guarantee(types.containsKey(tid), "Type with id %d was not correctly loaded.", tid); - /* - * The type needs to be looked up because it ensures the type is completely created, as the - * types Map is populated before the type is created. - */ - return universe.lookup(types.get(tid).getWrapped()); - } - - /** - * Returns the type id of the given type in the base layer if it exists. This makes the link - * between the base layer and the extension layer as the id is used to determine which constant - * should be linked to this type. - */ - public int lookupHostedTypeInBaseLayer(AnalysisType type) { - int id = getBaseLayerTypeId(type); - if (id == -1 || types.putIfAbsent(id, type) != null) { - /* A complete type is treated as a different type than its incomplete version */ - return -1; - } - return id; - } - - private int getBaseLayerTypeId(AnalysisType type) { - if (type.getWrapped() instanceof BaseLayerType baseLayerType) { - return baseLayerType.getBaseLayerId(); - } - String typeIdentifier = imageLayerSnapshotUtil.getTypeIdentifier(type); - EconomicMap typeData = getElementData(TYPES_TAG, typeIdentifier); - if (typeData == null) { - /* The type was not reachable in the base image */ - return -1; - } - int id = get(typeData, ID_TAG); - int hubIdentityHashCode = get(typeData, HUB_IDENTITY_HASH_CODE_TAG); - typeToHubIdentityHashCode.put(id, hubIdentityHashCode); - return id; - } - - public void initializeBaseLayerType(AnalysisType type) { - String typeIdentifier = typeIdToIdentifier.get(type.getId()); - boolean post = true; - if (typeIdentifier == null) { - /* - * This type was not already created when loading the base layer, so the flags can be - * registered directly. - */ - post = false; - typeIdentifier = imageLayerSnapshotUtil.getTypeIdentifier(type); - } - EconomicMap typeData = getElementData(TYPES_TAG, typeIdentifier); - if (typeData != null) { - boolean isInstantiated = get(typeData, IS_INSTANTIATED); - boolean isUnsafeAllocated = get(typeData, IS_UNSAFE_ALLOCATED); - boolean isReachable = get(typeData, IS_REACHABLE); - - registerFlag(isInstantiated, post, () -> type.registerAsInstantiated(PERSISTED)); - registerFlag(isUnsafeAllocated, post, () -> type.registerAsUnsafeAllocated(PERSISTED)); - registerFlag(isReachable, post, () -> type.registerAsReachable(PERSISTED)); - } - } - - /** - * Tries to look up the base layer type in the current VM. Some types cannot be looked up by - * name (for example $$Lambda types), so this method can return null. - */ - public Class lookupBaseLayerTypeInHostVM(String type) { - int arrayType = 0; - String componentType = type; - /* - * We cannot look up an array type directly. We have to look up the component type and then - * go back to the array type. - */ - while (componentType.endsWith("[]")) { - componentType = componentType.substring(0, componentType.length() - 2); - arrayType++; - } - Class clazz = lookupPrimitiveClass(componentType); - if (clazz == null) { - clazz = lookupClass(true, componentType); - } - if (clazz == null) { - return null; - } - while (arrayType > 0) { - assert clazz != null; - clazz = clazz.arrayType(); - arrayType--; - } - return clazz; - } - - private static Class lookupPrimitiveClass(String type) { - return switch (type) { - case "boolean" -> boolean.class; - case "byte" -> byte.class; - case "short" -> short.class; - case "char" -> char.class; - case "int" -> int.class; - case "long" -> long.class; - case "float" -> float.class; - case "double" -> double.class; - case "void" -> void.class; - default -> null; - }; - } - - private void loadMethod(EconomicMap methodData) { - int mid = get(methodData, ID_TAG); - - if (imageLayerLoaderHelper.loadMethod(methodData, mid)) { - return; - } - - int tid = get(methodData, TID_TAG); - AnalysisType type = getAnalysisType(tid); - - List parameterTypeIds = get(methodData, ARGUMENT_IDS_TAG); - AnalysisType[] parameterTypes = parameterTypeIds.stream().map(this::getAnalysisType).toList().toArray(new AnalysisType[0]); - - AnalysisType returnType = getAnalysisType(get(methodData, RETURN_TYPE_TAG)); - - String name = get(methodData, NAME_TAG); - String className = get(methodData, CLASS_NAME_TAG); - if (className != null) { - List arguments = get(methodData, ARGUMENTS_TAG); - - Executable method = null; - Class clazz = lookupBaseLayerTypeInHostVM(className); - if (clazz != null) { - Class[] argumentClasses = arguments.stream().map(this::lookupBaseLayerTypeInHostVM).toList().toArray(new Class[0]); - method = lookupMethodByReflection(name, clazz, argumentClasses); - } - - if (method != null) { - metaAccess.lookupJavaMethod(method); - if (methods.containsKey(mid)) { - return; - } - } - } - - Class[] argumentClasses = Arrays.stream(parameterTypes).map(AnalysisType::getJavaClass).toList().toArray(new Class[0]); - Executable method = lookupMethodByReflection(name, type.getJavaClass(), argumentClasses); - - if (method != null) { - metaAccess.lookupJavaMethod(method); - if (methods.containsKey(mid)) { - return; - } - } - - ResolvedSignature signature = ResolvedSignature.fromList(Arrays.stream(parameterTypes).toList(), returnType); - - if (name.equals(CONSTRUCTOR_NAME)) { - type.findConstructor(signature); - } else if (name.equals(CLASS_INIT_NAME)) { - type.getClassInitializer(); - } else { - type.findMethod(name, signature); - } - - if (!methods.containsKey(mid)) { - createBaseLayerMethod(methodData, mid, name, parameterTypes, returnType); - } - } - - public static Executable lookupMethodByReflection(String name, Class clazz, Class[] argumentClasses) { - try { - Executable method; - if (name.equals(CONSTRUCTOR_NAME)) { - method = ReflectionUtil.lookupConstructor(true, clazz, argumentClasses); - } else { - method = ReflectionUtil.lookupMethod(true, clazz, name, argumentClasses); - } - return method; - } catch (NoClassDefFoundError e) { - return null; - } - } - - private void createBaseLayerMethod(EconomicMap methodData, int mid, String name, AnalysisType[] parameterTypes, AnalysisType returnType) { - AnalysisType type = getAnalysisType(get(methodData, TID_TAG)); - ResolvedSignature signature = ResolvedSignature.fromArray(parameterTypes, returnType); - boolean canBeStaticallyBound = get(methodData, CAN_BE_STATICALLY_BOUND_TAG); - boolean isConstructor = get(methodData, IS_CONSTRUCTOR_TAG); - int modifiers = get(methodData, MODIFIERS_TAG); - boolean isSynthetic = get(methodData, IS_SYNTHETIC_TAG); - boolean isVarArgs = get(methodData, IS_VAR_ARGS_TAG); - List codeEncoding = get(methodData, CODE_TAG); - byte[] code = codeEncoding == null ? null : getBytes(codeEncoding); - int codeSize = get(methodData, CODE_SIZE_TAG); - String methodHandleIntrinsicName = get(methodData, METHOD_HANDLE_INTRINSIC_TAG); - IntrinsicMethod methodHandleIntrinsic = methodHandleIntrinsicName == null ? null : IntrinsicMethod.valueOf(methodHandleIntrinsicName); - Annotation[] annotations = getAnnotations(methodData); - - baseLayerMethods.computeIfAbsent(mid, methodId -> new BaseLayerMethod(mid, type, name, isVarArgs, signature, canBeStaticallyBound, isConstructor, modifiers, isSynthetic, code, codeSize, - methodHandleIntrinsic, annotations)); - BaseLayerMethod baseLayerMethod = baseLayerMethods.get(mid); - - universe.lookup(baseLayerMethod); - } - - public AnalysisMethod getAnalysisMethod(int mid) { - if (!methods.containsKey(mid)) { - EconomicMap methodsMap = get(jsonMap, METHODS_TAG); - loadMethod(get(methodsMap, methodIdToIdentifier.get(mid))); - } - - AnalysisMethod analysisMethod = methods.get(mid); - AnalysisError.guarantee(analysisMethod != null, "Method with id %d was not correctly loaded.", mid); - return analysisMethod; - } - - /** - * Returns the method id of the given method in the base layer if it exists. This makes the link - * between the base layer and the extension layer as the id is used to determine the method used - * in RelocatableConstants. - */ - public int lookupHostedMethodInBaseLayer(AnalysisMethod analysisMethod) { - return getBaseLayerMethodId(analysisMethod); - } - - private int getBaseLayerMethodId(AnalysisMethod analysisMethod) { - if (analysisMethod.getWrapped() instanceof BaseLayerMethod baseLayerMethod) { - return baseLayerMethod.getBaseLayerId(); - } - EconomicMap methodData = getMethodData(analysisMethod); - if (methodData == null || methods.containsKey(analysisMethod.getId())) { - /* The method was not reachable in the base image */ - return -1; - } - return get(methodData, ID_TAG); - } - - public void addBaseLayerMethod(AnalysisMethod analysisMethod) { - methods.putIfAbsent(analysisMethod.getId(), analysisMethod); - } - - public void initializeBaseLayerMethod(AnalysisMethod analysisMethod) { - initializeBaseLayerMethod(analysisMethod, getMethodData(analysisMethod)); - } - - protected void initializeBaseLayerMethod(AnalysisMethod analysisMethod, EconomicMap methodData) { - boolean isVirtualRootMethod = get(methodData, IS_VIRTUAL_ROOT_METHOD); - boolean isDirectRootMethod = get(methodData, IS_DIRECT_ROOT_METHOD); - boolean isInvoked = get(methodData, IS_INVOKED); - boolean isImplementationInvoked = get(methodData, IS_IMPLEMENTATION_INVOKED); - boolean isIntrinsicMethod = get(methodData, IS_INTRINSIC_METHOD); - - registerFlag(isVirtualRootMethod, true, () -> analysisMethod.registerAsVirtualRootMethod(PERSISTED)); - registerFlag(isDirectRootMethod, true, () -> analysisMethod.registerAsDirectRootMethod(PERSISTED)); - registerFlag(isInvoked, true, () -> analysisMethod.registerAsInvoked(PERSISTED)); - registerFlag(isImplementationInvoked, true, () -> analysisMethod.registerAsImplementationInvoked(PERSISTED)); - registerFlag(isIntrinsicMethod, true, () -> analysisMethod.registerAsIntrinsicMethod(PERSISTED)); - } - - /** - * Currently we save analysis parsed graphs for methods considered - * {@link AnalysisMethod#isReachable}. See {@link ImageLayerWriter#persistMethodGraphs} for - * implementation. - */ - public boolean hasAnalysisParsedGraph(AnalysisMethod analysisMethod) { - EconomicMap methodData = getMethodData(analysisMethod); - return get(methodData, ANALYSIS_PARSED_GRAPH_TAG) != null; - } - - public AnalysisParsedGraph getAnalysisParsedGraph(AnalysisMethod analysisMethod) { - EconomicMap methodData = getMethodData(analysisMethod); - byte[] encodedAnalyzedGraph = readEncodedGraph(methodData, ANALYSIS_PARSED_GRAPH_TAG); - Boolean intrinsic = get(methodData, INTRINSIC_TAG); - EncodedGraph analyzedGraph = (EncodedGraph) ObjectCopier.decode(imageLayerSnapshotUtil.getGraphDecoder(this, analysisMethod, universe.getSnippetReflection()), encodedAnalyzedGraph); - if (hasStrengthenedGraph(analysisMethod)) { - throw AnalysisError.shouldNotReachHere("Strengthened graphs are not supported until late loading is implemented."); - } - afterGraphDecodeHook(analyzedGraph); - return new AnalysisParsedGraph(analyzedGraph, intrinsic); - } - - private byte[] readEncodedGraph(EconomicMap methodData, String elementIdentifier) { - String location = get(methodData, elementIdentifier); - int closingBracketAt = location.length() - 1; - AnalysisError.guarantee(location.charAt(0) == '@' && location.charAt(closingBracketAt) == ']', "Location must start with '@' and end with ']': %s", location); - int openingBracketAt = location.indexOf('[', 1, closingBracketAt); - AnalysisError.guarantee(openingBracketAt < closingBracketAt, "Location does not contain '[' at expected location: %s", location); - long offset; - long nbytes; - try { - offset = Long.parseUnsignedLong(location.substring(1, openingBracketAt)); - nbytes = Long.parseUnsignedLong(location.substring(openingBracketAt + 1, closingBracketAt)); - } catch (NumberFormatException e) { - throw AnalysisError.shouldNotReachHere("Location contains invalid positive integer(s): " + location); - } - ByteBuffer bb = ByteBuffer.allocate(NumUtil.safeToInt(nbytes)); - try { - graphsChannel.read(bb, offset); - } catch (IOException e) { - throw AnalysisError.shouldNotReachHere("Failed reading a graph from location: " + location, e); - } - return bb.array(); - } - - public boolean hasStrengthenedGraph(AnalysisMethod analysisMethod) { - EconomicMap methodData = getMethodData(analysisMethod); - return get(methodData, STRENGTHENED_GRAPH_TAG) != null; - } - - public void setStrengthenedGraph(AnalysisMethod analysisMethod) { - EconomicMap methodData = getMethodData(analysisMethod); - byte[] encodedAnalyzedGraph = readEncodedGraph(methodData, STRENGTHENED_GRAPH_TAG); - EncodedGraph analyzedGraph = (EncodedGraph) ObjectCopier.decode(imageLayerSnapshotUtil.getGraphDecoder(this, analysisMethod, universe.getSnippetReflection()), encodedAnalyzedGraph); - afterGraphDecodeHook(analyzedGraph); - analysisMethod.setAnalyzedGraph(analyzedGraph); - } - - @SuppressWarnings("unused") - protected void afterGraphDecodeHook(EncodedGraph encodedGraph) { - - } - - protected static int getId(String line) { - return Integer.parseInt(line.split(" = ")[1]); - } - - private EconomicMap getMethodData(AnalysisMethod analysisMethod) { - String name; - int id = analysisMethod.getId(); - if (methodIdToIdentifier.containsKey(id)) { - name = methodIdToIdentifier.get(id); - } else { - name = imageLayerSnapshotUtil.getMethodIdentifier(analysisMethod); - } - return getElementData(METHODS_TAG, name); - } - - private void loadField(FieldIdentifier fieldIdentifier, EconomicMap fieldData) { - AnalysisType declaringClass = getAnalysisType(Integer.parseInt(fieldIdentifier.tid)); - String className = get(fieldData, CLASS_NAME_TAG); - int id = get(fieldData, ID_TAG); - - Class clazz = className != null ? lookupBaseLayerTypeInHostVM(className) : declaringClass.getJavaClass(); - if (clazz == null) { - clazz = declaringClass.getJavaClass(); - } - - Field field; - try { - field = ReflectionUtil.lookupField(true, clazz, fieldIdentifier.name); - } catch (Throwable e) { - field = null; - } - - if (field == null && !(declaringClass.getWrapped() instanceof BaseLayerType)) { - boolean isStatic = get(fieldData, IS_STATIC_TAG); - if (isStatic) { - declaringClass.getStaticFields(); - } else { - declaringClass.getInstanceFields(true); - } - - if (fields.containsKey(id)) { - return; - } - } - - if (field == null) { - AnalysisType type = getAnalysisType(get(fieldData, FIELD_TYPE_TAG)); - BaseLayerField baseLayerField = getBaseLayerField(fieldIdentifier, fieldData, id, declaringClass.getWrapped(), type.getWrapped()); - universe.lookup(baseLayerField); - } else { - metaAccess.lookupJavaField(field); - } - } - - private BaseLayerField getBaseLayerField(int id) { - FieldIdentifier fieldIdentifier = fieldIdToIdentifier.get(id); - EconomicMap fieldsMap = get(jsonMap, FIELDS_TAG); - EconomicMap fieldData = get(get(fieldsMap, fieldIdentifier.tid), fieldIdentifier.name); - - BaseLayerType declaringClass = getBaseLayerType(Integer.parseInt(fieldIdentifier.tid)); - ResolvedJavaType type = getResolvedJavaType(get(fieldData, FIELD_TYPE_TAG)); - - return getBaseLayerField(fieldIdentifier, fieldData, id, declaringClass, type); - } - - private BaseLayerField getBaseLayerField(FieldIdentifier fieldIdentifier, EconomicMap fieldData, int id, ResolvedJavaType declaringClass, ResolvedJavaType type) { - return baseLayerFields.computeIfAbsent(id, - fid -> new BaseLayerField(id, fieldIdentifier.name, declaringClass, type, get(fieldData, IS_INTERNAL_TAG), get(fieldData, IS_SYNTHETIC_TAG), get(fieldData, MODIFIERS_TAG), - getAnnotations(fieldData))); - } - - public AnalysisField getAnalysisField(int fid) { - if (!fields.containsKey(fid)) { - FieldIdentifier fieldIdentifier = fieldIdToIdentifier.get(fid); - EconomicMap fieldsMap = get(jsonMap, FIELDS_TAG); - loadField(fieldIdentifier, get(get(fieldsMap, fieldIdentifier.tid), fieldIdentifier.name)); - } - - AnalysisField analysisField = fields.get(fid); - AnalysisError.guarantee(analysisField != null, "Field with id %d was not correctly loaded.", fid); - return analysisField; - } - - /** - * Returns the field id of the given field in the base layer if it exists. This makes the link - * between the base layer and the extension image as the id allows to set the flags of the - * fields in the extension image. - */ - public int lookupHostedFieldInBaseLayer(AnalysisField analysisField) { - return getBaseLayerFieldId(analysisField); - } - - private int getBaseLayerFieldId(AnalysisField analysisField) { - if (analysisField.wrapped instanceof BaseLayerField baseLayerField) { - return baseLayerField.getBaseLayerId(); - } - EconomicMap fieldData = getFieldData(analysisField); - if (fieldData == null) { - /* The field was not reachable in the base image */ - return -1; - } - return get(fieldData, ID_TAG); - } - - public void addBaseLayerField(AnalysisField analysisField) { - fields.putIfAbsent(analysisField.getId(), analysisField); - } - - public void initializeBaseLayerField(AnalysisField analysisField) { - EconomicMap fieldData = getFieldData(analysisField); - - assert fieldData != null : "The field should be in the base layer"; - Integer location = get(fieldData, LOCATION_TAG); - if (location != null) { - fieldLocations.put(analysisField, location); - } - - boolean isAccessed = get(fieldData, FIELD_ACCESSED_TAG); - boolean isRead = get(fieldData, FIELD_READ_TAG); - boolean isWritten = get(fieldData, FIELD_WRITTEN_TAG); - boolean isFolded = get(fieldData, FIELD_FOLDED_TAG); - - if (!analysisField.isStatic() && (isAccessed || isRead)) { - analysisField.getDeclaringClass().getInstanceFields(true); - } - registerFlag(isAccessed, true, () -> analysisField.registerAsAccessed(PERSISTED)); - registerFlag(isRead, true, () -> analysisField.registerAsRead(PERSISTED)); - registerFlag(isWritten, true, () -> analysisField.registerAsWritten(PERSISTED)); - registerFlag(isFolded, true, () -> analysisField.registerAsFolded(PERSISTED)); - } - - protected EconomicMap getFieldData(AnalysisField analysisField) { - int tid = analysisField.getDeclaringClass().getId(); - EconomicMap typeFieldsMap = getElementData(FIELDS_TAG, Integer.toString(tid)); - if (typeFieldsMap == null) { - /* The type has no reachable field */ - return null; - } - return get(typeFieldsMap, analysisField.getName()); - } - - private void registerFlag(boolean flag, boolean post, Runnable runnable) { - if (flag) { - if (universe.getBigbang() != null) { - if (post) { - universe.getBigbang().postTask(debug -> runnable.run()); - } else { - runnable.run(); - } - } else { - heapScannerTasks.add(new AnalysisFuture<>(runnable)); - } - } - } - - public void executeHeapScannerTasks() { - guarantee(universe.getHeapScanner() != null, "Those tasks should only be executed when the bigbang is not null."); - for (AnalysisFuture task : heapScannerTasks) { - task.ensureDone(); - } - } - - /** - * Get the {@link ImageHeapConstant} representation for a specific base layer constant id. If - * known, the parentReachableHostedObject will point to the corresponding constant in the - * underlying host VM, found by querying the parent object that made this constant reachable - * (see {@link ImageLayerLoader#getReachableHostedValue(ImageHeapConstant, int)}). - */ - protected ImageHeapConstant getOrCreateConstant(EconomicMap constantsMap, int id, JavaConstant parentReachableHostedObjectCandidate) { - if (constants.containsKey(id)) { - return constants.get(id); - } - EconomicMap baseLayerConstant = get(constantsMap, Integer.toString(id)); - if (baseLayerConstant == null) { - throw GraalError.shouldNotReachHere("The constant was not reachable in the base image"); - } - - int tid = get(baseLayerConstant, TID_TAG); - AnalysisType type = getAnalysisType(tid); - - String objectOffset = get(baseLayerConstant, OBJECT_OFFSET_TAG); - int identityHashCode = get(baseLayerConstant, IDENTITY_HASH_CODE_TAG); - - JavaConstant parentReachableHostedObject; - if (parentReachableHostedObjectCandidate == null) { - Integer parentConstantId = get(baseLayerConstant, PARENT_CONSTANT_ID_TAG); - if (parentConstantId != null) { - ImageHeapConstant parentConstant = getOrCreateConstant(parentConstantId); - int index = get(baseLayerConstant, PARENT_CONSTANT_INDEX_TAG); - parentReachableHostedObject = getReachableHostedValue(parentConstant, index); - } else { - parentReachableHostedObject = null; - } - } else { - parentReachableHostedObject = parentReachableHostedObjectCandidate; - } - - if (parentReachableHostedObject != null && !type.getJavaClass().equals(Class.class)) { - /* - * The hash codes of DynamicHubs need to be injected before they are used in a map, - * which happens right after their creation. The injection of their hash codes can be - * found in SVMHost#registerType. - * - * Also, for DynamicHub constants, the identity hash code persisted is the hash code of - * the Class object, which we do not want to inject in the DynamicHub. - */ - injectIdentityHashCode(hostedValuesProvider.asObject(Object.class, parentReachableHostedObject), identityHashCode); - } - String constantType = get(baseLayerConstant, CONSTANT_TYPE_TAG); - switch (constantType) { - case INSTANCE_TAG -> { - List> instanceData = get(baseLayerConstant, DATA_TAG); - JavaConstant foundHostedObject = lookupHostedObject(baseLayerConstant, type); - if (foundHostedObject != null && parentReachableHostedObject != null) { - Object foundObject = hostedValuesProvider.asObject(Object.class, foundHostedObject); - Object reachableObject = hostedValuesProvider.asObject(Object.class, parentReachableHostedObject); - AnalysisError.guarantee(foundObject == reachableObject, "Found discrepancy between recipe-found hosted value %s and parent-reachable hosted value %s.", foundObject, - reachableObject); - } - - addBaseLayerObject(id, objectOffset, () -> { - ImageHeapInstance imageHeapInstance = new ImageHeapInstance(type, foundHostedObject == null ? parentReachableHostedObject : foundHostedObject, identityHashCode, id); - if (instanceData != null) { - Object[] fieldValues = getReferencedValues(constantsMap, imageHeapInstance, instanceData, imageLayerSnapshotUtil.getRelinkedFields(type, metaAccess)); - imageHeapInstance.setFieldValues(fieldValues); - } - return imageHeapInstance; - }); - } - case ARRAY_TAG -> { - List> arrayData = get(baseLayerConstant, DATA_TAG); - addBaseLayerObject(id, objectOffset, () -> { - ImageHeapObjectArray imageHeapObjectArray = new ImageHeapObjectArray(type, null, arrayData.size(), identityHashCode, id); - Object[] elementsValues = getReferencedValues(constantsMap, imageHeapObjectArray, arrayData, Set.of()); - imageHeapObjectArray.setElementValues(elementsValues); - return imageHeapObjectArray; - }); - } - case PRIMITIVE_ARRAY_TAG -> { - List primitiveData = get(baseLayerConstant, DATA_TAG); - Object array = getArray(type.getComponentType().getJavaKind(), primitiveData); - addBaseLayerObject(id, objectOffset, () -> new ImageHeapPrimitiveArray(type, null, array, primitiveData.size(), identityHashCode, id)); - } - case RELOCATED_CONSTANT_TAG -> { - String key = get(baseLayerConstant, DATA_TAG); - addBaseLayerObject(id, objectOffset, () -> ImageHeapRelocatableConstant.create(type, key, id)); - } - default -> throw GraalError.shouldNotReachHere("Unknown constant type: " + constantType); - } - - return constants.get(id); - } - - /** - * Look up an object in current hosted VM based on the recipe serialized from the base layer. - */ - protected JavaConstant lookupHostedObject(EconomicMap baseLayerConstant, AnalysisType analysisType) { - boolean simulated = get(baseLayerConstant, SIMULATED_TAG); - if (!simulated) { - Class clazz = analysisType.getJavaClass(); - return lookupHostedObject(baseLayerConstant, clazz); - } - return null; - } - - @SuppressWarnings("unchecked") - protected JavaConstant lookupHostedObject(EconomicMap baseLayerConstant, Class clazz) { - if (clazz.equals(String.class)) { - String value = get(baseLayerConstant, VALUE_TAG); - if (value != null) { - Object object = value.intern(); - return hostedValuesProvider.forObject(object); - } - } else if (Enum.class.isAssignableFrom(clazz)) { - Enum enumValue = getEnumValue(baseLayerConstant); - return hostedValuesProvider.forObject(enumValue); - } - return null; - } - - @SuppressWarnings("unused") - protected void injectIdentityHashCode(Object object, Integer identityHashCode) { - /* The hash code can only be injected in the SVM context. */ - } - - @SuppressWarnings("unchecked") - private static Object getArray(JavaKind kind, Object listObject) { - return switch (kind) { - case Boolean -> getBooleans((List) listObject); - case Byte -> getBytes((List) listObject); - case Short -> getShorts((List) listObject); - case Char -> ((List) listObject).stream().mapToInt(i -> i).mapToObj(i -> Character.toString((char) i)).collect(Collectors.joining()).toCharArray(); - case Int -> ((List) listObject).stream().mapToInt(i -> i).toArray(); - case Long -> ((List) listObject).stream().mapToLong(Long::parseLong).toArray(); - case Float -> getFloats((List) listObject); - case Double -> ((List) listObject).stream().mapToDouble(Double::parseDouble).toArray(); - default -> throw new IllegalArgumentException("Unsupported kind: " + kind); - }; - } - - private static float[] getFloats(List listObject) { - float[] primitiveFloats = new float[listObject.size()]; - for (int i = 0; i < listObject.size(); ++i) { - primitiveFloats[i] = Float.parseFloat(listObject.get(i)); - } - return primitiveFloats; - } - - private static byte[] getBytes(List listObject) { - byte[] primitiveBytes = new byte[listObject.size()]; - for (int i = 0; i < listObject.size(); ++i) { - primitiveBytes[i] = (byte) (int) listObject.get(i); - } - return primitiveBytes; - } - - private static short[] getShorts(List listObject) { - short[] primitiveShorts = new short[listObject.size()]; - for (int i = 0; i < listObject.size(); ++i) { - primitiveShorts[i] = (short) (int) listObject.get(i); - } - return primitiveShorts; - } - - private static boolean[] getBooleans(List listObject) { - boolean[] primitiveBooleans = new boolean[listObject.size()]; - for (int i = 0; i < listObject.size(); ++i) { - primitiveBooleans[i] = listObject.get(i); - } - return primitiveBooleans; - } - - private Object[] getReferencedValues(EconomicMap constantsMap, ImageHeapConstant parentConstant, List> data, Set positionsToRelink) { - Object[] values = new Object[data.size()]; - for (int position = 0; position < data.size(); ++position) { - List constantData = data.get(position); - String constantKind = (String) constantData.get(0); - Object constantValue = constantData.get(1); - if (delegateProcessing(constantKind, constantValue, constantData, values, position)) { - continue; - } - if (constantKind.equals(OBJECT_TAG)) { - int constantId = (int) constantValue; - if (constantId >= 0) { - boolean relink = positionsToRelink.contains(position); - int finalPosition = position; - values[position] = new AnalysisFuture<>(() -> { - ensureHubInitialized(parentConstant); - - JavaConstant hostedConstant = relink ? getReachableHostedValue(parentConstant, finalPosition) : null; - ImageHeapConstant baseLayerConstant = getOrCreateConstant(constantsMap, constantId, hostedConstant); - values[finalPosition] = baseLayerConstant; - - ensureHubInitialized(baseLayerConstant); - - if (hostedConstant != null) { - addBaseLayerValueToImageHeap(baseLayerConstant, parentConstant, finalPosition); - } - - return baseLayerConstant; - }); - } else if (constantId == NULL_POINTER_CONSTANT) { - values[position] = JavaConstant.NULL_POINTER; - } else { - /* - * This constant is a field value or an object value that was not materialized - * in the base image. - */ - guarantee(constantId == NOT_MATERIALIZED_CONSTANT); - values[position] = new AnalysisFuture<>(() -> { - throw AnalysisError.shouldNotReachHere("This constant was not materialized in the base image."); - }); - } - } else { - JavaKind kind = JavaKind.fromTypeString(constantKind); - values[position] = getPrimitiveValue(kind, constantValue); - } - } - return values; - } - - /** - * Hook for subclasses to do their own processing. - */ - @SuppressWarnings("unused") - protected boolean delegateProcessing(String constantType, Object constantValue, List constantData, Object[] values, int i) { - return false; - } - - /** - * For a parent constant return the referenced field-position or array-element-index value - * corresponding to index. - */ - private JavaConstant getReachableHostedValue(ImageHeapConstant parentConstant, int index) { - if (parentConstant instanceof ImageHeapObjectArray array) { - return getHostedElementValue(array, index); - } else if (parentConstant instanceof ImageHeapInstance instance) { - AnalysisField field = getFieldFromIndex(instance, index); - return getHostedFieldValue(instance, field); - } else { - throw AnalysisError.shouldNotReachHere("unexpected constant: " + parentConstant); - } - } - - private static AnalysisField getFieldFromIndex(ImageHeapInstance instance, int i) { - return (AnalysisField) instance.getType().getInstanceFields(true)[i]; - } - - private JavaConstant getHostedElementValue(ImageHeapObjectArray array, int idx) { - JavaConstant hostedArray = array.getHostedObject(); - JavaConstant rawElementValue = null; - if (hostedArray != null) { - rawElementValue = hostedValuesProvider.readArrayElement(hostedArray, idx); - } - return rawElementValue; - } - - private JavaConstant getHostedFieldValue(ImageHeapInstance instance, AnalysisField field) { - ValueSupplier rawFieldValue; - try { - JavaConstant hostedInstance = instance.getHostedObject(); - AnalysisError.guarantee(hostedInstance != null); - rawFieldValue = universe.getHeapScanner().readHostedFieldValue(field, hostedInstance); - } catch (InternalError | TypeNotPresentException | LinkageError e) { - /* Ignore missing type errors. */ - return null; - } - return rawFieldValue.get(); - } - - public void addBaseLayerValueToImageHeap(ImageHeapConstant constant, ImageHeapConstant parentConstant, int i) { - if (parentConstant instanceof ImageHeapInstance imageHeapInstance) { - universe.getHeapScanner().registerBaseLayerValue(constant, getFieldFromIndex(imageHeapInstance, i)); - } else if (parentConstant instanceof ImageHeapObjectArray) { - universe.getHeapScanner().registerBaseLayerValue(constant, i); - } else { - throw AnalysisError.shouldNotReachHere("unexpected constant: " + constant); - } - } - - /** - * Ensures the DynamicHub is consistent with the base layer value. - */ - public void ensureHubInitialized(@SuppressWarnings("unused") ImageHeapConstant constant) { - /* DynamicHub only exists in SVM, so the method does not need to do anything here. */ - } - - @SuppressWarnings("unused") - public void rescanHub(AnalysisType type, Object hubObject) { - /* DynamicHub only exists in SVM, so the method does not need to do anything here. */ - } - - private static PrimitiveConstant getPrimitiveValue(JavaKind kind, Object value) { - return switch (kind) { - case Boolean -> JavaConstant.forBoolean((int) value != 0); - case Byte -> JavaConstant.forByte((byte) (int) value); - case Short -> JavaConstant.forShort((short) (int) value); - case Char -> JavaConstant.forChar((char) Integer.parseInt((String) value)); - case Int -> JavaConstant.forInt((int) value); - case Long -> JavaConstant.forLong(Long.parseLong((String) value)); - case Float -> JavaConstant.forFloat(Float.parseFloat((String) value)); - case Double -> JavaConstant.forDouble(getDouble(value)); - default -> throw AnalysisError.shouldNotReachHere("Unexpected kind: " + kind); - }; - } - - private static double getDouble(Object value) { - if (value instanceof Integer integer) { - guarantee(integer == 0); - return 0; - } - return Double.longBitsToDouble((long) value); - } - - private void addBaseLayerObject(int id, String objectOffset, Supplier imageHeapConstantSupplier) { - constants.computeIfAbsent(id, key -> { - ImageHeapConstant heapObj = imageHeapConstantSupplier.get(); - heapObj.markInBaseLayer(); - /* - * Packages are normally rescanned when the DynamicHub is initialized. However, since - * they are not relinked, the packages from the base layer will never be marked as - * reachable without doing so manually. - */ - if (heapObj.getType().getJavaClass().equals(Package.class)) { - universe.getHeapScanner().doScan(heapObj); - } - if (objectOffset != null) { - objectOffsets.put(heapObj.constantData.id, Long.parseLong(objectOffset)); - } - return heapObj; - }); - } - - private EconomicMap getElementData(String registry, String elementIdentifier) { - EconomicMap innerMap = get(jsonMap, registry); - if (innerMap == null) { - return null; - } - return get(innerMap, elementIdentifier); - } - - @SuppressWarnings("unchecked") - protected Enum getEnumValue(EconomicMap enumData) { - String className = get(enumData, ENUM_CLASS_TAG); - Class enumClass = lookupClass(false, className); - String name = get(enumData, ENUM_NAME_TAG); - /* asSubclass produces an "unchecked" warning */ - return Enum.valueOf(enumClass.asSubclass(Enum.class), name); - } - - public Class lookupClass(boolean optional, String className) { - return ReflectionUtil.lookupClass(optional, className); - } - - public static T get(EconomicMap innerMap, String elementIdentifier) { - return cast(innerMap.get(elementIdentifier)); - } - - private static T getValue(MapCursor mapCursor) { - return cast(mapCursor.getValue()); - } - - @SuppressWarnings("unchecked") - protected static T cast(Object object) { - return (T) object; - } - - public boolean hasValueForConstant(JavaConstant javaConstant) { - Object object = hostedValuesProvider.asObject(Object.class, javaConstant); - return hasValueForObject(object); - } - - @SuppressFBWarnings(value = "ES", justification = "Reference equality check needed to detect intern status") - protected boolean hasValueForObject(Object object) { - if (object instanceof String string) { - return stringToConstant.containsKey(string) && string.intern() == string; - } else if (object instanceof Enum) { - return enumToConstant.containsKey(object); - } - return false; - } - - public ImageHeapConstant getValueForConstant(JavaConstant javaConstant) { - Object object = hostedValuesProvider.asObject(Object.class, javaConstant); - return getValueForObject(object); - } - - protected ImageHeapConstant getValueForObject(Object object) { - if (object instanceof String string) { - int id = stringToConstant.get(string); - return getOrCreateConstant(id); - } else if (object instanceof Enum) { - int id = enumToConstant.get(object); - return getOrCreateConstant(id); - } - throw AnalysisError.shouldNotReachHere("The constant was not in the persisted heap."); - } - - public ImageHeapConstant getOrCreateConstant(int id) { - return getOrCreateConstant(get(jsonMap, CONSTANTS_TAG), id, null); - } - - public AnalysisMetaAccess getMetaAccess() { - return metaAccess; - } - - public void setMetaAccess(AnalysisMetaAccess metaAccess) { - this.metaAccess = metaAccess; - } - - public void setHostedValuesProvider(HostedValuesProvider hostedValuesProvider) { - this.hostedValuesProvider = hostedValuesProvider; - } - - public Set getRelinkedFields(AnalysisType type) { - return imageLayerSnapshotUtil.getRelinkedFields(type, metaAccess); - } - - public Long getObjectOffset(JavaConstant javaConstant) { - ImageHeapConstant imageHeapConstant = (ImageHeapConstant) javaConstant; - return objectOffsets.get(imageHeapConstant.constantData.id); - } - - public int getFieldLocation(AnalysisField field) { - return fieldLocations.get(field); - } - - public ImageHeapConstant getBaseLayerStaticPrimitiveFields() { - return getTaggedImageHeapConstant(STATIC_PRIMITIVE_FIELDS_TAG); - } - - public ImageHeapConstant getBaseLayerStaticObjectFields() { - return getTaggedImageHeapConstant(STATIC_OBJECT_FIELDS_TAG); - } - - private ImageHeapConstant getTaggedImageHeapConstant(String tag) { - int id = get(jsonMap, tag); - return getOrCreateConstant(id); - } - - public long getImageHeapSize() { - return imageHeapSize; - } - - public boolean hasDynamicHubIdentityHashCode(int tid) { - return typeToHubIdentityHashCode.containsKey(tid); - } - - public int getDynamicHubIdentityHashCode(int tid) { - return typeToHubIdentityHashCode.get(tid); - } -} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerSnapshotUtil.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerSnapshotUtil.java deleted file mode 100644 index 70e6e39fbd37..000000000000 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerSnapshotUtil.java +++ /dev/null @@ -1,435 +0,0 @@ -/* - * 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.graal.pointsto.heap; - -import java.io.IOException; -import java.lang.reflect.Executable; -import java.lang.reflect.Field; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.oracle.graal.pointsto.infrastructure.OriginalMethodProvider; -import com.oracle.graal.pointsto.meta.AnalysisField; -import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; -import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.meta.PointsToAnalysisField; -import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; -import com.oracle.graal.pointsto.meta.PointsToAnalysisType; -import com.oracle.graal.pointsto.util.AnalysisError; -import com.oracle.graal.pointsto.util.AnalysisFuture; - -import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; -import jdk.graal.compiler.nodes.EncodedGraph; -import jdk.graal.compiler.nodes.FieldLocationIdentity; -import jdk.graal.compiler.util.ObjectCopier; -import jdk.graal.compiler.util.ObjectCopierInputStream; -import jdk.graal.compiler.util.ObjectCopierOutputStream; - -public class ImageLayerSnapshotUtil { - public static final String FILE_NAME_PREFIX = "layer-snapshot-"; - public static final String FILE_EXTENSION = ".json"; - public static final String GRAPHS_FILE_NAME_PREFIX = "layer-snapshot-graphs-"; - public static final String GRAPHS_FILE_EXTENSION = ".big"; - - public static final String CONSTRUCTOR_NAME = ""; - public static final String CLASS_INIT_NAME = ""; - - public static final String PERSISTED = "persisted"; - - public static final int UNDEFINED_CONSTANT_ID = -1; - public static final int UNDEFINED_FIELD_INDEX = -1; - public static final int NULL_POINTER_CONSTANT = -1; - public static final int NOT_MATERIALIZED_CONSTANT = -2; - public static final String OBJECT_TAG = "A"; - public static final String METHOD_POINTER_TAG = "M"; - public static final String C_ENTRY_POINT_LITERAL_CODE_POINTER = "CONSTANT"; - public static final String TYPES_TAG = "types"; - public static final String METHODS_TAG = "methods"; - public static final String FIELDS_TAG = "fields"; - public static final String IS_INTERNAL_TAG = "is internal"; - public static final String IS_STATIC_TAG = "is static"; - public static final String FIELD_TYPE_TAG = "field type"; - public static final String CLASS_JAVA_NAME_TAG = "class java name"; - public static final String CAN_BE_STATICALLY_BOUND_TAG = "can be statically bound"; - public static final String IS_CONSTRUCTOR_TAG = "is constructor"; - public static final String IS_SYNTHETIC_TAG = "is synthetic"; - public static final String CODE_TAG = "code"; - public static final String CODE_SIZE_TAG = "code size"; - public static final String METHOD_HANDLE_INTRINSIC_TAG = "method handle intrinsic"; - public static final String IS_VIRTUAL_ROOT_METHOD = "is virtual root method"; - public static final String IS_DIRECT_ROOT_METHOD = "is direct root method"; - public static final String IS_INVOKED = "is invoked"; - public static final String IS_IMPLEMENTATION_INVOKED = "is implementation invoked"; - public static final String IS_INTRINSIC_METHOD = "is intrinsic method"; - public static final String ANNOTATIONS_TAG = "annotations"; - public static final String ANNOTATION_VALUES_TAG = "annotation values"; - public static final String IS_INSTANTIATED = "is instantiated"; - public static final String IS_UNSAFE_ALLOCATED = "is unsafe allocated"; - public static final String IS_REACHABLE = "is reachable"; - public static final String CLASS_NAME_TAG = "class name"; - public static final String MODIFIERS_TAG = "modifiers"; - public static final String POSITION_TAG = "position"; - public static final String IS_INTERFACE_TAG = "is interface"; - public static final String IS_ENUM_TAG = "is enum"; - public static final String IS_INITIALIZED_TAG = "is initialized"; - public static final String IS_LINKED_TAG = "is linked"; - public static final String SOURCE_FILE_NAME_TAG = "source file name"; - public static final String ENCLOSING_TYPE_TAG = "enclosing type"; - public static final String COMPONENT_TYPE_TAG = "component type"; - public static final String SUPER_CLASS_TAG = "super class"; - public static final String INTERFACES_TAG = "interfaces"; - public static final String WRAPPED_TYPE_TAG = "wrapped type"; - public static final String GENERATED_SERIALIZATION_TAG = "generated serialization"; - public static final String LAMBDA_TYPE_TAG = "lambda type"; - public static final String CAPTURING_CLASS_TAG = "capturing class"; - public static final String PROXY_TYPE_TAG = "proxy type"; - public static final String RAW_DECLARING_CLASS_TAG = "raw declaring class"; - public static final String RAW_TARGET_CONSTRUCTOR_CLASS_TAG = "raw target constructor class"; - public static final String INSTANCE_FIELDS_TAG = "instance fields"; - public static final String INSTANCE_FIELDS_WITH_SUPER_TAG = "instance fields with super"; - public static final String CONSTANTS_TAG = "constants"; - public static final String CONSTANTS_TO_RELINK_TAG = "constants to relink"; - public static final String TID_TAG = "tid"; - public static final String NAME_TAG = "name"; - public static final String ARGUMENTS_TAG = "arguments"; - public static final String ARGUMENT_IDS_TAG = "argument ids"; - public static final String RETURN_TYPE_TAG = "return type"; - public static final String IS_VAR_ARGS_TAG = "is varArg"; - public static final String WRAPPED_METHOD_TAG = "wrapped method"; - public static final String METHOD_TYPE_PARAMETERS_TAG = "method type parameters"; - public static final String METHOD_TYPE_RETURN_TAG = "method type return"; - public static final String FACTORY_TAG = "factory"; - public static final String C_ENTRY_POINT_CALL_STUB_METHOD_TAG = "CEntryPointCallStubMethod"; - public static final String REFLECTION_EXPAND_SIGNATURE_METHOD_TAG = "reflection expand signature method"; - public static final String JNI_JAVA_CALL_VARIANT_WRAPPER_METHOD_TAG = "jni java call variant wrapper method"; - public static final String OUTLINED_SB_TAG = "outlinedSB"; - public static final String ORIGINAL_METHOD_ID_TAG = "original method id"; - public static final String NOT_AS_PUBLISHED_TAG = "not as published"; - public static final String TARGET_CONSTRUCTOR_TAG = "target constructor"; - public static final String INSTANTIATED_TYPE_TAG = "instantiated type"; - public static final String WRAPPED_MEMBER_CLASS_TAG = "wrapped member class"; - public static final String WRAPPED_MEMBER_NAME_TAG = "wrapped member name"; - public static final String WRAPPED_MEMBER_ARGUMENTS_TAG = "wrapped member arguments"; - public static final String THROW_ALLOCATED_OBJECT_TAG = "throw allocated object"; - public static final String IDENTITY_HASH_CODE_TAG = "identityHashCode"; - public static final String PARENT_CONSTANT_ID_TAG = "parent constant id"; - public static final String PARENT_CONSTANT_INDEX_TAG = "parent constant index"; - public static final String HUB_IDENTITY_HASH_CODE_TAG = "hub identityHashCode"; - public static final String IS_INITIALIZED_AT_BUILD_TIME_TAG = "is initialized at build time"; - public static final String IS_NO_INITIALIZER_NO_TRACKING_TAG = "in no initializer no tracking"; - public static final String IS_INITIALIZED_NO_TRACKING_TAG = "is initialized no tracking"; - public static final String IS_FAILED_NO_TRACKING_TAG = "is failed no tracking"; - public static final String INFO_IS_INITIALIZED_TAG = "info is initialized"; - public static final String INFO_IS_IN_ERROR_STATE_TAG = "info is in error state"; - public static final String INFO_IS_LINKED_TAG = "info is linked"; - public static final String INFO_HAS_INITIALIZER_TAG = "info has initializer"; - public static final String INFO_IS_BUILD_TIME_INITIALIZED_TAG = "info is build time initialized"; - public static final String INFO_IS_TRACKED_TAG = "info is tracked"; - public static final String INFO_CLASS_INITIALIZER_TAG = "info class initializer"; - public static final String ID_TAG = "id"; - public static final String ANALYSIS_PARSED_GRAPH_TAG = "analysis parsed graph"; - public static final String STRENGTHENED_GRAPH_TAG = "strengthened graph"; - public static final String INTRINSIC_TAG = "intrinsic"; - public static final String CONSTANT_TYPE_TAG = "constant type"; - public static final String DATA_TAG = "data"; - public static final String INSTANCE_TAG = "instance"; - public static final String ARRAY_TAG = "array"; - public static final String PRIMITIVE_ARRAY_TAG = "primitive array"; - public static final String RELOCATED_CONSTANT_TAG = "relocation constant"; - public static final String FIELD_CHECK_TAG = "field check"; - public static final String FIELD_ACCESSED_TAG = "accessed"; - public static final String FIELD_READ_TAG = "read"; - public static final String FIELD_WRITTEN_TAG = "written"; - public static final String FIELD_FOLDED_TAG = "folded"; - public static final String LOCATION_TAG = "location"; - public static final String NEXT_TYPE_ID_TAG = "next type id"; - public static final String NEXT_METHOD_ID_TAG = "next method id"; - public static final String NEXT_FIELD_ID_TAG = "next field id"; - public static final String NEXT_CONSTANT_ID_TAG = "next constant id"; - public static final String IMAGE_HEAP_SIZE_TAG = "image heap size"; - public static final String VALUE_TAG = "value"; - public static final String ENUM_CLASS_TAG = "enum class"; - public static final String ENUM_NAME_TAG = "enum name"; - public static final String CLASS_ID_TAG = "class id"; - public static final String SIMULATED_TAG = "simulated"; - public static final String OBJECT_OFFSET_TAG = "object offset"; - public static final String STATIC_PRIMITIVE_FIELDS_TAG = "static primitive fields"; - public static final String STATIC_OBJECT_FIELDS_TAG = "static object fields"; - public static final String IMAGE_SINGLETON_KEYS = "image singleton keys"; - public static final String IMAGE_SINGLETON_OBJECTS = "image singleton objects"; - - protected final List externalValueFields; - /** This needs to be initialized after analysis, as some fields are not available before. */ - protected Map externalValues; - - public ImageLayerSnapshotUtil() { - try { - this.externalValueFields = ObjectCopier.getExternalValueFields(); - } catch (IOException e) { - throw AnalysisError.shouldNotReachHere("Unexpected exception when creating external value fields list", e); - } - } - - /** - * Compute and cache the final {@code externalValues} map in - * {@link ImageLayerSnapshotUtil#externalValues} to avoid computing it for each graph. - *

      - * A single {@code ObjectCopier.Encoder} instance could alternatively be used for all graphs, - * but it would then be impossible to process multiple graphs concurrently. - */ - public void initializeExternalValues() { - assert externalValues == null : "The external values should be computed only once."; - externalValues = ObjectCopier.Encoder.gatherExternalValues(externalValueFields); - } - - public static String snapshotFileName(String imageName) { - return FILE_NAME_PREFIX + imageName + FILE_EXTENSION; - } - - public static String snapshotGraphsFileName(String imageName) { - return GRAPHS_FILE_NAME_PREFIX + imageName + GRAPHS_FILE_EXTENSION; - } - - public String getTypeIdentifier(AnalysisType type) { - String javaName = type.toJavaName(true); - return addModuleName(javaName, type.getJavaClass().getModule().getName()); - } - - public String getMethodIdentifier(AnalysisMethod method) { - AnalysisType declaringClass = method.getDeclaringClass(); - Executable originalMethod = OriginalMethodProvider.getJavaMethod(method); - String moduleName = declaringClass.getJavaClass().getModule().getName(); - if (originalMethod != null) { - return addModuleName(originalMethod.toString(), moduleName); - } - return addModuleName(getQualifiedName(method), moduleName); - } - - protected static String addModuleName(String elementName, String moduleName) { - return moduleName + ":" + elementName; - } - - protected static String getQualifiedName(AnalysisMethod method) { - return method.getSignature().getReturnType().toJavaName(true) + " " + method.getQualifiedName(); - } - - /** - * Get all the field indexes that should be relinked using the hosted value of a constant from - * the given type. - */ - @SuppressWarnings("unused") - public Set getRelinkedFields(AnalysisType type, AnalysisMetaAccess metaAccess) { - return Set.of(); - } - - public GraphEncoder getGraphEncoder(ImageLayerWriter imageLayerWriter) { - return new GraphEncoder(externalValues, imageLayerWriter); - } - - @SuppressWarnings("unused") - public GraphDecoder getGraphDecoder(ImageLayerLoader imageLayerLoader, AnalysisMethod analysisMethod, SnippetReflectionProvider snippetReflectionProvider) { - return new GraphDecoder(EncodedGraph.class.getClassLoader(), imageLayerLoader, analysisMethod); - } - - public static class GraphEncoder extends ObjectCopier.Encoder { - @SuppressWarnings("this-escape") - public GraphEncoder(Map externalValues, ImageLayerWriter imageLayerWriter) { - super(externalValues); - addBuiltin(new ImageHeapConstantBuiltIn(imageLayerWriter, null)); - addBuiltin(new AnalysisTypeBuiltIn(imageLayerWriter, null)); - addBuiltin(new AnalysisMethodBuiltIn(imageLayerWriter, null, null)); - addBuiltin(new AnalysisFieldBuiltIn(imageLayerWriter, null)); - addBuiltin(new FieldLocationIdentityBuiltIn(imageLayerWriter, null)); - } - } - - public static class GraphDecoder extends ObjectCopier.Decoder { - private final ImageLayerLoader imageLayerLoader; - - @SuppressWarnings("this-escape") - public GraphDecoder(ClassLoader classLoader, ImageLayerLoader imageLayerLoader, AnalysisMethod analysisMethod) { - super(classLoader); - this.imageLayerLoader = imageLayerLoader; - addBuiltin(new ImageHeapConstantBuiltIn(null, imageLayerLoader)); - addBuiltin(new AnalysisTypeBuiltIn(null, imageLayerLoader)); - addBuiltin(new AnalysisMethodBuiltIn(null, imageLayerLoader, analysisMethod)); - addBuiltin(new AnalysisFieldBuiltIn(null, imageLayerLoader)); - addBuiltin(new FieldLocationIdentityBuiltIn(null, imageLayerLoader)); - } - - @Override - public Class loadClass(String className) { - return imageLayerLoader.lookupClass(false, className); - } - } - - public static class ImageHeapConstantBuiltIn extends ObjectCopier.Builtin { - private final ImageLayerWriter imageLayerWriter; - private final ImageLayerLoader imageLayerLoader; - - protected ImageHeapConstantBuiltIn(ImageLayerWriter imageLayerWriter, ImageLayerLoader imageLayerLoader) { - super(ImageHeapConstant.class, ImageHeapInstance.class, ImageHeapObjectArray.class, ImageHeapPrimitiveArray.class); - this.imageLayerWriter = imageLayerWriter; - this.imageLayerLoader = imageLayerLoader; - } - - @Override - public void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { - ImageHeapConstant imageHeapConstant = (ImageHeapConstant) obj; - imageLayerWriter.elementsToPersist.add(new AnalysisFuture<>(() -> imageLayerWriter.persistConstant(UNDEFINED_CONSTANT_ID, UNDEFINED_FIELD_INDEX, imageHeapConstant))); - stream.writePackedUnsignedInt(imageHeapConstant.getConstantData().id); - } - - @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { - int id = stream.readPackedUnsignedInt(); - return imageLayerLoader.getOrCreateConstant(id); - } - } - - public static class AnalysisTypeBuiltIn extends ObjectCopier.Builtin { - private final ImageLayerWriter imageLayerWriter; - private final ImageLayerLoader imageLayerLoader; - - protected AnalysisTypeBuiltIn(ImageLayerWriter imageLayerWriter, ImageLayerLoader imageLayerLoader) { - super(AnalysisType.class, PointsToAnalysisType.class); - this.imageLayerWriter = imageLayerWriter; - this.imageLayerLoader = imageLayerLoader; - } - - @Override - public void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { - AnalysisType type = (AnalysisType) obj; - imageLayerWriter.persistType(type); - stream.writePackedUnsignedInt(type.getId()); - } - - @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { - int id = stream.readPackedUnsignedInt(); - return imageLayerLoader.getAnalysisType(id); - } - } - - public static class AnalysisMethodBuiltIn extends ObjectCopier.Builtin { - private final ImageLayerWriter imageLayerWriter; - private final ImageLayerLoader imageLayerLoader; - private final AnalysisMethod analysisMethod; - - protected AnalysisMethodBuiltIn(ImageLayerWriter imageLayerWriter, ImageLayerLoader imageLayerLoader, AnalysisMethod analysisMethod) { - super(AnalysisMethod.class, PointsToAnalysisMethod.class); - this.imageLayerWriter = imageLayerWriter; - this.imageLayerLoader = imageLayerLoader; - this.analysisMethod = analysisMethod; - } - - @Override - public void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { - AnalysisMethod method = (AnalysisMethod) obj; - AnalysisType declaringClass = method.getDeclaringClass(); - imageLayerWriter.elementsToPersist.add(new AnalysisFuture<>(() -> { - imageLayerWriter.persistAnalysisParsedGraph(method); - imageLayerWriter.persistMethod(method); - })); - for (AnalysisType parameter : method.toParameterList()) { - imageLayerWriter.persistType(parameter); - } - imageLayerWriter.persistType(declaringClass); - stream.writePackedUnsignedInt(method.getId()); - } - - @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { - int id = stream.readPackedUnsignedInt(); - if (id == analysisMethod.getId()) { - return analysisMethod; - } - return imageLayerLoader.getAnalysisMethod(id); - } - } - - public static class AnalysisFieldBuiltIn extends ObjectCopier.Builtin { - private final ImageLayerWriter imageLayerWriter; - private final ImageLayerLoader imageLayerLoader; - - protected AnalysisFieldBuiltIn(ImageLayerWriter imageLayerWriter, ImageLayerLoader imageLayerLoader) { - super(AnalysisField.class, PointsToAnalysisField.class); - this.imageLayerWriter = imageLayerWriter; - this.imageLayerLoader = imageLayerLoader; - } - - @Override - public void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { - AnalysisField field = (AnalysisField) obj; - int id = encodeField(field, imageLayerWriter); - stream.writePackedUnsignedInt(id); - } - - @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { - int id = stream.readPackedUnsignedInt(); - return decodeField(imageLayerLoader, id); - } - } - - public static class FieldLocationIdentityBuiltIn extends ObjectCopier.Builtin { - private final ImageLayerWriter imageLayerWriter; - private final ImageLayerLoader imageLayerLoader; - - protected FieldLocationIdentityBuiltIn(ImageLayerWriter imageLayerWriter, ImageLayerLoader imageLayerLoader) { - super(FieldLocationIdentity.class); - this.imageLayerWriter = imageLayerWriter; - this.imageLayerLoader = imageLayerLoader; - } - - @Override - public void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { - FieldLocationIdentity fieldLocationIdentity = (FieldLocationIdentity) obj; - AnalysisField field = (AnalysisField) fieldLocationIdentity.getField(); - int id = encodeField(field, imageLayerWriter); - stream.writePackedUnsignedInt(id); - } - - @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { - int id = stream.readPackedUnsignedInt(); - return new FieldLocationIdentity(decodeField(imageLayerLoader, id)); - } - } - - private static int encodeField(AnalysisField field, ImageLayerWriter imageLayerWriter) { - String declaringClassId = String.valueOf(field.getDeclaringClass().getId()); - if (!imageLayerWriter.fieldsMap.containsKey(declaringClassId) || !imageLayerWriter.fieldsMap.get(declaringClassId).containsKey(field.getName())) { - imageLayerWriter.persistField(field); - } - return field.getId(); - } - - private static AnalysisField decodeField(ImageLayerLoader imageLayerLoader, int id) { - return imageLayerLoader.getAnalysisField(id); - } -} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java deleted file mode 100644 index 0844e76d51b7..000000000000 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java +++ /dev/null @@ -1,719 +0,0 @@ -/* - * 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.graal.pointsto.heap; - -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ANALYSIS_PARSED_GRAPH_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ANNOTATIONS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ARGUMENTS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ARGUMENT_IDS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ARRAY_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CAN_BE_STATICALLY_BOUND_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CLASS_JAVA_NAME_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CLASS_NAME_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CODE_SIZE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CODE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.COMPONENT_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CONSTANTS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CONSTANTS_TO_RELINK_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CONSTANT_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.DATA_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ENCLOSING_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ENUM_CLASS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ENUM_NAME_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FIELDS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FIELD_ACCESSED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FIELD_FOLDED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FIELD_READ_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FIELD_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FIELD_WRITTEN_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IDENTITY_HASH_CODE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IMAGE_HEAP_SIZE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INSTANCE_FIELDS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INSTANCE_FIELDS_WITH_SUPER_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INSTANCE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INTERFACES_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INTRINSIC_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_CONSTRUCTOR_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_DIRECT_ROOT_METHOD; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_ENUM_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_IMPLEMENTATION_INVOKED; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INITIALIZED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INSTANTIATED; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INTERFACE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INTERNAL_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INTRINSIC_METHOD; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INVOKED; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_LINKED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_REACHABLE; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_STATIC_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_SYNTHETIC_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_UNSAFE_ALLOCATED; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_VAR_ARGS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_VIRTUAL_ROOT_METHOD; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.METHODS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.METHOD_HANDLE_INTRINSIC_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.MODIFIERS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NAME_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NEXT_CONSTANT_ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NEXT_FIELD_ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NEXT_METHOD_ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NEXT_TYPE_ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NOT_MATERIALIZED_CONSTANT; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NULL_POINTER_CONSTANT; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.OBJECT_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.PARENT_CONSTANT_ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.PARENT_CONSTANT_INDEX_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.POSITION_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.PRIMITIVE_ARRAY_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.RELOCATED_CONSTANT_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.RETURN_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.SIMULATED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.SOURCE_FILE_NAME_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.STRENGTHENED_GRAPH_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.SUPER_CLASS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.TID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.TYPES_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.UNDEFINED_CONSTANT_ID; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.UNDEFINED_FIELD_INDEX; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.VALUE_TAG; - -import java.io.IOException; -import java.io.PrintWriter; -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Executable; -import java.lang.reflect.Field; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.IntStream; - -import org.graalvm.collections.EconomicMap; -import org.graalvm.nativeimage.AnnotationAccess; - -import com.oracle.graal.pointsto.BigBang; -import com.oracle.graal.pointsto.flow.AnalysisParsedGraph; -import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; -import com.oracle.graal.pointsto.meta.AnalysisField; -import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.meta.AnalysisUniverse; -import com.oracle.graal.pointsto.util.AnalysisError; -import com.oracle.graal.pointsto.util.AnalysisFuture; -import com.oracle.svm.util.FileDumpingUtil; - -import jdk.graal.compiler.debug.GraalError; -import jdk.graal.compiler.java.LambdaUtils; -import jdk.graal.compiler.nodes.EncodedGraph; -import jdk.graal.compiler.nodes.spi.IdentityHashCodeProvider; -import jdk.graal.compiler.util.ObjectCopier; -import jdk.graal.compiler.util.json.JsonWriter; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.MethodHandleAccessProvider.IntrinsicMethod; -import jdk.vm.ci.meta.PrimitiveConstant; -import jdk.vm.ci.meta.ResolvedJavaField; - -public class ImageLayerWriter { - protected ImageLayerSnapshotUtil imageLayerSnapshotUtil; - private ImageLayerWriterHelper imageLayerWriterHelper; - private ImageHeap imageHeap; - protected AnalysisUniverse aUniverse; - private IdentityHashMap internedStringsIdentityMap; - - protected final EconomicMap jsonMap; - protected final List constantsToRelink; - private final Set persistedTypeIds; - private final Set persistedMethodIds; - protected final Map> typesMap; - protected final Map> methodsMap; - protected final Map> fieldsMap; - private final Map> constantsMap; - private FileInfo fileInfo; - private GraphsOutput graphsOutput; - private final boolean useSharedLayerGraphs; - private final boolean useSharedLayerStrengthenedGraphs; - - protected final Set> elementsToPersist = ConcurrentHashMap.newKeySet(); - - private record FileInfo(Path layerFilePath, String fileName, String suffix) { - } - - private static class GraphsOutput { - private final Path path; - private final Path tempPath; - private final FileChannel tempChannel; - - private final AtomicLong currentOffset = new AtomicLong(0); - - GraphsOutput(Path path, String fileName, String suffix) { - this.path = path; - this.tempPath = FileDumpingUtil.createTempFile(path.getParent(), fileName, suffix); - try { - this.tempChannel = FileChannel.open(this.tempPath, EnumSet.of(StandardOpenOption.WRITE)); - } catch (IOException e) { - throw GraalError.shouldNotReachHere(e, "Error opening temporary graphs file."); - } - } - - String add(byte[] encodedGraph) { - long offset = currentOffset.getAndAdd(encodedGraph.length); - try { - tempChannel.write(ByteBuffer.wrap(encodedGraph), offset); - } catch (Exception e) { - throw GraalError.shouldNotReachHere(e, "Error during graphs file dumping."); - } - return new StringBuilder("@").append(offset).append("[").append(encodedGraph.length).append("]").toString(); - } - - void finish() { - try { - tempChannel.close(); - FileDumpingUtil.moveTryAtomically(tempPath, path); - } catch (Exception e) { - throw GraalError.shouldNotReachHere(e, "Error during graphs file dumping."); - } - } - } - - public ImageLayerWriter() { - this(true); - } - - public ImageLayerWriter(boolean useSharedLayerGraphs) { - this.useSharedLayerGraphs = useSharedLayerGraphs; - this.useSharedLayerStrengthenedGraphs = false; - this.jsonMap = EconomicMap.create(); - this.constantsToRelink = new ArrayList<>(); - this.persistedTypeIds = ConcurrentHashMap.newKeySet(); - this.persistedMethodIds = ConcurrentHashMap.newKeySet(); - this.typesMap = new ConcurrentHashMap<>(); - this.methodsMap = new ConcurrentHashMap<>(); - this.fieldsMap = new ConcurrentHashMap<>(); - this.constantsMap = new ConcurrentHashMap<>(); - } - - public void setImageLayerSnapshotUtil(ImageLayerSnapshotUtil imageLayerSnapshotUtil) { - this.imageLayerSnapshotUtil = imageLayerSnapshotUtil; - } - - public void setInternedStringsIdentityMap(IdentityHashMap map) { - this.internedStringsIdentityMap = map; - } - - public void setImageHeap(ImageHeap heap) { - this.imageHeap = heap; - } - - public void setImageLayerWriterHelper(ImageLayerWriterHelper imageLayerWriterHelper) { - this.imageLayerWriterHelper = imageLayerWriterHelper; - } - - public void setSnapshotFileInfo(Path layerSnapshotPath, String fileName, String suffix) { - fileInfo = new FileInfo(layerSnapshotPath, fileName, suffix); - } - - public void openGraphsOutput(Path layerGraphsPath, String fileName, String suffix) { - AnalysisError.guarantee(graphsOutput == null, "Graphs file has already been opened"); - graphsOutput = new GraphsOutput(layerGraphsPath, fileName, suffix); - } - - public void setAnalysisUniverse(AnalysisUniverse aUniverse) { - this.aUniverse = aUniverse; - } - - public void dumpFiles() { - graphsOutput.finish(); - - FileDumpingUtil.dumpFile(fileInfo.layerFilePath, fileInfo.fileName, fileInfo.suffix, outputStream -> { - try (JsonWriter jw = new JsonWriter(new PrintWriter(outputStream))) { - jw.print(jsonMap); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } - - public void initializeExternalValues() { - imageLayerSnapshotUtil.initializeExternalValues(); - } - - public void persistImageHeapSize(long imageHeapSize) { - jsonMap.put(IMAGE_HEAP_SIZE_TAG, String.valueOf(imageHeapSize)); - } - - public void persistAnalysisInfo() { - persistHook(); - - jsonMap.put(NEXT_TYPE_ID_TAG, aUniverse.getNextTypeId()); - jsonMap.put(NEXT_METHOD_ID_TAG, aUniverse.getNextMethodId()); - jsonMap.put(NEXT_FIELD_ID_TAG, aUniverse.getNextFieldId()); - jsonMap.put(NEXT_CONSTANT_ID_TAG, ImageHeapConstant.getCurrentId()); - - for (AnalysisType type : aUniverse.getTypes().stream().filter(AnalysisType::isTrackedAcrossLayers).toList()) { - checkTypeStability(type); - persistType(type); - } - jsonMap.put(TYPES_TAG, typesMap); - - for (AnalysisMethod method : aUniverse.getMethods().stream().filter(AnalysisMethod::isTrackedAcrossLayers).toList()) { - persistMethod(method); - } - jsonMap.put(METHODS_TAG, methodsMap); - - for (AnalysisField field : aUniverse.getFields().stream().filter(AnalysisField::isTrackedAcrossLayers).toList()) { - persistField(field); - } - jsonMap.put(FIELDS_TAG, fieldsMap); - - for (Map.Entry> entry : imageHeap.getReachableObjects().entrySet()) { - for (ImageHeapConstant imageHeapConstant : entry.getValue()) { - persistConstant(UNDEFINED_CONSTANT_ID, UNDEFINED_FIELD_INDEX, imageHeapConstant); - } - } - for (AnalysisFuture task : elementsToPersist) { - task.ensureDone(); - } - jsonMap.put(CONSTANTS_TAG, constantsMap); - jsonMap.put(CONSTANTS_TO_RELINK_TAG, constantsToRelink); - } - - private void persistAnnotations(AnnotatedElement annotatedElement, EconomicMap typeMap) { - Class[] annotationTypes = AnnotationAccess.getAnnotationTypes(annotatedElement); - persistAnnotations(annotatedElement, typeMap, annotationTypes); - } - - @SuppressWarnings("unused") - protected void persistAnnotations(AnnotatedElement annotatedElement, EconomicMap typeMap, Class[] annotationTypes) { - typeMap.put(ANNOTATIONS_TAG, Arrays.stream(annotationTypes).map(Class::getName).toList()); - } - - /** - * A hook used to persist more general information about the base layer not accessible in - * pointsto. - */ - @SuppressWarnings("unused") - protected void persistHook() { - - } - - public boolean isTypePersisted(AnalysisType type) { - return persistedTypeIds.contains(type.getId()); - } - - protected void persistType(AnalysisType type) { - if (!persistedTypeIds.add(type.getId())) { - return; - } - String typeIdentifier = imageLayerSnapshotUtil.getTypeIdentifier(type); - AnalysisType superclass = type.getSuperclass(); - if (superclass != null) { - /* - * Some persisted types are not reachable. In this case, the super class has to be - * persisted manually as well. - */ - persistType(superclass); - } - - for (AnalysisType interfaceType : type.getInterfaces()) { - /* - * Some persisted types are not reachable. In this case, the interfaces have to be - * persisted manually as well. - */ - persistType(interfaceType); - } - - EconomicMap typeMap = EconomicMap.create(); - - persistType(type, typeMap); - - if (typesMap.containsKey(typeIdentifier)) { - throw GraalError.shouldNotReachHere("The type identifier should be unique, but " + typeIdentifier + " got added twice."); - } - typesMap.put(typeIdentifier, typeMap); - } - - protected void persistType(AnalysisType type, EconomicMap typeMap) { - typeMap.put(ID_TAG, type.getId()); - - List fields = new ArrayList<>(); - for (ResolvedJavaField field : type.getInstanceFields(true)) { - fields.add(((AnalysisField) field).getId()); - } - typeMap.put(FIELDS_TAG, fields); - typeMap.put(CLASS_JAVA_NAME_TAG, type.toJavaName()); - typeMap.put(CLASS_NAME_TAG, type.getName()); - typeMap.put(MODIFIERS_TAG, type.getModifiers()); - typeMap.put(IS_INTERFACE_TAG, type.isInterface()); - typeMap.put(IS_ENUM_TAG, type.isEnum()); - typeMap.put(IS_INITIALIZED_TAG, type.isInitialized()); - typeMap.put(IS_LINKED_TAG, type.isLinked()); - typeMap.put(SOURCE_FILE_NAME_TAG, type.getSourceFileName()); - try { - AnalysisType enclosingType = type.getEnclosingType(); - if (enclosingType != null) { - typeMap.put(ENCLOSING_TYPE_TAG, enclosingType.getId()); - } - } catch (AnalysisError.TypeNotFoundError e) { - /* - * GR-59571: The enclosing type is not automatically created when the inner type is - * created. If the enclosing type is missing, it is ignored for now. This try/catch - * block could be removed after the trackAcrossLayers is fully implemented. - */ - } - if (type.isArray()) { - typeMap.put(COMPONENT_TYPE_TAG, type.getComponentType().getId()); - } - if (type.getSuperclass() != null) { - typeMap.put(SUPER_CLASS_TAG, type.getSuperclass().getId()); - } - typeMap.put(INTERFACES_TAG, Arrays.stream(type.getInterfaces()).map(AnalysisType::getId).toList()); - typeMap.put(INSTANCE_FIELDS_TAG, Arrays.stream(type.getInstanceFields(false)).map(field -> ((AnalysisField) field).getId()).toList()); - typeMap.put(INSTANCE_FIELDS_WITH_SUPER_TAG, Arrays.stream(type.getInstanceFields(true)).map(field -> ((AnalysisField) field).getId()).toList()); - persistAnnotations(type, typeMap); - - typeMap.put(IS_INSTANTIATED, type.isInstantiated()); - typeMap.put(IS_UNSAFE_ALLOCATED, type.isUnsafeAllocated()); - typeMap.put(IS_REACHABLE, type.isReachable()); - - imageLayerWriterHelper.persistType(type, typeMap); - } - - /** - * Some types can have an unstable name between two different image builds. To avoid producing - * wrong results, a warning should be printed if such types exist in the resulting image. - */ - @SuppressWarnings("unused") - public void checkTypeStability(AnalysisType type) { - /* Do not need to check anything here */ - } - - public void persistMethod(AnalysisMethod method) { - if (!persistedMethodIds.add(method.getId())) { - return; - } - EconomicMap methodMap = getMethodMap(method); - persistMethod(method, methodMap); - } - - protected void persistMethod(AnalysisMethod method, EconomicMap methodMap) { - Executable executable = method.getJavaMethod(); - - if (methodMap.containsKey(ID_TAG)) { - throw GraalError.shouldNotReachHere("The method identifier should be unique, but " + imageLayerSnapshotUtil.getMethodIdentifier(method) + " got added twice."); - } - if (executable != null) { - methodMap.put(ARGUMENTS_TAG, Arrays.stream(executable.getParameterTypes()).map(Class::getName).toList()); - methodMap.put(CLASS_NAME_TAG, executable.getDeclaringClass().getName()); - } - - persistType(method.getSignature().getReturnType()); - - methodMap.put(TID_TAG, method.getDeclaringClass().getId()); - methodMap.put(ARGUMENT_IDS_TAG, method.getSignature().toParameterList(null).stream().map(AnalysisType::getId).toList()); - methodMap.put(ID_TAG, method.getId()); - methodMap.put(NAME_TAG, method.getName()); - methodMap.put(RETURN_TYPE_TAG, method.getSignature().getReturnType().getId()); - methodMap.put(IS_VAR_ARGS_TAG, method.isVarArgs()); - methodMap.put(CAN_BE_STATICALLY_BOUND_TAG, method.canBeStaticallyBound()); - methodMap.put(MODIFIERS_TAG, method.getModifiers()); - methodMap.put(IS_CONSTRUCTOR_TAG, method.isConstructor()); - methodMap.put(IS_SYNTHETIC_TAG, method.isSynthetic()); - byte[] code = method.getCode(); - if (code != null) { - methodMap.put(CODE_TAG, getString(JavaKind.Byte, method.getCode())); - } - methodMap.put(CODE_SIZE_TAG, method.getCodeSize()); - IntrinsicMethod intrinsicMethod = aUniverse.getBigbang().getConstantReflectionProvider().getMethodHandleAccess().lookupMethodHandleIntrinsic(method); - if (intrinsicMethod != null) { - methodMap.put(METHOD_HANDLE_INTRINSIC_TAG, intrinsicMethod.name()); - } - persistAnnotations(method, methodMap); - - methodMap.put(IS_VIRTUAL_ROOT_METHOD, method.isVirtualRootMethod()); - methodMap.put(IS_DIRECT_ROOT_METHOD, method.isDirectRootMethod()); - methodMap.put(IS_INVOKED, method.isSimplyInvoked()); - methodMap.put(IS_IMPLEMENTATION_INVOKED, method.isSimplyImplementationInvoked()); - methodMap.put(IS_INTRINSIC_METHOD, method.isIntrinsicMethod()); - - imageLayerWriterHelper.persistMethod(method, methodMap); - } - - public boolean isMethodPersisted(AnalysisMethod method) { - String name = imageLayerSnapshotUtil.getMethodIdentifier(method); - return methodsMap.containsKey(name); - } - - public void persistMethodGraphs() { - for (AnalysisMethod method : aUniverse.getMethods()) { - if (method.isTrackedAcrossLayers()) { - persistAnalysisParsedGraph(method); - } - } - } - - public void persistAnalysisParsedGraph(AnalysisMethod method) { - EconomicMap methodMap = getMethodMap(method); - - Object analyzedGraph = method.getGraph(); - if (analyzedGraph instanceof AnalysisParsedGraph analysisParsedGraph) { - if (!methodMap.containsKey(INTRINSIC_TAG)) { - if (!persistGraph(method, analysisParsedGraph.getEncodedGraph(), methodMap, ANALYSIS_PARSED_GRAPH_TAG)) { - return; - } - methodMap.put(INTRINSIC_TAG, analysisParsedGraph.isIntrinsic()); - } - } - } - - public void persistMethodStrengthenedGraph(AnalysisMethod method) { - if (!useSharedLayerStrengthenedGraphs) { - return; - } - - EconomicMap methodMap = getMethodMap(method); - - if (!methodMap.containsKey(STRENGTHENED_GRAPH_TAG)) { - EncodedGraph analyzedGraph = method.getAnalyzedGraph(); - persistGraph(method, analyzedGraph, methodMap, STRENGTHENED_GRAPH_TAG); - } - } - - private boolean persistGraph(AnalysisMethod method, EncodedGraph analyzedGraph, EconomicMap methodMap, String graphTag) { - if (!useSharedLayerGraphs) { - return false; - } - byte[] encodedGraph = ObjectCopier.encode(imageLayerSnapshotUtil.getGraphEncoder(this), analyzedGraph); - if (contains(encodedGraph, LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING.getBytes(StandardCharsets.UTF_8))) { - throw AnalysisError.shouldNotReachHere("The graph for the method %s contains a reference to a lambda type, which cannot be decoded: %s".formatted(method, encodedGraph)); - } - String location = graphsOutput.add(encodedGraph); - methodMap.put(graphTag, location); - return true; - } - - private static boolean contains(byte[] data, byte[] seq) { - outer: for (int i = 0; i <= data.length - seq.length; i++) { - for (int j = 0; j < seq.length; j++) { - if (data[i + j] != seq[j]) { - continue outer; - } - } - return true; - } - return false; - } - - private EconomicMap getMethodMap(AnalysisMethod method) { - String name = imageLayerSnapshotUtil.getMethodIdentifier(method); - EconomicMap methodMap = methodsMap.get(name); - if (methodMap == null) { - methodMap = EconomicMap.create(); - methodsMap.put(name, methodMap); - } - return methodMap; - } - - protected void persistField(AnalysisField field) { - EconomicMap fieldMap = EconomicMap.create(); - - persistField(field, fieldMap); - - Field originalField = OriginalFieldProvider.getJavaField(field); - if (originalField != null && !originalField.getDeclaringClass().equals(field.getDeclaringClass().getJavaClass())) { - fieldMap.put(CLASS_NAME_TAG, originalField.getDeclaringClass().getName()); - } - fieldMap.put(IS_STATIC_TAG, field.isStatic()); - fieldMap.put(IS_INTERNAL_TAG, field.isInternal()); - fieldMap.put(IS_SYNTHETIC_TAG, field.isSynthetic()); - fieldMap.put(FIELD_TYPE_TAG, field.getType().getId()); - fieldMap.put(MODIFIERS_TAG, field.getModifiers()); - fieldMap.put(POSITION_TAG, field.getPosition()); - persistAnnotations(field, fieldMap); - - String tid = String.valueOf(field.getDeclaringClass().getId()); - fieldsMap.computeIfAbsent(tid, key -> new ConcurrentHashMap<>()).put(field.getName(), fieldMap); - } - - protected void persistField(AnalysisField field, EconomicMap fieldMap) { - fieldMap.put(ID_TAG, field.getId()); - fieldMap.put(FIELD_ACCESSED_TAG, field.getAccessedReason() != null); - fieldMap.put(FIELD_READ_TAG, field.getReadReason() != null); - fieldMap.put(FIELD_WRITTEN_TAG, field.getWrittenReason() != null); - fieldMap.put(FIELD_FOLDED_TAG, field.getFoldedReason() != null); - } - - protected void persistConstant(int parentId, int index, ImageHeapConstant imageHeapConstant) { - if (!constantsMap.containsKey(Integer.toString(getConstantId(imageHeapConstant)))) { - EconomicMap constantMap = EconomicMap.create(); - persistConstant(parentId, index, imageHeapConstant, constantMap); - } - } - - protected void persistConstant(int parentId, int index, ImageHeapConstant imageHeapConstant, EconomicMap constantMap) { - int id = getConstantId(imageHeapConstant); - constantsMap.put(Integer.toString(id), constantMap); - constantMap.put(TID_TAG, imageHeapConstant.getType().getId()); - - IdentityHashCodeProvider identityHashCodeProvider = (IdentityHashCodeProvider) aUniverse.getBigbang().getConstantReflectionProvider(); - int identityHashCode = identityHashCodeProvider.identityHashCode(imageHeapConstant); - constantMap.put(IDENTITY_HASH_CODE_TAG, identityHashCode); - - switch (imageHeapConstant) { - case ImageHeapInstance imageHeapInstance -> { - Object[] fieldValues = imageHeapInstance.isReaderInstalled() ? imageHeapInstance.getFieldValues() : null; - persistConstant(id, imageHeapConstant.getType(), constantMap, INSTANCE_TAG, fieldValues); - persistConstantRelinkingInfo(constantMap, imageHeapConstant, aUniverse.getBigbang()); - } - case ImageHeapObjectArray imageHeapObjectArray -> - persistConstant(id, imageHeapConstant.getType(), constantMap, ARRAY_TAG, imageHeapObjectArray.getElementValues()); - case ImageHeapPrimitiveArray imageHeapPrimitiveArray -> { - constantMap.put(CONSTANT_TYPE_TAG, PRIMITIVE_ARRAY_TAG); - constantMap.put(DATA_TAG, getString(imageHeapPrimitiveArray.getType().getComponentType().getJavaKind(), imageHeapPrimitiveArray.getArray())); - } - case ImageHeapRelocatableConstant relocatableConstant -> { - constantMap.put(CONSTANT_TYPE_TAG, RELOCATED_CONSTANT_TAG); - constantMap.put(DATA_TAG, relocatableConstant.getConstantData().key); - } - default -> throw AnalysisError.shouldNotReachHere("Unexpected constant type " + imageHeapConstant); - } - - if (!constantsToRelink.contains(id) && parentId != UNDEFINED_CONSTANT_ID) { - constantMap.put(PARENT_CONSTANT_ID_TAG, parentId); - assert index != UNDEFINED_FIELD_INDEX : "Tried to persist child constant %s from parent constant %d, but got index %d".formatted(imageHeapConstant, parentId, index); - constantMap.put(PARENT_CONSTANT_INDEX_TAG, index); - } - } - - protected int getConstantId(ImageHeapConstant imageHeapConstant) { - return imageHeapConstant.constantData.id; - } - - public void persistConstantRelinkingInfo(EconomicMap constantMap, ImageHeapConstant imageHeapConstant, BigBang bb) { - Class clazz = imageHeapConstant.getType().getJavaClass(); - JavaConstant hostedObject = imageHeapConstant.getHostedObject(); - boolean simulated = hostedObject == null; - constantMap.put(SIMULATED_TAG, simulated); - if (!simulated) { - persistConstantRelinkingInfo(constantMap, bb, clazz, hostedObject, imageHeapConstant.constantData.id); - } - } - - public void persistConstantRelinkingInfo(EconomicMap constantMap, BigBang bb, Class clazz, JavaConstant hostedObject, int id) { - if (clazz.equals(String.class)) { - String value = bb.getSnippetReflectionProvider().asObject(String.class, hostedObject); - if (internedStringsIdentityMap.containsKey(value)) { - /* - * Interned strings must be relinked. - */ - constantMap.put(VALUE_TAG, value); - constantsToRelink.add(id); - } - } else if (Enum.class.isAssignableFrom(clazz)) { - Enum value = bb.getSnippetReflectionProvider().asObject(Enum.class, hostedObject); - constantMap.put(ENUM_CLASS_TAG, value.getDeclaringClass().getName()); - constantMap.put(ENUM_NAME_TAG, value.name()); - constantsToRelink.add(id); - } - } - - private static List getString(JavaKind kind, Object arrayObject) { - return switch (kind) { - case Boolean -> IntStream.range(0, ((boolean[]) arrayObject).length).mapToObj(idx -> ((boolean[]) arrayObject)[idx]).toList(); - case Byte -> IntStream.range(0, ((byte[]) arrayObject).length).mapToObj(idx -> ((byte[]) arrayObject)[idx]).toList(); - case Short -> IntStream.range(0, ((short[]) arrayObject).length).mapToObj(idx -> ((short[]) arrayObject)[idx]).toList(); - case Char -> new String((char[]) arrayObject).chars().boxed().toList(); - case Int -> Arrays.stream((int[]) arrayObject).boxed().toList(); - /* Have to persist it as a String as it would be converted to an Integer otherwise */ - case Long -> Arrays.stream(((long[]) arrayObject)).mapToObj(String::valueOf).toList(); - /* Have to persist it as a String as it would be converted to a Double otherwise */ - case Float -> IntStream.range(0, ((float[]) arrayObject).length).mapToObj(idx -> String.valueOf(((float[]) arrayObject)[idx])).toList(); - case Double -> Arrays.stream(((double[]) arrayObject)).mapToObj(String::valueOf).toList(); - default -> throw new IllegalArgumentException("Unsupported kind: " + kind); - }; - } - - protected void persistConstant(int id, AnalysisType type, EconomicMap constantMap, String constantType, Object[] values) { - constantMap.put(CONSTANT_TYPE_TAG, constantType); - if (values != null) { - List> data = new ArrayList<>(); - for (int i = 0; i < values.length; ++i) { - Object object = values[i]; - if (delegateProcessing(data, object)) { - /* The object was already persisted */ - } else if (object instanceof ImageHeapConstant imageHeapConstant) { - data.add(List.of(OBJECT_TAG, getConstantId(imageHeapConstant))); - /* - * Some constants are not in imageHeap#reachableObjects, but are still created - * in reachable constants. They can be created in the extension image, but - * should not be used. - */ - persistConstant(imageLayerSnapshotUtil.getRelinkedFields(type, aUniverse.getBigbang().getMetaAccess()).contains(i) ? id : UNDEFINED_CONSTANT_ID, i, imageHeapConstant); - } else if (object == JavaConstant.NULL_POINTER) { - data.add(List.of(OBJECT_TAG, NULL_POINTER_CONSTANT)); - } else if (object instanceof PrimitiveConstant primitiveConstant) { - JavaKind kind = primitiveConstant.getJavaKind(); - data.add(List.of(kind.getTypeChar(), getPrimitiveConstantValue(primitiveConstant, kind))); - } else { - AnalysisError.guarantee(object instanceof AnalysisFuture, "Unexpected constant %s", object); - data.add(List.of(OBJECT_TAG, NOT_MATERIALIZED_CONSTANT)); - } - } - constantMap.put(DATA_TAG, data); - } - } - - private static Object getPrimitiveConstantValue(PrimitiveConstant primitiveConstant, JavaKind kind) { - return switch (kind) { - case Boolean, Byte, Short, Int, Double -> primitiveConstant.getRawValue(); - /* - * Have to persist it as a String as it would be converted to an Integer or a Double - * otherwise - */ - case Char, Long, Float -> String.valueOf(primitiveConstant.getRawValue()); - default -> throw new IllegalArgumentException("Unsupported kind: " + kind); - }; - } - - /** - * Hook for subclasses to do their own processing. - */ - @SuppressWarnings("unused") - protected boolean delegateProcessing(List> data, Object constant) { - return false; - } -} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisElement.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisElement.java index f1b0a7fe4cd2..0bbebc55503b 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisElement.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisElement.java @@ -38,6 +38,7 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; +import com.oracle.graal.pointsto.util.AtomicUtils; import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; import com.oracle.graal.pointsto.BigBang; @@ -57,6 +58,18 @@ public abstract class AnalysisElement implements AnnotatedElement { + protected static final AtomicReferenceFieldUpdater trackAcrossLayersUpdater = AtomicReferenceFieldUpdater + .newUpdater(AnalysisElement.class, Object.class, "trackAcrossLayers"); + /** + * See {@link AnalysisElement#isTrackedAcrossLayers} for explanation. + */ + @SuppressWarnings("unused") private volatile Object trackAcrossLayers; + protected final boolean enableTrackAcrossLayers; + + protected AnalysisElement(boolean enableTrackAcrossLayers) { + this.enableTrackAcrossLayers = enableTrackAcrossLayers; + } + public abstract AnnotatedElement getWrapped(); protected abstract AnalysisUniverse getUniverse(); @@ -114,10 +127,20 @@ protected void notifyReachabilityCallbacks(AnalysisUniverse universe, List onTrackedAcrossLayers(reason)); + } + } + /** * Indicates we need this information to be saved in the layer archive file. */ - public abstract boolean isTrackedAcrossLayers(); + public boolean isTrackedAcrossLayers() { + return AtomicUtils.isSet(this, trackAcrossLayersUpdater); + } + + protected abstract void onTrackedAcrossLayers(Object reason); /** Return true if reachability handlers should be executed for this element. */ public boolean isTriggered() { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java index 7a8718ba80a8..616fd47c55c4 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java @@ -64,10 +64,6 @@ public abstract class AnalysisField extends AnalysisElement implements WrappedJa private static final AtomicReferenceFieldUpdater isUnsafeAccessedUpdater = AtomicReferenceFieldUpdater .newUpdater(AnalysisField.class, Object.class, "isUnsafeAccessed"); - - private static final AtomicReferenceFieldUpdater trackAcrossLayersUpdater = AtomicReferenceFieldUpdater - .newUpdater(AnalysisField.class, Object.class, "trackAcrossLayers"); - private final int id; /** Marks a field loaded from a base layer. */ private final boolean isInBaseLayer; @@ -93,12 +89,6 @@ public abstract class AnalysisField extends AnalysisElement implements WrappedJa @SuppressWarnings("unused") private volatile Object isFolded; @SuppressWarnings("unused") private volatile Object isUnsafeAccessed; - /** - * See {@link AnalysisElement#isTrackedAcrossLayers} for explanation. - */ - @SuppressWarnings("unused") private volatile Object trackAcrossLayers; - private final boolean enableTrackAcrossLayers; - private ConcurrentMap readBy; private ConcurrentMap writtenBy; @@ -117,6 +107,7 @@ public abstract class AnalysisField extends AnalysisElement implements WrappedJa @SuppressWarnings("this-escape") public AnalysisField(AnalysisUniverse universe, ResolvedJavaField wrappedField) { + super(universe.hostVM.enableTrackAcrossLayers()); assert !wrappedField.isInternal() : wrappedField; this.position = -1; @@ -159,8 +150,6 @@ public AnalysisField(AnalysisUniverse universe, ResolvedJavaField wrappedField) id = universe.computeNextFieldId(); isInBaseLayer = false; } - - this.enableTrackAcrossLayers = universe.hostVM.enableTrackAcrossLayers(); } @Override @@ -383,15 +372,13 @@ public boolean isReachable() { @Override public void onReachable(Object reason) { - if (enableTrackAcrossLayers) { - AtomicUtils.atomicSet(this, reason, trackAcrossLayersUpdater); - } + registerAsTrackedAcrossLayers(reason); notifyReachabilityCallbacks(declaringClass.getUniverse(), new ArrayList<>()); } @Override - public boolean isTrackedAcrossLayers() { - return AtomicUtils.isSet(this, trackAcrossLayersUpdater); + protected void onTrackedAcrossLayers(Object reason) { + AnalysisError.guarantee(!getUniverse().sealed(), "Field %s was marked as tracked after the universe was sealed", this); } public Object getFieldValueInterceptor() { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java index dc1b43c5bad2..b38ed1d63c7f 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java @@ -50,6 +50,7 @@ import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.api.ImageLayerLoader; import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.flow.AnalysisParsedGraph; @@ -112,8 +113,8 @@ public abstract class AnalysisMethod extends AnalysisElement implements WrappedJ static final AtomicReferenceFieldUpdater allImplementationsUpdater = AtomicReferenceFieldUpdater .newUpdater(AnalysisMethod.class, Object.class, "allImplementations"); - private static final AtomicReferenceFieldUpdater trackAcrossLayersUpdater = AtomicReferenceFieldUpdater - .newUpdater(AnalysisMethod.class, Object.class, "trackAcrossLayers"); + private static final AtomicReferenceFieldUpdater reachableInCurrentLayerUpdater = AtomicReferenceFieldUpdater + .newUpdater(AnalysisMethod.class, Boolean.class, "reachableInCurrentLayer"); public record Signature(String name, AnalysisType[] parameterTypes) { } @@ -169,6 +170,8 @@ public record Signature(String name, AnalysisType[] parameterTypes) { @SuppressWarnings("unused") private volatile Object implementationInvokedNotifications; @SuppressWarnings("unused") private volatile Object isIntrinsicMethod; @SuppressWarnings("unused") private volatile Object isInlined; + @SuppressWarnings("unused") private volatile Boolean reachableInCurrentLayer; + private final boolean enableReachableInCurrentLayer; private final AtomicReference parsedGraphCacheState = new AtomicReference<>(GRAPH_CACHE_UNPARSED); private static final Object GRAPH_CACHE_UNPARSED = "unparsed"; @@ -184,12 +187,6 @@ public record Signature(String name, AnalysisType[] parameterTypes) { */ @SuppressWarnings("unused") private volatile Object allImplementations; - /** - * See {@link AnalysisElement#isTrackedAcrossLayers} for explanation. - */ - @SuppressWarnings("unused") private volatile Object trackAcrossLayers; - private final boolean enableTrackAcrossLayers; - /** * Indicates that this method has opaque return. This is necessary when there are control flows * present which cannot be tracked by analysis, which happens for continuation support. @@ -201,6 +198,7 @@ public record Signature(String name, AnalysisType[] parameterTypes) { @SuppressWarnings({"this-escape", "unchecked"}) protected AnalysisMethod(AnalysisUniverse universe, ResolvedJavaMethod wrapped, MultiMethodKey multiMethodKey, Map multiMethodMap) { + super(universe.hostVM.enableTrackAcrossLayers()); this.wrapped = wrapped; declaringClass = universe.lookup(wrapped.getDeclaringClass()); @@ -275,11 +273,12 @@ protected AnalysisMethod(AnalysisUniverse universe, ResolvedJavaMethod wrapped, } parsingContextMaxDepth = PointstoOptions.ParsingContextMaxDepth.getValue(declaringClass.universe.hostVM.options()); - this.enableTrackAcrossLayers = universe.hostVM.enableTrackAcrossLayers(); + this.enableReachableInCurrentLayer = universe.hostVM.enableReachableInCurrentLayer(); } @SuppressWarnings("this-escape") protected AnalysisMethod(AnalysisMethod original, MultiMethodKey multiMethodKey) { + super(original.enableTrackAcrossLayers); wrapped = original.wrapped; id = original.id; isInBaseLayer = original.isInBaseLayer; @@ -304,7 +303,7 @@ protected AnalysisMethod(AnalysisMethod original, MultiMethodKey multiMethodKey) startTrackInvocations(); } - this.enableTrackAcrossLayers = original.enableTrackAcrossLayers; + this.enableReachableInCurrentLayer = original.enableReachableInCurrentLayer; } private static String createName(ResolvedJavaMethod wrapped, MultiMethodKey multiMethodKey) { @@ -470,6 +469,21 @@ public boolean analyzedInPriorLayer() { return analyzedInPriorLayer; } + public boolean reachableInCurrentLayer() { + return enableReachableInCurrentLayer && reachableInCurrentLayer != null && reachableInCurrentLayer; + } + + public void setReachableInCurrentLayer() { + if (enableReachableInCurrentLayer && !reachableInCurrentLayer()) { + AtomicUtils.atomicSetAndRun(this, true, reachableInCurrentLayerUpdater, () -> { + ImageLayerLoader imageLayerLoader = getUniverse().getImageLayerLoader(); + if (imageLayerLoader != null) { + imageLayerLoader.loadPriorStrengthenedGraphAnalysisElements(this); + } + }); + } + } + /** * Registers this method as intrinsified to Graal nodes via a {@link InvocationPlugin graph * builder plugin}. Such a method is treated similar to an invoked method. For example, method @@ -492,6 +506,7 @@ public void registerAsEntryPoint(Object newEntryPointData) { public boolean registerAsInvoked(Object reason) { assert isValidReason(reason) : "Registering a method as invoked needs to provide a valid reason, found: " + reason; + registerAsTrackedAcrossLayers(reason); return AtomicUtils.atomicSet(this, reason, isInvokedUpdater); } @@ -649,11 +664,6 @@ public boolean isReachable() { return isImplementationInvoked() || isInlined(); } - @Override - public boolean isTrackedAcrossLayers() { - return AtomicUtils.isSet(this, trackAcrossLayersUpdater); - } - @Override public boolean isTriggered() { if (isReachable()) { @@ -669,12 +679,21 @@ public void onImplementationInvoked(Object reason) { @Override public void onReachable(Object reason) { - if (enableTrackAcrossLayers) { - AtomicUtils.atomicSet(this, reason, trackAcrossLayersUpdater); - } + registerAsTrackedAcrossLayers(reason); notifyReachabilityCallbacks(declaringClass.getUniverse(), new ArrayList<>()); } + @Override + protected void onTrackedAcrossLayers(Object reason) { + AnalysisError.guarantee(!getUniverse().sealed(), "Method %s was marked as tracked after the universe was sealed", this); + getUniverse().getImageLayerWriter().onTrackedAcrossLayer(this, reason); + declaringClass.registerAsTrackedAcrossLayers(reason); + for (AnalysisType parameter : toParameterList()) { + parameter.registerAsTrackedAcrossLayers(reason); + } + signature.getReturnType().registerAsTrackedAcrossLayers(reason); + } + public void registerOverrideReachabilityNotification(MethodOverrideReachableNotification notification) { getUniverse().registerOverrideReachabilityNotification(this, notification); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java index 55d8acd023f9..c124c9439cad 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java @@ -108,9 +108,6 @@ public abstract class AnalysisType extends AnalysisElement implements WrappedJav static final AtomicReferenceFieldUpdater overrideableMethodsUpdater = AtomicReferenceFieldUpdater .newUpdater(AnalysisType.class, Object.class, "overrideableMethods"); - private static final AtomicReferenceFieldUpdater trackAcrossLayersUpdater = AtomicReferenceFieldUpdater - .newUpdater(AnalysisType.class, Object.class, "trackAcrossLayers"); - protected final AnalysisUniverse universe; private final ResolvedJavaType wrapped; private final String qualifiedName; @@ -221,14 +218,9 @@ public abstract class AnalysisType extends AnalysisElement implements WrappedJav */ @SuppressWarnings("unused") private volatile Object overrideableMethods; - /** - * See {@link AnalysisElement#isTrackedAcrossLayers} for explanation. - */ - @SuppressWarnings("unused") private volatile Object trackAcrossLayers; - private final boolean enableTrackAcrossLayers; - @SuppressWarnings("this-escape") public AnalysisType(AnalysisUniverse universe, ResolvedJavaType javaType, JavaKind storageKind, AnalysisType objectType, AnalysisType cloneableType) { + super(universe.hostVM.enableTrackAcrossLayers()); this.universe = universe; this.wrapped = javaType; qualifiedName = wrapped.toJavaName(true); @@ -352,8 +344,6 @@ public AnalysisType(AnalysisUniverse universe, ResolvedJavaType javaType, JavaKi AnalysisError.guarantee(universe.getHeapScanner() != null, "Heap scanner is not available."); return universe.getHeapScanner().computeTypeData(this); }); - - this.enableTrackAcrossLayers = universe.hostVM.enableTrackAcrossLayers(); } private AnalysisType[] convertTypes(ResolvedJavaType[] originalTypes) { @@ -611,9 +601,7 @@ public boolean registerAsReachable(Object reason) { @Override protected void onReachable(Object reason) { - if (enableTrackAcrossLayers) { - AtomicUtils.atomicSet(this, reason, trackAcrossLayersUpdater); - } + registerAsTrackedAcrossLayers(reason); List> futures = new ArrayList<>(); notifyReachabilityCallbacks(universe, futures); @@ -651,6 +639,28 @@ protected void onReachable(Object reason) { ensureOnTypeReachableTaskDone(); } + @Override + protected void onTrackedAcrossLayers(Object reason) { + AnalysisError.guarantee(!getUniverse().sealed(), "Type %s was marked as tracked after the universe was sealed", this); + if (superClass != null) { + superClass.registerAsTrackedAcrossLayers(reason); + } + for (AnalysisType inter : interfaces) { + inter.registerAsTrackedAcrossLayers(reason); + } + try { + AnalysisType enclosingType = getEnclosingType(); + if (enclosingType != null) { + enclosingType.registerAsTrackedAcrossLayers(reason); + } + } catch (InternalError | TypeNotPresentException | LinkageError e) { + /* + * Ignore missing type errors. The build process should not fail if the incomplete type + * is not reached through other paths. + */ + } + } + /** Prepare information that {@link AnalysisMethod#collectMethodImplementations} needs. */ private void prepareMethodImplementations(AnalysisType superType) { ConcurrentLightHashSet.forEach(superType, overrideableMethodsUpdater, (AnalysisMethod method) -> { @@ -875,11 +885,6 @@ public Object getReachableReason() { return isReachable; } - @Override - public boolean isTrackedAcrossLayers() { - return AtomicUtils.isSet(this, trackAcrossLayersUpdater); - } - /** * The kind of the field in memory (in contrast to {@link #getJavaKind()}, which is the kind of * the field on the Java type system level). For example {@link WordBase word types} have a diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java index a616e7e6f729..02070692e3cf 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java @@ -44,13 +44,13 @@ import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.ObjectScanner; import com.oracle.graal.pointsto.api.HostVM; +import com.oracle.graal.pointsto.api.ImageLayerLoader; +import com.oracle.graal.pointsto.api.ImageLayerWriter; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.heap.HeapSnapshotVerifier; import com.oracle.graal.pointsto.heap.HostedValuesProvider; import com.oracle.graal.pointsto.heap.ImageHeapConstant; import com.oracle.graal.pointsto.heap.ImageHeapScanner; -import com.oracle.graal.pointsto.heap.ImageLayerLoader; -import com.oracle.graal.pointsto.heap.ImageLayerWriter; import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; import com.oracle.graal.pointsto.infrastructure.ResolvedSignature; import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor; @@ -438,10 +438,6 @@ private AnalysisMethod createMethod(ResolvedJavaMethod method) { }); if (result.equals(newValue)) { - if (newValue.isInBaseLayer()) { - getImageLayerLoader().initializeBaseLayerMethod(newValue); - } - prepareMethodImplementations(newValue); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisType.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisType.java index 4e0c7b99e11f..dd7bfc2f0ccd 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisType.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisType.java @@ -62,7 +62,7 @@ public boolean registerAsUnsafeAllocated(Object reason) { var bb = (PointsToAnalysis) universe.getBigbang(); for (var f : getInstanceFields(true)) { var field = (AnalysisField) f; - field.getInitialFlow().addState(bb, TypeState.defaultValueForKind(field.getStorageKind())); + field.getInitialFlow().addState(bb, TypeState.defaultValueForKind(bb, field.getStorageKind())); } } return result; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java index a402d4edd5dd..c1deccc191b5 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java @@ -234,13 +234,21 @@ public final void applyResults(AnalysisMethod method) { ? ptaMethod.getTypeFlow().getMethodFlowsGraph().getNodeFlows().getKeys() : null; var debug = new DebugContext.Builder(bb.getOptions(), new GraalDebugHandlersFactory(bb.getSnippetReflectionProvider())).build(); - var graph = method.decodeAnalyzedGraph(debug, nodeReferences); - if (graph == null) { + + if (method.analyzedInPriorLayer()) { + /* + * The method was already strengthened in a prior layer. If the graph was persisted, it + * will be loaded on demand during compilation, so there is no need to strengthen it in + * this layer. + * + * GR-59646: The graphs from the base layer could be strengthened again in the + * application layer using closed world assumptions. + */ return; } - if (method.analyzedInPriorLayer()) { - useSharedLayerGraph(method); + var graph = method.decodeAnalyzedGraph(debug, nodeReferences); + if (graph == null) { return; } @@ -273,8 +281,6 @@ public final void applyResults(AnalysisMethod method) { protected abstract void postStrengthenGraphs(StructuredGraph graph, AnalysisMethod method); - protected abstract void useSharedLayerGraph(AnalysisMethod method); - protected abstract void persistStrengthenGraph(AnalysisMethod method); /* @@ -502,24 +508,37 @@ public void simplify(Node n, SimplifierTool tool) { Stamp newStamp = strengthenStamp(oldStamp); if (newStamp != null) { LogicNode replacement = graph.addOrUniqueWithInputs(InstanceOfNode.createHelper((ObjectStamp) oldStamp.improveWith(newStamp), node.getValue(), node.profile(), node.getAnchor())); + /* + * GR-59681: Once isAssignable is implemented for BaseLayerType, this check can + * be removed + */ + AnalysisError.guarantee(node != replacement, "The new stamp needs to be different from the old stamp"); node.replaceAndDelete(replacement); tool.addToWorkList(replacement); } - } else if (n instanceof ClassIsAssignableFromNode) { - ClassIsAssignableFromNode node = (ClassIsAssignableFromNode) n; - AnalysisType nonReachableType = asConstantNonReachableType(node.getThisClass(), tool); - if (nonReachableType != null) { - node.replaceAndDelete(LogicConstantNode.contradiction(graph)); + } else if (n instanceof ClassIsAssignableFromNode node) { + if (isClosedTypeWorld) { + /* + * If the constant receiver of a Class#isAssignableFrom is an unreachable type + * we can constant-fold the ClassIsAssignableFromNode to false. See also + * MethodTypeFlowBuilder#ignoreConstant where we avoid marking the corresponding + * type as reachable just because it is used by the ClassIsAssignableFromNode. + * We only apply this optimization if it's a closed type world, for open world + * we cannot fold the type check since the type may be used later. + */ + AnalysisType nonReachableType = asConstantNonReachableType(node.getThisClass(), tool); + if (nonReachableType != null) { + node.replaceAndDelete(LogicConstantNode.contradiction(graph)); + } } - - } else if (n instanceof BytecodeExceptionNode) { + } else if (n instanceof BytecodeExceptionNode node) { /* * We do not want a type to be reachable only to be used for the error message of a * ClassCastException. Therefore, in that case we replace the java.lang.Class with a - * java.lang.String that is then used directly in the error message. + * java.lang.String that is then used directly in the error message. We can apply + * this optimization optimistically for both closed and open type world. */ - BytecodeExceptionNode node = (BytecodeExceptionNode) n; if (node.getExceptionKind() == BytecodeExceptionNode.BytecodeExceptionKind.CLASS_CAST) { AnalysisType nonReachableType = asConstantNonReachableType(node.getArguments().get(1), tool); if (nonReachableType != null) { @@ -551,7 +570,13 @@ public void simplify(Node n, SimplifierTool tool) { Stamp oldStamp = node.piStamp(); Stamp newStamp = strengthenStamp(oldStamp); if (newStamp != null) { - node.strengthenPiStamp(oldStamp.improveWith(newStamp)); + Stamp newPiStamp = oldStamp.improveWith(newStamp); + /* + * GR-59681: Once isAssignable is implemented for BaseLayerType, this check can + * be removed + */ + AnalysisError.guarantee(!newPiStamp.equals(oldStamp), "The new stamp needs to be different from the old stamp"); + node.strengthenPiStamp(newPiStamp); tool.addToWorkList(node); } } @@ -1085,7 +1110,8 @@ private Stamp strengthenStamp(Stamp s) { return null; } - if (!originalType.isReachable()) { + /* In open world the type may become reachable later. */ + if (isClosedTypeWorld && !originalType.isReachable()) { /* We must be in dead code. */ if (stamp.nonNull()) { /* We must be in dead code. */ diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/ConstantTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/ConstantTypeState.java index 1303d7f1b709..efc44840e320 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/ConstantTypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/ConstantTypeState.java @@ -48,15 +48,15 @@ public class ConstantTypeState extends SingleTypeState { /** Creates a new type state from incoming objects. */ public ConstantTypeState(PointsToAnalysis bb, AnalysisType type, JavaConstant constant) { - super(bb, false, type); + super(false, type); assert !bb.analysisPolicy().isContextSensitiveAnalysis() : "The ConstantTypeState is indented to be used with a context insensitive analysis."; assert !(constant instanceof ImageHeapRelocatableConstant) : "relocatable constants have an unknown state and should not be represented by a constant type state: " + constant; - this.constant = constant; + this.constant = Objects.requireNonNull(constant); } /** Create a type state with the same content and a reversed canBeNull value. */ - protected ConstantTypeState(PointsToAnalysis bb, boolean canBeNull, ConstantTypeState other) { - super(bb, canBeNull, other); + protected ConstantTypeState(boolean canBeNull, ConstantTypeState other) { + super(canBeNull, other); this.constant = other.constant; } @@ -69,7 +69,7 @@ public TypeState forCanBeNull(PointsToAnalysis bb, boolean stateCanBeNull) { if (stateCanBeNull == this.canBeNull()) { return this; } else { - return new ConstantTypeState(bb, stateCanBeNull, this); + return PointsToStats.registerTypeState(bb, new ConstantTypeState(stateCanBeNull, this)); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisPolicy.java index 4d8466da972c..6bce082b74be 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultAnalysisPolicy.java @@ -138,7 +138,7 @@ public TypeState constantTypeState(PointsToAnalysis bb, JavaConstant constant, A */ return TypeState.forType(bb, relocatableConstant.getType(), false); } - return new ConstantTypeState(bb, exactType, constant); + return PointsToStats.registerTypeState(bb, new ConstantTypeState(bb, exactType, constant)); } @Override @@ -271,12 +271,12 @@ public TypeState forContextInsensitiveTypeState(PointsToAnalysis bb, TypeState s @Override public SingleTypeState singleTypeState(PointsToAnalysis bb, boolean canBeNull, AnalysisType type, AnalysisObject... objects) { - return new SingleTypeState(bb, canBeNull, type); + return PointsToStats.registerTypeState(bb, new SingleTypeState(canBeNull, type)); } @Override public MultiTypeState multiTypeState(PointsToAnalysis bb, boolean canBeNull, BitSet typesBitSet, int typesCount, AnalysisObject... objects) { - return new MultiTypeState(bb, canBeNull, typesBitSet, typesCount); + return PointsToStats.registerTypeState(bb, new MultiTypeState(canBeNull, typesBitSet, typesCount)); } /* @@ -324,7 +324,7 @@ public TypeState doUnion(PointsToAnalysis bb, SingleTypeState state1, SingleType */ BitSet typesBitSet = TypeStateUtils.newBitSet(s1.exactType().getId(), s2.exactType().getId()); assert typesBitSet.cardinality() == 2 : typesBitSet; - TypeState result = new MultiTypeState(bb, resultCanBeNull, typesBitSet, 2); + TypeState result = multiTypeState(bb, resultCanBeNull, typesBitSet, 2); PointsToStats.registerUnionOperation(bb, s1, s2, result); return result; } @@ -344,7 +344,7 @@ public TypeState doUnion(PointsToAnalysis bb, MultiTypeState s1, SingleTypeState BitSet typesBitSet = TypeStateUtils.set(s1.typesBitSet(), s2.exactType().getId()); int typesCount = s1.typesCount() + 1; assert typesCount == typesBitSet.cardinality() : typesBitSet; - MultiTypeState result = new MultiTypeState(bb, resultCanBeNull, typesBitSet, typesCount); + MultiTypeState result = multiTypeState(bb, resultCanBeNull, typesBitSet, typesCount); PointsToStats.registerUnionOperation(bb, s1, s2, result); return result; } @@ -372,7 +372,7 @@ public TypeState doUnion(PointsToAnalysis bb, MultiTypeState s1, MultiTypeState /* Logical OR the type bit sets. */ BitSet resultTypesBitSet = TypeStateUtils.or(s1.typesBitSet(), s2.typesBitSet()); - MultiTypeState result = new MultiTypeState(bb, resultCanBeNull, resultTypesBitSet, resultTypesBitSet.cardinality()); + MultiTypeState result = multiTypeState(bb, resultCanBeNull, resultTypesBitSet, resultTypesBitSet.cardinality()); assert !result.equals(s1) && !result.equals(s2) : result; PointsToStats.registerUnionOperation(bb, s1, s2, result); return result; @@ -419,9 +419,9 @@ public TypeState doIntersection(PointsToAnalysis bb, MultiTypeState s1, MultiTyp return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); } else if (typesCount == 1) { AnalysisType type = bb.getUniverse().getType(resultTypesBitSet.nextSetBit(0)); - return new SingleTypeState(bb, resultCanBeNull, type); + return singleTypeState(bb, resultCanBeNull, type); } else { - MultiTypeState result = new MultiTypeState(bb, resultCanBeNull, resultTypesBitSet, typesCount); + MultiTypeState result = multiTypeState(bb, resultCanBeNull, resultTypesBitSet, typesCount); assert !result.equals(s1) : result; return result; } @@ -437,9 +437,9 @@ public TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState s1, SingleTyp assert typesCount > 0 : typesCount; if (typesCount == 1) { AnalysisType type = bb.getUniverse().getType(resultTypesBitSet.nextSetBit(0)); - return new SingleTypeState(bb, resultCanBeNull, type); + return singleTypeState(bb, resultCanBeNull, type); } else { - return new MultiTypeState(bb, resultCanBeNull, resultTypesBitSet, typesCount); + return multiTypeState(bb, resultCanBeNull, resultTypesBitSet, typesCount); } } else { return s1.forCanBeNull(bb, resultCanBeNull); @@ -476,9 +476,9 @@ public TypeState doSubtraction(PointsToAnalysis bb, MultiTypeState s1, MultiType return TypeState.forEmpty().forCanBeNull(bb, resultCanBeNull); } else if (typesCount == 1) { AnalysisType type = bb.getUniverse().getType(resultTypesBitSet.nextSetBit(0)); - return new SingleTypeState(bb, resultCanBeNull, type); + return singleTypeState(bb, resultCanBeNull, type); } else { - return new MultiTypeState(bb, resultCanBeNull, resultTypesBitSet, typesCount); + return multiTypeState(bb, resultCanBeNull, resultTypesBitSet, typesCount); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java index 67e7ceda19fa..a0f3431e9a6c 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java @@ -48,25 +48,21 @@ public class MultiTypeState extends TypeState { protected boolean merged; /** Creates a new type state using the provided types bit set and objects. */ - @SuppressWarnings("this-escape") - public MultiTypeState(PointsToAnalysis bb, boolean canBeNull, BitSet typesBitSet, int typesCount) { + public MultiTypeState(boolean canBeNull, BitSet typesBitSet, int typesCount) { assert !TypeStateUtils.needsTrim(typesBitSet) : typesBitSet; this.typesBitSet = typesBitSet; this.typesCount = typesCount; this.canBeNull = canBeNull; this.merged = false; assert this.typesCount > 1 : "Multi type state with single type."; - PointsToStats.registerTypeState(bb, this); } /** Create a type state with the same content and a reversed canBeNull value. */ - @SuppressWarnings("this-escape") - protected MultiTypeState(PointsToAnalysis bb, boolean canBeNull, MultiTypeState other) { + protected MultiTypeState(boolean canBeNull, MultiTypeState other) { this.typesBitSet = other.typesBitSet; this.typesCount = other.typesCount; this.canBeNull = canBeNull; this.merged = other.merged; - PointsToStats.registerTypeState(bb, this); } /** Get the number of objects. */ @@ -141,7 +137,7 @@ public TypeState forCanBeNull(PointsToAnalysis bb, boolean resultCanBeNull) { return this; } else { /* Just flip the canBeNull flag and copy the rest of the values from this. */ - return new MultiTypeState(bb, resultCanBeNull, this); + return PointsToStats.registerTypeState(bb, new MultiTypeState(resultCanBeNull, this)); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PointsToStats.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PointsToStats.java index cb4c1427581c..eaa3923ca94a 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PointsToStats.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PointsToStats.java @@ -75,6 +75,7 @@ import com.oracle.graal.pointsto.flow.builder.TypeFlowBuilder; import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.svm.util.ClassUtil; import jdk.graal.compiler.graph.NodeSourcePosition; @@ -91,6 +92,8 @@ public class PointsToStats { public static void init(PointsToAnalysis bb) { registerTypeState(bb, EmptyTypeState.SINGLETON); registerTypeState(bb, NullTypeState.SINGLETON); + registerTypeState(bb, AnyPrimitiveTypeState.SINGLETON); + PrimitiveConstantTypeState.registerCachedTypeStates(bb); reportStatistics = bb.reportAnalysisStatistics(); } @@ -162,7 +165,7 @@ private static void reportPrunedTypeFlows(BufferedWriter out) { sourceStr = value.toString() + " @ " + value.graph().method().format("%H.%n(%p)"); } } else { - sourceStr = source.toString(); + sourceStr = source != null ? source.toString() : "null"; } doWrite(out, String.format("%-35s\t%-10s%n", ClassUtil.getUnqualifiedName(provider.getFlowClass()), sourceStr)); @@ -314,23 +317,30 @@ private static void reportTypeFlowStats(BufferedWriter out) { private static final AtomicInteger nextStateId = new AtomicInteger(); private static ConcurrentHashMap typeStateStats = new ConcurrentHashMap<>(); - public static void registerTypeState(PointsToAnalysis bb, TypeState state) { + public static T registerTypeState(PointsToAnalysis bb, T state) { if (!bb.reportAnalysisStatistics()) { - return; + return state; } Integer id = stateToId.computeIfAbsent(state, (s) -> nextStateId.incrementAndGet()); TypeState actualState = idToState.computeIfAbsent(id, (i) -> state); typeStateStats.computeIfAbsent(actualState, (s) -> new AtomicInteger()).incrementAndGet(); + return state; } private static int objectsCount(TypeState state) { + if (state.isPrimitive()) { + return 0; + } return state.objectsCount(); } private static int typesCount(TypeState state) { + if (state.isPrimitive()) { + return 0; + } return state.typesCount(); } @@ -541,7 +551,7 @@ private static String formatType(AnalysisType type) { } private static String formatType(AnalysisType type, boolean qualified) { - return type.toJavaName(qualified); + return type != null ? type.toJavaName(qualified) : "null"; } @SuppressWarnings("unused") @@ -567,6 +577,13 @@ public static String asString(TypeState s) { if (s.isNull()) { return ""; } + if (s.isPrimitive()) { + return switch (s) { + case AnyPrimitiveTypeState ignored -> ""; + case PrimitiveConstantTypeState constant -> ""; + default -> throw AnalysisError.shouldNotReachHere("Unknown primitive type state: " + s.getClass()); + }; + } String sKind = s.isAllocation() ? "Alloc" : s.asConstant() != null ? "Const" : s instanceof SingleTypeState ? "Single" : s instanceof MultiTypeState ? "Multi" : ""; String sSizeOrType = s instanceof MultiTypeState ? s.typesCount() + "" : s.exactType().toJavaName(false); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveConstantTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveConstantTypeState.java index 862e87623aad..e837d54c326e 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveConstantTypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveConstantTypeState.java @@ -26,6 +26,8 @@ import java.util.Objects; +import com.oracle.graal.pointsto.PointsToAnalysis; + /** * Represents a primitive constant that is propagated through the type flow graph. Instances for * corresponding primitive values are accessible via a factory method @@ -43,13 +45,19 @@ public final class PrimitiveConstantTypeState extends PrimitiveTypeState { } } + public static void registerCachedTypeStates(PointsToAnalysis bb) { + for (var typeState : CACHE) { + PointsToStats.registerTypeState(bb, typeState); + } + } + private final long value; - public static TypeState forValue(long value) { + public static TypeState forValue(PointsToAnalysis bb, long value) { if (value >= 0 && value < CACHE_SIZE) { return CACHE[(int) value]; } - return new PrimitiveConstantTypeState(value); + return PointsToStats.registerTypeState(bb, new PrimitiveConstantTypeState(value)); } private PrimitiveConstantTypeState(long value) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/SingleTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/SingleTypeState.java index 57c4470ca40d..b1be10e01be0 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/SingleTypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/SingleTypeState.java @@ -40,23 +40,17 @@ public class SingleTypeState extends TypeState { protected boolean merged; /** Creates a new type state from incoming objects. */ - @SuppressWarnings("this-escape") - public SingleTypeState(PointsToAnalysis bb, boolean canBeNull, AnalysisType type) { + public SingleTypeState(boolean canBeNull, AnalysisType type) { this.type = type; this.canBeNull = canBeNull; this.merged = false; - - PointsToStats.registerTypeState(bb, this); } /** Create a type state with the same content and a reversed canBeNull value. */ - @SuppressWarnings("this-escape") - protected SingleTypeState(PointsToAnalysis bb, boolean canBeNull, SingleTypeState other) { + protected SingleTypeState(boolean canBeNull, SingleTypeState other) { this.type = other.type; this.canBeNull = canBeNull; this.merged = other.merged; - - PointsToStats.registerTypeState(bb, this); } @Override @@ -121,7 +115,7 @@ public TypeState forCanBeNull(PointsToAnalysis bb, boolean stateCanBeNull) { if (stateCanBeNull == this.canBeNull()) { return this; } else { - return new SingleTypeState(bb, stateCanBeNull, this); + return PointsToStats.registerTypeState(bb, new SingleTypeState(stateCanBeNull, this)); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeState.java index c5b95038ac30..6799149a4e0e 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeState.java @@ -150,20 +150,20 @@ public static TypeState forNull() { return NullTypeState.SINGLETON; } - public static TypeState defaultValueForKind(JavaKind javaKind) { + public static TypeState defaultValueForKind(PointsToAnalysis bb, JavaKind javaKind) { if (javaKind.isPrimitive()) { - return TypeState.forPrimitiveConstant(0); + return TypeState.forPrimitiveConstant(bb, 0); } else { return TypeState.forNull(); } } - public static TypeState forPrimitiveConstant(long value) { - return PrimitiveConstantTypeState.forValue(value); + public static TypeState forPrimitiveConstant(PointsToAnalysis bb, long value) { + return PrimitiveConstantTypeState.forValue(bb, value); } - public static TypeState forBoolean(boolean value) { - return value ? TypeState.forPrimitiveConstant(1) : TypeState.forPrimitiveConstant(0); + public static TypeState forBoolean(PointsToAnalysis bb, boolean value) { + return value ? TypeState.forPrimitiveConstant(bb, 1) : TypeState.forPrimitiveConstant(bb, 0); } public static TypeState anyPrimitiveState() { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/PointsToOptionParser.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/PointsToOptionParser.java index d49daef85e8b..5a6864f56fd8 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/PointsToOptionParser.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/PointsToOptionParser.java @@ -38,16 +38,17 @@ import java.util.function.Predicate; import org.graalvm.collections.EconomicMap; -import jdk.graal.compiler.options.OptionDescriptor; -import jdk.graal.compiler.options.OptionDescriptors; -import jdk.graal.compiler.options.OptionKey; -import jdk.graal.compiler.options.OptionValues; import com.oracle.svm.common.option.CommonOptionParser; import com.oracle.svm.common.option.CommonOptionParser.BooleanOptionFormat; import com.oracle.svm.common.option.CommonOptionParser.OptionParseResult; import com.oracle.svm.common.option.UnsupportedOptionClassException; +import jdk.graal.compiler.options.OptionDescriptor; +import jdk.graal.compiler.options.OptionDescriptors; +import jdk.graal.compiler.options.OptionKey; +import jdk.graal.compiler.options.OptionValues; + public final class PointsToOptionParser { private static PointsToOptionParser instance = new PointsToOptionParser(); @@ -94,9 +95,10 @@ public OptionValues parse(String[] args) { AnalysisError.interruptAnalysis(String.format("Unknown options: %s", Arrays.toString(remainingArgs.toArray(new String[0])))); } if (!errors.isEmpty()) { - StringBuilder errMsg = new StringBuilder("Option format error:\n"); + StringBuilder errMsg = new StringBuilder("Option format error:"); + errMsg.append(System.lineSeparator()); for (String err : errors) { - errMsg.append(err).append("\n"); + errMsg.append(err).append(System.lineSeparator()); } AnalysisError.interruptAnalysis(errMsg.toString()); } diff --git a/substratevm/src/com.oracle.graal.reachability/src/com/oracle/graal/reachability/ReachabilityAnalysisEngine.java b/substratevm/src/com.oracle.graal.reachability/src/com/oracle/graal/reachability/ReachabilityAnalysisEngine.java index 695b081a919b..d94f1f31123c 100644 --- a/substratevm/src/com.oracle.graal.reachability/src/com/oracle/graal/reachability/ReachabilityAnalysisEngine.java +++ b/substratevm/src/com.oracle.graal.reachability/src/com/oracle/graal/reachability/ReachabilityAnalysisEngine.java @@ -143,8 +143,7 @@ public AnalysisType addRootField(Class clazz, String fieldName) { for (ResolvedJavaField javaField : type.getInstanceFields(true)) { AnalysisField field = (AnalysisField) javaField; if (field.getName().equals(fieldName)) { - field.registerAsAccessed("root field"); - return field.getType(); + return addRootField(field); } } throw AnalysisError.userError("Field not found: " + fieldName); @@ -153,6 +152,11 @@ public AnalysisType addRootField(Class clazz, String fieldName) { @Override public AnalysisType addRootField(Field field) { AnalysisField analysisField = getMetaAccess().lookupJavaField(field); + return addRootField(analysisField); + } + + @Override + public AnalysisType addRootField(AnalysisField analysisField) { analysisField.registerAsAccessed("root field"); return analysisField.getType(); } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java index 2df914597394..114c585637ea 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java @@ -29,8 +29,9 @@ import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugArrayTypeInfo; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; -import jdk.vm.ci.meta.ResolvedJavaType; + import jdk.graal.compiler.debug.DebugContext; +import jdk.vm.ci.meta.ResolvedJavaType; public class ArrayTypeEntry extends StructureTypeEntry { private TypeEntry elementType; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java index a840414838f6..f9747acaf638 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java @@ -30,14 +30,11 @@ import java.util.List; import java.util.stream.Stream; +import org.graalvm.collections.EconomicMap; + import com.oracle.objectfile.debugentry.range.PrimaryRange; import com.oracle.objectfile.debugentry.range.Range; import com.oracle.objectfile.debugentry.range.SubRange; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; -import org.graalvm.collections.EconomicMap; -import jdk.graal.compiler.debug.DebugContext; - import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFieldInfo; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugInstanceTypeInfo; @@ -47,6 +44,10 @@ import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; +import jdk.graal.compiler.debug.DebugContext; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + /** * Track debug info associated with a Java class. */ diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java index f012843e1910..47a68ad22445 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java @@ -737,8 +737,8 @@ public String getCachePath() { return cachePath; } - public boolean isHubClassEntry(ClassEntry classEntry) { - return classEntry.getTypeName().equals(DwarfDebugInfo.HUB_TYPE_NAME); + public boolean isHubClassEntry(StructureTypeEntry typeEntry) { + return typeEntry.getTypeName().equals(DwarfDebugInfo.HUB_TYPE_NAME); } public ClassEntry getHubClassEntry() { diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java index 9511fd54ca9e..f46378d57486 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java @@ -26,9 +26,10 @@ package com.oracle.objectfile.debugentry; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugEnumTypeInfo; +import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; + import jdk.graal.compiler.debug.DebugContext; public class EnumClassEntry extends ClassEntry { diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java index 349b755edebf..dc3dae992f16 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java @@ -27,10 +27,11 @@ package com.oracle.objectfile.debugentry; import com.oracle.objectfile.debuginfo.DebugInfoProvider; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugForeignTypeInfo; -import jdk.vm.ci.meta.ResolvedJavaType; +import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; + import jdk.graal.compiler.debug.DebugContext; +import jdk.vm.ci.meta.ResolvedJavaType; public class ForeignTypeEntry extends ClassEntry { private static final int FLAG_WORD = 1 << 0; @@ -57,6 +58,15 @@ public DebugInfoProvider.DebugTypeInfo.DebugTypeKind typeKind() { return DebugInfoProvider.DebugTypeInfo.DebugTypeKind.FOREIGN; } + /* + * When we process a foreign pointer type for the .debug_info section we want to reuse its type + * signature. After sizing the .debug_info the layout type signature contains the type signature + * of the type this foreign pointer points to. + */ + public void setLayoutTypeSignature(long typeSignature) { + this.layoutTypeSignature = typeSignature; + } + @Override public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) { assert debugTypeInfo instanceof DebugForeignTypeInfo; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java index b1836039a441..4ececbccd8bf 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java @@ -26,12 +26,12 @@ package com.oracle.objectfile.debugentry; -import jdk.graal.compiler.debug.DebugContext; - import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugHeaderTypeInfo; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; +import jdk.graal.compiler.debug.DebugContext; + public class HeaderTypeEntry extends StructureTypeEntry { public HeaderTypeEntry(String typeName, int size) { diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java index cb87c014c44c..42b2e7ac2d03 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java @@ -26,14 +26,15 @@ package com.oracle.objectfile.debugentry; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugInterfaceTypeInfo; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; -import jdk.graal.compiler.debug.DebugContext; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; +import jdk.graal.compiler.debug.DebugContext; public class InterfaceClassEntry extends ClassEntry { private final List implementors; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java index d663ac552099..a60ed3b1b1fd 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java @@ -26,14 +26,15 @@ package com.oracle.objectfile.debugentry; +import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_INTEGRAL; +import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_NUMERIC; +import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_SIGNED; + import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; -import jdk.graal.compiler.debug.DebugContext; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_INTEGRAL; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_NUMERIC; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_SIGNED; +import jdk.graal.compiler.debug.DebugContext; public class PrimitiveTypeEntry extends TypeEntry { private char typeChar; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java index 7556cf759a5f..43473ca1cfb2 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java @@ -26,15 +26,18 @@ package com.oracle.objectfile.debugentry; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFieldInfo; -import jdk.vm.ci.meta.ResolvedJavaType; -import jdk.graal.compiler.debug.DebugContext; - import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; +import com.oracle.objectfile.debuginfo.DebugInfoProvider; +import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFieldInfo; +import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo; + +import jdk.graal.compiler.debug.DebugContext; +import jdk.vm.ci.meta.ResolvedJavaType; + /** * An intermediate type that provides behaviour for managing fields. This unifies code for handling * header structures and Java instance and array classes that both support data members. @@ -45,9 +48,21 @@ public abstract class StructureTypeEntry extends TypeEntry { */ protected final List fields; + /** + * The type signature of this types' layout. The layout of a type contains debug info of fields + * and methods of a type, which is needed for representing the class hierarchy. The super type + * entry in the debug info needs to directly contain the type info instead of a pointer. + */ + protected long layoutTypeSignature; + public StructureTypeEntry(String typeName, int size) { super(typeName, size); this.fields = new ArrayList<>(); + this.layoutTypeSignature = 0; + } + + public long getLayoutTypeSignature() { + return layoutTypeSignature; } public Stream fields() { @@ -118,4 +133,15 @@ String memberModifiers(int modifiers) { return builder.toString(); } + + @Override + public void addDebugInfo(@SuppressWarnings("unused") DebugInfoBase debugInfoBase, DebugInfoProvider.DebugTypeInfo debugTypeInfo, @SuppressWarnings("unused") DebugContext debugContext) { + super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); + // header type does not have a separate layout type + if (this instanceof HeaderTypeEntry) { + this.layoutTypeSignature = typeSignature; + } else { + this.layoutTypeSignature = debugTypeInfo.typeSignature(DwarfDebugInfo.LAYOUT_PREFIX); + } + } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java index b6e870ee3d69..4bb5584a768b 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java @@ -26,10 +26,6 @@ package com.oracle.objectfile.debugentry; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; -import jdk.graal.compiler.debug.DebugContext; - import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.ARRAY; import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.ENUM; import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.FOREIGN; @@ -38,12 +34,29 @@ import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.INTERFACE; import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.PRIMITIVE; +import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; +import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; +import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo; + +import jdk.graal.compiler.debug.DebugContext; + public abstract class TypeEntry { /** * The name of this type. */ protected final String typeName; + /** + * The type signature of this type. This is a pointer to the underlying layout of the type. + */ + protected long typeSignature; + + /** + * The type signature for the compressed type. This points to the compressed layout which + * resolves oops to the actual address of an instances. + */ + protected long typeSignatureForCompressed; + /** * The offset of the java.lang.Class instance for this class in the image heap or -1 if no such * object exists. @@ -59,6 +72,16 @@ protected TypeEntry(String typeName, int size) { this.typeName = typeName; this.size = size; this.classOffset = -1; + this.typeSignature = 0; + this.typeSignatureForCompressed = 0; + } + + public long getTypeSignature() { + return typeSignature; + } + + public long getTypeSignatureForCompressed() { + return typeSignatureForCompressed; } public long getClassOffset() { @@ -127,5 +150,12 @@ public boolean isStructure() { public void addDebugInfo(@SuppressWarnings("unused") DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, @SuppressWarnings("unused") DebugContext debugContext) { /* Record the location of the Class instance in the heap if there is one */ this.classOffset = debugTypeInfo.classOffset(); + this.typeSignature = debugTypeInfo.typeSignature(""); + // primitives, header and foreign types are never stored compressed + if (!debugInfoBase.useHeapBase() || this instanceof PrimitiveTypeEntry || this instanceof HeaderTypeEntry || this instanceof ForeignTypeEntry) { + this.typeSignatureForCompressed = typeSignature; + } else { + this.typeSignatureForCompressed = debugTypeInfo.typeSignature(DwarfDebugInfo.COMPRESSED_PREFIX); + } } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java index 2c7be7251690..e8488c87b4f0 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java @@ -129,6 +129,11 @@ public String toString() { */ String typeName(); + /** + * @return a 64bit type signature to uniquely identify the type + */ + long typeSignature(String prefix); + DebugTypeKind typeKind(); /** diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java index d6ba23b7f019..ead4598414a3 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java @@ -26,20 +26,21 @@ package com.oracle.objectfile.elf.dwarf; +import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.AbbrevCode; import com.oracle.objectfile.elf.dwarf.constants.DwarfAttribute; import com.oracle.objectfile.elf.dwarf.constants.DwarfForm; import com.oracle.objectfile.elf.dwarf.constants.DwarfHasChildren; import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; import com.oracle.objectfile.elf.dwarf.constants.DwarfTag; + import jdk.graal.compiler.debug.DebugContext; -import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.AbbrevCode; /** * Section generator for debug_abbrev section. That section defines the layout of the * DWARF Information Entries (DIEs) used to model Java debug info. Top level DIEs define Java - * Compile Units (CUs). Embedded DIEs describe the content of the CU: types, code, variable, etc. - * These definitions are used to interpret the DIE content inserted into the debug_info - * section. + * Compile Units (CUs) and Type Units (TUs). Embedded DIEs describe the content of the CU/TU: types, + * code, variable, etc. These definitions are used to interpret the DIE content inserted into the + * debug_info section. *

      * * An abbrev table contains abbrev entries for one or more DIEs, the last one being a null entry. @@ -62,7 +63,7 @@ *

    1. LEB128 tag; .............. defines the type of the DIE (class, subprogram, var * etc) * - *
    2. uint8 has_chldren; ....... is the DIE followed by child DIEs or a sibling + *
    3. uint8 has_children; ....... is the DIE followed by child DIEs or a sibling * DIE * *
    4. attribute_spec* .......... zero or more attributes @@ -81,19 +82,22 @@ * * * - * For the moment we only use one abbrev table with two types of CU. There is one occurrence of the - * BUILTIN_UNIT CU which includes definitions of Java primitive value types and the - * struct type used to model a Java object header. There are multiple occurrences of the - * CLASS_UNIT CU, one for each Java class, interface or array class included in the - * generated native image binary. The latter describes the class, array or interface layout and - * defines a class, interface or array reference pointer type. It provides declarations for instance - * and static methods and static fields of a class, and methods of an interface. In the case of a - * class it may also include definitions of static fields (i.e. location info) and for a class or - * interface definitions of compiled methods (i.e. code address locations). The latter may include - * details of inlined method frames and top level or inlined parameter or local variable locations. + * For the moment we only use one abbrev table with three types of CU and one type of TU. There is + * one occurrence of the CLASS_CONSTANT_UNIT CU which contains class constant objects + * for all Java types that are defined in the debug info section. There are multiple occurrences of + * the CLASS_UNIT_1 and CLASS_UNIT_2 CUs, one for each Java class, + * interface or array class included in the generated native image binary. The latter differentiate + * of whether they contain compiled methods or not. It provides declarations for instance and static + * methods and static fields of a class, and methods of an interface. In the case of a class it may + * also include definitions of static fields (i.e. location info) and for a class or interface + * definitions of compiled methods (i.e. code address locations). The latter may include details of + * inlined method frames and top level or inlined parameter or local variable locations. There are + * also multiple occurrences of the TYPE_UNIT TU, one up to four for each Java class, + * interface or array class included in the generated native image binary. A type unit describes the + * class, array or interface layout or defines a class, interface or array reference pointer type *

      * - * These two CUs include the following top level and nested DIES + * These CUs and TU include the following top level and nested DIES *

      * * Level 0 DIEs @@ -102,11 +106,18 @@ * *

    5. abbrev_code == null, tag == null - empty terminator * - *
    6. abbrev_code == BUILTIN_UNIT, tag == DW_TAG_compilation_unit - CU that defines - * the Java object header struct and all Java primitive types. + *
    7. abbrev_code == CLASS_CONSTANT_UNIT, tag == DW_TAG_compile_unit - CU that defines + * class constant objects. + * + *
    8. abbrev_code == CLASS_UNIT_1, tag == DW_TAG_compile_unit - CU that defines + * declarations and locations for a specific Java object, interface or array type without + * compilations. * - *
    9. abbrev_code == CLASS_UNIT, tag == DW_TAG_compilation_unit - CU that defines a - * specific Java object, interface or array type. + *
    10. abbrev_code == CLASS_UNIT_2, tag == DW_TAG_compile_unit - CU that defines + * declarations and locations for a specific Java object, interface or array type with compilations. + * + *
    11. abbrev_code == TYPE_UNIT, tag == DW_TAG_type_unit - TU that defines a specific + * Java object, interface or array type. * * * @@ -114,56 +125,72 @@ * *
        * - *
      • abbrev_code == PRIMITIVE_TYPE, tag == DW_TAG_base_type, parent = BUILTIN_UNIT - + *
      • abbrev_code == PRIMITIVE_TYPE, tag == DW_TAG_base_type, parent = TYPE_UNIT - * Java primitive type (non-void) * - *
      • abbrev_code == VOID_TYPE, tag == DW_TAG_unspecified_type, parent = BUILTIN_UNIT - * - Java void type + *
      • abbrev_code == VOID_TYPE, tag == DW_TAG_unspecified_type, parent = TYPE_UNIT - + * Java void type + * + *
      • abbrev_code == OBJECT_HEADER, tag == DW_TAG_structure_type, parent = TYPE_UNIT - + * Java object header * - *
      • abbrev_code == OBJECT_HEADER, tag == DW_TAG_structure_type, parent = BUILTIN_UNIT - * - Java object header + *
      • abbrev_code == CLASS_CONSTANT, tag == DW_TAG_constant, parent = CLASS_CONSTANT_UNIT + * - class constant object * - *
      • abbrev_code == CLASS_LAYOUT, tag == DW_TAG_class_type, parent = CLASS_UNIT - - * Java instance type structure definition + *
      • abbrev_code == CLASS_LAYOUT_1/CLASS_LAYOUT_2, tag == DW_TAG_class_type, parent = TYPE_UNIT + * - Java instance type structure definition * - *
      • abbrev_code == CLASS_POINTER, tag == DW_TAG_pointer_type, parent = CLASS_UNIT - - * Java instance ref type + *
      • abbrev_code == CLASS_LAYOUT_3, tag == DW_TAG_class_type, parent = CLASS_UNIT_1/2 + * - Skeleton Java instance type structure definition for compilation units * - *
      • abbrev_code == METHOD_LOCATION, tag == DW_TAG_subprogram , parent = CLASS_UNIT - - * Java method code definition (i.e. location of code) + *
      • abbrev_code == TYPE_POINTER_SIG, tag == DW_TAG_pointer_type, parent = TYPE_UNIT + * - Points to a type using its type signature and is either of + *
          + *
        • Java instance ref type
        • + *
        • Java interface ref type
        • + *
        • Java array ref type
        • + *
        * - *
      • abbrev_code == STATIC_FIELD_LOCATION, tag == DW_TAG_variable, parent = CLASS_UNIT + *
      • abbrev_code == TYPE_POINTER, tag == DW_TAG_pointer_type, parent = TYPE_UNIT - + * Points to a type using a 4 byte offset and is used for + *
          + *
        • Java compressed ref type
        • - used to type compressed oops that encode the address of an + * object, whether by adding tag bits or representing the address as an offset from some base + * address. these are used to type object references stored in static and instance fields. They are + * not needed when typing local vars and parameters held in registers or on the stack as they appear + * as raw addresses. + *
        + * + *
      • abbrev_code == METHOD_LOCATION, tag == DW_TAG_subprogram , parent = CLASS_UNIT_2 + * - Java method code definition (i.e. location of code) + * + *
      • abbrev_code == STATIC_FIELD_LOCATION, tag == DW_TAG_variable, parent = CLASS_UNIT_1/2 * - Java static field definition (i.e. location of data) * - *
      • abbrev_code == ARRAY_LAYOUT, tag == DW_TAG_structure_type, parent = CLASS_UNIT - - * Java array type structure definition + *
      • abbrev_code == FOREIGN_TYPEDEF, tag == DW_TAG_typedef_type, parent = TYPE_UNIT - + * Definition of a typedef for foreign types * - *
      • abbrev_code == ARRAY_POINTER, tag == DW_TAG_pointer_type, parent = CLASS_UNIT - - * Java array ref type + *
      • abbrev_code == FOREIGN_STRUCT, tag == DW_TAG_structure_type, parent = TYPE_UNIT + * - Foreign structure type definition * - *
      • abbrev_code == INTERFACE_LAYOUT, tag == DW_TAG_union_type, parent = CLASS_UNIT - + *
      • abbrev_code == ARRAY_LAYOUT, tag == DW_TAG_structure_type, parent = TYPE_UNIT - * Java array type structure definition * - *
      • abbrev_code == INTERFACE_POINTER, tag == DW_TAG_pointer_type, parent = CLASS_UNIT - * - Java interface ref type + *
      • abbrev_code == INTERFACE_LAYOUT, tag == DW_TAG_union_type, parent = TYPE_UNIT - + * Java array type structure definition * - *
      • abbrev_code == INDIRECT_LAYOUT, tag == DW_TAG_class_type, parent = CLASS_UNIT - + *
      • abbrev_code == COMPRESSED_LAYOUT, tag == DW_TAG_class_type, parent = TYPE_UNIT - * wrapper layout attaches address rewriting logic to the layout types that it wraps using a * data_location attribute * - *
      • abbrev_code == INDIRECT_POINTER, tag == DW_TAG_pointer_type, parent = CLASS_UNIT - * - indirect ref type used to type indirect oops that encode the address of an object, whether by - * adding tag bits or representing the address as an offset from some base address. these are used - * to type object references stored in static and instance fields. They are not needed when typing - * local vars and parameters held in registers or on the stack as they appear as raw addresses. - * - *
      • abbrev_code == NAMESPACE, tag == DW_TAG_namespace, parent = CLASS_UNIT - a - * wrap-around DIE that is used to embed all the normal level 1 DIEs of a CLASS_UNIT in - * a namespace. This is needed when the corresponding class/interface or array base element type - * have been loaded by a loader with a non-empty loader in order to ensure that mangled names for - * the class and its members can legitimately employ the loader id as a namespace prefix. Note that - * use of a namespace wrapper DIE causes all the embedded level 1+ DIEs documented above and all - * their children to be generated at a level one greater than documented here. + *
      • abbrev_code == NAMESPACE, tag == DW_TAG_namespace, parent = TYPE_UNIT, CLASS_UNIT_1/2 + * - a wrap-around DIE that is used to embed all the normal level 1 DIEs of a + * CLASS_UNIT in a namespace. This is needed when the corresponding class/interface or + * array base element type have been loaded by a loader with a non-empty loader in order to ensure + * that mangled names for the class and its members can legitimately employ the loader id as a + * namespace prefix. Note that use of a namespace wrapper DIE causes all the embedded level 1+ DIEs + * documented above and all their children to be generated at a level one greater than documented + * here. * *
      * @@ -175,12 +202,16 @@ * object/array header field * *
    12. abbrev_code == METHOD_DECLARATION/METHOD_DECLARATION_STATIC, tag == DW_TAG_subprogram, - * parent = CLASS_LAYOUT, INTERFACE_LAYOUT + * parent = CLASS_LAYOUT_3 + * + *
    13. abbrev_code == METHOD_DECLARATION_SKELETON, tag == DW_TAG_subprogram, + * parent = CLASS_LAYOUT_1/2, INTERFACE_LAYOUT - a minimal method declaration to avoid + * unnecessary duplication of debug info * *
    14. abbrev_code == FIELD_DECLARATION_1/2/3/4, tag == DW_TAG_member, parent = CLASS_LAYOUT * - instance field declaration (i.e. specification of properties) * - *
    15. abbrev_code == SUPER_REFERENCE, tag == DW_TAG_inheritance, parent = CLASS_LAYOUT, + *
    16. abbrev_code == SUPER_REFERENCE, tag == DW_TAG_inheritance, parent = CLASS_LAYOUT_1/2, * ARRAY_LAYOUT - reference to super class layout or to appropriate header struct for {code * java.lang.Object} or arrays. * @@ -191,7 +222,7 @@ * parent = METHOD_LOCATION/INLINED_SUBROUTINE_WITH_CHILDREN - provides range and * abstract origin for an inlined subroutine * - *
    17. abbrev_code == METHOD_PARAMETER_DECLARATION_1/2/3, tag == DW_TAG_formal_parameter, parent = + *
    18. abbrev_code == METHOD_PARAMETER_DECLARATION_1/2/3/4/5, tag == DW_TAG_formal_parameter, parent = * METHOD_DECLARATION/METHOD_DECLARATION_STATIC - details of method parameters * *
    19. abbrev_code == METHOD_LOCAL_DECLARATION_1/2, tag == DW_TAG_variable, parent = @@ -203,27 +234,32 @@ * *
        * - *
      • abbrev_code == METHOD_LOCAL_LOCATION, tag == DW_TAG_formal_parameter, parent = - * METHOD_LOCATION - details of method parameter or local locations + *
      • abbrev_code == METHOD_PARAMETER_LOCATION_1/2, tag == DW_TAG_formal_parameter, parent = + * METHOD_LOCATION - details of method parameter locations + * + *
      • abbrev_code == METHOD_LOCAL_LOCATION_1/2, tag == DW_TAG_variable, parent = + * METHOD_LOCATION - details of method local locations * *
      * * Detailed layouts of the DIEs listed above are as follows: *

      * - * A single instance of the level 0 BUILTIN_UNIT compile unit provide details of all - * Java primitive types and the struct type used to model a Java object header + * A single instance of the level 0 CLASS_CONSTANT_UNIT CU provides a class constant + * object for all types in the image * *

        * - *
      • abbrev_code == BUILTIN_UNIT, tag == DW_TAG_compilation_unit, + *
      • abbrev_code == CLASS_CONSTANT_UNIT, tag == DW_TAG_compile_unit, * has_children * *
      • DW_AT_language : ... DW_FORM_data1 * + *
      • DW_AT_use_UTF8 : ... DW_FORM_flag + * *
      • DW_AT_name : ....... DW_FORM_strp * - *
      • DW_AT_use_UTF8 : ... DW_FORM_flag + *
      • DW_AT_comp_dir : ... DW_FORM_strp * *
      * @@ -232,25 +268,43 @@ * *
        * - *
      • abbrev_code == CLASS_UNIT1/2, tag == DW_TAG_compilation_unit, + *
      • abbrev_code == CLASS_UNIT_1/2, tag == DW_TAG_compile_unit, * has_children * - *
      • DW_AT_language : ... DW_FORM_data1 + *
      • DW_AT_language : ....... DW_FORM_data1 * - *
      • DW_AT_name : ....... DW_FORM_strp + *
      • DW_AT_use_UTF8 : ....... DW_FORM_flag * - *
      • DW_AT_comp_dir : ... DW_FORM_strp + *
      • DW_AT_name : ........... DW_FORM_strp * - *
      • DW_AT_low_pc : ..... DW_FORM_address only for CLASS_UNIT1 + *
      • DW_AT_comp_dir : ....... DW_FORM_strp * - *
      • DW_AT_hi_pc : ...... DW_FORM_address only for CLASS_UNIT1 + *
      • DW_AT_ranges : ......... DW_FORM_sec_offset only for CLASS_UNIT_2 * - *
      • DW_AT_use_UTF8 : ... DW_FORM_flag + *
      • DW_AT_low_pc : ......... DW_FORM_address only for CLASS_UNIT_2 + * + *
      • DW_AT_stmt_list : ...... DW_FORM_sec_offset only for CLASS_UNIT_2 + * + *
      • DW_AT_loclists_base : .. DW_FORM_sec_offset only for CLASS_UNIT_2 + * + *
      + * + * Instances of the level 0 TYPE_UNIT type unit provide details of all Java object + * types. * - *
    20. DW_AT_stmt_list : .. DW_FORM_sec_offset + *
        + * + *
      • abbrev_code == TYPE_UNIT, tag == DW_TAG_type_unit, + * has_children + * + *
      • DW_AT_language : ....... DW_FORM_data1 + * + *
      • DW_AT_use_UTF8 : ....... DW_FORM_flag * *
      * + * + * * Primitive Types: For each non-void Java primitive type there is a level 1 DIE defining a base * type * @@ -300,13 +354,13 @@ * *
    21. abbrev_code = HEADER_FIELD, tag == DW_TAG_member, no_children * - *
    22. Dw_AT_name : ................... DW_FORM_strp + *
    23. DW_AT_name : ................... DW_FORM_strp * - *
    24. Dw_AT_type : ................... DW_FORM_ref_addr + *
    25. DW_AT_type : ................... DW_FORM_ref_addr * - *
    26. Dw_AT_data_member_location : ... DW_FORM_data1 + *
    27. DW_AT_data_member_location : ... DW_FORM_data1 * - *
    28. Dw_AT_accessibility : .......... DW_FORM_data1 + *
    29. DW_AT_accessibility : .......... DW_FORM_data1 * * * @@ -337,22 +391,26 @@ * which a debugger can use to decode the oop pointer to a raw address. n.b. this only applies in * the case where normal oop references are raw addresses (no compressed oops, no isolates). If a * heapbase register is being used then decoding logic is encoded for both normal classes and for - * java.lang.Class using an indirect layout (see below). + * java.lang.Class using a compressed layout (see below). * *
        * - *
      • abbrev_code == CLASS_LAYOUT1/CLASS_LAYOUT2, tag == DW_TAG_class_type, + *
      • abbrev_code == CLASS_LAYOUT1/2/3, tag == DW_TAG_class_type, * has_children * - *
      • Dw_AT_name : ........ DW_FORM_strp + *
      • DW_AT_name : ........ DW_FORM_strp * - *
      • Dw_AT_byte_size : ... DW_FORM_data1/2 + *
      • DW_AT_declaration : . DW_FORM_flag only for CLASS_LAYOUT_3 * - *
      • Dw_AT_decl_file : ... DW_FORM_data1/2 + *
      • DW_AT_signature : ... DW_FORM_ref_sig8 only for CLASS_LAYOUT_3 * - *
      • Dw_AT_decl_line : ... DW_FORM_data1/2 + *
      • DW_AT_byte_size : ... DW_FORM_data1/2 only for CLASS_LAYOUT_1/2 * - *
      • Dw_AT_data_location : ... DW_FORM_expr_loc n.b. only for CLASS_LAYOUT2 + *
      • DW_AT_decl_file : ... DW_FORM_data1/2 only for CLASS_LAYOUT_1/2 + * + *
      • DW_AT_data_location : ... DW_FORM_expr_loc only for CLASS_LAYOUT_2 - contains + * the decoding logic for the layout of java.lang.Class if no compressed layout is + * available * *
      * @@ -373,20 +431,21 @@ * *
        * - *
      • abbrev_code == METHOD_DECLARATION/METHOD_DECLARATION_STATIC, tag == DW_TAG_subprogram, + *
      • abbrev_code == METHOD_DECLARATION/METHOD_DECLARATION_STATIC/METHOD_DECLARATION_SKELETON, tag == DW_TAG_subprogram, * has_children * *
      • DW_AT_external : .......... DW_FORM_flag * - *
      • Dw_AT_name : .............. DW_FORM_strp + *
      • DW_AT_name : .............. DW_FORM_strp * - *
      • DW_AT_decl_file : ......... DW_FORM_data1/2 + *
      • DW_AT_decl_file : ......... DW_FORM_data1/2 only for + * METHOD_DECLARATION/METHOD_DECLARATION_STATIC * - *
      • DW_AT_decl_line : ......... DW_FORM_data1/2 + *
      • DW_AT_decl_line : ......... DW_FORM_data1/2 only for METHOD_DECLARATION/METHOD_DECLARATION_STATIC * - *
      • Dw_AT_linkage_name : ...... DW_FORM_strp + *
      • DW_AT_linkage_name : ...... DW_FORM_strp * - *
      • Dw_AT_type : .............. DW_FORM_ref_addr (optional!!) + *
      • DW_AT_type : .............. DW_FORM_ref_sig8 (optional!!) * *
      • DW_AT_artificial : ........ DW_FORM_flag * @@ -394,12 +453,13 @@ * *
      • DW_AT_declaration : ....... DW_FORM_flag * - *
      • Dw_AT_object_pointer : .... DW_FORM_ref4 n.b. only for METHOD_DECLARATION, - * points to param 0 DIE + *
      • DW_AT_object_pointer : .... DW_FORM_ref4 only for METHOD_DECLARATION, points to + * param 0 DIE * - *
      • DW_AT_virtuality : ........ DW_FORM_data1 (for override methods) + *
      • DW_AT_virtuality : ........ DW_FORM_data1 (for override methods) only for METHOD_DECLARATION/METHOD_DECLARATION_STATIC * - *
      • DW_AT_containing_type : ... DW_FORM_ref4 (for override methods) + *
      • DW_AT_containing_type : ... DW_FORM_ref_sig8 (for override methods) only for + * METHOD_DECLARATION/METHOD_DECLARATION_STATIC * *
      * @@ -407,7 +467,7 @@ * *
    30. abbrev_code == FIELD_DECLARATION_1/2/3/4, tag == DW_TAG_member, no_children * - *
    31. Dw_AT_name : ................... DW_FORM_strp + *
    32. DW_AT_name : ................... DW_FORM_strp * *
    33. DW_AT_decl_file : .............. DW_FORM_data1/2 n.b. only for * FIELD_DECLARATION_2/4 @@ -415,19 +475,19 @@ *
    34. DW_AT_decl_line : .............. DW_FORM_data1/2 n.b. only for * FIELD_DECLARATION_2/4 * - *
    35. Dw_AT_type : ................... DW_FORM_ref_addr + *
    36. DW_AT_type : ................... DW_FORM_ref_sig8 * - *
    37. Dw_AT_data_member_location : ... DW_FORM_data1/2 (n.b. nly for + *
    38. DW_AT_data_member_location : ... DW_FORM_data1/2 (n.b. nly for * FIELD_DECLARATION_1/2 instance * - *
    39. Dw_AT_artificial : ............. DW_FORM_flag + *
    40. DW_AT_artificial : ............. DW_FORM_flag * - *
    41. Dw_AT_accessibility : .......... DW_FORM_data1 + *
    42. DW_AT_accessibility : .......... DW_FORM_data1 * - *
    43. Dw_AT_external : ............... DW_FORM_flag (n.b. only for + *
    44. DW_AT_external : ............... DW_FORM_flag (n.b. only for * FIELD_DECLARATION_3/4 static * - *
    45. Dw_AT_declaration : ............ DW_FORM_flag n.b. only for + *
    46. DW_AT_declaration : ............ DW_FORM_flag n.b. only for * FIELD_DECLARATION_3/4 static * * @@ -436,11 +496,11 @@ * *
    47. abbrev_code == SUPER_REFERENCE, tag == DW_TAG_inheritance, no_children * - *
    48. Dw_AT_type : ................... DW_FORM_ref_addr + *
    49. DW_AT_type : ................... DW_FORM_ref_sig8 * - *
    50. Dw_AT_data_member_location : ... DW_FORM_data1/2 + *
    51. DW_AT_data_member_location : ... DW_FORM_data1/2 * - *
    52. Dw_AT_accessibility :........... DW_FORM_data1 + *
    53. DW_AT_accessibility :........... DW_FORM_data1 * * * @@ -450,92 +510,83 @@ * *
        * - *
      • abbrev_code == METHOD_PARAMETER_DECLARATION_1/2/3, tag == + *
      • abbrev_code == METHOD_PARAMETER_DECLARATION_1/2/3/4/5, tag == * DW_TAG_formal_parameter, no_children * - *
      • Dw_AT_name : ... DW_FORM_strp (may be empty string) + *
      • DW_AT_name : ... DW_FORM_strp (may be empty string) only for + * METHOD_PARAMETER_DECLARATION_1/2/3 * - *
      • Dw_AT_file : ... DW_FORM_data1/2 n.b. only for METHOD_PARAMETER_DECLARATION_2 + *
      • DW_AT_file : ... DW_FORM_data1/2 only for METHOD_PARAMETER_DECLARATION_2 * - *
      • Dw_AT_line : ... DW_FORM_data1/2 n.b. only for METHOD_PARAMETER_DECLARATION_2 + *
      • DW_AT_line : ... DW_FORM_data1/2 only for METHOD_PARAMETER_DECLARATION_2 * - *
      • Dw_AT_type : ... DW_FORM_ref_addr + *
      • DW_AT_type : ... DW_FORM_ref_sig8 * - *
      • Dw_AT_artificial : ... DW_FORM_flag n.b. only for METHOD_PARAMETER_DECLARATION_1 + *
      • DW_AT_artificial : ... DW_FORM_flag only for METHOD_PARAMETER_DECLARATION_1/4 * used for this and access vars * - *
      • Dw_AT_declaration : ... DW_FORM_flag + *
      • DW_AT_declaration : ... DW_FORM_flag * *
      * *
    54. abbrev_code == METHOD_LOCAL_DECLARATION_1/2, tag == DW_TAG_variable, * no_children * - *
    55. Dw_AT_name : ... DW_FORM_strp (may be empty string) + *
    56. DW_AT_name : ... DW_FORM_strp (may be empty string) * - *
    57. Dw_AT_file : ... DW_FORM_data1/2 n.b. only for METHOD_PARAMETER_DECLARATION_1 + *
    58. DW_AT_file : ... DW_FORM_data1/2 n.b. only for METHOD_PARAMETER_DECLARATION_1 * - *
    59. Dw_AT_line : ... DW_FORM_data1/2 n.b. only for METHOD_PARAMETER_DECLARATION_1 + *
    60. DW_AT_line : ... DW_FORM_data1/2 n.b. only for METHOD_PARAMETER_DECLARATION_1 * - *
    61. Dw_AT_type : ... DW_FORM_ref_addr + *
    62. DW_AT_type : ... DW_FORM_ref_sig8 * - *
    63. Dw_AT_declaration : ... DW_FORM_flag + *
    64. DW_AT_declaration : ... DW_FORM_flag * * * - * Indirect Instance Class Structure: The level 1 class layout DIE may be followed by a level 1 - * INDIRECT_LAYOUT DIE. The indirect layout is only needed when a heapbase register is - * in use (isolates or compressed oops are enabled). This means that oop fields will hold encoded - * oops. The indirect layout defines an empty wrapper class which declares the previous layout as + * Compressed Instance Class Structure: The level 1 class layout DIE may be followed by a level 1 + * COMPRESSED_LAYOUT DIE. The compressed layout is only needed when a heapbase register + * is in use (isolates or compressed oops are enabled). This means that oop fields will hold encoded + * oops. The compressed layout defines an empty wrapper class which declares the previous layout as * its super class. This wrapper type also supplies a data_location attribute, ensuring - * that indirect pointers to the class (see next item) are translated to raw addresses. The name of - * the indirect type is constructed by prefixing the class name with - * DwarfDebugInfo.INDIRECT_PREFIX. This DIE has only one child DIE with type - * SUPER_REFERENCE (see above). This effectively embeds the standard layout type in the indirect - * layout as a type compatible referent for the Java oop. The size of the indirect layout is the + * that compressed pointers to the class (see next item) are translated to raw addresses. The name + * of the compressed type is constructed by prefixing the class name with + * DwarfDebugInfo.COMPRESSED_PREFIX. This DIE has only one child DIE with type + * SUPER_REFERENCE (see above). This effectively embeds the standard layout type in the compressed + * layout as a type compatible referent for the Java oop. The size of the compressed layout is the * same as the size of the class layout. * *
        * - *
      • abbrev_code == INDIRECT_LAYOUT, tag == DW_TAG_class_type, has_children + *
      • abbrev_code == COMPRESSED_LAYOUT, tag == DW_TAG_class_type, has_children * - *
      • Dw_AT_name : ........ DW_FORM_strp + *
      • DW_AT_name : ........ DW_FORM_strp * - *
      • Dw_AT_byte_size : ... DW_FORM_data1/2 + *
      • DW_AT_byte_size : ... DW_FORM_data1/2 * - *
      • Dw_AT_data_location : ... DW_FORM_expr_loc + *
      • DW_AT_data_location : ... DW_FORM_expr_loc * *
      * * Instance Class Reference Types: The level 1 CLASS_LAYOUT and - * INDIRECT_LAYOUT DIEs are followed by level 1 DIEs defining pointers to the - * respective class layouts. A CLASS_POINTER DIE defines a pointer type for the + * COMPRESSED_LAYOUT DIEs are followed by level 1 DIEs defining pointers to the + * respective class layouts. A TYPE_POINTER DIE defines a pointer type for the * CLASS_LAYOUT type and is used to type pointers which directly address an instance. * It is used to type local and parameter var references whether located in a register or on the - * stack. It may be followed by an INDIRECT_POINTER DIE which defines a pointer type - * for the class's INDIRECT_LAYOUT type. This is used to type references to instances + * stack. It may be followed by another TYPE_POINTER DIE which defines a pointer type + * for the class's COMPRESSED_LAYOUT type. This is used to type references to instances * of the class located in a static or instance field. These latter references require address * translation by masking off tag bits and/or rebasing from an offset to a raw address. The logic * for this translation is encoded in the data_location attribute of the corresponding - * INDIRECT_LAYOUT DIE. - * - *
        - * - *
      • abbrev_code == CLASS_POINTER, tag == DW_TAG_pointer_type, no_children - * - *
      • Dw_AT_byte_size : ... DW_FORM_data1 - * - *
      • Dw_AT_type : ........ DW_FORM_ref4 - * - *
      + * COMPRESSED_LAYOUT DIE. * *
        * - *
      • abbrev_code == INDIRECT_POINTER, tag == DW_TAG_pointer_type, no_children + *
      • abbrev_code == TYPE_POINTER, tag == DW_TAG_pointer_type, no_children * - *
      • Dw_AT_byte_size : ... DW_FORM_data1 + *
      • DW_AT_byte_size : ... DW_FORM_data1 * - *
      • Dw_AT_type : ........ DW_FORM_ref4 + *
      • DW_AT_type : ........ DW_FORM_ref_sig8 * *
      * @@ -566,7 +617,7 @@ * *
        * - *
      • abbrev_code == DW_ABBREV_CODE_METHOD_LOCATION, tag == DW_TAG_subprogram, + *
      • abbrev_code == METHOD_LOCATION, tag == DW_TAG_subprogram, * has_children * *
      • DW_AT_low_pc : .......... DW_FORM_addr @@ -575,28 +626,42 @@ * *
      • DW_AT_external : ........ DW_FORM_flag * - *
      • DW_AT_specification : ... DW_FORM_ref4 + *
      • DW_AT_specification : ... DW_FORM_ref_addr * *
      * * Method local locations: A method location may be followed by zero or more - * METHOD_LOCAL_LOCATION DIEs which identify the in-memory location of parameter and/or - * local values during execution of the compiled code. A METHOD_LOCAL_LOCATION DIE - * references the corresponding METHOD_PARAMETER_DECLARATION or * - * METHOD_LOCAL_DECLARATION. It also specifies a location list which defines address - * ranges where the parameter or local is valid and provides details of where to find the value of - * the parameter or local in memory. Likewise, an inlined subroutine DIE is followed by zero or more + * METHOD_PARAMETER_LOCATION DIEs which identify the in-memory location of parameter + * values METHOD_LOCAL_LOCATION DIEs which identify the in-memory location of local + * values during execution of the compiled code. A + * METHOD_PARAMETER_LOCATION/METHOD_LOCAL_LOCATION DIE references the + * corresponding METHOD_PARAMETER_DECLARATION/METHOD_LOCAL_DECLARATION. It + * also specifies a location list which defines address ranges where the parameter or local is valid + * and provides details of where to find the value of the parameter or local in memory. Likewise, an + * inlined subroutine DIE is followed by zero or more METHOD_PARAMETER_LOCATION and * METHOD_LOCAL_LOCATION DIEs, providing details of where to find the specification of * inlined parameters or locals and their value in memory. * *
        * - *
      • abbrev_code == DW_ABBREV_CODE_METHOD_LOCAL_LOCATION1/2, tag == + *
      • abbrev_code == METHOD_PARAMETER_LOCATION1/2, tag == * DW_TAG_formal_parameter, no_children * - *
      • DW_AT_specification : .......... DW_FORM_ref4 + *
      • DW_AT_abstract_origin : ........ DW_FORM_ref_addr + * + *
      • DW_AT_location: ................ DW_FORM_loclistx only for + * METHOD_PARAMETER_LOCATION2 + * + *
      * - *
    65. DW_AT_location: ................ DW_FORM_sec_offset n.b. only for + *
        + * + *
      • abbrev_code == METHOD_LOCAL_LOCATION1/2, tag == + * DW_TAG_variable, no_children + * + *
      • DW_AT_abstract_origin : ........ DW_FORM_ref_addr + * + *
      • DW_AT_location: ................ DW_FORM_loclistx only for * METHOD_LOCAL_LOCATION2 * *
      @@ -618,7 +683,7 @@ * *
        * - *
      • abbrev_code == DW_ABBREV_CODE_abstract_inline_method, tag == DW_TAG_subprogram, + *
      • abbrev_code == ABSTRACT_INLINE_METHOD, tag == DW_TAG_subprogram, * has_children * *
      • DW_AT_inline : .......... DW_FORM_data1 @@ -645,10 +710,10 @@ * *
          * - *
        • abbrev_code == DW_ABBREV_CODE_INLINED_SUBROUTINE, tag == DW_TAG_subprogram, + *
        • abbrev_code == INLINED_SUBROUTINE, tag == DW_TAG_subprogram, * no_children * - *
        • abbrev_code == DW_ABBREV_CODE_INLINED_SUBROUTINE_WITH_CHILDREN, tag == + *
        • abbrev_code == INLINED_SUBROUTINE_WITH_CHILDREN, tag == * DW_TAG_subprogram, has_children * *
        • DW_AT_abstract_origin : ... DW_FORM_ref4 @@ -703,36 +768,26 @@ * *
        • abbrev_code == ARRAY_LAYOUT, tag == DW_TAG_class_type, has_children * - *
        • Dw_AT_name : ........ DW_FORM_strp + *
        • DW_AT_name : ........ DW_FORM_strp * - *
        • Dw_AT_byte_size : ... DW_FORM_data1/2 + *
        • DW_AT_byte_size : ... DW_FORM_data1/2 * *
        * - * The immediately following DIE is an INDIRECT_LAYOUT (see above) that wraps the array layout as + * The immediately following DIE is an COMPRESSED_LAYOUT (see above) that wraps the array layout as * its super type (just as with class layouts). The wrapper type supplies a data_location attribute, - * allowing indirect pointers to the array to be translated to raw addresses. The name of the - * indirect array type is constructed by prefixing the array name with - * DwarfDebugInfo.INDIRECT_PREFIX. This DIE has only one child DIE with type + * allowing compressed pointers to the array to be translated to raw addresses. The name of the + * compressed array type is constructed by prefixing the array name with + * DwarfDebugInfo.COMPRESSED_PREFIX. This DIE has only one child DIE with type * SUPER_REFERENCE (see above). The latter references the array layout DIE, effectively - * embedding the standard array layout type in the indirect layout. The size of the indirect layout - * is the same as the size of the array layout. + * embedding the standard array layout type in the compressed layout. The size of the compressed + * layout is the same as the size of the array layout. *

        * * The third and fourth DIEs define array reference types as a pointers to the underlying structure - * layout types. As with classes, there is an ARRAY_POINTER type for raw address references used to - * type local and param vars and an INDIRECT_POINTER type (see above) for array references stored in - * static and instance fields. - * - *

          - * - *
        • abbrev_code == ARRAY_POINTER, tag == DW_TAG_pointer_type, no_children - * - *
        • Dw_AT_byte_size : ... DW_FORM_data1 - * - *
        • Dw_AT_type : ........ DW_FORM_ref4 - * - *
        + * layout types. As with classes, there is a TYPE_POINTER type for raw address references used to + * type local and param vars and a (compressed) TYPE_POINTER type (see above) for array references + * stored in static and instance fields. * * n.b. the name used in the ARRAY_LAYOUT DIE is the Java array name. This is deliberately * inconsistent with the Java naming where the name refers to the pointer type. As with normal @@ -745,11 +800,13 @@ * *
          * - *
        • abbrev_code == array_data_type, tag == DW_TAG_array_type, no_children + *
        • abbrev_code == ARRAY_DATA_TYPE_1, tag == DW_TAG_array_type, no_children + * + *
        • abbrev_code == ARRAY_DATA_TYPE_2, tag == DW_TAG_array_type, children * - *
        • Dw_AT_byte_size : ... DW_FORM_data1 + *
        • DW_AT_byte_size : ... DW_FORM_data4 only ARRAY_DATA_TYPE_2 * - *
        • Dw_AT_type : ........ DW_FORM_ref_addr + *
        • DW_AT_type : ........ DW_FORM_ref_sig8 * *
        * @@ -767,23 +824,23 @@ * *
      • abbrev_code == INTERFACE_LAYOUT, DW_TAG_union_type, has_children * - *
      • Dw_AT_name : ....... DW_FORM_strp + *
      • DW_AT_name : ....... DW_FORM_strp * *
      * - * A second level 1 DIE provides an indirect layout that wraps the interface layout as its super + * A second level 1 DIE provides a compressed layout that wraps the interface layout as its super * type (just as with class layouts). The wrapper type supplies a data_location - * attribute, allowing indirect pointers to the interface to be translated to raw addresses. The - * name of the indirect interface type is constructed by prefixing the interface name with - * DwarfDebugInfo.INDIRECT_PREFIX. This DIE has only one child DIE with type + * attribute, allowing compressed pointers to the interface to be translated to raw addresses. The + * name of the compressed interface type is constructed by prefixing the interface name with + * DwarfDebugInfo.COMPRESSED_PREFIX. This DIE has only one child DIE with type * sup[er_reference (see above). The latter references the interface layout DIE, - * effectively embedding the standard interface layout type in the indirect layout. The size of the - * indirect layout is the same as the size of the interface layout. + * effectively embedding the standard interface layout type in the compressed layout. The size of + * the compressed layout is the same as the size of the interface layout. * * The third and fourth DIEs define interface reference types as a pointers to the underlying - * structure layout types. As with classes, there is an INTERFACE_POINTER type for raw address - * references used to type local and param vars and an INDIRECT_POINTER type (see above) for - * interface references stored in static and instance fields. + * structure layout types. As with classes, there is an TYPE_POINTER type for raw address references + * used to type local and param vars and a (compressed) TYPE_POINTER type (see above) for interface + * references stored in static and instance fields. * * A second level 1 defines a pointer to this layout type. * @@ -792,16 +849,6 @@ * normal objects an interface reference in a Java signature appears as a pointer to an interface * layout when printed by gdb. * - *
        - * - *
      • abbrev_code == INTERFACE_POINTER, tag == DW_TAG_pointer_type, has_children - * - *
      • Dw_AT_byte_size : ... DW_FORM_data1 - * - *
      • DW_AT_TYPE : ....... DW_FORM_ref4 - * - *
      - * * The union type embeds level 2 DIEs with tag member. There is a member for each implementing * class, typed using the layout. * @@ -809,11 +856,11 @@ * *
    66. abbrev_code == INTERFACE_IMPLEMENTOR, tag == DW_TAG_member, no_children * - *
    67. Dw_AT_name : ................... DW_FORM_strp + *
    68. DW_AT_name : ................... DW_FORM_strp * - *
    69. Dw_AT_type : ................... DW_FORM_ref_addr + *
    70. DW_AT_type : ................... DW_FORM_ref_sig8 * - *
    71. Dw_AT_accessibility : .......... DW_FORM_data1 + *
    72. DW_AT_accessibility : .......... DW_FORM_data1 * * * @@ -834,7 +881,7 @@ public class DwarfAbbrevSectionImpl extends DwarfSectionImpl { public DwarfAbbrevSectionImpl(DwarfDebugInfo dwarfSections) { // abbrev section depends on ranges section - super(dwarfSections, DwarfSectionName.DW_ABBREV_SECTION, DwarfSectionName.DW_RANGES_SECTION); + super(dwarfSections, DwarfSectionName.DW_ABBREV_SECTION, DwarfSectionName.DW_RNGLISTS_SECTION); } @Override @@ -868,30 +915,29 @@ public void writeContent(DebugContext context) { public int writeAbbrevs(DebugContext context, byte[] buffer, int p) { int pos = p; pos = writeCompileUnitAbbrevs(context, buffer, pos); + pos = writeTypeUnitAbbrev(context, buffer, pos); pos = writePrimitiveTypeAbbrev(context, buffer, pos); pos = writeVoidTypeAbbrev(context, buffer, pos); pos = writeObjectHeaderAbbrev(context, buffer, pos); + pos = writeClassConstantAbbrev(context, buffer, pos); pos = writeNamespaceAbbrev(context, buffer, pos); pos = writeClassLayoutAbbrevs(context, buffer, pos); - pos = writeClassReferenceAbbrev(context, buffer, pos); + pos = writeClassReferenceAbbrevs(context, buffer, pos); pos = writeMethodDeclarationAbbrevs(context, buffer, pos); pos = writeFieldDeclarationAbbrevs(context, buffer, pos); - pos = writeClassConstantAbbrev(context, buffer, pos); pos = writeArrayLayoutAbbrev(context, buffer, pos); - pos = writeArrayReferenceAbbrev(context, buffer, pos); pos = writeInterfaceLayoutAbbrev(context, buffer, pos); - pos = writeInterfaceReferenceAbbrev(context, buffer, pos); - pos = writeForeignReferenceAbbrev(context, buffer, pos); pos = writeForeignTypedefAbbrev(context, buffer, pos); pos = writeForeignStructAbbrev(context, buffer, pos); pos = writeHeaderFieldAbbrev(context, buffer, pos); + pos = writeArrayElementFieldAbbrev(context, buffer, pos); pos = writeArrayDataTypeAbbrevs(context, buffer, pos); pos = writeArraySubrangeTypeAbbrev(context, buffer, pos); pos = writeMethodLocationAbbrev(context, buffer, pos); @@ -904,17 +950,16 @@ public int writeAbbrevs(DebugContext context, byte[] buffer, int p) { pos = writeInlinedSubroutineAbbrev(buffer, pos, true); /* - * if we address rebasing is required then then we need to use indirect layout types - * supplied with a suitable data_location attribute and indirect pointer types to ensure - * that gdb converts offsets embedded in static or instance fields to raw pointers. - * Transformed addresses are typed using pointers to the underlying layout. + * if we address rebasing is required then we need to use compressed layout types supplied + * with a suitable data_location attribute and compressed pointer types to ensure that gdb + * converts offsets embedded in static or instance fields to raw pointers. Transformed + * addresses are typed using pointers to the underlying layout. * - * if address rebasing is not required then we a data_location attribute on the layout type + * if address rebasing is not required then a data_location attribute on the layout type * will ensure that address tag bits are removed. */ if (dwarfSections.useHeapBase()) { - pos = writeIndirectLayoutAbbrev(context, buffer, pos); - pos = writeIndirectReferenceAbbrev(context, buffer, pos); + pos = writeCompressedLayoutAbbrev(context, buffer, pos); } pos = writeParameterDeclarationAbbrevs(context, buffer, pos); @@ -942,9 +987,10 @@ private int writeHasChildren(DwarfHasChildren hasChildren, byte[] buffer, int po private int writeCompileUnitAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; - pos = writeCompileUnitAbbrev(context, AbbrevCode.BUILTIN_UNIT, buffer, pos); + pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_CONSTANT_UNIT, buffer, pos); pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_UNIT_1, buffer, pos); pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_UNIT_2, buffer, pos); + pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_UNIT_3, buffer, pos); return pos; } @@ -961,13 +1007,17 @@ private int writeCompileUnitAbbrev(@SuppressWarnings("unused") DebugContext cont pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_comp_dir, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos); - if (abbrevCode == AbbrevCode.CLASS_UNIT_2) { + if (abbrevCode == AbbrevCode.CLASS_UNIT_2 || abbrevCode == AbbrevCode.CLASS_UNIT_3) { pos = writeAttrType(DwarfAttribute.DW_AT_ranges, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_sec_offset, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_low_pc, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_stmt_list, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_sec_offset, buffer, pos); + if (abbrevCode == AbbrevCode.CLASS_UNIT_3) { + pos = writeAttrType(DwarfAttribute.DW_AT_loclists_base, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_sec_offset, buffer, pos); + } } /* * Now terminate. @@ -977,6 +1027,23 @@ private int writeCompileUnitAbbrev(@SuppressWarnings("unused") DebugContext cont return pos; } + private int writeTypeUnitAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { + int pos = p; + pos = writeAbbrevCode(AbbrevCode.TYPE_UNIT, buffer, pos); + pos = writeTag(DwarfTag.DW_TAG_type_unit, buffer, pos); + pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_language, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_use_UTF8, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); + /* + * Now terminate. + */ + pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos); + return pos; + } + private int writePrimitiveTypeAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; pos = writeAbbrevCode(AbbrevCode.PRIMITIVE_TYPE, buffer, pos); @@ -1048,6 +1115,7 @@ private int writeNamespaceAbbrev(@SuppressWarnings("unused") DebugContext contex private int writeClassLayoutAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_1, buffer, pos); + pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_3, buffer, pos); if (!dwarfSections.useHeapBase()) { pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_2, buffer, pos); } @@ -1062,18 +1130,25 @@ private int writeClassLayoutAbbrev(@SuppressWarnings("unused") DebugContext cont pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos); - pos = writeAttrType(DwarfAttribute.DW_AT_byte_size, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); - pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); - /*- - * At present we definitely don't have a line number for the class itself. - pos = writeAttrType(DwarfDebugInfo.DW_AT_decl_line, buffer, pos); - pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data2, buffer, pos); - */ - if (abbrevCode == AbbrevCode.CLASS_LAYOUT_2) { - pos = writeAttrType(DwarfAttribute.DW_AT_data_location, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_expr_loc, buffer, pos); + if (abbrevCode == AbbrevCode.CLASS_LAYOUT_3) { + pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_signature, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos); + } else { + pos = writeAttrType(DwarfAttribute.DW_AT_byte_size, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); + /*- + * At present we definitely don't have a line number for the class itself. + pos = writeAttrType(DwarfDebugInfo.DW_AT_decl_line, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data2, buffer, pos); + */ + if (abbrevCode == AbbrevCode.CLASS_LAYOUT_2) { + pos = writeAttrType(DwarfAttribute.DW_AT_data_location, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_expr_loc, buffer, pos); + } } /* * Now terminate. @@ -1084,17 +1159,25 @@ private int writeClassLayoutAbbrev(@SuppressWarnings("unused") DebugContext cont return pos; } - private int writeClassReferenceAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { + private int writeClassReferenceAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { + int pos = p; + pos = writeClassReferenceAbbrev(context, AbbrevCode.TYPE_POINTER_SIG, buffer, pos); + pos = writeClassReferenceAbbrev(context, AbbrevCode.TYPE_POINTER, buffer, pos); + return pos; + } + + private int writeClassReferenceAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) { int pos = p; /* A pointer to the class struct type. */ - pos = writeAbbrevCode(AbbrevCode.CLASS_POINTER, buffer, pos); + pos = writeAbbrevCode(abbrevCode, buffer, pos); pos = writeTag(DwarfTag.DW_TAG_pointer_type, buffer, pos); pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_byte_size, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos); + pos = writeAttrForm(abbrevCode == AbbrevCode.TYPE_POINTER_SIG ? DwarfForm.DW_FORM_ref_sig8 : DwarfForm.DW_FORM_ref4, buffer, pos); + /* * Now terminate. */ @@ -1107,6 +1190,7 @@ private int writeMethodDeclarationAbbrevs(@SuppressWarnings("unused") DebugConte int pos = p; pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION, buffer, pos); pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_STATIC, buffer, pos); + pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_SKELETON, buffer, pos); return pos; } @@ -1119,25 +1203,29 @@ private int writeMethodDeclarationAbbrev(@SuppressWarnings("unused") DebugContex pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos); - pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); - pos = writeAttrType(DwarfAttribute.DW_AT_decl_line, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); + if (abbrevCode == AbbrevCode.METHOD_DECLARATION || abbrevCode == AbbrevCode.METHOD_DECLARATION_STATIC) { + pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_decl_line, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); + } pos = writeAttrType(DwarfAttribute.DW_AT_linkage_name, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_artificial, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_accessibility, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); - /* This is not in DWARF2 */ - // pos = writeAttrType(DW_AT_virtuality, buffer, pos); - // pos = writeAttrForm(DW_FORM_data1, buffer, pos); - pos = writeAttrType(DwarfAttribute.DW_AT_containing_type, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos); + if (abbrevCode == AbbrevCode.METHOD_DECLARATION || abbrevCode == AbbrevCode.METHOD_DECLARATION_STATIC) { + /* This is not in DWARF2 */ + // pos = writeAttrType(DW_AT_virtuality, buffer, pos); + // pos = writeAttrForm(DW_FORM_data1, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_containing_type, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos); + } if (abbrevCode == AbbrevCode.METHOD_DECLARATION) { pos = writeAttrType(DwarfAttribute.DW_AT_object_pointer, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos); @@ -1179,7 +1267,7 @@ private int writeFieldDeclarationAbbrev(@SuppressWarnings("unused") DebugContext // pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data2, buffer, pos); } pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos); if (abbrevCode == AbbrevCode.FIELD_DECLARATION_1 || abbrevCode == AbbrevCode.FIELD_DECLARATION_2) { /* Instance fields have a member offset relocated relative to the heap base register. */ pos = writeAttrType(DwarfAttribute.DW_AT_data_member_location, buffer, pos); @@ -1211,13 +1299,11 @@ private int writeClassConstantAbbrev(@SuppressWarnings("unused") DebugContext co pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos); /* We may not have a file and line for a field. */ pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_accessibility, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_external, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); - pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_location, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_expr_loc, buffer, pos); /* @@ -1246,24 +1332,6 @@ private int writeArrayLayoutAbbrev(@SuppressWarnings("unused") DebugContext cont return pos; } - private int writeArrayReferenceAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { - int pos = p; - - pos = writeAbbrevCode(AbbrevCode.ARRAY_POINTER, buffer, pos); - pos = writeTag(DwarfTag.DW_TAG_pointer_type, buffer, pos); - pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos); - pos = writeAttrType(DwarfAttribute.DW_AT_byte_size, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos); - pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos); - /* - * Now terminate. - */ - pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos); - return pos; - } - private int writeInterfaceLayoutAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; @@ -1280,24 +1348,6 @@ private int writeInterfaceLayoutAbbrev(@SuppressWarnings("unused") DebugContext return pos; } - private int writeInterfaceReferenceAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { - int pos = p; - - pos = writeAbbrevCode(AbbrevCode.INTERFACE_POINTER, buffer, pos); - pos = writeTag(DwarfTag.DW_TAG_pointer_type, buffer, pos); - pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos); - pos = writeAttrType(DwarfAttribute.DW_AT_byte_size, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos); - pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos); - /* - * Now terminate. - */ - pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos); - return pos; - } - private int writeInterfaceImplementorAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; @@ -1307,7 +1357,7 @@ private int writeInterfaceImplementorAbbrev(@SuppressWarnings("unused") DebugCon pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_accessibility, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos); /* @@ -1318,28 +1368,6 @@ private int writeInterfaceImplementorAbbrev(@SuppressWarnings("unused") DebugCon return pos; } - private int writeForeignReferenceAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { - int pos = p; - - /* A pointer to the class struct type. */ - pos = writeAbbrevCode(AbbrevCode.FOREIGN_POINTER, buffer, pos); - pos = writeTag(DwarfTag.DW_TAG_pointer_type, buffer, pos); - pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos); - pos = writeAttrType(DwarfAttribute.DW_AT_byte_size, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos); - // n.b we use a (relocatable) ref_addr here rather than a (CU-relative) ref4 - // because an unknown foreign pointer type will reference void which is not - // local to the current CU. - pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos); - /* - * Now terminate. - */ - pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos); - return pos; - } - private int writeForeignTypedefAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; @@ -1387,6 +1415,28 @@ private int writeHeaderFieldAbbrev(@SuppressWarnings("unused") DebugContext cont pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_data_member_location, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_accessibility, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos); + /* + * Now terminate. + */ + pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos); + return pos; + } + + private int writeArrayElementFieldAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { + int pos = p; + + pos = writeAbbrevCode(AbbrevCode.ARRAY_ELEMENT_FIELD, buffer, pos); + pos = writeTag(DwarfTag.DW_TAG_member, buffer, pos); + pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_data_member_location, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); @@ -1422,7 +1472,7 @@ private int writeArrayDataTypeAbbrev(@SuppressWarnings("unused") DebugContext co // because a foreign array type can reference another foreign type which is // not in the current CU. pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos); /* * Now terminate. */ @@ -1460,7 +1510,7 @@ private int writeMethodLocationAbbrev(@SuppressWarnings("unused") DebugContext c pos = writeAttrType(DwarfAttribute.DW_AT_external, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_specification, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos); /* * Now terminate. */ @@ -1510,12 +1560,11 @@ private int writeStaticFieldLocationAbbrev(@SuppressWarnings("unused") DebugCont private int writeSuperReferenceAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; - pos = writeAbbrevCode(AbbrevCode.SUPER_REFERENCE, buffer, pos); pos = writeTag(DwarfTag.DW_TAG_inheritance, buffer, pos); pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_data_member_location, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_accessibility, buffer, pos); @@ -1525,21 +1574,22 @@ private int writeSuperReferenceAbbrev(@SuppressWarnings("unused") DebugContext c */ pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos); + return pos; } - private int writeIndirectLayoutAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { + private int writeCompressedLayoutAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; /* - * oops are not necessarily raw addresses. they may contains pointer bits or be offsets from - * a base register. An indirect layout wraps a standard layout adding a data_location that - * translates indirect an oop to a raw address. It is used as the base for an indirect + * oops are not necessarily raw addresses. They may contain pointer bits or be offsets from + * a base register. A compressed layout wraps a standard layout adding a data_location that + * translates compressed oops to a raw addresses. It is used as the base for a compressed * pointer type that is used to type values that need translation to a raw address i.e. * values stored in static and instance fields. */ - /* the type for an indirect layout that includes address translation info */ - pos = writeAbbrevCode(AbbrevCode.INDIRECT_LAYOUT, buffer, pos); + /* The type for a compressed layout that includes address translation info */ + pos = writeAbbrevCode(AbbrevCode.COMPRESSED_LAYOUT, buffer, pos); pos = writeTag(DwarfTag.DW_TAG_class_type, buffer, pos); pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos); @@ -1558,30 +1608,13 @@ private int writeIndirectLayoutAbbrev(@SuppressWarnings("unused") DebugContext c return pos; } - private int writeIndirectReferenceAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { - int pos = p; - - /* The type for a pointer to the indirect layout type. */ - pos = writeAbbrevCode(AbbrevCode.INDIRECT_POINTER, buffer, pos); - pos = writeTag(DwarfTag.DW_TAG_pointer_type, buffer, pos); - pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos); - pos = writeAttrType(DwarfAttribute.DW_AT_byte_size, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos); - pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos); - /* - * Now terminate. - */ - pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos); - return pos; - } - private int writeParameterDeclarationAbbrevs(DebugContext context, byte[] buffer, int p) { int pos = p; pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_1, buffer, pos); pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_2, buffer, pos); pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_3, buffer, pos); + pos = writeSkeletonParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_4, buffer, pos); + pos = writeSkeletonParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_5, buffer, pos); return pos; } @@ -1600,7 +1633,7 @@ private int writeParameterDeclarationAbbrev(@SuppressWarnings("unused") DebugCon pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); } pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos); if (abbrevCode == AbbrevCode.METHOD_PARAMETER_DECLARATION_1) { /* Only this parameter is artificial and it has no line. */ pos = writeAttrType(DwarfAttribute.DW_AT_artificial, buffer, pos); @@ -1616,6 +1649,26 @@ private int writeParameterDeclarationAbbrev(@SuppressWarnings("unused") DebugCon return pos; } + private int writeSkeletonParameterDeclarationAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) { + int pos = p; + pos = writeAbbrevCode(abbrevCode, buffer, pos); + pos = writeTag(DwarfTag.DW_TAG_formal_parameter, buffer, pos); + pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos); + if (abbrevCode == AbbrevCode.METHOD_PARAMETER_DECLARATION_4) { + /* Only this parameter is artificial and it has no line. */ + pos = writeAttrType(DwarfAttribute.DW_AT_artificial, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); + } + /* + * Now terminate. + */ + pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos); + return pos; + } + private int writeLocalDeclarationAbbrevs(DebugContext context, byte[] buffer, int p) { int pos = p; pos = writeLocalDeclarationAbbrev(context, AbbrevCode.METHOD_LOCAL_DECLARATION_1, buffer, pos); @@ -1638,7 +1691,7 @@ private int writeLocalDeclarationAbbrev(@SuppressWarnings("unused") DebugContext pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); } pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); /* @@ -1669,10 +1722,10 @@ private int writeParameterLocationAbbrev(@SuppressWarnings("unused") DebugContex pos = writeTag(DwarfTag.DW_TAG_formal_parameter, buffer, pos); pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_abstract_origin, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos); if (abbrevCode == AbbrevCode.METHOD_PARAMETER_LOCATION_2) { pos = writeAttrType(DwarfAttribute.DW_AT_location, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_sec_offset, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_loclistx, buffer, pos); } /* * Now terminate. @@ -1688,10 +1741,10 @@ private int writeLocalLocationAbbrev(@SuppressWarnings("unused") DebugContext co pos = writeTag(DwarfTag.DW_TAG_variable, buffer, pos); pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_abstract_origin, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos); if (abbrevCode == AbbrevCode.METHOD_LOCAL_LOCATION_2) { pos = writeAttrType(DwarfAttribute.DW_AT_location, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_sec_offset, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_loclistx, buffer, pos); } /* * Now terminate. diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java index aee6600eeb85..e3634115c5b3 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java @@ -28,17 +28,17 @@ import java.nio.ByteOrder; +import org.graalvm.collections.EconomicMap; + import com.oracle.objectfile.debugentry.ClassEntry; import com.oracle.objectfile.debugentry.DebugInfoBase; - import com.oracle.objectfile.debugentry.MethodEntry; -import com.oracle.objectfile.debugentry.range.Range; import com.oracle.objectfile.debugentry.StructureTypeEntry; import com.oracle.objectfile.debugentry.TypeEntry; +import com.oracle.objectfile.debugentry.range.Range; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; import com.oracle.objectfile.elf.ELFMachine; import com.oracle.objectfile.elf.dwarf.constants.DwarfLanguage; -import org.graalvm.collections.EconomicMap; /** * A class that models the debug info in an organization that facilitates generation of the required @@ -56,37 +56,39 @@ enum AbbrevCode { /* null marker which must come first as its ordinal has to equal zero */ NULL, /* Level 0 DIEs. */ - BUILTIN_UNIT, + CLASS_CONSTANT_UNIT, CLASS_UNIT_1, CLASS_UNIT_2, + CLASS_UNIT_3, + TYPE_UNIT, /* Level 1 DIEs. */ PRIMITIVE_TYPE, VOID_TYPE, OBJECT_HEADER, + CLASS_CONSTANT, NAMESPACE, CLASS_LAYOUT_1, CLASS_LAYOUT_2, - CLASS_POINTER, - FOREIGN_POINTER, + CLASS_LAYOUT_3, + TYPE_POINTER_SIG, + TYPE_POINTER, FOREIGN_TYPEDEF, FOREIGN_STRUCT, METHOD_LOCATION, STATIC_FIELD_LOCATION, ARRAY_LAYOUT, - ARRAY_POINTER, INTERFACE_LAYOUT, - INTERFACE_POINTER, - INDIRECT_LAYOUT, - INDIRECT_POINTER, + COMPRESSED_LAYOUT, /* Level 2 DIEs. */ METHOD_DECLARATION, METHOD_DECLARATION_STATIC, + METHOD_DECLARATION_SKELETON, FIELD_DECLARATION_1, FIELD_DECLARATION_2, FIELD_DECLARATION_3, FIELD_DECLARATION_4, - CLASS_CONSTANT, HEADER_FIELD, + ARRAY_ELEMENT_FIELD, ARRAY_DATA_TYPE_1, ARRAY_DATA_TYPE_2, ARRAY_SUBRANGE, @@ -100,6 +102,8 @@ enum AbbrevCode { METHOD_PARAMETER_DECLARATION_1, METHOD_PARAMETER_DECLARATION_2, METHOD_PARAMETER_DECLARATION_3, + METHOD_PARAMETER_DECLARATION_4, + METHOD_PARAMETER_DECLARATION_5, METHOD_LOCAL_DECLARATION_1, METHOD_LOCAL_DECLARATION_2, METHOD_PARAMETER_LOCATION_1, @@ -125,7 +129,12 @@ enum AbbrevCode { * A prefix used to label indirect types used to ensure gdb performs oop reference --> raw * address translation */ - public static final String INDIRECT_PREFIX = "_z_."; + public static final String COMPRESSED_PREFIX = "_z_."; + /* + * A prefix used for type signature generation to generate unique type signatures for type + * layout type units + */ + public static final String LAYOUT_PREFIX = "_layout_."; /* * The name of the type for header field hub which needs special case processing to remove tag * bits @@ -155,7 +164,7 @@ enum AbbrevCode { * n.b. this collection includes entries for the structure types used to define the object and * array headers which do not have an associated TypeEntry. */ - private final EconomicMap typePropertiesIndex = EconomicMap.create(); + private final EconomicMap classPropertiesIndex = EconomicMap.create(); /** * A collection of method properties associated with each generated method record. @@ -231,80 +240,26 @@ public byte getThreadRegister() { } /** - * A class used to associate properties with a specific type, the most important one being its - * index in the info section. + * A class used to associate extra properties with an instance class type. */ - static class DwarfTypeProperties { - /** - * Index in debug_info section of type declaration for this class. - */ - private int typeInfoIndex; - /** - * Index in debug_info section of indirect type declaration for this class. - * - * this is normally just the same as the index of the normal type declaration, however, when - * oops are stored in static and instance fields as offsets from the heapbase register gdb - * needs to be told how to convert these oops to raw addresses and this requires attaching a - * data_location address translation expression to an indirect type that wraps the object - * layout type. so, with that encoding this field will identify the wrapper type whenever - * the original type is an object, interface or array layout. primitive types and header - * types do not need translating. - */ - private int indirectTypeInfoIndex; + + static class DwarfClassProperties { /** * The type entry with which these properties are associated. */ - private final TypeEntry typeEntry; - - public int getTypeInfoIndex() { - return typeInfoIndex; - } - - public void setTypeInfoIndex(int typeInfoIndex) { - this.typeInfoIndex = typeInfoIndex; - } - - public int getIndirectTypeInfoIndex() { - return indirectTypeInfoIndex; - } - - public void setIndirectTypeInfoIndex(int typeInfoIndex) { - this.indirectTypeInfoIndex = typeInfoIndex; - } - - public TypeEntry getTypeEntry() { - return typeEntry; - } - - DwarfTypeProperties(TypeEntry typeEntry) { - this.typeEntry = typeEntry; - this.typeInfoIndex = -1; - this.indirectTypeInfoIndex = -1; - } - - } - - /** - * A class used to associate extra properties with an instance class type. - */ - - static class DwarfClassProperties extends DwarfTypeProperties { + private final StructureTypeEntry typeEntry; /** * Index of the class entry's compile unit in the debug_info section. */ private int cuIndex; /** - * Index of the class entry's class_layout DIE in the debug_info section. - */ - private int layoutIndex; - /** - * Index of the class entry's indirect layout DIE in the debug_info section. + * Index of the class entry's code ranges data in the debug_rnglists section. */ - private int indirectLayoutIndex; + private int codeRangesIndex; /** - * Index of the class entry's code ranges data in the debug_ranges section. + * Index of the class entry's code location data in the debug_loclists section. */ - private int codeRangesIndex; + private int locationListIndex; /** * Index of the class entry's line data in the debug_line section. */ @@ -318,12 +273,15 @@ static class DwarfClassProperties extends DwarfTypeProperties { */ private EconomicMap fieldDeclarationIndex; - DwarfClassProperties(StructureTypeEntry entry) { - super(entry); + public StructureTypeEntry getTypeEntry() { + return typeEntry; + } + + DwarfClassProperties(StructureTypeEntry typeEntry) { + this.typeEntry = typeEntry; this.cuIndex = -1; - this.layoutIndex = -1; - this.indirectLayoutIndex = -1; this.codeRangesIndex = -1; + this.locationListIndex = 0; this.lineIndex = -1; this.linePrologueSize = -1; fieldDeclarationIndex = null; @@ -393,19 +351,9 @@ public int getAbstractInlineMethodIndex(ClassEntry classEntry) { } } - private DwarfTypeProperties addTypeProperties(TypeEntry typeEntry) { - assert typeEntry != null; - assert !typeEntry.isClass(); - assert typePropertiesIndex.get(typeEntry) == null; - DwarfTypeProperties typeProperties = new DwarfTypeProperties(typeEntry); - this.typePropertiesIndex.put(typeEntry, typeProperties); - return typeProperties; - } - private DwarfClassProperties addClassProperties(StructureTypeEntry entry) { - assert typePropertiesIndex.get(entry) == null; DwarfClassProperties classProperties = new DwarfClassProperties(entry); - this.typePropertiesIndex.put(entry, classProperties); + this.classPropertiesIndex.put(entry, classProperties); return classProperties; } @@ -416,22 +364,8 @@ private DwarfMethodProperties addMethodProperties(MethodEntry methodEntry) { return methodProperties; } - private DwarfTypeProperties lookupTypeProperties(TypeEntry typeEntry) { - if (typeEntry instanceof ClassEntry) { - return lookupClassProperties((ClassEntry) typeEntry); - } else { - DwarfTypeProperties typeProperties = typePropertiesIndex.get(typeEntry); - if (typeProperties == null) { - typeProperties = addTypeProperties(typeEntry); - } - return typeProperties; - } - } - private DwarfClassProperties lookupClassProperties(StructureTypeEntry entry) { - DwarfTypeProperties typeProperties = typePropertiesIndex.get(entry); - assert typeProperties == null || typeProperties instanceof DwarfClassProperties; - DwarfClassProperties classProperties = (DwarfClassProperties) typeProperties; + DwarfClassProperties classProperties = classPropertiesIndex.get(entry); if (classProperties == null) { classProperties = addClassProperties(entry); } @@ -446,40 +380,6 @@ private DwarfMethodProperties lookupMethodProperties(MethodEntry methodEntry) { return methodProperties; } - void setTypeIndex(TypeEntry typeEntry, int idx) { - assert idx >= 0; - DwarfTypeProperties typeProperties = lookupTypeProperties(typeEntry); - assert typeProperties.getTypeInfoIndex() == -1 || typeProperties.getTypeInfoIndex() == idx; - typeProperties.setTypeInfoIndex(idx); - } - - int getTypeIndex(TypeEntry typeEntry) { - DwarfTypeProperties typeProperties = lookupTypeProperties(typeEntry); - return getTypeIndex(typeProperties); - } - - int getTypeIndex(DwarfTypeProperties typeProperties) { - assert typeProperties.getTypeInfoIndex() >= 0; - return typeProperties.getTypeInfoIndex(); - } - - void setIndirectTypeIndex(TypeEntry typeEntry, int idx) { - assert idx >= 0; - DwarfTypeProperties typeProperties = lookupTypeProperties(typeEntry); - assert typeProperties.getIndirectTypeInfoIndex() == -1 || typeProperties.getIndirectTypeInfoIndex() == idx; - typeProperties.setIndirectTypeInfoIndex(idx); - } - - int getIndirectTypeIndex(TypeEntry typeEntry) { - DwarfTypeProperties typeProperties = lookupTypeProperties(typeEntry); - return getIndirectTypeIndex(typeProperties); - } - - int getIndirectTypeIndex(DwarfTypeProperties typeProperties) { - assert typeProperties.getIndirectTypeInfoIndex() >= 0; - return typeProperties.getIndirectTypeInfoIndex(); - } - public void setCUIndex(ClassEntry classEntry, int idx) { assert idx >= 0; DwarfClassProperties classProperties = lookupClassProperties(classEntry); @@ -496,64 +396,35 @@ public int getCUIndex(ClassEntry classEntry) { return classProperties.cuIndex; } - void setLayoutIndex(ClassEntry classEntry, int idx) { - assert idx >= 0 || idx == -1; - DwarfClassProperties classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; - assert classProperties.layoutIndex == -1 || classProperties.layoutIndex == idx; - classProperties.layoutIndex = idx; - } - - int getLayoutIndex(ClassEntry classEntry) { - DwarfClassProperties classProperties; - classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; - assert classProperties.layoutIndex >= 0; - return classProperties.layoutIndex; - } - - void setIndirectLayoutIndex(ClassEntry classEntry, int idx) { - // The layout index of a POINTER type is set to the type index of its referent. - // If the pointer type is generated before its referent that means it can be set - // with value -1 (unset) on the first sizing pass. The indirect layout will - // be reset to a positive offset on the second pass before it is used to write - // the referent of the pointer type. Hence the condition in the following assert. - assert idx >= 0 || idx == -1; - // Note however, that this possibility needs to be finessed when writing - // a foreign struct ADDRESS field of POINTER type (i.e. an embedded field). - // If the struct is generated before the POINTER type then the layout index will - // still be -1 during the second write pass when the field type needs to be - // written. This possibility is handled by typing the field using the typeIdx - // of the referent. the latter is guaranteed to have been set during the first pass. - + public void setCodeRangesIndex(ClassEntry classEntry, int idx) { + assert idx >= 0; DwarfClassProperties classProperties = lookupClassProperties(classEntry); assert classProperties.getTypeEntry() == classEntry; - assert classProperties.indirectLayoutIndex == -1 || classProperties.indirectLayoutIndex == idx; - classProperties.indirectLayoutIndex = idx; + assert classProperties.codeRangesIndex == -1 || classProperties.codeRangesIndex == idx; + classProperties.codeRangesIndex = idx; } - int getIndirectLayoutIndex(ClassEntry classEntry) { + public int getCodeRangesIndex(ClassEntry classEntry) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(classEntry); assert classProperties.getTypeEntry() == classEntry; - assert classProperties.indirectLayoutIndex >= 0; - return classProperties.indirectLayoutIndex; + assert classProperties.codeRangesIndex >= 0; + return classProperties.codeRangesIndex; } - public void setCodeRangesIndex(ClassEntry classEntry, int idx) { + public void setLocationListIndex(ClassEntry classEntry, int idx) { assert idx >= 0; DwarfClassProperties classProperties = lookupClassProperties(classEntry); assert classProperties.getTypeEntry() == classEntry; - assert classProperties.codeRangesIndex == -1 || classProperties.codeRangesIndex == idx; - classProperties.codeRangesIndex = idx; + assert classProperties.locationListIndex == 0 || classProperties.locationListIndex == idx; + classProperties.locationListIndex = idx; } - public int getCodeRangesIndex(ClassEntry classEntry) { + public int getLocationListIndex(ClassEntry classEntry) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(classEntry); assert classProperties.getTypeEntry() == classEntry; - assert classProperties.codeRangesIndex >= 0; - return classProperties.codeRangesIndex; + return classProperties.locationListIndex; } public void setLineIndex(ClassEntry classEntry, int idx) { diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java index e38565863930..6d5926c0530e 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java @@ -60,6 +60,7 @@ import com.oracle.objectfile.elf.dwarf.constants.DwarfInline; import com.oracle.objectfile.elf.dwarf.constants.DwarfLanguage; import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; +import com.oracle.objectfile.elf.dwarf.constants.DwarfUnitHeader; import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion; import jdk.graal.compiler.debug.DebugContext; @@ -71,42 +72,28 @@ * Section generator for debug_info section. */ public class DwarfInfoSectionImpl extends DwarfSectionImpl { - /** - * The name of a special DWARF struct type used to model an object header. - */ - private static final String OBJECT_HEADER_STRUCT_NAME = "_objhdr"; - /** * An info header section always contains a fixed number of bytes. */ - private static final int DIE_HEADER_SIZE = 11; - /** - * Normally the offset of DWARF type declarations are tracked using the type/class entry - * properties but that means they are only available to be read during the second pass when - * filling in type cross-references. However, we need to use the offset of the void type during - * the first pass as the target of later-generated foreign pointer types. So, this field saves - * it up front. - */ - private int voidOffset; - private int cuStart; + private static final int CU_DIE_HEADER_SIZE = 12; + private static final int TU_DIE_HEADER_SIZE = 24; + + private int unitStart; public DwarfInfoSectionImpl(DwarfDebugInfo dwarfSections) { // debug_info section depends on loc section - super(dwarfSections, DwarfSectionName.DW_INFO_SECTION, DwarfSectionName.DW_LOC_SECTION); - // initialize to an invalid value - voidOffset = -1; + super(dwarfSections, DwarfSectionName.DW_INFO_SECTION, DwarfSectionName.DW_LOCLISTS_SECTION); // initialize CU start to an invalid value - cuStart = -1; + unitStart = -1; } @Override public void createContent() { assert !contentByteArrayCreated(); - byte[] buffer = null; - int len = generateContent(null, buffer); + int len = generateContent(null, null); - buffer = new byte[len]; + byte[] buffer = new byte[len]; super.setContent(buffer); } @@ -154,33 +141,74 @@ DwarfEncoding computeEncoding(int flags, int bitCount) { public int generateContent(DebugContext context, byte[] buffer) { int pos = 0; - /* Write CU for primitive types and header struct. */ - + /* Write TUs for primitive types and header struct. */ pos = writeBuiltInTypes(context, buffer, pos); /* - * Write CUs for all instance classes, which includes interfaces and enums. That also - * incorporates interfaces that model foreign types. + * Write TUs and CUs for all instance classes, which includes interfaces and enums. That + * also incorporates interfaces that model foreign types. */ - pos = writeInstanceClasses(context, buffer, pos); - /* Write CUs for array types. */ - + /* Write TUs and CUs for array types. */ pos = writeArrays(context, buffer, pos); + /* Write CU for class constant objects. */ + pos = writeClassConstantObjects(context, buffer, pos); + return pos; } + private int writeSkeletonClassLayout(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { + int pos = p; + log(context, " [0x%08x] class layout", pos); + AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT_3; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + String name = classEntry.getTypeName(); + log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); + pos = writeStrSectionOffset(name, buffer, pos); + log(context, " [0x%08x] declaration true", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); + long typeSignature = classEntry.getLayoutTypeSignature(); + log(context, " [0x%08x] type specification 0x%x", pos, typeSignature); + pos = writeTypeSignature(typeSignature, buffer, pos); + + pos = writeStaticFieldDeclarations(context, classEntry, buffer, pos); + pos = writeMethodDeclarations(context, classEntry, buffer, pos); + /* + * Write a terminating null attribute. + */ + return writeAttrNull(buffer, pos); + } + private int writeBuiltInTypes(DebugContext context, byte[] buffer, int p) { int pos = p; + + log(context, " [0x%08x] primitive types", pos); + Cursor cursor = new Cursor(pos); + primitiveTypeStream().forEach(primitiveTypeEntry -> { + if (primitiveTypeEntry.getBitCount() > 0) { + cursor.set(writePrimitiveType(context, primitiveTypeEntry, buffer, cursor.get())); + } else { + cursor.set(writeVoidType(context, primitiveTypeEntry, buffer, cursor.get())); + } + }); + pos = cursor.get(); + + log(context, " [0x%08x] header type", pos); + return writeHeaderType(context, headerType(), buffer, pos); + } + + private int writeClassConstantObjects(DebugContext context, byte[] buffer, int p) { + int pos = p; + // Write the single Java builtin unit header int lengthPos = pos; log(context, " [0x%08x] <0> Java Builtin Compile Unit", pos); - cuStart = p; pos = writeCUHeader(buffer, pos); - assert pos == lengthPos + DIE_HEADER_SIZE; - AbbrevCode abbrevCode = AbbrevCode.BUILTIN_UNIT; + assert pos == lengthPos + CU_DIE_HEADER_SIZE; + AbbrevCode abbrevCode = AbbrevCode.CLASS_CONSTANT_UNIT; log(context, " [0x%08x] <0> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); log(context, " [0x%08x] language %s", pos, "DW_LANG_Java"); @@ -193,27 +221,13 @@ private int writeBuiltInTypes(DebugContext context, byte[] buffer, int p) { String compilationDirectory = dwarfSections.getCachePath(); log(context, " [0x%08x] comp_dir 0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory); pos = writeStrSectionOffset(compilationDirectory, buffer, pos); - /* Write child entries for primitive Java types. */ - - pos = primitiveTypeStream().reduce(pos, - (pos1, primitiveTypeEntry) -> { - if (primitiveTypeEntry.getBitCount() > 0) { - return writePrimitiveType(context, primitiveTypeEntry, buffer, pos1); - } else { - return writeVoidType(context, primitiveTypeEntry, buffer, pos1); - } - }, - (oldpos, newpos) -> newpos); - - /* Write child entry for object/array header struct. */ - - pos = writeHeaderType(context, headerType(), buffer, pos); - - /* write class constants for primitive type classes */ - pos = primitiveTypeStream().reduce(pos, - (pos1, primitiveTypeEntry) -> writeClassConstantDeclaration(context, primitiveTypeEntry, buffer, pos1), - (oldpos, newpos) -> newpos); + Cursor cursor = new Cursor(pos); + /* Write the location for the special Class object pseudo-static field for all types */ + typeStream().forEach(typeEntry -> { + cursor.set(writeClassConstantDeclaration(context, typeEntry, buffer, cursor.get())); + }); + pos = cursor.get(); /* * Write a terminating null attribute. @@ -221,22 +235,19 @@ private int writeBuiltInTypes(DebugContext context, byte[] buffer, int p) { pos = writeAttrNull(buffer, pos); /* Fix up the CU length. */ - patchLength(lengthPos, buffer, pos); return pos; } - public int writePrimitiveType(DebugContext context, PrimitiveTypeEntry primitiveTypeEntry, byte[] buffer, int p) { + private int writePrimitiveType(DebugContext context, PrimitiveTypeEntry primitiveTypeEntry, byte[] buffer, int p) { assert primitiveTypeEntry.getBitCount() > 0; int pos = p; + + // Write a type unit header + int lengthPos = pos; + pos = writeTUPreamble(context, primitiveTypeEntry.getTypeSignature(), "", buffer, p); + log(context, " [0x%08x] primitive type %s", pos, primitiveTypeEntry.getTypeName()); - /* Record the location of this type entry. */ - setTypeIndex(primitiveTypeEntry, pos); - /* - * primitive fields never need an indirection so use the same index for places where we - * might want an indirect type - */ - setIndirectTypeIndex(primitiveTypeEntry, pos); AbbrevCode abbrevCode = AbbrevCode.PRIMITIVE_TYPE; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); @@ -251,44 +262,50 @@ public int writePrimitiveType(DebugContext context, PrimitiveTypeEntry primitive pos = writeAttrEncoding(encoding, buffer, pos); String name = primitiveTypeEntry.getTypeName(); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); - return writeStrSectionOffset(name, buffer, pos); + pos = writeStrSectionOffset(name, buffer, pos); + + /* Write a terminating null attribute. */ + pos = writeAttrNull(buffer, pos); + + /* Fix up the type offset. */ + patchLength(lengthPos, buffer, pos); + return pos; } - public int writeVoidType(DebugContext context, PrimitiveTypeEntry primitiveTypeEntry, byte[] buffer, int p) { + private int writeVoidType(DebugContext context, PrimitiveTypeEntry primitiveTypeEntry, byte[] buffer, int p) { assert primitiveTypeEntry.getBitCount() == 0; int pos = p; + + // Write a type unit header + int lengthPos = pos; + pos = writeTUPreamble(context, primitiveTypeEntry.getTypeSignature(), "", buffer, p); + log(context, " [0x%08x] primitive type void", pos); - /* Record the location of this type entry. */ - setTypeIndex(primitiveTypeEntry, pos); - /* - * Type void never needs an indirection so use the same index for places where we might want - * an indirect type. - */ - setIndirectTypeIndex(primitiveTypeEntry, pos); - // specially record void type offset for immediate use during first pass of info generation - // we need to use it as the base layout for foreign types - assert voidOffset == -1 || voidOffset == pos; - voidOffset = pos; AbbrevCode abbrevCode = AbbrevCode.VOID_TYPE; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); String name = primitiveTypeEntry.getTypeName(); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); - return writeStrSectionOffset(name, buffer, pos); + pos = writeStrSectionOffset(name, buffer, pos); + + /* Write a terminating null attribute. */ + pos = writeAttrNull(buffer, pos); + + /* Fix up the type offset. */ + patchLength(lengthPos, buffer, pos); + return pos; } - public int writeHeaderType(DebugContext context, HeaderTypeEntry headerTypeEntry, byte[] buffer, int p) { + private int writeHeaderType(DebugContext context, HeaderTypeEntry headerTypeEntry, byte[] buffer, int p) { int pos = p; + + // Write a type unit header + int lengthPos = pos; + pos = writeTUPreamble(context, headerTypeEntry.getTypeSignature(), "", buffer, p); + String name = headerTypeEntry.getTypeName(); byte size = (byte) headerTypeEntry.getSize(); log(context, " [0x%08x] header type %s", pos, name); - /* Record the location of this type entry. */ - setTypeIndex(headerTypeEntry, pos); - /* - * Header records don't need an indirection so use the same index for places where we might - * want an indirect type. - */ - setIndirectTypeIndex(headerTypeEntry, pos); AbbrevCode abbrevCode = AbbrevCode.OBJECT_HEADER; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); @@ -297,10 +314,16 @@ public int writeHeaderType(DebugContext context, HeaderTypeEntry headerTypeEntry log(context, " [0x%08x] byte_size 0x%x", pos, size); pos = writeAttrData1(size, buffer, pos); pos = writeStructFields(context, headerTypeEntry.fields(), buffer, pos); - /* - * Write a terminating null attribute. - */ - return writeAttrNull(buffer, pos); + + /* Write a terminating null attribute. */ + pos = writeAttrNull(buffer, pos); + + /* Write a terminating null attribute. */ + pos = writeAttrNull(buffer, pos); + + /* Fix up the type offset. */ + patchLength(lengthPos, buffer, pos); + return pos; } private int writeStructFields(DebugContext context, Stream fields, byte[] buffer, int p) { @@ -311,74 +334,485 @@ private int writeStructFields(DebugContext context, Stream fields, b return cursor.get(); } - private int writeStructField(DebugContext context, FieldEntry fieldEntry, byte[] buffer, int p) { + private int writeStructField(DebugContext context, FieldEntry fieldEntry, byte[] buffer, int p) { + int pos = p; + String fieldName = fieldEntry.fieldName(); + TypeEntry valueType = fieldEntry.getValueType(); + long typeSignature = 0; + int typeIdx = 0; + AbbrevCode abbrevCode = AbbrevCode.HEADER_FIELD; + if (fieldEntry.isEmbedded()) { + // the field type must be a foreign type + ForeignTypeEntry foreignValueType = (ForeignTypeEntry) valueType; + /* use the layout type for the field */ + /* handle special case when the field is an array */ + int fieldSize = fieldEntry.getSize(); + int valueSize = foreignValueType.getSize(); + if (fieldEntry.getSize() != foreignValueType.getSize()) { + assert (fieldSize % valueSize == 0) : "embedded field size is not a multiple of value type size!"; + // declare a local array of the embedded type and use it as the value type + typeIdx = pos; + abbrevCode = AbbrevCode.ARRAY_ELEMENT_FIELD; + pos = writeEmbeddedArrayDataType(context, foreignValueType, valueSize, fieldSize / valueSize, buffer, pos); + } else { + if (foreignValueType.isPointer()) { + TypeEntry pointerTo = foreignValueType.getPointerTo(); + assert pointerTo != null : "ADDRESS field pointer type must have a known target type"; + // type the array using the referent of the pointer type + // + // n.b it is critical for correctness to use the index of the referent rather + // than the layout type of the referring type even though the latter will + // (eventually) be set to the same value. the type index of the referent is + // guaranteed to be set on the first sizing pass before it is consumed here + // on the second writing pass. + // However, if this embedded struct field definition precedes the definition + // of the referring type and the latter precedes the definition of the + // referent type then the layout index of the referring type may still be unset + // at this point. + typeSignature = pointerTo.getTypeSignature(); + } else { + typeSignature = foreignValueType.getLayoutTypeSignature(); + } + } + } else { + /* use the compressed type for the field so compressed pointers get translated */ + typeSignature = valueType.getTypeSignatureForCompressed(); + } + log(context, " [0x%08x] struct field", pos); + log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(fieldName), fieldName); + pos = writeStrSectionOffset(fieldName, buffer, pos); + if (abbrevCode == AbbrevCode.HEADER_FIELD) { + log(context, " [0x%08x] type 0x%x (%s)", pos, typeSignature, valueType.getTypeName()); + pos = writeTypeSignature(typeSignature, buffer, pos); + } else { + log(context, " [0x%08x] type 0x%x (%s)", pos, typeIdx, valueType.getTypeName()); + pos = writeInfoSectionOffset(typeIdx, buffer, pos); + } + short offset = (short) fieldEntry.getOffset(); + int size = fieldEntry.getSize(); + log(context, " [0x%08x] offset 0x%x (size 0x%x)", pos, offset, size); + pos = writeAttrData2(offset, buffer, pos); + int modifiers = fieldEntry.getModifiers(); + log(context, " [0x%08x] modifiers %s", pos, fieldEntry.getModifiersString()); + return writeAttrAccessibility(modifiers, buffer, pos); + } + + private int writeInstanceClasses(DebugContext context, byte[] buffer, int pos) { + log(context, " [0x%08x] instance classes", pos); + Cursor cursor = new Cursor(pos); + instanceClassStream().forEach(classEntry -> { + cursor.set(writeTypeUnits(context, classEntry, buffer, cursor.get())); + setCUIndex(classEntry, cursor.get()); + cursor.set(writeInstanceClassInfo(context, classEntry, buffer, cursor.get())); + }); + return cursor.get(); + } + + private int writeTypeUnits(DebugContext context, StructureTypeEntry typeEntry, byte[] buffer, int p) { + int pos = p; + + if (typeEntry.isForeign()) { + ForeignTypeEntry foreignTypeEntry = (ForeignTypeEntry) typeEntry; + pos = writeForeignLayoutTypeUnit(context, foreignTypeEntry, buffer, pos); + pos = writeForeignTypeUnit(context, foreignTypeEntry, buffer, pos); + } else { + if (typeEntry.isArray()) { + pos = writeArrayLayoutTypeUnit(context, (ArrayTypeEntry) typeEntry, buffer, pos); + } else if (typeEntry.isInterface()) { + pos = writeInterfaceLayoutTypeUnit(context, (InterfaceClassEntry) typeEntry, buffer, pos); + } else { + assert typeEntry instanceof ClassEntry; + pos = writeClassLayoutTypeUnit(context, (ClassEntry) typeEntry, buffer, pos); + } + pos = writePointerTypeUnit(context, typeEntry, buffer, pos); + if (dwarfSections.useHeapBase()) { + pos = writePointerTypeUnitForCompressed(context, typeEntry, buffer, pos); + } + } + return pos; + } + + private int writeTUPreamble(DebugContext context, long typeSignature, String loaderId, byte[] buffer, int p) { + int pos = p; + + // Write a type unit header + pos = writeTUHeader(typeSignature, buffer, pos); + int typeOffsetPos = pos - 4; + assert pos == p + TU_DIE_HEADER_SIZE; + AbbrevCode abbrevCode = AbbrevCode.TYPE_UNIT; + log(context, " [0x%08x] <0> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + log(context, " [0x%08x] language %s", pos, "DW_LANG_Java"); + pos = writeAttrLanguage(DwarfDebugInfo.LANG_ENCODING, buffer, pos); + log(context, " [0x%08x] use_UTF8", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); + + /* if the class has a loader then embed the children in a namespace */ + if (!loaderId.isEmpty()) { + pos = writeNameSpace(context, loaderId, buffer, pos); + } + + /* Fix up the type offset. */ + writeInt(pos - p, buffer, typeOffsetPos); + return pos; + } + + private int writePointerTypeUnit(DebugContext context, StructureTypeEntry typeEntry, byte[] buffer, int p) { + int pos = p; + + String loaderId = ""; + if (typeEntry.isArray()) { + loaderId = ((ArrayTypeEntry) typeEntry).getLoaderId(); + } else if (typeEntry.isClass()) { + loaderId = ((ClassEntry) typeEntry).getLoaderId(); + } + int lengthPos = pos; + long typeSignature = typeEntry.getTypeSignature(); + pos = writeTUPreamble(context, typeSignature, loaderId, buffer, p); + + /* Define a pointer type referring to the underlying layout. */ + log(context, " [0x%08x] %s pointer type", pos, typeEntry.isInterface() ? "interface" : "class"); + AbbrevCode abbrevCode = AbbrevCode.TYPE_POINTER_SIG; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + int pointerSize = dwarfSections.pointerSize(); + log(context, " [0x%08x] byte_size 0x%x", pos, pointerSize); + pos = writeAttrData1((byte) pointerSize, buffer, pos); + long layoutTypeSignature = typeEntry.getLayoutTypeSignature(); + log(context, " [0x%08x] type 0x%x", pos, layoutTypeSignature); + pos = writeTypeSignature(layoutTypeSignature, buffer, pos); + + if (!loaderId.isEmpty()) { + /* Write a terminating null attribute for the namespace. */ + pos = writeAttrNull(buffer, pos); + } + + /* Write a terminating null attribute for the top level TU DIE. */ + pos = writeAttrNull(buffer, pos); + + /* Fix up the TU length. */ + patchLength(lengthPos, buffer, pos); + return pos; + } + + private int writePointerTypeUnitForCompressed(DebugContext context, StructureTypeEntry typeEntry, byte[] buffer, int p) { + int pos = p; + long typeSignature = typeEntry.getTypeSignatureForCompressed(); + + // Write a type unit header + int lengthPos = pos; + pos = writeTUHeader(typeSignature, buffer, pos); + int typeOffsetPos = pos - 4; + assert pos == lengthPos + TU_DIE_HEADER_SIZE; + AbbrevCode abbrevCode = AbbrevCode.TYPE_UNIT; + log(context, " [0x%08x] <0> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + log(context, " [0x%08x] language %s", pos, "DW_LANG_Java"); + pos = writeAttrLanguage(DwarfDebugInfo.LANG_ENCODING, buffer, pos); + log(context, " [0x%08x] use_UTF8", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); + + /* if the class has a loader then embed the children in a namespace */ + String loaderId = ""; + if (typeEntry.isArray()) { + loaderId = ((ArrayTypeEntry) typeEntry).getLoaderId(); + } else if (typeEntry.isClass()) { + loaderId = ((ClassEntry) typeEntry).getLoaderId(); + } + if (!loaderId.isEmpty()) { + pos = writeNameSpace(context, loaderId, buffer, pos); + } + + /* + * Define a wrapper type with a data_location attribute that can act as a target for + * compressed oops + */ + int refTypeIdx = pos; + pos = writeLayoutTypeForCompressed(context, typeEntry, buffer, pos); + + /* Fix up the type offset. */ + writeInt(pos - lengthPos, buffer, typeOffsetPos); + + /* Define a pointer type referring to the underlying layout. */ + log(context, " [0x%08x] %s compressed pointer type", pos, typeEntry.isInterface() ? "interface" : "class"); + abbrevCode = AbbrevCode.TYPE_POINTER; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + int pointerSize = dwarfSections.referenceSize(); + log(context, " [0x%08x] byte_size 0x%x", pos, pointerSize); + pos = writeAttrData1((byte) pointerSize, buffer, pos); + log(context, " [0x%08x] type 0x%x", pos, refTypeIdx); + pos = writeAttrRef4(refTypeIdx, buffer, pos); + + if (!loaderId.isEmpty()) { + /* Write a terminating null attribute for the namespace. */ + pos = writeAttrNull(buffer, pos); + } + + /* Write a terminating null attribute for the top level TU DIE. */ + pos = writeAttrNull(buffer, pos); + + /* Fix up the TU length. */ + patchLength(lengthPos, buffer, pos); + return pos; + } + + private int writeLayoutTypeForCompressed(DebugContext context, StructureTypeEntry typeEntry, byte[] buffer, int p) { + int pos = p; + /* + * Write a wrapper type with a data_location attribute that can act as a target for + * compressed oops. + */ + log(context, " [0x%08x] compressed %s layout", pos, typeEntry.isInterface() ? "interface" : "class"); + AbbrevCode abbrevCode = AbbrevCode.COMPRESSED_LAYOUT; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + String compressedName = uniqueDebugString(DwarfDebugInfo.COMPRESSED_PREFIX + typeEntry.getTypeName()); + String name = uniqueDebugString(typeEntry.getTypeName()); + log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(compressedName), name); + pos = writeStrSectionOffset(compressedName, buffer, pos); + int size = typeEntry.getSize(); + log(context, " [0x%08x] byte_size 0x%x", pos, size); + pos = writeAttrData2((short) size, buffer, pos); + /* Write a data location expression to mask and/or rebase oop pointers. */ + log(context, " [0x%08x] data_location", pos); + pos = writeCompressedOopConversionExpression(dwarfSections.isHubClassEntry(typeEntry), buffer, pos); + + /* Now write the child field. */ + pos = writeSuperReference(context, typeEntry.getLayoutTypeSignature(), name, buffer, pos); + + /* Write a terminating null attribute for the compressed layout. */ + return writeAttrNull(buffer, pos); + } + + private int writeInterfaceLayoutTypeUnit(DebugContext context, InterfaceClassEntry interfaceClassEntry, byte[] buffer, int p) { + int pos = p; + + String loaderId = interfaceClassEntry.getLoaderId(); + int lengthPos = pos; + pos = writeTUPreamble(context, interfaceClassEntry.getLayoutTypeSignature(), loaderId, buffer, pos); + + log(context, " [0x%08x] interface layout", pos); + AbbrevCode abbrevCode = AbbrevCode.INTERFACE_LAYOUT; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + String name = interfaceClassEntry.getTypeName(); + log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); + pos = writeStrSectionOffset(name, buffer, pos); + + /* Now write references to all class layouts that implement this interface. */ + pos = writeInterfaceImplementors(context, interfaceClassEntry, buffer, pos); + pos = writeSkeletonMethodDeclarations(context, interfaceClassEntry, buffer, pos); + + /* Write a terminating null attribute for the interface layout. */ + pos = writeAttrNull(buffer, pos); + + if (!loaderId.isEmpty()) { + /* Write a terminating null attribute for the namespace. */ + pos = writeAttrNull(buffer, pos); + } + + /* Write a terminating null attribute for the top level TU DIE. */ + pos = writeAttrNull(buffer, pos); + + /* Fix up the TU length. */ + patchLength(lengthPos, buffer, pos); + return pos; + } + + private int writeClassLayoutTypeUnit(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { + int pos = p; + + String loaderId = classEntry.getLoaderId(); + int lengthPos = pos; + pos = writeTUPreamble(context, classEntry.getLayoutTypeSignature(), loaderId, buffer, pos); + + log(context, " [0x%08x] type layout", pos); + AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT_1; + /* + * when we don't have a separate compressed type then hub layouts need an extra + * data_location attribute + */ + if (!dwarfSections.useHeapBase() && dwarfSections.isHubClassEntry(classEntry)) { + abbrevCode = AbbrevCode.CLASS_LAYOUT_2; + } + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + String name = classEntry.getTypeName(); + log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); + pos = writeStrSectionOffset(name, buffer, pos); + int size = classEntry.getSize(); + log(context, " [0x%08x] byte_size 0x%x", pos, size); + pos = writeAttrData2((short) size, buffer, pos); + int fileIdx = classEntry.getFileIdx(); + log(context, " [0x%08x] file 0x%x (%s)", pos, fileIdx, classEntry.getFileName()); + pos = writeAttrData2((short) fileIdx, buffer, pos); + if (abbrevCode == AbbrevCode.CLASS_LAYOUT_2) { + /* Write a data location expression to mask and/or rebase oop pointers. */ + log(context, " [0x%08x] data_location", pos); + pos = writeCompressedOopConversionExpression(true, buffer, pos); + } + + StructureTypeEntry superClassEntry = classEntry.getSuperClass(); + if (superClassEntry == null) { + superClassEntry = headerType(); + } + + /* Now write the child fields. */ + pos = writeSuperReference(context, superClassEntry.getLayoutTypeSignature(), superClassEntry.getTypeName(), buffer, pos); + pos = writeFields(context, classEntry, buffer, pos); + pos = writeSkeletonMethodDeclarations(context, classEntry, buffer, pos); + + /* Write a terminating null attribute for the class layout. */ + pos = writeAttrNull(buffer, pos); + + if (!loaderId.isEmpty()) { + /* Write a terminating null attribute for the namespace. */ + pos = writeAttrNull(buffer, pos); + } + + /* Write a terminating null attribute for the top level TU DIE. */ + pos = writeAttrNull(buffer, pos); + + /* Fix up the TU length. */ + patchLength(lengthPos, buffer, pos); + return pos; + } + + private int writeForeignTypeUnit(DebugContext context, ForeignTypeEntry foreignTypeEntry, byte[] buffer, int p) { + int pos = p; + long typeSignature = foreignTypeEntry.getTypeSignature(); + + // Unlike with Java we use the Java name for the pointer type rather than the + // underlying base type, or rather for a typedef that targets the pointer type. + // That ensures that e.g. CCharPointer is a typedef for char*. + + // Write a type unit header + int lengthPos = pos; + pos = writeTUHeader(typeSignature, buffer, pos); + int typeOffsetPos = pos - 4; + assert pos == lengthPos + TU_DIE_HEADER_SIZE; + AbbrevCode abbrevCode = AbbrevCode.TYPE_UNIT; + log(context, " [0x%08x] <0> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + log(context, " [0x%08x] language %s", pos, "DW_LANG_Java"); + pos = writeAttrLanguage(DwarfDebugInfo.LANG_ENCODING, buffer, pos); + log(context, " [0x%08x] use_UTF8", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); + + /* if the class has a loader then embed the children in a namespace */ + String loaderId = foreignTypeEntry.getLoaderId(); + if (!loaderId.isEmpty()) { + pos = writeNameSpace(context, loaderId, buffer, pos); + } + + /* Define a pointer type referring to the base type */ + int refTypeIdx = pos; + log(context, " [0x%08x] foreign pointer type", pos); + abbrevCode = AbbrevCode.TYPE_POINTER_SIG; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + int pointerSize = dwarfSections.pointerSize(); + log(context, " [0x%08x] byte_size 0x%x", pos, pointerSize); + pos = writeAttrData1((byte) pointerSize, buffer, pos); + long layoutTypeSignature = foreignTypeEntry.getLayoutTypeSignature(); + log(context, " [0x%08x] type 0x%x", pos, layoutTypeSignature); + pos = writeTypeSignature(layoutTypeSignature, buffer, pos); + + /* Fix up the type offset. */ + writeInt(pos - lengthPos, buffer, typeOffsetPos); + + /* Define a typedef for the layout type using the Java name. */ + log(context, " [0x%08x] foreign typedef", pos); + abbrevCode = AbbrevCode.FOREIGN_TYPEDEF; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + String name = uniqueDebugString(foreignTypeEntry.getTypeName()); + log(context, " [0x%08x] name %s", pos, name); + pos = writeStrSectionOffset(name, buffer, pos); + log(context, " [0x%08x] type 0x%x", pos, refTypeIdx); + pos = writeAttrRef4(refTypeIdx, buffer, pos); + + if (!loaderId.isEmpty()) { + /* Write a terminating null attribute for the namespace. */ + pos = writeAttrNull(buffer, pos); + } + + /* Write a terminating null attribute for the top level TU DIE. */ + pos = writeAttrNull(buffer, pos); + + /* Fix up the TU length. */ + patchLength(lengthPos, buffer, pos); + return pos; + } + + private int writeForeignLayoutTypeUnit(DebugContext context, ForeignTypeEntry foreignTypeEntry, byte[] buffer, int p) { int pos = p; - String fieldName = fieldEntry.fieldName(); - TypeEntry valueType = fieldEntry.getValueType(); - int valueTypeIdx; - if (fieldEntry.isEmbedded()) { - // the field type must be a foreign type - ForeignTypeEntry foreignValueType = (ForeignTypeEntry) valueType; - /* use the indirect layout type for the field */ - /* handle special case when the field is an array */ - int fieldSize = fieldEntry.getSize(); - int valueSize = foreignValueType.getSize(); - if (fieldEntry.getSize() != foreignValueType.getSize()) { - assert (fieldSize % valueSize == 0) : "embedded field size is not a multiple of value type size!"; - // declare a local array of the embedded type and use it as the value type - valueTypeIdx = pos; - pos = writeEmbeddedArrayDataType(context, foreignValueType, valueSize, fieldSize / valueSize, buffer, pos); - } else { - if (foreignValueType.isPointer()) { - TypeEntry pointerTo = foreignValueType.getPointerTo(); - assert pointerTo != null : "ADDRESS field pointer type must have a known target type"; - // type the array using the referent of the pointer type - // - // n.b it is critical for correctness to use the index of the referent rather - // than the layout type of the referring type even though the latter will - // (eventually) be set to the same value. the type index of the referent is - // guaranteed to be set on the first sizing pass before it is consumed here - // on the second writing pass. - // However, if this embedded struct field definition precedes the definition - // of the referring type and the latter precedes the definition of the - // referent type then the layout index of the referring type may still be unset - // at this point. - valueTypeIdx = getTypeIndex(pointerTo); - } else { - valueTypeIdx = getIndirectLayoutIndex(foreignValueType); + + String loaderId = foreignTypeEntry.getLoaderId(); + int lengthPos = pos; + + /* Only write a TU preamble if we will write a new layout type. */ + if (foreignTypeEntry.isWord() || foreignTypeEntry.isIntegral() || foreignTypeEntry.isFloat() || foreignTypeEntry.isStruct()) { + pos = writeTUPreamble(context, foreignTypeEntry.getLayoutTypeSignature(), loaderId, buffer, pos); + } + + int size = foreignTypeEntry.getSize(); + if (foreignTypeEntry.isWord()) { + // define the type as a typedef for a signed or unsigned word i.e. we don't have a + // layout type + pos = writeForeignWordLayout(context, foreignTypeEntry, size, foreignTypeEntry.isSigned(), buffer, pos); + } else if (foreignTypeEntry.isIntegral()) { + // use a suitably sized signed or unsigned integral type as the layout type + pos = writeForeignIntegerLayout(context, foreignTypeEntry, size, foreignTypeEntry.isSigned(), buffer, pos); + } else if (foreignTypeEntry.isFloat()) { + // use a suitably sized float type as the layout type + pos = writeForeignFloatLayout(context, foreignTypeEntry, size, buffer, pos); + } else if (foreignTypeEntry.isStruct()) { + // define this type using a structure layout + pos = writeForeignStructLayout(context, foreignTypeEntry, size, buffer, pos); + } else { + // this must be a pointer. if the target type is known use it to declare the pointer + // type, otherwise default to 'void *' + TypeEntry targetType = voidType(); + if (foreignTypeEntry.isPointer()) { + TypeEntry pointerTo = foreignTypeEntry.getPointerTo(); + if (pointerTo != null) { + targetType = pointerTo; } } - } else { - /* use the indirect type for the field so pointers get translated */ - valueTypeIdx = getIndirectTypeIndex(valueType); + log(context, " [0x%08x] foreign pointer type %s referent 0x%x (%s)", pos, foreignTypeEntry.getTypeName(), targetType.getTypeSignature(), targetType.getTypeName()); + /* + * Setting the layout type to the type we point to reuses an available type unit, so we + * do not have to write are separate type unit. + * + * As we do not write anything, we can just return the initial position. + */ + foreignTypeEntry.setLayoutTypeSignature(targetType.getTypeSignature()); + return p; } - log(context, " [0x%08x] struct field", pos); - AbbrevCode abbrevCode = AbbrevCode.HEADER_FIELD; - log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); - pos = writeAbbrevCode(abbrevCode, buffer, pos); - log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(fieldName), fieldName); - pos = writeStrSectionOffset(fieldName, buffer, pos); - log(context, " [0x%08x] type 0x%x (%s)", pos, valueTypeIdx, valueType.getTypeName()); - pos = writeInfoSectionOffset(valueTypeIdx, buffer, pos); - short offset = (short) fieldEntry.getOffset(); - int size = fieldEntry.getSize(); - log(context, " [0x%08x] offset 0x%x (size 0x%x)", pos, offset, size); - pos = writeAttrData2(offset, buffer, pos); - int modifiers = fieldEntry.getModifiers(); - log(context, " [0x%08x] modifiers %s", pos, fieldEntry.getModifiersString()); - return writeAttrAccessibility(modifiers, buffer, pos); - } - private int writeInstanceClasses(DebugContext context, byte[] buffer, int pos) { - log(context, " [0x%08x] instance classes", pos); - Cursor cursor = new Cursor(pos); - instanceClassStream().forEach(classEntry -> { - // save current CU start so we can write Ref4 attributes as CU offsets - cuStart = cursor.get(); - setCUIndex(classEntry, cuStart); - cursor.set(writeInstanceClassInfo(context, classEntry, buffer, cursor.get())); - }); - return cursor.get(); + /* + * Write declarations for methods of the foreign types as functions + * + * n.b. these appear as standalone declarations rather than as children of a class layout + * DIE, so we don't need a terminating attribute. + */ + pos = writeSkeletonMethodDeclarations(context, foreignTypeEntry, buffer, pos); + + if (!loaderId.isEmpty()) { + /* Write a terminating null attribute for the namespace. */ + pos = writeAttrNull(buffer, pos); + } + + /* Write a terminating null attribute for the top level TU DIE. */ + pos = writeAttrNull(buffer, pos); + + /* Fix up the TU length. */ + patchLength(lengthPos, buffer, pos); + return pos; } private int writeInstanceClassInfo(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { @@ -387,8 +821,17 @@ private int writeInstanceClassInfo(DebugContext context, ClassEntry classEntry, int lengthPos = pos; log(context, " [0x%08x] Instance class unit", pos); pos = writeCUHeader(buffer, pos); - assert pos == lengthPos + DIE_HEADER_SIZE; - AbbrevCode abbrevCode = (classEntry.hasCompiledEntries() ? AbbrevCode.CLASS_UNIT_2 : AbbrevCode.CLASS_UNIT_1); + assert pos == lengthPos + CU_DIE_HEADER_SIZE; + AbbrevCode abbrevCode; + if (classEntry.hasCompiledEntries()) { + if (getLocationListIndex(classEntry) == 0) { + abbrevCode = AbbrevCode.CLASS_UNIT_2; + } else { + abbrevCode = AbbrevCode.CLASS_UNIT_3; + } + } else { + abbrevCode = AbbrevCode.CLASS_UNIT_1; + } log(context, " [0x%08x] <0> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); log(context, " [0x%08x] language %s", pos, "DW_LANG_Java"); @@ -404,17 +847,22 @@ private int writeInstanceClassInfo(DebugContext context, ClassEntry classEntry, String compilationDirectory = dwarfSections.getCachePath(); log(context, " [0x%08x] comp_dir 0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory); pos = writeStrSectionOffset(compilationDirectory, buffer, pos); - if (abbrevCode == AbbrevCode.CLASS_UNIT_2) { + if (abbrevCode == AbbrevCode.CLASS_UNIT_2 || abbrevCode == AbbrevCode.CLASS_UNIT_3) { int codeRangesIndex = getCodeRangesIndex(classEntry); log(context, " [0x%08x] ranges 0x%x", pos, codeRangesIndex); - pos = writeRangesSectionOffset(codeRangesIndex, buffer, pos); - // write low_pc as well as ranges so that lcoation lists can default the base address + pos = writeRangeListsSectionOffset(codeRangesIndex, buffer, pos); + // write low_pc as well as ranges so that location lists can default the base address int lo = classEntry.lowpc(); log(context, " [0x%08x] low_pc 0x%x", pos, codeRangesIndex); pos = writeAttrAddress(lo, buffer, pos); int lineIndex = getLineIndex(classEntry); log(context, " [0x%08x] stmt_list 0x%x", pos, lineIndex); pos = writeLineSectionOffset(lineIndex, buffer, pos); + if (abbrevCode == AbbrevCode.CLASS_UNIT_3) { + int locationListIndex = getLocationListIndex(classEntry); + log(context, " [0x%08x] loclists_base 0x%x", pos, locationListIndex); + pos = writeLocSectionOffset(locationListIndex, buffer, pos); + } } /* if the class has a loader then embed the children in a namespace */ String loaderId = classEntry.getLoaderId(); @@ -424,23 +872,9 @@ private int writeInstanceClassInfo(DebugContext context, ClassEntry classEntry, /* Now write the child DIEs starting with the layout and pointer type. */ - if (classEntry.isInterface()) { - InterfaceClassEntry interfaceClassEntry = (InterfaceClassEntry) classEntry; - pos = writeInterfaceLayout(context, interfaceClassEntry, buffer, pos); - pos = writeInterfaceType(context, interfaceClassEntry, buffer, pos); - } else if (classEntry.isForeign()) { - ForeignTypeEntry foreignTypeEntry = (ForeignTypeEntry) classEntry; - pos = writeForeignLayout(context, foreignTypeEntry, buffer, pos); - pos = writeForeignType(context, foreignTypeEntry, buffer, pos); - } else { - pos = writeClassLayout(context, classEntry, buffer, pos); - pos = writeClassType(context, classEntry, buffer, pos); - } - - /* Write a declaration for the special Class object pseudo-static field */ - pos = writeClassConstantDeclaration(context, classEntry, buffer, pos); - - /* if we opened a namespace then terminate its children */ + // this works for interfaces, foreign types and classes, entry kind specifics are in the + // type units + pos = writeSkeletonClassLayout(context, classEntry, buffer, pos); /* Write all compiled code locations */ @@ -454,6 +888,7 @@ private int writeInstanceClassInfo(DebugContext context, ClassEntry classEntry, pos = writeStaticFieldLocations(context, classEntry, buffer, pos); + /* if we opened a namespace then terminate its children */ if (!loaderId.isEmpty()) { pos = writeAttrNull(buffer, pos); } @@ -464,7 +899,6 @@ private int writeInstanceClassInfo(DebugContext context, ClassEntry classEntry, pos = writeAttrNull(buffer, pos); /* Fix up the CU length. */ - patchLength(lengthPos, buffer, pos); return pos; } @@ -503,15 +937,13 @@ private int writeClassConstantDeclaration(DebugContext context, TypeEntry typeEn * need to use the direct layout type for hub class to type it. */ ClassEntry valueType = dwarfSections.getHubClassEntry(); - int typeIdx = (valueType == null ? -1 : getLayoutIndex(valueType)); - log(context, " [0x%08x] type 0x%x ()", pos, typeIdx); - pos = writeInfoSectionOffset(typeIdx, buffer, pos); + long typeSignature = valueType == null ? lookupObjectClass().getLayoutTypeSignature() : valueType.getLayoutTypeSignature(); + log(context, " [0x%08x] type 0x%x ()", pos, typeSignature); + pos = writeTypeSignature(typeSignature, buffer, pos); log(context, " [0x%08x] accessibility public static final", pos); pos = writeAttrAccessibility(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL, buffer, pos); log(context, " [0x%08x] external(true)", pos); pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); - log(context, " [0x%08x] definition(true)", pos); - pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); /* * Encode this location as a relative relocatable address or an offset from the heapbase * register. @@ -521,98 +953,14 @@ private int writeClassConstantDeclaration(DebugContext context, TypeEntry typeEn return pos; } - private int writeClassLayout(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { - int pos = p; - int layoutIndex = pos; - setLayoutIndex(classEntry, layoutIndex); - log(context, " [0x%08x] class layout", pos); - AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT_1; - /* - * when we don't have a separate indirect type then hub layouts need an extra data_location - * attribute - */ - if (!dwarfSections.useHeapBase() && dwarfSections.isHubClassEntry(classEntry)) { - abbrevCode = AbbrevCode.CLASS_LAYOUT_2; - } - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = classEntry.getTypeName(); - log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); - pos = writeStrSectionOffset(name, buffer, pos); - int size = classEntry.getSize(); - log(context, " [0x%08x] byte_size 0x%x", pos, size); - pos = writeAttrData2((short) size, buffer, pos); - int fileIdx = classEntry.getFileIdx(); - log(context, " [0x%08x] file 0x%x (%s)", pos, fileIdx, classEntry.getFileName()); - pos = writeAttrData2((short) fileIdx, buffer, pos); - if (abbrevCode == AbbrevCode.CLASS_LAYOUT_2) { - /* Write a data location expression to mask and/or rebase oop pointers. */ - log(context, " [0x%08x] data_location", pos); - pos = writeIndirectOopConversionExpression(true, buffer, pos); - } - int superTypeOffset; - String superName; - ClassEntry superClassEntry = classEntry.getSuperClass(); - if (superClassEntry != null) { - /* Inherit layout from super class. */ - superName = superClassEntry.getTypeName(); - superTypeOffset = getLayoutIndex(superClassEntry); - } else { - /* Inherit layout from object header. */ - superName = OBJECT_HEADER_STRUCT_NAME; - TypeEntry headerType = headerType(); - superTypeOffset = getTypeIndex(headerType); - } - /* Now write the child fields. */ - pos = writeSuperReference(context, superTypeOffset, superName, buffer, pos); - pos = writeFields(context, classEntry, buffer, pos); - pos = writeMethodDeclarations(context, classEntry, buffer, pos); - /* - * Write a terminating null attribute. - */ - pos = writeAttrNull(buffer, pos); - - if (dwarfSections.useHeapBase()) { - /* - * Write a wrapper type with a data_location attribute that can act as a target for an - * indirect pointer. - */ - setIndirectLayoutIndex(classEntry, pos); - log(context, " [0x%08x] indirect class layout", pos); - abbrevCode = AbbrevCode.INDIRECT_LAYOUT; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - pos = writeAbbrevCode(abbrevCode, buffer, pos); - String indirectName = uniqueDebugString(DwarfDebugInfo.INDIRECT_PREFIX + classEntry.getTypeName()); - log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(indirectName), name); - pos = writeStrSectionOffset(indirectName, buffer, pos); - log(context, " [0x%08x] byte_size 0x%x", pos, size); - pos = writeAttrData2((short) size, buffer, pos); - /* Write a data location expression to mask and/or rebase oop pointers. */ - log(context, " [0x%08x] data_location", pos); - pos = writeIndirectOopConversionExpression(dwarfSections.isHubClassEntry(classEntry), buffer, pos); - superTypeOffset = layoutIndex; - /* Now write the child field. */ - pos = writeSuperReference(context, superTypeOffset, superName, buffer, pos); - /* - * Write a terminating null attribute. - */ - pos = writeAttrNull(buffer, pos); - } else { - log(context, " [0x%08x] setIndirectLayoutIndex %s 0x%x", pos, classEntry.getTypeName(), pos); - setIndirectLayoutIndex(classEntry, layoutIndex); - } - - return pos; - } - - private int writeSuperReference(DebugContext context, int superTypeOffset, String superName, byte[] buffer, int p) { + private int writeSuperReference(DebugContext context, long typeSignature, String superName, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] super reference", pos); AbbrevCode abbrevCode = AbbrevCode.SUPER_REFERENCE; log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - log(context, " [0x%08x] type 0x%x (%s)", pos, superTypeOffset, superName); - pos = writeInfoSectionOffset(superTypeOffset, buffer, pos); + log(context, " [0x%08x] type 0x%x (%s)", pos, typeSignature, superName); + pos = writeTypeSignature(typeSignature, buffer, pos); /* Parent layout is embedded at start of object. */ log(context, " [0x%08x] data_member_location (super) 0x%x", pos, 0); pos = writeAttrData1((byte) 0, buffer, pos); @@ -635,7 +983,7 @@ private static boolean isManifestedField(FieldEntry fieldEntry) { private int writeField(DebugContext context, StructureTypeEntry entry, FieldEntry fieldEntry, byte[] buffer, int p) { int pos = p; int modifiers = fieldEntry.getModifiers(); - boolean hasFile = fieldEntry.getFileName().length() > 0; + boolean hasFile = !fieldEntry.getFileName().isEmpty(); log(context, " [0x%08x] field definition", pos); AbbrevCode abbrevCode; boolean isStatic = Modifier.isStatic(modifiers); @@ -651,8 +999,6 @@ private int writeField(DebugContext context, StructureTypeEntry entry, FieldEntr } else { abbrevCode = AbbrevCode.FIELD_DECLARATION_4; } - /* Record the position of the declaration to use when we write the definition. */ - setFieldDeclarationIndex(entry, fieldEntry.fieldName(), pos); } log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); @@ -670,10 +1016,10 @@ private int writeField(DebugContext context, StructureTypeEntry entry, FieldEntr /* At present we definitely don't have line numbers. */ } TypeEntry valueType = fieldEntry.getValueType(); - /* use the indirect type for the field so pointers get translated if needed */ - int typeIdx = getIndirectTypeIndex(valueType); - log(context, " [0x%08x] type 0x%x (%s)", pos, typeIdx, valueType.getTypeName()); - pos = writeInfoSectionOffset(typeIdx, buffer, pos); + /* use the compressed type for the field so pointers get translated if needed */ + long typeSignature = valueType.getTypeSignatureForCompressed(); + log(context, " [0x%08x] type 0x%x (%s)", pos, typeSignature, valueType.getTypeName()); + pos = writeTypeSignature(typeSignature, buffer, pos); if (!isStatic) { int memberOffset = fieldEntry.getOffset(); log(context, " [0x%08x] member offset 0x%x", pos, memberOffset); @@ -691,6 +1037,89 @@ private int writeField(DebugContext context, StructureTypeEntry entry, FieldEntr return pos; } + private int writeSkeletonMethodDeclarations(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { + int pos = p; + for (MethodEntry method : classEntry.getMethods()) { + if (method.isInRange() || method.isInlined()) { + /* + * Declare all methods whether or not they have been compiled or inlined. + */ + pos = writeSkeletonMethodDeclaration(context, classEntry, method, buffer, pos); + } + } + + return pos; + } + + private int writeSkeletonMethodDeclaration(DebugContext context, ClassEntry classEntry, MethodEntry method, byte[] buffer, int p) { + int pos = p; + log(context, " [0x%08x] method declaration %s::%s", pos, classEntry.getTypeName(), method.methodName()); + AbbrevCode abbrevCode = AbbrevCode.METHOD_DECLARATION_SKELETON; + log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + log(context, " [0x%08x] external true", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); + String name = uniqueDebugString(method.methodName()); + log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); + pos = writeStrSectionOffset(name, buffer, pos); + String linkageName = uniqueDebugString(method.getSymbolName()); + log(context, " [0x%08x] linkage_name %s", pos, linkageName); + pos = writeStrSectionOffset(linkageName, buffer, pos); + TypeEntry returnType = method.getValueType(); + long retTypeSignature = returnType.getTypeSignature(); + log(context, " [0x%08x] type 0x%x (%s)", pos, retTypeSignature, returnType.getTypeName()); + pos = writeTypeSignature(retTypeSignature, buffer, pos); + log(context, " [0x%08x] artificial %s", pos, method.isDeopt() ? "true" : "false"); + pos = writeFlag((method.isDeopt() ? DwarfFlag.DW_FLAG_true : DwarfFlag.DW_FLAG_false), buffer, pos); + int modifiers = method.getModifiers(); + log(context, " [0x%08x] accessibility %s", pos, method.getModifiersString()); + pos = writeAttrAccessibility(modifiers, buffer, pos); + log(context, " [0x%08x] declaration true", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); + + /* Write method parameter declarations. */ + pos = writeSkeletonMethodParameterDeclarations(context, method, buffer, pos); + + /* Write a terminating null attribute. */ + return writeAttrNull(buffer, pos); + } + + private int writeSkeletonMethodParameterDeclarations(DebugContext context, MethodEntry method, byte[] buffer, int p) { + int pos = p; + if (!Modifier.isStatic(method.getModifiers())) { + DebugLocalInfo paramInfo = method.getThisParam(); + pos = writeSkeletonMethodParameterDeclaration(context, paramInfo, true, buffer, pos); + } + for (int i = 0; i < method.getParamCount(); i++) { + DebugLocalInfo paramInfo = method.getParam(i); + pos = writeSkeletonMethodParameterDeclaration(context, paramInfo, false, buffer, pos); + } + return pos; + } + + private int writeSkeletonMethodParameterDeclaration(DebugContext context, DebugLocalInfo paramInfo, boolean artificial, byte[] buffer, + int p) { + int pos = p; + log(context, " [0x%08x] method parameter declaration", pos); + AbbrevCode abbrevCode; + TypeEntry paramType = lookupType(paramInfo.valueType()); + if (artificial) { + abbrevCode = AbbrevCode.METHOD_PARAMETER_DECLARATION_4; + } else { + abbrevCode = AbbrevCode.METHOD_PARAMETER_DECLARATION_5; + } + log(context, " [0x%08x] <3> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + long typeSignature = paramType.getTypeSignature(); + log(context, " [0x%08x] type 0x%x (%s)", pos, typeSignature, paramType.getTypeName()); + pos = writeTypeSignature(typeSignature, buffer, pos); + if (abbrevCode == AbbrevCode.METHOD_PARAMETER_DECLARATION_4) { + log(context, " [0x%08x] artificial true", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); + } + return pos; + } + private int writeMethodDeclarations(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { int pos = p; for (MethodEntry method : classEntry.getMethods()) { @@ -735,18 +1164,18 @@ private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, log(context, " [0x%08x] linkage_name %s", pos, linkageName); pos = writeStrSectionOffset(linkageName, buffer, pos); TypeEntry returnType = method.getValueType(); - int retTypeIdx = getTypeIndex(returnType); - log(context, " [0x%08x] type 0x%x (%s)", pos, retTypeIdx, returnType.getTypeName()); - pos = writeInfoSectionOffset(retTypeIdx, buffer, pos); + long retTypeSignature = returnType.getTypeSignature(); + log(context, " [0x%08x] type 0x%x (%s)", pos, retTypeSignature, returnType.getTypeName()); + pos = writeTypeSignature(retTypeSignature, buffer, pos); log(context, " [0x%08x] artificial %s", pos, method.isDeopt() ? "true" : "false"); pos = writeFlag((method.isDeopt() ? DwarfFlag.DW_FLAG_true : DwarfFlag.DW_FLAG_false), buffer, pos); log(context, " [0x%08x] accessibility %s", pos, "public"); pos = writeAttrAccessibility(modifiers, buffer, pos); log(context, " [0x%08x] declaration true", pos); pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); - int typeIdx = getLayoutIndex(classEntry); - log(context, " [0x%08x] containing_type 0x%x (%s)", pos, typeIdx, classEntry.getTypeName()); - pos = writeAttrRef4(typeIdx, buffer, pos); + long typeSignature = classEntry.getLayoutTypeSignature(); + log(context, " [0x%08x] containing_type 0x%x (%s)", pos, typeSignature, classEntry.getTypeName()); + pos = writeTypeSignature(typeSignature, buffer, pos); if (abbrevCode == AbbrevCode.METHOD_DECLARATION) { /* Record the current position so we can back patch the object pointer. */ int objectPointerIndex = pos; @@ -813,9 +1242,9 @@ private int writeMethodParameterDeclaration(DebugContext context, DebugLocalInfo log(context, " [0x%08x] line 0x%x", pos, line); pos = writeAttrData2((short) line, buffer, pos); } - int typeIdx = getTypeIndex(paramType); - log(context, " [0x%08x] type 0x%x (%s)", pos, typeIdx, paramType.getTypeName()); - pos = writeInfoSectionOffset(typeIdx, buffer, pos); + long typeSignature = paramType.getTypeSignature(); + log(context, " [0x%08x] type 0x%x (%s)", pos, typeSignature, paramType.getTypeName()); + pos = writeTypeSignature(typeSignature, buffer, pos); if (abbrevCode == AbbrevCode.METHOD_PARAMETER_DECLARATION_1) { log(context, " [0x%08x] artificial true", pos); pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); @@ -856,69 +1285,15 @@ private int writeMethodLocalDeclaration(DebugContext context, DebugLocalInfo par pos = writeStrSectionOffset(uniqueDebugString(paramName), buffer, pos); if (abbrevCode == AbbrevCode.METHOD_LOCAL_DECLARATION_1) { log(context, " [0x%08x] file 0x%x", pos, fileIdx); - pos = writeAttrData2((short) fileIdx, buffer, pos); - log(context, " [0x%08x] line 0x%x", pos, line); - pos = writeAttrData2((short) line, buffer, pos); - } - int typeIdx = getTypeIndex(paramType); - log(context, " [0x%08x] type 0x%x (%s)", pos, typeIdx, paramType.getTypeName()); - pos = writeInfoSectionOffset(typeIdx, buffer, pos); - log(context, " [0x%08x] declaration true", pos); - pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); - return pos; - } - - private int writeInterfaceLayout(DebugContext context, InterfaceClassEntry interfaceClassEntry, byte[] buffer, int p) { - int pos = p; - int layoutOffset = pos; - setLayoutIndex(interfaceClassEntry, layoutOffset); - log(context, " [0x%08x] interface layout", pos); - AbbrevCode abbrevCode = AbbrevCode.INTERFACE_LAYOUT; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = interfaceClassEntry.getTypeName(); - log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); - pos = writeStrSectionOffset(name, buffer, pos); - /* - * Now write references to all class layouts that implement this interface. - */ - pos = writeInterfaceImplementors(context, interfaceClassEntry, buffer, pos); - pos = writeMethodDeclarations(context, interfaceClassEntry, buffer, pos); - - /* - * Write a terminating null attribute. - */ - pos = writeAttrNull(buffer, pos); - - if (dwarfSections.useHeapBase()) { - /* - * Write a wrapper type with a data_location attribute that can act as a target for an - * indirect pointer. - */ - setIndirectLayoutIndex(interfaceClassEntry, pos); - log(context, " [0x%08x] indirect class layout", pos); - abbrevCode = AbbrevCode.INDIRECT_LAYOUT; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - pos = writeAbbrevCode(abbrevCode, buffer, pos); - String indirectName = uniqueDebugString(DwarfDebugInfo.INDIRECT_PREFIX + interfaceClassEntry.getTypeName()); - log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(indirectName), name); - pos = writeStrSectionOffset(indirectName, buffer, pos); - int size = interfaceClassEntry.getSize(); - log(context, " [0x%08x] byte_size 0x%x", pos, size); - pos = writeAttrData2((short) size, buffer, pos); - /* Write a data location expression to mask and/or rebase oop pointers. */ - log(context, " [0x%08x] data_location", pos); - pos = writeIndirectOopConversionExpression(false, buffer, pos); - /* Now write the child field. */ - pos = writeSuperReference(context, layoutOffset, name, buffer, pos); - /* - * Write a terminating null attribute. - */ - pos = writeAttrNull(buffer, pos); - } else { - setIndirectLayoutIndex(interfaceClassEntry, layoutOffset); + pos = writeAttrData2((short) fileIdx, buffer, pos); + log(context, " [0x%08x] line 0x%x", pos, line); + pos = writeAttrData2((short) line, buffer, pos); } - + long typeSignature = paramType.getTypeSignature(); + log(context, " [0x%08x] type 0x%x (%s)", pos, typeSignature, paramType.getTypeName()); + pos = writeTypeSignature(typeSignature, buffer, pos); + log(context, " [0x%08x] declaration true", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); return pos; } @@ -937,63 +1312,15 @@ private int writeInterfaceImplementor(DebugContext context, ClassEntry classEntr String name = uniqueDebugString("_" + classEntry.getTypeName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); - int layoutOffset = getLayoutIndex(classEntry); - log(context, " [0x%08x] type 0x%x (%s)", pos, layoutOffset, classEntry.getTypeName()); - pos = writeInfoSectionOffset(layoutOffset, buffer, pos); + long typeSignature = classEntry.getLayoutTypeSignature(); + log(context, " [0x%08x] type 0x%x (%s)", pos, typeSignature, classEntry.getTypeName()); + pos = writeTypeSignature(typeSignature, buffer, pos); int modifiers = Modifier.PUBLIC; log(context, " [0x%08x] modifiers %s", pos, "public"); pos = writeAttrAccessibility(modifiers, buffer, pos); return pos; } - private int writeForeignLayout(DebugContext context, ForeignTypeEntry foreignTypeEntry, byte[] buffer, int p) { - int pos = p; - int size = foreignTypeEntry.getSize(); - int layoutOffset = pos; - if (foreignTypeEntry.isWord()) { - // define the type as a typedef for a signed or unsigned word i.e. we don't have a - // layout type - pos = writeForeignWordLayout(context, foreignTypeEntry, size, foreignTypeEntry.isSigned(), buffer, pos); - } else if (foreignTypeEntry.isIntegral()) { - // use a suitably sized signed or unsigned integral type as the layout type - pos = writeForeignIntegerLayout(context, foreignTypeEntry, size, foreignTypeEntry.isSigned(), buffer, pos); - } else if (foreignTypeEntry.isFloat()) { - // use a suitably sized float type as the layout type - pos = writeForeignFloatLayout(context, foreignTypeEntry, size, buffer, pos); - } else if (foreignTypeEntry.isStruct()) { - // define this type using a structure layout - pos = writeForeignStructLayout(context, foreignTypeEntry, size, buffer, pos); - } else { - // this must be a pointer. if the target type is known use it to declare the pointer - // type, otherwise default to 'void *' - layoutOffset = voidOffset; - String referentName = "void"; - if (foreignTypeEntry.isPointer()) { - TypeEntry pointerTo = foreignTypeEntry.getPointerTo(); - if (pointerTo != null) { - layoutOffset = getTypeIndex(foreignTypeEntry.getPointerTo()); - referentName = foreignTypeEntry.getTypeName(); - } - } - log(context, " [0x%08x] foreign pointer type %s referent 0x%x (%s)", pos, foreignTypeEntry.getTypeName(), layoutOffset, referentName); - } - setLayoutIndex(foreignTypeEntry, layoutOffset); - - /* - * Write declarations for methods of the foreign types as functions - * - * n.b. these appear as standalone declarations rather than as children of a class layout - * DIE so we don't need a terminating attribute. - */ - pos = writeMethodDeclarations(context, foreignTypeEntry, buffer, pos); - /* - * We don't need an indirect type because foreign pointers are never compressed - */ - setIndirectLayoutIndex(foreignTypeEntry, layoutOffset); - - return pos; - } - private int writeForeignStructLayout(DebugContext context, ForeignTypeEntry foreignTypeEntry, int size, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] foreign struct type for %s", pos, foreignTypeEntry.getTypeName()); @@ -1018,8 +1345,8 @@ private int writeForeignStructLayout(DebugContext context, ForeignTypeEntry fore // if we have a parent write a super attribute ForeignTypeEntry parent = foreignTypeEntry.getParent(); if (parent != null) { - int parentOffset = getLayoutIndex(parent); - pos = writeSuperReference(context, parentOffset, parent.getTypedefName(), buffer, pos); + long typeSignature = parent.getLayoutTypeSignature(); + pos = writeSuperReference(context, typeSignature, parent.getTypedefName(), buffer, pos); } pos = writeStructFields(context, foreignTypeEntry.fields(), buffer, pos); /* @@ -1109,121 +1436,6 @@ private static String integralTypeName(int byteSize, boolean isSigned) { return stringBuilder.toString(); } - private int writeClassType(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { - int pos = p; - - /* Define a pointer type referring to the underlying layout. */ - int typeIdx = pos; - setTypeIndex(classEntry, typeIdx); - log(context, " [0x%08x] class pointer type", pos); - AbbrevCode abbrevCode = AbbrevCode.CLASS_POINTER; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - pos = writeAbbrevCode(abbrevCode, buffer, pos); - int pointerSize = dwarfSections.pointerSize(); - log(context, " [0x%08x] byte_size 0x%x", pos, pointerSize); - pos = writeAttrData1((byte) pointerSize, buffer, pos); - int layoutOffset = getLayoutIndex(classEntry); - log(context, " [0x%08x] type 0x%x", pos, layoutOffset); - pos = writeAttrRef4(layoutOffset, buffer, pos); - - if (dwarfSections.useHeapBase()) { - /* Define an indirect pointer type referring to the indirect layout. */ - setIndirectTypeIndex(classEntry, pos); - log(context, " [0x%08x] class indirect pointer type", pos); - abbrevCode = AbbrevCode.INDIRECT_POINTER; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - pos = writeAbbrevCode(abbrevCode, buffer, pos); - int oopReferenceSize = dwarfSections.referenceSize(); - log(context, " [0x%08x] byte_size 0x%x", pos, oopReferenceSize); - pos = writeAttrData1((byte) oopReferenceSize, buffer, pos); - layoutOffset = getIndirectLayoutIndex(classEntry); - log(context, " [0x%08x] type 0x%x", pos, layoutOffset); - pos = writeAttrRef4(layoutOffset, buffer, pos); - } else { - setIndirectTypeIndex(classEntry, typeIdx); - } - - return pos; - } - - private int writeInterfaceType(DebugContext context, InterfaceClassEntry interfaceClassEntry, byte[] buffer, int p) { - int pos = p; - - /* Define a pointer type referring to the underlying layout. */ - int typeIdx = pos; - setTypeIndex(interfaceClassEntry, typeIdx); - log(context, " [0x%08x] interface pointer type", pos); - AbbrevCode abbrevCode = AbbrevCode.INTERFACE_POINTER; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - pos = writeAbbrevCode(abbrevCode, buffer, pos); - int pointerSize = dwarfSections.pointerSize(); - log(context, " [0x%08x] byte_size 0x%x", pos, pointerSize); - pos = writeAttrData1((byte) pointerSize, buffer, pos); - int layoutOffset = getLayoutIndex(interfaceClassEntry); - log(context, " [0x%08x] type 0x%x", pos, layoutOffset); - pos = writeAttrRef4(layoutOffset, buffer, pos); - - if (dwarfSections.useHeapBase()) { - /* Define an indirect pointer type referring to the indirect layout. */ - setIndirectTypeIndex(interfaceClassEntry, pos); - log(context, " [0x%08x] interface indirect pointer type", pos); - abbrevCode = AbbrevCode.INDIRECT_POINTER; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - pos = writeAbbrevCode(abbrevCode, buffer, pos); - int byteSize = dwarfSections.referenceSize(); - log(context, " [0x%08x] byte_size 0x%x", pos, byteSize); - pos = writeAttrData1((byte) byteSize, buffer, pos); - layoutOffset = getIndirectLayoutIndex(interfaceClassEntry); - log(context, " [0x%08x] type 0x%x", pos, layoutOffset); - pos = writeAttrRef4(layoutOffset, buffer, pos); - } else { - setIndirectTypeIndex(interfaceClassEntry, typeIdx); - } - - return pos; - } - - private int writeForeignType(DebugContext context, ForeignTypeEntry foreignTypeEntry, byte[] buffer, int p) { - int pos = p; - int layoutOffset = getLayoutIndex(foreignTypeEntry); - - // Unlike with Java we use the Java name for the pointer type rather than the - // underlying base type, or rather for a typedef that targets the pointer type. - // That ensures that e.g. CCharPointer is a typedef for char*. - - /* Define a pointer type referring to the base type */ - int refTypeIdx = pos; - log(context, " [0x%08x] foreign pointer type", pos); - AbbrevCode abbrevCode = AbbrevCode.FOREIGN_POINTER; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - pos = writeAbbrevCode(abbrevCode, buffer, pos); - int pointerSize = dwarfSections.pointerSize(); - log(context, " [0x%08x] byte_size 0x%x", pos, pointerSize); - pos = writeAttrData1((byte) pointerSize, buffer, pos); - // Note that we write a ref_addr offset here because an unknown (void *) - // foreign type can have a layout offset that is not in its CU - log(context, " [0x%08x] type 0x%x", pos, layoutOffset); - pos = writeInfoSectionOffset(layoutOffset, buffer, pos); - - /* Define a typedef for the layout type using the Java name. */ - int typedefIdx = pos; - log(context, " [0x%08x] foreign typedef", pos); - abbrevCode = AbbrevCode.FOREIGN_TYPEDEF; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = uniqueDebugString(foreignTypeEntry.getTypeName()); - log(context, " [0x%08x] name %s", pos, name); - pos = writeStrSectionOffset(name, buffer, pos); - log(context, " [0x%08x] type 0x%x", pos, refTypeIdx); - pos = writeAttrRef4(refTypeIdx, buffer, pos); - - setTypeIndex(foreignTypeEntry, typedefIdx); - // foreign pointers are never stored compressed so don't need a separate indirect type - setIndirectTypeIndex(foreignTypeEntry, typedefIdx); - - return pos; - } - private int writeStaticFieldLocations(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { /* * Only write locations for static fields that have an offset greater than 0. A negative @@ -1237,10 +1449,66 @@ private int writeStaticFieldLocations(DebugContext context, ClassEntry classEntr return cursor.get(); } + private int writeStaticFieldDeclarations(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { + /* + * Only write locations for static fields that have an offset greater than 0. A negative + * offset indicates that the field has been folded into code as an unmaterialized constant. + */ + Cursor cursor = new Cursor(p); + classEntry.fields().filter(DwarfInfoSectionImpl::isManifestedStaticField) + .forEach(fieldEntry -> { + cursor.set(writeClassStaticFieldDeclaration(context, classEntry, fieldEntry, buffer, cursor.get())); + }); + return cursor.get(); + } + private static boolean isManifestedStaticField(FieldEntry fieldEntry) { return Modifier.isStatic(fieldEntry.getModifiers()) && fieldEntry.getOffset() >= 0; } + private int writeClassStaticFieldDeclaration(DebugContext context, ClassEntry classEntry, FieldEntry fieldEntry, byte[] buffer, int p) { + assert Modifier.isStatic(fieldEntry.getModifiers()); + + int pos = p; + boolean hasFile = !fieldEntry.getFileName().isEmpty(); + log(context, " [0x%08x] field definition", pos); + AbbrevCode abbrevCode; + if (!hasFile) { + abbrevCode = AbbrevCode.FIELD_DECLARATION_3; + } else { + abbrevCode = AbbrevCode.FIELD_DECLARATION_4; + } + /* Record the position of the declaration to use when we write the definition. */ + setFieldDeclarationIndex(classEntry, fieldEntry.fieldName(), pos); + log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + + String name = fieldEntry.fieldName(); + log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); + pos = writeStrSectionOffset(name, buffer, pos); + /* We may not have a file and line for a field. */ + if (hasFile) { + int fileIdx = fieldEntry.getFileIdx(); + assert fileIdx > 0; + log(context, " [0x%08x] filename 0x%x (%s)", pos, fileIdx, fieldEntry.getFileName()); + pos = writeAttrData2((short) fileIdx, buffer, pos); + /* At present we definitely don't have line numbers. */ + } + TypeEntry valueType = fieldEntry.getValueType(); + /* use the compressed type for the field so pointers get translated if needed */ + long typeSignature = valueType.getTypeSignatureForCompressed(); + log(context, " [0x%08x] type 0x%x (%s)", pos, typeSignature, valueType.getTypeName()); + pos = writeTypeSignature(typeSignature, buffer, pos); + log(context, " [0x%08x] accessibility %s", pos, fieldEntry.getModifiersString()); + pos = writeAttrAccessibility(fieldEntry.getModifiers(), buffer, pos); + /* Static fields are only declared here and are external. */ + log(context, " [0x%08x] external(true)", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); + log(context, " [0x%08x] definition(true)", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); + return pos; + } + private int writeClassStaticFieldLocation(DebugContext context, ClassEntry classEntry, FieldEntry fieldEntry, byte[] buffer, int p) { int pos = p; String fieldName = fieldEntry.fieldName(); @@ -1263,19 +1531,71 @@ private int writeArrays(DebugContext context, byte[] buffer, int p) { log(context, " [0x%08x] array classes", p); Cursor cursor = new Cursor(p); arrayTypeStream().forEach(arrayTypeEntry -> { - cuStart = cursor.get(); + cursor.set(writeTypeUnits(context, arrayTypeEntry, buffer, cursor.get())); cursor.set(writeArray(context, arrayTypeEntry, buffer, cursor.get())); }); return cursor.get(); } + private int writeArrayLayoutTypeUnit(DebugContext context, ArrayTypeEntry arrayTypeEntry, byte[] buffer, int p) { + int pos = p; + + String loaderId = arrayTypeEntry.getLoaderId(); + int lengthPos = pos; + pos = writeTUPreamble(context, arrayTypeEntry.getLayoutTypeSignature(), loaderId, buffer, pos); + + /* Write the array layout and array reference DIEs. */ + TypeEntry elementType = arrayTypeEntry.getElementType(); + int size = arrayTypeEntry.getSize(); + log(context, " [0x%08x] array layout", pos); + AbbrevCode abbrevCode = AbbrevCode.ARRAY_LAYOUT; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + String name = arrayTypeEntry.getTypeName(); + log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); + pos = writeStrSectionOffset(name, buffer, pos); + log(context, " [0x%08x] byte_size 0x%x", pos, size); + pos = writeAttrData2((short) size, buffer, pos); + + /* Now the child DIEs. */ + + /* write a type definition for the element array field. */ + int arrayDataTypeIdx = pos; + pos = writeArrayDataType(context, elementType, buffer, pos); + pos = writeFields(context, arrayTypeEntry, buffer, pos); + + /* Write a zero length element array field. */ + pos = writeArrayElementField(context, size, arrayDataTypeIdx, buffer, pos); + + /* All arrays inherit from java.lang.Object */ + ClassEntry objectType = lookupObjectClass(); + String superName = objectType.getTypeName(); + long typeSignature = objectType.getLayoutTypeSignature(); + pos = writeSuperReference(context, typeSignature, superName, buffer, pos); + + /* Write a terminating null attribute for the array layout. */ + pos = writeAttrNull(buffer, pos); + + /* if we opened a namespace then terminate its children */ + if (!loaderId.isEmpty()) { + pos = writeAttrNull(buffer, pos); + } + + /* Write a terminating null attribute for the top level TU DIE. */ + pos = writeAttrNull(buffer, pos); + + /* Fix up the CU length. */ + patchLength(lengthPos, buffer, pos); + return pos; + } + private int writeArray(DebugContext context, ArrayTypeEntry arrayTypeEntry, byte[] buffer, int p) { int pos = p; // Write a Java class unit header int lengthPos = pos; log(context, " [0x%08x] Array class unit", pos); pos = writeCUHeader(buffer, pos); - assert pos == lengthPos + DIE_HEADER_SIZE; + assert pos == lengthPos + CU_DIE_HEADER_SIZE; AbbrevCode abbrevCode = AbbrevCode.CLASS_UNIT_1; log(context, " [0x%08x] <0> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); @@ -1297,18 +1617,7 @@ private int writeArray(DebugContext context, ArrayTypeEntry arrayTypeEntry, byte } /* Write the array layout and array reference DIEs. */ - TypeEntry elementType = arrayTypeEntry.getElementType(); - int size = arrayTypeEntry.getSize(); - int layoutIdx = pos; - pos = writeArrayLayout(context, arrayTypeEntry, elementType, size, buffer, pos); - int indirectLayoutIdx = pos; - if (dwarfSections.useHeapBase()) { - pos = writeIndirectArrayLayout(context, arrayTypeEntry, size, layoutIdx, buffer, pos); - } - pos = writeArrayTypes(context, arrayTypeEntry, layoutIdx, indirectLayoutIdx, buffer, pos); - - /* Write a declaration for the special Class object pseudo-static field */ - pos = writeClassConstantDeclaration(context, arrayTypeEntry, buffer, pos); + pos = writeSkeletonArrayLayout(context, arrayTypeEntry, buffer, pos); /* if we opened a namespace then terminate its children */ if (!loaderId.isEmpty()) { @@ -1326,27 +1635,20 @@ private int writeArray(DebugContext context, ArrayTypeEntry arrayTypeEntry, byte return pos; } - private int writeArrayLayout(DebugContext context, ArrayTypeEntry arrayTypeEntry, TypeEntry elementType, int size, byte[] buffer, int p) { + private int writeSkeletonArrayLayout(DebugContext context, ArrayTypeEntry arrayTypeEntry, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] array layout", pos); - AbbrevCode abbrevCode = AbbrevCode.ARRAY_LAYOUT; + AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT_3; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); String name = arrayTypeEntry.getTypeName(); - log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); + log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); - log(context, " [0x%08x] byte_size 0x%x", pos, size); - pos = writeAttrData2((short) size, buffer, pos); - - /* Now the child DIEs. */ - - /* write a type definition for the element array field. */ - int arrayDataTypeIdx = pos; - pos = writeArrayDataType(context, elementType, buffer, pos); - pos = writeFields(context, arrayTypeEntry, buffer, pos); - /* Write a zero length element array field. */ - pos = writeArrayElementField(context, size, arrayDataTypeIdx, buffer, pos); - pos = writeArraySuperReference(context, buffer, pos); + log(context, " [0x%08x] declaration true", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); + long typeSignature = arrayTypeEntry.getLayoutTypeSignature(); + log(context, " [0x%08x] type 0x%x", pos, typeSignature); + pos = writeTypeSignature(typeSignature, buffer, pos); /* * Write a terminating null attribute. */ @@ -1362,34 +1664,6 @@ private int writeFields(DebugContext context, ArrayTypeEntry arrayTypeEntry, byt return cursor.get(); } - private int writeIndirectArrayLayout(DebugContext context, ArrayTypeEntry arrayTypeEntry, int size, int layoutOffset, byte[] buffer, int p) { - int pos = p; - - /* - * write a wrapper type with a data_location attribute that can act as a target for an - * indirect pointer - */ - log(context, " [0x%08x] indirect class layout", pos); - AbbrevCode abbrevCode = AbbrevCode.INDIRECT_LAYOUT; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = arrayTypeEntry.getTypeName(); - String indirectName = uniqueDebugString(DwarfDebugInfo.INDIRECT_PREFIX + name); - log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(indirectName), name); - pos = writeStrSectionOffset(indirectName, buffer, pos); - log(context, " [0x%08x] byte_size 0x%x", pos, size); - pos = writeAttrData2((short) size, buffer, pos); - /* Write a data location expression to mask and/or rebase oop pointers. */ - log(context, " [0x%08x] data_location", pos); - pos = writeIndirectOopConversionExpression(false, buffer, pos); - /* Now write the child field. */ - pos = writeSuperReference(context, layoutOffset, name, buffer, pos); - /* - * Write a terminating null attribute. - */ - return writeAttrNull(buffer, pos); - } - private int writeArrayDataType(DebugContext context, TypeEntry elementType, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] array element data type", pos); @@ -1398,10 +1672,10 @@ private int writeArrayDataType(DebugContext context, TypeEntry elementType, byte pos = writeAbbrevCode(abbrevCode, buffer, pos); // Java arrays don't have a fixed byte_size String elementTypeName = elementType.getTypeName(); - /* use the indirect type for the element type so pointers get translated */ - int elementTypeIdx = getIndirectTypeIndex(elementType); - log(context, " [0x%08x] type idx 0x%x (%s)", pos, elementTypeIdx, elementTypeName); - pos = writeInfoSectionOffset(elementTypeIdx, buffer, pos); + /* use the compressed type for the element type so pointers get translated */ + long elementTypeSignature = elementType.getTypeSignatureForCompressed(); + log(context, " [0x%08x] type idx 0x%x (%s)", pos, elementTypeSignature, elementTypeName); + pos = writeTypeSignature(elementTypeSignature, buffer, pos); return pos; } @@ -1416,7 +1690,7 @@ private int writeEmbeddedArrayDataType(DebugContext context, ForeignTypeEntry fo log(context, " [0x%08x] byte_size 0x%x", pos, size); pos = writeAttrData4(size, buffer, pos); String elementTypeName = foreignValueType.getTypeName(); - int elementTypeIdx; + long elementTypeSignature; if (foreignValueType.isPointer()) { TypeEntry pointerTo = foreignValueType.getPointerTo(); assert pointerTo != null : "ADDRESS field pointer type must have a known target type"; @@ -1429,13 +1703,13 @@ private int writeEmbeddedArrayDataType(DebugContext context, ForeignTypeEntry fo // However, if this embedded struct field definition precedes the definition of the // referring type and the latter precedes the definition of the referent type then // the layout index of the referring type may still be unset at this point. - elementTypeIdx = getTypeIndex(pointerTo); + elementTypeSignature = pointerTo.getTypeSignature(); } else { // type the array using the layout type - elementTypeIdx = getIndirectLayoutIndex(foreignValueType); + elementTypeSignature = foreignValueType.getLayoutTypeSignature(); } - log(context, " [0x%08x] type idx 0x%x (%s)", pos, elementTypeIdx, elementTypeName); - pos = writeInfoSectionOffset(elementTypeIdx, buffer, pos); + log(context, " [0x%08x] type idx 0x%x (%s)", pos, elementTypeSignature, elementTypeName); + pos = writeTypeSignature(elementTypeSignature, buffer, pos); // write subrange child DIE log(context, " [0x%08x] embedded array element range", pos); abbrevCode = AbbrevCode.ARRAY_SUBRANGE; @@ -1452,7 +1726,7 @@ private int writeEmbeddedArrayDataType(DebugContext context, ForeignTypeEntry fo private int writeArrayElementField(DebugContext context, int offset, int arrayDataTypeIdx, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] array element data field", pos); - AbbrevCode abbrevCode = AbbrevCode.HEADER_FIELD; + AbbrevCode abbrevCode = AbbrevCode.ARRAY_ELEMENT_FIELD; log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); String fieldName = uniqueDebugString("data"); @@ -1466,54 +1740,6 @@ private int writeArrayElementField(DebugContext context, int offset, int arrayDa int modifiers = Modifier.PUBLIC; log(context, " [0x%08x] modifiers %s", pos, "public"); return writeAttrAccessibility(modifiers, buffer, pos); - - } - - private int writeArraySuperReference(DebugContext context, byte[] buffer, int p) { - int pos = p; - /* Arrays all inherit from java.lang.Object */ - TypeEntry objectType = lookupObjectClass(); - String superName = objectType.getTypeName(); - assert objectType != null; - assert objectType instanceof ClassEntry; - int superOffset = getLayoutIndex((ClassEntry) objectType); - return writeSuperReference(context, superOffset, superName, buffer, pos); - } - - private int writeArrayTypes(DebugContext context, ArrayTypeEntry arrayTypeEntry, int layoutOffset, int indirectLayoutOffset, byte[] buffer, int p) { - int pos = p; - String name = uniqueDebugString(arrayTypeEntry.getTypeName()); - - int typeIdx = pos; - setTypeIndex(arrayTypeEntry, pos); - /* Define a pointer type referring to the underlying layout. */ - log(context, " [0x%08x] array pointer type", pos); - AbbrevCode abbrevCode = AbbrevCode.ARRAY_POINTER; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - pos = writeAbbrevCode(abbrevCode, buffer, pos); - int pointerSize = dwarfSections.pointerSize(); - log(context, " [0x%08x] byte_size 0x%x", pos, pointerSize); - pos = writeAttrData1((byte) pointerSize, buffer, pos); - log(context, " [0x%08x] type (pointer) 0x%x (%s)", pos, layoutOffset, name); - pos = writeAttrRef4(layoutOffset, buffer, pos); - - if (dwarfSections.useHeapBase()) { - setIndirectTypeIndex(arrayTypeEntry, pos); - /* Define an indirect pointer type referring to the underlying indirect layout. */ - log(context, " [0x%08x] array indirect pointer type", pos); - abbrevCode = AbbrevCode.INDIRECT_POINTER; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - pos = writeAbbrevCode(abbrevCode, buffer, pos); - int byteSize = dwarfSections.referenceSize(); - log(context, " [0x%08x] byte_size 0x%x", pos, byteSize); - pos = writeAttrData1((byte) byteSize, buffer, pos); - log(context, " [0x%08x] type (pointer) 0x%x (%s)", pos, indirectLayoutOffset, name); - pos = writeAttrRef4(indirectLayoutOffset, buffer, pos); - } else { - setIndirectTypeIndex(arrayTypeEntry, typeIdx); - } - - return pos; } private int writeMethodLocations(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { @@ -1543,7 +1769,7 @@ private int writeMethodLocation(DebugContext context, ClassEntry classEntry, Com String methodKey = primary.getSymbolName(); int methodSpecOffset = getMethodDeclarationIndex(primary.getMethodEntry()); log(context, " [0x%08x] specification 0x%x (%s)", pos, methodSpecOffset, methodKey); - pos = writeAttrRef4(methodSpecOffset, buffer, pos); + pos = writeInfoSectionOffset(methodSpecOffset, buffer, pos); HashMap> varRangeMap = primary.getVarRangeMap(); pos = writeMethodParameterLocations(context, classEntry, varRangeMap, primary, 2, buffer, pos); pos = writeMethodLocalLocations(context, classEntry, varRangeMap, primary, 2, buffer, pos); @@ -1638,12 +1864,12 @@ private int writeMethodLocalLocation(DebugContext context, Range range, DebugLoc log(context, " [0x%08x] <%d> Abbrev Number %d", pos, depth, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); log(context, " [0x%08x] specification 0x%x", pos, refAddr); - pos = writeAttrRef4(refAddr, buffer, pos); + pos = writeInfoSectionOffset(refAddr, buffer, pos); if (abbrevCode == AbbrevCode.METHOD_LOCAL_LOCATION_2 || abbrevCode == AbbrevCode.METHOD_PARAMETER_LOCATION_2) { - int locRefAddr = getRangeLocalIndex(range, localInfo); - log(context, " [0x%08x] loc list 0x%x", pos, locRefAddr); - pos = writeLocSectionOffset(locRefAddr, buffer, pos); + int locRefOffset = getRangeLocalIndex(range, localInfo); + log(context, " [0x%08x] loc list 0x%x", pos, locRefOffset); + pos = writeULEB(locRefOffset, buffer, pos); } return pos; } @@ -1819,32 +2045,54 @@ private int writeAbstractInlineMethod(DebugContext context, ClassEntry classEntr private int writeAttrRef4(int reference, byte[] buffer, int p) { // make sure we have actually started writing a CU - assert cuStart >= 0; + assert unitStart >= 0; // writes a CU-relative offset - return writeAttrData4(reference - cuStart, buffer, p); + return writeAttrData4(reference - unitStart, buffer, p); } private int writeCUHeader(byte[] buffer, int p) { int pos = p; + /* save current CU start, so we can write Ref4 attributes as CU offsets. */ + unitStart = pos; /* CU length. */ pos = writeInt(0, buffer, pos); /* DWARF version. */ - pos = writeDwarfVersion(DwarfVersion.DW_VERSION_4, buffer, pos); + pos = writeDwarfVersion(DwarfVersion.DW_VERSION_5, buffer, pos); + /* unit type */ + pos = writeDwarfUnitHeader(DwarfUnitHeader.DW_UT_compile, buffer, pos); + /* Address size. */ + pos = writeByte((byte) 8, buffer, pos); /* Abbrev offset. */ - pos = writeAbbrevSectionOffset(0, buffer, pos); + return writeAbbrevSectionOffset(0, buffer, pos); + } + + private int writeTUHeader(long typeSignature, byte[] buffer, int p) { + int pos = p; + /* save current TU start, so we can write Ref4 attributes as CU offsets. */ + unitStart = pos; + /* CU length. */ + pos = writeInt(0, buffer, pos); + /* DWARF version. */ + pos = writeDwarfVersion(DwarfVersion.DW_VERSION_5, buffer, pos); + /* Unit type */ + pos = writeDwarfUnitHeader(DwarfUnitHeader.DW_UT_type, buffer, pos); /* Address size. */ - return writeByte((byte) 8, buffer, pos); + pos = writeByte((byte) 8, buffer, pos); + /* Abbrev offset. */ + pos = writeAbbrevSectionOffset(0, buffer, pos); + /* Type signature */ + pos = writeTypeSignature(typeSignature, buffer, pos); + /* Type offset */ + return writeInt(0, buffer, pos); } @SuppressWarnings("unused") public int writeAttrString(String value, byte[] buffer, int p) { - int pos = p; - return writeUTF8StringBytes(value, buffer, pos); + return writeUTF8StringBytes(value, buffer, p); } public int writeAttrLanguage(DwarfLanguage language, byte[] buffer, int p) { - int pos = p; - return writeByte(language.value(), buffer, pos); + return writeByte(language.value(), buffer, p); } public int writeAttrEncoding(DwarfEncoding encoding, byte[] buffer, int p) { @@ -1870,7 +2118,7 @@ public int writeAttrAccessibility(int modifiers, byte[] buffer, int p) { return writeByte(access.value(), buffer, p); } - public int writeIndirectOopConversionExpression(boolean isHub, byte[] buffer, int p) { + public int writeCompressedOopConversionExpression(boolean isHub, byte[] buffer, int p) { int pos = p; /* * For an explanation of the conversion rules @see com.oracle.svm.core.heap.ReferenceAccess diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java index c348cc2dd2ed..bdc82370dbc8 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java @@ -28,7 +28,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -39,7 +38,6 @@ import com.oracle.objectfile.LayoutDecisionMap; import com.oracle.objectfile.ObjectFile; import com.oracle.objectfile.debugentry.ClassEntry; -import com.oracle.objectfile.debugentry.CompiledMethodEntry; import com.oracle.objectfile.debugentry.range.Range; import com.oracle.objectfile.debugentry.range.SubRange; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; @@ -47,7 +45,9 @@ import com.oracle.objectfile.elf.ELFMachine; import com.oracle.objectfile.elf.ELFObjectFile; import com.oracle.objectfile.elf.dwarf.constants.DwarfExpressionOpcode; +import com.oracle.objectfile.elf.dwarf.constants.DwarfLocationListEntry; import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; +import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion; import jdk.graal.compiler.debug.DebugContext; import jdk.vm.ci.aarch64.AArch64; @@ -79,7 +79,7 @@ public class DwarfLocSectionImpl extends DwarfSectionImpl { public DwarfLocSectionImpl(DwarfDebugInfo dwarfSections) { // debug_loc section depends on text section - super(dwarfSections, DwarfSectionName.DW_LOC_SECTION, DwarfSectionName.TEXT_SECTION, targetLayoutKinds); + super(dwarfSections, DwarfSectionName.DW_LOCLISTS_SECTION, DwarfSectionName.TEXT_SECTION, targetLayoutKinds); initDwarfRegMap(); } @@ -102,10 +102,9 @@ public Set getDependencies(Map { - classEntry.compiledEntries().forEachOrdered(compiledMethodEntry -> { - cursor.set(writeCompiledMethodLocations(context, compiledMethodEntry, buffer, cursor.get())); - }); + List locationListEntries = getLocationListEntries(classEntry); + if (locationListEntries.isEmpty()) { + // no need to emit empty location list + // location list index can never be 0 as there is at least a header before + setLocationListIndex(classEntry, 0); + } else { + int entryCount = locationListEntries.size(); + + int lengthPos = cursor.get(); + cursor.set(writeLocationListsHeader(entryCount, buffer, cursor.get())); + + int baseOffset = cursor.get(); + setLocationListIndex(classEntry, baseOffset); + cursor.add(entryCount * 4); // space for offset array + + int index = 0; + for (LocationListEntry entry : locationListEntries) { + setRangeLocalIndex(entry.range(), entry.local(), index); + writeInt(cursor.get() - baseOffset, buffer, baseOffset + index * 4); + index++; + cursor.set(writeVarLocations(context, entry.local(), entry.base(), entry.rangeList(), buffer, cursor.get())); + } + + /* Fix up location list length */ + patchLength(lengthPos, buffer, cursor.get()); + } }); + return cursor.get(); } - private int writeCompiledMethodLocations(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) { + private int writeLocationListsHeader(int offsetEntries, byte[] buffer, int p) { int pos = p; - Range primary = compiledEntry.getPrimary(); - /* - * Note that offsets are written relative to the primary range base. This requires writing a - * base address entry before each of the location list ranges. It is possible to default the - * base address to the low_pc value of the compile unit for the compiled method's owning - * class, saving two words per location list. However, that forces the debugger to do a lot - * more up-front cross-referencing of CUs when it needs to resolve code addresses e.g. to - * set a breakpoint, leading to a very slow response for the user. - */ - int base = primary.getLo(); - log(context, " [0x%08x] top level locations [0x%x, 0x%x] method %s", pos, primary.getLo(), primary.getHi(), primary.getFullMethodNameWithParams()); - pos = writeTopLevelLocations(context, compiledEntry, base, buffer, pos); - if (!primary.isLeaf()) { - log(context, " [0x%08x] inline locations %s", pos, primary.getFullMethodNameWithParams()); - pos = writeInlineLocations(context, compiledEntry, base, buffer, pos); - } - return pos; + /* Loclists length. */ + pos = writeInt(0, buffer, pos); + /* DWARF version. */ + pos = writeDwarfVersion(DwarfVersion.DW_VERSION_5, buffer, pos); + /* Address size. */ + pos = writeByte((byte) 8, buffer, pos); + /* Segment selector size. */ + pos = writeByte((byte) 0, buffer, pos); + /* Offset entry count */ + return writeInt(offsetEntries, buffer, pos); } - private int writeTopLevelLocations(DebugContext context, CompiledMethodEntry compiledEntry, int base, byte[] buffer, int p) { - int pos = p; - Range primary = compiledEntry.getPrimary(); - HashMap> varRangeMap = primary.getVarRangeMap(); - for (DebugLocalInfo local : varRangeMap.keySet()) { - List rangeList = varRangeMap.get(local); - if (!rangeList.isEmpty()) { - setRangeLocalIndex(primary, local, pos); - pos = writeVarLocations(context, local, base, rangeList, buffer, pos); - } - } - return pos; + private record LocationListEntry(Range range, int base, DebugLocalInfo local, List rangeList) { } - private int writeInlineLocations(DebugContext context, CompiledMethodEntry compiledEntry, int base, byte[] buffer, int p) { - int pos = p; - Range primary = compiledEntry.getPrimary(); - assert !primary.isLeaf(); - Iterator iterator = compiledEntry.topDownRangeIterator(); - while (iterator.hasNext()) { - SubRange subrange = iterator.next(); - if (subrange.isLeaf()) { - continue; - } - HashMap> varRangeMap = subrange.getVarRangeMap(); - for (DebugLocalInfo local : varRangeMap.keySet()) { - List rangeList = varRangeMap.get(local); - if (!rangeList.isEmpty()) { - setRangeLocalIndex(subrange, local, pos); - pos = writeVarLocations(context, local, base, rangeList, buffer, pos); + private static List getLocationListEntries(ClassEntry classEntry) { + List locationListEntries = new ArrayList<>(); + + classEntry.compiledEntries().forEachOrdered(compiledEntry -> { + Range primary = compiledEntry.getPrimary(); + /* + * Note that offsets are written relative to the primary range base. This requires + * writing a base address entry before each of the location list ranges. It is possible + * to default the base address to the low_pc value of the compile unit for the compiled + * method's owning class, saving two words per location list. However, that forces the + * debugger to do a lot more up-front cross-referencing of CUs when it needs to resolve + * code addresses e.g. to set a breakpoint, leading to a very slow response for the + * user. + */ + int base = primary.getLo(); + // location list entries for primary range + locationListEntries.addAll(getRangeLocationListEntries(primary, base)); + // location list entries for inlined calls + if (!primary.isLeaf()) { + Iterator iterator = compiledEntry.topDownRangeIterator(); + while (iterator.hasNext()) { + SubRange subrange = iterator.next(); + if (subrange.isLeaf()) { + continue; + } + locationListEntries.addAll(getRangeLocationListEntries(subrange, base)); } } + }); + return locationListEntries; + } + + private static List getRangeLocationListEntries(Range range, int base) { + List locationListEntries = new ArrayList<>(); + + for (Map.Entry> entry : range.getVarRangeMap().entrySet()) { + if (!entry.getValue().isEmpty()) { + locationListEntries.add(new LocationListEntry(range, base, entry.getKey(), entry.getValue())); + } } - return pos; + + return locationListEntries; } private int writeVarLocations(DebugContext context, DebugLocalInfo local, int base, List rangeList, byte[] buffer, int p) { @@ -204,15 +232,16 @@ private int writeVarLocations(DebugContext context, DebugLocalInfo local, int ba // write start of primary range as base address - see comment above for reasons why // we choose ot do this rather than use the relevant compile unit low_pc - pos = writeAttrData8(-1L, buffer, pos); + pos = writeLocationListEntry(DwarfLocationListEntry.DW_LLE_base_address, buffer, pos); pos = writeAttrAddress(base, buffer, pos); // write ranges as offsets from base for (LocalValueExtent extent : extents) { DebugLocalValueInfo value = extent.value; assert (value != null); log(context, " [0x%08x] local %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.typeName(), extent.getLo(), extent.getHi(), formatValue(value)); - pos = writeAttrData8(extent.getLo() - base, buffer, pos); - pos = writeAttrData8(extent.getHi() - base, buffer, pos); + pos = writeLocationListEntry(DwarfLocationListEntry.DW_LLE_offset_pair, buffer, pos); + pos = writeULEB(extent.getLo() - base, buffer, pos); + pos = writeULEB(extent.getHi() - base, buffer, pos); switch (value.localKind()) { case REGISTER: pos = writeRegisterLocation(context, value.regIndex(), buffer, pos); @@ -236,10 +265,7 @@ private int writeVarLocations(DebugContext context, DebugLocalInfo local, int ba } } // write list terminator - pos = writeAttrData8(0, buffer, pos); - pos = writeAttrData8(0, buffer, pos); - - return pos; + return writeLocationListEntry(DwarfLocationListEntry.DW_LLE_end_of_list, buffer, pos); } private int writeRegisterLocation(DebugContext context, int regIndex, byte[] buffer, int p) { @@ -248,31 +274,31 @@ private int writeRegisterLocation(DebugContext context, int regIndex, byte[] buf int pos = p; if (targetIdx < 0x20) { // can write using DW_OP_reg - short byteCount = 1; + int byteCount = 1; byte reg = (byte) targetIdx; - pos = writeShort(byteCount, buffer, pos); + pos = writeULEB(byteCount, buffer, pos); pos = writeExprOpcodeReg(reg, buffer, pos); verboseLog(context, " [0x%08x] REGOP count %d op 0x%x", pos, byteCount, DwarfExpressionOpcode.DW_OP_reg0.value() + reg); } else { // have to write using DW_OP_regx + LEB operand assert targetIdx < 128 : "unexpectedly high reg index!"; - short byteCount = 2; - pos = writeShort(byteCount, buffer, pos); + int byteCount = 2; + pos = writeULEB(byteCount, buffer, pos); pos = writeExprOpcode(DwarfExpressionOpcode.DW_OP_regx, buffer, pos); pos = writeULEB(targetIdx, buffer, pos); verboseLog(context, " [0x%08x] REGOP count %d op 0x%x reg %d", pos, byteCount, DwarfExpressionOpcode.DW_OP_regx.value(), targetIdx); - // target idx written as ULEB should fit in one byte - assert pos == p + 4 : "wrote the wrong number of bytes!"; + // byte count and target idx written as ULEB should fit in one byte + assert pos == p + 3 : "wrote the wrong number of bytes!"; } return pos; } private int writeStackLocation(DebugContext context, int offset, byte[] buffer, int p) { int pos = p; - short byteCount = 0; + int byteCount = 0; byte sp = (byte) getDwarfStackRegister(); int patchPos = pos; - pos = writeShort(byteCount, buffer, pos); + pos = writeULEB(byteCount, buffer, pos); int zeroPos = pos; if (sp < 0x20) { // fold the base reg index into the op @@ -284,12 +310,12 @@ private int writeStackLocation(DebugContext context, int offset, byte[] buffer, } pos = writeSLEB(offset, buffer, pos); // now backpatch the byte count - byteCount = (byte) (pos - zeroPos); - writeShort(byteCount, buffer, patchPos); + byteCount = (pos - zeroPos); + writeULEB(byteCount, buffer, patchPos); if (sp < 0x20) { - verboseLog(context, " [0x%08x] STACKOP count %d op 0x%x offset %d", pos, byteCount, (DwarfExpressionOpcode.DW_OP_breg0.value() + sp), 0 - offset); + verboseLog(context, " [0x%08x] STACKOP count %d op 0x%x offset %d", pos, byteCount, (DwarfExpressionOpcode.DW_OP_breg0.value() + sp), -offset); } else { - verboseLog(context, " [0x%08x] STACKOP count %d op 0x%x reg %d offset %d", pos, byteCount, DwarfExpressionOpcode.DW_OP_bregx.value(), sp, 0 - offset); + verboseLog(context, " [0x%08x] STACKOP count %d op 0x%x reg %d offset %d", pos, byteCount, DwarfExpressionOpcode.DW_OP_bregx.value(), sp, -offset); } return pos; } @@ -302,7 +328,7 @@ private int writePrimitiveConstantLocation(DebugContext context, JavaConstant co int dataByteCount = kind.getByteCount(); // total bytes is op + uleb + dataByteCount int byteCount = 1 + 1 + dataByteCount; - pos = writeShort((short) byteCount, buffer, pos); + pos = writeULEB(byteCount, buffer, pos); pos = writeExprOpcode(op, buffer, pos); pos = writeULEB(dataByteCount, buffer, pos); if (dataByteCount == 1) { @@ -331,7 +357,7 @@ private int writeNullConstantLocation(DebugContext context, JavaConstant constan int dataByteCount = 8; // total bytes is op + uleb + dataByteCount int byteCount = 1 + 1 + dataByteCount; - pos = writeShort((short) byteCount, buffer, pos); + pos = writeULEB(byteCount, buffer, pos); pos = writeExprOpcode(op, buffer, pos); pos = writeULEB(dataByteCount, buffer, pos); pos = writeAttrData8(0, buffer, pos); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java index 155efe12feba..1d289c6307c8 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java @@ -26,35 +26,32 @@ package com.oracle.objectfile.elf.dwarf; +import java.util.Map; + import com.oracle.objectfile.LayoutDecision; import com.oracle.objectfile.LayoutDecisionMap; import com.oracle.objectfile.ObjectFile; import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.elf.dwarf.constants.DwarfRangeListEntry; import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; -import jdk.graal.compiler.debug.DebugContext; +import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion; -import java.util.Map; +import jdk.graal.compiler.debug.DebugContext; public class DwarfRangesSectionImpl extends DwarfSectionImpl { public DwarfRangesSectionImpl(DwarfDebugInfo dwarfSections) { - // debug_ranges section depends on debug_aranges section - super(dwarfSections, DwarfSectionName.DW_RANGES_SECTION, DwarfSectionName.DW_ARANGES_SECTION); + // debug_rnglists section depends on debug_aranges section + super(dwarfSections, DwarfSectionName.DW_RNGLISTS_SECTION, DwarfSectionName.DW_ARANGES_SECTION); } @Override public void createContent() { assert !contentByteArrayCreated(); - Cursor cursor = new Cursor(); - instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> { - setCodeRangesIndex(classEntry, cursor.get()); - // base address - cursor.add(2 * 8); - // per method lo and hi offsets - cursor.add(2 * 8 * classEntry.compiledEntryCount()); - // end marker - cursor.add(2 * 8); - }); - byte[] buffer = new byte[cursor.get()]; + + byte[] buffer = null; + int len = generateContent(null, buffer); + + buffer = new byte[len]; super.setContent(buffer); } @@ -78,39 +75,82 @@ public byte[] getOrDecideContent(Map alre @Override public void writeContent(DebugContext context) { assert contentByteArrayCreated(); + byte[] buffer = getContent(); int size = buffer.length; - Cursor cursor = new Cursor(); + int pos = 0; + + enableLog(context, pos); + log(context, " [0x%08x] DEBUG_RANGES", pos); + log(context, " [0x%08x] size = 0x%08x", pos, size); + + pos = generateContent(context, buffer); + assert pos == size; + } + + private int generateContent(DebugContext context, byte[] buffer) { + int pos = 0; + + int lengthPos = pos; + pos = writeRangeListsHeader(buffer, pos); + + pos = writeRangeLists(context, buffer, pos); + + patchLength(lengthPos, buffer, pos); + return pos; + } - enableLog(context, cursor.get()); - log(context, " [0x%08x] DEBUG_RANGES", cursor.get()); + private int writeRangeListsHeader(byte[] buffer, int p) { + int pos = p; + /* Rnglists length. */ + pos = writeInt(0, buffer, pos); + /* DWARF version. */ + pos = writeDwarfVersion(DwarfVersion.DW_VERSION_5, buffer, pos); + /* Address size. */ + pos = writeByte((byte) 8, buffer, pos); + /* Segment selector size. */ + pos = writeByte((byte) 0, buffer, pos); + /* + * Offset entry count. Not needed because we just use ranges in top level compile unit DIEs + */ + return writeInt(0, buffer, pos); + } + + private int writeRangeLists(DebugContext context, byte[] buffer, int p) { + Cursor entryCursor = new Cursor(p); instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> { - int pos = cursor.get(); - int start = pos; + int pos = entryCursor.get(); setCodeRangesIndex(classEntry, pos); - log(context, " [0x%08x] ranges start for class %s", pos, classEntry.getTypeName()); - int base = classEntry.compiledEntriesBase(); - log(context, " [0x%08x] base 0x%x", pos, base); - pos = writeLong(-1L, buffer, pos); - pos = writeRelocatableCodeOffset(base, buffer, pos); - cursor.set(pos); - classEntry.compiledEntries().forEach(compiledMethodEntry -> { - int lo = compiledMethodEntry.getPrimary().getLo(); - int hi = compiledMethodEntry.getPrimary().getHi(); - log(context, " [0x%08x] lo 0x%x (%s)", cursor.get(), lo - base, compiledMethodEntry.getPrimary().getFullMethodNameWithParams()); - cursor.set(writeLong(lo - base, buffer, cursor.get())); - log(context, " [0x%08x] hi 0x%x", cursor.get(), hi - base); - cursor.set(writeLong(hi - base, buffer, cursor.get())); - }); - pos = cursor.get(); - // write end marker - pos = writeLong(0, buffer, pos); - pos = writeLong(0, buffer, pos); - log(context, " [0x%08x] ranges size 0x%x for class %s", pos, pos - start, classEntry.getTypeName()); - cursor.set(pos); + /* Write range list for a class */ + entryCursor.set(writeRangeList(context, classEntry, buffer, pos)); }); + return entryCursor.get(); + } - assert cursor.get() == size; + private int writeRangeList(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { + int pos = p; + log(context, " [0x%08x] ranges start for class %s", pos, classEntry.getTypeName()); + int base = classEntry.compiledEntriesBase(); + log(context, " [0x%08x] base 0x%x", pos, base); + pos = writeRangeListEntry(DwarfRangeListEntry.DW_RLE_base_address, buffer, pos); + pos = writeRelocatableCodeOffset(base, buffer, pos); + + Cursor cursor = new Cursor(pos); + classEntry.compiledEntries().forEach(compiledMethodEntry -> { + cursor.set(writeRangeListEntry(DwarfRangeListEntry.DW_RLE_offset_pair, buffer, cursor.get())); + + int loOffset = compiledMethodEntry.getPrimary().getLo() - base; + int hiOffset = compiledMethodEntry.getPrimary().getHi() - base; + log(context, " [0x%08x] lo 0x%x (%s)", cursor.get(), loOffset, compiledMethodEntry.getPrimary().getFullMethodNameWithParams()); + cursor.set(writeULEB(loOffset, buffer, cursor.get())); + log(context, " [0x%08x] hi 0x%x", cursor.get(), hiOffset); + cursor.set(writeULEB(hiOffset, buffer, cursor.get())); + }); + pos = cursor.get(); + // write end marker + pos = writeRangeListEntry(DwarfRangeListEntry.DW_RLE_end_of_list, buffer, pos); + log(context, " [0x%08x] ranges size 0x%x for class %s", pos, pos - p, classEntry.getTypeName()); + return pos; } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java index cf6139d5cd00..04b550b7e9a8 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java @@ -55,8 +55,11 @@ import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.AbbrevCode; import com.oracle.objectfile.elf.dwarf.constants.DwarfExpressionOpcode; import com.oracle.objectfile.elf.dwarf.constants.DwarfFlag; +import com.oracle.objectfile.elf.dwarf.constants.DwarfLocationListEntry; +import com.oracle.objectfile.elf.dwarf.constants.DwarfRangeListEntry; import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; import com.oracle.objectfile.elf.dwarf.constants.DwarfTag; +import com.oracle.objectfile.elf.dwarf.constants.DwarfUnitHeader; import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion; import jdk.graal.compiler.debug.DebugContext; @@ -485,6 +488,14 @@ protected int writeAbbrevCode(AbbrevCode code, byte[] buffer, int pos) { return writeSLEB(code.ordinal(), buffer, pos); } + protected int writeRangeListEntry(DwarfRangeListEntry rangeListEntry, byte[] buffer, int pos) { + return writeByte(rangeListEntry.value(), buffer, pos); + } + + protected int writeLocationListEntry(DwarfLocationListEntry locationListEntry, byte[] buffer, int pos) { + return writeByte(locationListEntry.value(), buffer, pos); + } + protected int writeTag(DwarfTag dwarfTag, byte[] buffer, int pos) { int code = dwarfTag.value(); if (code == 0) { @@ -498,6 +509,14 @@ protected int writeDwarfVersion(DwarfVersion dwarfVersion, byte[] buffer, int po return writeShort(dwarfVersion.value(), buffer, pos); } + protected int writeDwarfUnitHeader(DwarfUnitHeader dwarfUnitHeader, byte[] buffer, int pos) { + return writeByte(dwarfUnitHeader.value(), buffer, pos); + } + + protected int writeTypeSignature(long typeSignature, byte[] buffer, int pos) { + return writeLong(typeSignature, buffer, pos); + } + protected int writeFlag(DwarfFlag flag, byte[] buffer, int pos) { return writeByte(flag.value(), buffer, pos); } @@ -531,8 +550,8 @@ protected int writeLineSectionOffset(int offset, byte[] buffer, int pos) { return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LINE_SECTION, pos); } - protected int writeRangesSectionOffset(int offset, byte[] buffer, int pos) { - return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_RANGES_SECTION, pos); + protected int writeRangeListsSectionOffset(int offset, byte[] buffer, int pos) { + return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_RNGLISTS_SECTION, pos); } protected int writeAbbrevSectionOffset(int offset, byte[] buffer, int pos) { @@ -550,7 +569,7 @@ private int writeStrSectionOffset(int offset, byte[] buffer, int pos) { } protected int writeLocSectionOffset(int offset, byte[] buffer, int pos) { - return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LOC_SECTION, pos); + return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LOCLISTS_SECTION, pos); } protected int writeDwarfSectionOffset(int offset, byte[] buffer, DwarfSectionName referencedSectionName, int pos) { @@ -597,15 +616,16 @@ protected int writeHeapLocationExprLoc(long offset, byte[] buffer, int p) { */ protected int writeHeapLocationLocList(long offset, byte[] buffer, int p) { int pos = p; - short len = 0; + int len = 0; int lenPos = pos; // write dummy length - pos = writeShort(len, buffer, pos); + pos = writeULEB(len, buffer, pos); + int zeroPos = pos; pos = writeHeapLocation(offset, buffer, pos); pos = writeExprOpcode(DwarfExpressionOpcode.DW_OP_stack_value, buffer, pos); // backpatch length - len = (short) (pos - (lenPos + 2)); - writeShort(len, buffer, lenPos); + len = pos - zeroPos; + writeULEB(len, buffer, lenPos); return pos; } @@ -841,28 +861,6 @@ protected ClassEntry lookupObjectClass() { return dwarfSections.lookupObjectClass(); } - protected int getTypeIndex(TypeEntry typeEntry) { - if (!contentByteArrayCreated()) { - return -1; - } - return dwarfSections.getTypeIndex(typeEntry); - } - - protected void setTypeIndex(TypeEntry typeEntry, int pos) { - dwarfSections.setTypeIndex(typeEntry, pos); - } - - protected int getIndirectTypeIndex(TypeEntry typeEntry) { - if (!contentByteArrayCreated()) { - return 0; - } - return dwarfSections.getIndirectTypeIndex(typeEntry); - } - - protected void setIndirectTypeIndex(TypeEntry typeEntry, int pos) { - dwarfSections.setIndirectTypeIndex(typeEntry, pos); - } - protected int getCUIndex(ClassEntry classEntry) { if (!contentByteArrayCreated()) { return 0; @@ -874,28 +872,6 @@ protected void setCUIndex(ClassEntry classEntry, int idx) { dwarfSections.setCUIndex(classEntry, idx); } - protected void setLayoutIndex(ClassEntry classEntry, int pos) { - dwarfSections.setLayoutIndex(classEntry, pos); - } - - protected int getLayoutIndex(ClassEntry classEntry) { - if (!contentByteArrayCreated()) { - return 0; - } - return dwarfSections.getLayoutIndex(classEntry); - } - - protected void setIndirectLayoutIndex(ClassEntry classEntry, int pos) { - dwarfSections.setIndirectLayoutIndex(classEntry, pos); - } - - protected int getIndirectLayoutIndex(ClassEntry classEntry) { - if (!contentByteArrayCreated()) { - return 0; - } - return dwarfSections.getIndirectLayoutIndex(classEntry); - } - protected void setCodeRangesIndex(ClassEntry classEntry, int pos) { dwarfSections.setCodeRangesIndex(classEntry, pos); } @@ -907,6 +883,14 @@ protected int getCodeRangesIndex(ClassEntry classEntry) { return dwarfSections.getCodeRangesIndex(classEntry); } + protected void setLocationListIndex(ClassEntry classEntry, int pos) { + dwarfSections.setLocationListIndex(classEntry, pos); + } + + protected int getLocationListIndex(ClassEntry classEntry) { + return dwarfSections.getLocationListIndex(classEntry); + } + protected void setLineIndex(ClassEntry classEntry, int pos) { dwarfSections.setLineIndex(classEntry, pos); } @@ -998,7 +982,7 @@ protected int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry * @param range the top level (primary) or inline range to which the local (or parameter) * belongs. * @param localInfo the local or param whose index is to be recorded. - * @param index the info section offset to be recorded. + * @param index the info section offset index to be recorded. */ protected void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int index) { dwarfSections.setRangeLocalIndex(range, localInfo, index); @@ -1014,9 +998,6 @@ protected void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int ind * @return the associated info section offset. */ protected int getRangeLocalIndex(Range range, DebugLocalInfo localInfo) { - if (!contentByteArrayCreated()) { - return 0; - } return dwarfSections.getRangeLocalIndex(range, localInfo); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java index 10c87e6b0715..050b9e9c726d 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java @@ -66,7 +66,9 @@ public enum DwarfAttribute { DW_AT_call_file(0x58), DW_AT_call_line(0x59), DW_AT_object_pointer(0x64), - DW_AT_linkage_name(0x6e); + DW_AT_signature(0x69), + DW_AT_linkage_name(0x6e), + DW_AT_loclists_base(0x8c); private final int value; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java index 0dc5b3aedad3..1855bb16850e 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java @@ -52,7 +52,9 @@ public enum DwarfForm { DW_FORM_data1(0x0b), DW_FORM_flag(0xc), DW_FORM_strp(0xe), - DW_FORM_expr_loc(0x18); + DW_FORM_expr_loc(0x18), + DW_FORM_ref_sig8(0x20), + DW_FORM_loclistx(0x22); private final int value; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLocationListEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLocationListEntry.java new file mode 100644 index 000000000000..128506075fdc --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLocationListEntry.java @@ -0,0 +1,45 @@ +/* + * 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.objectfile.elf.dwarf.constants; + +/** + * All the Dwarf range list entry tags needed to range list entries generated by GraalVM. + */ +public enum DwarfLocationListEntry { + DW_LLE_end_of_list((byte) 0), + DW_LLE_offset_pair((byte) 0x04), + DW_LLE_base_address((byte) 0x06); + + private final byte value; + + DwarfLocationListEntry(byte i) { + value = i; + } + + public byte value() { + return value; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfRangeListEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfRangeListEntry.java new file mode 100644 index 000000000000..949dd610427b --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfRangeListEntry.java @@ -0,0 +1,45 @@ +/* + * 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.objectfile.elf.dwarf.constants; + +/** + * All the Dwarf range list entry tags needed to range list entries generated by GraalVM. + */ +public enum DwarfRangeListEntry { + DW_RLE_end_of_list((byte) 0), + DW_RLE_offset_pair((byte) 0x04), + DW_RLE_base_address((byte) 0x05); + + private final byte value; + + DwarfRangeListEntry(byte i) { + value = i; + } + + public byte value() { + return value; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java index bb12f38edf89..9d91ebf24437 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java @@ -37,9 +37,9 @@ public enum DwarfSectionName { DW_FRAME_SECTION(".debug_frame"), DW_ABBREV_SECTION(".debug_abbrev"), DW_INFO_SECTION(".debug_info"), - DW_LOC_SECTION(".debug_loc"), + DW_LOCLISTS_SECTION(".debug_loclists"), DW_ARANGES_SECTION(".debug_aranges"), - DW_RANGES_SECTION(".debug_ranges"); + DW_RNGLISTS_SECTION(".debug_rnglists"); private final String value; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java index c15b59bd5938..77527e493900 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java @@ -41,6 +41,7 @@ public enum DwarfTag { DW_TAG_typedef(0x16), DW_TAG_union_type(0x17), DW_TAG_inheritance(0x1c), + DW_TAG_inlined_subroutine(0x1d), DW_TAG_subrange_type(0x21), DW_TAG_base_type(0x24), DW_TAG_constant(0x27), @@ -48,7 +49,7 @@ public enum DwarfTag { DW_TAG_variable(0x34), DW_TAG_namespace(0x39), DW_TAG_unspecified_type(0x3b), - DW_TAG_inlined_subroutine(0x1d); + DW_TAG_type_unit(0x41); private final int value; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfUnitHeader.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfUnitHeader.java new file mode 100644 index 000000000000..dc1db948e213 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfUnitHeader.java @@ -0,0 +1,44 @@ +/* + * 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.objectfile.elf.dwarf.constants; + +/** + * All the Dwarf unit headers needed in the debug info section. + */ +public enum DwarfUnitHeader { + DW_UT_compile((byte) 0x1), + DW_UT_type((byte) 0x2); + + private final byte value; + + DwarfUnitHeader(byte i) { + value = i; + } + + public byte value() { + return value; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java index d646f501499c..de98c7e83fb2 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java @@ -36,7 +36,8 @@ public enum DwarfVersion { * still need to be generated as version 2. */ DW_VERSION_2((short) 2), - DW_VERSION_4((short) 4); + DW_VERSION_4((short) 4), + DW_VERSION_5((short) 5); private final short value; diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 268e8129ad61..346e16fe3532 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -1102,7 +1102,7 @@ private static boolean readClassDescriptor(JNIEnvironment jni, JNIObjectHandle t name = nullHandle(); } var className = fromJniString(jni, name); - traceSerializeBreakpoint(jni, "ObjectInputStream.readClassDescriptor", true, state.getFullStackTraceOrNull(), className, null); + traceSerializeBreakpoint(jni, "ObjectInputStream.readClassDescriptor", true, state.getFullStackTraceOrNull(), className); return true; } @@ -1158,7 +1158,7 @@ private static boolean objectStreamClassConstructor(JNIEnvironment jni, JNIObjec Object interfaceNames = getClassArrayNames(jni, interfaces); traceSerializeBreakpoint(jni, "ProxyClassSerialization", validObjectStreamClassInstance, state.getFullStackTraceOrNull(), interfaceNames); } else { - traceSerializeBreakpoint(jni, "ObjectStreamClass.", validObjectStreamClassInstance, state.getFullStackTraceOrNull(), className, null); + traceSerializeBreakpoint(jni, "ObjectStreamClass.", validObjectStreamClassInstance, state.getFullStackTraceOrNull(), className); } } } @@ -1177,13 +1177,7 @@ private static boolean customTargetConstructorSerialization(JNIEnvironment jni, JNIObjectHandle serializeTargetClass = getObjectArgument(thread, 1); if (Support.isSerializable(jni, serializeTargetClass)) { String serializeTargetClassName = getClassNameOrNull(jni, serializeTargetClass); - - JNIObjectHandle customConstructorObj = getObjectArgument(thread, 2); - JNIObjectHandle customConstructorClass = jniFunctions().getGetObjectClass().invoke(jni, customConstructorObj); - JNIMethodId getDeclaringClassNameMethodID = agent.handles().getJavaLangReflectConstructorDeclaringClassName(jni, customConstructorClass); - JNIObjectHandle declaredClassNameObj = Support.callObjectMethod(jni, customConstructorObj, getDeclaringClassNameMethodID); - String customConstructorClassName = fromJniString(jni, declaredClassNameObj); - traceSerializeBreakpoint(jni, "ObjectStreamClass.", true, state.getFullStackTraceOrNull(), serializeTargetClassName, customConstructorClassName); + traceSerializeBreakpoint(jni, "ObjectStreamClass.", true, state.getFullStackTraceOrNull(), serializeTargetClassName); } return true; } diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/AddExports.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/AddExports.java new file mode 100644 index 000000000000..8bffae533d69 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/AddExports.java @@ -0,0 +1,44 @@ +/* + * 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.configure.test; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specifies packages concealed in JDK modules used by a test. The mx unit test runner will ensure + * the packages are exported to the module containing annotated test class. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface AddExports { + /** + * The qualified name of the concealed package in {@code /} format (e.g., + * "jdk.internal.vm.ci/jdk.vm.ci.code"). + */ + String[] value() default ""; +} diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java index d29330488c6e..2a608bf36979 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java @@ -52,10 +52,12 @@ import com.oracle.svm.configure.config.ResourceConfiguration; import com.oracle.svm.configure.config.SerializationConfiguration; import com.oracle.svm.configure.config.TypeConfiguration; +import com.oracle.svm.configure.test.AddExports; import com.oracle.svm.core.configure.ConfigurationTypeDescriptor; import com.oracle.svm.core.configure.NamedConfigurationTypeDescriptor; import com.oracle.svm.core.util.VMError; +@AddExports({"org.graalvm.nativeimage/org.graalvm.nativeimage.impl", "jdk.graal.compiler/jdk.graal.compiler.util"}) public class OmitPreviousConfigTests { private static final String PREVIOUS_CONFIG_DIR_NAME = "prev-config-dir"; @@ -117,7 +119,7 @@ public void testConfigDifference() { doTestResourceConfig(config.getResourceConfiguration()); - doTestSerializationConfig(config.getSerializationConfiguration()); + doTestSerializationConfig(config); doTestPredefinedClassesConfig(config.getPredefinedClassesConfiguration()); } @@ -181,6 +183,8 @@ private static void doTestMethods(TypeConfiguration typeConfig) { Assert.assertNull(ConfigurationType.TestBackdoor.getMethodInfoIfPresent(methodTestType, new ConfigurationMethod("", "(I)V"))); Assert.assertNotNull(ConfigurationType.TestBackdoor.getMethodInfoIfPresent(methodTestType, new ConfigurationMethod("method", "()V"))); + Assert.assertNull(ConfigurationType.TestBackdoor.getMethodInfoIfPresent(methodTestType, new ConfigurationMethod("method2", "()V"))); + Assert.assertNotNull(ConfigurationType.TestBackdoor.getMethodInfoIfPresent(methodTestType, new ConfigurationMethod("method3", "()V"))); } private static void doTestProxyConfig(ProxyConfiguration proxyConfig) { @@ -198,10 +202,16 @@ private static void doTestResourceConfig(ResourceConfiguration resourceConfig) { Assert.assertTrue(resourceConfig.anyBundleMatches(condition, "unseenBundle")); } - private static void doTestSerializationConfig(SerializationConfiguration serializationConfig) { + /* + * Note: the parameter cannot be a SerializationConfiguration because the type depends on some + * module exports (see the AddExports annotation) which only get applied _after_ the class is + * loaded. + */ + private static void doTestSerializationConfig(ConfigurationSet config) { + SerializationConfiguration serializationConfig = config.getSerializationConfiguration(); UnresolvedConfigurationCondition condition = UnresolvedConfigurationCondition.alwaysTrue(); - Assert.assertFalse(serializationConfig.contains(condition, "seenType", null)); - Assert.assertTrue(serializationConfig.contains(condition, "unseenType", null)); + Assert.assertFalse(serializationConfig.contains(condition, "seenType")); + Assert.assertTrue(serializationConfig.contains(condition, "unseenType")); } private static ConfigurationType getConfigTypeOrFail(TypeConfiguration typeConfig, String typeName) { diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/jni-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/jni-config.json index cbd1bab6e395..0b21c0bceaab 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/jni-config.json +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/jni-config.json @@ -55,6 +55,16 @@ { "name": "method", "parameterTypes": [] + }, + { + "name": "method3", + "parameterTypes": [] + } + ], + "queriedMethods": [ + { + "name": "method2", + "parameterTypes": [] } ] } diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/jni-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/jni-config.json index 1ec4eaa4962d..48495e38ca2b 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/jni-config.json +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/jni-config.json @@ -49,6 +49,16 @@ "parameterTypes": [ "int" ] + }, + { + "name": "method2", + "parameterTypes": [] + } + ], + "queriedMethods": [ + { + "name": "method3", + "parameterTypes": [] } ] } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMemberInfo.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMemberInfo.java index d2a9af19949d..aedbbbe05990 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMemberInfo.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMemberInfo.java @@ -92,10 +92,11 @@ private ConfigurationMemberDeclaration union(ConfigurationMemberDeclaration othe } public boolean includes(ConfigurationMemberDeclaration other) { - if (equals(DECLARED_AND_PUBLIC)) { - return DECLARED.equals(other) || PUBLIC.equals(other); - } - return equals(other); + return switch (this) { + case PRESENT -> true; + case DECLARED_AND_PUBLIC -> other == DECLARED || other == PUBLIC; + default -> this == other; + }; } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java index 39d0bcf00ca1..5f7577524ad1 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java @@ -105,6 +105,7 @@ static ConfigurationType copyAndMerge(ConfigurationType type, ConfigurationType private ConfigurationMemberAccessibility allPublicMethodsAccess = ConfigurationMemberAccessibility.NONE; private ConfigurationMemberAccessibility allDeclaredConstructorsAccess = ConfigurationMemberAccessibility.NONE; private ConfigurationMemberAccessibility allPublicConstructorsAccess = ConfigurationMemberAccessibility.NONE; + private boolean serializable = false; public ConfigurationType(UnresolvedConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean includeAllElements) { this.condition = condition; @@ -264,7 +265,15 @@ private void removeMethods(ConfigurationType other) { maybeRemoveMethods(allDeclaredMethodsAccess.combine(other.allDeclaredMethodsAccess), allPublicMethodsAccess.combine(other.allPublicMethodsAccess), allDeclaredConstructorsAccess.combine(other.allDeclaredConstructorsAccess), allPublicConstructorsAccess.combine(other.allPublicConstructorsAccess)); if (methods != null && other.methods != null) { - methods.entrySet().removeAll(other.methods.entrySet()); + for (Map.Entry entry : other.methods.entrySet()) { + ConfigurationMemberInfo otherMethodInfo = entry.getValue(); + methods.computeIfPresent(entry.getKey(), (method, methodInfo) -> { + if (otherMethodInfo.includes(methodInfo)) { + return null; // remove + } + return methodInfo; + }); + } if (methods.isEmpty()) { methods = null; } @@ -286,6 +295,7 @@ private void setFlagsFromOther(ConfigurationType other, BiPredicate... classes) { - for (Class clazz : classes) { - registerWithTargetConstructorClass(condition, clazz, null); - } - } - - @Override - public void registerWithTargetConstructorClass(UnresolvedConfigurationCondition condition, Class clazz, Class customTargetConstructorClazz) { - registerWithTargetConstructorClass(condition, clazz.getName(), customTargetConstructorClazz == null ? null : customTargetConstructorClazz.getName()); + public void register(UnresolvedConfigurationCondition condition, Class clazz) { + register(condition, clazz.getName()); } @Override - public void registerWithTargetConstructorClass(UnresolvedConfigurationCondition condition, String className, String customTargetConstructorClassName) { - serializations.add(createConfigurationType(condition, className, customTargetConstructorClassName)); + public void register(UnresolvedConfigurationCondition condition, String className) { + serializations.add(createConfigurationType(condition, className)); } @Override @@ -181,10 +174,9 @@ public boolean isEmpty() { return serializations.isEmpty() && lambdaSerializationCapturingTypes.isEmpty() && interfaceListsSerializableProxies.isEmpty(); } - private static SerializationConfigurationType createConfigurationType(UnresolvedConfigurationCondition condition, String className, String customTargetConstructorClassName) { + private static SerializationConfigurationType createConfigurationType(UnresolvedConfigurationCondition condition, String className) { String convertedClassName = SignatureUtil.toInternalClassName(className); - String convertedCustomTargetConstructorClassName = customTargetConstructorClassName == null ? null : SignatureUtil.toInternalClassName(customTargetConstructorClassName); - return new SerializationConfigurationType(condition, convertedClassName, convertedCustomTargetConstructorClassName); + return new SerializationConfigurationType(condition, convertedClassName); } private static SerializationConfigurationLambdaCapturingType createLambdaCapturingClassConfigurationType(UnresolvedConfigurationCondition condition, String className) { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java index bc6bc8fcadb0..99b429f4905b 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java @@ -24,45 +24,34 @@ */ package com.oracle.svm.configure.config; +import static com.oracle.svm.core.configure.ConfigurationParser.NAME_KEY; +import static com.oracle.svm.core.configure.ConfigurationParser.TYPE_KEY; + import java.io.IOException; -import java.util.Comparator; import java.util.Objects; import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; -import com.oracle.svm.core.configure.SerializationConfigurationParser; - import jdk.graal.compiler.util.json.JsonPrintable; import jdk.graal.compiler.util.json.JsonWriter; -import static com.oracle.svm.core.configure.ConfigurationParser.NAME_KEY; -import static com.oracle.svm.core.configure.ConfigurationParser.TYPE_KEY; - public class SerializationConfigurationType implements JsonPrintable, Comparable { private final UnresolvedConfigurationCondition condition; private final String qualifiedJavaName; - private final String qualifiedCustomTargetConstructorJavaName; - public SerializationConfigurationType(UnresolvedConfigurationCondition condition, String qualifiedJavaName, String qualifiedCustomTargetConstructorJavaName) { + public SerializationConfigurationType(UnresolvedConfigurationCondition condition, String qualifiedJavaName) { assert qualifiedJavaName.indexOf('/') == -1 : "Requires qualified Java name, not the internal representation"; assert !qualifiedJavaName.startsWith("[") : "Requires Java source array syntax, for example java.lang.String[]"; - assert qualifiedCustomTargetConstructorJavaName == null || qualifiedCustomTargetConstructorJavaName.indexOf('/') == -1 : "Requires qualified Java name, not internal representation"; - assert qualifiedCustomTargetConstructorJavaName == null || !qualifiedCustomTargetConstructorJavaName.startsWith("[") : "Requires Java source array syntax, for example java.lang.String[]"; Objects.requireNonNull(condition); this.condition = condition; Objects.requireNonNull(qualifiedJavaName); this.qualifiedJavaName = qualifiedJavaName; - this.qualifiedCustomTargetConstructorJavaName = qualifiedCustomTargetConstructorJavaName; } public String getQualifiedJavaName() { return qualifiedJavaName; } - public String getQualifiedCustomTargetConstructorJavaName() { - return qualifiedCustomTargetConstructorJavaName; - } - public UnresolvedConfigurationCondition getCondition() { return condition; } @@ -80,11 +69,6 @@ private void printJson(JsonWriter writer, boolean combinedFile) throws IOExcepti writer.appendObjectStart(); ConfigurationConditionPrintable.printConditionAttribute(condition, writer, combinedFile); writer.quote(combinedFile ? TYPE_KEY : NAME_KEY).appendFieldSeparator().quote(qualifiedJavaName); - if (qualifiedCustomTargetConstructorJavaName != null) { - writer.appendSeparator(); - writer.quote(SerializationConfigurationParser.CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY).appendFieldSeparator() - .quote(qualifiedCustomTargetConstructorJavaName); - } writer.appendObjectEnd(); } @@ -98,13 +82,12 @@ public boolean equals(Object o) { } SerializationConfigurationType that = (SerializationConfigurationType) o; return condition.equals(that.condition) && - qualifiedJavaName.equals(that.qualifiedJavaName) && - Objects.equals(qualifiedCustomTargetConstructorJavaName, that.qualifiedCustomTargetConstructorJavaName); + qualifiedJavaName.equals(that.qualifiedJavaName); } @Override public int hashCode() { - return Objects.hash(condition, qualifiedJavaName, qualifiedCustomTargetConstructorJavaName); + return Objects.hash(condition, qualifiedJavaName); } @Override @@ -113,11 +96,6 @@ public int compareTo(SerializationConfigurationType other) { if (compareName != 0) { return compareName; } - int compareCondition = condition.compareTo(other.condition); - if (compareCondition != 0) { - return compareCondition; - } - Comparator nullsFirstCompare = Comparator.nullsFirst(Comparator.naturalOrder()); - return nullsFirstCompare.compare(qualifiedCustomTargetConstructorJavaName, other.qualifiedCustomTargetConstructorJavaName); + return condition.compareTo(other.condition); } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java index 2ef063061a2b..406a38671f48 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java @@ -34,6 +34,8 @@ import com.oracle.svm.configure.config.ConfigurationSet; import com.oracle.svm.configure.config.SerializationConfiguration; +import com.oracle.svm.configure.config.TypeConfiguration; +import com.oracle.svm.core.configure.NamedConfigurationTypeDescriptor; import jdk.graal.compiler.java.LambdaUtils; @@ -55,9 +57,10 @@ void processEntry(EconomicMap entry, ConfigurationSet configurationSe String function = (String) entry.get("function"); List args = (List) entry.get("args"); SerializationConfiguration serializationConfiguration = configurationSet.getSerializationConfiguration(); + TypeConfiguration reflectionConfiguration = configurationSet.getReflectionConfiguration(); if ("ObjectStreamClass.".equals(function) || "ObjectInputStream.readClassDescriptor".equals(function)) { - expectSize(args, 2); + expectSize(args, 1); if (advisor.shouldIgnore(LazyValueUtils.lazyValue((String) args.get(0)), LazyValueUtils.lazyValue(null), false)) { return; @@ -68,7 +71,7 @@ void processEntry(EconomicMap entry, ConfigurationSet configurationSe if (className.contains(LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING)) { serializationConfiguration.registerLambdaCapturingClass(condition, className); } else { - serializationConfiguration.registerWithTargetConstructorClass(condition, className, (String) args.get(1)); + reflectionConfiguration.getOrCreateType(condition, new NamedConfigurationTypeDescriptor(className)).setSerializable(); } } else if ("SerializedLambda.readResolve".equals(function)) { expectSize(args, 1); diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/AbiUtils.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/AbiUtils.java index 97f4b326ba23..4485b5781def 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/AbiUtils.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/AbiUtils.java @@ -50,10 +50,12 @@ import com.oracle.svm.core.SubstrateTargetDescription; import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.foreign.AbiUtils.Adapter.Adaptation; import com.oracle.svm.core.graal.code.AssignedLocation; import com.oracle.svm.core.headers.LibC; import com.oracle.svm.core.headers.WindowsAPIs; import com.oracle.svm.core.util.BasedOnJDKClass; +import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.api.replacements.Fold; @@ -230,11 +232,15 @@ public static Adaptation check(Class type) { } public static Adaptation extract(Extracted as, Class type) { - return new Extract(Objects.requireNonNull(as), Objects.requireNonNull(type)); + return new ExtractSingle(as, type); + } + + public static Adaptation extractSegmentPair(Extracted as, Class type) { + return new ExtractSegmentPair(as); } public static Adaptation drop() { - return Extract.DROP; + return Drop.SINGLETON; } public static Adaptation reinterpret(JavaKind to) { @@ -342,41 +348,77 @@ public List apply(AssignedLocation parameter) { @Override public List apply(ValueNode parameter, Map extractedArguments, List originalArguments, int originalArgumentIndex, Consumer appendToGraph) { + return List.of(computeAbsolutePointerFromSegmentPair(parameter, originalArguments, originalArgumentIndex, appendToGraph)); + } + + static ValueNode computeAbsolutePointerFromSegmentPair(ValueNode parameter, List originalArguments, int originalArgumentIndex, Consumer appendToGraph) { var offsetArg = originalArguments.get(originalArgumentIndex + 1); - // I originally wanted to use an OffsetAddressNode here - // (followed by WordCastNode.addressToWord), - // but NativeMemorySegmentImpls return null for `unsafeGetBase`, - // which seems to break the graph somewhere later. + /* + * It would be most suitable to use OffsetAddressNode here (followed by + * WordCastNode.addressToWord) but NativeMemorySegmentImpls return null for + * `unsafeGetBase`,which seems to break the graph somewhere later. + */ var basePointer = WordCastNode.objectToUntrackedPointer(parameter, ConfigurationValues.getWordKind()); appendToGraph.accept(basePointer); var absolutePointer = AddNode.add(basePointer, offsetArg); appendToGraph.accept(absolutePointer); - return List.of(absolutePointer); + return absolutePointer; } } - private static final class Extract extends Adaptation { - private static final Extract DROP = new Extract(null, null); - private final Extracted as; - private final Class type; + /** + * Extract adaptations consume one or more stub parameters. In this case, "consuming" means + * that the adapted parameter(s) won't be passed to the stub's target but will be used by + * the stub itself. The result is usually one ValueNode that will be put into the + * 'extractedArguments' table. + */ + private abstract static class Extract extends Adaptation { + final Extracted as; - private Extract(Extracted as, Class type) { + private Extract(Extracted as) { this.as = as; - this.type = type; + } + + @Override + public final List apply(AssignedLocation parameter) { + return List.of(); + } + } + + private static final class Drop extends Extract { + private static final Drop SINGLETON = new Drop(); + + private Drop() { + super(null); } @Override public List> apply(Class parameter) { - if (type != null && parameter != type) { - throw new IllegalArgumentException("Expected type " + type + ", got " + parameter); - } return List.of(); } @Override - public List apply(AssignedLocation parameter) { + public List apply(ValueNode parameter, Map extractedArguments, List originalArguments, int originalArgumentIndex, + Consumer appendToGraph) { + return List.of(); + } + } + + private final static class ExtractSingle extends Extract { + private final Class type; + + private ExtractSingle(Extracted as, Class type) { + super(Objects.requireNonNull(as)); + this.type = Objects.requireNonNull(type); + } + + @Override + public List> apply(Class parameter) { + if (type != null && parameter != type) { + throw new IllegalArgumentException("Expected type " + type + ", got " + parameter); + } return List.of(); } @@ -392,6 +434,37 @@ public List apply(ValueNode parameter, Map extr return List.of(); } } + + /** + * Similar to {@link ComputeAddressFromSegmentPair}, consumes two parameters, i.e., an + * Object + offset pair, and creates and AddNode that computes the absolute address. + */ + private static final class ExtractSegmentPair extends Extract { + + private ExtractSegmentPair(Extracted as) { + super(Objects.requireNonNull(as)); + } + + @Override + public List> apply(Class parameter) { + if (parameter != Object.class) { + throw new IllegalArgumentException("Expected type " + Object.class + ", got " + parameter); + } + return List.of(); + } + + @Override + public List apply(ValueNode parameter, Map extractedArguments, List originalArguments, int originalArgumentIndex, + Consumer appendToGraph) { + assert as != null; + if (extractedArguments.containsKey(as)) { + throw new IllegalStateException("%s was already extracted (%s).".formatted(as, extractedArguments.get(as))); + } + ValueNode extracted = ComputeAddressFromSegmentPair.computeAbsolutePointerFromSegmentPair(parameter, originalArguments, originalArgumentIndex, appendToGraph); + extractedArguments.put(as, extracted); + return List.of(); + } + } } @Platforms(Platform.HOSTED_ONLY.class) @@ -438,6 +511,7 @@ public final Adapter.Result.TypeAdaptation adapt(JavaEntryPointInfo jep) { /** * Generate additional argument adaptations which are not done by HotSpot. */ + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/java.base/share/classes/jdk/internal/foreign/abi/CallingSequenceBuilder.java#L103-L147") @Platforms(Platform.HOSTED_ONLY.class) protected List generateAdaptations(NativeEntryPointInfo nep) { List adaptations = new ArrayList<>(Collections.nCopies(nep.methodType().parameterCount(), null)); @@ -446,36 +520,55 @@ protected List generateAdaptations(NativeEntryPointInfo nep) adaptations.set(current++, Adapter.check(long.class)); } adaptations.set(current++, Adapter.extract(Adapter.Extracted.CallTarget, long.class)); - if (nep.capturesCallState()) { - adaptations.set(current++, Adapter.extract(Adapter.Extracted.CaptureBufferAddress, long.class)); - } // Special handling in case Linker.Option.critical(true) is passed. - // See the doc of Adapter.CastObjectToWord.apply. + // See the doc of class Adapter.ComputeAddressFromSegmentPair var storages = nep.parametersAssignment(); + + /* + * It is possible to combine linker options 'captureCallState(...)' and 'critical(true)'. + * This means that one can pass a heap memory segment as capture buffer address. + */ + if (nep.capturesCallState()) { + if (nep.allowHeapAccess()) { + VMError.guarantee(storages[current] != null && storages[current + 1] == null); + // consumes two parameters (i.e. object + offset pair) + handleCriticalWithHeapAccess(nep, current + 1, adaptations, Adapter.extractSegmentPair(Adapter.Extracted.CaptureBufferAddress, long.class)); + current += 2; + } else { + adaptations.set(current, Adapter.extract(Adapter.Extracted.CaptureBufferAddress, long.class)); + current++; + } + } + for (int i = current; i < storages.length; ++i) { var storage = storages[i]; if (storage == null) { - VMError.guarantee(nep.allowHeapAccess(), "A storage may only be null when the Linker.Option.critical(true) option is passed."); - VMError.guarantee(JavaKind.fromJavaClass( - nep.methodType().parameterArray()[i]) == JavaKind.Long && - JavaKind.fromJavaClass(nep.methodType().parameterArray()[i - 1]) == JavaKind.Object, - """ - Storage is null, but the other parameters are inconsistent. - Storage may be null only if its kind is Long and previous kind is Object. - See jdk/internal/foreign/abi/x64/sysv/CallArranger.java:286"""); - VMError.guarantee( - adaptations.get(i) == null && adaptations.get(i - 1) == null, - "This parameter already has an adaptation when it should not."); - - adaptations.set(i, Adapter.drop()); - adaptations.set(i - 1, Adapter.computeAddressFromSegmentPair()); + handleCriticalWithHeapAccess(nep, i, adaptations, Adapter.computeAddressFromSegmentPair()); } } return adaptations; } + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/CallArranger.java#L280-L290") + private static void handleCriticalWithHeapAccess(NativeEntryPointInfo nep, int i, List adaptations, Adaptation adaptation) { + VMError.guarantee(nep.allowHeapAccess(), "A storage may only be null when the Linker.Option.critical(true) option is passed."); + VMError.guarantee( + JavaKind.fromJavaClass(nep.methodType().parameterArray()[i]) == JavaKind.Long && + JavaKind.fromJavaClass(nep.methodType().parameterArray()[i - 1]) == JavaKind.Object, + """ + Storage is null, but the other parameters are inconsistent. + Storage may be null only if its kind is Long and previous kind is Object. + See jdk/internal/foreign/abi/x64/sysv/CallArranger.java:286"""); + VMError.guarantee( + adaptations.get(i) == null && adaptations.get(i - 1) == null, + "This parameter already has an adaptation when it should not."); + + adaptations.set(i, Adapter.drop()); + adaptations.set(i - 1, adaptation); + } + @Platforms(Platform.HOSTED_ONLY.class) protected List generateAdaptations(JavaEntryPointInfo jep) { List adaptations = new ArrayList<>(Collections.nCopies(jep.handleType().parameterCount(), null)); diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java index abf07ec02d15..ed66d9578d8a 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java @@ -26,10 +26,13 @@ import static jdk.graal.compiler.core.common.spi.ForeignCallDescriptor.CallSideEffect.HAS_SIDE_EFFECT; +import java.lang.constant.DirectMethodHandleDesc; import java.lang.invoke.MethodHandle; import java.util.HashMap; import java.util.Map; +import java.util.function.BiConsumer; +import jdk.graal.compiler.word.Word; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; @@ -38,7 +41,6 @@ import org.graalvm.nativeimage.c.type.CIntPointer; import org.graalvm.word.LocationIdentity; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.FunctionPointerHolder; import com.oracle.svm.core.OS; @@ -61,11 +63,15 @@ public static ForeignFunctionsRuntime singleton() { private final AbiUtils.TrampolineTemplate trampolineTemplate = AbiUtils.singleton().generateTrampolineTemplate(); private final EconomicMap downcallStubs = EconomicMap.create(); + private final EconomicMap directUpcallStubs = EconomicMap.create(); private final EconomicMap upcallStubs = EconomicMap.create(); private final Map trampolines = new HashMap<>(); private TrampolineSet currentTrampolineSet; + // for testing: callback if direct upcall lookup succeeded + private BiConsumer usingSpecializedUpcallListener; + @Platforms(Platform.HOSTED_ONLY.class) public ForeignFunctionsRuntime() { } @@ -82,6 +88,12 @@ public void addUpcallStubPointer(JavaEntryPointInfo jep, CFunctionPointer ptr) { VMError.guarantee(upcallStubs.put(jep, new FunctionPointerHolder(ptr)) == null); } + @Platforms(Platform.HOSTED_ONLY.class) + public void addDirectUpcallStubPointer(DirectMethodHandleDesc desc, CFunctionPointer ptr) { + VMError.guarantee(!directUpcallStubs.containsKey(desc), "Seems like multiple stubs were generated for " + desc); + VMError.guarantee(directUpcallStubs.put(desc, new FunctionPointerHolder(ptr)) == null); + } + /** * We'd rather report the function descriptor than the native method type, but we don't have it * available here. One could intercept this exception in @@ -105,18 +117,64 @@ CFunctionPointer getUpcallStubPointer(JavaEntryPointInfo jep) { } Pointer registerForUpcall(MethodHandle methodHandle, JavaEntryPointInfo jep) { + /* + * Look up the upcall stub pointer first to avoid unnecessary allocation and synchronization + * if it doesn't exist. + */ + CFunctionPointer upcallStubPointer = getUpcallStubPointer(jep); synchronized (trampolines) { if (currentTrampolineSet == null || !currentTrampolineSet.hasFreeTrampolines()) { currentTrampolineSet = new TrampolineSet(trampolineTemplate); trampolines.put(currentTrampolineSet.base().rawValue(), currentTrampolineSet); } - return currentTrampolineSet.assignTrampoline(methodHandle, getUpcallStubPointer(jep)); + return currentTrampolineSet.assignTrampoline(methodHandle, upcallStubPointer); } } + /** + * Updates the stub address in the upcall trampoline with the address of a direct upcall stub. + * The trampoline is identified by the given native address and the direct upcall stub is + * identified by the method handle descriptor. + * + * @param trampolineAddress The address of the upcall trampoline. + * @param desc A direct method handle descriptor used to lookup the direct upcall stub. + */ + void patchForDirectUpcall(long trampolineAddress, DirectMethodHandleDesc desc) { + FunctionPointerHolder functionPointerHolder = directUpcallStubs.get(desc); + if (functionPointerHolder == null) { + return; + } + + Pointer trampolinePointer = Word.pointer(trampolineAddress); + Pointer trampolineSetBase = TrampolineSet.getAllocationBase(trampolinePointer); + TrampolineSet trampolineSet = trampolines.get(trampolineSetBase.rawValue()); + if (trampolineSet == null) { + return; + } + /* + * Synchronizing on 'trampolineSet' is not necessary at this point since we are still in the + * call context of 'Linker.upcallStub' and the allocated trampoline is owned by the + * allocating thread until it returns from the call. Also, the trampoline cannot be free'd + * between allocation and patching because the associated arena is still on the stack. + */ + trampolineSet.patchTrampolineForDirectUpcall(trampolinePointer, functionPointerHolder.functionPointer); + /* + * If we reach this point, everything went fine and the trampoline was patched with the + * specialized upcall stub's address. For testing, now report that the lookup and patching + * succeeded. + */ + if (usingSpecializedUpcallListener != null) { + usingSpecializedUpcallListener.accept(trampolineAddress, desc); + } + } + + public void setUsingSpecializedUpcallListener(BiConsumer listener) { + usingSpecializedUpcallListener = listener; + } + void freeTrampoline(long addr) { synchronized (trampolines) { - long base = TrampolineSet.getAllocationBase(WordFactory.pointer(addr)).rawValue(); + long base = TrampolineSet.getAllocationBase(Word.pointer(addr)).rawValue(); TrampolineSet trampolineSet = trampolines.get(base); if (trampolineSet.tryFree()) { trampolines.remove(base); diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/LinkToNativeSupportImpl.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/LinkToNativeSupportImpl.java index a96af26671d0..5ccebfd8a2be 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/LinkToNativeSupportImpl.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/LinkToNativeSupportImpl.java @@ -24,8 +24,8 @@ */ package com.oracle.svm.core.foreign; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.function.CFunctionPointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.LinkToNativeSupport; import com.oracle.svm.core.c.InvokeJavaFunctionPointer; @@ -47,7 +47,7 @@ public final class LinkToNativeSupportImpl implements LinkToNativeSupport { @Override public Object linkToNative(Object... args) throws Throwable { Target_jdk_internal_foreign_abi_NativeEntryPoint nep = (Target_jdk_internal_foreign_abi_NativeEntryPoint) args[args.length - 1]; - StubPointer pointer = WordFactory.pointer(nep.downcallStubAddress); + StubPointer pointer = Word.pointer(nep.downcallStubAddress); /* The nep argument will be dropped in the invoked function */ return pointer.invoke(args); } diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_AbstractLinker.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_AbstractLinker.java index 6d60e1a41344..4871809310e8 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_AbstractLinker.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_AbstractLinker.java @@ -24,11 +24,25 @@ */ package com.oracle.svm.core.foreign; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.MethodHandleDesc; +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemorySegment; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.util.Optional; + 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 jdk.internal.foreign.abi.AbstractLinker; +import jdk.internal.foreign.abi.AbstractLinker.UpcallStubFactory; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.x64.sysv.SysVx64Linker; +import jdk.internal.foreign.abi.x64.windows.Windowsx64Linker; @TargetClass(AbstractLinker.class) public final class Target_jdk_internal_foreign_abi_AbstractLinker { @@ -46,3 +60,55 @@ public final class Target_jdk_internal_foreign_abi_AbstractLinker { @TargetClass(className = "jdk.internal.foreign.abi.SoftReferenceCache") final class Target_jdk_internal_foreign_abi_SoftReferenceCache { } + +/** + * A decorator for jdk.internal.foreign.abi.UpcallStubFactory which intercepts the call to method + * 'makeStub'. It will (1) call the original factory to create the upcall, and (2) then use the + * method handle's descriptor to lookup if a specialized (direct) upcall stub is available. If so, + * the trampoline will be updated with the specialized stub's address. + * + * @param delegate The original upcall stub factory as created by JDK's call arranger. + */ +record UpcallStubFactoryDecorator(UpcallStubFactory delegate) implements UpcallStubFactory { + + @Override + public MemorySegment makeStub(MethodHandle target, Arena arena) { + MemorySegment segment = delegate.makeStub(target, arena); + + /* + * We cannot do this in 'UpcallLinker.makeUpcallStub' because that one already gets a + * different method handle that will handle parameter/return value bindings. Further, method + * handles cannot be compared. If the provided method handle is a DirectMethodHandle, we use + * the MH descriptor to check if there is a registered direct upcall stub. Then, we will + * patch the already allocated trampoline with a different upcall stub pointer. + */ + Optional methodHandleDesc = target.describeConstable(); + if (methodHandleDesc.isPresent() && methodHandleDesc.get() instanceof DirectMethodHandleDesc desc) { + ForeignFunctionsRuntime.singleton().patchForDirectUpcall(segment.address(), desc); + } + return segment; + } +} + +@TargetClass(value = SysVx64Linker.class, onlyWith = ForeignFunctionsEnabled.class) +final class Target_jdk_internal_foreign_abi_x64_sysv_SysVx64Linker { + + @Substitute + UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { + return new UpcallStubFactoryDecorator(jdk.internal.foreign.abi.x64.sysv.CallArranger.arrangeUpcall(targetType, function, options)); + } +} + +@TargetClass(value = Windowsx64Linker.class, onlyWith = ForeignFunctionsEnabled.class) +final class Target_jdk_internal_foreign_abi_x64_windows_Windowsx64Linker { + + @Substitute + UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { + return new UpcallStubFactoryDecorator(jdk.internal.foreign.abi.x64.windows.CallArranger.arrangeUpcall(targetType, function, options)); + } +} + +/* + * GR-58659, GR-58660: add substitutions for LinuxAArch64Linker and MacOsAArch64Linker here once we + * support them. + */ \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_NativeEntryPoint.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_NativeEntryPoint.java index 6bf7824d6b44..89a67468d7b6 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_NativeEntryPoint.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_NativeEntryPoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -58,7 +58,8 @@ public static Target_jdk_internal_foreign_abi_NativeEntryPoint make(ABIDescripto MethodType methodType, boolean needsReturnBuffer, int capturedStateMask, - boolean needsTransition) { + boolean needsTransition, + boolean usingAddressPairs) { /* * A VMStorage may be null only when the Linker.Option.critical(allowHeapAccess=true) option * is passed. (see diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java index c4580c07ca33..5efb26c5a24f 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java @@ -41,7 +41,7 @@ * It seems like this could be easily supported once thread-local handshakes are supported. */ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+15/src/java.base/share/classes/jdk/internal/misc/X-ScopedMemoryAccess-bin.java.template") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+15/src/java.base/share/classes/jdk/internal/misc/X-ScopedMemoryAccess.java.template") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+2/src/java.base/share/classes/jdk/internal/misc/X-ScopedMemoryAccess.java.template") @TargetClass(className = "jdk.internal.misc.ScopedMemoryAccess", onlyWith = ForeignFunctionsEnabled.class) public final class Target_jdk_internal_misc_ScopedMemoryAccess { @Substitute diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/TrampolineSet.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/TrampolineSet.java index 61974e0518b6..8ff65ab6f45c 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/TrampolineSet.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/TrampolineSet.java @@ -26,17 +26,19 @@ import java.lang.invoke.MethodHandle; import java.util.ArrayList; +import java.util.BitSet; import java.util.List; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.PinnedObject; import org.graalvm.nativeimage.c.function.CFunctionPointer; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.os.VirtualMemoryProvider; +import com.oracle.svm.core.util.UnsignedUtils; import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.core.common.NumUtil; @@ -73,8 +75,22 @@ public static Pointer getAllocationBase(Pointer ptr) { private final int trampolineCount = maxTrampolineCount(); private final PointerBase[] methodHandles = new PointerBase[trampolineCount]; private final CFunctionPointer[] stubs = new CFunctionPointer[trampolineCount]; + private final BitSet patchedStubs; private final Pointer trampolines; + private static BitSet initializedPatchedStubs(int nbits) { + BitSet patchedStubs = null; + assert (patchedStubs = new BitSet(nbits)).isEmpty(); + return patchedStubs; + } + + private boolean getAndSetPatchedStub(int id) { + assert patchedStubs != null; + boolean res = patchedStubs.get(id); + patchedStubs.set(id); + return res; + } + private PinnedObject pin(Object object) { PinnedObject pinned = PinnedObject.create(object); pins.add(pinned); @@ -86,6 +102,7 @@ private PinnedObject pin(Object object) { assert trampolineCount <= maxTrampolineCount(); trampolines = prepareTrampolines(pin(methodHandles), pin(stubs), template); + this.patchedStubs = initializedPatchedStubs(stubs.length); } Pointer base() { @@ -103,15 +120,24 @@ Pointer assignTrampoline(MethodHandle methodHandle, CFunctionPointer upcallStubP methodHandles[id] = pinned.addressOfObject(); stubs[id] = upcallStubPointer; + assert !patchedStubs.get(id); return trampolines.add(id * AbiUtils.singleton().trampolineSize()); } + void patchTrampolineForDirectUpcall(Pointer trampolinePointer, CFunctionPointer directUpcallStubPointer) { + VMError.guarantee(trampolinePointer.aboveOrEqual(trampolines), "invalid trampoline pointer"); + int id = UnsignedUtils.safeToInt(trampolinePointer.subtract(trampolines).unsignedDivide(AbiUtils.singleton().trampolineSize())); + VMError.guarantee(id >= 0 && id < stubs.length, "invalid trampoline id"); + assert !getAndSetPatchedStub(id) : "attempt to patch trampoline twice"; + stubs[id] = directUpcallStubPointer; + } + private Pointer prepareTrampolines(PinnedObject mhsArray, PinnedObject stubsArray, AbiUtils.TrampolineTemplate template) { VirtualMemoryProvider memoryProvider = VirtualMemoryProvider.get(); UnsignedWord pageSize = allocationSize(); /* We request a specific alignment to guarantee correctness of getAllocationBase */ - Pointer page = memoryProvider.commit(WordFactory.nullPointer(), pageSize, VirtualMemoryProvider.Access.WRITE | VirtualMemoryProvider.Access.FUTURE_EXECUTE); + Pointer page = memoryProvider.commit(Word.nullPointer(), pageSize, VirtualMemoryProvider.Access.WRITE | VirtualMemoryProvider.Access.FUTURE_EXECUTE); if (page.isNull()) { throw new OutOfMemoryError("Could not allocate memory for trampolines."); } @@ -142,6 +168,9 @@ boolean tryFree() { } VirtualMemoryProvider.get().free(trampolines, allocationSize()); assigned = FREED; + if (patchedStubs != null) { + patchedStubs.clear(); + } return true; } } diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/UpcallStubsHolder.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/UpcallStubsHolder.java index 1a71459d279e..e76dfe455bfe 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/UpcallStubsHolder.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/UpcallStubsHolder.java @@ -54,10 +54,14 @@ public static ConstantPool getConstantPool(MetaAccessProvider metaAccess) { * */ @Platforms(Platform.HOSTED_ONLY.class) - public static String stubName(JavaEntryPointInfo jep, boolean highLevel) { + public static String stubName(JavaEntryPointInfo jep, boolean highLevel, boolean direct) { MethodType type = jep.handleType(); - StringBuilder builder = new StringBuilder("upcall"); + StringBuilder builder = new StringBuilder(); + if (direct) { + builder.append("direct_"); + } + builder.append("upcall"); builder.append(highLevel ? "High" : "Low"); builder.append("_"); for (var param : type.parameterArray()) { diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/OWNERS.toml b/substratevm/src/com.oracle.svm.core.genscavenge/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.genscavenge/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java index 0602142883fd..7758d3083943 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java @@ -26,10 +26,10 @@ import java.util.concurrent.atomic.AtomicBoolean; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateGCOptions; import com.oracle.svm.core.Uninterruptible; @@ -66,7 +66,7 @@ static int getMaxSurvivorSpaces(Integer userValue) { protected static final int DEFAULT_TIME_WEIGHT = 25; // -XX:AdaptiveTimeWeight /* Constants to compute defaults for values which can be set through existing options. */ - protected static final UnsignedWord INITIAL_HEAP_SIZE = WordFactory.unsigned(128 * 1024 * 1024); + protected static final UnsignedWord INITIAL_HEAP_SIZE = Word.unsigned(128 * 1024 * 1024); protected static final int NEW_RATIO = 2; // HotSpot: -XX:NewRatio protected final AdaptiveWeightedAverage avgYoungGenAlignedChunkFraction = new AdaptiveWeightedAverage(DEFAULT_TIME_WEIGHT); @@ -326,7 +326,7 @@ protected SizeParameters computeSizeParameters(SizeParameters existing) { UnsignedWord maxHeap; long optionMax = SubstrateGCOptions.MaxHeapSize.getValue(); if (optionMax > 0L) { - maxHeap = WordFactory.unsigned(optionMax); + maxHeap = Word.unsigned(optionMax); } else { maxHeap = PhysicalMemory.size().unsignedDivide(100).multiply(HeapParameters.getMaximumHeapSizePercent()); } @@ -336,7 +336,7 @@ protected SizeParameters computeSizeParameters(SizeParameters existing) { UnsignedWord maxYoung; long optionMaxYoung = SubstrateGCOptions.MaxNewSize.getValue(); if (optionMaxYoung > 0L) { - maxYoung = WordFactory.unsigned(optionMaxYoung); + maxYoung = Word.unsigned(optionMaxYoung); } else if (SerialAndEpsilonGCOptions.MaximumYoungGenerationSizePercent.hasBeenSet()) { maxYoung = maxHeap.unsignedDivide(100).multiply(HeapParameters.getMaximumYoungGenerationSizePercent()); } else { @@ -349,10 +349,10 @@ protected SizeParameters computeSizeParameters(SizeParameters existing) { VMError.guarantee(maxOld.aboveOrEqual(minSpaceSize()) && maxHeap.belowOrEqual(addressSpaceSize) && (maxHeap.belowOrEqual(unadjustedMaxHeap) || unadjustedMaxHeap.belowThan(minAllSpaces))); - UnsignedWord minHeap = WordFactory.zero(); + UnsignedWord minHeap = Word.zero(); long optionMin = SubstrateGCOptions.MinHeapSize.getValue(); if (optionMin > 0L) { - minHeap = WordFactory.unsigned(optionMin); + minHeap = Word.unsigned(optionMin); } minHeap = UnsignedUtils.clamp(alignUp(minHeap), minAllSpaces, maxHeap); @@ -366,7 +366,7 @@ protected SizeParameters computeSizeParameters(SizeParameters existing) { initialYoung = initialHeap.unsignedDivide(initialNewRatio + 1); initialYoung = UnsignedUtils.clamp(alignUp(initialYoung), minYoungSpaces, maxYoung); } - UnsignedWord initialSurvivor = WordFactory.zero(); + UnsignedWord initialSurvivor = Word.zero(); if (HeapParameters.getMaxSurvivorSpaces() > 0) { /* * In HotSpot, this is the reserved capacity of each of the survivor From and To spaces, @@ -427,7 +427,7 @@ private SizeParameters(UnsignedWord maxHeapSize, UnsignedWord maxYoungSize, Unsi @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) UnsignedWord maxSurvivorSize() { if (HeapParameters.getMaxSurvivorSpaces() == 0) { - return WordFactory.zero(); + return Word.zero(); } UnsignedWord size = maxYoungSize.unsignedDivide(MIN_SURVIVOR_RATIO); return minSpaceSize(alignDown(size)); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractMemoryPoolMXBean.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractMemoryPoolMXBean.java index d0724fd7b597..8392cffda696 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractMemoryPoolMXBean.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractMemoryPoolMXBean.java @@ -33,10 +33,10 @@ import javax.management.ObjectName; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.heap.AbstractMXBean; import com.oracle.svm.core.jdk.UninterruptibleUtils; @@ -45,7 +45,7 @@ public abstract class AbstractMemoryPoolMXBean extends AbstractMXBean implements MemoryPoolMXBean { - protected static final UnsignedWord UNDEFINED = WordFactory.unsigned(UNDEFINED_MEMORY_USAGE); + protected static final UnsignedWord UNDEFINED = Word.unsigned(UNDEFINED_MEMORY_USAGE); private final String name; private final String[] managerNames; @@ -157,7 +157,7 @@ public long getCollectionUsageThresholdCount() { @Override public void resetPeakUsage() { - peakUsage.set(WordFactory.zero()); + peakUsage.set(Word.zero()); } void updatePeakUsage(UnsignedWord value) { diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java index ecefb5ddb7a8..576d94b7f8ee 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java @@ -26,11 +26,12 @@ import static com.oracle.svm.core.genscavenge.CollectionPolicy.shouldCollectYoungGenSeparately; +import jdk.graal.compiler.word.Word; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.heap.GCCause; +import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.core.util.TimeUtils; import com.oracle.svm.core.util.UnsignedUtils; @@ -42,6 +43,13 @@ * its base class {@code AdaptiveSizePolicy}. Method and variable names have been kept mostly the * same for comparability. */ +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+2/src/hotspot/share/gc/shared/adaptiveSizePolicy.hpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+1/src/hotspot/share/gc/shared/adaptiveSizePolicy.cpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+1/src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.hpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+1/src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.cpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+1/src/hotspot/share/gc/parallel/psParallelCompact.cpp#L951-L1174") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+1/src/hotspot/share/gc/parallel/psScavenge.cpp#L321-L639") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+1/src/hotspot/share/gc/shared/gc_globals.hpp#L308-L420") class AdaptiveCollectionPolicy extends AbstractCollectionPolicy { /* @@ -126,7 +134,7 @@ class AdaptiveCollectionPolicy extends AbstractCollectionPolicy { private long minorCount; private long latestMinorMutatorIntervalNanos; private boolean youngGenPolicyIsReady; - private UnsignedWord youngGenSizeIncrementSupplement = WordFactory.unsigned(YOUNG_GENERATION_SIZE_SUPPLEMENT); + private UnsignedWord youngGenSizeIncrementSupplement = Word.unsigned(YOUNG_GENERATION_SIZE_SUPPLEMENT); private long youngGenChangeForMinorThroughput; private int minorCountSinceMajorCollection; @@ -137,7 +145,7 @@ class AdaptiveCollectionPolicy extends AbstractCollectionPolicy { private final AdaptiveWeightedAverage avgOldLive = new AdaptiveWeightedAverage(ADAPTIVE_SIZE_POLICY_WEIGHT); private final ReciprocalLeastSquareFit majorCostEstimator = new ReciprocalLeastSquareFit(ADAPTIVE_SIZE_COST_ESTIMATORS_HISTORY_LENGTH); private long majorCount; - private UnsignedWord oldGenSizeIncrementSupplement = WordFactory.unsigned(TENURED_GENERATION_SIZE_SUPPLEMENT); + private UnsignedWord oldGenSizeIncrementSupplement = Word.unsigned(TENURED_GENERATION_SIZE_SUPPLEMENT); private long latestMajorMutatorIntervalNanos; private boolean oldSizeExceededInPreviousCollection; private long oldGenChangeForMajorThroughput; @@ -152,7 +160,7 @@ public String getName() { } @Override - public boolean shouldCollectCompletely(boolean followingIncrementalCollection) { // should_{attempt_scavenge,full_GC} + public boolean shouldCollectCompletely(boolean followingIncrementalCollection) { // should_attempt_scavenge guaranteeSizeParametersInitialized(); if (!followingIncrementalCollection && shouldCollectYoungGenSeparately(!SerialGCOptions.useCompactingOldGen())) { @@ -182,17 +190,7 @@ public boolean shouldCollectCompletely(boolean followingIncrementalCollection) { return true; } - UnsignedWord youngUsed = HeapImpl.getHeapImpl().getYoungGeneration().getChunkBytes(); - UnsignedWord oldUsed = HeapImpl.getHeapImpl().getOldGeneration().getChunkBytes(); - - /* - * If the remaining free space in the old generation is less than what is expected to be - * needed by the next collection, do a full collection now. - */ - UnsignedWord averagePromoted = UnsignedUtils.fromDouble(avgPromoted.getPaddedAverage()); - UnsignedWord promotionEstimate = UnsignedUtils.min(averagePromoted, youngUsed); - UnsignedWord oldFree = oldSize.subtract(oldUsed); - return promotionEstimate.aboveThan(oldFree); + return false; } private void updateAverages(boolean isSurvivorOverflow, UnsignedWord survivedChunkBytes, UnsignedWord promotedChunkBytes) { @@ -325,7 +323,7 @@ private static UnsignedWord scaleDown(UnsignedWord change, UnsignedWord part, Un } private static UnsignedWord edenDecrement(UnsignedWord curEden) { - return spaceIncrement(curEden, WordFactory.unsigned(YOUNG_GENERATION_SIZE_INCREMENT)) + return spaceIncrement(curEden, Word.unsigned(YOUNG_GENERATION_SIZE_INCREMENT)) .unsignedDivide(ADAPTIVE_SIZE_DECREMENT_SCALE_FACTOR); } @@ -514,7 +512,7 @@ private static UnsignedWord promoDecrement(UnsignedWord curPromo) { } private static UnsignedWord promoIncrement(UnsignedWord curPromo) { - return spaceIncrement(curPromo, WordFactory.unsigned(TENURED_GENERATION_SIZE_INCREMENT)); + return spaceIncrement(curPromo, Word.unsigned(TENURED_GENERATION_SIZE_INCREMENT)); } private UnsignedWord promoIncrementWithSupplementAlignedUp(UnsignedWord curPromo) { diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AddressRangeCommittedMemoryProvider.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AddressRangeCommittedMemoryProvider.java new file mode 100644 index 000000000000..d9542a7f1480 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AddressRangeCommittedMemoryProvider.java @@ -0,0 +1,826 @@ +/* + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.genscavenge; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import static com.oracle.svm.core.util.PointerUtils.roundDown; +import static com.oracle.svm.core.util.PointerUtils.roundUp; +import static com.oracle.svm.core.util.VMError.guarantee; +import static jdk.graal.compiler.word.Word.unsigned; +import static jdk.graal.compiler.word.Word.nullPointer; + +import jdk.graal.compiler.word.Word; +import org.graalvm.nativeimage.Isolate; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.word.Pointer; +import org.graalvm.word.PointerBase; +import org.graalvm.word.UnsignedWord; + +import com.oracle.svm.core.IsolateArgumentAccess; +import com.oracle.svm.core.IsolateArgumentParser; +import com.oracle.svm.core.IsolateArguments; +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.SubstrateGCOptions; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.VMInspectionOptions; +import com.oracle.svm.core.c.function.CEntryPointErrors; +import com.oracle.svm.core.graal.snippets.CEntryPointSnippets; +import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.heap.OutOfMemoryUtil; +import com.oracle.svm.core.heap.ReferenceAccess; +import com.oracle.svm.core.heap.RestrictHeapAccess; +import com.oracle.svm.core.locks.VMMutex; +import com.oracle.svm.core.log.Log; +import com.oracle.svm.core.memory.NullableNativeMemory; +import com.oracle.svm.core.nmt.NativeMemoryTracking; +import com.oracle.svm.core.nmt.NmtCategory; +import com.oracle.svm.core.os.ChunkBasedCommittedMemoryProvider; +import com.oracle.svm.core.os.ImageHeapProvider; +import com.oracle.svm.core.os.VirtualMemoryProvider; +import com.oracle.svm.core.snippets.KnownIntrinsics; +import com.oracle.svm.core.thread.VMOperation; +import com.oracle.svm.core.util.PointerUtils; +import com.oracle.svm.core.util.UnsignedUtils; +import com.oracle.svm.core.util.VMError; + +import jdk.graal.compiler.api.replacements.Fold; + +/** + * Reserves a fixed-size address range and provides memory from it by committing and uncommitting + * virtual memory within that range. + *

      + * The main objective of this code is to keep external fragmentation low so that an + * {@linkplain Isolate} is unlikely to run out of memory because its address space is exhausted. To + * accomplish that, allocation requests are satisfied with a best-fit strategy that traverses the + * {@linkplain #allocListHead entire list of allocatable blocks} and chooses the smallest block that + * satisfies the request. + *

      + * Allocating memory usually involves splitting a block. When a new block is smaller than the + * minimum size that can be allocated, it is kept only in a {@linkplain #unusedListHead separate + * list that contains all unused blocks}. This list is needed when adjacent memory areas are freed + * so that they can be coalesced into a larger memory area. Observations have shown that the list of + * allocatable blocks is around half of the size of the list of all unused blocks. Overall, list + * traversals should be negligible in contrast to the cost of the performed commit and uncommit + * operations that require system calls. Avoiding these operations is not a design goal of this + * class and should be implemented by code using it. + *

      + * However, traversing the list of allocatable blocks might become expensive in long-running + * programs due to increasing fragmentation. In that case, the list could be replaced by a + * self-balancing search tree with block sizes as keys, which guarantees logarithmic time complexity + * for allocation. Several blocks of the same size could be grouped in one tree node to reduce tree + * operations (particularly balancing). + */ +public class AddressRangeCommittedMemoryProvider extends ChunkBasedCommittedMemoryProvider { + private static final long MIN_RESERVED_ADDRESS_SPACE_SIZE = 32L * 1024 * 1024 * 1024; + + protected static final int NO_ERROR = 0; + protected static final int OUT_OF_ADDRESS_SPACE = 1; + protected static final int COMMIT_FAILED = 2; + + private static final OutOfMemoryError NODE_ALLOCATION_FAILED = new OutOfMemoryError("Could not allocate node for free list, OS may be out of memory."); + private static final OutOfMemoryError ALIGNED_OUT_OF_ADDRESS_SPACE = new OutOfMemoryError("Could not allocate an aligned heap chunk because the heap address space is exhausted. " + + "Consider increasing the address space size (see option -XX:ReservedAddressSpaceSize)."); + private static final OutOfMemoryError UNALIGNED_OUT_OF_ADDRESS_SPACE = new OutOfMemoryError("Could not allocate an unaligned heap chunk because the heap address space is exhausted. " + + "Consider increasing the address space size (see option -XX:ReservedAddressSpaceSize)."); + private static final OutOfMemoryError ALIGNED_COMMIT_FAILED = new OutOfMemoryError("Could not commit the memory for an aligned heap chunk, OS may be out of memory."); + private static final OutOfMemoryError UNALIGNED_COMMIT_FAILED = new OutOfMemoryError("Could not commit the memory for an unaligned heap chunk, OS may be out of memory."); + + /** + * This mutex is used by the GC and the application. The application may hold this mutex only in + * uninterruptible code to prevent the case that a safepoint can be initiated while the + * application holds the mutex. Otherwise, we would risk deadlocks between the application and + * the GC. + */ + private final VMMutex lock = new VMMutex("freeList"); + + /** Contains free blocks that are large enough to fit allocations. */ + protected FreeListNode allocListHead; + protected long allocListCount; + + /** Contains all free blocks, including small blocks that are needed for coalescing. */ + protected FreeListNode unusedListHead; + protected long unusedListCount; + + protected UnsignedWord reservedSpaceSize; + + protected Pointer collectedHeapBegin; + protected UnsignedWord collectedHeapSize; + + @Platforms(Platform.HOSTED_ONLY.class) + public AddressRangeCommittedMemoryProvider() { + assert SubstrateOptions.SpawnIsolates.getValue(); + } + + @Override + @Uninterruptible(reason = "Still being initialized.") + public int initialize(WordPointer heapBasePointer, IsolateArguments arguments) { + UnsignedWord addressSpaceSize = ReferenceAccess.singleton().getAddressSpaceSize(); + UnsignedWord reserved = Word.unsigned(IsolateArgumentAccess.readLong(arguments, IsolateArgumentParser.getOptionIndex(SubstrateGCOptions.ReservedAddressSpaceSize))); + if (reserved.equal(0)) { + /* + * By default, always reserve at least 32 GB of address space. If a large maximum heap + * size was specified, then reserve 2x of that maximum heap size (assuming that the max. + * address space size is large enough for that). + */ + UnsignedWord maxHeapSize = Word.unsigned(IsolateArgumentAccess.readLong(arguments, IsolateArgumentParser.getOptionIndex(SubstrateGCOptions.MaxHeapSize))).multiply(2); + reserved = UnsignedUtils.clamp(maxHeapSize, Word.unsigned(MIN_RESERVED_ADDRESS_SPACE_SIZE), addressSpaceSize); + } + + UnsignedWord alignment = unsigned(Heap.getHeap().getPreferredAddressSpaceAlignment()); + WordPointer beginOut = StackValue.get(WordPointer.class); + int errorCode = reserveHeapMemory(reserved, alignment, arguments, beginOut); + if (errorCode != CEntryPointErrors.NO_ERROR) { + return errorCode; + } + + Pointer begin = beginOut.read(); + WordPointer imageHeapEndOut = StackValue.get(WordPointer.class); + errorCode = ImageHeapProvider.get().initialize(begin, reserved, heapBasePointer, imageHeapEndOut); + if (errorCode != CEntryPointErrors.NO_ERROR) { + freeOnInitializeError(begin, reserved); + return errorCode; + } + + CEntryPointSnippets.setHeapBase(heapBasePointer.read()); + WordPointer runtimeHeapBeginOut = StackValue.get(WordPointer.class); + errorCode = getCollectedHeapBegin(arguments, begin, reserved, imageHeapEndOut.read(), runtimeHeapBeginOut); + if (errorCode != CEntryPointErrors.NO_ERROR) { + freeOnInitializeError(begin, reserved); + return errorCode; + } + + /* + * We can access the image heap from here on, but our `this` argument is not safe to use + * because the image heap was not initialized when we were called, so we invoke a static + * method that loads a new reference to our instance. + */ + errorCode = initialize(begin, reserved, runtimeHeapBeginOut.read()); + if (errorCode != CEntryPointErrors.NO_ERROR) { + freeOnInitializeError(begin, reserved); + } + return errorCode; + } + + @Uninterruptible(reason = "Still being initialized.") + protected int getCollectedHeapBegin(@SuppressWarnings("unused") IsolateArguments arguments, @SuppressWarnings("unused") Pointer begin, @SuppressWarnings("unused") UnsignedWord reserved, + Pointer imageHeapEnd, WordPointer collectedHeapBeginOut) { + Pointer result = roundUp(imageHeapEnd, getGranularity()); + collectedHeapBeginOut.write(result); + return CEntryPointErrors.NO_ERROR; + } + + @NeverInline("Ensure a newly looked up value is used as 'this', now that the image heap is initialized") + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE) + private static int initialize(Pointer spaceBegin, UnsignedWord spaceSize, Pointer collectedHeapBegin) { + if (VMInspectionOptions.hasNativeMemoryTrackingSupport()) { + UnsignedWord imageHeapAddressSpace = ImageHeapProvider.get().getImageHeapAddressSpaceSize(); + UnsignedWord javaHeapAddressSpace = spaceSize.subtract(imageHeapAddressSpace); + NativeMemoryTracking.singleton().trackReserve(javaHeapAddressSpace, NmtCategory.JavaHeap); + } + + AddressRangeCommittedMemoryProvider provider = (AddressRangeCommittedMemoryProvider) ChunkBasedCommittedMemoryProvider.get(); + return provider.initializeFields(spaceBegin, spaceSize, collectedHeapBegin); + } + + @SuppressWarnings("hiding") + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected int initializeFields(Pointer spaceBegin, UnsignedWord reservedSpaceSize, Pointer collectedHeapBegin) { + this.reservedSpaceSize = reservedSpaceSize; + this.collectedHeapBegin = collectedHeapBegin; + this.collectedHeapSize = spaceBegin.add(reservedSpaceSize).subtract(collectedHeapBegin); + + FreeListNode node = allocNodeOrNull(collectedHeapBegin, collectedHeapSize); + if (node.isNull()) { + return CEntryPointErrors.ALLOCATION_FAILED; + } + + this.unusedListHead = node; + this.unusedListCount = 1; + this.allocListHead = node; + this.allocListCount = 1; + + return CEntryPointErrors.NO_ERROR; + } + + @Uninterruptible(reason = "Still being initialized.") + protected int reserveHeapMemory(UnsignedWord reserved, UnsignedWord alignment, IsolateArguments arguments, WordPointer beginOut) { + Pointer begin = reserveHeapMemory0(reserved, alignment, arguments); + if (begin.isNull()) { + return CEntryPointErrors.RESERVE_ADDRESS_SPACE_FAILED; + } + beginOut.write(begin); + return CEntryPointErrors.NO_ERROR; + } + + @Uninterruptible(reason = "Still being initialized.") + protected Pointer reserveHeapMemory0(UnsignedWord reserved, UnsignedWord alignment, @SuppressWarnings("unused") IsolateArguments arguments) { + return VirtualMemoryProvider.get().reserve(reserved, alignment, false); + } + + @Uninterruptible(reason = "Still being initialized.") + protected void freeOnInitializeError(Pointer begin, UnsignedWord reserved) { + VirtualMemoryProvider.get().free(begin, reserved); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private FreeListNode allocNode(Pointer start, UnsignedWord size) { + FreeListNode node = allocNodeOrNull(start, size); + if (node.isNull()) { + throw NODE_ALLOCATION_FAILED; + } + return node; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private FreeListNode allocNodeOrNull(Pointer start, UnsignedWord size) { + FreeListNode node = NullableNativeMemory.calloc(sizeOfFreeListNode(), NmtCategory.GC); + if (node.isNonNull()) { + setBounds(node, start, size); + } + return node; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected UnsignedWord sizeOfFreeListNode() { + return SizeOf.unsigned(FreeListNode.class); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static void freeNode(FreeListNode node) { + NullableNativeMemory.free(node); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static boolean isAllocatable(UnsignedWord size) { + return size.aboveOrEqual(minAllocationSize()); + } + + @Fold + static UnsignedWord minAllocationSize() { + return UnsignedUtils.min(HeapParameters.getAlignedHeapChunkSize(), HeapParameters.getMinUnalignedChunkSize()); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void setBounds(FreeListNode node, Pointer start, UnsignedWord size) { + assert UnsignedUtils.isAMultiple(start, getGranularity()); + assert UnsignedUtils.isAMultiple(size, getGranularity()); + assert start.aboveOrEqual(collectedHeapBegin); + assert size.belowOrEqual(collectedHeapSize); + assert start.add(size).belowOrEqual(collectedHeapBegin.add(collectedHeapSize)); + + node.setStart(start); + node.setSize(size); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected static Pointer getNodeEnd(FreeListNode node) { + return node.getStart().add(node.getSize()); + } + + @Override + @Uninterruptible(reason = "Tear-down in progress.") + public int tearDown() { + FreeListNode node = unusedListHead; + while (node.isNonNull()) { + FreeListNode next = node.getUnusedNext(); + freeNode(node); + node = next; + } + // ImageHeapProvider.freeImageHeap must not be called because the ImageHeapProvider did not + // allocate any memory for the image heap. + return unmapAddressSpace(KnownIntrinsics.heapBase()); + } + + @Uninterruptible(reason = "Tear-down in progress.") + protected int unmapAddressSpace(PointerBase heapBase) { + if (VirtualMemoryProvider.get().free(heapBase, reservedSpaceSize) != 0) { + return CEntryPointErrors.FREE_ADDRESS_SPACE_FAILED; + } + return CEntryPointErrors.NO_ERROR; + } + + @Override + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public Pointer allocateAlignedChunk(UnsignedWord nbytes, UnsignedWord alignment) { + WordPointer allocOut = UnsafeStackValue.get(WordPointer.class); + int error = allocateInHeapAddressSpace(nbytes, alignment, allocOut); + if (error == NO_ERROR) { + if (VMInspectionOptions.hasNativeMemoryTrackingSupport()) { + NativeMemoryTracking.singleton().trackCommit(nbytes, NmtCategory.JavaHeap); + } + return allocOut.read(); + } + throw reportAlignedChunkAllocationFailed(error); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected OutOfMemoryError reportAlignedChunkAllocationFailed(int error) { + if (error == OUT_OF_ADDRESS_SPACE) { + throw OutOfMemoryUtil.reportOutOfMemoryError(ALIGNED_OUT_OF_ADDRESS_SPACE); + } else if (error == COMMIT_FAILED) { + throw OutOfMemoryUtil.reportOutOfMemoryError(ALIGNED_COMMIT_FAILED); + } else { + throw VMError.shouldNotReachHereAtRuntime(); + } + } + + @Override + public Pointer allocateUnalignedChunk(UnsignedWord nbytes) { + WordPointer allocOut = UnsafeStackValue.get(WordPointer.class); + int error = allocateInHeapAddressSpace(nbytes, getAlignmentForUnalignedChunks(), allocOut); + if (error == NO_ERROR) { + if (VMInspectionOptions.hasNativeMemoryTrackingSupport()) { + NativeMemoryTracking.singleton().trackCommit(nbytes, NmtCategory.JavaHeap); + } + return allocOut.read(); + } + throw reportUnalignedChunkAllocationFailed(error); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected OutOfMemoryError reportUnalignedChunkAllocationFailed(int error) { + if (error == OUT_OF_ADDRESS_SPACE) { + throw OutOfMemoryUtil.reportOutOfMemoryError(UNALIGNED_OUT_OF_ADDRESS_SPACE); + } else if (error == COMMIT_FAILED) { + throw OutOfMemoryUtil.reportOutOfMemoryError(UNALIGNED_COMMIT_FAILED); + } else { + throw VMError.shouldNotReachHereAtRuntime(); + } + } + + /** + * Allocates memory from the address space. If the allocation succeeded, {@link #NO_ERROR} is + * returned. The allocated memory is always committed. + */ + @Uninterruptible(reason = "Entering a safepoint in this code can deadlock garbage collection.") + protected int allocateInHeapAddressSpace(UnsignedWord size, UnsignedWord alignment, WordPointer allocOut) { + assert size.aboveThan(0); + assert alignment.aboveThan(0); + + // this code is also executed in JNI_CreateJavaVM, so we don't always know the owning thread + lock.lockNoTransitionUnspecifiedOwner(); + try { + // Find best fit for requested size and alignment + FreeListNode fit = nullPointer(); + FreeListNode fitAllocPrevious = nullPointer(); + UnsignedWord fitSize = UnsignedUtils.MAX_VALUE; + for (FreeListNode previous = nullPointer(), node = allocListHead; node.isNonNull(); previous = node, node = node.getAllocNext()) { + UnsignedWord nodeSize = node.getSize(); + if (nodeSize.aboveOrEqual(size) && nodeSize.belowThan(fitSize)) { + Pointer offset = roundUp(node.getStart(), alignment).subtract(node.getStart()); + if (nodeSize.subtract(size).aboveOrEqual(offset)) { + fitAllocPrevious = previous; + fit = node; + fitSize = nodeSize; + if (nodeSize.equal(size)) { + break; // perfect fit + } + } + } + } + + if (fit.isNull()) { + allocOut.write(nullPointer()); + return OUT_OF_ADDRESS_SPACE; + } + + UnsignedWord pageSize = getGranularity(); + + // Determine an area that satisfies both requested alignment and page size alignment + Pointer fitStart = fit.getStart(); + assert PointerUtils.isAMultiple(fitStart, pageSize); + assert UnsignedUtils.isAMultiple(fitSize, pageSize); + + Pointer allocated = roundUp(fitStart, alignment); + Pointer mapBegin = roundDown(allocated, pageSize); + Pointer mapEnd = roundUp(allocated.add(size), pageSize); + UnsignedWord alignedSize = mapEnd.subtract(mapBegin); + assert mapBegin.aboveOrEqual(fitStart) && mapEnd.belowOrEqual(fitStart.add(fitSize)); + + final int access = VirtualMemoryProvider.Access.READ | VirtualMemoryProvider.Access.WRITE; + Pointer actualMapBegin = VirtualMemoryProvider.get().commit(mapBegin, alignedSize, access); + if (actualMapBegin.isNull()) { + allocOut.write(nullPointer()); + return COMMIT_FAILED; + } + VMError.guarantee(actualMapBegin.equal(mapBegin), "Must not be mapped anywhere else."); + + /* + * Update lists with leftover memory, reusing the existing node whenever possible. If + * possible, we reuse the existing node in a way that we don't need to touch allocList. + */ + UnsignedWord leadingSize = mapBegin.subtract(fitStart); + UnsignedWord trailingSize = fitStart.add(fitSize).subtract(mapEnd); + + boolean perfectFit = false; + if (leadingSize.aboveThan(0) && trailingSize.aboveThan(0)) { + if (isAllocatable(leadingSize)) { + /* Reuse the existing node for the leading memory. */ + createNodeForTrailingMemory(fit, mapEnd, trailingSize); + trimBounds(fit, fit.getStart(), leadingSize); + } else { + /* Reuse the existing node for the trailing memory. */ + createNodeForLeadingMemory(fit, leadingSize); + trimBounds(fit, mapEnd, trailingSize); + } + } else if (trailingSize.aboveThan(0)) { + /* Reuse the existing node for the trailing memory. */ + trimBounds(fit, mapEnd, trailingSize); + } else if (leadingSize.aboveThan(0)) { + /* Reuse the existing node for the leading memory. */ + trimBounds(fit, fit.getStart(), leadingSize); + } else { + perfectFit = true; + } + + if (perfectFit || !isAllocatable(fit.getSize())) { + removeFromAllocList(fit, fitAllocPrevious); + } + + if (perfectFit) { + /* Perfect fit, remove node entirely. */ + removeFromUnusedList(fit); + freeNode(fit); + fit = nullPointer(); + } + + allocOut.write(allocated); + return NO_ERROR; + } finally { + lock.unlockNoTransitionUnspecifiedOwner(); + } + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void createNodeForLeadingMemory(FreeListNode fit, UnsignedWord leadingSize) { + FreeListNode leadingNode = createNodeWhenSplitting(fit, fit.getStart(), leadingSize); + addToUnusedList(leadingNode, fit.getUnusedPrevious()); + + /* No need to add the new node to allocList if leadingSize is too small. */ + assert !isAllocatable(leadingSize); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void createNodeForTrailingMemory(FreeListNode fit, Pointer trailingStart, UnsignedWord trailingSize) { + FreeListNode trailingNode = createNodeWhenSplitting(fit, trailingStart, trailingSize); + addToUnusedList(trailingNode, fit); + + if (isAllocatable(trailingSize)) { + addToAllocList(trailingNode, fit); + } + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected FreeListNode createNodeWhenSplitting(@SuppressWarnings("unused") FreeListNode fit, Pointer start, UnsignedWord size) { + return allocNode(start, size); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void addToUnusedList(FreeListNode newNode, FreeListNode prev) { + assert newNode.isNonNull(); + assert newNode.getUnusedNext().isNull(); + assert newNode.getUnusedPrevious().isNull(); + + FreeListNode next; + if (prev.isNull()) { + next = unusedListHead; + unusedListHead = newNode; + } else { + next = prev.getUnusedNext(); + prev.setUnusedNext(newNode); + newNode.setUnusedPrevious(prev); + } + + if (next.isNonNull()) { + next.setUnusedPrevious(newNode); + newNode.setUnusedNext(next); + } + unusedListCount++; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void removeFromUnusedList(FreeListNode node) { + assert node.isNonNull(); + + FreeListNode next = node.getUnusedNext(); + FreeListNode prev = node.getUnusedPrevious(); + if (next.isNonNull()) { + next.setUnusedPrevious(prev); + } + if (node == unusedListHead) { + unusedListHead = next; + } else { + prev.setUnusedNext(next); + } + unusedListCount--; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void addToAllocList(FreeListNode newNode, FreeListNode prev) { + assert newNode.isNonNull(); + assert newNode.getAllocNext().isNull(); + assert isAllocatable(newNode.getSize()); + + if (prev.isNull()) { + newNode.setAllocNext(allocListHead); + allocListHead = newNode; + } else { + newNode.setAllocNext(prev.getAllocNext()); + prev.setAllocNext(newNode); + } + allocListCount++; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void removeFromAllocList(FreeListNode node, FreeListNode prevNode) { + assert node.isNonNull(); + + if (node == allocListHead) { + assert prevNode.isNull(); + allocListHead = node.getAllocNext(); + } else { + prevNode.setAllocNext(node.getAllocNext()); + } + node.setAllocNext(nullPointer()); + allocListCount--; + } + + @Override + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void freeAlignedChunk(PointerBase start, UnsignedWord nbytes, UnsignedWord alignment) { + if (VMInspectionOptions.hasNativeMemoryTrackingSupport()) { + NativeMemoryTracking.singleton().trackUncommit(nbytes, NmtCategory.JavaHeap); + } + freeInHeapAddressSpace((Pointer) start, nbytes); + } + + @Override + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void freeUnalignedChunk(PointerBase start, UnsignedWord nbytes) { + if (VMInspectionOptions.hasNativeMemoryTrackingSupport()) { + NativeMemoryTracking.singleton().trackUncommit(nbytes, NmtCategory.JavaHeap); + } + freeInHeapAddressSpace((Pointer) start, nbytes); + } + + @Uninterruptible(reason = "Entering a safepoint in this code can deadlock garbage collection.") + protected void freeInHeapAddressSpace(Pointer start, UnsignedWord nbytes) { + assert start.isNonNull(); + assert nbytes.aboveThan(0); + + lock.lockNoTransition(); + try { + UnsignedWord pageSize = getGranularity(); + Pointer mapBegin = roundDown(start, pageSize); + + /* Find adjacent allocatable free blocks. */ + FreeListNode allocPrevious = nullPointer(); + FreeListNode allocNext = allocListHead; + while (allocNext.isNonNull() && allocNext.getStart().belowThan(mapBegin)) { + allocPrevious = allocNext; + allocNext = allocNext.getAllocNext(); + } + + /* Find adjacent unused blocks. */ + FreeListNode unusedPrevious = allocPrevious; + FreeListNode unusedNext = unusedPrevious.isNull() ? unusedListHead : unusedPrevious.getUnusedNext(); + while (unusedNext.isNonNull() && unusedNext.getStart().belowThan(mapBegin)) { + unusedPrevious = unusedNext; + unusedNext = unusedNext.getUnusedNext(); + } + + Pointer mapEnd = roundUp(start.add(nbytes), pageSize); + UnsignedWord alignedSize = mapEnd.subtract(mapBegin); + assert alignedSize.aboveOrEqual(nbytes); + assert unusedPrevious.isNull() || mapBegin.aboveOrEqual(getNodeEnd(unusedPrevious)); + assert unusedNext.isNull() || mapEnd.belowOrEqual(unusedNext.getStart()); + + /* + * Always try to add the freed memory to adjacent unused memory (i.e., by increasing the + * bounds of an existing node). If there is no adjacent unused memory, we need to create + * a new node. + */ + FreeListNode container; + boolean adjacentPredecessor = unusedPrevious.isNonNull() && getNodeEnd(unusedPrevious).equal(mapBegin); + boolean adjacentSuccessor = unusedNext.isNonNull() && mapEnd.equal(unusedNext.getStart()); + + if (adjacentPredecessor) { + /* Add the freed memory to the predecessor. */ + increaseBounds(unusedPrevious, mapBegin, alignedSize); + container = unusedPrevious; + + if (adjacentSuccessor) { + /* Merge the successor with the predecessor and remove the successor. */ + mergeNodes(container, unusedNext); + + if (allocNext == unusedNext) { + allocNext = allocNext.getAllocNext(); + removeFromAllocList(unusedNext, allocPrevious); + } + + removeFromUnusedList(unusedNext); + freeNode(unusedNext); + unusedNext = Word.nullPointer(); + } + } else if (adjacentSuccessor) { + /* Add the freed memory to the successor node. */ + increaseBounds(unusedNext, mapBegin, alignedSize); + container = unusedNext; + } else { + /* Create a new node for the freed memory because the adjacent memory is used. */ + FreeListNode node = allocNode(mapBegin, alignedSize); + addToUnusedList(node, unusedPrevious); + container = node; + } + + uncommit(container, mapBegin, alignedSize); + + /* Insert merged or created node into allocatables list if necessary. */ + if (isAllocatable(container.getSize()) && container != allocPrevious && container != allocNext) { + addToAllocList(container, allocPrevious); + } + } finally { + lock.unlock(); + } + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected void uncommit(@SuppressWarnings("unused") FreeListNode node, Pointer mapBegin, UnsignedWord mappingSize) { + if (VirtualMemoryProvider.get().uncommit(mapBegin, mappingSize) != 0) { + throw reportUncommitFailed(mapBegin, mappingSize); + } + } + + @Uninterruptible(reason = "Switch to interruptible code for error reporting.", calleeMustBe = false) + private static RuntimeException reportUncommitFailed(Pointer mapBegin, UnsignedWord mappingSize) { + throw reportUncommitFailedInterruptibly(mapBegin, mappingSize); + } + + private static RuntimeException reportUncommitFailedInterruptibly(Pointer mapBegin, UnsignedWord mappingSize) { + Log.log().string("Uncommitting ").unsigned(mappingSize).string(" bytes of unused memory at ").hex(mapBegin).string(" failed.").newline(); + throw VMError.shouldNotReachHere("Uncommitting memory failed."); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected void mergeNodes(FreeListNode target, FreeListNode obsolete) { + increaseBounds(target, obsolete.getStart(), obsolete.getSize()); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void increaseBounds(FreeListNode node, Pointer otherStart, UnsignedWord otherSize) { + assert getNodeEnd(node).equal(otherStart) || otherStart.add(otherSize).equal(node.getStart()) : "must be adjacent"; + assert UnsignedUtils.isAMultiple(otherSize, getGranularity()); + assert otherSize.belowOrEqual(reservedSpaceSize); + + Pointer newStart = PointerUtils.min(node.getStart(), otherStart); + UnsignedWord newSize = node.getSize().add(otherSize); + setBounds(node, newStart, newSize); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected void trimBounds(FreeListNode fit, Pointer newStart, UnsignedWord newSize) { + assert newSize.belowOrEqual(reservedSpaceSize); + assert fit.getStart().equal(newStart) && newSize.belowThan(fit.getSize()) || + fit.getStart().belowThan(newStart) && getNodeEnd(fit).equal(newStart.add(newSize)); + + setBounds(fit, newStart, newSize); + } + + @Override + @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called by the GC.") + public void beforeGarbageCollection() { + assert VMOperation.isGCInProgress() : "may only be called by the GC"; + assert !lock.hasOwner() : "Must not be locked -- is mutator code holding the lock?"; + if (SubstrateGCOptions.VerifyHeap.getValue()) { + verifyFreeList(); + } + } + + @Override + @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called by the GC.") + public void uncommitUnusedMemory() { + assert VMOperation.isGCInProgress() : "may only be called by the GC"; + assert !lock.hasOwner() : "Must not be locked"; + uncommitUnusedMemory0(); + } + + protected void uncommitUnusedMemory0() { + if (SubstrateGCOptions.VerifyHeap.getValue()) { + verifyFreeList(); + } + } + + protected void verifyFreeList() { + int unusedCount = 0; + FreeListNode unusedPrevious = nullPointer(); + for (FreeListNode unused = unusedListHead; unused.isNonNull(); unused = unused.getUnusedNext()) { + guarantee(unused.getUnusedPrevious().equal(unusedPrevious), "Unused list previous-next node linkage must be consistent"); + guarantee(unusedPrevious.isNull() || unused.getStart().aboveThan(getNodeEnd(unusedPrevious)), "Unused blocks must not be adjacent or overlapping"); + guarantee(isInAllocList(unused) == isAllocatable(unused.getSize()), "Allocatable blocks must be in the alloc list"); + unusedCount++; + unusedPrevious = unused; + } + guarantee(unusedCount == unusedListCount, "Number of unused list nodes must match recorded count"); + + int unusedReverseCounted = 0; + if (unusedPrevious.isNonNull()) { // iterate in reverse + FreeListNode unusedNext = unusedPrevious; + unusedReverseCounted++; + for (FreeListNode unused = unusedNext.getUnusedPrevious(); unused.isNonNull(); unused = unused.getUnusedPrevious()) { + guarantee(unused.getUnusedNext().equal(unusedNext), "Unused list previous-next node linkage must be consistent"); + guarantee(unusedNext.getStart().aboveThan(getNodeEnd(unused)), "Unused blocks must not be adjacent or overlapping"); + unusedReverseCounted++; + unusedNext = unused; + } + guarantee(unusedNext == unusedListHead, "Unused list reverse iteration must terminate at list head"); + } + guarantee(unusedCount == unusedReverseCounted, "Number of unused list nodes must be the same for forward and reverse iteration"); + + int allocCount = 0; + for (FreeListNode previous = nullPointer(), alloc = allocListHead; alloc.isNonNull(); previous = alloc, alloc = alloc.getAllocNext()) { + guarantee(previous.isNull() || alloc.getStart().aboveThan(getNodeEnd(previous)), "Allocatable blocks must not be adjacent or overlapping"); + guarantee(isAllocatable(alloc.getSize()), "Allocatable blocks must satisfy minimum allocatable size"); + allocCount++; + } + + guarantee(allocListHead.isNull() || unusedListHead.equal(allocListHead) || getNodeEnd(unusedListHead).belowOrEqual(allocListHead.getStart()), + "First unused block must start before first allocatable block, or be allocatable itself"); + guarantee(allocCount <= unusedCount, "Allocation list must not be longer than unused list"); + guarantee(allocCount == allocListCount, "Number of allocation list nodes must match recorded count"); + } + + private boolean isInAllocList(FreeListNode node) { + FreeListNode alloc = allocListHead; + while (alloc.isNonNull() && alloc.getStart().belowOrEqual(node.getStart())) { + if (alloc.equal(node)) { + return true; + } + alloc = alloc.getAllocNext(); + } + return false; + } + + /** Keeps track of unused memory. */ + @RawStructure + protected interface FreeListNode extends PointerBase { + @RawField + Pointer getStart(); + + @RawField + void setStart(Pointer start); + + @RawField + UnsignedWord getSize(); + + @RawField + void setSize(UnsignedWord size); + + @RawField + FreeListNode getAllocNext(); + + @RawField + void setAllocNext(FreeListNode next); + + @RawField + FreeListNode getUnusedPrevious(); + + @RawField + void setUnusedPrevious(FreeListNode unusedPrevious); + + @RawField + FreeListNode getUnusedNext(); + + @RawField + void setUnusedNext(FreeListNode unusedNext); + } +} diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AlignedHeapChunk.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AlignedHeapChunk.java index 70ae42d528a0..8b0da4008afd 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AlignedHeapChunk.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AlignedHeapChunk.java @@ -28,7 +28,6 @@ import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -94,7 +93,7 @@ public static void initialize(AlignedHeader chunk, UnsignedWord chunkSize) { public static void reset(AlignedHeader chunk) { long alignedChunkSize = SerialAndEpsilonGCOptions.AlignedHeapChunkSize.getValue(); assert HeapChunk.getEndOffset(chunk).rawValue() == alignedChunkSize; - initialize(chunk, WordFactory.unsigned(alignedChunkSize)); + initialize(chunk, Word.unsigned(alignedChunkSize)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -113,7 +112,7 @@ public static boolean isEmpty(AlignedHeader that) { /** Allocate uninitialized memory within this AlignedHeapChunk. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) static Pointer allocateMemory(AlignedHeader that, UnsignedWord size) { - Pointer result = WordFactory.nullPointer(); + Pointer result = Word.nullPointer(); UnsignedWord available = HeapChunk.availableObjectMemory(that); if (size.belowOrEqual(available)) { result = HeapChunk.getTopPointer(that); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/BasicCollectionPolicies.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/BasicCollectionPolicies.java index 3e2bfb98b4f3..ea442a7a91e9 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/BasicCollectionPolicies.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/BasicCollectionPolicies.java @@ -26,10 +26,10 @@ import static com.oracle.svm.core.genscavenge.CollectionPolicy.shouldCollectYoungGenSeparately; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateGCOptions; import com.oracle.svm.core.Uninterruptible; @@ -54,7 +54,7 @@ private BasicCollectionPolicies() { public abstract static class BasicPolicy implements CollectionPolicy { protected static UnsignedWord m(long bytes) { assert 0 <= bytes; - return WordFactory.unsigned(bytes).multiply(1024).multiply(1024); + return Word.unsigned(bytes).multiply(1024).multiply(1024); } @Override @@ -98,7 +98,7 @@ public UnsignedWord getMaximumEdenSize() { public final UnsignedWord getMaximumHeapSize() { long runtimeValue = SubstrateGCOptions.MaxHeapSize.getValue(); if (runtimeValue != 0L) { - return WordFactory.unsigned(runtimeValue); + return Word.unsigned(runtimeValue); } /* @@ -117,7 +117,7 @@ public final UnsignedWord getMaximumHeapSize() { public final UnsignedWord getMaximumYoungGenerationSize() { long runtimeValue = SubstrateGCOptions.MaxNewSize.getValue(); if (runtimeValue != 0L) { - return WordFactory.unsigned(runtimeValue); + return Word.unsigned(runtimeValue); } /* If no value is set, use a fraction of the maximum heap size. */ @@ -135,7 +135,7 @@ public final UnsignedWord getMinimumHeapSize() { long runtimeValue = SubstrateGCOptions.MinHeapSize.getValue(); if (runtimeValue != 0L) { /* If `-Xms` has been parsed from the command line, use that value. */ - return WordFactory.unsigned(runtimeValue); + return Word.unsigned(runtimeValue); } /* A default value chosen to delay the first full collection. */ @@ -155,13 +155,13 @@ public UnsignedWord getInitialSurvivorSize() { @Override public UnsignedWord getMaximumSurvivorSize() { - return WordFactory.zero(); + return Word.zero(); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public UnsignedWord getSurvivorSpacesCapacity() { - return WordFactory.zero(); + return Word.zero(); } @Override @@ -179,7 +179,7 @@ public UnsignedWord getOldGenerationCapacity() { UnsignedWord heapCapacity = getCurrentHeapCapacity(); UnsignedWord youngCapacity = getYoungGenerationCapacity(); if (youngCapacity.aboveThan(heapCapacity)) { - return WordFactory.zero(); // should never happen unless options change in between + return Word.zero(); // should never happen unless options change in between } return heapCapacity.subtract(youngCapacity); } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapAllocator.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapAllocator.java index d5f2502686ef..67a7313b3050 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapAllocator.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapAllocator.java @@ -28,8 +28,8 @@ import java.util.List; import jdk.graal.compiler.core.common.NumUtil; +import jdk.graal.compiler.word.Word; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.image.ImageHeap; @@ -195,7 +195,7 @@ public void alignBetweenChunks(int multiple) { public long allocateUnalignedChunkForObject(ImageHeapObject obj, boolean writable) { assert currentAlignedChunk == null; - UnsignedWord objSize = WordFactory.unsigned(obj.getSize()); + UnsignedWord objSize = Word.unsigned(obj.getSize()); long chunkSize = UnalignedHeapChunk.getChunkSizeForObject(objSize).rawValue(); long chunkBegin = allocateRaw(chunkSize); unalignedChunks.add(new UnalignedChunk(chunkBegin, chunkSize, writable)); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunksAccounting.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunksAccounting.java index 18f6f399f54f..8bb78a79e413 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunksAccounting.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunksAccounting.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core.genscavenge; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; @@ -57,7 +57,7 @@ final class ChunksAccounting { public void reset() { alignedCount = 0L; unalignedCount = 0L; - unalignedChunkBytes = WordFactory.zero(); + unalignedChunkBytes = Word.zero(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -71,7 +71,7 @@ public long getAlignedChunkCount() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public UnsignedWord getAlignedChunkBytes() { - return WordFactory.unsigned(alignedCount).multiply(HeapParameters.getAlignedHeapChunkSize()); + return Word.unsigned(alignedCount).multiply(HeapParameters.getAlignedHeapChunkSize()); } public long getUnalignedChunkCount() { diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java index 3a0e9b413401..6db2f15e3ea1 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core.genscavenge; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; @@ -38,7 +38,7 @@ /** The interface for a garbage collection policy. All sizes are in bytes. */ public interface CollectionPolicy { - UnsignedWord UNDEFINED = WordFactory.unsigned(-1); + UnsignedWord UNDEFINED = Word.unsigned(-1); @Platforms(Platform.HOSTED_ONLY.class) static String getInitialPolicyName() { diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CompactingOldGeneration.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CompactingOldGeneration.java index d219f81d010c..1b12444bdb88 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CompactingOldGeneration.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CompactingOldGeneration.java @@ -139,6 +139,11 @@ void absorb(YoungGeneration youngGen) { } } + @Override + void appendChunk(AlignedHeapChunk.AlignedHeader hdr) { + space.appendAlignedHeapChunk(hdr); + } + @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) void blackenDirtyCardRoots(GreyToBlackObjectVisitor visitor) { diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CopyingOldGeneration.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CopyingOldGeneration.java index 9962dff2e554..47e1cfe80779 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CopyingOldGeneration.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CopyingOldGeneration.java @@ -143,6 +143,11 @@ Space getToSpace() { return toSpace; } + @Override + void appendChunk(AlignedHeapChunk.AlignedHeader hdr) { + getToSpace().appendAlignedHeapChunk(hdr); + } + @Override void swapSpaces() { assert getFromSpace().isEmpty() : "fromSpace should be empty."; diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCAccounting.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCAccounting.java index ee06b99f1910..dd4df8380d3d 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCAccounting.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCAccounting.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core.genscavenge; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -47,24 +47,24 @@ public final class GCAccounting { private long incrementalCollectionTotalNanos = 0; private long completeCollectionCount = 0; private long completeCollectionTotalNanos = 0; - private UnsignedWord totalCollectedChunkBytes = WordFactory.zero(); - private UnsignedWord totalAllocatedChunkBytes = WordFactory.zero(); - private UnsignedWord lastIncrementalCollectionPromotedChunkBytes = WordFactory.zero(); + private UnsignedWord totalCollectedChunkBytes = Word.zero(); + private UnsignedWord totalAllocatedChunkBytes = Word.zero(); + private UnsignedWord lastIncrementalCollectionPromotedChunkBytes = Word.zero(); private boolean lastIncrementalCollectionOverflowedSurvivors = false; /* Before and after measures. */ - private UnsignedWord youngChunkBytesBefore = WordFactory.zero(); - private UnsignedWord oldChunkBytesBefore = WordFactory.zero(); - private UnsignedWord oldChunkBytesAfter = WordFactory.zero(); + private UnsignedWord youngChunkBytesBefore = Word.zero(); + private UnsignedWord oldChunkBytesBefore = Word.zero(); + private UnsignedWord oldChunkBytesAfter = Word.zero(); /* * Bytes allocated in Objects, as opposed to bytes of chunks. These are only maintained if * -R:+PrintGCSummary because they are expensive. */ - private UnsignedWord totalCollectedObjectBytes = WordFactory.zero(); - private UnsignedWord youngObjectBytesBefore = WordFactory.zero(); - private UnsignedWord oldObjectBytesBefore = WordFactory.zero(); - private UnsignedWord allocatedObjectBytes = WordFactory.zero(); + private UnsignedWord totalCollectedObjectBytes = Word.zero(); + private UnsignedWord youngObjectBytesBefore = Word.zero(); + private UnsignedWord oldObjectBytesBefore = Word.zero(); + private UnsignedWord allocatedObjectBytes = Word.zero(); @Platforms(Platform.HOSTED_ONLY.class) GCAccounting() { diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java index 359a5f4db10b..a15dddabfee6 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java @@ -31,6 +31,7 @@ import java.lang.ref.Reference; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; @@ -41,7 +42,6 @@ import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Isolates; @@ -126,7 +126,7 @@ public final class GCImpl implements GC { private final CollectionPolicy policy; private boolean completeCollection = false; - private UnsignedWord collectionEpoch = WordFactory.zero(); + private UnsignedWord collectionEpoch = Word.zero(); private long lastWholeHeapExaminedNanos = -1; @Platforms(Platform.HOSTED_ONLY.class) @@ -191,7 +191,7 @@ boolean collectWithoutAllocating(GCCause cause, boolean forceFullGC) { int size = SizeOf.get(CollectionVMOperationData.class); CollectionVMOperationData data = StackValue.get(size); - UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); + UnmanagedMemoryUtil.fill((Pointer) data, Word.unsigned(size), (byte) 0); data.setCauseId(cause.getId()); data.setRequestingEpoch(getCollectionEpoch()); data.setCompleteCollectionCount(GCImpl.getAccounting().getCompleteCollectionCount()); @@ -858,7 +858,7 @@ private static void walkStack(IsolateThread thread, JavaStackWalk walk, ObjectRe * passing (re-use of deopt slot). */ long varStackSize = DeoptimizationSlotPacking.decodeVariableFrameSizeFromDeoptSlot(sp.readLong(0)); - Pointer actualSP = sp.add(WordFactory.unsigned(varStackSize)); + Pointer actualSP = sp.add(Word.unsigned(varStackSize)); InterpreterSupport.walkInterpreterLeaveStubFrame(visitor, actualSP, sp); } else { @@ -1310,11 +1310,11 @@ public void add(UnalignedHeader chunks) { void release(boolean keepAllAlignedChunks) { if (firstAligned.isNonNull()) { HeapImpl.getChunkProvider().consumeAlignedChunks(firstAligned, keepAllAlignedChunks); - firstAligned = WordFactory.nullPointer(); + firstAligned = Word.nullPointer(); } if (firstUnaligned.isNonNull()) { HeapChunkProvider.consumeUnalignedChunks(firstUnaligned); - firstUnaligned = WordFactory.nullPointer(); + firstUnaligned = Word.nullPointer(); } } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GenScavengeMemoryPoolMXBeans.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GenScavengeMemoryPoolMXBeans.java index 093e7e9e3796..de5ed9b23d26 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GenScavengeMemoryPoolMXBeans.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GenScavengeMemoryPoolMXBeans.java @@ -27,11 +27,11 @@ import java.lang.management.MemoryUsage; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.util.VMError; @@ -122,7 +122,7 @@ public MemoryUsage getPeakUsage() { @Override public MemoryUsage getCollectionUsage() { - return memoryUsage(WordFactory.zero()); + return memoryUsage(Word.zero()); } private static UnsignedWord getCurrentUsage() { @@ -241,7 +241,7 @@ public MemoryUsage getPeakUsage() { @Override public MemoryUsage getCollectionUsage() { - return memoryUsage(WordFactory.zero()); + return memoryUsage(Word.zero()); } } } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GreyObjectsWalker.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GreyObjectsWalker.java index c0d70a40f52b..760c0093fec3 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GreyObjectsWalker.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GreyObjectsWalker.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core.genscavenge; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.NeverInline; @@ -62,7 +62,7 @@ void setScanStart(Space s) { space = s; AlignedHeapChunk.AlignedHeader aChunk = s.getLastAlignedHeapChunk(); alignedHeapChunk = aChunk; - alignedTop = (aChunk.isNonNull() ? HeapChunk.getTopPointer(aChunk) : WordFactory.nullPointer()); + alignedTop = (aChunk.isNonNull() ? HeapChunk.getTopPointer(aChunk) : Word.nullPointer()); unalignedHeapChunk = s.getLastUnalignedHeapChunk(); } @@ -91,7 +91,7 @@ private void walkAlignedGreyObjects() { if (alignedHeapChunk.isNull() && alignedTop.isNull()) { /* If the snapshot is empty, then I have to walk from the beginning of the Space. */ aChunk = space.getFirstAlignedHeapChunk(); - aStart = (aChunk.isNonNull() ? AlignedHeapChunk.getObjectsStart(aChunk) : WordFactory.nullPointer()); + aStart = (aChunk.isNonNull() ? AlignedHeapChunk.getObjectsStart(aChunk) : Word.nullPointer()); } else { /* Otherwise walk Objects that arrived after the snapshot. */ aChunk = alignedHeapChunk; @@ -107,7 +107,7 @@ private void walkAlignedGreyObjects() { throw VMError.shouldNotReachHereAtRuntime(); } aChunk = HeapChunk.getNext(aChunk); - aStart = (aChunk.isNonNull() ? AlignedHeapChunk.getObjectsStart(aChunk) : WordFactory.nullPointer()); + aStart = (aChunk.isNonNull() ? AlignedHeapChunk.getObjectsStart(aChunk) : Word.nullPointer()); } while (aChunk.isNonNull()); /* Move the scan point. */ diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunk.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunk.java index 50a42e9a5f9c..ab6b889ef5b6 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunk.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunk.java @@ -35,7 +35,6 @@ import org.graalvm.word.PointerBase; import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.NeverInline; @@ -174,8 +173,8 @@ public static void initialize(Header chunk, Pointer objectsStart, UnsignedWor HeapChunk.setEndOffset(chunk, endOffset); HeapChunk.setTopPointer(chunk, objectsStart); HeapChunk.setSpace(chunk, null); - HeapChunk.setNext(chunk, WordFactory.nullPointer()); - HeapChunk.setPrevious(chunk, WordFactory.nullPointer()); + HeapChunk.setNext(chunk, Word.nullPointer()); + HeapChunk.setPrevious(chunk, Word.nullPointer()); /* * The epoch is obviously not random, but cheap to use, and we cannot use a random number @@ -272,8 +271,8 @@ public static void setIdentityHashSalt(Header that, UnsignedWord value) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @SuppressWarnings("unchecked") private static T pointerFromOffset(Header that, ComparableWord offset) { - T pointer = WordFactory.nullPointer(); - if (offset.notEqual(WordFactory.zero())) { + T pointer = Word.nullPointer(); + if (offset.notEqual(Word.zero())) { pointer = (T) ((SignedWord) that).add((SignedWord) offset); } return pointer; @@ -286,7 +285,7 @@ private static T pointerFromOffset(Header that, Compa */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static SignedWord offsetFromPointer(Header that, PointerBase pointer) { - SignedWord offset = WordFactory.zero(); + SignedWord offset = Word.zero(); if (pointer.isNonNull()) { offset = ((SignedWord) pointer).subtract((SignedWord) that); } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkProvider.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkProvider.java index 3e6f97ff7bfa..c242551a780e 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkProvider.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkProvider.java @@ -24,12 +24,12 @@ */ package com.oracle.svm.core.genscavenge; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.FrameAccess; import com.oracle.svm.core.Uninterruptible; @@ -107,7 +107,7 @@ AlignedHeader produceAlignedChunk() { void freeExcessAlignedChunks() { assert VMOperation.isGCInProgress(); - consumeAlignedChunks(WordFactory.nullPointer(), false); + consumeAlignedChunks(Word.nullPointer(), false); } /** @@ -118,14 +118,14 @@ void consumeAlignedChunks(AlignedHeader firstChunk, boolean keepAll) { assert VMOperation.isGCInProgress(); assert firstChunk.isNull() || HeapChunk.getPrevious(firstChunk).isNull() : "prev must be null"; - UnsignedWord maxChunksToKeep = WordFactory.zero(); - UnsignedWord unusedChunksToFree = WordFactory.zero(); + UnsignedWord maxChunksToKeep = Word.zero(); + UnsignedWord unusedChunksToFree = Word.zero(); if (keepAll) { maxChunksToKeep = UnsignedUtils.MAX_VALUE; } else { UnsignedWord freeListBytes = getBytesInUnusedChunks(); UnsignedWord reserveBytes = GCImpl.getPolicy().getMaximumFreeAlignedChunksSize(); - UnsignedWord maxHeapFree = WordFactory.unsigned(SerialGCOptions.MaxHeapFree.getValue()); + UnsignedWord maxHeapFree = Word.unsigned(SerialGCOptions.MaxHeapFree.getValue()); if (maxHeapFree.aboveThan(0)) { reserveBytes = UnsignedUtils.min(reserveBytes, maxHeapFree); } @@ -178,7 +178,7 @@ private void pushUnusedAlignedChunk(AlignedHeader chunk) { HeapChunk.setNext(chunk, unusedAlignedChunks.get()); unusedAlignedChunks.set(chunk); - numUnusedAlignedChunks.addAndGet(WordFactory.unsigned(1)); + numUnusedAlignedChunks.addAndGet(Word.unsigned(1)); } /** @@ -194,9 +194,9 @@ private void pushUnusedAlignedChunk(AlignedHeader chunk) { private AlignedHeader popUnusedAlignedChunk() { AlignedHeader result = popUnusedAlignedChunkUninterruptibly(); if (result.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } else { - numUnusedAlignedChunks.subtractAndGet(WordFactory.unsigned(1)); + numUnusedAlignedChunks.subtractAndGet(Word.unsigned(1)); return result; } } @@ -206,11 +206,11 @@ private AlignedHeader popUnusedAlignedChunkUninterruptibly() { while (true) { AlignedHeader result = unusedAlignedChunks.get(); if (result.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } else { AlignedHeader next = HeapChunk.getNext(result); if (unusedAlignedChunks.compareAndSet(result, next)) { - HeapChunk.setNext(result, WordFactory.nullPointer()); + HeapChunk.setNext(result, Word.nullPointer()); return result; } } @@ -224,7 +224,7 @@ private void freeUnusedAlignedChunksAtSafepoint(UnsignedWord count) { } AlignedHeader chunk = unusedAlignedChunks.get(); - UnsignedWord released = WordFactory.zero(); + UnsignedWord released = Word.zero(); while (chunk.isNonNull() && released.belowThan(count)) { AlignedHeader next = HeapChunk.getNext(chunk); freeAlignedChunk(chunk); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java index c72ca246ec6e..eb38e7dc100d 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java @@ -28,13 +28,13 @@ import java.util.ArrayList; import java.util.List; +import com.oracle.svm.core.hub.LayoutEncoding; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.MemoryWalker; @@ -54,6 +54,7 @@ import com.oracle.svm.core.genscavenge.ThreadLocalAllocation.Descriptor; import com.oracle.svm.core.genscavenge.UnalignedHeapChunk.UnalignedHeader; import com.oracle.svm.core.genscavenge.graal.ForcedSerialPostWriteBarrier; +import com.oracle.svm.core.genscavenge.graal.nodes.FormatArrayNode; import com.oracle.svm.core.graal.snippets.SubstrateAllocationSnippets; import com.oracle.svm.core.heap.GC; import com.oracle.svm.core.heap.GCCause; @@ -66,6 +67,7 @@ import com.oracle.svm.core.heap.ReferenceInternals; import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.heap.RuntimeCodeInfoGCSupport; +import com.oracle.svm.core.heap.VMOperationInfos; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicReference; @@ -80,6 +82,7 @@ import com.oracle.svm.core.option.RuntimeOptionKey; import com.oracle.svm.core.os.ImageHeapProvider; import com.oracle.svm.core.snippets.KnownIntrinsics; +import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.PlatformThreads; import com.oracle.svm.core.thread.ThreadStatus; import com.oracle.svm.core.thread.VMOperation; @@ -95,6 +98,7 @@ import jdk.graal.compiler.core.common.SuppressFBWarnings; import jdk.graal.compiler.nodes.extended.MembarNode; import jdk.graal.compiler.nodes.memory.address.OffsetAddressNode; +import jdk.graal.compiler.replacements.AllocationSnippets; import jdk.graal.compiler.word.Word; public final class HeapImpl extends Heap { @@ -111,6 +115,8 @@ public final class HeapImpl extends Heap { private final RuntimeCodeInfoGCSupportImpl runtimeCodeInfoGcSupport; private final HeapAccounting accounting = new HeapAccounting(); + private AlignedHeader lastDynamicHubChunk; + /** Head of the linked list of currently pending (ready to be enqueued) {@link Reference}s. */ private Reference refPendingList; /** Total number of times when a new pending reference list became available. */ @@ -847,7 +853,7 @@ private static Descriptor getTlabUnsafe(IsolateThread thread) { public boolean verifyImageHeapMapping() { for (ImageHeapInfo info : HeapImpl.getImageHeapInfos()) { /* Read & write some data at the beginning and end of each writable chunk. */ - writeToEachChunk(info.getFirstWritableAlignedChunk(), WordFactory.nullPointer()); + writeToEachChunk(info.getFirstWritableAlignedChunk(), Word.nullPointer()); writeToEachChunk(info.getFirstWritableUnalignedChunk(), info.getLastWritableUnalignedChunk()); } return true; @@ -933,6 +939,83 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev log.indent(false); } } + + public static DynamicHub allocateDynamicHub(int vTableSlots) { + AllocateDynamicHubOp vmOp = new AllocateDynamicHubOp(vTableSlots); + vmOp.enqueue(); + return vmOp.result; + } + + private static class AllocateDynamicHubOp extends JavaVMOperation { + int vTableSlots; + DynamicHub result; + + AllocateDynamicHubOp(int vTableSlots) { + super(VMOperationInfos.get(AllocateDynamicHubOp.class, "Allocate DynamicHub", SystemEffect.SAFEPOINT)); + this.vTableSlots = vTableSlots; + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean isGC() { + /* needs to append chunks into oldGen */ + return true; + } + + @Override + protected void operate() { + DynamicHub hubOfDynamicHub = DynamicHub.fromClass(Class.class); + /* + * Note that layoutEncoding already encodes the size of a DynamicHub and it is aware of + * its hybrid nature, including the size required for a VTable slot. + * + * Also note that inlined fields like `closedTypeWorldTypeCheckSlots` are not relevant + * here, as they are not available in the open type world configuration. + */ + UnsignedWord size = LayoutEncoding.getArrayAllocationSize(hubOfDynamicHub.getLayoutEncoding(), vTableSlots); + + Pointer memory = Word.nullPointer(); + if (getHeapImpl().lastDynamicHubChunk.isNonNull()) { + /* + * GR-57355: move this fast-path out of vmOp. Needs some locking (it's not + * thread-local) + */ + memory = AlignedHeapChunk.allocateMemory(getHeapImpl().lastDynamicHubChunk, size); + } + + if (memory.isNull()) { + /* Either no storage for DynamicHubs yet or we are out of memory */ + allocateNewDynamicHubChunk(); + + memory = AlignedHeapChunk.allocateMemory(getHeapImpl().lastDynamicHubChunk, size); + } + + VMError.guarantee(memory.isNonNull(), "failed to allocate DynamicHub"); + + /* DynamicHubs live allocated on aligned heap chunks */ + boolean unaligned = false; + result = (DynamicHub) FormatArrayNode.formatArray(memory, DynamicHub.class, vTableSlots, true, unaligned, AllocationSnippets.FillContent.WITH_ZEROES, true); + } + + private static void allocateNewDynamicHubChunk() { + /* + * GR-60085: Should be a dedicated generation. Make sure that those chunks are close to + * the heap base. The hub is stored as offset relative to the heap base. There are 5 + * status bits in the header and in addition, compressed references use a three-bit + * shift that word-aligns objects. This results in a 35-bit address range of 32 GB, of + * which DynamicHubs must reside in the lowest 1 GB. + */ + OldGeneration oldGeneration = getHeapImpl().getOldGeneration(); + + /* + * GR-60085: DynamicHub objects must never be be moved. Pin them either by (1) pinning + * each DynamicHub, or (2) mark the whole chunk as pinned (not supported yet). + */ + getHeapImpl().lastDynamicHubChunk = oldGeneration.requestAlignedChunk(); + + oldGeneration.appendChunk(getHeapImpl().lastDynamicHubChunk); + } + } } @TargetClass(value = java.lang.Runtime.class, onlyWith = UseSerialOrEpsilonGC.class) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java index a581c9c1151a..52a9e376aa74 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java @@ -27,7 +27,6 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateGCOptions; import com.oracle.svm.core.SubstrateUtil; @@ -108,7 +107,7 @@ public static void setMaximumHeapFree(UnsignedWord bytes) { } public static UnsignedWord getMaximumHeapFree() { - return WordFactory.unsigned(SerialGCOptions.MaxHeapFree.getValue()); + return Word.unsigned(SerialGCOptions.MaxHeapFree.getValue()); } public static int getHeapChunkHeaderPadding() { @@ -129,7 +128,7 @@ static int getMaximumHeapSizePercent() { @Fold public static UnsignedWord getAlignedHeapChunkSize() { - return WordFactory.unsigned(SerialAndEpsilonGCOptions.AlignedHeapChunkSize.getValue()); + return Word.unsigned(SerialAndEpsilonGCOptions.AlignedHeapChunkSize.getValue()); } @Fold @@ -144,7 +143,7 @@ public static UnsignedWord getMinUnalignedChunkSize() { @Fold public static UnsignedWord getLargeArrayThreshold() { - return WordFactory.unsigned(SerialAndEpsilonGCOptions.LargeArrayThreshold.getValue()); + return Word.unsigned(SerialAndEpsilonGCOptions.LargeArrayThreshold.getValue()); } /* @@ -161,7 +160,7 @@ public static boolean getZapConsumedHeapChunks() { } static { - Word.ensureInitialized(); + /* Calling this method ensures that the static initializer has been executed. */ } /** Freshly committed but still uninitialized Java heap memory. */ @@ -169,10 +168,10 @@ public static boolean getZapConsumedHeapChunks() { /** Unused but still committed Java heap memory. */ private static final int UNUSED_JAVA_HEAP = 0xdeadbeef; - private static final UnsignedWord producedHeapChunkZapInt = WordFactory.unsigned(UNINITIALIZED_JAVA_HEAP); + private static final UnsignedWord producedHeapChunkZapInt = Word.unsigned(UNINITIALIZED_JAVA_HEAP); private static final UnsignedWord producedHeapChunkZapWord = producedHeapChunkZapInt.shiftLeft(32).or(producedHeapChunkZapInt); - private static final UnsignedWord consumedHeapChunkZapInt = WordFactory.unsigned(UNUSED_JAVA_HEAP); + private static final UnsignedWord consumedHeapChunkZapInt = Word.unsigned(UNUSED_JAVA_HEAP); private static final UnsignedWord consumedHeapChunkZapWord = consumedHeapChunkZapInt.shiftLeft(32).or(consumedHeapChunkZapInt); public static final class TestingBackDoor { diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapVerifier.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapVerifier.java index 91e1856014ea..0d1e7cbae8c9 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapVerifier.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapVerifier.java @@ -30,7 +30,6 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.genscavenge.AlignedHeapChunk.AlignedHeader; @@ -157,7 +156,7 @@ static boolean verifySpace(Space space) { private static boolean verifyChunkList(Space space, String kind, HeapChunk.Header firstChunk, HeapChunk.Header lastChunk) { boolean result = true; HeapChunk.Header current = firstChunk; - HeapChunk.Header previous = WordFactory.nullPointer(); + HeapChunk.Header previous = Word.nullPointer(); while (current.isNonNull()) { HeapChunk.Header previousOfCurrent = HeapChunk.getPrevious(current); if (previousOfCurrent.notEqual(previous)) { @@ -193,7 +192,7 @@ private static boolean verifyAlignedChunks(Space space, AlignedHeader firstAlign success = false; } - OBJECT_VERIFIER.initialize(aChunk, WordFactory.nullPointer()); + OBJECT_VERIFIER.initialize(aChunk, Word.nullPointer()); AlignedHeapChunk.walkObjects(aChunk, OBJECT_VERIFIER); aChunk = HeapChunk.getNext(aChunk); success &= OBJECT_VERIFIER.result; @@ -202,7 +201,7 @@ private static boolean verifyAlignedChunks(Space space, AlignedHeader firstAlign } private static boolean verifyUnalignedChunks(Space space, UnalignedHeader firstUnalignedHeapChunk) { - return verifyUnalignedChunks(space, firstUnalignedHeapChunk, WordFactory.nullPointer()); + return verifyUnalignedChunks(space, firstUnalignedHeapChunk, Word.nullPointer()); } private static boolean verifyUnalignedChunks(Space space, UnalignedHeader firstUnalignedHeapChunk, UnalignedHeader lastUnalignedHeapChunk) { @@ -216,7 +215,7 @@ private static boolean verifyUnalignedChunks(Space space, UnalignedHeader firstU success = false; } - OBJECT_VERIFIER.initialize(WordFactory.nullPointer(), uChunk); + OBJECT_VERIFIER.initialize(Word.nullPointer(), uChunk); UnalignedHeapChunk.walkObjects(uChunk, OBJECT_VERIFIER); success &= OBJECT_VERIFIER.result; diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ImageHeapInfo.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ImageHeapInfo.java index fedf181fc44f..5eab14eef0db 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ImageHeapInfo.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ImageHeapInfo.java @@ -28,7 +28,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.BuildPhaseProvider.AfterHeapLayout; import com.oracle.svm.core.Uninterruptible; @@ -210,7 +209,7 @@ public UnalignedHeader getLastWritableUnalignedChunk() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static Pointer getObjectEnd(Object obj) { if (obj == null) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } return LayoutEncoding.getImageHeapObjectEnd(obj); } @@ -219,9 +218,9 @@ private static Pointer getObjectEnd(Object obj) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static > T asImageHeapChunk(long offsetInImageHeap) { if (offsetInImageHeap < 0) { - return (T) WordFactory.nullPointer(); + return (T) Word.nullPointer(); } - UnsignedWord offset = WordFactory.unsigned(offsetInImageHeap); + UnsignedWord offset = Word.unsigned(offsetInImageHeap); return (T) KnownIntrinsics.heapBase().add(offset); } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ImageHeapWalker.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ImageHeapWalker.java index e81c61418f18..68fd58ea2638 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ImageHeapWalker.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ImageHeapWalker.java @@ -28,7 +28,6 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.MemoryWalker; @@ -153,7 +152,7 @@ abstract class MemoryWalkerAccessBase implements MemoryWalker.NativeImageHeapReg public UnsignedWord getSize(ImageHeapInfo info) { Pointer firstStart = Word.objectToUntrackedPointer(getFirstObject(info)); if (firstStart.isNull()) { // no objects - return WordFactory.zero(); + return Word.zero(); } Pointer lastEnd = LayoutEncoding.getImageHeapObjectEnd(getLastObject(info)); return lastEnd.subtract(firstStart); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/LibGraalCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/LibGraalCollectionPolicy.java index 2f6e24b44093..5eb5f30ec59e 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/LibGraalCollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/LibGraalCollectionPolicy.java @@ -25,8 +25,8 @@ package com.oracle.svm.core.genscavenge; import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.word.Word; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.heap.GCCause; import com.oracle.svm.core.option.RuntimeOptionKey; @@ -52,15 +52,15 @@ public static final class Options { public static final RuntimeOptionKey ExpectedEdenSize = new RuntimeOptionKey<>(32L * 1024L * 1024L); } - protected static final UnsignedWord INITIAL_HEAP_SIZE = WordFactory.unsigned(64L * 1024L * 1024L); - protected static final UnsignedWord FULL_GC_BONUS = WordFactory.unsigned(2L * 1024L * 1024L); + protected static final UnsignedWord INITIAL_HEAP_SIZE = Word.unsigned(64L * 1024L * 1024L); + protected static final UnsignedWord FULL_GC_BONUS = Word.unsigned(2L * 1024L * 1024L); /** * See class javadoc for rationale behind this 16G limit. */ - protected static final UnsignedWord MAXIMUM_HEAP_SIZE = WordFactory.unsigned(16L * 1024L * 1024L * 1024L); + protected static final UnsignedWord MAXIMUM_HEAP_SIZE = Word.unsigned(16L * 1024L * 1024L * 1024L); - private UnsignedWord sizeBefore = WordFactory.zero(); + private UnsignedWord sizeBefore = Word.zero(); private GCCause lastGCCause = null; /** @@ -83,7 +83,7 @@ public boolean shouldCollectOnHint(boolean fullGC) { */ edenUsedBytes = edenUsedBytes.add(FULL_GC_BONUS); } - return edenUsedBytes.aboveOrEqual(WordFactory.unsigned(Options.ExpectedEdenSize.getValue())) || + return edenUsedBytes.aboveOrEqual(Word.unsigned(Options.ExpectedEdenSize.getValue())) || (UnsignedUtils.toDouble(edenUsedBytes) / UnsignedUtils.toDouble(edenSize) >= Options.UsedEdenProportionThreshold.getValue()); } @@ -110,7 +110,7 @@ public void onCollectionBegin(boolean completeCollection, long requestingNanoTim @Override public void onCollectionEnd(boolean completeCollection, GCCause cause) { super.onCollectionEnd(completeCollection, cause); - sizeBefore = WordFactory.zero(); + sizeBefore = Word.zero(); lastGCCause = cause; } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ObjectHeaderImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ObjectHeaderImpl.java index 62cf1fbbe8c7..ceb77f8944e9 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ObjectHeaderImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ObjectHeaderImpl.java @@ -29,7 +29,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.SubstrateOptions; @@ -62,9 +61,9 @@ * {@link Heap#isInImageHeap}. */ public final class ObjectHeaderImpl extends ObjectHeader { - private static final UnsignedWord UNALIGNED_BIT = WordFactory.unsigned(0b00001); - private static final UnsignedWord REMSET_OR_MARKED1_BIT = WordFactory.unsigned(0b00010); - private static final UnsignedWord FORWARDED_OR_MARKED2_BIT = WordFactory.unsigned(0b00100); + private static final UnsignedWord UNALIGNED_BIT = Word.unsigned(0b00001); + private static final UnsignedWord REMSET_OR_MARKED1_BIT = Word.unsigned(0b00010); + private static final UnsignedWord FORWARDED_OR_MARKED2_BIT = Word.unsigned(0b00100); private static final UnsignedWord MARKED_BITS = REMSET_OR_MARKED1_BIT.or(FORWARDED_OR_MARKED2_BIT); /** @@ -72,12 +71,12 @@ public final class ObjectHeaderImpl extends ObjectHeader { * initialized to {@link #IDHASH_STATE_UNASSIGNED}. */ private static final int IDHASH_STATE_SHIFT = 3; - private static final UnsignedWord IDHASH_STATE_BITS = WordFactory.unsigned(0b11000); + private static final UnsignedWord IDHASH_STATE_BITS = Word.unsigned(0b11000); @SuppressWarnings("unused") // - private static final UnsignedWord IDHASH_STATE_UNASSIGNED = WordFactory.unsigned(0b00); - private static final UnsignedWord IDHASH_STATE_FROM_ADDRESS = WordFactory.unsigned(0b01); - private static final UnsignedWord IDHASH_STATE_IN_FIELD = WordFactory.unsigned(0b10); + private static final UnsignedWord IDHASH_STATE_UNASSIGNED = Word.unsigned(0b00); + private static final UnsignedWord IDHASH_STATE_FROM_ADDRESS = Word.unsigned(0b01); + private static final UnsignedWord IDHASH_STATE_IN_FIELD = Word.unsigned(0b10); private final int numAlignmentBits; private final int numReservedBits; @@ -141,7 +140,7 @@ protected void initializeObjectHeader(Pointer objectPointer, Word encodedHub, bo ObjectLayout ol = ConfigurationValues.getObjectLayout(); boolean isIdentityHashFieldInObjectHeader = ol.isIdentityHashFieldInObjectHeader() || ol.isIdentityHashFieldAtTypeSpecificOffset() && isArrayLike; if (getReferenceSize() == Integer.BYTES) { - dynamicAssert(encodedHub.and(WordFactory.unsigned(0xFFFFFFFF00000000L)).isNull(), "hub can only use 32 bits"); + dynamicAssert(encodedHub.and(Word.unsigned(0xFFFFFFFF00000000L)).isNull(), "hub can only use 32 bits"); if (isIdentityHashFieldInObjectHeader) { /* Use a single 64-bit write to initialize the hub and the identity hashcode. */ dynamicAssert(ol.getObjectHeaderIdentityHashOffset() == getHubOffset() + 4, "assumed layout to optimize initializing write"); @@ -279,7 +278,7 @@ public Word encodeAsObjectHeader(DynamicHub hub, boolean rememberedSet, boolean /** Clear the object header bits from a header. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) UnsignedWord clearBits(UnsignedWord header) { - UnsignedWord mask = WordFactory.unsigned(reservedBitsMask); + UnsignedWord mask = Word.unsigned(reservedBitsMask); return header.and(mask.not()); } @@ -444,7 +443,7 @@ private UnsignedWord getForwardHeader(Object copy) { if (hasShift()) { // Compression with a shift uses all bits of a reference, so store the forwarding // pointer in the location following the hub pointer. - result = compressedCopy.shiftLeft(32).or(WordFactory.unsigned(0x00000000e0e0e0e0L)); + result = compressedCopy.shiftLeft(32).or(Word.unsigned(0x00000000e0e0e0e0L)); } else { result = compressedCopy; } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/OldGeneration.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/OldGeneration.java index 22c43d93962a..f31a461a6059 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/OldGeneration.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/OldGeneration.java @@ -63,6 +63,8 @@ public abstract class OldGeneration extends Generation { abstract boolean isInSpace(Pointer ptr); + abstract void appendChunk(AlignedHeapChunk.AlignedHeader hdr); + abstract boolean verifyRememberedSets(); abstract boolean verifySpaces(); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ProportionateSpacesPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ProportionateSpacesPolicy.java index adfe3dc0dc8f..beebfea4d001 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ProportionateSpacesPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ProportionateSpacesPolicy.java @@ -26,8 +26,8 @@ import static com.oracle.svm.core.genscavenge.CollectionPolicy.shouldCollectYoungGenSeparately; +import jdk.graal.compiler.word.Word; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.heap.GCCause; @@ -107,7 +107,7 @@ private void adjustDesiredTenuringThreshold() { // DefNewGeneration::adjust_desi // AgeTable::compute_tenuring_threshold YoungGeneration youngGen = HeapImpl.getHeapImpl().getYoungGeneration(); - UnsignedWord total = WordFactory.zero(); + UnsignedWord total = Word.zero(); int i; for (i = 0; i < HeapParameters.getMaxSurvivorSpaces(); i++) { Space space = youngGen.getSurvivorFromSpaceAt(0); @@ -146,7 +146,7 @@ private void computeNewOldGenSize(boolean resizeOnlyForPromotions) { // TenuredG } UnsignedWord maxShrinkBytes = oldSize.subtract(minimumDesiredCapacity); - UnsignedWord shrinkBytes = WordFactory.zero(); + UnsignedWord shrinkBytes = Word.zero(); if (MAX_HEAP_FREE_RATIO < 100) { double maximumFreePercentage = MAX_HEAP_FREE_RATIO / 100.0; double minimumUsedPercentage = 1 - maximumFreePercentage; @@ -198,7 +198,7 @@ private void computeNewYoungGenSize() { // DefNewGeneration::compute_new_size // DefNewGeneration::compute_space_boundaries, DefNewGeneration::compute_survivor_size survivorSize = minSpaceSize(alignDown(desiredNewSize.unsignedDivide(SURVIVOR_RATIO))); - UnsignedWord desiredEdenSize = WordFactory.zero(); + UnsignedWord desiredEdenSize = Word.zero(); if (desiredNewSize.aboveThan(survivorSize.multiply(2))) { desiredEdenSize = desiredNewSize.subtract(survivorSize.multiply(2)); } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ReferenceObjectProcessing.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ReferenceObjectProcessing.java index 477fb6aadb4c..bb103917e1e8 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ReferenceObjectProcessing.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ReferenceObjectProcessing.java @@ -31,9 +31,9 @@ import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; +import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -131,7 +131,7 @@ private static void discover(Object obj, ObjectReferenceVisitor refVisitor) { if (timestamp == 0) { // created or last accessed before the clock was initialized timestamp = initialSoftRefClock; } - UnsignedWord elapsed = WordFactory.unsigned(clock - timestamp); + UnsignedWord elapsed = Word.unsigned(clock - timestamp); if (elapsed.belowThan(maxSoftRefAccessIntervalMs)) { // Important: we need to pass the reference object as holder so that the remembered // set can be updated accordingly! diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/RuntimeImageHeapChunkWriter.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/RuntimeImageHeapChunkWriter.java index dc357687a6b3..5880aa00d186 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/RuntimeImageHeapChunkWriter.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/RuntimeImageHeapChunkWriter.java @@ -29,7 +29,6 @@ import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.genscavenge.remset.RememberedSet; import com.oracle.svm.core.identityhashcode.IdentityHashCodeSupport; @@ -44,8 +43,8 @@ public class RuntimeImageHeapChunkWriter implements ImageHeapChunkWriter { RuntimeImageHeapChunkWriter(ByteBuffer buffer, long layoutToBufferOffsetAddend) { DirectBuffer direct = (DirectBuffer) buffer; // required from caller - this.heapBegin = WordFactory.pointer(direct.address()); - this.layoutToBufferOffsetAddend = WordFactory.signed(layoutToBufferOffsetAddend); + this.heapBegin = Word.pointer(direct.address()); + this.layoutToBufferOffsetAddend = Word.signed(layoutToBufferOffsetAddend); } private Pointer getChunkPointerInBuffer(int chunkPosition) { @@ -55,23 +54,23 @@ private Pointer getChunkPointerInBuffer(int chunkPosition) { @Override public void initializeAlignedChunk(int chunkPosition, long topOffset, long endOffset, long offsetToPreviousChunk, long offsetToNextChunk) { AlignedHeapChunk.AlignedHeader header = (AlignedHeapChunk.AlignedHeader) getChunkPointerInBuffer(chunkPosition); - header.setTopOffset(WordFactory.unsigned(topOffset)); - header.setEndOffset(WordFactory.unsigned(endOffset)); + header.setTopOffset(Word.unsigned(topOffset)); + header.setEndOffset(Word.unsigned(endOffset)); header.setSpace(null); - header.setOffsetToPreviousChunk(WordFactory.unsigned(offsetToPreviousChunk)); - header.setOffsetToNextChunk(WordFactory.unsigned(offsetToNextChunk)); - header.setIdentityHashSalt(WordFactory.zero(), IdentityHashCodeSupport.IDENTITY_HASHCODE_SALT_LOCATION); + header.setOffsetToPreviousChunk(Word.unsigned(offsetToPreviousChunk)); + header.setOffsetToNextChunk(Word.unsigned(offsetToNextChunk)); + header.setIdentityHashSalt(Word.zero(), IdentityHashCodeSupport.IDENTITY_HASHCODE_SALT_LOCATION); } @Override public void initializeUnalignedChunk(int chunkPosition, long topOffset, long endOffset, long offsetToPreviousChunk, long offsetToNextChunk) { UnalignedHeapChunk.UnalignedHeader header = (UnalignedHeapChunk.UnalignedHeader) getChunkPointerInBuffer(chunkPosition); - header.setTopOffset(WordFactory.unsigned(topOffset)); - header.setEndOffset(WordFactory.unsigned(endOffset)); + header.setTopOffset(Word.unsigned(topOffset)); + header.setEndOffset(Word.unsigned(endOffset)); header.setSpace(null); - header.setOffsetToPreviousChunk(WordFactory.unsigned(offsetToPreviousChunk)); - header.setOffsetToNextChunk(WordFactory.unsigned(offsetToNextChunk)); - header.setIdentityHashSalt(WordFactory.zero(), IdentityHashCodeSupport.IDENTITY_HASHCODE_SALT_LOCATION); + header.setOffsetToPreviousChunk(Word.unsigned(offsetToPreviousChunk)); + header.setOffsetToNextChunk(Word.unsigned(offsetToNextChunk)); + header.setIdentityHashSalt(Word.zero(), IdentityHashCodeSupport.IDENTITY_HASHCODE_SALT_LOCATION); } @Override diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Space.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Space.java index 99a7d7d56e48..233f4030b3b0 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Space.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Space.java @@ -33,7 +33,6 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -214,7 +213,7 @@ public void logChunks(Log log) { @AlwaysInline("GC performance") @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private Pointer allocateMemory(UnsignedWord objectSize) { - Pointer result = WordFactory.nullPointer(); + Pointer result = Word.nullPointer(); /* Fast-path: try allocating in the last chunk. */ AlignedHeapChunk.AlignedHeader oldChunk = getLastAlignedHeapChunk(); if (oldChunk.isNonNull()) { @@ -233,7 +232,7 @@ private Pointer allocateInNewChunk(UnsignedWord objectSize) { if (newChunk.isNonNull()) { return AlignedHeapChunk.allocateMemory(newChunk, objectSize); } - return WordFactory.nullPointer(); + return Word.nullPointer(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -241,10 +240,10 @@ public void releaseChunks(ChunkReleaser chunkReleaser) { chunkReleaser.add(firstAlignedHeapChunk); chunkReleaser.add(firstUnalignedHeapChunk); - firstAlignedHeapChunk = WordFactory.nullPointer(); - lastAlignedHeapChunk = WordFactory.nullPointer(); - firstUnalignedHeapChunk = WordFactory.nullPointer(); - lastUnalignedHeapChunk = WordFactory.nullPointer(); + firstAlignedHeapChunk = Word.nullPointer(); + lastAlignedHeapChunk = Word.nullPointer(); + firstUnalignedHeapChunk = Word.nullPointer(); + lastUnalignedHeapChunk = Word.nullPointer(); accounting.reset(); } @@ -264,7 +263,7 @@ private void appendAlignedHeapChunkUninterruptibly(AlignedHeapChunk.AlignedHeade AlignedHeapChunk.AlignedHeader oldLast = getLastAlignedHeapChunk(); HeapChunk.setSpace(aChunk, this); HeapChunk.setPrevious(aChunk, oldLast); - HeapChunk.setNext(aChunk, WordFactory.nullPointer()); + HeapChunk.setNext(aChunk, Word.nullPointer()); if (oldLast.isNonNull()) { HeapChunk.setNext(oldLast, aChunk); } @@ -295,8 +294,8 @@ private void extractAlignedHeapChunkUninterruptibly(AlignedHeapChunk.AlignedHead } else { setLastAlignedHeapChunk(chunkPrev); } - HeapChunk.setNext(aChunk, WordFactory.nullPointer()); - HeapChunk.setPrevious(aChunk, WordFactory.nullPointer()); + HeapChunk.setNext(aChunk, Word.nullPointer()); + HeapChunk.setPrevious(aChunk, Word.nullPointer()); HeapChunk.setSpace(aChunk, null); } @@ -316,7 +315,7 @@ private void appendUnalignedHeapChunkUninterruptibly(UnalignedHeapChunk.Unaligne UnalignedHeapChunk.UnalignedHeader oldLast = getLastUnalignedHeapChunk(); HeapChunk.setSpace(uChunk, this); HeapChunk.setPrevious(uChunk, oldLast); - HeapChunk.setNext(uChunk, WordFactory.nullPointer()); + HeapChunk.setNext(uChunk, Word.nullPointer()); if (oldLast.isNonNull()) { HeapChunk.setNext(oldLast, uChunk); } @@ -348,8 +347,8 @@ private void extractUnalignedHeapChunkUninterruptibly(UnalignedHeapChunk.Unalign setLastUnalignedHeapChunk(chunkPrev); } /* Reset the fields that the result chunk keeps for Space. */ - HeapChunk.setNext(uChunk, WordFactory.nullPointer()); - HeapChunk.setPrevious(uChunk, WordFactory.nullPointer()); + HeapChunk.setNext(uChunk, Word.nullPointer()); + HeapChunk.setPrevious(uChunk, Word.nullPointer()); HeapChunk.setSpace(uChunk, null); } @@ -577,7 +576,7 @@ UnsignedWord computeObjectBytes() { } private UnsignedWord computeAlignedObjectBytes() { - UnsignedWord result = WordFactory.zero(); + UnsignedWord result = Word.zero(); AlignedHeapChunk.AlignedHeader aChunk = getFirstAlignedHeapChunk(); while (aChunk.isNonNull()) { UnsignedWord allocatedBytes = HeapChunk.getTopOffset(aChunk).subtract(AlignedHeapChunk.getObjectsStartOffset()); @@ -588,7 +587,7 @@ private UnsignedWord computeAlignedObjectBytes() { } private UnsignedWord computeUnalignedObjectBytes() { - UnsignedWord result = WordFactory.zero(); + UnsignedWord result = Word.zero(); UnalignedHeapChunk.UnalignedHeader uChunk = getFirstUnalignedHeapChunk(); while (uChunk.isNonNull()) { UnsignedWord allocatedBytes = HeapChunk.getTopOffset(uChunk).subtract(UnalignedHeapChunk.getObjectStartOffset()); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java index 2dc00e124ef0..c725b3e80395 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java @@ -40,7 +40,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateGCOptions; import com.oracle.svm.core.Uninterruptible; @@ -266,6 +265,11 @@ private static Object slowPathNewPodInstance(Word objectHeader, int arrayLength, return slowPathNewArrayLikeObject(objectHeader, arrayLength, referenceMap); } + @SubstrateForeignCallTarget(stubCallingConvention = false) + private static Object newDynamicHub(int vTableSlots) { + return HeapImpl.allocateDynamicHub(vTableSlots); + } + private static Object slowPathNewArrayLikeObject(Word objectHeader, int length, byte[] podReferenceMap) { /* * Avoid stack overflow errors while producing memory chunks, because that could leave the @@ -431,7 +435,7 @@ private static UnsignedWord availableTlabMemory(Descriptor allocator) { assert top.belowOrEqual(end); if (top.isNull() || end.isNull()) { - return WordFactory.unsigned(0); + return Word.unsigned(0); } return end.subtract(top); } @@ -439,7 +443,7 @@ private static UnsignedWord availableTlabMemory(Descriptor allocator) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static void guaranteeZeroed(Pointer memory, UnsignedWord size) { int wordSize = ConfigurationValues.getTarget().wordSize; - VMError.guarantee(UnsignedUtils.isAMultiple(size, WordFactory.unsigned(wordSize))); + VMError.guarantee(UnsignedUtils.isAMultiple(size, Word.unsigned(wordSize))); Pointer pos = memory; Pointer end = memory.add(size); @@ -489,20 +493,20 @@ private static void retireTlabToEden(IsolateThread thread) { Descriptor tlab = retireCurrentAllocationChunk(thread); AlignedHeader alignedChunk = tlab.getAlignedChunk(); UnalignedHeader unalignedChunk = tlab.getUnalignedChunk(); - tlab.setAlignedChunk(WordFactory.nullPointer()); - tlab.setUnalignedChunk(WordFactory.nullPointer()); + tlab.setAlignedChunk(Word.nullPointer()); + tlab.setUnalignedChunk(Word.nullPointer()); Space eden = HeapImpl.getHeapImpl().getYoungGeneration().getEden(); while (alignedChunk.isNonNull()) { AlignedHeader next = HeapChunk.getNext(alignedChunk); - HeapChunk.setNext(alignedChunk, WordFactory.nullPointer()); + HeapChunk.setNext(alignedChunk, Word.nullPointer()); eden.appendAlignedHeapChunk(alignedChunk); alignedChunk = next; } while (unalignedChunk.isNonNull()) { UnalignedHeader next = HeapChunk.getNext(unalignedChunk); - HeapChunk.setNext(unalignedChunk, WordFactory.nullPointer()); + HeapChunk.setNext(unalignedChunk, Word.nullPointer()); eden.appendUnalignedHeapChunk(unalignedChunk); unalignedChunk = next; } @@ -519,7 +523,7 @@ private static void registerNewAllocationChunk(Descriptor tlab, AlignedHeader ne tlab.setAllocationTop(HeapChunk.getTopPointer(newChunk), TLAB_TOP_IDENTITY); tlab.setAllocationEnd(HeapChunk.getEndPointer(newChunk), TLAB_END_IDENTITY); - HeapChunk.setTopPointer(newChunk, WordFactory.nullPointer()); + HeapChunk.setTopPointer(newChunk, Word.nullPointer()); } @Uninterruptible(reason = "Modifies and returns TLAB", callerMustBe = true) @@ -538,8 +542,8 @@ private static Descriptor retireCurrentAllocationChunk(IsolateThread thread) { * and only set in the top aligned chunk when it is retired. */ HeapChunk.setTopPointer(alignedChunk, allocationTop); - tlab.setAllocationTop(WordFactory.nullPointer(), TLAB_TOP_IDENTITY); - tlab.setAllocationEnd(WordFactory.nullPointer(), TLAB_END_IDENTITY); + tlab.setAllocationTop(Word.nullPointer(), TLAB_TOP_IDENTITY); + tlab.setAllocationEnd(Word.nullPointer(), TLAB_END_IDENTITY); UnsignedWord usedTlabSize = HeapChunk.getTopPointer(alignedChunk).subtract(AlignedHeapChunk.getObjectsStart(alignedChunk)); allocatedBytes.set(thread, allocatedBytes.get(thread).add(usedTlabSize)); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/UnalignedHeapChunk.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/UnalignedHeapChunk.java index fab374bf0604..e0825c0f56d5 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/UnalignedHeapChunk.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/UnalignedHeapChunk.java @@ -27,7 +27,6 @@ import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -104,7 +103,7 @@ public static Pointer getObjectEnd(UnalignedHeader that) { static UnsignedWord getChunkSizeForObject(UnsignedWord objectSize) { UnsignedWord objectStart = getObjectStartOffset(); - UnsignedWord alignment = WordFactory.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); + UnsignedWord alignment = Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); return UnsignedUtils.roundUp(objectStart.add(objectSize), alignment); } @@ -112,7 +111,7 @@ static UnsignedWord getChunkSizeForObject(UnsignedWord objectSize) { @Uninterruptible(reason = "Returns uninitialized memory.", callerMustBe = true) public static Pointer allocateMemory(UnalignedHeader that, UnsignedWord size) { UnsignedWord available = HeapChunk.availableObjectMemory(that); - Pointer result = WordFactory.nullPointer(); + Pointer result = Word.nullPointer(); if (size.belowOrEqual(available)) { result = HeapChunk.getTopPointer(that); Pointer newTop = result.add(size); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/YoungGeneration.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/YoungGeneration.java index 847428e4ee2e..f70b053e279f 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/YoungGeneration.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/YoungGeneration.java @@ -24,11 +24,11 @@ */ package com.oracle.svm.core.genscavenge; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -193,7 +193,7 @@ UnsignedWord getChunkBytes() { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) UnsignedWord getSurvivorChunkBytes() { - UnsignedWord chunkBytes = WordFactory.zero(); + UnsignedWord chunkBytes = Word.zero(); for (int i = 0; i < maxSurvivorSpaces; i++) { chunkBytes = chunkBytes.add(getSurvivorChunkBytes(i)); } @@ -216,7 +216,7 @@ UnsignedWord getAlignedChunkBytes() { * This value is only updated during a GC, be careful: see {@link #getChunkBytes}. */ UnsignedWord getSurvivorAlignedChunkBytes() { - UnsignedWord chunkBytes = WordFactory.zero(); + UnsignedWord chunkBytes = Word.zero(); for (int i = 0; i < maxSurvivorSpaces; i++) { chunkBytes = chunkBytes.add(this.survivorFromSpaces[i].getAlignedChunkBytes()); chunkBytes = chunkBytes.add(this.survivorToSpaces[i].getAlignedChunkBytes()); @@ -229,7 +229,7 @@ UnsignedWord computeObjectBytes() { } UnsignedWord computeSurvivorObjectBytes() { - UnsignedWord usedObjectBytes = WordFactory.zero(); + UnsignedWord usedObjectBytes = Word.zero(); for (int i = 0; i < maxSurvivorSpaces; i++) { usedObjectBytes = usedObjectBytes.add(survivorFromSpaces[i].computeObjectBytes()); usedObjectBytes = usedObjectBytes.add(survivorToSpaces[i].computeObjectBytes()); @@ -327,7 +327,7 @@ private boolean unalignedChunkFitsInSurvivors(UnalignedHeapChunk.UnalignedHeader AlignedHeapChunk.AlignedHeader requestAlignedSurvivorChunk() { assert VMOperation.isGCInProgress() : "Should only be called from the collector."; if (!alignedChunkFitsInSurvivors()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } return HeapImpl.getChunkProvider().produceAlignedChunk(); } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/compacting/MarkStack.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/compacting/MarkStack.java index f32f0fd5e50a..e652da9304ff 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/compacting/MarkStack.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/compacting/MarkStack.java @@ -27,6 +27,7 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; import static jdk.vm.ci.code.CodeUtil.K; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.struct.RawField; @@ -35,7 +36,6 @@ import org.graalvm.nativeimage.c.struct.UniqueLocationIdentity; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -127,7 +127,7 @@ interface Segment extends PointerBase { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private static Segment allocateSegment(Segment next) { - UnsignedWord size = WordFactory.unsigned(SEGMENT_SIZE); + UnsignedWord size = Word.unsigned(SEGMENT_SIZE); Segment segment = NullableNativeMemory.malloc(size, NmtCategory.GC); VMError.guarantee(segment.isNonNull(), "Could not allocate mark stack memory: malloc() returned null."); segment.setNext(next); @@ -138,7 +138,7 @@ private static Segment allocateSegment(Segment next) { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private static UnsignedWord getOffsetAtIndex(int index) { int refSize = ConfigurationValues.getObjectLayout().getReferenceSize(); - return WordFactory.unsigned(index).multiply(refSize).add(SizeOf.unsigned(Segment.class)); + return Word.unsigned(index).multiply(refSize).add(SizeOf.unsigned(Segment.class)); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) @@ -146,7 +146,7 @@ public void tearDown() { if (top.isNonNull()) { assert top.getNext().isNull(); NullableNativeMemory.free(top); - top = WordFactory.nullPointer(); + top = Word.nullPointer(); } } } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/compacting/ObjectMoveInfo.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/compacting/ObjectMoveInfo.java index 4ff328417040..e0e0d9fe9240 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/compacting/ObjectMoveInfo.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/compacting/ObjectMoveInfo.java @@ -27,9 +27,9 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -98,7 +98,7 @@ static Pointer getNewAddress(Pointer objSeqStart) { if (useCompressedLayout()) { long offset = objSeqStart.readInt(-8); offset *= ConfigurationValues.getObjectLayout().getAlignment(); - return objSeqStart.add(WordFactory.signed(offset)); + return objSeqStart.add(Word.signed(offset)); } else { return objSeqStart.readWord(-16); } @@ -117,10 +117,10 @@ static void setObjectSeqSize(Pointer objSeqStart, UnsignedWord nbytes) { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) static UnsignedWord getObjectSeqSize(Pointer objSeqStart) { if (useCompressedLayout()) { - UnsignedWord value = WordFactory.unsigned(objSeqStart.readShort(-4) & 0xffff); + UnsignedWord value = Word.unsigned(objSeqStart.readShort(-4) & 0xffff); return value.multiply(ConfigurationValues.getObjectLayout().getAlignment()); } else { - return WordFactory.unsigned(objSeqStart.readInt(-8)); + return Word.unsigned(objSeqStart.readInt(-8)); } } @@ -137,10 +137,10 @@ static void setNextObjectSeqOffset(Pointer objSeqStart, UnsignedWord offset) { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) static UnsignedWord getNextObjectSeqOffset(Pointer objSeqStart) { if (useCompressedLayout()) { - UnsignedWord value = WordFactory.unsigned(objSeqStart.readShort(-2) & 0xffff); + UnsignedWord value = Word.unsigned(objSeqStart.readShort(-2) & 0xffff); return value.multiply(ConfigurationValues.getObjectLayout().getAlignment()); } else { - return WordFactory.unsigned(objSeqStart.readInt(-4)); + return Word.unsigned(objSeqStart.readInt(-4)); } } @@ -148,7 +148,7 @@ static UnsignedWord getNextObjectSeqOffset(Pointer objSeqStart) { static Pointer getNextObjectSeqAddress(Pointer objSeqStart) { UnsignedWord offset = getNextObjectSeqOffset(objSeqStart); if (offset.equal(0)) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } return objSeqStart.add(offset); } @@ -190,12 +190,12 @@ static Pointer getNewObjectAddress(Pointer objPointer) { AlignedHeapChunk.AlignedHeader chunk = AlignedHeapChunk.getEnclosingChunkFromObjectPointer(objPointer); if (objPointer.aboveOrEqual(HeapChunk.getTopPointer(chunk))) { - return WordFactory.nullPointer(); // object did not survive, is in gap at chunk end + return Word.nullPointer(); // object did not survive, is in gap at chunk end } Pointer objSeq = BrickTable.getEntry(chunk, BrickTable.getIndex(chunk, objPointer)); if (objSeq.aboveThan(objPointer)) { // object not alive, in gap across brick table entries - return WordFactory.nullPointer(); + return Word.nullPointer(); } Pointer nextObjSeq = getNextObjectSeqAddress(objSeq); @@ -204,7 +204,7 @@ static Pointer getNewObjectAddress(Pointer objPointer) { nextObjSeq = getNextObjectSeqAddress(objSeq); } if (objPointer.aboveOrEqual(objSeq.add(getObjectSeqSize(objSeq)))) { - return WordFactory.nullPointer(); // object did not survive, in gap between objects + return Word.nullPointer(); // object did not survive, in gap between objects } Pointer newObjSeqAddress = getNewAddress(objSeq); @@ -224,9 +224,9 @@ public static void visit(AlignedHeapChunk.AlignedHeader chunk, Visitor visitor) Pointer next = getNextObjectSeqAddress(p); do { // The visitor might overwrite the current and/or next move info, so read it eagerly. - UnsignedWord nextSize = next.isNonNull() ? getObjectSeqSize(next) : WordFactory.zero(); - Pointer nextNewAddress = next.isNonNull() ? getNewAddress(next) : WordFactory.nullPointer(); - Pointer nextNext = next.isNonNull() ? getNextObjectSeqAddress(next) : WordFactory.nullPointer(); + UnsignedWord nextSize = next.isNonNull() ? getObjectSeqSize(next) : Word.zero(); + Pointer nextNewAddress = next.isNonNull() ? getNewAddress(next) : Word.nullPointer(); + Pointer nextNext = next.isNonNull() ? getNextObjectSeqAddress(next) : Word.nullPointer(); if (!visitor.visit(p, size, newAddress, next)) { return; diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/compacting/PlanningVisitor.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/compacting/PlanningVisitor.java index 27ef374685f1..5d3c0ff28f8b 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/compacting/PlanningVisitor.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/compacting/PlanningVisitor.java @@ -28,7 +28,6 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.genscavenge.AlignedHeapChunk; @@ -65,14 +64,14 @@ public boolean visitChunk(AlignedHeapChunk.AlignedHeader chunk) { Pointer initialTop = HeapChunk.getTopPointer(chunk); // top doesn't move until we are done Pointer objSeq = AlignedHeapChunk.getObjectsStart(chunk); - UnsignedWord gapSize = WordFactory.zero(); - UnsignedWord objSeqSize = WordFactory.zero(); - UnsignedWord brickIndex = WordFactory.zero(); + UnsignedWord gapSize = Word.zero(); + UnsignedWord objSeqSize = Word.zero(); + UnsignedWord brickIndex = Word.zero(); /* Initialize the move info structure at the chunk's object start location. */ ObjectMoveInfo.setNewAddress(objSeq, allocPointer); - ObjectMoveInfo.setObjectSeqSize(objSeq, WordFactory.zero()); - ObjectMoveInfo.setNextObjectSeqOffset(objSeq, WordFactory.zero()); + ObjectMoveInfo.setObjectSeqSize(objSeq, Word.zero()); + ObjectMoveInfo.setNextObjectSeqOffset(objSeq, Word.zero()); BrickTable.setEntry(chunk, brickIndex, objSeq); @@ -110,9 +109,9 @@ public boolean visitChunk(AlignedHeapChunk.AlignedHeader chunk) { // Initialize new move info. objSeq = p; - ObjectMoveInfo.setNextObjectSeqOffset(objSeq, WordFactory.zero()); + ObjectMoveInfo.setNextObjectSeqOffset(objSeq, Word.zero()); - gapSize = WordFactory.zero(); + gapSize = Word.zero(); } objSeqSize = objSeqSize.add(objSize); @@ -123,7 +122,7 @@ public boolean visitChunk(AlignedHeapChunk.AlignedHeader chunk) { ObjectMoveInfo.setNewAddress(objSeq, newAddress); ObjectMoveInfo.setObjectSeqSize(objSeq, objSeqSize); - objSeqSize = WordFactory.zero(); + objSeqSize = Word.zero(); /* Set brick table entries. */ UnsignedWord currentBrick = BrickTable.getIndex(chunk, p); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeAllocationSupport.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeAllocationSupport.java index cc3e14ed6296..a90ab31bb8dc 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeAllocationSupport.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeAllocationSupport.java @@ -33,6 +33,7 @@ import com.oracle.svm.core.graal.meta.SubstrateForeignCallsProvider; import com.oracle.svm.core.graal.snippets.GCAllocationSupport; import com.oracle.svm.core.heap.Pod; +import com.oracle.svm.core.hub.RuntimeClassLoading; import com.oracle.svm.core.snippets.SnippetRuntime; import com.oracle.svm.core.snippets.SnippetRuntime.SubstrateForeignCallDescriptor; import com.oracle.svm.core.thread.ContinuationSupport; @@ -45,6 +46,7 @@ public class GenScavengeAllocationSupport implements GCAllocationSupport { private static final SubstrateForeignCallDescriptor SLOW_NEW_ARRAY = SnippetRuntime.findForeignCall(ThreadLocalAllocation.class, "slowPathNewArray", NO_SIDE_EFFECT); private static final SubstrateForeignCallDescriptor SLOW_NEW_STORED_CONTINUATION = SnippetRuntime.findForeignCall(ThreadLocalAllocation.class, "slowPathNewStoredContinuation", NO_SIDE_EFFECT); private static final SubstrateForeignCallDescriptor SLOW_NEW_POD_INSTANCE = SnippetRuntime.findForeignCall(ThreadLocalAllocation.class, "slowPathNewPodInstance", NO_SIDE_EFFECT); + private static final SubstrateForeignCallDescriptor NEW_DYNAMICHUB = SnippetRuntime.findForeignCall(ThreadLocalAllocation.class, "newDynamicHub", NO_SIDE_EFFECT); private static final SubstrateForeignCallDescriptor[] UNCONDITIONAL_FOREIGN_CALLS = new SubstrateForeignCallDescriptor[]{SLOW_NEW_INSTANCE, SLOW_NEW_ARRAY}; public static void registerForeignCalls(SubstrateForeignCallsProvider foreignCalls) { @@ -55,6 +57,9 @@ public static void registerForeignCalls(SubstrateForeignCallsProvider foreignCal if (Pod.RuntimeSupport.isPresent()) { foreignCalls.register(SLOW_NEW_POD_INSTANCE); } + if (RuntimeClassLoading.isSupported()) { + foreignCalls.register(NEW_DYNAMICHUB); + } } @Override @@ -77,6 +82,11 @@ public ForeignCallDescriptor getNewPodInstanceStub() { return SLOW_NEW_POD_INSTANCE; } + @Override + public SubstrateForeignCallDescriptor getNewDynamicHub() { + return NEW_DYNAMICHUB; + } + @Override public boolean useTLAB() { return true; diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeGCFeature.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeGCFeature.java index bbf2acc34a74..14d45bfd9f64 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeGCFeature.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeGCFeature.java @@ -36,6 +36,7 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.genscavenge.AddressRangeCommittedMemoryProvider; import com.oracle.svm.core.genscavenge.ChunkedImageHeapLayouter; import com.oracle.svm.core.genscavenge.GenScavengeMemoryPoolMXBeans; import com.oracle.svm.core.genscavenge.HeapImpl; @@ -60,6 +61,8 @@ import com.oracle.svm.core.jvmstat.PerfDataFeature; import com.oracle.svm.core.jvmstat.PerfDataHolder; import com.oracle.svm.core.jvmstat.PerfManager; +import com.oracle.svm.core.os.CommittedMemoryProvider; +import com.oracle.svm.core.os.OSCommittedMemoryProvider; import jdk.graal.compiler.graph.Node; import jdk.graal.compiler.options.OptionValues; @@ -121,6 +124,10 @@ public void registerLowerings(RuntimeConfiguration runtimeConfig, OptionValues o @Override public void beforeAnalysis(BeforeAnalysisAccess access) { + if (!ImageSingletons.contains(CommittedMemoryProvider.class)) { + ImageSingletons.add(CommittedMemoryProvider.class, createCommittedMemoryProvider()); + } + // Needed for the barrier set. access.registerAsUsed(Object[].class); } @@ -149,17 +156,23 @@ public void registerForeignCalls(SubstrateForeignCallsProvider foreignCalls) { private static RememberedSet createRememberedSet() { if (SerialGCOptions.useRememberedSet()) { return new CardTableBasedRememberedSet(); - } else { - return new NoRememberedSet(); } + return new NoRememberedSet(); } private static PerfDataHolder createPerfData() { if (SubstrateOptions.useSerialGC()) { return new SerialGCPerfData(); - } else { - assert SubstrateOptions.useEpsilonGC(); - return new EpsilonGCPerfData(); } + + assert SubstrateOptions.useEpsilonGC(); + return new EpsilonGCPerfData(); + } + + private static CommittedMemoryProvider createCommittedMemoryProvider() { + if (SubstrateOptions.SpawnIsolates.getValue()) { + return new AddressRangeCommittedMemoryProvider(); + } + return new OSCommittedMemoryProvider(); } } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/AlignedChunkRememberedSet.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/AlignedChunkRememberedSet.java index 0a65ad6feb70..fb2358b02856 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/AlignedChunkRememberedSet.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/AlignedChunkRememberedSet.java @@ -31,7 +31,6 @@ import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -70,7 +69,7 @@ public static UnsignedWord getHeaderSize() { // Compaction needs room for a ObjectMoveInfo structure before the first object. headerSize = headerSize.add(ObjectMoveInfo.getSize()); } - UnsignedWord alignment = WordFactory.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); + UnsignedWord alignment = Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); return UnsignedUtils.roundUp(headerSize, alignment); } @@ -84,10 +83,10 @@ public static void enableRememberedSet(HostedByteBufferPointer chunk, int chunkP UnsignedWord objectsStartOffset = AlignedHeapChunk.getObjectsStartOffset(); for (ImageHeapObject obj : objects) { long offsetWithinChunk = obj.getOffset() - chunkPosition; - assert offsetWithinChunk > 0 && WordFactory.unsigned(offsetWithinChunk).aboveOrEqual(objectsStartOffset); + assert offsetWithinChunk > 0 && Word.unsigned(offsetWithinChunk).aboveOrEqual(objectsStartOffset); - UnsignedWord startOffset = WordFactory.unsigned(offsetWithinChunk).subtract(objectsStartOffset); - UnsignedWord endOffset = startOffset.add(WordFactory.unsigned(obj.getSize())); + UnsignedWord startOffset = Word.unsigned(offsetWithinChunk).subtract(objectsStartOffset); + UnsignedWord endOffset = startOffset.add(Word.unsigned(obj.getSize())); FirstObjectTable.setTableForObject(fotStart, startOffset, endOffset); // The remembered set bit in the header will be set by the code that writes the objects. } @@ -175,7 +174,7 @@ public static void walkDirtyObjects(AlignedHeader chunk, GreyToBlackObjectVisito walkObjects(chunk, dirtyHeapStart, dirtyHeapEnd, visitor); } - if (PointerUtils.isAMultiple(cardPos, WordFactory.unsigned(wordSize()))) { + if (PointerUtils.isAMultiple(cardPos, Word.unsigned(wordSize()))) { /* Fast forward through word-aligned continuous range of clean cards. */ cardPos = cardPos.subtract(wordSize()); while (cardPos.aboveOrEqual(cardTableStart) && ((UnsignedWord) cardPos.readWord(0)).equal(CardTable.CLEAN_WORD)) { @@ -229,7 +228,7 @@ private static UnsignedWord getObjectIndex(AlignedHeader chunk, Pointer objectPo @Fold static UnsignedWord getStructSize() { - return WordFactory.unsigned(SizeOf.get(AlignedHeader.class)); + return Word.unsigned(SizeOf.get(AlignedHeader.class)); } @Fold @@ -238,7 +237,7 @@ static UnsignedWord getCardTableSize() { UnsignedWord structSize = getStructSize(); UnsignedWord available = HeapParameters.getAlignedHeapChunkSize().subtract(structSize); UnsignedWord requiredSize = CardTable.tableSizeForMemorySize(available); - UnsignedWord alignment = WordFactory.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); + UnsignedWord alignment = Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); return UnsignedUtils.roundUp(requiredSize, alignment); } @@ -250,7 +249,7 @@ static UnsignedWord getFirstObjectTableSize() { @Fold static UnsignedWord getFirstObjectTableStartOffset() { UnsignedWord cardTableLimit = getCardTableLimitOffset(); - UnsignedWord alignment = WordFactory.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); + UnsignedWord alignment = Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); return UnsignedUtils.roundUp(cardTableLimit, alignment); } @@ -259,14 +258,14 @@ static UnsignedWord getFirstObjectTableLimitOffset() { UnsignedWord fotStart = getFirstObjectTableStartOffset(); UnsignedWord fotSize = getFirstObjectTableSize(); UnsignedWord fotLimit = fotStart.add(fotSize); - UnsignedWord alignment = WordFactory.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); + UnsignedWord alignment = Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); return UnsignedUtils.roundUp(fotLimit, alignment); } @Fold static UnsignedWord getCardTableStartOffset() { UnsignedWord structSize = getStructSize(); - UnsignedWord alignment = WordFactory.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); + UnsignedWord alignment = Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); return UnsignedUtils.roundUp(structSize, alignment); } @@ -275,7 +274,7 @@ static UnsignedWord getCardTableLimitOffset() { UnsignedWord tableStart = getCardTableStartOffset(); UnsignedWord tableSize = getCardTableSize(); UnsignedWord tableLimit = tableStart.add(tableSize); - UnsignedWord alignment = WordFactory.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); + UnsignedWord alignment = Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); return UnsignedUtils.roundUp(tableLimit, alignment); } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/CardTable.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/CardTable.java index 18a130b8a55f..4c2159fe7459 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/CardTable.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/CardTable.java @@ -28,7 +28,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.UnmanagedMemoryUtil; @@ -81,7 +80,7 @@ final class CardTable { static final byte DIRTY_ENTRY = 0; static final byte CLEAN_ENTRY = 1; - static final UnsignedWord CLEAN_WORD = WordFactory.unsigned(0x0101010101010101L); + static final UnsignedWord CLEAN_WORD = Word.unsigned(0x0101010101010101L); private static final CardTableVerificationVisitor CARD_TABLE_VERIFICATION_VISITOR = new CardTableVerificationVisitor(); @@ -148,7 +147,7 @@ public static UnsignedWord tableSizeForMemorySize(UnsignedWord memorySize) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static UnsignedWord indexLimitForMemorySize(UnsignedWord memorySize) { - UnsignedWord roundedMemory = UnsignedUtils.roundUp(memorySize, WordFactory.unsigned(BYTES_COVERED_BY_ENTRY)); + UnsignedWord roundedMemory = UnsignedUtils.roundUp(memorySize, Word.unsigned(BYTES_COVERED_BY_ENTRY)); return CardTable.memoryOffsetToIndex(roundedMemory); } @@ -238,8 +237,8 @@ public void initialize(Object parentObject, Pointer cardTableStart, Pointer obje public void reset() { this.parentObject = null; - this.cardTableStart = WordFactory.nullPointer(); - this.objectsStart = WordFactory.nullPointer(); + this.cardTableStart = Word.nullPointer(); + this.objectsStart = Word.nullPointer(); this.success = false; } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/CardTableBasedRememberedSet.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/CardTableBasedRememberedSet.java index 5db53d2d4e74..d9cbb6c97bbd 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/CardTableBasedRememberedSet.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/CardTableBasedRememberedSet.java @@ -26,10 +26,10 @@ import java.util.List; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -215,7 +215,7 @@ public boolean verify(AlignedHeader firstAlignedHeapChunk) { @Override public boolean verify(UnalignedHeader firstUnalignedHeapChunk) { - return verify(firstUnalignedHeapChunk, WordFactory.nullPointer()); + return verify(firstUnalignedHeapChunk, Word.nullPointer()); } @Override diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/FirstObjectTable.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/FirstObjectTable.java index 6c927f47cf0d..da526e240466 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/FirstObjectTable.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/FirstObjectTable.java @@ -24,9 +24,9 @@ */ package com.oracle.svm.core.genscavenge.remset; +import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.SubstrateUtil; @@ -288,7 +288,7 @@ private static Pointer getFirstObject(Pointer tableStart, Pointer objectsStart, @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static UnsignedWord entryToMemoryOffset(UnsignedWord index, int entry) { assert isMemoryOffsetEntry(entry) : "Entry out of bounds."; - UnsignedWord entryOffset = WordFactory.unsigned(-entry).multiply(memoryOffsetScale()); + UnsignedWord entryOffset = Word.unsigned(-entry).multiply(memoryOffsetScale()); assert entryOffset.belowThan(BYTES_COVERED_BY_ENTRY) : "Entry out of bounds."; UnsignedWord indexOffset = indexToMemoryOffset(index); @@ -297,7 +297,7 @@ private static UnsignedWord entryToMemoryOffset(UnsignedWord index, int entry) { public static boolean verify(Pointer tableStart, Pointer objectsStart, Pointer objectsLimit) { UnsignedWord indexLimit = getTableSizeForMemoryRange(objectsStart, objectsLimit); - for (UnsignedWord index = WordFactory.unsigned(0); index.belowThan(indexLimit); index = index.add(1)) { + for (UnsignedWord index = Word.unsigned(0); index.belowThan(indexLimit); index = index.add(1)) { Pointer objStart = getFirstObject(tableStart, objectsStart, index); if (objStart.belowThan(objectsStart) || objectsLimit.belowOrEqual(objStart)) { Log.log().string("The first object table entry at index ").unsigned(index).string(" points to an object that is outside of the current chunk: obj: ").zhex(objStart) @@ -327,7 +327,7 @@ public static boolean verify(Pointer tableStart, Pointer objectsStart, Pointer o private static UnsignedWord getTableSizeForMemoryRange(Pointer memoryStart, Pointer memoryLimit) { assert memoryStart.belowOrEqual(memoryLimit) : "Pointers out of order"; UnsignedWord memorySize = memoryLimit.subtract(memoryStart); - UnsignedWord roundedMemory = UnsignedUtils.roundUp(memorySize, WordFactory.unsigned(BYTES_COVERED_BY_ENTRY)); + UnsignedWord roundedMemory = UnsignedUtils.roundUp(memorySize, Word.unsigned(BYTES_COVERED_BY_ENTRY)); UnsignedWord index = FirstObjectTable.memoryOffsetToIndex(roundedMemory); return index.multiply(ENTRY_SIZE_BYTES); } @@ -394,7 +394,7 @@ private static int unbiasExponent(int entry) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static UnsignedWord exponentToOffset(int n) { assert 0 <= n && n <= 63 : "Exponent out of bounds."; - return WordFactory.unsigned(1L << n); + return Word.unsigned(1L << n); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/NoRememberedSet.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/NoRememberedSet.java index 572ce4534907..d9a95e236f00 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/NoRememberedSet.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/NoRememberedSet.java @@ -26,11 +26,11 @@ import java.util.List; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -61,15 +61,15 @@ public BarrierSet createBarrierSet(MetaAccessProvider metaAccess) { @Override public UnsignedWord getHeaderSizeOfAlignedChunk() { - UnsignedWord headerSize = WordFactory.unsigned(SizeOf.get(AlignedHeader.class)); - UnsignedWord alignment = WordFactory.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); + UnsignedWord headerSize = Word.unsigned(SizeOf.get(AlignedHeader.class)); + UnsignedWord alignment = Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); return UnsignedUtils.roundUp(headerSize, alignment); } @Override public UnsignedWord getHeaderSizeOfUnalignedChunk() { - UnsignedWord headerSize = WordFactory.unsigned(SizeOf.get(UnalignedHeader.class)); - UnsignedWord alignment = WordFactory.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); + UnsignedWord headerSize = Word.unsigned(SizeOf.get(UnalignedHeader.class)); + UnsignedWord alignment = Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); return UnsignedUtils.roundUp(headerSize, alignment); } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/UnalignedChunkRememberedSet.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/UnalignedChunkRememberedSet.java index 18856b644f49..851e27ca5d99 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/UnalignedChunkRememberedSet.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/remset/UnalignedChunkRememberedSet.java @@ -24,12 +24,12 @@ */ package com.oracle.svm.core.genscavenge.remset; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.config.ConfigurationValues; @@ -51,7 +51,7 @@ private UnalignedChunkRememberedSet() { @Fold public static UnsignedWord getHeaderSize() { UnsignedWord headerSize = getCardTableLimitOffset(); - UnsignedWord alignment = WordFactory.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); + UnsignedWord alignment = Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); return UnsignedUtils.roundUp(headerSize, alignment); } @@ -112,15 +112,15 @@ public static boolean verify(UnalignedHeader chunk) { @Fold static UnsignedWord getCardTableStartOffset() { - UnsignedWord headerSize = WordFactory.unsigned(SizeOf.get(UnalignedHeader.class)); - UnsignedWord alignment = WordFactory.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); + UnsignedWord headerSize = Word.unsigned(SizeOf.get(UnalignedHeader.class)); + UnsignedWord alignment = Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); return UnsignedUtils.roundUp(headerSize, alignment); } @Fold static UnsignedWord getCardTableSize() { - UnsignedWord requiredSize = CardTable.tableSizeForMemorySize(WordFactory.unsigned(1)); - UnsignedWord alignment = WordFactory.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); + UnsignedWord requiredSize = CardTable.tableSizeForMemorySize(Word.unsigned(1)); + UnsignedWord alignment = Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); return UnsignedUtils.roundUp(requiredSize, alignment); } @@ -129,13 +129,13 @@ static UnsignedWord getCardTableLimitOffset() { UnsignedWord tableStart = getCardTableStartOffset(); UnsignedWord tableSize = getCardTableSize(); UnsignedWord tableLimit = tableStart.add(tableSize); - UnsignedWord alignment = WordFactory.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); + UnsignedWord alignment = Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()); return UnsignedUtils.roundUp(tableLimit, alignment); } @Fold static UnsignedWord getObjectIndex() { - return WordFactory.zero(); + return Word.zero(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/AArch64InterpreterStubs.java b/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/AArch64InterpreterStubs.java new file mode 100644 index 000000000000..0b0055494816 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/AArch64InterpreterStubs.java @@ -0,0 +1,632 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.graal.aarch64; + +import com.oracle.svm.core.c.struct.OffsetOf; +import com.oracle.svm.core.deopt.DeoptimizationSlotPacking; +import com.oracle.svm.core.graal.code.InterpreterAccessStubData; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.asm.Label; +import jdk.graal.compiler.asm.aarch64.AArch64Address; +import jdk.graal.compiler.asm.aarch64.AArch64MacroAssembler; +import jdk.graal.compiler.core.common.NumUtil; +import jdk.graal.compiler.lir.asm.CompilationResultBuilder; +import jdk.graal.compiler.word.Word; +import jdk.vm.ci.aarch64.AArch64; +import jdk.vm.ci.code.Register; +import jdk.vm.ci.code.RegisterValue; +import jdk.vm.ci.code.StackSlot; +import jdk.vm.ci.meta.AllocatableValue; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.word.Pointer; +import org.graalvm.word.PointerBase; + +import static jdk.graal.compiler.asm.aarch64.AArch64Address.AddressingMode.IMMEDIATE_POST_INDEXED; +import static jdk.graal.compiler.asm.aarch64.AArch64Address.AddressingMode.IMMEDIATE_SIGNED_UNSCALED; +import static jdk.graal.compiler.asm.aarch64.AArch64Address.AddressingMode.IMMEDIATE_UNSIGNED_SCALED; +import static jdk.graal.compiler.asm.aarch64.AArch64Address.createImmediateAddress; +import static jdk.vm.ci.aarch64.AArch64.r0; +import static jdk.vm.ci.aarch64.AArch64.r1; +import static jdk.vm.ci.aarch64.AArch64.r2; +import static jdk.vm.ci.aarch64.AArch64.r3; +import static jdk.vm.ci.aarch64.AArch64.r4; +import static jdk.vm.ci.aarch64.AArch64.r5; +import static jdk.vm.ci.aarch64.AArch64.r6; +import static jdk.vm.ci.aarch64.AArch64.r7; +import static jdk.vm.ci.aarch64.AArch64.sp; +import static jdk.vm.ci.aarch64.AArch64.v0; +import static jdk.vm.ci.aarch64.AArch64.v1; +import static jdk.vm.ci.aarch64.AArch64.v2; +import static jdk.vm.ci.aarch64.AArch64.v3; +import static jdk.vm.ci.aarch64.AArch64.v4; +import static jdk.vm.ci.aarch64.AArch64.v5; +import static jdk.vm.ci.aarch64.AArch64.v6; +import static jdk.vm.ci.aarch64.AArch64.v7; + +public class AArch64InterpreterStubs { + + public static final Register TRAMPOLINE_ARGUMENT = AArch64.r10; + + public static class InterpreterEnterStubContext extends SubstrateAArch64Backend.SubstrateAArch64FrameContext { + + public InterpreterEnterStubContext(SharedMethod method) { + super(method); + } + + private static AArch64Address createImmediate(int offset) { + int deoptSlotSize = 8 + 8 /* padding */; + return createImmediateAddress(64, IMMEDIATE_UNSIGNED_SCALED, sp, deoptSlotSize + offset); + } + + @Override + public void enter(CompilationResultBuilder crb) { + AArch64MacroAssembler masm = (AArch64MacroAssembler) crb.asm; + + Register trampArg = TRAMPOLINE_ARGUMENT; + Register spCopy = AArch64.r11; + + masm.mov(64, spCopy, sp); + + super.enter(crb); + + /* sp points to InterpreterData struct */ + masm.str(64, spCopy, createImmediate(offsetAbiSpReg())); + + masm.str(64, r0, createImmediate(offsetAbiGpArg0())); + masm.str(64, r1, createImmediate(offsetAbiGpArg1())); + masm.str(64, r2, createImmediate(offsetAbiGpArg2())); + masm.str(64, r3, createImmediate(offsetAbiGpArg3())); + masm.str(64, r4, createImmediate(offsetAbiGpArg4())); + masm.str(64, r5, createImmediate(offsetAbiGpArg5())); + masm.str(64, r6, createImmediate(offsetAbiGpArg6())); + masm.str(64, r7, createImmediate(offsetAbiGpArg7())); + + masm.fstr(64, v0, createImmediate(offsetAbiFpArg0())); + masm.fstr(64, v1, createImmediate(offsetAbiFpArg1())); + masm.fstr(64, v2, createImmediate(offsetAbiFpArg2())); + masm.fstr(64, v3, createImmediate(offsetAbiFpArg3())); + masm.fstr(64, v4, createImmediate(offsetAbiFpArg4())); + masm.fstr(64, v5, createImmediate(offsetAbiFpArg5())); + masm.fstr(64, v6, createImmediate(offsetAbiFpArg6())); + masm.fstr(64, v7, createImmediate(offsetAbiFpArg7())); + + /* sp+deoptSlotSize points to InterpreterData struct */ + masm.add(64, r1, sp, 16 /* deoptSlotSize */); + + /* Pass the interpreter method index as first arg */ + masm.mov(64, r0, trampArg); + } + + @Override + public void leave(CompilationResultBuilder crb) { + AArch64MacroAssembler masm = (AArch64MacroAssembler) crb.asm; + + /* r0 is a pointer to InterpreterEnterData */ + + /* Move fp return value into ABI register */ + masm.fldr(64, v0, createImmediateAddress(64, IMMEDIATE_UNSIGNED_SCALED, r0, offsetAbiFpRet())); + + /* Move gp return value into ABI register */ + masm.ldr(64, r0, createImmediateAddress(64, IMMEDIATE_UNSIGNED_SCALED, r0, offsetAbiGpRet())); + + super.leave(crb); + } + } + + public static class InterpreterLeaveStubContext extends SubstrateAArch64Backend.SubstrateAArch64FrameContext { + + public InterpreterLeaveStubContext(SharedMethod method) { + super(method); + } + + @Override + public void enter(CompilationResultBuilder crb) { + super.enter(crb); + AArch64MacroAssembler masm = (AArch64MacroAssembler) crb.asm; + + /* sp points to four reserved stack slots for this stub */ + + /* Pointer to InterpreterData struct */ + masm.str(64, r1, createImmediateAddress(64, IMMEDIATE_UNSIGNED_SCALED, sp, 0)); + /* Variable stack size */ + masm.str(64, r2, createImmediateAddress(64, IMMEDIATE_UNSIGNED_SCALED, sp, 8)); + /* gcReferenceMap next */ + masm.str(64, r3, createImmediateAddress(64, IMMEDIATE_UNSIGNED_SCALED, sp, 16)); + + /* 4th slot is for stack alignment to 0x10 */ + + masm.sub(64, sp, sp, r2 /* variable stack size */); + } + + @Override + public void leave(CompilationResultBuilder crb) { + AArch64MacroAssembler masm = (AArch64MacroAssembler) crb.asm; + Register callTarget = AArch64.r10; + + /* Save call target */ + masm.mov(64, callTarget, r0); + + /* Get pointer to InterpreterData struct */ + masm.mov(64, r0, r1); + + Register stackSize = AArch64.r2; + Label regsHandling = new Label(); + /* if stackSize == 0 */ + masm.cbz(64, stackSize, regsHandling); + + /* Copy prepared outgoing args to the stack where the ABI expects it */ + Register calleeSpArgs = AArch64.r4; + Register interpDataSp = AArch64.r1; + masm.ldr(64, interpDataSp, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, r0, offsetAbiSpReg())); + masm.mov(64, calleeSpArgs, sp); + + int wordSize = 8; + Label spCopyBegin = new Label(); + masm.bind(spCopyBegin); + masm.ldr(64, r3, createImmediateAddress(64, IMMEDIATE_POST_INDEXED, interpDataSp, wordSize)); + masm.str(64, r3, createImmediateAddress(64, IMMEDIATE_POST_INDEXED, calleeSpArgs, wordSize)); + masm.sub(64, stackSize, stackSize, wordSize); + + masm.cbnz(64, stackSize, spCopyBegin); + + masm.bind(regsHandling); + + /* Set fp argument registers */ + masm.fldr(64, v7, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiFpArg7())); + masm.fldr(64, v6, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiFpArg6())); + masm.fldr(64, v5, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiFpArg5())); + masm.fldr(64, v4, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiFpArg4())); + masm.fldr(64, v3, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiFpArg3())); + masm.fldr(64, v2, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiFpArg2())); + masm.fldr(64, v1, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiFpArg1())); + masm.fldr(64, v0, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiFpArg0())); + + /* Set gp argument registers */ + masm.ldr(64, r7, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiGpArg7())); + masm.ldr(64, r6, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiGpArg6())); + masm.ldr(64, r5, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiGpArg5())); + masm.ldr(64, r4, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiGpArg4())); + masm.ldr(64, r3, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiGpArg3())); + masm.ldr(64, r2, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiGpArg2())); + masm.ldr(64, r1, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiGpArg1())); + masm.ldr(64, r0, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, AArch64.r0, offsetAbiGpArg0())); + + /* Call into target method */ + masm.blr(callTarget); + + Register resultCopy = AArch64.r8; + masm.mov(64, resultCopy, r0); + + /* Obtain stack size from deopt slot */ + masm.ldr(64, r2, createImmediateAddress(64, IMMEDIATE_UNSIGNED_SCALED, sp, 0)); + + /* Assumption of deopt slot encoding */ + assert crb.target.stackAlignment == 0x10; + masm.lsr(64, r2, r2, DeoptimizationSlotPacking.POS_VARIABLE_FRAMESIZE - DeoptimizationSlotPacking.STACK_ALIGNMENT); + + /* Restore stack pointer */ + masm.add(64, sp, sp, r2); + + /* Pointer InterpreterData struct */ + masm.ldr(64, r0, createImmediateAddress(64, IMMEDIATE_UNSIGNED_SCALED, sp, 0)); + + /* Save gp ABI register into InterpreterData struct */ + masm.str(64, resultCopy, createImmediateAddress(64, IMMEDIATE_UNSIGNED_SCALED, r0, offsetAbiGpRet())); + + /* Save fp ABI register into InterpreterData struct */ + masm.fstr(64, v0, createImmediateAddress(64, IMMEDIATE_UNSIGNED_SCALED, r0, offsetAbiFpRet())); + + super.leave(crb); + } + } + + public static int sizeOfInterpreterData() { + return NumUtil.roundUp(SizeOf.get(InterpreterDataAArch64.class), 0x10); + } + + public static int additionalFrameSizeEnterStub() { + int wordSize = 8; + int deoptSlotSize = wordSize + wordSize /* for padding */; + return sizeOfInterpreterData() + deoptSlotSize; + } + + public static int additionalFrameSizeLeaveStub() { + int wordSize = 8; + /* + * reserve four slots for: base address of outgoing stack args, variable stack size, + * gcReferenceMap, padding + */ + return 4 * wordSize; + } + + @RawStructure + public interface InterpreterDataAArch64 extends PointerBase { + @RawField + long getStackSize(); + + @RawField + void setStackSize(long val); + + @RawField + long getAbiSpReg(); + + @RawField + void setAbiSpReg(long val); + + /* arch specific */ + @RawField + long getAbiGpArg0(); + + @RawField + void setAbiGpArg0(long val); + + @RawField + long getAbiGpArg1(); + + @RawField + void setAbiGpArg1(long val); + + @RawField + long getAbiGpArg2(); + + @RawField + void setAbiGpArg2(long val); + + @RawField + long getAbiGpArg3(); + + @RawField + void setAbiGpArg3(long val); + + @RawField + long getAbiGpArg4(); + + @RawField + void setAbiGpArg4(long val); + + @RawField + long getAbiGpArg5(); + + @RawField + void setAbiGpArg5(long val); + + @RawField + long getAbiGpArg6(); + + @RawField + void setAbiGpArg6(long val); + + @RawField + long getAbiGpArg7(); + + @RawField + void setAbiGpArg7(long val); + + @RawField + long getAbiFpArg0(); + + @RawField + void setAbiFpArg0(long val); + + @RawField + long getAbiFpArg1(); + + @RawField + void setAbiFpArg1(long val); + + @RawField + long getAbiFpArg2(); + + @RawField + void setAbiFpArg2(long val); + + @RawField + long getAbiFpArg3(); + + @RawField + void setAbiFpArg3(long val); + + @RawField + long getAbiFpArg4(); + + @RawField + void setAbiFpArg4(long val); + + @RawField + long getAbiFpArg5(); + + @RawField + void setAbiFpArg5(long val); + + @RawField + long getAbiFpArg6(); + + @RawField + void setAbiFpArg6(long val); + + @RawField + long getAbiFpArg7(); + + @RawField + void setAbiFpArg7(long val); + + @RawField + void setAbiGpRet(long val); + + @RawField + long getAbiGpRet(); + + @RawField + void setAbiFpRet(long val); + + @RawField + long getAbiFpRet(); + } + + @Fold + public static int offsetAbiSpReg() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiSpReg"); + } + + @Fold + public static int offsetAbiGpArg0() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiGpArg0"); + } + + @Fold + public static int offsetAbiGpArg1() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiGpArg1"); + } + + @Fold + public static int offsetAbiGpArg2() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiGpArg2"); + } + + @Fold + public static int offsetAbiGpArg3() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiGpArg3"); + } + + @Fold + public static int offsetAbiGpArg4() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiGpArg4"); + } + + @Fold + public static int offsetAbiGpArg5() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiGpArg5"); + } + + @Fold + public static int offsetAbiGpArg6() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiGpArg6"); + } + + @Fold + public static int offsetAbiGpArg7() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiGpArg7"); + } + + @Fold + public static int offsetAbiFpArg0() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiFpArg0"); + } + + @Fold + public static int offsetAbiFpArg1() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiFpArg1"); + } + + @Fold + public static int offsetAbiFpArg2() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiFpArg2"); + } + + @Fold + public static int offsetAbiFpArg3() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiFpArg3"); + } + + @Fold + public static int offsetAbiFpArg4() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiFpArg4"); + } + + @Fold + public static int offsetAbiFpArg5() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiFpArg5"); + } + + @Fold + public static int offsetAbiFpArg6() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiFpArg6"); + } + + @Fold + public static int offsetAbiFpArg7() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiFpArg7"); + } + + @Fold + public static int offsetAbiFpRet() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiFpRet"); + } + + @Fold + public static int offsetAbiGpRet() { + return OffsetOf.get(InterpreterDataAArch64.class, "AbiGpRet"); + } + + public static class AArch64InterpreterAccessStubData implements InterpreterAccessStubData { + + @Override + public void setSp(Pointer data, int stackSize, Pointer stackBuffer) { + VMError.guarantee(stackBuffer.isNonNull()); + + InterpreterDataAArch64 p = (InterpreterDataAArch64) data; + p.setAbiSpReg(stackBuffer.rawValue()); + p.setStackSize(stackSize); + + /* + * We re-use the deopt slot to leave the stack size at a known place for the stack + * walker. See + * + * com.oracle.svm.core.graal.aarch64.SubstrateAArch64RegisterConfig#getCallingConvention + * + * comment above `currentStackOffset` declaration. + */ + assert stackSize % 0x10 == 0; + stackBuffer.writeLong(0, DeoptimizationSlotPacking.encodeVariableFrameSizeIntoDeoptSlot(stackSize)); + } + + @Override + public long getGpArgumentAt(AllocatableValue ccArg, Pointer data, int pos) { + InterpreterDataAArch64 p = (InterpreterDataAArch64) data; + return switch (pos) { + case 0 -> p.getAbiGpArg0(); + case 1 -> p.getAbiGpArg1(); + case 2 -> p.getAbiGpArg2(); + case 3 -> p.getAbiGpArg3(); + case 4 -> p.getAbiGpArg4(); + case 5 -> p.getAbiGpArg5(); + case 6 -> p.getAbiGpArg6(); + case 7 -> p.getAbiGpArg7(); + default -> { + StackSlot stackSlot = (StackSlot) ccArg; + Pointer spVal = Word.pointer(p.getAbiSpReg()); + yield spVal.readLong(stackSlot.getOffset(0)); + } + }; + } + + @Override + public long setGpArgumentAt(AllocatableValue ccArg, Pointer data, int pos, long val) { + InterpreterDataAArch64 p = (InterpreterDataAArch64) data; + if (pos >= 0 && pos <= 7) { + VMError.guarantee(ccArg instanceof RegisterValue); + switch (pos) { + case 0 -> p.setAbiGpArg0(val); + case 1 -> p.setAbiGpArg1(val); + case 2 -> p.setAbiGpArg2(val); + case 3 -> p.setAbiGpArg3(val); + case 4 -> p.setAbiGpArg4(val); + case 5 -> p.setAbiGpArg5(val); + case 6 -> p.setAbiGpArg6(val); + case 7 -> p.setAbiGpArg7(val); + } + /* no GC mask required */ + return 0; + } + StackSlot stackSlot = (StackSlot) ccArg; + + Pointer spVal = Word.pointer(p.getAbiSpReg()); + int offset = stackSlot.getOffset(0); + VMError.guarantee(spVal.isNonNull()); + VMError.guarantee(offset < p.getStackSize()); + + spVal.writeLong(offset, val); + + VMError.guarantee((pos - 8) < Long.SIZE, "more than 64 stack args are not supported"); + return 1L << (pos - 8); + } + + @Override + public long getFpArgumentAt(AllocatableValue ccArg, Pointer data, int pos) { + InterpreterDataAArch64 p = (InterpreterDataAArch64) data; + return switch (pos) { + case 0 -> p.getAbiFpArg0(); + case 1 -> p.getAbiFpArg1(); + case 2 -> p.getAbiFpArg2(); + case 3 -> p.getAbiFpArg3(); + case 4 -> p.getAbiFpArg4(); + case 5 -> p.getAbiFpArg5(); + case 6 -> p.getAbiFpArg6(); + case 7 -> p.getAbiFpArg7(); + default -> { + StackSlot stackSlot = (StackSlot) ccArg; + Pointer spVal = Word.pointer(p.getAbiSpReg()); + yield spVal.readLong(stackSlot.getOffset(0)); + } + }; + } + + @Override + public void setFpArgumentAt(AllocatableValue ccArg, Pointer data, int pos, long val) { + InterpreterDataAArch64 p = (InterpreterDataAArch64) data; + switch (pos) { + case 0 -> p.setAbiFpArg0(val); + case 1 -> p.setAbiFpArg1(val); + case 2 -> p.setAbiFpArg2(val); + case 3 -> p.setAbiFpArg3(val); + case 4 -> p.setAbiFpArg4(val); + case 5 -> p.setAbiFpArg5(val); + case 6 -> p.setAbiFpArg6(val); + case 7 -> p.setAbiFpArg7(val); + default -> { + StackSlot stackSlot = (StackSlot) ccArg; + + Pointer spVal = Word.pointer(p.getAbiSpReg()); + int offset = stackSlot.getOffset(0); + + VMError.guarantee(spVal.isNonNull()); + VMError.guarantee(offset < p.getStackSize()); + + spVal.writeLong(offset, val); + } + } + } + + @Override + public long getGpReturn(Pointer data) { + return ((InterpreterDataAArch64) data).getAbiGpRet(); + } + + @Override + public void setGpReturn(Pointer data, long gpReturn) { + ((InterpreterDataAArch64) data).setAbiGpRet(gpReturn); + } + + @Override + public long getFpReturn(Pointer data) { + return ((InterpreterDataAArch64) data).getAbiFpRet(); + } + + @Override + public void setFpReturn(Pointer data, long fpReturn) { + ((InterpreterDataAArch64) data).setAbiFpRet(fpReturn); + } + + @Override + @Fold + public int allocateStubDataSize() { + return sizeOfInterpreterData(); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64Backend.java b/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64Backend.java index b87390e83b09..37663a29b2c1 100755 --- a/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64Backend.java +++ b/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64Backend.java @@ -38,6 +38,7 @@ import java.util.function.BiConsumer; +import com.oracle.svm.core.interpreter.InterpreterSupport; import org.graalvm.nativeimage.ImageSingletons; import com.oracle.svm.core.FrameAccess; @@ -1244,7 +1245,14 @@ protected FrameContext createFrameContext(SharedMethod method, Deoptimizer.StubT return new DeoptEntryStubContext(method, callingConvention); } else if (stubType == Deoptimizer.StubType.ExitStub) { return new DeoptExitStubContext(method, callingConvention); + } else if (stubType == Deoptimizer.StubType.InterpreterEnterStub) { + assert InterpreterSupport.isEnabled(); + return new AArch64InterpreterStubs.InterpreterEnterStubContext(method); + } else if (stubType == Deoptimizer.StubType.InterpreterLeaveStub) { + assert InterpreterSupport.isEnabled(); + return new AArch64InterpreterStubs.InterpreterLeaveStubContext(method); } + return new SubstrateAArch64FrameContext(method); } @@ -1364,7 +1372,19 @@ protected void resetForEmittingCode(CompilationResultBuilder crb) { public LIRGenerationResult newLIRGenerationResult(CompilationIdentifier compilationId, LIR lir, RegisterAllocationConfig registerAllocationConfig, StructuredGraph graph, Object stub) { SharedMethod method = (SharedMethod) graph.method(); CallingConvention callingConvention = CodeUtil.getCallingConvention(getCodeCache(), method.getCallingConventionKind().toType(false), method, this); - return new SubstrateLIRGenerationResult(compilationId, lir, newFrameMapBuilder(registerAllocationConfig.getRegisterConfig()), registerAllocationConfig, callingConvention, method); + LIRGenerationResult lirGenerationResult = new SubstrateLIRGenerationResult(compilationId, lir, newFrameMapBuilder(registerAllocationConfig.getRegisterConfig()), registerAllocationConfig, + callingConvention, method); + + FrameMap frameMap = ((FrameMapBuilderTool) lirGenerationResult.getFrameMapBuilder()).getFrameMap(); + Deoptimizer.StubType stubType = method.getDeoptStubType(); + if (stubType == Deoptimizer.StubType.InterpreterEnterStub) { + assert InterpreterSupport.isEnabled(); + frameMap.reserveOutgoing(AArch64InterpreterStubs.additionalFrameSizeEnterStub()); + } else if (stubType == Deoptimizer.StubType.InterpreterLeaveStub) { + assert InterpreterSupport.isEnabled(); + frameMap.reserveOutgoing(AArch64InterpreterStubs.additionalFrameSizeLeaveStub()); + } + return lirGenerationResult; } @Override diff --git a/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/AMD64InterpreterStubs.java b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/AMD64InterpreterStubs.java new file mode 100644 index 000000000000..e14f17ea2fe1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/AMD64InterpreterStubs.java @@ -0,0 +1,648 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.graal.amd64; + +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.c.struct.OffsetOf; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.deopt.DeoptimizationSlotPacking; +import com.oracle.svm.core.graal.code.InterpreterAccessStubData; +import com.oracle.svm.core.graal.meta.SubstrateRegisterConfig; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.asm.Label; +import jdk.graal.compiler.asm.amd64.AMD64Address; +import jdk.graal.compiler.asm.amd64.AMD64Assembler; +import jdk.graal.compiler.asm.amd64.AMD64MacroAssembler; +import jdk.graal.compiler.core.common.NumUtil; +import jdk.graal.compiler.lir.asm.CompilationResultBuilder; +import jdk.graal.compiler.word.Word; +import jdk.vm.ci.amd64.AMD64; +import jdk.vm.ci.code.CallingConvention; +import jdk.vm.ci.code.Register; +import jdk.vm.ci.code.RegisterArray; +import jdk.vm.ci.code.RegisterValue; +import jdk.vm.ci.code.StackSlot; +import jdk.vm.ci.meta.AllocatableValue; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.word.Pointer; +import org.graalvm.word.PointerBase; + +import static jdk.vm.ci.amd64.AMD64.rax; +import static jdk.vm.ci.amd64.AMD64.rsp; +import static jdk.vm.ci.amd64.AMD64.xmm0; + +public class AMD64InterpreterStubs { + + private static SubstrateAMD64RegisterConfig getRegisterConfig() { + return new SubstrateAMD64RegisterConfig(SubstrateRegisterConfig.ConfigKind.NORMAL, null, ConfigurationValues.getTarget(), + SubstrateOptions.PreserveFramePointer.getValue()); + } + + public static final Register TRAMPOLINE_ARGUMENT = AMD64.rax; + + public static class InterpreterEnterStubContext extends SubstrateAMD64Backend.SubstrateAMD64FrameContext { + + public InterpreterEnterStubContext(SharedMethod method, CallingConvention callingConvention) { + super(method, callingConvention); + } + + private static AMD64Address createAddress(int offset) { + int deoptSlotSize = 8 + 8 /* padding */; + return new AMD64Address(rsp, deoptSlotSize + offset); + } + + @Override + public void enter(CompilationResultBuilder crb) { + AMD64MacroAssembler masm = (AMD64MacroAssembler) crb.asm; + + Register trampArg = TRAMPOLINE_ARGUMENT; + Register spCopy = AMD64.r11; + + masm.movq(spCopy, rsp); + + super.enter(crb); + + /* sp points to InterpreterData struct */ + masm.movq(createAddress(offsetAbiSpReg()), spCopy); + + RegisterArray gps = getRegisterConfig().getJavaGeneralParameterRegs(); + VMError.guarantee(gps.size() == 6); + + masm.movq(createAddress(offsetAbiGp0()), gps.get(0)); + masm.movq(createAddress(offsetAbiGp1()), gps.get(1)); + masm.movq(createAddress(offsetAbiGp2()), gps.get(2)); + masm.movq(createAddress(offsetAbiGp3()), gps.get(3)); + masm.movq(createAddress(offsetAbiGp4()), gps.get(4)); + masm.movq(createAddress(offsetAbiGp5()), gps.get(5)); + + RegisterArray fps = getRegisterConfig().getFloatingPointParameterRegs(); + + masm.movq(createAddress(offsetAbiFpArg0()), fps.get(0)); + masm.movq(createAddress(offsetAbiFpArg1()), fps.get(1)); + masm.movq(createAddress(offsetAbiFpArg2()), fps.get(2)); + masm.movq(createAddress(offsetAbiFpArg3()), fps.get(3)); + + if (Platform.includedIn(Platform.LINUX.class) || Platform.includedIn(Platform.DARWIN.class)) { + VMError.guarantee(fps.size() == 8); + masm.movq(createAddress(offsetAbiFpArg4()), fps.get(4)); + masm.movq(createAddress(offsetAbiFpArg5()), fps.get(5)); + masm.movq(createAddress(offsetAbiFpArg6()), fps.get(6)); + masm.movq(createAddress(offsetAbiFpArg7()), fps.get(7)); + } else { + assert Platform.includedIn(Platform.WINDOWS.class); + VMError.guarantee(fps.size() == 4); + } + + /* sp points to InterpreterData struct, move it as 2nd arg */ + masm.movq(gps.get(1), rsp); + masm.addq(gps.get(1), 16 /* deoptSlotSize */); + + /* Pass the interpreter method index as first arg */ + masm.movq(gps.get(0), trampArg); + } + + @Override + public void leave(CompilationResultBuilder crb) { + AMD64MacroAssembler masm = (AMD64MacroAssembler) crb.asm; + + /* rax is a pointer to InterpreterEnterData */ + + /* Move fp return value into ABI register */ + masm.movq(xmm0, new AMD64Address(rax, offsetAbiFpRet())); + + /* Move gp return value into ABI register */ + masm.movq(rax, new AMD64Address(rax, offsetAbiGpRet())); + + super.leave(crb); + } + } + + public static class InterpreterLeaveStubContext extends SubstrateAMD64Backend.SubstrateAMD64FrameContext { + + public InterpreterLeaveStubContext(SharedMethod method, CallingConvention callingConvention) { + super(method, callingConvention); + } + + @Override + public void enter(CompilationResultBuilder crb) { + super.enter(crb); + AMD64MacroAssembler masm = (AMD64MacroAssembler) crb.asm; + RegisterArray gps = getRegisterConfig().getJavaGeneralParameterRegs(); + + /* sp points to four reserved stack slots for this stub */ + + /* arg0 is untouched by this extra prolog */ + + /* arg1: Pointer to InterpreterData struct */ + masm.movq(new AMD64Address(rsp, 0), gps.get(1)); + /* arg2: Variable stack size */ + masm.movq(new AMD64Address(rsp, 8), gps.get(2)); + /* arg3: gcReferenceMap next */ + masm.movq(new AMD64Address(rsp, 16), gps.get(3)); + + /* 4th slot is for stack alignment to 0x10 */ + + masm.subq(rsp, gps.get(2) /* variable stack size */); + } + + @Override + public void leave(CompilationResultBuilder crb) { + AMD64MacroAssembler masm = (AMD64MacroAssembler) crb.asm; + RegisterArray gps = getRegisterConfig().getJavaGeneralParameterRegs(); + RegisterArray fps = getRegisterConfig().getFloatingPointParameterRegs(); + + /* Save call target */ + Register callTarget = AMD64.r10; + masm.movq(callTarget, rax); + + /* Get pointer to InterpreterData struct */ + masm.movq(rax, gps.get(1)); + + Register stackSize = AMD64.r11; + masm.movq(stackSize, gps.get(2)); + + Label regsHandling = new Label(); + /* if stackSize == 0 */ + masm.testq(stackSize, stackSize); + masm.jccb(AMD64Assembler.ConditionFlag.Zero, regsHandling); + + /* Copy prepared outgoing args to the stack where the ABI expects it */ + Register calleeSpArgs = AMD64.r12; + Register interpDataSp = AMD64.r13; + masm.movq(interpDataSp, new AMD64Address(rax, offsetAbiSpReg())); + masm.movq(calleeSpArgs, rsp); + + int wordSize = 8; + Label spCopyBegin = new Label(); + masm.bind(spCopyBegin); + /* 5th arg is not used, use it as a temp register */ + masm.movq(gps.get(4), new AMD64Address(interpDataSp, 0)); + masm.movq(new AMD64Address(calleeSpArgs, 0), gps.get(4)); + masm.addq(interpDataSp, wordSize); + masm.addq(calleeSpArgs, wordSize); + masm.subl(stackSize, wordSize); + + masm.testq(stackSize, stackSize); + masm.jccb(AMD64Assembler.ConditionFlag.NotZero, spCopyBegin); + + masm.bind(regsHandling); + + /* Set fp argument registers */ + masm.movq(fps.get(0), new AMD64Address(rax, offsetAbiFpArg0())); + masm.movq(fps.get(1), new AMD64Address(rax, offsetAbiFpArg1())); + masm.movq(fps.get(2), new AMD64Address(rax, offsetAbiFpArg2())); + masm.movq(fps.get(3), new AMD64Address(rax, offsetAbiFpArg3())); + + if (Platform.includedIn(Platform.LINUX.class) || Platform.includedIn(Platform.DARWIN.class)) { + masm.movq(fps.get(4), new AMD64Address(rax, offsetAbiFpArg4())); + masm.movq(fps.get(5), new AMD64Address(rax, offsetAbiFpArg5())); + masm.movq(fps.get(6), new AMD64Address(rax, offsetAbiFpArg6())); + masm.movq(fps.get(7), new AMD64Address(rax, offsetAbiFpArg7())); + } + + /* Set gp argument registers */ + masm.movq(gps.get(0), new AMD64Address(rax, offsetAbiGp0())); + masm.movq(gps.get(1), new AMD64Address(rax, offsetAbiGp1())); + masm.movq(gps.get(2), new AMD64Address(rax, offsetAbiGp2())); + masm.movq(gps.get(3), new AMD64Address(rax, offsetAbiGp3())); + masm.movq(gps.get(4), new AMD64Address(rax, offsetAbiGp4())); + masm.movq(gps.get(5), new AMD64Address(rax, offsetAbiGp5())); + + /* Call into target method */ + masm.call(callTarget); + + Register resultCopy = AMD64.r10; + masm.movq(resultCopy, rax); + + /* Obtain stack size from deopt slot */ + masm.movq(AMD64.r12, new AMD64Address(rsp, 0)); + + /* Assumption of deopt slot encoding */ + assert crb.target.stackAlignment == 0x10; + masm.shrq(AMD64.r12, DeoptimizationSlotPacking.POS_VARIABLE_FRAMESIZE - DeoptimizationSlotPacking.STACK_ALIGNMENT); + + /* Restore stack pointer */ + masm.addq(rsp, AMD64.r12); + + /* Pointer InterpreterData struct */ + masm.movq(rax, new AMD64Address(rsp, 0)); + + /* Save gp ABI register into InterpreterData struct */ + masm.movq(new AMD64Address(rax, offsetAbiGpRet()), resultCopy); + + /* Save fp ABI register into InterpreterData struct */ + masm.movq(new AMD64Address(rax, offsetAbiFpRet()), xmm0); + + super.leave(crb); + } + } + + public static int sizeOfInterpreterData() { + return NumUtil.roundUp(SizeOf.get(InterpreterDataAMD64.class), 0x10); + } + + public static int additionalFrameSizeEnterStub() { + int wordSize = 8; + int deoptSlotSize = wordSize + wordSize /* for padding */; + return sizeOfInterpreterData() + deoptSlotSize; + } + + public static int additionalFrameSizeLeaveStub() { + int wordSize = 8; + /* + * reserve four slots for: base address of outgoing stack args, variable stack size, + * gcReferenceMap, padding + */ + return 4 * wordSize; + } + + @RawStructure + public interface InterpreterDataAMD64 extends PointerBase { + @RawField + long getStackSize(); + + @RawField + void setStackSize(long val); + + @RawField + long getAbiSpReg(); + + @RawField + void setAbiSpReg(long val); + + /* arch specific */ + + @RawField + void setAbiGpRet(long val); + + @RawField + long getAbiGpRet(); + + @RawField + void setAbiFpRet(long val); + + @RawField + long getAbiFpRet(); + + @RawField + long getAbiGp0(); + + @RawField + void setAbiGp0(long val); + + @RawField + long getAbiGp1(); + + @RawField + void setAbiGp1(long val); + + @RawField + long getAbiGp2(); + + @RawField + void setAbiGp2(long val); + + @RawField + long getAbiGp3(); + + @RawField + void setAbiGp3(long val); + + @RawField + long getAbiGp4(); + + @RawField + void setAbiGp4(long val); + + @RawField + long getAbiGp5(); + + @RawField + void setAbiGp5(long val); + + @RawField + long getAbiFpArg0(); + + @RawField + void setAbiFpArg0(long val); + + @RawField + long getAbiFpArg1(); + + @RawField + void setAbiFpArg1(long val); + + @RawField + long getAbiFpArg2(); + + @RawField + void setAbiFpArg2(long val); + + @RawField + long getAbiFpArg3(); + + @RawField + void setAbiFpArg3(long val); + + // GR-55154: We could save 32 bytes on Windows by exluding storage for xmm4-xmm7 + @RawField + long getAbiFpArg4(); + + @RawField + void setAbiFpArg4(long val); + + @RawField + long getAbiFpArg5(); + + @RawField + void setAbiFpArg5(long val); + + @RawField + long getAbiFpArg6(); + + @RawField + void setAbiFpArg6(long val); + + @RawField + long getAbiFpArg7(); + + @RawField + void setAbiFpArg7(long val); + } + + @Fold + public static int offsetAbiSpReg() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiSpReg"); + } + + @Fold + public static int offsetAbiGp0() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiGp0"); + } + + @Fold + public static int offsetAbiGp1() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiGp1"); + } + + @Fold + public static int offsetAbiGp2() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiGp2"); + } + + @Fold + public static int offsetAbiGp3() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiGp3"); + } + + @Fold + public static int offsetAbiGp4() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiGp4"); + } + + @Fold + public static int offsetAbiGp5() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiGp5"); + } + + @Fold + public static int offsetAbiFpArg0() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiFpArg0"); + } + + @Fold + public static int offsetAbiFpArg1() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiFpArg1"); + } + + @Fold + public static int offsetAbiFpArg2() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiFpArg2"); + } + + @Fold + public static int offsetAbiFpArg3() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiFpArg3"); + } + + @Fold + public static int offsetAbiFpArg4() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiFpArg4"); + } + + @Fold + public static int offsetAbiFpArg5() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiFpArg5"); + } + + @Fold + public static int offsetAbiFpArg6() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiFpArg6"); + } + + @Fold + public static int offsetAbiFpArg7() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiFpArg7"); + } + + @Fold + public static int offsetAbiGpRet() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiGpRet"); + } + + @Fold + public static int offsetAbiFpRet() { + return OffsetOf.get(InterpreterDataAMD64.class, "AbiFpRet"); + } + + public static class AMD64InterpreterAccessStubData implements InterpreterAccessStubData { + + @Override + public void setSp(Pointer data, int stackSize, Pointer stackBuffer) { + VMError.guarantee(stackBuffer.isNonNull()); + + InterpreterDataAMD64 p = (InterpreterDataAMD64) data; + p.setAbiSpReg(stackBuffer.rawValue()); + p.setStackSize(stackSize); + + /* + * We re-use the deopt slot to leave the stack size at a known place for the stack + * walker. See + * + * com.oracle.svm.core.graal.amd64.SubstrateAMD64RegisterConfig#getCallingConvention + * + * comment above `currentStackOffset` declaration. + */ + assert stackSize % 0x10 == 0; + stackBuffer.writeLong(0, DeoptimizationSlotPacking.encodeVariableFrameSizeIntoDeoptSlot(stackSize)); + } + + @Override + public long getGpArgumentAt(AllocatableValue ccArg, Pointer data, int pos) { + InterpreterDataAMD64 p = (InterpreterDataAMD64) data; + return switch (pos) { + case 0 -> p.getAbiGp0(); + case 1 -> p.getAbiGp1(); + case 2 -> p.getAbiGp2(); + case 3 -> p.getAbiGp3(); + case 4 -> p.getAbiGp4(); + case 5 -> p.getAbiGp5(); + default -> { + StackSlot stackSlot = (StackSlot) ccArg; + Pointer sp = Word.pointer(p.getAbiSpReg()); + int spAdjustmentOnCall = ConfigurationValues.getTarget().wordSize; + int offset = stackSlot.getOffset(0) + spAdjustmentOnCall; + yield sp.readLong(offset); + } + }; + } + + @Override + public long setGpArgumentAt(AllocatableValue ccArg, Pointer data, int pos, long val) { + InterpreterDataAMD64 p = (InterpreterDataAMD64) data; + if (pos >= 0 && pos <= 5) { + VMError.guarantee(ccArg instanceof RegisterValue); + switch (pos) { + case 0 -> p.setAbiGp0(val); + case 1 -> p.setAbiGp1(val); + case 2 -> p.setAbiGp2(val); + case 3 -> p.setAbiGp3(val); + case 4 -> p.setAbiGp4(val); + case 5 -> p.setAbiGp5(val); + } + /* no GC mask required */ + return 0; + } + StackSlot stackSlot = (StackSlot) ccArg; + + Pointer sp = Word.pointer(p.getAbiSpReg()); + int offset = stackSlot.getOffset(0); + VMError.guarantee(sp.isNonNull()); + VMError.guarantee(offset < p.getStackSize()); + + sp.writeLong(offset, val); + + VMError.guarantee((pos - 6) < Long.SIZE, "more than 64 stack args are not supported"); + return 1L << (pos - 6); + } + + private static int upperFpEnd() { + /* only 4 floating point regs on Windows, 8 otherwise */ + return Platform.includedIn(Platform.WINDOWS.class) ? 3 : 7; + } + + @Override + public long getFpArgumentAt(AllocatableValue ccArg, Pointer data, int pos) { + InterpreterDataAMD64 p = (InterpreterDataAMD64) data; + if (pos >= 0 && pos <= upperFpEnd()) { + VMError.guarantee(ccArg instanceof RegisterValue); + switch (pos) { + case 0: + return p.getAbiFpArg0(); + case 1: + return p.getAbiFpArg1(); + case 2: + return p.getAbiFpArg2(); + case 3: + return p.getAbiFpArg3(); + case 4: + return p.getAbiFpArg4(); + case 5: + return p.getAbiFpArg5(); + case 6: + return p.getAbiFpArg6(); + case 7: + return p.getAbiFpArg7(); + } + } + StackSlot stackSlot = (StackSlot) ccArg; + Pointer sp = Word.pointer(p.getAbiSpReg()); + + int spAdjustmentOnCall = ConfigurationValues.getTarget().wordSize; + int offset = stackSlot.getOffset(0) + spAdjustmentOnCall; + + return sp.readLong(offset); + } + + @Override + public void setFpArgumentAt(AllocatableValue ccArg, Pointer data, int pos, long val) { + InterpreterDataAMD64 p = (InterpreterDataAMD64) data; + if (pos >= 0 && pos <= upperFpEnd()) { + VMError.guarantee(ccArg instanceof RegisterValue); + switch (pos) { + case 0 -> p.setAbiFpArg0(val); + case 1 -> p.setAbiFpArg1(val); + case 2 -> p.setAbiFpArg2(val); + case 3 -> p.setAbiFpArg3(val); + case 4 -> p.setAbiFpArg4(val); + case 5 -> p.setAbiFpArg5(val); + case 6 -> p.setAbiFpArg6(val); + case 7 -> p.setAbiFpArg7(val); + } + } else { + StackSlot stackSlot = (StackSlot) ccArg; + + Pointer sp = Word.pointer(p.getAbiSpReg()); + int offset = stackSlot.getOffset(0); + + VMError.guarantee(sp.isNonNull()); + VMError.guarantee(offset < p.getStackSize()); + + sp.writeLong(offset, val); + } + } + + @Override + public long getGpReturn(Pointer data) { + return ((InterpreterDataAMD64) data).getAbiGpRet(); + } + + @Override + public void setGpReturn(Pointer data, long gpReturn) { + ((InterpreterDataAMD64) data).setAbiGpRet(gpReturn); + } + + @Override + public long getFpReturn(Pointer data) { + return ((InterpreterDataAMD64) data).getAbiFpRet(); + } + + @Override + public void setFpReturn(Pointer data, long fpReturn) { + ((InterpreterDataAMD64) data).setAbiFpRet(fpReturn); + } + + @Override + @Fold + public int allocateStubDataSize() { + return sizeOfInterpreterData(); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java index 2c42cbd0803b..7e330886f8f6 100644 --- a/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java +++ b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java @@ -46,6 +46,7 @@ import java.util.EnumSet; import java.util.function.BiConsumer; +import com.oracle.svm.core.interpreter.InterpreterSupport; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; @@ -1705,10 +1706,24 @@ private static boolean isCalleeSaved(Register register, RegisterConfig config, S @Override public LIRGenerationResult newLIRGenerationResult(CompilationIdentifier compilationId, LIR lir, RegisterAllocationConfig registerAllocationConfig, StructuredGraph graph, Object stub) { SharedMethod method = (SharedMethod) graph.method(); + SubstrateCallingConventionKind ccKind = method.getCallingConventionKind(); SubstrateCallingConventionType ccType = ccKind.isCustom() ? method.getCustomCallingConventionType() : ccKind.toType(false); CallingConvention callingConvention = CodeUtil.getCallingConvention(getCodeCache(), ccType, method, this); - return new SubstrateLIRGenerationResult(compilationId, lir, newFrameMapBuilder(registerAllocationConfig.getRegisterConfig(), method), callingConvention, registerAllocationConfig, method); + LIRGenerationResult lirGenerationResult = new SubstrateLIRGenerationResult(compilationId, lir, newFrameMapBuilder(registerAllocationConfig.getRegisterConfig(), method), callingConvention, + registerAllocationConfig, method); + + FrameMap frameMap = ((FrameMapBuilderTool) lirGenerationResult.getFrameMapBuilder()).getFrameMap(); + Deoptimizer.StubType stubType = method.getDeoptStubType(); + if (stubType == Deoptimizer.StubType.InterpreterEnterStub) { + assert InterpreterSupport.isEnabled(); + frameMap.reserveOutgoing(AMD64InterpreterStubs.additionalFrameSizeEnterStub()); + } else if (stubType == Deoptimizer.StubType.InterpreterLeaveStub) { + assert InterpreterSupport.isEnabled(); + frameMap.reserveOutgoing(AMD64InterpreterStubs.additionalFrameSizeLeaveStub()); + } + + return lirGenerationResult; } protected AMD64ArithmeticLIRGenerator createArithmeticLIRGen(RegisterValue nullRegisterValue) { @@ -1799,7 +1814,14 @@ protected FrameContext createFrameContext(SharedMethod method, Deoptimizer.StubT return new DeoptEntryStubContext(method, callingConvention); } else if (stubType == Deoptimizer.StubType.ExitStub) { return new DeoptExitStubContext(method, callingConvention); + } else if (stubType == Deoptimizer.StubType.InterpreterEnterStub) { + assert InterpreterSupport.isEnabled(); + return new AMD64InterpreterStubs.InterpreterEnterStubContext(method, callingConvention); + } else if (stubType == Deoptimizer.StubType.InterpreterLeaveStub) { + assert InterpreterSupport.isEnabled(); + return new AMD64InterpreterStubs.InterpreterLeaveStubContext(method, callingConvention); } + return new SubstrateAMD64FrameContext(method, callingConvention); } diff --git a/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/LLVMToolchainUtils.java b/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/LLVMToolchainUtils.java index c73ef135d593..5d96c0aca7ab 100644 --- a/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/LLVMToolchainUtils.java +++ b/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/LLVMToolchainUtils.java @@ -32,9 +32,6 @@ import java.util.function.Function; import java.util.function.IntFunction; -import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.debug.GraalError; - import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.util.CompletionExecutor; import com.oracle.objectfile.ObjectFile; @@ -44,6 +41,9 @@ import com.oracle.svm.core.graal.llvm.util.LLVMTargetSpecific; import com.oracle.svm.hosted.image.LLVMToolchain; +import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.debug.GraalError; + public class LLVMToolchainUtils { public static void llvmOptimize(DebugContext debug, String outputPath, String inputPath, Path basePath, Function outputPathFormat) { List args = new ArrayList<>(); @@ -78,7 +78,7 @@ public static void llvmOptimize(DebugContext debug, String outputPath, String in LLVMToolchain.runLLVMCommand("opt", basePath, args); } catch (LLVMToolchain.RunFailureException e) { debug.log("%s", e.getOutput()); - throw new GraalError("LLVM optimization failed for " + outputPathFormat.apply(inputPath) + ": " + e.getStatus() + "\nCommand: opt " + String.join(" ", args)); + throw new GraalError("LLVM optimization failed for " + outputPathFormat.apply(inputPath) + ": " + e.getStatus() + System.lineSeparator() + "Command: opt " + String.join(" ", args)); } } @@ -104,7 +104,7 @@ public static void llvmCompile(DebugContext debug, String outputPath, String inp LLVMToolchain.runLLVMCommand("llc", basePath, args); } catch (LLVMToolchain.RunFailureException e) { debug.log("%s", e.getOutput()); - throw new GraalError("LLVM compilation failed for " + outputPathFormat.apply(inputPath) + ": " + e.getStatus() + "\nCommand: llc " + String.join(" ", args)); + throw new GraalError("LLVM compilation failed for " + outputPathFormat.apply(inputPath) + ": " + e.getStatus() + System.lineSeparator() + "Command: llc " + String.join(" ", args)); } } @@ -162,7 +162,7 @@ public static void llvmCleanupStackMaps(DebugContext debug, String inputPath, Pa LLVMToolchain.runLLVMCommand("llvm-objcopy", basePath, args); } catch (LLVMToolchain.RunFailureException e) { debug.log("%s", e.getOutput()); - throw new GraalError("Removing stack maps failed for " + inputPath + ": " + e.getStatus() + "\nCommand: llvm-objcopy " + String.join(" ", args)); + throw new GraalError("Removing stack maps failed for " + inputPath + ": " + e.getStatus() + System.lineSeparator() + "Command: llvm-objcopy " + String.join(" ", args)); } } diff --git a/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/lowering/LLVMLoadExceptionObjectLowering.java b/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/lowering/LLVMLoadExceptionObjectLowering.java index 8d8e87efb296..04c9669f0747 100644 --- a/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/lowering/LLVMLoadExceptionObjectLowering.java +++ b/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/lowering/LLVMLoadExceptionObjectLowering.java @@ -24,19 +24,19 @@ */ package com.oracle.svm.core.graal.llvm.lowering; -import jdk.graal.compiler.core.common.type.StampFactory; +import com.oracle.svm.core.graal.nodes.ReadExceptionObjectNode; +import com.oracle.svm.core.graal.snippets.NodeLoweringProvider; +import com.oracle.svm.core.nodes.CFunctionEpilogueNode; +import com.oracle.svm.core.thread.VMThreads; + import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.nodes.FixedWithNextNode; import jdk.graal.compiler.nodes.FrameState; +import jdk.graal.compiler.nodes.NodeView; import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.java.LoadExceptionObjectNode; import jdk.graal.compiler.nodes.spi.LoweringTool; -import com.oracle.svm.core.graal.nodes.ReadExceptionObjectNode; -import com.oracle.svm.core.graal.snippets.NodeLoweringProvider; -import com.oracle.svm.core.nodes.CFunctionEpilogueNode; -import com.oracle.svm.core.thread.VMThreads; - public class LLVMLoadExceptionObjectLowering implements NodeLoweringProvider { @Override @@ -46,7 +46,7 @@ public void lower(LoadExceptionObjectNode node, LoweringTool tool) { StructuredGraph graph = node.graph(); GraalError.guarantee(graph.getGuardsStage().areFrameStatesAtDeopts(), "Should be after FSA %s", node); - FixedWithNextNode readRegNode = graph.add(new ReadExceptionObjectNode(StampFactory.objectNonNull())); + FixedWithNextNode readRegNode = graph.add(new ReadExceptionObjectNode(node.stamp(NodeView.DEFAULT))); graph.replaceFixedWithFixed(node, readRegNode); /* diff --git a/substratevm/src/com.oracle.svm.core.posix/OWNERS.toml b/substratevm/src/com.oracle.svm.core.posix/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixNativeLibraryFeature.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixNativeLibraryFeature.java index 1e6d2e1748e5..8b059ed67407 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixNativeLibraryFeature.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixNativeLibraryFeature.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.posix; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -32,7 +33,6 @@ import org.graalvm.nativeimage.impl.InternalPlatform; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Isolates; import com.oracle.svm.core.annotate.Alias; @@ -83,7 +83,7 @@ public boolean initializeBuiltinLibraries() { if (Platform.includedIn(Platform.DARWIN.class)) { // On Darwin, getrlimit may return RLIM_INFINITY for rlim_max, but then OPEN_MAX // must be used for setrlimit or it will fail with errno EINVAL. - newValue = WordFactory.unsigned(DarwinSyslimits.OPEN_MAX()); + newValue = Word.unsigned(DarwinSyslimits.OPEN_MAX()); } rlp.set_rlim_cur(newValue); if (Resource.setrlimit(Resource.RLIMIT_NOFILE(), rlp) != 0) { @@ -114,7 +114,7 @@ public boolean initializeBuiltinLibraries() { */ if (Platform.includedIn(Platform.DARWIN.class)) { Time.timeval tv = UnsafeStackValue.get(Time.timeval.class); - Time.NoTransitions.gettimeofday(tv, WordFactory.nullPointer()); + Time.NoTransitions.gettimeofday(tv, Word.nullPointer()); Time.tm tm = UnsafeStackValue.get(Time.tm.class); Time.NoTransitions.localtime_r(tv.addressOftv_sec(), tm); } @@ -163,7 +163,7 @@ class PosixNativeLibrary implements NativeLibrary { private final String canonicalIdentifier; private final boolean builtin; - private PointerBase dlhandle = WordFactory.nullPointer(); + private PointerBase dlhandle = Word.nullPointer(); private boolean loaded = false; PosixNativeLibrary(String canonicalIdentifier, boolean builtin) { @@ -196,7 +196,7 @@ public boolean unload() { } assert dlhandle.isNonNull(); if (PosixUtils.dlclose(dlhandle)) { - dlhandle = WordFactory.nullPointer(); + dlhandle = Word.nullPointer(); return true; } else { return false; diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java index 0a8ef5e06db5..12b87ac0b2c3 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java @@ -33,11 +33,11 @@ import java.util.Map.Entry; import java.util.stream.Collectors; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.BaseProcessPropertiesSupport; import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; @@ -96,7 +96,7 @@ public String getObjectFile(PointerBase symbolAddress) { if (Dlfcn.dladdr(symbolAddress, info) == 0) { return null; } - CCharPointer realpath = Stdlib.realpath(info.dli_fname(), WordFactory.nullPointer()); + CCharPointer realpath = Stdlib.realpath(info.dli_fname(), Word.nullPointer()); if (realpath.isNull()) { return null; } @@ -157,7 +157,7 @@ protected static String realpath(String path) { * pointer to it, so I have to free it. */ try (CCharPointerHolder pathHolder = CTypeConversion.toCString(path)) { - CCharPointer realpath = Stdlib.realpath(pathHolder.get(), WordFactory.nullPointer()); + CCharPointer realpath = Stdlib.realpath(pathHolder.get(), Word.nullPointer()); if (realpath.isNull()) { /* Failure to find a real path. */ return null; diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java index 19648de2d7c6..ee413e5bf95e 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java @@ -27,6 +27,7 @@ import java.io.File; import java.nio.ByteOrder; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -35,7 +36,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; @@ -56,9 +56,9 @@ public PosixRawFileOperationSupport(boolean useNativeByteOrder) { @Override public CCharPointer allocateCPath(String path) { byte[] data = path.getBytes(); - CCharPointer filename = UntrackedNullableNativeMemory.malloc(WordFactory.unsigned(data.length + 1)); + CCharPointer filename = UntrackedNullableNativeMemory.malloc(Word.unsigned(data.length + 1)); if (filename.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } for (int i = 0; i < data.length; i++) { @@ -105,7 +105,7 @@ private static RawFileDescriptor open0(String path, int flags) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static RawFileDescriptor open0(CCharPointer cPath, int flags) { int permissions = PosixStat.S_IRUSR() | PosixStat.S_IWUSR(); - return WordFactory.signed(Fcntl.NoTransitions.open(cPath, flags, permissions)); + return Word.signed(Fcntl.NoTransitions.open(cPath, flags, permissions)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -135,14 +135,14 @@ public long size(RawFileDescriptor fd) { @Override public long position(RawFileDescriptor fd) { int posixFd = getPosixFileDescriptor(fd); - return Unistd.NoTransitions.lseek(posixFd, WordFactory.signed(0), Unistd.SEEK_CUR()).rawValue(); + return Unistd.NoTransitions.lseek(posixFd, Word.signed(0), Unistd.SEEK_CUR()).rawValue(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override public boolean seek(RawFileDescriptor fd, long position) { int posixFd = getPosixFileDescriptor(fd); - SignedWord newPos = Unistd.NoTransitions.lseek(posixFd, WordFactory.signed(position), Unistd.SEEK_SET()); + SignedWord newPos = Unistd.NoTransitions.lseek(posixFd, Word.signed(position), Unistd.SEEK_SET()); return position == newPos.rawValue(); } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSignalHandlerSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSignalHandlerSupport.java index a6619236b292..d45f31e6925c 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSignalHandlerSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSignalHandlerSupport.java @@ -46,7 +46,6 @@ import org.graalvm.word.LocationIdentity; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; @@ -178,7 +177,7 @@ private static long installJavaSignalHandler0(int sig, long nativeH, boolean isS sigset_tPointer sigset = UnsafeStackValue.get(sigset_tPointer.class); Signal.NoTransitions.sigemptyset(sigset); Signal.NoTransitions.sigaddset(sigset, sig); - Signal.NoTransitions.sigprocmask(Signal.SIG_UNBLOCK(), sigset, WordFactory.nullPointer()); + Signal.NoTransitions.sigprocmask(Signal.SIG_UNBLOCK(), sigset, Word.nullPointer()); return dispatcherToHandler(oldDispatcher); } finally { @@ -290,7 +289,7 @@ private static SignalDispatcher handlerToDispatcher(long nativeH) { } else if (nativeH == ERROR_HANDLER) { return Signal.SIG_ERR(); } else { - return WordFactory.pointer(nativeH); + return Word.pointer(nativeH); } } @@ -351,7 +350,7 @@ static SignalDispatcher installNativeSignalHandler0(int signum, SignalDispatcher int structSigActionSize = SizeOf.get(Signal.sigaction.class); Signal.sigaction act = UnsafeStackValue.get(structSigActionSize); - LibC.memset(act, WordFactory.signed(0), WordFactory.unsigned(structSigActionSize)); + LibC.memset(act, Word.signed(0), Word.unsigned(structSigActionSize)); act.sa_flags(flags); act.sa_handler(handler); @@ -370,11 +369,11 @@ private static void installNativeSignalHandler0(int signum, Signal.AdvancedSigna int structSigActionSize = SizeOf.get(Signal.sigaction.class); Signal.sigaction act = UnsafeStackValue.get(structSigActionSize); - LibC.memset(act, WordFactory.signed(0), WordFactory.unsigned(structSigActionSize)); + LibC.memset(act, Word.signed(0), Word.unsigned(structSigActionSize)); act.sa_flags(Signal.SA_SIGINFO() | flags); act.sa_sigaction(handler); - int result = sigaction(signum, act, WordFactory.nullPointer(), isSignalHandlingAllowed); + int result = sigaction(signum, act, Word.nullPointer(), isSignalHandlingAllowed); PosixUtils.checkStatusIs0(result, "sigaction failed in installSignalHandler()."); } @@ -383,7 +382,7 @@ static PointerBase getCurrentDispatcher(int sig) { assert NativeSpinLockUtils.isLocked(LOCK.get()); Signal.sigaction handler = UnsafeStackValue.get(Signal.sigaction.class); - Signal.sigaction(sig, WordFactory.nullPointer(), handler); + Signal.sigaction(sig, Word.nullPointer(), handler); if ((handler.sa_flags() & Signal.SA_SIGINFO()) != Signal.SA_SIGINFO()) { return handler.sa_sigaction(); } @@ -523,8 +522,8 @@ public void execute(boolean isFirstIsolate) { } private static boolean isFirst() { - Word expected = WordFactory.zero(); - Word actual = NOOP_HANDLERS_INSTALLED.get().compareAndSwapWord(0, expected, WordFactory.unsigned(1), LocationIdentity.ANY_LOCATION); + Word expected = Word.zero(); + Word actual = NOOP_HANDLERS_INSTALLED.get().compareAndSwapWord(0, expected, Word.unsigned(1), LocationIdentity.ANY_LOCATION); return expected == actual; } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateGlobalSigprofHandler.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateGlobalSigprofHandler.java index e39a05d005a6..42b717c03967 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateGlobalSigprofHandler.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateGlobalSigprofHandler.java @@ -27,10 +27,10 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; @@ -58,7 +58,7 @@ private static void updateInterval(long us) { newValue.it_interval().set_tv_sec(us / TimeUtils.microsPerSecond); newValue.it_interval().set_tv_usec(us % TimeUtils.microsPerSecond); - int status = Time.NoTransitions.setitimer(Time.TimerTypeEnum.ITIMER_PROF, newValue, WordFactory.nullPointer()); + int status = Time.NoTransitions.setitimer(Time.TimerTypeEnum.ITIMER_PROF, newValue, Word.nullPointer()); PosixUtils.checkStatusIs0(status, "setitimer(which, newValue, oldValue): wrong arguments."); } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSystemPropertiesSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSystemPropertiesSupport.java index a93cd35ff9f0..928b61540c85 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSystemPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSystemPropertiesSupport.java @@ -24,9 +24,9 @@ */ package com.oracle.svm.core.posix; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; import com.oracle.svm.core.jdk.SystemPropertiesSupport; @@ -56,7 +56,7 @@ protected String userHomeValue() { protected String userDirValue() { int bufSize = Limits.MAXPATHLEN(); CCharPointer buf = UnsafeStackValue.get(bufSize); - if (Unistd.getcwd(buf, WordFactory.unsigned(bufSize)).isNonNull()) { + if (Unistd.getcwd(buf, Word.unsigned(bufSize)).isNonNull()) { return CTypeConversion.toJavaString(buf); } else { throw new java.lang.Error("Properties init: Could not determine current working directory."); diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixUtils.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixUtils.java index a477be5230f3..ffb232eb456e 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixUtils.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixUtils.java @@ -39,7 +39,6 @@ import org.graalvm.word.PointerBase; import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; @@ -79,7 +78,7 @@ static String setLocale(String category, String locale) { private static String setLocale(int category, String locale) { if (locale == null) { - CCharPointer cstrResult = Locale.setlocale(category, WordFactory.nullPointer()); + CCharPointer cstrResult = Locale.setlocale(category, Word.nullPointer()); return CTypeConversion.toJavaString(cstrResult); } try (CCharPointerHolder localePin = CTypeConversion.toCString(locale)) { @@ -224,7 +223,7 @@ public static boolean writeUninterruptibly(int fd, byte[] data) { DynamicHub hub = KnownIntrinsics.readHub(data); UnsignedWord baseOffset = LayoutEncoding.getArrayBaseOffset(hub.getLayoutEncoding()); Pointer dataPtr = Word.objectToUntrackedPointer(data).add(baseOffset); - return writeUninterruptibly(fd, dataPtr, WordFactory.unsigned(data.length)); + return writeUninterruptibly(fd, dataPtr, Word.unsigned(data.length)); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) @@ -283,7 +282,7 @@ public static int readUninterruptibly(int fd, Pointer buffer, int bufferLen, int int readBytes = -1; if (bufferOffset < bufferLen) { do { - readBytes = (int) Unistd.NoTransitions.read(fd, buffer.add(bufferOffset), WordFactory.unsigned(bufferLen - bufferOffset)).rawValue(); + readBytes = (int) Unistd.NoTransitions.read(fd, buffer.add(bufferOffset), Word.unsigned(bufferLen - bufferOffset)).rawValue(); } while (readBytes == -1 && LibC.errno() == Errno.EINTR()); } return readBytes; @@ -292,7 +291,7 @@ public static int readUninterruptibly(int fd, Pointer buffer, int bufferLen, int @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public static int readUninterruptibly(int fd, Pointer buffer, int bufferSize) { VMError.guarantee(bufferSize >= 0); - long readBytes = readUninterruptibly(fd, buffer, WordFactory.unsigned(bufferSize)); + long readBytes = readUninterruptibly(fd, buffer, Word.unsigned(bufferSize)); assert (int) readBytes == readBytes; return (int) readBytes; } @@ -336,7 +335,7 @@ private static String getUserNameOrDir(int uid, boolean name) { } /* Does not use StackValue because it is not safe to use in virtual threads. */ - UnsignedWord allocSize = WordFactory.unsigned(SizeOf.get(passwdPointer.class) + SizeOf.get(passwd.class) + bufSize); + UnsignedWord allocSize = Word.unsigned(SizeOf.get(passwdPointer.class) + SizeOf.get(passwd.class) + bufSize); Pointer alloc = NullableNativeMemory.malloc(allocSize, NmtCategory.Internal); if (alloc.isNull()) { return null; @@ -346,7 +345,7 @@ private static String getUserNameOrDir(int uid, boolean name) { passwdPointer p = (passwdPointer) alloc; passwd pwent = (passwd) ((Pointer) p).add(SizeOf.get(passwdPointer.class)); CCharPointer pwBuf = (CCharPointer) ((Pointer) pwent).add(SizeOf.get(passwd.class)); - int code = Pwd.getpwuid_r(uid, pwent, pwBuf, WordFactory.unsigned(bufSize), p); + int code = Pwd.getpwuid_r(uid, pwent, pwBuf, Word.unsigned(bufSize), p); if (code != 0) { return null; } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixVirtualMemoryProvider.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixVirtualMemoryProvider.java index cffcbbb4550d..9668834fff82 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixVirtualMemoryProvider.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixVirtualMemoryProvider.java @@ -37,7 +37,7 @@ import static com.oracle.svm.core.posix.headers.Mman.NoTransitions.mmap; import static com.oracle.svm.core.posix.headers.Mman.NoTransitions.mprotect; import static com.oracle.svm.core.posix.headers.Mman.NoTransitions.munmap; -import static org.graalvm.word.WordFactory.nullPointer; +import static jdk.graal.compiler.word.Word.nullPointer; import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; @@ -47,7 +47,6 @@ import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CGlobalData; @@ -78,9 +77,9 @@ public class PosixVirtualMemoryProvider implements VirtualMemoryProvider { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static UnsignedWord getPageSize() { Word value = CACHED_PAGE_SIZE.get().read(); - if (value.equal(WordFactory.zero())) { + if (value.equal(Word.zero())) { long queried = Unistd.NoTransitions.sysconf(Unistd._SC_PAGE_SIZE()); - value = WordFactory.unsigned(queried); + value = Word.unsigned(queried); CACHED_PAGE_SIZE.get().write(value); } return value; @@ -111,7 +110,7 @@ public UnsignedWord getGranularity() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public Pointer reserve(UnsignedWord nbytes, UnsignedWord alignment, boolean executable) { if (nbytes.equal(0)) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } UnsignedWord granularity = getGranularity(); @@ -148,7 +147,7 @@ public Pointer reserve(UnsignedWord nbytes, UnsignedWord alignment, boolean exec @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public Pointer mapFile(PointerBase start, UnsignedWord nbytes, WordBase fileHandle, UnsignedWord offset, int access) { if ((start.isNonNull() && !isAligned(start)) || nbytes.equal(0)) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } int flags = MAP_PRIVATE(); @@ -157,14 +156,14 @@ public Pointer mapFile(PointerBase start, UnsignedWord nbytes, WordBase fileHand } int fd = (int) fileHandle.rawValue(); Pointer result = mmap(start, nbytes, accessAsProt(access), flags, fd, offset.rawValue()); - return result.notEqual(MAP_FAILED()) ? result : WordFactory.nullPointer(); + return result.notEqual(MAP_FAILED()) ? result : Word.nullPointer(); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public Pointer commit(PointerBase start, UnsignedWord nbytes, int access) { if ((start.isNonNull() && !isAligned(start)) || nbytes.equal(0)) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } int flags = MAP_ANON() | MAP_PRIVATE(); diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/aarch64/AArch64DarwinUContextRegisterDumper.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/aarch64/AArch64DarwinUContextRegisterDumper.java index 0a13b54d8364..f5b126432f96 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/aarch64/AArch64DarwinUContextRegisterDumper.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/aarch64/AArch64DarwinUContextRegisterDumper.java @@ -26,10 +26,10 @@ import static com.oracle.svm.core.RegisterDumper.dumpReg; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.RegisterDumper; import com.oracle.svm.core.Uninterruptible; @@ -93,27 +93,27 @@ public void dumpRegisters(Log log, Signal.ucontext_t uContext, boolean printLoca @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getHeapBase(Signal.ucontext_t uContext) { Signal.AArch64DarwinMContext64 sigcontext = uContext.uc_mcontext_darwin_aarch64(); - return WordFactory.pointer(sigcontext.regs().read(27)); + return Word.pointer(sigcontext.regs().read(27)); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getThreadPointer(Signal.ucontext_t uContext) { Signal.AArch64DarwinMContext64 sigcontext = uContext.uc_mcontext_darwin_aarch64(); - return WordFactory.pointer(sigcontext.regs().read(28)); + return Word.pointer(sigcontext.regs().read(28)); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getSP(Signal.ucontext_t uContext) { Signal.AArch64DarwinMContext64 sigcontext = uContext.uc_mcontext_darwin_aarch64(); - return WordFactory.pointer(sigcontext.sp()); + return Word.pointer(sigcontext.sp()); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getIP(Signal.ucontext_t uContext) { Signal.AArch64DarwinMContext64 sigcontext = uContext.uc_mcontext_darwin_aarch64(); - return WordFactory.pointer(sigcontext.pc()); + return Word.pointer(sigcontext.pc()); } } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/aarch64/AArch64LinuxUContextRegisterDumper.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/aarch64/AArch64LinuxUContextRegisterDumper.java index 11f718db9c49..7593dd45437f 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/aarch64/AArch64LinuxUContextRegisterDumper.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/aarch64/AArch64LinuxUContextRegisterDumper.java @@ -26,10 +26,10 @@ import static com.oracle.svm.core.RegisterDumper.dumpReg; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.RegisterDumper; import com.oracle.svm.core.Uninterruptible; @@ -95,27 +95,27 @@ public void dumpRegisters(Log log, ucontext_t uContext, boolean printLocationInf @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getHeapBase(ucontext_t uContext) { GregsPointer regs = uContext.uc_mcontext_linux_aarch64().regs(); - return WordFactory.pointer(regs.read(27)); + return Word.pointer(regs.read(27)); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getThreadPointer(ucontext_t uContext) { GregsPointer regs = uContext.uc_mcontext_linux_aarch64().regs(); - return WordFactory.pointer(regs.read(28)); + return Word.pointer(regs.read(28)); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getSP(ucontext_t uContext) { mcontext_linux_aarch64_t sigcontext = uContext.uc_mcontext_linux_aarch64(); - return WordFactory.pointer(sigcontext.sp()); + return Word.pointer(sigcontext.sp()); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getIP(ucontext_t uContext) { mcontext_linux_aarch64_t sigcontext = uContext.uc_mcontext_linux_aarch64(); - return WordFactory.pointer(sigcontext.pc()); + return Word.pointer(sigcontext.pc()); } } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/amd64/AMD64LinuxUContextRegisterDumper.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/amd64/AMD64LinuxUContextRegisterDumper.java index 2704808f1d62..174c48cb3467 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/amd64/AMD64LinuxUContextRegisterDumper.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/amd64/AMD64LinuxUContextRegisterDumper.java @@ -26,10 +26,10 @@ import static com.oracle.svm.core.RegisterDumper.dumpReg; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.RegisterDumper; import com.oracle.svm.core.Uninterruptible; @@ -79,27 +79,27 @@ public void dumpRegisters(Log log, ucontext_t uContext, boolean printLocationInf @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getHeapBase(ucontext_t uContext) { GregsPointer gregs = uContext.uc_mcontext_linux_amd64_gregs(); - return WordFactory.pointer(gregs.read(GregEnumLinuxAMD64.REG_R14())); + return Word.pointer(gregs.read(GregEnumLinuxAMD64.REG_R14())); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getThreadPointer(ucontext_t uContext) { GregsPointer gregs = uContext.uc_mcontext_linux_amd64_gregs(); - return WordFactory.pointer(gregs.read(GregEnumLinuxAMD64.REG_R15())); + return Word.pointer(gregs.read(GregEnumLinuxAMD64.REG_R15())); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getSP(ucontext_t uContext) { GregsPointer gregs = uContext.uc_mcontext_linux_amd64_gregs(); - return WordFactory.pointer(gregs.read(GregEnumLinuxAMD64.REG_RSP())); + return Word.pointer(gregs.read(GregEnumLinuxAMD64.REG_RSP())); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getIP(ucontext_t uContext) { GregsPointer gregs = uContext.uc_mcontext_linux_amd64_gregs(); - return WordFactory.pointer(gregs.read(GregEnumLinuxAMD64.REG_RIP())); + return Word.pointer(gregs.read(GregEnumLinuxAMD64.REG_RIP())); } } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/attach/PosixAttachListenerThread.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/attach/PosixAttachListenerThread.java index 55b930a77782..4c9ffb20d965 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/attach/PosixAttachListenerThread.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/attach/PosixAttachListenerThread.java @@ -28,11 +28,11 @@ import java.nio.charset.StandardCharsets; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.attach.AttachListenerThread; import com.oracle.svm.core.posix.PosixUtils; @@ -40,11 +40,11 @@ import com.oracle.svm.core.util.BasedOnJDKFile; public final class PosixAttachListenerThread extends AttachListenerThread { - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/os/posix/attachListener_posix.cpp#L84") // + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+3/src/hotspot/os/aix/attachListener_aix.cpp#L82") // private static final String PROTOCOL_VERSION = "1"; - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/os/posix/attachListener_posix.cpp#L259") // + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+3/src/hotspot/os/aix/attachListener_aix.cpp#L269") // private static final int VERSION_SIZE = 8; - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/os/posix/attachListener_posix.cpp#L87") // + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+3/src/hotspot/os/aix/attachListener_aix.cpp#L85") // private static final int ATTACH_ERROR_BAD_VERSION = 101; /** @@ -61,7 +61,7 @@ public PosixAttachListenerThread(int listener) { } @Override - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/os/posix/attachListener_posix.cpp#L354-L411") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+3/src/hotspot/os/posix/attachListener_posix.cpp#L254-L311") protected PosixAttachOperation dequeue() { while (true) { int socket = AttachHelper.waitForRequest(listener); @@ -80,7 +80,7 @@ protected PosixAttachOperation dequeue() { } /** This method reads and processes a single request from the socket. */ - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/os/posix/attachListener_posix.cpp#L258-L347") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+3/src/hotspot/os/aix/attachListener_aix.cpp#L268-L359") private static PosixAttachOperation readRequest(int socket) { int strCount = 0; int[] stringEnds = new int[EXPECTED_STRING_COUNT]; @@ -145,10 +145,10 @@ private static String decodeString(Pointer buf, int[] stringEnds, int index) { int start = index == 0 ? 0 : stringEnds[index - 1] + 1; int length = stringEnds[index] - start; assert length >= 0; - return CTypeConversion.toJavaString((CCharPointer) buf.add(start), WordFactory.unsigned(length), StandardCharsets.UTF_8); + return CTypeConversion.toJavaString((CCharPointer) buf.add(start), Word.unsigned(length), StandardCharsets.UTF_8); } - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/os/posix/attachListener_posix.cpp#L436-L455") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+3/src/hotspot/os/posix/attachListener_posix.cpp#L321-L328") private static void complete(int socket, int code, String response) { /* Send the return code. */ byte[] returnCodeData = Integer.toString(code).getBytes(StandardCharsets.UTF_8); diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinGOTHeapSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinGOTHeapSupport.java index 489fea0db173..1cea26ef4858 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinGOTHeapSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinGOTHeapSupport.java @@ -31,11 +31,11 @@ import static com.oracle.svm.core.posix.headers.Mman.PROT_WRITE; import static com.oracle.svm.core.posix.headers.Mman.NoTransitions.mmap; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.type.CIntPointer; import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CGlobalData; @@ -55,7 +55,7 @@ public class DarwinGOTHeapSupport extends GOTHeapSupport { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) protected int initialize(WordPointer gotStartAddress) { int flags = MAP_ANON() | MAP_PRIVATE(); - Pointer gotMemory = mmap(WordFactory.nullPointer(), getPageAlignedGotSize(), PROT_READ() | PROT_WRITE(), flags, -1, 0); + Pointer gotMemory = mmap(Word.nullPointer(), getPageAlignedGotSize(), PROT_READ() | PROT_WRITE(), flags, -1, 0); if (gotMemory.isNull() || gotMemory.equal(MAP_FAILED())) { return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_FD_CREATE_FAILED; } @@ -92,7 +92,7 @@ public int mapGot(Pointer start) { * Map reserved address space for GOT to "global" GOT allocation, so that all isolates are * backed by the same table. */ - ret = DarwinVirtualMemory.vm_remap(taskSelf, gotStart, getPageAlignedGotSize(), WordFactory.nullPointer(), intFalse, + ret = DarwinVirtualMemory.vm_remap(taskSelf, gotStart, getPageAlignedGotSize(), Word.nullPointer(), intFalse, taskSelf, DARWIN_GOT_START_ADDRESS.get().read(), intFalse, currentProt, maxProt, DarwinVirtualMemory.VM_INHERIT_SHARE()); if (ret != 0) { return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_WRONG_MMAP; diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinPhysicalMemorySupportImpl.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinPhysicalMemorySupportImpl.java index efc0ef928943..7e68fb59d37c 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinPhysicalMemorySupportImpl.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinPhysicalMemorySupportImpl.java @@ -24,11 +24,11 @@ */ package com.oracle.svm.core.posix.darwin; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.c.type.CIntPointer; import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; @@ -51,7 +51,7 @@ public UnsignedWord size() { WordPointer physicalMemoryPointer = UnsafeStackValue.get(WordPointer.class); WordPointer physicalMemorySizePointer = UnsafeStackValue.get(WordPointer.class); physicalMemorySizePointer.write(SizeOf.unsigned(WordPointer.class)); - final int sysctlResult = Sysctl.sysctl(namePointer, 2, physicalMemoryPointer, physicalMemorySizePointer, WordFactory.nullPointer(), 0); + final int sysctlResult = Sysctl.sysctl(namePointer, 2, physicalMemoryPointer, physicalMemorySizePointer, Word.nullPointer(), 0); if (sysctlResult != 0) { Log.log().string("DarwinPhysicalMemory.PhysicalMemorySupportImpl.size(): sysctl() returns with errno: ").signed(LibC.errno()).newline(); throw VMError.shouldNotReachHere("DarwinPhysicalMemory.PhysicalMemorySupportImpl.size() failed."); diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinProcessPropertiesSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinProcessPropertiesSupport.java index 8e8da1cd3b98..1a476e124260 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinProcessPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinProcessPropertiesSupport.java @@ -24,11 +24,11 @@ */ package com.oracle.svm.core.posix.darwin; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CIntPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.nativeimage.impl.ProcessPropertiesSupport; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; @@ -45,7 +45,7 @@ public String getExecutableName() { /* Find out how long the executable path is. */ final CIntPointer sizePointer = UnsafeStackValue.get(CIntPointer.class); sizePointer.write(0); - if (DarwinDyld._NSGetExecutablePath(WordFactory.nullPointer(), sizePointer) != -1) { + if (DarwinDyld._NSGetExecutablePath(Word.nullPointer(), sizePointer) != -1) { VMError.shouldNotReachHere("DarwinProcessPropertiesSupport.getExecutableName: Executable path length is 0?"); } /* Allocate a correctly-sized buffer and ask again. */ diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinStackOverflowSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinStackOverflowSupport.java index 190ce2c5487f..8156c2999610 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinStackOverflowSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinStackOverflowSupport.java @@ -32,11 +32,11 @@ import static com.oracle.svm.core.posix.headers.darwin.DarwinVirtualMemory.mach_vm_region; import static com.oracle.svm.core.posix.headers.darwin.DarwinVirtualMemory.vm_region_basic_info_data_64_t; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.type.CIntPointer; import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; @@ -66,12 +66,12 @@ public boolean lookupStack(WordPointer stackBasePtr, WordPointer stackEndPtr) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static UnsignedWord vmComputeStackGuardSize(UnsignedWord stackend) { - UnsignedWord guardsize = WordFactory.zero(); + UnsignedWord guardsize = Word.zero(); WordPointer address = StackValue.get(WordPointer.class); address.write(stackend); WordPointer size = StackValue.get(WordPointer.class); - size.write(WordFactory.zero()); + size.write(Word.zero()); vm_region_basic_info_data_64_t info = StackValue.get(vm_region_basic_info_data_64_t.class); WordPointer task = mach_task_self(); diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinSystemPropertiesSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinSystemPropertiesSupport.java index 283f30947ae1..c1c93cde9435 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinSystemPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinSystemPropertiesSupport.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.posix.darwin; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.c.function.CLibrary; @@ -32,7 +33,6 @@ import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder; import org.graalvm.nativeimage.impl.RuntimeSystemPropertiesSupport; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; @@ -53,7 +53,7 @@ protected String javaIoTmpdirValue() { /* Darwin has a per-user temp dir */ int buflen = Limits.PATH_MAX(); CCharPointer tmpPath = UnsafeStackValue.get(buflen); - UnsignedWord pathSize = Unistd.confstr(Unistd._CS_DARWIN_USER_TEMP_DIR(), tmpPath, WordFactory.unsigned(buflen)); + UnsignedWord pathSize = Unistd.confstr(Unistd._CS_DARWIN_USER_TEMP_DIR(), tmpPath, Word.unsigned(buflen)); if (pathSize.aboveThan(0) && pathSize.belowOrEqual(buflen)) { return CTypeConversion.toJavaString(tmpPath); } else { diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/jvmstat/PosixPerfMemoryProvider.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/jvmstat/PosixPerfMemoryProvider.java index b05734d4e4fd..3522eb6b9d05 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/jvmstat/PosixPerfMemoryProvider.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/jvmstat/PosixPerfMemoryProvider.java @@ -40,6 +40,7 @@ import java.nio.ByteBuffer; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platform.LINUX; @@ -48,7 +49,6 @@ import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.VMInspectionOptions; @@ -97,7 +97,7 @@ class PosixPerfMemoryProvider implements PerfMemoryProvider { * null on failure. A return value of null will ultimately disable the shared memory feature. */ @Override - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+13/src/hotspot/os/posix/perfMemory_posix.cpp#L1015-L1082") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+3/src/hotspot/os/posix/perfMemory_posix.cpp#L1015-L1082") public ByteBuffer create() { assert backingFilePath == null; @@ -128,7 +128,7 @@ public ByteBuffer create() { return null; } - Pointer mapAddress = Mman.mmap(WordFactory.nullPointer(), WordFactory.unsigned(size), Mman.PROT_READ() | Mman.PROT_WRITE(), Mman.MAP_SHARED(), fd, 0); + Pointer mapAddress = Mman.mmap(Word.nullPointer(), Word.unsigned(size), Mman.PROT_READ() | Mman.PROT_WRITE(), Mman.MAP_SHARED(), fd, 0); int result = Unistd.NoTransitions.close(fd); assert result != -1; @@ -142,7 +142,7 @@ public ByteBuffer create() { backingFilePath = filePath; /* Clear the shared memory region. */ - LibC.memset(mapAddress, WordFactory.signed(0), WordFactory.unsigned(size)); + LibC.memset(mapAddress, Word.signed(0), Word.unsigned(size)); return DirectByteBufferUtil.allocate(mapAddress.rawValue(), size); } @@ -275,7 +275,7 @@ private static int createSharedMemFile(CCharPointer directoryPath, CCharPointer * memory accesses if we don't. */ RawFileOperationSupport fs = RawFileOperationSupport.nativeByteOrder(); - RawFileDescriptor rawFd = WordFactory.signed(fd); + RawFileDescriptor rawFd = Word.signed(fd); int pageSize = NumUtil.safeToInt(VirtualMemoryProvider.get().getGranularity().rawValue()); boolean success = true; @@ -506,7 +506,7 @@ private static int restartableUnlink(CCharPointer pathname) { private static int restartableFtruncate(int fd, int size) { int result; do { - result = Unistd.NoTransitions.ftruncate(fd, WordFactory.signed(size)); + result = Unistd.NoTransitions.ftruncate(fd, Word.signed(size)); } while (result == -1 && LibC.errno() == Errno.EINTR()); return result; diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/DumpLinuxOSInfo.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/DumpLinuxOSInfo.java index 4c8f39a4617d..7f7e9cd76071 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/DumpLinuxOSInfo.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/DumpLinuxOSInfo.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core.posix.linux; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateDiagnostics; import com.oracle.svm.core.SubstrateDiagnostics.DiagnosticThunkRegistry; @@ -84,7 +84,7 @@ private static void printFirstLine(Log log, CCharPointer filename) { try { int bufferSize = 64; CCharPointer buffer = StackValue.get(bufferSize); - long readBytes = fs.read(fd, (Pointer) buffer, WordFactory.unsigned(bufferSize)); + long readBytes = fs.read(fd, (Pointer) buffer, Word.unsigned(bufferSize)); int length = countLineBytes(buffer, NumUtil.safeToInt(readBytes)); log.string(buffer, length); } finally { diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxGOTHeapSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxGOTHeapSupport.java index cddb80ab1096..ae7a2c88cd60 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxGOTHeapSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxGOTHeapSupport.java @@ -31,6 +31,7 @@ import static com.oracle.svm.core.posix.headers.Mman.NoTransitions.shm_open; import static com.oracle.svm.core.posix.headers.Mman.NoTransitions.shm_unlink; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.c.type.CCharPointer; @@ -39,7 +40,6 @@ import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CGlobalData; @@ -58,7 +58,7 @@ public class LinuxGOTHeapSupport extends GOTHeapSupport { private static final String FILE_NAME_PREFIX = "/ni-got-"; private static final int FILE_NAME_PREFIX_LEN = FILE_NAME_PREFIX.length(); - private static final CGlobalData memoryViewFd = CGlobalDataFactory.createWord((WordBase) WordFactory.signed(-1)); + private static final CGlobalData memoryViewFd = CGlobalDataFactory.createWord((WordBase) Word.signed(-1)); private static final CGlobalData fileNamePrefix = CGlobalDataFactory.createCString(FILE_NAME_PREFIX); @Override @@ -74,7 +74,7 @@ protected int initialize(WordPointer gotStartAddress) { /* GOT shared memory file name format: fileNamePrefix-pid */ CCharPointer nameStr = StackValue.get(64 * SizeOf.get(CCharPointer.class)); - LibC.memcpy(nameStr, fileNamePrefix.get(), WordFactory.unsigned(FILE_NAME_PREFIX_LEN)); + LibC.memcpy(nameStr, fileNamePrefix.get(), Word.unsigned(FILE_NAME_PREFIX_LEN)); int iter = FILE_NAME_PREFIX_LEN + pidLen; nameStr.write(iter, (byte) '\0'); @@ -107,12 +107,12 @@ protected int initialize(WordPointer gotStartAddress) { UnsignedWord gotPageAlignedSize = getPageAlignedGotSize(); - if (Unistd.NoTransitions.ftruncate(fd, WordFactory.signed(gotPageAlignedSize.rawValue())) != 0) { + if (Unistd.NoTransitions.ftruncate(fd, Word.signed(gotPageAlignedSize.rawValue())) != 0) { Unistd.NoTransitions.close(fd); return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_FD_RESIZE_FAILED; } - Pointer gotMemory = mmap(WordFactory.nullPointer(), gotPageAlignedSize, PROT_READ() | PROT_WRITE(), MAP_SHARED(), fd, 0); + Pointer gotMemory = mmap(Word.nullPointer(), gotPageAlignedSize, PROT_READ() | PROT_WRITE(), MAP_SHARED(), fd, 0); if (gotMemory.isNull()) { Unistd.NoTransitions.close(fd); return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_FD_MAP_FAILED; @@ -123,7 +123,7 @@ protected int initialize(WordPointer gotStartAddress) { /* Keep the initial GOT mapping for writing. */ - memoryViewFd.get().write(WordFactory.signed(fd)); + memoryViewFd.get().write(Word.signed(fd)); gotStartAddress.write(gotMemory); return CEntryPointErrors.NO_ERROR; @@ -141,7 +141,7 @@ public int mapGot(Pointer start) { start, getPageAlignedGotSize(), memViewFd, - WordFactory.zero(), + Word.zero(), Access.READ); if (mappedAddress.isNull()) { diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxImageHeapProvider.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxImageHeapProvider.java index 88cb354d9fb5..eb21dc34deb3 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxImageHeapProvider.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxImageHeapProvider.java @@ -33,7 +33,6 @@ import static com.oracle.svm.core.Isolates.IMAGE_HEAP_WRITABLE_END; import static com.oracle.svm.core.Isolates.IMAGE_HEAP_WRITABLE_PATCHED_BEGIN; import static com.oracle.svm.core.Isolates.IMAGE_HEAP_WRITABLE_PATCHED_END; -import static com.oracle.svm.core.imagelayer.ImageLayerSection.SectionEntries.HEAP_ANY_RELOCATABLE_POINTER; import static com.oracle.svm.core.imagelayer.ImageLayerSection.SectionEntries.HEAP_BEGIN; import static com.oracle.svm.core.imagelayer.ImageLayerSection.SectionEntries.HEAP_END; import static com.oracle.svm.core.imagelayer.ImageLayerSection.SectionEntries.HEAP_RELOCATABLE_BEGIN; @@ -47,7 +46,7 @@ import static com.oracle.svm.core.util.PointerUtils.roundDown; import static com.oracle.svm.core.util.UnsignedUtils.isAMultiple; import static com.oracle.svm.core.util.UnsignedUtils.roundUp; -import static org.graalvm.word.WordFactory.signed; +import static jdk.graal.compiler.word.Word.signed; import java.util.concurrent.ThreadLocalRandom; @@ -60,7 +59,6 @@ import org.graalvm.word.PointerBase; import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; @@ -103,7 +101,7 @@ */ public class LinuxImageHeapProvider extends AbstractImageHeapProvider { /** Magic value to verify that a located image file matches our loaded image. */ - public static final CGlobalData MAGIC = CGlobalDataFactory.createWord(WordFactory. signed(ThreadLocalRandom.current().nextLong())); + public static final CGlobalData MAGIC = CGlobalDataFactory.createWord(Word. signed(ThreadLocalRandom.current().nextLong())); private static final CGlobalData PROC_SELF_MAPS = CGlobalDataFactory.createCString("/proc/self/maps"); @@ -133,7 +131,7 @@ private static UnsignedWord getLayeredImageHeapAddressSpaceSize() { } int imageHeapOffset = Heap.getHeap().getImageHeapOffsetInAddressSpace(); assert imageHeapOffset >= 0; - UnsignedWord size = WordFactory.unsigned(imageHeapOffset); + UnsignedWord size = Word.unsigned(imageHeapOffset); UnsignedWord granularity = VirtualMemoryProvider.get().getGranularity(); assert isAMultiple(size, granularity); @@ -188,7 +186,8 @@ protected int initializeLayeredImage(Pointer firstHeapStart, Pointer selfReserve Word heapEnd = currentSection.readWord(ImageLayerSection.getEntryOffset(HEAP_END)); Word heapRelocBegin = currentSection.readWord(ImageLayerSection.getEntryOffset(HEAP_RELOCATABLE_BEGIN)); Word heapRelocEnd = currentSection.readWord(ImageLayerSection.getEntryOffset(HEAP_RELOCATABLE_END)); - Word heapAnyRelocPointer = currentSection.readWord(ImageLayerSection.getEntryOffset(HEAP_ANY_RELOCATABLE_POINTER)); + /* This value is not used for layered images. */ + Word heapAnyRelocPointer = Word.nullPointer(); Word heapWritableBegin = currentSection.readWord(ImageLayerSection.getEntryOffset(HEAP_WRITEABLE_BEGIN)); Word heapWritableEnd = currentSection.readWord(ImageLayerSection.getEntryOffset(HEAP_WRITEABLE_END)); Word heapWritablePatchedBegin = currentSection.readWord(ImageLayerSection.getEntryOffset(HEAP_WRITEABLE_PATCHED_BEGIN)); @@ -217,10 +216,10 @@ protected int initializeLayeredImage(Pointer firstHeapStart, Pointer selfReserve @Override @Uninterruptible(reason = "Called during isolate initialization.") public int initialize(Pointer reservedAddressSpace, UnsignedWord reservedSize, WordPointer basePointer, WordPointer endPointer) { - Pointer selfReservedMemory = WordFactory.nullPointer(); + Pointer selfReservedMemory = Word.nullPointer(); UnsignedWord requiredSize = getTotalRequiredAddressSpaceSize(); if (reservedAddressSpace.isNull()) { - UnsignedWord alignment = WordFactory.unsigned(Heap.getHeap().getPreferredAddressSpaceAlignment()); + UnsignedWord alignment = Word.unsigned(Heap.getHeap().getPreferredAddressSpaceAlignment()); selfReservedMemory = VirtualMemoryProvider.get().reserve(requiredSize, alignment, false); if (selfReservedMemory.isNull()) { return CEntryPointErrors.RESERVE_ADDRESS_SPACE_FAILED; @@ -239,7 +238,7 @@ public int initialize(Pointer reservedAddressSpace, UnsignedWord reservedSize, W heapBase = selfReservedHeapBase; } else { heapBase = reservedAddressSpace.add(preHeapRequiredBytes); - selfReservedHeapBase = WordFactory.nullPointer(); + selfReservedHeapBase = Word.nullPointer(); } remainingSize = remainingSize.subtract(preHeapRequiredBytes); @@ -285,7 +284,7 @@ private static int initializeImageHeap(Pointer imageHeap, UnsignedWord reservedS Word heapWritablePatchedEndSym, Word heapWritableSym, Word heapWritableEndSym) { assert heapBeginSym.belowOrEqual(heapWritableSym) && heapWritableSym.belowOrEqual(heapWritableEndSym) && heapWritableEndSym.belowOrEqual(heapEndSym); assert heapBeginSym.belowOrEqual(heapRelocsSym) && heapRelocsSym.belowOrEqual(heapRelocsEndSym) && heapRelocsEndSym.belowOrEqual(heapEndSym); - assert heapRelocsSym.belowOrEqual(heapAnyRelocPointer) && heapAnyRelocPointer.belowThan(heapRelocsEndSym); + assert ImageLayerBuildingSupport.buildingImageLayer() || heapRelocsSym.belowOrEqual(heapAnyRelocPointer) && heapAnyRelocPointer.belowThan(heapRelocsEndSym); assert heapRelocsSym.belowOrEqual(heapRelocsEndSym) && heapRelocsEndSym.belowOrEqual(heapWritablePatchedSym) && heapWritablePatchedSym.belowOrEqual(heapWritableEndSym); SignedWord fd = cachedFd.read(); @@ -340,7 +339,7 @@ private static int initializeImageHeap(Pointer imageHeap, UnsignedWord reservedS } int result = copyRelocations(imageHeap, pageSize, heapBeginSym, heapRelocsSym, heapAnyRelocPointer, heapRelocsEndSym, heapWritablePatchedSym, heapWritablePatchedEndSym, - WordFactory.nullPointer()); + Word.nullPointer()); if (result != CEntryPointErrors.NO_ERROR) { return result; } @@ -395,7 +394,7 @@ private static int initializeImageHeapWithMremap(Pointer imageHeap, UnsignedWord private static Pointer getCachedImageHeapRelocations(Pointer cachedImageHeapRelocationsPtr, UnsignedWord pageSize, Word heapRelocsSym, Word heapWritablePatchedEndSym) { Pointer imageHeapRelocations = cachedImageHeapRelocationsPtr.readWord(0, LocationIdentity.ANY_LOCATION); if (imageHeapRelocations.isNull() || imageHeapRelocations.equal(COPY_RELOCATIONS_IN_PROGRESS)) { - if (!cachedImageHeapRelocationsPtr.logicCompareAndSwapWord(0, WordFactory.nullPointer(), COPY_RELOCATIONS_IN_PROGRESS, LocationIdentity.ANY_LOCATION)) { + if (!cachedImageHeapRelocationsPtr.logicCompareAndSwapWord(0, Word.nullPointer(), COPY_RELOCATIONS_IN_PROGRESS, LocationIdentity.ANY_LOCATION)) { /* Wait for other thread to initialize heap relocations. */ while ((imageHeapRelocations = cachedImageHeapRelocationsPtr.readWordVolatile(0, LocationIdentity.ANY_LOCATION)).equal(COPY_RELOCATIONS_IN_PROGRESS)) { PauseNode.pause(); @@ -409,7 +408,7 @@ private static Pointer getCachedImageHeapRelocations(Pointer cachedImageHeapRelo Pointer linkedRelocsBoundary = roundDown(heapRelocsSym, pageSize); UnsignedWord heapRelocsLength = roundUp(heapWritablePatchedEndSym.subtract(linkedRelocsBoundary), pageSize); int mremapFlags = LinuxLibCHelper.MREMAP_MAYMOVE() | LinuxLibCHelper.MREMAP_DONTUNMAP(); - imageHeapRelocations = LinuxLibCHelper.NoTransitions.mremapP(linkedRelocsBoundary, heapRelocsLength, heapRelocsLength, mremapFlags, WordFactory.nullPointer()); + imageHeapRelocations = LinuxLibCHelper.NoTransitions.mremapP(linkedRelocsBoundary, heapRelocsLength, heapRelocsLength, mremapFlags, Word.nullPointer()); if (imageHeapRelocations.equal(-1)) { if (LibC.errno() == Errno.EINVAL()) { @@ -420,13 +419,13 @@ private static Pointer getCachedImageHeapRelocations(Pointer cachedImageHeapRelo * https://github.com/torvalds/linux/commit/ * a4609387859f0281951f5e476d9f76d7fb9ab321 */ - imageHeapRelocations = WordFactory.pointer(-CEntryPointErrors.MREMAP_NOT_SUPPORTED); + imageHeapRelocations = Word.pointer(-CEntryPointErrors.MREMAP_NOT_SUPPORTED); } else { - imageHeapRelocations = WordFactory.pointer(-CEntryPointErrors.MAP_HEAP_FAILED); + imageHeapRelocations = Word.pointer(-CEntryPointErrors.MAP_HEAP_FAILED); } } else { if (VirtualMemoryProvider.get().protect(imageHeapRelocations, heapRelocsLength, Access.READ) != 0) { - imageHeapRelocations = WordFactory.pointer(-CEntryPointErrors.PROTECT_HEAP_FAILED); + imageHeapRelocations = Word.pointer(-CEntryPointErrors.PROTECT_HEAP_FAILED); } } @@ -443,16 +442,28 @@ private static int copyRelocations(Pointer imageHeap, UnsignedWord pageSize, Wor Word heapWritablePatchedSym, Word heapWritablePatchedEndSym, Pointer cachedRelocsBoundary) { Pointer linkedRelocsBoundary = roundDown(heapRelocsSym, pageSize); Pointer sourceRelocsBoundary = cachedRelocsBoundary.isNonNull() ? cachedRelocsBoundary : linkedRelocsBoundary; - Pointer linkedCopyStart = WordFactory.nullPointer(); + Pointer linkedCopyStart = Word.nullPointer(); if (heapRelocsEndSym.subtract(heapRelocsSym).isNonNull()) { - /* - * Use a representative pointer to determine whether it is necessary to copy over the - * relocations from the original image, or if it has already been performed during - * build-link time. - */ - ComparableWord relocatedValue = sourceRelocsBoundary.readWord(heapAnyRelocPointer.subtract(linkedRelocsBoundary)); - ComparableWord mappedValue = imageHeap.readWord(heapAnyRelocPointer.subtract(heapBeginSym)); - if (relocatedValue.notEqual(mappedValue)) { + boolean copyRequired; + if (ImageLayerBuildingSupport.buildingImageLayer()) { + assert heapAnyRelocPointer.isNull(); + /* + * The relocatable section with layered images will normally contain relocations + * referring to both the same and different layers. Hence, it is not possible to use + * a representative pointer to determine whether copying is necessary. + */ + copyRequired = true; + } else { + /* + * Use a representative pointer to determine whether it is necessary to copy over + * the relocations from the original image, or if it has already been performed + * during build-link time. + */ + ComparableWord relocatedValue = sourceRelocsBoundary.readWord(heapAnyRelocPointer.subtract(linkedRelocsBoundary)); + ComparableWord mappedValue = imageHeap.readWord(heapAnyRelocPointer.subtract(heapBeginSym)); + copyRequired = relocatedValue.notEqual(mappedValue); + } + if (copyRequired) { linkedCopyStart = heapRelocsSym; } } @@ -465,14 +476,17 @@ private static int copyRelocations(Pointer imageHeap, UnsignedWord pageSize, Wor } if (linkedCopyStart.isNonNull()) { /* - * A portion of the image heap has relocations either resolved by the dynamic linker or - * (for layered images) manually during our runtime initialization code. To preserve the - * relocations, we must copy this code directly from the original image heap and not - * reload it from disk. + * A portion of the image heap needs to be manually copied over from the original image. + * This is needed whenever: + * + * 1. Relocations resolved by the dynamic linker are present. + * + * 2. (For layered images only): Heap relative relocations are present (i.e. the + * heapWritablePatched section is non-zero). * - * We need to round to page boundaries, so we may copy some extra data which could be - * copy-on-write. Also, we must first remap the pages to avoid loading them from disk - * (only to then overwrite them). + * To preserve this information, we must copy over this data from the original image + * heap and not reload it from disk. Note this copying must be performed at a page + * granularity, and hence may copy some extra data which could be copy-on-write. */ Pointer linkedCopyStartBoundary = roundDown(linkedCopyStart, pageSize); UnsignedWord copyAlignedSize = roundUp(heapWritablePatchedEndSym.subtract(linkedCopyStartBoundary), pageSize); diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxPhysicalMemorySupportImpl.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxPhysicalMemorySupportImpl.java index 9be180c19b3d..b776fa262c78 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxPhysicalMemorySupportImpl.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxPhysicalMemorySupportImpl.java @@ -31,9 +31,9 @@ import java.util.ArrayList; import java.util.List; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.container.Container; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; @@ -54,7 +54,7 @@ public UnsignedWord size() { if (numberOfPhysicalMemoryPages == -1 || sizeOfAPhysicalMemoryPage == -1) { throw VMError.shouldNotReachHere("Physical memory size (number of pages or page size) not available"); } - return WordFactory.unsigned(numberOfPhysicalMemoryPages).multiply(WordFactory.unsigned(sizeOfAPhysicalMemoryPage)); + return Word.unsigned(numberOfPhysicalMemoryPages).multiply(Word.unsigned(sizeOfAPhysicalMemoryPage)); } @Override diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxStackOverflowSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxStackOverflowSupport.java index 7c574bc63e73..22793fffcc77 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxStackOverflowSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxStackOverflowSupport.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core.posix.linux; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; @@ -42,8 +42,8 @@ final class LinuxStackOverflowSupport implements StackOverflowCheck.PlatformSupp public boolean lookupStack(WordPointer stackBasePtr, WordPointer stackEndPtr) { boolean result = lookupStack0(stackBasePtr, stackEndPtr); if (!result) { - stackBasePtr.write(WordFactory.zero()); - stackEndPtr.write(WordFactory.zero()); + stackBasePtr.write(Word.zero()); + stackEndPtr.write(Word.zero()); } return result; } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxSubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxSubstrateSigprofHandler.java index 50a2740c435a..792b53ec5766 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxSubstrateSigprofHandler.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxSubstrateSigprofHandler.java @@ -27,6 +27,7 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; @@ -35,7 +36,6 @@ import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; @@ -89,7 +89,7 @@ protected void install0(IsolateThread thread) { sigevent.sigev_notify(Signal.SIGEV_SIGNAL()); sigevent.sigev_signo(Signal.SignalEnum.SIGPROF.getCValue()); WordPointer timerPointer = StackValue.get(WordPointer.class); - timerPointer.write(WordFactory.zero()); + timerPointer.write(Word.zero()); int status = LinuxTime.NoTransitions.timer_create(LinuxTime.CLOCK_MONOTONIC(), sigevent, timerPointer); PosixUtils.checkStatusIs0(status, "timer_create(clockid, sevp, timerid): wrong arguments."); @@ -118,31 +118,31 @@ private void updateInterval(IsolateThread thread) { newTimerSpec.it_interval().set_tv_sec(ns / TimeUtils.nanosPerSecond); newTimerSpec.it_interval().set_tv_nsec(ns % TimeUtils.nanosPerSecond); - int status = LinuxTime.NoTransitions.timer_settime(getSamplerTimerId(thread), 0, newTimerSpec, WordFactory.nullPointer()); + int status = LinuxTime.NoTransitions.timer_settime(getSamplerTimerId(thread), 0, newTimerSpec, Word.nullPointer()); PosixUtils.checkStatusIs0(status, "timer_settime(timerid, flags, newTimerSpec, oldValue): wrong arguments."); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private static boolean hasSamplerTimerId(IsolateThread thread) { assert CurrentIsolate.getCurrentThread() == thread || VMOperation.isInProgressAtSafepoint(); - return samplerTimerId.get(thread).and(WordFactory.unsigned(MARKER)).notEqual(0); + return samplerTimerId.get(thread).and(Word.unsigned(MARKER)).notEqual(0); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private static UnsignedWord getSamplerTimerId(IsolateThread thread) { assert hasSamplerTimerId(thread); - return samplerTimerId.get(thread).and(WordFactory.unsigned(~MARKER)); + return samplerTimerId.get(thread).and(Word.unsigned(~MARKER)); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private static void setSamplerTimerId(IsolateThread thread, UnsignedWord timerId) { - assert !hasSamplerTimerId(thread) && timerId.and(WordFactory.unsigned(MARKER)).equal(0); - samplerTimerId.set(thread, timerId.or(WordFactory.unsigned(MARKER))); + assert !hasSamplerTimerId(thread) && timerId.and(Word.unsigned(MARKER)).equal(0); + samplerTimerId.set(thread, timerId.or(Word.unsigned(MARKER))); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private static void clearSamplerTimerId(IsolateThread thread) { assert hasSamplerTimerId(thread); - samplerTimerId.set(thread, WordFactory.zero()); + samplerTimerId.set(thread, Word.zero()); } } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxVMLockSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxVMLockSupport.java index c7acba7cd6dc..15ca7eb2eef8 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxVMLockSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxVMLockSupport.java @@ -24,9 +24,9 @@ */ package com.oracle.svm.core.posix.linux; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CIsolateData; @@ -64,7 +64,7 @@ private Semaphore.sem_t getStructPointer() { @Override @Uninterruptible(reason = "Too early for safepoints.") public int initialize() { - return Semaphore.NoTransitions.sem_init(getStructPointer(), WordFactory.signed(0), WordFactory.unsigned(0)); + return Semaphore.NoTransitions.sem_init(getStructPointer(), Word.signed(0), Word.unsigned(0)); } @Override diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/ProcFSSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/ProcFSSupport.java index f9e68a418260..e499a92fb5b8 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/ProcFSSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/ProcFSSupport.java @@ -24,11 +24,11 @@ */ package com.oracle.svm.core.posix.linux; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.posix.PosixUtils; @@ -72,9 +72,9 @@ static boolean findMapping(int fd, CCharPointer buffer, int bufferLen, UnsignedW int state = ST_ADDR_START; int b; - UnsignedWord start = WordFactory.zero(); - UnsignedWord end = WordFactory.zero(); - UnsignedWord fileOffset = WordFactory.zero(); + UnsignedWord start = Word.zero(); + UnsignedWord end = Word.zero(); + UnsignedWord fileOffset = Word.zero(); OUT: for (;;) { while (position == endOffset) { // fill buffer int readBytes = PosixUtils.readUninterruptibly(fd, (Pointer) buffer, bufferLen, readOffset); @@ -112,7 +112,7 @@ static boolean findMapping(int fd, CCharPointer buffer, int bufferLen, UnsignedW } case ST_PERMS: { if (b == ' ') { - fileOffset = WordFactory.zero(); + fileOffset = Word.zero(); state = ST_OFFSET; } break; // ignore anything else @@ -169,8 +169,8 @@ static boolean findMapping(int fd, CCharPointer buffer, int bufferLen, UnsignedW } case ST_SKIP: { if (b == '\n') { - start = WordFactory.zero(); - end = WordFactory.zero(); + start = Word.zero(); + end = Word.zero(); state = ST_ADDR_START; } break; diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadConditionUtils.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadConditionUtils.java index 154f2b691505..04c651442edd 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadConditionUtils.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadConditionUtils.java @@ -24,9 +24,9 @@ */ package com.oracle.svm.core.posix.pthread; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.StackValue; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.posix.PosixUtils; @@ -78,7 +78,7 @@ static boolean useMonotonicClockForRelativeWait() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static int initConditionWithAbsoluteTime(pthread_cond_t cond) { - return Pthread.pthread_cond_init(cond, WordFactory.nullPointer()); + return Pthread.pthread_cond_init(cond, Word.nullPointer()); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadVMLockSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadVMLockSupport.java index deff0643b8fd..e2a48f6ef6ef 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadVMLockSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadVMLockSupport.java @@ -26,11 +26,11 @@ import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.LogHandler; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CIsolateData; @@ -102,7 +102,7 @@ Pthread.pthread_mutex_t getStructPointer() { @Override @Uninterruptible(reason = "Too early for safepoints.") public int initialize() { - return Pthread.pthread_mutex_init(getStructPointer(), WordFactory.nullPointer()); + return Pthread.pthread_mutex_init(getStructPointer(), Word.nullPointer()); } @Override diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/riscv64/RISCV64LinuxUContextRegisterDumperFeature.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/riscv64/RISCV64LinuxUContextRegisterDumperFeature.java index 5d363826b2cf..0286633e3eac 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/riscv64/RISCV64LinuxUContextRegisterDumperFeature.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/riscv64/RISCV64LinuxUContextRegisterDumperFeature.java @@ -26,10 +26,10 @@ import static com.oracle.svm.core.RegisterDumper.dumpReg; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.RegisterDumper; import com.oracle.svm.core.Uninterruptible; @@ -95,21 +95,21 @@ public void dumpRegisters(Log log, Signal.ucontext_t uContext, boolean printLoca @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getHeapBase(ucontext_t uContext) { GregsPointer regs = uContext.uc_mcontext_linux_riscv64().gregs(); - return WordFactory.pointer(regs.read(27)); + return Word.pointer(regs.read(27)); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getThreadPointer(ucontext_t uContext) { GregsPointer regs = uContext.uc_mcontext_linux_riscv64().gregs(); - return WordFactory.pointer(regs.read(23)); + return Word.pointer(regs.read(23)); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getSP(ucontext_t uContext) { GregsPointer regs = uContext.uc_mcontext_linux_riscv64().gregs(); - return WordFactory.pointer(regs.read(2)); + return Word.pointer(regs.read(2)); } @Override @@ -117,6 +117,6 @@ public PointerBase getSP(ucontext_t uContext) { public PointerBase getIP(ucontext_t uContext) { // gregs[0] holds the program counter. GregsPointer regs = uContext.uc_mcontext_linux_riscv64().gregs(); - return WordFactory.pointer(regs.read(0)); + return Word.pointer(regs.read(0)); } } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixPlatformThreads.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixPlatformThreads.java index 56c0f1767285..80dca1fbd70f 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixPlatformThreads.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixPlatformThreads.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.posix.thread; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platform.HOSTED_ONLY; @@ -39,7 +40,6 @@ import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.Uninterruptible; @@ -97,13 +97,13 @@ protected boolean doStartThread(Thread thread, long stackSize) { return false; } - UnsignedWord threadStackSize = WordFactory.unsigned(stackSize); + UnsignedWord threadStackSize = Word.unsigned(stackSize); /* If there is a chosen stack size, use it as the stack size. */ - if (threadStackSize.notEqual(WordFactory.zero())) { + if (threadStackSize.notEqual(Word.zero())) { /* Make sure the chosen stack size is large enough. */ threadStackSize = UnsignedUtils.max(threadStackSize, Pthread.PTHREAD_STACK_MIN()); /* Make sure the chosen stack size is a multiple of the system page size. */ - threadStackSize = UnsignedUtils.roundUp(threadStackSize, WordFactory.unsigned(Unistd.getpagesize())); + threadStackSize = UnsignedUtils.roundUp(threadStackSize, Word.unsigned(Unistd.getpagesize())); if (Pthread.pthread_attr_setstacksize(attributes, threadStackSize) != 0) { return false; @@ -213,25 +213,25 @@ public OSThreadHandle startThreadUnmanaged(CFunctionPointer threadRoutine, Point pthread_attr_t attributes = StackValue.get(pthread_attr_t.class); int status = Pthread.pthread_attr_init_no_transition(attributes); if (status != 0) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } try { status = Pthread.pthread_attr_setdetachstate_no_transition(attributes, Pthread.PTHREAD_CREATE_JOINABLE()); if (status != 0) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } - UnsignedWord threadStackSize = WordFactory.unsigned(stackSize); + UnsignedWord threadStackSize = Word.unsigned(stackSize); /* If there is a chosen stack size, use it as the stack size. */ - if (threadStackSize.notEqual(WordFactory.zero())) { + if (threadStackSize.notEqual(Word.zero())) { /* Make sure the chosen stack size is large enough. */ threadStackSize = UnsignedUtils.max(threadStackSize, Pthread.PTHREAD_STACK_MIN()); /* Make sure the chosen stack size is a multiple of the system page size. */ - threadStackSize = UnsignedUtils.roundUp(threadStackSize, WordFactory.unsigned(Unistd.NoTransitions.getpagesize())); + threadStackSize = UnsignedUtils.roundUp(threadStackSize, Word.unsigned(Unistd.NoTransitions.getpagesize())); status = Pthread.pthread_attr_setstacksize_no_transition(attributes, threadStackSize); if (status != 0) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } } @@ -239,7 +239,7 @@ public OSThreadHandle startThreadUnmanaged(CFunctionPointer threadRoutine, Point status = Pthread.pthread_create_no_transition(newThread, attributes, threadRoutine, userData); if (status != 0) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } return newThread.read(); @@ -259,7 +259,7 @@ public boolean joinThreadUnmanaged(OSThreadHandle threadHandle, WordPointer thre @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public ThreadLocalKey createUnmanagedThreadLocal() { Pthread.pthread_key_tPointer key = StackValue.get(Pthread.pthread_key_tPointer.class); - PosixUtils.checkStatusIs0(Pthread.pthread_key_create(key, WordFactory.nullPointer()), "pthread_key_create(key, keyDestructor): failed."); + PosixUtils.checkStatusIs0(Pthread.pthread_key_create(key, Word.nullPointer()), "pthread_key_create(key, keyDestructor): failed."); return (ThreadLocalKey) key.read(); } @@ -326,7 +326,7 @@ final class PosixParker extends Parker { relativeCond = (pthread_cond_t) memory.add(mutexSize); absoluteCond = (pthread_cond_t) memory.add(mutexSize).add(condSize); - final Pthread.pthread_mutexattr_t mutexAttr = WordFactory.nullPointer(); + final Pthread.pthread_mutexattr_t mutexAttr = Word.nullPointer(); PosixUtils.checkStatusIs0(Pthread.pthread_mutex_init(mutex, mutexAttr), "mutex initialization"); PosixUtils.checkStatusIs0(PthreadConditionUtils.initConditionWithRelativeTime(relativeCond), "relative-time condition variable initialization"); PosixUtils.checkStatusIs0(PthreadConditionUtils.initConditionWithAbsoluteTime(absoluteCond), "absolute-time condition variable initialization"); @@ -378,7 +378,7 @@ private void park0(boolean isAbsolute, long time) { } assert status == 0 || status == Errno.ETIMEDOUT(); } finally { - currentCond = WordFactory.nullPointer(); + currentCond = Word.nullPointer(); } } event = 0; @@ -429,16 +429,16 @@ protected void release() { /* The conditions and the mutex are allocated with a single malloc. */ int status = Pthread.pthread_cond_destroy(relativeCond); assert status == 0; - relativeCond = WordFactory.nullPointer(); + relativeCond = Word.nullPointer(); status = Pthread.pthread_cond_destroy(absoluteCond); assert status == 0; - absoluteCond = WordFactory.nullPointer(); + absoluteCond = Word.nullPointer(); status = Pthread.pthread_mutex_destroy(mutex); assert status == 0; NativeMemory.free(mutex); - mutex = WordFactory.nullPointer(); + mutex = Word.nullPointer(); } } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixVMThreads.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixVMThreads.java index a32408e9283c..6dd8b554f850 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixVMThreads.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixVMThreads.java @@ -33,7 +33,6 @@ import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.word.ComparableWord; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CGlobalData; @@ -51,6 +50,8 @@ import com.oracle.svm.core.util.TimeUtils; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.word.Word; + @AutomaticallyRegisteredImageSingleton(VMThreads.class) public final class PosixVMThreads extends VMThreads { @@ -70,11 +71,11 @@ public OSThreadHandle getCurrentOSThreadHandle() { protected OSThreadId getCurrentOSThreadId() { if (Platform.includedIn(Platform.DARWIN.class)) { Pthread.pthread_t pthread = Pthread.pthread_self(); - return WordFactory.unsigned(DarwinPthread.pthread_mach_thread_np(pthread)); + return Word.unsigned(DarwinPthread.pthread_mach_thread_np(pthread)); } else if (Platform.includedIn(Platform.LINUX.class)) { int result = LinuxLibCHelper.getThreadId(); VMError.guarantee(result != -1, "SYS_gettid failed"); - return WordFactory.signed(result); + return Word.signed(result); } else { throw VMError.unsupportedFeature("PosixVMThreads.getCurrentOSThreadId() on unexpected OS: " + ImageSingletons.lookup(Platform.class).getOS()); } @@ -84,7 +85,7 @@ protected OSThreadId getCurrentOSThreadId() { @Override protected void joinNoTransition(OSThreadHandle osThreadHandle) { Pthread.pthread_t pthread = (Pthread.pthread_t) osThreadHandle; - PosixUtils.checkStatusIs0(Pthread.pthread_join_no_transition(pthread, WordFactory.nullPointer()), "Pthread.joinNoTransition"); + PosixUtils.checkStatusIs0(Pthread.pthread_join_no_transition(pthread, Word.nullPointer()), "Pthread.joinNoTransition"); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -93,7 +94,7 @@ public void nativeSleep(int milliseconds) { timespec ts = StackValue.get(timespec.class); ts.set_tv_sec(milliseconds / TimeUtils.millisPerSecond); ts.set_tv_nsec((milliseconds % TimeUtils.millisPerSecond) * TimeUtils.nanosPerMilli); - Time.NoTransitions.nanosleep(ts, WordFactory.nullPointer()); + Time.NoTransitions.nanosleep(ts, Word.nullPointer()); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -118,7 +119,7 @@ interface FILE extends PointerBase { private static native int fprintfSD(FILE stream, CCharPointer format, CCharPointer arg0, int arg1); private static final CGlobalData FAIL_FATALLY_FDOPEN_MODE = CGlobalDataFactory.createCString("w"); - private static final CGlobalData FAIL_FATALLY_MESSAGE_FORMAT = CGlobalDataFactory.createCString("Fatal error: %s (code %d)\n"); + private static final CGlobalData FAIL_FATALLY_MESSAGE_FORMAT = CGlobalDataFactory.createCString("Fatal error: %s (code %d)" + System.lineSeparator()); @Uninterruptible(reason = "Thread state not set up.") @Override diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsGOTHeapSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsGOTHeapSupport.java index 85081e020f09..6af693468dec 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsGOTHeapSupport.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsGOTHeapSupport.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core.windows; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CGlobalData; @@ -53,11 +53,11 @@ protected int initialize(WordPointer gotStartAddress) { HANDLE gotMappingHandle = MemoryAPI.CreateFileMappingW( WinBase.INVALID_HANDLE_VALUE(), // in-memory - WordFactory.nullPointer(), + Word.nullPointer(), MemoryAPI.PAGE_READWRITE(), 0, UnsignedUtils.safeToInt(alignedGotSize), - WordFactory.nullPointer() // anonymous + Word.nullPointer() // anonymous ); if (gotMappingHandle.isNull()) { @@ -69,7 +69,7 @@ protected int initialize(WordPointer gotStartAddress) { MemoryAPI.FILE_MAP_READ() | MemoryAPI.FILE_MAP_WRITE(), 0, 0, - WordFactory.zero() // map it whole + Word.zero() // map it whole ); if (gotMappedAddress.isNull()) { @@ -100,7 +100,7 @@ public int mapGot(Pointer address) { address, getPageAlignedGotSize(), gotMappingHandle, - WordFactory.zero(), + Word.zero(), Access.READ); if (mappedAddress.isNull()) { diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsImageHeapProvider.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsImageHeapProvider.java index 5b06c4420457..b240f3b5e874 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsImageHeapProvider.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsImageHeapProvider.java @@ -29,6 +29,7 @@ import static com.oracle.svm.core.Isolates.IMAGE_HEAP_RELOCATABLE_END; import static org.graalvm.nativeimage.c.function.CFunction.Transition.NO_TRANSITION; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.function.CFunctionPointer; import org.graalvm.nativeimage.c.function.InvokeCFunctionPointer; @@ -38,7 +39,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CGlobalData; @@ -126,19 +126,19 @@ private static HANDLE createImageHeapFileMapping() { WCharPointer filePath = StackValue.get(WinBase.MAX_PATH, WCharPointer.class); int length = LibLoaderAPI.GetModuleFileNameW((HMODULE) IMAGE_BASE.get(), filePath, WinBase.MAX_PATH); if (length == 0 || length == WinBase.MAX_PATH) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } /* Open the file for mapping. */ HANDLE fileHandle = FileAPI.CreateFileW(filePath, FileAPI.GENERIC_READ(), FileAPI.FILE_SHARE_READ() | FileAPI.FILE_SHARE_DELETE(), - WordFactory.nullPointer(), FileAPI.OPEN_EXISTING(), 0, WordFactory.nullPointer()); + Word.nullPointer(), FileAPI.OPEN_EXISTING(), 0, Word.nullPointer()); if (fileHandle.equal(WinBase.INVALID_HANDLE_VALUE())) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } /* Create the mapping and close the file. */ - HANDLE fileMapping = MemoryAPI.CreateFileMappingW(fileHandle, WordFactory.nullPointer(), MemoryAPI.PAGE_READONLY(), - 0, 0, WordFactory.nullPointer()); + HANDLE fileMapping = MemoryAPI.CreateFileMappingW(fileHandle, Word.nullPointer(), MemoryAPI.PAGE_READONLY(), + 0, 0, Word.nullPointer()); WinBase.CloseHandle(fileHandle); return fileMapping; } @@ -202,7 +202,7 @@ private interface RtlImageNtHeader extends CFunctionPointer { @Uninterruptible(reason = "Called during isolate initialization.") private static UnsignedWord invokeRtlAddressInSectionTable(PointerBase ntHeader, int rva) { RtlAddressInSectionTable rtlAddressInSectionTable = WindowsUtils.getFunctionPointer(NTDLL_DLL.get(), RTL_ADDRESS_IN_SECTION_TABLE.get(), true); - UnsignedWord offset = (UnsignedWord) rtlAddressInSectionTable.invoke(ntHeader, WordFactory.nullPointer(), rva); + UnsignedWord offset = (UnsignedWord) rtlAddressInSectionTable.invoke(ntHeader, Word.nullPointer(), rva); if (offset.equal(0)) { CEntryPointActions.failFatally(ERROR_BAD_EXE_FORMAT, RTL_ADDRESS_IN_SECTION_TABLE.get()); } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsJavaLangSubstitutions.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsJavaLangSubstitutions.java index ca8801c88e32..a79a2664a7ba 100755 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsJavaLangSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsJavaLangSubstitutions.java @@ -27,9 +27,9 @@ import java.io.Console; import java.util.Objects; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.annotate.Alias; @@ -59,7 +59,7 @@ public static String mapLibraryName(String libname) { @Substitute @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static long currentTimeMillis() { - return Jvm.JVM_CurrentTimeMillis(WordFactory.nullPointer(), WordFactory.nullPointer()); + return Jvm.JVM_CurrentTimeMillis(Word.nullPointer(), Word.nullPointer()); } } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsNativeLibrarySupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsNativeLibrarySupport.java index 173d1f30d184..3ff52c85a68d 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsNativeLibrarySupport.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsNativeLibrarySupport.java @@ -26,13 +26,13 @@ import java.io.FileDescriptor; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Isolates; import com.oracle.svm.core.annotate.Alias; @@ -110,7 +110,7 @@ public WindowsNativeLibrary createLibrary(String canonical, boolean builtIn) { @Override public PointerBase findBuiltinSymbol(String name) { try (CCharPointerHolder symbol = CTypeConversion.toCString(name)) { - HMODULE builtinHandle = LibLoaderAPI.GetModuleHandleA(WordFactory.nullPointer()); + HMODULE builtinHandle = LibLoaderAPI.GetModuleHandleA(Word.nullPointer()); return LibLoaderAPI.GetProcAddress(builtinHandle, symbol.get()); } } @@ -152,7 +152,7 @@ public boolean unload() { } assert dlhandle.isNonNull(); if (LibLoaderAPI.FreeLibrary(dlhandle) != 0) { - dlhandle = WordFactory.nullPointer(); + dlhandle = Word.nullPointer(); return true; } else { return false; diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPhysicalMemorySupportImpl.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPhysicalMemorySupportImpl.java index 5c3c65a63d24..28e13fbf9a09 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPhysicalMemorySupportImpl.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPhysicalMemorySupportImpl.java @@ -26,9 +26,9 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; @@ -45,6 +45,6 @@ public UnsignedWord size() { SysinfoAPI.MEMORYSTATUSEX memStatusEx = UnsafeStackValue.get(SysinfoAPI.MEMORYSTATUSEX.class); memStatusEx.set_dwLength(SizeOf.get(SysinfoAPI.MEMORYSTATUSEX.class)); SysinfoAPI.GlobalMemoryStatusEx(memStatusEx); - return WordFactory.unsigned(memStatusEx.ullTotalPhys()); + return Word.unsigned(memStatusEx.ullTotalPhys()); } } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformThreads.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformThreads.java index fdeffec10ac7..b619fafbd9f7 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformThreads.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformThreads.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.windows; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platform.HOSTED_ONLY; import org.graalvm.nativeimage.Platforms; @@ -34,7 +35,6 @@ import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.PointerBase; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; @@ -84,7 +84,7 @@ protected boolean doStartThread(Thread thread, long stackSize) { private boolean doStartThread0(Thread thread, int threadStackSize, int initFlag) { ThreadStartData startData = prepareStart(thread, SizeOf.get(ThreadStartData.class)); try { - WinBase.HANDLE osThreadHandle = Process._beginthreadex(WordFactory.nullPointer(), threadStackSize, threadStartRoutine.getFunctionPointer(), startData, initFlag, WordFactory.nullPointer()); + WinBase.HANDLE osThreadHandle = Process._beginthreadex(Word.nullPointer(), threadStackSize, threadStartRoutine.getFunctionPointer(), startData, initFlag, Word.nullPointer()); if (osThreadHandle.isNull()) { undoPrepareStartOnError(thread, startData); return false; @@ -106,8 +106,8 @@ public OSThreadHandle startThreadUnmanaged(CFunctionPointer threadRoutine, Point initFlag |= Process.STACK_SIZE_PARAM_IS_A_RESERVATION(); } - WinBase.HANDLE osThreadHandle = Process.NoTransitions._beginthreadex(WordFactory.nullPointer(), stackSize, - threadRoutine, userData, initFlag, WordFactory.nullPointer()); + WinBase.HANDLE osThreadHandle = Process.NoTransitions._beginthreadex(Word.nullPointer(), stackSize, + threadRoutine, userData, initFlag, Word.nullPointer()); return (OSThreadHandle) osThreadHandle; } @@ -122,7 +122,7 @@ public boolean joinThreadUnmanaged(OSThreadHandle threadHandle, WordPointer thre } // Since only an int is written, first clear word - threadExitStatus.write(WordFactory.zero()); + threadExitStatus.write(Word.zero()); return Process.NoTransitions.GetExitCodeThread((WinBase.HANDLE) threadHandle, (CIntPointer) threadExitStatus) != 0; } @@ -131,7 +131,7 @@ public boolean joinThreadUnmanaged(OSThreadHandle threadHandle, WordPointer thre public ThreadLocalKey createUnmanagedThreadLocal() { int result = Process.NoTransitions.TlsAlloc(); VMError.guarantee(result != Process.TLS_OUT_OF_INDEXES(), "TlsAlloc failed."); - return WordFactory.unsigned(result); + return Word.unsigned(result); } @Override @@ -192,7 +192,7 @@ class WindowsParker extends Parker { private WinBase.HANDLE eventHandle; WindowsParker() { - eventHandle = SynchAPI.CreateEventA(WordFactory.nullPointer(), 1, 0, WordFactory.nullPointer()); + eventHandle = SynchAPI.CreateEventA(Word.nullPointer(), 1, 0, Word.nullPointer()); VMError.guarantee(eventHandle.rawValue() != 0, "CreateEventA failed"); } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformTimeUtils.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformTimeUtils.java index 4abac3e44faa..15dca5f55343 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformTimeUtils.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformTimeUtils.java @@ -26,8 +26,8 @@ import static com.oracle.svm.core.windows.headers.SysinfoAPI.GetSystemTimeAsFileTime; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.util.BasedOnJDKFile; @@ -48,7 +48,7 @@ private static long offset() { /* Returns time ticks in (10th of micro seconds) */ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+3/src/hotspot/os/windows/os_windows.cpp#L1158-L1161") private static long windowsToTimeTicks(FILETIME wt) { - long a = WordFactory.unsigned(wt.dwHighDateTime()).shiftLeft(32).or(WordFactory.unsigned(wt.dwLowDateTime())).rawValue(); + long a = Word.unsigned(wt.dwHighDateTime()).shiftLeft(32).or(Word.unsigned(wt.dwLowDateTime())).rawValue(); return (a - offset()); } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java index c87d13963067..057ba03622d4 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java @@ -32,11 +32,11 @@ import java.util.List; import java.util.Map; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.nativeimage.impl.ProcessPropertiesSupport; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.BaseProcessPropertiesSupport; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; @@ -53,7 +53,7 @@ public class WindowsProcessPropertiesSupport extends BaseProcessPropertiesSuppor @Override public String getExecutableName() { - return getModulePath(LibLoaderAPI.GetModuleHandleA(WordFactory.nullPointer())); + return getModulePath(LibLoaderAPI.GetModuleHandleA(Word.nullPointer())); } @Override @@ -104,7 +104,7 @@ public long getProcessID(java.lang.Process process) { @Override public String getObjectFile(String symbol) { try (CTypeConversion.CCharPointerHolder symbolHolder = CTypeConversion.toCString(symbol)) { - WinBase.HMODULE builtinHandle = LibLoaderAPI.GetModuleHandleA(WordFactory.nullPointer()); + WinBase.HMODULE builtinHandle = LibLoaderAPI.GetModuleHandleA(Word.nullPointer()); PointerBase symbolAddress = LibLoaderAPI.GetProcAddress(builtinHandle, symbolHolder.get()); if (symbolAddress.isNonNull()) { return getObjectFile(symbolAddress); diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsRegisterDumper.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsRegisterDumper.java index 609606e17e52..7173eb1f66fb 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsRegisterDumper.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsRegisterDumper.java @@ -26,8 +26,8 @@ import static com.oracle.svm.core.RegisterDumper.dumpReg; +import jdk.graal.compiler.word.Word; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.RegisterDumper; import com.oracle.svm.core.Uninterruptible; @@ -75,24 +75,24 @@ private static void dumpRegisters(Log log, CONTEXT context, boolean printLocatio @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getHeapBase(Context context) { - return WordFactory.pointer(((CONTEXT) context).R14()); + return Word.pointer(((CONTEXT) context).R14()); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getThreadPointer(Context context) { - return WordFactory.pointer(((CONTEXT) context).R15()); + return Word.pointer(((CONTEXT) context).R15()); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getSP(Context context) { - return WordFactory.pointer(((CONTEXT) context).Rsp()); + return Word.pointer(((CONTEXT) context).Rsp()); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public PointerBase getIP(Context context) { - return WordFactory.pointer(((CONTEXT) context).Rip()); + return Word.pointer(((CONTEXT) context).Rip()); } } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSignalHandlerSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSignalHandlerSupport.java index ad1cc8f6cd93..ea07b3ce6af2 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSignalHandlerSupport.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSignalHandlerSupport.java @@ -28,10 +28,10 @@ import java.util.Locale; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.function.CFunction; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Isolates; import com.oracle.svm.core.Uninterruptible; @@ -60,7 +60,7 @@ public long installJavaSignalHandler(int sig, long nativeH) { assert MonitorSupport.singleton().isLockedByCurrentThread(Target_jdk_internal_misc_Signal.class); ensureInitialized(); - return Jvm.JVM_RegisterSignal(sig, WordFactory.pointer(nativeH)).rawValue(); + return Jvm.JVM_RegisterSignal(sig, Word.pointer(nativeH)).rawValue(); } private void ensureInitialized() { diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsStackOverflowSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsStackOverflowSupport.java index b1728b3512f5..bed3cfb65791 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsStackOverflowSupport.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsStackOverflowSupport.java @@ -24,12 +24,12 @@ */ package com.oracle.svm.core.windows; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; @@ -43,14 +43,14 @@ final class WindowsStackOverflowSupport implements StackOverflowCheck.PlatformSu public boolean lookupStack(WordPointer stackBasePtr, WordPointer stackEndPtr) { int sizeOfMInfo = SizeOf.get(MemoryAPI.MEMORY_BASIC_INFORMATION.class); MemoryAPI.MEMORY_BASIC_INFORMATION minfo = StackValue.get(sizeOfMInfo); - MemoryAPI.VirtualQuery(minfo, minfo, WordFactory.unsigned(sizeOfMInfo)); + MemoryAPI.VirtualQuery(minfo, minfo, Word.unsigned(sizeOfMInfo)); Pointer bottom = (Pointer) minfo.AllocationBase(); stackEndPtr.write(bottom); UnsignedWord stackSize = minfo.RegionSize(); /* Add up the sizes of all the regions with the same AllocationBase. */ while (true) { - MemoryAPI.VirtualQuery(bottom.add(stackSize), minfo, WordFactory.unsigned(sizeOfMInfo)); + MemoryAPI.VirtualQuery(bottom.add(stackSize), minfo, Word.unsigned(sizeOfMInfo)); if (bottom.equal(minfo.AllocationBase())) { stackSize = stackSize.add(minfo.RegionSize()); } else { diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSystemPropertiesSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSystemPropertiesSupport.java index 05e2f5fa881a..d7e3fec75571 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSystemPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSystemPropertiesSupport.java @@ -28,6 +28,7 @@ import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -38,7 +39,6 @@ import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.nativeimage.impl.RuntimeSystemPropertiesSupport; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.c.NonmovableArrays; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; @@ -152,7 +152,7 @@ protected String javaLibraryPathValue() { StringBuilder libraryPath = new StringBuilder(3 * WinBase.MAX_PATH + pathLength + 5); /* Add the directory from which application is loaded. */ - tmpLength = LibLoaderAPI.GetModuleFileNameW(WordFactory.nullPointer(), tmp, WinBase.MAX_PATH); + tmpLength = LibLoaderAPI.GetModuleFileNameW(Word.nullPointer(), tmp, WinBase.MAX_PATH); VMError.guarantee(tmpLength > 0 && tmpLength < WinBase.MAX_PATH); libraryPath.append(asCharBuffer(tmp, tmpLength)); /* Get rid of `\.exe`. */ @@ -238,15 +238,15 @@ private void computeOsNameAndVersion() { if (ret == 0 || ret > len) { break; } - WindowsLibC.wcsncat(kernel32Path, kernel32Dll, WordFactory.unsigned(WinBase.MAX_PATH - ret)); + WindowsLibC.wcsncat(kernel32Path, kernel32Dll, Word.unsigned(WinBase.MAX_PATH - ret)); /* ... and use that for determining what version of Windows we're running on. */ - int versionSize = WinVer.GetFileVersionInfoSizeW(kernel32Path, WordFactory.nullPointer()); + int versionSize = WinVer.GetFileVersionInfoSizeW(kernel32Path, Word.nullPointer()); if (versionSize == 0) { break; } - VoidPointer versionInfo = NullableNativeMemory.malloc(WordFactory.unsigned(versionSize), NmtCategory.Internal); + VoidPointer versionInfo = NullableNativeMemory.malloc(Word.unsigned(versionSize), NmtCategory.Internal); if (versionInfo.isNull()) { break; } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsThreadCpuTimeSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsThreadCpuTimeSupport.java index 2c00384c01e6..483643ad01e0 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsThreadCpuTimeSupport.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsThreadCpuTimeSupport.java @@ -25,11 +25,11 @@ package com.oracle.svm.core.windows; import com.oracle.svm.core.util.BasedOnJDKFile; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.StackValue; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; @@ -83,7 +83,7 @@ private static long getThreadCpuTime(HANDLE hThread, boolean includeSystemTime) */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static UnsignedWord fileTimeToNanos(FILETIME ft) { - UnsignedWord value = WordFactory.unsigned(ft.dwHighDateTime()).shiftLeft(32).or(WordFactory.unsigned(ft.dwLowDateTime())); + UnsignedWord value = Word.unsigned(ft.dwHighDateTime()).shiftLeft(32).or(Word.unsigned(ft.dwLowDateTime())); return value.multiply(100); } } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsUtils.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsUtils.java index 4477cf634730..cf3261f8953b 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsUtils.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsUtils.java @@ -28,6 +28,7 @@ import java.io.FileDescriptor; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.function.CFunctionPointer; import org.graalvm.nativeimage.c.struct.CPointerTo; @@ -37,7 +38,6 @@ import org.graalvm.nativeimage.hosted.FieldValueTransformer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; @@ -61,7 +61,7 @@ private static final class Target_java_lang_ProcessImpl { public static int getpid(java.lang.Process process) { Target_java_lang_ProcessImpl processImpl = SubstrateUtil.cast(process, Target_java_lang_ProcessImpl.class); - return com.oracle.svm.core.windows.headers.Process.NoTransitions.GetProcessId(WordFactory.pointer(processImpl.handle)); + return com.oracle.svm.core.windows.headers.Process.NoTransitions.GetProcessId(Word.pointer(processImpl.handle)); } @TargetClass(java.io.FileDescriptor.class) @@ -102,7 +102,7 @@ public static boolean writeBytes(int handle, CCharPointer bytes, UnsignedWord le CIntPointer bytesWritten = UnsafeStackValue.get(CIntPointer.class); - int ret = FileAPI.WriteFile(handle, curBuf, curLen, bytesWritten, WordFactory.nullPointer()); + int ret = FileAPI.WriteFile(handle, curBuf, curLen, bytesWritten, Word.nullPointer()); if (ret == 0) { return false; @@ -150,7 +150,7 @@ public static long getNanoCounter() { } /** Sentinel value denoting the uninitialized kernel handle. */ - public static final PointerBase UNINITIALIZED_HANDLE = WordFactory.pointer(1); + public static final PointerBase UNINITIALIZED_HANDLE = Word.pointer(1); @CPointerTo(nameOfCType = "void*") interface CFunctionPointerPointer extends PointerBase { @@ -160,7 +160,7 @@ interface CFunctionPointerPointer extends PointerBas } /** Sentinel value denoting the uninitialized pointer. */ - static final PointerBase UNINITIALIZED_POINTER = WordFactory.pointer(0xBAD); + static final PointerBase UNINITIALIZED_POINTER = Word.pointer(0xBAD); /** * Retrieves and caches the address of an exported function from an already loaded DLL if the diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVMLockSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVMLockSupport.java index 07b8fef0f76d..f6957a1b47a6 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVMLockSupport.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVMLockSupport.java @@ -26,11 +26,11 @@ import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.LogHandler; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CIsolateData; @@ -289,7 +289,7 @@ final class WindowsVMSemaphore extends VMSemaphore { @Override @Uninterruptible(reason = "Too early for safepoints.") public int initialize() { - hSemaphore = WinBase.CreateSemaphoreA(WordFactory.nullPointer(), 0, Integer.MAX_VALUE, WordFactory.nullPointer()); + hSemaphore = WinBase.CreateSemaphoreA(Word.nullPointer(), 0, Integer.MAX_VALUE, Word.nullPointer()); return hSemaphore.isNonNull() ? 0 : 1; } @@ -297,7 +297,7 @@ public int initialize() { @Uninterruptible(reason = "The isolate teardown is in progress.") public int destroy() { int errorCode = WinBase.CloseHandle(hSemaphore); - hSemaphore = WordFactory.nullPointer(); + hSemaphore = Word.nullPointer(); return errorCode; } @@ -309,6 +309,6 @@ public void await() { @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void signal() { - WindowsVMLockSupport.checkResult(SynchAPI.NoTransitions.ReleaseSemaphore(hSemaphore, 1, WordFactory.nullPointer()), "ReleaseSemaphore"); + WindowsVMLockSupport.checkResult(SynchAPI.NoTransitions.ReleaseSemaphore(hSemaphore, 1, Word.nullPointer()), "ReleaseSemaphore"); } } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVMThreads.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVMThreads.java index f419619eab5b..84de384f11fb 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVMThreads.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVMThreads.java @@ -24,9 +24,9 @@ */ package com.oracle.svm.core.windows; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.type.CCharPointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; @@ -59,7 +59,7 @@ public OSThreadHandle getCurrentOSThreadHandle() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override protected OSThreadId getCurrentOSThreadId() { - return WordFactory.unsigned(Process.NoTransitions.GetCurrentThreadId()); + return Word.unsigned(Process.NoTransitions.GetCurrentThreadId()); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVirtualMemoryProvider.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVirtualMemoryProvider.java index b833a15f32f7..7e63304342e8 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVirtualMemoryProvider.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVirtualMemoryProvider.java @@ -26,6 +26,7 @@ import static org.graalvm.nativeimage.c.function.CFunction.Transition.NO_TRANSITION; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.function.CFunctionPointer; import org.graalvm.nativeimage.c.function.InvokeCFunctionPointer; @@ -39,7 +40,6 @@ import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CGlobalData; @@ -65,14 +65,14 @@ public class WindowsVirtualMemoryProvider implements VirtualMemoryProvider { private static void initCaches() { SysinfoAPI.SYSTEM_INFO sysInfo = StackValue.get(SysinfoAPI.SYSTEM_INFO.class); SysinfoAPI.GetSystemInfo(sysInfo); - CACHED_PAGE_SIZE.get().write(WordFactory.unsigned(sysInfo.dwPageSize())); - CACHED_ALLOC_GRAN.get().write(WordFactory.unsigned(sysInfo.dwAllocationGranularity())); + CACHED_PAGE_SIZE.get().write(Word.unsigned(sysInfo.dwPageSize())); + CACHED_ALLOC_GRAN.get().write(Word.unsigned(sysInfo.dwAllocationGranularity())); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static UnsignedWord getPageSize() { UnsignedWord value = CACHED_PAGE_SIZE.get().read(); - if (value.equal(WordFactory.zero())) { + if (value.equal(Word.zero())) { initCaches(); value = CACHED_PAGE_SIZE.get().read(); } @@ -82,7 +82,7 @@ private static UnsignedWord getPageSize() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static UnsignedWord getAllocationGranularity() { UnsignedWord value = CACHED_ALLOC_GRAN.get().read(); - if (value.equal(WordFactory.zero())) { + if (value.equal(Word.zero())) { initCaches(); value = CACHED_ALLOC_GRAN.get().read(); } @@ -121,13 +121,13 @@ private static int accessAsProt(int access) { } /** Sentinel value indicating that no special alignment is required. */ - private static final UnsignedWord UNALIGNED = WordFactory.zero(); + private static final UnsignedWord UNALIGNED = Word.zero(); @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public Pointer reserve(UnsignedWord nbytes, UnsignedWord alignment, boolean executable) { if (nbytes.equal(0)) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } UnsignedWord requiredAlignment = alignment; @@ -156,9 +156,9 @@ public Pointer reserve(UnsignedWord nbytes, UnsignedWord alignment, boolean exec assert reservedPlaceholder.isNull(); /* Reserve a container that is large enough for the requested size *and* the alignment. */ - Pointer reserved = MemoryAPI.VirtualAlloc(WordFactory.nullPointer(), nbytes.add(requiredAlignment), MemoryAPI.MEM_RESERVE(), MemoryAPI.PAGE_NOACCESS()); + Pointer reserved = MemoryAPI.VirtualAlloc(Word.nullPointer(), nbytes.add(requiredAlignment), MemoryAPI.MEM_RESERVE(), MemoryAPI.PAGE_NOACCESS()); if (reserved.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } return requiredAlignment.equal(UNALIGNED) ? reserved : PointerUtils.roundUp(reserved, requiredAlignment); } @@ -169,7 +169,7 @@ public Pointer reserve(UnsignedWord nbytes, UnsignedWord alignment, boolean exec @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static Pointer reservePlaceholder(UnsignedWord size, UnsignedWord alignment) { int allocationType = MemoryAPI.MEM_RESERVE() | MEM_RESERVE_PLACEHOLDER; - return invokeVirtualAlloc2(WordFactory.nullPointer(), size, allocationType, MemoryAPI.PAGE_NOACCESS(), alignment); + return invokeVirtualAlloc2(Word.nullPointer(), size, allocationType, MemoryAPI.PAGE_NOACCESS(), alignment); } private static final int MEM_REPLACE_PLACEHOLDER = 0x00004000; @@ -179,7 +179,7 @@ private static Pointer reservePlaceholder(UnsignedWord size, UnsignedWord alignm @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static void replacePlaceholder(PointerBase placeholder, UnsignedWord size) { int allocationType = MemoryAPI.MEM_RESERVE() | MEM_REPLACE_PLACEHOLDER; - if (invokeVirtualAlloc2(placeholder, size, allocationType, MemoryAPI.PAGE_NOACCESS(), WordFactory.zero()).isNull()) { + if (invokeVirtualAlloc2(placeholder, size, allocationType, MemoryAPI.PAGE_NOACCESS(), Word.zero()).isNull()) { CEntryPointActions.failFatally(WinBase.GetLastError(), REPLACE_PLACEHOLDER_ERROR_MESSAGE.get()); } } @@ -201,11 +201,11 @@ private static Pointer invokeVirtualAlloc2(PointerBase baseAddress, UnsignedWord VirtualAlloc2 virtualAlloc2 = WindowsUtils.getAndCacheFunctionPointer(VIRTUAL_ALLOC_2_POINTER.get(), KERNELBASE_DLL.get(), VIRTUAL_ALLOC_2.get()); if (virtualAlloc2.isNull()) { /* The OS does not provide VirtualAlloc2 (nor placeholders). */ - return WordFactory.nullPointer(); + return Word.nullPointer(); } MEM_EXTENDED_PARAMETER extendedParameter = StackValue.get(MEM_EXTENDED_PARAMETER.class); specifyAlignment(extendedParameter, StackValue.get(MEM_ADDRESS_REQUIREMENTS.class), alignment); - return virtualAlloc2.invoke(WordFactory.nullPointer(), baseAddress, size, allocationType, pageProtection, extendedParameter, 1); + return virtualAlloc2.invoke(Word.nullPointer(), baseAddress, size, allocationType, pageProtection, extendedParameter, 1); } private interface VirtualAlloc2 extends CFunctionPointer { @@ -230,8 +230,8 @@ private static void specifyAlignment(MEM_EXTENDED_PARAMETER extendedParameter, M */ extendedParameter.setF1Type(MemExtendedParameterAddressRequirements); extendedParameter.setF2Pointer(addressRequirements.rawValue()); - addressRequirements.setF1LowestStartingAddress(WordFactory.nullPointer()); - addressRequirements.setF2HighestEndingAddress(WordFactory.nullPointer()); + addressRequirements.setF1LowestStartingAddress(Word.nullPointer()); + addressRequirements.setF2HighestEndingAddress(Word.nullPointer()); addressRequirements.setF3Alignment(alignment); } @@ -285,7 +285,7 @@ private interface MEM_ADDRESS_REQUIREMENTS extends PointerBase { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public Pointer mapFile(PointerBase start, UnsignedWord nbytes, WordBase fileHandle, UnsignedWord offset, int access) { if ((start.isNonNull() && !isAligned(start)) || nbytes.equal(0)) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } /* @@ -297,13 +297,13 @@ public Pointer mapFile(PointerBase start, UnsignedWord nbytes, WordBase fileHand * Memory mapping to an unreserved address range imposes an additional restriction on * the alignment of the `offset`, which we do not currently support. */ - return WordFactory.nullPointer(); + return Word.nullPointer(); } /* First split off a placeholder from the reserved address range ... */ if (!splitPlaceholder(start, nbytes)) { /* The OS does not support placeholders. */ - return WordFactory.nullPointer(); + return Word.nullPointer(); } /* ... and then map a view into the placeholder. */ @@ -337,10 +337,10 @@ private static Pointer invokeMapViewOfFile3(HANDLE fileMapping, PointerBase base MapViewOfFile3 mapViewOfFile3 = WindowsUtils.getAndCacheFunctionPointer(MAP_VIEW_OF_FILE_3_POINTER.get(), KERNELBASE_DLL.get(), MAP_VIEW_OF_FILE_3.get()); if (mapViewOfFile3.isNull()) { /* The OS does not provide MapViewOfFile3 (nor placeholders). */ - return WordFactory.nullPointer(); + return Word.nullPointer(); } - return mapViewOfFile3.invoke(fileMapping, WordFactory.nullPointer(), baseAddress, offset, viewSize, MEM_REPLACE_PLACEHOLDER, - pageProtection, WordFactory.nullPointer(), 0); + return mapViewOfFile3.invoke(fileMapping, Word.nullPointer(), baseAddress, offset, viewSize, MEM_REPLACE_PLACEHOLDER, + pageProtection, Word.nullPointer(), 0); } private interface MapViewOfFile3 extends CFunctionPointer { @@ -353,7 +353,7 @@ Pointer invoke(HANDLE fileMapping, HANDLE process, PointerBase baseAddress, long @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public Pointer commit(PointerBase start, UnsignedWord nbytes, int access) { if ((start.isNonNull() && !isAligned(start)) || nbytes.equal(0)) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } /* @@ -417,7 +417,7 @@ private static boolean free(PointerBase allocationBase, boolean isMemoryMapped) if (isMemoryMapped) { return MemoryAPI.UnmapViewOfFile(allocationBase) != 0; } else { - return MemoryAPI.VirtualFree(allocationBase, WordFactory.zero(), MemoryAPI.MEM_RELEASE()) != 0; + return MemoryAPI.VirtualFree(allocationBase, Word.zero(), MemoryAPI.MEM_RELEASE()) != 0; } } diff --git a/substratevm/src/com.oracle.svm.core/headers/OWNERS.toml b/substratevm/src/com.oracle.svm.core/headers/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/headers/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/BuildPhaseProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/BuildPhaseProvider.java index 1977582a3bab..ed8dd47df91e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/BuildPhaseProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/BuildPhaseProvider.java @@ -33,6 +33,7 @@ @Platforms(Platform.HOSTED_ONLY.class) public final class BuildPhaseProvider { + private boolean featureRegistrationFinished; private boolean analysisFinished; private boolean hostedUniverseBuilt; private boolean readyForCompilation; @@ -51,6 +52,14 @@ static BuildPhaseProvider singleton() { BuildPhaseProvider() { } + public static void markFeatureRegistrationFinished() { + singleton().featureRegistrationFinished = true; + } + + public static boolean isFeatureRegistrationFinished() { + return ImageSingletons.contains(BuildPhaseProvider.class) && singleton().featureRegistrationFinished; + } + public static void markAnalysisFinished() { singleton().analysisFinished = true; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DebugHelper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DebugHelper.java index 0f1f4d11c36e..ec2cbcb86cea 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DebugHelper.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DebugHelper.java @@ -33,7 +33,6 @@ import org.graalvm.nativeimage.c.function.CEntryPoint.Publish; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.c.SetThreadAndHeapBasePrologue; import com.oracle.svm.core.c.function.CEntryPointOptions; @@ -279,7 +278,7 @@ public static void printString(@SuppressWarnings("unused") IsolateThread thread, @CEntryPoint(name = "svm_dbg_print_fatalErrorDiagnostics", include = IncludeDebugHelperMethods.class, publishAs = Publish.SymbolOnly) @CEntryPointOptions(prologue = SetThreadAndHeapBasePrologue.class, epilogue = NoEpilogue.class) public static void printFatalErrorDiagnostics(@SuppressWarnings("unused") IsolateThread thread, Pointer sp, CodePointer ip) { - SubstrateDiagnostics.printFatalError(Log.log(), sp, ip, WordFactory.nullPointer(), false); + SubstrateDiagnostics.printFatalError(Log.log(), sp, ip, Word.nullPointer(), false); } @Uninterruptible(reason = "Just to keep the verification happy.", calleeMustBe = false) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpRuntimeCompilationOnSignalFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpRuntimeCompilationOnSignalFeature.java index cfaf48ba7b87..9d0104b0754c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpRuntimeCompilationOnSignalFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpRuntimeCompilationOnSignalFeature.java @@ -39,12 +39,14 @@ public class DumpRuntimeCompilationOnSignalFeature implements InternalFeature { @Override public boolean isInConfiguration(IsInConfigurationAccess access) { - return VMInspectionOptions.DumpRuntimeCompilationOnSignal.getValue() && !Platform.includedIn(WINDOWS.class) && RuntimeCompilation.isEnabled(); + return VMInspectionOptions.DumpRuntimeCompilationOnSignal.getValue() && !Platform.includedIn(WINDOWS.class); } @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - RuntimeSupport.getRuntimeSupport().addStartupHook(new DumpRuntimeCompilationStartupHook()); + if (RuntimeCompilation.isEnabled()) { + RuntimeSupport.getRuntimeSupport().addStartupHook(new DumpRuntimeCompilationStartupHook()); + } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/InvalidMethodPointerHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/InvalidMethodPointerHandler.java index 4fee287e98a8..8f11b8253c1e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/InvalidMethodPointerHandler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/InvalidMethodPointerHandler.java @@ -28,13 +28,13 @@ import java.lang.reflect.Method; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.LogHandler; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.graal.code.StubCallingConvention; import com.oracle.svm.core.heap.RestrictHeapAccess; @@ -89,7 +89,7 @@ private static void failFatally(Pointer callerSP, CodePointer callerIP, String m LogHandler logHandler = ImageSingletons.lookup(LogHandler.class); Log log = Log.enterFatalContext(logHandler, callerIP, message, null); if (log != null) { - SubstrateDiagnostics.printFatalError(log, callerSP, callerIP, WordFactory.nullPointer(), true); + SubstrateDiagnostics.printFatalError(log, callerSP, callerIP, Word.nullPointer(), true); log.string(message).newline(); } logHandler.fatalError(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentAccess.java index 5a1099b4d4a3..f2a3871dc2a0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentAccess.java @@ -26,8 +26,8 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.type.CCharPointer; -import org.graalvm.word.WordFactory; public class IsolateArgumentAccess { @@ -63,7 +63,7 @@ public static void writeBoolean(IsolateArguments arguments, int index, boolean v @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public static CCharPointer readCCharPointer(IsolateArguments arguments, int index) { - return WordFactory.pointer(readLong(arguments, index)); + return Word.pointer(readLong(arguments, index)); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java index ea23188e9079..f103edbb59a2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java @@ -35,6 +35,7 @@ import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -45,7 +46,6 @@ import org.graalvm.nativeimage.c.type.CLongPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.c.CGlobalData; import com.oracle.svm.core.c.CGlobalDataFactory; @@ -224,7 +224,7 @@ public boolean tearDown(IsolateArguments arguments) { for (int i = 0; i < getOptionCount(); i++) { if (OPTION_TYPES.get().read(i) == OptionValueType.C_CHAR_POINTER) { UntrackedNullableNativeMemory.free(readCCharPointer(arguments, i)); - writeCCharPointer(arguments, i, WordFactory.nullPointer()); + writeCCharPointer(arguments, i, Word.nullPointer()); } } return true; @@ -234,7 +234,7 @@ public boolean tearDown(IsolateArguments arguments) { public boolean tearDown() { for (int i = 0; i < getOptionCount(); i++) { if (OPTION_TYPES.get().read(i) == OptionValueType.C_CHAR_POINTER) { - UntrackedNullableNativeMemory.free(WordFactory.pointer(parsedOptionValues[i])); + UntrackedNullableNativeMemory.free(Word.pointer(parsedOptionValues[i])); parsedOptionValues[i] = 0; } } @@ -260,7 +260,7 @@ private static void copyStringArguments(IsolateArguments arguments) { VMError.guarantee(copy.isNonNull(), "Copying of string argument failed."); writeCCharPointer(arguments, i, copy); } else { - writeCCharPointer(arguments, i, WordFactory.nullPointer()); + writeCCharPointer(arguments, i, Word.nullPointer()); } } } @@ -332,7 +332,7 @@ public long getLongOptionValue(int index) { } protected CCharPointer getCCharPointerOptionValue(int index) { - return WordFactory.pointer(parsedOptionValues[index]); + return Word.pointer(parsedOptionValues[index]); } protected Object getOptionValue(int index) { @@ -350,7 +350,7 @@ protected Object getOptionValue(int index) { return null; } - return CTypeConversion.toJavaString(WordFactory.pointer(value)); + return CTypeConversion.toJavaString(Word.pointer(value)); } else { throw VMError.shouldNotReachHere("Option value has unexpected type: " + optionValueType); } @@ -380,7 +380,7 @@ protected void initialize(IsolateArguments arguments, CEntryPointCreateIsolatePa arguments.setProtectionKey(parameters.protectionKey()); } else { arguments.setArgc(0); - arguments.setArgv(WordFactory.nullPointer()); + arguments.setArgv(Word.nullPointer()); arguments.setProtectionKey(0); } @@ -398,7 +398,7 @@ private static CCharPointer matchPrefix(CCharPointer arg) { if (arg.read(0) == '-') { return arg.addressOf(1); } - return WordFactory.nullPointer(); + return Word.nullPointer(); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) @@ -406,7 +406,7 @@ private static CCharPointer matchXOption(CCharPointer arg) { if (arg.read(0) == 'X' && arg.read(1) == 'm') { return arg.addressOf(2); } - return WordFactory.nullPointer(); + return Word.nullPointer(); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) @@ -414,7 +414,7 @@ private static CCharPointer matchXXOption(CCharPointer arg) { if (arg.read(0) == 'X' && arg.read(1) == 'X' && arg.read(2) == ':') { return arg.addressOf(3); } - return WordFactory.nullPointer(); + return Word.nullPointer(); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) @@ -519,7 +519,7 @@ private static boolean atojulong(CCharPointer s, CLongPointer result) { } } - UnsignedWord value = n.multiply(WordFactory.unsigned(modifier)); + UnsignedWord value = n.multiply(Word.unsigned(modifier)); if (checkForOverflow(value, n, modifier)) { return false; } @@ -529,7 +529,7 @@ private static boolean atojulong(CCharPointer s, CLongPointer result) { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private static boolean checkForOverflow(UnsignedWord value, UnsignedWord n, long modifier) { - return value.unsignedDivide(WordFactory.unsigned(modifier)) != n; + return value.unsignedDivide(Word.unsigned(modifier)) != n; } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) @@ -548,7 +548,7 @@ private static CCharPointer startsWith(CCharPointer input, CCharPointer prefix) if (prefix.read(i) == 0) { return input.addressOf(i); } - return WordFactory.nullPointer(); + return Word.nullPointer(); } @Fold diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Isolates.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Isolates.java index 078760c71491..f32850b6a2fd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Isolates.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Isolates.java @@ -32,7 +32,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.c.CGlobalData; import com.oracle.svm.core.c.CGlobalDataFactory; @@ -70,7 +69,7 @@ public class Isolates { public static final CGlobalData IMAGE_HEAP_WRITABLE_END = CGlobalDataFactory.forSymbol(IMAGE_HEAP_WRITABLE_END_SYMBOL_NAME); public static final CGlobalData IMAGE_HEAP_WRITABLE_PATCHED_BEGIN = CGlobalDataFactory.forSymbol(IMAGE_HEAP_WRITABLE_PATCHED_BEGIN_SYMBOL_NAME); public static final CGlobalData IMAGE_HEAP_WRITABLE_PATCHED_END = CGlobalDataFactory.forSymbol(IMAGE_HEAP_WRITABLE_PATCHED_END_SYMBOL_NAME); - public static final CGlobalData ISOLATE_COUNTER = CGlobalDataFactory.createWord((WordBase) WordFactory.unsigned(1)); + public static final CGlobalData ISOLATE_COUNTER = CGlobalDataFactory.createWord((WordBase) Word.unsigned(1)); /* Only used if SpawnIsolates is disabled. */ private static final CGlobalData SINGLE_ISOLATE_ALREADY_CREATED = CGlobalDataFactory.createWord(); @@ -148,7 +147,7 @@ public static int checkIsolate(Isolate isolate) { @Uninterruptible(reason = "Thread state not yet set up.") public static int create(WordPointer isolatePointer, IsolateArguments arguments) { if (!SubstrateOptions.SpawnIsolates.getValue()) { - if (!SINGLE_ISOLATE_ALREADY_CREATED.get().logicCompareAndSwapWord(0, WordFactory.zero(), WordFactory.signed(1), NamedLocationIdentity.OFF_HEAP_LOCATION)) { + if (!SINGLE_ISOLATE_ALREADY_CREATED.get().logicCompareAndSwapWord(0, Word.zero(), Word.signed(1), NamedLocationIdentity.OFF_HEAP_LOCATION)) { return CEntryPointErrors.SINGLE_ISOLATE_ALREADY_CREATED; } } @@ -162,7 +161,7 @@ public static int create(WordPointer isolatePointer, IsolateArguments arguments) Isolate isolate = heapBasePointer.read(); result = checkIsolate(isolate); if (result != CEntryPointErrors.NO_ERROR) { - isolatePointer.write(WordFactory.nullPointer()); + isolatePointer.write(Word.nullPointer()); return result; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java index 6df39db9fd5d..bb18e8e369af 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java @@ -35,6 +35,7 @@ import java.util.List; import java.util.function.BooleanSupplier; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Isolate; @@ -54,7 +55,6 @@ import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.c.CGlobalData; import com.oracle.svm.core.c.CGlobalDataFactory; @@ -80,8 +80,6 @@ import com.oracle.svm.util.ClassUtil; import com.oracle.svm.util.ReflectionUtil; -import jdk.graal.compiler.word.Word; - @InternalVMMethod public class JavaMainWrapper { /* @@ -90,11 +88,7 @@ public class JavaMainWrapper { */ public static final CGlobalData MAIN_ISOLATE_PARAMETERS = CGlobalDataFactory.createBytes(() -> SizeOf.get(CEntryPointCreateIsolateParameters.class)); - static { - /* WordFactory.boxFactory is initialized by the static initializer of Word. */ - Word.ensureInitialized(); - } - private static UnsignedWord argvLength = WordFactory.zero(); + private static UnsignedWord argvLength = Word.zero(); public static class JavaMainSupport { @@ -327,7 +321,7 @@ private static int doRunInNewThread(int argc, CCharPointerPointer argv) { MAIN_ISOLATE_PARAMETERS.get().setArgc(argc); MAIN_ISOLATE_PARAMETERS.get().setArgv(argv); long stackSize = SubstrateOptions.StackSize.getHostedValue(); - OSThreadHandle osThreadHandle = PlatformThreads.singleton().startThreadUnmanaged(RUN_MAIN_ROUTINE.get(), WordFactory.nullPointer(), (int) stackSize); + OSThreadHandle osThreadHandle = PlatformThreads.singleton().startThreadUnmanaged(RUN_MAIN_ROUTINE.get(), Word.nullPointer(), (int) stackSize); if (osThreadHandle.isNull()) { CEntryPointActions.failFatally(1, START_THREAD_UNMANAGED_ERROR_MESSAGE.get()); return 1; @@ -363,7 +357,7 @@ public boolean getAsBoolean() { @CEntryPointOptions(prologue = CEntryPointOptions.NoPrologue.class, epilogue = CEntryPointOptions.NoEpilogue.class) static WordBase runMainRoutine(PointerBase data) { int exitStatus = doRun(MAIN_ISOLATE_PARAMETERS.get().getArgc(), MAIN_ISOLATE_PARAMETERS.get().getArgv()); - return WordFactory.signed(exitStatus); + return Word.signed(exitStatus); } private static boolean isArgumentBlockSupported() { @@ -398,13 +392,13 @@ public static int getCRuntimeArgumentBlockLength() { CEntryPointCreateIsolateParameters args = MAIN_ISOLATE_PARAMETERS.get(); CCharPointer firstArgPos = args.getArgv().read(0); - if (argvLength.equal(WordFactory.zero())) { + if (argvLength.equal(Word.zero())) { // Get char* to last program argument CCharPointer lastArgPos = args.getArgv().read(args.getArgc() - 1); // Determine the length of the last program argument UnsignedWord lastArgLength = SubstrateUtil.strlen(lastArgPos); // Determine maximum C string length that can be stored in the program argument part - argvLength = WordFactory.unsigned(lastArgPos.rawValue()).add(lastArgLength).subtract(WordFactory.unsigned(firstArgPos.rawValue())); + argvLength = Word.unsigned(lastArgPos.rawValue()).add(lastArgLength).subtract(Word.unsigned(firstArgPos.rawValue())); } return Math.toIntExact(argvLength.rawValue()); } @@ -419,7 +413,7 @@ public static boolean setCRuntimeArgument0(String arg0) { CCharPointer arg0Pointer = arg0Pin.get(); UnsignedWord arg0Length = SubstrateUtil.strlen(arg0Pointer); - UnsignedWord origLength = WordFactory.unsigned(getCRuntimeArgumentBlockLength()); + UnsignedWord origLength = Word.unsigned(getCRuntimeArgumentBlockLength()); UnsignedWord newArgLength = origLength; if (arg0Length.add(1).belowThan(origLength)) { newArgLength = arg0Length.add(1); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMemoryUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMemoryUtil.java index af9c31b6b7e4..eacc7e181e80 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMemoryUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMemoryUtil.java @@ -26,7 +26,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.hub.LayoutEncoding; @@ -142,7 +141,7 @@ public static void copyForward(Pointer from, Pointer to, UnsignedWord size) { UnsignedWord alignBits = diffBits.not().and(diffBits.subtract(1)).and(0x7); UnsignedWord alignment = alignBits.add(1); if (alignment.equal(1)) { - for (UnsignedWord offset = WordFactory.zero(); offset.belowThan(size); offset = offset.add(1)) { + for (UnsignedWord offset = Word.zero(); offset.belowThan(size); offset = offset.add(1)) { to.writeByte(offset, from.readByte(offset)); } return; @@ -179,7 +178,7 @@ public static void copyForward(Pointer from, Pointer to, UnsignedWord size) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static void copyUnalignedLower(Pointer from, Pointer to, UnsignedWord length) { - UnsignedWord offset = WordFactory.zero(); + UnsignedWord offset = Word.zero(); if (length.and(1).notEqual(0)) { to.writeByte(offset, from.readByte(offset)); offset = offset.add(1); @@ -195,7 +194,7 @@ private static void copyUnalignedLower(Pointer from, Pointer to, UnsignedWord le @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static void copyUnalignedUpper(Pointer from, Pointer to, UnsignedWord length) { - UnsignedWord offset = WordFactory.zero(); + UnsignedWord offset = Word.zero(); if (length.and(4).notEqual(0)) { to.writeInt(offset, from.readInt(offset)); offset = offset.add(4); @@ -293,8 +292,8 @@ public static void fill(Pointer to, UnsignedWord size, byte value) { v = (v << 16) | v; v = (v << 32) | v; - UnsignedWord alignMask = WordFactory.unsigned(0x7); - UnsignedWord lowerSize = WordFactory.unsigned(0x8).subtract(to).and(alignMask); + UnsignedWord alignMask = Word.unsigned(0x7); + UnsignedWord lowerSize = Word.unsigned(0x8).subtract(to).and(alignMask); if (lowerSize.aboveThan(0)) { if (size.belowThan(lowerSize)) { lowerSize = size.and(lowerSize); @@ -315,7 +314,7 @@ public static void fill(Pointer to, UnsignedWord size, byte value) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static void fillUnalignedLower(Pointer to, long value, UnsignedWord length) { - UnsignedWord offset = WordFactory.zero(); + UnsignedWord offset = Word.zero(); if (length.and(1).notEqual(0)) { to.writeByte(offset, (byte) value); offset = offset.add(1); @@ -332,7 +331,7 @@ private static void fillUnalignedLower(Pointer to, long value, UnsignedWord leng @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static void fillUnalignedUpper(Pointer to, long value, UnsignedWord upperSize) { - UnsignedWord offset = WordFactory.zero(); + UnsignedWord offset = Word.zero(); if (upperSize.and(4).notEqual(0)) { to.writeInt(offset, (int) value); offset = offset.add(4); @@ -349,8 +348,8 @@ private static void fillUnalignedUpper(Pointer to, long value, UnsignedWord uppe @Uninterruptible(reason = "Accessed memory is on the heap, code must not be interrupted.") static void fillOnHeap(Object destBase, long destOffset, long bytes, byte bvalue) { - Word fromPtr = Word.objectToUntrackedPointer(destBase).add(WordFactory.unsigned(destOffset)); - fill(fromPtr, WordFactory.unsigned(bytes), bvalue); + Word fromPtr = Word.objectToUntrackedPointer(destBase).add(Word.unsigned(destOffset)); + fill(fromPtr, Word.unsigned(bytes), bvalue); } /** @@ -380,15 +379,15 @@ public static void copySwap(Pointer from, Pointer to, UnsignedWord size, Unsigne @Uninterruptible(reason = "Accessed memory is on the heap, code must not be interrupted.") static void copySwapOnHeap(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes, long elemSize) { - Word fromPtr = Word.objectToUntrackedPointer(srcBase).add(WordFactory.unsigned(srcOffset)); - Word toPtr = Word.objectToUntrackedPointer(destBase).add(WordFactory.unsigned(destOffset)); - copySwap(fromPtr, toPtr, WordFactory.unsigned(bytes), WordFactory.unsigned(elemSize)); + Word fromPtr = Word.objectToUntrackedPointer(srcBase).add(Word.unsigned(srcOffset)); + Word toPtr = Word.objectToUntrackedPointer(destBase).add(Word.unsigned(destOffset)); + copySwap(fromPtr, toPtr, Word.unsigned(bytes), Word.unsigned(elemSize)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static void copySwap2(Pointer from, Pointer to, UnsignedWord size) { if (from.aboveThan(to)) { - for (UnsignedWord offset = WordFactory.zero(); offset.belowThan(size); offset = offset.add(2)) { + for (UnsignedWord offset = Word.zero(); offset.belowThan(size); offset = offset.add(2)) { to.writeShort(offset, Short.reverseBytes(from.readShort(offset))); } } else if (from.belowThan(to)) { @@ -401,7 +400,7 @@ private static void copySwap2(Pointer from, Pointer to, UnsignedWord size) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static void copySwap4(Pointer from, Pointer to, UnsignedWord size) { if (from.aboveThan(to)) { - for (UnsignedWord offset = WordFactory.zero(); offset.belowThan(size); offset = offset.add(4)) { + for (UnsignedWord offset = Word.zero(); offset.belowThan(size); offset = offset.add(4)) { to.writeInt(offset, Integer.reverseBytes(from.readInt(offset))); } } else if (from.belowThan(to)) { @@ -414,7 +413,7 @@ private static void copySwap4(Pointer from, Pointer to, UnsignedWord size) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static void copySwap8(Pointer from, Pointer to, UnsignedWord size) { if (from.aboveThan(to)) { - for (UnsignedWord offset = WordFactory.zero(); offset.belowThan(size); offset = offset.add(8)) { + for (UnsignedWord offset = Word.zero(); offset.belowThan(size); offset = offset.add(8)) { to.writeLong(offset, Long.reverseBytes(from.readLong(offset))); } } else if (from.belowThan(to)) { @@ -434,7 +433,7 @@ public static void copyObjectArrayForward(Object fromArray, int fromIndex, Objec UnsignedWord fromOffset = LayoutEncoding.getArrayElementOffset(layoutEncoding, fromIndex); UnsignedWord toOffset = LayoutEncoding.getArrayElementOffset(layoutEncoding, toIndex); - UnsignedWord count = WordFactory.unsigned(length); + UnsignedWord count = Word.unsigned(length); copyReferencesForward(fromArray, fromOffset, toArray, toOffset, count); } @@ -449,7 +448,7 @@ public static void copyObjectArrayBackward(Object fromArray, int fromIndex, Obje UnsignedWord fromOffset = LayoutEncoding.getArrayElementOffset(layoutEncoding, fromIndex); UnsignedWord toOffset = LayoutEncoding.getArrayElementOffset(layoutEncoding, toIndex); - UnsignedWord count = WordFactory.unsigned(length); + UnsignedWord count = Word.unsigned(length); copyReferencesBackward(fromArray, fromOffset, toArray, toOffset, count); } @@ -462,7 +461,7 @@ public static void copyObjectArrayBackward(Object fromArray, int fromIndex, Obje public static void copyReferencesForward(Object from, UnsignedWord fromOffset, Object to, UnsignedWord toOffset, UnsignedWord length) { int elementSize = ConfigurationValues.getObjectLayout().getReferenceSize(); UnsignedWord size = length.multiply(elementSize); - UnsignedWord copied = WordFactory.zero(); + UnsignedWord copied = Word.zero(); while (copied.belowThan(size)) { BarrieredAccess.writeObject(to, toOffset.add(copied), BarrieredAccess.readObject(from, fromOffset.add(copied))); copied = copied.add(elementSize); @@ -509,7 +508,7 @@ public static void copyPrimitiveArrayForward(Object fromArray, int fromIndex, Ob UnsignedWord fromOffset = LayoutEncoding.getArrayElementOffset(layoutEncoding, fromIndex); UnsignedWord toOffset = LayoutEncoding.getArrayElementOffset(layoutEncoding, toIndex); - UnsignedWord elementSize = WordFactory.unsigned(LayoutEncoding.getArrayIndexScale(layoutEncoding)); + UnsignedWord elementSize = Word.unsigned(LayoutEncoding.getArrayIndexScale(layoutEncoding)); UnsignedWord size = elementSize.multiply(length); copyPrimitiveArrayForward(fromArray, fromOffset, toArray, toOffset, size); @@ -544,7 +543,7 @@ public static void copyPrimitiveArrayBackward(Object fromArray, int fromIndex, O UnsignedWord fromOffset = LayoutEncoding.getArrayElementOffset(layoutEncoding, fromIndex); UnsignedWord toOffset = LayoutEncoding.getArrayElementOffset(layoutEncoding, toIndex); - UnsignedWord elementSize = WordFactory.unsigned(LayoutEncoding.getArrayIndexScale(layoutEncoding)); + UnsignedWord elementSize = Word.unsigned(LayoutEncoding.getArrayIndexScale(layoutEncoding)); UnsignedWord size = elementSize.multiply(length); copyPrimitiveArrayBackward(fromArray, fromOffset, toArray, toOffset, size); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationUtils.java index e6d574509d2d..510cca5f604d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationUtils.java @@ -65,7 +65,7 @@ public static void report(Error exception, StackTraceElement responsibleClass) { int printed = 0; StackTraceElement entryPoint = null; StringBuilder sb = new StringBuilder(exception.toString()); - sb.append("\n"); + sb.append(System.lineSeparator()); for (StackTraceElement stackTraceElement : stackTrace) { if (printed == 0) { String moduleName = stackTraceElement.getModuleName(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RegisterDumper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RegisterDumper.java index 9fde3ae40ef1..2f3e132815b9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RegisterDumper.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RegisterDumper.java @@ -24,9 +24,9 @@ */ package com.oracle.svm.core; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.util.VMError; @@ -44,7 +44,7 @@ static void dumpReg(Log log, String label, long value, boolean printLocationInfo log.string(label).zhex(value); if (printLocationInfo) { log.spaces(1); - SubstrateDiagnostics.printLocationInfo(log, WordFactory.unsigned(value), allowJavaHeapAccess, allowUnsafeOperations); + SubstrateDiagnostics.printLocationInfo(log, Word.unsigned(value), allowJavaHeapAccess, allowUnsafeOperations); } log.newline(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java index fb76f59ebd03..f6c6809c52e0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java @@ -27,8 +27,8 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; import static com.oracle.svm.core.option.RuntimeOptionKey.RuntimeOptionKeyFlag.RelevantForCompilationIsolates; -import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.CurrentIsolate; @@ -44,8 +44,12 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; +import com.oracle.svm.core.SubstrateDiagnostics.DiagnosticThunkRegistry; +import com.oracle.svm.core.SubstrateDiagnostics.DumpCodeCacheHistory; +import com.oracle.svm.core.SubstrateDiagnostics.DumpDeoptStubPointer; +import com.oracle.svm.core.SubstrateDiagnostics.DumpRecentDeoptimizations; +import com.oracle.svm.core.SubstrateDiagnostics.DumpRuntimeCodeInfoMemory; import com.oracle.svm.core.c.NonmovableArrays; import com.oracle.svm.core.code.CodeInfo; import com.oracle.svm.core.code.CodeInfoAccess; @@ -59,7 +63,9 @@ import com.oracle.svm.core.container.OperatingSystem; import com.oracle.svm.core.deopt.DeoptimizationSupport; import com.oracle.svm.core.deopt.Deoptimizer; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.graal.RuntimeCompilation; import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; import com.oracle.svm.core.heap.Heap; @@ -88,6 +94,7 @@ import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; import com.oracle.svm.core.threadlocal.VMThreadLocalInfos; import com.oracle.svm.core.util.CounterSupport; +import com.oracle.svm.core.util.ImageHeapList; import com.oracle.svm.core.util.TimeUtils; import com.oracle.svm.core.util.VMError; @@ -190,8 +197,8 @@ private static void printSomeArrayData(Log log, Object obj, int length, int layo int maxPrintedBytes = elementSize == 1 ? 8 : 16; int printedElements = UninterruptibleUtils.Math.min(length, maxPrintedBytes / elementSize); - UnsignedWord curOffset = WordFactory.unsigned(arrayBaseOffset); - UnsignedWord endOffset = curOffset.add(WordFactory.unsigned(printedElements).multiply(elementSize)); + UnsignedWord curOffset = Word.unsigned(arrayBaseOffset); + UnsignedWord endOffset = curOffset.add(Word.unsigned(printedElements).multiply(elementSize)); while (curOffset.belowThan(endOffset)) { switch (elementSize) { case 1 -> log.zhex(ObjectAccess.readByte(obj, curOffset)); @@ -213,7 +220,7 @@ private static void printSomeArrayData(Log log, Object obj, int length, int layo * See {@link #printInformation(Log, Pointer, CodePointer, RegisterDumper.Context, boolean)}. */ public static void printInformation(Log log, Pointer sp, CodePointer ip) { - printInformation(log, sp, ip, WordFactory.nullPointer(), false); + printInformation(log, sp, ip, Word.nullPointer(), false); } /** @@ -246,7 +253,7 @@ public static void printInformation(Log log, Pointer sp, CodePointer ip, Registe * See {@link #printFatalError(Log, Pointer, CodePointer, RegisterDumper.Context, boolean)}. */ public static boolean printFatalError(Log log, Pointer sp, CodePointer ip) { - return printFatalError(log, sp, ip, WordFactory.nullPointer(), false); + return printFatalError(log, sp, ip, Word.nullPointer(), false); } /** @@ -498,7 +505,7 @@ private static Pointer findPotentialReturnAddressPosition(Pointer originalSp) { } pos = pos.add(wordSize); } - return WordFactory.nullPointer(); + return Word.nullPointer(); } @Uninterruptible(reason = "Prevent the GC from freeing the CodeInfo.") @@ -530,7 +537,7 @@ public ErrorContext getErrorContext() { @SuppressWarnings("hiding") public boolean trySet(Log log, Pointer sp, CodePointer ip, RegisterDumper.Context registerContext, boolean frameHasCalleeSavedRegisters) { - if (diagnosticThread.compareAndSet(WordFactory.nullPointer(), CurrentIsolate.getCurrentThread())) { + if (diagnosticThread.compareAndSet(Word.nullPointer(), CurrentIsolate.getCurrentThread())) { assert diagnosticThunkIndex == -1; assert invocationCount == 0; this.log = log; @@ -549,15 +556,15 @@ public void clear() { log = null; ErrorContext errorContext = getErrorContext(); - errorContext.setStackPointer(WordFactory.nullPointer()); - errorContext.setInstructionPointer(WordFactory.nullPointer()); - errorContext.setRegisterContext(WordFactory.nullPointer()); + errorContext.setStackPointer(Word.nullPointer()); + errorContext.setInstructionPointer(Word.nullPointer()); + errorContext.setRegisterContext(Word.nullPointer()); errorContext.setFrameHasCalleeSavedRegisters(false); diagnosticThunkIndex = -1; invocationCount = 0; - diagnosticThread.set(WordFactory.nullPointer()); + diagnosticThread.set(Word.nullPointer()); } } @@ -679,7 +686,7 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev } } - private static class DumpDeoptStubPointer extends DiagnosticThunk { + static class DumpDeoptStubPointer extends DiagnosticThunk { @Override public int maxInvocationCount() { return 1; @@ -796,7 +803,7 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev } } - private static class DumpCodeCacheHistory extends DiagnosticThunk { + static class DumpCodeCacheHistory extends DiagnosticThunk { @Override public int maxInvocationCount() { return 2; @@ -810,7 +817,7 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev } } - private static class DumpRuntimeCodeInfoMemory extends DiagnosticThunk { + static class DumpRuntimeCodeInfoMemory extends DiagnosticThunk { @Override public int maxInvocationCount() { return 3; @@ -825,7 +832,7 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev } } - private static class DumpRecentDeoptimizations extends DiagnosticThunk { + static class DumpRecentDeoptimizations extends DiagnosticThunk { @Override public int maxInvocationCount() { return 1; @@ -1239,11 +1246,15 @@ public abstract static class DiagnosticThunk { } public static class DiagnosticThunkRegistry { - private DiagnosticThunk[] diagnosticThunks; + @Platforms(Platform.HOSTED_ONLY.class) // + final int runtimeCompilationPosition; + + private final List thunks = ImageHeapList.create(DiagnosticThunk.class); private int[] initialInvocationCount; @Fold public static synchronized DiagnosticThunkRegistry singleton() { + /* The registry is already used very early during the image build. */ if (!ImageSingletons.contains(DiagnosticThunkRegistry.class)) { ImageSingletons.add(DiagnosticThunkRegistry.class, new DiagnosticThunkRegistry()); } @@ -1252,7 +1263,6 @@ public static synchronized DiagnosticThunkRegistry singleton() { @Platforms(Platform.HOSTED_ONLY.class) DiagnosticThunkRegistry() { - ArrayList thunks = new ArrayList<>(); thunks.add(new DumpRegisters()); thunks.add(new DumpInstructions()); thunks.add(new DumpTopOfCurrentThreadStack()); @@ -1272,45 +1282,43 @@ public static synchronized DiagnosticThunkRegistry singleton() { if (CounterSupport.isEnabled()) { thunks.add(new DumpCounters()); } - if (RuntimeCompilation.isEnabled()) { - thunks.add(new DumpCodeCacheHistory()); - thunks.add(new DumpRuntimeCodeInfoMemory()); - thunks.add(new DumpDeoptStubPointer()); - thunks.add(new DumpRecentDeoptimizations()); - } - this.diagnosticThunks = thunks.toArray(new DiagnosticThunk[0]); - this.initialInvocationCount = new int[diagnosticThunks.length]; - Arrays.fill(initialInvocationCount, 1); + resizeInitialInvocationCount(); + this.runtimeCompilationPosition = thunks.size(); } @Platforms(Platform.HOSTED_ONLY.class) public synchronized void add(DiagnosticThunk thunk) { - diagnosticThunks = Arrays.copyOf(diagnosticThunks, diagnosticThunks.length + 1); - diagnosticThunks[diagnosticThunks.length - 1] = thunk; + thunks.add(thunk); + resizeInitialInvocationCount(); + } - initialInvocationCount = Arrays.copyOf(initialInvocationCount, initialInvocationCount.length + 1); - initialInvocationCount[initialInvocationCount.length - 1] = 1; + @Platforms(Platform.HOSTED_ONLY.class) + public synchronized void add(int insertPos, DiagnosticThunk... extraThunks) { + for (int i = 0; i < extraThunks.length; i++) { + thunks.add(insertPos + i, extraThunks[i]); + } + resizeInitialInvocationCount(); } @Platforms(Platform.HOSTED_ONLY.class) public synchronized void addAfter(DiagnosticThunk thunk, Class before) { int insertPos = indexOf(before) + 1; + assert insertPos > 0; + thunks.add(insertPos, thunk); + resizeInitialInvocationCount(); + } - DiagnosticThunk[] newThunks = new DiagnosticThunk[diagnosticThunks.length + 1]; - System.arraycopy(diagnosticThunks, 0, newThunks, 0, insertPos); - newThunks[insertPos] = thunk; - System.arraycopy(diagnosticThunks, insertPos, newThunks, insertPos + 1, diagnosticThunks.length - insertPos); - diagnosticThunks = newThunks; - - initialInvocationCount = Arrays.copyOf(initialInvocationCount, initialInvocationCount.length + 1); - initialInvocationCount[initialInvocationCount.length - 1] = 1; + @Platforms(Platform.HOSTED_ONLY.class) + private void resizeInitialInvocationCount() { + initialInvocationCount = new int[thunks.size()]; + Arrays.fill(initialInvocationCount, 1); } @Platforms(Platform.HOSTED_ONLY.class) private int indexOf(Class clazz) { - for (int i = 0; i < diagnosticThunks.length; i++) { - if (diagnosticThunks[i].getClass() == clazz) { + for (int i = 0; i < thunks.size(); i++) { + if (thunks.get(i).getClass() == clazz) { return i; } } @@ -1319,11 +1327,11 @@ private int indexOf(Class clazz) { @Fold int size() { - return diagnosticThunks.length; + return thunks.size(); } DiagnosticThunk getThunk(int index) { - return diagnosticThunks[index]; + return thunks.get(index); } int getInitialInvocationCount(int index) { @@ -1394,3 +1402,21 @@ public static boolean implicitExceptionWithoutStacktraceIsFatal() { } } } + +@AutomaticallyRegisteredFeature +class SubstrateDiagnosticsFeature implements InternalFeature { + /** + * {@link RuntimeCompilation#isEnabled()} can't be executed in the + * {@link DiagnosticThunkRegistry} constructor because the feature registration is not + * necessarily finished. So, we need to do it at a later point in time. + */ + @Override + public void afterRegistration(AfterRegistrationAccess access) { + DiagnosticThunkRegistry registry = DiagnosticThunkRegistry.singleton(); + if (RuntimeCompilation.isEnabled()) { + int pos = registry.runtimeCompilationPosition; + SubstrateDiagnostics.DiagnosticThunk[] thunks = {new DumpCodeCacheHistory(), new DumpRuntimeCodeInfoMemory(), new DumpDeoptStubPointer(), new DumpRecentDeoptimizations()}; + registry.add(pos, thunks); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateGCOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateGCOptions.java index 384d91fb9d80..982ded5cea10 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateGCOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateGCOptions.java @@ -27,8 +27,8 @@ import static com.oracle.svm.core.option.RuntimeOptionKey.RuntimeOptionKeyFlag.Immutable; import static com.oracle.svm.core.option.RuntimeOptionKey.RuntimeOptionKeyFlag.IsolateCreationOnly; +import jdk.graal.compiler.word.Word; import org.graalvm.collections.EconomicMap; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.heap.HeapSizeVerifier; import com.oracle.svm.core.option.HostedOptionKey; @@ -51,7 +51,7 @@ public class SubstrateGCOptions { @Override protected void onValueUpdate(EconomicMap, Object> values, Long oldValue, Long newValue) { if (!SubstrateUtil.HOSTED) { - HeapSizeVerifier.verifyMinHeapSizeAgainstMaxAddressSpaceSize(WordFactory.unsigned(newValue)); + HeapSizeVerifier.verifyMinHeapSizeAgainstMaxAddressSpaceSize(Word.unsigned(newValue)); } super.onValueUpdate(values, oldValue, newValue); } @@ -62,7 +62,7 @@ protected void onValueUpdate(EconomicMap, Object> values, Long oldV @Override protected void onValueUpdate(EconomicMap, Object> values, Long oldValue, Long newValue) { if (!SubstrateUtil.HOSTED) { - HeapSizeVerifier.verifyMaxHeapSizeAgainstMaxAddressSpaceSize(WordFactory.unsigned(newValue)); + HeapSizeVerifier.verifyMaxHeapSizeAgainstMaxAddressSpaceSize(Word.unsigned(newValue)); } super.onValueUpdate(values, oldValue, newValue); } @@ -73,7 +73,7 @@ protected void onValueUpdate(EconomicMap, Object> values, Long oldV @Override protected void onValueUpdate(EconomicMap, Object> values, Long oldValue, Long newValue) { if (!SubstrateUtil.HOSTED) { - HeapSizeVerifier.verifyMaxNewSizeAgainstMaxAddressSpaceSize(WordFactory.unsigned(newValue)); + HeapSizeVerifier.verifyMaxNewSizeAgainstMaxAddressSpaceSize(Word.unsigned(newValue)); } super.onValueUpdate(values, oldValue, newValue); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 33e3e3e347ab..5ca5744b6619 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -109,12 +109,27 @@ public class SubstrateOptions { @Option(help = "Build shared library")// public static final HostedOptionKey SharedLibrary = new HostedOptionKey<>(false); - @Option(help = "Persist and reload graphs across layers. If false, graphs defined in the base layer can be reparsed by the current layer and inlined before analysis, " + + @Option(help = "Persist and reload all graphs across layers. If false, graphs defined in the base layer can be reparsed by the current layer and inlined before analysis, " + "but will not be inlined after analysis has completed via our other inlining infrastructure")// public static final HostedOptionKey UseSharedLayerGraphs = new HostedOptionKey<>(true) { @Override protected void onValueUpdate(EconomicMap, Object> values, Boolean oldValue, Boolean newValue) { - NeverInline.update(values, "SubstrateStringConcatHelper.simpleConcat"); + if (!newValue) { + UseSharedLayerStrengthenedGraphs.update(values, false); + } + } + }; + + @Option(help = "Persist and reload strengthened graphs across layers. If false, inlining after analysis will be disabled")// + public static final HostedOptionKey UseSharedLayerStrengthenedGraphs = new HostedOptionKey<>(false) { + @Override + protected void onValueUpdate(EconomicMap, Object> values, Boolean oldValue, Boolean newValue) { + if (newValue) { + UserError.guarantee(UseSharedLayerStrengthenedGraphs.getValueOrDefault(values), + "UseSharedLayerStrengthenedGraph is a subset of UseSharedLayerGraphs, so the former cannot be enabled alone."); + } else { + NeverInline.update(values, "SubstrateStringConcatHelper.simpleConcat"); + } } }; @@ -130,11 +145,14 @@ protected void onValueUpdate(EconomicMap, Object> values, Boolean o } }); - // @APIOption(name = "layer-create")// + public static final String LAYER_OPTION_PREFIX = "-H:Layer"; // "--layer" + public static final String LAYER_CREATE_OPTION = LAYER_OPTION_PREFIX + "Create"; // "-create" + // @APIOption(name = LAYER_CREATE_OPTION) // use when non-experimental @Option(help = "Experimental: Build a Native Image layer.")// public static final HostedOptionKey LayerCreate = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); - // @APIOption(name = "layer-use")// + // public static final String LAYER_USE_OPTION = LAYER_OPTION_PREFIX + "-use"; + // @APIOption(name = LAYER_USE_OPTION) // use when non-experimental @Option(help = "Experimental: Build an image based on a Native Image layer.")// @BundleMember(role = Role.Input) // public static final HostedOptionKey LayerUse = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Paths.build()); @@ -1286,11 +1304,15 @@ public enum ReportingMode { @Option(help = "Include all classes, methods, fields, and resources from given paths", type = OptionType.Debug) // public static final HostedOptionKey IncludeAllFromPath = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); + @Option(help = "Include all classes, methods and fields from the given packages", type = OptionType.Debug) // + public static final HostedOptionKey IncludeAllFromPackage = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + @Option(help = "Include all classes, methods, fields, and resources from the class path", type = OptionType.Debug) // public static final HostedOptionKey IncludeAllFromClassPath = new HostedOptionKey<>(false); public static boolean includeAll() { - return IncludeAllFromModule.hasBeenSet() || IncludeAllFromPath.hasBeenSet() || IncludeAllFromClassPath.hasBeenSet(); + return IncludeAllFromModule.hasBeenSet() || IncludeAllFromPath.hasBeenSet() || IncludeAllFromPackage.hasBeenSet() || IncludeAllFromClassPath.hasBeenSet(); } @Option(help = "Force include include all public types and methods that can be reached using normal Java access rules.")// @@ -1331,10 +1353,11 @@ public static boolean useClosedTypeWorld() { public static class TruffleStableOptions { - @Option(help = "Automatically copy the necessary language resources to the resources/languages directory next to the produced image." + - "Language resources for each language are specified in the native-image-resources.filelist file located in the language home directory." + - "If there is no native-image-resources.filelist file in the language home directory or the file is empty, then no resources are copied.", type = User, stability = OptionStability.STABLE)// - public static final HostedOptionKey CopyLanguageResources = new HostedOptionKey<>(true); + @Option(help = "Automatically copy the necessary language resources to the resources directory next to the produced image.", type = User, stability = OptionStability.STABLE)// + public static final HostedOptionKey CopyLanguageResources = new HostedOptionKey<>(false); + + @Option(help = "Automatically include the necessary language internal resources in the produced image.", type = User, stability = OptionStability.STABLE)// + public static final HostedOptionKey IncludeLanguageResources = new HostedOptionKey<>(true); } @Option(help = "Reduce the amount of metadata in the image for implicit exceptions by removing inlining information from the stack trace. " + diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java index 16ee279e4e30..a6d974254765 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java @@ -41,7 +41,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.BuildPhaseProvider.ReadyForCompilation; import com.oracle.svm.core.IsolateListenerSupport.IsolateListener; @@ -120,8 +119,8 @@ public void execute(boolean isFirstIsolate) { } private static boolean isFirst() { - Word expected = WordFactory.zero(); - Word actual = SEGFAULT_HANDLER_INSTALLED.get().compareAndSwapWord(0, expected, WordFactory.unsigned(1), LocationIdentity.ANY_LOCATION); + Word expected = Word.zero(); + Word actual = SEGFAULT_HANDLER_INSTALLED.get().compareAndSwapWord(0, expected, Word.unsigned(1), LocationIdentity.ANY_LOCATION); return expected == actual; } } @@ -182,7 +181,7 @@ private static boolean tryEnterIsolateViaThreadRegister(RegisterDumper.Context c * Try to determine the isolate via the thread register. Set the thread register to null so * that we don't execute this code more than once if we trigger a recursive segfault. */ - WriteCurrentVMThreadNode.writeCurrentVMThread(WordFactory.nullPointer()); + WriteCurrentVMThreadNode.writeCurrentVMThread(Word.nullPointer()); IsolateThread isolateThread = (IsolateThread) RegisterDumper.singleton().getThreadPointer(context); if (isolateThread.isNonNull()) { @@ -203,7 +202,7 @@ private static boolean tryEnterIsolateViaHeapBaseRegister(RegisterDumper.Context * Set the heap base register to null so that we don't execute this code more than once if * we trigger a recursive segfault. */ - CEntryPointSnippets.setHeapBase(WordFactory.nullPointer()); + CEntryPointSnippets.setHeapBase(Word.nullPointer()); Isolate isolate = (Isolate) RegisterDumper.singleton().getHeapBase(context); if (isValid(isolate)) { @@ -225,7 +224,7 @@ private static boolean isValid(Isolate isolate) { * invalid value when we execute this code, which makes things a bit more complex. */ UnsignedWord staticFieldsOffsets = ReferenceAccess.singleton().getCompressedRepresentation(StaticFieldsSupport.getStaticPrimitiveFields()); - UnsignedWord wellKnownFieldOffset = staticFieldsOffsets.shiftLeft(ReferenceAccess.singleton().getCompressionShift()).add(WordFactory.unsigned(offsetOfStaticFieldWithWellKnownValue)); + UnsignedWord wellKnownFieldOffset = staticFieldsOffsets.shiftLeft(ReferenceAccess.singleton().getCompressionShift()).add(Word.unsigned(offsetOfStaticFieldWithWellKnownValue)); Pointer wellKnownField = ((Pointer) isolate).add(wellKnownFieldOffset); return wellKnownField.readLong(0) == MARKER_VALUE; } @@ -246,7 +245,7 @@ public static void enterIsolateAsyncSignalSafe(Isolate isolate) { */ int isolateThreadSize = VMThreadLocalSupport.singleton().vmThreadSize; IsolateThread structForUnattachedThread = UnsafeLateStackValue.get(isolateThreadSize); - UnmanagedMemoryUtil.fill((Pointer) structForUnattachedThread, WordFactory.unsigned(isolateThreadSize), (byte) 0); + UnmanagedMemoryUtil.fill((Pointer) structForUnattachedThread, Word.unsigned(isolateThreadSize), (byte) 0); CEntryPointSnippets.initializeIsolateThreadForCrashHandler(isolate, structForUnattachedThread); } } @@ -321,9 +320,9 @@ public static SingleIsolateSegfaultSetup singleton() { @Override @Uninterruptible(reason = "Thread state not yet set up.") public void afterCreateIsolate(Isolate isolate) { - PointerBase value = baseIsolate.get().compareAndSwapWord(0, WordFactory.zero(), isolate, LocationIdentity.ANY_LOCATION); + PointerBase value = baseIsolate.get().compareAndSwapWord(0, Word.zero(), isolate, LocationIdentity.ANY_LOCATION); if (!value.isNull()) { - baseIsolate.get().writeWord(0, WordFactory.signed(-1)); + baseIsolate.get().writeWord(0, Word.signed(-1)); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java index e220a2b68f4c..d7459d547695 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java @@ -38,6 +38,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.type.CCharPointer; @@ -45,12 +46,12 @@ import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.util.HostedSubstrateUtil; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.StringUtil; @@ -183,7 +184,7 @@ public static String[] convertCToJavaArgs(int argc, CCharPointerPointer argv) { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static UnsignedWord strlen(CCharPointer str) { - UnsignedWord n = WordFactory.zero(); + UnsignedWord n = Word.zero(); while (((Pointer) str).readByte(n) != 0) { n = n.add(1); } @@ -202,7 +203,7 @@ public static CCharPointer strchr(CCharPointer str, int c) { return str.addressOf(index); } if (b == 0) { - return WordFactory.zero(); + return Word.zero(); } index += 1; } @@ -376,12 +377,14 @@ public static String defaultUniqueShortName(Member m) { * @return A unique identifier for the classloader or the empty string when the loader is one of * the special set whose method names do not need qualification. */ - public static String classLoaderNameAndId(ClassLoader loader) { - if (loader == null) { + public static String runtimeClassLoaderNameAndId(ClassLoader loader) { + ClassLoader runtimeClassLoader = SubstrateUtil.HOSTED ? HostedSubstrateUtil.getRuntimeClassLoader(loader) : loader; + + if (runtimeClassLoader == null) { return ""; } try { - return (String) classLoaderNameAndId.get(loader); + return (String) classLoaderNameAndId.get(runtimeClassLoader); } catch (IllegalAccessException e) { throw VMError.shouldNotReachHere("Cannot reflectively access ClassLoader.nameAndId"); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UniqueShortNameProviderDefaultImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UniqueShortNameProviderDefaultImpl.java index 247cd2b92b10..6522e7ff0f8c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UniqueShortNameProviderDefaultImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UniqueShortNameProviderDefaultImpl.java @@ -42,7 +42,7 @@ public class UniqueShortNameProviderDefaultImpl implements UniqueShortNameProvider { @Override public String uniqueShortName(ClassLoader loader, ResolvedJavaType declaringClass, String methodName, Signature methodSignature, boolean isConstructor) { - return SubstrateUtil.defaultUniqueShortName(SubstrateUtil.classLoaderNameAndId(loader), declaringClass, methodName, methodSignature, isConstructor); + return SubstrateUtil.defaultUniqueShortName(SubstrateUtil.runtimeClassLoaderNameAndId(loader), declaringClass, methodName, methodSignature, isConstructor); } @Override @@ -52,7 +52,7 @@ public String uniqueShortName(Member m) { @Override public String uniqueShortLoaderName(ClassLoader classLoader) { - return SubstrateUtil.classLoaderNameAndId(classLoader); + return SubstrateUtil.runtimeClassLoaderNameAndId(classLoader); } public static class UseDefault implements BooleanSupplier { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UnmanagedMemoryUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UnmanagedMemoryUtil.java index ac7ed1797c47..f31ba4a5bbed 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UnmanagedMemoryUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UnmanagedMemoryUtil.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core; +import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.config.ConfigurationValues; @@ -76,7 +76,7 @@ public static void copy(Pointer from, Pointer to, UnsignedWord size) { @IntrinsicCandidate @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void copyForward(Pointer from, Pointer to, UnsignedWord size) { - UnsignedWord alignBits = WordFactory.unsigned(0x7); + UnsignedWord alignBits = Word.unsigned(0x7); UnsignedWord alignedSize = size.and(alignBits.not()); copyLongsForward(from, to, alignedSize); @@ -105,7 +105,7 @@ public static void copyForward(Pointer from, Pointer to, UnsignedWord size) { @IntrinsicCandidate @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void copyBackward(Pointer from, Pointer to, UnsignedWord size) { - UnsignedWord alignBits = WordFactory.unsigned(0x7); + UnsignedWord alignBits = Word.unsigned(0x7); UnsignedWord alignedSize = size.and(alignBits.not()); UnsignedWord unalignedSize = size.subtract(alignedSize); copyLongsBackward(from.add(unalignedSize), to.add(unalignedSize), alignedSize); @@ -135,7 +135,7 @@ public static void copyBackward(Pointer from, Pointer to, UnsignedWord size) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void copyLongsForward(Pointer from, Pointer to, UnsignedWord size) { assert size.unsignedRemainder(8).equal(0); - UnsignedWord offset = WordFactory.zero(); + UnsignedWord offset = Word.zero(); for (UnsignedWord next = offset.add(32); next.belowOrEqual(size); next = offset.add(32)) { Pointer src = from.add(offset); Pointer dst = to.add(offset); @@ -180,7 +180,7 @@ public static void copyWordsForward(Pointer from, Pointer to, UnsignedWord size) } while (src.belowThan(srcEnd)) { - dst.writeWord(WordFactory.zero(), src.readWord(WordFactory.zero())); + dst.writeWord(Word.zero(), src.readWord(Word.zero())); src = src.add(wordSize); dst = dst.add(wordSize); } @@ -219,7 +219,7 @@ public static void copyLongsBackward(Pointer from, Pointer to, UnsignedWord size @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static UnsignedWord compareLongs(Pointer x, Pointer y, UnsignedWord size) { assert size.unsignedRemainder(8).equal(0); - UnsignedWord offset = WordFactory.zero(); + UnsignedWord offset = Word.zero(); while (offset.belowThan(size)) { if (x.readLong(offset) != y.readLong(offset)) { return offset; @@ -231,7 +231,7 @@ private static UnsignedWord compareLongs(Pointer x, Pointer y, UnsignedWord size @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static UnsignedWord compareBytes(Pointer x, Pointer y, UnsignedWord size) { - UnsignedWord offset = WordFactory.zero(); + UnsignedWord offset = Word.zero(); while (offset.belowThan(size)) { if (x.readByte(offset) != y.readByte(offset)) { return offset; @@ -247,7 +247,7 @@ private static UnsignedWord compareBytes(Pointer x, Pointer y, UnsignedWord size */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static UnsignedWord compare(Pointer x, Pointer y, UnsignedWord size) { - UnsignedWord alignBits = WordFactory.unsigned(0x7); + UnsignedWord alignBits = Word.unsigned(0x7); UnsignedWord alignedSize = size.and(alignBits.not()); UnsignedWord offset = compareLongs(x, y, alignedSize); return offset.add(compareBytes(x.add(offset), y.add(offset), size.subtract(offset))); @@ -265,7 +265,7 @@ public static void fill(Pointer to, UnsignedWord size, byte value) { v = (v << 16) | v; v = (v << 32) | v; - UnsignedWord alignBits = WordFactory.unsigned(0x7); + UnsignedWord alignBits = Word.unsigned(0x7); UnsignedWord alignedSize = size.and(alignBits.not()); fillLongs(to, alignedSize, v); @@ -294,7 +294,7 @@ public static void fill(Pointer to, UnsignedWord size, byte value) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void fillLongs(Pointer to, UnsignedWord size, long longValue) { assert size.unsignedRemainder(8).equal(0); - UnsignedWord offset = WordFactory.zero(); + UnsignedWord offset = Word.zero(); for (UnsignedWord next = offset.add(32); next.belowOrEqual(size); next = offset.add(32)) { Pointer p = to.add(offset); p.writeLong(0, longValue); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UnsafeMemoryUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UnsafeMemoryUtil.java index b328ec874ee3..f86e4744e531 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UnsafeMemoryUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UnsafeMemoryUtil.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.impl.InternalPlatform; import org.graalvm.nativeimage.impl.UnsafeMemorySupport; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; @@ -39,9 +39,9 @@ public class UnsafeMemoryUtil implements UnsafeMemorySupport { @Override public void unsafeCopyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes) { if (srcBase != null || destBase != null) { - JavaMemoryUtil.copyOnHeap(srcBase, WordFactory.unsigned(srcOffset), destBase, WordFactory.unsigned(destOffset), WordFactory.unsigned(bytes)); + JavaMemoryUtil.copyOnHeap(srcBase, Word.unsigned(srcOffset), destBase, Word.unsigned(destOffset), Word.unsigned(bytes)); } else { - UnmanagedMemoryUtil.copy(WordFactory.pointer(srcOffset), WordFactory.pointer(destOffset), WordFactory.unsigned(bytes)); + UnmanagedMemoryUtil.copy(Word.pointer(srcOffset), Word.pointer(destOffset), Word.unsigned(bytes)); } } @@ -52,7 +52,7 @@ public void unsafeSetMemory(Object destBase, long destOffset, long bytes, byte b if (destBase != null) { JavaMemoryUtil.fillOnHeap(destBase, destOffset, bytes, bvalue); } else { - JavaMemoryUtil.fill(WordFactory.pointer(destOffset), WordFactory.unsigned(bytes), bvalue); + JavaMemoryUtil.fill(Word.pointer(destOffset), Word.unsigned(bytes), bvalue); } } @@ -62,7 +62,7 @@ public void unsafeCopySwapMemory(Object srcBase, long srcOffset, Object destBase if (srcBase != null || destBase != null) { JavaMemoryUtil.copySwapOnHeap(srcBase, srcOffset, destBase, destOffset, bytes, elemSize); } else { - JavaMemoryUtil.copySwap(WordFactory.unsigned(srcOffset), WordFactory.unsigned(destOffset), WordFactory.unsigned(bytes), WordFactory.unsigned(elemSize)); + JavaMemoryUtil.copySwap(Word.unsigned(srcOffset), Word.unsigned(destOffset), Word.unsigned(bytes), Word.unsigned(elemSize)); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/aarch64/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/aarch64/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/aarch64/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/allocationprofile/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/allocationprofile/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/allocationprofile/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/amd64/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/amd64/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/amd64/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/attach/AttachListenerThread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/attach/AttachListenerThread.java index b824b39dd37d..20f9b2923249 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/attach/AttachListenerThread.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/attach/AttachListenerThread.java @@ -44,7 +44,6 @@ * A dedicated listener thread that accepts client connections and that handles diagnostic command * requests. At the moment, only jcmd is supported. */ -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/services/attachListener.cpp#L453-L467") public abstract class AttachListenerThread extends Thread { private static final String JCMD_COMMAND_STRING = "jcmd"; @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/services/attachListener.hpp#L142") // @@ -55,7 +54,7 @@ public abstract class AttachListenerThread extends Thread { protected static final int ARG_COUNT_MAX = 3; @SuppressWarnings("this-escape") - // This constructor should be annotated with @BasedOnJDK instead of the class, see GR-59171. + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/services/attachListener.cpp#L453-L467") public AttachListenerThread() { super(PlatformThreads.singleton().systemGroup, "Attach Listener"); this.setDaemon(true); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/attach/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/attach/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/attach/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CIsolateDataFactory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CIsolateDataFactory.java index 2e078c643823..4e266b0a443f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CIsolateDataFactory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CIsolateDataFactory.java @@ -29,7 +29,6 @@ import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; @Platforms(Platform.HOSTED_ONLY.class) public final class CIsolateDataFactory { @@ -38,10 +37,6 @@ public static CIsolateData createStruct(String name, return create(name, SizeOf.unsigned(clazz)); } - public static CIsolateData create(String name, int size) { - return create(name, WordFactory.unsigned(size)); - } - public static CIsolateData create(String name, UnsignedWord size) { return new CIsolateData<>(name, size.rawValue()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CIsolateDataStorage.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CIsolateDataStorage.java index e89b2ebb7562..53053edbb334 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CIsolateDataStorage.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CIsolateDataStorage.java @@ -40,7 +40,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import jdk.vm.ci.meta.JavaKind; @@ -67,7 +66,7 @@ public CIsolateDataStorage() { @Platforms(Platform.HOSTED_ONLY.class) public void setSize(UnsignedWord size) { assert managedIsolateSectionData == null; - UnsignedWord allocationSize = size.add(WordFactory.unsigned(ALIGNMENT - 1)); + UnsignedWord allocationSize = size.add(Word.unsigned(ALIGNMENT - 1)); managedIsolateSectionData = new byte[UnsignedUtils.safeToInt(allocationSize)]; } @@ -79,7 +78,7 @@ public static CIsolateDataStorage singleton() { @Fold protected static UnsignedWord arrayBaseOffset() { int offset = ConfigurationValues.getObjectLayout().getArrayBaseOffset(JavaKind.Byte); - return UnsignedUtils.roundUp(WordFactory.unsigned(offset), WordFactory.unsigned(ALIGNMENT)); + return UnsignedUtils.roundUp(Word.unsigned(offset), Word.unsigned(ALIGNMENT)); } @SuppressWarnings("unchecked") diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CTypeConversionSupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CTypeConversionSupportImpl.java index 5a0c9b172ce6..28444296e3a9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CTypeConversionSupportImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CTypeConversionSupportImpl.java @@ -29,6 +29,7 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CCharPointerPointer; @@ -38,7 +39,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.config.ConfigurationValues; @@ -52,7 +52,7 @@ class CTypeConversionSupportImpl implements CTypeConversionSupport { static final CCharPointerHolder NULL_HOLDER = new CCharPointerHolder() { @Override public CCharPointer get() { - return WordFactory.nullPointer(); + return Word.nullPointer(); } @Override @@ -64,7 +64,7 @@ public void close() { static final CCharPointerPointerHolder NULL_POINTER_POINTER_HOLDER = new CCharPointerPointerHolder() { @Override public CCharPointerPointer get() { - return WordFactory.nullPointer(); + return Word.nullPointer(); } @Override @@ -144,7 +144,7 @@ public UnsignedWord toCString(CharSequence javaString, Charset charset, CCharPoi if (capacity != 0) { throw new IllegalArgumentException("Non zero buffer size passed along with nullptr"); } - return WordFactory.unsigned(baseString.length); + return Word.unsigned(baseString.length); } else if (capacity < baseString.length + 1) { throw new IllegalArgumentException("Provided buffer is too small to hold 0 terminated java string."); @@ -157,7 +157,7 @@ public UnsignedWord toCString(CharSequence javaString, Charset charset, CCharPoi // write null terminator at end buffer.write(baseString.length, (byte) 0); - return WordFactory.unsigned(baseString.length); + return Word.unsigned(baseString.length); } @Override @@ -235,7 +235,7 @@ final class CCharPointerPointerHolderImpl extends CCharPointerPointerHolder { ccpArray[i] = ccpHolderArray[i].get(); } /* Null-terminate the CCharPointer[]. */ - ccpArray[csArray.length] = WordFactory.nullPointer(); + ccpArray[csArray.length] = Word.nullPointer(); /* Pin the CCharPointer[] so I can get the &ccpArray[0]. */ refCCPArray = PrimitiveArrayView.createForReading(ccpArray); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/NonmovableArrays.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/NonmovableArrays.java index f646411e321c..fe20441cf3e2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/NonmovableArrays.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/NonmovableArrays.java @@ -35,7 +35,6 @@ import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.JavaMemoryUtil; import com.oracle.svm.core.SubstrateUtil; @@ -148,7 +147,7 @@ public static int lengthOf(NonmovableArray array) { /** Provides the size of the given array in bytes. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static UnsignedWord byteSizeOf(NonmovableArray array) { - return array.isNonNull() ? LayoutEncoding.getArrayAllocationSize(readLayoutEncoding(array), lengthOf(array)) : WordFactory.zero(); + return array.isNonNull() ? LayoutEncoding.getArrayAllocationSize(readLayoutEncoding(array), lengthOf(array)) : Word.zero(); } /** @see System#arraycopy */ @@ -160,7 +159,7 @@ public static void arraycopy(NonmovableArray src, int srcPos, NonmovableArray } assert srcPos >= 0 && destPos >= 0 && length >= 0 && srcPos + length <= lengthOf(src) && destPos + length <= lengthOf(dest); assert readHub(src) == readHub(dest) : "copying is only allowed with same component types"; - UnmanagedMemoryUtil.copy(addressOf(src, srcPos), addressOf(dest, destPos), WordFactory.unsigned(length << readElementShift(dest))); + UnmanagedMemoryUtil.copy(addressOf(src, srcPos), addressOf(dest, destPos), Word.unsigned(length << readElementShift(dest))); } /** Provides an array for which {@link NonmovableArray#isNull()} returns {@code true}. */ @@ -170,7 +169,7 @@ public static > T nullArray() { if (SubstrateUtil.HOSTED) { return (T) HOSTED_NULL_VALUE; } - return WordFactory.nullPointer(); + return Word.nullPointer(); } /** @@ -286,7 +285,7 @@ private static T arraycopyToHeap(NonmovableArray src, int srcPos, T dest, Pointer destAddressAtPos = Word.objectToUntrackedPointer(dest).add(LayoutEncoding.getArrayElementOffset(destHub.getLayoutEncoding(), destPos)); if (LayoutEncoding.isPrimitiveArray(destHub.getLayoutEncoding())) { Pointer srcAddressAtPos = addressOf(src, srcPos); - JavaMemoryUtil.copyPrimitiveArrayForward(srcAddressAtPos, destAddressAtPos, WordFactory.unsigned(length << readElementShift(src))); + JavaMemoryUtil.copyPrimitiveArrayForward(srcAddressAtPos, destAddressAtPos, Word.unsigned(length << readElementShift(src))); } else { // needs barriers Object[] destArr = (Object[]) dest; for (int i = 0; i < length; i++) { @@ -322,7 +321,7 @@ public static NonmovableArray fromImageHeap(Object array) { return new HostedNonmovableArray<>(array); } assert array == null || Heap.getHeap().isInImageHeap(array); - return (array != null) ? (NonmovableArray) Word.objectToUntrackedPointer(array) : WordFactory.nullPointer(); + return (array != null) ? (NonmovableArray) Word.objectToUntrackedPointer(array) : Word.nullPointer(); } /** Returns a {@link NonmovableObjectArray} for an object array in the image heap. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/ProjectHeaderFile.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/ProjectHeaderFile.java index 72046725340a..6083afc72fa0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/ProjectHeaderFile.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/ProjectHeaderFile.java @@ -127,10 +127,12 @@ public String resolve(String projectName, String headerFile) { } /* If the header was not found at any of the specified locations an error is thrown. */ - throw VMError.shouldNotReachHere("Header file " + headerFile + - " not found at main search location(s): \n" + String.join("\n", mainResult.locations) + - (fallbackLocations.size() > 0 ? "\n or any of the fallback locations: \n" + String.join("\n", fallbackLocations) : "") + - "\n Use option -H:CLibraryPath to specify header file search locations."); + throw VMError.shouldNotReachHere("Header file " + headerFile + " not found at main search location(s): " + + System.lineSeparator() + String.join(System.lineSeparator(), mainResult.locations) + + (!fallbackLocations.isEmpty() + ? System.lineSeparator() + " or any of the fallback locations: " + System.lineSeparator() + String.join(System.lineSeparator(), fallbackLocations) + : "") + + System.lineSeparator() + " Use option -H:CLibraryPath to specify header file search locations."); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/UnmanagedPrimitiveArrays.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/UnmanagedPrimitiveArrays.java index 251b914c27ec..3d6932283d80 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/UnmanagedPrimitiveArrays.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/UnmanagedPrimitiveArrays.java @@ -27,7 +27,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.JavaMemoryUtil; import com.oracle.svm.core.SubstrateUtil; @@ -61,7 +60,7 @@ public final class UnmanagedPrimitiveArrays { public static > T createArray(int length, Class arrayType, NmtCategory nmtCategory) { DynamicHub hub = SubstrateUtil.cast(arrayType, DynamicHub.class); VMError.guarantee(LayoutEncoding.isPrimitiveArray(hub.getLayoutEncoding())); - UnsignedWord size = WordFactory.unsigned(length).shiftLeft(LayoutEncoding.getArrayIndexShift(hub.getLayoutEncoding())); + UnsignedWord size = Word.unsigned(length).shiftLeft(LayoutEncoding.getArrayIndexShift(hub.getLayoutEncoding())); Pointer array = NullableNativeMemory.calloc(size, nmtCategory); if (array.isNull()) { throw OUT_OF_MEMORY_ERROR; @@ -107,7 +106,7 @@ public static T copyToHeap(UnmanagedPrimitiveArray src, int srcPos, T des VMError.guarantee(srcPos >= 0 && destPos >= 0 && length >= 0 && destPos + length <= ArrayLengthNode.arrayLength(dest)); Pointer destAddressAtPos = Word.objectToUntrackedPointer(dest).add(LayoutEncoding.getArrayElementOffset(destHub.getLayoutEncoding(), destPos)); Pointer srcAddressAtPos = getAddressOf(src, destHub.getLayoutEncoding(), srcPos); - JavaMemoryUtil.copyPrimitiveArrayForward(srcAddressAtPos, destAddressAtPos, WordFactory.unsigned(length).shiftLeft(LayoutEncoding.getArrayIndexShift(destHub.getLayoutEncoding()))); + JavaMemoryUtil.copyPrimitiveArrayForward(srcAddressAtPos, destAddressAtPos, Word.unsigned(length).shiftLeft(LayoutEncoding.getArrayIndexShift(destHub.getLayoutEncoding()))); return dest; } @@ -119,13 +118,13 @@ public static T compareOrCopyToHeap(UnmanagedPrimitiveArray src, int srcP Pointer destAddressAtPos = Word.objectToUntrackedPointer(dest).add(LayoutEncoding.getArrayElementOffset(destHub.getLayoutEncoding(), destPos)); Pointer srcAddressAtPos = getAddressOf(src, destHub.getLayoutEncoding(), srcPos); - UnsignedWord size = WordFactory.unsigned(length).shiftLeft(LayoutEncoding.getArrayIndexShift(destHub.getLayoutEncoding())); + UnsignedWord size = Word.unsigned(length).shiftLeft(LayoutEncoding.getArrayIndexShift(destHub.getLayoutEncoding())); // First compare until a difference is found UnsignedWord equalBytes = UnmanagedMemoryUtil.compare(srcAddressAtPos, destAddressAtPos, size); if (equalBytes.belowThan(size)) { // If the two arrays are not equal, copy over the remaining bytes - UnsignedWord alignBits = WordFactory.unsigned(0x7); + UnsignedWord alignBits = Word.unsigned(0x7); UnsignedWord offset = equalBytes.and(alignBits.not()); JavaMemoryUtil.copyPrimitiveArrayForward(srcAddressAtPos.add(offset), destAddressAtPos.add(offset), size.subtract(offset)); } @@ -140,7 +139,7 @@ public static UnmanagedPrimitiveArray copyFromHeap(T src, int srcPos, Unm VMError.guarantee(srcPos >= 0 && destPos >= 0 && length >= 0 && srcPos + length <= ArrayLengthNode.arrayLength(src)); Pointer srcAddressAtPos = Word.objectToUntrackedPointer(src).add(LayoutEncoding.getArrayElementOffset(srcHub.getLayoutEncoding(), srcPos)); Pointer destAddressAtPos = getAddressOf(dest, srcHub.getLayoutEncoding(), destPos); - JavaMemoryUtil.copyPrimitiveArrayForward(srcAddressAtPos, destAddressAtPos, WordFactory.unsigned(length).shiftLeft(LayoutEncoding.getArrayIndexShift(srcHub.getLayoutEncoding()))); + JavaMemoryUtil.copyPrimitiveArrayForward(srcAddressAtPos, destAddressAtPos, Word.unsigned(length).shiftLeft(LayoutEncoding.getArrayIndexShift(srcHub.getLayoutEncoding()))); return dest; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumRuntimeData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumRuntimeData.java index 5be2efeaedf5..48bbbfbf56ac 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumRuntimeData.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumRuntimeData.java @@ -24,9 +24,9 @@ */ package com.oracle.svm.core.c.enums; +import jdk.graal.compiler.word.Word; import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jdk.UninterruptibleUtils.CodeUtil; @@ -82,13 +82,13 @@ public final long enumToLong(Enum value) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public final SignedWord enumToSignedWord(Enum value) { long result = enumToLong(value); - return WordFactory.signed(result); + return Word.signed(result); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public final UnsignedWord enumToUnsignedWord(Enum value) { long result = CodeUtil.zeroExtend(enumToLong(value), bytesInC * Byte.SIZE); - return WordFactory.unsigned(result); + return Word.unsigned(result); } /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointActions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointActions.java index 003ff67acf9f..3d6e9458e46f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointActions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointActions.java @@ -24,11 +24,11 @@ */ package com.oracle.svm.core.c.function; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Isolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.c.function.CEntryPoint; import org.graalvm.nativeimage.c.type.CCharPointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.thread.PlatformThreads; @@ -124,7 +124,7 @@ private CEntryPointActions() { * * @param code An integer representing the cause (should be non-zero by convention). * @param message A message describing the cause (may be omitted by passing - * {@link WordFactory#nullPointer() null}). + * {@link Word#nullPointer() null}). */ public static native void failFatally(int code, CCharPointer message); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointBuiltins.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointBuiltins.java index bc36b0de2738..7e6957f6329a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointBuiltins.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointBuiltins.java @@ -29,13 +29,13 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; 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.Publish; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.function.CEntryPointOptions.NoEpilogue; @@ -61,9 +61,9 @@ public final class CEntryPointBuiltins { @CEntryPointOptions(prologue = NoPrologue.class, epilogue = NoEpilogue.class) @CEntryPointBuiltinImplementation(builtin = Builtin.CREATE_ISOLATE) public static IsolateThread createIsolate() { - int status = CEntryPointActions.enterCreateIsolate(WordFactory.nullPointer()); + int status = CEntryPointActions.enterCreateIsolate(Word.nullPointer()); if (status != 0) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } IsolateThread result = CurrentIsolate.getCurrentThread(); @@ -78,7 +78,7 @@ public static IsolateThread createIsolate() { public static IsolateThread attachThread(Isolate isolate) { int status = CEntryPointActions.enterAttachThread(isolate, false, true); if (status != 0) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } IsolateThread result = CurrentIsolate.getCurrentThread(); @@ -93,7 +93,7 @@ public static IsolateThread attachThread(Isolate isolate) { public static IsolateThread getCurrentThread(Isolate isolate) { int status = CEntryPointActions.enterByIsolate(isolate); if (status != 0) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } IsolateThread thread = CurrentIsolate.getCurrentThread(); @@ -108,7 +108,7 @@ public static IsolateThread getCurrentThread(Isolate isolate) { public static Isolate getIsolate(IsolateThread thread) { int status = CEntryPointActions.enter(thread); if (status != 0) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } Isolate isolate = CurrentIsolate.getIsolate(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointErrors.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointErrors.java index 6a5ee2608944..8fd66dc87389 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointErrors.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointErrors.java @@ -36,12 +36,12 @@ import java.util.Arrays; import java.util.Objects; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CGlobalData; @@ -219,7 +219,7 @@ private static Pointer getDescriptionAsCString(int code, Pointer cstrings) { } offset++; } - return WordFactory.nullPointer(); + return Word.nullPointer(); } private static final String[] DESCRIPTIONS; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointNativeFunctions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointNativeFunctions.java index 34c8b3dde8e2..082d5460bb2e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointNativeFunctions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointNativeFunctions.java @@ -26,6 +26,7 @@ import java.util.function.Function; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.Isolate; import org.graalvm.nativeimage.IsolateThread; @@ -33,7 +34,6 @@ import org.graalvm.nativeimage.c.function.CEntryPoint; import org.graalvm.nativeimage.c.struct.CPointerTo; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; @@ -117,7 +117,7 @@ public static int attachThread(Isolate isolate, IsolateThreadPointer thread) { public static IsolateThread getCurrentThread(Isolate isolate) { int status = CEntryPointActions.enterByIsolate(isolate); if (status != 0) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } IsolateThread thread = CurrentIsolate.getCurrentThread(); @@ -136,7 +136,7 @@ public static Isolate getIsolate(IsolateThread thread) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static Isolate getIsolateOf(IsolateThread thread) { - Isolate isolate = WordFactory.nullPointer(); + Isolate isolate = Word.nullPointer(); if (thread.isNonNull()) { isolate = VMThreads.IsolateTL.get(thread); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointOptions.java index 2f96df732f2a..4bcb1d7ef0fe 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointOptions.java @@ -30,9 +30,9 @@ import java.lang.annotation.Target; import java.util.function.Function; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.function.CEntryPoint; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; @@ -106,7 +106,7 @@ final class ReturnNullPointer implements PrologueBailout { @SuppressWarnings("unused") @Uninterruptible(reason = "Thread state not set up yet.") public static PointerBase bailout(int prologueResult) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointSetup.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointSetup.java index fcedac498ae9..0c31edbaf1f0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointSetup.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointSetup.java @@ -24,9 +24,9 @@ */ package com.oracle.svm.core.c.function; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.c.type.CCharPointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CGlobalData; @@ -52,7 +52,7 @@ public static final class EnterCreateIsolatePrologue implements CEntryPointOptio @Uninterruptible(reason = "prologue") public static void enter() { - int code = CEntryPointActions.enterCreateIsolate(WordFactory.nullPointer()); + int code = CEntryPointActions.enterCreateIsolate(Word.nullPointer()); if (code != CEntryPointErrors.NO_ERROR) { CEntryPointActions.failFatally(code, errorMessage.get()); } 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 90777b9a6100..e0dc0b6165eb 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 @@ -28,6 +28,7 @@ import com.oracle.svm.core.c.CGlobalData; import com.oracle.svm.core.c.CGlobalDataFactory; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Isolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Isolates.CreateIsolateParameters; @@ -38,7 +39,6 @@ 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; import com.oracle.svm.core.c.function.CEntryPointNativeFunctions.IsolateThreadPointer; @@ -85,7 +85,7 @@ public static IsolateThread createIsolate(CreateIsolateParameters parameters, bo // Prepare argc and argv. int argc = 0; - CCharPointerPointer argv = WordFactory.nullPointer(); + CCharPointerPointer argv = Word.nullPointer(); List args = parameters.getArguments(); CTypeConversion.CCharPointerHolder[] pointerHolders = null; @@ -96,7 +96,7 @@ public static IsolateThread createIsolate(CreateIsolateParameters parameters, bo // the name of the binary. We use null when isolates are created manually. argc = isolateArgCount + 1; argv = NativeMemory.malloc(SizeOf.unsigned(CCharPointerPointer.class).multiply(argc), NmtCategory.Internal); - argv.write(0, WordFactory.nullPointer()); + argv.write(0, Word.nullPointer()); pointerHolders = new CTypeConversion.CCharPointerHolder[isolateArgCount]; for (int i = 0; i < isolateArgCount; i++) { @@ -119,7 +119,7 @@ public static IsolateThread createIsolate(CreateIsolateParameters parameters, bo // Try to create the isolate. IsolateThreadPointer isolateThreadPtr = UnsafeStackValue.get(IsolateThreadPointer.class); - int result = CEntryPointNativeFunctions.createIsolate(params, WordFactory.nullPointer(), isolateThreadPtr); + int result = CEntryPointNativeFunctions.createIsolate(params, Word.nullPointer(), isolateThreadPtr); IsolateThread isolateThread = isolateThreadPtr.read(); // Cleanup all native memory related to argv. @@ -173,7 +173,7 @@ private static void throwOnError(int code) { } } - private static final CGlobalData nextIsolateId = CGlobalDataFactory.createWord((Pointer) WordFactory.unsigned(1L)); + private static final CGlobalData nextIsolateId = CGlobalDataFactory.createWord((Pointer) Word.unsigned(1L)); private volatile long isolateId = 0; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/struct/OffsetOf.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/struct/OffsetOf.java index 22691b3cceb1..f877b14075fc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/struct/OffsetOf.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/struct/OffsetOf.java @@ -24,12 +24,12 @@ */ package com.oracle.svm.core.c.struct; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.struct.CStruct; import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; /** * Supplies static methods that provide access to the offset of fields of {@link CStruct} and @@ -48,6 +48,6 @@ public static int get(Class clazz, String fieldName) { } public static UnsignedWord unsigned(Class clazz, String fieldName) { - return WordFactory.unsigned(get(clazz, fieldName)); + return Word.unsigned(get(clazz, fieldName)); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java index 1f1c1338f952..258dd7436253 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java @@ -27,12 +27,13 @@ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; +import com.oracle.svm.core.hub.RuntimeClassLoading; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.function.CFunctionPointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.FunctionPointerHolder; import com.oracle.svm.core.c.InvokeJavaFunctionPointer; @@ -232,6 +233,19 @@ public ClassInitializationInfo(CFunctionPointer classInitializer, boolean typeRe this.hasInitializer = classInitializer != null; } + public ClassInitializationInfo(boolean typeReachedTracked) { + assert RuntimeClassLoading.isSupported(); + + this.classInitializer = null; + this.hasInitializer = true; + + // GR-59739: Needs a new state "Loaded". + this.initState = InitState.Linked; + this.typeReached = typeReachedTracked ? TypeReached.NOT_REACHED : TypeReached.UNTRACKED; + this.slowPathRequired = true; + this.initLock = new ReentrantLock(); + } + public boolean hasInitializer() { return hasInitializer; } @@ -542,7 +556,7 @@ private void setInitializationStateAndNotify(InitState state) { if (initState == InitState.FullyInitialized) { this.slowPathRequired = false; } - this.initThread = WordFactory.nullPointer(); + this.initThread = Word.nullPointer(); /* Make sure previous stores are all done, notably the initState. */ Unsafe.getUnsafe().storeFence(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/AbstractRuntimeCodeInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/AbstractRuntimeCodeInstaller.java index 670cde302cb2..694377ba3ceb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/AbstractRuntimeCodeInstaller.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/AbstractRuntimeCodeInstaller.java @@ -24,12 +24,12 @@ */ package com.oracle.svm.core.code; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.deopt.SubstrateInstalledCode; import com.oracle.svm.core.heap.VMOperationInfos; @@ -39,7 +39,7 @@ public class AbstractRuntimeCodeInstaller { protected Pointer allocateCodeMemory(long size) { - PointerBase result = RuntimeCodeInfoAccess.allocateCodeMemory(WordFactory.unsigned(size)); + PointerBase result = RuntimeCodeInfoAccess.allocateCodeMemory(Word.unsigned(size)); if (result.isNull()) { throw new OutOfMemoryError("Could not allocate memory for runtime-compiled code."); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java index 719b86f47663..d3eab8589ab2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core.code; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.RuntimeAssertionsSupport; @@ -249,7 +249,7 @@ public static long relativeIP(CodeInfo info, CodePointer ip) { } public static CodePointer absoluteIP(CodeInfo info, long relativeIP) { - return (CodePointer) ((UnsignedWord) cast(info).getCodeStart()).add(WordFactory.unsigned(relativeIP)); + return (CodePointer) ((UnsignedWord) cast(info).getCodeStart()).add(Word.unsigned(relativeIP)); } @SuppressWarnings("unchecked") diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java index a471ab0ce67a..cc9874230e81 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java @@ -34,12 +34,12 @@ import java.util.function.IntFunction; import java.util.stream.Stream; +import jdk.graal.compiler.word.Word; import org.graalvm.collections.EconomicSet; import org.graalvm.collections.Equivalence; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.CalleeSavedRegisters; import com.oracle.svm.core.ReservedRegisters; @@ -673,7 +673,7 @@ void verifyMethod(SharedMethod method, CompilationResult compilation, int compil CodeInfoAccess.lookupCodeInfo(info, offset + compilationOffset, queryResult, constantAccess); CollectingObjectReferenceVisitor visitor = new CollectingObjectReferenceVisitor(); - CodeReferenceMapDecoder.walkOffsetsFromPointer(WordFactory.zero(), CodeInfoAccess.getStackReferenceMapEncoding(info), queryResult.getReferenceMapIndex(), visitor, null); + CodeReferenceMapDecoder.walkOffsetsFromPointer(Word.zero(), CodeInfoAccess.getStackReferenceMapEncoding(info), queryResult.getReferenceMapIndex(), visitor, null); ReferenceMapEncoder.Input expected = (ReferenceMapEncoder.Input) infopoint.debugInfo.getReferenceMap(); visitor.result.verify(); assert expected.equals(visitor.result) : infopoint; @@ -791,7 +791,7 @@ private void verifyVirtualObject(CompilationResult compilation, VirtualObject ex int expectedLength = 0; for (int i = 0; i < expectedObject.getValues().length; i++) { JavaValue expectedValue = expectedObject.getValues()[i]; - UnsignedWord expectedOffset = WordFactory.unsigned(objectLayout.getArrayElementOffset(kind, expectedLength)); + UnsignedWord expectedOffset = Word.unsigned(objectLayout.getArrayElementOffset(kind, expectedLength)); ValueInfo actualValue = findActualArrayElement(actualObject, expectedOffset); verifyValue(compilation, expectedValue, actualValue, actualFrame, visitedVirtualObjects); @@ -831,7 +831,7 @@ private void verifyVirtualObject(CompilationResult compilation, VirtualObject ex fieldIdx++; } - UnsignedWord expectedOffset = WordFactory.unsigned(expectedField.getLocation()); + UnsignedWord expectedOffset = Word.unsigned(expectedField.getLocation()); ValueInfo actualValue = findActualField(actualObject, expectedOffset); verifyValue(compilation, expectedValue, actualValue, actualFrame, visitedVirtualObjects); } @@ -849,7 +849,7 @@ private ValueInfo findActualField(ValueInfo[] actualObject, UnsignedWord expecte DynamicHub hub = (DynamicHub) constantAccess.asObject(actualObject[0].getValue()); ObjectLayout objectLayout = ConfigurationValues.getObjectLayout(); assert LayoutEncoding.isPureInstance(hub.getLayoutEncoding()) : hub; - return findActualValue(actualObject, expectedOffset, objectLayout, WordFactory.unsigned(objectLayout.getFirstFieldOffset()), 1); + return findActualValue(actualObject, expectedOffset, objectLayout, Word.unsigned(objectLayout.getFirstFieldOffset()), 1); } private static ValueInfo findActualValue(ValueInfo[] actualObject, UnsignedWord expectedOffset, ObjectLayout objectLayout, UnsignedWord startOffset, int startIdx) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java index b18816cc1e85..458a250a6bfd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java @@ -27,11 +27,11 @@ import java.util.Arrays; import java.util.List; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.NonmovableArray; @@ -91,12 +91,12 @@ public static RuntimeCodeCache getRuntimeCodeCache() { public static void prepareImageCodeInfo() { // Stored in this class because ImageCodeInfo is immutable imageCodeInfo = getImageCodeCache().prepareCodeInfo(); - assert imageCodeInfo.notEqual(WordFactory.zero()); + assert imageCodeInfo.notEqual(Word.zero()); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static CodeInfo getFirstImageCodeInfo() { - assert imageCodeInfo.notEqual(WordFactory.zero()) : "uninitialized"; + assert imageCodeInfo.notEqual(Word.zero()) : "uninitialized"; return imageCodeInfo; } @@ -289,7 +289,7 @@ private static class InvalidateInstalledCodeOperation extends JavaVMOperation { @Override protected void operate() { counters().invalidateInstalledCodeCount.inc(); - CodePointer codePointer = WordFactory.pointer(installedCode.getAddress()); + CodePointer codePointer = Word.pointer(installedCode.getAddress()); invalidateInstalledCodeAtSafepoint(installedCode, codePointer); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java index 5312c2063a96..c31ee7208676 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java @@ -735,7 +735,7 @@ public void apply(FrameNode node, Object... args) { if (node.getStartPos() > node.getEndPos()) { printer.accept("Error: Node startPos > endPos: "); printer.accept(node.toString()); - printer.accept("\n"); + printer.accept(System.lineSeparator()); issues += 1; } if (node.nextSibling != null) { @@ -744,7 +744,7 @@ public void apply(FrameNode node, Object... args) { printer.accept(node.toString()); printer.accept(" with "); printer.accept(node.nextSibling.toString()); - printer.accept("\n"); + printer.accept(System.lineSeparator()); issues += 1; } } @@ -754,9 +754,9 @@ public void apply(FrameNode node, Object... args) { public static void dump(FrameNode node, Consumer printer, boolean onlyCallTree, boolean showInfopoints, int maxDepth) { if (node != null) { - printer.accept("\n"); + printer.accept(System.lineSeparator()); node.visit(new FrameTreeDumper(printer, onlyCallTree, showInfopoints, maxDepth), 0); - printer.accept("\n"); + printer.accept(System.lineSeparator()); } } @@ -791,7 +791,7 @@ public void apply(FrameNode node, Object... args) { indent(level); printer.accept(node.toString()); if (showSourcePos) { - printer.accept("\n"); + printer.accept(System.lineSeparator()); indent(level); printer.accept(" sourcePos: " + node.sourcePos.toString()); } else { @@ -802,7 +802,7 @@ public void apply(FrameNode node, Object... args) { printer.accept(" locals: "); printer.accept(node.getLocalsStr()); } - printer.accept("\n"); + printer.accept(System.lineSeparator()); node.visitChildren(this, level + 1); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java index 8d891e6b591d..1634beabd0a6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java @@ -295,7 +295,7 @@ void addFrameSlice(FrameData data, List slice) { sliceFrequency.addObject(frameSliceIndex); } - void encodeCompressedData(UnsafeArrayTypeWriter encodingBuffer, Encoders encoders) { + void encodeCompressedData(Runnable recordActivity, UnsafeArrayTypeWriter encodingBuffer, Encoders encoders) { assert !sealed : "already sealed"; sealed = true; @@ -332,6 +332,7 @@ void encodeCompressedData(UnsafeArrayTypeWriter encodingBuffer, Encoders encoder uniqueSuccessorIndex = NO_SUCCESSOR_INDEX_MARKER; } encodeCompressedFrame(encodingBuffer, encoders, frame, uniqueSuccessorIndex); + recordActivity.run(); } /* @@ -901,7 +902,7 @@ private static void afterInstallation(CodeInfo info) { private NonmovableArray encodeFrameDatas(Runnable recordActivity) { UnsafeArrayTypeWriter encodingBuffer = UnsafeArrayTypeWriter.create(ByteArrayReader.supportsUnalignedMemoryAccess()); - frameMetadata.encodeCompressedData(encodingBuffer, encoders); + frameMetadata.encodeCompressedData(recordActivity, encodingBuffer, encoders); for (FrameData data : allDebugInfos) { if (data.frameSliceIndex == UNCOMPRESSED_FRAME_SLICE_INDEX) { data.encodedFrameInfoIndex = encodingBuffer.getBytesWritten(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java index ebf6e6593751..bdaa3e862dc8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java @@ -26,8 +26,8 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.function.CodePointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.CalleeSavedRegisters; import com.oracle.svm.core.ReservedRegisters; @@ -191,7 +191,7 @@ public void init() { caller = null; deoptMethod = null; deoptMethodOffset = 0; - deoptMethodImageCodeInfo = SubstrateUtil.HOSTED ? null : WordFactory.nullPointer(); + deoptMethodImageCodeInfo = SubstrateUtil.HOSTED ? null : Word.nullPointer(); isDeoptEntry = false; numLocals = 0; numStack = 0; @@ -250,7 +250,7 @@ public CodeInfo getDeoptMethodImageCodeInfo() { */ public CodePointer getDeoptMethodAddress() { if (deoptMethodOffset == 0) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } return CodeInfoAccess.absoluteIP(getDeoptMethodImageCodeInfo(), deoptMethodOffset); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ImageCodeInfo.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ImageCodeInfo.java index 702e0e1be23f..e109288fd855 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ImageCodeInfo.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ImageCodeInfo.java @@ -32,7 +32,6 @@ import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.ComparableWord; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.BuildPhaseProvider.AfterCompilation; import com.oracle.svm.core.Uninterruptible; @@ -92,7 +91,7 @@ CodeInfo prepareCodeInfo() { for (int i = 0; i < size; i++) { ImageCodeInfo imageCodeInfo = imageCodeInfos[i]; CodeInfoImpl codeInfoImpl = runtimeCodeInfos[i].getData(); - CodeInfoImpl nextCodeInfoImpl = i + 1 < size ? runtimeCodeInfos[i + 1].getData() : WordFactory.nullPointer(); + CodeInfoImpl nextCodeInfoImpl = i + 1 < size ? runtimeCodeInfos[i + 1].getData() : Word.nullPointer(); ImageCodeInfo.prepareCodeInfo0(imageCodeInfo, codeInfoImpl, nextCodeInfoImpl); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ImageCodeInfoStorage.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ImageCodeInfoStorage.java index 0aa9eba718b4..0171b43fa9d0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ImageCodeInfoStorage.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ImageCodeInfoStorage.java @@ -30,7 +30,6 @@ import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CIsolateData; @@ -71,7 +70,7 @@ static CodeInfoImpl get() { @Fold protected static UnsignedWord offset() { - return WordFactory.unsigned(calculateOffset()); + return Word.unsigned(calculateOffset()); } static int calculateOffset() { @@ -96,7 +95,7 @@ class ImageCodeInfoStorageFeature implements InternalFeature, UnsavedSingleton, @Override public void duringSetup(DuringSetupAccess access) { - long size = SizeOf.unsigned(CodeInfoImpl.class).rawValue(); + long size = SizeOf.get(CodeInfoImpl.class); int arrayBaseOffset = ConfigurationValues.getObjectLayout().getArrayBaseOffset(JavaKind.Byte); int actualOffset = ImageCodeInfoStorage.calculateOffset(); int addend = actualOffset - arrayBaseOffset; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java index 63a53f6691ca..8a3b92ec2b51 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java @@ -27,10 +27,10 @@ import java.util.ArrayList; import java.util.List; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.NonmovableArray; @@ -129,7 +129,7 @@ public static void removeObserversOnTearDown(NonmovableArray= 0 : "info must be in table"; NonmovableArrays.arraycopy(codeInfos, idx + 1, codeInfos, idx, numCodeInfos - (idx + 1)); numCodeInfos--; - NonmovableArrays.setWord(codeInfos, numCodeInfos, WordFactory.nullPointer()); + NonmovableArrays.setWord(codeInfos, numCodeInfos, Word.nullPointer()); RuntimeCodeInfoAccess.markAsInvalidated(info); assert verifyTable(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoAccess.java index 6ba90aaa721b..1268cc368d53 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoAccess.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core.code; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; @@ -70,11 +70,11 @@ public static void initialize(CodeInfo info, Pointer codeStart, int entryPointOf CodeInfoImpl impl = cast(info); impl.setCodeStart((CodePointer) codeStart); - impl.setCodeEntryPointOffset(WordFactory.unsigned(entryPointOffset)); - impl.setCodeSize(WordFactory.unsigned(codeSize)); - impl.setDataOffset(WordFactory.unsigned(dataOffset)); - impl.setDataSize(WordFactory.unsigned(dataSize)); - impl.setCodeAndDataMemorySize(WordFactory.unsigned(codeAndDataMemorySize)); + impl.setCodeEntryPointOffset(Word.unsigned(entryPointOffset)); + impl.setCodeSize(Word.unsigned(codeSize)); + impl.setDataOffset(Word.unsigned(dataOffset)); + impl.setDataSize(Word.unsigned(dataSize)); + impl.setCodeAndDataMemorySize(Word.unsigned(codeAndDataMemorySize)); impl.setTier(tier); impl.setCodeObserverHandles(observerHandles); impl.setAllObjectsAreInImageHeap(allObjectsAreInImageHeap); @@ -227,12 +227,12 @@ static void markAsInvalidated(CodeInfo info) { } public static CodePointer allocateCodeMemory(UnsignedWord size) { - return (CodePointer) CommittedMemoryProvider.get().allocateExecutableMemory(size, WordFactory.unsigned(SubstrateOptions.codeAlignment())); + return (CodePointer) CommittedMemoryProvider.get().allocateExecutableMemory(size, Word.unsigned(SubstrateOptions.codeAlignment())); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static void releaseCodeMemory(CodePointer codeStart, UnsignedWord codeSize) { - CommittedMemoryProvider.get().freeExecutableMemory(codeStart, codeSize, WordFactory.unsigned(SubstrateOptions.codeAlignment())); + CommittedMemoryProvider.get().freeExecutableMemory(codeStart, codeSize, Word.unsigned(SubstrateOptions.codeAlignment())); } public static void makeCodeMemoryExecutableReadOnly(CodePointer codeStart, UnsignedWord codeSize) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java index cb6632bbc274..d6227faa5780 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java @@ -26,12 +26,12 @@ import java.util.concurrent.locks.ReentrantLock; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; @@ -110,11 +110,11 @@ public UnsignedWord totalSize() { } public void clearPeakCodeAndDataCounters() { - peakCodeAndDataMemorySize = WordFactory.zero(); + peakCodeAndDataMemorySize = Word.zero(); } public void clearPeakNativeMetadataCounters() { - peakNativeMetadataSize = WordFactory.zero(); + peakNativeMetadataSize = Word.zero(); } @Uninterruptible(reason = "Manipulate the counters atomically with regard to GC.") @@ -276,7 +276,7 @@ private boolean resize(int newLength) { for (int i = 0; i < oldLength; i++) { UntetheredCodeInfo tag = NonmovableArrays.getWord(oldTable, i); if (tag.isNonNull()) { - NonmovableArrays.setWord(oldTable, i, WordFactory.zero()); + NonmovableArrays.setWord(oldTable, i, Word.zero()); int u = hashIndex(tag, newLength); while (NonmovableArrays.getWord(table, u).isNonNull()) { u = nextIndex(u, newLength); @@ -295,7 +295,7 @@ private boolean remove0(CodeInfo info) { UntetheredCodeInfo entry = NonmovableArrays.getWord(table, index); while (entry.isNonNull()) { if (entry.equal(info)) { - NonmovableArrays.setWord(table, index, WordFactory.zero()); + NonmovableArrays.setWord(table, index, Word.zero()); count--; assert count >= 0 : "invalid counter value"; rehashAfterUnregisterAt(index); @@ -319,7 +319,7 @@ private void rehashAfterUnregisterAt(int index) { // from IdentityHashMap: Knuth int r = hashIndex(info, length); if ((i < r && (r <= d || d <= i)) || (r <= d && d <= i)) { NonmovableArrays.setWord(table, d, info); - NonmovableArrays.setWord(table, i, WordFactory.zero()); + NonmovableArrays.setWord(table, i, Word.zero()); d = i; } i = nextIndex(i, length); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/AbstractUninterruptibleHashtable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/AbstractUninterruptibleHashtable.java index f5b915281843..f056da7f97b6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/AbstractUninterruptibleHashtable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/AbstractUninterruptibleHashtable.java @@ -24,11 +24,11 @@ */ package com.oracle.svm.core.collections; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.UnmanagedMemoryUtil; @@ -74,7 +74,7 @@ protected UninterruptibleEntry copyToHeap(UninterruptibleEntry pointerOnStack, U UnmanagedMemoryUtil.copy((Pointer) pointerOnStack, (Pointer) pointerOnHeap, sizeToAlloc); return pointerOnHeap; } - return WordFactory.nullPointer(); + return Word.nullPointer(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -94,14 +94,14 @@ protected UninterruptibleEntry insertEntry(UninterruptibleEntry valueOnStack) { size++; return newEntry; } - return WordFactory.nullPointer(); + return Word.nullPointer(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean remove(UninterruptibleEntry valueOnStack) { int index = Integer.remainderUnsigned(valueOnStack.getHash(), DEFAULT_TABLE_LENGTH); UninterruptibleEntry entry = table[index]; - UninterruptibleEntry prev = WordFactory.nullPointer(); + UninterruptibleEntry prev = Word.nullPointer(); while (entry.isNonNull()) { if (isEqual(valueOnStack, entry)) { if (prev.isNull()) { @@ -141,7 +141,7 @@ public UninterruptibleEntry get(UninterruptibleEntry valueOnStack) { } entry = entry.getNext(); } - return WordFactory.nullPointer(); + return Word.nullPointer(); } @Override @@ -189,7 +189,7 @@ public void clear() { entry = entry.getNext(); free(tmp); } - table[i] = WordFactory.nullPointer(); + table[i] = Word.nullPointer(); } assert size == 0 : "The table is not empty!"; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArrayAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArrayAccess.java index 6b3142d625bd..9f7874f31eb8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArrayAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArrayAccess.java @@ -26,7 +26,6 @@ import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.config.ConfigurationValues; @@ -42,7 +41,7 @@ public class GrowableWordArrayAccess { public static void initialize(GrowableWordArray array) { array.setSize(0); array.setCapacity(0); - array.setData(WordFactory.nullPointer()); + array.setData(Word.nullPointer()); } public static Word get(GrowableWordArray array, int i) { @@ -63,7 +62,7 @@ public static boolean add(GrowableWordArray array, Word element, NmtCategory nmt public static void freeData(GrowableWordArray array) { if (array.isNonNull()) { NullableNativeMemory.free(array.getData()); - array.setData(WordFactory.nullPointer()); + array.setData(Word.nullPointer()); array.setSize(0); array.setCapacity(0); } @@ -78,12 +77,12 @@ private static boolean grow(GrowableWordArray array, NmtCategory nmtCategory) { assert newCapacity >= INITIAL_CAPACITY; WordPointer oldData = array.getData(); - WordPointer newData = NullableNativeMemory.malloc(WordFactory.unsigned(newCapacity).multiply(wordSize()), nmtCategory); + WordPointer newData = NullableNativeMemory.malloc(Word.unsigned(newCapacity).multiply(wordSize()), nmtCategory); if (newData.isNull()) { return false; } - UnmanagedMemoryUtil.copyForward((Pointer) oldData, (Pointer) newData, WordFactory.unsigned(array.getSize()).multiply(wordSize())); + UnmanagedMemoryUtil.copyForward((Pointer) oldData, (Pointer) newData, Word.unsigned(array.getSize()).multiply(wordSize())); NullableNativeMemory.free(oldData); array.setData(newData); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/config/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/config/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/config/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java index 53bfa1a703ea..ed156431cb91 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java @@ -122,7 +122,7 @@ public static final class Options { AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "Resources describing reachability metadata needed for the program " + - "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json", type = OptionType.User)// + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json", type = OptionType.User)// public static final HostedOptionKey ReachabilityMetadataResources = new HostedOptionKey<>( AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java index a123418dc670..37cd6b903a53 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java @@ -41,7 +41,7 @@ final class LegacyReflectionConfigurationParser extends ReflectionConfigur "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", "allDeclaredClasses", "allRecordComponents", "allPermittedSubclasses", "allNestMembers", "allSigners", "allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY, - "queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods", "unsafeAllocated"); + "queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods", "unsafeAllocated", "serializable"); private final boolean treatAllNameEntriesAsType; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java index 46c92f8b389c..bc4aeba5c112 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java @@ -33,6 +33,8 @@ import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; +import com.oracle.svm.util.LogUtils; + import jdk.graal.compiler.util.json.JsonParserException; final class LegacySerializationConfigurationParser extends SerializationConfigurationParser { @@ -79,6 +81,8 @@ private void parseNewConfiguration(EconomicMap listOfSerializati } } + private boolean customConstructorWarningTriggered = false; + @Override protected void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType) { if (lambdaCapturingType) { @@ -87,7 +91,7 @@ protected void parseSerializationDescriptorObject(EconomicMap da checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Arrays.asList(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY, CONDITIONAL_KEY)); } - ConfigurationTypeDescriptor targetSerializationClass = new NamedConfigurationTypeDescriptor(asString(data.get(NAME_KEY))); + NamedConfigurationTypeDescriptor targetSerializationClass = new NamedConfigurationTypeDescriptor(asString(data.get(NAME_KEY))); UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, false); var condition = conditionResolver.resolveCondition(unresolvedCondition); if (!condition.isPresent()) { @@ -95,18 +99,15 @@ protected void parseSerializationDescriptorObject(EconomicMap da } if (lambdaCapturingType) { - String className = ((NamedConfigurationTypeDescriptor) targetSerializationClass).name(); + String className = targetSerializationClass.name(); serializationSupport.registerLambdaCapturingClass(condition.get(), className); } else { - Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY); - String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null; - if (targetSerializationClass instanceof NamedConfigurationTypeDescriptor namedClass) { - serializationSupport.registerWithTargetConstructorClass(condition.get(), namedClass.name(), customTargetConstructorClass); - } else if (targetSerializationClass instanceof ProxyConfigurationTypeDescriptor proxyClass) { - serializationSupport.registerProxyClass(condition.get(), proxyClass.interfaceNames()); - } else { - throw new JsonParserException("Unknown configuration type descriptor: %s".formatted(targetSerializationClass.toString())); + if (!customConstructorWarningTriggered && data.containsKey(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY)) { + customConstructorWarningTriggered = true; + LogUtils.warning("\"" + CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY + + "\" is deprecated in serialization-config.json. All serializable classes can be instantiated through any superclass constructor without the use of the flag."); } + serializationSupport.register(condition.get(), targetSerializationClass.name()); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java index 748262afabb7..73463542d5db 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java @@ -70,6 +70,8 @@ public interface ReflectionConfigurationParserDelegate { void registerUnsafeAllocated(C condition, T clazz); + void registerAsSerializable(C condition, T clazz); + String getTypeName(T type); String getSimpleName(T type); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java index 33bd0028be6c..ffa54d572be1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java @@ -38,7 +38,7 @@ class ReflectionMetadataParser extends ReflectionConfigurationParser { private static final List OPTIONAL_REFLECT_METADATA_ATTRS = Arrays.asList(CONDITIONAL_KEY, "allDeclaredConstructors", "allPublicConstructors", "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", - "methods", "fields", "unsafeAllocated"); + "methods", "fields", "unsafeAllocated", "serializable"); private final String combinedFileKey; @@ -107,6 +107,8 @@ protected void parseClass(EconomicMap data) { registerIfNotDefault(data, false, clazz, "allPublicFields", () -> delegate.registerPublicFields(condition, false, clazz)); registerIfNotDefault(data, false, clazz, "unsafeAllocated", () -> delegate.registerUnsafeAllocated(condition, clazz)); + registerIfNotDefault(data, false, clazz, "serializable", () -> delegate.registerAsSerializable(condition, clazz)); + MapCursor cursor = data.getEntries(); while (cursor.advance()) { String name = cursor.getKey(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java index e34d651a3030..e79b76ed074f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java @@ -62,10 +62,9 @@ protected void parseSerializationTypes(List listOfSerializationTypes, bo protected abstract void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType); - protected void registerType(ConfigurationTypeDescriptor targetSerializationClass, C condition, Object optionalCustomCtorValue) { - String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null; + protected void registerType(ConfigurationTypeDescriptor targetSerializationClass, C condition) { if (targetSerializationClass instanceof NamedConfigurationTypeDescriptor namedClass) { - serializationSupport.registerWithTargetConstructorClass(condition, namedClass.name(), customTargetConstructorClass); + serializationSupport.register(condition, namedClass.name()); } else if (targetSerializationClass instanceof ProxyConfigurationTypeDescriptor proxyClass) { serializationSupport.registerProxyClass(condition, proxyClass.interfaceNames()); } else { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java index 9b74ed811243..cc20eb92e2da 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java @@ -32,6 +32,8 @@ import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; +import com.oracle.svm.util.LogUtils; + final class SerializationMetadataParser extends SerializationConfigurationParser { SerializationMetadataParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { @@ -46,6 +48,8 @@ public void parseAndRegister(Object json, URI origin) { } } + private boolean customConstructorWarningTriggered = false; + @Override protected void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType) { checkAttributes(data, "serialization descriptor object", List.of(TYPE_KEY), List.of(CONDITIONAL_KEY, CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY)); @@ -61,7 +65,11 @@ protected void parseSerializationDescriptorObject(EconomicMap da return; } - Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY); - registerType(targetSerializationClass.get(), condition.get(), optionalCustomCtorValue); + if (!customConstructorWarningTriggered && data.containsKey(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY)) { + customConstructorWarningTriggered = true; + LogUtils.warning("\"" + CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY + + "\" is deprecated in reachability-metadata.json. All serializable classes can be instantiated through any superclass constructor without the use of the flag."); + } + registerType(targetSerializationClass.get(), condition.get()); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt index fa8efe7afe28..3bcedff33109 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt @@ -22,18 +22,6 @@ In rare cases an application might explicitly make calls to ReflectionFactory.newConstructorForSerialization(Class cl, Constructor constructorToCall) -where the passed `constructorToCall` differs from what would automatically be used if regular serialization of `cl` -would happen. To also support such serialization usecases it is possible to register serialization for a class with a -custom constructorToCall. For example, to allow serialization of `org.apache.spark.SparkContext$$anonfun$hadoopFile$1` -using the DeclaredConstructor of java.lang.Object as custom targetConstructor the following can be used in -serialization-config.json: - - [ - { - "condition" : { - "typeReachable" : "org.apache.spark.SparkContext" - }, - "name" : "org.apache.spark.SparkContext$$anonfun$hadoopFile$1", - "customTargetConstructorClass" : "java.lang.Object" - } - ] +The specified `constructorToCall` differs from the one that would be automatically used during regular serialization of `cl`. +When a class is registered for run-time serialization, all potential custom constructors are automatically registered. +As a result, this use case does not require any additional metadata. \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/container/Container.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/container/Container.java index 515976578701..75892227f5e8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/container/Container.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/container/Container.java @@ -26,13 +26,13 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.LocationIdentity; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; @@ -176,12 +176,12 @@ public long getCachedMemoryLimitInBytes() { } private static class State { - static final UnsignedWord UNINITIALIZED = WordFactory.unsigned(0); - static final UnsignedWord INITIALIZING = WordFactory.unsigned(1); - static final UnsignedWord NOT_CONTAINERIZED = WordFactory.unsigned(2); - static final UnsignedWord CONTAINERIZED = WordFactory.unsigned(3); - static final UnsignedWord ERROR_LIBCONTAINER_TOO_OLD = WordFactory.unsigned(4); - static final UnsignedWord ERROR_LIBCONTAINER_TOO_NEW = WordFactory.unsigned(5); - static final UnsignedWord ERROR_UNKNOWN = WordFactory.unsigned(6); + static final UnsignedWord UNINITIALIZED = Word.unsigned(0); + static final UnsignedWord INITIALIZING = Word.unsigned(1); + static final UnsignedWord NOT_CONTAINERIZED = Word.unsigned(2); + static final UnsignedWord CONTAINERIZED = Word.unsigned(3); + static final UnsignedWord ERROR_LIBCONTAINER_TOO_OLD = Word.unsigned(4); + static final UnsignedWord ERROR_LIBCONTAINER_TOO_NEW = Word.unsigned(5); + static final UnsignedWord ERROR_UNKNOWN = Word.unsigned(6); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/container/ContainerLibrary.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/container/ContainerLibrary.java index 3e462a19662a..87d268a7148d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/container/ContainerLibrary.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/container/ContainerLibrary.java @@ -51,43 +51,43 @@ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+24/src/java.base/share/native/include/jni.h") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+24/src/java.base/unix/native/include/jni_md.h") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+24/src/hotspot/os/linux/cgroupSubsystem_linux.cpp") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/os/linux/cgroupSubsystem_linux.hpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+2/src/hotspot/os/linux/cgroupSubsystem_linux.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/os/linux/cgroupUtil_linux.cpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/os/linux/cgroupUtil_linux.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+25/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+19/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/os/linux/osContainer_linux.cpp") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/os/linux/osContainer_linux.hpp") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+21/src/hotspot/os/linux/os_linux.cpp") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/os/linux/os_linux.hpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+2/src/hotspot/os/linux/osContainer_linux.cpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+2/src/hotspot/os/linux/osContainer_linux.hpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+2/src/hotspot/os/linux/os_linux.cpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+2/src/hotspot/os/linux/os_linux.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/os/linux/os_linux.inline.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+24/src/hotspot/os/posix/include/jvm_md.h") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+25/src/hotspot/os/posix/os_posix.cpp") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+20/src/hotspot/os/posix/os_posix.hpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/hotspot/os/posix/os_posix.cpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+2/src/hotspot/os/posix/os_posix.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/os/posix/os_posix.inline.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+25/src/hotspot/share/memory/allocation.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/share/memory/allocation.inline.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/share/memory/allStatic.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/share/nmt/memTag.hpp") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+24/src/hotspot/share/runtime/os.cpp") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+25/src/hotspot/share/runtime/os.hpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+3/src/hotspot/share/runtime/os.cpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+3/src/hotspot/share/runtime/os.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/share/runtime/os.inline.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/share/utilities/checkedCast.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/share/utilities/compilerWarnings_gcc.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/share/utilities/compilerWarnings.hpp") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/share/utilities/globalDefinitions_gcc.hpp") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+20/src/hotspot/share/utilities/globalDefinitions.hpp") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/utilities/macros.hpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/hotspot/share/utilities/globalDefinitions_gcc.hpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/hotspot/share/utilities/globalDefinitions.hpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/hotspot/share/utilities/macros.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/share/utilities/ostream.cpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/share/utilities/ostream.hpp") // The following annotations are for files in `src/svm`, which are completely customized for SVM @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/share/logging/log.hpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/share/memory/allocation.cpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+25/src/hotspot/share/runtime/globals.hpp") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+22/src/hotspot/share/utilities/debug.cpp") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+16/src/hotspot/share/utilities/debug.hpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+3/src/hotspot/share/utilities/debug.cpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+26/src/hotspot/share/utilities/debug.hpp") public class ContainerLibrary { static final int VERSION = 240100; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/container/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/container/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/container/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/AbstractDCmd.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/AbstractDCmd.java index 4c835d8080e0..0c2ed0795b50 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/AbstractDCmd.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/AbstractDCmd.java @@ -146,7 +146,7 @@ private DCmdOption findOption(String optionName) { return null; } - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/services/diagnosticArgument.cpp#L140-L166") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+26/src/hotspot/share/services/diagnosticArgument.cpp#L141-L171") private static Object parseValue(DCmdOption option, String valueString) { Class type = option.type(); if (type == Boolean.class) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/GCHeapDumpDCmd.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/GCHeapDumpDCmd.java index 359c940ab228..69ba1c14d2ca 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/GCHeapDumpDCmd.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/GCHeapDumpDCmd.java @@ -32,7 +32,7 @@ import com.oracle.svm.core.heap.dump.HeapDumping; import com.oracle.svm.core.util.BasedOnJDKFile; -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/services/diagnosticCommand.hpp#L317-L343") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/hotspot/share/services/diagnosticCommand.hpp#L262-L283") public class GCHeapDumpDCmd extends AbstractDCmd { private static final DCmdOption FILENAME = new DCmdOption<>(String.class, "filename", "File path of where to put the heap dump", true, null); private static final DCmdOption DUMP_ALL = new DCmdOption<>(Boolean.class, "-all", "Dump all objects, including unreachable objects", false, false); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrCheckDCmd.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrCheckDCmd.java index f3ea302ca5a2..f19e3ffd83c3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrCheckDCmd.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrCheckDCmd.java @@ -35,10 +35,9 @@ import com.oracle.svm.core.jfr.Target_jdk_jfr_internal_dcmd_AbstractDCmd; import com.oracle.svm.core.util.BasedOnJDKFile; -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp#L106-L108") public class JfrCheckDCmd extends AbstractJfrDCmd { - // This constructor should be annotated with @BasedOnJDK instead of the class, see GR-59171. @Platforms(Platform.HOSTED_ONLY.class) + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp#L106-L108") public JfrCheckDCmd() { super("JFR.check", "Checks running JFR recording(s)", Impact.Low); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrDumpDCmd.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrDumpDCmd.java index 94cf4b6421c1..5987690fdf4f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrDumpDCmd.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrDumpDCmd.java @@ -35,10 +35,9 @@ import com.oracle.svm.core.jfr.Target_jdk_jfr_internal_dcmd_AbstractDCmd; import com.oracle.svm.core.util.BasedOnJDKFile; -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp#L81-L83") public class JfrDumpDCmd extends AbstractJfrDCmd { - // This constructor should be annotated with @BasedOnJDK instead of the class, see GR-59171. @Platforms(Platform.HOSTED_ONLY.class) + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp#L81-L83") public JfrDumpDCmd() { super("JFR.dump", "Copies contents of a JFR recording to file. Either the name or the recording id must be specified.", Impact.Medium); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrStartDCmd.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrStartDCmd.java index 5ba6b45cd741..f19551698915 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrStartDCmd.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrStartDCmd.java @@ -34,10 +34,9 @@ import com.oracle.svm.core.jfr.Target_jdk_jfr_internal_dcmd_DCmdStart; import com.oracle.svm.core.util.BasedOnJDKFile; -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp#L56-L58") public class JfrStartDCmd extends AbstractJfrDCmd { - // This constructor should be annotated with @BasedOnJDK instead of the class, see GR-59171. @Platforms(Platform.HOSTED_ONLY.class) + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp#L56-L58") public JfrStartDCmd() { super("JFR.start", "Starts a new JFR recording.", Impact.Medium); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrStopDCmd.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrStopDCmd.java index e8bd70b76ba2..83eecf22087f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrStopDCmd.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/JfrStopDCmd.java @@ -35,10 +35,9 @@ import com.oracle.svm.core.jfr.Target_jdk_jfr_internal_dcmd_AbstractDCmd; import com.oracle.svm.core.util.BasedOnJDKFile; -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp#L131-L133") public class JfrStopDCmd extends AbstractJfrDCmd { - // This constructor should be annotated with @BasedOnJDK instead of the class, see GR-59171. @Platforms(Platform.HOSTED_ONLY.class) + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp#L131-L133") public JfrStopDCmd() { super("JFR.stop", "Stops a JFR recording.", Impact.Low); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/ThreadDumpToFileDCmd.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/ThreadDumpToFileDCmd.java index 32edb6cc5a2f..233ca6713cb5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/ThreadDumpToFileDCmd.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/ThreadDumpToFileDCmd.java @@ -34,7 +34,7 @@ import jdk.internal.vm.ThreadDumper; -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/services/diagnosticCommand.hpp#L934-L958") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/hotspot/share/services/diagnosticCommand.hpp#L757-L777") public class ThreadDumpToFileDCmd extends AbstractDCmd { private static final DCmdOption FILEPATH = new DCmdOption<>(String.class, "filepath", "The file path to the output file", true, null); private static final DCmdOption OVERWRITE = new DCmdOption<>(Boolean.class, "-overwrite", "May overwrite existing file", false, false); @@ -52,7 +52,7 @@ public ThreadDumpToFileDCmd() { } @Override - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/services/diagnosticCommand.cpp#L1110-L1161") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/hotspot/share/services/diagnosticCommand.cpp#L1062-L1106") public String execute(DCmdArguments args) throws Throwable { String path = args.get(FILEPATH); boolean overwrite = args.get(OVERWRITE); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/ThreadPrintDCmd.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/ThreadPrintDCmd.java index ac8e09b49c35..2f2000ab1a2e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/ThreadPrintDCmd.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/ThreadPrintDCmd.java @@ -32,7 +32,7 @@ import com.oracle.svm.core.DumpThreadStacksSupport; import com.oracle.svm.core.util.BasedOnJDKFile; -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/services/diagnosticCommand.hpp#L425-L445") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/hotspot/share/services/diagnosticCommand.hpp#L350-L365") public class ThreadPrintDCmd extends AbstractDCmd { @Platforms(Platform.HOSTED_ONLY.class) public ThreadPrintDCmd() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMCommandLineDCmd.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMCommandLineDCmd.java index 9a945e363147..2575d99be049 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMCommandLineDCmd.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMCommandLineDCmd.java @@ -32,7 +32,7 @@ import com.oracle.svm.core.JavaMainWrapper.JavaMainSupport; import com.oracle.svm.core.util.BasedOnJDKFile; -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/services/diagnosticCommand.hpp#L75-L91") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/hotspot/share/services/diagnosticCommand.hpp#L70-L81") public class VMCommandLineDCmd extends AbstractDCmd { @Platforms(Platform.HOSTED_ONLY.class) public VMCommandLineDCmd() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMNativeMemoryDCmd.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMNativeMemoryDCmd.java index 51d1c20c1632..3ffbbacfdde6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMNativeMemoryDCmd.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMNativeMemoryDCmd.java @@ -32,15 +32,14 @@ import com.oracle.svm.core.nmt.NativeMemoryTracking; import com.oracle.svm.core.util.BasedOnJDKFile; -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/nmt/nmtDCmd.hpp#L49-L52") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/nmt/nmtDCmd.cpp#L34-L64") public class VMNativeMemoryDCmd extends AbstractDCmd { private static final DCmdOption SUMMARY = new DCmdOption<>(Boolean.class, "summary", "Request runtime to report current memory summary, which includes total reserved and committed memory, along with memory usage summary by each subsystem.", false, false); - // This constructor should be annotated with @BasedOnJDK instead of the class, see GR-59171. @Platforms(Platform.HOSTED_ONLY.class) + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/nmt/nmtDCmd.hpp#L49-L52") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/nmt/nmtDCmd.cpp#L34-L64") public VMNativeMemoryDCmd() { super("VM.native_memory", "Print native memory usage", Impact.Low, new DCmdOption[0], new DCmdOption[]{SUMMARY}, new String[]{"$ jcmd VM.native_memory summary"}); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMSystemPropertiesDCmd.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMSystemPropertiesDCmd.java index e04677398879..e0183d966ba3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMSystemPropertiesDCmd.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMSystemPropertiesDCmd.java @@ -34,7 +34,7 @@ import jdk.internal.vm.VMSupport; -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/services/diagnosticCommand.hpp#L94-L110") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/hotspot/share/services/diagnosticCommand.hpp#L84-L95") public class VMSystemPropertiesDCmd extends AbstractDCmd { @Platforms(Platform.HOSTED_ONLY.class) public VMSystemPropertiesDCmd() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMVersionDmd.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMVersionDmd.java index b6e1f92022c0..b7165229630b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMVersionDmd.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/dcmd/VMVersionDmd.java @@ -32,7 +32,7 @@ import com.oracle.svm.core.VM; import com.oracle.svm.core.util.BasedOnJDKFile; -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+18/src/hotspot/share/services/diagnosticCommand.hpp#L59-L73") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/hotspot/share/services/diagnosticCommand.hpp#L59-L68") public class VMVersionDmd extends AbstractDCmd { @Platforms(Platform.HOSTED_ONLY.class) public VMVersionDmd() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/DeoptState.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/DeoptState.java index ba32a456cbac..3c78353adb54 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/DeoptState.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/DeoptState.java @@ -44,7 +44,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import java.lang.reflect.Array; @@ -96,7 +95,7 @@ protected JavaConstant readValue(FrameInfoQueryResult.ValueInfo valueInfo, Frame return valueInfo.getValue(); case StackSlot: case Register: - return readConstant(sourceSp, WordFactory.signed(valueInfo.getData()), valueInfo.getKind(), valueInfo.isCompressedReference(), sourceFrame); + return readConstant(sourceSp, Word.signed(valueInfo.getData()), valueInfo.getKind(), valueInfo.isCompressedReference(), sourceFrame); case ReservedRegister: if (ReservedRegisters.singleton().getThreadRegister() != null && ReservedRegisters.singleton().getThreadRegister().number == valueInfo.getData()) { return JavaConstant.forIntegerKind(ConfigurationValues.getWordKind(), targetThread.rawValue()); @@ -158,7 +157,7 @@ private Object materializeObject(int virtualObjectId, FrameInfoQueryResult sourc } catch (InstantiationException ex) { throw fatalDeoptimizationError("Instantiation exception: " + ex, sourceFrame); } - curOffset = WordFactory.unsigned(objectLayout.getFirstFieldOffset()); + curOffset = Word.unsigned(objectLayout.getFirstFieldOffset()); curIdx = 1; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/DeoptimizedFrame.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/DeoptimizedFrame.java index b606e6be612e..308e5c64078c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/DeoptimizedFrame.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/DeoptimizedFrame.java @@ -26,10 +26,10 @@ import java.lang.ref.WeakReference; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.PinnedObject; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.Uninterruptible; @@ -247,7 +247,7 @@ protected SavedBasePointer(int offset, long valueRelativeToNewSp) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) protected void write(Deoptimizer.TargetContent targetContent, Pointer newSp) { - targetContent.writeWord(offset, newSp.add(WordFactory.unsigned(valueRelativeToNewSp))); + targetContent.writeWord(offset, newSp.add(Word.unsigned(valueRelativeToNewSp))); } } @@ -410,7 +410,7 @@ public void takeException() { return; } ReturnAddress firstAddressEntry = topFrame.returnAddress; - CodePointer ip = WordFactory.pointer(firstAddressEntry.returnAddress); + CodePointer ip = Word.pointer(firstAddressEntry.returnAddress); CodeInfo info = CodeInfoTable.getImageCodeInfo(ip); SimpleCodeInfoQueryResult codeInfoQueryResult = UnsafeStackValue.get(SimpleCodeInfoQueryResult.class); CodeInfoAccess.lookupCodeInfo(info, ip, codeInfoQueryResult); @@ -425,7 +425,7 @@ public void takeException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.UNRESTRICTED, reason = "Printing out error and then crashing.") private static void throwMissingExceptionHandler(CodeInfo info, ReturnAddress firstAddressEntry) { CodeInfoQueryResult detailedQueryResult = new CodeInfoQueryResult(); - CodeInfoAccess.lookupCodeInfo(info, WordFactory.pointer(firstAddressEntry.returnAddress), detailedQueryResult); + CodeInfoAccess.lookupCodeInfo(info, Word.pointer(firstAddressEntry.returnAddress), detailedQueryResult); FrameInfoQueryResult frameInfo = detailedQueryResult.getFrameInfo(); throw Deoptimizer.fatalDeoptimizationError("No exception handler registered for deopt target", frameInfo); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java index 2dbecf6ff31c..5399848bdd17 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java @@ -41,7 +41,6 @@ import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.FrameAccess; import com.oracle.svm.core.Isolates; @@ -305,7 +304,7 @@ private static class DeoptimizeAllOperation extends JavaVMOperation { @Override protected void operate() { - deoptimizeInRange(WordFactory.zero(), WordFactory.zero(), true); + deoptimizeInRange(Word.zero(), Word.zero(), true); } } @@ -545,7 +544,11 @@ public enum StubType { *

      * Custom epilogue: prepare stack layout and ABI registers for outgoing call. */ - InterpreterLeaveStub + InterpreterLeaveStub; + + public boolean isInterpreterStub() { + return equals(InterpreterEnterStub) || equals(InterpreterLeaveStub); + } } @Retention(RetentionPolicy.RUNTIME) @@ -593,7 +596,7 @@ public enum StubType { @DeoptStub(stubType = StubType.EntryStub) @Uninterruptible(reason = "Frame holds Objects in unmanaged storage.") public static UnsignedWord deoptStub(Pointer framePointer, UnsignedWord gpReturnValue, UnsignedWord fpReturnValue) { - assert PointerUtils.isAMultiple(KnownIntrinsics.readStackPointer(), WordFactory.unsigned(ConfigurationValues.getTarget().stackAlignment)); + assert PointerUtils.isAMultiple(KnownIntrinsics.readStackPointer(), Word.unsigned(ConfigurationValues.getTarget().stackAlignment)); VMError.guarantee(VMThreads.StatusSupport.isStatusJava(), "Deopt stub execution must not be visible to other threads."); DeoptimizedFrame frame = (DeoptimizedFrame) ReferenceAccess.singleton().readObjectAt(framePointer, true); @@ -605,7 +608,7 @@ public static UnsignedWord deoptStub(Pointer framePointer, UnsignedWord gpReturn /* Computation of the new stack pointer: we start with the stack pointer of this frame. */ final Pointer newSp = framePointer /* Remove the size of the frame that gets deoptimized. */ - .add(WordFactory.unsigned(frame.getSourceTotalFrameSize())) + .add(Word.unsigned(frame.getSourceTotalFrameSize())) /* Add the size of the deoptimization target frames. */ .subtract(frame.getTargetContent().getSize()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/RuntimeCompilation.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/RuntimeCompilation.java index 1954418d5116..d234446571fb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/RuntimeCompilation.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/RuntimeCompilation.java @@ -24,11 +24,14 @@ */ package com.oracle.svm.core.graal; -import jdk.graal.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.deopt.DeoptimizationSupport; +import com.oracle.svm.core.util.VMError; + +import jdk.graal.compiler.api.replacements.Fold; public final class RuntimeCompilation { /** @@ -38,6 +41,7 @@ public final class RuntimeCompilation { */ @Fold public static boolean isEnabled() { + VMError.guarantee(BuildPhaseProvider.isFeatureRegistrationFinished(), "RuntimeCompilation.isEnabled() must not be called before the feature registration is finished."); boolean enabled = ImageSingletons.contains(RuntimeCompilationCanaryFeature.class); assert !enabled || DeoptimizationSupport.enabled(); return enabled; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/InterpreterAccessStubData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/InterpreterAccessStubData.java new file mode 100644 index 000000000000..8d0b2d5a8e9c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/InterpreterAccessStubData.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.graal.code; + +import jdk.vm.ci.meta.AllocatableValue; +import org.graalvm.word.Pointer; + +/* Helper class to set ABI specific data */ +public interface InterpreterAccessStubData { + void setSp(Pointer data, int stackSize, Pointer stackBuffer); + + long getGpArgumentAt(AllocatableValue ccArg, Pointer data, int pos); + + long setGpArgumentAt(AllocatableValue ccArg, Pointer data, int pos, long val); + + long getFpArgumentAt(AllocatableValue ccArg, Pointer data, int pos); + + void setFpArgumentAt(AllocatableValue ccArg, Pointer data, int pos, long val); + + long getGpReturn(Pointer data); + + void setGpReturn(Pointer data, long gpReturn); + + long getFpReturn(Pointer data); + + void setFpReturn(Pointer data, long fpReturn); + + int allocateStubDataSize(); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/jdk/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/jdk/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/jdk/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/jdk/SubstrateObjectCloneSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/jdk/SubstrateObjectCloneSnippets.java index ef396465107b..b0590e650e42 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/jdk/SubstrateObjectCloneSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/jdk/SubstrateObjectCloneSnippets.java @@ -30,8 +30,8 @@ import java.util.Map; +import jdk.graal.compiler.word.Word; import org.graalvm.word.LocationIdentity; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.JavaMemoryUtil; import com.oracle.svm.core.c.NonmovableArray; @@ -144,13 +144,13 @@ private static Object doClone(Object original) throws CloneNotSupportedException int primitiveDataSize = objectOffset - curOffset; assert primitiveDataSize >= 0; assert curOffset >= 0; - JavaMemoryUtil.copyForward(original, WordFactory.unsigned(curOffset), result, WordFactory.unsigned(curOffset), WordFactory.unsigned(primitiveDataSize)); + JavaMemoryUtil.copyForward(original, Word.unsigned(curOffset), result, Word.unsigned(curOffset), Word.unsigned(primitiveDataSize)); curOffset += primitiveDataSize; // copy object data assert curOffset >= 0; assert count >= 0; - JavaMemoryUtil.copyReferencesForward(original, WordFactory.unsigned(curOffset), result, WordFactory.unsigned(curOffset), WordFactory.unsigned(count)); + JavaMemoryUtil.copyReferencesForward(original, Word.unsigned(curOffset), result, Word.unsigned(curOffset), Word.unsigned(count)); curOffset += count * referenceSize; } @@ -160,7 +160,7 @@ private static Object doClone(Object original) throws CloneNotSupportedException int primitiveDataSize = endOffset - curOffset; assert primitiveDataSize >= 0; assert curOffset >= 0; - JavaMemoryUtil.copyForward(original, WordFactory.unsigned(curOffset), result, WordFactory.unsigned(curOffset), WordFactory.unsigned(primitiveDataSize)); + JavaMemoryUtil.copyForward(original, Word.unsigned(curOffset), result, Word.unsigned(curOffset), Word.unsigned(primitiveDataSize)); curOffset += primitiveDataSize; assert curOffset == endOffset; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/DynamicHubOffsets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/DynamicHubOffsets.java new file mode 100644 index 000000000000..479b6c8b41b9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/DynamicHubOffsets.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.graal.meta; + +import java.lang.reflect.Field; +import java.util.Arrays; + +import jdk.graal.compiler.word.BarrieredAccess; +import org.graalvm.nativeimage.AnnotationAccess; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.BuildPhaseProvider; +import com.oracle.svm.core.annotate.InjectAccessors; +import com.oracle.svm.core.heap.UnknownPrimitiveField; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.graal.compiler.api.replacements.Fold; +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.ResolvedJavaField; + +public class DynamicHubOffsets { + /* defining order in DynamicHub */ + + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int nameOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int hubTypeOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int referenceTypeOffset; + + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int layoutEncodingOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int typeIDOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int typeIDDepthOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int numClassTypesOffset; + + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int numInterfaceTypesOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int openTypeWorldTypeCheckSlotsOffset; + + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int monitorOffsetOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int identityHashOffsetOffset; + + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int flagsOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int additionalFlagsOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int modifiersOffset; + + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int superHubOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int componentTypeOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int arrayHubOffset; + + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int declaringClassOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int interfacesEncodingOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int enumConstantsReferenceOffset; + + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int referenceMapIndexOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int layerIdOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int metaTypeOffset; + + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int sourceFileNameOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int classInitializationInfoOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int moduleOffset; + + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int nestHostOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int simpleBinaryNameOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int companionOffset; + + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int signatureOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int classRedefinedCountOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int hubMetadataOffset; + @UnknownPrimitiveField(availability = BuildPhaseProvider.ReadyForCompilation.class) // + private int reflectionMetadataOffset; + + @Fold + public static DynamicHubOffsets singleton() { + return ImageSingletons.lookup(DynamicHubOffsets.class); + } + + private static final String[] SKIPPED_FIELDS = new String[]{ + /* closed world only */ + "typeCheckStart", "typeCheckRange", "typeCheckSlot", "closedTypeWorldTypeCheckSlots", + + /* handled by KnownOffsets */ + "vtable" + }; + + @Platforms(Platform.HOSTED_ONLY.class) + public void initializeOffsets(MetaAccessProvider metaAccess) { + for (ResolvedJavaField field : metaAccess.lookupJavaType(DynamicHub.class).getInstanceFields(true)) { + if (Arrays.stream(SKIPPED_FIELDS).anyMatch(field.getName()::equals)) { + continue; + } + + if (AnnotationAccess.isAnnotationPresent(field, InjectAccessors.class)) { + continue; + } + + try { + Field offsetField = ReflectionUtil.lookupField(DynamicHubOffsets.class, field.getName() + "Offset"); + offsetField.setInt(singleton(), field.getOffset()); + } catch (IllegalAccessException e) { + throw VMError.shouldNotReachHere(e); + } + } + } + + public int getNameOffset() { + return nameOffset; + } + + public int getHubTypeOffset() { + return hubTypeOffset; + } + + public int getReferenceTypeOffset() { + return referenceTypeOffset; + } + + public int getLayoutEncodingOffset() { + return layoutEncodingOffset; + } + + public int getTypeIDOffset() { + return typeIDOffset; + } + + public int getTypeIDDepthOffset() { + return typeIDDepthOffset; + } + + public int getNumClassTypesOffset() { + return numClassTypesOffset; + } + + public int getNumInterfaceTypesOffset() { + return numInterfaceTypesOffset; + } + + public int getOpenTypeWorldTypeCheckSlotsOffset() { + return openTypeWorldTypeCheckSlotsOffset; + } + + public int getMonitorOffsetOffset() { + return monitorOffsetOffset; + } + + public int getIdentityHashOffsetOffset() { + return identityHashOffsetOffset; + } + + public int getFlagsOffset() { + return flagsOffset; + } + + public int getAdditionalFlagsOffset() { + return additionalFlagsOffset; + } + + public int getModifiersOffset() { + return modifiersOffset; + } + + public int getSuperHubOffset() { + return superHubOffset; + } + + public int getComponentTypeOffset() { + return componentTypeOffset; + } + + public int getArrayHubOffset() { + return arrayHubOffset; + } + + public int getDeclaringClassOffset() { + return declaringClassOffset; + } + + public int getInterfacesEncodingOffset() { + return interfacesEncodingOffset; + } + + public int getEnumConstantsReferenceOffset() { + return enumConstantsReferenceOffset; + } + + public int getReferenceMapIndexOffset() { + return referenceMapIndexOffset; + } + + public int getLayerIdOffset() { + return layerIdOffset; + } + + public int getMetaTypeOffset() { + return metaTypeOffset; + } + + public int getSourceFileNameOffset() { + return sourceFileNameOffset; + } + + public int getClassInitializationInfoOffset() { + return classInitializationInfoOffset; + } + + public int getModuleOffset() { + return moduleOffset; + } + + public int getNestHostOffset() { + return nestHostOffset; + } + + public int getSimpleBinaryNameOffset() { + return simpleBinaryNameOffset; + } + + public int getCompanionOffset() { + return companionOffset; + } + + public int getSignatureOffset() { + return signatureOffset; + } + + public int getClassRedefinedCountOffset() { + return classRedefinedCountOffset; + } + + public int getHubMetadataOffset() { + return hubMetadataOffset; + } + + public int getReflectionMetadataOffset() { + return reflectionMetadataOffset; + } + + public static void writeObject(DynamicHub hub, int offset, Object value) { + if (offset < 0) { + /* field removed by analysis */ + return; + } + BarrieredAccess.writeObject(hub, offset, value); + } + + public static void writeInt(DynamicHub hub, int offset, int value) { + if (offset < 0) { + /* field removed by analysis */ + return; + } + BarrieredAccess.writeInt(hub, offset, value); + } + + public static void writeShort(DynamicHub hub, int offset, short value) { + if (offset < 0) { + /* field removed by analysis */ + return; + } + BarrieredAccess.writeShort(hub, offset, value); + } + + public static void writeChar(DynamicHub hub, int offset, char value) { + if (offset < 0) { + /* field removed by analysis */ + return; + } + BarrieredAccess.writeChar(hub, offset, value); + } + + public static void writeByte(DynamicHub hub, int offset, byte value) { + if (offset < 0) { + /* field removed by analysis */ + return; + } + BarrieredAccess.writeByte(hub, offset, value); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/KnownOffsets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/KnownOffsets.java index 77a2ddd5e4f6..3e1eed3ce325 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/KnownOffsets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/KnownOffsets.java @@ -53,8 +53,6 @@ public final class KnownOffsets { @UnknownPrimitiveField(availability = ReadyForCompilation.class) // private int typeIDSlotsOffset; @UnknownPrimitiveField(availability = ReadyForCompilation.class) // - private int componentHubOffset; - @UnknownPrimitiveField(availability = ReadyForCompilation.class) // private int javaFrameAnchorLastSPOffset; @UnknownPrimitiveField(availability = ReadyForCompilation.class) // private int javaFrameAnchorLastIPOffset; @@ -69,14 +67,15 @@ public static KnownOffsets singleton() { } @Platforms(Platform.HOSTED_ONLY.class) - public void setLazyState(int vtableBaseOffset, int vtableEntrySize, int typeIDSlotsOffset, int componentHubOffset, - int javaFrameAnchorLastSPOffset, int javaFrameAnchorLastIPOffset, int vmThreadStatusOffset, int imageCodeInfoCodeStartOffset) { + public void setLazyState(int vtableBaseOffset, int vtableEntrySize, int typeIDSlotsOffset, + int javaFrameAnchorLastSPOffset, int javaFrameAnchorLastIPOffset, + int vmThreadStatusOffset, int imageCodeInfoCodeStartOffset) { assert !isFullyInitialized(); this.vtableBaseOffset = vtableBaseOffset; this.vtableEntrySize = vtableEntrySize; this.typeIDSlotsOffset = typeIDSlotsOffset; - this.componentHubOffset = componentHubOffset; + this.javaFrameAnchorLastSPOffset = javaFrameAnchorLastSPOffset; this.javaFrameAnchorLastIPOffset = javaFrameAnchorLastIPOffset; this.vmThreadStatusOffset = vmThreadStatusOffset; @@ -89,7 +88,7 @@ public void setLazyState(int vtableBaseOffset, int vtableEntrySize, int typeIDSl vtableBaseOffset, vtableEntrySize, typeIDSlotsOffset, - componentHubOffset, + javaFrameAnchorLastSPOffset, javaFrameAnchorLastIPOffset, vmThreadStatusOffset, @@ -137,11 +136,6 @@ public int getTypeIDSlotsOffset() { return typeIDSlotsOffset; } - public int getComponentHubOffset() { - assert isFullyInitialized(); - return componentHubOffset; - } - public int getJavaFrameAnchorLastSPOffset() { assert isFullyInitialized(); return javaFrameAnchorLastSPOffset; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SubstrateBasicLoweringProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SubstrateBasicLoweringProvider.java index 598fbc3f6e2e..2b90c7c45471 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SubstrateBasicLoweringProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SubstrateBasicLoweringProvider.java @@ -102,6 +102,7 @@ public abstract class SubstrateBasicLoweringProvider extends DefaultJavaLowering private RuntimeConfiguration runtimeConfig; private final KnownOffsets knownOffsets; + private final DynamicHubOffsets dynamicHubOffsets; private final AbstractObjectStamp hubStamp; @Platforms(Platform.HOSTED_ONLY.class) @@ -117,6 +118,7 @@ public SubstrateBasicLoweringProvider(MetaAccessProvider metaAccess, ForeignCall } hubStamp = hubRefStamp; knownOffsets = KnownOffsets.singleton(); + dynamicHubOffsets = DynamicHubOffsets.singleton(); } @Override @@ -220,7 +222,7 @@ private static ValueNode maybeUncompress(ValueNode node) { @Override protected ValueNode createReadArrayComponentHub(StructuredGraph graph, ValueNode arrayHub, boolean isKnownObjectArray, FixedNode anchor) { - ConstantNode componentHubOffset = ConstantNode.forIntegerKind(target.wordJavaKind, knownOffsets.getComponentHubOffset(), graph); + ConstantNode componentHubOffset = ConstantNode.forIntegerKind(target.wordJavaKind, dynamicHubOffsets.getComponentTypeOffset(), graph); AddressNode componentHubAddress = graph.unique(new OffsetAddressNode(arrayHub, componentHubOffset)); FloatingReadNode componentHubRef = graph.unique(new FloatingReadNode(componentHubAddress, NamedLocationIdentity.FINAL_LOCATION, null, hubStamp, null, BarrierType.NONE)); return maybeUncompress(componentHubRef); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/nodes/SubstrateNewDynamicHubNode.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/nodes/SubstrateNewDynamicHubNode.java new file mode 100644 index 000000000000..28b7a4aabff4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/nodes/SubstrateNewDynamicHubNode.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.graal.nodes; + +import com.oracle.svm.core.hub.DynamicHub; +import jdk.graal.compiler.core.common.type.StampFactory; +import jdk.graal.compiler.core.common.type.TypeReference; +import jdk.graal.compiler.graph.NodeClass; +import jdk.graal.compiler.nodeinfo.NodeInfo; +import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.java.AbstractNewObjectNode; +import jdk.vm.ci.meta.ResolvedJavaType; + +/** + * The {@link SubstrateNewDynamicHubNode} represents the allocation of a new + * {@link com.oracle.svm.core.hub.DynamicHub} at execution time. + */ +@NodeInfo(nameTemplate = "SubstrateNewDynamicHubNode") +public final class SubstrateNewDynamicHubNode extends AbstractNewObjectNode { + public static final NodeClass TYPE = NodeClass.create(SubstrateNewDynamicHubNode.class); + + @Input ValueNode vTableEntries; + + public SubstrateNewDynamicHubNode(ResolvedJavaType dynamicHubType, ValueNode vTableEntries) { + super(TYPE, StampFactory.objectNonNull(TypeReference.createExactTrusted(dynamicHubType)), true, null); + this.vTableEntries = vTableEntries; + } + + public ValueNode getVTableEntries() { + return vTableEntries; + } + + @NodeIntrinsic + public static native DynamicHub allocate(@ConstantNodeParameter Class dynamicHubType, int vTableEntries); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/phases/SubstrateSafepointInsertionPhase.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/phases/SubstrateSafepointInsertionPhase.java index f7700df0499e..1a6e0b22378a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/phases/SubstrateSafepointInsertionPhase.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/phases/SubstrateSafepointInsertionPhase.java @@ -64,13 +64,19 @@ public static boolean needSafepointCheck(ResolvedJavaMethod method) { return true; } - @Override - protected void run(StructuredGraph graph, MidTierContext context) { + /** + * Determines if this (potentially special) method needs safepoint checks. + */ + public static boolean needsSafepointCheck(StructuredGraph graph) { SharedMethod method = (SharedMethod) graph.method(); - if (!method.needSafepointCheck()) { - return; - } + return method.needSafepointCheck(); + } + /** + * Insert SVM specific safepoints at the method end if necessary. + */ + public static void insertMethodEndSafepoints(StructuredGraph graph, MidTierContext context) { + SharedMethod method = (SharedMethod) graph.method(); if (!((SubstrateBackend) context.getTargetProvider()).safepointCheckedInEpilogue(method)) { /* Insert method-end safepoints. */ for (ReturnNode returnNode : graph.getNodes(ReturnNode.TYPE)) { @@ -78,6 +84,14 @@ protected void run(StructuredGraph graph, MidTierContext context) { graph.addBeforeFixed(returnNode, safepointNode); } } + } + + @Override + protected void run(StructuredGraph graph, MidTierContext context) { + if (!needsSafepointCheck(graph)) { + return; + } + insertMethodEndSafepoints(graph, context); /* Insert loop safepoints. */ super.run(graph, context); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java index 564d5a944f46..204644fef217 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java @@ -46,7 +46,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.CPUFeatureAccess; import com.oracle.svm.core.IsolateArgumentParser; @@ -206,7 +205,7 @@ public static void setHeapBase(PointerBase heapBase) { @Snippet(allowMissingProbabilities = true) public static int createIsolateSnippet(CEntryPointCreateIsolateParameters parameters) { - writeCurrentVMThread(WordFactory.nullPointer()); + writeCurrentVMThread(Word.nullPointer()); int result = runtimeCall(CREATE_ISOLATE, parameters); if (result != CEntryPointErrors.NO_ERROR) { return result; @@ -219,9 +218,9 @@ public static int createIsolateSnippet(CEntryPointCreateIsolateParameters parame private static final CGlobalData IMAGE_HEAP_PATCHING_STATE = CGlobalDataFactory.createWord(); private static final class ImageHeapPatchingState { - static final Word UNINITIALIZED = WordFactory.zero(); - static final Word IN_PROGRESS = WordFactory.unsigned(1); - static final Word SUCCESSFUL = WordFactory.unsigned(2); + static final Word UNINITIALIZED = Word.zero(); + static final Word IN_PROGRESS = Word.unsigned(1); + static final Word SUCCESSFUL = Word.unsigned(2); } @Uninterruptible(reason = "Thread state not yet set up.") @@ -282,7 +281,7 @@ private static int createIsolate(CEntryPointCreateIsolateParameters providedPara } UnsignedWord runtimePageSize = VirtualMemoryProvider.get().getGranularity(); - UnsignedWord imagePageSize = WordFactory.unsigned(SubstrateOptions.getPageSize()); + UnsignedWord imagePageSize = Word.unsigned(SubstrateOptions.getPageSize()); boolean validPageSize = UnsignedUtils.isAMultiple(imagePageSize, runtimePageSize); if (!validPageSize) { return CEntryPointErrors.PAGE_SIZE_CHECK_FAILED; @@ -295,7 +294,7 @@ private static int createIsolate(CEntryPointCreateIsolateParameters providedPara CEntryPointCreateIsolateParameters parameters = providedParameters; if (parameters.isNull() || parameters.version() < 1) { parameters = StackValue.get(CEntryPointCreateIsolateParameters.class); - parameters.setReservedSpaceSize(WordFactory.zero()); + parameters.setReservedSpaceSize(Word.zero()); parameters.setVersion(1); } @@ -500,7 +499,7 @@ private static int initializeIsolateInterruptibly1(CEntryPointCreateIsolateParam @Snippet(allowMissingProbabilities = true) public static int attachThreadSnippet(Isolate isolate, boolean startedByIsolate, boolean ensureJavaThread) { - writeCurrentVMThread(WordFactory.nullPointer()); + writeCurrentVMThread(Word.nullPointer()); int error = runtimeCall(ATTACH_THREAD, isolate, startedByIsolate, ensureJavaThread); if (error != CEntryPointErrors.NO_ERROR) { return error; @@ -536,7 +535,7 @@ private static int enterAttachThread0(Isolate isolate, boolean startedByIsolate, return CEntryPointErrors.UNINITIALIZED_ISOLATE; } - IsolateThread thread = WordFactory.nullPointer(); + IsolateThread thread = Word.nullPointer(); if (startedByIsolate) { assert VMThreads.singleton().findIsolateThreadForCurrentOSThread(inCrashHandler).isNull(); } else { @@ -711,7 +710,7 @@ private static boolean initiateTearDownIsolateInterruptibly() { @Snippet(allowMissingProbabilities = true) public static int enterByIsolateSnippet(Isolate isolate) { int result; - writeCurrentVMThread(WordFactory.nullPointer()); + writeCurrentVMThread(Word.nullPointer()); result = runtimeCall(ENTER_BY_ISOLATE, isolate); if (result == CEntryPointErrors.NO_ERROR) { if (VMThreads.StatusSupport.isStatusNativeOrSafepoint()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/ExceptionSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/ExceptionSnippets.java index 6306146c2ab0..43d00a51b1bd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/ExceptionSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/ExceptionSnippets.java @@ -44,9 +44,9 @@ import jdk.graal.compiler.api.replacements.Snippet; import jdk.graal.compiler.api.replacements.Snippet.ConstantParameter; -import jdk.graal.compiler.core.common.type.StampFactory; import jdk.graal.compiler.graph.Node; import jdk.graal.compiler.nodes.FixedWithNextNode; +import jdk.graal.compiler.nodes.NodeView; import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.UnwindNode; import jdk.graal.compiler.nodes.java.LoadExceptionObjectNode; @@ -111,7 +111,7 @@ public static class LoadExceptionObjectLowering implements NodeLoweringProvider< @Override public void lower(LoadExceptionObjectNode node, LoweringTool tool) { StructuredGraph graph = node.graph(); - FixedWithNextNode readRegNode = graph.add(new ReadExceptionObjectNode(StampFactory.objectNonNull())); + FixedWithNextNode readRegNode = graph.add(new ReadExceptionObjectNode(node.stamp(NodeView.DEFAULT))); graph.replaceFixedWithFixed(node, readRegNode); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/GCAllocationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/GCAllocationSupport.java index f8a091d2aed2..6ba29e4f40ea 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/GCAllocationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/GCAllocationSupport.java @@ -41,6 +41,8 @@ public interface GCAllocationSupport { ForeignCallDescriptor getNewPodInstanceStub(); + ForeignCallDescriptor getNewDynamicHub(); + boolean useTLAB(); boolean shouldAllocateInTLAB(UnsignedWord size, boolean isArray); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/NonSnippetLowerings.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/NonSnippetLowerings.java index 4a3383574fbf..60f8ed9ba6ea 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/NonSnippetLowerings.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/NonSnippetLowerings.java @@ -112,6 +112,7 @@ import jdk.vm.ci.meta.DeoptimizationReason; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.JavaMethodProfile; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaMethod; @@ -494,8 +495,8 @@ protected LoweredCallTargetNode createDirectCall(StructuredGraph graph, MethodCa protected IndirectCallTargetNode createIndirectCall(StructuredGraph graph, MethodCallTargetNode callTarget, NodeInputList parameters, SharedMethod method, JavaType[] signature, CallingConvention.Type callType, InvokeKind invokeKind, ValueNode entry) { - return graph.add(new SubstrateIndirectCallTargetNode(entry, parameters.toArray(new ValueNode[parameters.size()]), callTarget.returnStamp(), signature, method, callType, invokeKind, - ((SubstrateMethodCallTargetNode) callTarget).getMethodProfile())); + JavaMethodProfile methodProfile = callTarget instanceof SubstrateMethodCallTargetNode substrateCallTarget ? substrateCallTarget.getMethodProfile() : null; + return graph.add(new SubstrateIndirectCallTargetNode(entry, parameters.toArray(ValueNode.EMPTY_ARRAY), callTarget.returnStamp(), signature, method, callType, invokeKind, methodProfile)); } private static CallTargetNode createUnreachableCallTarget(LoweringTool tool, FixedNode node, NodeInputList parameters, StampPair returnStamp, JavaType[] signature, diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/StackOverflowCheckImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/StackOverflowCheckImpl.java index 7946f1945c76..d11e230c60dc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/StackOverflowCheckImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/StackOverflowCheckImpl.java @@ -32,13 +32,13 @@ import java.util.Map; import java.util.function.Predicate; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageInfo; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -259,7 +259,7 @@ public void disableStackOverflowChecksForFatalError() { * Setting the boundary to a low value effectively disables the check. We are not using 0 so * that we can distinguish the value set here from an uninitialized value. */ - stackBoundaryTL.set(WordFactory.unsigned(1)); + stackBoundaryTL.set(Word.unsigned(1)); /* * A random marker value. The actual value does not matter, but having a high value also * ensures that any future calls to protectYellowZone() do not modify the stack boundary @@ -293,7 +293,7 @@ private static void updateStackOverflowBoundary(UnsignedWord requestedStackSize) public void updateStackOverflowBoundary() { long threadSize = PlatformThreads.getRequestedStackSize(Thread.currentThread()); if (threadSize != 0) { - updateStackOverflowBoundary(WordFactory.unsigned(threadSize)); + updateStackOverflowBoundary(Word.unsigned(threadSize)); } } @@ -466,7 +466,7 @@ private static void stackOverflowCheckSnippet(@ConstantParameter boolean mustNot * Methods that can deoptimize must have enough space on the stack for all frames after * deoptimization. */ - stackBoundary = stackBoundary.add(WordFactory.unsigned(deoptFrameSize)); + stackBoundary = stackBoundary.add(Word.unsigned(deoptFrameSize)); } if (probability(EXTREMELY_SLOW_PATH_PROBABILITY, KnownIntrinsics.readStackPointer().belowOrEqual(stackBoundary))) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java index 7de4fe5f89fc..bcdab1271587 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java @@ -36,10 +36,11 @@ import java.util.Arrays; import java.util.Map; +import com.oracle.svm.core.graal.meta.KnownOffsets; +import jdk.graal.compiler.core.common.NumUtil; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.word.LocationIdentity; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.MissingRegistrationUtils; import com.oracle.svm.core.SubstrateOptions; @@ -50,11 +51,13 @@ import com.oracle.svm.core.graal.meta.SubstrateForeignCallsProvider; import com.oracle.svm.core.graal.nodes.NewPodInstanceNode; import com.oracle.svm.core.graal.nodes.NewStoredContinuationNode; +import com.oracle.svm.core.graal.nodes.SubstrateNewDynamicHubNode; import com.oracle.svm.core.graal.nodes.SubstrateNewHybridInstanceNode; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.heap.Pod; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.LayoutEncoding; +import com.oracle.svm.core.hub.RuntimeClassLoading; import com.oracle.svm.core.identityhashcode.IdentityHashCodeSupport; import com.oracle.svm.core.meta.SharedType; import com.oracle.svm.core.option.HostedOptionValues; @@ -138,7 +141,7 @@ protected Object allocateInstance(@NonNullParameter DynamicHub hub, @ConstantParameter boolean emitMemoryBarrier, @ConstantParameter AllocationProfilingData profilingData, @ConstantParameter boolean withException) { - Object result = allocateInstanceImpl(encodeAsTLABObjectHeader(hub), WordFactory.unsigned(size), forceSlowPath, fillContents, emitMemoryBarrier, true, profilingData, withException); + Object result = allocateInstanceImpl(encodeAsTLABObjectHeader(hub), Word.unsigned(size), forceSlowPath, fillContents, emitMemoryBarrier, true, profilingData, withException); return piCastToSnippetReplaceeStamp(result); } @@ -227,6 +230,22 @@ public Object allocatePod(@NonNullParameter DynamicHub hub, return piArrayCastToSnippetReplaceeStamp(verifyOop(result), arrayLength); } + @Snippet + public Object allocateDynamicHub(int vTableEntries, + @ConstantParameter int vTableBaseOffset, + @ConstantParameter int log2VTableEntrySize, + @ConstantParameter AllocationProfilingData profilingData) { + profilingData.snippetCounters.stub.inc(); + + // always slow path, because DynamicHubs are allocated into dedicated chunks + Object result = callNewDynamicHub(gcAllocationSupport().getNewDynamicHub(), vTableEntries); + + UnsignedWord allocationSize = arrayAllocationSize(vTableEntries, vTableBaseOffset, log2VTableEntrySize); + profileAllocation(profilingData, allocationSize); + + return piArrayCastToSnippetReplaceeStamp(verifyOop(result), vTableEntries); + } + @Snippet public Object allocateInstanceDynamic(@NonNullParameter DynamicHub hub, @ConstantParameter boolean forceSlowPath, @@ -384,7 +403,7 @@ public Object formatArray(Word hub, UnsignedWord allocationSize, int length, Wor public Object formatStoredContinuation(Word objectHeader, UnsignedWord allocationSize, int arrayLength, Word memory, boolean emitMemoryBarrier, long ipOffset, AllocationSnippetCounters snippetCounters) { Object result = formatArray(objectHeader, allocationSize, arrayLength, memory, FillContent.DO_NOT_FILL, false, false, false, false, snippetCounters); - memory.writeWord(WordFactory.unsigned(ipOffset), WordFactory.nullPointer(), LocationIdentity.init()); + memory.writeWord(Word.unsigned(ipOffset), Word.nullPointer(), LocationIdentity.init()); emitMemoryBarrierIf(emitMemoryBarrier); return result; } @@ -521,6 +540,9 @@ protected final Object callNewMultiArrayStub(Word objectHeader, int rank, Word d @NodeIntrinsic(value = ForeignCallNode.class) private static native Object callSlowNewStoredContinuation(@ConstantNodeParameter ForeignCallDescriptor descriptor, Word hub, int length); + @NodeIntrinsic(value = ForeignCallNode.class) + private static native Object callNewDynamicHub(@ConstantNodeParameter ForeignCallDescriptor descriptor, int vTableEntries); + @NodeIntrinsic(value = ForeignCallNode.class) private static native Object callNewMultiArray(@ConstantNodeParameter ForeignCallDescriptor descriptor, Word hub, int rank, Word dimensions); @@ -591,6 +613,7 @@ public static class Templates extends SubstrateTemplates { private final SnippetInfo allocateStoredContinuation; private final SnippetInfo allocatePod; + private final SnippetInfo allocateDynamicHub; @SuppressWarnings("this-escape") public Templates(OptionValues options, Providers providers, SubstrateAllocationSnippets receiver) { @@ -659,6 +682,17 @@ public Templates(OptionValues options, Providers providers, SubstrateAllocationS podLocations); } allocatePod = allocatePodSnippet; + + SnippetInfo allocateDynamicHubSnippet = null; + if (RuntimeClassLoading.isSupported()) { + allocateDynamicHubSnippet = snippet(providers, + SubstrateAllocationSnippets.class, + "allocateDynamicHub", + null, + receiver, + ALLOCATION_LOCATIONS); + } + allocateDynamicHub = allocateDynamicHubSnippet; } public void registerLowering(Map, NodeLoweringProvider> lowerings) { @@ -687,6 +721,9 @@ public void registerLowering(Map, NodeLoweringProvider> if (Pod.RuntimeSupport.isPresent()) { lowerings.put(NewPodInstanceNode.class, new NewPodInstanceLowering()); } + if (RuntimeClassLoading.isSupported()) { + lowerings.put(SubstrateNewDynamicHubNode.class, new NewDynamicHubLowering()); + } } public AllocationSnippetCounters getSnippetCounters() { @@ -1097,6 +1134,39 @@ public void lower(NewPodInstanceNode node, LoweringTool tool) { template(tool, node, args).instantiate(tool.getMetaAccess(), node, SnippetTemplate.DEFAULT_REPLACER, args); } } + + private class NewDynamicHubLowering implements NodeLoweringProvider { + @Override + public void lower(SubstrateNewDynamicHubNode node, LoweringTool tool) { + StructuredGraph graph = node.graph(); + if (graph.getGuardsStage().areFrameStatesAtSideEffects()) { + return; + } + + assert node.getVTableEntries() != null; + assert node.fillContents() : "fillContents must be true for DynamicHub allocations"; + + ValueNode vTableEntries = node.getVTableEntries(); + SharedType type = (SharedType) tool.getMetaAccess().lookupJavaType(Class.class); + DynamicHub hubOfDynamicHub = type.getHub(); + + int layoutEncoding = hubOfDynamicHub.getLayoutEncoding(); + + int vTableBaseOffset = getArrayBaseOffset(layoutEncoding); + assert vTableBaseOffset == KnownOffsets.singleton().getVTableBaseOffset(); + + int log2VTableEntrySize = LayoutEncoding.getArrayIndexShift(layoutEncoding); + assert log2VTableEntrySize == NumUtil.unsignedLog2(KnownOffsets.singleton().getVTableEntrySize()); + + Arguments args = new Arguments(allocateDynamicHub, graph.getGuardsStage(), tool.getLoweringStage()); + args.add("vTableEntries", vTableEntries); + args.add("vTableBaseOffset", vTableBaseOffset); + args.add("log2VTableEntrySize", log2VTableEntrySize); + args.add("profilingData", getProfilingData(node, type)); + + template(tool, node, args).instantiate(tool.getMetaAccess(), node, SnippetTemplate.DEFAULT_REPLACER, args); + } + } } /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/stackvalue/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/stackvalue/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/stackvalue/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java index 259bb3068b16..6c5d6417dd45 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java @@ -26,11 +26,11 @@ import java.lang.ref.WeakReference; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ObjectHandle; import org.graalvm.nativeimage.ObjectHandles; import org.graalvm.word.SignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import jdk.internal.misc.Unsafe; @@ -73,7 +73,7 @@ private static final class HandleWeakReference extends WeakReference { private volatile long unusedHandleSearchIndex = 0; public ObjectHandlesImpl() { - this(WordFactory.signed(1), WordFactory.signed(Long.MAX_VALUE), WordFactory.signed(0)); + this(Word.signed(1), Word.signed(Long.MAX_VALUE), Word.signed(0)); } public ObjectHandlesImpl(SignedWord rangeMin, SignedWord rangeMax, SignedWord nullHandle) { @@ -106,7 +106,7 @@ private static long toIndex(int bucketIndex, int indexInBucket) { private ObjectHandle toHandle(int bucketIndex, int indexInBucket) { long index = toIndex(bucketIndex, indexInBucket); - return (ObjectHandle) rangeMin.add(WordFactory.signed(index)); + return (ObjectHandle) rangeMin.add(Word.signed(index)); } private long toIndex(WordBase handle) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ThreadLocalHandles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ThreadLocalHandles.java index 5ae87ea2fb24..0b5487093923 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ThreadLocalHandles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ThreadLocalHandles.java @@ -26,9 +26,9 @@ import java.util.Arrays; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ObjectHandle; import org.graalvm.word.SignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.Uninterruptible; @@ -46,7 +46,7 @@ public final class ThreadLocalHandles { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static U nullHandle() { - return WordFactory.signed(0); + return Word.signed(0); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -109,7 +109,7 @@ public T tryCreateNonNull(Object obj) { int index = top; objects[index] = obj; top++; - return WordFactory.signed(index); + return Word.signed(index); } @SuppressWarnings("unchecked") diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/CodeReferenceMapDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/CodeReferenceMapDecoder.java index 8e8a9fd596b5..f527aeaae498 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/CodeReferenceMapDecoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/CodeReferenceMapDecoder.java @@ -26,10 +26,10 @@ import jdk.graal.compiler.core.common.util.AbstractTypeReader; import jdk.graal.compiler.core.common.util.UnsafeArrayTypeWriter; +import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.FrameAccess; @@ -58,8 +58,8 @@ public class CodeReferenceMapDecoder { public static boolean walkOffsetsFromPointer(PointerBase baseAddress, NonmovableArray referenceMapEncoding, long referenceMapIndex, ObjectReferenceVisitor visitor, Object holderObject) { assert referenceMapIndex != ReferenceMapIndex.NO_REFERENCE_MAP; assert referenceMapEncoding.isNonNull(); - UnsignedWord uncompressedSize = WordFactory.unsigned(FrameAccess.uncompressedReferenceSize()); - UnsignedWord compressedSize = WordFactory.unsigned(ConfigurationValues.getObjectLayout().getReferenceSize()); + UnsignedWord uncompressedSize = Word.unsigned(FrameAccess.uncompressedReferenceSize()); + UnsignedWord compressedSize = Word.unsigned(ConfigurationValues.getObjectLayout().getReferenceSize()); Pointer objRef = (Pointer) baseAddress; long idx = referenceMapIndex; @@ -115,7 +115,7 @@ public static boolean walkOffsetsFromPointer(PointerBase baseAddress, Nonmovable } firstRun = false; - objRef = objRef.add(WordFactory.unsigned(gap)); + objRef = objRef.add(Word.unsigned(gap)); boolean compressed = (count < 0); UnsignedWord refSize = compressed ? compressedSize : uncompressedSize; count = (count < 0) ? -count : count; @@ -152,9 +152,9 @@ public static boolean walkOffsetsFromPointer(PointerBase baseAddress, Nonmovable Pointer derivedRef; if (refOffset >= 0) { - derivedRef = objRef.add(WordFactory.unsigned(refOffset).multiply(refSize)); + derivedRef = objRef.add(Word.unsigned(refOffset).multiply(refSize)); } else { - derivedRef = objRef.subtract(WordFactory.unsigned(-refOffset).multiply(refSize)); + derivedRef = objRef.subtract(Word.unsigned(-refOffset).multiply(refSize)); } Pointer derivedPtr = baseAddress.isNull() ? derivedRef : derivedRef.readWord(0); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/HeapSizeVerifier.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/HeapSizeVerifier.java index 37156bc6cdec..92b3972fb19f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/HeapSizeVerifier.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/HeapSizeVerifier.java @@ -24,8 +24,8 @@ */ package com.oracle.svm.core.heap; +import jdk.graal.compiler.word.Word; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateGCOptions; import com.oracle.svm.core.SubstrateUtil; @@ -55,33 +55,33 @@ public static void verifyHeapOptions() { } private static void verifyReservedAddressSpaceSize() { - UnsignedWord reservedAddressSpaceSize = WordFactory.unsigned(SubstrateGCOptions.ReservedAddressSpaceSize.getValue()); + UnsignedWord reservedAddressSpaceSize = Word.unsigned(SubstrateGCOptions.ReservedAddressSpaceSize.getValue()); verifyAgainstMaxAddressSpaceSize(reservedAddressSpaceSize, "value of the option '" + SubstrateGCOptions.ReservedAddressSpaceSize.getName() + "'"); } private static void verifyMaxHeapSize() { - UnsignedWord maxHeapSize = WordFactory.unsigned(SubstrateGCOptions.MaxHeapSize.getValue()); + UnsignedWord maxHeapSize = Word.unsigned(SubstrateGCOptions.MaxHeapSize.getValue()); verifyMaxHeapSizeAgainstMaxAddressSpaceSize(maxHeapSize); verifyAgainstReservedAddressSpaceSize(maxHeapSize, MAX_HEAP_SIZE_NAME); } private static void verifyMinHeapSize() { - UnsignedWord minHeapSize = WordFactory.unsigned(SubstrateGCOptions.MinHeapSize.getValue()); + UnsignedWord minHeapSize = Word.unsigned(SubstrateGCOptions.MinHeapSize.getValue()); verifyMinHeapSizeAgainstMaxAddressSpaceSize(minHeapSize); verifyAgainstReservedAddressSpaceSize(minHeapSize, MIN_HEAP_SIZE_NAME); - UnsignedWord maxHeapSize = WordFactory.unsigned(SubstrateGCOptions.MaxHeapSize.getValue()); + UnsignedWord maxHeapSize = Word.unsigned(SubstrateGCOptions.MaxHeapSize.getValue()); if (maxHeapSize.notEqual(0) && minHeapSize.aboveThan(maxHeapSize)) { throwError(minHeapSize, MIN_HEAP_SIZE_NAME, maxHeapSize, MAX_HEAP_SIZE_NAME); } } private static void verifyMaxNewSize() { - UnsignedWord maxNewSize = WordFactory.unsigned(SubstrateGCOptions.MaxNewSize.getValue()); + UnsignedWord maxNewSize = Word.unsigned(SubstrateGCOptions.MaxNewSize.getValue()); verifyMaxNewSizeAgainstMaxAddressSpaceSize(maxNewSize); verifyAgainstReservedAddressSpaceSize(maxNewSize, MAX_NEW_SIZE_NAME); - UnsignedWord maxHeapSize = WordFactory.unsigned(SubstrateGCOptions.MaxHeapSize.getValue()); + UnsignedWord maxHeapSize = Word.unsigned(SubstrateGCOptions.MaxHeapSize.getValue()); if (maxHeapSize.notEqual(0) && maxNewSize.aboveThan(maxHeapSize)) { throwError(maxNewSize, MAX_NEW_SIZE_NAME, maxHeapSize, MAX_HEAP_SIZE_NAME); } @@ -107,7 +107,7 @@ private static void verifyAgainstMaxAddressSpaceSize(UnsignedWord actualValue, S } private static void verifyAgainstReservedAddressSpaceSize(UnsignedWord actualValue, String actualValueName) { - UnsignedWord reservedAddressSpaceSize = WordFactory.unsigned(SubstrateGCOptions.ReservedAddressSpaceSize.getValue()); + UnsignedWord reservedAddressSpaceSize = Word.unsigned(SubstrateGCOptions.ReservedAddressSpaceSize.getValue()); if (reservedAddressSpaceSize.notEqual(0) && actualValue.aboveThan(reservedAddressSpaceSize)) { throwError(actualValue, actualValueName, reservedAddressSpaceSize, "value of the option '" + SubstrateGCOptions.ReservedAddressSpaceSize.getName() + "'"); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/InstanceReferenceMapDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/InstanceReferenceMapDecoder.java index 55ebdabaa3e0..c303de2453cb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/InstanceReferenceMapDecoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/InstanceReferenceMapDecoder.java @@ -24,9 +24,9 @@ */ package com.oracle.svm.core.heap; +import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -52,7 +52,7 @@ public static boolean walkOffsetsFromPointer(Pointer baseAddress, NonmovableArra boolean compressed = ReferenceAccess.singleton().haveCompressedReferences(); assert entryCount >= 0; - UnsignedWord sizeOfEntries = WordFactory.unsigned(InstanceReferenceMapEncoder.MAP_ENTRY_SIZE).multiply(entryCount); + UnsignedWord sizeOfEntries = Word.unsigned(InstanceReferenceMapEncoder.MAP_ENTRY_SIZE).multiply(entryCount); Pointer end = position.add(sizeOfEntries); while (position.belowThan(end)) { int offset = position.readInt(0); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ObjectHeader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ObjectHeader.java index 2dad97ec6e97..b43a91332442 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ObjectHeader.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ObjectHeader.java @@ -28,7 +28,6 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.word.LocationIdentity; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -83,7 +82,7 @@ public static DynamicHub readDynamicHubFromObject(Object o) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static Word readHeaderFromPointer(Pointer objectPointer) { if (getReferenceSize() == Integer.BYTES) { - return WordFactory.unsigned(objectPointer.readInt(getHubOffset())); + return Word.unsigned(objectPointer.readInt(getHubOffset())); } else { return objectPointer.readWord(getHubOffset()); } @@ -92,7 +91,7 @@ public static Word readHeaderFromPointer(Pointer objectPointer) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static Word readHeaderFromObject(Object o) { if (getReferenceSize() == Integer.BYTES) { - return WordFactory.unsigned(ObjectAccess.readInt(o, getHubOffset())); + return Word.unsigned(ObjectAccess.readInt(o, getHubOffset())); } else { return ObjectAccess.readWord(o, getHubOffset()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PhysicalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PhysicalMemory.java index c669adc5c2cb..69afe73a2299 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PhysicalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PhysicalMemory.java @@ -26,10 +26,10 @@ import java.lang.management.ManagementFactory; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.IsolateArgumentParser; import com.oracle.svm.core.SubstrateOptions; @@ -83,7 +83,7 @@ public static void initialize() { assert !isInitialized() : "Physical memory already initialized."; long memoryLimit = IsolateArgumentParser.singleton().getLongOptionValue(IsolateArgumentParser.getOptionIndex(SubstrateOptions.ConcealedOptions.MaxRAM)); if (memoryLimit > 0) { - cachedSize = WordFactory.unsigned(memoryLimit); + cachedSize = Word.unsigned(memoryLimit); } else if (Container.singleton().isContainerized()) { cachedSize = Container.singleton().getPhysicalMemory(); } else { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PodReferenceMapDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PodReferenceMapDecoder.java index 7e2afe0f586e..a956b3990a55 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PodReferenceMapDecoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PodReferenceMapDecoder.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core.heap; +import jdk.graal.compiler.word.Word; import org.graalvm.word.LocationIdentity; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.JavaMemoryUtil; @@ -129,8 +129,8 @@ private static void copyArray(Object original, Object copy, int layoutEncoding, UnsignedWord nrefs; do { mapOffset = mapOffset.subtract(2); - gap = WordFactory.unsigned(Byte.toUnsignedInt(BarrieredAccess.readByte(copy, mapOffset))); - nrefs = WordFactory.unsigned(Byte.toUnsignedInt(BarrieredAccess.readByte(copy, mapOffset.add(1)))); + gap = Word.unsigned(Byte.toUnsignedInt(BarrieredAccess.readByte(copy, mapOffset))); + nrefs = Word.unsigned(Byte.toUnsignedInt(BarrieredAccess.readByte(copy, mapOffset.add(1)))); // Copy references separately with the required barriers JavaMemoryUtil.copyReferencesForward(original, refOffset, copy, refOffset, nrefs); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceAccessImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceAccessImpl.java index 347067c558f7..0afb66052c16 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceAccessImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceAccessImpl.java @@ -29,7 +29,6 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.SubstrateOptions; @@ -62,7 +61,7 @@ public Word readObjectAsUntrackedPointer(Pointer p, boolean compressed) { public Object readObjectAt(Pointer p, boolean compressed) { Word w = (Word) p; if (compressed) { - return ObjectAccess.readObject(WordFactory.nullPointer(), p); + return ObjectAccess.readObject(Word.nullPointer(), p); } else { return w.readObject(0); } @@ -74,7 +73,7 @@ public Object readObjectAt(Pointer p, boolean compressed) { public void writeObjectAt(Pointer p, Object value, boolean compressed) { Word w = (Word) p; if (compressed) { - ObjectAccess.writeObject(WordFactory.nullPointer(), p, value); + ObjectAccess.writeObject(Word.nullPointer(), p, value); } else { // this overload has no uncompression semantics w.writeObject(0, value); @@ -114,10 +113,10 @@ public UnsignedWord getAddressSpaceSize() { int compressionShift = ReferenceAccess.singleton().getCompressEncoding().getShift(); if (compressionShift > 0) { int referenceSize = ConfigurationValues.getObjectLayout().getReferenceSize(); - return WordFactory.unsigned(1L << (referenceSize * Byte.SIZE)).shiftLeft(compressionShift); + return Word.unsigned(1L << (referenceSize * Byte.SIZE)).shiftLeft(compressionShift); } // Assume that 48 bit is the maximum address space that can be used. - return WordFactory.unsigned((1L << 48) - 1); + return Word.unsigned((1L << 48) - 1); } @Fold diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceInternals.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceInternals.java index ffbb65214cdc..6e0a6cccb877 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceInternals.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceInternals.java @@ -31,7 +31,6 @@ import java.lang.ref.SoftReference; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.SubstrateUtil; @@ -73,7 +72,7 @@ static Reference uncast(Target_java_lang_ref_Reference instance) { /** Barrier-less read of {@link Target_java_lang_ref_Reference#referent} as a pointer. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static Pointer getReferentPointer(Reference instance) { - return Word.objectToUntrackedPointer(ObjectAccess.readObject(instance, WordFactory.signed(Target_java_lang_ref_Reference.referentFieldOffset))); + return Word.objectToUntrackedPointer(ObjectAccess.readObject(instance, Word.signed(Target_java_lang_ref_Reference.referentFieldOffset))); } @SuppressWarnings("unchecked") @@ -85,7 +84,7 @@ public static T getReferent(Reference instance) { /** Write {@link Target_java_lang_ref_Reference#referent}. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void setReferent(Reference instance, Object value) { - BarrieredAccess.writeObject(instance, WordFactory.signed(Target_java_lang_ref_Reference.referentFieldOffset), value); + BarrieredAccess.writeObject(instance, Word.signed(Target_java_lang_ref_Reference.referentFieldOffset), value); } @Uninterruptible(reason = "Must be atomic with regard to garbage collection.") @@ -94,7 +93,7 @@ public static boolean refersTo(Reference instance, Object value) { * Use a read without a barrier to avoid that this code keeps the object alive, see * JDK-8188055. */ - return value == ObjectAccess.readObject(instance, WordFactory.signed(Target_java_lang_ref_Reference.referentFieldOffset)); + return value == ObjectAccess.readObject(instance, Word.signed(Target_java_lang_ref_Reference.referentFieldOffset)); } @Uninterruptible(reason = "Must be atomic with regard to garbage collection.") @@ -114,12 +113,12 @@ public static void clear(Reference instance) { * * This barrier-less write is to resolve JDK-8240696. */ - ObjectAccess.writeObject(instance, WordFactory.signed(Target_java_lang_ref_Reference.referentFieldOffset), null); + ObjectAccess.writeObject(instance, Word.signed(Target_java_lang_ref_Reference.referentFieldOffset), null); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static Pointer getReferentFieldAddress(Reference instance) { - return Word.objectToUntrackedPointer(instance).add(WordFactory.unsigned(Target_java_lang_ref_Reference.referentFieldOffset)); + return Word.objectToUntrackedPointer(instance).add(Word.unsigned(Target_java_lang_ref_Reference.referentFieldOffset)); } public static long getReferentFieldOffset() { @@ -134,7 +133,7 @@ public static Reference getNextDiscovered(Reference instance) { /** Barrier-less read of {@link Target_java_lang_ref_Reference#discovered} as a pointer. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static Pointer getDiscoveredPointer(Reference instance) { - return Word.objectToUntrackedPointer(ObjectAccess.readObject(instance, WordFactory.signed(Target_java_lang_ref_Reference.discoveredFieldOffset))); + return Word.objectToUntrackedPointer(ObjectAccess.readObject(instance, Word.signed(Target_java_lang_ref_Reference.discoveredFieldOffset))); } public static long getQueueFieldOffset() { @@ -157,7 +156,7 @@ public static boolean isAnyReferenceFieldOffset(long offset) { /** Write {@link Target_java_lang_ref_Reference#discovered}. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void setNextDiscovered(Reference instance, Reference newNext) { - BarrieredAccess.writeObject(instance, WordFactory.signed(Target_java_lang_ref_Reference.discoveredFieldOffset), newNext); + BarrieredAccess.writeObject(instance, Word.signed(Target_java_lang_ref_Reference.discoveredFieldOffset), newNext); } public static boolean hasQueue(Reference instance) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/StoredContinuationAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/StoredContinuationAccess.java index 48a2af10dbf6..701b6fb03e5c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/StoredContinuationAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/StoredContinuationAccess.java @@ -32,7 +32,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -127,7 +126,7 @@ public static int allocateToYield(Target_jdk_internal_vm_Continuation c, Pointer @Uninterruptible(reason = "Prevent modifications to the stack while initializing instance and copying frames.") private static void fillUninterruptibly(StoredContinuation stored, CodePointer ip, Pointer sp, int size) { - UnmanagedMemoryUtil.copyWordsForward(sp, getFramesStart(stored), WordFactory.unsigned(size)); + UnmanagedMemoryUtil.copyWordsForward(sp, getFramesStart(stored), Word.unsigned(size)); setIP(stored, ip); afterFill(stored); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Target_jdk_internal_ref_Cleaner.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Target_jdk_internal_ref_Cleaner.java index 045a160af84d..682ec5381bff 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Target_jdk_internal_ref_Cleaner.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Target_jdk_internal_ref_Cleaner.java @@ -27,6 +27,7 @@ import java.lang.ref.Cleaner; import java.lang.ref.ReferenceQueue; +import jdk.graal.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.hosted.FieldValueTransformer; import com.oracle.svm.core.NeverInline; @@ -35,7 +36,11 @@ 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.jdk.JDK21OrEarlier; +import com.oracle.svm.core.jdk.JDKLatest; import com.oracle.svm.core.thread.VMThreads; +import com.oracle.svm.util.ReflectionUtil; import jdk.internal.misc.InnocuousThread; @@ -74,8 +79,13 @@ final class Target_java_lang_ref_Cleaner_Cleanable { @TargetClass(className = "jdk.internal.ref.CleanerImpl") final class Target_jdk_internal_ref_CleanerImpl { + @TargetElement(onlyWith = JDK21OrEarlier.class)// @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClassName = "jdk.internal.ref.CleanerImpl$PhantomCleanableRef")// - Target_jdk_internal_ref_PhantomCleanable phantomCleanableList; + Target_jdk_internal_ref_PhantomCleanable_JDK21 phantomCleanableList; + + @TargetElement(onlyWith = JDKLatest.class)// + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClassName = "jdk.internal.ref.CleanerImpl$CleanableList")// + Target_jdk_internal_ref_CleanerImpl_CleanableList activeList; @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClassName = "java.lang.ref.ReferenceQueue")// public ReferenceQueue queue; @@ -85,8 +95,9 @@ final class Target_jdk_internal_ref_CleanerImpl { * the {@code Cleaner} itself), ignoring {@link InterruptedException}. This blocks VM tear-down, * so we add a check if the VM is tearing down here. */ + @TargetElement(name = "run", onlyWith = JDK21OrEarlier.class) @Substitute - public void run() { + public void runJDK21() { Thread t = Thread.currentThread(); InnocuousThread mlThread = (t instanceof InnocuousThread) ? (InnocuousThread) t : null; while (!phantomCleanableList.isListEmpty()) { @@ -105,20 +116,42 @@ public void run() { } } } + + @TargetElement(name = "run", onlyWith = JDKLatest.class) + @Substitute + public void run() { + Thread t = Thread.currentThread(); + InnocuousThread mlThread = (t instanceof InnocuousThread) ? (InnocuousThread) t : null; + while (!activeList.isEmpty()) { + if (mlThread != null) { + mlThread.eraseThreadLocals(); + } + try { + Cleaner.Cleanable ref = (Cleaner.Cleanable) queue.remove(60 * 1000L); + if (ref != null) { + ref.clean(); + } + } catch (Throwable e) { + if (VMThreads.isTearingDown()) { + return; + } + } + } + } } -@TargetClass(className = "jdk.internal.ref.PhantomCleanable") -final class Target_jdk_internal_ref_PhantomCleanable { +@TargetClass(className = "jdk.internal.ref.PhantomCleanable", onlyWith = JDK21OrEarlier.class) +final class Target_jdk_internal_ref_PhantomCleanable_JDK21 { /* * Unlink from the list for the image heap so that we cannot reach Cleanables irrelevant for the * image heap which could fail the image build; we reset the list head anyway. */ @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = HolderObjectFieldTransformer.class) // - Target_jdk_internal_ref_PhantomCleanable prev; + Target_jdk_internal_ref_PhantomCleanable_JDK21 prev; @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = HolderObjectFieldTransformer.class) // - Target_jdk_internal_ref_PhantomCleanable next; + Target_jdk_internal_ref_PhantomCleanable_JDK21 next; @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = HolderObjectFieldTransformer.class) // - Target_jdk_internal_ref_PhantomCleanable list; + Target_jdk_internal_ref_PhantomCleanable_JDK21 list; @Alias native boolean isListEmpty(); @@ -128,9 +161,59 @@ final class Target_jdk_internal_ref_PhantomCleanable { /* final */ native void clean(); } +@TargetClass(className = "jdk.internal.ref.PhantomCleanable", onlyWith = JDKLatest.class) +final class Target_jdk_internal_ref_PhantomCleanable { + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = GetCleanableListSingletonTransformer.class) // + Target_jdk_internal_ref_CleanerImpl_CleanableList list; + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // + Target_jdk_internal_ref_CleanerImpl_CleanableList_Node node; + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ResetToMinusOneTransformer.class) // + int index; + + @AnnotateOriginal + @NeverInline("Ensure that every exception can be caught, including implicit exceptions.") + /* final */ native void clean(); +} + +@TargetClass(className = "jdk.internal.ref.CleanerImpl$CleanableList", onlyWith = JDKLatest.class) +final class Target_jdk_internal_ref_CleanerImpl_CleanableList { + + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClassName = "jdk.internal.ref.CleanerImpl$CleanableList$Node") // + Target_jdk_internal_ref_CleanerImpl_CleanableList_Node head; + + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // + Target_jdk_internal_ref_CleanerImpl_CleanableList_Node cache; + + @Alias + native boolean isEmpty(); + +} + +@TargetClass(className = "jdk.internal.ref.CleanerImpl$CleanableList$Node", onlyWith = JDKLatest.class) +final class Target_jdk_internal_ref_CleanerImpl_CleanableList_Node { +} + final class HolderObjectFieldTransformer implements FieldValueTransformer { @Override public Object transform(Object receiver, Object originalValue) { return receiver; } } + +final class Target_jdk_internal_ref_CleanerImpl_CleanableList_Singleton { + static final Object list = JavaVersionUtil.JAVA_SPEC > 21 ? ReflectionUtil.newInstance(ReflectionUtil.lookupClass("jdk.internal.ref.CleanerImpl$CleanableList")) : null; +} + +final class GetCleanableListSingletonTransformer implements FieldValueTransformer { + @Override + public Object transform(Object receiver, Object originalValue) { + return Target_jdk_internal_ref_CleanerImpl_CleanableList_Singleton.list; + } +} + +final class ResetToMinusOneTransformer implements FieldValueTransformer { + @Override + public Object transform(Object receiver, Object originalValue) { + return -1; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java index 942812612c05..79f73b148ae6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.heap.dump; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -36,7 +37,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.BuildPhaseProvider.AfterCompilation; import com.oracle.svm.core.c.NonmovableArrays; @@ -141,19 +141,19 @@ public boolean initialize() { * encoded data more efficiently. At first, we allocate some memory for those data * structures so that we can abort right away in case that an allocation fails. */ - UnsignedWord classInfosSize = WordFactory.unsigned(classInfoCount).multiply(SizeOf.get(ClassInfo.class)); + UnsignedWord classInfosSize = Word.unsigned(classInfoCount).multiply(SizeOf.get(ClassInfo.class)); classInfos = NullableNativeMemory.calloc(classInfosSize, NmtCategory.HeapDump); if (classInfos.isNull()) { return false; } - UnsignedWord fieldStartsSize = WordFactory.unsigned(totalFieldCount).multiply(SizeOf.get(FieldInfoPointer.class)); + UnsignedWord fieldStartsSize = Word.unsigned(totalFieldCount).multiply(SizeOf.get(FieldInfoPointer.class)); fieldInfoTable = NullableNativeMemory.calloc(fieldStartsSize, NmtCategory.HeapDump); if (fieldInfoTable.isNull()) { return false; } - UnsignedWord fieldNameTableSize = WordFactory.unsigned(fieldNameCount).multiply(SizeOf.get(FieldNamePointer.class)); + UnsignedWord fieldNameTableSize = Word.unsigned(fieldNameCount).multiply(SizeOf.get(FieldNamePointer.class)); fieldNameTable = NullableNativeMemory.calloc(fieldNameTableSize, NmtCategory.HeapDump); if (fieldNameTable.isNull()) { return false; @@ -217,13 +217,13 @@ public boolean initialize() { */ public void teardown() { NullableNativeMemory.free(classInfos); - classInfos = WordFactory.nullPointer(); + classInfos = Word.nullPointer(); NullableNativeMemory.free(fieldInfoTable); - fieldInfoTable = WordFactory.nullPointer(); + fieldInfoTable = Word.nullPointer(); NullableNativeMemory.free(fieldNameTable); - fieldNameTable = WordFactory.nullPointer(); + fieldNameTable = Word.nullPointer(); } public int getClassInfoCount() { @@ -232,14 +232,14 @@ public int getClassInfoCount() { public ClassInfo getClassInfo(Class clazz) { if (clazz == null) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } return getClassInfo(DynamicHub.fromClass(clazz)); } public ClassInfo getClassInfo(DynamicHub hub) { if (hub == null) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } return getClassInfo(hub.getTypeID()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java index 173fae2fecab..b4f26a9ff46d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java @@ -28,6 +28,7 @@ import java.io.IOException; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; @@ -36,7 +37,6 @@ import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.heap.GCCause; @@ -81,7 +81,7 @@ public void initializeDumpHeapOnOutOfMemoryError() { @Override public void teardownDumpHeapOnOutOfMemoryError() { UntrackedNullableNativeMemory.free(outOfMemoryHeapDumpPath); - outOfMemoryHeapDumpPath = WordFactory.nullPointer(); + outOfMemoryHeapDumpPath = Word.nullPointer(); } @Override @@ -149,7 +149,7 @@ public void dumpHeap(String filename, boolean gcBefore, boolean overwrite) throw private boolean dumpHeap(RawFileDescriptor fd, boolean gcBefore) { int size = SizeOf.get(HeapDumpVMOperationData.class); HeapDumpVMOperationData data = StackValue.get(size); - UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); + UnmanagedMemoryUtil.fill((Pointer) data, Word.unsigned(size), (byte) 0); data.setGCBefore(gcBefore); data.setRawFileDescriptor(fd); heapDumpOperation.enqueue(data); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java index 3e540f152d02..196999b34800 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java @@ -35,7 +35,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.StaticFieldsSupport; @@ -459,7 +458,7 @@ private void teardown() { assert f.isNull() || error || file().getUnflushedDataSize(f) == 0; file().free(f); - this.f = WordFactory.nullPointer(); + this.f = Word.nullPointer(); this.topLevelRecordBegin = -1; this.subRecordBegin = -1; @@ -562,7 +561,7 @@ private void writeSymbol(String value) { private void writeSymbol(FieldName fieldName) { startTopLevelRecord(HProfTopLevelRecord.UTF8); writeFieldNameId(fieldName); - write((Pointer) FieldNameAccess.getChars(fieldName), WordFactory.unsigned(FieldNameAccess.getLength(fieldName))); + write((Pointer) FieldNameAccess.getChars(fieldName), Word.unsigned(FieldNameAccess.getLength(fieldName))); endTopLevelRecord(); } @@ -774,7 +773,7 @@ private void writeObjects() { writeLargeObjects(largeObjects); } finally { GrowableWordArrayAccess.freeData(largeObjects); - largeObjects = WordFactory.nullPointer(); + largeObjects = Word.nullPointer(); } } @@ -968,8 +967,8 @@ private void writeObjectArray(Object array) { */ private static int calculateMaxArrayLength(Object array, int elementSize, int recordHeaderSize) { int length = ArrayLengthNode.arrayLength(array); - UnsignedWord lengthInBytes = WordFactory.unsigned(length).multiply(elementSize); - UnsignedWord maxBytes = WordFactory.unsigned(MAX_UNSIGNED_INT).subtract(recordHeaderSize); + UnsignedWord lengthInBytes = Word.unsigned(length).multiply(elementSize); + UnsignedWord maxBytes = Word.unsigned(MAX_UNSIGNED_INT).subtract(recordHeaderSize); if (lengthInBytes.belowOrEqual(maxBytes)) { return length; @@ -993,7 +992,7 @@ private void writeWordArray(Object array, int length, int arrayBaseOffset) { private void writeU1ArrayData(Object array, int length, int arrayBaseOffset) { Pointer data = getArrayData(array, arrayBaseOffset); - write(data, WordFactory.unsigned(length)); + write(data, Word.unsigned(length)); } private void writeU2ArrayData(Object array, int length, int arrayBaseOffset) { @@ -1377,10 +1376,10 @@ private UnsignedWord getObjectSize(Object obj) { elementSize = wordSize(); } int length = ArrayLengthNode.arrayLength(obj); - return WordFactory.unsigned(length).multiply(elementSize); + return Word.unsigned(length).multiply(elementSize); } else { ClassInfo classInfo = metadata.getClassInfo(obj.getClass()); - return WordFactory.unsigned(classInfo.getInstanceFieldsDumpSize()); + return Word.unsigned(classInfo.getInstanceFieldsDumpSize()); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index 9d83bf6bd9ed..f329bf51111d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -40,6 +40,11 @@ import static com.oracle.svm.core.code.RuntimeMetadataDecoderImpl.ALL_RECORD_COMPONENTS_FLAG; import static com.oracle.svm.core.code.RuntimeMetadataDecoderImpl.ALL_SIGNERS_FLAG; import static com.oracle.svm.core.code.RuntimeMetadataDecoderImpl.CLASS_ACCESS_FLAGS_MASK; +import static com.oracle.svm.core.graal.meta.DynamicHubOffsets.writeObject; +import static com.oracle.svm.core.graal.meta.DynamicHubOffsets.writeInt; +import static com.oracle.svm.core.graal.meta.DynamicHubOffsets.writeChar; +import static com.oracle.svm.core.graal.meta.DynamicHubOffsets.writeShort; +import static com.oracle.svm.core.graal.meta.DynamicHubOffsets.writeByte; import static com.oracle.svm.core.reflect.RuntimeMetadataDecoder.NO_DATA; import java.io.InputStream; @@ -85,6 +90,7 @@ import com.oracle.svm.core.BuildPhaseProvider.AfterCompilation; import com.oracle.svm.core.BuildPhaseProvider.AfterHostedUniverse; import com.oracle.svm.core.BuildPhaseProvider.CompileQueueFinished; +import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.RuntimeAssertionsSupport; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; @@ -101,8 +107,11 @@ import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.config.ObjectLayout; import com.oracle.svm.core.configure.RuntimeConditionSet; +import com.oracle.svm.core.graal.meta.DynamicHubOffsets; +import com.oracle.svm.core.graal.nodes.SubstrateNewDynamicHubNode; import com.oracle.svm.core.heap.UnknownObjectField; import com.oracle.svm.core.heap.UnknownPrimitiveField; +import com.oracle.svm.core.imagelayer.DynamicImageLayerInfo; import com.oracle.svm.core.jdk.JDK21OrEarlier; import com.oracle.svm.core.jdk.JDKLatest; import com.oracle.svm.core.jdk.Resources; @@ -123,6 +132,7 @@ import jdk.graal.compiler.api.directives.GraalDirectives; import jdk.graal.compiler.core.common.NumUtil; import jdk.graal.compiler.core.common.SuppressFBWarnings; +import jdk.graal.compiler.nodes.java.FinalFieldBarrierNode; import jdk.graal.compiler.replacements.ReplacementsUtil; import jdk.graal.compiler.serviceprovider.JavaVersionUtil; import jdk.internal.access.JavaLangReflectAccess; @@ -141,6 +151,10 @@ /** * Instantiations of this class have a special layout. See {@code DynamicHubLayout} for a * description of how the object is arranged. + *

      + * A {@code DynamicHub} ends up initialized in the read-only part of the image heap, and therefore + * fields are considered immutable. In scenarios where a {@code DynamicHub} can be allocated at + * run-time it is important to keep this property. */ @Substitute @TargetClass(java.lang.Class.class) @@ -384,6 +398,7 @@ public final class DynamicHub implements AnnotatedElement, java.lang.reflect.Typ * classes/interfaces already initialized during image generation, i.e., this field is never * null at run time. */ + @Stable // private ClassInitializationInfo classInitializationInfo; @UnknownObjectField(availability = AfterHostedUniverse.class)// @@ -430,17 +445,16 @@ public final class DynamicHub implements AnnotatedElement, java.lang.reflect.Typ @InjectAccessors(CachedConstructorAccessors.class) // private Constructor cachedConstructor; - @UnknownObjectField(canBeNull = true, availability = AfterCompilation.class) private DynamicHubMetadata hubMetadata; + @UnknownObjectField(canBeNull = true, availability = AfterCompilation.class) // + private DynamicHubMetadata hubMetadata; - @UnknownObjectField(canBeNull = true, availability = AfterCompilation.class) private ReflectionMetadata reflectionMetadata; + @UnknownObjectField(canBeNull = true, availability = AfterCompilation.class) // + private ReflectionMetadata reflectionMetadata; @Platforms(Platform.HOSTED_ONLY.class) - public DynamicHub(Class hostedJavaClass, String name, int hubType, ReferenceType referenceType, DynamicHub superType, DynamicHub componentHub, String sourceFileName, int modifiers, - ClassLoader classLoader, boolean isHidden, boolean isRecord, Class nestHost, boolean assertionStatus, boolean hasDefaultMethods, boolean declaresDefaultMethods, - boolean isSealed, boolean isVMInternal, boolean isLambdaFormHidden, boolean isLinked, String simpleBinaryName, Object declaringClass, String signature, boolean isProxyClass, - int layerId) { - VMError.guarantee(layerId == (byte) layerId, "Layer id %d not in expected range", layerId); - + public DynamicHub(Class hostedJavaClass, String name, int hubType, ReferenceType referenceType, DynamicHub superType, + DynamicHub componentHub, String sourceFileName, int modifiers, short flags, ClassLoader classLoader, + Class nestHost, String simpleBinaryName, Object declaringClass, String signature, int layerId) { this.hostedJavaClass = hostedJavaClass; this.module = hostedJavaClass.getModule(); this.name = name; @@ -454,10 +468,145 @@ public DynamicHub(Class hostedJavaClass, String name, int hubType, ReferenceT this.simpleBinaryName = simpleBinaryName; this.declaringClass = declaringClass; this.signature = signature; - this.layerId = (byte) layerId; - this.flags = NumUtil.safeToUShort(makeFlag(IS_PRIMITIVE_FLAG_BIT, hostedJavaClass.isPrimitive()) | - makeFlag(IS_INTERFACE_FLAG_BIT, hostedJavaClass.isInterface()) | + assert layerId < DynamicImageLayerInfo.CREMA_LAYER_ID; + this.layerId = NumUtil.safeToByte(layerId); + + this.flags = flags; + + this.companion = new DynamicHubCompanion(hostedJavaClass, classLoader); + } + + /** + * This helper is used for allocating and initializing a new {@code DynamicHub} at runtime. + *

      + * Fields in {@code DynamicHub} are immutable, and writes in this method have location identity + * {@code ANY_LOCATION}: With this setup the compiler is allowed to float reads above such + * writes, therefore there must not be any reads in this helper. Also, further code must not be + * reachable that reads from the just created {@code DynamicHub} in this method. + *

      + * Regular stores should also not be used for non-final fields of {@code DynamicHub}, otherwise + * the analysis won't conclude immutability for such fields. + *

      + * Note that the GC can handle partially initialized {@code DynamicHub}s therefore this helper + * does not need to be {@link Uninterruptible}. However, other components might not, e.g. a + * {@code DynamicHub} must be fully initialized when it is used in an object header. + */ + @NeverInline("Fields of DynamicHub are immutable. Immutable reads could float above ANY_LOCATION writes.") + public static DynamicHub allocate(String name, DynamicHub superHub, DynamicHub componentHub, String sourceFileName, + int modifiers, short flags, ClassLoader classLoader, Class nestHost, String simpleBinaryName, + Object declaringClass, String signature) { + VMError.guarantee(RuntimeClassLoading.isSupported()); + + ReferenceType referenceType = ReferenceType.computeReferenceType(DynamicHub.toClass(superHub)); + // GR-59683: HubType.OBJECT_ARRAY? + int hubTybe = referenceType == ReferenceType.None ? HubType.INSTANCE : HubType.REFERENCE_INSTANCE; + + DynamicHubCompanion companion = new DynamicHubCompanion(classLoader); + /* Always allow unsafe allocation for classes that were loaded at run-time. */ + companion.setUnsafeAllocate(); + + // GR-59687: Correct size and content for vtable + int vTableEntries = 0x100; + ClassInitializationInfo classInitializationInfo = new ClassInitializationInfo(false); + + // GR-60069: Determine size for instance and offsets for monitor and identityHashCode + int layoutEncoding = 0x40; + char monitorOffset = 0; + char identityHashOffset = 0; + + // GR-59687: Determine typecheck related infos + int typeID = 0; + int typeIDDepth = 0; + int numClassTypes = 2; + int numInterfacesTypes = 0; + int[] openTypeWorldTypeCheckSlots = new int[numClassTypes + (numInterfacesTypes * 2)]; + + byte additionalFlags = NumUtil.safeToUByte(makeFlag(IS_INSTANTIATED_BIT, true)); + + // GR-59683: Proper values needed. + DynamicHub arrayHub = null; + Object interfacesEncoding = null; + Object enumConstantsReference = null; + + // GR-60080: Proper referenceMap needed. + int referenceMapIndex = DynamicHub.fromClass(Object.class).referenceMapIndex; + + // GR-59683: Maybe can be used to inject a backreference to InterpreterResolvedObjectType + SharedType metaType = null; + + // GR-59683 + Module module = null; + + // GR-57813 + int classRedefinedCount = 0; + DynamicHubMetadata hubMetadata = null; + ReflectionMetadata reflectionMetadata = null; + + /* + * We cannot do the allocation via {@code new DynamicHub(...)} because we need to inject the + * length for its vtable. + */ + DynamicHub hub = SubstrateNewDynamicHubNode.allocate(DynamicHub.class, vTableEntries); + + DynamicHubOffsets dynamicHubOffsets = DynamicHubOffsets.singleton(); + /* Write fields in defining order. */ + writeObject(hub, dynamicHubOffsets.getNameOffset(), name); + writeInt(hub, dynamicHubOffsets.getHubTypeOffset(), hubTybe); + writeByte(hub, dynamicHubOffsets.getReferenceTypeOffset(), referenceType.getValue()); + + writeInt(hub, dynamicHubOffsets.getLayoutEncodingOffset(), layoutEncoding); + writeInt(hub, dynamicHubOffsets.getTypeIDOffset(), typeID); + // skip typeCheckStart, typeCheckRange, typeCheckSlot and + // closedTypeWorldTypeCheckSlots (closed-world only) + writeInt(hub, dynamicHubOffsets.getTypeIDDepthOffset(), typeIDDepth); + writeInt(hub, dynamicHubOffsets.getNumClassTypesOffset(), numClassTypes); + + writeInt(hub, dynamicHubOffsets.getNumInterfaceTypesOffset(), numInterfacesTypes); + writeObject(hub, dynamicHubOffsets.getOpenTypeWorldTypeCheckSlotsOffset(), openTypeWorldTypeCheckSlots); + + writeChar(hub, dynamicHubOffsets.getMonitorOffsetOffset(), monitorOffset); + writeChar(hub, dynamicHubOffsets.getIdentityHashOffsetOffset(), identityHashOffset); + + writeShort(hub, dynamicHubOffsets.getFlagsOffset(), flags); + writeByte(hub, dynamicHubOffsets.getAdditionalFlagsOffset(), additionalFlags); + writeInt(hub, dynamicHubOffsets.getModifiersOffset(), modifiers); + + writeObject(hub, dynamicHubOffsets.getSuperHubOffset(), superHub); + writeObject(hub, dynamicHubOffsets.getComponentTypeOffset(), componentHub); + writeObject(hub, dynamicHubOffsets.getArrayHubOffset(), arrayHub); + + writeObject(hub, dynamicHubOffsets.getDeclaringClassOffset(), declaringClass); + writeObject(hub, dynamicHubOffsets.getInterfacesEncodingOffset(), interfacesEncoding); + writeObject(hub, dynamicHubOffsets.getEnumConstantsReferenceOffset(), enumConstantsReference); + + writeInt(hub, dynamicHubOffsets.getReferenceMapIndexOffset(), referenceMapIndex); + writeByte(hub, dynamicHubOffsets.getLayerIdOffset(), NumUtil.safeToByte(DynamicImageLayerInfo.CREMA_LAYER_ID)); + writeObject(hub, dynamicHubOffsets.getMetaTypeOffset(), metaType); + + writeObject(hub, dynamicHubOffsets.getSourceFileNameOffset(), sourceFileName); + writeObject(hub, dynamicHubOffsets.getClassInitializationInfoOffset(), classInitializationInfo); + // skip vtable (special treatment) + writeObject(hub, dynamicHubOffsets.getModuleOffset(), module); + + writeObject(hub, dynamicHubOffsets.getNestHostOffset(), nestHost); + writeObject(hub, dynamicHubOffsets.getSimpleBinaryNameOffset(), simpleBinaryName); + writeObject(hub, dynamicHubOffsets.getCompanionOffset(), companion); + + writeObject(hub, dynamicHubOffsets.getSignatureOffset(), signature); + writeInt(hub, dynamicHubOffsets.getClassRedefinedCountOffset(), classRedefinedCount); + writeObject(hub, dynamicHubOffsets.getHubMetadataOffset(), hubMetadata); + writeObject(hub, dynamicHubOffsets.getReflectionMetadataOffset(), reflectionMetadata); + + FinalFieldBarrierNode.finalFieldBarrier(hub); + + return hub; + } + + public static short makeFlags(boolean isPrimitive, boolean isInterface, boolean isHidden, boolean isRecord, boolean assertionStatus, boolean hasDefaultMethods, boolean declaresDefaultMethods, + boolean isSealed, boolean isVMInternal, boolean isLambdaFormHidden, boolean isLinked, boolean isProxyClass) { + return NumUtil.safeToUShort(makeFlag(IS_PRIMITIVE_FLAG_BIT, isPrimitive) | + makeFlag(IS_INTERFACE_FLAG_BIT, isInterface) | makeFlag(IS_HIDDEN_FLAG_BIT, isHidden) | makeFlag(IS_RECORD_FLAG_BIT, isRecord) | makeFlag(ASSERTION_STATUS_FLAG_BIT, assertionStatus) | @@ -468,11 +617,8 @@ public DynamicHub(Class hostedJavaClass, String name, int hubType, ReferenceT makeFlag(IS_LAMBDA_FORM_HIDDEN_BIT, isLambdaFormHidden) | makeFlag(IS_LINKED_BIT, isLinked) | makeFlag(IS_PROXY_CLASS_BIT, isProxyClass)); - - this.companion = new DynamicHubCompanion(hostedJavaClass, classLoader); } - @Platforms(Platform.HOSTED_ONLY.class) private static int makeFlag(int flagBit, boolean value) { int flagMask = 1 << flagBit; return value ? flagMask : 0; @@ -492,6 +638,7 @@ private static boolean isFlagSet(short flags, int flagBit) { @Platforms(Platform.HOSTED_ONLY.class) public void setClassInitializationInfo(ClassInitializationInfo classInitializationInfo) { + assert classInitializationInfo != null; this.classInitializationInfo = classInitializationInfo; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java index d3748d52e5c6..16f94b255765 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java @@ -66,6 +66,11 @@ public final class DynamicHubCompanion { this.classLoader = PredefinedClassesSupport.isPredefined(hostedJavaClass) ? NO_CLASS_LOADER : classLoader; } + DynamicHubCompanion(ClassLoader classLoader) { + assert RuntimeClassLoading.isSupported(); + this.classLoader = classLoader; + } + String getPackageName(DynamicHub hub) { if (packageName == null) { packageName = hub.computePackageName(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/InteriorObjRefWalker.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/InteriorObjRefWalker.java index 8ffa4550e36d..74a16935b981 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/InteriorObjRefWalker.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/InteriorObjRefWalker.java @@ -27,7 +27,6 @@ import java.util.function.IntConsumer; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.NeverInline; @@ -104,7 +103,7 @@ public static boolean walkInstanceReferenceOffsets(DynamicHub objHub, IntConsume NonmovableArray referenceMapEncoding = DynamicHubSupport.forLayer(objHub.getLayerId()).getReferenceMapEncoding(); long referenceMapIndex = objHub.getReferenceMapIndex(); - return InstanceReferenceMapDecoder.walkOffsetsFromPointer(WordFactory.zero(), referenceMapEncoding, referenceMapIndex, new ObjectReferenceVisitor() { + return InstanceReferenceMapDecoder.walkOffsetsFromPointer(Word.zero(), referenceMapEncoding, referenceMapIndex, new ObjectReferenceVisitor() { @Override public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { offsetConsumer.accept((int) objRef.rawValue()); @@ -127,7 +126,7 @@ private static boolean walkInstance(Object obj, ObjectReferenceVisitor visitor, @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static boolean walkReferenceInstance(Object obj, ObjectReferenceVisitor visitor, DynamicHub objHub, Pointer objPointer) { long discoveredOffset = ReferenceInternals.getNextDiscoveredFieldOffset(); - Pointer objRef = objPointer.add(WordFactory.unsigned(discoveredOffset)); + Pointer objRef = objPointer.add(Word.unsigned(discoveredOffset)); // The Object reference at the discovered offset needs to be visited separately as it is not // part of the reference map. @@ -167,7 +166,7 @@ private static boolean walkObjectArray(Object obj, ObjectReferenceVisitor visito boolean isCompressed = ReferenceAccess.singleton().haveCompressedReferences(); Pointer pos = objPointer.add(LayoutEncoding.getArrayBaseOffset(objHub.getLayoutEncoding())); - Pointer end = pos.add(WordFactory.unsigned(referenceSize).multiply(length)); + Pointer end = pos.add(Word.unsigned(referenceSize).multiply(length)); while (pos.belowThan(end)) { final boolean visitResult = callVisitor(obj, visitor, isCompressed, pos); if (!visitResult) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/LayoutEncoding.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/LayoutEncoding.java index 4dbf7d647ab9..a39a8b09a2f7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/LayoutEncoding.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/LayoutEncoding.java @@ -28,7 +28,6 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.Uninterruptible; @@ -125,7 +124,7 @@ public static int forPureInstance(ResolvedJavaType type, int size) { guaranteeEncoding(type, false, isHybrid(encoding), "Instance type encoding denotes a hybrid"); guaranteeEncoding(type, false, isObjectArray(encoding) || isArrayLikeWithObjectElements(encoding), "Instance type encoding denotes an object array"); guaranteeEncoding(type, false, isPrimitiveArray(encoding) || isArrayLikeWithPrimitiveElements(encoding), "Instance type encoding denotes a primitive array"); - guaranteeEncoding(type, true, getPureInstanceAllocationSize(encoding).equal(WordFactory.unsigned(size)), "Instance type encoding size matches type size"); + guaranteeEncoding(type, true, getPureInstanceAllocationSize(encoding).equal(Word.unsigned(size)), "Instance type encoding size matches type size"); return encoding; } @@ -159,7 +158,7 @@ private static int forArrayLike(ResolvedJavaType type, boolean isHybrid, boolean guaranteeEncoding(type, !objectElements, isArrayLikeWithPrimitiveElements(encoding), "Encoding denotes an array-like object with primitive elements"); guaranteeEncoding(type, !isHybrid && objectElements, isObjectArray(encoding), "Encoding denotes an object array"); guaranteeEncoding(type, !isHybrid && !objectElements, isPrimitiveArray(encoding), "Encoding denotes a primitive array"); - guaranteeEncoding(type, true, getArrayBaseOffset(encoding).equal(WordFactory.unsigned(arrayBaseOffset)), + guaranteeEncoding(type, true, getArrayBaseOffset(encoding).equal(Word.unsigned(arrayBaseOffset)), "Encoding denotes a base offset of " + arrayBaseOffset + " (actual value: " + getArrayBaseOffset(encoding) + ')'); guaranteeEncoding(type, true, getArrayIndexShift(encoding) == arrayIndexShift, "Encoding denotes an index shift of " + arrayIndexShift + " (actual value: " + getArrayIndexShift(encoding) + ')'); @@ -195,7 +194,7 @@ public static boolean isPureInstance(int encoding) { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static UnsignedWord getPureInstanceAllocationSize(int encoding) { - return WordFactory.unsigned(encoding); + return Word.unsigned(encoding); } /** @@ -210,7 +209,7 @@ public static UnsignedWord getPureInstanceSize(DynamicHub hub, boolean withOptio int afterIdHashField = hub.getIdentityHashOffset() + Integer.BYTES; if (size.belowThan(afterIdHashField)) { /* Identity hash is at the end of the object and does not fit in a gap. */ - size = WordFactory.unsigned(ol.alignUp(afterIdHashField)); + size = Word.unsigned(ol.alignUp(afterIdHashField)); } } return size; @@ -262,7 +261,7 @@ public static int getArrayBaseOffsetAsInt(int encoding) { // May be inlined because it does not deal in Pointers. @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static UnsignedWord getArrayBaseOffset(int encoding) { - return WordFactory.unsigned(getArrayBaseOffsetAsInt(encoding)); + return Word.unsigned(getArrayBaseOffsetAsInt(encoding)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -277,7 +276,7 @@ public static int getArrayIndexScale(int encoding) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static UnsignedWord getArrayElementOffset(int encoding, int index) { - return getArrayBaseOffset(encoding).add(WordFactory.unsigned(index).shiftLeft(getArrayIndexShift(encoding))); + return getArrayBaseOffset(encoding).add(Word.unsigned(index).shiftLeft(getArrayIndexShift(encoding))); } /** @@ -297,7 +296,7 @@ public static UnsignedWord getArrayAllocationSize(int encoding, int length) { public static UnsignedWord getArraySize(int encoding, int length, boolean withOptionalIdHashField) { long unalignedSize = getArrayElementOffset(encoding, length).rawValue(); long totalSize = ConfigurationValues.getObjectLayout().computeArrayTotalSize(unalignedSize, withOptionalIdHashField); - return WordFactory.unsigned(totalSize); + return Word.unsigned(totalSize); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ReferenceType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ReferenceType.java index 9bf1c10b44ea..7a23e9e5c478 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ReferenceType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ReferenceType.java @@ -24,10 +24,14 @@ */ package com.oracle.svm.core.hub; -import jdk.graal.compiler.core.common.NumUtil; +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; import com.oracle.svm.core.util.DuplicatedInNativeCode; +import jdk.graal.compiler.core.common.NumUtil; + @DuplicatedInNativeCode public enum ReferenceType { None(0), // non-reference class @@ -45,4 +49,18 @@ public enum ReferenceType { public byte getValue() { return value; } + + public static ReferenceType computeReferenceType(Class type) { + if (Reference.class.isAssignableFrom(type)) { + if (PhantomReference.class.isAssignableFrom(type)) { + return ReferenceType.Phantom; + } else if (SoftReference.class.isAssignableFrom(type)) { + return ReferenceType.Soft; + } else { + /* Treat all other java.lang.Reference subclasses as weak references. */ + return ReferenceType.Weak; + } + } + return ReferenceType.None; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeClassLoadingSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeClassLoading.java similarity index 77% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeClassLoadingSupport.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeClassLoading.java index 2596e2d4fef0..4b62bdb17c66 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeClassLoadingSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeClassLoading.java @@ -24,6 +24,10 @@ */ package com.oracle.svm.core.hub; +import static jdk.graal.compiler.options.OptionStability.EXPERIMENTAL; + +import org.graalvm.collections.EconomicMap; + import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.SubstrateOptionsParser; @@ -31,11 +35,22 @@ import jdk.graal.compiler.api.replacements.Fold; import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.options.OptionKey; -public class RuntimeClassLoadingSupport { +public class RuntimeClassLoading { public static final class Options { - @Option(help = "Enable support for runtime class loading.") // - public static final HostedOptionKey SupportRuntimeClassLoading = new HostedOptionKey<>(false, Options::validate); + @Option(help = "Enable support for runtime class loading.", stability = EXPERIMENTAL) // + public static final HostedOptionKey SupportRuntimeClassLoading = new HostedOptionKey<>(false, Options::validate) { + + @Override + protected void onValueUpdate(EconomicMap, Object> values, Boolean oldValue, Boolean newValue) { + super.onValueUpdate(values, oldValue, newValue); + if (newValue) { + /* requires open type world */ + SubstrateOptions.ClosedTypeWorld.update(values, false); + } + } + }; private static void validate(HostedOptionKey optionKey) { if (optionKey.hasBeenSet() && optionKey.getValue() && SubstrateOptions.ClosedTypeWorld.getValue()) { @@ -45,10 +60,8 @@ private static void validate(HostedOptionKey optionKey) { } } - public static final String ENABLE_CLASS_LOADING_OPTION = SubstrateOptionsParser.commandArgument(Options.SupportRuntimeClassLoading, "+"); - @Fold - public static boolean supportsRuntimeClassLoading() { + public static boolean isSupported() { return Options.SupportRuntimeClassLoading.getValue(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/SunReflectTypeSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/SunReflectTypeSubstitutions.java index de5b1007f859..7fc00500f05c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/SunReflectTypeSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/SunReflectTypeSubstitutions.java @@ -29,8 +29,9 @@ import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.jdk.JDK21OrEarlier; -@TargetClass(sun.reflect.generics.reflectiveObjects.TypeVariableImpl.class) +@TargetClass(value = sun.reflect.generics.reflectiveObjects.TypeVariableImpl.class, onlyWith = JDK21OrEarlier.class) final class Target_sun_reflect_generics_reflectiveObjects_TypeVariableImpl { @Alias GenericDeclaration genericDeclaration; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/IdentityHashCodeSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/IdentityHashCodeSupport.java index 61830ad92036..1b6ab6077687 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/IdentityHashCodeSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/IdentityHashCodeSupport.java @@ -28,7 +28,6 @@ import org.graalvm.word.LocationIdentity; import org.graalvm.word.SignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.config.ConfigurationValues; @@ -90,7 +89,7 @@ public static int generateIdentityHashCode(Object obj) { public static int computeHashCodeFromAddress(Object obj) { Word address = Word.objectToUntrackedPointer(obj); long salt = Heap.getHeap().getIdentityHashSalt(obj); - SignedWord salted = WordFactory.signed(salt).xor(address); + SignedWord salted = Word.signed(salt).xor(address); int hash = mix32(salted.rawValue()) >>> 1; // shift: ensure positive, same as on HotSpot return (hash == 0) ? 1 : hash; // ensure nonzero } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/identityhashcode/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/DynamicImageLayerInfo.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/DynamicImageLayerInfo.java index c09d1665b895..f6dc62618f11 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/DynamicImageLayerInfo.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/DynamicImageLayerInfo.java @@ -30,6 +30,8 @@ import com.oracle.svm.core.meta.SharedMethod; public abstract class DynamicImageLayerInfo { + public static final int CREMA_LAYER_ID = Byte.MAX_VALUE; + public final int layerNumber; public final int nextLayerNumber; public final int numLayers; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerSection.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerSection.java index cd642b947f26..1591129b4a98 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerSection.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerSection.java @@ -57,7 +57,6 @@ public enum SectionEntries { HEAP_END, HEAP_RELOCATABLE_BEGIN, HEAP_RELOCATABLE_END, - HEAP_ANY_RELOCATABLE_POINTER, HEAP_WRITEABLE_BEGIN, HEAP_WRITEABLE_END, HEAP_WRITEABLE_PATCHED_BEGIN, diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jcmd/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jcmd/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jcmd/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BacktraceDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BacktraceDecoder.java index 63eac3eaa6ec..d7ecfa995246 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BacktraceDecoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BacktraceDecoder.java @@ -24,8 +24,8 @@ */ package com.oracle.svm.core.jdk; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.function.CodePointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.code.CodeInfo; @@ -68,7 +68,7 @@ protected final int visitBacktrace(long[] backtrace, int maxFramesProcessed, int backtraceIndex += BacktraceVisitor.entriesPerSourceReference(); } else { /* Entry is a raw code pointer. */ - CodePointer ip = WordFactory.pointer(entry); + CodePointer ip = Word.pointer(entry); /* Arbitrary number of Java frames for a single native frame (inlining). */ framesDecoded = visitCodePointer(ip, framesDecoded, maxFramesProcessed, maxFramesDecodeLimit); backtraceIndex++; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ForeignDisabledSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ForeignDisabledSubstitutions.java index e195b3e125ce..c53431a100bc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ForeignDisabledSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ForeignDisabledSubstitutions.java @@ -54,7 +54,15 @@ public boolean getAsBoolean() { final class Target_jdk_internal_foreign_MemorySessionImpl { @Substitute @SuppressWarnings("static-method") - Target_java_lang_foreign_Arena asArena() { + @TargetElement(name = "asArena", onlyWith = JDK21OrEarlier.class) + Target_java_lang_foreign_Arena asArenaJDK21() { + throw ForeignDisabledSubstitutions.fail(); + } + + @Substitute + @SuppressWarnings("static-method") + @TargetElement(onlyWith = JDKLatest.class) + Target_jdk_internal_foreign_ArenaImpl asArena() { throw ForeignDisabledSubstitutions.fail(); } } @@ -63,6 +71,10 @@ Target_java_lang_foreign_Arena asArena() { final class Target_java_lang_foreign_Arena { } +@TargetClass(className = "jdk.internal.foreign.ArenaImpl", onlyWith = {ForeignDisabled.class, JDKLatest.class}) +final class Target_jdk_internal_foreign_ArenaImpl { +} + @TargetClass(className = "java.lang.foreign.Linker", onlyWith = ForeignDisabled.class) final class Target_java_lang_foreign_Linker { } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java index 7f5466dd8213..e748403a552d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -33,9 +33,7 @@ import java.io.PrintStream; import java.net.URL; import java.security.Permission; -import java.util.Arrays; import java.util.Enumeration; -import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; @@ -81,7 +79,6 @@ import jdk.graal.compiler.replacements.nodes.UnaryMathIntrinsicNode; import jdk.graal.compiler.replacements.nodes.UnaryMathIntrinsicNode.UnaryOperation; import jdk.internal.loader.ClassLoaderValue; -import jdk.internal.module.ServicesCatalog; @TargetClass(java.lang.Object.class) @SuppressWarnings("static-method") @@ -714,6 +711,7 @@ public static Enumeration findResources(String name) { final class Target_jdk_internal_logger_LoggerFinderLoader { // Checkstyle: stop @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset, isFinal = true)// + @TargetElement(onlyWith = JDK21OrEarlier.class)// static Permission READ_PERMISSION; // Checkstyle: resume } @@ -724,24 +722,7 @@ public Object transform(Object receiver, Object originalValue) { if (originalValue == null) { return null; } - - ConcurrentHashMap original = (ConcurrentHashMap) originalValue; - List> clvs = Arrays.asList( - ReflectionUtil.readField(ServicesCatalog.class, "CLV", null), - ReflectionUtil.readField(ModuleLayer.class, "CLV", null)); - - var res = new ConcurrentHashMap<>(); - for (ClassLoaderValue clv : clvs) { - if (clv == null) { - throw VMError.shouldNotReachHere("Field must not be null. Please check what changed in the JDK."); - } - var catalog = original.get(clv); - if (catalog != null) { - res.put(clv, catalog); - } - } - - return res; + return RuntimeClassLoaderValueSupport.instance().getClassLoaderValueMapForLoader((ClassLoader) receiver); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaNetSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaNetSubstitutions.java index a124d7ac99ce..6d72082686a6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaNetSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaNetSubstitutions.java @@ -36,13 +36,13 @@ import java.util.List; import java.util.Set; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.LocationIdentity; import org.graalvm.word.Pointer; import org.graalvm.word.SignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.annotate.Alias; @@ -107,8 +107,8 @@ static boolean get() { return hasSystemProxies; } - static final SignedWord UNINITIALIZED = WordFactory.signed(-2); - static final SignedWord INITIALIZING = WordFactory.signed(-1); + static final SignedWord UNINITIALIZED = Word.signed(-2); + static final SignedWord INITIALIZING = Word.signed(-1); static final CGlobalData initState = CGlobalDataFactory.createWord(UNINITIALIZED); @@ -124,7 +124,7 @@ static boolean ensureInitialized() { } if (initState.get().logicCompareAndSwapWord(0, UNINITIALIZED, INITIALIZING, LocationIdentity.ANY_LOCATION)) { boolean result = Target_sun_net_spi_DefaultProxySelector.init(); - initState.get().writeWord(0, WordFactory.signed(result ? 1 : 0)); + initState.get().writeWord(0, Word.signed(result ? 1 : 0)); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaUtilSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaUtilSubstitutions.java index 1e79daf96c57..d85cd9cc1c51 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaUtilSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaUtilSubstitutions.java @@ -37,6 +37,7 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Inject; +import com.oracle.svm.core.annotate.InjectAccessors; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; import com.oracle.svm.core.annotate.Substitute; @@ -150,6 +151,46 @@ final class Target_java_util_concurrent_ConcurrentHashMap { @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// Target_java_util_concurrent_ConcurrentHashMap_EntrySetView entrySet; + @Alias @InjectAccessors(NCPUAccessor.class) // + private static int NCPU; +} + +final class NCPUAccessor { + private static int cachedNCPU = -1; + + static int get() { + if (cachedNCPU != -1) { + return cachedNCPU; + } + return initializeNCPU(); + } + + private static synchronized int initializeNCPU() { + if (cachedNCPU != -1) { + return cachedNCPU; + } + + cachedNCPU = Runtime.getRuntime().availableProcessors(); + return cachedNCPU; + } + + static synchronized void set(int value) { + cachedNCPU = value; + } +} + +@TargetClass(java.util.concurrent.Phaser.class) +final class Target_java_util_concurrent_Phaser { + + @Alias @InjectAccessors(NCPUAccessor.class) // + private static int NCPU; +} + +@TargetClass(className = "java.util.concurrent.atomic.Striped64") +final class Target_java_util_concurrent_atomic_Striped64 { + + @Alias @InjectAccessors(NCPUAccessor.class) // + private static int NCPU; } @TargetClass(value = java.util.concurrent.ConcurrentHashMap.class, innerClass = "KeySetView") @@ -289,6 +330,37 @@ public boolean getAsBoolean() { } } +@TargetClass(className = "java.util.concurrent.LinkedTransferQueue", innerClass = "DualNode") +final class Target_java_util_concurrent_LinkedTransferQueue_DualNode { + + @Alias @InjectAccessors(LinkedTransferQueueDualNodeIsUniprocessorAccessor.class) // + private static boolean isUniprocessor; +} + +final class LinkedTransferQueueDualNodeIsUniprocessorAccessor { + private static Boolean cachedIsUniprocessor = null; + + static boolean get() { + if (cachedIsUniprocessor != null) { + return cachedIsUniprocessor; + } + return initializeIsUniprocessor(); + } + + static void set(boolean value) { + cachedIsUniprocessor = value; + } + + private static synchronized boolean initializeIsUniprocessor() { + if (cachedIsUniprocessor != null) { + return cachedIsUniprocessor; + } + + cachedIsUniprocessor = Runtime.getRuntime().availableProcessors() == 1; + return cachedIsUniprocessor; + } +} + /** Dummy class to have a class with the file's name. */ public final class JavaUtilSubstitutions { } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/NativeLibraries.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/NativeLibraries.java index 5daa87344382..0c551ff417d1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/NativeLibraries.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/NativeLibraries.java @@ -31,11 +31,11 @@ import java.util.Arrays; import java.util.Collection; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.ProcessProperties; import org.graalvm.nativeimage.impl.ProcessPropertiesSupport; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.SubstrateOptions; @@ -58,7 +58,7 @@ protected static PointerBase findSymbol(Collection + * Runtime support for {@link ClassLoaderValue} (CLV) mappings in the JDK. + * {@link jdk.internal.loader.BootLoader} has a static CLV, whereas every {@link ClassLoader} + * instance has its own {@link ClassLoaderValue} map. Most CLV mappings are cleared for runtime, + * except for {@link ServicesCatalog} and {@link ModuleLayer} CLV mappings. + *

      + *

      + * As these mappings contain {@link ModuleLayer} (and through it, also {@link Module}) references, + * it is necessary that we do not capture hosted {@link Module} and {@link ModuleLayer} instances. + * Since {@link ModuleLayer} synthesis occurs after analysis, this singleton is only populated after + * analysis (hence the {@link UnknownObjectField} annotation). The fields in this singleton are used + * inside {@link org.graalvm.nativeimage.hosted.FieldValueTransformer}s for CLV mappings (see + * {@link ClassLoaderValueMapFieldValueTransformer}, {@link ModuleLayerCLVTransformer} and + * {@link ServicesCatalogCLVTransformer}). + *

      + */ +@AutomaticallyRegisteredImageSingleton +public final class RuntimeClassLoaderValueSupport { + + public static RuntimeClassLoaderValueSupport instance() { + return ImageSingletons.lookup(RuntimeClassLoaderValueSupport.class); + } + + @UnknownObjectField(availability = AfterHostedUniverse.class) // + private ConcurrentHashMap bootLoaderCLV = new ConcurrentHashMap<>(); + + @UnknownObjectField(availability = AfterHostedUniverse.class) // + private ConcurrentHashMap> classLoaderValueMaps = new ConcurrentHashMap<>(); + + @UnknownObjectField(availability = AfterHostedUniverse.class) // + ClassLoaderValue servicesCatalogCLV = new ClassLoaderValue<>(); + + @UnknownObjectField(availability = AfterHostedUniverse.class) // + ClassLoaderValue> moduleLayerCLV = new ClassLoaderValue<>(); + + @Platforms(Platform.HOSTED_ONLY.class) // + public void update(List runtimeModuleLayers) { + for (ModuleLayer runtimeLayer : runtimeModuleLayers) { + Set loaders = runtimeLayer.modules().stream() + .map(Module::getClassLoader) + .collect(Collectors.toSet()); + for (ClassLoader loader : loaders) { + bindRuntimeModuleLayerToLoader(runtimeLayer, loader); + registerServicesCatalog(loader); + } + } + } + + @Platforms(Platform.HOSTED_ONLY.class) // + private ClassLoader hostedBootLoader; + + @Platforms(Platform.HOSTED_ONLY.class) // + ConcurrentHashMap getClassLoaderValueMapForLoader(ClassLoader loader) { + if (loader == null) { + return bootLoaderCLV; + } else { + return classLoaderValueMaps.computeIfAbsent(loader, k -> new ConcurrentHashMap<>()); + } + } + + @Platforms(Platform.HOSTED_ONLY.class) + private ClassLoader resolveClassLoader(ClassLoader loaderOrNull) { + if (loaderOrNull == null) { + if (hostedBootLoader != null) { + return hostedBootLoader; + } + Method method = ReflectionUtil.lookupMethod(ClassLoaders.class, "bootLoader"); + hostedBootLoader = ReflectionUtil.invokeMethod(method, null); + return hostedBootLoader; + } else { + return loaderOrNull; + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) // + @Platforms(Platform.HOSTED_ONLY.class) // + private void bindRuntimeModuleLayerToLoader(ModuleLayer layer, ClassLoader loaderOrNull) { + ClassLoader loader = resolveClassLoader(loaderOrNull); + + /** + * Runtime {@link ModuleLayer}s are synthesized and bound to loaders after analysis. This + * implementation is identical to {@link java.lang.ModuleLayer.bindToLoader}. + */ + List list = moduleLayerCLV.get(loader); + if (list == null) { + list = new CopyOnWriteArrayList<>(); + List previous = moduleLayerCLV.putIfAbsent(loader, list); + if (previous != null) { + list = previous; + } + } + list.add(layer); + + /* + * Register the module layer in the appropriate CLV for the given loader. + */ + var classLoaderValueMap = getClassLoaderValueMapForLoader(loader); + ((ConcurrentHashMap) classLoaderValueMap).put(moduleLayerCLV, list); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) // + @Platforms(Platform.HOSTED_ONLY.class) // + private void registerServicesCatalog(ClassLoader loaderOrNull) { + ClassLoader loader = resolveClassLoader(loaderOrNull); + + /* + * Register the catalog in the ServicesCatalog CLV. + */ + ServicesCatalog servicesCatalog = getHostedServiceCatalogForLoader(loader); + if (servicesCatalog == null) { + servicesCatalog = ServicesCatalog.create(); + } + servicesCatalogCLV.putIfAbsent(loader, servicesCatalog); + + /* + * Register the module layer in the appropriate CLV for the given loader. + */ + var classLoaderValueMap = getClassLoaderValueMapForLoader(loader); + ((ConcurrentHashMap) classLoaderValueMap).put(servicesCatalogCLV, servicesCatalog); + } + + @Platforms(Platform.HOSTED_ONLY.class) // + private final Field classLoaderCLVField = ReflectionUtil.lookupField(ClassLoader.class, "classLoaderValueMap"); + + @Platforms(Platform.HOSTED_ONLY.class) // + private final ClassLoaderValue hostedServicesCatalogCLV = ReflectionUtil.readField(ServicesCatalog.class, "CLV", null); + + @Platforms(Platform.HOSTED_ONLY.class) // + private ServicesCatalog getHostedServiceCatalogForLoader(ClassLoader loader) { + try { + ConcurrentHashMap hostedLoaderCLV = (ConcurrentHashMap) classLoaderCLVField.get(loader); + ServicesCatalog servicesCatalog = (ServicesCatalog) hostedLoaderCLV.get(hostedServicesCatalogCLV); + return servicesCatalog; + } catch (IllegalAccessException e) { + throw VMError.shouldNotReachHere("Failed to query ClassLoader.classLoaderValueMap", e); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java index b8f747438913..51fa6c0300ec 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 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 @@ -28,7 +28,6 @@ import java.lang.ref.ReferenceQueue; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.security.AccessControlContext; import java.security.CodeSource; @@ -47,6 +46,7 @@ import java.util.WeakHashMap; import java.util.function.BooleanSupplier; import java.util.function.Predicate; +import java.util.function.Supplier; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -65,6 +65,7 @@ import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.graal.snippets.CEntryPointSnippets; import com.oracle.svm.core.thread.Target_java_lang_Thread; +import com.oracle.svm.core.thread.Target_java_lang_ThreadLocal; import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ReflectionUtil; @@ -140,12 +141,13 @@ static T executePrivileged(PrivilegedAction action, AccessControlContext static AccessControlContext checkContext(AccessControlContext context, Class caller) { if (context != null && context.equals(AccessControllerUtil.DISALLOWED_CONTEXT_MARKER)) { - VMError.shouldNotReachHere("Non-allowed AccessControlContext that was replaced with a blank one at build time was invoked without being reinitialized at run time.\n" + - "This might be an indicator of improper build time initialization, or of a non-compatible JDK version.\n" + - "In order to fix this you can either:\n" + - " * Annotate the offending context's field with @RecomputeFieldValue\n" + - " * Implement a custom runtime accessor and annotate said field with @InjectAccessors\n" + - " * If this context originates from the JDK, and it doesn't leak sensitive info, you can allow it in 'AccessControlContextReplacerFeature.duringSetup'"); + VMError.shouldNotReachHere( + "Non-allowed AccessControlContext that was replaced with a blank one at build time was invoked without being reinitialized at run time." + System.lineSeparator() + + "This might be an indicator of improper build time initialization, or of a non-compatible JDK version." + System.lineSeparator() + + "In order to fix this you can either:" + System.lineSeparator() + + " * Annotate the offending context's field with @RecomputeFieldValue" + System.lineSeparator() + + " * Implement a custom runtime accessor and annotate said field with @InjectAccessors" + System.lineSeparator() + + " * If this context originates from the JDK, and it doesn't leak sensitive info, you can allow it in 'AccessControlContextReplacerFeature.duringSetup'"); } // check if caller is authorized to create context @@ -196,7 +198,13 @@ final class Target_java_security_Provider_ServiceKey { final class Target_java_security_Provider { @Alias // @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ServiceKeyComputer.class) // - private static Target_java_security_Provider_ServiceKey previousKey; + @TargetElement(name = "previousKey", onlyWith = JDK21OrEarlier.class) // + private static Target_java_security_Provider_ServiceKey previousKeyJDK21; + + @Alias // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ThreadLocalServiceKeyComputer.class) // + @TargetElement(onlyWith = JDKLatest.class) // + private static Target_java_lang_ThreadLocal previousKey; } @TargetClass(value = java.security.Provider.class, innerClass = "Service") @@ -211,17 +219,40 @@ final class Target_java_security_Provider_Service { private Object constructorCache; } +class ServiceKeyProvider { + static Object getNewServiceKey() { + Class serviceKey = ReflectionUtil.lookupClass("java.security.Provider$ServiceKey"); + Constructor constructor = ReflectionUtil.lookupConstructor(serviceKey, String.class, String.class, boolean.class); + return ReflectionUtil.newInstance(constructor, "", "", false); + } + + /** + * Originally the thread local creates a new default service key each time. Here we always + * return the singleton default service key. This default key will be replaced with an actual + * key in {@code java.security.Provider.parseLegacy} + */ + static Supplier getNewServiceKeySupplier() { + final Object singleton = ServiceKeyProvider.getNewServiceKey(); + return () -> singleton; + } +} + @Platforms(Platform.HOSTED_ONLY.class) class ServiceKeyComputer implements FieldValueTransformer { @Override public Object transform(Object receiver, Object originalValue) { - try { - Class serviceKey = Class.forName("java.security.Provider$ServiceKey"); - Constructor constructor = ReflectionUtil.lookupConstructor(serviceKey, String.class, String.class, boolean.class); - return constructor.newInstance("", "", false); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) { - throw VMError.shouldNotReachHere(e); - } + return ServiceKeyProvider.getNewServiceKey(); + } +} + +@Platforms(Platform.HOSTED_ONLY.class) +class ThreadLocalServiceKeyComputer implements FieldValueTransformer { + @Override + public Object transform(Object receiver, Object originalValue) { + // Originally the thread local creates a new default service key each time. + // Here we always return the singleton default service key. This default key + // will be replaced with an actual key in Provider.parseLegacy + return ThreadLocal.withInitial(ServiceKeyProvider.getNewServiceKeySupplier()); } } @@ -308,7 +339,7 @@ private static void setJavaHome(String newJavaHome) { } @TargetClass(className = "javax.crypto.JceSecurity") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+25/src/java.base/share/classes/javax/crypto/JceSecurity.java.template") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/java.base/share/classes/javax/crypto/JceSecurity.java.template") @SuppressWarnings({"unused"}) final class Target_javax_crypto_JceSecurity { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java index 071671a3e5b8..190a09d1c3ac 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java @@ -41,7 +41,6 @@ import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.SubstrateOptions; @@ -112,7 +111,7 @@ public static StackTraceElement[] getStackTraceAtSafepoint(Thread thread) { } public static StackTraceElement[] getStackTraceAtSafepoint(IsolateThread isolateThread) { - return getStackTraceAtSafepoint(isolateThread, WordFactory.nullPointer()); + return getStackTraceAtSafepoint(isolateThread, Word.nullPointer()); } public static StackTraceElement[] getStackTraceAtSafepoint(IsolateThread isolateThread, Pointer endSP) { @@ -128,7 +127,7 @@ public static StackTraceElement[] getStackTraceAtSafepoint(IsolateThread isolate public static StackTraceElement[] getStackTraceAtSafepoint(IsolateThread isolateThread, Pointer startSP, Pointer endSP) { assert VMOperation.isInProgressAtSafepoint(); BuildStackTraceVisitor visitor = new BuildStackTraceVisitor(false, SubstrateOptions.maxJavaStackTraceDepth()); - JavaStackWalker.walkThread(isolateThread, startSP, endSP, WordFactory.nullPointer(), visitor); + JavaStackWalker.walkThread(isolateThread, startSP, endSP, Word.nullPointer(), visitor); return visitor.trace.toArray(NO_ELEMENTS); } @@ -543,10 +542,10 @@ static int readSourceLineNumber(long[] backtrace, int pos) { */ static Class readSourceClass(long[] backtrace, int pos) { if (useCompressedReferences()) { - UnsignedWord ref = WordFactory.unsigned(backtrace[pos + 1]).unsignedShiftRight(32); + UnsignedWord ref = Word.unsigned(backtrace[pos + 1]).unsignedShiftRight(32); return (Class) ReferenceAccess.singleton().uncompressReference(ref); } else { - Word sourceClassPtr = WordFactory.pointer(backtrace[pos + 1]); + Word sourceClassPtr = Word.pointer(backtrace[pos + 1]); return sourceClassPtr.toObject(Class.class, true); } } @@ -561,10 +560,10 @@ static Class readSourceClass(long[] backtrace, int pos) { */ static String readSourceMethodName(long[] backtrace, int pos) { if (useCompressedReferences()) { - UnsignedWord ref = WordFactory.unsigned(backtrace[pos + 1]).and(WordFactory.unsigned(0xffffffffL)); + UnsignedWord ref = Word.unsigned(backtrace[pos + 1]).and(Word.unsigned(0xffffffffL)); return (String) ReferenceAccess.singleton().uncompressReference(ref); } else { - Word sourceMethodNamePtr = WordFactory.pointer(backtrace[pos + 2]); + Word sourceMethodNamePtr = Word.pointer(backtrace[pos + 2]); return sourceMethodNamePtr.toObject(String.class, true); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SunMiscSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SunMiscSubstitutions.java index 0d8a683fcc28..b6dd6faf452d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SunMiscSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SunMiscSubstitutions.java @@ -29,9 +29,9 @@ import java.lang.reflect.Method; import java.security.ProtectionDomain; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.impl.UnsafeMemorySupport; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.annotate.Alias; @@ -57,17 +57,17 @@ final class Target_jdk_internal_misc_Unsafe_Core { @Substitute private long allocateMemory0(long bytes) { - return NativeMemory.malloc(WordFactory.unsigned(bytes), NmtCategory.Unsafe).rawValue(); + return NativeMemory.malloc(Word.unsigned(bytes), NmtCategory.Unsafe).rawValue(); } @Substitute private long reallocateMemory0(long address, long bytes) { - return NativeMemory.realloc(WordFactory.unsigned(address), WordFactory.unsigned(bytes), NmtCategory.Unsafe).rawValue(); + return NativeMemory.realloc(Word.unsigned(address), Word.unsigned(bytes), NmtCategory.Unsafe).rawValue(); } @Substitute private void freeMemory0(long address) { - NativeMemory.free(WordFactory.unsigned(address)); + NativeMemory.free(Word.unsigned(address)); } @Substitute @@ -153,7 +153,7 @@ private int getLoadAverage0(double[] loadavg, int nelems) { @Substitute public Object getUncompressedObject(long address) { /* Adapted from `Unsafe_GetUncompressedObject` in `src/hotspot/share/prims/unsafe.cpp`. */ - return ReferenceAccess.singleton().readObjectAt(WordFactory.pointer(address), false); + return ReferenceAccess.singleton().readObjectAt(Word.pointer(address), false); } /* diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemInOutErrSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemInOutErrSupport.java index 65006c0bbbaf..fc7add09ee79 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemInOutErrSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemInOutErrSupport.java @@ -59,9 +59,11 @@ * static analysis starts, i.e., in a {@link Feature#beforeAnalysis} method. */ public final class SystemInOutErrSupport implements InitialLayerOnlyImageSingleton { - private InputStream in = new BufferedInputStream(new FileInputStream(FileDescriptor.in)); + private final InputStream initialIn = new BufferedInputStream(new FileInputStream(FileDescriptor.in)); + private InputStream in = initialIn; private PrintStream out = newPrintStream(new FileOutputStream(FileDescriptor.out), System.getProperty("sun.stdout.encoding")); - private PrintStream err = newPrintStream(new FileOutputStream(FileDescriptor.err), System.getProperty("sun.stderr.encoding")); + private final PrintStream initialErr = newPrintStream(new FileOutputStream(FileDescriptor.err), System.getProperty("sun.stderr.encoding")); + private PrintStream err = initialErr; @Platforms(Platform.HOSTED_ONLY.class) // final AtomicBoolean isSealed = new AtomicBoolean(false); @@ -123,6 +125,18 @@ public PrintStream err() { return err; } + @Fold + public InputStream initialIn() { + seal(); + return initialIn; + } + + @Fold + public PrintStream initialErr() { + seal(); + return initialErr; + } + @Platforms(Platform.HOSTED_ONLY.class) public static void setErr(PrintStream err) { var support = singleton(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java index eb76759a767f..ca93e9d93755 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java @@ -45,7 +45,8 @@ import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.hub.ClassForNameSupport; import com.oracle.svm.core.hub.PredefinedClassesSupport; -import com.oracle.svm.core.hub.RuntimeClassLoadingSupport; +import com.oracle.svm.core.hub.RuntimeClassLoading; +import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.java.LambdaUtils; @@ -275,7 +276,7 @@ private Class defineClass(String name, byte[] b, int off, int len, Protection @SuppressWarnings({"unused", "static-method"}) private Class defineClass(String name, java.nio.ByteBuffer b, ProtectionDomain protectionDomain) { // only bother extracting the bytes if it has a chance to work - if (PredefinedClassesSupport.hasBytecodeClasses() || RuntimeClassLoadingSupport.supportsRuntimeClassLoading()) { + if (PredefinedClassesSupport.hasBytecodeClasses() || RuntimeClassLoading.isSupported()) { byte[] array; int off; int len = b.remaining(); @@ -323,11 +324,13 @@ private static Class defineClass0(ClassLoader loader, Class lookup, String } final class ClassLoaderHelper { + private static final String ERROR_MSG = SubstrateOptionsParser.commandArgument(RuntimeClassLoading.Options.SupportRuntimeClassLoading, "+") + " is not yet supported."; + public static Class defineClass(ClassLoader loader, String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) { if (PredefinedClassesSupport.hasBytecodeClasses()) { return PredefinedClassesSupport.loadClass(loader, name, b, off, len, protectionDomain); } - throw VMError.unimplemented("Crema"); + throw VMError.unimplemented(ERROR_MSG); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ModuleLayer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ModuleLayer.java index 25d1e4dff325..b1144eaef57b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ModuleLayer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ModuleLayer.java @@ -24,16 +24,31 @@ */ package com.oracle.svm.core.jdk; +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 jdk.internal.loader.ClassLoaderValue; +import org.graalvm.nativeimage.hosted.FieldValueTransformer; + +import java.util.List; @SuppressWarnings("unused") @TargetClass(value = java.lang.ModuleLayer.class) final class Target_java_lang_ModuleLayer { - @SuppressWarnings("unused") @Substitute public static ModuleLayer boot() { return RuntimeModuleSupport.instance().getBootLayer(); } + + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ModuleLayerCLVTransformer.class, isFinal = true) // + static ClassLoaderValue> CLV; +} + +final class ModuleLayerCLVTransformer implements FieldValueTransformer { + @Override + public Object transform(Object receiver, Object originalValue) { + return originalValue != null ? RuntimeClassLoaderValueSupport.instance().moduleLayerCLV : null; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_StackWalker.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_StackWalker.java index c4c861281ea3..2e87c44fb35b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_StackWalker.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_StackWalker.java @@ -35,13 +35,13 @@ import java.util.stream.StreamSupport; import com.oracle.svm.core.code.FrameSourceInfo; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.nativeimage.impl.InternalPlatform; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.Uninterruptible; @@ -150,7 +150,7 @@ private T walk(Function, ? extends T> function) { /* Walk the stack of the current thread. */ IsolateThread isolateThread = CurrentIsolate.getCurrentThread(); Pointer sp = KnownIntrinsics.readCallerStackPointer(); - Pointer endSP = WordFactory.nullPointer(); + Pointer endSP = Word.nullPointer(); if (ContinuationSupport.isSupported() && (contScope != null || JavaThreads.isCurrentThreadVirtual())) { var scope = (contScope != null) ? contScope : Target_java_lang_VirtualThread.continuationScope(); var top = Target_jdk_internal_vm_Continuation.getCurrentContinuation(scope); @@ -335,7 +335,7 @@ private boolean advancePhysically0() { @Override protected void invalidate() { - walk = WordFactory.nullPointer(); + walk = Word.nullPointer(); continuation = null; stored = null; } @@ -366,7 +366,7 @@ final class StackFrameSpliterator extends AbstractStackFrameSpliterator { @Override protected void invalidate() { - walk = WordFactory.nullPointer(); + walk = Word.nullPointer(); } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_nio_DirectByteBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_nio_DirectByteBuffer.java index 7f90d2e12685..62efa2660aa9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_nio_DirectByteBuffer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_nio_DirectByteBuffer.java @@ -34,7 +34,7 @@ import com.oracle.svm.core.util.VMError; @TargetClass(className = "java.nio.DirectByteBuffer") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+23/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+26/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template") public final class Target_java_nio_DirectByteBuffer { /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_module_ServicesCatalog.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_module_ServicesCatalog.java new file mode 100644 index 000000000000..bc4cec8d4c92 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_module_ServicesCatalog.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jdk; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.TargetClass; +import jdk.internal.loader.ClassLoaderValue; +import jdk.internal.module.ServicesCatalog; +import org.graalvm.nativeimage.hosted.FieldValueTransformer; + +@SuppressWarnings("unused") +@TargetClass(value = ServicesCatalog.class) +final class Target_jdk_internal_module_ServicesCatalog { + + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ServicesCatalogCLVTransformer.class, isFinal = true) // + static ClassLoaderValue CLV; +} + +final class ServicesCatalogCLVTransformer implements FieldValueTransformer { + @Override + public Object transform(Object receiver, Object originalValue) { + return originalValue != null ? RuntimeClassLoaderValueSupport.instance().servicesCatalogCLV : null; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/TimeZoneSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/TimeZoneSubstitutions.java index 925f43e24b0f..c5d162f7b4d8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/TimeZoneSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/TimeZoneSubstitutions.java @@ -31,13 +31,13 @@ import java.time.ZoneId; import java.util.TimeZone; +import jdk.graal.compiler.word.Word; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.nativeimage.impl.InternalPlatform; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.LibCHelper; import com.oracle.svm.core.OS; @@ -88,7 +88,7 @@ final class Target_java_util_TimeZone { @Substitute private static String getSystemTimeZoneID(String javaHome) { - CCharPointer tzMappingsPtr = WordFactory.nullPointer(); + CCharPointer tzMappingsPtr = Word.nullPointer(); int contentLen = 0; PrimitiveArrayView refContent = null; try { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java index f7c432322b09..6ed9536c7a5f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java @@ -26,11 +26,11 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; @@ -225,7 +225,7 @@ public static class AtomicWord { protected final AtomicLong value; /** - * Creates a new AtomicWord with initial value {@link WordFactory#zero}. + * Creates a new AtomicWord with initial value {@link Word#zero}. */ public AtomicWord() { value = new AtomicLong(0L); @@ -238,7 +238,7 @@ public AtomicWord() { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public final T get() { - return WordFactory.unsigned(value.get()); + return Word.unsigned(value.get()); } /** @@ -259,7 +259,7 @@ public final void set(T newValue) { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public final T getAndSet(T newValue) { - return WordFactory.unsigned(value.getAndSet(newValue.rawValue())); + return Word.unsigned(value.getAndSet(newValue.rawValue())); } /** @@ -292,7 +292,7 @@ public static class AtomicUnsigned extends AtomicWord { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public final UnsignedWord getAndAdd(UnsignedWord delta) { - return WordFactory.unsigned(value.getAndAdd(delta.rawValue())); + return Word.unsigned(value.getAndAdd(delta.rawValue())); } /** @@ -303,7 +303,7 @@ public final UnsignedWord getAndAdd(UnsignedWord delta) { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public final UnsignedWord addAndGet(UnsignedWord delta) { - return WordFactory.unsigned(value.addAndGet(delta.rawValue())); + return Word.unsigned(value.addAndGet(delta.rawValue())); } /** @@ -314,7 +314,7 @@ public final UnsignedWord addAndGet(UnsignedWord delta) { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public final UnsignedWord getAndSubtract(UnsignedWord delta) { - return WordFactory.unsigned(value.getAndAdd(-delta.rawValue())); + return Word.unsigned(value.getAndAdd(-delta.rawValue())); } /** @@ -325,7 +325,7 @@ public final UnsignedWord getAndSubtract(UnsignedWord delta) { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public final UnsignedWord subtractAndGet(UnsignedWord delta) { - return WordFactory.unsigned(value.addAndGet(-delta.rawValue())); + return Word.unsigned(value.addAndGet(-delta.rawValue())); } } @@ -346,7 +346,7 @@ public static class AtomicPointer { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public T get() { - return WordFactory.pointer(value); + return Word.pointer(value); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_java_util_ResourceBundle.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_java_util_ResourceBundle.java index 4af19f6951ef..b38721dff8af 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_java_util_ResourceBundle.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_java_util_ResourceBundle.java @@ -24,8 +24,6 @@ */ package com.oracle.svm.core.jdk.localization.substitutions; -import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION; - import java.util.Locale; import java.util.Objects; import java.util.ResourceBundle; @@ -39,11 +37,14 @@ 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.jdk.JDK21OrEarlier; import com.oracle.svm.core.jdk.localization.LocalizationSupport; import com.oracle.svm.core.jdk.localization.substitutions.modes.OptimizedLocaleMode; import com.oracle.svm.core.jdk.resources.MissingResourceRegistrationUtils; +import com.oracle.svm.util.ReflectionUtil; import jdk.internal.loader.BootLoader; +import sun.security.util.SecurityConstants; @TargetClass(java.util.ResourceBundle.class) @SuppressWarnings({"unused"}) @@ -137,6 +138,7 @@ private static ResourceBundle getBundleImpl(String baseName, @Substitute @SuppressWarnings({"removal", "deprecation"}) + @TargetElement(onlyWith = JDK21OrEarlier.class) private static ResourceBundle getBundleFromModule(Class caller, Module module, String baseName, @@ -147,7 +149,8 @@ private static ResourceBundle getBundleFromModule(Class caller, if (callerModule != module) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { - sm.checkPermission(GET_CLASSLOADER_PERMISSION); + RuntimePermission getClassLoaderPermission = ReflectionUtil.readField(SecurityConstants.class, "GET_CLASSLOADER_PERMISSION", null); + sm.checkPermission(getClassLoaderPermission); } } if (!ImageSingletons.lookup(LocalizationSupport.class).isRegisteredBundleLookup(baseName, locale, control)) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/HasJfrSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/HasJfrSupport.java index 976a9005c27f..83a69108af5c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/HasJfrSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/HasJfrSupport.java @@ -26,11 +26,15 @@ import java.util.function.BooleanSupplier; -import jdk.graal.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import com.oracle.svm.core.BuildPhaseProvider; +import com.oracle.svm.core.util.VMError; + +import jdk.graal.compiler.api.replacements.Fold; + /** * Returns {@code true} if the Native Image is built with JFR support. This does not necessarily * mean that JFR is really enabled at runtime. @@ -43,6 +47,7 @@ public boolean getAsBoolean() { @Fold public static boolean get() { + VMError.guarantee(BuildPhaseProvider.isFeatureRegistrationFinished(), "HasJfrSupport.get() must not be called before the feature registration is finished."); return ImageSingletons.contains(JfrManager.class); } } @@ -55,6 +60,7 @@ public static boolean get() { class JfrHostedEnabled implements BooleanSupplier { @Override public boolean getAsBoolean() { + VMError.guarantee(BuildPhaseProvider.isFeatureRegistrationFinished(), "JfrHostedEnabled.get() must not be called before the feature registration is finished."); return ImageSingletons.contains(JfrManager.class) && ImageSingletons.lookup(JfrManager.class).hostedEnabled; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java index cad4e1d5f74b..cc38a316cba5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core.jfr; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.config.ConfigurationValues; @@ -50,7 +50,7 @@ private JfrBufferAccess() { @Fold public static UnsignedWord getHeaderSize() { - return UnsignedUtils.roundUp(SizeOf.unsigned(JfrBuffer.class), WordFactory.unsigned(ConfigurationValues.getTarget().wordSize)); + return UnsignedUtils.roundUp(SizeOf.unsigned(JfrBuffer.class), Word.unsigned(ConfigurationValues.getTarget().wordSize)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -66,7 +66,7 @@ public static JfrBuffer allocate(UnsignedWord dataSize, JfrBufferType bufferType if (result.isNonNull()) { result.setSize(dataSize); result.setBufferType(bufferType); - result.setNode(WordFactory.nullPointer()); + result.setNode(Word.nullPointer()); result.setFlags(NO_FLAGS); reinitialize(result); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java index 320a0922a66c..2e53cc8bc1d0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferList.java @@ -26,9 +26,9 @@ package com.oracle.svm.core.jfr; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.thread.JavaSpinLockUtils; @@ -69,14 +69,14 @@ public void teardown() { JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); if (buffer.isNonNull()) { assert JfrBufferAccess.isRetired(buffer); - buffer.setNode(WordFactory.nullPointer()); + buffer.setNode(Word.nullPointer()); } JfrBufferNode next = node.getNext(); JfrBufferNodeAccess.free(node); node = next; } - head = WordFactory.nullPointer(); + head = Word.nullPointer(); } @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") @@ -96,7 +96,7 @@ public JfrBufferNode addNode(JfrBuffer buffer) { JfrBufferNode node = JfrBufferNodeAccess.allocate(buffer); if (node.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } lockNoTransition(); @@ -145,7 +145,7 @@ public void removeNode(JfrBufferNode node, JfrBufferNode prev) { @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") private JfrBufferNode findPrev(JfrBufferNode node) { JfrBufferNode cur = head; - JfrBufferNode prev = WordFactory.nullPointer(); + JfrBufferNode prev = Word.nullPointer(); while (cur.isNonNull()) { if (cur == node) { return prev; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java index a72e5118208c..14bfd317c85a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferNodeAccess.java @@ -26,11 +26,11 @@ package com.oracle.svm.core.jfr; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.c.type.CIntPointer; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.memory.NullableNativeMemory; @@ -50,8 +50,8 @@ public static JfrBufferNode allocate(JfrBuffer buffer) { JfrBufferNode node = NullableNativeMemory.malloc(SizeOf.unsigned(JfrBufferNode.class), NmtCategory.JFR); if (node.isNonNull()) { node.setBuffer(buffer); - node.setNext(WordFactory.nullPointer()); - node.setLockOwner(WordFactory.nullPointer()); + node.setNext(Word.nullPointer()); + node.setLockOwner(Word.nullPointer()); NativeSpinLockUtils.initialize(ptrToLock(node)); } return node; @@ -90,7 +90,7 @@ public static void lockNoTransition(JfrBufferNode node) { public static void unlock(JfrBufferNode node) { assert node.isNonNull(); assert isLockedByCurrentThread(node); - node.setLockOwner(WordFactory.nullPointer()); + node.setLockOwner(Word.nullPointer()); NativeSpinLockUtils.unlock(ptrToLock(node)); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkFileWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkFileWriter.java index 475877728577..4ec94068c78a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkFileWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkFileWriter.java @@ -30,11 +30,11 @@ import java.nio.charset.StandardCharsets; import com.oracle.svm.core.jfr.oldobject.JfrOldObjectRepository; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.heap.VMOperationInfos; @@ -238,7 +238,7 @@ public void closeFile() { getFileSupport().close(fd); filename = null; - fd = WordFactory.nullPointer(); + fd = Word.nullPointer(); } private void writeFileHeader() { @@ -542,7 +542,7 @@ private void flushStorage(boolean flushpoint) { @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") private void traverseThreadLocalBuffers(JfrBufferList list, boolean flushpoint) { JfrBufferNode node = list.getHead(); - JfrBufferNode prev = WordFactory.nullPointer(); + JfrBufferNode prev = Word.nullPointer(); while (node.isNonNull()) { JfrBufferNode next = node.getNext(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrExecutionSamplerSupported.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrExecutionSamplerSupported.java index 60ab5fe561e3..d2a4fed9f99a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrExecutionSamplerSupported.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrExecutionSamplerSupported.java @@ -36,13 +36,11 @@ */ @Platforms(Platform.HOSTED_ONLY.class) public class JfrExecutionSamplerSupported { - public static boolean isSupported() { if (ImageSingletons.contains(JfrExecutionSamplerSupported.class)) { return !RuntimeCompilation.isEnabled() && ImageSingletons.lookup(JfrExecutionSamplerSupported.class).isSupported0(); - } else { - return false; } + return false; } protected boolean isSupported0() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java index 249ba598e0a8..772cd071b090 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core.jfr; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.UnmanagedMemoryUtil; @@ -96,7 +96,7 @@ private void freeBuffers() { try { JfrBuffer buffer = JfrBufferNodeAccess.getBuffer(node); JfrBufferAccess.free(buffer); - node.setBuffer(WordFactory.nullPointer()); + node.setBuffer(Word.nullPointer()); } finally { JfrBufferNodeAccess.unlock(node); } @@ -169,6 +169,6 @@ private JfrBufferNode tryAcquirePromotionBuffer(UnsignedWord size) { node = node.getNext(); } } - return WordFactory.nullPointer(); + return Word.nullPointer(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java index abde3709d4c7..4a7e4d1d1e39 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core.jfr; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jdk.StackTraceUtils; @@ -153,7 +153,7 @@ void teardown() { table.teardown(); unflushedEntries = 0; JfrBufferAccess.free(buffer); - buffer = WordFactory.nullPointer(); + buffer = Word.nullPointer(); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java index 9c6da80d0ee7..4d23fe0f96e0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java @@ -24,9 +24,9 @@ */ package com.oracle.svm.core.jfr; +import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.UnmanagedMemoryUtil; @@ -234,7 +234,7 @@ public static void putString(JfrNativeEventWriterData data, Pointer utf8Buffer, putByte(data, JfrChunkFileWriter.StringEncoding.UTF8_BYTE_ARRAY.getValue()); putInt(data, numBytes); if (ensureSize(data, numBytes)) { - UnmanagedMemoryUtil.copy(utf8Buffer, data.getCurrentPos(), WordFactory.unsigned(numBytes)); + UnmanagedMemoryUtil.copy(utf8Buffer, data.getCurrentPos(), Word.unsigned(numBytes)); data.setCurrentPos(data.getCurrentPos().add(numBytes)); } } @@ -308,7 +308,7 @@ private static void reset(JfrNativeEventWriterData data) { @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true) private static void cancel(JfrNativeEventWriterData data) { - data.setEndPos(WordFactory.nullPointer()); + data.setEndPos(Word.nullPointer()); } @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true) @@ -353,7 +353,7 @@ private static JfrBuffer reuseOrReallocateBuffer(JfrBuffer oldBuffer, UnsignedWo JfrBuffer result = JfrBufferAccess.allocate(newSize, oldBuffer.getBufferType()); if (result.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } // Copy all unflushed data (no matter if committed or uncommitted) from the old buffer diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java index d5ccbd4f2bae..a8ea1976fac3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java @@ -24,8 +24,8 @@ */ package com.oracle.svm.core.jfr; +import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; @@ -49,10 +49,10 @@ public static void initialize(JfrNativeEventWriterData data, JfrBuffer buffer) { data.setCurrentPos(buffer.getCommittedPos()); data.setEndPos(JfrBufferAccess.getDataEnd(buffer)); } else { - data.setJfrBuffer(WordFactory.nullPointer()); - data.setStartPos(WordFactory.nullPointer()); - data.setCurrentPos(WordFactory.nullPointer()); - data.setEndPos(WordFactory.nullPointer()); + data.setJfrBuffer(Word.nullPointer()); + data.setStartPos(Word.nullPointer()); + data.setCurrentPos(Word.nullPointer()); + data.setEndPos(Word.nullPointer()); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java index 6db818c4cfe3..323b26dc48ad 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.jfr; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -35,7 +36,6 @@ import org.graalvm.nativeimage.c.type.CIntPointer; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.FrameAccess; import com.oracle.svm.core.NeverInline; @@ -211,12 +211,12 @@ private JfrStackTraceTableEntry getOrPutStackTrace0(Pointer start, UnsignedWord /* Hashtable entry allocation failed. */ NullableNativeMemory.free(to); - to = WordFactory.nullPointer(); + to = Word.nullPointer(); } /* Some allocation failed. */ statusPtr.write(JfrStackTraceTableEntryStatus.INSERT_FAILED); - return WordFactory.nullPointer(); + return Word.nullPointer(); } } @@ -326,7 +326,7 @@ protected boolean isEqual(UninterruptibleEntry a, UninterruptibleEntry b) { JfrStackTraceTableEntry entry1 = (JfrStackTraceTableEntry) a; JfrStackTraceTableEntry entry2 = (JfrStackTraceTableEntry) b; /* We explicitly ignore the field 'serialized' because its value can change. */ - return entry1.getSize() == entry2.getSize() && LibC.memcmp(entry1.getRawStackTrace(), entry2.getRawStackTrace(), WordFactory.unsigned(entry1.getSize())) == 0; + return entry1.getSize() == entry2.getSize() && LibC.memcmp(entry1.getRawStackTrace(), entry2.getRawStackTrace(), Word.unsigned(entry1.getSize())) == 0; } @Override @@ -385,7 +385,7 @@ void teardown() { table.teardown(); unflushedEntries = 0; JfrBufferAccess.free(buffer); - buffer = WordFactory.nullPointer(); + buffer = Word.nullPointer(); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackWalker.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackWalker.java index 29b43be5f1da..ab264cbfd782 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackWalker.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackWalker.java @@ -27,6 +27,7 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; @@ -34,7 +35,6 @@ import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.FrameAccess; import com.oracle.svm.core.SubstrateOptions; @@ -156,7 +156,7 @@ public static int walkCurrentThread(SamplerSampleWriterData data, CodePointer to } else { /* Both the top frame and its caller are probably Java frames. */ if (isSPAligned(sp)) { - UnsignedWord topFrameSize = WordFactory.unsigned(CodeInfoQueryResult.getTotalFrameSize(topFrameEncodedSize)); + UnsignedWord topFrameSize = Word.unsigned(CodeInfoQueryResult.getTotalFrameSize(topFrameEncodedSize)); if (SubstrateOptions.hasFramePointer() && !hasValidCaller(sp, topFrameSize, topFrameIsEntryPoint, anchor)) { /* * If we have a frame pointer, then the stack pointer can be aligned @@ -372,7 +372,7 @@ private static boolean isCallerSPValid(Pointer currentSP, Pointer callerSP) { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private static boolean isSPAligned(Pointer sp) { - return PointerUtils.isAMultiple(sp, WordFactory.unsigned(ConfigurationValues.getTarget().stackAlignment)); + return PointerUtils.isAMultiple(sp, Word.unsigned(ConfigurationValues.getTarget().stackAlignment)); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index a5b6f6bee8e9..57f8e1a9452b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -30,7 +30,6 @@ import org.graalvm.nativeimage.c.struct.RawField; import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.nativeimage.c.struct.SizeOf; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.struct.PinnedObjectField; @@ -242,7 +241,7 @@ void teardown() { table.teardown(); unflushedEntries = 0; JfrBufferAccess.free(buffer); - buffer = WordFactory.nullPointer(); + buffer = Word.nullPointer(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index abafc3f6fa7a..d5f58382182d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -26,13 +26,13 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.JavaMainWrapper; import com.oracle.svm.core.SubstrateUtil; @@ -157,11 +157,11 @@ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { public static void stopRecording(IsolateThread isolateThread, boolean freeJavaBuffer) { /* Flush event buffers. From this point onwards, no further JFR events may be emitted. */ JfrBuffer nb = nativeBuffer.get(isolateThread); - nativeBuffer.set(isolateThread, WordFactory.nullPointer()); + nativeBuffer.set(isolateThread, Word.nullPointer()); flushToGlobalMemoryAndFreeBuffer(nb); JfrBuffer jb = javaBuffer.get(isolateThread); - javaBuffer.set(isolateThread, WordFactory.nullPointer()); + javaBuffer.set(isolateThread, Word.nullPointer()); if (freeJavaBuffer) { flushToGlobalMemoryAndFreeBuffer(jb); } else { @@ -170,7 +170,7 @@ public static void stopRecording(IsolateThread isolateThread, boolean freeJavaBu /* Clear the other event-related thread-locals. */ javaEventWriter.set(isolateThread, null); - dataLost.set(isolateThread, WordFactory.unsigned(0)); + dataLost.set(isolateThread, Word.unsigned(0)); /* Clear stacktrace-related thread-locals. */ SamplerStatistics.singleton().addMissedSamples(getMissedSamples(isolateThread)); @@ -181,7 +181,7 @@ public static void stopRecording(IsolateThread isolateThread, boolean freeJavaBu SamplerBuffer buffer = samplerBuffer.get(isolateThread); if (buffer.isNonNull()) { SubstrateJVM.getSamplerBufferPool().pushFullBuffer(buffer); - samplerBuffer.set(isolateThread, WordFactory.nullPointer()); + samplerBuffer.set(isolateThread, Word.nullPointer()); } } @@ -202,8 +202,8 @@ private static void flushToGlobalMemoryAndFreeBuffer(JfrBuffer buffer) { /* Free the buffer but leave the node alive as it may still be needed. */ JfrBufferNodeAccess.lockNoTransition(node); try { - flushToGlobalMemory0(buffer, WordFactory.unsigned(0), 0); - node.setBuffer(WordFactory.nullPointer()); + flushToGlobalMemory0(buffer, Word.unsigned(0), 0); + node.setBuffer(Word.nullPointer()); JfrBufferAccess.free(buffer); } finally { JfrBufferNodeAccess.unlock(node); @@ -220,7 +220,7 @@ private static void flushToGlobalMemoryAndRetireBuffer(JfrBuffer buffer) { JfrBufferNode node = buffer.getNode(); JfrBufferNodeAccess.lockNoTransition(node); try { - flushToGlobalMemory0(buffer, WordFactory.unsigned(0), 0); + flushToGlobalMemory0(buffer, Word.unsigned(0), 0); JfrBufferAccess.setRetired(buffer); } finally { JfrBufferNodeAccess.unlock(node); @@ -302,7 +302,7 @@ public Target_jdk_jfr_internal_event_EventWriter newEventWriter() { @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") private static JfrBuffer reinstateJavaBuffer(JfrBuffer buffer) { if (buffer.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } JfrBufferNode node = buffer.getNode(); @@ -313,7 +313,7 @@ private static JfrBuffer reinstateJavaBuffer(JfrBuffer buffer) { node = javaBufferList.addNode(buffer); if (node.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } } @@ -331,13 +331,13 @@ public JfrBuffer getJavaBuffer() { if (buffer.isNull()) { buffer = JfrBufferAccess.allocate(threadLocalBufferSize, JfrBufferType.THREAD_LOCAL_JAVA); if (buffer.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } JfrBufferNode node = javaBufferList.addNode(buffer); if (node.isNull()) { JfrBufferAccess.free(buffer); - return WordFactory.nullPointer(); + return Word.nullPointer(); } javaBuffer.set(buffer); } @@ -350,13 +350,13 @@ public JfrBuffer getNativeBuffer() { if (buffer.isNull()) { buffer = JfrBufferAccess.allocate(threadLocalBufferSize, JfrBufferType.THREAD_LOCAL_NATIVE); if (buffer.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } JfrBufferNode node = nativeBufferList.addNode(buffer); if (node.isNull()) { JfrBufferAccess.free(buffer); - return WordFactory.nullPointer(); + return Word.nullPointer(); } nativeBuffer.set(buffer); } @@ -391,7 +391,7 @@ public static JfrBuffer flushToGlobalMemory(JfrBuffer buffer, UnsignedWord uncom JfrBufferNode node = buffer.getNode(); if (node.isNull()) { assert JfrBufferAccess.isRetired(buffer); - return WordFactory.nullPointer(); + return Word.nullPointer(); } JfrBufferNodeAccess.lockNoTransition(node); @@ -411,7 +411,7 @@ private static JfrBuffer flushToGlobalMemory0(JfrBuffer buffer, UnsignedWord unc if (!SubstrateJVM.getGlobalMemory().write(buffer, unflushedSize, false)) { JfrBufferAccess.reinitialize(buffer); writeDataLoss(buffer, unflushedSize); - return WordFactory.nullPointer(); + return Word.nullPointer(); } if (uncommitted.aboveThan(0)) { @@ -424,7 +424,7 @@ private static JfrBuffer flushToGlobalMemory0(JfrBuffer buffer, UnsignedWord unc if (buffer.getSize().aboveOrEqual(uncommitted.add(requested))) { return buffer; } - return WordFactory.nullPointer(); + return Word.nullPointer(); } @Uninterruptible(reason = "Accesses a JFR buffer.") diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index fa8b9ffc7ff0..62bd2f978384 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -24,11 +24,11 @@ */ package com.oracle.svm.core.jfr; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jdk.UninterruptibleUtils; @@ -307,10 +307,10 @@ void teardown() { unflushedThreadGroupCount = 0; JfrBufferAccess.free(threadBuffer); - threadBuffer = WordFactory.nullPointer(); + threadBuffer = Word.nullPointer(); JfrBufferAccess.free(threadGroupBuffer); - threadGroupBuffer = WordFactory.nullPointer(); + threadGroupBuffer = Word.nullPointer(); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java index eff3527c0480..d29ec2501445 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java @@ -26,12 +26,12 @@ import java.util.List; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.heap.VMOperationInfos; @@ -238,11 +238,11 @@ public boolean createJFR(boolean simulateFailure) { long threadLocalBufferSize = options.threadBufferSize.getValue(); assert threadLocalBufferSize > 0; - threadLocal.initialize(WordFactory.unsigned(threadLocalBufferSize)); + threadLocal.initialize(Word.unsigned(threadLocalBufferSize)); long globalBufferSize = options.globalBufferSize.getValue(); assert globalBufferSize > 0; - globalMemory.initialize(WordFactory.unsigned(globalBufferSize), options.globalBufferCount.getValue()); + globalMemory.initialize(Word.unsigned(globalBufferSize), options.globalBufferCount.getValue()); unlockedChunkWriter.initialize(options.maxChunkSize.getValue()); stackTraceRepo.setStackTraceDepth(NumUtil.safeToInt(options.stackDepth.getValue())); @@ -513,7 +513,7 @@ public boolean flush(Target_jdk_jfr_internal_event_EventWriter writer, int uncom JfrBuffer oldBuffer = threadLocal.getJavaBuffer(); assert oldBuffer.isNonNull() : "Java EventWriter should not be used otherwise"; - JfrBuffer newBuffer = JfrThreadLocal.flushToGlobalMemory(oldBuffer, WordFactory.unsigned(uncommittedSize), requestedSize); + JfrBuffer newBuffer = JfrThreadLocal.flushToGlobalMemory(oldBuffer, Word.unsigned(uncommittedSize), requestedSize); if (newBuffer.isNull()) { /* The flush failed, so mark the EventWriter as invalid for this write attempt. */ JfrEventWriterAccess.update(writer, oldBuffer, 0, false); @@ -552,7 +552,7 @@ public long commit(long nextPosition) { return nextPosition; } - Pointer next = WordFactory.pointer(nextPosition); + Pointer next = Word.pointer(nextPosition); assert next.aboveOrEqual(current.getCommittedPos()) : "invariant"; assert next.belowOrEqual(JfrBufferAccess.getDataEnd(current)) : "invariant"; if (JfrThreadLocal.isNotified()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObject.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObject.java index 787ec08a004a..65453fc6976b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObject.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObject.java @@ -30,8 +30,8 @@ import java.lang.ref.WeakReference; +import jdk.graal.compiler.word.Word; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.collections.UninterruptibleComparable; @@ -75,12 +75,12 @@ void initialize(Object obj, UnsignedWord span, UnsignedWord allocatedSize, long @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) void reset() { ReferenceInternals.setReferent(reference, null); - this.span = WordFactory.zero(); - this.objectSize = WordFactory.zero(); + this.span = Word.zero(); + this.objectSize = Word.zero(); this.allocationTicks = 0L; this.threadId = 0L; this.stackTraceId = 0L; - this.heapUsedAfterLastGC = WordFactory.zero(); + this.heapUsedAfterLastGC = Word.zero(); this.arrayLength = 0; this.next = null; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectRepository.java index 83cee25f32b9..18441411bbe0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectRepository.java @@ -32,7 +32,6 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; @@ -198,7 +197,7 @@ void clear(@SuppressWarnings("unused") boolean flushpoint) { void teardown() { unflushedEntries = 0; JfrBufferAccess.free(buffer); - buffer = WordFactory.nullPointer(); + buffer = Word.nullPointer(); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIJavaVMList.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIJavaVMList.java index 8966b02ff48c..e909ee86129c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIJavaVMList.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIJavaVMList.java @@ -25,9 +25,9 @@ package com.oracle.svm.core.jni; import static org.graalvm.word.LocationIdentity.ANY_LOCATION; -import static org.graalvm.word.WordFactory.nullPointer; -import static org.graalvm.word.WordFactory.unsigned; -import static org.graalvm.word.WordFactory.zero; +import static jdk.graal.compiler.word.Word.nullPointer; +import static jdk.graal.compiler.word.Word.unsigned; +import static jdk.graal.compiler.word.Word.zero; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.c.type.CIntPointer; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNILibraryInitializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNILibraryInitializer.java index c403d1fd55d2..70f683aae88b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNILibraryInitializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNILibraryInitializer.java @@ -29,6 +29,7 @@ import java.util.Collection; import java.util.List; +import jdk.graal.compiler.word.Word; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.Equivalence; import org.graalvm.nativeimage.Platform; @@ -36,7 +37,6 @@ import org.graalvm.nativeimage.c.function.InvokeCFunctionPointer; import org.graalvm.nativeimage.c.type.VoidPointer; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.c.CGlobalData; import com.oracle.svm.core.c.CGlobalDataFactory; @@ -115,7 +115,7 @@ public void initialize(PlatformNativeLibrarySupport.NativeLibrary lib) { } if (onLoadFunction.isNonNull()) { JNIOnLoadFunctionPointer onLoad = (JNIOnLoadFunctionPointer) onLoadFunction; - int expected = onLoad.invoke(JNIFunctionTables.singleton().getGlobalJavaVM(), WordFactory.nullPointer()); + int expected = onLoad.invoke(JNIFunctionTables.singleton().getGlobalJavaVM(), Word.nullPointer()); if (!JNIVersion.isSupported(expected, lib.isBuiltin())) { throw new UnsatisfiedLinkError("Unsupported JNI version 0x" + Integer.toHexString(expected) + ", required by " + libName); } @@ -124,6 +124,6 @@ public void initialize(PlatformNativeLibrarySupport.NativeLibrary lib) { private PointerBase getOnLoadSymbolAddress(String libName) { CGlobalData symbol = onLoadCGlobalDataMap.get(libName); - return symbol == null ? WordFactory.nullPointer() : symbol.get(); + return symbol == null ? Word.nullPointer() : symbol.get(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIObjectHandles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIObjectHandles.java index 67f0f4977001..91b39f6ec6eb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIObjectHandles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIObjectHandles.java @@ -29,7 +29,6 @@ import org.graalvm.nativeimage.ObjectHandle; import org.graalvm.word.Pointer; import org.graalvm.word.SignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.RuntimeAssertionsSupport; @@ -294,18 +293,18 @@ static long computeCurrentGlobalHandleCount() { * for example by native code that is unaware of isolates. */ final class JNIGlobalHandles { - static final SignedWord MIN_VALUE = WordFactory.signed(Long.MIN_VALUE); + static final SignedWord MIN_VALUE = Word.signed(Long.MIN_VALUE); static final SignedWord MAX_VALUE = JNIObjectHandles.nullHandle().subtract(1); static { - assert JNIObjectHandles.nullHandle().equal(WordFactory.zero()); + assert JNIObjectHandles.nullHandle().equal(Word.zero()); } private static final int HANDLE_BITS_COUNT = 31; - private static final SignedWord HANDLE_BITS_MASK = WordFactory.signed((1L << HANDLE_BITS_COUNT) - 1); + private static final SignedWord HANDLE_BITS_MASK = Word.signed((1L << HANDLE_BITS_COUNT) - 1); private static final int VALIDATION_BITS_SHIFT = HANDLE_BITS_COUNT; private static final int VALIDATION_BITS_COUNT = 32; - private static final SignedWord VALIDATION_BITS_MASK = WordFactory.signed((1L << VALIDATION_BITS_COUNT) - 1).shiftLeft(VALIDATION_BITS_SHIFT); - private static final SignedWord MSB = WordFactory.signed(1L << 63); + private static final SignedWord VALIDATION_BITS_MASK = Word.signed((1L << VALIDATION_BITS_COUNT) - 1).shiftLeft(VALIDATION_BITS_SHIFT); + private static final SignedWord MSB = Word.signed(1L << 63); private static final ObjectHandlesImpl globalHandles = new ObjectHandlesImpl(JNIObjectHandles.nullHandle().add(1), HANDLE_BITS_MASK, JNIObjectHandles.nullHandle()); @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -315,7 +314,7 @@ static boolean isInRange(JNIObjectHandle handle) { private static Word isolateHash() { int isolateHash = Long.hashCode(CurrentIsolate.getIsolate().rawValue()); - return WordFactory.unsigned(isolateHash); + return Word.unsigned(isolateHash); } private static JNIObjectHandle encode(ObjectHandle handle) { @@ -383,10 +382,10 @@ public static long computeCurrentCount() { */ final class JNIImageHeapHandles { private static final int OBJ_OFFSET_BITS_COUNT = 32; - private static final Word OBJ_OFFSET_BITS_MASK = WordFactory.unsigned((1L << OBJ_OFFSET_BITS_COUNT) - 1); - private static final SignedWord LOCAL_RANGE_MIN = WordFactory.signed(0b01).shiftLeft(OBJ_OFFSET_BITS_COUNT); - private static final SignedWord GLOBAL_RANGE_MIN = WordFactory.signed(0b10).shiftLeft(OBJ_OFFSET_BITS_COUNT); - private static final SignedWord WEAK_GLOBAL_RANGE_MIN = WordFactory.signed(0b11).shiftLeft(OBJ_OFFSET_BITS_COUNT); + private static final Word OBJ_OFFSET_BITS_MASK = Word.unsigned((1L << OBJ_OFFSET_BITS_COUNT) - 1); + private static final SignedWord LOCAL_RANGE_MIN = Word.signed(0b01).shiftLeft(OBJ_OFFSET_BITS_COUNT); + private static final SignedWord GLOBAL_RANGE_MIN = Word.signed(0b10).shiftLeft(OBJ_OFFSET_BITS_COUNT); + private static final SignedWord WEAK_GLOBAL_RANGE_MIN = Word.signed(0b11).shiftLeft(OBJ_OFFSET_BITS_COUNT); private static final SignedWord ENTIRE_RANGE_MIN = LOCAL_RANGE_MIN; private static final SignedWord ENTIRE_RANGE_MAX = WEAK_GLOBAL_RANGE_MIN.add(OBJ_OFFSET_BITS_MASK); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleField.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleField.java index d158f3d104de..f1092f39756d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleField.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleField.java @@ -26,12 +26,12 @@ import java.lang.reflect.Modifier; +import jdk.graal.compiler.word.Word; import org.graalvm.collections.EconomicSet; import org.graalvm.nativeimage.Platform.HOSTED_ONLY; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.BuildPhaseProvider.ReadyForCompilation; import com.oracle.svm.core.StaticFieldsSupport; @@ -46,7 +46,7 @@ */ public final class JNIAccessibleField extends JNIAccessibleMember { /* 10000000...0 */ - private static final UnsignedWord ID_STATIC_FLAG = WordFactory.unsigned(-1L).unsignedShiftRight(1).add(1); + private static final UnsignedWord ID_STATIC_FLAG = Word.unsigned(-1L).unsignedShiftRight(1).add(1); /* 01000000...0 */ private static final UnsignedWord ID_OBJECT_FLAG = ID_STATIC_FLAG.unsignedShiftRight(1); /* 00100000...0 */ @@ -86,13 +86,13 @@ public static WordBase getOffsetFromId(JNIFieldId id) { * */ @UnknownPrimitiveField(availability = ReadyForCompilation.class)// - private UnsignedWord id = WordFactory.zero(); + private UnsignedWord id = Word.zero(); @Platforms(HOSTED_ONLY.class) public JNIAccessibleField(JNIAccessibleClass declaringClass, JavaKind kind, int modifiers) { super(declaringClass); - UnsignedWord bits = Modifier.isStatic(modifiers) ? ID_STATIC_FLAG : WordFactory.zero(); + UnsignedWord bits = Modifier.isStatic(modifiers) ? ID_STATIC_FLAG : Word.zero(); if (kind == null) { bits = bits.or(ID_NEGATIVE_FLAG); } else if (kind.isObject()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleMethod.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleMethod.java index 7f2861b816eb..add0b97ac30f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleMethod.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleMethod.java @@ -26,12 +26,12 @@ import java.lang.reflect.Modifier; +import jdk.graal.compiler.word.Word; import org.graalvm.collections.EconomicSet; import org.graalvm.nativeimage.Platform.HOSTED_ONLY; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.BuildPhaseProvider.ReadyForCompilation; @@ -138,7 +138,7 @@ CodePointer getJavaCallAddress(Object instance, boolean nonVirtual) { if (vtableOffset != STATICALLY_BOUND_METHOD) { long tableStartingOffset = LoadOpenTypeWorldDispatchTableStartingOffset.createOpenTypeWorldLoadDispatchTableStartingOffset(instance.getClass(), interfaceTypeID); - return BarrieredAccess.readWord(instance.getClass(), WordFactory.pointer(tableStartingOffset + vtableOffset), NamedLocationIdentity.FINAL_LOCATION); + return BarrieredAccess.readWord(instance.getClass(), Word.pointer(tableStartingOffset + vtableOffset), NamedLocationIdentity.FINAL_LOCATION); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNINativeLinkage.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNINativeLinkage.java index cfda78d1dc40..91436c06ebdf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNINativeLinkage.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNINativeLinkage.java @@ -28,9 +28,9 @@ import java.util.function.Function; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.function.CFunctionPointer; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.graal.code.CGlobalDataInfo; import com.oracle.svm.core.jdk.NativeLibrarySupport; @@ -46,7 +46,7 @@ */ public final class JNINativeLinkage { - private PointerBase entryPoint = WordFactory.nullPointer(); + private PointerBase entryPoint = Word.nullPointer(); private final CharSequence declaringClass; private final CharSequence name; @@ -105,7 +105,7 @@ public void setEntryPoint(CFunctionPointer fnptr) { * symbol lookup when the method is called the next time. */ public void unsetEntryPoint() { - entryPoint = WordFactory.nullPointer(); + entryPoint = Word.nullPointer(); } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java index fcf53ecaddb6..35d4f96ead00 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java @@ -39,7 +39,6 @@ import org.graalvm.nativeimage.Platform.HOSTED_ONLY; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; @@ -260,7 +259,7 @@ public JNIMethodId getMethodID(Class classObject, CharSequence name, CharSequ private static JNIMethodId toMethodID(JNIAccessibleMethod method) { if (method == null) { - return WordFactory.zero(); + return Word.zero(); } assert Heap.getHeap().isInImageHeap(method); return (JNIMethodId) Word.objectToUntrackedPointer(method).subtract(KnownIntrinsics.heapBase()); @@ -268,7 +267,7 @@ private static JNIMethodId toMethodID(JNIAccessibleMethod method) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static JNIAccessibleMethod getMethodByID(JNIMethodId method) { - if (!SubstrateOptions.SpawnIsolates.getValue() && method == WordFactory.zero()) { + if (!SubstrateOptions.SpawnIsolates.getValue() && method == Word.zero()) { return null; } Pointer p = KnownIntrinsics.heapBase().add((Pointer) method); @@ -305,7 +304,7 @@ private JNIAccessibleField getDeclaredField(Class classObject, CharSequence n public JNIFieldId getDeclaredFieldID(Class classObject, String name, boolean isStatic) { JNIAccessibleField field = getDeclaredField(classObject, name, isStatic, "getDeclaredFieldID"); field = checkField(field, classObject, name); - return (field != null) ? field.getId() : WordFactory.nullPointer(); + return (field != null) ? field.getId() : Word.nullPointer(); } private JNIAccessibleField findField(Class clazz, CharSequence name, boolean isStatic, String dumpLabel) { @@ -336,7 +335,7 @@ private JNIAccessibleField findSuperinterfaceField(Class clazz, CharSequence public JNIFieldId getFieldID(Class clazz, CharSequence name, boolean isStatic) { JNIAccessibleField field = findField(clazz, name, isStatic, "getFieldID"); field = checkField(field, clazz, name); - return (field != null && field.isDiscoverableIn(clazz)) ? field.getId() : WordFactory.nullPointer(); + return (field != null && field.isDiscoverableIn(clazz)) ? field.getId() : Word.nullPointer(); } public String getFieldNameByID(Class classObject, JNIFieldId id) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java index eaf036acc8bd..3fa74785a3e1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java @@ -34,6 +34,7 @@ import java.nio.ByteBuffer; import java.util.Arrays; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.LogHandler; @@ -53,7 +54,6 @@ import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.JavaMemoryUtil; import com.oracle.svm.core.NeverInline; @@ -557,7 +557,7 @@ static void ReleaseStringChars(JNIEnvironment env, JNIObjectHandle hstr, CShortP static CCharPointer GetStringUTFChars(JNIEnvironment env, JNIObjectHandle hstr, CCharPointer isCopy) { String str = JNIObjectHandles.getObject(hstr); if (str == null) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } if (isCopy.isNonNull()) { isCopy.write((byte) 1); @@ -661,7 +661,7 @@ static final class Target_java_nio_Buffer { @CEntryPointOptions(prologue = JNIEnvEnterPrologue.class, prologueBailout = ReturnNullPointer.class) static WordPointer GetDirectBufferAddress(JNIEnvironment env, JNIObjectHandle handle) { Target_java_nio_Buffer buf = Support.directBufferFromJNIHandle(handle); - return (buf == null) ? WordFactory.nullPointer() : WordFactory.pointer(buf.address); + return (buf == null) ? Word.nullPointer() : Word.pointer(buf.address); } /* @@ -801,7 +801,7 @@ static void SetObjectArrayElement(JNIEnvironment env, JNIObjectHandle harray, in static WordPointer GetPrimitiveArrayCritical(JNIEnvironment env, JNIObjectHandle harray, CCharPointer isCopy) { Object array = JNIObjectHandles.getObject(harray); if (array == null) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } PrimitiveArrayView ref = JNIThreadLocalPrimitiveArrayViews.createArrayView(array); if (isCopy.isNonNull()) { @@ -948,7 +948,7 @@ static int GetJavaVM(JNIEnvironment env, JNIJavaVMPointer vm) { @CEntryPoint(exceptionHandler = JNIExceptionHandlerReturnNullWord.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = Publish.NotPublished) @CEntryPointOptions(prologue = JNIEnvEnterPrologue.class, prologueBailout = ReturnNullPointer.class) static JNIFieldId FromReflectedField(JNIEnvironment env, JNIObjectHandle fieldHandle) { - JNIFieldId fieldId = WordFactory.zero(); + JNIFieldId fieldId = Word.zero(); Field obj = JNIObjectHandles.getObject(fieldHandle); if (obj != null) { boolean isStatic = Modifier.isStatic(obj.getModifiers()); @@ -984,7 +984,7 @@ static JNIObjectHandle ToReflectedField(JNIEnvironment env, JNIObjectHandle clas @CEntryPoint(exceptionHandler = JNIExceptionHandlerReturnNullWord.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = Publish.NotPublished) @CEntryPointOptions(prologue = JNIEnvEnterPrologue.class, prologueBailout = ReturnNullPointer.class) static JNIMethodId FromReflectedMethod(JNIEnvironment env, JNIObjectHandle methodHandle) { - JNIMethodId methodId = WordFactory.nullPointer(); + JNIMethodId methodId = Word.nullPointer(); Executable method = JNIObjectHandles.getObject(methodHandle); if (method != null) { boolean isStatic = Modifier.isStatic(method.getModifiers()); @@ -1730,7 +1730,7 @@ static class JNIExceptionHandlerReturnNullWord implements CEntryPoint.ExceptionH @Uninterruptible(reason = "exception handler") static WordBase handle(Throwable t) { Support.handleException(t); - return WordFactory.nullPointer(); + return Word.nullPointer(); } } @@ -1875,7 +1875,7 @@ static JNIFieldId getFieldID(JNIObjectHandle hclazz, CCharPointer cname, CCharPo static CShortPointer getNulTerminatedStringCharsAndPin(JNIObjectHandle hstr, CCharPointer isCopy) { String str = JNIObjectHandles.getObject(hstr); if (str == null) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } if (isCopy.isNonNull()) { isCopy.write((byte) 1); @@ -1971,8 +1971,8 @@ static void getPrimitiveArrayRegion(JavaKind elementKind, JNIObjectHandle handle if (count > 0) { long offset = ConfigurationValues.getObjectLayout().getArrayElementOffset(elementKind, start); int elementSize = ConfigurationValues.getObjectLayout().sizeInBytes(elementKind); - UnsignedWord bytes = WordFactory.unsigned(count).multiply(elementSize); - JavaMemoryUtil.copyOnHeap(obj, WordFactory.unsigned(offset), null, WordFactory.unsigned(buffer.rawValue()), bytes); + UnsignedWord bytes = Word.unsigned(count).multiply(elementSize); + JavaMemoryUtil.copyOnHeap(obj, Word.unsigned(offset), null, Word.unsigned(buffer.rawValue()), bytes); } } @@ -1984,8 +1984,8 @@ static void setPrimitiveArrayRegion(JavaKind elementKind, JNIObjectHandle handle if (count > 0) { long offset = ConfigurationValues.getObjectLayout().getArrayElementOffset(elementKind, start); int elementSize = ConfigurationValues.getObjectLayout().sizeInBytes(elementKind); - UnsignedWord bytes = WordFactory.unsigned(count).multiply(elementSize); - JavaMemoryUtil.copyOnHeap(null, WordFactory.unsigned(buffer.rawValue()), obj, WordFactory.unsigned(offset), bytes); + UnsignedWord bytes = Word.unsigned(count).multiply(elementSize); + JavaMemoryUtil.copyOnHeap(null, Word.unsigned(buffer.rawValue()), obj, Word.unsigned(offset), bytes); } } } 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 7e815e10a81c..a3b11aa67ded 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 jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.LogHandler; import org.graalvm.nativeimage.StackValue; @@ -37,7 +38,6 @@ import org.graalvm.nativeimage.impl.IsolateSupport; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; @@ -113,8 +113,8 @@ static class JNICreateJavaVMPrologue implements CEntryPointOptions.Prologue { @Uninterruptible(reason = "prologue") static int enter(JNIJavaVMPointer vmBuf, JNIEnvironmentPointer penv, JNIJavaVMInitArgs vmArgs) { boolean hasSpecialVmOptions = false; - CEntryPointCreateIsolateParameters params = WordFactory.nullPointer(); - CCharPointerPointer errorstr = WordFactory.nullPointer(); + CEntryPointCreateIsolateParameters params = Word.nullPointer(); + CCharPointerPointer errorstr = Word.nullPointer(); if (vmArgs.isNonNull()) { int vmArgc = vmArgs.getNOptions(); if (vmArgc > 0) { @@ -127,7 +127,7 @@ static int enter(JNIJavaVMPointer vmBuf, JNIEnvironmentPointer penv, JNIJavaVMIn // The first argument is reserved for the name of the binary. We use null // when we are called via JNI. int argc = 0; - argv.addressOf(argc).write(WordFactory.nullPointer()); + argv.addressOf(argc).write(Word.nullPointer()); argc++; Pointer p = (Pointer) vmArgs.getOptions(); @@ -161,7 +161,7 @@ static int enter(JNIJavaVMPointer vmBuf, JNIEnvironmentPointer penv, JNIJavaVMIn int code = CEntryPointActions.enterCreateIsolate(params); if (params.isNonNull()) { UntrackedNullableNativeMemory.free(params.getArgv()); - params = WordFactory.nullPointer(); + params = Word.nullPointer(); } if (code == CEntryPointErrors.NO_ERROR) { @@ -171,7 +171,7 @@ static int enter(JNIJavaVMPointer vmBuf, JNIEnvironmentPointer penv, JNIJavaVMIn if (errorstr.isNonNull()) { CCharPointer msg = CEntryPointErrors.getDescriptionAsCString(code); - CCharPointer msgCopy = msg.isNonNull() ? LibC.strdup(msg) : WordFactory.nullPointer(); + CCharPointer msgCopy = msg.isNonNull() ? LibC.strdup(msg) : Word.nullPointer(); errorstr.write(msgCopy); } return JNIFunctions.Support.convertCEntryPointErrorToJNIError(code, true); @@ -303,7 +303,7 @@ static int GetEnv(JNIJavaVM vm, WordPointer env, int version) { env.write(JNIThreadLocalEnvironment.getAddress()); return JNIErrors.JNI_OK(); } else { - env.write(WordFactory.nullPointer()); + env.write(Word.nullPointer()); return JNIErrors.JNI_EVERSION(); } } @@ -326,7 +326,7 @@ static int enter(JNIJavaVM vm, WordPointer env) { return JNIErrors.JNI_ERR(); } if (!CEntryPointActions.isCurrentThreadAttachedTo(vm.getFunctions().getIsolate())) { - env.write(WordFactory.nullPointer()); + env.write(Word.nullPointer()); return JNIErrors.JNI_EDETACHED(); } if (CEntryPointActions.enterByIsolate(vm.getFunctions().getIsolate()) != 0) { @@ -385,7 +385,7 @@ static int finishInitialization(JNIJavaVMPointer vmBuf, JNIEnvironmentPointer pe } private static int finishInitialization0(JNIJavaVMPointer vmBuf, JNIEnvironmentPointer penv, JNIJavaVMInitArgs vmArgs, boolean hasSpecialVmOptions) { - WordPointer javaVmIdPointer = WordFactory.nullPointer(); + WordPointer javaVmIdPointer = Word.nullPointer(); if (hasSpecialVmOptions) { javaVmIdPointer = parseVMOptions(vmArgs); } @@ -394,7 +394,7 @@ private static int finishInitialization0(JNIJavaVMPointer vmBuf, JNIEnvironmentP JNIJavaVMList.addJavaVM(javaVm); if (javaVmIdPointer.isNonNull()) { long javaVmId = ImageSingletons.lookup(IsolateSupport.class).getIsolateID(); - javaVmIdPointer.write(WordFactory.pointer(javaVmId)); + javaVmIdPointer.write(Word.pointer(javaVmId)); } RuntimeSupport.getRuntimeSupport().addTearDownHook(new RuntimeSupport.Hook() { @Override @@ -408,7 +408,7 @@ public void execute(boolean isFirstIsolate) { } static WordPointer parseVMOptions(JNIJavaVMInitArgs vmArgs) { - WordPointer javaVmIdPointer = WordFactory.nullPointer(); + WordPointer javaVmIdPointer = Word.nullPointer(); Pointer p = (Pointer) vmArgs.getOptions(); int vmArgc = vmArgs.getNOptions(); for (int i = 0; i < vmArgc; i++) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/CHeapPerfMemoryProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/CHeapPerfMemoryProvider.java index ac1417b0e57b..8f3c42e2f6ab 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/CHeapPerfMemoryProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/CHeapPerfMemoryProvider.java @@ -26,8 +26,8 @@ import java.nio.ByteBuffer; +import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.jdk.DirectByteBufferUtil; import com.oracle.svm.core.memory.NativeMemory; @@ -51,7 +51,7 @@ public ByteBuffer create() { public void teardown() { if (memory.isNonNull()) { NativeMemory.free(memory); - memory = WordFactory.nullPointer(); + memory = Word.nullPointer(); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/PerfManager.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/PerfManager.java index 380d1d1a61b7..93795fa4c3cf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/PerfManager.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/PerfManager.java @@ -34,7 +34,6 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.type.CLongPointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.IsolateArgumentParser; import com.oracle.svm.core.SubstrateOptions; @@ -119,7 +118,7 @@ public CLongPointer getLongPerfEntry(String name) { waitForInitialization(); PerfLong entry = longEntries.get(name); assert Heap.getHeap().isInImageHeap(entry); - return (CLongPointer) Word.objectToUntrackedPointer(entry).add(WordFactory.unsigned(PerfLong.VALUE_OFFSET)); + return (CLongPointer) Word.objectToUntrackedPointer(entry).add(Word.unsigned(PerfLong.VALUE_OFFSET)); } public boolean hasLongPerfEntry(String name) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/PerfMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/PerfMemory.java index 75b4dfb96d96..577f3b5b2b1a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/PerfMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/PerfMemory.java @@ -35,7 +35,6 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.word.LocationIdentity; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.SubstrateUtil; @@ -112,7 +111,7 @@ private boolean createBuffer() { memoryProvider = m; buffer = b; capacity = b.capacity(); - rawMemory = WordFactory.pointer(SubstrateUtil.cast(b, Target_java_nio_Buffer.class).address); + rawMemory = Word.pointer(SubstrateUtil.cast(b, Target_java_nio_Buffer.class).address); assert verifyRawMemoryAccess(); @@ -193,7 +192,7 @@ public void setAccessible() { public void teardown() { if (buffer != null) { buffer = null; - rawMemory = WordFactory.zero(); + rawMemory = Word.zero(); capacity = 0; used = 0; } @@ -213,13 +212,13 @@ public void teardown() { private static boolean tryAcquirePerfDataFile() { Pointer perfDataIsolatePtr = PERF_DATA_ISOLATE.get(); - return perfDataIsolatePtr.logicCompareAndSwapWord(0, WordFactory.nullPointer(), CurrentIsolate.getIsolate(), LocationIdentity.ANY_LOCATION); + return perfDataIsolatePtr.logicCompareAndSwapWord(0, Word.nullPointer(), CurrentIsolate.getIsolate(), LocationIdentity.ANY_LOCATION); } private static void releasePerfDataFile() { Pointer perfDataIsolatePtr = PERF_DATA_ISOLATE.get(); if (perfDataIsolatePtr.readWord(0) == CurrentIsolate.getIsolate()) { - perfDataIsolatePtr.writeWord(0, WordFactory.nullPointer()); + perfDataIsolatePtr.writeWord(0, Word.nullPointer()); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiAgents.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiAgents.java index d0ff735f5334..83ae666058ca 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiAgents.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiAgents.java @@ -29,6 +29,7 @@ import java.io.Serial; import java.util.ArrayList; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -38,7 +39,6 @@ import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.nativeimage.c.type.VoidPointer; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.jdk.NativeLibrarySupport; @@ -152,7 +152,7 @@ private static String getAgentFile(String agent, boolean relative) { private static boolean callOnLoadFunction(PointerBase function, String options) { JvmtiOnLoadFunctionPointer onLoad = (JvmtiOnLoadFunctionPointer) function; try (CTypeConversion.CCharPointerHolder holder = CTypeConversion.toCString(options)) { - int result = onLoad.invoke(JNIFunctionTables.singleton().getGlobalJavaVM(), holder.get(), WordFactory.nullPointer()); + int result = onLoad.invoke(JNIFunctionTables.singleton().getGlobalJavaVM(), holder.get(), Word.nullPointer()); /* Any value other than 0 is an error. */ return result == 0; } @@ -160,7 +160,7 @@ private static boolean callOnLoadFunction(PointerBase function, String options) private static void callOnUnLoadFunction(PointerBase function) { JvmtiOnUnLoadFunctionPointer onLoad = (JvmtiOnUnLoadFunctionPointer) function; - onLoad.invoke(JNIFunctionTables.singleton().getGlobalJavaVM(), WordFactory.nullPointer()); + onLoad.invoke(JNIFunctionTables.singleton().getGlobalJavaVM(), Word.nullPointer()); } private static class AgentInitException extends RuntimeException { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiEnvUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiEnvUtil.java index 835c93cd17aa..4106dc5b32f0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiEnvUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiEnvUtil.java @@ -27,6 +27,7 @@ import static com.oracle.svm.core.jvmti.headers.JvmtiError.JVMTI_ERROR_NONE; import static com.oracle.svm.core.jvmti.headers.JvmtiError.JVMTI_ERROR_NOT_AVAILABLE; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.Isolate; import org.graalvm.nativeimage.Platform; @@ -34,7 +35,6 @@ import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.config.ConfigurationValues; @@ -62,13 +62,13 @@ private JvmtiEnvUtil() { static JvmtiEnv allocate() { JvmtiInterface functionTable = JvmtiFunctionTable.allocateFunctionTable(); if (functionTable.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } - JvmtiEnv env = NullableNativeMemory.calloc(WordFactory.unsigned(internalEnvSize()), NmtCategory.JVMTI); + JvmtiEnv env = NullableNativeMemory.calloc(Word.unsigned(internalEnvSize()), NmtCategory.JVMTI); if (env.isNull()) { JvmtiFunctionTable.freeFunctionTable(functionTable); - return WordFactory.nullPointer(); + return Word.nullPointer(); } env.setIsolate(CurrentIsolate.getIsolate()); @@ -89,7 +89,7 @@ static void dispose(JvmtiEnv env) { static void free(JvmtiEnv env) { JvmtiExternalEnv externalEnv = toExternal(env); JvmtiFunctionTable.freeFunctionTable(externalEnv.getFunctions()); - externalEnv.setFunctions(WordFactory.nullPointer()); + externalEnv.setFunctions(Word.nullPointer()); NullableNativeMemory.free(env); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiEnvs.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiEnvs.java index 65678fb6d34e..3a5ef12e88c4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiEnvs.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiEnvs.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core.jvmti; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.jvmti.headers.JvmtiExternalEnv; import com.oracle.svm.core.locks.VMMutex; @@ -62,7 +62,7 @@ public JvmtiEnv getHead() { public JvmtiExternalEnv create() { JvmtiEnv env = JvmtiEnvUtil.allocate(); if (env.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } mutex.lock(); @@ -117,7 +117,7 @@ private void cleanup() { assert hasDisposedEnvs; JvmtiEnv cur = headEnv; - JvmtiEnv prev = WordFactory.nullPointer(); + JvmtiEnv prev = Word.nullPointer(); while (cur.isNonNull()) { if (JvmtiEnvUtil.isDead(cur)) { remove(cur, prev); @@ -153,7 +153,7 @@ public void teardown() { cur = next; } - headEnv = WordFactory.nullPointer(); - tailEnv = WordFactory.nullPointer(); + headEnv = Word.nullPointer(); + tailEnv = Word.nullPointer(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiEventCallbacksUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiEventCallbacksUtil.java index bfadc921eafc..cc579c5aeed8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiEventCallbacksUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiEventCallbacksUtil.java @@ -24,11 +24,11 @@ */ package com.oracle.svm.core.jvmti; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.jdk.UninterruptibleUtils; @@ -42,11 +42,11 @@ private JvmtiEventCallbacksUtil() { public static void setEventCallbacks(JvmtiEventCallbacks envEventCallbacks, JvmtiEventCallbacks newCallbacks, int sizeOfCallbacks) { int internalStructSize = SizeOf.get(JvmtiEventCallbacks.class); - UnmanagedMemoryUtil.fill((Pointer) envEventCallbacks, WordFactory.unsigned(internalStructSize), (byte) 0); + UnmanagedMemoryUtil.fill((Pointer) envEventCallbacks, Word.unsigned(internalStructSize), (byte) 0); if (newCallbacks.isNonNull()) { int bytesToCopy = UninterruptibleUtils.Math.min(internalStructSize, sizeOfCallbacks); - UnmanagedMemoryUtil.copy((Pointer) newCallbacks, (Pointer) envEventCallbacks, WordFactory.unsigned(bytesToCopy)); + UnmanagedMemoryUtil.copy((Pointer) newCallbacks, (Pointer) envEventCallbacks, Word.unsigned(bytesToCopy)); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiFunctions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiFunctions.java index a06baf29a457..6e0fd4c70286 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiFunctions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/JvmtiFunctions.java @@ -33,6 +33,7 @@ import static com.oracle.svm.core.jvmti.headers.JvmtiError.JVMTI_ERROR_NULL_POINTER; import static com.oracle.svm.core.jvmti.headers.JvmtiError.JVMTI_ERROR_OUT_OF_MEMORY; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.function.CEntryPoint; @@ -49,7 +50,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.function.CEntryPointActions; @@ -142,14 +142,14 @@ static int Allocate(JvmtiExternalEnv externalEnv, long size, CCharPointerPointer if (memPtr.isNull()) { return JVMTI_ERROR_NULL_POINTER.getCValue(); } else if (size < 0) { - memPtr.write(WordFactory.nullPointer()); + memPtr.write(Word.nullPointer()); return JVMTI_ERROR_ILLEGAL_ARGUMENT.getCValue(); } if (size == 0) { - memPtr.write(WordFactory.nullPointer()); + memPtr.write(Word.nullPointer()); } else { - CCharPointer mem = NullableNativeMemory.malloc(WordFactory.unsigned(size), NmtCategory.JVMTI); + CCharPointer mem = NullableNativeMemory.malloc(Word.unsigned(size), NmtCategory.JVMTI); memPtr.write(mem); if (mem.isNull()) { return JVMTI_ERROR_OUT_OF_MEMORY.getCValue(); @@ -180,7 +180,7 @@ static int GetThreadState(JvmtiExternalEnv externalEnv, JThread thread, CIntPoin @CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished) @CEntryPointOptions(prologue = JvmtiEnvEnterPrologue.class) static int GetCurrentThread(JvmtiExternalEnv externalEnv, JThreadPointer threadPtr) { - if (threadPtr.equal(WordFactory.nullPointer())) { + if (threadPtr.equal(Word.nullPointer())) { return JVMTI_ERROR_NULL_POINTER.getCValue(); } return JVMTI_ERROR_ACCESS_DENIED.getCValue(); @@ -1345,12 +1345,12 @@ static int GetErrorName(JvmtiExternalEnv externalEnv, JvmtiError jvmtiError, CCh if (namePtr.isNull()) { return JVMTI_ERROR_NULL_POINTER.getCValue(); } else if (jvmtiError == null) { - namePtr.write(WordFactory.nullPointer()); + namePtr.write(Word.nullPointer()); return JVMTI_ERROR_ILLEGAL_ARGUMENT.getCValue(); } String name = jvmtiError.name(); - UnsignedWord bufferSize = WordFactory.unsigned(name.length() + 1); + UnsignedWord bufferSize = Word.unsigned(name.length() + 1); Pointer mem = NullableNativeMemory.malloc(bufferSize, NmtCategory.JVMTI); namePtr.write((CCharPointer) mem); if (mem.isNull()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmti/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/ImageSingletonLoader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/ImageSingletonLoader.java index 3d8ed02c18dd..7f423f31645d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/ImageSingletonLoader.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/ImageSingletonLoader.java @@ -39,6 +39,4 @@ public interface ImageSingletonLoader { String readString(String keyName); List readStringList(String keyName); - - Class lookupClass(String className); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java index 7786ec5ce0f4..903b5f5f3a69 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMMutex.java @@ -24,12 +24,12 @@ */ package com.oracle.svm.core.locks; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CIsolateDataFactory; @@ -51,7 +51,7 @@ * with platform-specific implementations. */ public class VMMutex extends VMLockingPrimitive { - static final UnsignedWord UNSPECIFIED_OWNER = WordFactory.unsigned(-1); + static final UnsignedWord UNSPECIFIED_OWNER = Word.unsigned(-1); private final String name; IsolateThread owner; @@ -176,13 +176,13 @@ public void setOwnerToUnspecified() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void clearCurrentThreadOwner() { assert isOwner() : "Only the thread that holds the mutex can clear the owner."; - owner = WordFactory.nullPointer(); + owner = Word.nullPointer(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void clearUnspecifiedOwner() { assert hasUnspecifiedOwner(); - owner = WordFactory.nullPointer(); + owner = Word.nullPointer(); } /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/RealLog.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/RealLog.java index 559289c7b0d8..819e36de937f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/RealLog.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/RealLog.java @@ -34,7 +34,6 @@ import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.SubstrateOptions; @@ -120,7 +119,7 @@ public Log string(byte[] value, int offset, int length) { } else if ((offset < 0) || (offset > value.length) || (length < 0) || ((offset + length) > value.length) || ((offset + length) < 0)) { rawString("OUT OF BOUNDS"); } else if (Heap.getHeap().isInImageHeap(value)) { - rawBytes(NonmovableArrays.addressOf(NonmovableArrays.fromImageHeap(value), offset), WordFactory.unsigned(length)); + rawBytes(NonmovableArrays.addressOf(NonmovableArrays.fromImageHeap(value), offset), Word.unsigned(length)); } else { rawBytes(value, offset, length); } @@ -165,7 +164,7 @@ private void rawBytes(Object value, int offset, int length) { } bytes.write(i, b); } - rawBytes(bytes, WordFactory.unsigned(chunkLength)); + rawBytes(bytes, Word.unsigned(chunkLength)); chunkOffset += chunkLength; inputLength -= chunkLength; @@ -181,7 +180,7 @@ private static char charAt(String s, int index) { @NeverInline("Logging is always slow-path code") @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate when logging.") public Log string(CCharPointer value) { - if (value.notEqual(WordFactory.nullPointer())) { + if (value.notEqual(Word.nullPointer())) { rawBytes(value, SubstrateUtil.strlen(value)); } else { rawString("null"); @@ -195,7 +194,7 @@ public Log string(CCharPointer bytes, int length) { if (length == 0) { return this; } - return rawBytes(bytes, WordFactory.unsigned(length)); + return rawBytes(bytes, Word.unsigned(length)); } @Override @@ -204,7 +203,7 @@ public Log string(CCharPointer bytes, int length) { public Log character(char value) { CCharPointer bytes = UnsafeStackValue.get(CCharPointer.class); bytes.write((byte) value); - rawBytes(bytes, WordFactory.unsigned(1)); + rawBytes(bytes, Word.unsigned(1)); return this; } @@ -284,7 +283,7 @@ private Log number(long value, int radix, boolean signed, int fill, int align) { spaces(spaces); } - rawBytes(bytes.addressOf(charPos), WordFactory.unsigned(length)); + rawBytes(bytes.addressOf(charPos), Word.unsigned(length)); if (align == LEFT_ALIGN) { int spaces = fill - length; @@ -611,7 +610,7 @@ public Log hexdump(PointerBase from, int wordSize, int numWords) { @NeverInline("Logging is always slow-path code") @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate when logging.") public Log hexdump(PointerBase from, int wordSize, int numWords, int bytesPerLine) { - Pointer base = WordFactory.pointer(from.rawValue()); + Pointer base = Word.pointer(from.rawValue()); int sanitizedWordsize = wordSize > 0 ? Integer.highestOneBit(Math.min(wordSize, 8)) : 2; for (int offset = 0; offset < sanitizedWordsize * numWords; offset += sanitizedWordsize) { if (offset % bytesPerLine == 0) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/NativeMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/NativeMemory.java index 31929d623cd1..5d5c0422e903 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/NativeMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/NativeMemory.java @@ -27,11 +27,11 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.UnmanagedMemory; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.nmt.NmtCategory; @@ -78,7 +78,7 @@ public static T malloc(UnsignedWord size, NmtCategory ca */ public static T malloc(int size, NmtCategory category) { assert size >= 0; - return malloc(WordFactory.unsigned(size), category); + return malloc(Word.unsigned(size), category); } /** @@ -103,7 +103,7 @@ public static T calloc(UnsignedWord size, NmtCategory ca */ public static T calloc(int size, NmtCategory category) { assert size >= 0; - return calloc(WordFactory.unsigned(size), category); + return calloc(Word.unsigned(size), category); } /** @@ -130,7 +130,7 @@ public static T realloc(T ptr, UnsignedWord size, NmtCat */ public static T realloc(T ptr, int size, NmtCategory category) { assert size >= 0; - return realloc(ptr, WordFactory.unsigned(size), category); + return realloc(ptr, Word.unsigned(size), category); } /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/NullableNativeMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/NullableNativeMemory.java index e0109644e7e6..759692322cbb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/NullableNativeMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/NullableNativeMemory.java @@ -27,12 +27,12 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.UnmanagedMemory; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; @@ -77,7 +77,7 @@ public static T malloc(UnsignedWord size, NmtCategory ca @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public static T malloc(int size, NmtCategory category) { assert size >= 0; - return malloc(WordFactory.unsigned(size), category); + return malloc(Word.unsigned(size), category); } /** @@ -99,7 +99,7 @@ public static T calloc(UnsignedWord size, NmtCategory ca @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public static T calloc(int size, NmtCategory category) { assert size >= 0; - return calloc(WordFactory.unsigned(size), category); + return calloc(Word.unsigned(size), category); } /** @@ -127,10 +127,10 @@ public static T realloc(T ptr, UnsignedWord size, NmtCat /* Try to realloc. */ T newOuterPointer = UntrackedNullableNativeMemory.realloc(oldOuterPointer, getAllocationSize(size)); if (newOuterPointer.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } - oldOuterPointer = WordFactory.nullPointer(); + oldOuterPointer = Word.nullPointer(); /* Only untrack the old block if the allocation was successful. */ NativeMemoryTracking.singleton().untrack(oldSize, oldCategory); @@ -154,7 +154,7 @@ public static T realloc(T ptr, UnsignedWord size, NmtCat @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public static T realloc(T ptr, int size, NmtCategory category) { assert size >= 0; - return realloc(ptr, WordFactory.unsigned(size), category); + return realloc(ptr, Word.unsigned(size), category); } /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/UnmanagedMemorySupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/UnmanagedMemorySupportImpl.java index 9c41d7ce1b7c..602dfb7f4876 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/UnmanagedMemorySupportImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/UnmanagedMemorySupportImpl.java @@ -24,11 +24,11 @@ */ package com.oracle.svm.core.memory; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; @@ -50,14 +50,14 @@ public T malloc(UnsignedWord size) { * Some libc implementations may return a nullptr when the size is 0. We use a minimum size * of 1 to ensure that we always get a unique pointer if the allocation succeeds. */ - UnsignedWord allocationSize = UnsignedUtils.max(size, WordFactory.unsigned(1)); + UnsignedWord allocationSize = UnsignedUtils.max(size, Word.unsigned(1)); return libc().malloc(allocationSize); } @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public T calloc(UnsignedWord size) { - return libc().calloc(WordFactory.unsigned(1), size); + return libc().calloc(Word.unsigned(1), size); } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/UntrackedNullableNativeMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/UntrackedNullableNativeMemory.java index d05fa8041444..686c7609908d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/UntrackedNullableNativeMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/memory/UntrackedNullableNativeMemory.java @@ -26,11 +26,11 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; @@ -65,7 +65,7 @@ public static T malloc(UnsignedWord size) { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public static T malloc(int size) { assert size >= 0; - return malloc(WordFactory.unsigned(size)); + return malloc(Word.unsigned(size)); } /** @@ -86,7 +86,7 @@ public static T calloc(UnsignedWord size) { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public static T calloc(int size) { assert size >= 0; - return calloc(WordFactory.unsigned(size)); + return calloc(Word.unsigned(size)); } /** @@ -111,7 +111,7 @@ public static T realloc(T ptr, UnsignedWord size) { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public static T realloc(T ptr, int size) { assert size >= 0; - return realloc(ptr, WordFactory.unsigned(size)); + return realloc(ptr, Word.unsigned(size)); } /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java index c46a177f4d5e..2158e99a0090 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java @@ -28,6 +28,7 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -35,7 +36,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.VMInspectionOptions; @@ -61,7 +61,7 @@ * model of virtual memory is maintained. */ public class NativeMemoryTracking { - private static final UnsignedWord ALIGNMENT = WordFactory.unsigned(16); + private static final UnsignedWord ALIGNMENT = Word.unsigned(16); private static final int MAGIC = 0xF0F1F2F3; private static final long KB = 1024; @@ -140,7 +140,7 @@ public void track(PointerBase innerPtr) { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public PointerBase untrack(PointerBase innerPtr) { if (innerPtr.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } NmtMallocHeader header = getHeader(innerPtr); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nodes/SubstrateMethodCallTargetNode.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nodes/SubstrateMethodCallTargetNode.java index cda372db451f..ecac94eb9d35 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nodes/SubstrateMethodCallTargetNode.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nodes/SubstrateMethodCallTargetNode.java @@ -33,6 +33,12 @@ import jdk.vm.ci.meta.JavaTypeProfile; import jdk.vm.ci.meta.ResolvedJavaMethod; +/** + * Extension of {@link MethodCallTargetNode} that adds a {@link JavaMethodProfile} when available. + * Note that the replacement from {@link MethodCallTargetNode} to + * {@link SubstrateMethodCallTargetNode} is not mandatory. SVM code can introduce new + * {@link MethodCallTargetNode}s. + */ @NodeInfo public final class SubstrateMethodCallTargetNode extends MethodCallTargetNode { public static final NodeClass TYPE = NodeClass.create(SubstrateMethodCallTargetNode.class); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractCommittedMemoryProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractCommittedMemoryProvider.java index 905d5db2135d..1d5e927182d5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractCommittedMemoryProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractCommittedMemoryProvider.java @@ -28,12 +28,12 @@ import static com.oracle.svm.core.Isolates.IMAGE_HEAP_END; import static com.oracle.svm.core.Isolates.IMAGE_HEAP_WRITABLE_BEGIN; import static com.oracle.svm.core.Isolates.IMAGE_HEAP_WRITABLE_END; -import static org.graalvm.word.WordFactory.nullPointer; +import static jdk.graal.compiler.word.Word.nullPointer; +import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; @@ -80,7 +80,7 @@ public Pointer allocateExecutableMemory(UnsignedWord nbytes, UnsignedWord alignm @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) protected Pointer allocate(UnsignedWord size, UnsignedWord alignment, boolean executable, NmtCategory nmtCategory) { - Pointer reserved = WordFactory.nullPointer(); + Pointer reserved = Word.nullPointer(); if (!UnsignedUtils.isAMultiple(getGranularity(), alignment)) { reserved = VirtualMemoryProvider.get().reserve(size, alignment, executable); if (reserved.isNull()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractCopyingImageHeapProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractCopyingImageHeapProvider.java index 1dd8ca8906b4..f81660685d81 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractCopyingImageHeapProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractCopyingImageHeapProvider.java @@ -30,11 +30,11 @@ import static com.oracle.svm.core.Isolates.IMAGE_HEAP_WRITABLE_END; import static com.oracle.svm.core.util.PointerUtils.roundUp; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.function.CEntryPointErrors; @@ -47,10 +47,10 @@ public abstract class AbstractCopyingImageHeapProvider extends AbstractImageHeap @Override @Uninterruptible(reason = "Called during isolate initialization.") public int initialize(Pointer reservedAddressSpace, UnsignedWord reservedSize, WordPointer basePointer, WordPointer endPointer) { - Pointer selfReservedMemory = WordFactory.nullPointer(); + Pointer selfReservedMemory = Word.nullPointer(); UnsignedWord requiredSize = getTotalRequiredAddressSpaceSize(); if (reservedAddressSpace.isNull()) { - UnsignedWord alignment = WordFactory.unsigned(Heap.getHeap().getPreferredAddressSpaceAlignment()); + UnsignedWord alignment = Word.unsigned(Heap.getHeap().getPreferredAddressSpaceAlignment()); selfReservedMemory = VirtualMemoryProvider.get().reserve(requiredSize, alignment, false); if (selfReservedMemory.isNull()) { return CEntryPointErrors.RESERVE_ADDRESS_SPACE_FAILED; @@ -68,7 +68,7 @@ public int initialize(Pointer reservedAddressSpace, UnsignedWord reservedSize, W heapBase = selfReservedHeapBase; } else { heapBase = reservedAddressSpace.add(preHeapRequiredBytes); - selfReservedHeapBase = WordFactory.nullPointer(); + selfReservedHeapBase = Word.nullPointer(); } int error = DynamicMethodAddressResolutionHeapSupport.get().initialize(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractImageHeapProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractImageHeapProvider.java index ff24a75058d9..a9aa47620350 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractImageHeapProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractImageHeapProvider.java @@ -31,7 +31,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.code.DynamicMethodAddressResolutionHeapSupport; @@ -56,7 +55,7 @@ public UnsignedWord getImageHeapAddressSpaceSize() { UnsignedWord pageSize = VirtualMemoryProvider.get().getGranularity(); int imageHeapOffset = Heap.getHeap().getImageHeapOffsetInAddressSpace(); assert imageHeapOffset >= 0; - UnsignedWord size = WordFactory.unsigned(imageHeapOffset); + UnsignedWord size = Word.unsigned(imageHeapOffset); size = size.add(getImageHeapSizeInFile()); size = UnsignedUtils.roundUp(size, pageSize); return size; @@ -82,7 +81,7 @@ protected static Pointer getImageHeapBegin(Pointer heapBase) { protected static UnsignedWord getPreHeapAlignedSizeForDynamicMethodAddressResolver() { UnsignedWord requiredPreHeapMemoryInBytes = DynamicMethodAddressResolutionHeapSupport.get().getRequiredPreHeapMemoryInBytes(); /* Ensure there is enough space to properly align the heap */ - UnsignedWord heapAlignment = WordFactory.unsigned(Heap.getHeap().getPreferredAddressSpaceAlignment()); + UnsignedWord heapAlignment = Word.unsigned(Heap.getHeap().getPreferredAddressSpaceAlignment()); return roundUp((PointerBase) requiredPreHeapMemoryInBytes, heapAlignment); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractRawFileOperationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractRawFileOperationSupport.java index 753e63da36d0..e3434db566b3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractRawFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractRawFileOperationSupport.java @@ -32,7 +32,6 @@ import org.graalvm.nativeimage.StackValue; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.hub.DynamicHub; @@ -66,7 +65,7 @@ public boolean write(RawFileDescriptor fd, byte[] data) { DynamicHub hub = KnownIntrinsics.readHub(data); UnsignedWord baseOffset = LayoutEncoding.getArrayBaseOffset(hub.getLayoutEncoding()); Pointer dataPtr = Word.objectToUntrackedPointer(data).add(baseOffset); - return write(fd, dataPtr, WordFactory.unsigned(data.length)); + return write(fd, dataPtr, Word.unsigned(data.length)); } @Override @@ -81,7 +80,7 @@ public boolean writeByte(RawFileDescriptor fd, byte data) { int sizeInBytes = Byte.BYTES; Pointer dataPtr = StackValue.get(sizeInBytes); dataPtr.writeByte(0, data); - return write(fd, dataPtr, WordFactory.unsigned(sizeInBytes)); + return write(fd, dataPtr, Word.unsigned(sizeInBytes)); } @Override @@ -90,7 +89,7 @@ public boolean writeShort(RawFileDescriptor fd, short data) { int sizeInBytes = Short.BYTES; Pointer dataPtr = StackValue.get(sizeInBytes); dataPtr.writeShort(0, useNativeByteOrder ? data : Short.reverseBytes(data)); - return write(fd, dataPtr, WordFactory.unsigned(sizeInBytes)); + return write(fd, dataPtr, Word.unsigned(sizeInBytes)); } @Override @@ -99,7 +98,7 @@ public boolean writeChar(RawFileDescriptor fd, char data) { int sizeInBytes = Character.BYTES; Pointer dataPtr = StackValue.get(sizeInBytes); dataPtr.writeChar(0, useNativeByteOrder ? data : Character.reverseBytes(data)); - return write(fd, dataPtr, WordFactory.unsigned(sizeInBytes)); + return write(fd, dataPtr, Word.unsigned(sizeInBytes)); } @Override @@ -108,7 +107,7 @@ public boolean writeInt(RawFileDescriptor fd, int data) { int sizeInBytes = Integer.BYTES; Pointer dataPtr = StackValue.get(sizeInBytes); dataPtr.writeInt(0, useNativeByteOrder ? data : Integer.reverseBytes(data)); - return write(fd, dataPtr, WordFactory.unsigned(sizeInBytes)); + return write(fd, dataPtr, Word.unsigned(sizeInBytes)); } @Override @@ -117,7 +116,7 @@ public boolean writeLong(RawFileDescriptor fd, long data) { int sizeInBytes = Long.BYTES; Pointer dataPtr = StackValue.get(sizeInBytes); dataPtr.writeLong(0, useNativeByteOrder ? data : Long.reverseBytes(data)); - return write(fd, dataPtr, WordFactory.unsigned(sizeInBytes)); + return write(fd, dataPtr, Word.unsigned(sizeInBytes)); } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java index b7c8bdd5789f..bb82329409cb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java @@ -35,7 +35,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.UnmanagedMemoryUtil; @@ -103,18 +102,18 @@ protected BufferedFileOperationSupport(boolean useNativeByteOrder) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public BufferedFile allocate(RawFileDescriptor fd, NmtCategory nmtCategory) { if (!rawFiles().isValid(fd)) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } long filePosition = rawFiles().position(fd); if (filePosition < 0) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } /* Use a single allocation for the struct and the corresponding buffer. */ - UnsignedWord totalSize = SizeOf.unsigned(BufferedFile.class).add(WordFactory.unsigned(BUFFER_SIZE)); + UnsignedWord totalSize = SizeOf.unsigned(BufferedFile.class).add(Word.unsigned(BUFFER_SIZE)); BufferedFile result = NullableNativeMemory.malloc(totalSize, nmtCategory); if (result.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } result.setFileDescriptor(fd); @@ -145,7 +144,7 @@ public boolean flush(BufferedFile f) { return true; } - boolean success = rawFiles().write(f.getFileDescriptor(), getBufferStart(f), WordFactory.unsigned(unflushed)); + boolean success = rawFiles().write(f.getFileDescriptor(), getBufferStart(f), Word.unsigned(unflushed)); if (success) { f.setBufferPos(getBufferStart(f)); f.setFilePosition(f.getFilePosition() + unflushed); @@ -220,7 +219,7 @@ public boolean write(BufferedFile f, byte[] data) { DynamicHub hub = KnownIntrinsics.readHub(data); UnsignedWord baseOffset = LayoutEncoding.getArrayBaseOffset(hub.getLayoutEncoding()); Pointer dataPtr = Word.objectToUntrackedPointer(data).add(baseOffset); - return write(f, dataPtr, WordFactory.unsigned(data.length)); + return write(f, dataPtr, Word.unsigned(data.length)); } /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/ChunkBasedCommittedMemoryProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/ChunkBasedCommittedMemoryProvider.java index 7bdcbd36581b..77ef55d28e94 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/ChunkBasedCommittedMemoryProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/ChunkBasedCommittedMemoryProvider.java @@ -24,16 +24,17 @@ */ package com.oracle.svm.core.os; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.nmt.NmtCategory; +import com.oracle.svm.core.thread.VMOperation; import jdk.graal.compiler.api.replacements.Fold; @@ -90,7 +91,7 @@ public void freeUnalignedChunk(PointerBase start, UnsignedWord nbytes) { @Fold protected static UnsignedWord getAlignmentForUnalignedChunks() { int alignment = Math.max(ConfigurationValues.getTarget().wordSize, ConfigurationValues.getObjectLayout().getAlignment()); - return WordFactory.unsigned(alignment); + return Word.unsigned(alignment); } /** @@ -99,6 +100,7 @@ protected static UnsignedWord getAlignmentForUnalignedChunks() { */ @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called by the GC.") public void beforeGarbageCollection() { + assert VMOperation.isGCInProgress() : "may only be called by the GC"; } /** @@ -107,6 +109,7 @@ public void beforeGarbageCollection() { */ @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called by the GC.") public void afterGarbageCollection() { + assert VMOperation.isGCInProgress() : "may only be called by the GC"; } /** @@ -115,5 +118,6 @@ public void afterGarbageCollection() { */ @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called by the GC.") public void uncommitUnusedMemory() { + assert VMOperation.isGCInProgress() : "may only be called by the GC"; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/OSCommittedMemoryProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/OSCommittedMemoryProvider.java index 60d06ade6fb4..98318d2f801c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/OSCommittedMemoryProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/OSCommittedMemoryProvider.java @@ -24,10 +24,9 @@ */ package com.oracle.svm.core.os; -import static org.graalvm.word.WordFactory.nullPointer; -import static org.graalvm.word.WordFactory.zero; +import static jdk.graal.compiler.word.Word.nullPointer; +import static jdk.graal.compiler.word.Word.zero; -import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.type.WordPointer; @@ -37,8 +36,6 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.function.CEntryPointErrors; -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; -import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.snippets.KnownIntrinsics; public class OSCommittedMemoryProvider extends ChunkBasedCommittedMemoryProvider { @@ -68,13 +65,3 @@ public int tearDown() { return ImageHeapProvider.get().freeImageHeap(KnownIntrinsics.heapBase()); } } - -@AutomaticallyRegisteredFeature -class OSCommittedMemoryProviderFeature implements InternalFeature { - @Override - public void beforeAnalysis(BeforeAnalysisAccess access) { - if (!ImageSingletons.contains(CommittedMemoryProvider.class)) { - ImageSingletons.add(CommittedMemoryProvider.class, new OSCommittedMemoryProvider()); - } - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/VirtualMemoryProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/VirtualMemoryProvider.java index 7aa211c1bd41..fa650a7912b8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/VirtualMemoryProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/VirtualMemoryProvider.java @@ -25,12 +25,12 @@ package com.oracle.svm.core.os; import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; /** * Primitive operations for low-level virtual memory management. @@ -90,7 +90,7 @@ default UnsignedWord getAlignment() { * @param alignment The alignment in bytes of the start of the address range to be reserved. * @param code whether the memory may store instructions (see {@link Access#FUTURE_EXECUTE}). * @return An {@linkplain #getAlignment aligned} pointer to the beginning of the reserved - * address range, or {@link WordFactory#nullPointer()} in case of an error. + * address range, or {@link Word#nullPointer()} in case of an error. */ Pointer reserve(UnsignedWord nbytes, UnsignedWord alignment, boolean code); @@ -102,17 +102,17 @@ default UnsignedWord getAlignment() { * visible through the mapping. * * @param start The start of the address range to contain the mapping, which must be a multiple - * of the {@linkplain #getGranularity() granularity}, or - * {@link WordFactory#nullPointer() null} to select an available (unreserved, - * uncommitted) address range in an arbitrary location. + * of the {@linkplain #getGranularity() granularity}, or {@link Word#nullPointer() + * null} to select an available (unreserved, uncommitted) address range in an + * arbitrary location. * @param nbytes The size in bytes of the file region to be mapped, which need not be a multiple * of the {@linkplain #getGranularity() granularity}. This value must not be 0. * @param fileHandle A platform-specific open file handle. * @param offset The offset in bytes of the region within the file to be mapped, which must be a * multiple of the {@linkplain #getGranularity() granularity}. * @param access The modes in which the memory is permitted to be accessed, see {@link Access}. - * @return The start of the mapped address range, or {@link WordFactory#nullPointer()} in case - * of an error. + * @return The start of the mapped address range, or {@link Word#nullPointer()} in case of an + * error. */ Pointer mapFile(PointerBase start, UnsignedWord nbytes, WordBase fileHandle, UnsignedWord offset, int access); @@ -126,21 +126,21 @@ default UnsignedWord getAlignment() { * ranges. If the provided range covers addresses outside of such ranges, or from multiple * independently reserved ranges, undefined effects can occur. *

      - * Alternatively, {@link WordFactory#nullPointer() NULL} can be passed for the start address, in - * which case an available (unreserved, uncommitted) address range in an arbitrary but + * Alternatively, {@link Word#nullPointer() NULL} can be passed for the start address, in which + * case an available (unreserved, uncommitted) address range in an arbitrary but * {@linkplain #getAlignment aligned} location will be selected, reserved and committed in one * step. * * @param start The start of the address range to be committed, which must be a multiple of the - * {@linkplain #getGranularity() granularity}, or {@link WordFactory#nullPointer() - * NULL} to select an available (unreserved, uncommitted) address range in an - * arbitrary but {@linkplain #getAlignment aligned} location. + * {@linkplain #getGranularity() granularity}, or {@link Word#nullPointer() NULL} to + * select an available (unreserved, uncommitted) address range in an arbitrary but + * {@linkplain #getAlignment aligned} location. * @param nbytes The size in bytes of the address range to be committed, which will be rounded * up to a multiple of the {@linkplain #getGranularity() granularity}. This value * must not be 0. * @param access The modes in which the memory is permitted to be accessed, see {@link Access}. - * @return The start of the committed address range, or {@link WordFactory#nullPointer()} in - * case of an error, such as inadequate physical memory. + * @return The start of the committed address range, or {@link Word#nullPointer()} in case of an + * error, such as inadequate physical memory. */ Pointer commit(PointerBase start, UnsignedWord nbytes, int access); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/GOTHeapSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/GOTHeapSupport.java index eb26f7b4aff5..007bb5d3183a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/GOTHeapSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/GOTHeapSupport.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.pltgot; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.LocationIdentity; @@ -31,7 +32,6 @@ import org.graalvm.word.PointerBase; import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CGlobalData; @@ -54,8 +54,8 @@ public abstract class GOTHeapSupport extends DynamicMethodAddressResolutionHeapS public static final String IMAGE_GOT_BEGIN_SYMBOL_NAME = "__svm_got_begin"; public static final CGlobalData IMAGE_GOT_BEGIN = CGlobalDataFactory.forSymbol(IMAGE_GOT_BEGIN_SYMBOL_NAME); - private static final SignedWord GOT_UNINITIALIZED = WordFactory.signed(-1); - private static final SignedWord GOT_INITIALIZATION_IN_PROGRESS = WordFactory.signed(-2); + private static final SignedWord GOT_UNINITIALIZED = Word.signed(-1); + private static final SignedWord GOT_INITIALIZATION_IN_PROGRESS = Word.signed(-2); private static final CGlobalData GOT_STATUS = CGlobalDataFactory.createWord(GOT_UNINITIALIZED); static final CGlobalData GOT_START_ADDRESS = CGlobalDataFactory.createWord(); @@ -150,7 +150,7 @@ public int initialize() { if (ret == CEntryPointErrors.NO_ERROR) { makeGOTReadOnly(); } - GOT_STATUS.get().writeWordVolatile(0, WordFactory.signed(ret)); + GOT_STATUS.get().writeWordVolatile(0, Word.signed(ret)); return ret; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/IdentityMethodAddressResolver.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/IdentityMethodAddressResolver.java index 996d6cf8d4a5..19ad8af5d4d8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/IdentityMethodAddressResolver.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/IdentityMethodAddressResolver.java @@ -24,9 +24,9 @@ */ package com.oracle.svm.core.pltgot; +import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CGlobalData; @@ -41,7 +41,7 @@ public class IdentityMethodAddressResolver implements MethodAddressResolver { @Uninterruptible(reason = "Called from the PLT stub where stack walks are not safe.") public long resolveMethodWithGotEntry(long gotEntry) { /* Fetch the absolute address of the method that corresponds to the target GOT entry. */ - UnsignedWord methodTableOffset = WordFactory.unsigned(gotEntry).multiply(ConfigurationValues.getTarget().wordSize); + UnsignedWord methodTableOffset = Word.unsigned(gotEntry).multiply(ConfigurationValues.getTarget().wordSize); UnsignedWord address = methodTable.get().readWord(methodTableOffset); /* * Write the resolved address to the GOT entry so that it can be directly used for future diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/aarch64/AArch64MethodAddressResolutionDispatcher.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/aarch64/AArch64MethodAddressResolutionDispatcher.java index 15dcfb3f23f6..cdb31ba54562 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/aarch64/AArch64MethodAddressResolutionDispatcher.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/aarch64/AArch64MethodAddressResolutionDispatcher.java @@ -26,8 +26,8 @@ import static com.oracle.svm.core.pltgot.ExitMethodAddressResolutionNode.exitMethodAddressResolution; +import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.Uninterruptible; @@ -54,7 +54,7 @@ public static void resolveMethodAddress() { Pointer paddingSlot = KnownIntrinsics.readCallerStackPointer(); long gotEntry = DeoptimizationSlotPacking.decodeGOTIndex(paddingSlot.readWord(0).rawValue()); long resolvedMethodAddress = MethodAddressResolutionDispatcher.resolveMethodAddress(gotEntry); - exitMethodAddressResolution(WordFactory.pointer(resolvedMethodAddress)); + exitMethodAddressResolution(Word.pointer(resolvedMethodAddress)); throw UnreachableNode.unreachable(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/amd64/AMD64MethodAddressResolutionDispatcher.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/amd64/AMD64MethodAddressResolutionDispatcher.java index 8f06638f0394..cb6d6826cb88 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/amd64/AMD64MethodAddressResolutionDispatcher.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/amd64/AMD64MethodAddressResolutionDispatcher.java @@ -26,7 +26,7 @@ import static com.oracle.svm.core.pltgot.ExitMethodAddressResolutionNode.exitMethodAddressResolution; -import org.graalvm.word.WordFactory; +import jdk.graal.compiler.word.Word; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.Uninterruptible; @@ -57,7 +57,7 @@ public final class AMD64MethodAddressResolutionDispatcher extends MethodAddressR @NeverInline("This method must never be inlined or called directly because we only jump to it from the PLT stub.") public static long resolveMethodAddress(long gotEntry) { long resolvedMethodAddress = MethodAddressResolutionDispatcher.resolveMethodAddress(gotEntry); - exitMethodAddressResolution(WordFactory.pointer(resolvedMethodAddress)); + exitMethodAddressResolution(Word.pointer(resolvedMethodAddress)); throw UnreachableNode.unreachable(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java index 5e23fd94927d..5e2bd925b4ce 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java @@ -26,10 +26,10 @@ import java.lang.reflect.Executable; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.function.CFunctionPointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; @@ -128,7 +128,7 @@ private CFunctionPointer invokeTarget(Object obj) { */ long methodOffset = tableStartingOffset + vtableOffset; - target = BarrieredAccess.readWord(obj.getClass(), WordFactory.pointer(methodOffset), NamedLocationIdentity.FINAL_LOCATION); + target = BarrieredAccess.readWord(obj.getClass(), Word.pointer(methodOffset), NamedLocationIdentity.FINAL_LOCATION); } else { target = directTarget; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java index 649fb633a48f..969441356c1e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java @@ -27,10 +27,10 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.config.ConfigurationValues; @@ -48,7 +48,7 @@ private SamplerBufferAccess() { @Fold static UnsignedWord getHeaderSize() { - return UnsignedUtils.roundUp(SizeOf.unsigned(SamplerBuffer.class), WordFactory.unsigned(ConfigurationValues.getTarget().wordSize)); + return UnsignedUtils.roundUp(SizeOf.unsigned(SamplerBuffer.class), Word.unsigned(ConfigurationValues.getTarget().wordSize)); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java index 53e58add5ed7..0d67b8aa3235 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java @@ -25,11 +25,11 @@ package com.oracle.svm.core.sampler; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jdk.management.SubstrateThreadMXBean; @@ -163,7 +163,7 @@ private SamplerBuffer tryAllocateBuffer0() { if (result.isNonNull()) { bufferCount++; result.setSize(dataSize); - result.setNext(WordFactory.nullPointer()); + result.setNext(Word.nullPointer()); SamplerBufferAccess.reinitialize(result); } return result; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java index 06a84eb86ec7..2101c234ab75 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java @@ -25,9 +25,9 @@ package com.oracle.svm.core.sampler; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; @@ -71,7 +71,7 @@ public SamplerBuffer popBuffer() { SamplerBuffer result = head; if (result.isNonNull()) { head = head.getNext(); - result.setNext(WordFactory.nullPointer()); + result.setNext(Word.nullPointer()); } return result; } finally { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerJfrStackTraceSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerJfrStackTraceSerializer.java index bcf8762e2c2b..f6676245f6d0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerJfrStackTraceSerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerJfrStackTraceSerializer.java @@ -25,11 +25,11 @@ package com.oracle.svm.core.sampler; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.nativeimage.c.type.CIntPointer; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.code.CodeInfo; @@ -67,7 +67,7 @@ public Pointer serializeStackTrace(Pointer rawStackTrace, Pointer bufferEnd, int boolean isTruncated, long sampleTick, long threadId, long threadState) { Pointer current = rawStackTrace; CIntPointer statusPtr = StackValue.get(CIntPointer.class); - JfrStackTraceRepository.JfrStackTraceTableEntry entry = SubstrateJVM.getStackTraceRepo().getOrPutStackTrace(current, WordFactory.unsigned(sampleSize), sampleHash, statusPtr); + JfrStackTraceRepository.JfrStackTraceTableEntry entry = SubstrateJVM.getStackTraceRepo().getOrPutStackTrace(current, Word.unsigned(sampleSize), sampleHash, statusPtr); long stackTraceId = entry.isNull() ? 0 : entry.getId(); int status = statusPtr.read(); @@ -117,7 +117,7 @@ private static boolean serializeStackTrace(Pointer rawStackTrace, int sampleSize * number of stack trace elements because the count can't be patched later on * (JfrNativeEventWriter.putInt() would not necessarily reserve enough bytes). */ - int numStackTraceElements = visitRawStackTrace(rawStackTrace, sampleSize, WordFactory.nullPointer()); + int numStackTraceElements = visitRawStackTrace(rawStackTrace, sampleSize, Word.nullPointer()); if (numStackTraceElements == 0) { return false; } @@ -150,7 +150,7 @@ private static int visitRawStackTrace(Pointer rawStackTrace, int sampleSize, Jfr @Uninterruptible(reason = "Prevent JFR recording, epoch change, and that the GC frees the CodeInfo.") private static int visitFrame(JfrNativeEventWriterData data, long address) { - CodePointer ip = WordFactory.pointer(address); + CodePointer ip = Word.pointer(address); UntetheredCodeInfo untetheredInfo = CodeInfoTable.lookupCodeInfo(ip); if (untetheredInfo.isNull()) { /* Unknown frame. Must not happen for AOT-compiled code. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java index 59299fd9f5d8..322b3bceac11 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java @@ -26,9 +26,9 @@ package com.oracle.svm.core.sampler; import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.UnmanagedMemoryUtil; @@ -111,7 +111,7 @@ private static void putUncheckedLong(SamplerSampleWriterData data, long value) { /* This method is only called if ensureSize() succeeded earlier. */ assert getAvailableSize(data).aboveOrEqual(Long.BYTES); data.getCurrentPos().writeLong(0, value); - increaseCurrentPos(data, WordFactory.unsigned(Long.BYTES)); + increaseCurrentPos(data, Word.unsigned(Long.BYTES)); } @Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true) @@ -129,7 +129,7 @@ private static void putUncheckedInt(SamplerSampleWriterData data, int value) { /* This method is only called if ensureSize() succeeded earlier. */ assert getAvailableSize(data).aboveOrEqual(Integer.BYTES); data.getCurrentPos().writeInt(0, value); - increaseCurrentPos(data, WordFactory.unsigned(Integer.BYTES)); + increaseCurrentPos(data, Word.unsigned(Integer.BYTES)); } @Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true) @@ -207,7 +207,7 @@ private static void reset(SamplerSampleWriterData data) { @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true) private static void cancel(SamplerSampleWriterData data) { - data.setEndPos(WordFactory.nullPointer()); + data.setEndPos(Word.nullPointer()); JfrThreadLocal.increaseMissedSamples(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java index 68a91da9394d..9842598baa02 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java @@ -26,11 +26,11 @@ package com.oracle.svm.core.sampler; import jdk.graal.compiler.nodes.PauseNode; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jdk.UninterruptibleUtils; @@ -61,7 +61,7 @@ public boolean isOwner() { public void lock() { VMError.guarantee(!isOwner(), "The current thread already has the lock!"); IsolateThread currentThread = CurrentIsolate.getCurrentThread(); - while (!owner.compareAndSet(WordFactory.nullPointer(), currentThread)) { + while (!owner.compareAndSet(Word.nullPointer(), currentThread)) { PauseNode.pause(); } } @@ -69,6 +69,6 @@ public void lock() { @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) public void unlock() { VMError.guarantee(isOwner(), "The current thread doesn't have the lock!"); - owner.compareAndSet(CurrentIsolate.getCurrentThread(), WordFactory.nullPointer()); + owner.compareAndSet(CurrentIsolate.getCurrentThread(), Word.nullPointer()); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java index a45d9c076fb9..3e75f11189a0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.sampler; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Isolate; @@ -31,7 +32,6 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.IsolateListenerSupport.IsolateListener; import com.oracle.svm.core.Isolates; @@ -86,7 +86,7 @@ public void afterCreateIsolate(Isolate isolate) { @Uninterruptible(reason = "The isolate teardown is in progress.") public void onIsolateTeardown() { ThreadLocalKey oldKey = keyForNativeThreadLocal; - keyForNativeThreadLocal = WordFactory.nullPointer(); + keyForNativeThreadLocal = Word.nullPointer(); PlatformThreads.singleton().deleteUnmanagedThreadLocal(oldKey); } @@ -119,7 +119,7 @@ protected void stopSampling() { /* Wait until all threads exited the signal handler and cleanup no longer needed data. */ disallowThreadsInSamplerCode(); try { - setSignalHandlerIsolate(WordFactory.nullPointer()); + setSignalHandlerIsolate(Word.nullPointer()); } finally { allowThreadsInSamplerCode(); } @@ -180,7 +180,7 @@ protected void uninstall(IsolateThread thread) { * Invalidate thread-local area. Once this value is set to null, the signal handler * can't interrupt this thread anymore. */ - storeIsolateThreadInNativeThreadLocal(WordFactory.nullPointer()); + storeIsolateThreadInNativeThreadLocal(Word.nullPointer()); ExecutionSamplerInstallation.uninstalled(thread); uninstall0(thread); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ExceptionUnwind.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ExceptionUnwind.java index beb733aff743..af76191567d2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ExceptionUnwind.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ExceptionUnwind.java @@ -26,6 +26,7 @@ import static jdk.graal.compiler.core.common.spi.ForeignCallDescriptor.CallSideEffect.NO_SIDE_EFFECT; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.IsolateThread; @@ -35,7 +36,6 @@ import org.graalvm.word.LocationIdentity; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.code.CodeInfoQueryResult; @@ -208,7 +208,7 @@ private static void defaultUnwindException(Pointer startSP, boolean fromMethodWi long exceptionOffset = frame.getExceptionOffset(); if (exceptionOffset != CodeInfoQueryResult.NO_EXCEPTION_OFFSET) { - CodePointer handlerIP = (CodePointer) ((UnsignedWord) frame.getIP()).add(WordFactory.signed(exceptionOffset)); + CodePointer handlerIP = (CodePointer) ((UnsignedWord) frame.getIP()).add(Word.signed(exceptionOffset)); jumpToHandler(sp, handlerIP, hasCalleeSavedRegisters); UnreachableNode.unreachable(); return; /* Unreachable */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaFrameAnchors.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaFrameAnchors.java index e1bd34ad1e9e..f53e995ed585 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaFrameAnchors.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaFrameAnchors.java @@ -27,10 +27,10 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; import static jdk.graal.compiler.core.common.spi.ForeignCallDescriptor.CallSideEffect.NO_SIDE_EFFECT; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateDiagnostics; import com.oracle.svm.core.SubstrateOptions; @@ -75,8 +75,8 @@ public static void pushFrameAnchor(JavaFrameAnchor newAnchor) { * proper ones (see usages of KnownOffsets.getJavaFrameAnchorLastSPOffset() in the backend). * The intention is to not see stale values when debugging or in signal handlers. */ - newAnchor.setLastJavaIP(WordFactory.nullPointer()); - newAnchor.setLastJavaSP(WordFactory.nullPointer()); + newAnchor.setLastJavaIP(Word.nullPointer()); + newAnchor.setLastJavaSP(Word.nullPointer()); JavaFrameAnchor prev = lastAnchorTL.get(); newAnchor.setPreviousAnchor(prev); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaFrames.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaFrames.java index 4d39b73216ad..5de17c85306c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaFrames.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaFrames.java @@ -27,10 +27,10 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; import com.oracle.svm.core.interpreter.InterpreterSupport; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.code.CodeInfo; @@ -66,7 +66,7 @@ public static boolean isInterpreterLeaveStub(JavaFrame frame) { public static UnsignedWord getTotalFrameSize(JavaFrame frame) { long size = CodeInfoQueryResult.getTotalFrameSize(frame.getEncodedFrameSize()); assert size > 0; - return WordFactory.unsigned(size); + return Word.unsigned(size); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) @@ -77,9 +77,9 @@ public static Pointer getCallerSP(JavaFrame frame) { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) static void clearData(JavaFrame frame) { - frame.setSP(WordFactory.nullPointer()); - frame.setIP(WordFactory.nullPointer()); - frame.setIPCodeInfo(WordFactory.nullPointer()); + frame.setSP(Word.nullPointer()); + frame.setIP(Word.nullPointer()); + frame.setIPCodeInfo(Word.nullPointer()); frame.setEncodedFrameSize(CodeInfoDecoder.INVALID_SIZE_ENCODING); frame.setExceptionOffset(CodeInfoQueryResult.NO_EXCEPTION_OFFSET); @@ -93,7 +93,7 @@ public static void setData(JavaFrame frame, Pointer sp, CodePointer ip) { DeoptimizedFrame deoptimizedFrame = Deoptimizer.checkDeoptimized(frame); if (deoptimizedFrame != null) { - frame.setIPCodeInfo(WordFactory.nullPointer()); + frame.setIPCodeInfo(Word.nullPointer()); frame.setEncodedFrameSize(deoptimizedFrame.getSourceEncodedFrameSize()); frame.setExceptionOffset(CodeInfoQueryResult.NO_EXCEPTION_OFFSET); frame.setReferenceMapIndex(ReferenceMapIndex.NO_REFERENCE_MAP); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaStackWalker.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaStackWalker.java index 1c14917a8e25..807b1679bf04 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaStackWalker.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaStackWalker.java @@ -27,6 +27,7 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; import com.oracle.svm.core.deopt.DeoptimizationSlotPacking; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; @@ -37,7 +38,6 @@ import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.FrameAccess; import com.oracle.svm.core.Uninterruptible; @@ -145,17 +145,17 @@ public static JavaFrameAnchor getFrameAnchor(JavaStackWalk walk) { @Uninterruptible(reason = "Prevent deoptimization of stack frames while in this method.", callerMustBe = true) public static void initialize(JavaStackWalk walk, IsolateThread thread) { - initializeFromFrameAnchor(walk, thread, WordFactory.nullPointer()); + initializeFromFrameAnchor(walk, thread, Word.nullPointer()); } @Uninterruptible(reason = "Prevent deoptimization of stack frames while in this method.", callerMustBe = true) public static void initialize(JavaStackWalk walk, IsolateThread thread, Pointer startSP) { - initWalk(walk, thread, startSP, WordFactory.nullPointer(), WordFactory.nullPointer(), JavaFrameAnchors.getFrameAnchor(thread)); + initWalk(walk, thread, startSP, Word.nullPointer(), Word.nullPointer(), JavaFrameAnchors.getFrameAnchor(thread)); } @Uninterruptible(reason = "Prevent deoptimization of stack frames while in this method.", callerMustBe = true) public static void initialize(JavaStackWalk walk, IsolateThread thread, Pointer startSP, Pointer endSP) { - initWalk(walk, thread, startSP, endSP, WordFactory.nullPointer(), JavaFrameAnchors.getFrameAnchor(thread)); + initWalk(walk, thread, startSP, endSP, Word.nullPointer(), JavaFrameAnchors.getFrameAnchor(thread)); } /** @@ -164,7 +164,7 @@ public static void initialize(JavaStackWalk walk, IsolateThread thread, Pointer */ @Uninterruptible(reason = "Prevent deoptimization of stack frames while in this method.", callerMustBe = true) public static void initialize(JavaStackWalk walk, IsolateThread thread, Pointer startSP, CodePointer startIP) { - initWalk(walk, thread, startSP, WordFactory.nullPointer(), startIP, JavaFrameAnchors.getFrameAnchor(thread)); + initWalk(walk, thread, startSP, Word.nullPointer(), startIP, JavaFrameAnchors.getFrameAnchor(thread)); } /** @@ -173,7 +173,7 @@ public static void initialize(JavaStackWalk walk, IsolateThread thread, Pointer */ @Uninterruptible(reason = "Prevent deoptimization of stack frames while in this method.", callerMustBe = true) public static void initialize(JavaStackWalk walk, IsolateThread thread, Pointer startSP, CodePointer startIP, JavaFrameAnchor anchor) { - initWalk(walk, thread, startSP, WordFactory.nullPointer(), startIP, anchor); + initWalk(walk, thread, startSP, Word.nullPointer(), startIP, anchor); } @Uninterruptible(reason = "StoredContinuation must not move.", callerMustBe = true) @@ -187,7 +187,7 @@ public static void initializeForContinuation(JavaStackWalk walk, StoredContinuat } else { Pointer startSP = StoredContinuationAccess.getFramesStart(continuation); Pointer endSP = StoredContinuationAccess.getFramesEnd(continuation); - initWalk0(walk, startSP, endSP, startIP, WordFactory.nullPointer()); + initWalk0(walk, startSP, endSP, startIP, Word.nullPointer()); } } @@ -198,7 +198,7 @@ public static void initializeForContinuation(JavaStackWalk walk, StoredContinuat Pointer startSP = StoredContinuationAccess.getFramesStart(continuation); Pointer endSP = StoredContinuationAccess.getFramesEnd(continuation); - initWalk0(walk, startSP, endSP, startIP, WordFactory.nullPointer()); + initWalk0(walk, startSP, endSP, startIP, Word.nullPointer()); } @Uninterruptible(reason = "Prevent deoptimization of stack frames while in this method.", callerMustBe = true) @@ -230,7 +230,7 @@ private static void initWalk(JavaStackWalk walk, IsolateThread thread, Pointer s @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private static void markAsNotWalkable(JavaStackWalk walk) { - initWalk0(walk, WordFactory.nullPointer(), WordFactory.nullPointer(), WordFactory.nullPointer(), WordFactory.nullPointer()); + initWalk0(walk, Word.nullPointer(), Word.nullPointer(), Word.nullPointer(), Word.nullPointer()); } @Uninterruptible(reason = "JavaStackWalk must not contain stale values when this method returns.", callerMustBe = true) @@ -254,12 +254,12 @@ public static void updateStackPointerForContinuation(JavaStackWalk walk, StoredC long newEndSP = w.getEndSP().rawValue() + delta; w.setStartSP(newStartSP); - w.setEndSP(WordFactory.pointer(newEndSP)); + w.setEndSP(Word.pointer(newEndSP)); JavaFrame frame = getCurrentFrame(walk); if (frame.getSP().isNonNull()) { long newSP = frame.getSP().rawValue() + delta; - frame.setSP(WordFactory.pointer(newSP)); + frame.setSP(Word.pointer(newSP)); } } @@ -270,7 +270,7 @@ public static boolean advance(JavaStackWalk walk, IsolateThread thread) { @Uninterruptible(reason = "Prevent deoptimization and GC while in this method.", callerMustBe = true) public static boolean advanceForContinuation(JavaStackWalk walk, StoredContinuation continuation) { - return advance0(walk, WordFactory.nullPointer(), continuation); + return advance0(walk, Word.nullPointer(), continuation); } @Uninterruptible(reason = "Prevent deoptimization and GC while in this method.", callerMustBe = true) @@ -347,7 +347,7 @@ private static boolean continueStackWalk(JavaStackWalkImpl walk, IsolateThread t */ long deoptSlot = sp.readLong((int) -totalFrameSize); long varStackSize = DeoptimizationSlotPacking.decodeVariableFrameSizeFromDeoptSlot(deoptSlot); - Pointer actualSp = sp.add(WordFactory.unsigned(varStackSize)); + Pointer actualSp = sp.add(Word.unsigned(varStackSize)); CodePointer ip = readReturnAddress(thread, continuation, actualSp); JavaFrames.setData(frame, actualSp, ip); @@ -416,17 +416,17 @@ public static boolean walkCurrentThread(Pointer startSP, StackFrameVisitor visit @Uninterruptible(reason = "Prevent deoptimization of stack frames while in this method.") public static boolean walkCurrentThread(Pointer startSP, Pointer endSP, StackFrameVisitor visitor) { assert startSP.isNonNull(); - return walkCurrentThread(startSP, endSP, WordFactory.nullPointer(), visitor, null); + return walkCurrentThread(startSP, endSP, Word.nullPointer(), visitor, null); } @Uninterruptible(reason = "Prevent deoptimization of stack frames while in this method.") public static boolean walkCurrentThread(Pointer startSP, ParameterizedStackFrameVisitor visitor, Object data) { - return walkCurrentThread(startSP, WordFactory.nullPointer(), WordFactory.nullPointer(), visitor, data); + return walkCurrentThread(startSP, Word.nullPointer(), Word.nullPointer(), visitor, data); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public static boolean walkCurrentThread(Pointer startSP, CodePointer startIP, ParameterizedStackFrameVisitor visitor) { - return walkCurrentThread(startSP, WordFactory.nullPointer(), startIP, visitor, null); + return walkCurrentThread(startSP, Word.nullPointer(), startIP, visitor, null); } @Uninterruptible(reason = "Prevent deoptimization of stack frames while in this method.") @@ -444,7 +444,7 @@ public static boolean walkThread(IsolateThread thread, StackFrameVisitor visitor @Uninterruptible(reason = "Prevent deoptimization of stack frames while in this method.") public static boolean walkThread(IsolateThread thread, ParameterizedStackFrameVisitor visitor, Object data) { - return walkThread(thread, WordFactory.nullPointer(), visitor, data); + return walkThread(thread, Word.nullPointer(), visitor, data); } @Uninterruptible(reason = "Prevent deoptimization of stack frames while in this method.") diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/SubstrateStackIntrospection.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/SubstrateStackIntrospection.java index a079b17052f7..5ea94619e4eb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/SubstrateStackIntrospection.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/SubstrateStackIntrospection.java @@ -375,7 +375,7 @@ public String toString() { for (int i = 0; i < frameInfo.getNumLocals(); i++) { JavaConstant con = getLocalConstant(i); if (con.getJavaKind() != JavaKind.Illegal) { - result.append("\n local ").append(i); + result.append(System.lineSeparator()).append(" local ").append(i); if (con.getJavaKind() == JavaKind.Object) { if (isVirtual(i)) { result.append(" [virtual object]"); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java index b701ae117d9f..25511da2d42b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java @@ -24,11 +24,11 @@ */ package com.oracle.svm.core.stack; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.code.CodeInfo; @@ -127,7 +127,7 @@ private boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, Deopti @Override protected final boolean unknownFrame(Pointer sp, CodePointer ip, Object data) { Log log = (Log) data; - logFrameRaw(log, sp, ip, WordFactory.nullPointer()); + logFrameRaw(log, sp, ip, Word.nullPointer()); log.string(" IP is not within Java code. Aborting stack trace printing.").newline(); printedFrames++; return false; @@ -173,7 +173,7 @@ protected void logVirtualFrames(Log log, Pointer sp, CodePointer ip, CodeInfo co } boolean compilationRoot = frame.getCaller() == null; - printFrameIdentifier(log, WordFactory.nullPointer(), deoptFrame, compilationRoot); + printFrameIdentifier(log, Word.nullPointer(), deoptFrame, compilationRoot); logFrameRaw(log, sp, ip, codeInfo); logFrameInfo(log, frame.getFrameInfo(), ImageCodeInfo.CODE_INFO_NAME + ", deopt"); if (!compilationRoot) { @@ -245,7 +245,7 @@ public static boolean printStacktrace(IsolateThread thread, Pointer initialSP, C @Uninterruptible(reason = "IP is not within Java code, so there is no risk that it gets invalidated.", calleeMustBe = false) private static void logFrame(Log log, Pointer sp, CodePointer ip) { - Stage0StackFramePrintVisitor.logFrameRaw(log, sp, ip, WordFactory.nullPointer()); + Stage0StackFramePrintVisitor.logFrameRaw(log, sp, ip, Word.nullPointer()); log.string(" IP is not within Java code. Trying frame anchor of last Java frame instead.").newline(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ContinuationInternals.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ContinuationInternals.java index 93ddf2947283..e10fdda19449 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ContinuationInternals.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ContinuationInternals.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.core.thread; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.Uninterruptible; @@ -122,9 +122,9 @@ private static void enterSpecial2(Target_jdk_internal_vm_Continuation c) { Pointer returnSP = c.sp; CodePointer returnIP = c.ip; - c.ip = WordFactory.nullPointer(); - c.sp = WordFactory.nullPointer(); - c.baseSP = WordFactory.nullPointer(); + c.ip = Word.nullPointer(); + c.sp = Word.nullPointer(); + c.baseSP = Word.nullPointer(); assert c.isEmpty(); KnownIntrinsics.farReturn(null, returnSP, returnIP, false); @@ -155,9 +155,9 @@ private static Integer doYield1(Target_jdk_internal_vm_Continuation c) { return preemptStatus; } - c.ip = WordFactory.nullPointer(); - c.sp = WordFactory.nullPointer(); - c.baseSP = WordFactory.nullPointer(); + c.ip = Word.nullPointer(); + c.sp = Word.nullPointer(); + c.baseSP = Word.nullPointer(); KnownIntrinsics.farReturn(null, returnSP, returnIP, false); throw VMError.shouldNotReachHereAtRuntime(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ContinuationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ContinuationSupport.java index a7de46564a3f..4036379d99bf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ContinuationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ContinuationSupport.java @@ -24,12 +24,12 @@ */ package com.oracle.svm.core.thread; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.Uninterruptible; @@ -123,7 +123,7 @@ protected CodePointer copyFrames(StoredContinuation storedCont, Pointer topSP, @ assert totalSize % ConfigurationValues.getTarget().wordSize == 0; Pointer frameData = StoredContinuationAccess.getFramesStart(storedCont); - UnmanagedMemoryUtil.copyWordsForward(frameData, topSP, WordFactory.unsigned(totalSize)); + UnmanagedMemoryUtil.copyWordsForward(frameData, topSP, Word.unsigned(totalSize)); return StoredContinuationAccess.getIP(storedCont); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java index 7edad49d9e46..b58d8ac6d069 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java @@ -31,6 +31,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.IsolateThread; @@ -38,7 +39,6 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.impl.InternalPlatform; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.NeverInline; @@ -249,7 +249,7 @@ private static StackTraceElement[] getMountedVirtualThreadStackTrace(boolean fil if (carrier == null) { return null; } - Pointer endSP = PlatformThreads.getCarrierSPOrElse(carrier, WordFactory.nullPointer()); + Pointer endSP = PlatformThreads.getCarrierSPOrElse(carrier, Word.nullPointer()); if (endSP.isNull()) { return null; } @@ -303,7 +303,7 @@ public static void visitCurrentStackFrames(StackFrameVisitor visitor) { private static void visitCurrentVirtualThreadStackFrames(Pointer callerSP, StackFrameVisitor visitor) { Thread carrier = toVirtualTarget(Thread.currentThread()).carrierThread; if (carrier != null) { - Pointer endSP = PlatformThreads.getCarrierSPOrElse(carrier, WordFactory.nullPointer()); + Pointer endSP = PlatformThreads.getCarrierSPOrElse(carrier, Word.nullPointer()); if (endSP.isNonNull()) { StackTraceUtils.visitCurrentThreadStackFrames(callerSP, endSP, visitor); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaVMOperation.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaVMOperation.java index e4499746ed5b..f26f131a637c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaVMOperation.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaVMOperation.java @@ -24,9 +24,9 @@ */ package com.oracle.svm.core.thread; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions.ConcealedOptions; import com.oracle.svm.core.SubstrateUtil; @@ -105,7 +105,7 @@ protected void markAsQueued(NativeVMOperationData data) { @Override protected void markAsFinished(NativeVMOperationData data) { - queuingThread = WordFactory.nullPointer(); + queuingThread = Word.nullPointer(); queuingThreadId = 0; finished = true; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java index 0d0a07730d0c..1948d6dcfbbc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java @@ -24,11 +24,11 @@ */ package com.oracle.svm.core.thread; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.heap.VMOperationInfo; @@ -81,7 +81,7 @@ protected void markAsQueued(NativeVMOperationData data) { @Override protected void markAsFinished(NativeVMOperationData data) { - data.setQueuingThread(WordFactory.nullPointer()); + data.setQueuingThread(Word.nullPointer()); data.setQueuingThreadId(0); data.setFinished(true); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java index e8930ec79a07..6d41c5aa31cc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java @@ -47,6 +47,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageInfo; import org.graalvm.nativeimage.ImageSingletons; @@ -67,7 +68,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.SubstrateDiagnostics; @@ -481,7 +481,7 @@ public static void detach(IsolateThread vmThread) { Thread thread = currentThread.get(vmThread); if (thread != null) { toTarget(thread).threadData.detach(); - toTarget(thread).isolateThread = WordFactory.nullPointer(); + toTarget(thread).isolateThread = Word.nullPointer(); if (!thread.isDaemon()) { decrementNonDaemonThreads(); @@ -713,8 +713,8 @@ protected interface ThreadStartData extends PointerBase { } protected T prepareStart(Thread thread, int startDataSize) { - T startData = WordFactory.nullPointer(); - ObjectHandle threadHandle = WordFactory.zero(); + T startData = Word.nullPointer(); + ObjectHandle threadHandle = Word.zero(); try { startData = NativeMemory.malloc(startDataSize, NmtCategory.Threading); threadHandle = ObjectHandles.getGlobal().create(thread); @@ -725,7 +725,7 @@ protected T prepareStart(Thread thread, int startDat if (startData.isNonNull()) { freeStartData(startData); } - if (threadHandle.notEqual(WordFactory.zero())) { + if (threadHandle.notEqual(Word.zero())) { ObjectHandles.getGlobal().destroy(threadHandle); } throw e; @@ -806,7 +806,7 @@ protected static WordBase threadStartRoutine(ThreadStartData data) { freeStartData(data); threadStartRoutine(threadHandle); - return WordFactory.nullPointer(); + return Word.nullPointer(); } @SuppressFBWarnings(value = "Ru", justification = "We really want to call Thread.run and not Thread.start because we are in the low-level thread start routine") @@ -861,7 +861,7 @@ static StackTraceElement[] getStackTrace(boolean filterExceptions, Thread thread assert !isVirtual(thread); if (thread != null && thread == currentThread.get()) { Pointer startSP = getCarrierSPOrElse(thread, callerSP); - return StackTraceUtils.getCurrentThreadStackTrace(filterExceptions, startSP, WordFactory.nullPointer()); + return StackTraceUtils.getCurrentThreadStackTrace(filterExceptions, startSP, Word.nullPointer()); } assert !filterExceptions : "exception stack traces can be taken only for the current thread"; return StackTraceUtils.asyncGetStackTrace(thread); @@ -870,12 +870,12 @@ static StackTraceElement[] getStackTrace(boolean filterExceptions, Thread thread static void visitCurrentStackFrames(Pointer callerSP, StackFrameVisitor visitor) { assert !isVirtual(Thread.currentThread()); Pointer startSP = getCarrierSPOrElse(Thread.currentThread(), callerSP); - StackTraceUtils.visitCurrentThreadStackFrames(startSP, WordFactory.nullPointer(), visitor); + StackTraceUtils.visitCurrentThreadStackFrames(startSP, Word.nullPointer(), visitor); } static StackTraceElement[] getStackTraceAtSafepoint(Thread thread, Pointer callerSP) { assert thread != null && !isVirtual(thread) : "may only be called for platform or carrier threads"; - Pointer carrierSP = getCarrierSPOrElse(thread, WordFactory.nullPointer()); + Pointer carrierSP = getCarrierSPOrElse(thread, Word.nullPointer()); IsolateThread isolateThread = getIsolateThread(thread); if (isolateThread == CurrentIsolate.getCurrentThread()) { Pointer startSP = carrierSP.isNonNull() ? carrierSP : callerSP; @@ -883,7 +883,7 @@ static StackTraceElement[] getStackTraceAtSafepoint(Thread thread, Pointer calle * Internal frames from the VMOperation handling show up in the stack traces, but we are * OK with that. */ - return StackTraceUtils.getCurrentThreadStackTrace(false, startSP, WordFactory.nullPointer()); + return StackTraceUtils.getCurrentThreadStackTrace(false, startSP, Word.nullPointer()); } if (carrierSP.isNonNull()) { /* @@ -891,7 +891,7 @@ static StackTraceElement[] getStackTraceAtSafepoint(Thread thread, Pointer calle * frames of the virtual thread and only visit the stack frames that belong to the * carrier thread. */ - return StackTraceUtils.getStackTraceAtSafepoint(isolateThread, carrierSP, WordFactory.nullPointer()); + return StackTraceUtils.getStackTraceAtSafepoint(isolateThread, carrierSP, Word.nullPointer()); } return StackTraceUtils.getStackTraceAtSafepoint(isolateThread); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java index d62a822e78de..791ef5cd7517 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java @@ -29,6 +29,7 @@ import java.util.concurrent.atomic.AtomicInteger; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.IsolateThread; @@ -36,7 +37,6 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.word.LocationIdentity; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.NeverInline; @@ -629,7 +629,7 @@ void thaw(boolean unlock) { SafepointEndEvent.emit(getSafepointId(), startTicks); ImageSingletons.lookup(Heap.class).endSafepoint(); Statistics.setThawedNanos(); - requestingThread = WordFactory.nullPointer(); + requestingThread = Word.nullPointer(); if (unlock) { VMThreads.THREAD_MUTEX.unlock(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java index 976ae88778e3..4eaac32be541 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java @@ -33,7 +33,6 @@ import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Isolates; import com.oracle.svm.core.NeverInline; @@ -140,7 +139,7 @@ public static void shutdownAndDetachVMOperationThread() { int size = SizeOf.get(NativeVMOperationData.class); NativeVMOperationData data = StackValue.get(size); - UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); + UnmanagedMemoryUtil.fill((Pointer) data, Word.unsigned(size), (byte) 0); NativeStopVMOperationThread operation = get().stopVMOperationThreadOperation; data.setNativeVMOperation(operation); /* @@ -266,7 +265,7 @@ void setInProgress(VMOperation operation, IsolateThread queueingThread, IsolateT } void enqueue(JavaVMOperation operation) { - enqueue(operation, WordFactory.nullPointer()); + enqueue(operation, Word.nullPointer()); } void enqueue(NativeVMOperationData data) { @@ -619,9 +618,9 @@ private void drain(JavaVMOperationQueue workQueue) { while (!workQueue.isEmpty()) { JavaVMOperation operation = workQueue.pop(); try { - operation.execute(WordFactory.nullPointer()); + operation.execute(Word.nullPointer()); } finally { - markAsFinished(operation, WordFactory.nullPointer(), operationFinished); + markAsFinished(operation, Word.nullPointer(), operationFinished); } } trace.string("]").newline(); @@ -634,10 +633,10 @@ private void filterUnnecessary(JavaVMOperationQueue workQueue) { JavaVMOperation op = workQueue.peek(); while (op != null) { JavaVMOperation next = op.getNext(); - if (!op.hasWork(WordFactory.nullPointer())) { + if (!op.hasWork(Word.nullPointer())) { trace.string("[Skipping unnecessary operation in queue ").string(workQueue.name).string(": ").string(op.getName()); workQueue.remove(prev, op); - markAsFinished(op, WordFactory.nullPointer(), operationFinished); + markAsFinished(op, Word.nullPointer(), operationFinished); } else { prev = op; } @@ -647,7 +646,7 @@ private void filterUnnecessary(JavaVMOperationQueue workQueue) { private void filterUnnecessary(NativeVMOperationQueue workQueue) { Log trace = log(); - NativeVMOperationData prev = WordFactory.nullPointer(); + NativeVMOperationData prev = Word.nullPointer(); NativeVMOperationData data = workQueue.peek(); while (data.isNonNull()) { NativeVMOperation op = data.getNativeVMOperation(); @@ -821,11 +820,11 @@ public void push(NativeVMOperationData element) { @Override public NativeVMOperationData pop() { if (head.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } NativeVMOperationData resultElement = head; head = resultElement.getNext(); - resultElement.setNext(WordFactory.nullPointer()); + resultElement.setNext(Word.nullPointer()); return resultElement; } @@ -839,7 +838,7 @@ void remove(NativeVMOperationData prev, NativeVMOperationData remove) { if (prev.isNull()) { assert head == remove; head = remove.getNext(); - remove.setNext(WordFactory.nullPointer()); + remove.setNext(Word.nullPointer()); } else { prev.setNext(remove.getNext()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java index 63c09cf6e19d..8e291e8b06b5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java @@ -26,6 +26,7 @@ import static com.oracle.svm.core.graal.nodes.WriteCurrentVMThreadNode.writeCurrentVMThread; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Isolate; @@ -36,7 +37,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.Uninterruptible; @@ -251,12 +251,12 @@ public IsolateThread allocateIsolateThread(int isolateThreadSize) { * size from the OS, we just use a hard-coded best guess. Using an inaccurate value does not * lead to correctness problems. */ - UnsignedWord alignment = WordFactory.unsigned(64); + UnsignedWord alignment = Word.unsigned(64); - UnsignedWord memorySize = WordFactory.unsigned(isolateThreadSize).add(alignment); + UnsignedWord memorySize = Word.unsigned(isolateThreadSize).add(alignment); Pointer memory = UntrackedNullableNativeMemory.calloc(memorySize); if (memory.isNull()) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } IsolateThread isolateThread = (IsolateThread) UnsignedUtils.roundUp(memory, alignment); @@ -267,7 +267,7 @@ public IsolateThread allocateIsolateThread(int isolateThreadSize) { @Uninterruptible(reason = "Thread state no longer set up.") public void freeCurrentIsolateThread() { freeIsolateThread(CurrentIsolate.getCurrentThread()); - writeCurrentVMThread(WordFactory.nullPointer()); + writeCurrentVMThread(Word.nullPointer()); } /** Free the native memory allocated by {@link #allocateIsolateThread}. */ @@ -371,7 +371,7 @@ protected int attachThread(IsolateThread thread) { public void detachCurrentThread() { threadExit(); detachThread(CurrentIsolate.getCurrentThread(), true); - writeCurrentVMThread(WordFactory.nullPointer()); + writeCurrentVMThread(Word.nullPointer()); } /** @@ -386,7 +386,7 @@ protected void detachThread(IsolateThread thread, boolean currentThread) { assert currentThread == (thread == CurrentIsolate.getCurrentThread()); assert currentThread || VMOperation.isInProgressAtSafepoint(); - OSThreadHandle threadToCleanup = WordFactory.nullPointer(); + OSThreadHandle threadToCleanup = Word.nullPointer(); if (currentThread) { lockThreadMutexInNativeCode(false); } @@ -454,7 +454,7 @@ private static void lockThreadMutexInNativeCode0(boolean unspecifiedOwner) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) protected void cleanupExitedOsThreads() { - OSThreadHandle threadToCleanup = detachedOsThreadToCleanup.getAndSet(WordFactory.nullPointer()); + OSThreadHandle threadToCleanup = detachedOsThreadToCleanup.getAndSet(Word.nullPointer()); cleanupExitedOsThread(threadToCleanup); } @@ -472,7 +472,7 @@ private void cleanupExitedOsThread(OSThreadHandle threadToCleanup) { @Uninterruptible(reason = "Thread is detaching and holds the THREAD_MUTEX.") private static void removeFromThreadList(IsolateThread thread) { - IsolateThread previous = WordFactory.nullPointer(); + IsolateThread previous = Word.nullPointer(); IsolateThread current = head; while (current.isNonNull()) { IsolateThread next = nextTL.get(current); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalInfos.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalInfos.java index 1566f30757ad..49811c460312 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalInfos.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalInfos.java @@ -35,7 +35,6 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.BuildPhaseProvider.ReadyForCompilation; import com.oracle.svm.core.SubstrateDiagnostics; @@ -69,13 +68,13 @@ public static void dumpToLog(Log log, IsolateThread thread, boolean isJavaHeapAc for (VMThreadLocalInfo info : ImageSingletons.lookup(VMThreadLocalInfos.class).infos) { log.signed(info.offset).string(": ").string(info.name).string(" = "); if (info.threadLocalClass == FastThreadLocalInt.class) { - int value = threadLocals.readInt(WordFactory.signed(info.offset)); + int value = threadLocals.readInt(Word.signed(info.offset)); log.string("(int) ").zhex(value).string(" (").signed(value).string(")"); } else if (info.threadLocalClass == FastThreadLocalLong.class) { - long value = threadLocals.readLong(WordFactory.signed(info.offset)); + long value = threadLocals.readLong(Word.signed(info.offset)); log.string("(long) ").zhex(value).string(" (").signed(value).string(")"); } else if (info.threadLocalClass == FastThreadLocalWord.class) { - WordBase value = threadLocals.readWord(WordFactory.signed(info.offset)); + WordBase value = threadLocals.readWord(Word.signed(info.offset)); log.string("(Word) ").zhex(value).string(" (").signed(value).string(")"); } else if (info.threadLocalClass == FastThreadLocalObject.class) { if (isJavaHeapAccessAllowed) { @@ -92,7 +91,7 @@ public static void dumpToLog(Log log, IsolateThread thread, boolean isJavaHeapAc } } else if (info.threadLocalClass == FastThreadLocalBytes.class) { log.string("(bytes) "); - Pointer data = threadLocals.add(WordFactory.signed(info.offset)); + Pointer data = threadLocals.add(Word.signed(info.offset)); if (info.sizeInBytes == 8) { log.zhex(data.readWord(0)); } else { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/HostedSubstrateUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/HostedSubstrateUtil.java new file mode 100644 index 000000000000..750e8222a333 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/HostedSubstrateUtil.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.util; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +@Platforms(Platform.HOSTED_ONLY.class) +public interface HostedSubstrateUtil { + + static ClassLoader getRuntimeClassLoader(ClassLoader loader) { + return ImageSingletons.lookup(HostedSubstrateUtil.class).doGetRuntimeClassLoader(loader); + } + + ClassLoader doGetRuntimeClassLoader(ClassLoader loader); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/MetricsLogUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/MetricsLogUtils.java index 0a0f1513b73a..aa948966c869 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/MetricsLogUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/MetricsLogUtils.java @@ -24,8 +24,8 @@ */ package com.oracle.svm.core.util; +import jdk.graal.compiler.word.Word; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.log.Log; @@ -61,7 +61,7 @@ public static void logSection(String section, int ident) { } public static void logMemoryMetric(String category, long bytes) { - logMemoryMetric(category, WordFactory.unsigned(bytes)); + logMemoryMetric(category, Word.unsigned(bytes)); } public static void logMemoryMetric(String category, UnsignedWord bytes) { @@ -202,7 +202,7 @@ public String symbol() { abstract UnsignedWord fromBytes(UnsignedWord bytes); private static UnsignedWord toKilo(final UnsignedWord bytes) { - final UnsignedWord bytedPerKilo = WordFactory.unsigned(1_024L); + final UnsignedWord bytedPerKilo = Word.unsigned(1_024L); final UnsignedWord result = bytes.unsignedDivide(bytedPerKilo); return result; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/OWNERS.toml b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/OWNERS.toml new file mode 100644 index 000000000000..1f7d0b0a8706 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/OWNERS.toml @@ -0,0 +1,7 @@ +[[rule]] +files = "*" +all = [ + "christian.haeubl@oracle.com", +] +any = [ +] diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/TimeUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/TimeUtils.java index bbd1100154ba..6d940b9d0847 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/TimeUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/TimeUtils.java @@ -26,8 +26,8 @@ import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import jdk.graal.compiler.word.Word; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jdk.UninterruptibleUtils; @@ -150,7 +150,7 @@ public static long roundedUpDivide(long numerator, long denominator) { /** Weight a nanosecond value by a percentage between 0 and 100. */ public static long weightedNanos(int percent, long nanos) { - final UnsignedWord unweightedNanos = WordFactory.unsigned(nanos); + final UnsignedWord unweightedNanos = Word.unsigned(nanos); return unweightedNanos.unsignedDivide(100).multiply(percent).rawValue(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/UnsignedUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/UnsignedUtils.java index e696b1582367..8964da7fe5d8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/UnsignedUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/UnsignedUtils.java @@ -24,8 +24,8 @@ */ package com.oracle.svm.core.util; +import jdk.graal.compiler.word.Word; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; @@ -35,7 +35,7 @@ public final class UnsignedUtils { /** The UnsignedWord of the greatest magnitude. */ - public static final UnsignedWord MAX_VALUE = WordFactory.unsigned(0xffffffffffffffffL); + public static final UnsignedWord MAX_VALUE = Word.unsigned(0xffffffffffffffffL); private UnsignedUtils() { // This is a class of static methods, so no need for any instances. @@ -133,10 +133,10 @@ public static double toDouble(UnsignedWord u) { public static UnsignedWord fromDouble(double d) { // follows JLS 5.1.3 long l = (long) d; if (Double.isNaN(d) || l <= 0) { // includes -inf - return WordFactory.zero(); + return Word.zero(); } if (l < Long.MAX_VALUE) { - return WordFactory.unsigned(l); + return Word.unsigned(l); } /* * This division does not lose precision with these large numbers because the double's @@ -148,6 +148,6 @@ public static UnsignedWord fromDouble(double d) { // follows JLS 5.1.3 if (l == Long.MAX_VALUE) { // too large or +inf return UnsignedUtils.MAX_VALUE; } - return WordFactory.unsigned(l).shiftLeft(1); + return Word.unsigned(l).shiftLeft(1); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/NativeCoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/NativeCoder.java index bc616f0a8786..712232a38f67 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/NativeCoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/NativeCoder.java @@ -24,9 +24,9 @@ */ package com.oracle.svm.core.util.coder; +import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; @@ -49,7 +49,7 @@ public static long readU4(Pointer ptr) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static UnsignedWord readU8(Pointer ptr) { - return WordFactory.unsigned(ptr.readLong(0)); + return Word.unsigned(ptr.readLong(0)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.driver.launcher/src/com/oracle/svm/driver/launcher/BundleLauncher.java b/substratevm/src/com.oracle.svm.driver.launcher/src/com/oracle/svm/driver/launcher/BundleLauncher.java index 1a77c03ed26e..4ab216f15fd3 100644 --- a/substratevm/src/com.oracle.svm.driver.launcher/src/com/oracle/svm/driver/launcher/BundleLauncher.java +++ b/substratevm/src/com.oracle.svm.driver.launcher/src/com/oracle/svm/driver/launcher/BundleLauncher.java @@ -126,8 +126,8 @@ public static void main(String[] args) { .sorted() .toList(); showMessage("Executing ["); - showMessage(String.join(" \\\n", environmentList)); - showMessage(String.join(" \\\n", pb.command())); + showMessage(String.join(" \\" + System.lineSeparator(), environmentList)); + showMessage(String.join(" \\" + System.lineSeparator(), pb.command())); showMessage("]"); } diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/CmdLineOptionHandler.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/CmdLineOptionHandler.java index de407337f12f..0110eb07df67 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/CmdLineOptionHandler.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/CmdLineOptionHandler.java @@ -31,10 +31,14 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.VM; import com.oracle.svm.core.option.OptionOrigin; import com.oracle.svm.core.util.ExitStatus; import com.oracle.svm.driver.NativeImage.ArgumentQueue; +import com.oracle.svm.hosted.imagelayer.LayerArchiveSupport; +import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.ExtendedOption; +import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.LayerOption; import com.oracle.svm.util.LogUtils; import jdk.graal.compiler.options.OptionType; @@ -138,6 +142,25 @@ private boolean consume(ArgumentQueue args, String headArg) { return true; } + if (headArg.startsWith(SubstrateOptions.LAYER_CREATE_OPTION)) { + String layerCreateValue = headArg.substring(SubstrateOptions.LAYER_CREATE_OPTION.length()); + if (!layerCreateValue.isEmpty()) { + LayerOption layerOption = LayerOption.parse(layerCreateValue); + for (ExtendedOption option : layerOption.extendedOptions()) { + switch (option.key()) { + case LayerArchiveSupport.PACKAGE_OPTION -> { + String packageName = option.value(); + String moduleName = nativeImage.systemPackagesToModules.get(packageName); + if (moduleName != null) { + nativeImage.addAddedModules(moduleName); + } + } + } + } + } + return false; + } + if (headArg.startsWith(DEBUG_ATTACH_OPTION)) { if (useDebugAttach) { throw NativeImage.showError("The " + DEBUG_ATTACH_OPTION + " option can only be used once."); diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOption.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOption.java index d0ef525d7294..6e3355bbd1f1 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOption.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOption.java @@ -43,9 +43,9 @@ import java.util.stream.Stream; import com.oracle.svm.core.option.OptionUtils; +import com.oracle.svm.core.util.ArchiveSupport; import com.oracle.svm.driver.NativeImage.BuildConfiguration; import com.oracle.svm.driver.metainf.NativeImageMetaInfWalker; -import com.oracle.svm.core.util.ArchiveSupport; final class MacroOption { @@ -89,7 +89,7 @@ public String getMessage(Registry registry) { } else { sb.append(message); } - Consumer lineOut = s -> sb.append("\n" + s); + Consumer lineOut = s -> sb.append(System.lineSeparator()).append(s); registry.showOptions(forKind, context == null, lineOut); return sb.toString(); } diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java index 5760be4fad64..78b4eabe4a24 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java @@ -145,6 +145,19 @@ private static String flagsFileName(String versionTag) { static final Map graalCompilerFlags = getCompilerFlags(); + private static Map getSystemPackages() { + Map res = new HashMap<>(); + for (ModuleReference moduleRef : ModuleFinder.ofSystem().findAll()) { + ModuleDescriptor moduleDescriptor = moduleRef.descriptor(); + for (String packageName : moduleDescriptor.packages()) { + res.put(packageName, moduleDescriptor.name()); + } + } + return Map.copyOf(res); + } + + final Map systemPackagesToModules = getSystemPackages(); + static String getResource(String resourceName) { try (InputStream input = NativeImage.class.getResourceAsStream(resourceName)) { BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); @@ -602,6 +615,27 @@ public List getBuilderJavaArgs() { builderJavaArgs.add("-XX:+UseJVMCINativeLibrary"); } else if (getHostFlags().hasUseJVMCICompiler()) { builderJavaArgs.add("-XX:-UseJVMCICompiler"); + + if (JavaVersionUtil.JAVA_SPEC == 21) { + Runtime.Version version = Runtime.version(); + if (version.interim() == 0 && version.update() < 4) { + /* + * Native Image regularly crashes due to JDK-8328702. In JDK 21, the fix + * only landed in 20.0.4 so in earlier JDK 21 versions, disable C2. This + * workaround can be removed when GR-55515, GR-60648 or GR-60669 is + * resolved. + */ + builderJavaArgs.add("-XX:TieredStopAtLevel=1"); + + /* + * From the java man page section on ReservedCodeCacheSize: "the default + * maximum code cache size is 240 MB; if you disable tiered compilation ... + * then the default size is 48 MB". Experience shows that Native Image needs + * the larger code cache, even when C2 is disabled. + */ + builderJavaArgs.add("-XX:ReservedCodeCacheSize=240M"); + } + } } return builderJavaArgs; @@ -963,7 +997,7 @@ private void prepareImageBuildArgs() { /* * The presence of CDS and custom system class loaders disables the use of archived - * non-system class and and triggers a warning. + * non-system class and triggers a warning. */ addImageBuilderJavaArgs("-Xshare:off"); @@ -1055,6 +1089,7 @@ static boolean processJarManifestMainAttributes(Path jarFilePath, BiConsumer 0) { - profileLoadPath = CTypeConversion.toJavaString(WordFactory.pointer(profilePathBufferAddress)); + profileLoadPath = CTypeConversion.toJavaString(Word.pointer(profilePathBufferAddress)); } else { profileLoadPath = null; } @@ -485,8 +485,8 @@ private static JNIMethodScope openScope(Enum id, JNIEnv env) throws Throwable offset = (int) singleton().getLastJavaPCOffset.invoke(); lastJavaPCOffset = offset; } - CLongPointer currentThreadLastJavaPCOffset = (CLongPointer) WordFactory.unsigned((long) singleton().getCurrentJavaThread.invoke()).add(offset); - PointerBase javaFrameAnchor = WordFactory.pointer(currentThreadLastJavaPCOffset.read()); + CLongPointer currentThreadLastJavaPCOffset = (CLongPointer) Word.unsigned((long) singleton().getCurrentJavaThread.invoke()).add(offset); + PointerBase javaFrameAnchor = Word.pointer(currentThreadLastJavaPCOffset.read()); return LibGraalJNIMethodScope.open(scopeName, env, javaFrameAnchor.isNonNull()); } @@ -573,7 +573,7 @@ public static JString getCompilerConfigurationFactoryName(JNIEnv env, JClass hsC return scope.getObjectResult(); } catch (Throwable t) { JNIExceptionWrapper.throwInHotSpot(env, t); - return WordFactory.nullPointer(); + return Word.nullPointer(); } } @@ -664,13 +664,13 @@ public static JString getSuppliedString(JNIEnv env, JClass hsClazz, @IsolateThre String stackTrace = (String) singleton().getSuppliedString.invoke(stringSupplier); scope.setObjectResult(JNIUtil.createHSString(env, stackTrace)); } else { - scope.setObjectResult(WordFactory.nullPointer()); + scope.setObjectResult(Word.nullPointer()); } } return scope.getObjectResult(); } catch (Throwable t) { JNIExceptionWrapper.throwInHotSpot(env, t); - return WordFactory.nullPointer(); + return Word.nullPointer(); } } @@ -697,7 +697,7 @@ public static JObjectArray getNodeTypes(JNIEnv env, JClass hsClazz, @IsolateThre Object graphInfo = LibGraalObjectHandles.resolve(handle, Object.class); String[] nodeTypes = (String[]) singleton().getNodeTypes.invoke(graphInfo, simpleNames); JClass componentType = getStringClass(env); - JObjectArray res = JNIUtil.NewObjectArray(env, nodeTypes.length, componentType, WordFactory.nullPointer()); + JObjectArray res = JNIUtil.NewObjectArray(env, nodeTypes.length, componentType, Word.nullPointer()); for (int i = 0; i < nodeTypes.length; i++) { JNIUtil.SetObjectArrayElement(env, res, i, JNIUtil.createHSString(env, nodeTypes[i])); } @@ -706,7 +706,7 @@ public static JObjectArray getNodeTypes(JNIEnv env, JClass hsClazz, @IsolateThre return scope.getObjectResult(); } catch (Throwable t) { JNIExceptionWrapper.throwInHotSpot(env, t); - return WordFactory.nullPointer(); + return Word.nullPointer(); } } @@ -776,7 +776,7 @@ public static JObjectArray getInfopoints(JNIEnv env, JClass hsClazz, @IsolateThr Object compilationResultInfo = LibGraalObjectHandles.resolve(handle, Object.class); String[] infoPoints = (String[]) singleton().getInfopoints.invoke(compilationResultInfo); JClass componentType = getStringClass(env); - JObjectArray res = JNIUtil.NewObjectArray(env, infoPoints.length, componentType, WordFactory.nullPointer()); + JObjectArray res = JNIUtil.NewObjectArray(env, infoPoints.length, componentType, Word.nullPointer()); for (int i = 0; i < infoPoints.length; i++) { JNIUtil.SetObjectArrayElement(env, res, i, JNIUtil.createHSString(env, infoPoints[i])); } @@ -785,7 +785,7 @@ public static JObjectArray getInfopoints(JNIEnv env, JClass hsClazz, @IsolateThr return scope.getObjectResult(); } catch (Throwable t) { JNIExceptionWrapper.throwInHotSpot(env, t); - return WordFactory.nullPointer(); + return Word.nullPointer(); } } @@ -839,7 +839,7 @@ public static JByteArray listCompilerOptions(JNIEnv env, JClass hsClazz, @Isolat return scope.getObjectResult(); } catch (Throwable t) { JNIExceptionWrapper.throwInHotSpot(env, t); - return WordFactory.nullPointer(); + return Word.nullPointer(); } } @@ -868,7 +868,7 @@ public static JString validateCompilerOption(JNIEnv env, JClass hsClazz, @Isolat return scope.getObjectResult(); } catch (Throwable t) { JNIExceptionWrapper.throwInHotSpot(env, t); - return WordFactory.nullPointer(); + return Word.nullPointer(); } } @@ -899,7 +899,7 @@ public static JString getCompilerVersion(JNIEnv env, JClass hsClass, @IsolateThr return scope.getObjectResult(); } catch (Throwable t) { JNIExceptionWrapper.throwInHotSpot(env, t); - return WordFactory.nullPointer(); + return Word.nullPointer(); } } @@ -907,7 +907,7 @@ public static JString getCompilerVersion(JNIEnv env, JClass hsClass, @IsolateThr @SuppressWarnings("unused") public static boolean releaseHandle(JNIEnv jniEnv, JClass jclass, @IsolateThreadContext long isolateThreadId, long handle) { try { - ObjectHandles.getGlobal().destroy(WordFactory.pointer(handle)); + ObjectHandles.getGlobal().destroy(Word.pointer(handle)); return true; } catch (Throwable t) { return false; diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalObjectHandles.java b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalObjectHandles.java index 2064db9dd8b3..9e9819d0beb0 100644 --- a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalObjectHandles.java +++ b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalObjectHandles.java @@ -24,8 +24,8 @@ */ package com.oracle.svm.graal.hotspot.libgraal; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ObjectHandles; -import org.graalvm.word.WordFactory; /** * Manages handles to libgraal objects whose lifetime is controlled by @@ -49,7 +49,7 @@ static long create(Object object) { * @throws IllegalArgumentException if {@code} is invalid */ static T resolve(long handle, Class type) { - return type.cast(ObjectHandles.getGlobal().get(WordFactory.pointer(handle))); + return type.cast(ObjectHandles.getGlobal().get(Word.pointer(handle))); } /** @@ -57,6 +57,6 @@ static T resolve(long handle, Class type) { * method, the handle must not be used anymore. */ static void remove(long handle) { - ObjectHandles.getGlobal().destroy(WordFactory.pointer(handle)); + ObjectHandles.getGlobal().destroy(Word.pointer(handle)); } } 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 6f7e380da013..3a70db24f24b 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 @@ -30,6 +30,7 @@ import java.util.Map; import java.util.function.Supplier; +import jdk.graal.compiler.word.Word; import org.graalvm.jniutils.JNI; import org.graalvm.jniutils.JNIExceptionWrapper; import org.graalvm.jniutils.JNIMethodScope; @@ -44,7 +45,6 @@ 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; @@ -212,7 +212,7 @@ public Object transform(Object receiver, Object originalValue) { } catch (Throwable e) { throw VMError.shouldNotReachHere(e); } - return CGlobalDataFactory.createWord(WordFactory.unsigned(initialValue), null, true); + return CGlobalDataFactory.createWord(Word.unsigned(initialValue), null, true); } } diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/NativeImageHostEntryPoints.java b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/NativeImageHostEntryPoints.java index c48c198665b8..40d44b7f3f68 100644 --- a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/NativeImageHostEntryPoints.java +++ b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/NativeImageHostEntryPoints.java @@ -25,11 +25,11 @@ package com.oracle.svm.graal.hotspot.libgraal; import com.oracle.truffle.compiler.TruffleCompilerOptionDescriptor; +import jdk.graal.compiler.word.Word; import org.graalvm.jniutils.HSObject; import org.graalvm.jniutils.JNI.JObject; import org.graalvm.jniutils.JNIMethodScope; import org.graalvm.jniutils.JNIUtil; -import org.graalvm.word.WordFactory; /** * Entry points for native-image specific methods called by guest Graal using method handles. @@ -40,7 +40,7 @@ private NativeImageHostEntryPoints() { } public static void initializeHost(long runtimeClass) { - TruffleFromLibGraalStartPoints.initializeJNI(WordFactory.pointer(runtimeClass)); + TruffleFromLibGraalStartPoints.initializeJNI(Word.pointer(runtimeClass)); } public static Object createLocalHandleForLocalReference(long jniLocalRef) { @@ -48,12 +48,12 @@ public static Object createLocalHandleForLocalReference(long jniLocalRef) { if (scope == null) { return null; } - return new HSObject(scope, WordFactory.pointer(jniLocalRef)); + return new HSObject(scope, Word.pointer(jniLocalRef)); } public static Object createLocalHandleForWeakGlobalReference(long jniWeakGlobalRef) { JNIMethodScope scope = JNIMethodScope.scope(); - JObject localRef = JNIUtil.NewLocalRef(scope.getEnv(), WordFactory.pointer(jniWeakGlobalRef)); + JObject localRef = JNIUtil.NewLocalRef(scope.getEnv(), Word.pointer(jniWeakGlobalRef)); return localRef.isNull() ? null : new HSObject(scope, localRef); } diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/TruffleFromLibGraalStartPoints.java b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/TruffleFromLibGraalStartPoints.java index 082800d4ac3d..f42aecce5bca 100644 --- a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/TruffleFromLibGraalStartPoints.java +++ b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/TruffleFromLibGraalStartPoints.java @@ -80,6 +80,7 @@ import java.util.LinkedHashMap; import java.util.Map; +import jdk.graal.compiler.word.Word; import org.graalvm.jniutils.HSObject; import org.graalvm.jniutils.JNI.JByteArray; import org.graalvm.jniutils.JNI.JClass; @@ -95,7 +96,6 @@ import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; -import org.graalvm.word.WordFactory; import com.oracle.truffle.compiler.hotspot.libgraal.FromLibGraalId; import com.oracle.truffle.compiler.hotspot.libgraal.TruffleFromLibGraal; @@ -340,7 +340,7 @@ public static boolean cancelCompilation(Object hsHandle, CharSequence reason) { public static boolean isSameOrSplit(Object hsHandle, Object otherHsHandle) { JNIEnv env = JNIMethodScope.env(); return callIsSameOrSplit(calls, env, ((HSObject) hsHandle).getHandle(), - otherHsHandle == null ? WordFactory.nullPointer() : ((HSObject) otherHsHandle).getHandle()); + otherHsHandle == null ? Word.nullPointer() : ((HSObject) otherHsHandle).getHandle()); } @TruffleFromLibGraal(Id.GetKnownCallSiteCount) @@ -353,7 +353,7 @@ public static int getKnownCallSiteCount(Object hsHandle) { public static void consumeOptimizedAssumptionDependency(Object hsHandle, Object compilableHsHandle, long installedCode) { JNIEnv env = JNIMethodScope.env(); callConsumeOptimizedAssumptionDependency(calls, env, ((HSObject) hsHandle).getHandle(), - compilableHsHandle == null ? WordFactory.nullPointer() : ((HSObject) compilableHsHandle).getHandle(), + compilableHsHandle == null ? Word.nullPointer() : ((HSObject) compilableHsHandle).getHandle(), installedCode); } 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 efbd6e1de548..fdf914682b3c 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 @@ -456,8 +456,8 @@ public void updateSubstrateDataAfterCompilation(HostedUniverse hUniverse, Provid } HostedType hType = hUniverse.lookup(aType); - if (hType.getUniqueConcreteImplementation() != null) { - sType.setTypeCheckData(hType.getUniqueConcreteImplementation().getHub()); + if (hType.getSingleImplementor() != null) { + sType.setSingleImplementor(hType.getSingleImplementor().getHub()); } if (sType.getInstanceFieldCount() > 1) { diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/ConstantData.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/ConstantData.java index a52161af937b..271f5de94bb5 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/ConstantData.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/ConstantData.java @@ -24,11 +24,11 @@ */ package com.oracle.svm.graal.isolated; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.struct.RawField; import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.word.PointerBase; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.meta.DirectSubstrateObjectConstant; import com.oracle.svm.core.meta.SubstrateObjectConstant; @@ -114,11 +114,11 @@ private static void fromCompilerOrClient(JavaConstant constant, ConstantData dat rawBits = IsolatedHandles.nullHandle(); data.setCompressed(SubstrateObjectConstant.isCompressed(constant)); } else if (kind.isNumericInteger()) { - rawBits = WordFactory.signed(constant.asLong()); + rawBits = Word.signed(constant.asLong()); } else if (kind == JavaKind.Float) { - rawBits = WordFactory.unsigned(Float.floatToRawIntBits(constant.asFloat())); + rawBits = Word.unsigned(Float.floatToRawIntBits(constant.asFloat())); } else if (kind == JavaKind.Double) { - rawBits = WordFactory.unsigned(Double.doubleToRawLongBits(constant.asDouble())); + rawBits = Word.unsigned(Double.doubleToRawLongBits(constant.asDouble())); } else { throw VMError.shouldNotReachHere("unsupported constant kind: " + kind); } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/ImageHeapObjects.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/ImageHeapObjects.java index 3bf3cdac064a..af78dae7d21c 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/ImageHeapObjects.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/ImageHeapObjects.java @@ -26,7 +26,6 @@ import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; @@ -45,12 +44,12 @@ static boolean isInImageHeap(Object obj) { /** * Provides the image heap location of the specified image heap object that is independent of a - * specific isolate. Java {@code null} becomes {@link WordFactory#nullPointer() NULL}. + * specific isolate. Java {@code null} becomes {@link Word#nullPointer() NULL}. */ @SuppressWarnings("unchecked") public static ImageHeapRef ref(T t) { if (t == null) { - return WordFactory.nullPointer(); + return Word.nullPointer(); } VMError.guarantee(isInImageHeap(t)); Word result = Word.objectToUntrackedPointer(t); @@ -62,10 +61,10 @@ public static ImageHeapRef ref(T t) { /** * Provides the object instance in the current isolate at the given image heap location. - * {@link WordFactory#nullPointer() NULL} becomes Java {@code null}. + * {@link Word#nullPointer() NULL} becomes Java {@code null}. */ public static T deref(ImageHeapRef ref) { - if (ref.equal(WordFactory.nullPointer())) { + if (ref.equal(Word.nullPointer())) { return null; } Pointer objectAddress = (Pointer) ref; diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolateAwareConstantReflectionProvider.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolateAwareConstantReflectionProvider.java index 691a3347890c..2cb710a0bcb4 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolateAwareConstantReflectionProvider.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolateAwareConstantReflectionProvider.java @@ -27,9 +27,9 @@ import java.lang.reflect.Array; import jdk.graal.compiler.core.common.CompressEncoding; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.function.CEntryPoint; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.graal.meta.SubstrateMemoryAccessProvider; @@ -236,7 +236,7 @@ public ResolvedJavaType asJavaType(Constant hub) { private static ImageHeapRef getHubConstantAsImageHeapRef(@SuppressWarnings("unused") ClientIsolateThread client, ConstantData hubData) { JavaConstant hub = ConstantDataConverter.toClient(hubData); Object target = SubstrateObjectConstant.asObject(hub); - return (target instanceof DynamicHub) ? ImageHeapObjects.ref((DynamicHub) target) : WordFactory.nullPointer(); + return (target instanceof DynamicHub) ? ImageHeapObjects.ref((DynamicHub) target) : Word.nullPointer(); } @Override diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolatedGraalUtils.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolatedGraalUtils.java index 676639ceb64e..5fbe5e5cb5b5 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolatedGraalUtils.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolatedGraalUtils.java @@ -27,6 +27,7 @@ import java.lang.reflect.Array; import java.nio.ByteBuffer; +import jdk.graal.compiler.word.Word; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.Isolates; @@ -36,7 +37,6 @@ import org.graalvm.nativeimage.c.function.CEntryPoint; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.c.function.IsolateSupportImpl; @@ -94,7 +94,7 @@ public static CompilerIsolateThread createCompilationIsolate() { CreateIsolateParameters.Builder builder = new CreateIsolateParameters.Builder(); long addressSpaceSize = SubstrateOptions.CompilationIsolateAddressSpaceSize.getValue(); if (addressSpaceSize > 0) { - builder.reservedAddressSpaceSize(WordFactory.signed(addressSpaceSize)); + builder.reservedAddressSpaceSize(Word.signed(addressSpaceSize)); } /* * if protection keys are used, the compilation isolate needs to use the same protection diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolatedReferenceAdjuster.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolatedReferenceAdjuster.java index 664db15b0826..793471fa7bba 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolatedReferenceAdjuster.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolatedReferenceAdjuster.java @@ -24,11 +24,11 @@ */ package com.oracle.svm.graal.isolated; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ObjectHandle; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.NonmovableArray; @@ -120,8 +120,8 @@ public ForeignIsolateReferenceAdjusterData exportData() { data.setAddresses(addresses); data.setHandles(handles); - addresses = WordFactory.nullPointer(); - handles = WordFactory.nullPointer(); + addresses = Word.nullPointer(); + handles = Word.nullPointer(); count = 0; return data; diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolatedRuntimeCodeInstaller.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolatedRuntimeCodeInstaller.java index b8c223490296..62a62c31a6c1 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolatedRuntimeCodeInstaller.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolatedRuntimeCodeInstaller.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.graal.isolated; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.c.function.CEntryPoint; import org.graalvm.nativeimage.c.function.CodePointer; @@ -31,7 +32,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.code.CodeInfo; import com.oracle.svm.core.code.RuntimeCodeInfoAccess; @@ -129,7 +129,7 @@ private static void installPrepared(SharedMethod method, CodeInstallInfo install IsolatedRuntimeMethodInfoAccess.startTrackingInCurrentIsolate(installInfo); IsolatedReferenceAdjuster.adjustAndDispose(installInfo.getAdjusterData(), IsolatedCompileClient.get().getHandleSet()); - installInfo.setAdjusterData(WordFactory.nullPointer()); + installInfo.setAdjusterData(Word.nullPointer()); doInstallPrepared(method, installInfo.getCodeInfo(), installedCode); NativeMemory.free(installInfo); @@ -146,7 +146,7 @@ private IsolatedRuntimeCodeInstaller(IsolateThread targetIsolate, SharedRuntimeM @Override protected Pointer allocateCodeMemory(long size) { - PointerBase memory = allocateCodeMemory0(targetIsolate, WordFactory.unsigned(size)); + PointerBase memory = allocateCodeMemory0(targetIsolate, Word.unsigned(size)); if (memory.isNull()) { throw new OutOfMemoryError("Could not allocate memory for runtime-compiled code."); } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/OptionValuesEncoder.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/OptionValuesEncoder.java index 799bbd1931db..3f19a9232570 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/OptionValuesEncoder.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/OptionValuesEncoder.java @@ -28,13 +28,13 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import jdk.graal.compiler.word.Word; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.UnmodifiableEconomicMap; import org.graalvm.collections.UnmodifiableMapCursor; import jdk.graal.compiler.options.OptionKey; import jdk.graal.compiler.util.TypedDataInputStream; import jdk.graal.compiler.util.TypedDataOutputStream; -import org.graalvm.word.WordFactory; final class OptionValuesEncoder { @@ -65,7 +65,7 @@ public static EconomicMap, Object> decode(byte[] input) { try (TypedDataInputStream in = new TypedDataInputStream(new ByteArrayInputStream(input))) { final int size = in.readInt(); for (int i = 0; i < size; i++) { - ImageHeapRef> keyRef = WordFactory.signed(in.readLong()); + ImageHeapRef> keyRef = Word.signed(in.readLong()); final OptionKey key = ImageHeapObjects.deref(keyRef); final Object value = in.readTypedValue(); options.put(key, value); diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/RuntimeCodeInstaller.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/RuntimeCodeInstaller.java index e572fe8f0f25..acf3c18576ff 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/RuntimeCodeInstaller.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/RuntimeCodeInstaller.java @@ -29,11 +29,11 @@ import java.util.HashSet; import java.util.Map; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; @@ -130,15 +130,15 @@ private void prepareCodeMemory() { dataOffset = NumUtil.roundUp(codeSize, compilation.getDataSection().getSectionAlignment()); if (!RuntimeCodeCache.Options.WriteableCodeCache.getValue()) { // round up for readonly code cache so that the data section can remain writeable - dataOffset = UnsignedUtils.safeToInt(UnsignedUtils.roundUp(WordFactory.unsigned(dataOffset), CommittedMemoryProvider.get().getGranularity())); + dataOffset = UnsignedUtils.safeToInt(UnsignedUtils.roundUp(Word.unsigned(dataOffset), CommittedMemoryProvider.get().getGranularity())); } - codeAndDataMemorySize = UnsignedUtils.safeToInt(UnsignedUtils.roundUp(WordFactory.unsigned(dataOffset + dataSize), CommittedMemoryProvider.get().getGranularity())); + codeAndDataMemorySize = UnsignedUtils.safeToInt(UnsignedUtils.roundUp(Word.unsigned(dataOffset + dataSize), CommittedMemoryProvider.get().getGranularity())); code = allocateCodeMemory(codeAndDataMemorySize); compiledBytes = compilation.getTargetCode(); if (RuntimeCodeCache.Options.WriteableCodeCache.getValue()) { - UnsignedWord alignedAfterCodeOffset = UnsignedUtils.roundUp(WordFactory.unsigned(codeSize), CommittedMemoryProvider.get().getGranularity()); + UnsignedWord alignedAfterCodeOffset = UnsignedUtils.roundUp(Word.unsigned(codeSize), CommittedMemoryProvider.get().getGranularity()); assert alignedAfterCodeOffset.belowOrEqual(codeAndDataMemorySize); makeCodeMemoryExecutableWritable(code, alignedAfterCodeOffset); @@ -217,7 +217,7 @@ protected void doPrepareInstall(ReferenceAdjuster adjuster, CodeInfo codeInfo) { // remove write access from code if (!RuntimeCodeCache.Options.WriteableCodeCache.getValue()) { - makeCodeMemoryExecutableReadOnly(code, WordFactory.unsigned(codeSize)); + makeCodeMemoryExecutableReadOnly(code, Word.unsigned(codeSize)); } /* Write primitive constants to the buffer, record object constants with offsets */ diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMemoryAccessProviderImpl.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMemoryAccessProviderImpl.java index 6505cde99d31..d3d61d76a4d1 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMemoryAccessProviderImpl.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMemoryAccessProviderImpl.java @@ -26,10 +26,10 @@ import jdk.graal.compiler.core.common.CompressEncoding; import jdk.graal.compiler.word.BarrieredAccess; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.SignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.graal.meta.SubstrateMemoryAccessProvider; import com.oracle.svm.core.heap.ReferenceAccess; @@ -109,7 +109,7 @@ private static JavaConstant readObjectChecked(Constant baseConstant, long displa if (displacement < LayoutEncoding.getArrayBaseOffsetAsInt(layoutEncoding)) { /* Trying to read before the first array element. */ return null; - } else if (WordFactory.unsigned(displacement).aboveOrEqual(LayoutEncoding.getArrayElementOffset(layoutEncoding, ((Object[]) baseObject).length))) { + } else if (Word.unsigned(displacement).aboveOrEqual(LayoutEncoding.getArrayElementOffset(layoutEncoding, ((Object[]) baseObject).length))) { /* Trying to read after the last array element. */ return null; } else if ((displacement & (LayoutEncoding.getArrayIndexScale(layoutEncoding) - 1)) != 0) { @@ -124,7 +124,7 @@ private static JavaConstant readObjectChecked(Constant baseConstant, long displa } static JavaConstant readObjectUnchecked(Object baseObject, long displacement, boolean createCompressedConstant, boolean isVolatile) { - Object rawValue = isVolatile ? BarrieredAccess.readObjectVolatile(baseObject, WordFactory.signed(displacement)) : BarrieredAccess.readObject(baseObject, WordFactory.signed(displacement)); + Object rawValue = isVolatile ? BarrieredAccess.readObjectVolatile(baseObject, Word.signed(displacement)) : BarrieredAccess.readObject(baseObject, Word.signed(displacement)); return SubstrateObjectConstant.forObject(rawValue, createCompressedConstant); } @@ -150,7 +150,7 @@ private static JavaConstant readPrimitiveChecked(JavaKind kind, Constant baseCon } else if (displacement <= 0) { /* Trying to read before the object, or the hub. No need to look into the object. */ return null; - } else if (WordFactory.unsigned(displacement + bits / 8).aboveThan(LayoutEncoding.getMomentarySizeFromObject(baseObject))) { + } else if (Word.unsigned(displacement + bits / 8).aboveThan(LayoutEncoding.getMomentarySizeFromObject(baseObject))) { /* Trying to read after the end of the object. */ return null; } @@ -158,7 +158,7 @@ private static JavaConstant readPrimitiveChecked(JavaKind kind, Constant baseCon } static JavaConstant readPrimitiveUnchecked(JavaKind kind, Object baseObject, long displacement, int bits, boolean isVolatile) { - SignedWord offset = WordFactory.signed(displacement); + SignedWord offset = Word.signed(displacement); long rawValue; switch (bits) { case Byte.SIZE: diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateType.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateType.java index 44c6e88c3849..cc6d32e61c43 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateType.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateType.java @@ -87,7 +87,7 @@ public SubstrateField[] getRawAllInstanceFields() { } @Platforms(Platform.HOSTED_ONLY.class) - public void setTypeCheckData(DynamicHub uniqueConcreteImplementation) { + public void setSingleImplementor(DynamicHub uniqueConcreteImplementation) { this.uniqueConcreteImplementation = uniqueConcreteImplementation; } 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 d2dc641d9e5d..670bc0cf9f0e 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 @@ -34,6 +34,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import jdk.graal.compiler.word.Word; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.EconomicSet; import org.graalvm.collections.Equivalence; @@ -42,7 +43,6 @@ 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.SubstrateTargetDescription; import com.oracle.svm.core.annotate.Alias; @@ -234,7 +234,7 @@ class GlobalAtomicLongAddressProvider implements FieldValueTransformer { @Override public Object transform(Object receiver, Object originalValue) { long initialValue = ((GlobalAtomicLong) receiver).getInitialValue(); - return CGlobalDataFactory.createWord(WordFactory.unsigned(initialValue), null, true); + return CGlobalDataFactory.createWord(Word.unsigned(initialValue), null, true); } } diff --git a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/DowncallStub.java b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/DowncallStub.java index 7412d9586f41..3b1282f1da12 100644 --- a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/DowncallStub.java +++ b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/DowncallStub.java @@ -86,7 +86,7 @@ * capture. */ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+22/src/hotspot/share/prims/nativeEntryPoint.cpp") -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+15/src/hotspot/cpu/x86/downcallLinker_x86_64.cpp") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+2/src/hotspot/cpu/x86/downcallLinker_x86_64.cpp") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-23+12/src/hotspot/cpu/aarch64/downcallLinker_aarch64.cpp") class DowncallStub extends NonBytecodeMethod { public static Signature createSignature(MetaAccessProvider metaAccess) { diff --git a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsConfigurationParser.java b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsConfigurationParser.java index cd3b2b260faa..7aa1b11a03c5 100644 --- a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsConfigurationParser.java @@ -27,6 +27,9 @@ import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.Linker; import java.lang.foreign.Linker.Option; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.net.URI; import java.util.ArrayList; import java.util.List; @@ -41,10 +44,12 @@ import com.oracle.svm.core.configure.ConfigurationParser; import com.oracle.svm.core.util.BasedOnJDKFile; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.hosted.ImageClassLoader; import jdk.graal.compiler.util.json.JsonParserException; -@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+23/src/java.base/share/classes/jdk/internal/foreign/abi/LinkerOptions.java") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/java.base/share/classes/jdk/internal/foreign/abi/LinkerOptions.java") @Platforms(Platform.HOSTED_ONLY.class) public class ForeignFunctionsConfigurationParser extends ConfigurationParser { private static final String DOWNCALL_OPTION_CAPTURE_CALL_STATE = "captureCallState"; @@ -52,38 +57,68 @@ public class ForeignFunctionsConfigurationParser extends ConfigurationParser { private static final String DOWNCALL_OPTION_CRITICAL = "critical"; private static final String DOWNCALL_OPTION_ALLOW_HEAP_ACCESS = "allowHeapAccess"; + private final ImageClassLoader imageClassLoader; private final RuntimeForeignAccessSupport accessSupport; - public ForeignFunctionsConfigurationParser(RuntimeForeignAccessSupport access) { + public ForeignFunctionsConfigurationParser(ImageClassLoader imageClassLoader, RuntimeForeignAccessSupport access) { super(true); + this.imageClassLoader = imageClassLoader; this.accessSupport = access; } @Override public void parseAndRegister(Object json, URI origin) { var topLevel = asMap(json, "first level of document must be a map"); - checkAttributes(topLevel, "foreign methods categories", List.of(), List.of("downcalls", "upcalls")); + checkAttributes(topLevel, "foreign methods categories", List.of(), List.of("downcalls", "upcalls", "directUpcalls")); - var downcalls = asList(topLevel.get("downcalls", List.of()), "downcalls must be an array of method signatures"); + var downcalls = asList(topLevel.get("downcalls", List.of()), "downcalls must be an array of function descriptor and linker options"); for (Object downcall : downcalls) { parseAndRegisterForeignCall(downcall, this::parseDowncallOptions, (descriptor, options) -> accessSupport.registerForDowncall(ConfigurationCondition.alwaysTrue(), descriptor, options)); } - var upcalls = asList(topLevel.get("upcalls", List.of()), "upcalls must be an array of method signatures"); + var upcalls = asList(topLevel.get("upcalls", List.of()), "upcalls must be an array of function descriptor and linker options"); for (Object upcall : upcalls) { parseAndRegisterForeignCall(upcall, this::parseUpcallOptions, (descriptor, options) -> accessSupport.registerForUpcall(ConfigurationCondition.alwaysTrue(), descriptor, options)); } + + var directUpcalls = asList(topLevel.get("directUpcalls", List.of()), "direct upcalls must be an array of method references, function descriptors, and linker options"); + for (Object upcall : directUpcalls) { + parseAndRegisterDirectUpcall(upcall); + } } private void parseAndRegisterForeignCall(Object call, Function, List

        - *
      1. We have an analyzed graph available. See {@link ImageLayerLoader#hasAnalysisParsedGraph} - * for which analysis graphs are persisted.
      2. + *
      3. We have a strengthened graph available. See + * {@link SVMImageLayerLoader#hasStrengthenedGraph} for which strengthened graphs are persisted. + * Having an analysis parsed graph (see {@link SVMImageLayerLoader#hasAnalysisParsedGraph}) is + * not enough because methods with only an analysis parsed graph are inlined before analysis, + * but not analyzed. Additionally, having a strengthened graph implies also having an analysis + * parsed graph.
      4. *
      5. A compile target exists this layer can call.
      6. *
      * @@ -270,8 +278,8 @@ public boolean useBaseLayer() { */ @Override public boolean analyzedInPriorLayer(AnalysisMethod method) { - ImageLayerLoader imageLayerLoader = HostedImageLayerBuildingSupport.singleton().getLoader(); - return imageLayerLoader.hasAnalysisParsedGraph(method) || HostedDynamicLayerInfo.singleton().compiledInPriorLayer(method); + SVMImageLayerLoader imageLayerLoader = HostedImageLayerBuildingSupport.singleton().getLoader(); + return imageLayerLoader.hasStrengthenedGraph(method) || HostedDynamicLayerInfo.singleton().compiledInPriorLayer(method); } protected InlineBeforeAnalysisPolicyUtils getInlineBeforeAnalysisPolicyUtils() { @@ -527,9 +535,13 @@ private DynamicHub createHub(AnalysisType type) { */ boolean isProxyClass = Proxy.isProxyClass(javaClass); - return new DynamicHub(javaClass, className, computeHubType(type), computeReferenceType(type), superHub, componentHub, sourceFileName, modifiers, hubClassLoader, - isHidden, isRecord, nestHost, assertionStatus, type.hasDefaultMethods(), type.declaresDefaultMethods(), isSealed, isVMInternal, isLambdaFormHidden, isLinked, simpleBinaryName, - getDeclaringClass(javaClass), getSignature(javaClass), isProxyClass, layerId); + short flags = DynamicHub.makeFlags(javaClass.isPrimitive(), javaClass.isInterface(), isHidden, isRecord, assertionStatus, + type.hasDefaultMethods(), type.declaresDefaultMethods(), isSealed, isVMInternal, + isLambdaFormHidden, isLinked, isProxyClass); + + return new DynamicHub(javaClass, className, computeHubType(type), ReferenceType.computeReferenceType(javaClass), + superHub, componentHub, sourceFileName, modifiers, flags, hubClassLoader, nestHost, + simpleBinaryName, getDeclaringClass(javaClass), getSignature(javaClass), layerId); } private static final Method getSignature = ReflectionUtil.lookupMethod(Class.class, "getGenericSignature0"); @@ -621,21 +633,6 @@ private static int computeHubType(AnalysisType type) { return HubType.OTHER; } - private static ReferenceType computeReferenceType(AnalysisType type) { - Class clazz = type.getJavaClass(); - if (Reference.class.isAssignableFrom(clazz)) { - if (PhantomReference.class.isAssignableFrom(clazz)) { - return ReferenceType.Phantom; - } else if (SoftReference.class.isAssignableFrom(clazz)) { - return ReferenceType.Soft; - } else { - /* Treat all other java.lang.Reference subclasses as weak references. */ - return ReferenceType.Weak; - } - } - return ReferenceType.None; - } - @Override public void checkType(ResolvedJavaType type, AnalysisUniverse universe) { Class originalClass = OriginalClassProvider.getJavaClass(type); @@ -916,6 +913,10 @@ private void initializeExcludedFields() { excludedFields.add(ReflectionUtil.lookupField(NativeLibraries.class, "nativeLibraryLockMap")); } + /** + * This method cannot use an {@link AnalysisField} because it is used before the analysis is set + * up. + */ @Override public boolean isFieldIncluded(BigBang bb, Field field) { /* @@ -945,7 +946,53 @@ public boolean isFieldIncluded(BigBang bb, Field field) { /* Fields that are deleted or substituted should not be in the image */ if (bb instanceof NativeImagePointsToAnalysis nativeImagePointsToAnalysis) { AnnotationSubstitutionProcessor annotationSubstitutionProcessor = nativeImagePointsToAnalysis.getAnnotationSubstitutionProcessor(); - return !annotationSubstitutionProcessor.isDeleted(field) && !annotationSubstitutionProcessor.isAnnotationPresent(field, InjectAccessors.class); + boolean included = !annotationSubstitutionProcessor.isDeleted(field) && !annotationSubstitutionProcessor.isAnnotationPresent(field, InjectAccessors.class); + if (!included) { + return false; + } + } + return super.isFieldIncluded(bb, field); + } + + /** + * This method needs to be kept in sync with {@link SVMHost#isFieldIncluded(BigBang, Field)}. + */ + @Override + public boolean isFieldIncluded(BigBang bb, AnalysisField field) { + /* + * Fields of type CGlobalData can use a CGlobalDataFactory which must not be reachable at + * run time + */ + if (cGlobalData == null) { + cGlobalData = bb.getMetaAccess().lookupJavaType(CGlobalData.class); + } + if (field.getType().equals(cGlobalData)) { + return false; + } + + if (excludedFields.contains(OriginalFieldProvider.getJavaField(field))) { + return false; + } + + /* Fields with those names are not allowed in the image */ + if (NativeImageGenerator.checkName(field.getType().toJavaName() + "." + field.getName()) != null) { + return false; + } + /* Options should not be in the image */ + if (optionKey == null) { + optionKey = bb.getMetaAccess().lookupJavaType(OptionKey.class); + } + if (optionKey.isAssignableFrom(field.getType())) { + return false; + } + /* Fields from this package should not be in the image */ + if (field.getDeclaringClass().toJavaName().startsWith("jdk.graal.compiler")) { + return false; + } + /* Fields that are deleted or substituted should not be in the image */ + boolean included = field.getAnnotation(Delete.class) == null && field.getAnnotation(InjectAccessors.class) == null; + if (!included) { + return false; } return super.isFieldIncluded(bb, field); } @@ -960,6 +1007,11 @@ public boolean enableTrackAcrossLayers() { return enableTrackAcrossLayers; } + @Override + public boolean enableReachableInCurrentLayer() { + return enableReachableInCurrentLayer; + } + private final List> neverInlineTrivialHandlers = new CopyOnWriteArrayList<>(); public void registerNeverInlineTrivialHandler(BiPredicate handler) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index 690ff5d5421d..68adeb8a6745 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -41,8 +41,8 @@ import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.jdk.ServiceCatalogSupport; -import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; +import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.hosted.analysis.Inflation; import jdk.graal.compiler.options.Option; @@ -218,7 +218,21 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, Class service if (nullaryConstructor != null || nullaryProviderMethod != null) { RuntimeReflection.register(providerClass); if (nullaryConstructor != null) { + /* + * Registering a constructor with + * RuntimeReflection.registerConstructorLookup(providerClass) does not produce + * the same behavior as using RuntimeReflection.register(nullaryConstructor). In + * the first case, the constructor is marked for query purposes only, so this + * if-statement cannot be eliminated. + * + */ RuntimeReflection.register(nullaryConstructor); + } else { + /* + * If there's no nullary constructor, register it as negative lookup to avoid + * throwing a MissingReflectionRegistrationError at run time. + */ + RuntimeReflection.registerConstructorLookup(providerClass); } if (nullaryProviderMethod != null) { RuntimeReflection.register(nullaryProviderMethod); @@ -229,8 +243,14 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, Class service */ RuntimeReflection.registerMethodLookup(providerClass, "provider"); } - registeredProviders.add(provider); } + /* + * Register the provider in both cases: when it is JCA-compliant (has a nullary + * constructor or a provider method) or when it lacks both. If neither is present, a + * ServiceConfigurationError will be thrown at runtime, consistent with HotSpot + * behavior. + */ + registeredProviders.add(provider); } if (!registeredProviders.isEmpty()) { String serviceResourceLocation = "META-INF/services/" + serviceProvider.getName(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SubstrateStrengthenGraphs.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SubstrateStrengthenGraphs.java index 711810d23e09..8f33fd0b94eb 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SubstrateStrengthenGraphs.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SubstrateStrengthenGraphs.java @@ -26,7 +26,6 @@ import java.util.function.Supplier; -import com.oracle.graal.pointsto.heap.ImageLayerLoader; import com.oracle.graal.pointsto.infrastructure.Universe; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; @@ -44,8 +43,8 @@ import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; import com.oracle.svm.hosted.meta.HostedType; - import com.oracle.svm.hosted.phases.AnalyzeJavaHomeAccessPhase; + import jdk.graal.compiler.graph.Node; import jdk.graal.compiler.nodes.ConstantNode; import jdk.graal.compiler.nodes.DeoptimizeNode; @@ -78,21 +77,9 @@ protected void postStrengthenGraphs(StructuredGraph graph, AnalysisMethod method } } - @Override - protected void useSharedLayerGraph(AnalysisMethod method) { - ImageLayerLoader imageLayerLoader = HostedImageLayerBuildingSupport.singleton().getLoader(); - /* - * GR-55294: When the analysis elements from the base layer will be able to be materialized - * after the analysis, fewer graphs will have to be loaded here as well. - */ - if (imageLayerLoader.hasStrengthenedGraph(method)) { - imageLayerLoader.setStrengthenedGraph(method); - } - } - @Override protected void persistStrengthenGraph(AnalysisMethod method) { - if (HostedImageLayerBuildingSupport.buildingSharedLayer() && method.isReachable()) { + if (HostedImageLayerBuildingSupport.buildingSharedLayer() && method.isTrackedAcrossLayers()) { HostedImageLayerBuildingSupport.singleton().getWriter().persistMethodStrengthenedGraph(method); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SystemInOutErrFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SystemInOutErrFeature.java index 91c45ea13563..c27978d41f74 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SystemInOutErrFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SystemInOutErrFeature.java @@ -27,7 +27,11 @@ import java.io.InputStream; import java.io.PrintStream; +import java.lang.reflect.Field; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.ReflectionUtil; +import jdk.internal.access.SharedSecrets; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; @@ -51,12 +55,27 @@ public class SystemInOutErrFeature implements InternalFeature, FeatureSingleton private final InputStream hostedIn; private final PrintStream hostedOut; private final PrintStream hostedErr; + private final InputStream hostedInitialIn; + private final PrintStream hostedInitialErr; public SystemInOutErrFeature() { hostedIn = System.in; NativeImageSystemIOWrappers wrappers = NativeImageSystemIOWrappers.singleton(); hostedOut = wrappers.outWrapper; hostedErr = wrappers.errWrapper; + hostedInitialIn = SharedSecrets.getJavaLangAccess().initialSystemIn(); + /* + * GR-55515: Migrate to JavaLangAccess#initialSystemErr(). The method + * JavaLangAccess#initialSystemErr() and the System#initialErr field were both introduced in + * JDK 23. Once JDK 21 compatibility is no longer required, consider switching to + * SharedSecrets.getJavaLangAccess().initialSystemErr(). + */ + Field initialErrField = ReflectionUtil.lookupField(true, System.class, "initialErr"); + try { + hostedInitialErr = initialErrField != null ? (PrintStream) initialErrField.get(null) : null; + } catch (IllegalAccessException illegalAccess) { + throw VMError.shouldNotReachHere(illegalAccess); + } } private SystemInOutErrSupport runtime; @@ -64,6 +83,8 @@ public SystemInOutErrFeature() { private static final String SYSTEM_IN_KEY_NAME = "System#in"; private static final String SYSTEM_ERR_KEY_NAME = "System#err"; private static final String SYSTEM_OUT_KEY_NAME = "System#out"; + private static final String SYSTEM_INITIAL_IN_KEY_NAME = "System#initialIn"; + private static final String SYSTEM_INITIAL_ERR_KEY_NAME = "System#initialErr"; @Override public void afterRegistration(AfterRegistrationAccess access) { @@ -82,6 +103,8 @@ public void duringSetup(DuringSetupAccess access) { registry.registerHeapConstant(SYSTEM_IN_KEY_NAME, runtime.in()); registry.registerHeapConstant(SYSTEM_OUT_KEY_NAME, runtime.out()); registry.registerHeapConstant(SYSTEM_ERR_KEY_NAME, runtime.err()); + registry.registerHeapConstant(SYSTEM_INITIAL_IN_KEY_NAME, runtime.initialIn()); + registry.registerHeapConstant(SYSTEM_INITIAL_ERR_KEY_NAME, runtime.initialErr()); } access.registerObjectReplacer(this::replaceStreamsWithRuntimeObject); } else { @@ -102,6 +125,10 @@ Object replaceStreamsWithRuntimeObject(Object object) { return runtime.out(); } else if (object == hostedErr) { return runtime.err(); + } else if (object == hostedInitialErr) { + return runtime.initialErr(); + } else if (object == hostedInitialIn) { + return runtime.initialIn(); } else { return object; } @@ -114,6 +141,10 @@ ImageHeapConstant replaceStreamsWithLayerConstant(CrossLayerConstantRegistry reg return registry.getConstant(SYSTEM_OUT_KEY_NAME); } else if (object == hostedErr) { return registry.getConstant(SYSTEM_ERR_KEY_NAME); + } else if (object == hostedInitialErr) { + return registry.getConstant(SYSTEM_INITIAL_ERR_KEY_NAME); + } else if (object == hostedInitialIn) { + return registry.getConstant(SYSTEM_INITIAL_IN_KEY_NAME); } else { return null; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/VMFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/VMFeature.java index 3be20f534bcd..6855785b4deb 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/VMFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/VMFeature.java @@ -28,10 +28,10 @@ import java.util.Locale; import java.util.stream.Collectors; +import jdk.graal.compiler.word.Word; 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.reports.ReportUtils; import com.oracle.svm.core.SubstrateOptions; @@ -114,7 +114,7 @@ public void afterAnalysis(AfterAnalysisAccess access) { } if (!Platform.includedIn(Platform.WINDOWS.class)) { - CGlobalData isStaticBinaryMarker = CGlobalDataFactory.createWord(WordFactory.unsigned(SubstrateOptions.StaticExecutable.getValue() ? 1 : 0), STATIC_BINARY_MARKER_SYMBOL_NAME); + CGlobalData isStaticBinaryMarker = CGlobalDataFactory.createWord(Word.unsigned(SubstrateOptions.StaticExecutable.getValue() ? 1 : 0), STATIC_BINARY_MARKER_SYMBOL_NAME); CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(isStaticBinaryMarker); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java index 04c2d7fea0a3..d5b5abe52f89 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java @@ -37,7 +37,6 @@ import com.oracle.graal.pointsto.constraints.UnsupportedFeatures; import com.oracle.graal.pointsto.flow.MethodFlowsGraph; import com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder; -import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; @@ -51,6 +50,7 @@ import com.oracle.svm.hosted.ameta.CustomTypeFieldHandler; import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import com.oracle.svm.hosted.code.IncompatibleClassChangeFallbackMethod; +import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; import com.oracle.svm.hosted.meta.HostedType; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; @@ -145,7 +145,7 @@ public void onTypeReachable(AnalysisType type) { * avoid deadlocks, the hub needs to be rescanned manually after the metadata is * initialized. */ - universe.getImageLayerLoader().rescanHub(type, ((SVMHost) hostVM).dynamicHub(type)); + HostedImageLayerBuildingSupport.singleton().getLoader().rescanHub(type, ((SVMHost) hostVM).dynamicHub(type)); } if (SubstrateOptions.includeAll()) { /* @@ -154,9 +154,8 @@ public void onTypeReachable(AnalysisType type) { */ Stream.concat(Arrays.stream(getOrDefault(type, t -> t.getInstanceFields(true), new AnalysisField[0])), Arrays.stream(getOrDefault(type, AnalysisType::getStaticFields, new AnalysisField[0]))) - .map(OriginalFieldProvider::getJavaField) - .filter(field -> field != null && classInclusionPolicy.isFieldIncluded(field)) - .forEach(classInclusionPolicy::includeField); + .filter(field -> field != null && classInclusionPolicy.isFieldIncluded((AnalysisField) field)) + .forEach(field -> classInclusionPolicy.includeField((AnalysisField) field)); /* * Only the class initializers that are executed at run time should be included in diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/CInterfaceError.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/CInterfaceError.java index 3d08b1b174aa..5cfec3e3c6bf 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/CInterfaceError.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/CInterfaceError.java @@ -51,11 +51,11 @@ private String fullMessage(String msg) { StringBuilder result = new StringBuilder(msg); for (Object element : this.context) { if (element instanceof ResolvedJavaMethod) { - result.append("\n method ").append(((ResolvedJavaMethod) element).format("%H.%n(%p)")); + result.append(System.lineSeparator()).append(" method ").append(((ResolvedJavaMethod) element).format("%H.%n(%p)")); } else if (element instanceof ResolvedJavaType) { - result.append("\n type ").append(((ResolvedJavaType) element).toJavaName(true)); + result.append(System.lineSeparator()).append(" type ").append(((ResolvedJavaType) element).toJavaName(true)); } else { - result.append("\n ").append(element); + result.append(System.lineSeparator()).append(" ").append(element); } } return result.toString(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/CIsolateDataFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/CIsolateDataFeature.java index d0614d63c0e0..8c72ba43ab46 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/CIsolateDataFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/CIsolateDataFeature.java @@ -36,8 +36,8 @@ import com.oracle.svm.core.util.UnsignedUtils; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.word.Word; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; @AutomaticallyRegisteredFeature public class CIsolateDataFeature implements InternalFeature { @@ -62,13 +62,13 @@ private Object replaceObject(Object obj) { @Override public void afterAnalysis(AfterAnalysisAccess access) { - UnsignedWord offset = WordFactory.zero(); + UnsignedWord offset = Word.zero(); CIsolateData[] entries = usedEntries.values().toArray(new CIsolateData[0]); Arrays.sort(entries, Comparator.comparing(CIsolateData::getSize).thenComparing(CIsolateData::getName)); for (CIsolateData entry : entries) { - offset = UnsignedUtils.roundUp(offset, WordFactory.unsigned(CIsolateDataStorage.ALIGNMENT)); + offset = UnsignedUtils.roundUp(offset, Word.unsigned(CIsolateDataStorage.ALIGNMENT)); entry.setOffset(offset); - offset = offset.add(WordFactory.unsigned(entry.getSize())); + offset = offset.add(Word.unsigned(entry.getSize())); } CIsolateDataStorage.singleton().setSize(offset); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java index 9b2b2dbe2a52..19f005c69186 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java @@ -328,7 +328,7 @@ static String getTraceString(StackTraceElement[] trace) { StringBuilder b = new StringBuilder(); for (StackTraceElement stackTraceElement : trace) { - b.append("\tat ").append(stackTraceElement.toString()).append("\n"); + b.append("\tat ").append(stackTraceElement.toString()).append(System.lineSeparator()); } return b.toString(); 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 8ded7f4ebc6a..845f878b3f55 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 @@ -70,7 +70,9 @@ import com.oracle.svm.hosted.NativeImageOptions; import com.oracle.svm.hosted.ProgressReporter; import com.oracle.svm.hosted.diagnostic.HostedHeapDumpFeature; +import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; import com.oracle.svm.hosted.imagelayer.LayeredDispatchTableSupport; +import com.oracle.svm.hosted.imagelayer.SVMImageLayerLoader; import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.hosted.meta.HostedUniverse; import com.oracle.svm.hosted.phases.ImageBuildStatisticsCounterPhase; @@ -449,6 +451,9 @@ public void finish(DebugContext debug) { if (ImageSingletons.contains(HostedHeapDumpFeature.class)) { ImageSingletons.lookup(HostedHeapDumpFeature.class).compileQueueAfterCompilation(); } + if (ImageLayerBuildingSupport.buildingExtensionLayer()) { + HostedImageLayerBuildingSupport.singleton().getLoader().cleanupAfterCompilation(); + } } protected void checkUninterruptibleAnnotations() { @@ -541,7 +546,7 @@ private void printMethodHistogram() { System.out.format("Code Size; Nodes Parsing; Nodes Before; Nodes After; Is Trivial;" + " Deopt Target; Code Size; Nodes Parsing; Nodes Before; Nodes After; Deopt Entries; Deopt During Call;" + - " Entry Points; Direct Calls; Virtual Calls; Method\n"); + " Entry Points; Direct Calls; Virtual Calls; Method%n"); List tasks = new ArrayList<>(compilations.values()); tasks.sort(Comparator.comparing(t2 -> t2.method.format("%H.%n(%p) %r"))); @@ -847,8 +852,7 @@ private void doInlineTrivial(DebugContext debug, HostedMethod method) { } private boolean makeInlineDecision(HostedMethod method, HostedMethod callee) { - // GR-57832 this will be removed - if (callee.compilationInfo.getCompilationGraph() == null) { + if (!SubstrateOptions.UseSharedLayerStrengthenedGraphs.getValue() && callee.compilationInfo.getCompilationGraph() == null) { /* * We have compiled this method in a prior layer, but don't have the graph available * here. @@ -856,7 +860,6 @@ private boolean makeInlineDecision(HostedMethod method, HostedMethod callee) { assert callee.isCompiledInPriorLayer() : method; return false; } - if (universe.hostVM().neverInlineTrivial(method.getWrapped(), callee.getWrapped())) { return false; } @@ -983,9 +986,11 @@ public void scheduleDeoptTargets() { private static boolean parseInCurrentLayer(HostedMethod method) { var hasAnalyzedGraph = method.wrapped.getAnalyzedGraph() != null; - if (!hasAnalyzedGraph) { - assert method.isCompiledInPriorLayer() || method.compilationInfo.inParseQueue.get() : method; + if (!hasAnalyzedGraph && method.wrapped.reachableInCurrentLayer()) { + SVMImageLayerLoader imageLayerLoader = HostedImageLayerBuildingSupport.singleton().getLoader(); + hasAnalyzedGraph = imageLayerLoader.hasStrengthenedGraph(method.wrapped); } + assert hasAnalyzedGraph || method.isCompiledInPriorLayer() || method.compilationInfo.inParseQueue.get() : method; return hasAnalyzedGraph; } @@ -1006,6 +1011,11 @@ protected void ensureParsed(HostedMethod method, HostedMethod callerMethod, Comp } protected final void doParse(DebugContext debug, ParseTask task) { + HostedMethod method = task.method; + if (HostedImageLayerBuildingSupport.buildingExtensionLayer()) { + loadPriorStrengthenedGraph(method); + } + ParseFunction customFunction = task.method.compilationInfo.getCustomParseFunction(); if (customFunction != null) { customFunction.parse(debug, task.method, task.reason, runtimeConfig); @@ -1019,6 +1029,25 @@ protected final void doParse(DebugContext debug, ParseTask task) { } } + private static void loadPriorStrengthenedGraph(HostedMethod method) { + if (method.wrapped.getAnalyzedGraph() == null && method.wrapped.reachableInCurrentLayer()) { + /* + * Only the strengthened graphs of methods that need to be analyzed in the current layer + * are loaded. + */ + SVMImageLayerLoader imageLayerLoader = HostedImageLayerBuildingSupport.singleton().getLoader(); + boolean hasStrengthenedGraph = imageLayerLoader.hasStrengthenedGraph(method.wrapped); + assert method.isCompiledInPriorLayer() || method.compilationInfo.inParseQueue.get() || hasStrengthenedGraph : method; + if (hasStrengthenedGraph) { + /* + * GR-59679: The loading of graphs could be even more lazy than this. It could be + * loaded only when the inlining will be performed + */ + method.wrapped.setAnalyzedGraph(imageLayerLoader.getStrengthenedGraph(method.wrapped)); + } + } + } + @SuppressWarnings("try") private void defaultParseFunction(DebugContext debug, HostedMethod method, CompileReason reason, RuntimeConfiguration config, ParseHooks hooks) { if (method.getAnnotation(NodeIntrinsic.class) != null) { @@ -1095,7 +1124,7 @@ private void ensureParsed(HostedMethod method, CompileReason reason, CallTargetN * already applied during parsing before we reach this point, so we look at the "simple" * implementation invoked status. */ - if (invokeTarget.wrapped.isSimplyImplementationInvoked() || invokeTarget.wrapped.isInBaseLayer()) { + if (invokeTarget.wrapped.isSimplyImplementationInvoked()) { handleSpecialization(method, targetNode, invokeTarget, invokeTarget); ensureParsed(invokeTarget, method, new DirectCallReason(method, reason)); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RestrictHeapAccessAnnotationChecker.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RestrictHeapAccessAnnotationChecker.java index 24b28f9863cd..2d01f3ecbca8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RestrictHeapAccessAnnotationChecker.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RestrictHeapAccessAnnotationChecker.java @@ -29,9 +29,6 @@ import java.util.Deque; import java.util.Map; -import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.graph.NodeSourcePosition; -import jdk.graal.compiler.options.Option; import org.graalvm.nativeimage.ImageSingletons; import com.oracle.graal.pointsto.meta.AnalysisMethod; @@ -43,6 +40,10 @@ import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.hosted.meta.HostedUniverse; +import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.graph.NodeSourcePosition; +import jdk.graal.compiler.options.Option; + public final class RestrictHeapAccessAnnotationChecker { /* @@ -137,17 +138,17 @@ private void postRestrictHeapAccessWarning(AnalysisMethod violatingCallee, Map> create(String combinedFileKey, boolean strictMetadata, - ConfigurationConditionResolver conditionResolver, ReflectionRegistry registry, ProxyRegistry proxyRegistry, ImageClassLoader imageClassLoader) { + ConfigurationConditionResolver conditionResolver, ReflectionRegistry registry, ProxyRegistry proxyRegistry, + RuntimeSerializationSupport serializationSupport, ImageClassLoader imageClassLoader) { return ReflectionConfigurationParser.create(combinedFileKey, strictMetadata, conditionResolver, - RegistryAdapter.create(registry, proxyRegistry, imageClassLoader), + RegistryAdapter.create(registry, proxyRegistry, serializationSupport, imageClassLoader), ConfigurationFiles.Options.StrictConfiguration.getValue(), ConfigurationFiles.Options.WarnAboutMissingReflectionOrJNIMetadataElements.getValue(), TreatAllNameEntriesAsType.getValue()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java index c2b13640d2f6..58b71e955769 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java @@ -27,24 +27,28 @@ import java.lang.reflect.Proxy; import java.util.Arrays; -import com.oracle.svm.hosted.reflect.ReflectionDataBuilder; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; +import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; import com.oracle.svm.core.TypeResult; import com.oracle.svm.core.configure.ConfigurationTypeDescriptor; import com.oracle.svm.core.configure.NamedConfigurationTypeDescriptor; import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.reflect.ReflectionDataBuilder; import com.oracle.svm.hosted.reflect.proxy.ProxyRegistry; public class ReflectionRegistryAdapter extends RegistryAdapter { private final RuntimeReflectionSupport reflectionSupport; private final ProxyRegistry proxyRegistry; + private final RuntimeSerializationSupport serializationSupport; - ReflectionRegistryAdapter(RuntimeReflectionSupport reflectionSupport, ProxyRegistry proxyRegistry, ImageClassLoader classLoader) { + ReflectionRegistryAdapter(RuntimeReflectionSupport reflectionSupport, ProxyRegistry proxyRegistry, RuntimeSerializationSupport serializationSupport, + ImageClassLoader classLoader) { super(reflectionSupport, classLoader); this.reflectionSupport = reflectionSupport; this.proxyRegistry = proxyRegistry; + this.serializationSupport = serializationSupport; } @Override @@ -126,4 +130,9 @@ public void registerPublicConstructors(ConfigurationCondition condition, boolean public void registerDeclaredConstructors(ConfigurationCondition condition, boolean queriedOnly, Class type) { reflectionSupport.registerAllDeclaredConstructorsQuery(condition, queriedOnly, type); } + + @Override + public void registerAsSerializable(ConfigurationCondition condition, Class clazz) { + serializationSupport.register(condition, clazz); + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java index 76ea23a764d6..6766d62d1593 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java @@ -36,6 +36,7 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.ReflectionRegistry; import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; +import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; import com.oracle.svm.core.TypeResult; import com.oracle.svm.core.configure.ConfigurationTypeDescriptor; @@ -52,9 +53,10 @@ public class RegistryAdapter implements ReflectionConfigurationParserDelegate serializationSupport, + ImageClassLoader classLoader) { if (registry instanceof RuntimeReflectionSupport) { - return new ReflectionRegistryAdapter((RuntimeReflectionSupport) registry, proxyRegistry, classLoader); + return new ReflectionRegistryAdapter((RuntimeReflectionSupport) registry, proxyRegistry, serializationSupport, classLoader); } else { return new RegistryAdapter(registry, classLoader); } @@ -289,6 +291,11 @@ private void registerExecutable(ConfigurationCondition condition, boolean querie registry.register(condition, queriedOnly, executable); } + @Override + public void registerAsSerializable(ConfigurationCondition condition, Class clazz) { + /* Serializable has no effect on JNI registrations */ + } + @Override public String getTypeName(Class type) { return type.getTypeName(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java deleted file mode 100644 index b10124d54c34..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java +++ /dev/null @@ -1,478 +0,0 @@ -/* - * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.hosted.heap; - -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ANNOTATIONS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ANNOTATION_VALUES_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CLASS_ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.C_ENTRY_POINT_LITERAL_CODE_POINTER; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FIELD_CHECK_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IMAGE_SINGLETON_KEYS; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IMAGE_SINGLETON_OBJECTS; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INFO_CLASS_INITIALIZER_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INFO_HAS_INITIALIZER_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INFO_IS_BUILD_TIME_INITIALIZED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INFO_IS_INITIALIZED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INFO_IS_IN_ERROR_STATE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INFO_IS_LINKED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INFO_IS_TRACKED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_FAILED_NO_TRACKING_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INITIALIZED_NO_TRACKING_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_NO_INITIALIZER_NO_TRACKING_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.METHOD_POINTER_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.PERSISTED; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.TYPES_TAG; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.UnmodifiableEconomicMap; -import org.graalvm.nativeimage.impl.CEntryPointLiteralCodePointer; - -import com.oracle.graal.pointsto.flow.AnalysisParsedGraph; -import com.oracle.graal.pointsto.heap.ImageHeapConstant; -import com.oracle.graal.pointsto.heap.ImageHeapInstance; -import com.oracle.graal.pointsto.heap.ImageLayerLoader; -import com.oracle.graal.pointsto.meta.AnalysisField; -import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.util.AnalysisError; -import com.oracle.graal.pointsto.util.AnalysisFuture; -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.TypeResult; -import com.oracle.svm.core.classinitialization.ClassInitializationInfo; -import com.oracle.svm.core.graal.code.CGlobalDataInfo; -import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader; -import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; -import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton.PersistFlags; -import com.oracle.svm.core.meta.MethodPointer; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.ImageClassLoader; -import com.oracle.svm.hosted.SVMHost; -import com.oracle.svm.hosted.c.CGlobalDataFeature; -import com.oracle.svm.hosted.fieldfolding.StaticFinalFieldFoldingFeature; -import com.oracle.svm.hosted.imagelayer.HostedDynamicLayerInfo; -import com.oracle.svm.hosted.meta.HostedUniverse; -import com.oracle.svm.hosted.meta.RelocatableConstant; -import com.oracle.svm.hosted.util.IdentityHashCodeUtil; -import com.oracle.svm.util.LogUtils; -import com.oracle.svm.util.ReflectionUtil; - -import jdk.graal.compiler.nodes.EncodedGraph; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import sun.reflect.annotation.AnnotationParser; - -public class SVMImageLayerLoader extends ImageLayerLoader { - - private final Field dynamicHubArrayHubField; - - private HostedUniverse hostedUniverse; - private final ImageClassLoader imageClassLoader; - - public SVMImageLayerLoader(List loadPaths, ImageClassLoader imageClassLoader) { - super(loadPaths); - dynamicHubArrayHubField = ReflectionUtil.lookupField(DynamicHub.class, "arrayHub"); - this.imageClassLoader = imageClassLoader; - } - - public void setHostedUniverse(HostedUniverse hostedUniverse) { - this.hostedUniverse = hostedUniverse; - } - - public HostedUniverse getHostedUniverse() { - return hostedUniverse; - } - - public ClassInitializationInfo getClassInitializationInfo(AnalysisType type) { - int tid = type.getId(); - EconomicMap typesMap = get(jsonMap, TYPES_TAG); - EconomicMap typeMap = get(typesMap, typeIdToIdentifier.get(tid)); - - boolean isNoInitializerNoTracking = get(typeMap, IS_NO_INITIALIZER_NO_TRACKING_TAG); - boolean isInitializedNoTracking = get(typeMap, IS_INITIALIZED_NO_TRACKING_TAG); - boolean isFailedNoTracking = get(typeMap, IS_FAILED_NO_TRACKING_TAG); - - if (isNoInitializerNoTracking) { - return ClassInitializationInfo.forNoInitializerInfo(false); - } else if (isInitializedNoTracking) { - return ClassInitializationInfo.forInitializedInfo(false); - } else if (isFailedNoTracking) { - return ClassInitializationInfo.forFailedInfo(false); - } else { - boolean isInitialized = get(typeMap, INFO_IS_INITIALIZED_TAG); - boolean isInErrorState = get(typeMap, INFO_IS_IN_ERROR_STATE_TAG); - boolean isLinked = get(typeMap, INFO_IS_LINKED_TAG); - boolean hasInitializer = get(typeMap, INFO_HAS_INITIALIZER_TAG); - boolean isBuildTimeInitialized = get(typeMap, INFO_IS_BUILD_TIME_INITIALIZED_TAG); - boolean isTracked = get(typeMap, INFO_IS_TRACKED_TAG); - - ClassInitializationInfo.InitState initState; - if (isInitialized) { - initState = ClassInitializationInfo.InitState.FullyInitialized; - } else if (isInErrorState) { - initState = ClassInitializationInfo.InitState.InitializationError; - } else { - assert isLinked : "Invalid state"; - Integer classInitializerId = get(typeMap, INFO_CLASS_INITIALIZER_TAG); - MethodPointer classInitializer = classInitializerId == null ? null : new MethodPointer(getAnalysisMethod(classInitializerId)); - return new ClassInitializationInfo(classInitializer, isTracked); - } - - return new ClassInitializationInfo(initState, hasInitializer, isBuildTimeInitialized, isTracked); - } - } - - @Override - public Class lookupClass(boolean optional, String className) { - TypeResult> typeResult = imageClassLoader.findClass(className); - if (!typeResult.isPresent()) { - if (optional) { - return null; - } else { - throw AnalysisError.shouldNotReachHere("Class not found: " + className); - } - } - return typeResult.get(); - } - - @Override - protected Annotation[] getAnnotations(EconomicMap elementData) { - List annotationNames = get(elementData, ANNOTATIONS_TAG); - List> annotationValuesList = get(elementData, ANNOTATION_VALUES_TAG); - Annotation[] annotations = new Annotation[annotationNames.size()]; - - for (int i = 0; i < annotationNames.size(); ++i) { - Class annotationType = cast(lookupBaseLayerTypeInHostVM(annotationNames.get(i))); - EconomicMap annotationValues = annotationValuesList.get(i); - Map annotationValuesMap = new HashMap<>(); - var cursor = annotationValues.getEntries(); - while (cursor.advance()) { - Object value = cursor.getValue(); - if (value instanceof EconomicMap) { - EconomicMap enumData = cast(value); - value = getEnumValue(enumData); - } - annotationValuesMap.put(cursor.getKey(), value); - } - annotations[i] = AnnotationParser.annotationForMap(annotationType, annotationValuesMap); - } - - return annotations; - } - - @Override - protected void initializeBaseLayerMethod(AnalysisMethod analysisMethod, EconomicMap methodData) { - if (!HostedDynamicLayerInfo.singleton().compiledInPriorLayer(analysisMethod) && hasAnalysisParsedGraph(analysisMethod)) { - /* - * GR-55294: When the analysis elements from the base layer will be able to be - * materialized after the analysis, this will not be needed anymore. - */ - analysisMethod.ensureGraphParsed(universe.getBigbang()); - analysisMethod.setAnalyzedGraph(((AnalysisParsedGraph) analysisMethod.getGraph()).getEncodedGraph()); - } - super.initializeBaseLayerMethod(analysisMethod, methodData); - } - - @Override - protected void afterGraphDecodeHook(EncodedGraph encodedGraph) { - super.afterGraphDecodeHook(encodedGraph); - for (int i = 0; i < encodedGraph.getNumObjects(); ++i) { - if (encodedGraph.getObject(i) instanceof CGlobalDataInfo cGlobalDataInfo) { - encodedGraph.setObject(i, CGlobalDataFeature.singleton().registerAsAccessedOrGet(cGlobalDataInfo.getData())); - } - } - } - - @Override - public void initializeBaseLayerField(AnalysisField analysisField) { - EconomicMap fieldData = getFieldData(analysisField); - - Integer fieldCheckIndex = get(fieldData, FIELD_CHECK_TAG); - if (fieldCheckIndex != null) { - StaticFinalFieldFoldingFeature.singleton().putBaseLayerFieldCheckIndex(analysisField.getId(), fieldCheckIndex); - } - - super.initializeBaseLayerField(analysisField); - } - - @Override - protected void prepareConstantRelinking(EconomicMap constantData, int identityHashCode, int id) { - Integer tid = get(constantData, CLASS_ID_TAG); - if (tid != null) { - typeToConstant.put(tid, id); - } else { - super.prepareConstantRelinking(constantData, identityHashCode, id); - } - } - - @Override - protected boolean delegateProcessing(String constantType, Object constantValue, List constantData, Object[] values, int i) { - if (constantType.equals(METHOD_POINTER_TAG)) { - AnalysisFuture task = new AnalysisFuture<>(() -> { - AnalysisType methodPointerType = metaAccess.lookupJavaType(MethodPointer.class); - int mid = (int) constantValue; - AnalysisMethod method = getAnalysisMethod(mid); - RelocatableConstant constant = new RelocatableConstant(new MethodPointer(method), methodPointerType); - values[i] = constant; - return constant; - }); - values[i] = task; - return true; - } else if (constantType.equals(C_ENTRY_POINT_LITERAL_CODE_POINTER)) { - AnalysisType cEntryPointerLiteralPointerType = metaAccess.lookupJavaType(CEntryPointLiteralCodePointer.class); - String methodName = (String) constantValue; - Class definingClass = lookupBaseLayerTypeInHostVM((String) constantData.get(2)); - List parameters = cast(constantData.get(3)); - Class[] parameterTypes = parameters.stream().map(this::lookupBaseLayerTypeInHostVM).toList().toArray(new Class[0]); - values[i] = new RelocatableConstant(new CEntryPointLiteralCodePointer(definingClass, methodName, parameterTypes), cEntryPointerLiteralPointerType); - return true; - } - return super.delegateProcessing(constantType, constantValue, constantData, values, i); - } - - @Override - protected JavaConstant lookupHostedObject(EconomicMap baseLayerConstant, Class clazz) { - if (clazz.equals(Class.class)) { - Integer tid = get(baseLayerConstant, CLASS_ID_TAG); - /* DynamicHub corresponding to $$TypeSwitch classes are not relinked */ - if (tid != null) { - return getDynamicHub(tid); - } - } - return super.lookupHostedObject(baseLayerConstant, clazz); - } - - private JavaConstant getDynamicHub(int tid) { - AnalysisType type = getAnalysisType(tid); - DynamicHub hub = ((SVMHost) universe.hostVM()).dynamicHub(type); - return hostedValuesProvider.forObject(hub); - } - - @Override - protected void injectIdentityHashCode(Object object, Integer identityHashCode) { - if (object == null || identityHashCode == null) { - return; - } - boolean result = IdentityHashCodeUtil.injectIdentityHashCode(object, identityHashCode); - if (!result) { - if (SubstrateOptions.LoggingHashCodeInjection.getValue()) { - LogUtils.warning("Object of type %s already had an hash code: %s", object.getClass(), object); - } - } - } - - @Override - public void ensureHubInitialized(ImageHeapConstant constant) { - JavaConstant javaConstant = constant.getHostedObject(); - if (constant.getType().getJavaClass().equals(Class.class)) { - DynamicHub hub = universe.getHostedValuesProvider().asObject(DynamicHub.class, javaConstant); - AnalysisType type = ((SVMHost) universe.hostVM()).lookupType(hub); - ensureHubInitialized(type); - /* - * If the persisted constant contains a non-null arrayHub, the corresponding DynamicHub - * must be created and the initializeMetaDataTask needs to be executed to ensure the - * hosted object matches the persisted constant. - */ - if (((ImageHeapInstance) constant).getFieldValue(metaAccess.lookupJavaField(dynamicHubArrayHubField)) != JavaConstant.NULL_POINTER && hub.getArrayHub() == null) { - AnalysisType arrayClass = type.getArrayClass(); - ensureHubInitialized(arrayClass); - } - } - } - - private static void ensureHubInitialized(AnalysisType type) { - type.registerAsReachable(PERSISTED); - type.getInitializeMetaDataTask().ensureDone(); - } - - @Override - public void rescanHub(AnalysisType type, Object hubObject) { - DynamicHub hub = (DynamicHub) hubObject; - universe.getHeapScanner().rescanObject(hub); - universe.getHeapScanner().rescanField(hub, SVMImageLayerSnapshotUtil.classInitializationInfo); - if (type.getJavaKind() == JavaKind.Object) { - if (type.isArray()) { - universe.getHeapScanner().rescanField(hub.getComponentHub(), SVMImageLayerSnapshotUtil.arrayHub); - } - universe.getHeapScanner().rescanField(hub, SVMImageLayerSnapshotUtil.interfacesEncoding); - if (type.isEnum()) { - universe.getHeapScanner().rescanField(hub, SVMImageLayerSnapshotUtil.enumConstantsReference); - } - } - } - - @Override - protected boolean hasValueForObject(Object object) { - if (object instanceof DynamicHub dynamicHub) { - AnalysisType type = ((SVMHost) universe.hostVM()).lookupType(dynamicHub); - return typeToConstant.containsKey(type.getId()); - } - return super.hasValueForObject(object); - } - - @Override - protected ImageHeapConstant getValueForObject(Object object) { - if (object instanceof DynamicHub dynamicHub) { - AnalysisType type = ((SVMHost) universe.hostVM()).lookupType(dynamicHub); - int id = typeToConstant.get(type.getId()); - return getOrCreateConstant(id); - } - return super.getValueForObject(object); - } - - public Map>> loadImageSingletons(Object forbiddenObject) { - openFilesAndLoadJsonMap(); - return loadImageSingletons0(forbiddenObject); - } - - private Map>> loadImageSingletons0(Object forbiddenObject) { - List singletonObjects = cast(jsonMap.get(IMAGE_SINGLETON_OBJECTS)); - Map idToObjectMap = new HashMap<>(); - for (Object entry : singletonObjects) { - List list = cast(entry); - Integer id = cast(list.get(0)); - String className = cast(list.get(1)); - EconomicMap keyStore = cast(list.get(2)); - - // create singleton object instance - Object result; - try { - Class clazz = lookupClass(false, className); - Method createMethod = ReflectionUtil.lookupMethod(clazz, "createFromLoader", ImageSingletonLoader.class); - result = createMethod.invoke(null, new ImageSingletonLoaderImpl(keyStore, imageClassLoader)); - } catch (Throwable t) { - throw VMError.shouldNotReachHere("Failed to recreate image singleton", t); - } - - idToObjectMap.put(id, result); - } - - Map>> singletonInitializationMap = new HashMap<>(); - List singletonKeys = cast(jsonMap.get(IMAGE_SINGLETON_KEYS)); - for (Object entry : singletonKeys) { - List list = cast(entry); - String key = cast(list.get(0)); - LayeredImageSingleton.PersistFlags persistInfo = LayeredImageSingleton.PersistFlags.values()[(int) list.get(1)]; - int id = cast(list.get(2)); - if (persistInfo == PersistFlags.CREATE) { - assert id != -1 : "Create image singletons should be linked to an object"; - Object singletonObject = idToObjectMap.get(id); - Class clazz = lookupClass(false, key); - singletonInitializationMap.computeIfAbsent(singletonObject, (k) -> new HashSet<>()); - singletonInitializationMap.get(singletonObject).add(clazz); - } else if (persistInfo == PersistFlags.FORBIDDEN) { - assert id == -1 : "Unrestored image singleton should not be linked to an object"; - Class clazz = lookupClass(false, key); - singletonInitializationMap.computeIfAbsent(forbiddenObject, (k) -> new HashSet<>()); - singletonInitializationMap.get(forbiddenObject).add(clazz); - } else { - assert persistInfo == PersistFlags.NOTHING : "Unexpected PersistFlags value: " + persistInfo; - assert id == -1 : "Unrestored image singleton should not be linked to an object"; - } - } - - return singletonInitializationMap; - } - -} - -class ImageSingletonLoaderImpl implements ImageSingletonLoader { - private final UnmodifiableEconomicMap keyStore; - private final ImageClassLoader imageClassLoader; - - ImageSingletonLoaderImpl(UnmodifiableEconomicMap keyStore, ImageClassLoader imageClassLoader) { - this.keyStore = keyStore; - this.imageClassLoader = imageClassLoader; - } - - @SuppressWarnings("unchecked") - private static T cast(Object object) { - return (T) object; - } - - @Override - public List readBoolList(String keyName) { - List value = cast(keyStore.get(keyName)); - String type = cast(value.get(0)); - assert type.equals("B(") : type; - List internalValue = cast(value.get(1)); - return internalValue.stream().map(e -> e == 1).toList(); - } - - @Override - public int readInt(String keyName) { - List value = cast(keyStore.get(keyName)); - String type = cast(value.get(0)); - assert type.equals("I") : type; - return cast(value.get(1)); - } - - @Override - public List readIntList(String keyName) { - List value = cast(keyStore.get(keyName)); - String type = cast(value.get(0)); - assert type.equals("I(") : type; - return cast(value.get(1)); - } - - @Override - public long readLong(String keyName) { - List value = cast(keyStore.get(keyName)); - String type = cast(value.get(0)); - assert type.equals("L") : type; - Number number = cast(value.get(1)); - return number.longValue(); - } - - @Override - public String readString(String keyName) { - List value = cast(keyStore.get(keyName)); - String type = cast(value.get(0)); - assert type.equals("S") : type; - return cast(value.get(1)); - } - - @Override - public List readStringList(String keyName) { - List value = cast(keyStore.get(keyName)); - String type = cast(value.get(0)); - assert type.equals("S(") : type; - return cast(value.get(1)); - } - - @Override - public Class lookupClass(String className) { - return imageClassLoader.findClass(className).get(); - } -} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoaderHelper.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoaderHelper.java deleted file mode 100644 index c94a24ee9fad..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoaderHelper.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.hosted.heap; - -import static com.oracle.graal.pointsto.heap.ImageLayerLoader.get; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CAPTURING_CLASS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.C_ENTRY_POINT_CALL_STUB_METHOD_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FACTORY_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.GENERATED_SERIALIZATION_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INSTANTIATED_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INTERFACES_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.JNI_JAVA_CALL_VARIANT_WRAPPER_METHOD_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.LAMBDA_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NOT_AS_PUBLISHED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ORIGINAL_METHOD_ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.PROXY_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.RAW_DECLARING_CLASS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.RAW_TARGET_CONSTRUCTOR_CLASS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.REFLECTION_EXPAND_SIGNATURE_METHOD_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.TARGET_CONSTRUCTOR_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.THROW_ALLOCATED_OBJECT_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.WRAPPED_MEMBER_ARGUMENTS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.WRAPPED_MEMBER_CLASS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.WRAPPED_MEMBER_NAME_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.WRAPPED_METHOD_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.WRAPPED_TYPE_TAG; -import static com.oracle.svm.hosted.lambda.LambdaParser.createMethodGraph; -import static com.oracle.svm.hosted.lambda.LambdaParser.getLambdaClassFromConstantNode; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.lang.reflect.Proxy; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.graalvm.collections.EconomicMap; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.c.function.CEntryPoint; - -import com.oracle.graal.pointsto.BigBang; -import com.oracle.graal.pointsto.heap.ImageLayerLoader; -import com.oracle.graal.pointsto.heap.ImageLayerLoaderHelper; -import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.meta.AnalysisUniverse; -import com.oracle.graal.pointsto.meta.BaseLayerMethod; -import com.oracle.svm.core.reflect.serialize.SerializationSupport; -import com.oracle.svm.hosted.FeatureImpl; -import com.oracle.svm.hosted.code.CEntryPointCallStubSupport; -import com.oracle.svm.hosted.code.CEntryPointData; -import com.oracle.svm.hosted.code.FactoryMethodSupport; -import com.oracle.svm.hosted.jni.JNIAccessFeature; -import com.oracle.svm.hosted.lambda.LambdaParser; -import com.oracle.svm.hosted.reflect.ReflectionFeature; -import com.oracle.svm.hosted.reflect.serialize.SerializationFeature; -import com.oracle.svm.util.ReflectionUtil; - -import jdk.graal.compiler.graph.iterators.NodeIterable; -import jdk.graal.compiler.java.BytecodeParser; -import jdk.graal.compiler.nodes.ConstantNode; -import jdk.graal.compiler.nodes.StructuredGraph; -import jdk.graal.compiler.options.OptionValues; -import jdk.internal.reflect.ReflectionFactory; -import jdk.vm.ci.meta.ResolvedJavaMethod; - -public class SVMImageLayerLoaderHelper extends ImageLayerLoaderHelper { - private final Map, Boolean> capturingClasses = new ConcurrentHashMap<>(); - - public SVMImageLayerLoaderHelper(ImageLayerLoader imageLayerLoader) { - super(imageLayerLoader); - } - - @Override - @SuppressWarnings("deprecation") - protected boolean loadType(EconomicMap typeData, int tid) { - String wrappedType = get(typeData, WRAPPED_TYPE_TAG); - if (wrappedType == null) { - return false; - } - if (wrappedType.equals(GENERATED_SERIALIZATION_TAG)) { - String rawDeclaringClassName = get(typeData, RAW_DECLARING_CLASS_TAG); - String rawTargetConstructorClassName = get(typeData, RAW_TARGET_CONSTRUCTOR_CLASS_TAG); - Class rawDeclaringClass = imageLayerLoader.lookupClass(false, rawDeclaringClassName); - Class rawTargetConstructorClass = imageLayerLoader.lookupClass(false, rawTargetConstructorClassName); - SerializationSupport serializationSupport = SerializationSupport.singleton(); - Constructor rawTargetConstructor = ReflectionUtil.lookupConstructor(rawTargetConstructorClass); - Constructor constructor = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(rawDeclaringClass, rawTargetConstructor); - serializationSupport.addConstructorAccessor(rawDeclaringClass, rawTargetConstructorClass, SerializationFeature.getConstructorAccessor(constructor)); - Class constructorAccessor = serializationSupport.getSerializationConstructorAccessor(rawDeclaringClass, rawTargetConstructorClass).getClass(); - imageLayerLoader.getMetaAccess().lookupJavaType(constructorAccessor); - return true; - } else if (wrappedType.equals(LAMBDA_TYPE_TAG)) { - String capturingClassName = get(typeData, CAPTURING_CLASS_TAG); - Class capturingClass = imageLayerLoader.lookupClass(false, capturingClassName); - loadLambdaTypes(capturingClass); - } else if (wrappedType.equals(PROXY_TYPE_TAG)) { - List interfaceIds = get(typeData, INTERFACES_TAG); - Class[] interfaces = interfaceIds.stream().map(i -> imageLayerLoader.getAnalysisType(i).getJavaClass()).toArray(Class[]::new); - /* GR-59854: The deprecation warning comes from this call to Proxy.getProxyClass. */ - Class proxy = Proxy.getProxyClass(interfaces[0].getClassLoader(), interfaces); - imageLayerLoader.getMetaAccess().lookupJavaType(proxy); - return true; - } - - return super.loadType(typeData, tid); - } - - /** - * Load all lambda types of the given capturing class. Each method of the capturing class is - * parsed (see {@link LambdaParser#createMethodGraph(ResolvedJavaMethod, OptionValues)}). The - * lambda types can then be found in the constant nodes of the graphs. - */ - private void loadLambdaTypes(Class capturingClass) { - AnalysisUniverse universe = imageLayerLoader.getUniverse(); - capturingClasses.computeIfAbsent(capturingClass, key -> { - LambdaParser.allExecutablesDeclaredInClass(universe.getOriginalMetaAccess().lookupJavaType(capturingClass)) - .filter(m -> m.getCode() != null) - .forEach(m -> loadLambdaTypes(m, universe.getBigbang())); - return true; - }); - } - - private static void loadLambdaTypes(ResolvedJavaMethod m, BigBang bigBang) { - StructuredGraph graph; - try { - graph = createMethodGraph(m, bigBang.getOptions()); - } catch (NoClassDefFoundError | BytecodeParser.BytecodeParserError e) { - /* Skip the method if it refers to a missing class */ - return; - } - - NodeIterable constantNodes = ConstantNode.getConstantNodes(graph); - - for (ConstantNode cNode : constantNodes) { - Class lambdaClass = getLambdaClassFromConstantNode(cNode); - - if (lambdaClass != null) { - bigBang.getMetaAccess().lookupJavaType(lambdaClass); - } - } - } - - @Override - protected boolean loadMethod(EconomicMap methodData, int mid) { - String wrappedMethod = get(methodData, WRAPPED_METHOD_TAG); - if (wrappedMethod == null) { - return false; - } - if (wrappedMethod.equals(FACTORY_TAG)) { - int constructorId = get(methodData, TARGET_CONSTRUCTOR_TAG); - boolean throwAllocatedObject = get(methodData, THROW_ALLOCATED_OBJECT_TAG); - AnalysisMethod analysisMethod = imageLayerLoader.getAnalysisMethod(constructorId); - if (analysisMethod.wrapped instanceof BaseLayerMethod) { - return false; - } - int instantiatedTypeId = get(methodData, INSTANTIATED_TYPE_TAG); - AnalysisType instantiatedType = imageLayerLoader.getAnalysisType(instantiatedTypeId); - FactoryMethodSupport.singleton().lookup(imageLayerLoader.getMetaAccess(), analysisMethod, instantiatedType, throwAllocatedObject); - return true; - } else if (wrappedMethod.equals(C_ENTRY_POINT_CALL_STUB_METHOD_TAG)) { - int originalMethodId = get(methodData, ORIGINAL_METHOD_ID_TAG); - boolean asNotPublished = get(methodData, NOT_AS_PUBLISHED_TAG); - AnalysisMethod originalMethod = imageLayerLoader.getAnalysisMethod(originalMethodId); - CEntryPointCallStubSupport.singleton().registerStubForMethod(originalMethod, () -> { - CEntryPointData data = CEntryPointData.create(originalMethod); - if (asNotPublished) { - data = data.copyWithPublishAs(CEntryPoint.Publish.NotPublished); - } - return data; - }); - return true; - } else if (wrappedMethod.equals(REFLECTION_EXPAND_SIGNATURE_METHOD_TAG)) { - Executable member = getWrappedMember(methodData); - if (member == null) { - return false; - } - ImageSingletons.lookup(ReflectionFeature.class).getOrCreateAccessor(member); - return true; - } else if (wrappedMethod.equals(JNI_JAVA_CALL_VARIANT_WRAPPER_METHOD_TAG)) { - Executable member = getWrappedMember(methodData); - if (member == null) { - return false; - } - JNIAccessFeature.singleton().addMethod(member, (FeatureImpl.DuringAnalysisAccessImpl) imageLayerLoader.getUniverse().getConcurrentAnalysisAccess()); - return true; - } - return super.loadMethod(methodData, mid); - } - - private Executable getWrappedMember(EconomicMap methodData) { - String className = get(methodData, WRAPPED_MEMBER_CLASS_TAG); - Class declaringClass = imageLayerLoader.lookupClass(true, className); - if (declaringClass == null) { - return null; - } - String name = get(methodData, WRAPPED_MEMBER_NAME_TAG); - List parameterNames = get(methodData, WRAPPED_MEMBER_ARGUMENTS_TAG); - Class[] parameters = parameterNames.stream().map(c -> imageLayerLoader.lookupClass(false, c)).toArray(Class[]::new); - return ImageLayerLoader.lookupMethodByReflection(name, declaringClass, parameters); - } -} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java deleted file mode 100644 index dd58ed3ad8d7..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java +++ /dev/null @@ -1,392 +0,0 @@ -/* - * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.hosted.heap; - -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ANNOTATIONS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ANNOTATION_VALUES_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CLASS_ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.C_ENTRY_POINT_LITERAL_CODE_POINTER; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ENUM_CLASS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ENUM_NAME_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FIELD_CHECK_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.HUB_IDENTITY_HASH_CODE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IMAGE_SINGLETON_KEYS; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IMAGE_SINGLETON_OBJECTS; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INFO_CLASS_INITIALIZER_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INFO_HAS_INITIALIZER_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INFO_IS_BUILD_TIME_INITIALIZED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INFO_IS_INITIALIZED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INFO_IS_IN_ERROR_STATE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INFO_IS_LINKED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INFO_IS_TRACKED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_FAILED_NO_TRACKING_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INITIALIZED_AT_BUILD_TIME_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INITIALIZED_NO_TRACKING_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_NO_INITIALIZER_NO_TRACKING_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.LOCATION_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.METHOD_POINTER_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.OBJECT_OFFSET_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.STATIC_OBJECT_FIELDS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.STATIC_PRIMITIVE_FIELDS_TAG; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.graalvm.collections.EconomicMap; -import org.graalvm.nativeimage.AnnotationAccess; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.c.function.RelocatedPointer; -import org.graalvm.nativeimage.impl.CEntryPointLiteralCodePointer; - -import com.oracle.graal.pointsto.BigBang; -import com.oracle.graal.pointsto.api.HostVM; -import com.oracle.graal.pointsto.heap.ImageHeapConstant; -import com.oracle.graal.pointsto.heap.ImageLayerWriter; -import com.oracle.graal.pointsto.meta.AnalysisField; -import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.svm.core.FunctionPointerHolder; -import com.oracle.svm.core.StaticFieldsSupport; -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.classinitialization.ClassInitializationInfo; -import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter; -import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; -import com.oracle.svm.core.layeredimagesingleton.RuntimeOnlyWrapper; -import com.oracle.svm.core.meta.MethodPointer; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.SVMHost; -import com.oracle.svm.hosted.annotation.AnnotationMemberValue; -import com.oracle.svm.hosted.annotation.AnnotationMetadata; -import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; -import com.oracle.svm.hosted.fieldfolding.StaticFinalFieldFoldingFeature; -import com.oracle.svm.hosted.image.NativeImageHeap; -import com.oracle.svm.hosted.image.NativeImageHeap.ObjectInfo; -import com.oracle.svm.hosted.imagelayer.HostedDynamicLayerInfo; -import com.oracle.svm.hosted.lambda.LambdaSubstitutionType; -import com.oracle.svm.hosted.lambda.StableLambdaProxyNameFeature; -import com.oracle.svm.hosted.meta.HostedField; -import com.oracle.svm.hosted.meta.HostedMethod; -import com.oracle.svm.hosted.meta.HostedUniverse; -import com.oracle.svm.hosted.meta.RelocatableConstant; -import com.oracle.svm.hosted.methodhandles.MethodHandleFeature; -import com.oracle.svm.hosted.methodhandles.MethodHandleInvokerSubstitutionType; -import com.oracle.svm.hosted.reflect.proxy.ProxyRenamingSubstitutionProcessor; -import com.oracle.svm.hosted.reflect.proxy.ProxySubstitutionType; -import com.oracle.svm.util.LogUtils; -import com.oracle.svm.util.ModuleSupport; - -import jdk.graal.compiler.debug.Assertions; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; -import sun.reflect.annotation.AnnotationType; - -public class SVMImageLayerWriter extends ImageLayerWriter { - private NativeImageHeap nativeImageHeap; - private HostedUniverse hUniverse; - - public SVMImageLayerWriter(boolean useSharedLayerGraphs) { - super(useSharedLayerGraphs); - } - - public void setNativeImageHeap(NativeImageHeap nativeImageHeap) { - this.nativeImageHeap = nativeImageHeap; - } - - public void setHostedUniverse(HostedUniverse hUniverse) { - this.hUniverse = hUniverse; - } - - @Override - protected void persistAnnotations(AnnotatedElement annotatedElement, EconomicMap elementMap, Class[] annotationTypes) { - elementMap.put(ANNOTATION_VALUES_TAG, Arrays.stream(annotationTypes).map(annotationClass -> { - EconomicMap members = EconomicMap.create(); - AnnotationType annotationType = AnnotationType.getInstance(annotationClass); - Annotation annotation = AnnotationAccess.getAnnotation(annotatedElement, annotationClass); - annotationType.members().forEach((memberName, memberAccessor) -> { - try { - String moduleName = memberAccessor.getDeclaringClass().getModule().getName(); - if (moduleName != null) { - ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, SVMImageLayerWriter.class, false, moduleName); - } - AnnotationMemberValue memberValue = AnnotationMemberValue.getMemberValue(annotation, memberName, memberAccessor, annotationType); - Object value = memberValue.get(annotationType.memberTypes().get(memberName)); - if (value.getClass().isEnum()) { - HashMap enumEncoding = new HashMap<>(); - enumEncoding.put(ENUM_CLASS_TAG, value.getClass().getName()); - enumEncoding.put(ENUM_NAME_TAG, value.toString()); - value = enumEncoding; - } - members.put(memberName, value); - } catch (AnnotationMetadata.AnnotationExtractionError e) { - /* We skip the incorrect annotation */ - } - }); - return members; - }).toList()); - elementMap.put(ANNOTATIONS_TAG, Arrays.stream(annotationTypes).map(Class::getName).toList()); - super.persistAnnotations(annotatedElement, elementMap, annotationTypes); - } - - @Override - protected void persistHook() { - ImageHeapConstant staticPrimitiveFields = (ImageHeapConstant) hUniverse.getSnippetReflection().forObject(StaticFieldsSupport.getStaticPrimitiveFields()); - ImageHeapConstant staticObjectFields = (ImageHeapConstant) hUniverse.getSnippetReflection().forObject(StaticFieldsSupport.getStaticObjectFields()); - - jsonMap.put(STATIC_PRIMITIVE_FIELDS_TAG, getConstantId(staticPrimitiveFields)); - jsonMap.put(STATIC_OBJECT_FIELDS_TAG, getConstantId(staticObjectFields)); - } - - @Override - protected void persistType(AnalysisType type, EconomicMap typeMap) { - HostVM hostVM = aUniverse.hostVM(); - SVMHost svmHost = (SVMHost) hostVM; - DynamicHub hub = svmHost.dynamicHub(type); - typeMap.put(HUB_IDENTITY_HASH_CODE_TAG, System.identityHashCode(hub)); - - typeMap.put(IS_INITIALIZED_AT_BUILD_TIME_TAG, ClassInitializationSupport.singleton().maybeInitializeAtBuildTime(type)); - - ClassInitializationInfo info = hub.getClassInitializationInfo(); - if (info != null) { - typeMap.put(IS_NO_INITIALIZER_NO_TRACKING_TAG, info == ClassInitializationInfo.forNoInitializerInfo(false)); - typeMap.put(IS_INITIALIZED_NO_TRACKING_TAG, info == ClassInitializationInfo.forInitializedInfo(false)); - typeMap.put(IS_FAILED_NO_TRACKING_TAG, info == ClassInitializationInfo.forFailedInfo(false)); - typeMap.put(INFO_IS_INITIALIZED_TAG, info.isInitialized()); - typeMap.put(INFO_IS_IN_ERROR_STATE_TAG, info.isInErrorState()); - typeMap.put(INFO_IS_LINKED_TAG, info.isLinked()); - typeMap.put(INFO_HAS_INITIALIZER_TAG, info.hasInitializer()); - typeMap.put(INFO_IS_BUILD_TIME_INITIALIZED_TAG, info.isBuildTimeInitialized()); - typeMap.put(INFO_IS_TRACKED_TAG, info.isTracked()); - FunctionPointerHolder classInitializer = info.getClassInitializer(); - if (classInitializer != null) { - MethodPointer methodPointer = (MethodPointer) classInitializer.functionPointer; - AnalysisMethod classInitializerMethod = (AnalysisMethod) methodPointer.getMethod(); - typeMap.put(INFO_CLASS_INITIALIZER_TAG, classInitializerMethod.getId()); - } - } - - super.persistType(type, typeMap); - } - - @Override - public void checkTypeStability(AnalysisType type) { - /* - * Lambda functions containing the same method invocations will return the same hash. They - * will still have a different name, but in a multi threading context, the names can be - * switched. - */ - if (type.getWrapped() instanceof LambdaSubstitutionType lambdaSubstitutionType) { - StableLambdaProxyNameFeature stableLambdaProxyNameFeature = ImageSingletons.lookup(StableLambdaProxyNameFeature.class); - if (!stableLambdaProxyNameFeature.getLambdaSubstitutionProcessor().isNameAlwaysStable(lambdaSubstitutionType.getName())) { - String message = "The lambda method " + lambdaSubstitutionType.getName() + " might not have a stable name in the extension image."; - handleNameConflict(message); - } - } - /* - * Method handle with the same inner method handles will return the same hash. They will - * still have a different name, but in a multi threading context, the names can be switched. - */ - if (type.getWrapped() instanceof MethodHandleInvokerSubstitutionType methodHandleSubstitutionType) { - MethodHandleFeature methodHandleFeature = ImageSingletons.lookup(MethodHandleFeature.class); - if (!methodHandleFeature.getMethodHandleSubstitutionProcessor().isNameAlwaysStable(methodHandleSubstitutionType.getName())) { - String message = "The method handle " + methodHandleSubstitutionType.getName() + " might not have a stable name in the extension image."; - handleNameConflict(message); - } - } - - if (type.getWrapped() instanceof ProxySubstitutionType proxySubstitutionType) { - if (!ProxyRenamingSubstitutionProcessor.isNameAlwaysStable(proxySubstitutionType.getName())) { - String message = "The Proxy type " + proxySubstitutionType.getName() + " might not have a stable name in the extension image."; - handleNameConflict(message); - } - } - } - - private static void handleNameConflict(String message) { - if (SubstrateOptions.AbortOnNameConflict.getValue()) { - throw VMError.shouldNotReachHere(message); - } else { - LogUtils.warning(message); - } - } - - @Override - public void persistMethod(AnalysisMethod method, EconomicMap methodMap) { - super.persistMethod(method, methodMap); - - // register this method as persisted for name resolution - HostedDynamicLayerInfo.singleton().recordPersistedMethod(hUniverse.lookup(method)); - } - - @Override - protected void persistField(AnalysisField field, EconomicMap fieldMap) { - HostedField hostedField = hUniverse.lookup(field); - int location = hostedField.getLocation(); - if (location > 0) { - fieldMap.put(LOCATION_TAG, location); - } - Integer fieldCheck = StaticFinalFieldFoldingFeature.singleton().getFieldCheckIndex(field); - if (fieldCheck != null) { - fieldMap.put(FIELD_CHECK_TAG, fieldCheck); - } - super.persistField(field, fieldMap); - } - - @Override - protected void persistConstant(int parentId, int index, ImageHeapConstant imageHeapConstant, EconomicMap constantMap) { - ObjectInfo objectInfo = nativeImageHeap.getConstantInfo(imageHeapConstant); - if (objectInfo != null) { - constantMap.put(OBJECT_OFFSET_TAG, String.valueOf(objectInfo.getOffset())); - } - super.persistConstant(parentId, index, imageHeapConstant, constantMap); - } - - @Override - public void persistConstantRelinkingInfo(EconomicMap constantMap, BigBang bb, Class clazz, JavaConstant hostedObject, int id) { - ResolvedJavaType type = bb.getConstantReflectionProvider().asJavaType(hostedObject); - if (type instanceof AnalysisType analysisType) { - constantMap.put(CLASS_ID_TAG, analysisType.getId()); - constantsToRelink.add(id); - } else { - super.persistConstantRelinkingInfo(constantMap, bb, clazz, hostedObject, id); - } - } - - @Override - protected boolean delegateProcessing(List> data, Object constant) { - if (constant instanceof RelocatableConstant relocatableConstant) { - RelocatedPointer pointer = relocatableConstant.getPointer(); - if (pointer instanceof MethodPointer methodPointer) { - AnalysisMethod method = getRelocatableConstantMethod(methodPointer); - persistMethod(method); - data.add(List.of(METHOD_POINTER_TAG, method.getId())); - return true; - } else if (pointer instanceof CEntryPointLiteralCodePointer cEntryPointLiteralCodePointer) { - data.add(List.of(C_ENTRY_POINT_LITERAL_CODE_POINTER, cEntryPointLiteralCodePointer.methodName, cEntryPointLiteralCodePointer.definingClass.getName(), - Arrays.stream(cEntryPointLiteralCodePointer.parameterTypes).map(Class::getName))); - return true; - } - } - return super.delegateProcessing(data, constant); - } - - private static AnalysisMethod getRelocatableConstantMethod(MethodPointer methodPointer) { - ResolvedJavaMethod method = methodPointer.getMethod(); - if (method instanceof HostedMethod hostedMethod) { - return hostedMethod.wrapped; - } else { - return (AnalysisMethod) method; - } - } - - record SingletonPersistInfo(LayeredImageSingleton.PersistFlags flags, int id, EconomicMap keyStore) { - } - - public void writeImageSingletonInfo(List, Object>> layeredImageSingletons) { - List singletonsList = new ArrayList<>(); - Map singletonInfoMap = new HashMap<>(); - int nextID = 1; - for (var singletonInfo : layeredImageSingletons) { - LayeredImageSingleton singleton; - if (singletonInfo.getValue() instanceof RuntimeOnlyWrapper wrapper) { - singleton = wrapper.wrappedObject(); - } else { - singleton = (LayeredImageSingleton) singletonInfo.getValue(); - } - String key = singletonInfo.getKey().getName(); - if (!singletonInfoMap.containsKey(singleton)) { - var writer = new ImageSingletonWriterImpl(); - var flags = singleton.preparePersist(writer); - boolean persistData = flags == LayeredImageSingleton.PersistFlags.CREATE; - var info = new SingletonPersistInfo(flags, persistData ? nextID++ : -1, persistData ? writer.getKeyValueStore() : null); - singletonInfoMap.put(singleton, info); - } - var info = singletonInfoMap.get(singleton); - singletonsList.add(List.of(key, info.flags.ordinal(), info.id)); - } - jsonMap.put(IMAGE_SINGLETON_KEYS, singletonsList); - - List objectList = new ArrayList<>(); - var sortedByIDs = singletonInfoMap.entrySet().stream().filter(e -> e.getValue().flags == LayeredImageSingleton.PersistFlags.CREATE).sorted(Comparator.comparingInt(e -> e.getValue().id)) - .toList(); - for (var entry : sortedByIDs) { - var info = entry.getValue(); - objectList.add(List.of(info.id, entry.getKey().getClass().getName(), info.keyStore)); - } - jsonMap.put(IMAGE_SINGLETON_OBJECTS, objectList); - } -} - -class ImageSingletonWriterImpl implements ImageSingletonWriter { - private final EconomicMap keyValueStore = EconomicMap.create(); - - EconomicMap getKeyValueStore() { - return keyValueStore; - } - - @Override - public void writeBoolList(String keyName, List value) { - var internalValue = value.stream().map(e -> e ? 1 : 0).toList(); - var previous = keyValueStore.put(keyName, List.of("B(", internalValue)); - assert previous == null : previous; - } - - @Override - public void writeInt(String keyName, int value) { - var previous = keyValueStore.put(keyName, List.of("I", value)); - assert previous == null : previous; - } - - @Override - public void writeIntList(String keyName, List value) { - var previous = keyValueStore.put(keyName, List.of("I(", value)); - assert previous == null : Assertions.errorMessage(keyName, previous); - } - - @Override - public void writeLong(String keyName, long value) { - var previous = keyValueStore.put(keyName, List.of("L", value)); - assert previous == null : Assertions.errorMessage(keyName, previous); - } - - @Override - public void writeString(String keyName, String value) { - var previous = keyValueStore.put(keyName, List.of("S", value)); - assert previous == null : Assertions.errorMessage(keyName, previous); - } - - @Override - public void writeStringList(String keyName, List value) { - var previous = keyValueStore.put(keyName, List.of("S(", value)); - assert previous == null : Assertions.errorMessage(keyName, previous); - } -} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriterHelper.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriterHelper.java deleted file mode 100644 index fd7f9f8cb4f5..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriterHelper.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.hosted.heap; - -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CAPTURING_CLASS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CONSTRUCTOR_NAME; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.C_ENTRY_POINT_CALL_STUB_METHOD_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FACTORY_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.GENERATED_SERIALIZATION_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.INSTANTIATED_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.JNI_JAVA_CALL_VARIANT_WRAPPER_METHOD_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.LAMBDA_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.NOT_AS_PUBLISHED_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.ORIGINAL_METHOD_ID_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.PROXY_TYPE_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.RAW_DECLARING_CLASS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.RAW_TARGET_CONSTRUCTOR_CLASS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.REFLECTION_EXPAND_SIGNATURE_METHOD_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.TARGET_CONSTRUCTOR_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.THROW_ALLOCATED_OBJECT_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.WRAPPED_MEMBER_ARGUMENTS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.WRAPPED_MEMBER_CLASS_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.WRAPPED_MEMBER_NAME_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.WRAPPED_METHOD_TAG; -import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.WRAPPED_TYPE_TAG; -import static com.oracle.svm.hosted.heap.SVMImageLayerSnapshotUtil.GENERATED_SERIALIZATION; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.util.Arrays; - -import org.graalvm.collections.EconomicMap; - -import com.oracle.graal.pointsto.heap.ImageLayerWriter; -import com.oracle.graal.pointsto.heap.ImageLayerWriterHelper; -import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.svm.core.reflect.serialize.SerializationSupport; -import com.oracle.svm.hosted.code.CEntryPointCallStubMethod; -import com.oracle.svm.hosted.code.CEntryPointCallStubSupport; -import com.oracle.svm.hosted.code.FactoryMethod; -import com.oracle.svm.hosted.jni.JNIJavaCallVariantWrapperMethod; -import com.oracle.svm.hosted.reflect.ReflectionExpandSignatureMethod; -import com.oracle.svm.hosted.reflect.proxy.ProxyRenamingSubstitutionProcessor; - -import jdk.graal.compiler.java.LambdaUtils; - -public class SVMImageLayerWriterHelper extends ImageLayerWriterHelper { - public SVMImageLayerWriterHelper(ImageLayerWriter imageLayerWriter) { - super(imageLayerWriter); - } - - @Override - protected void persistType(AnalysisType type, EconomicMap typeMap) { - if (type.toJavaName(true).contains(GENERATED_SERIALIZATION)) { - typeMap.put(WRAPPED_TYPE_TAG, GENERATED_SERIALIZATION_TAG); - var key = SerializationSupport.singleton().getKeyFromConstructorAccessorClass(type.getJavaClass()); - typeMap.put(RAW_DECLARING_CLASS_TAG, key.getDeclaringClass().getName()); - typeMap.put(RAW_TARGET_CONSTRUCTOR_CLASS_TAG, key.getTargetConstructorClass().getName()); - } else if (LambdaUtils.isLambdaType(type)) { - typeMap.put(WRAPPED_TYPE_TAG, LAMBDA_TYPE_TAG); - typeMap.put(CAPTURING_CLASS_TAG, LambdaUtils.capturingClass(type.toJavaName())); - } else if (ProxyRenamingSubstitutionProcessor.isProxyType(type)) { - typeMap.put(WRAPPED_TYPE_TAG, PROXY_TYPE_TAG); - } - super.persistType(type, typeMap); - } - - @Override - protected void persistMethod(AnalysisMethod method, EconomicMap methodMap) { - if (method.wrapped instanceof FactoryMethod factoryMethod) { - methodMap.put(WRAPPED_METHOD_TAG, FACTORY_TAG); - AnalysisMethod targetConstructor = method.getUniverse().lookup(factoryMethod.getTargetConstructor()); - imageLayerWriter.persistAnalysisParsedGraph(targetConstructor); - imageLayerWriter.persistMethod(targetConstructor); - methodMap.put(TARGET_CONSTRUCTOR_TAG, targetConstructor.getId()); - methodMap.put(THROW_ALLOCATED_OBJECT_TAG, factoryMethod.throwAllocatedObject()); - AnalysisType instantiatedType = method.getUniverse().lookup(factoryMethod.getInstantiatedType()); - methodMap.put(INSTANTIATED_TYPE_TAG, instantiatedType.getId()); - } else if (method.wrapped instanceof CEntryPointCallStubMethod cEntryPointCallStubMethod) { - methodMap.put(WRAPPED_METHOD_TAG, C_ENTRY_POINT_CALL_STUB_METHOD_TAG); - AnalysisMethod originalMethod = CEntryPointCallStubSupport.singleton().getMethodForStub(cEntryPointCallStubMethod); - methodMap.put(ORIGINAL_METHOD_ID_TAG, originalMethod.getId()); - methodMap.put(NOT_AS_PUBLISHED_TAG, cEntryPointCallStubMethod.isNotPublished()); - } else if (method.wrapped instanceof ReflectionExpandSignatureMethod reflectionExpandSignatureMethod) { - methodMap.put(WRAPPED_METHOD_TAG, REFLECTION_EXPAND_SIGNATURE_METHOD_TAG); - Executable member = reflectionExpandSignatureMethod.getMember(); - persistWrappedMember(methodMap, member); - } else if (method.wrapped instanceof JNIJavaCallVariantWrapperMethod jniJavaCallVariantWrapperMethod) { - Executable executable = jniJavaCallVariantWrapperMethod.getMember(); - methodMap.put(WRAPPED_METHOD_TAG, JNI_JAVA_CALL_VARIANT_WRAPPER_METHOD_TAG); - persistWrappedMember(methodMap, executable); - } - super.persistMethod(method, methodMap); - } - - private static void persistWrappedMember(EconomicMap methodMap, Executable member) { - methodMap.put(WRAPPED_MEMBER_CLASS_TAG, member.getDeclaringClass().getName()); - methodMap.put(WRAPPED_MEMBER_NAME_TAG, member instanceof Constructor ? CONSTRUCTOR_NAME : member.getName()); - methodMap.put(WRAPPED_MEMBER_ARGUMENTS_TAG, Arrays.stream(member.getParameters()).map(p -> p.getType().getName()).toList()); - } -} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/LIRNativeImageCodeCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/LIRNativeImageCodeCache.java index 61762f2f47ce..d66ad09a1058 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/LIRNativeImageCodeCache.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/LIRNativeImageCodeCache.java @@ -384,7 +384,7 @@ public void patchMethods(DebugContext debug, RelocatableBuffer relocs, ObjectFil // which is also in the code cache (a.k.a. a section-local call). // This will change, and we will have to case-split here... but not yet. HostedMethod callTarget = (HostedMethod) call.target; - VMError.guarantee(!callTarget.wrapped.isInBaseLayer(), "Unexpected direct call to base layer method %s. These calls are currently lowered to indirect calls.", callTarget); + VMError.guarantee(!callTarget.isCompiledInPriorLayer(), "Unexpected direct call to base layer method %s. These calls are currently lowered to indirect calls.", callTarget); int callTargetStart = callTarget.getCodeAddressOffset(); if (trampolineOffsetMap != null && trampolineOffsetMap.containsKey(callTarget)) { callTargetStart = trampolineOffsetMap.get(callTarget); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java index 6ca23e3b36cb..e96bd05d7522 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java @@ -24,8 +24,8 @@ */ package com.oracle.svm.hosted.image; -import static com.oracle.svm.core.SubstrateOptions.SpawnIsolates; import static com.oracle.svm.core.SubstrateOptions.MremapImageHeap; +import static com.oracle.svm.core.SubstrateOptions.SpawnIsolates; import static com.oracle.svm.core.SubstrateUtil.mangleName; import static com.oracle.svm.core.util.VMError.shouldNotReachHere; @@ -443,7 +443,7 @@ public void build(String imageName, DebugContext debug) { long imageHeapSize = getImageHeapSize(); if (ImageLayerBuildingSupport.buildingSharedLayer()) { - HostedImageLayerBuildingSupport.singleton().getWriter().persistImageHeapSize(imageHeapSize); + HostedImageLayerBuildingSupport.singleton().getWriter().setImageHeapSize(imageHeapSize); } // Text section (code) @@ -504,7 +504,7 @@ public void build(String imageName, DebugContext debug) { objectFile.createDefinedSymbol(heapSection.getName(), heapSection, 0, 0, false, false); long sectionOffsetOfARelocatablePointer = writer.writeHeap(debug, heapSectionBuffer); - if (SpawnIsolates.getValue()) { + if (!ImageLayerBuildingSupport.buildingImageLayer() && SpawnIsolates.getValue()) { if (heapLayout.getReadOnlyRelocatableSize() == 0) { /* * When there isn't a read only relocation section, the value of the relocatable @@ -530,7 +530,10 @@ public void build(String imageName, DebugContext debug) { defineDataSymbol(Isolates.IMAGE_HEAP_RELOCATABLE_BEGIN_SYMBOL_NAME, heapSection, heapLayout.getReadOnlyRelocatableOffset() - heapLayout.getStartOffset()); defineDataSymbol(Isolates.IMAGE_HEAP_RELOCATABLE_END_SYMBOL_NAME, heapSection, heapLayout.getReadOnlyRelocatableOffset() + heapLayout.getReadOnlyRelocatableSize() - heapLayout.getStartOffset()); - defineDataSymbol(Isolates.IMAGE_HEAP_A_RELOCATABLE_POINTER_SYMBOL_NAME, heapSection, sectionOffsetOfARelocatablePointer); + if (!ImageLayerBuildingSupport.buildingImageLayer()) { + /* Layered native-image builds do not use this symbol. */ + defineDataSymbol(Isolates.IMAGE_HEAP_A_RELOCATABLE_POINTER_SYMBOL_NAME, heapSection, sectionOffsetOfARelocatablePointer); + } defineDataSymbol(Isolates.IMAGE_HEAP_WRITABLE_BEGIN_SYMBOL_NAME, heapSection, heapLayout.getWritableOffset() - heapLayout.getStartOffset()); defineDataSymbol(Isolates.IMAGE_HEAP_WRITABLE_END_SYMBOL_NAME, heapSection, heapLayout.getWritableOffset() + heapLayout.getWritableSize() - heapLayout.getStartOffset()); defineDataSymbol(Isolates.IMAGE_HEAP_WRITABLE_PATCHED_BEGIN_SYMBOL_NAME, heapSection, heapLayout.getWritablePatchedOffset() - heapLayout.getStartOffset()); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java index 8b6b69841020..e90a74dd6e52 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java @@ -87,7 +87,7 @@ public String uniqueShortLoaderName(ClassLoader loader) { if (isGraalImageLoader(loader)) { return ""; } - String name = SubstrateUtil.classLoaderNameAndId(loader); + String name = SubstrateUtil.runtimeClassLoaderNameAndId(loader); // name will look like "org.foo.bar.FooBarClassLoader @1234" // trim it down to something more manageable // escaping quotes in the classlaoder name does not work in GDB diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java index 863c241d40a5..4a45b8784331 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java @@ -49,12 +49,12 @@ import java.util.Set; import java.util.stream.Collectors; +import jdk.graal.compiler.word.Word; import org.graalvm.collections.Pair; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.c.function.CFunctionPointer; import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; import com.oracle.graal.pointsto.AbstractAnalysisEngine; import com.oracle.graal.pointsto.BigBang; @@ -290,7 +290,7 @@ public int getAlignedConstantsSize() { } public void buildRuntimeMetadata(DebugContext debug, SnippetReflectionProvider snippetReflectionProvider) { - buildRuntimeMetadata(debug, snippetReflectionProvider, new MethodPointer(getFirstCompilation().getLeft(), true), WordFactory.signed(getCodeAreaSize())); + buildRuntimeMetadata(debug, snippetReflectionProvider, new MethodPointer(getFirstCompilation().getLeft(), true), Word.signed(getCodeAreaSize())); } static class HostedConstantAccess extends ConstantAccess { @@ -435,6 +435,8 @@ protected void buildRuntimeMetadata(DebugContext debug, SnippetReflectionProvide } } + watchdog.recordActivity(); + if (SubstrateOptions.IncludeMethodData.getValue()) { for (HostedField field : hUniverse.getFields()) { if (field.isAccessed() && !field.getWrapped().isInBaseLayer() && !includedFields.contains(field.getWrapped())) { @@ -528,7 +530,7 @@ protected HostedImageCodeInfo installCodeInfo(SnippetReflectionProvider snippetR imageCodeInfo.setCodeStart(firstMethod); imageCodeInfo.setCodeSize(codeSize); imageCodeInfo.setDataOffset(codeSize); - imageCodeInfo.setDataSize(WordFactory.zero()); // (only for data immediately after code) + imageCodeInfo.setDataSize(Word.zero()); // (only for data immediately after code) imageCodeInfo.setCodeAndDataMemorySize(codeSize); return imageCodeInfo; } 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 9edd872061b6..818cb347bde3 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 @@ -31,10 +31,10 @@ import java.util.function.Supplier; import com.oracle.svm.core.ReservedRegisters; +import jdk.graal.compiler.word.Word; 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; @@ -118,11 +118,11 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { access.registerAsAccessed(ReflectionUtil.lookupField(ClassLoader.class, "nameAndId")); CompressEncoding compressEncoding = ImageSingletons.lookup(CompressEncoding.class); - CGlobalData compressionShift = CGlobalDataFactory.createWord(WordFactory.signed(compressEncoding.getShift()), "__svm_compression_shift"); - CGlobalData useHeapBase = CGlobalDataFactory.createWord(WordFactory.unsigned(compressEncoding.hasBase() ? 1 : 0), "__svm_use_heap_base"); - CGlobalData reservedBitsMask = CGlobalDataFactory.createWord(WordFactory.unsigned(Heap.getHeap().getObjectHeader().getReservedBitsMask()), "__svm_reserved_bits_mask"); - CGlobalData objectAlignment = CGlobalDataFactory.createWord(WordFactory.unsigned(ConfigurationValues.getObjectLayout().getAlignment()), "__svm_object_alignment"); - CGlobalData heapBaseRegnum = CGlobalDataFactory.createWord(WordFactory.unsigned(ReservedRegisters.singleton().getHeapBaseRegister().number), "__svm_heap_base_regnum"); + CGlobalData compressionShift = CGlobalDataFactory.createWord(Word.signed(compressEncoding.getShift()), "__svm_compression_shift"); + CGlobalData useHeapBase = CGlobalDataFactory.createWord(Word.unsigned(compressEncoding.hasBase() ? 1 : 0), "__svm_use_heap_base"); + CGlobalData reservedBitsMask = CGlobalDataFactory.createWord(Word.unsigned(Heap.getHeap().getObjectHeader().getReservedBitsMask()), "__svm_reserved_bits_mask"); + CGlobalData objectAlignment = CGlobalDataFactory.createWord(Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()), "__svm_object_alignment"); + CGlobalData heapBaseRegnum = CGlobalDataFactory.createWord(Word.unsigned(ReservedRegisters.singleton().getHeapBaseRegister().number), "__svm_heap_base_regnum"); CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(compressionShift); CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(useHeapBase); CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(reservedBitsMask); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java index f91bd5a94d11..d2e22544f084 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java @@ -93,6 +93,7 @@ import jdk.graal.compiler.debug.DebugContext; import jdk.graal.compiler.graph.NodeSourcePosition; import jdk.graal.compiler.java.StableMethodNameFormatter; +import jdk.graal.compiler.util.Digest; import jdk.vm.ci.aarch64.AArch64; import jdk.vm.ci.amd64.AMD64; import jdk.vm.ci.code.BytecodeFrame; @@ -248,6 +249,11 @@ public void debugContext(Consumer action) { } } + @Override + public long typeSignature(String prefix) { + return Digest.digestAsUUID(prefix + typeName()).getLeastSignificantBits(); + } + public String toJavaName(@SuppressWarnings("hiding") HostedType hostedType) { return getDeclaringClass(hostedType, true).toJavaName(); } @@ -335,6 +341,11 @@ public String typeName() { return typeName; } + @Override + public long typeSignature(String prefix) { + return Digest.digestAsUUID(typeName).getLeastSignificantBits(); + } + @Override public DebugTypeKind typeKind() { return DebugTypeKind.HEADER; @@ -459,6 +470,11 @@ private class NativeImageDebugInstanceTypeInfo extends NativeImageDebugTypeInfo super(hostedType); } + @Override + public long typeSignature(String prefix) { + return super.typeSignature(prefix + loaderName()); + } + @Override public DebugTypeKind typeKind() { return DebugTypeKind.INSTANCE; @@ -466,7 +482,6 @@ public DebugTypeKind typeKind() { @Override public String loaderName() { - return UniqueShortNameProvider.singleton().uniqueShortLoaderName(hostedType.getJavaClass().getClassLoader()); } @@ -814,6 +829,19 @@ void addField(String name, ResolvedJavaType valueType, int offset, @SuppressWarn fieldInfos.add(fieldinfo); } + @Override + public long typeSignature(String prefix) { + HostedType elementType = hostedType.getComponentType(); + while (elementType.isArray()) { + elementType = elementType.getComponentType(); + } + String loaderId = ""; + if (elementType.isInstanceClass() || elementType.isInterface() || elementType.isEnum()) { + loaderId = UniqueShortNameProvider.singleton().uniqueShortLoaderName(elementType.getJavaClass().getClassLoader()); + } + return super.typeSignature(prefix + loaderId); + } + @Override public DebugTypeKind typeKind() { return DebugTypeKind.ARRAY; @@ -849,6 +877,15 @@ private class NativeImageDebugPrimitiveTypeInfo extends NativeImageDebugTypeInfo this.primitiveType = primitiveType; } + @Override + public long typeSignature(String prefix) { + /* + * primitive types never need an indirection so use the same signature for places where + * we might want a special type + */ + return super.typeSignature(""); + } + @Override public DebugTypeKind typeKind() { return DebugTypeKind.PRIMITIVE; @@ -2208,9 +2245,7 @@ public boolean usesStack() { public class NativeImageDebugLocalInfo implements DebugLocalInfo { protected final String name; - protected final ResolvedJavaType type; - protected final ResolvedJavaType valueType; - protected final String typeName; + protected ResolvedJavaType type; protected final JavaKind kind; protected int slot; protected int line; @@ -2223,14 +2258,14 @@ public class NativeImageDebugLocalInfo implements DebugLocalInfo { // if we don't have a type default it for the JavaKind // it may still end up null when kind is Undefined. this.type = (resolvedType != null ? resolvedType : hostedTypeForKind(kind)); - - this.valueType = (type != null && type instanceof HostedType) ? getOriginal((HostedType) type) : type; - this.typeName = valueType == null ? "" : valueType().toJavaName(); } @Override public ResolvedJavaType valueType() { - return valueType; + if (type != null && type instanceof HostedType) { + return getOriginal((HostedType) type); + } + return type; } @Override @@ -2240,7 +2275,8 @@ public String name() { @Override public String typeName() { - return typeName; + ResolvedJavaType valueType = valueType(); + return (valueType == null ? "" : valueType().toJavaName()); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java index 1791478301fd..873876286db0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java @@ -647,9 +647,9 @@ private void addObjectToImageHeap(final JavaConstant constant, boolean immutable * elements, we only want the JavaConstant -> ObjectInfo mapping for base layer constants that * are reachable from regular constants in this layer. */ - private boolean processBaseLayerConstant(JavaConstant constant, ObjectInfo info) { + private static boolean processBaseLayerConstant(JavaConstant constant, ObjectInfo info) { if (((ImageHeapConstant) constant).isInBaseLayer()) { - info.setOffsetInPartition(aUniverse.getImageLayerLoader().getObjectOffset(constant)); + info.setOffsetInPartition(HostedImageLayerBuildingSupport.singleton().getLoader().getObjectOffset(constant)); info.setHeapPartition(BASE_LAYER_PARTITION); return true; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistryFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistryFeature.java index 3b170b85cff3..bffef42ff9e0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistryFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistryFeature.java @@ -36,7 +36,6 @@ import com.oracle.graal.pointsto.heap.ImageHeapConstant; import com.oracle.graal.pointsto.heap.ImageHeapRelocatableConstant; -import com.oracle.graal.pointsto.heap.ImageLayerLoader; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; @@ -67,7 +66,7 @@ private record FutureConstantCandidateInfo(ImageHeapRelocatableConstant constant ImageLayerIdTrackingSingleton tracker; boolean candidateRegistrySealed = false; boolean patchingSealed = false; - ImageLayerLoader loader; + SVMImageLayerLoader loader; Map constantCandidates; Map requiredConstants; @@ -112,7 +111,7 @@ public void afterRegistration(AfterRegistrationAccess access) { @Override public void duringSetup(DuringSetupAccess access) { var config = (FeatureImpl.DuringSetupAccessImpl) access; - loader = config.getUniverse().getImageLayerLoader(); + loader = HostedImageLayerBuildingSupport.singleton().getLoader(); LayeredImageHeapObjectAdder.singleton().registerObjectAdder(this::addInitialObjects); var registry = CrossLayerConstantRegistry.singletonOrNull(); config.registerObjectToConstantReplacer(obj -> replacePriorMarkersWithConstant(registry, obj)); @@ -294,7 +293,7 @@ public ImageHeapConstant getConstant(String keyName) { // A constant has not been stored in the heap yet. Create and cache a constant candidate FutureConstantCandidateInfo info = (FutureConstantCandidateInfo) constantCandidates.computeIfAbsent(keyName, (k) -> { - AnalysisType type = loader.getAnalysisType(future.loaderId()); + AnalysisType type = loader.getAnalysisTypeForBaseLayerId(future.loaderId()); return new FutureConstantCandidateInfo(ImageHeapRelocatableConstant.create(type, k)); }); return info.constant(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedDynamicLayerInfo.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedDynamicLayerInfo.java index 442e951f8087..dda1d9348674 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedDynamicLayerInfo.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedDynamicLayerInfo.java @@ -84,8 +84,8 @@ private HostedDynamicLayerInfo(int layerNumber, String codeSectionStartSymbol, M super(layerNumber); this.methodIdToOffsetMap = methodIdToOffsetMap; this.methodIdToNameInfoMap = methodIdToNameInfoMap; - this.libNames = libNames; - cGlobalData = codeSectionStartSymbol == null ? null : CGlobalDataFactory.forSymbol(codeSectionStartSymbol); + this.libNames = new ArrayList<>(libNames); + this.cGlobalData = codeSectionStartSymbol == null ? null : CGlobalDataFactory.forSymbol(codeSectionStartSymbol); } @Override @@ -186,7 +186,8 @@ public PersistFlags preparePersist(ImageSingletonWriter writer) { /* * First write out next layer number. */ - writer.writeInt("nextLayerNumber", nextLayerNumber); + var snapshotBuilder = ((SVMImageLayerWriter.ImageSingletonWriterImpl) writer).getSnapshotBuilder(); + snapshotBuilder.setNextLayerNumber(nextLayerNumber); /* * Next write the start of the code section @@ -228,7 +229,8 @@ public static Object createFromLoader(ImageSingletonLoader loader) { assert loader.readIntList("offsets").size() == loader.readIntList("methodOffsetIDs").size() : Assertions.errorMessage("Offsets and methodIDs are incompatible", loader.readIntList("offsets"), loader.readIntList("methodIDs")); - int layerNumber = loader.readInt("nextLayerNumber"); + var snapshotReader = ((SVMImageLayerSingletonLoader.ImageSingletonLoaderImpl) loader).getSnapshotReader(); + int layerNumber = snapshotReader.getNextLayerNumber(); String codeSectionStartSymbol = loader.readString("codeSectionStartSymbol"); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java index 9692e4824cde..faabe5269d9d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java @@ -24,27 +24,23 @@ */ package com.oracle.svm.hosted.imagelayer; -import static com.oracle.svm.core.SubstrateOptions.IncludeAllFromModule; -import static com.oracle.svm.core.SubstrateOptions.IncludeAllFromPath; -import static com.oracle.svm.core.SubstrateOptions.LayerCreate; -import static com.oracle.svm.core.SubstrateOptions.LayerUse; -import static com.oracle.svm.core.SubstrateOptions.imageLayerCreateEnabledHandler; -import static com.oracle.svm.core.SubstrateOptions.imageLayerEnabledHandler; -import static com.oracle.svm.hosted.imagelayer.LayerArchiveSupport.MODULE_OPTION; -import static com.oracle.svm.hosted.imagelayer.LayerArchiveSupport.PACKAGE_OPTION; - import java.io.File; +import java.io.IOException; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; import java.nio.file.Path; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; +import org.capnproto.ReaderOptions; +import org.capnproto.Serialize; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; -import com.oracle.graal.pointsto.heap.ImageLayerLoader; -import com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil; +import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.svm.core.BuildArtifacts; import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.TypeResult; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; import com.oracle.svm.core.option.HostedOptionKey; @@ -56,8 +52,7 @@ import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.NativeImageGenerator; import com.oracle.svm.hosted.c.NativeLibraries; -import com.oracle.svm.hosted.heap.SVMImageLayerLoader; -import com.oracle.svm.hosted.heap.SVMImageLayerWriter; +import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.ExtendedOption; import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.LayerOption; import jdk.graal.compiler.core.common.SuppressFBWarnings; @@ -65,16 +60,27 @@ import jdk.graal.compiler.options.OptionValues; public final class HostedImageLayerBuildingSupport extends ImageLayerBuildingSupport { - private final SVMImageLayerLoader loader; - private final SVMImageLayerWriter writer; + private SVMImageLayerLoader loader; + private SVMImageLayerWriter writer; + private SVMImageLayerSingletonLoader singletonLoader; + private final ImageClassLoader imageClassLoader; + private final List snapshots; + private final List graphsChannels; private final WriteLayerArchiveSupport writeLayerArchiveSupport; private final LoadLayerArchiveSupport loadLayerArchiveSupport; - private HostedImageLayerBuildingSupport(SVMImageLayerLoader loader, SVMImageLayerWriter writer, boolean buildingImageLayer, boolean buildingInitialLayer, boolean buildingApplicationLayer, + public record FilePaths(Path snapshot, Path snapshotGraphs) { + } + + private HostedImageLayerBuildingSupport(SVMImageLayerSingletonLoader singletonLoader, ImageClassLoader imageClassLoader, + List snapshots, List graphsChannels, boolean buildingImageLayer, boolean buildingInitialLayer, + boolean buildingApplicationLayer, WriteLayerArchiveSupport writeLayerArchiveSupport, LoadLayerArchiveSupport loadLayerArchiveSupport) { super(buildingImageLayer, buildingInitialLayer, buildingApplicationLayer); - this.loader = loader; - this.writer = writer; + this.singletonLoader = singletonLoader; + this.imageClassLoader = imageClassLoader; + this.snapshots = snapshots; + this.graphsChannels = graphsChannels; this.writeLayerArchiveSupport = writeLayerArchiveSupport; this.loadLayerArchiveSupport = loadLayerArchiveSupport; } @@ -83,14 +89,30 @@ public static HostedImageLayerBuildingSupport singleton() { return (HostedImageLayerBuildingSupport) ImageSingletons.lookup(ImageLayerBuildingSupport.class); } + public SVMImageLayerSingletonLoader getSingletonLoader() { + return singletonLoader; + } + + public void setSingletonLoader(SVMImageLayerSingletonLoader singletonLoader) { + this.singletonLoader = singletonLoader; + } + public SVMImageLayerLoader getLoader() { return loader; } + public void setLoader(SVMImageLayerLoader loader) { + this.loader = loader; + } + public SVMImageLayerWriter getWriter() { return writer; } + public void setWriter(SVMImageLayerWriter writer) { + this.writer = writer; + } + public LoadLayerArchiveSupport getLoadLayerArchiveSupport() { return loadLayerArchiveSupport; } @@ -100,6 +122,26 @@ public void archiveLayer(String imageName) { writeLayerArchiveSupport.write(imageName); } + public SharedLayerSnapshotCapnProtoSchemaHolder.SharedLayerSnapshot.Reader getSnapshot() { + return snapshots.get(0); + } + + public FileChannel getGraphsChannel() { + return graphsChannels.get(0); + } + + public Class lookupClass(boolean optional, String className) { + TypeResult> typeResult = imageClassLoader.findClass(className); + if (!typeResult.isPresent()) { + if (optional) { + return null; + } else { + throw AnalysisError.shouldNotReachHere("Class not found: " + className); + } + } + return typeResult.get(); + } + /** * Process layer-create and layer-use options. The semantics of these options allow a user to * specify them any number of times, only the last instance wins. This processing cannot be done @@ -108,50 +150,54 @@ public void archiveLayer(String imageName) { */ public static void processLayerOptions(EconomicMap, Object> values) { OptionValues hostedOptions = new OptionValues(values); - if (LayerCreate.hasBeenSet(hostedOptions)) { + if (SubstrateOptions.LayerCreate.hasBeenSet(hostedOptions)) { /* The last value wins, GR-55565 will warn about the overwritten values. */ - String layerCreateValue = LayerCreate.getValue(hostedOptions).lastValue().orElseThrow(); + String layerCreateValue = SubstrateOptions.LayerCreate.getValue(hostedOptions).lastValue().orElseThrow(); if (layerCreateValue.isEmpty()) { /* Nothing to do, an empty --layer-create= disables the layer creation. */ } else { LayerOption layerOption = LayerOption.parse(layerCreateValue); - String buildLayer = SubstrateOptionsParser.commandArgument(LayerCreate, ""); - Arrays.stream(layerOption.extendedOptions()).forEach(option -> { + String buildLayer = SubstrateOptionsParser.commandArgument(SubstrateOptions.LayerCreate, ""); + for (ExtendedOption option : layerOption.extendedOptions()) { switch (option.key()) { - case MODULE_OPTION -> { + case LayerArchiveSupport.MODULE_OPTION -> { UserError.guarantee(option.value() != null, "Option %s of %s requires a module name argument, e.g., %s=module-name.", option.key(), buildLayer, option.key()); - IncludeAllFromModule.update(values, option.value()); + SubstrateOptions.IncludeAllFromModule.update(values, option.value()); } - case PACKAGE_OPTION -> { + case LayerArchiveSupport.PACKAGE_OPTION -> { UserError.guarantee(option.value() != null, "Option %s of %s requires a package name argument, e.g., %s=package-name.", option.key(), buildLayer, option.key()); - IncludeAllFromPath.update(values, option.value()); + SubstrateOptions.IncludeAllFromPackage.update(values, option.value()); + } + case LayerArchiveSupport.PATH_OPTION -> { + UserError.guarantee(option.value() != null, "Option %s of %s requires a class-path entry, e.g., %s=path/to/cp-entry.", option.key(), buildLayer, option.key()); + SubstrateOptions.IncludeAllFromPath.update(values, option.value()); } default -> throw UserError.abort("Unknown option %s of %s. Use --help-extra for usage instructions.", option.key(), buildLayer); } - }); + } SubstrateOptions.UseBaseLayerInclusionPolicy.update(values, true); SubstrateOptions.ClosedTypeWorld.update(values, false); - if (imageLayerEnabledHandler != null) { - imageLayerEnabledHandler.onOptionEnabled(values); + if (SubstrateOptions.imageLayerEnabledHandler != null) { + SubstrateOptions.imageLayerEnabledHandler.onOptionEnabled(values); } - if (imageLayerCreateEnabledHandler != null) { - imageLayerCreateEnabledHandler.onOptionEnabled(values); + if (SubstrateOptions.imageLayerCreateEnabledHandler != null) { + SubstrateOptions.imageLayerCreateEnabledHandler.onOptionEnabled(values); } SubstrateOptions.UseContainerSupport.update(values, false); } } - if (LayerUse.hasBeenSet(hostedOptions)) { + if (SubstrateOptions.LayerUse.hasBeenSet(hostedOptions)) { /* The last value wins, GR-55565 will warn about the overwritten values. */ - Path layerUseValue = LayerUse.getValue(hostedOptions).lastValue().orElseThrow(); + Path layerUseValue = SubstrateOptions.LayerUse.getValue(hostedOptions).lastValue().orElseThrow(); if (layerUseValue.toString().isEmpty()) { /* Nothing to do, an empty --layer-use= disables the layer application. */ } else { SubstrateOptions.ClosedTypeWorldHubLayout.update(values, false); SubstrateOptions.ParseRuntimeOptions.update(values, false); - if (imageLayerEnabledHandler != null) { - imageLayerEnabledHandler.onOptionEnabled(values); + if (SubstrateOptions.imageLayerEnabledHandler != null) { + SubstrateOptions.imageLayerEnabledHandler.onOptionEnabled(values); } } } @@ -166,8 +212,8 @@ private static boolean isLayerOptionEnabled(HostedOptionKey snapshots = null; + List graphsChannels = null; if (buildingExtensionLayer) { - Path layerFileName = LayerUse.getValue(values).lastValue().orElseThrow(); + Path layerFileName = SubstrateOptions.LayerUse.getValue(values).lastValue().orElseThrow(); loadLayerArchiveSupport = new LoadLayerArchiveSupport(layerFileName, archiveSupport); - ImageLayerLoader.FilePaths paths = new ImageLayerLoader.FilePaths(loadLayerArchiveSupport.getSnapshotPath(), loadLayerArchiveSupport.getSnapshotGraphsPath()); - loader = new SVMImageLayerLoader(List.of(paths), imageClassLoader); + FilePaths filePaths = new FilePaths(loadLayerArchiveSupport.getSnapshotPath(), loadLayerArchiveSupport.getSnapshotGraphsPath()); + List loadPaths = List.of(filePaths); + snapshots = new ArrayList<>(); + graphsChannels = new ArrayList<>(); + for (FilePaths paths : loadPaths) { + try { + graphsChannels.add(FileChannel.open(paths.snapshotGraphs)); + + try (FileChannel ch = FileChannel.open(paths.snapshot)) { + MappedByteBuffer bb = ch.map(FileChannel.MapMode.READ_ONLY, ch.position(), ch.size()); + ReaderOptions opt = new ReaderOptions(Long.MAX_VALUE, ReaderOptions.DEFAULT_READER_OPTIONS.nestingLimit); + snapshots.add(Serialize.read(bb, opt).getRoot(SharedLayerSnapshotCapnProtoSchemaHolder.SharedLayerSnapshot.factory)); + // NOTE: buffer is never unmapped, but is read-only and pages can be evicted + } + } catch (IOException e) { + throw AnalysisError.shouldNotReachHere("Error during image layer snapshot loading", e); + } + } + + assert loadPaths.size() == 1 : "Currently only one path is supported for image layer loading " + loadPaths; + } + + HostedImageLayerBuildingSupport imageLayerBuildingSupport = new HostedImageLayerBuildingSupport(singletonLoader, imageClassLoader, snapshots, graphsChannels, buildingImageLayer, + buildingInitialLayer, buildingFinalLayer, writeLayerArchiveSupport, loadLayerArchiveSupport); + + if (buildingExtensionLayer) { + imageLayerBuildingSupport.setSingletonLoader(new SVMImageLayerSingletonLoader(imageLayerBuildingSupport, snapshots.get(0))); } - return new HostedImageLayerBuildingSupport(loader, writer, buildingImageLayer, buildingInitialLayer, buildingFinalLayer, writeLayerArchiveSupport, loadLayerArchiveSupport); + return imageLayerBuildingSupport; } @SuppressFBWarnings(value = "NP", justification = "FB reports null pointer dereferencing because it doesn't see through UserError.guarantee.") @@ -215,14 +286,14 @@ public static void setupSharedLayerLibrary(NativeLibraries nativeLibs) { public static void setupImageLayerArtifacts(String imageName) { VMError.guarantee(!imageName.contains(File.separator), "Expected simple file name, found %s.", imageName); - Path snapshotFile = NativeImageGenerator.getOutputDirectory().resolve(ImageLayerSnapshotUtil.snapshotFileName(imageName)); + Path snapshotFile = NativeImageGenerator.getOutputDirectory().resolve(SVMImageLayerSnapshotUtil.snapshotFileName(imageName)); Path snapshotFileName = getFileName(snapshotFile); - HostedImageLayerBuildingSupport.singleton().getWriter().setSnapshotFileInfo(snapshotFile, snapshotFileName.toString(), ImageLayerSnapshotUtil.FILE_EXTENSION); + HostedImageLayerBuildingSupport.singleton().getWriter().setSnapshotFileInfo(snapshotFile, snapshotFileName.toString(), SVMImageLayerSnapshotUtil.FILE_EXTENSION); BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.LAYER_SNAPSHOT, snapshotFile); - Path graphsFile = NativeImageGenerator.getOutputDirectory().resolve(ImageLayerSnapshotUtil.snapshotGraphsFileName(imageName)); + Path graphsFile = NativeImageGenerator.getOutputDirectory().resolve(SVMImageLayerSnapshotUtil.snapshotGraphsFileName(imageName)); Path graphsFileName = getFileName(graphsFile); - HostedImageLayerBuildingSupport.singleton().getWriter().openGraphsOutput(graphsFile, graphsFileName.toString(), ImageLayerSnapshotUtil.FILE_EXTENSION); + HostedImageLayerBuildingSupport.singleton().getWriter().openGraphsOutput(graphsFile, graphsFileName.toString(), SVMImageLayerSnapshotUtil.FILE_EXTENSION); BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.LAYER_SNAPSHOT_GRAPHS, graphsFile); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/ImageLayerSectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/ImageLayerSectionFeature.java index 47610d019641..1748f8400e3c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/ImageLayerSectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/ImageLayerSectionFeature.java @@ -24,7 +24,7 @@ */ package com.oracle.svm.hosted.imagelayer; -import static org.graalvm.word.WordFactory.signed; +import static jdk.graal.compiler.word.Word.signed; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -38,7 +38,6 @@ import org.graalvm.word.Pointer; import org.graalvm.word.SignedWord; import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; import com.oracle.objectfile.BasicProgbitsSectionImpl; import com.oracle.objectfile.ObjectFile; @@ -76,13 +75,12 @@ * | 8 | heap end | * | 16 | heap relocatable begin | * | 24 | heap relocatable end | - * | 32 | heap any relocatable pointer | - * | 40 | heap writable begin | - * | 48 | heap writable end | - * | 56 | heap writable patched begin | - * | 64 | heap writable patched end | - * | 72 | next layer section (0 if final layer) | - * | 80 | cross-layer singleton table start | + * | 32 | heap writable begin | + * | 40 | heap writable end | + * | 48 | heap writable patched begin | + * | 56 | heap writable patched end | + * | 64 | next layer section (0 if final layer) | + * | 72 | cross-layer singleton table start | * | (after table) | heap relative patches | * --------------------------------------------------------- * @@ -96,13 +94,12 @@ public final class ImageLayerSectionFeature implements InternalFeature, FeatureS private static final int HEAP_END_OFFSET = 8; private static final int HEAP_RELOCATABLE_BEGIN_OFFSET = 16; private static final int HEAP_RELOCATABLE_END_OFFSET = 24; - private static final int HEAP_ANY_RELOCATABLE_POINTER_OFFSET = 32; - private static final int HEAP_WRITABLE_BEGIN_OFFSET = 40; - private static final int HEAP_WRITABLE_END_OFFSET = 48; - private static final int HEAP_WRITABLE_PATCHED_BEGIN_OFFSET = 56; - private static final int HEAP_WRITABLE_PATCHED_END_OFFSET = 64; - private static final int NEXT_SECTION_OFFSET = 72; - private static final int STATIC_SECTION_SIZE = 80; + private static final int HEAP_WRITABLE_BEGIN_OFFSET = 32; + private static final int HEAP_WRITABLE_END_OFFSET = 40; + private static final int HEAP_WRITABLE_PATCHED_BEGIN_OFFSET = 48; + private static final int HEAP_WRITABLE_PATCHED_END_OFFSET = 56; + private static final int NEXT_SECTION_OFFSET = 64; + private static final int STATIC_SECTION_SIZE = 72; private static final String CACHED_IMAGE_FDS_NAME = "__svm_layer_cached_image_fds"; private static final String CACHED_IMAGE_HEAP_OFFSETS_NAME = "__svm_layer_cached_image_heap_offsets"; @@ -154,8 +151,8 @@ private static ImageLayerSectionImpl createImageLayerSection() { cachedImageHeapRelocations = CGlobalDataFactory.forSymbol(CACHED_IMAGE_HEAP_RELOCATIONS_NAME); } else if (ImageLayerBuildingSupport.buildingApplicationLayer()) { cachedImageFDs = CGlobalDataFactory.createBytes(() -> createWords(DynamicImageLayerInfo.singleton().numLayers, UNASSIGNED_FD), CACHED_IMAGE_FDS_NAME); - cachedImageHeapOffsets = CGlobalDataFactory.createBytes(() -> createWords(DynamicImageLayerInfo.singleton().numLayers, WordFactory.zero()), CACHED_IMAGE_HEAP_OFFSETS_NAME); - cachedImageHeapRelocations = CGlobalDataFactory.createBytes(() -> createWords(DynamicImageLayerInfo.singleton().numLayers, WordFactory.zero()), CACHED_IMAGE_HEAP_RELOCATIONS_NAME); + cachedImageHeapOffsets = CGlobalDataFactory.createBytes(() -> createWords(DynamicImageLayerInfo.singleton().numLayers, Word.zero()), CACHED_IMAGE_HEAP_OFFSETS_NAME); + cachedImageHeapRelocations = CGlobalDataFactory.createBytes(() -> createWords(DynamicImageLayerInfo.singleton().numLayers, Word.zero()), CACHED_IMAGE_HEAP_RELOCATIONS_NAME); } else { cachedImageFDs = null; cachedImageHeapOffsets = null; @@ -242,7 +239,6 @@ public void beforeImageWrite(BeforeImageWriteAccess access) { layeredSectionData.markRelocationSite(HEAP_END_OFFSET, ObjectFile.RelocationKind.DIRECT_8, Isolates.IMAGE_HEAP_END_SYMBOL_NAME, 0); layeredSectionData.markRelocationSite(HEAP_RELOCATABLE_BEGIN_OFFSET, ObjectFile.RelocationKind.DIRECT_8, Isolates.IMAGE_HEAP_RELOCATABLE_BEGIN_SYMBOL_NAME, 0); layeredSectionData.markRelocationSite(HEAP_RELOCATABLE_END_OFFSET, ObjectFile.RelocationKind.DIRECT_8, Isolates.IMAGE_HEAP_RELOCATABLE_END_SYMBOL_NAME, 0); - layeredSectionData.markRelocationSite(HEAP_ANY_RELOCATABLE_POINTER_OFFSET, ObjectFile.RelocationKind.DIRECT_8, Isolates.IMAGE_HEAP_A_RELOCATABLE_POINTER_SYMBOL_NAME, 0); layeredSectionData.markRelocationSite(HEAP_WRITABLE_BEGIN_OFFSET, ObjectFile.RelocationKind.DIRECT_8, Isolates.IMAGE_HEAP_WRITABLE_BEGIN_SYMBOL_NAME, 0); layeredSectionData.markRelocationSite(HEAP_WRITABLE_END_OFFSET, ObjectFile.RelocationKind.DIRECT_8, Isolates.IMAGE_HEAP_WRITABLE_END_SYMBOL_NAME, 0); layeredSectionData.markRelocationSite(HEAP_WRITABLE_PATCHED_BEGIN_OFFSET, ObjectFile.RelocationKind.DIRECT_8, Isolates.IMAGE_HEAP_WRITABLE_PATCHED_BEGIN_SYMBOL_NAME, 0); @@ -306,7 +302,6 @@ public int getEntryOffsetInternal(SectionEntries entry) { case HEAP_END -> HEAP_END_OFFSET; case HEAP_RELOCATABLE_BEGIN -> HEAP_RELOCATABLE_BEGIN_OFFSET; case HEAP_RELOCATABLE_END -> HEAP_RELOCATABLE_END_OFFSET; - case HEAP_ANY_RELOCATABLE_POINTER -> HEAP_ANY_RELOCATABLE_POINTER_OFFSET; case HEAP_WRITEABLE_BEGIN -> HEAP_WRITABLE_BEGIN_OFFSET; case HEAP_WRITEABLE_END -> HEAP_WRITABLE_END_OFFSET; case HEAP_WRITEABLE_PATCHED_BEGIN -> HEAP_WRITABLE_PATCHED_BEGIN_OFFSET; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerArchiveSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerArchiveSupport.java index 8d5406a148ce..44c0e723034c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerArchiveSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerArchiveSupport.java @@ -44,7 +44,8 @@ public class LayerArchiveSupport { protected static final String MODULE_OPTION = "module"; - protected static final String PACKAGE_OPTION = "package"; + public static final String PACKAGE_OPTION = "package"; + protected static final String PATH_OPTION = "path"; private static final int LAYER_FILE_FORMAT_VERSION_MAJOR = 0; private static final int LAYER_FILE_FORMAT_VERSION_MINOR = 1; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredDispatchTableSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredDispatchTableSupport.java index 8de26bb55c08..4488e54e5c85 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredDispatchTableSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredDispatchTableSupport.java @@ -63,7 +63,6 @@ import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.OpenTypeWorldFeature; import com.oracle.svm.hosted.SVMHost; -import com.oracle.svm.hosted.heap.SVMImageLayerWriter; import com.oracle.svm.hosted.image.NativeImage; import com.oracle.svm.hosted.image.NativeImageCodeCache; import com.oracle.svm.hosted.meta.HostedMetaAccess; @@ -228,13 +227,17 @@ private void injectPriorLayerInfo(HostedType type, HostedDispatchTable dispatchT var priorInfo = priorDispatchTables.get(type.getWrapped().getId()); if (priorInfo != null) { compareTypeInfo(dispatchTable, priorInfo); - dispatchTable.status = HubStatus.INSTALLED_PRIOR_LAYER; - for (int i = 0; i < dispatchTable.slots.length; i++) { - HostedDispatchSlot slot = dispatchTable.slots[i]; - PriorDispatchSlot priorSlot = priorInfo.slots[i]; - if (priorSlot.status.isCompiled()) { - slot.status = SlotResolutionStatus.PRIOR_LAYER; - slot.symbol = priorSlot.slotSymbolName; + dispatchTable.status = priorInfo.installed ? HubStatus.INSTALLED_PRIOR_LAYER : HubStatus.COMPUTED_PRIOR_LAYER; + if (priorInfo.installed) { + // record symbol info for installed hubs + for (int i = 0; i < dispatchTable.slots.length; i++) { + HostedDispatchSlot slot = dispatchTable.slots[i]; + PriorDispatchSlot priorSlot = priorInfo.slots[i]; + if (priorSlot.status.isCompiled()) { + slot.status = SlotResolutionStatus.PRIOR_LAYER; + assert !priorSlot.slotSymbolName.equals(PriorDispatchSlot.INVALID_SYMBOL_NAME); + slot.symbol = priorSlot.slotSymbolName; + } } } } @@ -326,7 +329,7 @@ public void registerWrittenDynamicHub(DynamicHub hub, AnalysisUniverse aUniverse var dispatchTable = typeToDispatchTable.get(hType); // upgrade status to being installed in current layer - assert dispatchTable.status == HubStatus.DISPATCH_INFO_CALCULATED : dispatchTable; + assert dispatchTable.status == HubStatus.DISPATCH_INFO_CALCULATED || dispatchTable.status == HubStatus.COMPUTED_PRIOR_LAYER : dispatchTable; dispatchTable.status = HubStatus.INSTALLED_CURRENT_LAYER; assert dispatchTable.slots.length == vtableLength : Assertions.errorMessage(vTable, dispatchTable.slots); @@ -492,7 +495,6 @@ public PersistFlags preparePersist(ImageSingletonWriter writer) { * To reduce redundancy, we maintain a separate table of all methods we refer to within the * dispatch tables. */ - SVMImageLayerWriter layerWriter = HostedImageLayerBuildingSupport.singleton().getWriter(); Map methodToOffsetMap = new HashMap<>(); List methodBooleans = new ArrayList<>(); List methodInts = new ArrayList<>(); @@ -504,7 +506,7 @@ public PersistFlags preparePersist(ImageSingletonWriter writer) { } else { offset = methodToOffsetMap.size(); methodToOffsetMap.put(hMethod, offset); - methodInts.add(layerWriter.isMethodPersisted(hMethod.getWrapped()) ? hMethod.wrapped.getId() : PriorDispatchMethod.UNKNOWN_ID); + methodInts.add(hMethod.getWrapped().isTrackedAcrossLayers() ? hMethod.wrapped.getId() : PriorDispatchMethod.UNKNOWN_ID); methodInts.add(hMethod.hasVTableIndex() ? hMethod.getVTableIndex() : PriorDispatchMethod.UNKNOWN_VTABLE_IDX); methodBooleans.add(virtualCallTargets.contains(hMethod)); methodStrings.add(NativeImage.localSymbolNameForMethod(hMethod)); @@ -517,11 +519,12 @@ public PersistFlags preparePersist(ImageSingletonWriter writer) { * Write out dispatch tables for persisted types. */ List dispatchTableInts = new ArrayList<>(); + List dispatchTableBooleans = new ArrayList<>(); List dispatchSlotInts = new ArrayList<>(); List dispatchSlotStrings = new ArrayList<>(); int nextSlotIdx = 0; for (HostedDispatchTable info : typeToDispatchTable.values()) { - if (!layerWriter.isTypePersisted(info.type.getWrapped())) { + if (!info.type.getWrapped().isTrackedAcrossLayers()) { // if a type contains target of a virtual call, then it should be persisted assert Arrays.stream(info.locallyDeclaredSlots).noneMatch(virtualCallTargets::contains) : "Type should be persisted: " + info.type; continue; @@ -534,9 +537,11 @@ public PersistFlags preparePersist(ImageSingletonWriter writer) { localTargets.add(methodToOffsetMapper.apply(hMethod)); } + boolean hubInstalled = info.status == HubStatus.INSTALLED_CURRENT_LAYER; if (info.slots != null) { for (var slotInfo : info.slots) { - dispatchSlotStrings.add(slotInfo.symbol); + var symbolName = hubInstalled ? slotInfo.symbol : PriorDispatchSlot.INVALID_SYMBOL_NAME; + dispatchSlotStrings.add(symbolName); dispatchSlotInts.add(slotInfo.slotNum); dispatchSlotInts.add(slotInfo.status.ordinal()); dispatchSlotInts.add(methodToOffsetMapper.apply(slotInfo.declaredMethod)); @@ -555,9 +560,11 @@ public PersistFlags preparePersist(ImageSingletonWriter writer) { dispatchTableInts.add(slotOffsets.size()); dispatchTableInts.addAll(localTargets); dispatchTableInts.addAll(slotOffsets); + dispatchTableBooleans.add(hubInstalled); } writer.writeIntList("dispatchTableInts", dispatchTableInts); + writer.writeBoolList("dispatchTableBooleans", dispatchTableBooleans); writer.writeIntList("dispatchSlotInts", dispatchSlotInts); writer.writeStringList("dispatchSlotStrings", dispatchSlotStrings); writer.writeBoolList("methodBooleans", methodBooleans); @@ -570,6 +577,7 @@ public PersistFlags preparePersist(ImageSingletonWriter writer) { @SuppressWarnings("unused") public static Object createFromLoader(ImageSingletonLoader loader) { List dispatchTableInts = loader.readIntList("dispatchTableInts"); + List dispatchTableBooleans = loader.readBoolList("dispatchTableBooleans"); List dispatchSlotInts = loader.readIntList("dispatchSlotInts"); List dispatchSlotStrings = loader.readStringList("dispatchSlotStrings"); List methodBooleans = loader.readBoolList("methodBooleans"); @@ -609,17 +617,15 @@ public static Object createFromLoader(ImageSingletonLoader loader) { resolvedMethod = priorMethods.get(index); } - if (status == SlotResolutionStatus.UNRESOLVED || status == SlotResolutionStatus.NOT_COMPILED) { - unresolvedSymbols.add(slotSymbolName); - } - var slotInfo = new PriorDispatchSlot(declaredMethod, resolvedMethod, slotNum, status, slotSymbolName); priorDispatchSlots.add(slotInfo); } intIterator = dispatchTableInts.iterator(); + boolIterator = dispatchTableBooleans.iterator(); while (intIterator.hasNext()) { int typeId = intIterator.next(); + boolean hubInstalled = boolIterator.next(); int locallyDeclaredMethodsSize = intIterator.next(); int allSlotsSize = intIterator.next(); PriorDispatchMethod[] locallyDeclaredSlots = new PriorDispatchMethod[locallyDeclaredMethodsSize]; @@ -629,9 +635,17 @@ public static Object createFromLoader(ImageSingletonLoader loader) { } for (int i = 0; i < allSlotsSize; i++) { dispatchTableSlots[i] = priorDispatchSlots.get(intIterator.next()); + if (hubInstalled) { + var status = dispatchTableSlots[i].status; + if (status == SlotResolutionStatus.UNRESOLVED || status == SlotResolutionStatus.NOT_COMPILED) { + assert !dispatchTableSlots[i].slotSymbolName.equals(PriorDispatchSlot.INVALID_SYMBOL_NAME); + unresolvedSymbols.add(dispatchTableSlots[i].slotSymbolName); + } + + } } - var priorDispatchTable = new PriorDispatchTable(typeId, locallyDeclaredSlots, dispatchTableSlots); + var priorDispatchTable = new PriorDispatchTable(typeId, hubInstalled, locallyDeclaredSlots, dispatchTableSlots); Object prev = priorTypes.put(typeId, priorDispatchTable); assert prev == null : prev; @@ -649,6 +663,7 @@ public static Object createFromLoader(ImageSingletonLoader loader) { enum HubStatus { UNINITIALIZED, DISPATCH_INFO_CALCULATED, + COMPUTED_PRIOR_LAYER, INSTALLED_PRIOR_LAYER, INSTALLED_CURRENT_LAYER, } @@ -697,7 +712,7 @@ static class HostedDispatchSlot { * symbols. */ - record PriorDispatchTable(int typeID, + record PriorDispatchTable(int typeID, boolean installed, PriorDispatchMethod[] locallyDeclaredSlots, PriorDispatchSlot[] slots) { } @@ -705,6 +720,7 @@ record PriorDispatchSlot( PriorDispatchMethod declaredMethod, PriorDispatchMethod resolvedMethod, int slotNum, SlotResolutionStatus status, String slotSymbolName) { static final int UNKNOWN_METHOD = -1; + static final String INVALID_SYMBOL_NAME = "invalid"; } /** @@ -743,7 +759,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { var loader = HostedImageLayerBuildingSupport.singleton().getLoader(); var singleton = LayeredDispatchTableSupport.singleton(); for (var entry : singleton.priorVirtualCallTargets.entrySet()) { - AnalysisType type = loader.getAnalysisType(entry.getKey()); + AnalysisType type = loader.getAnalysisTypeForBaseLayerId(entry.getKey()); var methods = type.getOrCalculateOpenTypeWorldDispatchTableMethods(); var virtualCallTargets = entry.getValue(); methods.forEach(aMethod -> { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadImageSingletonFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadImageSingletonFeature.java index a8e1b6d810a9..853fa32e6cdc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadImageSingletonFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadImageSingletonFeature.java @@ -68,7 +68,6 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.c.CGlobalDataFeature; -import com.oracle.svm.hosted.heap.SVMImageLayerLoader; import com.oracle.svm.hosted.image.NativeImageHeap; import com.oracle.svm.hosted.meta.HostedMetaAccess; import com.oracle.svm.hosted.meta.HostedUniverse; @@ -602,7 +601,7 @@ public PersistFlags preparePersist(ImageSingletonWriter writer) { @SuppressWarnings("unused") public static Object createFromLoader(ImageSingletonLoader loader) { - SVMImageLayerLoader imageLayerLoader = HostedImageLayerBuildingSupport.singleton().getLoader(); + SVMImageLayerSingletonLoader imageLayerLoader = HostedImageLayerBuildingSupport.singleton().getSingletonLoader(); Iterator keyClasses = loader.readStringList("keyClasses").iterator(); Iterator slotAssignments = loader.readIntList("slotAssignments").iterator(); Iterator slotKinds = loader.readStringList("slotKinds").iterator(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadLayerArchiveSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadLayerArchiveSupport.java index 6431886058bd..527d44abab13 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadLayerArchiveSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadLayerArchiveSupport.java @@ -28,7 +28,6 @@ import java.nio.file.Path; import java.util.concurrent.atomic.AtomicBoolean; -import com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil; import com.oracle.svm.core.util.ArchiveSupport; import com.oracle.svm.core.util.UserError; @@ -52,11 +51,11 @@ public Path getSharedLibraryPath() { } public Path getSnapshotPath() { - return expandedInputLayerDir.resolve(ImageLayerSnapshotUtil.snapshotFileName(layerProperties.layerName())); + return expandedInputLayerDir.resolve(SVMImageLayerSnapshotUtil.snapshotFileName(layerProperties.layerName())); } public Path getSnapshotGraphsPath() { - return expandedInputLayerDir.resolve(ImageLayerSnapshotUtil.snapshotGraphsFileName(layerProperties.layerName())); + return expandedInputLayerDir.resolve(SVMImageLayerSnapshotUtil.snapshotGraphsFileName(layerProperties.layerName())); } private static Path validateLayerFile(Path layerFile) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java new file mode 100644 index 000000000000..4e7e93915249 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java @@ -0,0 +1,1571 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.imagelayer; + +import static com.oracle.graal.pointsto.util.AnalysisError.guarantee; +import static com.oracle.svm.hosted.imagelayer.SVMImageLayerSnapshotUtil.CLASS_INIT_NAME; +import static com.oracle.svm.hosted.imagelayer.SVMImageLayerSnapshotUtil.CONSTRUCTOR_NAME; +import static com.oracle.svm.hosted.imagelayer.SVMImageLayerSnapshotUtil.PERSISTED; +import static com.oracle.svm.hosted.lambda.LambdaParser.createMethodGraph; +import static com.oracle.svm.hosted.lambda.LambdaParser.getLambdaClassFromConstantNode; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Proxy; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Supplier; +import java.util.function.ToIntFunction; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.capnproto.ListReader; +import org.capnproto.PrimitiveList; +import org.capnproto.StructList; +import org.capnproto.StructReader; +import org.capnproto.Text; +import org.capnproto.TextList; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.function.CEntryPoint; +import org.graalvm.nativeimage.impl.CEntryPointLiteralCodePointer; + +import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.api.ImageLayerLoader; +import com.oracle.graal.pointsto.flow.AnalysisParsedGraph; +import com.oracle.graal.pointsto.heap.HostedValuesProvider; +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.graal.pointsto.heap.ImageHeapInstance; +import com.oracle.graal.pointsto.heap.ImageHeapObjectArray; +import com.oracle.graal.pointsto.heap.ImageHeapPrimitiveArray; +import com.oracle.graal.pointsto.heap.ImageHeapRelocatableConstant; +import com.oracle.graal.pointsto.heap.value.ValueSupplier; +import com.oracle.graal.pointsto.infrastructure.ResolvedSignature; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.graal.pointsto.meta.BaseLayerField; +import com.oracle.graal.pointsto.meta.BaseLayerMethod; +import com.oracle.graal.pointsto.meta.BaseLayerType; +import com.oracle.graal.pointsto.util.AnalysisError; +import com.oracle.graal.pointsto.util.AnalysisFuture; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.classinitialization.ClassInitializationInfo; +import com.oracle.svm.core.graal.code.CGlobalDataInfo; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.meta.MethodPointer; +import com.oracle.svm.core.reflect.serialize.SerializationSupport; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.c.CGlobalDataFeature; +import com.oracle.svm.hosted.code.CEntryPointCallStubSupport; +import com.oracle.svm.hosted.code.CEntryPointData; +import com.oracle.svm.hosted.code.FactoryMethodSupport; +import com.oracle.svm.hosted.fieldfolding.StaticFinalFieldFoldingFeature; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.AnnotationValue; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.CEntryPointLiteralReference; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisField; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisMethod; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisType; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedConstant; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.SharedLayerSnapshot; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisMethod.WrappedMethod; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisMethod.WrappedMethod.WrappedMember; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisType.WrappedType; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisType.WrappedType.SerializationGenerated; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedConstant.Object.Relinking.EnumConstant; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedConstant.Object.Relinking.StringConstant; +import com.oracle.svm.hosted.jni.JNIAccessFeature; +import com.oracle.svm.hosted.lambda.LambdaParser; +import com.oracle.svm.hosted.meta.HostedUniverse; +import com.oracle.svm.hosted.meta.RelocatableConstant; +import com.oracle.svm.hosted.reflect.ReflectionFeature; +import com.oracle.svm.hosted.reflect.serialize.SerializationFeature; +import com.oracle.svm.hosted.util.IdentityHashCodeUtil; +import com.oracle.svm.util.LogUtils; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.graal.compiler.core.common.NumUtil; +import jdk.graal.compiler.core.common.SuppressFBWarnings; +import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.graph.iterators.NodeIterable; +import jdk.graal.compiler.java.BytecodeParser; +import jdk.graal.compiler.nodes.ConstantNode; +import jdk.graal.compiler.nodes.EncodedGraph; +import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.util.ObjectCopier; +import jdk.internal.reflect.ReflectionFactory; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.JavaMethodProfile; +import jdk.vm.ci.meta.MethodHandleAccessProvider.IntrinsicMethod; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; +import sun.reflect.annotation.AnnotationParser; + +public class SVMImageLayerLoader extends ImageLayerLoader { + private final Field dynamicHubArrayHubField; + private final boolean useSharedLayerGraphs; + private final SVMImageLayerSnapshotUtil imageLayerSnapshotUtil; + private final HostedImageLayerBuildingSupport imageLayerBuildingSupport; + private final SharedLayerSnapshot.Reader snapshot; + private final FileChannel graphsChannel; + + private HostedUniverse hostedUniverse; + + protected final Map types = new ConcurrentHashMap<>(); + protected final Map methods = new ConcurrentHashMap<>(); + protected final Map fields = new ConcurrentHashMap<>(); + protected final Map constants = new ConcurrentHashMap<>(); + + private final Map baseLayerTypes = new ConcurrentHashMap<>(); + private final Map typeToHubIdentityHashCode = new ConcurrentHashMap<>(); + private final Map baseLayerMethods = new ConcurrentHashMap<>(); + private final Map baseLayerFields = new ConcurrentHashMap<>(); + + protected final Set> heapScannerTasks = ConcurrentHashMap.newKeySet(); + protected final Map typeToConstant = new ConcurrentHashMap<>(); + protected final Map stringToConstant = new ConcurrentHashMap<>(); + protected final Map, Integer> enumToConstant = new ConcurrentHashMap<>(); + protected final Map objectOffsets = new ConcurrentHashMap<>(); + protected final Map fieldLocations = new ConcurrentHashMap<>(); + private final Map, Boolean> capturingClasses = new ConcurrentHashMap<>(); + + /** Map from {@link SVMImageLayerSnapshotUtil#getTypeDescriptor} to base layer type ids. */ + private final Map typeDescriptorToBaseLayerId = new HashMap<>(); + /** Map from {@link SVMImageLayerSnapshotUtil#getMethodDescriptor} to base layer method ids. */ + private final Map methodDescriptorToBaseLayerId = new HashMap<>(); + + protected AnalysisUniverse universe; + protected AnalysisMetaAccess metaAccess; + protected HostedValuesProvider hostedValuesProvider; + + public SVMImageLayerLoader(SVMImageLayerSnapshotUtil imageLayerSnapshotUtil, HostedImageLayerBuildingSupport imageLayerBuildingSupport, SharedLayerSnapshot.Reader snapshot, + FileChannel graphChannel, boolean useSharedLayerGraphs) { + dynamicHubArrayHubField = ReflectionUtil.lookupField(DynamicHub.class, "arrayHub"); + this.imageLayerSnapshotUtil = imageLayerSnapshotUtil; + this.imageLayerBuildingSupport = imageLayerBuildingSupport; + this.snapshot = snapshot; + this.graphsChannel = graphChannel; + this.useSharedLayerGraphs = useSharedLayerGraphs; + } + + public AnalysisUniverse getUniverse() { + return universe; + } + + public void setUniverse(AnalysisUniverse universe) { + this.universe = universe; + } + + public AnalysisMetaAccess getMetaAccess() { + return metaAccess; + } + + public void setMetaAccess(AnalysisMetaAccess metaAccess) { + this.metaAccess = metaAccess; + } + + public void setHostedValuesProvider(HostedValuesProvider hostedValuesProvider) { + this.hostedValuesProvider = hostedValuesProvider; + } + + public HostedUniverse getHostedUniverse() { + return hostedUniverse; + } + + public void setHostedUniverse(HostedUniverse hostedUniverse) { + this.hostedUniverse = hostedUniverse; + } + + public HostedImageLayerBuildingSupport getImageLayerBuildingSupport() { + return imageLayerBuildingSupport; + } + + public void loadLayerAnalysis() { + /* + * The new ids of the extension image need to be different from the ones from the base + * layer. The start id is set to the next id of the base layer. + */ + universe.setStartTypeId(snapshot.getNextTypeId()); + universe.setStartMethodId(snapshot.getNextMethodId()); + universe.setStartFieldId(snapshot.getNextFieldId()); + ImageHeapConstant.setCurrentId(snapshot.getNextConstantId()); + + for (PersistedAnalysisType.Reader typeData : snapshot.getTypes()) { + String descriptor = typeData.getDescriptor().toString(); + typeDescriptorToBaseLayerId.put(descriptor, typeData.getId()); + } + + for (PersistedAnalysisMethod.Reader methodData : snapshot.getMethods()) { + String descriptor = methodData.getDescriptor().toString(); + methodDescriptorToBaseLayerId.put(descriptor, methodData.getId()); + } + + streamInts(snapshot.getConstantsToRelink()).mapToObj(this::findConstant) + .forEach(c -> prepareConstantRelinking(c, c.getIdentityHashCode(), c.getId())); + } + + private static IntStream streamInts(PrimitiveList.Int.Reader reader) { + return IntStream.range(0, reader.size()).map(reader::get); + } + + private static Stream streamStrings(TextList.Reader reader) { + return IntStream.range(0, reader.size()).mapToObj(i -> reader.get(i).toString()); + } + + private PersistedConstant.Reader findConstant(int id) { + return binarySearchUnique(id, snapshot.getConstants(), PersistedConstant.Reader::getId); + } + + private static T binarySearchUnique(int key, StructList.Reader sortedList, ToIntFunction keyExtractor) { + int low = 0; + int high = sortedList.size() - 1; + + int prevMid = -1; + int prevKey = 0; + while (low <= high) { + int mid = (low + high) >>> 1; + T midStruct = sortedList.get(mid); + int midKey = keyExtractor.applyAsInt(midStruct); + + assert prevMid == -1 || (mid < prevMid && midKey < prevKey) || (mid > prevMid && midKey > prevKey) : "unsorted or contains duplicates"; + + if (midKey < key) { + low = mid + 1; + } else if (midKey > key) { + high = mid - 1; + } else { + return midStruct; + } + + prevMid = mid; + prevKey = midKey; + } + return null; + } + + private void prepareConstantRelinking(PersistedConstant.Reader constantData, int identityHashCode, int id) { + if (!constantData.isObject()) { + return; + } + + PersistedConstant.Object.Relinking.Reader relinking = constantData.getObject().getRelinking(); + if (relinking.isClassConstant()) { + int typeId = relinking.getClassConstant().getTypeId(); + typeToConstant.put(typeId, id); + } else if (relinking.isStringConstant()) { + String value = relinking.getStringConstant().getValue().toString(); + injectIdentityHashCode(value.intern(), identityHashCode); + stringToConstant.put(value, id); + } else if (relinking.isEnumConstant()) { + EnumConstant.Reader enumConstant = relinking.getEnumConstant(); + Enum enumValue = getEnumValue(enumConstant.getEnumClass(), enumConstant.getEnumName()); + injectIdentityHashCode(enumValue, identityHashCode); + enumToConstant.put(enumValue, id); + } + } + + public void cleanupAfterCompilation() { + if (graphsChannel != null) { + try { + graphsChannel.close(); + } catch (IOException e) { + throw AnalysisError.shouldNotReachHere(e); + } + } + } + + public AnalysisType getAnalysisTypeForBaseLayerId(int tid) { + if (!types.containsKey(tid)) { + loadType(findType(tid)); + } + guarantee(types.containsKey(tid), "Type with id %d was not correctly loaded.", tid); + /* + * The type needs to be looked up because it ensures the type is completely created, as the + * types Map is populated before the type is created. + */ + return universe.lookup(types.get(tid).getWrapped()); + } + + private PersistedAnalysisType.Reader findType(int tid) { + return binarySearchUnique(tid, snapshot.getTypes(), PersistedAnalysisType.Reader::getId); + } + + private void loadType(PersistedAnalysisType.Reader typeData) { + int tid = typeData.getId(); + + if (delegateLoadType(typeData)) { + return; + } + + String name = typeData.getClassJavaName().toString(); + Class clazz = lookupBaseLayerTypeInHostVM(name); + + ResolvedJavaType superClass = getResolvedJavaTypeForBaseLayerId(typeData.getSuperClassTypeId()); + + ResolvedJavaType[] interfaces = streamInts(typeData.getInterfaces()) + .mapToObj(this::getResolvedJavaTypeForBaseLayerId).toArray(ResolvedJavaType[]::new); + + if (clazz != null) { + /* + * When looking up the class by name, the host VM will create the corresponding + * AnalysisType. During this process, the method lookupHostedTypeInBaseLayer will be + * called to see if the type already exists in the base layer. If it is the case, the id + * from the base layer will be reused and the ImageLayerLoader#types map will be + * populated. + */ + metaAccess.lookupJavaType(clazz); + } + + if (!types.containsKey(tid)) { + /* + * If the type cannot be looked up by name, an incomplete AnalysisType, which uses a + * BaseLayerType in its wrapped field, has to be created + */ + BaseLayerType baseLayerType = getBaseLayerType(typeData, tid, superClass, interfaces); + + baseLayerType.setInstanceFields(streamInts(typeData.getInstanceFieldIds()) + .mapToObj(this::getBaseLayerField).toArray(ResolvedJavaField[]::new)); + baseLayerType.setInstanceFieldsWithSuper(streamInts(typeData.getInstanceFieldIdsWithSuper()) + .mapToObj(this::getBaseLayerField).toArray(ResolvedJavaField[]::new)); + + AnalysisType type = universe.lookup(baseLayerType); + guarantee(getBaseLayerTypeId(type) == tid, "The base layer type %s is not correctly matched to the id %d", type, tid); + } + } + + @SuppressWarnings("deprecation") + protected boolean delegateLoadType(PersistedAnalysisType.Reader typeData) { + WrappedType.Reader wrappedType = typeData.getWrappedType(); + if (wrappedType.isNone()) { + return false; + } + if (wrappedType.isSerializationGenerated()) { + SerializationGenerated.Reader sg = wrappedType.getSerializationGenerated(); + String rawDeclaringClassName = sg.getRawDeclaringClass().toString(); + String rawTargetConstructorClassName = sg.getRawTargetConstructor().toString(); + Class rawDeclaringClass = imageLayerBuildingSupport.lookupClass(false, rawDeclaringClassName); + Class rawTargetConstructorClass = imageLayerBuildingSupport.lookupClass(false, rawTargetConstructorClassName); + SerializationSupport serializationSupport = SerializationSupport.singleton(); + Constructor rawTargetConstructor = ReflectionUtil.lookupConstructor(rawTargetConstructorClass); + Constructor constructor = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(rawDeclaringClass, rawTargetConstructor); + serializationSupport.addConstructorAccessor(rawDeclaringClass, rawTargetConstructorClass, SerializationFeature.getConstructorAccessor(constructor)); + Class constructorAccessor = serializationSupport.getSerializationConstructorAccessor(rawDeclaringClass, rawTargetConstructorClass).getClass(); + metaAccess.lookupJavaType(constructorAccessor); + return true; + } else if (wrappedType.isLambda()) { + String capturingClassName = wrappedType.getLambda().getCapturingClass().toString(); + Class capturingClass = imageLayerBuildingSupport.lookupClass(false, capturingClassName); + loadLambdaTypes(capturingClass); + } else if (wrappedType.isProxyType()) { + Class[] interfaces = Stream.of(typeData.getInterfaces()).flatMapToInt(r -> IntStream.range(0, r.size()).map(r::get)) + .mapToObj(i -> getAnalysisTypeForBaseLayerId(i).getJavaClass()).toArray(Class[]::new); + /* GR-59854: The deprecation warning comes from this call to Proxy.getProxyClass. */ + Class proxy = Proxy.getProxyClass(interfaces[0].getClassLoader(), interfaces); + metaAccess.lookupJavaType(proxy); + return true; + } + return false; + } + + /** + * Load all lambda types of the given capturing class. Each method of the capturing class is + * parsed (see {@link LambdaParser#createMethodGraph(ResolvedJavaMethod, OptionValues)}). The + * lambda types can then be found in the constant nodes of the graphs. + */ + private void loadLambdaTypes(Class capturingClass) { + capturingClasses.computeIfAbsent(capturingClass, key -> { + LambdaParser.allExecutablesDeclaredInClass(universe.getOriginalMetaAccess().lookupJavaType(capturingClass)) + .filter(m -> m.getCode() != null) + .forEach(m -> loadLambdaTypes(m, universe.getBigbang())); + return true; + }); + } + + private static void loadLambdaTypes(ResolvedJavaMethod m, BigBang bigBang) { + StructuredGraph graph; + try { + graph = createMethodGraph(m, bigBang.getOptions()); + } catch (NoClassDefFoundError | BytecodeParser.BytecodeParserError e) { + /* Skip the method if it refers to a missing class */ + return; + } + + NodeIterable constantNodes = ConstantNode.getConstantNodes(graph); + + for (ConstantNode cNode : constantNodes) { + Class lambdaClass = getLambdaClassFromConstantNode(cNode); + + if (lambdaClass != null) { + bigBang.getMetaAccess().lookupJavaType(lambdaClass); + } + } + } + + private ResolvedJavaType getResolvedJavaTypeForBaseLayerId(int tid) { + return (tid == 0) ? null : getAnalysisTypeForBaseLayerId(tid).getWrapped(); + } + + /** + * Tries to look up the base layer type in the current VM. Some types cannot be looked up by + * name (for example $$Lambda types), so this method can return null. + */ + protected Class lookupBaseLayerTypeInHostVM(String type) { + int arrayType = 0; + String componentType = type; + /* + * We cannot look up an array type directly. We have to look up the component type and then + * go back to the array type. + */ + while (componentType.endsWith("[]")) { + componentType = componentType.substring(0, componentType.length() - 2); + arrayType++; + } + Class clazz = lookupPrimitiveClass(componentType); + if (clazz == null) { + clazz = imageLayerBuildingSupport.lookupClass(true, componentType); + } + if (clazz == null) { + return null; + } + while (arrayType > 0) { + assert clazz != null; + clazz = clazz.arrayType(); + arrayType--; + } + return clazz; + } + + private static Class lookupPrimitiveClass(String type) { + return switch (type) { + case "boolean" -> boolean.class; + case "byte" -> byte.class; + case "short" -> short.class; + case "char" -> char.class; + case "int" -> int.class; + case "long" -> long.class; + case "float" -> float.class; + case "double" -> double.class; + case "void" -> void.class; + default -> null; + }; + } + + private BaseLayerType getBaseLayerType(int tid) { + PersistedAnalysisType.Reader typeData = findType(tid); + ResolvedJavaType superClass = getResolvedJavaTypeForBaseLayerId(typeData.getSuperClassTypeId()); + ResolvedJavaType[] interfaces = streamInts(typeData.getInterfaces()).mapToObj(this::getResolvedJavaTypeForBaseLayerId).toArray(ResolvedJavaType[]::new); + return getBaseLayerType(typeData, tid, superClass, interfaces); + } + + private BaseLayerType getBaseLayerType(PersistedAnalysisType.Reader td, int tid, ResolvedJavaType superClass, ResolvedJavaType[] interfaces) { + return baseLayerTypes.computeIfAbsent(tid, (typeId) -> { + String className = td.getClassName().toString(); + String sourceFileName = td.hasSourceFileName() ? td.getSourceFileName().toString() : null; + ResolvedJavaType enclosingType = getResolvedJavaTypeForBaseLayerId(td.getEnclosingTypeId()); + ResolvedJavaType componentType = getResolvedJavaTypeForBaseLayerId(td.getComponentTypeId()); + ResolvedJavaType objectType = universe.getOriginalMetaAccess().lookupJavaType(Object.class); + Annotation[] annotations = getAnnotations(td.getAnnotationList()); + + return new BaseLayerType(className, tid, td.getModifiers(), td.getIsInterface(), td.getIsEnum(), td.getIsInitialized(), td.getIsInitializedAtBuildTime(), td.getIsLinked(), sourceFileName, + enclosingType, componentType, superClass, interfaces, objectType, annotations); + }); + } + + private Annotation[] getAnnotations(StructList.Reader reader) { + return IntStream.range(0, reader.size()).mapToObj(reader::get).map(this::getAnnotation).toArray(Annotation[]::new); + } + + private Annotation getAnnotation(SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.Reader a) { + String typeName = a.getTypeName().toString(); + Class annotationType = lookupBaseLayerTypeInHostVM(typeName).asSubclass(Annotation.class); + Map annotationValuesMap = new HashMap<>(); + a.getValues().forEach(v -> { + Object value = getAnnotationValue(v); + annotationValuesMap.put(v.getName().toString(), value); + }); + return AnnotationParser.annotationForMap(annotationType, annotationValuesMap); + } + + private Object getAnnotationValue(AnnotationValue.Reader v) { + return switch (v.which()) { + case STRING -> v.getString().toString(); + case ENUM -> getEnumValue(v.getEnum().getClassName(), v.getEnum().getName()); + case PRIMITIVE -> { + var p = v.getPrimitive(); + long rawValue = p.getRawValue(); + char typeChar = (char) p.getTypeChar(); + yield switch (JavaKind.fromPrimitiveOrVoidTypeChar(typeChar)) { + case Boolean -> rawValue != 0; + case Byte -> (byte) rawValue; + case Char -> (char) rawValue; + case Short -> (short) rawValue; + case Int -> (int) rawValue; + case Long -> rawValue; + case Float -> Float.intBitsToFloat((int) rawValue); + case Double -> Double.longBitsToDouble(rawValue); + default -> throw AnalysisError.shouldNotReachHere("Unknown annotation value type: " + typeChar); + }; + } + case PRIMITIVE_ARRAY -> getArray(v.getPrimitiveArray()); + case CLASS_NAME -> imageLayerBuildingSupport.lookupClass(false, v.getClassName().toString()); + case ANNOTATION -> getAnnotation(v.getAnnotation()); + case MEMBERS -> { + var m = v.getMembers(); + var mv = m.getMemberValues(); + Class membersClass = imageLayerBuildingSupport.lookupClass(false, m.getClassName().toString()); + var array = Array.newInstance(membersClass, mv.size()); + for (int i = 0; i < mv.size(); ++i) { + Array.set(array, i, getAnnotationValue(mv.get(i))); + } + yield array; + } + case _NOT_IN_SCHEMA -> throw AnalysisError.shouldNotReachHere("Unknown annotation value kind: " + v.which()); + }; + } + + @Override + public int lookupHostedTypeInBaseLayer(AnalysisType type) { + int id = getBaseLayerTypeId(type); + if (id == -1 || types.putIfAbsent(id, type) != null) { + /* A complete type is treated as a different type than its incomplete version */ + return -1; + } + return id; + } + + private int getBaseLayerTypeId(AnalysisType type) { + if (type.getWrapped() instanceof BaseLayerType baseLayerType) { + return baseLayerType.getBaseLayerId(); + } + String typeDescriptor = imageLayerSnapshotUtil.getTypeDescriptor(type); + Integer typeId = typeDescriptorToBaseLayerId.get(typeDescriptor); + if (typeId == null) { + /* The type was not reachable in the base image */ + return -1; + } + PersistedAnalysisType.Reader typeData = findType(typeId); + int id = typeData.getId(); + int hubIdentityHashCode = typeData.getHubIdentityHashCode(); + typeToHubIdentityHashCode.put(id, hubIdentityHashCode); + return id; + } + + @Override + public void initializeBaseLayerType(AnalysisType type) { + int id = getBaseLayerTypeId(type); + if (id == -1) { + return; + } + PersistedAnalysisType.Reader td = findType(id); + registerFlag(td.getIsInstantiated(), () -> type.registerAsInstantiated(PERSISTED)); + registerFlag(td.getIsUnsafeAllocated(), () -> type.registerAsUnsafeAllocated(PERSISTED)); + registerFlag(td.getIsReachable(), () -> type.registerAsReachable(PERSISTED)); + } + + private void registerFlag(boolean flag, Runnable runnable) { + if (flag) { + if (universe.getBigbang() != null) { + universe.getBigbang().postTask(debug -> runnable.run()); + } else { + heapScannerTasks.add(new AnalysisFuture<>(runnable)); + } + } + } + + public AnalysisMethod getAnalysisMethodForBaseLayerId(int mid) { + if (!methods.containsKey(mid)) { + PersistedAnalysisMethod.Reader methodData = findMethod(mid); + loadMethod(methodData); + } + + AnalysisMethod analysisMethod = methods.get(mid); + AnalysisError.guarantee(analysisMethod != null, "Method with id %d was not correctly loaded.", mid); + return analysisMethod; + } + + private PersistedAnalysisMethod.Reader findMethod(int mid) { + return binarySearchUnique(mid, snapshot.getMethods(), PersistedAnalysisMethod.Reader::getId); + } + + private void loadMethod(PersistedAnalysisMethod.Reader methodData) { + int mid = methodData.getId(); + + if (delegateLoadMethod(methodData)) { + return; + } + + int tid = methodData.getDeclaringTypeId(); + AnalysisType type = getAnalysisTypeForBaseLayerId(tid); + + AnalysisType[] parameterTypes = streamInts(methodData.getArgumentTypeIds()).mapToObj(this::getAnalysisTypeForBaseLayerId).toArray(AnalysisType[]::new); + + AnalysisType returnType = getAnalysisTypeForBaseLayerId(methodData.getReturnTypeId()); + + String name = methodData.getName().toString(); + if (methodData.hasClassName()) { + String className = methodData.getClassName().toString(); + + Executable method = null; + Class clazz = lookupBaseLayerTypeInHostVM(className); + if (clazz != null) { + Class[] argumentClasses = streamStrings(methodData.getArgumentClassNames()).map(this::lookupBaseLayerTypeInHostVM).toArray(Class[]::new); + method = lookupMethodByReflection(name, clazz, argumentClasses); + } + + if (method != null) { + metaAccess.lookupJavaMethod(method); + if (methods.containsKey(mid)) { + return; + } + } + } + + Class[] argumentClasses = Arrays.stream(parameterTypes).map(AnalysisType::getJavaClass).toArray(Class[]::new); + Executable method = lookupMethodByReflection(name, type.getJavaClass(), argumentClasses); + + if (method != null) { + metaAccess.lookupJavaMethod(method); + if (methods.containsKey(mid)) { + return; + } + } + + ResolvedSignature signature = ResolvedSignature.fromList(Arrays.stream(parameterTypes).toList(), returnType); + + if (name.equals(CONSTRUCTOR_NAME)) { + type.findConstructor(signature); + } else if (name.equals(CLASS_INIT_NAME)) { + type.getClassInitializer(); + } else { + type.findMethod(name, signature); + } + + if (!methods.containsKey(mid)) { + createBaseLayerMethod(methodData, mid, name, parameterTypes, returnType); + } + } + + protected boolean delegateLoadMethod(PersistedAnalysisMethod.Reader methodData) { + WrappedMethod.Reader wrappedMethod = methodData.getWrappedMethod(); + if (wrappedMethod.isNone()) { + return false; + } + if (wrappedMethod.isFactoryMethod()) { + WrappedMethod.FactoryMethod.Reader fm = wrappedMethod.getFactoryMethod(); + AnalysisMethod analysisMethod = getAnalysisMethodForBaseLayerId(fm.getTargetConstructorId()); + if (analysisMethod.wrapped instanceof BaseLayerMethod) { + return false; + } + AnalysisType instantiatedType = getAnalysisTypeForBaseLayerId(fm.getInstantiatedTypeId()); + FactoryMethodSupport.singleton().lookup(metaAccess, analysisMethod, instantiatedType, fm.getThrowAllocatedObject()); + return true; + } else if (wrappedMethod.isCEntryPointCallStub()) { + WrappedMethod.CEntryPointCallStub.Reader stub = wrappedMethod.getCEntryPointCallStub(); + boolean asNotPublished = stub.getNotPublished(); + AnalysisMethod originalMethod = getAnalysisMethodForBaseLayerId(stub.getOriginalMethodId()); + CEntryPointCallStubSupport.singleton().registerStubForMethod(originalMethod, () -> { + CEntryPointData data = CEntryPointData.create(originalMethod); + if (asNotPublished) { + data = data.copyWithPublishAs(CEntryPoint.Publish.NotPublished); + } + return data; + }); + return true; + } else if (wrappedMethod.isWrappedMember()) { + WrappedMember.Reader wm = wrappedMethod.getWrappedMember(); + Executable member = getWrappedMember(wm); + if (member == null) { + return false; + } + if (wm.isReflectionExpandSignature()) { + ImageSingletons.lookup(ReflectionFeature.class).getOrCreateAccessor(member); + } else if (wm.isJavaCallVariantWrapper()) { + JNIAccessFeature.singleton().addMethod(member, (FeatureImpl.DuringAnalysisAccessImpl) universe.getConcurrentAnalysisAccess()); + } + return true; + } + return false; + } + + private Executable getWrappedMember(WrappedMethod.WrappedMember.Reader memberData) { + String className = memberData.getDeclaringClassName().toString(); + Class declaringClass = imageLayerBuildingSupport.lookupClass(true, className); + if (declaringClass == null) { + return null; + } + String name = memberData.getName().toString(); + Class[] parameters = StreamSupport.stream(memberData.getArgumentTypeNames().spliterator(), false).map(Text.Reader::toString) + .map(c -> imageLayerBuildingSupport.lookupClass(false, c)).toArray(Class[]::new); + return lookupMethodByReflection(name, declaringClass, parameters); + } + + private static Executable lookupMethodByReflection(String name, Class clazz, Class[] argumentClasses) { + try { + Executable method; + if (name.equals(CONSTRUCTOR_NAME)) { + method = ReflectionUtil.lookupConstructor(true, clazz, argumentClasses); + } else { + method = ReflectionUtil.lookupMethod(true, clazz, name, argumentClasses); + } + return method; + } catch (NoClassDefFoundError e) { + return null; + } + } + + private void createBaseLayerMethod(PersistedAnalysisMethod.Reader md, int mid, String name, AnalysisType[] parameterTypes, AnalysisType returnType) { + AnalysisType type = getAnalysisTypeForBaseLayerId(md.getDeclaringTypeId()); + ResolvedSignature signature = ResolvedSignature.fromArray(parameterTypes, returnType); + byte[] code = md.hasCode() ? md.getCode().toArray() : null; + IntrinsicMethod methodHandleIntrinsic = !md.hasMethodHandleIntrinsicName() ? null + : IntrinsicMethod.valueOf(md.getMethodHandleIntrinsicName().toString()); + Annotation[] annotations = getAnnotations(md.getAnnotationList()); + + baseLayerMethods.computeIfAbsent(mid, + methodId -> new BaseLayerMethod(mid, type, name, md.getIsVarArgs(), signature, md.getCanBeStaticallyBound(), md.getIsConstructor(), + md.getModifiers(), md.getIsSynthetic(), code, md.getCodeSize(), methodHandleIntrinsic, annotations)); + BaseLayerMethod baseLayerMethod = baseLayerMethods.get(mid); + + universe.lookup(baseLayerMethod); + } + + @Override + public int lookupHostedMethodInBaseLayer(AnalysisMethod analysisMethod) { + return getBaseLayerMethodId(analysisMethod); + } + + private int getBaseLayerMethodId(AnalysisMethod analysisMethod) { + if (analysisMethod.getWrapped() instanceof BaseLayerMethod baseLayerMethod) { + return baseLayerMethod.getBaseLayerId(); + } + if (methods.containsKey(analysisMethod.getId())) { + return -1; + } + PersistedAnalysisMethod.Reader methodData = getMethodData(analysisMethod); + if (methodData == null) { + /* The method was not reachable in the base image */ + return -1; + } + return methodData.getId(); + } + + @Override + public void addBaseLayerMethod(AnalysisMethod analysisMethod) { + methods.putIfAbsent(analysisMethod.getId(), analysisMethod); + + PersistedAnalysisMethod.Reader md = getMethodData(analysisMethod); + registerFlag(md.getIsVirtualRootMethod(), () -> analysisMethod.registerAsVirtualRootMethod(PERSISTED)); + registerFlag(md.getIsDirectRootMethod(), () -> analysisMethod.registerAsDirectRootMethod(PERSISTED)); + registerFlag(md.getIsInvoked(), () -> analysisMethod.registerAsInvoked(PERSISTED)); + registerFlag(md.getIsImplementationInvoked(), () -> analysisMethod.registerAsImplementationInvoked(PERSISTED)); + registerFlag(md.getIsIntrinsicMethod(), () -> analysisMethod.registerAsIntrinsicMethod(PERSISTED)); + } + + private PersistedAnalysisMethod.Reader getMethodData(AnalysisMethod analysisMethod) { + if (analysisMethod.getWrapped() instanceof BaseLayerMethod m) { + return findMethod(m.getBaseLayerId()); + } + String descriptor = imageLayerSnapshotUtil.getMethodDescriptor(analysisMethod); + Integer id = methodDescriptorToBaseLayerId.get(descriptor); + return (id != null) ? findMethod(id) : null; + } + + /** + * See {@link SVMImageLayerWriter#persistAnalysisParsedGraphs()} for implementation. + */ + @Override + public boolean hasAnalysisParsedGraph(AnalysisMethod analysisMethod) { + if (!useSharedLayerGraphs) { + return false; + } + return hasGraph(analysisMethod, PersistedAnalysisMethod.Reader::hasAnalysisGraphLocation); + } + + @Override + public AnalysisParsedGraph getAnalysisParsedGraph(AnalysisMethod analysisMethod) { + PersistedAnalysisMethod.Reader methodData = getMethodData(analysisMethod); + boolean intrinsic = methodData.getAnalysisGraphIsIntrinsic(); + EncodedGraph analyzedGraph = getEncodedGraph(analysisMethod, methodData.getAnalysisGraphLocation()); + return new AnalysisParsedGraph(analyzedGraph, intrinsic); + } + + public boolean hasStrengthenedGraph(AnalysisMethod analysisMethod) { + return hasGraph(analysisMethod, PersistedAnalysisMethod.Reader::hasStrengthenedGraphLocation); + } + + public EncodedGraph getStrengthenedGraph(AnalysisMethod analysisMethod) { + PersistedAnalysisMethod.Reader methodData = getMethodData(analysisMethod); + return getEncodedGraph(analysisMethod, methodData.getStrengthenedGraphLocation()); + } + + private boolean hasGraph(AnalysisMethod analysisMethod, Function hasGraphFunction) { + var methodData = getMethodData(analysisMethod); + if (methodData == null) { + return false; + } + return hasGraphFunction.apply(methodData); + } + + private EncodedGraph getEncodedGraph(AnalysisMethod analysisMethod, Text.Reader location) { + byte[] encodedAnalyzedGraph = readEncodedGraph(location.toString()); + EncodedGraph encodedGraph = (EncodedGraph) ObjectCopier.decode(imageLayerSnapshotUtil.getGraphDecoder(this, analysisMethod, universe.getSnippetReflection()), encodedAnalyzedGraph); + for (int i = 0; i < encodedGraph.getNumObjects(); ++i) { + if (encodedGraph.getObject(i) instanceof CGlobalDataInfo cGlobalDataInfo) { + encodedGraph.setObject(i, CGlobalDataFeature.singleton().registerAsAccessedOrGet(cGlobalDataInfo.getData())); + } + } + return encodedGraph; + } + + private byte[] readEncodedGraph(String location) { + int closingBracketAt = location.length() - 1; + AnalysisError.guarantee(location.charAt(0) == '@' && location.charAt(closingBracketAt) == ']', "Location must start with '@' and end with ']': %s", location); + int openingBracketAt = location.indexOf('[', 1, closingBracketAt); + AnalysisError.guarantee(openingBracketAt < closingBracketAt, "Location does not contain '[' at expected location: %s", location); + long offset; + long nbytes; + try { + offset = Long.parseUnsignedLong(location.substring(1, openingBracketAt)); + nbytes = Long.parseUnsignedLong(location.substring(openingBracketAt + 1, closingBracketAt)); + } catch (NumberFormatException e) { + throw AnalysisError.shouldNotReachHere("Location contains invalid positive integer(s): " + location); + } + ByteBuffer bb = ByteBuffer.allocate(NumUtil.safeToInt(nbytes)); + try { + graphsChannel.read(bb, offset); + } catch (IOException e) { + throw AnalysisError.shouldNotReachHere("Failed reading a graph from location: " + location, e); + } + return bb.array(); + } + + /** + * This method is needed to ensure all the base layer analysis elements from the strengthened + * graph are created early enough and seen by the analysis. This is done by decoding the graph + * using a decoder that loads analysis elements instead of hosted elements. + */ + @Override + public void loadPriorStrengthenedGraphAnalysisElements(AnalysisMethod analysisMethod) { + if (hasStrengthenedGraph(analysisMethod)) { + PersistedAnalysisMethod.Reader methodData = getMethodData(analysisMethod); + byte[] encodedAnalyzedGraph = readEncodedGraph(methodData.getStrengthenedGraphLocation().toString()); + EncodedGraph graph = (EncodedGraph) ObjectCopier.decode(imageLayerSnapshotUtil.getGraphHostedToAnalysisElementsDecoder(this, analysisMethod, universe.getSnippetReflection()), + encodedAnalyzedGraph); + for (Object o : graph.getObjects()) { + if (o instanceof AnalysisMethod m) { + m.setReachableInCurrentLayer(); + } else if (o instanceof JavaMethodProfile javaMethodProfile) { + for (var m : javaMethodProfile.getMethods()) { + if (m.getMethod() instanceof AnalysisMethod aMethod) { + aMethod.setReachableInCurrentLayer(); + } + } + } else if (o instanceof ImageHeapConstant constant) { + loadMaterializedChildren(constant); + } + } + } + } + + private void loadMaterializedChildren(ImageHeapConstant constant) { + if (constant instanceof ImageHeapInstance imageHeapInstance) { + loadMaterializedChildren(constant, imageHeapInstance::getFieldValue, imageHeapInstance.getFieldValuesSize()); + } else if (constant instanceof ImageHeapObjectArray imageHeapObjectArray) { + loadMaterializedChildren(constant, imageHeapObjectArray::getElement, imageHeapObjectArray.getLength()); + } + } + + private void loadMaterializedChildren(ImageHeapConstant constant, IntFunction valuesFunction, int size) { + PersistedConstant.Reader baseLayerConstant = findConstant(ImageHeapConstant.getConstantID(constant)); + if (baseLayerConstant != null) { + StructList.Reader data = baseLayerConstant.getObject().getData(); + assert size == data.size() : "The size of the constant in the base layer does not match the size in the application: %d != %d".formatted(data.size(), size); + for (int i = 0; i < data.size(); ++i) { + ConstantReference.Reader childConstant = data.get(i); + if (childConstant.isObjectConstant()) { + if (childConstant.isNotMaterialized()) { + continue; + } + loadMaterializedChild(valuesFunction.apply(i)); + } + } + } + } + + private void loadMaterializedChild(Object child) { + if (child instanceof AnalysisFuture analysisFuture) { + if (analysisFuture.ensureDone() instanceof ImageHeapConstant imageHeapConstant) { + loadMaterializedChildren(imageHeapConstant); + } + } + } + + public AnalysisField getAnalysisFieldForBaseLayerId(int fid) { + if (!fields.containsKey(fid)) { + loadField(findField(fid)); + } + + AnalysisField analysisField = fields.get(fid); + AnalysisError.guarantee(analysisField != null, "Field with id %d was not correctly loaded.", fid); + return analysisField; + } + + private PersistedAnalysisField.Reader findField(int fid) { + return binarySearchUnique(fid, snapshot.getFields(), PersistedAnalysisField.Reader::getId); + } + + private void loadField(PersistedAnalysisField.Reader fieldData) { + AnalysisType declaringClass = getAnalysisTypeForBaseLayerId(fieldData.getDeclaringTypeId()); + String className = fieldData.hasClassName() ? fieldData.getClassName().toString() : null; + int id = fieldData.getId(); + + Class clazz = className != null ? lookupBaseLayerTypeInHostVM(className) : declaringClass.getJavaClass(); + if (clazz == null) { + clazz = declaringClass.getJavaClass(); + } + + Field field; + try { + field = ReflectionUtil.lookupField(true, clazz, fieldData.getName().toString()); + } catch (Throwable e) { + field = null; + } + + if (field == null && !(declaringClass.getWrapped() instanceof BaseLayerType)) { + if (fieldData.getIsStatic()) { + declaringClass.getStaticFields(); + } else { + declaringClass.getInstanceFields(true); + } + + if (fields.containsKey(id)) { + return; + } + } + + if (field == null) { + AnalysisType type = getAnalysisTypeForBaseLayerId(fieldData.getTypeId()); + BaseLayerField baseLayerField = getBaseLayerField(fieldData, id, declaringClass.getWrapped(), type.getWrapped()); + universe.lookup(baseLayerField); + } else { + metaAccess.lookupJavaField(field); + } + } + + private BaseLayerField getBaseLayerField(int id) { + PersistedAnalysisField.Reader fieldData = findField(id); + + BaseLayerType declaringClass = getBaseLayerType(fieldData.getDeclaringTypeId()); + ResolvedJavaType type = getResolvedJavaTypeForBaseLayerId(fieldData.getTypeId()); + + return getBaseLayerField(fieldData, id, declaringClass, type); + } + + private BaseLayerField getBaseLayerField(PersistedAnalysisField.Reader fd, int id, ResolvedJavaType declaringClass, ResolvedJavaType type) { + return baseLayerFields.computeIfAbsent(id, + fid -> new BaseLayerField(id, fd.getName().toString(), declaringClass, type, fd.getIsInternal(), + fd.getIsSynthetic(), fd.getModifiers(), getAnnotations(fd.getAnnotationList()))); + } + + @Override + public int lookupHostedFieldInBaseLayer(AnalysisField analysisField) { + return getBaseLayerFieldId(analysisField); + } + + private int getBaseLayerFieldId(AnalysisField analysisField) { + if (analysisField.wrapped instanceof BaseLayerField baseLayerField) { + return baseLayerField.getBaseLayerId(); + } + PersistedAnalysisField.Reader fieldData = getFieldData(analysisField); + if (fieldData == null) { + /* The field was not reachable in the base image */ + return -1; + } + return fieldData.getId(); + } + + @Override + public void addBaseLayerField(AnalysisField analysisField) { + fields.putIfAbsent(analysisField.getId(), analysisField); + } + + @Override + public void initializeBaseLayerField(AnalysisField analysisField) { + PersistedAnalysisField.Reader fieldData = getFieldData(analysisField); + + int fieldCheckIndex = fieldData.getFieldCheckIndex(); + if (fieldCheckIndex != -1) { + StaticFinalFieldFoldingFeature.singleton().putBaseLayerFieldCheckIndex(analysisField.getId(), fieldCheckIndex); + } + + assert fieldData != null : "The field should be in the base layer"; + int location = fieldData.getLocation(); + if (location != 0) { + fieldLocations.put(analysisField, location); + } + + boolean isAccessed = fieldData.getIsAccessed(); + boolean isRead = fieldData.getIsRead(); + if (!analysisField.isStatic() && (isAccessed || isRead)) { + analysisField.getDeclaringClass().getInstanceFields(true); + } + registerFlag(isAccessed, () -> analysisField.registerAsAccessed(PERSISTED)); + registerFlag(isRead, () -> analysisField.registerAsRead(PERSISTED)); + registerFlag(fieldData.getIsWritten(), () -> analysisField.registerAsWritten(PERSISTED)); + registerFlag(fieldData.getIsFolded(), () -> analysisField.registerAsFolded(PERSISTED)); + } + + private PersistedAnalysisField.Reader getFieldData(AnalysisField analysisField) { + if (analysisField.wrapped instanceof BaseLayerField baseLayerField) { + return findField(baseLayerField.getBaseLayerId()); + } + String declTypeDescriptor = imageLayerSnapshotUtil.getTypeDescriptor(analysisField.getDeclaringClass()); + Integer declTypeId = typeDescriptorToBaseLayerId.get(declTypeDescriptor); + if (declTypeId == null) { + return null; + } + PersistedAnalysisType.Reader typeData = findType(declTypeId); + PrimitiveList.Int.Reader fieldIds; + if (analysisField.isStatic()) { + fieldIds = typeData.getStaticFieldIds(); + } else { + fieldIds = typeData.getInstanceFieldIds(); + } + for (int i = 0; i < fieldIds.size(); i++) { + PersistedAnalysisField.Reader fieldData = findField(fieldIds.get(i)); + if (fieldData != null && analysisField.getName().equals(fieldData.getName().toString())) { + return fieldData; + } + } + return null; + } + + public void executeHeapScannerTasks() { + guarantee(universe.getHeapScanner() != null, "Those tasks should only be executed when the bigbang is not null."); + for (AnalysisFuture task : heapScannerTasks) { + task.ensureDone(); + } + } + + @Override + public boolean hasValueForConstant(JavaConstant javaConstant) { + Object object = hostedValuesProvider.asObject(Object.class, javaConstant); + return hasValueForObject(object); + } + + @SuppressFBWarnings(value = "ES", justification = "Reference equality check needed to detect intern status") + private boolean hasValueForObject(Object object) { + if (object instanceof DynamicHub dynamicHub) { + AnalysisType type = ((SVMHost) universe.hostVM()).lookupType(dynamicHub); + return typeToConstant.containsKey(type.getId()); + } else if (object instanceof String string) { + return stringToConstant.containsKey(string) && string.intern() == string; + } else if (object instanceof Enum) { + return enumToConstant.containsKey(object); + } + return false; + } + + @Override + public ImageHeapConstant getValueForConstant(JavaConstant javaConstant) { + Object object = hostedValuesProvider.asObject(Object.class, javaConstant); + return getValueForObject(object); + } + + private ImageHeapConstant getValueForObject(Object object) { + if (object instanceof DynamicHub dynamicHub) { + AnalysisType type = ((SVMHost) universe.hostVM()).lookupType(dynamicHub); + int id = typeToConstant.get(type.getId()); + return getOrCreateConstant(id); + } else if (object instanceof String string) { + int id = stringToConstant.get(string); + return getOrCreateConstant(id); + } else if (object instanceof Enum) { + int id = enumToConstant.get(object); + return getOrCreateConstant(id); + } + throw AnalysisError.shouldNotReachHere("The constant was not in the persisted heap."); + } + + @Override + public Set getRelinkedFields(AnalysisType type) { + return imageLayerSnapshotUtil.getRelinkedFields(type, metaAccess); + } + + public ImageHeapConstant getOrCreateConstant(int id) { + return getOrCreateConstant(id, null); + } + + /** + * Get the {@link ImageHeapConstant} representation for a specific base layer constant id. If + * known, the parentReachableHostedObject will point to the corresponding constant in the + * underlying host VM, found by querying the parent object that made this constant reachable + * (see {@link SVMImageLayerLoader#getReachableHostedValue(ImageHeapConstant, int)}). + */ + private ImageHeapConstant getOrCreateConstant(int id, JavaConstant parentReachableHostedObjectCandidate) { + if (constants.containsKey(id)) { + return constants.get(id); + } + PersistedConstant.Reader baseLayerConstant = findConstant(id); + if (baseLayerConstant == null) { + throw GraalError.shouldNotReachHere("The constant was not reachable in the base image"); + } + + AnalysisType type = getAnalysisTypeForBaseLayerId(baseLayerConstant.getTypeId()); + + long objectOffset = baseLayerConstant.getObjectOffset(); + int identityHashCode = baseLayerConstant.getIdentityHashCode(); + + JavaConstant parentReachableHostedObject; + if (parentReachableHostedObjectCandidate == null) { + int parentConstantId = baseLayerConstant.getParentConstantId(); + if (parentConstantId != 0) { + ImageHeapConstant parentConstant = getOrCreateConstant(parentConstantId); + int index = baseLayerConstant.getParentIndex(); + parentReachableHostedObject = getReachableHostedValue(parentConstant, index); + } else { + parentReachableHostedObject = null; + } + } else { + parentReachableHostedObject = parentReachableHostedObjectCandidate; + } + + if (parentReachableHostedObject != null && !type.getJavaClass().equals(Class.class)) { + /* + * The hash codes of DynamicHubs need to be injected before they are used in a map, + * which happens right after their creation. The injection of their hash codes can be + * found in SVMHost#registerType. + * + * Also, for DynamicHub constants, the identity hash code persisted is the hash code of + * the Class object, which we do not want to inject in the DynamicHub. + */ + injectIdentityHashCode(hostedValuesProvider.asObject(Object.class, parentReachableHostedObject), identityHashCode); + } + switch (baseLayerConstant.which()) { + case OBJECT -> { + switch (baseLayerConstant.getObject().which()) { + case INSTANCE -> { + StructList.Reader instanceData = baseLayerConstant.getObject().getData(); + JavaConstant foundHostedObject = lookupHostedObject(baseLayerConstant, type); + if (foundHostedObject != null && parentReachableHostedObject != null) { + Object foundObject = hostedValuesProvider.asObject(Object.class, foundHostedObject); + Object reachableObject = hostedValuesProvider.asObject(Object.class, parentReachableHostedObject); + guarantee(foundObject == reachableObject, "Found discrepancy between recipe-found hosted value %s and parent-reachable hosted value %s.", foundObject, + reachableObject); + } + + addBaseLayerObject(id, objectOffset, () -> { + ImageHeapInstance imageHeapInstance = new ImageHeapInstance(type, foundHostedObject == null ? parentReachableHostedObject : foundHostedObject, identityHashCode, id); + if (instanceData != null) { + Object[] fieldValues = getReferencedValues(imageHeapInstance, instanceData, imageLayerSnapshotUtil.getRelinkedFields(type, metaAccess)); + imageHeapInstance.setFieldValues(fieldValues); + } + return imageHeapInstance; + }); + } + case OBJECT_ARRAY -> { + StructList.Reader arrayData = baseLayerConstant.getObject().getData(); + addBaseLayerObject(id, objectOffset, () -> { + ImageHeapObjectArray imageHeapObjectArray = new ImageHeapObjectArray(type, null, arrayData.size(), identityHashCode, id); + Object[] elementsValues = getReferencedValues(imageHeapObjectArray, arrayData, Set.of()); + imageHeapObjectArray.setElementValues(elementsValues); + return imageHeapObjectArray; + }); + } + default -> throw GraalError.shouldNotReachHere("Unknown object type: " + baseLayerConstant.getObject().which()); + } + } + case PRIMITIVE_DATA -> { + Object array = getArray(baseLayerConstant.getPrimitiveData()); + addBaseLayerObject(id, objectOffset, () -> new ImageHeapPrimitiveArray(type, null, array, Array.getLength(array), identityHashCode, id)); + } + case RELOCATABLE -> { + String key = baseLayerConstant.getRelocatable().getKey().toString(); + addBaseLayerObject(id, objectOffset, () -> ImageHeapRelocatableConstant.create(type, key, id)); + } + default -> throw GraalError.shouldNotReachHere("Unknown constant type: " + baseLayerConstant.which()); + } + + return constants.get(id); + } + + private Object[] getReferencedValues(ImageHeapConstant parentConstant, StructList.Reader data, Set positionsToRelink) { + Object[] values = new Object[data.size()]; + for (int position = 0; position < data.size(); ++position) { + ConstantReference.Reader constantData = data.get(position); + if (delegateProcessing(constantData, values, position)) { + continue; + } + values[position] = switch (constantData.which()) { + case OBJECT_CONSTANT -> { + int constantId = constantData.getObjectConstant().getConstantId(); + boolean relink = positionsToRelink.contains(position); + int finalPosition = position; + yield new AnalysisFuture<>(() -> { + ensureHubInitialized(parentConstant); + + JavaConstant hostedConstant = relink ? getReachableHostedValue(parentConstant, finalPosition) : null; + ImageHeapConstant baseLayerConstant = getOrCreateConstant(constantId, hostedConstant); + values[finalPosition] = baseLayerConstant; + + ensureHubInitialized(baseLayerConstant); + + if (hostedConstant != null) { + addBaseLayerValueToImageHeap(baseLayerConstant, parentConstant, finalPosition); + } + + return baseLayerConstant; + }); + } + case NULL_POINTER -> JavaConstant.NULL_POINTER; + case NOT_MATERIALIZED -> + /* + * This constant is a field value or an object value that was not materialized + * in the base image. + */ + new AnalysisFuture<>(() -> { + throw AnalysisError.shouldNotReachHere("This constant was not materialized in the base image."); + }); + case PRIMITIVE_VALUE -> { + PrimitiveValue.Reader pv = constantData.getPrimitiveValue(); + yield JavaConstant.forPrimitive((char) pv.getTypeChar(), pv.getRawValue()); + } + default -> throw GraalError.shouldNotReachHere("Unexpected constant reference: " + constantData.which()); + }; + } + return values; + } + + private boolean delegateProcessing(ConstantReference.Reader constantRef, Object[] values, int i) { + if (constantRef.isMethodPointer()) { + AnalysisFuture task = new AnalysisFuture<>(() -> { + AnalysisType methodPointerType = metaAccess.lookupJavaType(MethodPointer.class); + int mid = constantRef.getMethodPointer().getMethodId(); + AnalysisMethod method = getAnalysisMethodForBaseLayerId(mid); + RelocatableConstant constant = new RelocatableConstant(new MethodPointer(method), methodPointerType); + values[i] = constant; + return constant; + }); + values[i] = task; + return true; + } else if (constantRef.isCEntryPointLiteralCodePointer()) { + AnalysisType cEntryPointerLiteralPointerType = metaAccess.lookupJavaType(CEntryPointLiteralCodePointer.class); + CEntryPointLiteralReference.Reader ref = constantRef.getCEntryPointLiteralCodePointer(); + String methodName = ref.getMethodName().toString(); + Class definingClass = lookupBaseLayerTypeInHostVM(ref.getDefiningClass().toString()); + Class[] parameterTypes = IntStream.range(0, ref.getParameterNames().size()) + .mapToObj(j -> ref.getParameterNames().get(j).toString()) + .map(this::lookupBaseLayerTypeInHostVM).toArray(Class[]::new); + values[i] = new RelocatableConstant(new CEntryPointLiteralCodePointer(definingClass, methodName, parameterTypes), cEntryPointerLiteralPointerType); + return true; + } + return false; + } + + private static Object getArray(PrimitiveArray.Reader reader) { + return switch (reader.which()) { + case Z -> getBooleans(reader.getZ()); + case B -> toArray(reader.getB(), r -> IntStream.range(0, r.size()).collect(() -> new byte[r.size()], (a, i) -> a[i] = r.get(i), combineUnsupported())); + case S -> toArray(reader.getS(), r -> IntStream.range(0, r.size()).collect(() -> new short[r.size()], (a, i) -> a[i] = r.get(i), combineUnsupported())); + case C -> toArray(reader.getC(), r -> IntStream.range(0, r.size()).collect(() -> new char[r.size()], (a, i) -> a[i] = (char) r.get(i), combineUnsupported())); + case I -> toArray(reader.getI(), r -> IntStream.range(0, r.size()).collect(() -> new int[r.size()], (a, i) -> a[i] = r.get(i), combineUnsupported())); + case F -> toArray(reader.getF(), r -> IntStream.range(0, r.size()).collect(() -> new float[r.size()], (a, i) -> a[i] = r.get(i), combineUnsupported())); + case J -> toArray(reader.getJ(), r -> IntStream.range(0, r.size()).collect(() -> new long[r.size()], (a, i) -> a[i] = r.get(i), combineUnsupported())); + case D -> toArray(reader.getD(), r -> IntStream.range(0, r.size()).collect(() -> new double[r.size()], (a, i) -> a[i] = r.get(i), combineUnsupported())); + case _NOT_IN_SCHEMA -> throw new IllegalArgumentException("Unsupported kind: " + reader.which()); + }; + } + + protected static boolean[] getBooleans(PrimitiveList.Boolean.Reader r) { + return IntStream.range(0, r.size()).collect(() -> new boolean[r.size()], (a, i) -> a[i] = r.get(i), combineUnsupported()); + } + + /** Enables concise one-liners without explicit types in {@link #getArray}. */ + private static A toArray(T reader, Function fun) { + return fun.apply(reader); + } + + private static BiConsumer combineUnsupported() { + return (u, v) -> { + throw new UnsupportedOperationException("Combining partial results not supported, streams must be sequential"); + }; + } + + /** + * For a parent constant return the referenced field-position or array-element-index value + * corresponding to index. + */ + private JavaConstant getReachableHostedValue(ImageHeapConstant parentConstant, int index) { + if (parentConstant instanceof ImageHeapObjectArray array) { + return getHostedElementValue(array, index); + } else if (parentConstant instanceof ImageHeapInstance instance) { + AnalysisField field = getFieldFromIndex(instance, index); + return getHostedFieldValue(instance, field); + } else { + throw AnalysisError.shouldNotReachHere("unexpected constant: " + parentConstant); + } + } + + private JavaConstant getHostedElementValue(ImageHeapObjectArray array, int idx) { + JavaConstant hostedArray = array.getHostedObject(); + JavaConstant rawElementValue = null; + if (hostedArray != null) { + rawElementValue = hostedValuesProvider.readArrayElement(hostedArray, idx); + } + return rawElementValue; + } + + private JavaConstant getHostedFieldValue(ImageHeapInstance instance, AnalysisField field) { + ValueSupplier rawFieldValue; + try { + JavaConstant hostedInstance = instance.getHostedObject(); + AnalysisError.guarantee(hostedInstance != null); + rawFieldValue = hostedValuesProvider.readFieldValue(field, hostedInstance); + } catch (InternalError | TypeNotPresentException | LinkageError e) { + /* Ignore missing type errors. */ + return null; + } + return rawFieldValue.get(); + } + + private static AnalysisField getFieldFromIndex(ImageHeapInstance instance, int i) { + return (AnalysisField) instance.getType().getInstanceFields(true)[i]; + } + + private void addBaseLayerObject(int id, long objectOffset, Supplier imageHeapConstantSupplier) { + constants.computeIfAbsent(id, key -> { + ImageHeapConstant heapObj = imageHeapConstantSupplier.get(); + heapObj.markInBaseLayer(); + /* + * Packages are normally rescanned when the DynamicHub is initialized. However, since + * they are not relinked, the packages from the base layer will never be marked as + * reachable without doing so manually. + */ + if (heapObj.getType().getJavaClass().equals(Package.class)) { + universe.getHeapScanner().doScan(heapObj); + } + if (objectOffset != -1) { + objectOffsets.put(ImageHeapConstant.getConstantID(heapObj), objectOffset); + } + return heapObj; + }); + } + + /** + * Look up an object in current hosted VM based on the recipe serialized from the base layer. + */ + private JavaConstant lookupHostedObject(PersistedConstant.Reader baseLayerConstant, AnalysisType analysisType) { + if (!baseLayerConstant.getIsSimulated()) { + Class clazz = analysisType.getJavaClass(); + return lookupHostedObject(baseLayerConstant, clazz); + } + return null; + } + + private JavaConstant lookupHostedObject(PersistedConstant.Reader baseLayerConstant, Class clazz) { + if (!baseLayerConstant.isObject()) { + return null; + } + PersistedConstant.Object.Relinking.Reader relinking = baseLayerConstant.getObject().getRelinking(); + if (relinking.isNotRelinked()) { + return null; + } + if (clazz.equals(Class.class)) { + /* DynamicHub corresponding to $$TypeSwitch classes are not relinked */ + if (baseLayerConstant.isObject() && relinking.isClassConstant()) { + int typeId = relinking.getClassConstant().getTypeId(); + return getDynamicHub(typeId); + } + } else if (clazz.equals(String.class)) { + assert relinking.isStringConstant(); + StringConstant.Reader stringConstant = relinking.getStringConstant(); + if (stringConstant.hasValue()) { + String value = stringConstant.getValue().toString(); + Object object = value.intern(); + return hostedValuesProvider.forObject(object); + } + } else if (Enum.class.isAssignableFrom(clazz)) { + assert relinking.isEnumConstant(); + EnumConstant.Reader enumConstant = relinking.getEnumConstant(); + Enum enumValue = getEnumValue(enumConstant.getEnumClass(), enumConstant.getEnumName()); + return hostedValuesProvider.forObject(enumValue); + } + return null; + } + + @SuppressWarnings("unchecked") + private Enum getEnumValue(Text.Reader className, Text.Reader name) { + Class enumClass = imageLayerBuildingSupport.lookupClass(false, className.toString()); + /* asSubclass produces an "unchecked" warning */ + return Enum.valueOf(enumClass.asSubclass(Enum.class), name.toString()); + } + + private void addBaseLayerValueToImageHeap(ImageHeapConstant constant, ImageHeapConstant parentConstant, int i) { + if (parentConstant instanceof ImageHeapInstance imageHeapInstance) { + universe.getHeapScanner().registerBaseLayerValue(constant, getFieldFromIndex(imageHeapInstance, i)); + } else if (parentConstant instanceof ImageHeapObjectArray) { + universe.getHeapScanner().registerBaseLayerValue(constant, i); + } else { + throw AnalysisError.shouldNotReachHere("unexpected constant: " + constant); + } + } + + private void ensureHubInitialized(ImageHeapConstant constant) { + JavaConstant javaConstant = constant.getHostedObject(); + if (constant.getType().getJavaClass().equals(Class.class)) { + DynamicHub hub = universe.getHostedValuesProvider().asObject(DynamicHub.class, javaConstant); + AnalysisType type = ((SVMHost) universe.hostVM()).lookupType(hub); + ensureHubInitialized(type); + /* + * If the persisted constant contains a non-null arrayHub, the corresponding DynamicHub + * must be created and the initializeMetaDataTask needs to be executed to ensure the + * hosted object matches the persisted constant. + */ + if (((ImageHeapInstance) constant).getFieldValue(metaAccess.lookupJavaField(dynamicHubArrayHubField)) != JavaConstant.NULL_POINTER && hub.getArrayHub() == null) { + AnalysisType arrayClass = type.getArrayClass(); + ensureHubInitialized(arrayClass); + } + } + } + + private static void ensureHubInitialized(AnalysisType type) { + type.registerAsReachable(PERSISTED); + type.getInitializeMetaDataTask().ensureDone(); + } + + public Long getObjectOffset(JavaConstant javaConstant) { + ImageHeapConstant imageHeapConstant = (ImageHeapConstant) javaConstant; + return objectOffsets.get(ImageHeapConstant.getConstantID(imageHeapConstant)); + } + + public int getFieldLocation(AnalysisField field) { + return fieldLocations.get(field); + } + + public ImageHeapConstant getBaseLayerStaticPrimitiveFields() { + return getOrCreateConstant(snapshot.getStaticPrimitiveFieldsConstantId()); + } + + public ImageHeapConstant getBaseLayerStaticObjectFields() { + return getOrCreateConstant(snapshot.getStaticObjectFieldsConstantId()); + } + + public long getImageHeapSize() { + return snapshot.getImageHeapSize(); + } + + @Override + public boolean hasDynamicHubIdentityHashCode(int tid) { + return typeToHubIdentityHashCode.containsKey(tid); + } + + @Override + public int getDynamicHubIdentityHashCode(int tid) { + return typeToHubIdentityHashCode.get(tid); + } + + private JavaConstant getDynamicHub(int tid) { + AnalysisType type = getAnalysisTypeForBaseLayerId(tid); + DynamicHub hub = ((SVMHost) universe.hostVM()).dynamicHub(type); + return hostedValuesProvider.forObject(hub); + } + + private static void injectIdentityHashCode(Object object, Integer identityHashCode) { + if (object == null || identityHashCode == null) { + return; + } + boolean result = IdentityHashCodeUtil.injectIdentityHashCode(object, identityHashCode); + if (!result) { + if (SubstrateOptions.LoggingHashCodeInjection.getValue()) { + LogUtils.warning("Object of type %s already had an hash code: %s", object.getClass(), object); + } + } + } + + public void rescanHub(AnalysisType type, Object hubObject) { + DynamicHub hub = (DynamicHub) hubObject; + universe.getHeapScanner().rescanObject(hub); + universe.getHeapScanner().rescanField(hub, SVMImageLayerSnapshotUtil.classInitializationInfo); + if (type.getJavaKind() == JavaKind.Object) { + if (type.isArray()) { + universe.getHeapScanner().rescanField(hub.getComponentHub(), SVMImageLayerSnapshotUtil.arrayHub); + } + universe.getHeapScanner().rescanField(hub, SVMImageLayerSnapshotUtil.interfacesEncoding); + if (type.isEnum()) { + universe.getHeapScanner().rescanField(hub, SVMImageLayerSnapshotUtil.enumConstantsReference); + } + } + } + + public ClassInitializationInfo getClassInitializationInfo(AnalysisType type) { + PersistedAnalysisType.Reader typeMap = findType(type.getId()); + + var initInfo = typeMap.getClassInitializationInfo(); + if (initInfo.getIsNoInitializerNoTracking()) { + return ClassInitializationInfo.forNoInitializerInfo(false); + } else if (initInfo.getIsInitializedNoTracking()) { + return ClassInitializationInfo.forInitializedInfo(false); + } else if (initInfo.getIsFailedNoTracking()) { + return ClassInitializationInfo.forFailedInfo(false); + } else { + boolean isTracked = initInfo.getIsTracked(); + + ClassInitializationInfo.InitState initState; + if (initInfo.getIsInitialized()) { + initState = ClassInitializationInfo.InitState.FullyInitialized; + } else if (initInfo.getIsInErrorState()) { + initState = ClassInitializationInfo.InitState.InitializationError; + } else { + assert initInfo.getIsLinked() : "Invalid state"; + int classInitializerId = initInfo.getInitializerMethodId(); + MethodPointer classInitializer = (classInitializerId == 0) ? null : new MethodPointer(getAnalysisMethodForBaseLayerId(classInitializerId)); + return new ClassInitializationInfo(classInitializer, isTracked); + } + + return new ClassInitializationInfo(initState, initInfo.getHasInitializer(), initInfo.getIsBuildTimeInitialized(), isTracked); + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerSingletonLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerSingletonLoader.java new file mode 100644 index 000000000000..06942a9695e0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerSingletonLoader.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.imagelayer; + +import static com.oracle.svm.hosted.imagelayer.SVMImageLayerLoader.getBooleans; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.capnproto.Text; +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.UnmodifiableEconomicMap; + +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton.PersistFlags; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ImageSingletonKey; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ImageSingletonObject; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.KeyStoreEntry; +import com.oracle.svm.util.ReflectionUtil; + +public class SVMImageLayerSingletonLoader { + private final HostedImageLayerBuildingSupport imageLayerBuildingSupport; + private final SharedLayerSnapshotCapnProtoSchemaHolder.SharedLayerSnapshot.Reader snapshot; + + public SVMImageLayerSingletonLoader(HostedImageLayerBuildingSupport imageLayerBuildingSupport, SharedLayerSnapshotCapnProtoSchemaHolder.SharedLayerSnapshot.Reader snapshot) { + this.imageLayerBuildingSupport = imageLayerBuildingSupport; + this.snapshot = snapshot; + } + + public Map>> loadImageSingletons(Object forbiddenObject) { + return loadImageSingletons0(forbiddenObject); + } + + private Map>> loadImageSingletons0(Object forbiddenObject) { + Map idToObjectMap = new HashMap<>(); + for (ImageSingletonObject.Reader obj : snapshot.getSingletonObjects()) { + String className = obj.getClassName().toString(); + + EconomicMap keyStore = EconomicMap.create(); + for (KeyStoreEntry.Reader entry : obj.getStore()) { + KeyStoreEntry.Value.Reader v = entry.getValue(); + Object value = switch (v.which()) { + case I -> v.getI(); + case J -> v.getJ(); + case STR -> v.getStr().toString(); + case IL -> Stream.of(v.getIl()).flatMapToInt(r -> IntStream.range(0, r.size()).map(r::get)).toArray(); + case ZL -> getBooleans(v.getZl()); + case STRL -> StreamSupport.stream(v.getStrl().spliterator(), false).map(Text.Reader::toString).toArray(String[]::new); + case _NOT_IN_SCHEMA -> throw new IllegalStateException("Unexpected value: " + v.which()); + }; + keyStore.put(entry.getKey().toString(), value); + } + + // create singleton object instance + Object result; + try { + Class clazz = imageLayerBuildingSupport.lookupClass(false, className); + Method createMethod = ReflectionUtil.lookupMethod(clazz, "createFromLoader", ImageSingletonLoader.class); + result = createMethod.invoke(null, new ImageSingletonLoaderImpl(keyStore, snapshot)); + } catch (Throwable t) { + throw VMError.shouldNotReachHere("Failed to recreate image singleton", t); + } + + idToObjectMap.put(obj.getId(), result); + } + + Map>> singletonInitializationMap = new HashMap<>(); + for (ImageSingletonKey.Reader entry : snapshot.getSingletonKeys()) { + String className = entry.getKeyClassName().toString(); + PersistFlags persistInfo = PersistFlags.values()[entry.getPersistFlag()]; + int id = entry.getObjectId(); + if (persistInfo == PersistFlags.CREATE) { + assert id != -1 : "Create image singletons should be linked to an object"; + Object singletonObject = idToObjectMap.get(id); + Class clazz = imageLayerBuildingSupport.lookupClass(false, className); + singletonInitializationMap.computeIfAbsent(singletonObject, (k) -> new HashSet<>()); + singletonInitializationMap.get(singletonObject).add(clazz); + } else if (persistInfo == PersistFlags.FORBIDDEN) { + assert id == -1 : "Unrestored image singleton should not be linked to an object"; + Class clazz = imageLayerBuildingSupport.lookupClass(false, className); + singletonInitializationMap.computeIfAbsent(forbiddenObject, (k) -> new HashSet<>()); + singletonInitializationMap.get(forbiddenObject).add(clazz); + } else { + assert persistInfo == PersistFlags.NOTHING : "Unexpected PersistFlags value: " + persistInfo; + assert id == -1 : "Unrestored image singleton should not be linked to an object"; + } + } + + return singletonInitializationMap; + } + + public Class lookupClass(boolean optional, String className) { + return imageLayerBuildingSupport.lookupClass(optional, className); + } + + public static class ImageSingletonLoaderImpl implements ImageSingletonLoader { + private final UnmodifiableEconomicMap keyStore; + private final SharedLayerSnapshotCapnProtoSchemaHolder.SharedLayerSnapshot.Reader snapshotReader; + + ImageSingletonLoaderImpl(UnmodifiableEconomicMap keyStore, SharedLayerSnapshotCapnProtoSchemaHolder.SharedLayerSnapshot.Reader snapshotReader) { + this.keyStore = keyStore; + this.snapshotReader = snapshotReader; + } + + @Override + public List readBoolList(String keyName) { + boolean[] l = (boolean[]) keyStore.get(keyName); + return IntStream.range(0, l.length).mapToObj(i -> l[i]).toList(); + } + + @Override + public int readInt(String keyName) { + return (int) keyStore.get(keyName); + } + + @Override + public List readIntList(String keyName) { + int[] l = (int[]) keyStore.get(keyName); + return IntStream.of(l).boxed().toList(); + } + + @Override + public long readLong(String keyName) { + return (long) keyStore.get(keyName); + } + + @Override + public String readString(String keyName) { + return (String) keyStore.get(keyName); + } + + @Override + public List readStringList(String keyName) { + return List.of((String[]) keyStore.get(keyName)); + } + + public SharedLayerSnapshotCapnProtoSchemaHolder.SharedLayerSnapshot.Reader getSnapshotReader() { + return snapshotReader; + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerSnapshotUtil.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerSnapshotUtil.java similarity index 56% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerSnapshotUtil.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerSnapshotUtil.java index 5b395f31dc66..5b78e478191f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerSnapshotUtil.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerSnapshotUtil.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.hosted.heap; +package com.oracle.svm.hosted.imagelayer; import static com.oracle.svm.hosted.methodhandles.InjectedInvokerRenamingSubstitutionProcessor.isInjectedInvokerType; import static com.oracle.svm.hosted.methodhandles.MethodHandleInvokerRenamingSubstitutionProcessor.isMethodHandleType; @@ -35,19 +35,25 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.graalvm.nativeimage.ImageSingletons; -import com.oracle.graal.pointsto.heap.ImageLayerLoader; -import com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil; -import com.oracle.graal.pointsto.heap.ImageLayerWriter; +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.graal.pointsto.heap.ImageHeapInstance; +import com.oracle.graal.pointsto.heap.ImageHeapObjectArray; +import com.oracle.graal.pointsto.heap.ImageHeapPrimitiveArray; +import com.oracle.graal.pointsto.infrastructure.OriginalMethodProvider; import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.PointsToAnalysisField; +import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; +import com.oracle.graal.pointsto.meta.PointsToAnalysisType; import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.svm.core.c.struct.CInterfaceLocationIdentity; import com.oracle.svm.core.hub.DynamicHub; @@ -65,18 +71,34 @@ import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.hosted.meta.HostedSnippetReflectionProvider; import com.oracle.svm.hosted.meta.HostedType; +import com.oracle.svm.hosted.meta.HostedUniverse; import com.oracle.svm.hosted.thread.VMThreadLocalCollector; import com.oracle.svm.util.ModuleSupport; import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; import jdk.graal.compiler.nodes.EncodedGraph; +import jdk.graal.compiler.nodes.FieldLocationIdentity; import jdk.graal.compiler.util.ObjectCopier; import jdk.graal.compiler.util.ObjectCopierInputStream; import jdk.graal.compiler.util.ObjectCopierOutputStream; import jdk.vm.ci.hotspot.HotSpotResolvedJavaMethod; -public class SVMImageLayerSnapshotUtil extends ImageLayerSnapshotUtil { +public class SVMImageLayerSnapshotUtil { + public static final String FILE_NAME_PREFIX = "layer-snapshot-"; + public static final String FILE_EXTENSION = ".lsb"; + public static final String GRAPHS_FILE_NAME_PREFIX = "layer-snapshot-graphs-"; + public static final String GRAPHS_FILE_EXTENSION = ".big"; + + public static final String CONSTRUCTOR_NAME = ""; + public static final String CLASS_INIT_NAME = ""; + + public static final String PERSISTED = "persisted"; + public static final String TRACKED_REASON = "reachable from a graph"; + + public static final int UNDEFINED_CONSTANT_ID = -1; + public static final int UNDEFINED_FIELD_INDEX = -1; + public static final String GENERATED_SERIALIZATION = "jdk.internal.reflect.GeneratedSerializationConstructorAccessor"; public static final Field companion = ReflectionUtil.lookupField(DynamicHub.class, "companion"); @@ -96,10 +118,17 @@ public class SVMImageLayerSnapshotUtil extends ImageLayerSnapshotUtil { */ protected final Map> fieldsToRelink = new HashMap<>(); private final ImageClassLoader imageClassLoader; + protected final List externalValueFields; + /** This needs to be initialized after analysis, as some fields are not available before. */ + protected Map externalValues; @SuppressWarnings("this-escape") public SVMImageLayerSnapshotUtil(ImageClassLoader imageClassLoader) { - super(); + try { + this.externalValueFields = ObjectCopier.getExternalValueFields(); + } catch (IOException e) { + throw AnalysisError.shouldNotReachHere("Unexpected exception when creating external value fields list", e); + } this.imageClassLoader = imageClassLoader; addSVMExternalValueFields(); } @@ -150,23 +179,70 @@ protected boolean shouldScanPackage(String packageName) { return true; } - @Override - public String getTypeIdentifier(AnalysisType type) { - if (type.toJavaName(true).contains(GENERATED_SERIALIZATION)) { + /** + * Get all the field indexes that should be relinked using the hosted value of a constant from + * the given type. + */ + public Set getRelinkedFields(AnalysisType type, AnalysisMetaAccess metaAccess) { + Set result = fieldsToRelink.computeIfAbsent(type, key -> { + Class clazz = type.getJavaClass(); + if (clazz == Class.class) { + type.getInstanceFields(true); + return dynamicHubRelinkedFields.stream().map(metaAccess::lookupJavaField).map(AnalysisField::getPosition).collect(Collectors.toSet()); + } else { + return null; + } + }); + if (result == null) { + return Set.of(); + } + return result; + } + + public SVMGraphEncoder getGraphEncoder(SVMImageLayerWriter imageLayerWriter) { + return new SVMGraphEncoder(externalValues, imageLayerWriter); + } + + public AbstractSVMGraphDecoder getGraphHostedToAnalysisElementsDecoder(SVMImageLayerLoader imageLayerLoader, AnalysisMethod analysisMethod, SnippetReflectionProvider snippetReflectionProvider) { + return new SVMGraphHostedToAnalysisElementsDecoder(EncodedGraph.class.getClassLoader(), imageLayerLoader, analysisMethod, snippetReflectionProvider); + } + + public AbstractSVMGraphDecoder getGraphDecoder(SVMImageLayerLoader imageLayerLoader, AnalysisMethod analysisMethod, SnippetReflectionProvider snippetReflectionProvider) { + return new SVMGraphDecoder(EncodedGraph.class.getClassLoader(), imageLayerLoader, analysisMethod, snippetReflectionProvider); + } + + /** + * Compute and cache the final {@code externalValues} map in + * {@link SVMImageLayerSnapshotUtil#externalValues} to avoid computing it for each graph. + *

      + * A single {@code ObjectCopier.Encoder} instance could alternatively be used for all graphs, + * but it would then be impossible to process multiple graphs concurrently. + */ + public void initializeExternalValues() { + assert externalValues == null : "The external values should be computed only once."; + externalValues = ObjectCopier.Encoder.gatherExternalValues(externalValueFields); + } + + public static String snapshotFileName(String imageName) { + return FILE_NAME_PREFIX + imageName + FILE_EXTENSION; + } + + public static String snapshotGraphsFileName(String imageName) { + return GRAPHS_FILE_NAME_PREFIX + imageName + GRAPHS_FILE_EXTENSION; + } + + public String getTypeDescriptor(AnalysisType type) { + String javaName = type.toJavaName(true); + if (javaName.contains(GENERATED_SERIALIZATION)) { return getGeneratedSerializationName(type); } if (isProxyType(type)) { - return type.toJavaName(true); + return javaName; } - return super.getTypeIdentifier(type); + return addModuleName(javaName, type.getJavaClass().getModule().getName()); } - private static String generatedSerializationClassName(SerializationSupport.SerializationLookupKey serializationLookupKey) { - return GENERATED_SERIALIZATION + ":" + serializationLookupKey.getDeclaringClass() + "," + serializationLookupKey.getTargetConstructorClass(); - } - - @Override - public String getMethodIdentifier(AnalysisMethod method) { + public String getMethodDescriptor(AnalysisMethod method) { AnalysisType declaringClass = method.getDeclaringClass(); String moduleName = declaringClass.getJavaClass().getModule().getName(); if (declaringClass.toJavaName(true).contains(GENERATED_SERIALIZATION)) { @@ -192,7 +268,11 @@ public String getMethodIdentifier(AnalysisMethod method) { if (isLambdaType(declaringClass) || isInjectedInvokerType(declaringClass) || isMethodHandleType(declaringClass) || isProxyType(declaringClass)) { return getQualifiedName(method); } - return super.getMethodIdentifier(method); + Executable originalMethod = OriginalMethodProvider.getJavaMethod(method); + if (originalMethod != null) { + return addModuleName(originalMethod.toString(), moduleName); + } + return addModuleName(getQualifiedName(method), moduleName); } /* @@ -207,37 +287,27 @@ private static String getGeneratedSerializationName(AnalysisType type) { return generatedSerializationClassName(serializationLookupKey); } - @Override - public Set getRelinkedFields(AnalysisType type, AnalysisMetaAccess metaAccess) { - Set result = fieldsToRelink.computeIfAbsent(type, key -> { - Class clazz = type.getJavaClass(); - if (clazz == Class.class) { - type.getInstanceFields(true); - return dynamicHubRelinkedFields.stream().map(metaAccess::lookupJavaField).map(AnalysisField::getPosition).collect(Collectors.toSet()); - } else { - return null; - } - }); - if (result == null) { - return Set.of(); - } - return result; + private static String generatedSerializationClassName(SerializationSupport.SerializationLookupKey serializationLookupKey) { + return GENERATED_SERIALIZATION + ":" + serializationLookupKey.getDeclaringClass() + "," + serializationLookupKey.getTargetConstructorClass(); } - @Override - public GraphEncoder getGraphEncoder(ImageLayerWriter imageLayerWriter) { - return new SVMGraphEncoder(externalValues, imageLayerWriter); + private static String addModuleName(String elementName, String moduleName) { + return moduleName + ":" + elementName; } - @Override - public GraphDecoder getGraphDecoder(ImageLayerLoader imageLayerLoader, AnalysisMethod analysisMethod, SnippetReflectionProvider snippetReflectionProvider) { - return new SVMGraphDecoder(EncodedGraph.class.getClassLoader(), (SVMImageLayerLoader) imageLayerLoader, analysisMethod, snippetReflectionProvider); + private static String getQualifiedName(AnalysisMethod method) { + return method.getSignature().getReturnType().toJavaName(true) + " " + method.getQualifiedName(); } - public static class SVMGraphEncoder extends GraphEncoder { + public static class SVMGraphEncoder extends ObjectCopier.Encoder { @SuppressWarnings("this-escape") - public SVMGraphEncoder(Map externalValues, ImageLayerWriter imageLayerWriter) { - super(externalValues, imageLayerWriter); + public SVMGraphEncoder(Map externalValues, SVMImageLayerWriter imageLayerWriter) { + super(externalValues); + addBuiltin(new ImageHeapConstantBuiltIn(imageLayerWriter, null)); + addBuiltin(new AnalysisTypeBuiltIn(null)); + addBuiltin(new AnalysisMethodBuiltIn(null, null)); + addBuiltin(new AnalysisFieldBuiltIn(null)); + addBuiltin(new FieldLocationIdentityBuiltIn(null)); addBuiltin(new HostedTypeBuiltIn(null)); addBuiltin(new HostedMethodBuiltIn(null)); addBuiltin(new HostedOptionValuesBuiltIn()); @@ -248,24 +318,180 @@ public SVMGraphEncoder(Map externalValues, ImageLayerWriter image } } - public static class SVMGraphDecoder extends GraphDecoder { + public abstract static class AbstractSVMGraphDecoder extends ObjectCopier.Decoder { + private final HostedImageLayerBuildingSupport imageLayerBuildingSupport; + @SuppressWarnings("this-escape") - public SVMGraphDecoder(ClassLoader classLoader, SVMImageLayerLoader svmImageLayerLoader, AnalysisMethod analysisMethod, SnippetReflectionProvider snippetReflectionProvider) { - super(classLoader, svmImageLayerLoader, analysisMethod); - addBuiltin(new HostedTypeBuiltIn(svmImageLayerLoader)); - addBuiltin(new HostedMethodBuiltIn(svmImageLayerLoader)); + public AbstractSVMGraphDecoder(ClassLoader classLoader, SVMImageLayerLoader imageLayerLoader, AnalysisMethod analysisMethod, SnippetReflectionProvider snippetReflectionProvider) { + super(classLoader); + this.imageLayerBuildingSupport = imageLayerLoader.getImageLayerBuildingSupport(); + addBuiltin(new ImageHeapConstantBuiltIn(null, imageLayerLoader)); + addBuiltin(new AnalysisTypeBuiltIn(imageLayerLoader)); + addBuiltin(new AnalysisMethodBuiltIn(imageLayerLoader, analysisMethod)); + addBuiltin(new AnalysisFieldBuiltIn(imageLayerLoader)); + addBuiltin(new FieldLocationIdentityBuiltIn(imageLayerLoader)); addBuiltin(new HostedOptionValuesBuiltIn()); addBuiltin(new HostedSnippetReflectionProviderBuiltIn(snippetReflectionProvider)); addBuiltin(new CInterfaceLocationIdentityBuiltIn()); addBuiltin(new FastThreadLocalLocationIdentityBuiltIn()); addBuiltin(new VMThreadLocalInfoBuiltIn()); } + + @Override + public Class loadClass(String className) { + return imageLayerBuildingSupport.lookupClass(false, className); + } } - public static class HostedTypeBuiltIn extends ObjectCopier.Builtin { - private final SVMImageLayerLoader svmImageLayerLoader; + public static class SVMGraphHostedToAnalysisElementsDecoder extends AbstractSVMGraphDecoder { + @SuppressWarnings("this-escape") + public SVMGraphHostedToAnalysisElementsDecoder(ClassLoader classLoader, SVMImageLayerLoader svmImageLayerLoader, AnalysisMethod analysisMethod, + SnippetReflectionProvider snippetReflectionProvider) { + super(classLoader, svmImageLayerLoader, analysisMethod, snippetReflectionProvider); + addBuiltin(new HostedToAnalysisTypeDecoderBuiltIn(svmImageLayerLoader)); + addBuiltin(new HostedToAnalysisMethodDecoderBuiltIn(svmImageLayerLoader)); + } + } - protected HostedTypeBuiltIn(SVMImageLayerLoader svmImageLayerLoader) { + public static class SVMGraphDecoder extends AbstractSVMGraphDecoder { + @SuppressWarnings("this-escape") + public SVMGraphDecoder(ClassLoader classLoader, SVMImageLayerLoader svmImageLayerLoader, AnalysisMethod analysisMethod, SnippetReflectionProvider snippetReflectionProvider) { + super(classLoader, svmImageLayerLoader, analysisMethod, snippetReflectionProvider); + addBuiltin(new HostedTypeBuiltIn(svmImageLayerLoader)); + addBuiltin(new HostedMethodBuiltIn(svmImageLayerLoader)); + } + } + + public static class ImageHeapConstantBuiltIn extends ObjectCopier.Builtin { + private final SVMImageLayerWriter imageLayerWriter; + private final SVMImageLayerLoader imageLayerLoader; + + protected ImageHeapConstantBuiltIn(SVMImageLayerWriter imageLayerWriter, SVMImageLayerLoader imageLayerLoader) { + super(ImageHeapConstant.class, ImageHeapInstance.class, ImageHeapObjectArray.class, ImageHeapPrimitiveArray.class); + this.imageLayerWriter = imageLayerWriter; + this.imageLayerLoader = imageLayerLoader; + } + + @Override + public void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { + ImageHeapConstant imageHeapConstant = (ImageHeapConstant) obj; + imageLayerWriter.ensureConstantPersisted(imageHeapConstant); + stream.writePackedUnsignedInt(ImageHeapConstant.getConstantID(imageHeapConstant)); + } + + @Override + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + int id = stream.readPackedUnsignedInt(); + return imageLayerLoader.getOrCreateConstant(id); + } + } + + public static class AnalysisTypeBuiltIn extends ObjectCopier.Builtin { + private final SVMImageLayerLoader imageLayerLoader; + + protected AnalysisTypeBuiltIn(SVMImageLayerLoader imageLayerLoader) { + super(AnalysisType.class, PointsToAnalysisType.class); + this.imageLayerLoader = imageLayerLoader; + } + + @Override + public void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { + AnalysisType type = (AnalysisType) obj; + type.registerAsTrackedAcrossLayers(TRACKED_REASON); + stream.writePackedUnsignedInt(type.getId()); + } + + @Override + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + int id = stream.readPackedUnsignedInt(); + return imageLayerLoader.getAnalysisTypeForBaseLayerId(id); + } + } + + public static class AnalysisMethodBuiltIn extends ObjectCopier.Builtin { + private final SVMImageLayerLoader imageLayerLoader; + private final AnalysisMethod analysisMethod; + + protected AnalysisMethodBuiltIn(SVMImageLayerLoader imageLayerLoader, AnalysisMethod analysisMethod) { + super(AnalysisMethod.class, PointsToAnalysisMethod.class); + this.imageLayerLoader = imageLayerLoader; + this.analysisMethod = analysisMethod; + } + + @Override + public void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { + AnalysisMethod method = (AnalysisMethod) obj; + method.registerAsTrackedAcrossLayers(TRACKED_REASON); + stream.writePackedUnsignedInt(method.getId()); + } + + @Override + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + int id = stream.readPackedUnsignedInt(); + if (id == analysisMethod.getId()) { + return analysisMethod; + } + return imageLayerLoader.getAnalysisMethodForBaseLayerId(id); + } + } + + public static class AnalysisFieldBuiltIn extends ObjectCopier.Builtin { + private final SVMImageLayerLoader imageLayerLoader; + + protected AnalysisFieldBuiltIn(SVMImageLayerLoader imageLayerLoader) { + super(AnalysisField.class, PointsToAnalysisField.class); + this.imageLayerLoader = imageLayerLoader; + } + + @Override + public void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { + AnalysisField field = (AnalysisField) obj; + int id = encodeField(field); + stream.writePackedUnsignedInt(id); + } + + @Override + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + int id = stream.readPackedUnsignedInt(); + return decodeField(imageLayerLoader, id); + } + } + + public static class FieldLocationIdentityBuiltIn extends ObjectCopier.Builtin { + private final SVMImageLayerLoader imageLayerLoader; + + protected FieldLocationIdentityBuiltIn(SVMImageLayerLoader imageLayerLoader) { + super(FieldLocationIdentity.class); + this.imageLayerLoader = imageLayerLoader; + } + + @Override + public void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { + FieldLocationIdentity fieldLocationIdentity = (FieldLocationIdentity) obj; + int id = encodeField((AnalysisField) fieldLocationIdentity.getField()); + stream.writePackedUnsignedInt(id); + } + + @Override + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + int id = stream.readPackedUnsignedInt(); + return new FieldLocationIdentity(decodeField(imageLayerLoader, id)); + } + } + + private static int encodeField(AnalysisField field) { + field.registerAsTrackedAcrossLayers(TRACKED_REASON); + return field.getId(); + } + + private static AnalysisField decodeField(SVMImageLayerLoader imageLayerLoader, int id) { + return imageLayerLoader.getAnalysisFieldForBaseLayerId(id); + } + + public abstract static class AbstractHostedTypeBuiltIn extends ObjectCopier.Builtin { + protected final SVMImageLayerLoader svmImageLayerLoader; + + protected AbstractHostedTypeBuiltIn(SVMImageLayerLoader svmImageLayerLoader) { super(HostedType.class, HostedInstanceClass.class, HostedArrayClass.class); this.svmImageLayerLoader = svmImageLayerLoader; } @@ -275,19 +501,40 @@ protected void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream str int id = ((HostedType) obj).getWrapped().getId(); stream.writePackedUnsignedInt(id); } + } + + public static class HostedToAnalysisTypeDecoderBuiltIn extends AbstractHostedTypeBuiltIn { + protected HostedToAnalysisTypeDecoderBuiltIn(SVMImageLayerLoader svmImageLayerLoader) { + super(svmImageLayerLoader); + } @Override protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { - int id = stream.readPackedUnsignedInt(); - AnalysisType type = svmImageLayerLoader.getAnalysisType(id); - return svmImageLayerLoader.getHostedUniverse().lookup(type); + return getAnalysisType(svmImageLayerLoader, stream); } } - public static class HostedMethodBuiltIn extends ObjectCopier.Builtin { - private final SVMImageLayerLoader svmImageLayerLoader; + public static class HostedTypeBuiltIn extends AbstractHostedTypeBuiltIn { + protected HostedTypeBuiltIn(SVMImageLayerLoader svmImageLayerLoader) { + super(svmImageLayerLoader); + } - protected HostedMethodBuiltIn(SVMImageLayerLoader svmImageLayerLoader) { + @Override + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + HostedUniverse hostedUniverse = svmImageLayerLoader.getHostedUniverse(); + return hostedUniverse.lookup(getAnalysisType(svmImageLayerLoader, stream)); + } + } + + private static AnalysisType getAnalysisType(SVMImageLayerLoader imageLayerLoader, ObjectCopierInputStream stream) throws IOException { + int id = stream.readPackedUnsignedInt(); + return imageLayerLoader.getAnalysisTypeForBaseLayerId(id); + } + + public abstract static class AbstractHostedMethodBuiltIn extends ObjectCopier.Builtin { + protected final SVMImageLayerLoader svmImageLayerLoader; + + protected AbstractHostedMethodBuiltIn(SVMImageLayerLoader svmImageLayerLoader) { super(HostedMethod.class); this.svmImageLayerLoader = svmImageLayerLoader; } @@ -296,13 +543,34 @@ protected HostedMethodBuiltIn(SVMImageLayerLoader svmImageLayerLoader) { protected void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { stream.writePackedUnsignedInt(((HostedMethod) obj).getWrapped().getId()); } + } + + public static class HostedToAnalysisMethodDecoderBuiltIn extends AbstractHostedMethodBuiltIn { + protected HostedToAnalysisMethodDecoderBuiltIn(SVMImageLayerLoader svmImageLayerLoader) { + super(svmImageLayerLoader); + } @Override protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { - int id = stream.readPackedUnsignedInt(); - AnalysisMethod method = svmImageLayerLoader.getAnalysisMethod(id); - return svmImageLayerLoader.getHostedUniverse().lookup(method); + return getAnalysisMethod(svmImageLayerLoader, stream); + } + } + + public static class HostedMethodBuiltIn extends AbstractHostedMethodBuiltIn { + protected HostedMethodBuiltIn(SVMImageLayerLoader svmImageLayerLoader) { + super(svmImageLayerLoader); } + + @Override + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + HostedUniverse hostedUniverse = svmImageLayerLoader.getHostedUniverse(); + return hostedUniverse.lookup(getAnalysisMethod(svmImageLayerLoader, stream)); + } + } + + private static AnalysisMethod getAnalysisMethod(SVMImageLayerLoader imageLayerLoader, ObjectCopierInputStream stream) throws IOException { + int id = stream.readPackedUnsignedInt(); + return imageLayerLoader.getAnalysisMethodForBaseLayerId(id); } public static class HostedOptionValuesBuiltIn extends ObjectCopier.Builtin { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java new file mode 100644 index 000000000000..ecb7e5063914 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java @@ -0,0 +1,1136 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.imagelayer; + +import static com.oracle.svm.hosted.imagelayer.SVMImageLayerSnapshotUtil.CONSTRUCTOR_NAME; +import static com.oracle.svm.hosted.imagelayer.SVMImageLayerSnapshotUtil.GENERATED_SERIALIZATION; +import static com.oracle.svm.hosted.imagelayer.SVMImageLayerSnapshotUtil.UNDEFINED_CONSTANT_ID; +import static com.oracle.svm.hosted.imagelayer.SVMImageLayerSnapshotUtil.UNDEFINED_FIELD_INDEX; +import static com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ClassInitializationInfo.Builder; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Parameter; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiConsumer; +import java.util.function.IntFunction; +import java.util.function.ObjIntConsumer; +import java.util.function.Supplier; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.capnproto.ListBuilder; +import org.capnproto.MessageBuilder; +import org.capnproto.PrimitiveList; +import org.capnproto.Serialize; +import org.capnproto.StructBuilder; +import org.capnproto.StructList; +import org.capnproto.Text; +import org.capnproto.TextList; +import org.capnproto.Void; +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; +import org.graalvm.nativeimage.AnnotationAccess; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.function.RelocatedPointer; +import org.graalvm.nativeimage.impl.CEntryPointLiteralCodePointer; + +import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.api.HostVM; +import com.oracle.graal.pointsto.api.ImageLayerWriter; +import com.oracle.graal.pointsto.flow.AnalysisParsedGraph; +import com.oracle.graal.pointsto.heap.ImageHeap; +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.graal.pointsto.heap.ImageHeapInstance; +import com.oracle.graal.pointsto.heap.ImageHeapObjectArray; +import com.oracle.graal.pointsto.heap.ImageHeapPrimitiveArray; +import com.oracle.graal.pointsto.heap.ImageHeapRelocatableConstant; +import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.graal.pointsto.util.AnalysisError; +import com.oracle.graal.pointsto.util.AnalysisFuture; +import com.oracle.svm.core.FunctionPointerHolder; +import com.oracle.svm.core.StaticFieldsSupport; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.classinitialization.ClassInitializationInfo; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; +import com.oracle.svm.core.layeredimagesingleton.RuntimeOnlyWrapper; +import com.oracle.svm.core.meta.MethodPointer; +import com.oracle.svm.core.reflect.serialize.SerializationSupport; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.annotation.AnnotationMemberValue; +import com.oracle.svm.hosted.annotation.AnnotationMetadata; +import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; +import com.oracle.svm.hosted.code.CEntryPointCallStubMethod; +import com.oracle.svm.hosted.code.CEntryPointCallStubSupport; +import com.oracle.svm.hosted.code.FactoryMethod; +import com.oracle.svm.hosted.fieldfolding.StaticFinalFieldFoldingFeature; +import com.oracle.svm.hosted.image.NativeImageHeap; +import com.oracle.svm.hosted.image.NativeImageHeap.ObjectInfo; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.AnnotationValue; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.CEntryPointLiteralReference; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ImageSingletonKey; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ImageSingletonObject; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.KeyStoreEntry; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisField; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisMethod; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisMethod.WrappedMethod; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisType; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisType.WrappedType; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedConstant; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedConstant.Object.Relinking; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedConstant.Object.Relinking.EnumConstant; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedConstant.Object.Relinking.StringConstant; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.SharedLayerSnapshot; +import com.oracle.svm.hosted.jni.JNIJavaCallVariantWrapperMethod; +import com.oracle.svm.hosted.lambda.LambdaSubstitutionType; +import com.oracle.svm.hosted.lambda.StableLambdaProxyNameFeature; +import com.oracle.svm.hosted.meta.HostedField; +import com.oracle.svm.hosted.meta.HostedMethod; +import com.oracle.svm.hosted.meta.HostedUniverse; +import com.oracle.svm.hosted.meta.RelocatableConstant; +import com.oracle.svm.hosted.methodhandles.MethodHandleFeature; +import com.oracle.svm.hosted.methodhandles.MethodHandleInvokerSubstitutionType; +import com.oracle.svm.hosted.reflect.ReflectionExpandSignatureMethod; +import com.oracle.svm.hosted.reflect.proxy.ProxyRenamingSubstitutionProcessor; +import com.oracle.svm.hosted.reflect.proxy.ProxySubstitutionType; +import com.oracle.svm.util.FileDumpingUtil; +import com.oracle.svm.util.LogUtils; +import com.oracle.svm.util.ModuleSupport; + +import jdk.graal.compiler.core.common.NumUtil; +import jdk.graal.compiler.debug.Assertions; +import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.java.LambdaUtils; +import jdk.graal.compiler.nodes.EncodedGraph; +import jdk.graal.compiler.nodes.spi.IdentityHashCodeProvider; +import jdk.graal.compiler.util.ObjectCopier; +import jdk.vm.ci.meta.ConstantReflectionProvider; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.MethodHandleAccessProvider.IntrinsicMethod; +import jdk.vm.ci.meta.PrimitiveConstant; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; +import sun.reflect.annotation.AnnotationType; + +public class SVMImageLayerWriter extends ImageLayerWriter { + private final SVMImageLayerSnapshotUtil imageLayerSnapshotUtil; + private ImageHeap imageHeap; + private AnalysisUniverse aUniverse; + private IdentityHashMap internedStringsIdentityMap; + + private final MessageBuilder snapshotFileBuilder = new MessageBuilder(); + private final SharedLayerSnapshot.Builder snapshotBuilder = this.snapshotFileBuilder.initRoot(SharedLayerSnapshot.factory); + private Map constantsMap; + private final Map methodsMap = new ConcurrentHashMap<>(); + private FileInfo fileInfo; + private GraphsOutput graphsOutput; + private final boolean useSharedLayerGraphs; + private final boolean useSharedLayerStrengthenedGraphs; + + private final Set constantsToPersist = ConcurrentHashMap.newKeySet(); + + public void ensureConstantPersisted(ImageHeapConstant constant) { + constantsToPersist.add(constant); + afterConstantAdded(constant); + } + + private NativeImageHeap nativeImageHeap; + private HostedUniverse hUniverse; + + private record ConstantParent(int constantId, int index) { + static ConstantParent NONE = new ConstantParent(UNDEFINED_CONSTANT_ID, UNDEFINED_FIELD_INDEX); + } + + private record FileInfo(Path layerFilePath, String fileName, String suffix) { + } + + private record MethodGraphsInfo(String analysisGraphLocation, boolean analysisGraphIsIntrinsic, + String strengthenedGraphLocation) { + + static final MethodGraphsInfo NO_GRAPHS = new MethodGraphsInfo(null, false, null); + + MethodGraphsInfo withAnalysisGraph(String location, boolean isIntrinsic) { + assert analysisGraphLocation == null && !analysisGraphIsIntrinsic; + return new MethodGraphsInfo(location, isIntrinsic, strengthenedGraphLocation); + } + + MethodGraphsInfo withStrengthenedGraph(String location) { + assert strengthenedGraphLocation == null; + return new MethodGraphsInfo(analysisGraphLocation, analysisGraphIsIntrinsic, location); + } + } + + private static class GraphsOutput { + private final Path path; + private final Path tempPath; + private final FileChannel tempChannel; + + private final AtomicLong currentOffset = new AtomicLong(0); + + GraphsOutput(Path path, String fileName, String suffix) { + this.path = path; + this.tempPath = FileDumpingUtil.createTempFile(path.getParent(), fileName, suffix); + try { + this.tempChannel = FileChannel.open(this.tempPath, EnumSet.of(StandardOpenOption.WRITE)); + } catch (IOException e) { + throw GraalError.shouldNotReachHere(e, "Error opening temporary graphs file."); + } + } + + String add(byte[] encodedGraph) { + long offset = currentOffset.getAndAdd(encodedGraph.length); + try { + tempChannel.write(ByteBuffer.wrap(encodedGraph), offset); + } catch (Exception e) { + throw GraalError.shouldNotReachHere(e, "Error during graphs file dumping."); + } + return new StringBuilder("@").append(offset).append("[").append(encodedGraph.length).append("]").toString(); + } + + void finish() { + try { + tempChannel.close(); + FileDumpingUtil.moveTryAtomically(tempPath, path); + } catch (Exception e) { + throw GraalError.shouldNotReachHere(e, "Error during graphs file dumping."); + } + } + } + + public SVMImageLayerWriter(SVMImageLayerSnapshotUtil imageLayerSnapshotUtil, boolean useSharedLayerGraphs, boolean useSharedLayerStrengthenedGraphs) { + this.imageLayerSnapshotUtil = imageLayerSnapshotUtil; + this.useSharedLayerGraphs = useSharedLayerGraphs; + this.useSharedLayerStrengthenedGraphs = useSharedLayerStrengthenedGraphs; + } + + public void setInternedStringsIdentityMap(IdentityHashMap map) { + this.internedStringsIdentityMap = map; + } + + public void setImageHeap(ImageHeap heap) { + this.imageHeap = heap; + } + + public void setSnapshotFileInfo(Path layerSnapshotPath, String fileName, String suffix) { + fileInfo = new FileInfo(layerSnapshotPath, fileName, suffix); + } + + public void setAnalysisUniverse(AnalysisUniverse aUniverse) { + this.aUniverse = aUniverse; + } + + public void setNativeImageHeap(NativeImageHeap nativeImageHeap) { + this.nativeImageHeap = nativeImageHeap; + } + + public void setHostedUniverse(HostedUniverse hUniverse) { + this.hUniverse = hUniverse; + } + + public void openGraphsOutput(Path layerGraphsPath, String fileName, String suffix) { + AnalysisError.guarantee(graphsOutput == null, "Graphs file has already been opened"); + graphsOutput = new GraphsOutput(layerGraphsPath, fileName, suffix); + } + + public void dumpFiles() { + graphsOutput.finish(); + + FileDumpingUtil.dumpFile(fileInfo.layerFilePath, fileInfo.fileName, fileInfo.suffix, outputStream -> { + try { + Serialize.write(Channels.newChannel(outputStream), snapshotFileBuilder); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + public void initializeExternalValues() { + imageLayerSnapshotUtil.initializeExternalValues(); + } + + public void setImageHeapSize(long imageHeapSize) { + snapshotBuilder.setImageHeapSize(imageHeapSize); + } + + @Override + public void onTrackedAcrossLayer(AnalysisMethod method, Object reason) { + if (method.wrapped instanceof FactoryMethod factoryMethod) { + AnalysisMethod targetConstructor = method.getUniverse().lookup(factoryMethod.getTargetConstructor()); + targetConstructor.registerAsTrackedAcrossLayers(reason); + } + } + + public void persistAnalysisInfo() { + ImageHeapConstant staticPrimitiveFields = (ImageHeapConstant) hUniverse.getSnippetReflection().forObject(StaticFieldsSupport.getStaticPrimitiveFields()); + ImageHeapConstant staticObjectFields = (ImageHeapConstant) hUniverse.getSnippetReflection().forObject(StaticFieldsSupport.getStaticObjectFields()); + + snapshotBuilder.setStaticPrimitiveFieldsConstantId(ImageHeapConstant.getConstantID(staticPrimitiveFields)); + snapshotBuilder.setStaticObjectFieldsConstantId(ImageHeapConstant.getConstantID(staticObjectFields)); + + // Late constant scan so all of them are known with values available (readers installed) + List constantsToScan = new ArrayList<>(constantsToPersist); + imageHeap.getReachableObjects().values().forEach(constantsToScan::addAll); + constantsMap = HashMap.newHashMap(constantsToScan.size()); + constantsToScan.forEach(c -> constantsMap.put(c, ConstantParent.NONE)); + while (!constantsToScan.isEmpty()) { + List discoveredConstants = new ArrayList<>(); + constantsToScan.forEach(con -> scanConstantReferencedObjects(con, discoveredConstants)); + constantsToScan = discoveredConstants; + } + + snapshotBuilder.setNextTypeId(aUniverse.getNextTypeId()); + snapshotBuilder.setNextMethodId(aUniverse.getNextMethodId()); + snapshotBuilder.setNextFieldId(aUniverse.getNextFieldId()); + snapshotBuilder.setNextConstantId(ImageHeapConstant.getCurrentId()); + + List typesToPersist = aUniverse.getTypes().stream().filter(AnalysisType::isTrackedAcrossLayers).toList(); + List methodsToPersist = aUniverse.getMethods().stream().filter(AnalysisMethod::isTrackedAcrossLayers).toList(); + List fieldsToPersist = aUniverse.getFields().stream().filter(AnalysisField::isTrackedAcrossLayers).toList(); + + initSortedList(snapshotBuilder::initTypes, typesToPersist, Comparator.comparingInt(AnalysisType::getId), this::persistType); + initSortedList(snapshotBuilder::initMethods, methodsToPersist, Comparator.comparingInt(AnalysisMethod::getId), this::persistMethod); + initSortedList(snapshotBuilder::initFields, fieldsToPersist, Comparator.comparingInt(AnalysisField::getId), this::persistField); + + Set constantsToRelink = new HashSet<>(); + initSortedList(snapshotBuilder::initConstants, constantsMap.entrySet(), + Comparator.comparingInt(a -> ImageHeapConstant.getConstantID(a.getKey())), + (entry, bsupplier) -> persistConstant(entry.getKey(), entry.getValue(), bsupplier.get(), constantsToRelink)); + initInts(snapshotBuilder::initConstantsToRelink, constantsToRelink.stream().mapToInt(i -> i).sorted()); + } + + private static void initInts(IntFunction builderSupplier, IntStream ids) { + int[] values = ids.toArray(); + PrimitiveList.Int.Builder builder = builderSupplier.apply(values.length); + for (int i = 0; i < values.length; i++) { + builder.set(i, values[i]); + } + } + + private static void initStringList(IntFunction builderSupplier, Stream strings) { + Object[] array = strings.toArray(); + TextList.Builder builder = builderSupplier.apply(array.length); + for (int i = 0; i < array.length; i++) { + builder.set(i, new Text.Reader(array[i].toString())); + } + } + + private static void initSortedList(IntFunction> init, Collection objects, Comparator comparator, BiConsumer> action) { + @SuppressWarnings("unchecked") + T[] array = (T[]) objects.toArray(); + Arrays.sort(array, comparator); + + StructList.Builder builder = init.apply(objects.size()); + Iterator iterator = builder.iterator(); + for (T t : array) { + action.accept(t, iterator::next); + } + AnalysisError.guarantee(!iterator.hasNext(), "all created struct builders must have been used"); + } + + private void persistType(AnalysisType type, Supplier builderSupplier) { + PersistedAnalysisType.Builder builder = builderSupplier.get(); + HostVM hostVM = aUniverse.hostVM(); + SVMHost svmHost = (SVMHost) hostVM; + DynamicHub hub = svmHost.dynamicHub(type); + builder.setHubIdentityHashCode(System.identityHashCode(hub)); + + builder.setIsInitializedAtBuildTime(ClassInitializationSupport.singleton().maybeInitializeAtBuildTime(type)); + + ClassInitializationInfo info = hub.getClassInitializationInfo(); + if (info != null) { + Builder b = builder.initClassInitializationInfo(); + b.setIsNoInitializerNoTracking(info == ClassInitializationInfo.forNoInitializerInfo(false)); + b.setIsInitializedNoTracking(info == ClassInitializationInfo.forInitializedInfo(false)); + b.setIsFailedNoTracking(info == ClassInitializationInfo.forFailedInfo(false)); + b.setIsInitialized(info.isInitialized()); + b.setIsInErrorState(info.isInErrorState()); + b.setIsLinked(info.isLinked()); + b.setHasInitializer(info.hasInitializer()); + b.setIsBuildTimeInitialized(info.isBuildTimeInitialized()); + b.setIsTracked(info.isTracked()); + FunctionPointerHolder classInitializer = info.getClassInitializer(); + if (classInitializer != null) { + MethodPointer methodPointer = (MethodPointer) classInitializer.functionPointer; + AnalysisMethod classInitializerMethod = (AnalysisMethod) methodPointer.getMethod(); + b.setInitializerMethodId(classInitializerMethod.getId()); + } + } + + builder.setId(type.getId()); + builder.setDescriptor(imageLayerSnapshotUtil.getTypeDescriptor(type)); + + initInts(builder::initFields, Arrays.stream(type.getInstanceFields(true)).mapToInt(f -> ((AnalysisField) f).getId())); + builder.setClassJavaName(type.toJavaName()); + builder.setClassName(type.getName()); + builder.setModifiers(type.getModifiers()); + builder.setIsInterface(type.isInterface()); + builder.setIsEnum(type.isEnum()); + builder.setIsInitialized(type.isInitialized()); + builder.setIsLinked(type.isLinked()); + if (type.getSourceFileName() != null) { + builder.setSourceFileName(type.getSourceFileName()); + } + try { + AnalysisType enclosingType = type.getEnclosingType(); + if (enclosingType != null) { + builder.setEnclosingTypeId(enclosingType.getId()); + } + } catch (InternalError | TypeNotPresentException | LinkageError e) { + /* Ignore missing type errors. */ + } + if (type.isArray()) { + builder.setComponentTypeId(type.getComponentType().getId()); + } + if (type.getSuperclass() != null) { + builder.setSuperClassTypeId(type.getSuperclass().getId()); + } + initInts(builder::initInterfaces, Arrays.stream(type.getInterfaces()).mapToInt(AnalysisType::getId)); + initInts(builder::initInstanceFieldIds, Arrays.stream(type.getInstanceFields(false)).mapToInt(f -> ((AnalysisField) f).getId())); + initInts(builder::initInstanceFieldIdsWithSuper, Arrays.stream(type.getInstanceFields(true)).mapToInt(f -> ((AnalysisField) f).getId())); + initInts(builder::initStaticFieldIds, Arrays.stream(type.getStaticFields()).mapToInt(f -> ((AnalysisField) f).getId())); + persistAnnotations(type, builder::initAnnotationList); + + builder.setIsInstantiated(type.isInstantiated()); + builder.setIsUnsafeAllocated(type.isUnsafeAllocated()); + builder.setIsReachable(type.isReachable()); + + delegatePersistType(type, builder); + + afterTypeAdded(type); + } + + protected void delegatePersistType(AnalysisType type, PersistedAnalysisType.Builder builder) { + if (type.toJavaName(true).contains(GENERATED_SERIALIZATION)) { + WrappedType.SerializationGenerated.Builder b = builder.getWrappedType().initSerializationGenerated(); + var key = SerializationSupport.singleton().getKeyFromConstructorAccessorClass(type.getJavaClass()); + b.setRawDeclaringClass(key.getDeclaringClass().getName()); + b.setRawTargetConstructor(key.getTargetConstructorClass().getName()); + } else if (LambdaUtils.isLambdaType(type)) { + WrappedType.Lambda.Builder b = builder.getWrappedType().initLambda(); + b.setCapturingClass(LambdaUtils.capturingClass(type.toJavaName())); + } else if (ProxyRenamingSubstitutionProcessor.isProxyType(type)) { + builder.getWrappedType().setProxyType(Void.VOID); + } + } + + /** + * Some types can have an unstable name between two different image builds. To avoid producing + * wrong results, a warning should be printed if such types exist in the resulting image. + */ + private static void afterTypeAdded(AnalysisType type) { + /* + * Lambda functions containing the same method invocations will return the same hash. They + * will still have a different name, but in a multi threading context, the names can be + * switched. + */ + if (type.getWrapped() instanceof LambdaSubstitutionType lambdaSubstitutionType) { + StableLambdaProxyNameFeature stableLambdaProxyNameFeature = ImageSingletons.lookup(StableLambdaProxyNameFeature.class); + if (!stableLambdaProxyNameFeature.getLambdaSubstitutionProcessor().isNameAlwaysStable(lambdaSubstitutionType.getName())) { + String message = "The lambda method " + lambdaSubstitutionType.getName() + " might not have a stable name in the extension image."; + handleNameConflict(message); + } + } + /* + * Method handle with the same inner method handles will return the same hash. They will + * still have a different name, but in a multi threading context, the names can be switched. + */ + if (type.getWrapped() instanceof MethodHandleInvokerSubstitutionType methodHandleSubstitutionType) { + MethodHandleFeature methodHandleFeature = ImageSingletons.lookup(MethodHandleFeature.class); + if (!methodHandleFeature.getMethodHandleSubstitutionProcessor().isNameAlwaysStable(methodHandleSubstitutionType.getName())) { + String message = "The method handle " + methodHandleSubstitutionType.getName() + " might not have a stable name in the extension image."; + handleNameConflict(message); + } + } + + if (type.getWrapped() instanceof ProxySubstitutionType proxySubstitutionType) { + if (!ProxyRenamingSubstitutionProcessor.isNameAlwaysStable(proxySubstitutionType.getName())) { + String message = "The Proxy type " + proxySubstitutionType.getName() + " might not have a stable name in the extension image."; + handleNameConflict(message); + } + } + } + + private static void handleNameConflict(String message) { + if (SubstrateOptions.AbortOnNameConflict.getValue()) { + throw VMError.shouldNotReachHere(message); + } else { + LogUtils.warning(message); + } + } + + private void persistMethod(AnalysisMethod method, Supplier builderSupplier) { + PersistedAnalysisMethod.Builder builder = builderSupplier.get(); + MethodGraphsInfo graphsInfo = methodsMap.putIfAbsent(imageLayerSnapshotUtil.getMethodDescriptor(method), MethodGraphsInfo.NO_GRAPHS); + Executable executable = method.getJavaMethod(); + + if (builder.getId() != 0) { + throw GraalError.shouldNotReachHere("The method descriptor should be unique, but " + imageLayerSnapshotUtil.getMethodDescriptor(method) + " got added twice."); + } + if (executable != null) { + initStringList(builder::initArgumentClassNames, Arrays.stream(executable.getParameterTypes()).map(Class::getName)); + builder.setClassName(executable.getDeclaringClass().getName()); + } + + builder.setDescriptor(imageLayerSnapshotUtil.getMethodDescriptor(method)); + builder.setDeclaringTypeId(method.getDeclaringClass().getId()); + initInts(builder::initArgumentTypeIds, method.getSignature().toParameterList(null).stream().mapToInt(AnalysisType::getId)); + builder.setId(method.getId()); + builder.setName(method.getName()); + builder.setReturnTypeId(method.getSignature().getReturnType().getId()); + builder.setIsVarArgs(method.isVarArgs()); + builder.setCanBeStaticallyBound(method.canBeStaticallyBound()); + builder.setModifiers(method.getModifiers()); + builder.setIsConstructor(method.isConstructor()); + builder.setIsSynthetic(method.isSynthetic()); + byte[] code = method.getCode(); + if (code != null) { + builder.setCode(code); + } + builder.setCodeSize(method.getCodeSize()); + IntrinsicMethod intrinsicMethod = aUniverse.getBigbang().getConstantReflectionProvider().getMethodHandleAccess().lookupMethodHandleIntrinsic(method); + if (intrinsicMethod != null) { + builder.setMethodHandleIntrinsicName(intrinsicMethod.name()); + } + persistAnnotations(method, builder::initAnnotationList); + + builder.setIsVirtualRootMethod(method.isVirtualRootMethod()); + builder.setIsDirectRootMethod(method.isDirectRootMethod()); + builder.setIsInvoked(method.isSimplyInvoked()); + builder.setIsImplementationInvoked(method.isSimplyImplementationInvoked()); + builder.setIsIntrinsicMethod(method.isIntrinsicMethod()); + + if (graphsInfo != null && graphsInfo.analysisGraphLocation != null) { + builder.setAnalysisGraphLocation(graphsInfo.analysisGraphLocation); + builder.setAnalysisGraphIsIntrinsic(graphsInfo.analysisGraphIsIntrinsic); + } + if (graphsInfo != null && graphsInfo.strengthenedGraphLocation != null) { + builder.setStrengthenedGraphLocation(graphsInfo.strengthenedGraphLocation); + } + + delegatePersistMethod(method, builder); + + // register this method as persisted for name resolution + HostedDynamicLayerInfo.singleton().recordPersistedMethod(hUniverse.lookup(method)); + } + + protected void delegatePersistMethod(AnalysisMethod method, PersistedAnalysisMethod.Builder builder) { + if (method.wrapped instanceof FactoryMethod factoryMethod) { + WrappedMethod.FactoryMethod.Builder b = builder.getWrappedMethod().initFactoryMethod(); + AnalysisMethod targetConstructor = method.getUniverse().lookup(factoryMethod.getTargetConstructor()); + b.setTargetConstructorId(targetConstructor.getId()); + b.setThrowAllocatedObject(factoryMethod.throwAllocatedObject()); + AnalysisType instantiatedType = method.getUniverse().lookup(factoryMethod.getInstantiatedType()); + b.setInstantiatedTypeId(instantiatedType.getId()); + } else if (method.wrapped instanceof CEntryPointCallStubMethod cEntryPointCallStubMethod) { + WrappedMethod.CEntryPointCallStub.Builder b = builder.getWrappedMethod().initCEntryPointCallStub(); + AnalysisMethod originalMethod = CEntryPointCallStubSupport.singleton().getMethodForStub(cEntryPointCallStubMethod); + b.setOriginalMethodId(originalMethod.getId()); + b.setNotPublished(cEntryPointCallStubMethod.isNotPublished()); + } else if (method.wrapped instanceof ReflectionExpandSignatureMethod reflectionExpandSignatureMethod) { + WrappedMethod.WrappedMember.Builder b = builder.getWrappedMethod().initWrappedMember(); + b.setReflectionExpandSignature(Void.VOID); + Executable member = reflectionExpandSignatureMethod.getMember(); + persistMethodWrappedMember(b, member); + } else if (method.wrapped instanceof JNIJavaCallVariantWrapperMethod jniJavaCallVariantWrapperMethod) { + WrappedMethod.WrappedMember.Builder b = builder.getWrappedMethod().initWrappedMember(); + b.setJavaCallVariantWrapper(Void.VOID); + Executable executable = jniJavaCallVariantWrapperMethod.getMember(); + persistMethodWrappedMember(b, executable); + } + } + + private static void persistMethodWrappedMember(PersistedAnalysisMethod.WrappedMethod.WrappedMember.Builder b, Executable member) { + b.setName(member instanceof Constructor ? CONSTRUCTOR_NAME : member.getName()); + b.setDeclaringClassName(member.getDeclaringClass().getName()); + Parameter[] params = member.getParameters(); + TextList.Builder atb = b.initArgumentTypeNames(params.length); + for (int i = 0; i < params.length; i++) { + atb.set(i, new Text.Reader(params[i].getName())); + } + } + + private void persistField(AnalysisField field, Supplier fieldBuilderSupplier) { + PersistedAnalysisField.Builder builder = fieldBuilderSupplier.get(); + + builder.setId(field.getId()); + builder.setDeclaringTypeId(field.getDeclaringClass().getId()); + builder.setName(field.getName()); + builder.setIsAccessed(field.getAccessedReason() != null); + builder.setIsRead(field.getReadReason() != null); + builder.setIsWritten(field.getWrittenReason() != null); + builder.setIsFolded(field.getFoldedReason() != null); + + HostedField hostedField = hUniverse.lookup(field); + int location = hostedField.getLocation(); + if (location > 0) { + builder.setLocation(location); + } + Integer fieldCheckIndex = StaticFinalFieldFoldingFeature.singleton().getFieldCheckIndex(field); + builder.setFieldCheckIndex((fieldCheckIndex != null) ? fieldCheckIndex : -1); + + Field originalField = OriginalFieldProvider.getJavaField(field); + if (originalField != null && !originalField.getDeclaringClass().equals(field.getDeclaringClass().getJavaClass())) { + builder.setClassName(originalField.getDeclaringClass().getName()); + } + builder.setIsStatic(field.isStatic()); + builder.setIsInternal(field.isInternal()); + builder.setIsSynthetic(field.isSynthetic()); + builder.setTypeId(field.getType().getId()); + builder.setModifiers(field.getModifiers()); + builder.setPosition(field.getPosition()); + + persistAnnotations(field, builder::initAnnotationList); + } + + private void persistAnnotations(AnnotatedElement annotatedElement, IntFunction> builder) { + Class[] annotationTypes = AnnotationAccess.getAnnotationTypes(annotatedElement); + persistAnnotations(annotatedElement, annotationTypes, builder); + } + + private void persistAnnotations(AnnotatedElement annotatedElement, Class[] annotationTypes, + IntFunction> builder) { + var b = builder.apply(annotationTypes.length); + for (int i = 0; i < annotationTypes.length; i++) { + Class annotationClass = annotationTypes[i]; + SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.Builder annotationBuilder = b.get(i); + annotationBuilder.setTypeName(annotationClass.getName()); + Annotation annotation = AnnotationAccess.getAnnotation(annotatedElement, annotationClass); + persistAnnotationValues(annotation, annotationClass, annotationBuilder::initValues); + } + } + + private void persistAnnotationValues(Annotation annotation, Class annotationClass, IntFunction> builder) { + AnnotationType annotationType = AnnotationType.getInstance(annotationClass); + EconomicMap members = EconomicMap.create(); + annotationType.members().forEach((memberName, memberAccessor) -> { + try { + String moduleName = memberAccessor.getDeclaringClass().getModule().getName(); + if (moduleName != null) { + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, SVMImageLayerWriter.class, false, moduleName); + } + AnnotationMemberValue memberValue = AnnotationMemberValue.getMemberValue(annotation, memberName, memberAccessor, annotationType); + Object value = memberValue.get(annotationType.memberTypes().get(memberName)); + members.put(memberName, value); + } catch (AnnotationMetadata.AnnotationExtractionError e) { + /* We skip the incorrect annotation */ + } + }); + if (!members.isEmpty()) { + var list = builder.apply(members.size()); + MapCursor cursor = members.getEntries(); + for (int i = 0; cursor.advance(); i++) { + var b = list.get(i); + b.setName(cursor.getKey()); + Object v = cursor.getValue(); + persistAnnotationValue(v, b); + } + } + } + + private void persistAnnotationValue(Object v, AnnotationValue.Builder b) { + if (v.getClass().isArray()) { + if (v instanceof Object[] array) { + var ba = b.initMembers(); + ba.setClassName(v.getClass().getComponentType().getName()); + var bav = ba.initMemberValues(array.length); + for (int i = 0; i < array.length; ++i) { + persistAnnotationValue(array[i], bav.get(i)); + } + } else { + Class componentType = v.getClass().getComponentType(); + assert componentType.isPrimitive() : v + " should be a primitive array"; + persistConstantPrimitiveArray(b.initPrimitiveArray(), JavaKind.fromJavaClass(componentType), v); + } + } else { + switch (v) { + case Boolean z -> setAnnotationPrimitiveValue(b, JavaKind.Boolean, z ? 1L : 0L); + case Byte z -> setAnnotationPrimitiveValue(b, JavaKind.Byte, z); + case Short s -> setAnnotationPrimitiveValue(b, JavaKind.Short, s); + case Character c -> setAnnotationPrimitiveValue(b, JavaKind.Char, c); + case Integer i -> setAnnotationPrimitiveValue(b, JavaKind.Int, i); + case Float f -> setAnnotationPrimitiveValue(b, JavaKind.Float, Float.floatToRawIntBits(f)); + case Long j -> setAnnotationPrimitiveValue(b, JavaKind.Long, j); + case Double d -> setAnnotationPrimitiveValue(b, JavaKind.Double, Double.doubleToRawLongBits(d)); + case Class clazz -> b.setClassName(clazz.getName()); + case Annotation innerAnnotation -> + persistAnnotationValues(innerAnnotation, innerAnnotation.annotationType(), b.initAnnotation()::initValues); + case String s -> b.setString(s); + case Enum e -> { + var ba = b.initEnum(); + ba.setClassName(e.getDeclaringClass().getName()); + ba.setName(e.name()); + } + default -> throw AnalysisError.shouldNotReachHere("Unknown annotation value: " + v); + } + } + } + + private static void setAnnotationPrimitiveValue(AnnotationValue.Builder b, JavaKind kind, long rawValue) { + var pv = b.initPrimitive(); + pv.setTypeChar(NumUtil.safeToUByte(kind.getTypeChar())); + pv.setRawValue(rawValue); + } + + private void persistConstant(ImageHeapConstant imageHeapConstant, ConstantParent parent, PersistedConstant.Builder builder, Set constantsToRelink) { + ObjectInfo objectInfo = nativeImageHeap.getConstantInfo(imageHeapConstant); + builder.setObjectOffset((objectInfo == null) ? -1 : objectInfo.getOffset()); + + int id = ImageHeapConstant.getConstantID(imageHeapConstant); + builder.setId(id); + AnalysisType type = imageHeapConstant.getType(); + AnalysisError.guarantee(type.isTrackedAcrossLayers(), "Type %s from constant %s should have been marked as trackedAcrossLayers, but was not", type, imageHeapConstant); + builder.setTypeId(type.getId()); + + IdentityHashCodeProvider identityHashCodeProvider = (IdentityHashCodeProvider) aUniverse.getBigbang().getConstantReflectionProvider(); + int identityHashCode = identityHashCodeProvider.identityHashCode(imageHeapConstant); + builder.setIdentityHashCode(identityHashCode); + + switch (imageHeapConstant) { + case ImageHeapInstance imageHeapInstance -> { + builder.initObject().setInstance(Void.VOID); + persistConstantObjectData(builder.getObject(), imageHeapInstance::getFieldValue, imageHeapInstance.getFieldValuesSize()); + persistConstantRelinkingInfo(builder, imageHeapConstant, constantsToRelink, aUniverse.getBigbang()); + } + case ImageHeapObjectArray imageHeapObjectArray -> { + builder.initObject().setObjectArray(Void.VOID); + persistConstantObjectData(builder.getObject(), imageHeapObjectArray::getElement, imageHeapObjectArray.getLength()); + } + case ImageHeapPrimitiveArray imageHeapPrimitiveArray -> + persistConstantPrimitiveArray(builder.initPrimitiveData(), imageHeapPrimitiveArray.getType().getComponentType().getJavaKind(), imageHeapPrimitiveArray.getArray()); + case ImageHeapRelocatableConstant relocatableConstant -> { + builder.initRelocatable().setKey(relocatableConstant.getConstantData().key); + } + default -> throw AnalysisError.shouldNotReachHere("Unexpected constant type " + imageHeapConstant); + } + + if (!constantsToRelink.contains(id) && parent != ConstantParent.NONE) { + builder.setParentConstantId(parent.constantId); + assert parent.index != UNDEFINED_FIELD_INDEX : "Tried to persist child constant %s from parent constant %d, but got index %d".formatted(imageHeapConstant, parent.constantId, parent.index); + builder.setParentIndex(parent.index); + } + } + + private void persistConstantRelinkingInfo(PersistedConstant.Builder builder, ImageHeapConstant imageHeapConstant, Set constantsToRelink, BigBang bb) { + Class clazz = imageHeapConstant.getType().getJavaClass(); + JavaConstant hostedObject = imageHeapConstant.getHostedObject(); + boolean simulated = hostedObject == null; + builder.setIsSimulated(simulated); + if (!simulated) { + Relinking.Builder relinkingBuilder = builder.getObject().getRelinking(); + int id = ImageHeapConstant.getConstantID(imageHeapConstant); + ResolvedJavaType type = bb.getConstantReflectionProvider().asJavaType(hostedObject); + if (type instanceof AnalysisType analysisType) { + relinkingBuilder.initClassConstant().setTypeId(analysisType.getId()); + constantsToRelink.add(id); + } else if (clazz.equals(String.class)) { + StringConstant.Builder stringConstantBuilder = relinkingBuilder.initStringConstant(); + String value = bb.getSnippetReflectionProvider().asObject(String.class, hostedObject); + if (internedStringsIdentityMap.containsKey(value)) { + /* + * Interned strings must be relinked. + */ + stringConstantBuilder.setValue(value); + constantsToRelink.add(id); + } + } else if (Enum.class.isAssignableFrom(clazz)) { + EnumConstant.Builder enumBuilder = relinkingBuilder.initEnumConstant(); + Enum value = bb.getSnippetReflectionProvider().asObject(Enum.class, hostedObject); + enumBuilder.setEnumClass(value.getDeclaringClass().getName()); + enumBuilder.setEnumName(value.name()); + constantsToRelink.add(id); + } + } + } + + private static void persistConstantPrimitiveArray(PrimitiveArray.Builder builder, JavaKind componentKind, Object array) { + assert componentKind.toJavaClass().equals(array.getClass().getComponentType()); + switch (array) { + case boolean[] a -> persistArray(a, builder::initZ, (b, i) -> b.set(i, a[i])); + case byte[] a -> persistArray(a, builder::initB, (b, i) -> b.set(i, a[i])); + case short[] a -> persistArray(a, builder::initS, (b, i) -> b.set(i, a[i])); + case char[] a -> persistArray(a, builder::initC, (b, i) -> b.set(i, (short) a[i])); + case int[] a -> persistArray(a, builder::initI, (b, i) -> b.set(i, a[i])); + case long[] a -> persistArray(a, builder::initJ, (b, i) -> b.set(i, a[i])); + case float[] a -> persistArray(a, builder::initF, (b, i) -> b.set(i, a[i])); + case double[] a -> persistArray(a, builder::initD, (b, i) -> b.set(i, a[i])); + default -> throw new IllegalArgumentException("Unsupported kind: " + componentKind); + } + } + + /** Enables concise one-liners in {@link #persistConstantPrimitiveArray}. */ + private static void persistArray(A array, IntFunction init, ObjIntConsumer setter) { + int length = Array.getLength(array); + T builder = init.apply(length); + for (int i = 0; i < length; i++) { + setter.accept(builder, i); + } + } + + private void persistConstantObjectData(PersistedConstant.Object.Builder builder, IntFunction valuesFunction, int size) { + StructList.Builder refsBuilder = builder.initData(size); + for (int i = 0; i < size; ++i) { + Object object = valuesFunction.apply(i); + ConstantReference.Builder b = refsBuilder.get(i); + if (delegateProcessing(b, object)) { + /* The object was already persisted */ + } else if (object instanceof ImageHeapConstant imageHeapConstant) { + assert constantsMap.containsKey(imageHeapConstant); + b.initObjectConstant().setConstantId(ImageHeapConstant.getConstantID(imageHeapConstant)); + } else if (object == JavaConstant.NULL_POINTER) { + b.setNullPointer(Void.VOID); + } else if (object instanceof PrimitiveConstant pc) { + PrimitiveValue.Builder pb = b.initPrimitiveValue(); + pb.setTypeChar(NumUtil.safeToUByte(pc.getJavaKind().getTypeChar())); + pb.setRawValue(pc.getRawValue()); + } else { + AnalysisError.guarantee(object instanceof AnalysisFuture, "Unexpected constant %s", object); + b.setNotMaterialized(Void.VOID); + } + } + } + + private static boolean delegateProcessing(ConstantReference.Builder builder, Object constant) { + if (constant instanceof RelocatableConstant relocatableConstant) { + RelocatedPointer pointer = relocatableConstant.getPointer(); + if (pointer instanceof MethodPointer methodPointer) { + AnalysisMethod method = getRelocatableConstantMethod(methodPointer); + builder.initMethodPointer().setMethodId(method.getId()); + return true; + } else if (pointer instanceof CEntryPointLiteralCodePointer cp) { + CEntryPointLiteralReference.Builder b = builder.initCEntryPointLiteralCodePointer(); + b.setMethodName(cp.methodName); + b.setDefiningClass(cp.definingClass.getName()); + b.initParameterNames(cp.parameterTypes.length); + for (int i = 0; i < cp.parameterTypes.length; i++) { + b.getParameterNames().set(i, new Text.Reader(cp.parameterTypes[i].getName())); + } + return true; + } + } + return false; + } + + private void afterConstantAdded(ImageHeapConstant constant) { + constant.getType().registerAsTrackedAcrossLayers(constant); + /* If this is a Class constant persist the corresponding type. */ + ConstantReflectionProvider constantReflection = aUniverse.getBigbang().getConstantReflectionProvider(); + AnalysisType typeFromClassConstant = (AnalysisType) constantReflection.asJavaType(constant); + if (typeFromClassConstant != null) { + typeFromClassConstant.registerAsTrackedAcrossLayers(constant); + } + } + + private void scanConstantReferencedObjects(ImageHeapConstant constant, Collection discoveredConstants) { + if (Objects.requireNonNull(constant) instanceof ImageHeapInstance instance) { + scanConstantReferencedObjects(constant, instance::getFieldValue, instance.getFieldValuesSize(), discoveredConstants); + } else if (constant instanceof ImageHeapObjectArray objArray) { + scanConstantReferencedObjects(constant, objArray::getElement, objArray.getLength(), discoveredConstants); + } + } + + private void scanConstantReferencedObjects(ImageHeapConstant constant, IntFunction referencedObjectFunction, int size, Collection discoveredConstants) { + for (int i = 0; i < size; i++) { + AnalysisType parentType = constant.getType(); + Object obj = referencedObjectFunction.apply(i); + if (obj instanceof ImageHeapConstant con && !constantsMap.containsKey(con)) { + /* + * Some constants are not in imageHeap#reachableObjects, but are still created in + * reachable constants. They can be created in the extension image, but should not + * be used. + */ + Set relinkedFields = imageLayerSnapshotUtil.getRelinkedFields(parentType, aUniverse.getBigbang().getMetaAccess()); + ConstantParent parent = relinkedFields.contains(i) ? new ConstantParent(ImageHeapConstant.getConstantID(constant), i) : ConstantParent.NONE; + + discoveredConstants.add(con); + constantsMap.put(con, parent); + } else if (obj instanceof MethodPointer mp) { + getRelocatableConstantMethod(mp).registerAsTrackedAcrossLayers("In method pointer"); + } + } + } + + private static AnalysisMethod getRelocatableConstantMethod(MethodPointer methodPointer) { + ResolvedJavaMethod method = methodPointer.getMethod(); + if (method instanceof HostedMethod hostedMethod) { + return hostedMethod.wrapped; + } else { + return (AnalysisMethod) method; + } + } + + public void persistAnalysisParsedGraphs() { + // Persisting graphs discovers additional types, members and constants that need persisting + Set persistedGraphMethods = new HashSet<>(); + boolean modified; + do { + modified = false; + /* + * GR-60503: It would be better to mark all the elements as trackedAcrossLayers before + * the end of the analysis and only iterate only once over all methods. + */ + for (AnalysisMethod method : aUniverse.getMethods().stream().filter(AnalysisMethod::isTrackedAcrossLayers).toList()) { + if (persistedGraphMethods.add(method)) { + modified = true; + persistAnalysisParsedGraph(method); + } + } + } while (modified); + + // Note that constants are scanned late so all values are available. + } + + private void persistAnalysisParsedGraph(AnalysisMethod method) { + Object analyzedGraph = method.getGraph(); + if (analyzedGraph instanceof AnalysisParsedGraph analysisParsedGraph) { + String name = imageLayerSnapshotUtil.getMethodDescriptor(method); + MethodGraphsInfo graphsInfo = methodsMap.get(name); + if (graphsInfo == null || graphsInfo.analysisGraphLocation == null) { + String location = persistGraph(method, analysisParsedGraph.getEncodedGraph()); + if (location != null) { + methodsMap.compute(name, (n, mgi) -> (mgi != null ? mgi : MethodGraphsInfo.NO_GRAPHS) + .withAnalysisGraph(location, analysisParsedGraph.isIntrinsic())); + } + } + } + } + + public void persistMethodStrengthenedGraph(AnalysisMethod method) { + if (!useSharedLayerStrengthenedGraphs) { + return; + } + + String name = imageLayerSnapshotUtil.getMethodDescriptor(method); + MethodGraphsInfo graphsInfo = methodsMap.get(name); + + if (graphsInfo == null || graphsInfo.strengthenedGraphLocation == null) { + EncodedGraph analyzedGraph = method.getAnalyzedGraph(); + String location = persistGraph(method, analyzedGraph); + methodsMap.compute(name, (n, mgi) -> (mgi != null ? mgi : MethodGraphsInfo.NO_GRAPHS).withStrengthenedGraph(location)); + } + } + + private String persistGraph(AnalysisMethod method, EncodedGraph analyzedGraph) { + if (!useSharedLayerGraphs) { + return null; + } + byte[] encodedGraph = ObjectCopier.encode(imageLayerSnapshotUtil.getGraphEncoder(this), analyzedGraph); + if (contains(encodedGraph, LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING.getBytes(StandardCharsets.UTF_8))) { + throw AnalysisError.shouldNotReachHere("The graph for the method %s contains a reference to a lambda type, which cannot be decoded: %s".formatted(method, encodedGraph)); + } + return graphsOutput.add(encodedGraph); + } + + private static boolean contains(byte[] data, byte[] seq) { + outer: for (int i = 0; i <= data.length - seq.length; i++) { + for (int j = 0; j < seq.length; j++) { + if (data[i + j] != seq[j]) { + continue outer; + } + } + return true; + } + return false; + } + + record SingletonPersistInfo(LayeredImageSingleton.PersistFlags flags, int id, EconomicMap keyStore) { + } + + public void writeImageSingletonInfo(List, Object>> layeredImageSingletons) { + StructList.Builder singletonsBuilder = snapshotBuilder.initSingletonKeys(layeredImageSingletons.size()); + Map singletonInfoMap = new HashMap<>(); + int nextID = 1; + for (int i = 0; i < layeredImageSingletons.size(); i++) { + var singletonInfo = layeredImageSingletons.get(i); + LayeredImageSingleton singleton; + if (singletonInfo.getValue() instanceof RuntimeOnlyWrapper wrapper) { + singleton = wrapper.wrappedObject(); + } else { + singleton = (LayeredImageSingleton) singletonInfo.getValue(); + } + String key = singletonInfo.getKey().getName(); + if (!singletonInfoMap.containsKey(singleton)) { + var writer = new ImageSingletonWriterImpl(snapshotBuilder); + var flags = singleton.preparePersist(writer); + boolean persistData = flags == LayeredImageSingleton.PersistFlags.CREATE; + var info = new SingletonPersistInfo(flags, persistData ? nextID++ : -1, persistData ? writer.getKeyValueStore() : null); + singletonInfoMap.put(singleton, info); + } + var info = singletonInfoMap.get(singleton); + + ImageSingletonKey.Builder sb = singletonsBuilder.get(i); + sb.setKeyClassName(key); + sb.setObjectId(info.id); + sb.setPersistFlag(info.flags.ordinal()); + } + + var sortedByIDs = singletonInfoMap.entrySet().stream() + .filter(e -> e.getValue().flags == LayeredImageSingleton.PersistFlags.CREATE) + .sorted(Comparator.comparingInt(e -> e.getValue().id)) + .toList(); + StructList.Builder objectsBuilder = snapshotBuilder.initSingletonObjects(sortedByIDs.size()); + for (int i = 0; i < sortedByIDs.size(); i++) { + var entry = sortedByIDs.get(i); + var info = entry.getValue(); + + ImageSingletonObject.Builder ob = objectsBuilder.get(i); + ob.setId(info.id); + ob.setClassName(entry.getKey().getClass().getName()); + writeImageSingletonKeyStore(ob, info.keyStore); + } + } + + private static void writeImageSingletonKeyStore(ImageSingletonObject.Builder objectData, EconomicMap keyStore) { + StructList.Builder lb = objectData.initStore(keyStore.size()); + MapCursor cursor = keyStore.getEntries(); + for (int i = 0; cursor.advance(); i++) { + KeyStoreEntry.Builder b = lb.get(i); + b.setKey(cursor.getKey()); + switch (cursor.getValue()) { + case Integer iv -> b.getValue().setI(iv); + case Long jv -> b.getValue().setJ(jv); + case String str -> b.getValue().setStr(str); + case int[] il -> { + PrimitiveList.Int.Builder ilb = b.getValue().initIl(il.length); + for (int j = 0; j < il.length; j++) { + ilb.set(j, il[j]); + } + } + case String[] strl -> { + TextList.Builder strlb = b.getValue().initStrl(strl.length); + for (int j = 0; j < strl.length; j++) { + strlb.set(j, new Text.Reader(strl[j])); + } + } + case boolean[] zl -> { + PrimitiveList.Boolean.Builder zlb = b.getValue().initZl(zl.length); + for (int j = 0; j < zl.length; j++) { + zlb.set(j, zl[j]); + } + } + default -> throw new IllegalStateException("Unexpected type: " + cursor.getValue()); + } + } + } + + public static class ImageSingletonWriterImpl implements ImageSingletonWriter { + private final EconomicMap keyValueStore = EconomicMap.create(); + private final SharedLayerSnapshotCapnProtoSchemaHolder.SharedLayerSnapshot.Builder snapshotBuilder; + + ImageSingletonWriterImpl(SharedLayerSnapshotCapnProtoSchemaHolder.SharedLayerSnapshot.Builder snapshotBuilder) { + this.snapshotBuilder = snapshotBuilder; + } + + EconomicMap getKeyValueStore() { + return keyValueStore; + } + + private static boolean nonNullEntries(List list) { + return list.stream().filter(Objects::isNull).findAny().isEmpty(); + } + + @Override + public void writeBoolList(String keyName, List value) { + assert nonNullEntries(value); + boolean[] b = new boolean[value.size()]; + for (int i = 0; i < value.size(); i++) { + b[i] = value.get(i); + } + var previous = keyValueStore.put(keyName, b); + assert previous == null : Assertions.errorMessage(keyName, previous); + } + + @Override + public void writeInt(String keyName, int value) { + var previous = keyValueStore.put(keyName, value); + assert previous == null : previous; + } + + @Override + public void writeIntList(String keyName, List value) { + assert nonNullEntries(value); + var previous = keyValueStore.put(keyName, value.stream().mapToInt(i -> i).toArray()); + assert previous == null : Assertions.errorMessage(keyName, previous); + } + + @Override + public void writeLong(String keyName, long value) { + var previous = keyValueStore.put(keyName, value); + assert previous == null : Assertions.errorMessage(keyName, previous); + } + + @Override + public void writeString(String keyName, String value) { + var previous = keyValueStore.put(keyName, value); + assert previous == null : Assertions.errorMessage(keyName, previous); + } + + @Override + public void writeStringList(String keyName, List value) { + assert nonNullEntries(value); + var previous = keyValueStore.put(keyName, value.toArray(String[]::new)); + assert previous == null : Assertions.errorMessage(keyName, previous); + } + + public SharedLayerSnapshotCapnProtoSchemaHolder.SharedLayerSnapshot.Builder getSnapshotBuilder() { + return snapshotBuilder; + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SharedLayerSnapshotCapnProtoSchemaHolder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SharedLayerSnapshotCapnProtoSchemaHolder.java new file mode 100644 index 000000000000..72d86aefda2c --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SharedLayerSnapshotCapnProtoSchemaHolder.java @@ -0,0 +1,4936 @@ +/* + * 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. + */ +//@formatter:off +//Checkstyle: stop +// Generated by Cap'n Proto compiler, DO NOT EDIT +// source: SharedLayerSnapshotCapnProtoSchema.capnp + +package com.oracle.svm.hosted.imagelayer; + +@SuppressWarnings("all") +public final class SharedLayerSnapshotCapnProtoSchemaHolder { + public static class PersistedAnalysisType { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)4,(short)13); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedAnalysisType.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final int getId() { + return _getIntField(0); + } + public final void setId(int value) { + _setIntField(0, value); + } + + public final boolean hasDescriptor() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.Text.Builder getDescriptor() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + public final void setDescriptor(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 0, value); + } + public final void setDescriptor(String value) { + _setPointerField(org.capnproto.Text.factory, 0, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initDescriptor(int size) { + return _initPointerField(org.capnproto.Text.factory, 0, size); + } + public final boolean hasFields() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.PrimitiveList.Int.Builder getFields() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 1, null, 0); + } + public final void setFields(org.capnproto.PrimitiveList.Int.Reader value) { + _setPointerField(org.capnproto.PrimitiveList.Int.factory, 1, value); + } + public final org.capnproto.PrimitiveList.Int.Builder initFields(int size) { + return _initPointerField(org.capnproto.PrimitiveList.Int.factory, 1, size); + } + public final int getHubIdentityHashCode() { + return _getIntField(1); + } + public final void setHubIdentityHashCode(int value) { + _setIntField(1, value); + } + + public final boolean hasClassJavaName() { + return !_pointerFieldIsNull(2); + } + public final org.capnproto.Text.Builder getClassJavaName() { + return _getPointerField(org.capnproto.Text.factory, 2, null, 0, 0); + } + public final void setClassJavaName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 2, value); + } + public final void setClassJavaName(String value) { + _setPointerField(org.capnproto.Text.factory, 2, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initClassJavaName(int size) { + return _initPointerField(org.capnproto.Text.factory, 2, size); + } + public final boolean hasClassName() { + return !_pointerFieldIsNull(3); + } + public final org.capnproto.Text.Builder getClassName() { + return _getPointerField(org.capnproto.Text.factory, 3, null, 0, 0); + } + public final void setClassName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 3, value); + } + public final void setClassName(String value) { + _setPointerField(org.capnproto.Text.factory, 3, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initClassName(int size) { + return _initPointerField(org.capnproto.Text.factory, 3, size); + } + public final int getModifiers() { + return _getIntField(2); + } + public final void setModifiers(int value) { + _setIntField(2, value); + } + + public final boolean getIsInterface() { + return _getBooleanField(96); + } + public final void setIsInterface(boolean value) { + _setBooleanField(96, value); + } + + public final boolean getIsEnum() { + return _getBooleanField(97); + } + public final void setIsEnum(boolean value) { + _setBooleanField(97, value); + } + + public final boolean getIsInitialized() { + return _getBooleanField(98); + } + public final void setIsInitialized(boolean value) { + _setBooleanField(98, value); + } + + public final boolean getIsInitializedAtBuildTime() { + return _getBooleanField(99); + } + public final void setIsInitializedAtBuildTime(boolean value) { + _setBooleanField(99, value); + } + + public final boolean getIsLinked() { + return _getBooleanField(100); + } + public final void setIsLinked(boolean value) { + _setBooleanField(100, value); + } + + public final boolean hasSourceFileName() { + return !_pointerFieldIsNull(4); + } + public final org.capnproto.Text.Builder getSourceFileName() { + return _getPointerField(org.capnproto.Text.factory, 4, null, 0, 0); + } + public final void setSourceFileName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 4, value); + } + public final void setSourceFileName(String value) { + _setPointerField(org.capnproto.Text.factory, 4, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initSourceFileName(int size) { + return _initPointerField(org.capnproto.Text.factory, 4, size); + } + public final int getEnclosingTypeId() { + return _getIntField(4); + } + public final void setEnclosingTypeId(int value) { + _setIntField(4, value); + } + + public final int getComponentTypeId() { + return _getIntField(5); + } + public final void setComponentTypeId(int value) { + _setIntField(5, value); + } + + public final int getSuperClassTypeId() { + return _getIntField(6); + } + public final void setSuperClassTypeId(int value) { + _setIntField(6, value); + } + + public final boolean getIsInstantiated() { + return _getBooleanField(101); + } + public final void setIsInstantiated(boolean value) { + _setBooleanField(101, value); + } + + public final boolean getIsUnsafeAllocated() { + return _getBooleanField(102); + } + public final void setIsUnsafeAllocated(boolean value) { + _setBooleanField(102, value); + } + + public final boolean getIsReachable() { + return _getBooleanField(103); + } + public final void setIsReachable(boolean value) { + _setBooleanField(103, value); + } + + public final boolean hasInterfaces() { + return !_pointerFieldIsNull(5); + } + public final org.capnproto.PrimitiveList.Int.Builder getInterfaces() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 5, null, 0); + } + public final void setInterfaces(org.capnproto.PrimitiveList.Int.Reader value) { + _setPointerField(org.capnproto.PrimitiveList.Int.factory, 5, value); + } + public final org.capnproto.PrimitiveList.Int.Builder initInterfaces(int size) { + return _initPointerField(org.capnproto.PrimitiveList.Int.factory, 5, size); + } + public final boolean hasInstanceFieldIds() { + return !_pointerFieldIsNull(6); + } + public final org.capnproto.PrimitiveList.Int.Builder getInstanceFieldIds() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 6, null, 0); + } + public final void setInstanceFieldIds(org.capnproto.PrimitiveList.Int.Reader value) { + _setPointerField(org.capnproto.PrimitiveList.Int.factory, 6, value); + } + public final org.capnproto.PrimitiveList.Int.Builder initInstanceFieldIds(int size) { + return _initPointerField(org.capnproto.PrimitiveList.Int.factory, 6, size); + } + public final boolean hasInstanceFieldIdsWithSuper() { + return !_pointerFieldIsNull(7); + } + public final org.capnproto.PrimitiveList.Int.Builder getInstanceFieldIdsWithSuper() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 7, null, 0); + } + public final void setInstanceFieldIdsWithSuper(org.capnproto.PrimitiveList.Int.Reader value) { + _setPointerField(org.capnproto.PrimitiveList.Int.factory, 7, value); + } + public final org.capnproto.PrimitiveList.Int.Builder initInstanceFieldIdsWithSuper(int size) { + return _initPointerField(org.capnproto.PrimitiveList.Int.factory, 7, size); + } + public final boolean hasStaticFieldIds() { + return !_pointerFieldIsNull(8); + } + public final org.capnproto.PrimitiveList.Int.Builder getStaticFieldIds() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 8, null, 0); + } + public final void setStaticFieldIds(org.capnproto.PrimitiveList.Int.Reader value) { + _setPointerField(org.capnproto.PrimitiveList.Int.factory, 8, value); + } + public final org.capnproto.PrimitiveList.Int.Builder initStaticFieldIds(int size) { + return _initPointerField(org.capnproto.PrimitiveList.Int.factory, 8, size); + } + public final boolean hasAnnotationList() { + return !_pointerFieldIsNull(9); + } + public final org.capnproto.StructList.Builder getAnnotationList() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.listFactory, 9, null, 0); + } + public final void setAnnotationList(org.capnproto.StructList.Reader value) { + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.listFactory, 9, value); + } + public final org.capnproto.StructList.Builder initAnnotationList(int size) { + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.listFactory, 9, size); + } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ClassInitializationInfo.Builder getClassInitializationInfo() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ClassInitializationInfo.factory, 10, null, 0); + } + public final void setClassInitializationInfo(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ClassInitializationInfo.Reader value) { + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ClassInitializationInfo.factory,10, value); + } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ClassInitializationInfo.Builder initClassInitializationInfo() { + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ClassInitializationInfo.factory,10, 0); + } + public final WrappedType.Builder getWrappedType() { + return new PersistedAnalysisType.WrappedType.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final WrappedType.Builder initWrappedType() { + _setShortField(7,(short)0); + _clearPointerField(11); + _clearPointerField(12); + return new PersistedAnalysisType.WrappedType.Builder(segment, data, pointers, dataSize, pointerCount); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final int getId() { + return _getIntField(0); + } + + public boolean hasDescriptor() { + return !_pointerFieldIsNull(0); + } + public org.capnproto.Text.Reader getDescriptor() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + + public final boolean hasFields() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.PrimitiveList.Int.Reader getFields() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 1, null, 0); + } + + public final int getHubIdentityHashCode() { + return _getIntField(1); + } + + public boolean hasClassJavaName() { + return !_pointerFieldIsNull(2); + } + public org.capnproto.Text.Reader getClassJavaName() { + return _getPointerField(org.capnproto.Text.factory, 2, null, 0, 0); + } + + public boolean hasClassName() { + return !_pointerFieldIsNull(3); + } + public org.capnproto.Text.Reader getClassName() { + return _getPointerField(org.capnproto.Text.factory, 3, null, 0, 0); + } + + public final int getModifiers() { + return _getIntField(2); + } + + public final boolean getIsInterface() { + return _getBooleanField(96); + } + + public final boolean getIsEnum() { + return _getBooleanField(97); + } + + public final boolean getIsInitialized() { + return _getBooleanField(98); + } + + public final boolean getIsInitializedAtBuildTime() { + return _getBooleanField(99); + } + + public final boolean getIsLinked() { + return _getBooleanField(100); + } + + public boolean hasSourceFileName() { + return !_pointerFieldIsNull(4); + } + public org.capnproto.Text.Reader getSourceFileName() { + return _getPointerField(org.capnproto.Text.factory, 4, null, 0, 0); + } + + public final int getEnclosingTypeId() { + return _getIntField(4); + } + + public final int getComponentTypeId() { + return _getIntField(5); + } + + public final int getSuperClassTypeId() { + return _getIntField(6); + } + + public final boolean getIsInstantiated() { + return _getBooleanField(101); + } + + public final boolean getIsUnsafeAllocated() { + return _getBooleanField(102); + } + + public final boolean getIsReachable() { + return _getBooleanField(103); + } + + public final boolean hasInterfaces() { + return !_pointerFieldIsNull(5); + } + public final org.capnproto.PrimitiveList.Int.Reader getInterfaces() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 5, null, 0); + } + + public final boolean hasInstanceFieldIds() { + return !_pointerFieldIsNull(6); + } + public final org.capnproto.PrimitiveList.Int.Reader getInstanceFieldIds() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 6, null, 0); + } + + public final boolean hasInstanceFieldIdsWithSuper() { + return !_pointerFieldIsNull(7); + } + public final org.capnproto.PrimitiveList.Int.Reader getInstanceFieldIdsWithSuper() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 7, null, 0); + } + + public final boolean hasStaticFieldIds() { + return !_pointerFieldIsNull(8); + } + public final org.capnproto.PrimitiveList.Int.Reader getStaticFieldIds() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 8, null, 0); + } + + public final boolean hasAnnotationList() { + return !_pointerFieldIsNull(9); + } + public final org.capnproto.StructList.Reader getAnnotationList() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.listFactory, 9, null, 0); + } + + public boolean hasClassInitializationInfo() { + return !_pointerFieldIsNull(10); + } + public com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ClassInitializationInfo.Reader getClassInitializationInfo() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ClassInitializationInfo.factory,10,null, 0); + } + + public WrappedType.Reader getWrappedType() { + return new PersistedAnalysisType.WrappedType.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + } + + public static class WrappedType { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)4,(short)13); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedAnalysisType.WrappedType.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public Which which() { + switch(_getShortField(7)) { + case 0 : return Which.NONE; + case 1 : return Which.SERIALIZATION_GENERATED; + case 2 : return Which.LAMBDA; + case 3 : return Which.PROXY_TYPE; + default: return Which._NOT_IN_SCHEMA; + } + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean isNone() { + return which() == PersistedAnalysisType.WrappedType.Which.NONE; + } + public final org.capnproto.Void getNone() { + assert which() == PersistedAnalysisType.WrappedType.Which.NONE: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + public final void setNone(org.capnproto.Void value) { + _setShortField(7, (short)PersistedAnalysisType.WrappedType.Which.NONE.ordinal()); + } + + public final boolean isSerializationGenerated() { + return which() == PersistedAnalysisType.WrappedType.Which.SERIALIZATION_GENERATED; + } + public final SerializationGenerated.Builder getSerializationGenerated() { + return new PersistedAnalysisType.WrappedType.SerializationGenerated.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final SerializationGenerated.Builder initSerializationGenerated() { + _setShortField(7, (short)PersistedAnalysisType.WrappedType.Which.SERIALIZATION_GENERATED.ordinal()); + _clearPointerField(11); + _clearPointerField(12); + return new PersistedAnalysisType.WrappedType.SerializationGenerated.Builder(segment, data, pointers, dataSize, pointerCount); + } + + public final boolean isLambda() { + return which() == PersistedAnalysisType.WrappedType.Which.LAMBDA; + } + public final Lambda.Builder getLambda() { + return new PersistedAnalysisType.WrappedType.Lambda.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final Lambda.Builder initLambda() { + _setShortField(7, (short)PersistedAnalysisType.WrappedType.Which.LAMBDA.ordinal()); + _clearPointerField(11); + return new PersistedAnalysisType.WrappedType.Lambda.Builder(segment, data, pointers, dataSize, pointerCount); + } + + public final boolean isProxyType() { + return which() == PersistedAnalysisType.WrappedType.Which.PROXY_TYPE; + } + public final org.capnproto.Void getProxyType() { + assert which() == PersistedAnalysisType.WrappedType.Which.PROXY_TYPE: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + public final void setProxyType(org.capnproto.Void value) { + _setShortField(7, (short)PersistedAnalysisType.WrappedType.Which.PROXY_TYPE.ordinal()); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public Which which() { + switch(_getShortField(7)) { + case 0 : return Which.NONE; + case 1 : return Which.SERIALIZATION_GENERATED; + case 2 : return Which.LAMBDA; + case 3 : return Which.PROXY_TYPE; + default: return Which._NOT_IN_SCHEMA; + } + } + public final boolean isNone() { + return which() == PersistedAnalysisType.WrappedType.Which.NONE; + } + public final org.capnproto.Void getNone() { + assert which() == PersistedAnalysisType.WrappedType.Which.NONE: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + + public final boolean isSerializationGenerated() { + return which() == PersistedAnalysisType.WrappedType.Which.SERIALIZATION_GENERATED; + } + public SerializationGenerated.Reader getSerializationGenerated() { + return new PersistedAnalysisType.WrappedType.SerializationGenerated.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final boolean isLambda() { + return which() == PersistedAnalysisType.WrappedType.Which.LAMBDA; + } + public Lambda.Reader getLambda() { + return new PersistedAnalysisType.WrappedType.Lambda.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final boolean isProxyType() { + return which() == PersistedAnalysisType.WrappedType.Which.PROXY_TYPE; + } + public final org.capnproto.Void getProxyType() { + assert which() == PersistedAnalysisType.WrappedType.Which.PROXY_TYPE: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + + } + + public enum Which { + NONE, + SERIALIZATION_GENERATED, + LAMBDA, + PROXY_TYPE, + _NOT_IN_SCHEMA, + } + public static class SerializationGenerated { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)4,(short)13); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedAnalysisType.WrappedType.SerializationGenerated.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean hasRawDeclaringClass() { + return !_pointerFieldIsNull(11); + } + public final org.capnproto.Text.Builder getRawDeclaringClass() { + return _getPointerField(org.capnproto.Text.factory, 11, null, 0, 0); + } + public final void setRawDeclaringClass(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 11, value); + } + public final void setRawDeclaringClass(String value) { + _setPointerField(org.capnproto.Text.factory, 11, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initRawDeclaringClass(int size) { + return _initPointerField(org.capnproto.Text.factory, 11, size); + } + public final boolean hasRawTargetConstructor() { + return !_pointerFieldIsNull(12); + } + public final org.capnproto.Text.Builder getRawTargetConstructor() { + return _getPointerField(org.capnproto.Text.factory, 12, null, 0, 0); + } + public final void setRawTargetConstructor(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 12, value); + } + public final void setRawTargetConstructor(String value) { + _setPointerField(org.capnproto.Text.factory, 12, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initRawTargetConstructor(int size) { + return _initPointerField(org.capnproto.Text.factory, 12, size); + } + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public boolean hasRawDeclaringClass() { + return !_pointerFieldIsNull(11); + } + public org.capnproto.Text.Reader getRawDeclaringClass() { + return _getPointerField(org.capnproto.Text.factory, 11, null, 0, 0); + } + + public boolean hasRawTargetConstructor() { + return !_pointerFieldIsNull(12); + } + public org.capnproto.Text.Reader getRawTargetConstructor() { + return _getPointerField(org.capnproto.Text.factory, 12, null, 0, 0); + } + + } + + } + + + public static class Lambda { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)4,(short)13); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedAnalysisType.WrappedType.Lambda.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean hasCapturingClass() { + return !_pointerFieldIsNull(11); + } + public final org.capnproto.Text.Builder getCapturingClass() { + return _getPointerField(org.capnproto.Text.factory, 11, null, 0, 0); + } + public final void setCapturingClass(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 11, value); + } + public final void setCapturingClass(String value) { + _setPointerField(org.capnproto.Text.factory, 11, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initCapturingClass(int size) { + return _initPointerField(org.capnproto.Text.factory, 11, size); + } + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public boolean hasCapturingClass() { + return !_pointerFieldIsNull(11); + } + public org.capnproto.Text.Reader getCapturingClass() { + return _getPointerField(org.capnproto.Text.factory, 11, null, 0, 0); + } + + } + + } + + + } + + + } + + + public static class ClassInitializationInfo { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)1,(short)0); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return ClassInitializationInfo.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean getIsNoInitializerNoTracking() { + return _getBooleanField(0); + } + public final void setIsNoInitializerNoTracking(boolean value) { + _setBooleanField(0, value); + } + + public final boolean getIsInitializedNoTracking() { + return _getBooleanField(1); + } + public final void setIsInitializedNoTracking(boolean value) { + _setBooleanField(1, value); + } + + public final boolean getIsFailedNoTracking() { + return _getBooleanField(2); + } + public final void setIsFailedNoTracking(boolean value) { + _setBooleanField(2, value); + } + + public final boolean getIsInitialized() { + return _getBooleanField(3); + } + public final void setIsInitialized(boolean value) { + _setBooleanField(3, value); + } + + public final boolean getIsInErrorState() { + return _getBooleanField(4); + } + public final void setIsInErrorState(boolean value) { + _setBooleanField(4, value); + } + + public final boolean getIsLinked() { + return _getBooleanField(5); + } + public final void setIsLinked(boolean value) { + _setBooleanField(5, value); + } + + public final boolean getHasInitializer() { + return _getBooleanField(6); + } + public final void setHasInitializer(boolean value) { + _setBooleanField(6, value); + } + + public final boolean getIsBuildTimeInitialized() { + return _getBooleanField(7); + } + public final void setIsBuildTimeInitialized(boolean value) { + _setBooleanField(7, value); + } + + public final boolean getIsTracked() { + return _getBooleanField(8); + } + public final void setIsTracked(boolean value) { + _setBooleanField(8, value); + } + + public final int getInitializerMethodId() { + return _getIntField(1); + } + public final void setInitializerMethodId(int value) { + _setIntField(1, value); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final boolean getIsNoInitializerNoTracking() { + return _getBooleanField(0); + } + + public final boolean getIsInitializedNoTracking() { + return _getBooleanField(1); + } + + public final boolean getIsFailedNoTracking() { + return _getBooleanField(2); + } + + public final boolean getIsInitialized() { + return _getBooleanField(3); + } + + public final boolean getIsInErrorState() { + return _getBooleanField(4); + } + + public final boolean getIsLinked() { + return _getBooleanField(5); + } + + public final boolean getHasInitializer() { + return _getBooleanField(6); + } + + public final boolean getIsBuildTimeInitialized() { + return _getBooleanField(7); + } + + public final boolean getIsTracked() { + return _getBooleanField(8); + } + + public final int getInitializerMethodId() { + return _getIntField(1); + } + + } + + } + + + public static class PersistedAnalysisMethod { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)5,(short)13); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedAnalysisMethod.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final int getId() { + return _getIntField(0); + } + public final void setId(int value) { + _setIntField(0, value); + } + + public final boolean hasDescriptor() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.Text.Builder getDescriptor() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + public final void setDescriptor(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 0, value); + } + public final void setDescriptor(String value) { + _setPointerField(org.capnproto.Text.factory, 0, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initDescriptor(int size) { + return _initPointerField(org.capnproto.Text.factory, 0, size); + } + public final boolean hasName() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.Text.Builder getName() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + public final void setName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 1, value); + } + public final void setName(String value) { + _setPointerField(org.capnproto.Text.factory, 1, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initName(int size) { + return _initPointerField(org.capnproto.Text.factory, 1, size); + } + public final boolean hasClassName() { + return !_pointerFieldIsNull(2); + } + public final org.capnproto.Text.Builder getClassName() { + return _getPointerField(org.capnproto.Text.factory, 2, null, 0, 0); + } + public final void setClassName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 2, value); + } + public final void setClassName(String value) { + _setPointerField(org.capnproto.Text.factory, 2, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initClassName(int size) { + return _initPointerField(org.capnproto.Text.factory, 2, size); + } + public final int getDeclaringTypeId() { + return _getIntField(1); + } + public final void setDeclaringTypeId(int value) { + _setIntField(1, value); + } + + public final boolean hasArgumentClassNames() { + return !_pointerFieldIsNull(3); + } + public final org.capnproto.TextList.Builder getArgumentClassNames() { + return _getPointerField(org.capnproto.TextList.factory, 3, null, 0); + } + public final void setArgumentClassNames(org.capnproto.TextList.Reader value) { + _setPointerField(org.capnproto.TextList.factory, 3, value); + } + public final org.capnproto.TextList.Builder initArgumentClassNames(int size) { + return _initPointerField(org.capnproto.TextList.factory, 3, size); + } + public final boolean hasArgumentTypeIds() { + return !_pointerFieldIsNull(4); + } + public final org.capnproto.PrimitiveList.Int.Builder getArgumentTypeIds() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 4, null, 0); + } + public final void setArgumentTypeIds(org.capnproto.PrimitiveList.Int.Reader value) { + _setPointerField(org.capnproto.PrimitiveList.Int.factory, 4, value); + } + public final org.capnproto.PrimitiveList.Int.Builder initArgumentTypeIds(int size) { + return _initPointerField(org.capnproto.PrimitiveList.Int.factory, 4, size); + } + public final int getReturnTypeId() { + return _getIntField(2); + } + public final void setReturnTypeId(int value) { + _setIntField(2, value); + } + + public final int getModifiers() { + return _getIntField(3); + } + public final void setModifiers(int value) { + _setIntField(3, value); + } + + public final boolean hasCode() { + return !_pointerFieldIsNull(5); + } + public final org.capnproto.Data.Builder getCode() { + return _getPointerField(org.capnproto.Data.factory, 5, null, 0, 0); + } + public final void setCode(org.capnproto.Data.Reader value) { + _setPointerField(org.capnproto.Data.factory, 5, value); + } + public final void setCode(byte [] value) { + _setPointerField(org.capnproto.Data.factory, 5, new org.capnproto.Data.Reader(value)); + } + public final org.capnproto.Data.Builder initCode(int size) { + return _initPointerField(org.capnproto.Data.factory, 5, size); + } + public final int getCodeSize() { + return _getIntField(4); + } + public final void setCodeSize(int value) { + _setIntField(4, value); + } + + public final boolean getIsConstructor() { + return _getBooleanField(160); + } + public final void setIsConstructor(boolean value) { + _setBooleanField(160, value); + } + + public final boolean getIsSynthetic() { + return _getBooleanField(161); + } + public final void setIsSynthetic(boolean value) { + _setBooleanField(161, value); + } + + public final boolean getCanBeStaticallyBound() { + return _getBooleanField(162); + } + public final void setCanBeStaticallyBound(boolean value) { + _setBooleanField(162, value); + } + + public final boolean getIsVirtualRootMethod() { + return _getBooleanField(163); + } + public final void setIsVirtualRootMethod(boolean value) { + _setBooleanField(163, value); + } + + public final boolean getIsDirectRootMethod() { + return _getBooleanField(164); + } + public final void setIsDirectRootMethod(boolean value) { + _setBooleanField(164, value); + } + + public final boolean getIsInvoked() { + return _getBooleanField(165); + } + public final void setIsInvoked(boolean value) { + _setBooleanField(165, value); + } + + public final boolean getIsImplementationInvoked() { + return _getBooleanField(166); + } + public final void setIsImplementationInvoked(boolean value) { + _setBooleanField(166, value); + } + + public final boolean getIsIntrinsicMethod() { + return _getBooleanField(167); + } + public final void setIsIntrinsicMethod(boolean value) { + _setBooleanField(167, value); + } + + public final boolean hasMethodHandleIntrinsicName() { + return !_pointerFieldIsNull(6); + } + public final org.capnproto.Text.Builder getMethodHandleIntrinsicName() { + return _getPointerField(org.capnproto.Text.factory, 6, null, 0, 0); + } + public final void setMethodHandleIntrinsicName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 6, value); + } + public final void setMethodHandleIntrinsicName(String value) { + _setPointerField(org.capnproto.Text.factory, 6, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initMethodHandleIntrinsicName(int size) { + return _initPointerField(org.capnproto.Text.factory, 6, size); + } + public final boolean hasAnnotationList() { + return !_pointerFieldIsNull(7); + } + public final org.capnproto.StructList.Builder getAnnotationList() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.listFactory, 7, null, 0); + } + public final void setAnnotationList(org.capnproto.StructList.Reader value) { + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.listFactory, 7, value); + } + public final org.capnproto.StructList.Builder initAnnotationList(int size) { + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.listFactory, 7, size); + } + public final boolean getIsVarArgs() { + return _getBooleanField(168); + } + public final void setIsVarArgs(boolean value) { + _setBooleanField(168, value); + } + + public final boolean hasAnalysisGraphLocation() { + return !_pointerFieldIsNull(8); + } + public final org.capnproto.Text.Builder getAnalysisGraphLocation() { + return _getPointerField(org.capnproto.Text.factory, 8, null, 0, 0); + } + public final void setAnalysisGraphLocation(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 8, value); + } + public final void setAnalysisGraphLocation(String value) { + _setPointerField(org.capnproto.Text.factory, 8, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initAnalysisGraphLocation(int size) { + return _initPointerField(org.capnproto.Text.factory, 8, size); + } + public final boolean getAnalysisGraphIsIntrinsic() { + return _getBooleanField(169); + } + public final void setAnalysisGraphIsIntrinsic(boolean value) { + _setBooleanField(169, value); + } + + public final boolean hasStrengthenedGraphLocation() { + return !_pointerFieldIsNull(9); + } + public final org.capnproto.Text.Builder getStrengthenedGraphLocation() { + return _getPointerField(org.capnproto.Text.factory, 9, null, 0, 0); + } + public final void setStrengthenedGraphLocation(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 9, value); + } + public final void setStrengthenedGraphLocation(String value) { + _setPointerField(org.capnproto.Text.factory, 9, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initStrengthenedGraphLocation(int size) { + return _initPointerField(org.capnproto.Text.factory, 9, size); + } + public final WrappedMethod.Builder getWrappedMethod() { + return new PersistedAnalysisMethod.WrappedMethod.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final WrappedMethod.Builder initWrappedMethod() { + _setShortField(11,(short)0); + _setIntField(6,0); + _setBooleanField(224,false); + _setIntField(8,0); + _clearPointerField(10); + _clearPointerField(11); + _clearPointerField(12); + return new PersistedAnalysisMethod.WrappedMethod.Builder(segment, data, pointers, dataSize, pointerCount); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final int getId() { + return _getIntField(0); + } + + public boolean hasDescriptor() { + return !_pointerFieldIsNull(0); + } + public org.capnproto.Text.Reader getDescriptor() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + + public boolean hasName() { + return !_pointerFieldIsNull(1); + } + public org.capnproto.Text.Reader getName() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + + public boolean hasClassName() { + return !_pointerFieldIsNull(2); + } + public org.capnproto.Text.Reader getClassName() { + return _getPointerField(org.capnproto.Text.factory, 2, null, 0, 0); + } + + public final int getDeclaringTypeId() { + return _getIntField(1); + } + + public final boolean hasArgumentClassNames() { + return !_pointerFieldIsNull(3); + } + public final org.capnproto.TextList.Reader getArgumentClassNames() { + return _getPointerField(org.capnproto.TextList.factory, 3, null, 0); + } + + public final boolean hasArgumentTypeIds() { + return !_pointerFieldIsNull(4); + } + public final org.capnproto.PrimitiveList.Int.Reader getArgumentTypeIds() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 4, null, 0); + } + + public final int getReturnTypeId() { + return _getIntField(2); + } + + public final int getModifiers() { + return _getIntField(3); + } + + public boolean hasCode() { + return !_pointerFieldIsNull(5); + } + public org.capnproto.Data.Reader getCode() { + return _getPointerField(org.capnproto.Data.factory, 5, null, 0, 0); + } + + public final int getCodeSize() { + return _getIntField(4); + } + + public final boolean getIsConstructor() { + return _getBooleanField(160); + } + + public final boolean getIsSynthetic() { + return _getBooleanField(161); + } + + public final boolean getCanBeStaticallyBound() { + return _getBooleanField(162); + } + + public final boolean getIsVirtualRootMethod() { + return _getBooleanField(163); + } + + public final boolean getIsDirectRootMethod() { + return _getBooleanField(164); + } + + public final boolean getIsInvoked() { + return _getBooleanField(165); + } + + public final boolean getIsImplementationInvoked() { + return _getBooleanField(166); + } + + public final boolean getIsIntrinsicMethod() { + return _getBooleanField(167); + } + + public boolean hasMethodHandleIntrinsicName() { + return !_pointerFieldIsNull(6); + } + public org.capnproto.Text.Reader getMethodHandleIntrinsicName() { + return _getPointerField(org.capnproto.Text.factory, 6, null, 0, 0); + } + + public final boolean hasAnnotationList() { + return !_pointerFieldIsNull(7); + } + public final org.capnproto.StructList.Reader getAnnotationList() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.listFactory, 7, null, 0); + } + + public final boolean getIsVarArgs() { + return _getBooleanField(168); + } + + public boolean hasAnalysisGraphLocation() { + return !_pointerFieldIsNull(8); + } + public org.capnproto.Text.Reader getAnalysisGraphLocation() { + return _getPointerField(org.capnproto.Text.factory, 8, null, 0, 0); + } + + public final boolean getAnalysisGraphIsIntrinsic() { + return _getBooleanField(169); + } + + public boolean hasStrengthenedGraphLocation() { + return !_pointerFieldIsNull(9); + } + public org.capnproto.Text.Reader getStrengthenedGraphLocation() { + return _getPointerField(org.capnproto.Text.factory, 9, null, 0, 0); + } + + public WrappedMethod.Reader getWrappedMethod() { + return new PersistedAnalysisMethod.WrappedMethod.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + } + + public static class WrappedMethod { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)5,(short)13); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedAnalysisMethod.WrappedMethod.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public Which which() { + switch(_getShortField(11)) { + case 0 : return Which.NONE; + case 1 : return Which.FACTORY_METHOD; + case 2 : return Which.OUTLINED_S_B; + case 3 : return Which.C_ENTRY_POINT_CALL_STUB; + case 4 : return Which.WRAPPED_MEMBER; + default: return Which._NOT_IN_SCHEMA; + } + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean isNone() { + return which() == PersistedAnalysisMethod.WrappedMethod.Which.NONE; + } + public final org.capnproto.Void getNone() { + assert which() == PersistedAnalysisMethod.WrappedMethod.Which.NONE: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + public final void setNone(org.capnproto.Void value) { + _setShortField(11, (short)PersistedAnalysisMethod.WrappedMethod.Which.NONE.ordinal()); + } + + public final boolean isFactoryMethod() { + return which() == PersistedAnalysisMethod.WrappedMethod.Which.FACTORY_METHOD; + } + public final FactoryMethod.Builder getFactoryMethod() { + return new PersistedAnalysisMethod.WrappedMethod.FactoryMethod.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final FactoryMethod.Builder initFactoryMethod() { + _setShortField(11, (short)PersistedAnalysisMethod.WrappedMethod.Which.FACTORY_METHOD.ordinal()); + _setIntField(6,0); + _setBooleanField(224,false); + _setIntField(8,0); + return new PersistedAnalysisMethod.WrappedMethod.FactoryMethod.Builder(segment, data, pointers, dataSize, pointerCount); + } + + public final boolean isOutlinedSB() { + return which() == PersistedAnalysisMethod.WrappedMethod.Which.OUTLINED_S_B; + } + public final OutlinedSB.Builder getOutlinedSB() { + return new PersistedAnalysisMethod.WrappedMethod.OutlinedSB.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final OutlinedSB.Builder initOutlinedSB() { + _setShortField(11, (short)PersistedAnalysisMethod.WrappedMethod.Which.OUTLINED_S_B.ordinal()); + _clearPointerField(10); + _clearPointerField(11); + return new PersistedAnalysisMethod.WrappedMethod.OutlinedSB.Builder(segment, data, pointers, dataSize, pointerCount); + } + + public final boolean isCEntryPointCallStub() { + return which() == PersistedAnalysisMethod.WrappedMethod.Which.C_ENTRY_POINT_CALL_STUB; + } + public final CEntryPointCallStub.Builder getCEntryPointCallStub() { + return new PersistedAnalysisMethod.WrappedMethod.CEntryPointCallStub.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final CEntryPointCallStub.Builder initCEntryPointCallStub() { + _setShortField(11, (short)PersistedAnalysisMethod.WrappedMethod.Which.C_ENTRY_POINT_CALL_STUB.ordinal()); + _setBooleanField(192,false); + _setIntField(8,0); + return new PersistedAnalysisMethod.WrappedMethod.CEntryPointCallStub.Builder(segment, data, pointers, dataSize, pointerCount); + } + + public final boolean isWrappedMember() { + return which() == PersistedAnalysisMethod.WrappedMethod.Which.WRAPPED_MEMBER; + } + public final WrappedMember.Builder getWrappedMember() { + return new PersistedAnalysisMethod.WrappedMethod.WrappedMember.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final WrappedMember.Builder initWrappedMember() { + _setShortField(11, (short)PersistedAnalysisMethod.WrappedMethod.Which.WRAPPED_MEMBER.ordinal()); + _setShortField(16,(short)0); + _clearPointerField(10); + _clearPointerField(11); + _clearPointerField(12); + return new PersistedAnalysisMethod.WrappedMethod.WrappedMember.Builder(segment, data, pointers, dataSize, pointerCount); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public Which which() { + switch(_getShortField(11)) { + case 0 : return Which.NONE; + case 1 : return Which.FACTORY_METHOD; + case 2 : return Which.OUTLINED_S_B; + case 3 : return Which.C_ENTRY_POINT_CALL_STUB; + case 4 : return Which.WRAPPED_MEMBER; + default: return Which._NOT_IN_SCHEMA; + } + } + public final boolean isNone() { + return which() == PersistedAnalysisMethod.WrappedMethod.Which.NONE; + } + public final org.capnproto.Void getNone() { + assert which() == PersistedAnalysisMethod.WrappedMethod.Which.NONE: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + + public final boolean isFactoryMethod() { + return which() == PersistedAnalysisMethod.WrappedMethod.Which.FACTORY_METHOD; + } + public FactoryMethod.Reader getFactoryMethod() { + return new PersistedAnalysisMethod.WrappedMethod.FactoryMethod.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final boolean isOutlinedSB() { + return which() == PersistedAnalysisMethod.WrappedMethod.Which.OUTLINED_S_B; + } + public OutlinedSB.Reader getOutlinedSB() { + return new PersistedAnalysisMethod.WrappedMethod.OutlinedSB.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final boolean isCEntryPointCallStub() { + return which() == PersistedAnalysisMethod.WrappedMethod.Which.C_ENTRY_POINT_CALL_STUB; + } + public CEntryPointCallStub.Reader getCEntryPointCallStub() { + return new PersistedAnalysisMethod.WrappedMethod.CEntryPointCallStub.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final boolean isWrappedMember() { + return which() == PersistedAnalysisMethod.WrappedMethod.Which.WRAPPED_MEMBER; + } + public WrappedMember.Reader getWrappedMember() { + return new PersistedAnalysisMethod.WrappedMethod.WrappedMember.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + } + + public enum Which { + NONE, + FACTORY_METHOD, + OUTLINED_S_B, + C_ENTRY_POINT_CALL_STUB, + WRAPPED_MEMBER, + _NOT_IN_SCHEMA, + } + public static class FactoryMethod { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)5,(short)13); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedAnalysisMethod.WrappedMethod.FactoryMethod.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final int getTargetConstructorId() { + return _getIntField(6); + } + public final void setTargetConstructorId(int value) { + _setIntField(6, value); + } + + public final boolean getThrowAllocatedObject() { + return _getBooleanField(224); + } + public final void setThrowAllocatedObject(boolean value) { + _setBooleanField(224, value); + } + + public final int getInstantiatedTypeId() { + return _getIntField(8); + } + public final void setInstantiatedTypeId(int value) { + _setIntField(8, value); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final int getTargetConstructorId() { + return _getIntField(6); + } + + public final boolean getThrowAllocatedObject() { + return _getBooleanField(224); + } + + public final int getInstantiatedTypeId() { + return _getIntField(8); + } + + } + + } + + + public static class OutlinedSB { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)5,(short)13); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedAnalysisMethod.WrappedMethod.OutlinedSB.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean hasMethodTypeReturn() { + return !_pointerFieldIsNull(10); + } + public final org.capnproto.Text.Builder getMethodTypeReturn() { + return _getPointerField(org.capnproto.Text.factory, 10, null, 0, 0); + } + public final void setMethodTypeReturn(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 10, value); + } + public final void setMethodTypeReturn(String value) { + _setPointerField(org.capnproto.Text.factory, 10, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initMethodTypeReturn(int size) { + return _initPointerField(org.capnproto.Text.factory, 10, size); + } + public final boolean hasMethodTypeParameters() { + return !_pointerFieldIsNull(11); + } + public final org.capnproto.TextList.Builder getMethodTypeParameters() { + return _getPointerField(org.capnproto.TextList.factory, 11, null, 0); + } + public final void setMethodTypeParameters(org.capnproto.TextList.Reader value) { + _setPointerField(org.capnproto.TextList.factory, 11, value); + } + public final org.capnproto.TextList.Builder initMethodTypeParameters(int size) { + return _initPointerField(org.capnproto.TextList.factory, 11, size); + } + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public boolean hasMethodTypeReturn() { + return !_pointerFieldIsNull(10); + } + public org.capnproto.Text.Reader getMethodTypeReturn() { + return _getPointerField(org.capnproto.Text.factory, 10, null, 0, 0); + } + + public final boolean hasMethodTypeParameters() { + return !_pointerFieldIsNull(11); + } + public final org.capnproto.TextList.Reader getMethodTypeParameters() { + return _getPointerField(org.capnproto.TextList.factory, 11, null, 0); + } + + } + + } + + + public static class CEntryPointCallStub { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)5,(short)13); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedAnalysisMethod.WrappedMethod.CEntryPointCallStub.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final int getOriginalMethodId() { + return _getIntField(8); + } + public final void setOriginalMethodId(int value) { + _setIntField(8, value); + } + + public final boolean getNotPublished() { + return _getBooleanField(192); + } + public final void setNotPublished(boolean value) { + _setBooleanField(192, value); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final int getOriginalMethodId() { + return _getIntField(8); + } + + public final boolean getNotPublished() { + return _getBooleanField(192); + } + + } + + } + + + public static class WrappedMember { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)5,(short)13); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedAnalysisMethod.WrappedMethod.WrappedMember.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public Which which() { + switch(_getShortField(16)) { + case 0 : return Which.REFLECTION_EXPAND_SIGNATURE; + case 1 : return Which.JAVA_CALL_VARIANT_WRAPPER; + default: return Which._NOT_IN_SCHEMA; + } + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean isReflectionExpandSignature() { + return which() == PersistedAnalysisMethod.WrappedMethod.WrappedMember.Which.REFLECTION_EXPAND_SIGNATURE; + } + public final org.capnproto.Void getReflectionExpandSignature() { + assert which() == PersistedAnalysisMethod.WrappedMethod.WrappedMember.Which.REFLECTION_EXPAND_SIGNATURE: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + public final void setReflectionExpandSignature(org.capnproto.Void value) { + _setShortField(16, (short)PersistedAnalysisMethod.WrappedMethod.WrappedMember.Which.REFLECTION_EXPAND_SIGNATURE.ordinal()); + } + + public final boolean isJavaCallVariantWrapper() { + return which() == PersistedAnalysisMethod.WrappedMethod.WrappedMember.Which.JAVA_CALL_VARIANT_WRAPPER; + } + public final org.capnproto.Void getJavaCallVariantWrapper() { + assert which() == PersistedAnalysisMethod.WrappedMethod.WrappedMember.Which.JAVA_CALL_VARIANT_WRAPPER: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + public final void setJavaCallVariantWrapper(org.capnproto.Void value) { + _setShortField(16, (short)PersistedAnalysisMethod.WrappedMethod.WrappedMember.Which.JAVA_CALL_VARIANT_WRAPPER.ordinal()); + } + + public final boolean hasName() { + return !_pointerFieldIsNull(10); + } + public final org.capnproto.Text.Builder getName() { + return _getPointerField(org.capnproto.Text.factory, 10, null, 0, 0); + } + public final void setName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 10, value); + } + public final void setName(String value) { + _setPointerField(org.capnproto.Text.factory, 10, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initName(int size) { + return _initPointerField(org.capnproto.Text.factory, 10, size); + } + public final boolean hasDeclaringClassName() { + return !_pointerFieldIsNull(11); + } + public final org.capnproto.Text.Builder getDeclaringClassName() { + return _getPointerField(org.capnproto.Text.factory, 11, null, 0, 0); + } + public final void setDeclaringClassName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 11, value); + } + public final void setDeclaringClassName(String value) { + _setPointerField(org.capnproto.Text.factory, 11, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initDeclaringClassName(int size) { + return _initPointerField(org.capnproto.Text.factory, 11, size); + } + public final boolean hasArgumentTypeNames() { + return !_pointerFieldIsNull(12); + } + public final org.capnproto.TextList.Builder getArgumentTypeNames() { + return _getPointerField(org.capnproto.TextList.factory, 12, null, 0); + } + public final void setArgumentTypeNames(org.capnproto.TextList.Reader value) { + _setPointerField(org.capnproto.TextList.factory, 12, value); + } + public final org.capnproto.TextList.Builder initArgumentTypeNames(int size) { + return _initPointerField(org.capnproto.TextList.factory, 12, size); + } + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public Which which() { + switch(_getShortField(16)) { + case 0 : return Which.REFLECTION_EXPAND_SIGNATURE; + case 1 : return Which.JAVA_CALL_VARIANT_WRAPPER; + default: return Which._NOT_IN_SCHEMA; + } + } + public final boolean isReflectionExpandSignature() { + return which() == PersistedAnalysisMethod.WrappedMethod.WrappedMember.Which.REFLECTION_EXPAND_SIGNATURE; + } + public final org.capnproto.Void getReflectionExpandSignature() { + assert which() == PersistedAnalysisMethod.WrappedMethod.WrappedMember.Which.REFLECTION_EXPAND_SIGNATURE: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + + public final boolean isJavaCallVariantWrapper() { + return which() == PersistedAnalysisMethod.WrappedMethod.WrappedMember.Which.JAVA_CALL_VARIANT_WRAPPER; + } + public final org.capnproto.Void getJavaCallVariantWrapper() { + assert which() == PersistedAnalysisMethod.WrappedMethod.WrappedMember.Which.JAVA_CALL_VARIANT_WRAPPER: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + + public boolean hasName() { + return !_pointerFieldIsNull(10); + } + public org.capnproto.Text.Reader getName() { + return _getPointerField(org.capnproto.Text.factory, 10, null, 0, 0); + } + + public boolean hasDeclaringClassName() { + return !_pointerFieldIsNull(11); + } + public org.capnproto.Text.Reader getDeclaringClassName() { + return _getPointerField(org.capnproto.Text.factory, 11, null, 0, 0); + } + + public final boolean hasArgumentTypeNames() { + return !_pointerFieldIsNull(12); + } + public final org.capnproto.TextList.Reader getArgumentTypeNames() { + return _getPointerField(org.capnproto.TextList.factory, 12, null, 0); + } + + } + + public enum Which { + REFLECTION_EXPAND_SIGNATURE, + JAVA_CALL_VARIANT_WRAPPER, + _NOT_IN_SCHEMA, + } + } + + + } + + + } + + + public static class PersistedAnalysisField { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)4,(short)3); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedAnalysisField.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final int getId() { + return _getIntField(0); + } + public final void setId(int value) { + _setIntField(0, value); + } + + public final boolean hasClassName() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.Text.Builder getClassName() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + public final void setClassName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 0, value); + } + public final void setClassName(String value) { + _setPointerField(org.capnproto.Text.factory, 0, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initClassName(int size) { + return _initPointerField(org.capnproto.Text.factory, 0, size); + } + public final int getDeclaringTypeId() { + return _getIntField(1); + } + public final void setDeclaringTypeId(int value) { + _setIntField(1, value); + } + + public final int getTypeId() { + return _getIntField(2); + } + public final void setTypeId(int value) { + _setIntField(2, value); + } + + public final int getPosition() { + return _getIntField(3); + } + public final void setPosition(int value) { + _setIntField(3, value); + } + + public final int getLocation() { + return _getIntField(4); + } + public final void setLocation(int value) { + _setIntField(4, value); + } + + public final int getModifiers() { + return _getIntField(5); + } + public final void setModifiers(int value) { + _setIntField(5, value); + } + + public final boolean getIsInternal() { + return _getBooleanField(192); + } + public final void setIsInternal(boolean value) { + _setBooleanField(192, value); + } + + public final boolean getIsAccessed() { + return _getBooleanField(193); + } + public final void setIsAccessed(boolean value) { + _setBooleanField(193, value); + } + + public final boolean getIsRead() { + return _getBooleanField(194); + } + public final void setIsRead(boolean value) { + _setBooleanField(194, value); + } + + public final boolean getIsWritten() { + return _getBooleanField(195); + } + public final void setIsWritten(boolean value) { + _setBooleanField(195, value); + } + + public final boolean getIsFolded() { + return _getBooleanField(196); + } + public final void setIsFolded(boolean value) { + _setBooleanField(196, value); + } + + public final boolean getIsStatic() { + return _getBooleanField(197); + } + public final void setIsStatic(boolean value) { + _setBooleanField(197, value); + } + + public final boolean getIsSynthetic() { + return _getBooleanField(198); + } + public final void setIsSynthetic(boolean value) { + _setBooleanField(198, value); + } + + public final boolean hasAnnotationList() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.StructList.Builder getAnnotationList() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.listFactory, 1, null, 0); + } + public final void setAnnotationList(org.capnproto.StructList.Reader value) { + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.listFactory, 1, value); + } + public final org.capnproto.StructList.Builder initAnnotationList(int size) { + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.listFactory, 1, size); + } + public final boolean hasName() { + return !_pointerFieldIsNull(2); + } + public final org.capnproto.Text.Builder getName() { + return _getPointerField(org.capnproto.Text.factory, 2, null, 0, 0); + } + public final void setName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 2, value); + } + public final void setName(String value) { + _setPointerField(org.capnproto.Text.factory, 2, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initName(int size) { + return _initPointerField(org.capnproto.Text.factory, 2, size); + } + public final int getFieldCheckIndex() { + return _getIntField(7); + } + public final void setFieldCheckIndex(int value) { + _setIntField(7, value); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final int getId() { + return _getIntField(0); + } + + public boolean hasClassName() { + return !_pointerFieldIsNull(0); + } + public org.capnproto.Text.Reader getClassName() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + + public final int getDeclaringTypeId() { + return _getIntField(1); + } + + public final int getTypeId() { + return _getIntField(2); + } + + public final int getPosition() { + return _getIntField(3); + } + + public final int getLocation() { + return _getIntField(4); + } + + public final int getModifiers() { + return _getIntField(5); + } + + public final boolean getIsInternal() { + return _getBooleanField(192); + } + + public final boolean getIsAccessed() { + return _getBooleanField(193); + } + + public final boolean getIsRead() { + return _getBooleanField(194); + } + + public final boolean getIsWritten() { + return _getBooleanField(195); + } + + public final boolean getIsFolded() { + return _getBooleanField(196); + } + + public final boolean getIsStatic() { + return _getBooleanField(197); + } + + public final boolean getIsSynthetic() { + return _getBooleanField(198); + } + + public final boolean hasAnnotationList() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.StructList.Reader getAnnotationList() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.listFactory, 1, null, 0); + } + + public boolean hasName() { + return !_pointerFieldIsNull(2); + } + public org.capnproto.Text.Reader getName() { + return _getPointerField(org.capnproto.Text.factory, 2, null, 0, 0); + } + + public final int getFieldCheckIndex() { + return _getIntField(7); + } + + } + + } + + + public static class CEntryPointLiteralReference { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)0,(short)3); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return CEntryPointLiteralReference.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean hasMethodName() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.Text.Builder getMethodName() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + public final void setMethodName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 0, value); + } + public final void setMethodName(String value) { + _setPointerField(org.capnproto.Text.factory, 0, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initMethodName(int size) { + return _initPointerField(org.capnproto.Text.factory, 0, size); + } + public final boolean hasDefiningClass() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.Text.Builder getDefiningClass() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + public final void setDefiningClass(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 1, value); + } + public final void setDefiningClass(String value) { + _setPointerField(org.capnproto.Text.factory, 1, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initDefiningClass(int size) { + return _initPointerField(org.capnproto.Text.factory, 1, size); + } + public final boolean hasParameterNames() { + return !_pointerFieldIsNull(2); + } + public final org.capnproto.TextList.Builder getParameterNames() { + return _getPointerField(org.capnproto.TextList.factory, 2, null, 0); + } + public final void setParameterNames(org.capnproto.TextList.Reader value) { + _setPointerField(org.capnproto.TextList.factory, 2, value); + } + public final org.capnproto.TextList.Builder initParameterNames(int size) { + return _initPointerField(org.capnproto.TextList.factory, 2, size); + } + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public boolean hasMethodName() { + return !_pointerFieldIsNull(0); + } + public org.capnproto.Text.Reader getMethodName() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + + public boolean hasDefiningClass() { + return !_pointerFieldIsNull(1); + } + public org.capnproto.Text.Reader getDefiningClass() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + + public final boolean hasParameterNames() { + return !_pointerFieldIsNull(2); + } + public final org.capnproto.TextList.Reader getParameterNames() { + return _getPointerField(org.capnproto.TextList.factory, 2, null, 0); + } + + } + + } + + + public static class ConstantReference { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)1,(short)1); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return ConstantReference.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public Which which() { + switch(_getShortField(2)) { + case 0 : return Which.OBJECT_CONSTANT; + case 1 : return Which.NULL_POINTER; + case 2 : return Which.NOT_MATERIALIZED; + case 3 : return Which.PRIMITIVE_VALUE; + case 4 : return Which.METHOD_POINTER; + case 5 : return Which.C_ENTRY_POINT_LITERAL_CODE_POINTER; + default: return Which._NOT_IN_SCHEMA; + } + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean isObjectConstant() { + return which() == ConstantReference.Which.OBJECT_CONSTANT; + } + public final ObjectConstant.Builder getObjectConstant() { + return new ConstantReference.ObjectConstant.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final ObjectConstant.Builder initObjectConstant() { + _setShortField(2, (short)ConstantReference.Which.OBJECT_CONSTANT.ordinal()); + _setIntField(0,0); + return new ConstantReference.ObjectConstant.Builder(segment, data, pointers, dataSize, pointerCount); + } + + public final boolean isNullPointer() { + return which() == ConstantReference.Which.NULL_POINTER; + } + public final org.capnproto.Void getNullPointer() { + assert which() == ConstantReference.Which.NULL_POINTER: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + public final void setNullPointer(org.capnproto.Void value) { + _setShortField(2, (short)ConstantReference.Which.NULL_POINTER.ordinal()); + } + + public final boolean isNotMaterialized() { + return which() == ConstantReference.Which.NOT_MATERIALIZED; + } + public final org.capnproto.Void getNotMaterialized() { + assert which() == ConstantReference.Which.NOT_MATERIALIZED: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + public final void setNotMaterialized(org.capnproto.Void value) { + _setShortField(2, (short)ConstantReference.Which.NOT_MATERIALIZED.ordinal()); + } + + public final boolean isPrimitiveValue() { + return which() == ConstantReference.Which.PRIMITIVE_VALUE; + } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.Builder getPrimitiveValue() { + assert which() == ConstantReference.Which.PRIMITIVE_VALUE: + "Must check which() before get()ing a union member."; + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.factory, 0, null, 0); + } + public final void setPrimitiveValue(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.Reader value) { + _setShortField(2, (short)ConstantReference.Which.PRIMITIVE_VALUE.ordinal()); + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.factory,0, value); + } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.Builder initPrimitiveValue() { + _setShortField(2, (short)ConstantReference.Which.PRIMITIVE_VALUE.ordinal()); + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.factory,0, 0); + } + public final boolean isMethodPointer() { + return which() == ConstantReference.Which.METHOD_POINTER; + } + public final MethodPointer.Builder getMethodPointer() { + return new ConstantReference.MethodPointer.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final MethodPointer.Builder initMethodPointer() { + _setShortField(2, (short)ConstantReference.Which.METHOD_POINTER.ordinal()); + _setIntField(0,0); + return new ConstantReference.MethodPointer.Builder(segment, data, pointers, dataSize, pointerCount); + } + + public final boolean isCEntryPointLiteralCodePointer() { + return which() == ConstantReference.Which.C_ENTRY_POINT_LITERAL_CODE_POINTER; + } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.CEntryPointLiteralReference.Builder getCEntryPointLiteralCodePointer() { + assert which() == ConstantReference.Which.C_ENTRY_POINT_LITERAL_CODE_POINTER: + "Must check which() before get()ing a union member."; + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.CEntryPointLiteralReference.factory, 0, null, 0); + } + public final void setCEntryPointLiteralCodePointer(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.CEntryPointLiteralReference.Reader value) { + _setShortField(2, (short)ConstantReference.Which.C_ENTRY_POINT_LITERAL_CODE_POINTER.ordinal()); + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.CEntryPointLiteralReference.factory,0, value); + } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.CEntryPointLiteralReference.Builder initCEntryPointLiteralCodePointer() { + _setShortField(2, (short)ConstantReference.Which.C_ENTRY_POINT_LITERAL_CODE_POINTER.ordinal()); + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.CEntryPointLiteralReference.factory,0, 0); + } + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public Which which() { + switch(_getShortField(2)) { + case 0 : return Which.OBJECT_CONSTANT; + case 1 : return Which.NULL_POINTER; + case 2 : return Which.NOT_MATERIALIZED; + case 3 : return Which.PRIMITIVE_VALUE; + case 4 : return Which.METHOD_POINTER; + case 5 : return Which.C_ENTRY_POINT_LITERAL_CODE_POINTER; + default: return Which._NOT_IN_SCHEMA; + } + } + public final boolean isObjectConstant() { + return which() == ConstantReference.Which.OBJECT_CONSTANT; + } + public ObjectConstant.Reader getObjectConstant() { + return new ConstantReference.ObjectConstant.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final boolean isNullPointer() { + return which() == ConstantReference.Which.NULL_POINTER; + } + public final org.capnproto.Void getNullPointer() { + assert which() == ConstantReference.Which.NULL_POINTER: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + + public final boolean isNotMaterialized() { + return which() == ConstantReference.Which.NOT_MATERIALIZED; + } + public final org.capnproto.Void getNotMaterialized() { + assert which() == ConstantReference.Which.NOT_MATERIALIZED: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + + public final boolean isPrimitiveValue() { + return which() == ConstantReference.Which.PRIMITIVE_VALUE; + } + public boolean hasPrimitiveValue() { + return !_pointerFieldIsNull(0); + } + public com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.Reader getPrimitiveValue() { + assert which() == ConstantReference.Which.PRIMITIVE_VALUE: + "Must check which() before get()ing a union member."; + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.factory,0,null, 0); + } + + public final boolean isMethodPointer() { + return which() == ConstantReference.Which.METHOD_POINTER; + } + public MethodPointer.Reader getMethodPointer() { + return new ConstantReference.MethodPointer.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final boolean isCEntryPointLiteralCodePointer() { + return which() == ConstantReference.Which.C_ENTRY_POINT_LITERAL_CODE_POINTER; + } + public boolean hasCEntryPointLiteralCodePointer() { + return !_pointerFieldIsNull(0); + } + public com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.CEntryPointLiteralReference.Reader getCEntryPointLiteralCodePointer() { + assert which() == ConstantReference.Which.C_ENTRY_POINT_LITERAL_CODE_POINTER: + "Must check which() before get()ing a union member."; + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.CEntryPointLiteralReference.factory,0,null, 0); + } + + } + + public enum Which { + OBJECT_CONSTANT, + NULL_POINTER, + NOT_MATERIALIZED, + PRIMITIVE_VALUE, + METHOD_POINTER, + C_ENTRY_POINT_LITERAL_CODE_POINTER, + _NOT_IN_SCHEMA, + } + public static class ObjectConstant { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)1,(short)1); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return ConstantReference.ObjectConstant.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final int getConstantId() { + return _getIntField(0); + } + public final void setConstantId(int value) { + _setIntField(0, value); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final int getConstantId() { + return _getIntField(0); + } + + } + + } + + + public static class MethodPointer { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)1,(short)1); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return ConstantReference.MethodPointer.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final int getMethodId() { + return _getIntField(0); + } + public final void setMethodId(int value) { + _setIntField(0, value); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final int getMethodId() { + return _getIntField(0); + } + + } + + } + + + } + + + public static class PersistedConstant { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)6,(short)3); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedConstant.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public Which which() { + switch(_getShortField(16)) { + case 0 : return Which.OBJECT; + case 1 : return Which.PRIMITIVE_DATA; + case 2 : return Which.RELOCATABLE; + default: return Which._NOT_IN_SCHEMA; + } + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final int getId() { + return _getIntField(0); + } + public final void setId(int value) { + _setIntField(0, value); + } + + public final int getTypeId() { + return _getIntField(1); + } + public final void setTypeId(int value) { + _setIntField(1, value); + } + + public final int getIdentityHashCode() { + return _getIntField(2); + } + public final void setIdentityHashCode(int value) { + _setIntField(2, value); + } + + public final boolean getIsSimulated() { + return _getBooleanField(96); + } + public final void setIsSimulated(boolean value) { + _setBooleanField(96, value); + } + + public final long getObjectOffset() { + return _getLongField(2); + } + public final void setObjectOffset(long value) { + _setLongField(2, value); + } + + public final boolean isObject() { + return which() == PersistedConstant.Which.OBJECT; + } + public final Object.Builder getObject() { + return new PersistedConstant.Object.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final Object.Builder initObject() { + _setShortField(16, (short)PersistedConstant.Which.OBJECT.ordinal()); + _setShortField(7,(short)0); + _setShortField(12,(short)0); + _setIntField(7,0); + _clearPointerField(0); + _clearPointerField(1); + _clearPointerField(2); + return new PersistedConstant.Object.Builder(segment, data, pointers, dataSize, pointerCount); + } + + public final boolean isPrimitiveData() { + return which() == PersistedConstant.Which.PRIMITIVE_DATA; + } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.Builder getPrimitiveData() { + assert which() == PersistedConstant.Which.PRIMITIVE_DATA: + "Must check which() before get()ing a union member."; + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.factory, 0, null, 0); + } + public final void setPrimitiveData(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.Reader value) { + _setShortField(16, (short)PersistedConstant.Which.PRIMITIVE_DATA.ordinal()); + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.factory,0, value); + } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.Builder initPrimitiveData() { + _setShortField(16, (short)PersistedConstant.Which.PRIMITIVE_DATA.ordinal()); + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.factory,0, 0); + } + public final boolean isRelocatable() { + return which() == PersistedConstant.Which.RELOCATABLE; + } + public final Relocatable.Builder getRelocatable() { + return new PersistedConstant.Relocatable.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final Relocatable.Builder initRelocatable() { + _setShortField(16, (short)PersistedConstant.Which.RELOCATABLE.ordinal()); + _clearPointerField(0); + return new PersistedConstant.Relocatable.Builder(segment, data, pointers, dataSize, pointerCount); + } + + public final int getParentConstantId() { + return _getIntField(9); + } + public final void setParentConstantId(int value) { + _setIntField(9, value); + } + + public final int getParentIndex() { + return _getIntField(10); + } + public final void setParentIndex(int value) { + _setIntField(10, value); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public Which which() { + switch(_getShortField(16)) { + case 0 : return Which.OBJECT; + case 1 : return Which.PRIMITIVE_DATA; + case 2 : return Which.RELOCATABLE; + default: return Which._NOT_IN_SCHEMA; + } + } + public final int getId() { + return _getIntField(0); + } + + public final int getTypeId() { + return _getIntField(1); + } + + public final int getIdentityHashCode() { + return _getIntField(2); + } + + public final boolean getIsSimulated() { + return _getBooleanField(96); + } + + public final long getObjectOffset() { + return _getLongField(2); + } + + public final boolean isObject() { + return which() == PersistedConstant.Which.OBJECT; + } + public Object.Reader getObject() { + return new PersistedConstant.Object.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final boolean isPrimitiveData() { + return which() == PersistedConstant.Which.PRIMITIVE_DATA; + } + public boolean hasPrimitiveData() { + return !_pointerFieldIsNull(0); + } + public com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.Reader getPrimitiveData() { + assert which() == PersistedConstant.Which.PRIMITIVE_DATA: + "Must check which() before get()ing a union member."; + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.factory,0,null, 0); + } + + public final boolean isRelocatable() { + return which() == PersistedConstant.Which.RELOCATABLE; + } + public Relocatable.Reader getRelocatable() { + return new PersistedConstant.Relocatable.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final int getParentConstantId() { + return _getIntField(9); + } + + public final int getParentIndex() { + return _getIntField(10); + } + + } + + public enum Which { + OBJECT, + PRIMITIVE_DATA, + RELOCATABLE, + _NOT_IN_SCHEMA, + } + public static class Object { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)6,(short)3); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedConstant.Object.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public Which which() { + switch(_getShortField(7)) { + case 0 : return Which.INSTANCE; + case 1 : return Which.OBJECT_ARRAY; + default: return Which._NOT_IN_SCHEMA; + } + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean hasData() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.StructList.Builder getData() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference.listFactory, 0, null, 0); + } + public final void setData(org.capnproto.StructList.Reader value) { + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference.listFactory, 0, value); + } + public final org.capnproto.StructList.Builder initData(int size) { + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference.listFactory, 0, size); + } + public final boolean isInstance() { + return which() == PersistedConstant.Object.Which.INSTANCE; + } + public final org.capnproto.Void getInstance() { + assert which() == PersistedConstant.Object.Which.INSTANCE: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + public final void setInstance(org.capnproto.Void value) { + _setShortField(7, (short)PersistedConstant.Object.Which.INSTANCE.ordinal()); + } + + public final boolean isObjectArray() { + return which() == PersistedConstant.Object.Which.OBJECT_ARRAY; + } + public final org.capnproto.Void getObjectArray() { + assert which() == PersistedConstant.Object.Which.OBJECT_ARRAY: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + public final void setObjectArray(org.capnproto.Void value) { + _setShortField(7, (short)PersistedConstant.Object.Which.OBJECT_ARRAY.ordinal()); + } + + public final Relinking.Builder getRelinking() { + return new PersistedConstant.Object.Relinking.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final Relinking.Builder initRelinking() { + _setShortField(12,(short)0); + _setIntField(7,0); + _clearPointerField(1); + _clearPointerField(2); + return new PersistedConstant.Object.Relinking.Builder(segment, data, pointers, dataSize, pointerCount); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public Which which() { + switch(_getShortField(7)) { + case 0 : return Which.INSTANCE; + case 1 : return Which.OBJECT_ARRAY; + default: return Which._NOT_IN_SCHEMA; + } + } + public final boolean hasData() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.StructList.Reader getData() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference.listFactory, 0, null, 0); + } + + public final boolean isInstance() { + return which() == PersistedConstant.Object.Which.INSTANCE; + } + public final org.capnproto.Void getInstance() { + assert which() == PersistedConstant.Object.Which.INSTANCE: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + + public final boolean isObjectArray() { + return which() == PersistedConstant.Object.Which.OBJECT_ARRAY; + } + public final org.capnproto.Void getObjectArray() { + assert which() == PersistedConstant.Object.Which.OBJECT_ARRAY: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + + public Relinking.Reader getRelinking() { + return new PersistedConstant.Object.Relinking.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + } + + public enum Which { + INSTANCE, + OBJECT_ARRAY, + _NOT_IN_SCHEMA, + } + public static class Relinking { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)6,(short)3); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedConstant.Object.Relinking.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public Which which() { + switch(_getShortField(12)) { + case 0 : return Which.NOT_RELINKED; + case 1 : return Which.STRING_CONSTANT; + case 2 : return Which.ENUM_CONSTANT; + case 3 : return Which.CLASS_CONSTANT; + default: return Which._NOT_IN_SCHEMA; + } + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean isNotRelinked() { + return which() == PersistedConstant.Object.Relinking.Which.NOT_RELINKED; + } + public final org.capnproto.Void getNotRelinked() { + assert which() == PersistedConstant.Object.Relinking.Which.NOT_RELINKED: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + public final void setNotRelinked(org.capnproto.Void value) { + _setShortField(12, (short)PersistedConstant.Object.Relinking.Which.NOT_RELINKED.ordinal()); + } + + public final boolean isStringConstant() { + return which() == PersistedConstant.Object.Relinking.Which.STRING_CONSTANT; + } + public final StringConstant.Builder getStringConstant() { + return new PersistedConstant.Object.Relinking.StringConstant.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final StringConstant.Builder initStringConstant() { + _setShortField(12, (short)PersistedConstant.Object.Relinking.Which.STRING_CONSTANT.ordinal()); + _clearPointerField(1); + return new PersistedConstant.Object.Relinking.StringConstant.Builder(segment, data, pointers, dataSize, pointerCount); + } + + public final boolean isEnumConstant() { + return which() == PersistedConstant.Object.Relinking.Which.ENUM_CONSTANT; + } + public final EnumConstant.Builder getEnumConstant() { + return new PersistedConstant.Object.Relinking.EnumConstant.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final EnumConstant.Builder initEnumConstant() { + _setShortField(12, (short)PersistedConstant.Object.Relinking.Which.ENUM_CONSTANT.ordinal()); + _clearPointerField(1); + _clearPointerField(2); + return new PersistedConstant.Object.Relinking.EnumConstant.Builder(segment, data, pointers, dataSize, pointerCount); + } + + public final boolean isClassConstant() { + return which() == PersistedConstant.Object.Relinking.Which.CLASS_CONSTANT; + } + public final ClassConstant.Builder getClassConstant() { + return new PersistedConstant.Object.Relinking.ClassConstant.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final ClassConstant.Builder initClassConstant() { + _setShortField(12, (short)PersistedConstant.Object.Relinking.Which.CLASS_CONSTANT.ordinal()); + _setIntField(7,0); + return new PersistedConstant.Object.Relinking.ClassConstant.Builder(segment, data, pointers, dataSize, pointerCount); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public Which which() { + switch(_getShortField(12)) { + case 0 : return Which.NOT_RELINKED; + case 1 : return Which.STRING_CONSTANT; + case 2 : return Which.ENUM_CONSTANT; + case 3 : return Which.CLASS_CONSTANT; + default: return Which._NOT_IN_SCHEMA; + } + } + public final boolean isNotRelinked() { + return which() == PersistedConstant.Object.Relinking.Which.NOT_RELINKED; + } + public final org.capnproto.Void getNotRelinked() { + assert which() == PersistedConstant.Object.Relinking.Which.NOT_RELINKED: + "Must check which() before get()ing a union member."; + return org.capnproto.Void.VOID; + } + + public final boolean isStringConstant() { + return which() == PersistedConstant.Object.Relinking.Which.STRING_CONSTANT; + } + public StringConstant.Reader getStringConstant() { + return new PersistedConstant.Object.Relinking.StringConstant.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final boolean isEnumConstant() { + return which() == PersistedConstant.Object.Relinking.Which.ENUM_CONSTANT; + } + public EnumConstant.Reader getEnumConstant() { + return new PersistedConstant.Object.Relinking.EnumConstant.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final boolean isClassConstant() { + return which() == PersistedConstant.Object.Relinking.Which.CLASS_CONSTANT; + } + public ClassConstant.Reader getClassConstant() { + return new PersistedConstant.Object.Relinking.ClassConstant.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + } + + public enum Which { + NOT_RELINKED, + STRING_CONSTANT, + ENUM_CONSTANT, + CLASS_CONSTANT, + _NOT_IN_SCHEMA, + } + public static class StringConstant { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)6,(short)3); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedConstant.Object.Relinking.StringConstant.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean hasValue() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.Text.Builder getValue() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + public final void setValue(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 1, value); + } + public final void setValue(String value) { + _setPointerField(org.capnproto.Text.factory, 1, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initValue(int size) { + return _initPointerField(org.capnproto.Text.factory, 1, size); + } + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public boolean hasValue() { + return !_pointerFieldIsNull(1); + } + public org.capnproto.Text.Reader getValue() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + + } + + } + + + public static class EnumConstant { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)6,(short)3); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedConstant.Object.Relinking.EnumConstant.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean hasEnumClass() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.Text.Builder getEnumClass() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + public final void setEnumClass(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 1, value); + } + public final void setEnumClass(String value) { + _setPointerField(org.capnproto.Text.factory, 1, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initEnumClass(int size) { + return _initPointerField(org.capnproto.Text.factory, 1, size); + } + public final boolean hasEnumName() { + return !_pointerFieldIsNull(2); + } + public final org.capnproto.Text.Builder getEnumName() { + return _getPointerField(org.capnproto.Text.factory, 2, null, 0, 0); + } + public final void setEnumName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 2, value); + } + public final void setEnumName(String value) { + _setPointerField(org.capnproto.Text.factory, 2, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initEnumName(int size) { + return _initPointerField(org.capnproto.Text.factory, 2, size); + } + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public boolean hasEnumClass() { + return !_pointerFieldIsNull(1); + } + public org.capnproto.Text.Reader getEnumClass() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + + public boolean hasEnumName() { + return !_pointerFieldIsNull(2); + } + public org.capnproto.Text.Reader getEnumName() { + return _getPointerField(org.capnproto.Text.factory, 2, null, 0, 0); + } + + } + + } + + + public static class ClassConstant { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)6,(short)3); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedConstant.Object.Relinking.ClassConstant.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final int getTypeId() { + return _getIntField(7); + } + public final void setTypeId(int value) { + _setIntField(7, value); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final int getTypeId() { + return _getIntField(7); + } + + } + + } + + + } + + + } + + + public static class Relocatable { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)6,(short)3); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PersistedConstant.Relocatable.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean hasKey() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.Text.Builder getKey() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + public final void setKey(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 0, value); + } + public final void setKey(String value) { + _setPointerField(org.capnproto.Text.factory, 0, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initKey(int size) { + return _initPointerField(org.capnproto.Text.factory, 0, size); + } + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public boolean hasKey() { + return !_pointerFieldIsNull(0); + } + public org.capnproto.Text.Reader getKey() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + + } + + } + + + } + + + public static class KeyStoreEntry { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)2,(short)2); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return KeyStoreEntry.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean hasKey() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.Text.Builder getKey() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + public final void setKey(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 0, value); + } + public final void setKey(String value) { + _setPointerField(org.capnproto.Text.factory, 0, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initKey(int size) { + return _initPointerField(org.capnproto.Text.factory, 0, size); + } + public final Value.Builder getValue() { + return new KeyStoreEntry.Value.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final Value.Builder initValue() { + _setIntField(0,0); + _setShortField(2,(short)0); + _setLongField(1,0L); + _clearPointerField(1); + return new KeyStoreEntry.Value.Builder(segment, data, pointers, dataSize, pointerCount); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public boolean hasKey() { + return !_pointerFieldIsNull(0); + } + public org.capnproto.Text.Reader getKey() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + + public Value.Reader getValue() { + return new KeyStoreEntry.Value.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + } + + public static class Value { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)2,(short)2); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return KeyStoreEntry.Value.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public Which which() { + switch(_getShortField(2)) { + case 0 : return Which.I; + case 1 : return Which.IL; + case 2 : return Which.J; + case 3 : return Which.STR; + case 4 : return Which.STRL; + case 5 : return Which.ZL; + default: return Which._NOT_IN_SCHEMA; + } + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean isI() { + return which() == KeyStoreEntry.Value.Which.I; + } + public final int getI() { + assert which() == KeyStoreEntry.Value.Which.I: + "Must check which() before get()ing a union member."; + return _getIntField(0); + } + public final void setI(int value) { + _setShortField(2, (short)KeyStoreEntry.Value.Which.I.ordinal()); + _setIntField(0, value); + } + + public final boolean isIl() { + return which() == KeyStoreEntry.Value.Which.IL; + } + public final boolean hasIl() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.PrimitiveList.Int.Builder getIl() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 1, null, 0); + } + public final void setIl(org.capnproto.PrimitiveList.Int.Reader value) { + _setShortField(2, (short)KeyStoreEntry.Value.Which.IL.ordinal()); + _setPointerField(org.capnproto.PrimitiveList.Int.factory, 1, value); + } + public final org.capnproto.PrimitiveList.Int.Builder initIl(int size) { + _setShortField(2, (short)KeyStoreEntry.Value.Which.IL.ordinal()); + return _initPointerField(org.capnproto.PrimitiveList.Int.factory, 1, size); + } + public final boolean isJ() { + return which() == KeyStoreEntry.Value.Which.J; + } + public final long getJ() { + assert which() == KeyStoreEntry.Value.Which.J: + "Must check which() before get()ing a union member."; + return _getLongField(1); + } + public final void setJ(long value) { + _setShortField(2, (short)KeyStoreEntry.Value.Which.J.ordinal()); + _setLongField(1, value); + } + + public final boolean isStr() { + return which() == KeyStoreEntry.Value.Which.STR; + } + public final boolean hasStr() { + if (which() != KeyStoreEntry.Value.Which.STR) return false; + return !_pointerFieldIsNull(1); + } + public final org.capnproto.Text.Builder getStr() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + public final void setStr(org.capnproto.Text.Reader value) { + _setShortField(2, (short)KeyStoreEntry.Value.Which.STR.ordinal()); + _setPointerField(org.capnproto.Text.factory, 1, value); + } + public final void setStr(String value) { + _setShortField(2, (short)KeyStoreEntry.Value.Which.STR.ordinal()); + _setPointerField(org.capnproto.Text.factory, 1, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initStr(int size) { + _setShortField(2, (short)KeyStoreEntry.Value.Which.STR.ordinal()); + return _initPointerField(org.capnproto.Text.factory, 1, size); + } + public final boolean isStrl() { + return which() == KeyStoreEntry.Value.Which.STRL; + } + public final boolean hasStrl() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.TextList.Builder getStrl() { + return _getPointerField(org.capnproto.TextList.factory, 1, null, 0); + } + public final void setStrl(org.capnproto.TextList.Reader value) { + _setShortField(2, (short)KeyStoreEntry.Value.Which.STRL.ordinal()); + _setPointerField(org.capnproto.TextList.factory, 1, value); + } + public final org.capnproto.TextList.Builder initStrl(int size) { + _setShortField(2, (short)KeyStoreEntry.Value.Which.STRL.ordinal()); + return _initPointerField(org.capnproto.TextList.factory, 1, size); + } + public final boolean isZl() { + return which() == KeyStoreEntry.Value.Which.ZL; + } + public final boolean hasZl() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.PrimitiveList.Boolean.Builder getZl() { + return _getPointerField(org.capnproto.PrimitiveList.Boolean.factory, 1, null, 0); + } + public final void setZl(org.capnproto.PrimitiveList.Boolean.Reader value) { + _setShortField(2, (short)KeyStoreEntry.Value.Which.ZL.ordinal()); + _setPointerField(org.capnproto.PrimitiveList.Boolean.factory, 1, value); + } + public final org.capnproto.PrimitiveList.Boolean.Builder initZl(int size) { + _setShortField(2, (short)KeyStoreEntry.Value.Which.ZL.ordinal()); + return _initPointerField(org.capnproto.PrimitiveList.Boolean.factory, 1, size); + } + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public Which which() { + switch(_getShortField(2)) { + case 0 : return Which.I; + case 1 : return Which.IL; + case 2 : return Which.J; + case 3 : return Which.STR; + case 4 : return Which.STRL; + case 5 : return Which.ZL; + default: return Which._NOT_IN_SCHEMA; + } + } + public final boolean isI() { + return which() == KeyStoreEntry.Value.Which.I; + } + public final int getI() { + assert which() == KeyStoreEntry.Value.Which.I: + "Must check which() before get()ing a union member."; + return _getIntField(0); + } + + public final boolean isIl() { + return which() == KeyStoreEntry.Value.Which.IL; + } + public final boolean hasIl() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.PrimitiveList.Int.Reader getIl() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 1, null, 0); + } + + public final boolean isJ() { + return which() == KeyStoreEntry.Value.Which.J; + } + public final long getJ() { + assert which() == KeyStoreEntry.Value.Which.J: + "Must check which() before get()ing a union member."; + return _getLongField(1); + } + + public final boolean isStr() { + return which() == KeyStoreEntry.Value.Which.STR; + } + public boolean hasStr() { + if (which() != KeyStoreEntry.Value.Which.STR) return false; + return !_pointerFieldIsNull(1); + } + public org.capnproto.Text.Reader getStr() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + + public final boolean isStrl() { + return which() == KeyStoreEntry.Value.Which.STRL; + } + public final boolean hasStrl() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.TextList.Reader getStrl() { + return _getPointerField(org.capnproto.TextList.factory, 1, null, 0); + } + + public final boolean isZl() { + return which() == KeyStoreEntry.Value.Which.ZL; + } + public final boolean hasZl() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.PrimitiveList.Boolean.Reader getZl() { + return _getPointerField(org.capnproto.PrimitiveList.Boolean.factory, 1, null, 0); + } + + } + + public enum Which { + I, + IL, + J, + STR, + STRL, + ZL, + _NOT_IN_SCHEMA, + } + } + + + } + + + public static class ImageSingletonKey { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)1,(short)1); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return ImageSingletonKey.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean hasKeyClassName() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.Text.Builder getKeyClassName() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + public final void setKeyClassName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 0, value); + } + public final void setKeyClassName(String value) { + _setPointerField(org.capnproto.Text.factory, 0, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initKeyClassName(int size) { + return _initPointerField(org.capnproto.Text.factory, 0, size); + } + public final int getPersistFlag() { + return _getIntField(0); + } + public final void setPersistFlag(int value) { + _setIntField(0, value); + } + + public final int getObjectId() { + return _getIntField(1); + } + public final void setObjectId(int value) { + _setIntField(1, value); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public boolean hasKeyClassName() { + return !_pointerFieldIsNull(0); + } + public org.capnproto.Text.Reader getKeyClassName() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + + public final int getPersistFlag() { + return _getIntField(0); + } + + public final int getObjectId() { + return _getIntField(1); + } + + } + + } + + + public static class ImageSingletonObject { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)1,(short)2); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return ImageSingletonObject.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final int getId() { + return _getIntField(0); + } + public final void setId(int value) { + _setIntField(0, value); + } + + public final boolean hasClassName() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.Text.Builder getClassName() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + public final void setClassName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 0, value); + } + public final void setClassName(String value) { + _setPointerField(org.capnproto.Text.factory, 0, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initClassName(int size) { + return _initPointerField(org.capnproto.Text.factory, 0, size); + } + public final boolean hasStore() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.StructList.Builder getStore() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.KeyStoreEntry.listFactory, 1, null, 0); + } + public final void setStore(org.capnproto.StructList.Reader value) { + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.KeyStoreEntry.listFactory, 1, value); + } + public final org.capnproto.StructList.Builder initStore(int size) { + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.KeyStoreEntry.listFactory, 1, size); + } + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final int getId() { + return _getIntField(0); + } + + public boolean hasClassName() { + return !_pointerFieldIsNull(0); + } + public org.capnproto.Text.Reader getClassName() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + + public final boolean hasStore() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.StructList.Reader getStore() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.KeyStoreEntry.listFactory, 1, null, 0); + } + + } + + } + + + public static class Annotation { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)0,(short)2); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return Annotation.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean hasTypeName() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.Text.Builder getTypeName() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + public final void setTypeName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 0, value); + } + public final void setTypeName(String value) { + _setPointerField(org.capnproto.Text.factory, 0, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initTypeName(int size) { + return _initPointerField(org.capnproto.Text.factory, 0, size); + } + public final boolean hasValues() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.StructList.Builder getValues() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.AnnotationValue.listFactory, 1, null, 0); + } + public final void setValues(org.capnproto.StructList.Reader value) { + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.AnnotationValue.listFactory, 1, value); + } + public final org.capnproto.StructList.Builder initValues(int size) { + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.AnnotationValue.listFactory, 1, size); + } + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public boolean hasTypeName() { + return !_pointerFieldIsNull(0); + } + public org.capnproto.Text.Reader getTypeName() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + + public final boolean hasValues() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.StructList.Reader getValues() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.AnnotationValue.listFactory, 1, null, 0); + } + + } + + } + + + public static class AnnotationValue { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)1,(short)3); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return AnnotationValue.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public Which which() { + switch(_getShortField(0)) { + case 0 : return Which.STRING; + case 1 : return Which.PRIMITIVE; + case 2 : return Which.PRIMITIVE_ARRAY; + case 3 : return Which.ENUM; + case 4 : return Which.CLASS_NAME; + case 5 : return Which.ANNOTATION; + case 6 : return Which.MEMBERS; + default: return Which._NOT_IN_SCHEMA; + } + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean hasName() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.Text.Builder getName() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + public final void setName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 0, value); + } + public final void setName(String value) { + _setPointerField(org.capnproto.Text.factory, 0, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initName(int size) { + return _initPointerField(org.capnproto.Text.factory, 0, size); + } + public final boolean isString() { + return which() == AnnotationValue.Which.STRING; + } + public final boolean hasString() { + if (which() != AnnotationValue.Which.STRING) return false; + return !_pointerFieldIsNull(1); + } + public final org.capnproto.Text.Builder getString() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + public final void setString(org.capnproto.Text.Reader value) { + _setShortField(0, (short)AnnotationValue.Which.STRING.ordinal()); + _setPointerField(org.capnproto.Text.factory, 1, value); + } + public final void setString(String value) { + _setShortField(0, (short)AnnotationValue.Which.STRING.ordinal()); + _setPointerField(org.capnproto.Text.factory, 1, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initString(int size) { + _setShortField(0, (short)AnnotationValue.Which.STRING.ordinal()); + return _initPointerField(org.capnproto.Text.factory, 1, size); + } + public final boolean isPrimitive() { + return which() == AnnotationValue.Which.PRIMITIVE; + } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.Builder getPrimitive() { + assert which() == AnnotationValue.Which.PRIMITIVE: + "Must check which() before get()ing a union member."; + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.factory, 1, null, 0); + } + public final void setPrimitive(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.Reader value) { + _setShortField(0, (short)AnnotationValue.Which.PRIMITIVE.ordinal()); + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.factory,1, value); + } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.Builder initPrimitive() { + _setShortField(0, (short)AnnotationValue.Which.PRIMITIVE.ordinal()); + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.factory,1, 0); + } + public final boolean isPrimitiveArray() { + return which() == AnnotationValue.Which.PRIMITIVE_ARRAY; + } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.Builder getPrimitiveArray() { + assert which() == AnnotationValue.Which.PRIMITIVE_ARRAY: + "Must check which() before get()ing a union member."; + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.factory, 1, null, 0); + } + public final void setPrimitiveArray(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.Reader value) { + _setShortField(0, (short)AnnotationValue.Which.PRIMITIVE_ARRAY.ordinal()); + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.factory,1, value); + } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.Builder initPrimitiveArray() { + _setShortField(0, (short)AnnotationValue.Which.PRIMITIVE_ARRAY.ordinal()); + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.factory,1, 0); + } + public final boolean isEnum() { + return which() == AnnotationValue.Which.ENUM; + } + public final Enum.Builder getEnum() { + return new AnnotationValue.Enum.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final Enum.Builder initEnum() { + _setShortField(0, (short)AnnotationValue.Which.ENUM.ordinal()); + _clearPointerField(1); + _clearPointerField(2); + return new AnnotationValue.Enum.Builder(segment, data, pointers, dataSize, pointerCount); + } + + public final boolean isClassName() { + return which() == AnnotationValue.Which.CLASS_NAME; + } + public final boolean hasClassName() { + if (which() != AnnotationValue.Which.CLASS_NAME) return false; + return !_pointerFieldIsNull(1); + } + public final org.capnproto.Text.Builder getClassName() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + public final void setClassName(org.capnproto.Text.Reader value) { + _setShortField(0, (short)AnnotationValue.Which.CLASS_NAME.ordinal()); + _setPointerField(org.capnproto.Text.factory, 1, value); + } + public final void setClassName(String value) { + _setShortField(0, (short)AnnotationValue.Which.CLASS_NAME.ordinal()); + _setPointerField(org.capnproto.Text.factory, 1, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initClassName(int size) { + _setShortField(0, (short)AnnotationValue.Which.CLASS_NAME.ordinal()); + return _initPointerField(org.capnproto.Text.factory, 1, size); + } + public final boolean isAnnotation() { + return which() == AnnotationValue.Which.ANNOTATION; + } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.Builder getAnnotation() { + assert which() == AnnotationValue.Which.ANNOTATION: + "Must check which() before get()ing a union member."; + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.factory, 1, null, 0); + } + public final void setAnnotation(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.Reader value) { + _setShortField(0, (short)AnnotationValue.Which.ANNOTATION.ordinal()); + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.factory,1, value); + } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.Builder initAnnotation() { + _setShortField(0, (short)AnnotationValue.Which.ANNOTATION.ordinal()); + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.factory,1, 0); + } + public final boolean isMembers() { + return which() == AnnotationValue.Which.MEMBERS; + } + public final Members.Builder getMembers() { + return new AnnotationValue.Members.Builder(segment, data, pointers, dataSize, pointerCount); + } + public final Members.Builder initMembers() { + _setShortField(0, (short)AnnotationValue.Which.MEMBERS.ordinal()); + _clearPointerField(1); + _clearPointerField(2); + return new AnnotationValue.Members.Builder(segment, data, pointers, dataSize, pointerCount); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public Which which() { + switch(_getShortField(0)) { + case 0 : return Which.STRING; + case 1 : return Which.PRIMITIVE; + case 2 : return Which.PRIMITIVE_ARRAY; + case 3 : return Which.ENUM; + case 4 : return Which.CLASS_NAME; + case 5 : return Which.ANNOTATION; + case 6 : return Which.MEMBERS; + default: return Which._NOT_IN_SCHEMA; + } + } + public boolean hasName() { + return !_pointerFieldIsNull(0); + } + public org.capnproto.Text.Reader getName() { + return _getPointerField(org.capnproto.Text.factory, 0, null, 0, 0); + } + + public final boolean isString() { + return which() == AnnotationValue.Which.STRING; + } + public boolean hasString() { + if (which() != AnnotationValue.Which.STRING) return false; + return !_pointerFieldIsNull(1); + } + public org.capnproto.Text.Reader getString() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + + public final boolean isPrimitive() { + return which() == AnnotationValue.Which.PRIMITIVE; + } + public boolean hasPrimitive() { + return !_pointerFieldIsNull(1); + } + public com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.Reader getPrimitive() { + assert which() == AnnotationValue.Which.PRIMITIVE: + "Must check which() before get()ing a union member."; + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveValue.factory,1,null, 0); + } + + public final boolean isPrimitiveArray() { + return which() == AnnotationValue.Which.PRIMITIVE_ARRAY; + } + public boolean hasPrimitiveArray() { + return !_pointerFieldIsNull(1); + } + public com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.Reader getPrimitiveArray() { + assert which() == AnnotationValue.Which.PRIMITIVE_ARRAY: + "Must check which() before get()ing a union member."; + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PrimitiveArray.factory,1,null, 0); + } + + public final boolean isEnum() { + return which() == AnnotationValue.Which.ENUM; + } + public Enum.Reader getEnum() { + return new AnnotationValue.Enum.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final boolean isClassName() { + return which() == AnnotationValue.Which.CLASS_NAME; + } + public boolean hasClassName() { + if (which() != AnnotationValue.Which.CLASS_NAME) return false; + return !_pointerFieldIsNull(1); + } + public org.capnproto.Text.Reader getClassName() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + + public final boolean isAnnotation() { + return which() == AnnotationValue.Which.ANNOTATION; + } + public boolean hasAnnotation() { + return !_pointerFieldIsNull(1); + } + public com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.Reader getAnnotation() { + assert which() == AnnotationValue.Which.ANNOTATION: + "Must check which() before get()ing a union member."; + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.Annotation.factory,1,null, 0); + } + + public final boolean isMembers() { + return which() == AnnotationValue.Which.MEMBERS; + } + public Members.Reader getMembers() { + return new AnnotationValue.Members.Reader(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + } + + public enum Which { + STRING, + PRIMITIVE, + PRIMITIVE_ARRAY, + ENUM, + CLASS_NAME, + ANNOTATION, + MEMBERS, + _NOT_IN_SCHEMA, + } + public static class Enum { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)1,(short)3); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return AnnotationValue.Enum.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean hasClassName() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.Text.Builder getClassName() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + public final void setClassName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 1, value); + } + public final void setClassName(String value) { + _setPointerField(org.capnproto.Text.factory, 1, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initClassName(int size) { + return _initPointerField(org.capnproto.Text.factory, 1, size); + } + public final boolean hasName() { + return !_pointerFieldIsNull(2); + } + public final org.capnproto.Text.Builder getName() { + return _getPointerField(org.capnproto.Text.factory, 2, null, 0, 0); + } + public final void setName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 2, value); + } + public final void setName(String value) { + _setPointerField(org.capnproto.Text.factory, 2, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initName(int size) { + return _initPointerField(org.capnproto.Text.factory, 2, size); + } + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public boolean hasClassName() { + return !_pointerFieldIsNull(1); + } + public org.capnproto.Text.Reader getClassName() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + + public boolean hasName() { + return !_pointerFieldIsNull(2); + } + public org.capnproto.Text.Reader getName() { + return _getPointerField(org.capnproto.Text.factory, 2, null, 0, 0); + } + + } + + } + + + public static class Members { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)1,(short)3); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return AnnotationValue.Members.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean hasClassName() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.Text.Builder getClassName() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + public final void setClassName(org.capnproto.Text.Reader value) { + _setPointerField(org.capnproto.Text.factory, 1, value); + } + public final void setClassName(String value) { + _setPointerField(org.capnproto.Text.factory, 1, new org.capnproto.Text.Reader(value)); + } + public final org.capnproto.Text.Builder initClassName(int size) { + return _initPointerField(org.capnproto.Text.factory, 1, size); + } + public final boolean hasMemberValues() { + return !_pointerFieldIsNull(2); + } + public final org.capnproto.StructList.Builder getMemberValues() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.AnnotationValue.listFactory, 2, null, 0); + } + public final void setMemberValues(org.capnproto.StructList.Reader value) { + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.AnnotationValue.listFactory, 2, value); + } + public final org.capnproto.StructList.Builder initMemberValues(int size) { + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.AnnotationValue.listFactory, 2, size); + } + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public boolean hasClassName() { + return !_pointerFieldIsNull(1); + } + public org.capnproto.Text.Reader getClassName() { + return _getPointerField(org.capnproto.Text.factory, 1, null, 0, 0); + } + + public final boolean hasMemberValues() { + return !_pointerFieldIsNull(2); + } + public final org.capnproto.StructList.Reader getMemberValues() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.AnnotationValue.listFactory, 2, null, 0); + } + + } + + } + + + } + + + public static class SharedLayerSnapshot { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)5,(short)7); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return SharedLayerSnapshot.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final int getNextTypeId() { + return _getIntField(0); + } + public final void setNextTypeId(int value) { + _setIntField(0, value); + } + + public final int getNextMethodId() { + return _getIntField(1); + } + public final void setNextMethodId(int value) { + _setIntField(1, value); + } + + public final int getNextFieldId() { + return _getIntField(2); + } + public final void setNextFieldId(int value) { + _setIntField(2, value); + } + + public final int getNextConstantId() { + return _getIntField(3); + } + public final void setNextConstantId(int value) { + _setIntField(3, value); + } + + public final int getStaticPrimitiveFieldsConstantId() { + return _getIntField(4); + } + public final void setStaticPrimitiveFieldsConstantId(int value) { + _setIntField(4, value); + } + + public final int getStaticObjectFieldsConstantId() { + return _getIntField(5); + } + public final void setStaticObjectFieldsConstantId(int value) { + _setIntField(5, value); + } + + public final long getImageHeapSize() { + return _getLongField(3); + } + public final void setImageHeapSize(long value) { + _setLongField(3, value); + } + + public final boolean hasConstantsToRelink() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Int.Builder getConstantsToRelink() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 0, null, 0); + } + public final void setConstantsToRelink(org.capnproto.PrimitiveList.Int.Reader value) { + _setPointerField(org.capnproto.PrimitiveList.Int.factory, 0, value); + } + public final org.capnproto.PrimitiveList.Int.Builder initConstantsToRelink(int size) { + return _initPointerField(org.capnproto.PrimitiveList.Int.factory, 0, size); + } + public final boolean hasTypes() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.StructList.Builder getTypes() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisType.listFactory, 1, null, 0); + } + public final void setTypes(org.capnproto.StructList.Reader value) { + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisType.listFactory, 1, value); + } + public final org.capnproto.StructList.Builder initTypes(int size) { + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisType.listFactory, 1, size); + } + public final boolean hasMethods() { + return !_pointerFieldIsNull(2); + } + public final org.capnproto.StructList.Builder getMethods() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisMethod.listFactory, 2, null, 0); + } + public final void setMethods(org.capnproto.StructList.Reader value) { + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisMethod.listFactory, 2, value); + } + public final org.capnproto.StructList.Builder initMethods(int size) { + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisMethod.listFactory, 2, size); + } + public final boolean hasConstants() { + return !_pointerFieldIsNull(3); + } + public final org.capnproto.StructList.Builder getConstants() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedConstant.listFactory, 3, null, 0); + } + public final void setConstants(org.capnproto.StructList.Reader value) { + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedConstant.listFactory, 3, value); + } + public final org.capnproto.StructList.Builder initConstants(int size) { + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedConstant.listFactory, 3, size); + } + public final boolean hasSingletonKeys() { + return !_pointerFieldIsNull(4); + } + public final org.capnproto.StructList.Builder getSingletonKeys() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ImageSingletonKey.listFactory, 4, null, 0); + } + public final void setSingletonKeys(org.capnproto.StructList.Reader value) { + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ImageSingletonKey.listFactory, 4, value); + } + public final org.capnproto.StructList.Builder initSingletonKeys(int size) { + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ImageSingletonKey.listFactory, 4, size); + } + public final boolean hasSingletonObjects() { + return !_pointerFieldIsNull(5); + } + public final org.capnproto.StructList.Builder getSingletonObjects() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ImageSingletonObject.listFactory, 5, null, 0); + } + public final void setSingletonObjects(org.capnproto.StructList.Reader value) { + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ImageSingletonObject.listFactory, 5, value); + } + public final org.capnproto.StructList.Builder initSingletonObjects(int size) { + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ImageSingletonObject.listFactory, 5, size); + } + public final boolean hasFields() { + return !_pointerFieldIsNull(6); + } + public final org.capnproto.StructList.Builder getFields() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisField.listFactory, 6, null, 0); + } + public final void setFields(org.capnproto.StructList.Reader value) { + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisField.listFactory, 6, value); + } + public final org.capnproto.StructList.Builder initFields(int size) { + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisField.listFactory, 6, size); + } + public final int getNextLayerNumber() { + return _getIntField(8); + } + public final void setNextLayerNumber(int value) { + _setIntField(8, value); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final int getNextTypeId() { + return _getIntField(0); + } + + public final int getNextMethodId() { + return _getIntField(1); + } + + public final int getNextFieldId() { + return _getIntField(2); + } + + public final int getNextConstantId() { + return _getIntField(3); + } + + public final int getStaticPrimitiveFieldsConstantId() { + return _getIntField(4); + } + + public final int getStaticObjectFieldsConstantId() { + return _getIntField(5); + } + + public final long getImageHeapSize() { + return _getLongField(3); + } + + public final boolean hasConstantsToRelink() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Int.Reader getConstantsToRelink() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 0, null, 0); + } + + public final boolean hasTypes() { + return !_pointerFieldIsNull(1); + } + public final org.capnproto.StructList.Reader getTypes() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisType.listFactory, 1, null, 0); + } + + public final boolean hasMethods() { + return !_pointerFieldIsNull(2); + } + public final org.capnproto.StructList.Reader getMethods() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisMethod.listFactory, 2, null, 0); + } + + public final boolean hasConstants() { + return !_pointerFieldIsNull(3); + } + public final org.capnproto.StructList.Reader getConstants() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedConstant.listFactory, 3, null, 0); + } + + public final boolean hasSingletonKeys() { + return !_pointerFieldIsNull(4); + } + public final org.capnproto.StructList.Reader getSingletonKeys() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ImageSingletonKey.listFactory, 4, null, 0); + } + + public final boolean hasSingletonObjects() { + return !_pointerFieldIsNull(5); + } + public final org.capnproto.StructList.Reader getSingletonObjects() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ImageSingletonObject.listFactory, 5, null, 0); + } + + public final boolean hasFields() { + return !_pointerFieldIsNull(6); + } + public final org.capnproto.StructList.Reader getFields() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisField.listFactory, 6, null, 0); + } + + public final int getNextLayerNumber() { + return _getIntField(8); + } + + } + + } + + + public static class PrimitiveValue { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)2,(short)0); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PrimitiveValue.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final byte getTypeChar() { + return _getByteField(0); + } + public final void setTypeChar(byte value) { + _setByteField(0, value); + } + + public final long getRawValue() { + return _getLongField(1); + } + public final void setRawValue(long value) { + _setLongField(1, value); + } + + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public final byte getTypeChar() { + return _getByteField(0); + } + + public final long getRawValue() { + return _getLongField(1); + } + + } + + } + + + public static class PrimitiveArray { + public static final org.capnproto.StructSize STRUCT_SIZE = new org.capnproto.StructSize((short)1,(short)1); + public static final class Factory extends org.capnproto.StructFactory { + public Factory() { + } + public final Reader constructReader(org.capnproto.SegmentReader segment, int data,int pointers, int dataSize, short pointerCount, int nestingLimit) { + return new Reader(segment,data,pointers,dataSize,pointerCount,nestingLimit); + } + public final Builder constructBuilder(org.capnproto.SegmentBuilder segment, int data,int pointers, int dataSize, short pointerCount) { + return new Builder(segment, data, pointers, dataSize, pointerCount); + } + public final org.capnproto.StructSize structSize() { + return PrimitiveArray.STRUCT_SIZE; + } + public final Reader asReader(Builder builder) { + return builder.asReader(); + } + } + public static final Factory factory = new Factory(); + public static final org.capnproto.StructList.Factory listFactory = + new org.capnproto.StructList.Factory(factory); + public static final class Builder extends org.capnproto.StructBuilder { + Builder(org.capnproto.SegmentBuilder segment, int data, int pointers,int dataSize, short pointerCount){ + super(segment, data, pointers, dataSize, pointerCount); + } + public Which which() { + switch(_getShortField(0)) { + case 0 : return Which.Z; + case 1 : return Which.B; + case 2 : return Which.S; + case 3 : return Which.C; + case 4 : return Which.I; + case 5 : return Which.F; + case 6 : return Which.J; + case 7 : return Which.D; + default: return Which._NOT_IN_SCHEMA; + } + } + public final Reader asReader() { + return new Reader(segment, data, pointers, dataSize, pointerCount, 0x7fffffff); + } + public final boolean isZ() { + return which() == PrimitiveArray.Which.Z; + } + public final boolean hasZ() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Boolean.Builder getZ() { + return _getPointerField(org.capnproto.PrimitiveList.Boolean.factory, 0, null, 0); + } + public final void setZ(org.capnproto.PrimitiveList.Boolean.Reader value) { + _setShortField(0, (short)PrimitiveArray.Which.Z.ordinal()); + _setPointerField(org.capnproto.PrimitiveList.Boolean.factory, 0, value); + } + public final org.capnproto.PrimitiveList.Boolean.Builder initZ(int size) { + _setShortField(0, (short)PrimitiveArray.Which.Z.ordinal()); + return _initPointerField(org.capnproto.PrimitiveList.Boolean.factory, 0, size); + } + public final boolean isB() { + return which() == PrimitiveArray.Which.B; + } + public final boolean hasB() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Byte.Builder getB() { + return _getPointerField(org.capnproto.PrimitiveList.Byte.factory, 0, null, 0); + } + public final void setB(org.capnproto.PrimitiveList.Byte.Reader value) { + _setShortField(0, (short)PrimitiveArray.Which.B.ordinal()); + _setPointerField(org.capnproto.PrimitiveList.Byte.factory, 0, value); + } + public final org.capnproto.PrimitiveList.Byte.Builder initB(int size) { + _setShortField(0, (short)PrimitiveArray.Which.B.ordinal()); + return _initPointerField(org.capnproto.PrimitiveList.Byte.factory, 0, size); + } + public final boolean isS() { + return which() == PrimitiveArray.Which.S; + } + public final boolean hasS() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Short.Builder getS() { + return _getPointerField(org.capnproto.PrimitiveList.Short.factory, 0, null, 0); + } + public final void setS(org.capnproto.PrimitiveList.Short.Reader value) { + _setShortField(0, (short)PrimitiveArray.Which.S.ordinal()); + _setPointerField(org.capnproto.PrimitiveList.Short.factory, 0, value); + } + public final org.capnproto.PrimitiveList.Short.Builder initS(int size) { + _setShortField(0, (short)PrimitiveArray.Which.S.ordinal()); + return _initPointerField(org.capnproto.PrimitiveList.Short.factory, 0, size); + } + public final boolean isC() { + return which() == PrimitiveArray.Which.C; + } + public final boolean hasC() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Short.Builder getC() { + return _getPointerField(org.capnproto.PrimitiveList.Short.factory, 0, null, 0); + } + public final void setC(org.capnproto.PrimitiveList.Short.Reader value) { + _setShortField(0, (short)PrimitiveArray.Which.C.ordinal()); + _setPointerField(org.capnproto.PrimitiveList.Short.factory, 0, value); + } + public final org.capnproto.PrimitiveList.Short.Builder initC(int size) { + _setShortField(0, (short)PrimitiveArray.Which.C.ordinal()); + return _initPointerField(org.capnproto.PrimitiveList.Short.factory, 0, size); + } + public final boolean isI() { + return which() == PrimitiveArray.Which.I; + } + public final boolean hasI() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Int.Builder getI() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 0, null, 0); + } + public final void setI(org.capnproto.PrimitiveList.Int.Reader value) { + _setShortField(0, (short)PrimitiveArray.Which.I.ordinal()); + _setPointerField(org.capnproto.PrimitiveList.Int.factory, 0, value); + } + public final org.capnproto.PrimitiveList.Int.Builder initI(int size) { + _setShortField(0, (short)PrimitiveArray.Which.I.ordinal()); + return _initPointerField(org.capnproto.PrimitiveList.Int.factory, 0, size); + } + public final boolean isF() { + return which() == PrimitiveArray.Which.F; + } + public final boolean hasF() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Float.Builder getF() { + return _getPointerField(org.capnproto.PrimitiveList.Float.factory, 0, null, 0); + } + public final void setF(org.capnproto.PrimitiveList.Float.Reader value) { + _setShortField(0, (short)PrimitiveArray.Which.F.ordinal()); + _setPointerField(org.capnproto.PrimitiveList.Float.factory, 0, value); + } + public final org.capnproto.PrimitiveList.Float.Builder initF(int size) { + _setShortField(0, (short)PrimitiveArray.Which.F.ordinal()); + return _initPointerField(org.capnproto.PrimitiveList.Float.factory, 0, size); + } + public final boolean isJ() { + return which() == PrimitiveArray.Which.J; + } + public final boolean hasJ() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Long.Builder getJ() { + return _getPointerField(org.capnproto.PrimitiveList.Long.factory, 0, null, 0); + } + public final void setJ(org.capnproto.PrimitiveList.Long.Reader value) { + _setShortField(0, (short)PrimitiveArray.Which.J.ordinal()); + _setPointerField(org.capnproto.PrimitiveList.Long.factory, 0, value); + } + public final org.capnproto.PrimitiveList.Long.Builder initJ(int size) { + _setShortField(0, (short)PrimitiveArray.Which.J.ordinal()); + return _initPointerField(org.capnproto.PrimitiveList.Long.factory, 0, size); + } + public final boolean isD() { + return which() == PrimitiveArray.Which.D; + } + public final boolean hasD() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Double.Builder getD() { + return _getPointerField(org.capnproto.PrimitiveList.Double.factory, 0, null, 0); + } + public final void setD(org.capnproto.PrimitiveList.Double.Reader value) { + _setShortField(0, (short)PrimitiveArray.Which.D.ordinal()); + _setPointerField(org.capnproto.PrimitiveList.Double.factory, 0, value); + } + public final org.capnproto.PrimitiveList.Double.Builder initD(int size) { + _setShortField(0, (short)PrimitiveArray.Which.D.ordinal()); + return _initPointerField(org.capnproto.PrimitiveList.Double.factory, 0, size); + } + } + + public static final class Reader extends org.capnproto.StructReader { + Reader(org.capnproto.SegmentReader segment, int data, int pointers,int dataSize, short pointerCount, int nestingLimit){ + super(segment, data, pointers, dataSize, pointerCount, nestingLimit); + } + + public Which which() { + switch(_getShortField(0)) { + case 0 : return Which.Z; + case 1 : return Which.B; + case 2 : return Which.S; + case 3 : return Which.C; + case 4 : return Which.I; + case 5 : return Which.F; + case 6 : return Which.J; + case 7 : return Which.D; + default: return Which._NOT_IN_SCHEMA; + } + } + public final boolean isZ() { + return which() == PrimitiveArray.Which.Z; + } + public final boolean hasZ() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Boolean.Reader getZ() { + return _getPointerField(org.capnproto.PrimitiveList.Boolean.factory, 0, null, 0); + } + + public final boolean isB() { + return which() == PrimitiveArray.Which.B; + } + public final boolean hasB() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Byte.Reader getB() { + return _getPointerField(org.capnproto.PrimitiveList.Byte.factory, 0, null, 0); + } + + public final boolean isS() { + return which() == PrimitiveArray.Which.S; + } + public final boolean hasS() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Short.Reader getS() { + return _getPointerField(org.capnproto.PrimitiveList.Short.factory, 0, null, 0); + } + + public final boolean isC() { + return which() == PrimitiveArray.Which.C; + } + public final boolean hasC() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Short.Reader getC() { + return _getPointerField(org.capnproto.PrimitiveList.Short.factory, 0, null, 0); + } + + public final boolean isI() { + return which() == PrimitiveArray.Which.I; + } + public final boolean hasI() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Int.Reader getI() { + return _getPointerField(org.capnproto.PrimitiveList.Int.factory, 0, null, 0); + } + + public final boolean isF() { + return which() == PrimitiveArray.Which.F; + } + public final boolean hasF() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Float.Reader getF() { + return _getPointerField(org.capnproto.PrimitiveList.Float.factory, 0, null, 0); + } + + public final boolean isJ() { + return which() == PrimitiveArray.Which.J; + } + public final boolean hasJ() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Long.Reader getJ() { + return _getPointerField(org.capnproto.PrimitiveList.Long.factory, 0, null, 0); + } + + public final boolean isD() { + return which() == PrimitiveArray.Which.D; + } + public final boolean hasD() { + return !_pointerFieldIsNull(0); + } + public final org.capnproto.PrimitiveList.Double.Reader getD() { + return _getPointerField(org.capnproto.PrimitiveList.Double.factory, 0, null, 0); + } + + } + + public enum Which { + Z, + B, + S, + C, + I, + F, + J, + D, + _NOT_IN_SCHEMA, + } + } + + + +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java index 7197aa323e1a..4fcf9f22a46a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java @@ -261,7 +261,7 @@ public void afterRegistration(AfterRegistrationAccess access) { allLocales = processLocalesOption(); if (Options.DefaultLocale.hasBeenSet()) { LogUtils.warning("Option %s is deprecated and has no effect. The program's default locale is determined at run-time. " + - "Use %s and %s to manage the locales included in the image.\n", + "Use %s and %s to manage the locales included in the image.%n", Options.DefaultLocale.getName(), Options.IncludeLocales.getName(), Options.IncludeAllLocales.getName()); } String defaultCharsetOptionValue = Options.DefaultCharset.getValue(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java index 8a78b8efe37b..89e5a0d46dfe 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java @@ -93,7 +93,7 @@ public void duringSetup(DuringSetupAccess c) { for (Class eventSubClass : config.findSubclasses(Event.class)) { RuntimeClassInitialization.initializeAtBuildTime(eventSubClass.getName()); } - config.registerSubstitutionProcessor(new JfrEventSubstitution(metaAccess)); + config.registerSubstitutionProcessor(new JfrEventSubstitution(metaAccess, config.getUniverse().getHeapScanner())); /* * The value of this field is set later in the beforeCompilation method after analysis diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java index 579732324495..439dd2db5451 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java @@ -33,8 +33,10 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import com.oracle.graal.pointsto.heap.ImageHeapScanner; import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.jfr.JfrJavaEvents; import com.oracle.svm.core.jfr.JfrJdkCompatibility; import com.oracle.svm.core.util.ObservableImageHeapMapProvider; @@ -57,6 +59,8 @@ @Platforms(Platform.HOSTED_ONLY.class) public class JfrEventSubstitution extends SubstitutionProcessor { + private final ImageHeapScanner heapScanner; + private final ResolvedJavaType baseEventType; private final ConcurrentHashMap typeSubstitution; private final ConcurrentHashMap methodSubstitutions; @@ -64,8 +68,10 @@ public class JfrEventSubstitution extends SubstitutionProcessor { private final Map> mirrorEventMapping; private static final Method registerMirror = JavaVersionUtil.JAVA_SPEC < 22 ? ReflectionUtil.lookupMethod(SecuritySupport.class, "registerMirror", Class.class) : null; + private static final Method getConfiguration = ReflectionUtil.lookupMethod(JVM.class, "getConfiguration", Class.class); - JfrEventSubstitution(MetaAccessProvider metaAccess) { + JfrEventSubstitution(MetaAccessProvider metaAccess, ImageHeapScanner heapScanner) { + this.heapScanner = heapScanner; baseEventType = metaAccess.lookupJavaType(jdk.internal.event.Event.class); typeSubstitution = new ConcurrentHashMap<>(); methodSubstitutions = new ConcurrentHashMap<>(); @@ -147,6 +153,7 @@ private static ResolvedJavaMethod initEventMethod(ResolvedJavaMethod oldMethod) } private Boolean initEventClass(ResolvedJavaType eventType) throws RuntimeException { + VMError.guarantee(!BuildPhaseProvider.isAnalysisFinished()); try { Class newEventClass = OriginalClassProvider.getJavaClass(eventType).asSubclass(jdk.internal.event.Event.class); eventType.initialize(); @@ -170,6 +177,10 @@ private Boolean initEventClass(ResolvedJavaType eventType) throws RuntimeExcepti // the reflection registration for the event handler field is delayed to the JfrFeature // duringAnalysis callback so it does not race/interfere with other retransforms JfrJdkCompatibility.retransformClasses(new Class[]{newEventClass}); + + // make sure the EventConfiguration object is fully scanned + heapScanner.rescanObject(getConfiguration.invoke(JfrJdkCompatibility.getJVMOrNull(), newEventClass)); + return Boolean.TRUE; } catch (Throwable ex) { throw VMError.shouldNotReachHere(ex); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java index c79b30e0ab31..ecd895a00128 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java @@ -43,6 +43,7 @@ import java.util.function.Predicate; import java.util.stream.Stream; +import jdk.graal.compiler.word.Word; import org.graalvm.collections.EconomicSet; import org.graalvm.collections.Equivalence; import org.graalvm.collections.Pair; @@ -53,7 +54,6 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeJNIAccessSupport; import org.graalvm.word.PointerBase; -import org.graalvm.word.WordFactory; import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.infrastructure.ResolvedSignature; @@ -207,9 +207,10 @@ public void afterRegistration(AfterRegistrationAccess arg) { ConfigurationConditionResolver conditionResolver = new NativeImageConditionResolver(access.getImageClassLoader(), ClassInitializationSupport.singleton()); - ReflectionConfigurationParser> parser = ConfigurationParserUtils.create(JNI_KEY, true, conditionResolver, runtimeSupport, null, access.getImageClassLoader()); + ReflectionConfigurationParser> parser = ConfigurationParserUtils.create(JNI_KEY, true, conditionResolver, runtimeSupport, null, null, + access.getImageClassLoader()); loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurationsFromCombinedFile(parser, access.getImageClassLoader(), "JNI"); - ReflectionConfigurationParser> legacyParser = ConfigurationParserUtils.create(null, false, conditionResolver, runtimeSupport, null, + ReflectionConfigurationParser> legacyParser = ConfigurationParserUtils.create(null, false, conditionResolver, runtimeSupport, null, null, access.getImageClassLoader()); loadedConfigurations += ConfigurationParserUtils.parseAndRegisterConfigurations(legacyParser, access.getImageClassLoader(), "JNI", ConfigurationFiles.Options.JNIConfigurationFiles, ConfigurationFiles.Options.JNIConfigurationResources, ConfigurationFile.JNI.getFileName()); @@ -602,7 +603,7 @@ private static void finishMethodBeforeCompilation(JNICallableJavaMethod method, newObjectTarget = new MethodPointer(hUniverse.lookup(aUniverse.lookup(method.newObjectMethod))); } else if (method.targetMethod.isConstructor()) { assert method.targetMethod.getDeclaringClass().isAbstract(); - newObjectTarget = WordFactory.signed(JNIAccessibleMethod.NEW_OBJECT_INVALID_FOR_ABSTRACT_TYPE); + newObjectTarget = Word.signed(JNIAccessibleMethod.NEW_OBJECT_INVALID_FOR_ABSTRACT_TYPE); } CodePointer callWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(method.callWrapper))); CodePointer varargs = new MethodPointer(hUniverse.lookup(aUniverse.lookup(method.variantWrappers.varargs))); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/DynamicHubOffsetsFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/DynamicHubOffsetsFeature.java new file mode 100644 index 000000000000..b244e4420365 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/DynamicHubOffsetsFeature.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.meta; + +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.graal.meta.DynamicHubOffsets; +import com.oracle.svm.core.layeredimagesingleton.FeatureSingleton; +import com.oracle.svm.hosted.FeatureImpl; + +@AutomaticallyRegisteredFeature +public final class DynamicHubOffsetsFeature implements InternalFeature, FeatureSingleton { + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + ImageSingletons.add(DynamicHubOffsets.class, new DynamicHubOffsets()); + } + + @Override + public void beforeCompilation(BeforeCompilationAccess a) { + DynamicHubOffsets.singleton().initializeOffsets(((FeatureImpl.BeforeCompilationAccessImpl) a).getMetaAccess()); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java index b8c1ce7648c1..79023003e7e8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.hosted.meta; -import com.oracle.graal.pointsto.heap.ImageLayerLoader; import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; import com.oracle.graal.pointsto.infrastructure.WrappedJavaField; import com.oracle.graal.pointsto.meta.AnalysisField; @@ -32,6 +31,8 @@ import com.oracle.svm.core.StaticFieldsSupport; import com.oracle.svm.core.meta.SharedField; import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; +import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; +import com.oracle.svm.hosted.imagelayer.SVMImageLayerLoader; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; @@ -187,7 +188,7 @@ public JavaConstant getStaticFieldBase() { AnalysisUniverse universe = type.wrapped.getUniverse(); boolean primitive = getStorageKind().isPrimitive(); if (isInBaseLayer()) { - ImageLayerLoader imageLayerLoader = universe.getImageLayerLoader(); + SVMImageLayerLoader imageLayerLoader = HostedImageLayerBuildingSupport.singleton().getLoader(); return primitive ? imageLayerLoader.getBaseLayerStaticPrimitiveFields() : imageLayerLoader.getBaseLayerStaticObjectFields(); } return universe.getSnippetReflection().forObject(primitive ? StaticFieldsSupport.getStaticPrimitiveFields() : StaticFieldsSupport.getStaticObjectFields()); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java index 484f152115d5..671a8bc00044 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java @@ -34,7 +34,6 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; -import java.util.function.Function; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.infrastructure.OriginalMethodProvider; @@ -150,20 +149,30 @@ static HostedMethod create(HostedUniverse universe, AnalysisMethod wrapped, Host private static HostedMethod create0(AnalysisMethod wrapped, HostedType holder, ResolvedSignature signature, ConstantPool constantPool, ExceptionHandler[] handlers, MultiMethodKey key, Map multiMethodMap, LocalVariableTable localVariableTable) { - Function nameGenerator = (collisionCount) -> { - String name = wrapped.wrapped.getName(); // want name w/o any multimethodkey suffix - if (key != ORIGINAL_METHOD) { - name += StableMethodNameFormatter.MULTI_METHOD_KEY_SEPARATOR + key; - } - if (collisionCount > 0) { - name = name + METHOD_NAME_COLLISION_SEPARATOR + collisionCount; + var generator = new HostedMethodNameFactory.NameGenerator() { + + @Override + public HostedMethodNameFactory.MethodNameInfo generateMethodNameInfo(int collisionCount) { + String name = wrapped.wrapped.getName(); // want name w/o any multimethodkey suffix + if (key != ORIGINAL_METHOD) { + name += StableMethodNameFormatter.MULTI_METHOD_KEY_SEPARATOR + key; + } + if (collisionCount > 0) { + name = name + METHOD_NAME_COLLISION_SEPARATOR + collisionCount; + } + + String uniqueShortName = generateUniqueName(name); + + return new HostedMethodNameFactory.MethodNameInfo(name, uniqueShortName); } - String uniqueShortName = SubstrateUtil.uniqueShortName(holder.getJavaClass().getClassLoader(), holder, name, signature, wrapped.isConstructor()); - return new HostedMethodNameFactory.MethodNameInfo(name, uniqueShortName); + @Override + public String generateUniqueName(String name) { + return SubstrateUtil.uniqueShortName(holder.getJavaClass().getClassLoader(), holder, name, signature, wrapped.isConstructor()); + } }; - HostedMethodNameFactory.MethodNameInfo names = HostedMethodNameFactory.singleton().createNames(nameGenerator, wrapped); + HostedMethodNameFactory.MethodNameInfo names = HostedMethodNameFactory.singleton().createNames(generator, wrapped); return new HostedMethod(wrapped, holder, signature, constantPool, handlers, names.name(), names.uniqueShortName(), localVariableTable, key, multiMethodMap); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethodNameFactory.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethodNameFactory.java index 9ca4a1169ad0..cc4fbcd36665 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethodNameFactory.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethodNameFactory.java @@ -27,7 +27,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; import org.graalvm.nativeimage.ImageSingletons; @@ -35,27 +34,49 @@ import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.imagelayer.HostedDynamicLayerInfo; +import com.oracle.svm.util.LogUtils; + +import jdk.graal.compiler.options.Option; @AutomaticallyRegisteredFeature public class HostedMethodNameFactory implements InternalFeature { + public static final class Options { + @Option(help = "Log unique names which do not match across layers. This is an experimental option which will be removed.") // + public static final HostedOptionKey LogUniqueNameInconsistencies = new HostedOptionKey<>(false); + } + private Map methodNameCount = new ConcurrentHashMap<>(); private Set uniqueShortNames = ConcurrentHashMap.newKeySet(); private final boolean buildingExtensionLayer = ImageLayerBuildingSupport.buildingExtensionLayer(); private Set reservedUniqueShortNames = buildingExtensionLayer ? HostedDynamicLayerInfo.singleton().getReservedNames() : null; + private final boolean logUniqueNameInconsistencies = Options.LogUniqueNameInconsistencies.getValue(); public record MethodNameInfo(String name, String uniqueShortName) { } + public interface NameGenerator { + MethodNameInfo generateMethodNameInfo(int collisionCount); + + String generateUniqueName(String name); + } + public static HostedMethodNameFactory singleton() { return ImageSingletons.lookup(HostedMethodNameFactory.class); } - MethodNameInfo createNames(Function nameGenerator, AnalysisMethod aMethod) { + MethodNameInfo createNames(NameGenerator generator, AnalysisMethod aMethod) { MethodNameInfo result = buildingExtensionLayer ? HostedDynamicLayerInfo.singleton().loadMethodNameInfo(aMethod) : null; if (result != null) { assert reservedUniqueShortNames.contains(result.uniqueShortName()) : result; + if (logUniqueNameInconsistencies) { + boolean consistentNames = generator.generateUniqueName(result.name()).equals(result.uniqueShortName); + if (!consistentNames) { + LogUtils.warning("Unique names are inconsistent for %s", aMethod.getQualifiedName()); + } + } boolean added = uniqueShortNames.add(result.uniqueShortName()); if (added) { @@ -67,13 +88,13 @@ MethodNameInfo createNames(Function nameGenerator, Anal } } - MethodNameInfo initialName = nameGenerator.apply(0); + MethodNameInfo initialName = generator.generateMethodNameInfo(0); result = initialName; do { int collisionCount = methodNameCount.merge(initialName.uniqueShortName(), 0, (oldValue, value) -> oldValue + 1); if (collisionCount != 0) { - result = nameGenerator.apply(collisionCount); + result = generator.generateMethodNameInfo(collisionCount); } /* * Redo if the short name is reserved. diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedType.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedType.java index 7ea94ed15c43..b8162ee99532 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedType.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedType.java @@ -133,6 +133,9 @@ public abstract class HostedType extends HostedElement implements SharedType, Wr * this type is never instantiated and does not have any instantiated subtype, i.e., if no value * of this type can ever exist. Equal to this type if this type is instantiated, i.e, this type * cannot be strengthened. + * + * For open world the strengthen stamp type is equal to this type itself if the type is not a + * leaf type, i.e., it cannot be extended. */ protected HostedType strengthenStampType; @@ -276,10 +279,6 @@ public HostedMethod[] getAllDeclaredMethods() { return allDeclaredMethods; } - public HostedType getUniqueConcreteImplementation() { - return uniqueConcreteImplementation; - } - public void loadTypeID(int newTypeID) { this.typeID = newTypeID; this.loadedFromPriorLayer = true; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedUniverse.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedUniverse.java index e284b03539db..ade7c8c7cbe1 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedUniverse.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedUniverse.java @@ -671,7 +671,7 @@ public int compare(HostedType o1, HostedType o2) { ClassLoader l1 = Optional.ofNullable(o1.getJavaClass()).map(Class::getClassLoader).orElse(null); ClassLoader l2 = Optional.ofNullable(o2.getJavaClass()).map(Class::getClassLoader).orElse(null); - result = SubstrateUtil.classLoaderNameAndId(l1).compareTo(SubstrateUtil.classLoaderNameAndId(l2)); + result = SubstrateUtil.runtimeClassLoaderNameAndId(l1).compareTo(SubstrateUtil.runtimeClassLoaderNameAndId(l2)); VMError.guarantee(result != 0, "HostedType objects not distinguishable by name and classloader: %s, %s", o1, o2); return result; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/KnownOffsetsFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/KnownOffsetsFeature.java index bdce9d509380..7d950ba61a05 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/KnownOffsetsFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/KnownOffsetsFeature.java @@ -37,7 +37,6 @@ import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.graal.meta.KnownOffsets; -import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.layeredimagesingleton.FeatureSingleton; import com.oracle.svm.core.layeredimagesingleton.UnsavedSingleton; import com.oracle.svm.core.stack.JavaFrameAnchor; @@ -72,8 +71,6 @@ public void beforeCompilation(BeforeCompilationAccess a) { int vtableEntrySize = dynamicHubLayout.vTableSlotSize; int typeIDSlotsOffset = SubstrateOptions.useClosedTypeWorldHubLayout() ? dynamicHubLayout.getClosedTypeWorldTypeCheckSlotsOffset() : -1; - int componentHubOffset = findFieldOffset(access, DynamicHub.class, "componentType"); - int javaFrameAnchorLastSPOffset = findStructOffset(access, JavaFrameAnchor.class, "getLastJavaSP"); int javaFrameAnchorLastIPOffset = findStructOffset(access, JavaFrameAnchor.class, "getLastJavaIP"); @@ -81,7 +78,7 @@ public void beforeCompilation(BeforeCompilationAccess a) { int imageCodeInfoCodeStartOffset = findFieldOffset(access, ImageCodeInfo.class, "codeStart"); - KnownOffsets.singleton().setLazyState(vtableBaseOffset, vtableEntrySize, typeIDSlotsOffset, componentHubOffset, + KnownOffsets.singleton().setLazyState(vtableBaseOffset, vtableEntrySize, typeIDSlotsOffset, javaFrameAnchorLastSPOffset, javaFrameAnchorLastIPOffset, vmThreadStatusOffset, imageCodeInfoCodeStartOffset); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java index 9fd4be7c151f..3b763ee5cf20 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java @@ -44,7 +44,6 @@ import java.util.function.Function; import java.util.stream.Collectors; -import jdk.graal.compiler.serviceprovider.GraalServices; import org.graalvm.nativeimage.c.function.CEntryPointLiteral; import org.graalvm.nativeimage.c.function.CFunction; import org.graalvm.nativeimage.c.function.CFunctionPointer; @@ -107,6 +106,7 @@ import jdk.graal.compiler.core.common.NumUtil; import jdk.graal.compiler.debug.DebugContext; import jdk.graal.compiler.debug.Indent; +import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.internal.vm.annotation.Contended; import jdk.vm.ci.meta.ConstantPool; import jdk.vm.ci.meta.ExceptionHandler; @@ -779,7 +779,7 @@ private void layoutStaticFields() { if (skipStaticField(field)) { // does not require memory. } else if (field.wrapped.isInBaseLayer()) { - field.setLocation(aUniverse.getImageLayerLoader().getFieldLocation(field.wrapped)); + field.setLocation(HostedImageLayerBuildingSupport.singleton().getLoader().getFieldLocation(field.wrapped)); } else if (field.getStorageKind() == JavaKind.Object) { field.setLocation(NumUtil.safeToInt(layout.getArrayElementOffset(JavaKind.Object, nextObjectField))); nextObjectField += 1; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleInvokerRenamingSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleInvokerRenamingSubstitutionProcessor.java index d04ba535716d..30e4b3a92245 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleInvokerRenamingSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleInvokerRenamingSubstitutionProcessor.java @@ -42,6 +42,7 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.BaseLayerType; import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.util.BasedOnJDKClass; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ReflectionUtil; @@ -52,6 +53,20 @@ * which are assigned more or less arbitrary names by the host VM, to stable names that are based on * the {@code LambdaForm} which they were compiled from. */ +@BasedOnJDKClass(value = MethodHandle.class) +@BasedOnJDKClass(value = MethodType.class) +@BasedOnJDKClass(className = "java.lang.invoke.MethodHandleStatics") +@BasedOnJDKClass(className = "java.lang.invoke.ClassSpecializer") +@BasedOnJDKClass(className = "java.lang.invoke.ClassSpecializer", innerClass = "SpeciesData") +@BasedOnJDKClass(className = "java.lang.invoke.MemberName") +@BasedOnJDKClass(className = "java.lang.invoke.MethodHandleNatives") +@BasedOnJDKClass(className = "java.lang.invoke.LambdaForm") +@BasedOnJDKClass(className = "java.lang.invoke.LambdaForm", innerClass = "BasicType") +@BasedOnJDKClass(className = "java.lang.invoke.LambdaForm", innerClass = "Name") +@BasedOnJDKClass(className = "java.lang.invoke.LambdaForm", innerClass = "NamedFunction") +@BasedOnJDKClass(className = "java.lang.invoke.BoundMethodHandle") +@BasedOnJDKClass(className = "java.lang.invoke.DirectMethodHandle") +@BasedOnJDKClass(className = "java.lang.invoke.MethodHandleImpl", innerClass = "IntrinsicMethodHandle") public class MethodHandleInvokerRenamingSubstitutionProcessor extends SubstitutionProcessor { private static final Class METHOD_HANDLE_STATICS_CLASS = ReflectionUtil.lookupClass(false, "java.lang.invoke.MethodHandleStatics"); private static final Field DEBUG_METHOD_HANDLE_NAMES_FIELD = ReflectionUtil.lookupField(METHOD_HANDLE_STATICS_CLASS, "DEBUG_METHOD_HANDLE_NAMES"); @@ -85,6 +100,8 @@ public class MethodHandleInvokerRenamingSubstitutionProcessor extends Substituti private static final Method BOUND_METHOD_HANDLE_SPECIES_DATA_METHOD = ReflectionUtil.lookupMethod(BOUND_METHOD_HANDLE_CLASS, "speciesData"); private static final Class DIRECT_METHOD_HANDLE_CLASS = ReflectionUtil.lookupClass(false, "java.lang.invoke.DirectMethodHandle"); private static final Method DIRECT_METHOD_HANDLE_INTERNAL_MEMBER_NAME_METHOD = ReflectionUtil.lookupMethod(DIRECT_METHOD_HANDLE_CLASS, "internalMemberName"); + private static final Class METHOD_HANDLE_IMPL_INTRINSIC_METHOD_HANDLE_CLASS = ReflectionUtil.lookupClass("java.lang.invoke.MethodHandleImpl$IntrinsicMethodHandle"); + private static final Method METHOD_HANDLE_IMPL_INTRINSIC_METHOD_HANDLE_INTRINSIC_DATA_METHOD = ReflectionUtil.lookupMethod(METHOD_HANDLE_IMPL_INTRINSIC_METHOD_HANDLE_CLASS, "intrinsicData"); private static final String DMH_CLASS_NAME_SUBSTRING = "LambdaForm$DMH"; private static final String DMH_STABLE_NAME_TEMPLATE = "Ljava/lang/invoke/LambdaForm$DMH.s"; @@ -289,41 +306,44 @@ private int getUniqueStableHash(Object lambdaForm) throws ReflectiveOperationExc * result. */ hash = hash * 31 + memberNameToString(member).hashCode(); - } else { + } + /* + * The method handle of the NamedFunction is used in the string representation. To + * avoid potential aliasing, the hash of the descriptor string is mixed in the + * result. + */ + Object resolvedHandle = NAMED_FUNCTION_RESOLVED_HANDLE_METHOD.invoke(function); + MethodType methodType = ((MethodHandle) resolvedHandle).type(); + hash = hash * 31 + methodType.descriptorString().hashCode(); + if (METHOD_HANDLE_IMPL_INTRINSIC_METHOD_HANDLE_CLASS.isInstance(resolvedHandle)) { + if (METHOD_HANDLE_IMPL_INTRINSIC_METHOD_HANDLE_INTRINSIC_DATA_METHOD.invoke(resolvedHandle) instanceof Integer integer) { + hash = hash * 31 + integer; + } + } + + if (BOUND_METHOD_HANDLE_CLASS.isInstance(resolvedHandle)) { /* - * If the member field is null, the method handle of the NamedFunction is used - * in the string representation. To avoid potential aliasing, the hash of the - * descriptor string is mixed in the result. + * BoundMethodHandle.internalValues calls BoundMethodHandle.arg, which retrieves + * the object that was bound to the corresponding argument, and return its + * string representation. This method is only used if the debug method handle + * names are activated. The object used may not have a stable string + * representation, which would lead to an unstable name. */ - Object innerMethodHandle = NAMED_FUNCTION_RESOLVED_HANDLE_METHOD.invoke(function); - MethodType methodType = ((MethodHandle) innerMethodHandle).type(); - hash = hash * 31 + methodType.descriptorString().hashCode(); - - if (BOUND_METHOD_HANDLE_CLASS.isInstance(innerMethodHandle)) { - /* - * BoundMethodHandle.internalValues calls BoundMethodHandle.arg, which - * retrieves the object that was bound to the corresponding argument, and - * return its string representation. This method is only used if the debug - * method handle names are activated. The object used may not have a stable - * string representation, which would lead to an unstable name. - */ - assert !DEBUG_METHOD_HANDLE_NAMES_FIELD.getBoolean(null) : "The method handle " + innerMethodHandle + - " with debug method handle names can contain the string representation from any object, which would cause the name to be unstable."; - - /* - * Without the debug method handle names, the MethodHandle.toString method - * does not include any additional detail if the method handle is a bound - * method handle. To avoid potential aliasing, the custom hash of the - * species data is mixed with the result. - */ - Object speciesData = BOUND_METHOD_HANDLE_SPECIES_DATA_METHOD.invoke(innerMethodHandle); - hash = hash * 31 + getSpeciesDataHash(speciesData); - } + assert !DEBUG_METHOD_HANDLE_NAMES_FIELD.getBoolean(null) : "The method handle " + resolvedHandle + + " with debug method handle names can contain the string representation from any object, which would cause the name to be unstable."; + + /* + * Without the debug method handle names, the MethodHandle.toString method does + * not include any additional detail if the method handle is a bound method + * handle. To avoid potential aliasing, the custom hash of the species data is + * mixed with the result. + */ + Object speciesData = BOUND_METHOD_HANDLE_SPECIES_DATA_METHOD.invoke(resolvedHandle); + hash = hash * 31 + getSpeciesDataHash(speciesData); } - Object innerMethodHandle = NAMED_FUNCTION_RESOLVED_HANDLE_METHOD.invoke(function); - Object innerLambdaForm = FORM_FIELD.get(innerMethodHandle); - hash = hash * 31 + computeLambdaFormHash(innerLambdaForm, DIRECT_METHOD_HANDLE_CLASS.isInstance(innerMethodHandle)); + Object innerLambdaForm = FORM_FIELD.get(resolvedHandle); + hash = hash * 31 + computeLambdaFormHash(innerLambdaForm, DIRECT_METHOD_HANDLE_CLASS.isInstance(resolvedHandle)); } } return hash * 31 + lambdaFormString.hashCode(); @@ -379,7 +399,7 @@ public boolean isNameAlwaysStable(String methodHandleName) { if (lastIndex < 0) { return true; } - return !uniqueTypeNames.contains(methodHandleName.substring(lastIndex) + "_1;"); + return !uniqueTypeNames.contains(methodHandleName.substring(0, lastIndex) + "_1;"); } boolean checkAllTypeNames() { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/CInterfaceInvocationPlugin.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/CInterfaceInvocationPlugin.java index be25f6c96fa5..ffbd5030f59f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/CInterfaceInvocationPlugin.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/CInterfaceInvocationPlugin.java @@ -29,13 +29,13 @@ import java.util.Arrays; +import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.function.CEntryPoint; import org.graalvm.nativeimage.c.function.CFunctionPointer; import org.graalvm.nativeimage.c.function.InvokeCFunctionPointer; import org.graalvm.word.LocationIdentity; import org.graalvm.word.WordBase; -import org.graalvm.word.impl.WordBoxFactory; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; @@ -649,7 +649,7 @@ private static Object createReturnObject(Class returnType, long value) { } else if (returnType == Long.class) { return value; } else if (WordBase.class.isAssignableFrom(returnType)) { - return WordBoxFactory.box(value); + return Word.unsigned(value); } else { throw VMError.shouldNotReachHere("Unexpected returnType: " + returnType.getName()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java index d2c35b12c883..b4e360ac9e0e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java @@ -284,6 +284,7 @@ protected Object loadReferenceTypeLock() { @Override protected void maybeEagerlyResolve(int cpi, int bytecode) { + lastUnresolvedElementException = null; try { super.maybeEagerlyResolve(cpi, bytecode); } catch (UnresolvedElementException e) { @@ -294,12 +295,19 @@ protected void maybeEagerlyResolve(int cpi, int bytecode) { * ConstantPool.lookupType() which should return an UnresolvedJavaType which we * know how to deal with. */ + lastUnresolvedElementException = e; } else { throw e; } } } + /** + * The type resolution error, if any, encountered in the last call to + * {@link #maybeEagerlyResolve}. + */ + UnresolvedElementException lastUnresolvedElementException; + @Override protected JavaType maybeEagerlyResolve(JavaType type, ResolvedJavaType accessingClass) { try { @@ -594,7 +602,7 @@ private static Class[] signatureToClasses(JavaMethod method) { } private void reportUnresolvedElement(String elementKind, String elementAsString) { - reportUnresolvedElement(elementKind, elementAsString, null); + reportUnresolvedElement(elementKind, elementAsString, lastUnresolvedElementException); } private void reportUnresolvedElement(String elementKind, String elementAsString, Throwable cause) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/VerifyDeoptLIRFrameStatesPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/VerifyDeoptLIRFrameStatesPhase.java index c2e2da58d6f2..20ba80a38395 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/VerifyDeoptLIRFrameStatesPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/VerifyDeoptLIRFrameStatesPhase.java @@ -27,6 +27,12 @@ import java.util.HashMap; import java.util.Map; +import com.oracle.svm.core.ReservedRegisters; +import com.oracle.svm.core.graal.lir.DeoptEntryOp; +import com.oracle.svm.core.heap.SubstrateReferenceMap; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.meta.HostedMethod; + import jdk.graal.compiler.core.common.cfg.BasicBlock; import jdk.graal.compiler.debug.DebugContext; import jdk.graal.compiler.lir.LIR; @@ -36,13 +42,6 @@ import jdk.graal.compiler.lir.gen.LIRGenerationResult; import jdk.graal.compiler.lir.phases.FinalCodeAnalysisPhase; import jdk.graal.compiler.nodes.FrameState; - -import com.oracle.svm.core.ReservedRegisters; -import com.oracle.svm.core.graal.lir.DeoptEntryOp; -import com.oracle.svm.core.heap.SubstrateReferenceMap; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.meta.HostedMethod; - import jdk.vm.ci.code.BytecodeFrame; import jdk.vm.ci.code.StackLockValue; import jdk.vm.ci.code.StackSlot; @@ -95,23 +94,22 @@ public void run(LIRGenerationResult lirGenRes) { private void reportError(LIRFrameState state, LIRInstruction op, String message) { if (errors == null) { errors = new StringBuilder(); - errors.append("\nProblems found within VerifyDeoptLIRFrameStatesPhase\n"); + errors.append(System.lineSeparator()).append("Problems found within VerifyDeoptLIRFrameStatesPhase").append(System.lineSeparator()); } - errors.append("\nProblem: ").append(message); + errors.append(System.lineSeparator()).append("Problem: ").append(message); BytecodeFrame frame = state.topFrame; while (frame.caller() != null) { frame = frame.caller(); } - errors.append("\nMethod: ").append(frame.getMethod()); - errors.append("\nop id: ").append(op.id()).append(", ").append(op); + errors.append(System.lineSeparator()).append("Method: ").append(frame.getMethod()); + errors.append(System.lineSeparator()).append("op id: ").append(op.id()).append(", ").append(op); frame = state.topFrame; do { - errors.append("\nat: bci ").append(frame.getBCI()).append(", duringCall: ").append(frame.duringCall).append(", rethrowException: ").append(frame.rethrowException).append(", method: ") - .append(frame.getMethod()); - + errors.append(System.lineSeparator()).append("at: bci ").append(frame.getBCI()).append(", duringCall: ").append(frame.duringCall).append(", rethrowException: ") + .append(frame.rethrowException).append(", method: ").append(frame.getMethod()); frame = frame.caller(); } while (frame != null); - errors.append("\nEnd Problem\n"); + errors.append(System.lineSeparator()).append("End Problem").append(System.lineSeparator()); } private static boolean isImplicitDeoptEntry(LIRFrameState state) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java index c4bd43670b13..0d788afc7dad 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java @@ -46,6 +46,7 @@ import org.graalvm.nativeimage.impl.AnnotationExtractor; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; +import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; import com.oracle.graal.pointsto.ObjectScanner; import com.oracle.graal.pointsto.infrastructure.UniverseMetaAccess; @@ -292,11 +293,12 @@ public void duringSetup(DuringSetupAccess a) { var conditionResolver = new NativeImageConditionResolver(access.getImageClassLoader(), ClassInitializationSupport.singleton()); reflectionData.duringSetup(access.getMetaAccess(), aUniverse); ProxyRegistry proxyRegistry = ImageSingletons.lookup(ProxyRegistry.class); + RuntimeSerializationSupport serializationSupport = RuntimeSerializationSupport.singleton(); ReflectionConfigurationParser> parser = ConfigurationParserUtils.create(REFLECTION_KEY, true, conditionResolver, reflectionData, proxyRegistry, - access.getImageClassLoader()); + serializationSupport, access.getImageClassLoader()); loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurationsFromCombinedFile(parser, access.getImageClassLoader(), "reflection"); ReflectionConfigurationParser> legacyParser = ConfigurationParserUtils.create(null, false, conditionResolver, reflectionData, proxyRegistry, - access.getImageClassLoader()); + serializationSupport, access.getImageClassLoader()); loadedConfigurations += ConfigurationParserUtils.parseAndRegisterConfigurations(legacyParser, access.getImageClassLoader(), "reflection", ConfigurationFiles.Options.ReflectionConfigurationFiles, ConfigurationFiles.Options.ReflectionConfigurationResources, ConfigurationFile.REFLECTION.getFileName()); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java index b8367d0ffe59..04e96e716b3f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java @@ -39,6 +39,7 @@ import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; import com.oracle.svm.core.reflect.proxy.DynamicProxySupport; import com.oracle.svm.hosted.FallbackFeature; +import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.hosted.ImageClassLoader; @@ -50,18 +51,29 @@ public final class DynamicProxyFeature implements InternalFeature { private int loadedConfigurations; private Field proxyCacheField; + private ProxyRegistry proxyRegistry; @Override - public void duringSetup(DuringSetupAccess a) { - DuringSetupAccessImpl access = (DuringSetupAccessImpl) a; - + public void afterRegistration(AfterRegistrationAccess a) { + FeatureImpl.AfterRegistrationAccessImpl access = (FeatureImpl.AfterRegistrationAccessImpl) a; ImageClassLoader imageClassLoader = access.getImageClassLoader(); - ConfigurationConditionResolver conditionResolver = new NativeImageConditionResolver(imageClassLoader, ClassInitializationSupport.singleton()); DynamicProxySupport dynamicProxySupport = new DynamicProxySupport(); ImageSingletons.add(DynamicProxyRegistry.class, dynamicProxySupport); ImageSingletons.add(RuntimeProxyCreationSupport.class, dynamicProxySupport); - ProxyRegistry proxyRegistry = new ProxyRegistry(dynamicProxySupport, imageClassLoader); + proxyRegistry = new ProxyRegistry(dynamicProxySupport, imageClassLoader); + /* + * ImageSingletons registration has to happen after registration to be available for + * SerializationFeature + */ ImageSingletons.add(ProxyRegistry.class, proxyRegistry); + } + + @Override + public void duringSetup(DuringSetupAccess a) { + DuringSetupAccessImpl access = (DuringSetupAccessImpl) a; + ImageClassLoader imageClassLoader = access.getImageClassLoader(); + ConfigurationConditionResolver conditionResolver = new NativeImageConditionResolver(imageClassLoader, ClassInitializationSupport.singleton()); + ProxyConfigurationParser parser = new ProxyConfigurationParser<>(conditionResolver, ConfigurationFiles.Options.StrictConfiguration.getValue(), proxyRegistry); loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "dynamic proxy", ConfigurationFiles.Options.DynamicProxyConfigurationFiles, ConfigurationFiles.Options.DynamicProxyConfigurationResources, diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java index 4335f139eb23..271f8268ac16 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java @@ -103,6 +103,7 @@ public class SerializationFeature implements InternalFeature { final Set> capturingClasses = ConcurrentHashMap.newKeySet(); private SerializationBuilder serializationBuilder; + private SerializationDenyRegistry serializationDenyRegistry; private int loadedConfigurations; @Override @@ -111,15 +112,24 @@ public List> getRequiredFeatures() { } @Override - public void duringSetup(DuringSetupAccess a) { - FeatureImpl.DuringSetupAccessImpl access = (FeatureImpl.DuringSetupAccessImpl) a; + public void afterRegistration(AfterRegistrationAccess a) { + FeatureImpl.AfterRegistrationAccessImpl access = (FeatureImpl.AfterRegistrationAccessImpl) a; ImageClassLoader imageClassLoader = access.getImageClassLoader(); - ConfigurationConditionResolver conditionResolver = new NativeImageConditionResolver(imageClassLoader, ClassInitializationSupport.singleton()); ConfigurationTypeResolver typeResolver = new ConfigurationTypeResolver("serialization configuration", imageClassLoader); - SerializationDenyRegistry serializationDenyRegistry = new SerializationDenyRegistry(typeResolver); + serializationDenyRegistry = new SerializationDenyRegistry(typeResolver); serializationBuilder = new SerializationBuilder(serializationDenyRegistry, access, typeResolver, ImageSingletons.lookup(ProxyRegistry.class)); + /* + * The serialization builder registration has to happen after registration so the + * ReflectionFeature can access it when creating parsers during setup. + */ ImageSingletons.add(RuntimeSerializationSupport.class, serializationBuilder); + } + @Override + public void duringSetup(DuringSetupAccess a) { + FeatureImpl.DuringSetupAccessImpl access = (FeatureImpl.DuringSetupAccessImpl) a; + ImageClassLoader imageClassLoader = access.getImageClassLoader(); + ConfigurationConditionResolver conditionResolver = new NativeImageConditionResolver(imageClassLoader, ClassInitializationSupport.singleton()); Boolean strictConfiguration = ConfigurationFiles.Options.StrictConfiguration.getValue(); SerializationConfigurationParser parser = SerializationConfigurationParser.create(true, conditionResolver, serializationBuilder, @@ -225,22 +235,15 @@ public void registerIncludingAssociatedClasses(ConfigurationCondition condition, } @Override - public void register(ConfigurationCondition condition, Class... classes) { - for (Class clazz : classes) { - registerWithTargetConstructorClass(condition, clazz, null); + public void register(ConfigurationCondition condition, Class clazz) { + if (clazz != null) { + deniedClasses.put(clazz, true); } } @Override - public void registerWithTargetConstructorClass(ConfigurationCondition condition, String className, String customTargetConstructorClassName) { - registerWithTargetConstructorClass(condition, typeResolver.resolveType(className), null); - } - - @Override - public void registerWithTargetConstructorClass(ConfigurationCondition condition, Class clazz, Class customTargetConstructorClazz) { - if (clazz != null) { - deniedClasses.put(clazz, true); - } + public void register(ConfigurationCondition condition, String className) { + this.register(condition, typeResolver.resolveType(className)); } @Override @@ -277,7 +280,7 @@ final class SerializationBuilder extends ConditionalConfigurationRegistry implem final SerializationSupport serializationSupport; private final SerializationDenyRegistry denyRegistry; private final ConfigurationTypeResolver typeResolver; - private final FeatureImpl.DuringSetupAccessImpl access; + private final FeatureImpl.AfterRegistrationAccessImpl access; private final Method disableSerialConstructorChecks; private final Method superHasAccessibleConstructor; private final Method packageEquals; @@ -285,7 +288,7 @@ final class SerializationBuilder extends ConditionalConfigurationRegistry implem private final ProxyRegistry proxyRegistry; private List pendingConstructorRegistrations; - SerializationBuilder(SerializationDenyRegistry serializationDenyRegistry, FeatureImpl.DuringSetupAccessImpl access, ConfigurationTypeResolver typeResolver, ProxyRegistry proxyRegistry) { + SerializationBuilder(SerializationDenyRegistry serializationDenyRegistry, FeatureImpl.AfterRegistrationAccessImpl access, ConfigurationTypeResolver typeResolver, ProxyRegistry proxyRegistry) { this.access = access; Class classDataSlotClazz = access.findClassByName("java.io.ObjectStreamClass$ClassDataSlot"); this.descField = ReflectionUtil.lookupField(classDataSlotClazz, "desc"); @@ -361,13 +364,6 @@ private void registerIncludingAssociatedClasses(ConfigurationCondition condition } } - @Override - public void register(ConfigurationCondition condition, Class... classes) { - for (Class clazz : classes) { - registerWithTargetConstructorClass(condition, clazz, null); - } - } - @Override public void registerLambdaCapturingClass(ConfigurationCondition condition, String lambdaCapturingClassName) { abortIfSealed(); @@ -394,12 +390,12 @@ public void registerLambdaCapturingClass(ConfigurationCondition condition, Strin public void registerProxyClass(ConfigurationCondition condition, List implementedInterfaces) { registerConditionalConfiguration(condition, (cnd) -> { Class proxyClass = proxyRegistry.createProxyClassForSerialization(implementedInterfaces); - registerWithTargetConstructorClass(cnd, proxyClass, Object.class); + register(cnd, proxyClass); }); } @Override - public void registerWithTargetConstructorClass(ConfigurationCondition condition, String targetClassName, String customTargetConstructorClassName) { + public void register(ConfigurationCondition condition, String targetClassName) { abortIfSealed(); Class serializationTargetClass = typeResolver.resolveType(targetClassName); /* With invalid streams we have to register the class for lookup */ @@ -407,20 +403,11 @@ public void registerWithTargetConstructorClass(ConfigurationCondition condition, if (serializationTargetClass == null) { return; } - - if (customTargetConstructorClassName != null) { - Class customTargetConstructorClass = typeResolver.resolveType(customTargetConstructorClassName); - if (customTargetConstructorClass == null) { - return; - } - registerWithTargetConstructorClass(condition, serializationTargetClass, customTargetConstructorClass); - } else { - registerWithTargetConstructorClass(condition, serializationTargetClass, null); - } + register(condition, serializationTargetClass); } @Override - public void registerWithTargetConstructorClass(ConfigurationCondition condition, Class serializationTargetClass, Class customTargetConstructorClass) { + public void register(ConfigurationCondition condition, Class serializationTargetClass) { abortIfSealed(); registerConditionalConfiguration(condition, (cnd) -> { /* @@ -439,18 +426,7 @@ public void registerWithTargetConstructorClass(ConfigurationCondition condition, RuntimeReflection.register(java.io.ObjectOutputStream.class); if (denyRegistry.isAllowed(serializationTargetClass)) { - if (customTargetConstructorClass != null) { - if (!customTargetConstructorClass.isAssignableFrom(serializationTargetClass)) { - LogUtils.warning("The given customTargetConstructorClass %s is not a superclass of the serialization target %s.", customTargetConstructorClass.getName(), - serializationTargetClass); - return; - } - if (ReflectionUtil.lookupConstructor(true, customTargetConstructorClass) == null) { - LogUtils.warning("The given customTargetConstructorClass %s does not declare a parameterless constructor.", customTargetConstructorClass.getName()); - return; - } - } - addOrQueueConstructorAccessor(cnd, serializationTargetClass, customTargetConstructorClass); + addOrQueueConstructorAccessors(cnd, serializationTargetClass); Class superclass = serializationTargetClass.getSuperclass(); if (superclass != null) { @@ -466,20 +442,28 @@ public void registerWithTargetConstructorClass(ConfigurationCondition condition, }); } - private void addOrQueueConstructorAccessor(ConfigurationCondition cnd, Class serializationTargetClass, Class customTargetConstructorClass) { + private void addOrQueueConstructorAccessors(ConfigurationCondition cnd, Class serializationTargetClass) { if (pendingConstructorRegistrations != null) { // cannot yet create constructor accessor -> add to pending - pendingConstructorRegistrations.add(() -> registerConstructorAccessor(cnd, serializationTargetClass, customTargetConstructorClass)); + pendingConstructorRegistrations.add(() -> registerConstructorAccessors(cnd, serializationTargetClass)); } else { // can already run the registration - registerConstructorAccessor(cnd, serializationTargetClass, customTargetConstructorClass); + registerConstructorAccessors(cnd, serializationTargetClass); } } - private void registerConstructorAccessor(ConfigurationCondition cnd, Class serializationTargetClass, Class customTargetConstructorClass) { - Optional.ofNullable(addConstructorAccessor(cnd, serializationTargetClass, customTargetConstructorClass)) + private void registerConstructorAccessors(ConfigurationCondition cnd, Class serializationTargetClass) { + serializationSupport.registerSerializationTargetClass(cnd, serializationTargetClass); + registerConstructorAccessor(cnd, serializationTargetClass, null); + for (Class superclass = serializationTargetClass; superclass != null; superclass = superclass.getSuperclass()) { + registerConstructorAccessor(cnd, serializationTargetClass, superclass); + } + } + + private void registerConstructorAccessor(ConfigurationCondition cnd, Class serializationTargetClass, Class targetConstructorClass) { + Optional.ofNullable(addConstructorAccessor(serializationTargetClass, targetConstructorClass)) .map(ReflectionUtil::lookupConstructor) - .ifPresent(methods -> ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.alwaysTrue(), false, methods)); + .ifPresent(methods -> ImageSingletons.lookup(RuntimeReflectionSupport.class).register(cnd, false, methods)); } void beforeAnalysis(Feature.BeforeAnalysisAccess beforeAnalysisAccess) { @@ -683,9 +667,7 @@ private static Constructor getExternalizableConstructor(Class serializatio return ReflectionUtil.invokeMethod(getExternalizableConstructorMethod, null, serializationTargetClass); } - private Class addConstructorAccessor(ConfigurationCondition cnd, Class serializationTargetClass, Class customTargetConstructorClass) { - serializationSupport.registerSerializationTargetClass(cnd, serializationTargetClass); - + private Class addConstructorAccessor(Class serializationTargetClass, Class customTargetConstructorClass) { // Don't generate SerializationConstructorAccessor class for Externalizable case if (Externalizable.class.isAssignableFrom(serializationTargetClass)) { try { @@ -706,13 +688,17 @@ private Class addConstructorAccessor(ConfigurationCondition cnd, Class ser VMError.guarantee(stubConstructor != null, "stubConstructor is null, calling this too early"); targetConstructor = stubConstructor; } else { - if (customTargetConstructorClass == serializationTargetClass) { - /* No custom constructor needed. Simply use existing no-arg constructor. */ - return customTargetConstructorClass; - } Constructor customConstructorToCall = null; if (customTargetConstructorClass != null) { - customConstructorToCall = ReflectionUtil.lookupConstructor(customTargetConstructorClass); + customConstructorToCall = ReflectionUtil.lookupConstructor(true, customTargetConstructorClass); + if (customConstructorToCall == null) { + /* No suitable constructor, no need to register */ + return null; + } + if (customTargetConstructorClass == serializationTargetClass) { + /* No custom constructor needed. Simply use existing no-arg constructor. */ + return customTargetConstructorClass; + } } targetConstructor = newConstructorForSerialization(serializationTargetClass, customConstructorToCall); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java index e229c0a8be34..1bb561e5a132 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java @@ -260,7 +260,7 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec var clazz = asConstantObject(b, Class.class, clazzNode); var constructor = asConstantObject(b, Constructor.class, constructorNode); if (clazz != null && constructor != null) { - b.add(ReachabilityRegistrationNode.create(() -> RuntimeSerialization.registerWithTargetConstructorClass(clazz, constructor.getDeclaringClass()), reason)); + b.add(ReachabilityRegistrationNode.create(() -> RuntimeSerialization.register(clazz), reason)); return true; } return false; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/DynamicHubPlugin.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/DynamicHubPlugin.java new file mode 100644 index 000000000000..3c65f2c8a445 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/DynamicHubPlugin.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.substitute; + +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.util.VMError; + +import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; +import jdk.graal.compiler.nodes.graphbuilderconf.NodePlugin; + +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaType; + +public class DynamicHubPlugin implements NodePlugin { + + @Override + public boolean handleNewInstance(GraphBuilderContext b, ResolvedJavaType type) { + if (b.getMetaAccess().lookupJavaType(DynamicHub.class).isAssignableFrom(type)) { + // GR-60200: This should never be reached, but is reached by serialization code. + // throw VMError.shouldNotReachHere("Using 'new' for DynamicHub is not permitted"); + } + return false; + } + + @Override + public boolean handleStoreField(GraphBuilderContext b, ValueNode object, ResolvedJavaField field, ValueNode value) { + ResolvedJavaType dynamicHubType = b.getMetaAccess().lookupJavaType(DynamicHub.class); + + if (dynamicHubType.isAssignableFrom(field.getDeclaringClass())) { + throw VMError.shouldNotReachHere("Stores to DynamicHub are not permitted, see documentation of DynamicHub."); + } + return false; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/util/HostedSubstrateUtilDefaultImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/util/HostedSubstrateUtilDefaultImpl.java new file mode 100644 index 000000000000..f08d656a0de6 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/util/HostedSubstrateUtilDefaultImpl.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.util; + +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.util.HostedSubstrateUtil; +import com.oracle.svm.hosted.ClassLoaderFeature; + +@AutomaticallyRegisteredImageSingleton(value = HostedSubstrateUtil.class) +public class HostedSubstrateUtilDefaultImpl implements HostedSubstrateUtil { + + @Override + public ClassLoader doGetRuntimeClassLoader(ClassLoader loader) { + return ClassLoaderFeature.getRuntimeClassLoader(loader); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/ByteUtils.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/ByteUtils.java new file mode 100644 index 000000000000..7e6c6a87ecc6 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/ByteUtils.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.interpreter.metadata; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; + +/** + * A collection of utility methods for dealing with bytes, particularly in byte arrays. + */ +public final class ByteUtils { + + private static final VarHandle BYTE_ARRAY_VARHANDLE = MethodHandles.arrayElementVarHandle(byte[].class); + + /** + * Gets a signed 1-byte value. + * + * @param data the array containing the data + * @param bci the start index of the value to retrieve + * @return the signed 1-byte value at index {@code bci} in array {@code data} + */ + public static int beS1(byte[] data, int bci) { + return data[bci]; + } + + /** + * Gets a signed 2-byte big-endian value. + * + * @param data the array containing the data + * @param bci the start index of the value to retrieve + * @return the signed 2-byte, big-endian, value at index {@code bci} in array {@code data} + */ + public static int beS2(byte[] data, int bci) { + return (data[bci] << 8) | (data[bci + 1] & 0xff); + } + + /** + * Gets an unsigned 1-byte value. + * + * @param data the array containing the data + * @param bci the start index of the value to retrieve + * @return the unsigned 1-byte value at index {@code bci} in array {@code data} + */ + public static int beU1(byte[] data, int bci) { + return data[bci] & 0xff; + } + + /** + * Gets an unsigned 1-byte value in a volatile fashion. + * + * @param data the array containing the data + * @param bci the start index of the value to retrieve + * @return the unsigned 1-byte value at index {@code bci} in array {@code data} + */ + public static int volatileBeU1(byte[] data, int bci) { + return ((byte) BYTE_ARRAY_VARHANDLE.getVolatile(data, bci)) & 0xff; + } + + /** + * Gets an unsigned 1-byte value in, with opaque semantics. + * + * @param data the array containing the data + * @param bci the start index of the value to retrieve + * @return the unsigned 1-byte value at index {@code bci} in array {@code data} + */ + public static int opaqueBeU1(byte[] data, int bci) { + return ((byte) BYTE_ARRAY_VARHANDLE.getOpaque(data, bci)) & 0xff; + } + + /** + * Gets an unsigned 2-byte big-endian value. + * + * @param data the array containing the data + * @param bci the start index of the value to retrieve + * @return the unsigned 2-byte, big-endian, value at index {@code bci} in array {@code data} + */ + public static int beU2(byte[] data, int bci) { + return ((data[bci] & 0xff) << 8) | (data[bci + 1] & 0xff); + } + + /** + * Gets a signed 4-byte big-endian value. + * + * @param data the array containing the data + * @param bci the start index of the value to retrieve + * @return the signed 4-byte, big-endian, value at index {@code bci} in array {@code data} + */ + public static int beS4(byte[] data, int bci) { + return (data[bci] << 24) | ((data[bci + 1] & 0xff) << 16) | ((data[bci + 2] & 0xff) << 8) | (data[bci + 3] & 0xff); + } + + /** + * Gets either a signed 2-byte or a signed 4-byte big-endian value. + * + * @param data the array containing the data + * @param bci the start index of the value to retrieve + * @param fourByte if true, this method will return a 4-byte value + * @return the signed 2 or 4-byte, big-endian, value at index {@code bci} in array {@code data} + */ + public static int beSVar(byte[] data, int bci, boolean fourByte) { + if (fourByte) { + return beS4(data, bci); + } else { + return beS2(data, bci); + } + } + + public static void opaqueWrite(byte[] array, int index, byte value) { + BYTE_ARRAY_VARHANDLE.setOpaque(array, index, value); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/BytecodeStream.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/BytecodeStream.java new file mode 100644 index 000000000000..cfb888790ab9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/BytecodeStream.java @@ -0,0 +1,429 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.interpreter.metadata; + +import static com.oracle.svm.interpreter.metadata.Bytecodes.ANEWARRAY; +import static com.oracle.svm.interpreter.metadata.Bytecodes.CHECKCAST; +import static com.oracle.svm.interpreter.metadata.Bytecodes.GETFIELD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.GETSTATIC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INSTANCEOF; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEDYNAMIC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEINTERFACE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKESPECIAL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKESTATIC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEVIRTUAL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LDC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LDC2_W; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LDC_W; +import static com.oracle.svm.interpreter.metadata.Bytecodes.MULTIANEWARRAY; +import static com.oracle.svm.interpreter.metadata.Bytecodes.NEW; +import static com.oracle.svm.interpreter.metadata.Bytecodes.PUTFIELD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.PUTSTATIC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.WIDE; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.util.VMError; + +/** + * A utility class that makes iterating over bytecodes and reading operands simpler and less + * error-prone. For example, it handles the {@link Bytecodes#WIDE} instruction and wide variants of + * instructions internally. + * + * Some accessors have a suffix indicating the type of bytecode it handles, these do NOT + * handle the {@link Bytecodes#WIDE} modifier. Methods without the numeric suffix will handle their + * {@link Bytecodes#WIDE} modifier internally, but may be slower. + */ +public final class BytecodeStream { + + private BytecodeStream() { + throw VMError.shouldNotReachHere("private constructor"); + } + + /** + * Gets the next bytecode index (no side-effects). + * + * @return the next bytecode index + */ + public static int nextBCI(byte[] code, int curBCI) { + return curBCI + lengthOf(code, curBCI); + } + + /** + * Gets the bytecode index of the end of the code. + * + * @return the index of the end of the code + */ + public static int endBCI(byte[] code) { + return code.length; + } + + /** + * Gets the current opcode. This method will never return the {@link Bytecodes#WIDE WIDE} + * opcode, but will instead return the opcode that is modified by the {@code WIDE} opcode. + * + * @return the current opcode; + * @see #opcode(byte[], int) + */ + public static int currentBC(byte[] code, int curBCI) { + int opcode = opcode(code, curBCI); + if (opcode == WIDE) { + return ByteUtils.beU1(code, curBCI + 1); + } else { + return opcode; + } + } + + /** + * Gets the current opcode in a volatile fashion. This method will never return the + * {@link Bytecodes#WIDE WIDE} opcode, but will instead return the opcode that is modified by + * the {@code WIDE} opcode. + * + * @return the current opcode; {@link Bytecodes#END} if at or beyond the end of the code + */ + public static int currentVolatileBC(byte[] code, int curBCI) { + int opcode = volatileOpcode(code, curBCI); + if (opcode == WIDE) { + return ByteUtils.volatileBeU1(code, curBCI + 1); + } else { + return opcode; + } + } + + /** + * Reads the index of a local variable for one of the load or store instructions. The WIDE + * modifier is handled internally. + * + * @return the index of the local variable + */ + public static int readLocalIndex(byte[] code, int curBCI) { + // read local variable index for load/store + if (opcode(code, curBCI) == WIDE) { + return ByteUtils.beU2(code, curBCI + 2); + } + return ByteUtils.beU1(code, curBCI + 1); + } + + /** + * Reads the index of a local variable for one of the load or store instructions. The + * {@link Bytecodes#WIDE} modifier is handled internally. + * + * @return the index of the local variable + */ + public static int readLocalIndex1(byte[] code, int curBCI) { + // read local variable index for load/store + return ByteUtils.beU1(code, curBCI + 1); + } + + /** + * Reads the index of a local variable for one of the load or store instructions. The + * {@link Bytecodes#WIDE} modifier is NOT handled internally. + * + * @return the index of the local variable + */ + public static int readLocalIndex2(byte[] code, int curBCI) { + // read local variable index for load/store + return ByteUtils.beU2(code, curBCI + 2); + } + + /** + * Read the delta for an {@link Bytecodes#IINC} bytecode. The {@link Bytecodes#WIDE} is handled. + * + * @return the delta for the {@code IINC} + */ + public static int readIncrement(byte[] code, int curBCI) { + // read the delta for the iinc bytecode + if (opcode(code, curBCI) == WIDE) { + return ByteUtils.beS2(code, curBCI + 4); + } + return ByteUtils.beS1(code, curBCI + 2); + } + + /** + * Read the delta for an {@link Bytecodes#IINC} bytecode. The {@link Bytecodes#WIDE} modifier is + * NOThandled internally. + * + * @return the delta for the {@code IINC} + */ + public static int readIncrement1(byte[] code, int curBCI) { + // read the delta for the iinc bytecode + return ByteUtils.beS1(code, curBCI + 2); + } + + /** + * Read the delta for a {@link Bytecodes#WIDE} + {@link Bytecodes#IINC} bytecode. + * + * @return the delta for the {@code WIDE IINC} + */ + public static int readIncrement2(byte[] code, int curBCI) { + // read the delta for the iinc bytecode + return ByteUtils.beS2(code, curBCI + 4); + } + + /** + * Read the destination of a {@link Bytecodes#GOTO} or {@code IF} instructions. Wide bytecodes: + * {@link Bytecodes#GOTO_W} {@link Bytecodes#JSR_W}, are handled internally. + * + * @return the destination bytecode index + */ + public static int readBranchDest(byte[] code, int curBCI) { + // reads the destination for a branch bytecode + int opcode = opcode(code, curBCI); + if (opcode == Bytecodes.GOTO_W || opcode == Bytecodes.JSR_W) { + return curBCI + ByteUtils.beS4(code, curBCI + 1); + } else { + return curBCI + ByteUtils.beS2(code, curBCI + 1); + } + } + + /** + * Read the destination of a {@link Bytecodes#GOTO_W} or {@code JSR_W} instructions. + * + * @return the destination bytecode index + */ + public static int readBranchDest4(byte[] code, int curBCI) { + // reads the destination for a branch bytecode + return curBCI + ByteUtils.beS4(code, curBCI + 1); + } + + /** + * Read the destination of a {@link Bytecodes#GOTO} or {@code IF} instructions. + * + * @return the destination bytecode index + */ + public static int readBranchDest2(byte[] code, int curBCI) { + // reads the destination for a branch bytecode + return curBCI + ByteUtils.beS2(code, curBCI + 1); + } + + /** + * Read a signed 4-byte integer from the bytecode stream at the specified bytecode index. + * + * @param bci the bytecode index + * @return the integer value + */ + public static int readInt(byte[] code, int bci) { + // reads a 4-byte signed value + return ByteUtils.beS4(code, bci); + } + + /** + * Reads an unsigned, 1-byte value from the bytecode stream at the specified bytecode index. + * + * @param bci the bytecode index + * @return the byte + */ + public static int readUByte(byte[] code, int bci) { + return ByteUtils.beU1(code, bci); + } + + /** + * Reads a 1-byte constant pool index for the current instruction. Used by + * {@link Bytecodes#LDC}. + * + * @return the constant pool index + */ + public static char readCPI1(byte[] code, int curBCI) { + return (char) ByteUtils.beU1(code, curBCI + 1); + } + + /** + * Reads a 2-byte constant pool index for the current instruction. + * + * @return the constant pool index + */ + public static char readCPI2(byte[] code, int curBCI) { + return (char) ByteUtils.beU2(code, curBCI + 1); + } + + /** + * Reads a constant pool index for the current instruction. + * + * @return the constant pool index + */ + public static char readCPI(byte[] code, int curBCI) { + if (opcode(code, curBCI) == LDC) { + return (char) ByteUtils.beU1(code, curBCI + 1); + } + return (char) ByteUtils.beU2(code, curBCI + 1); + } + + /** + * Reads a constant pool index for an invokedynamic instruction. + * + * @return the constant pool index + */ + public static int readCPI4(byte[] code, int curBCI) { + assert opcode(code, curBCI) == Bytecodes.INVOKEDYNAMIC; + return ByteUtils.beS4(code, curBCI + 1); + } + + /** + * Reads a signed, 1-byte value for the current instruction (e.g. BIPUSH). + * + * @return the byte + */ + public static byte readByte(byte[] code, int curBCI) { + return code[curBCI + 1]; + } + + /** + * Reads a signed, 2-byte short for the current instruction (e.g. SIPUSH). + * + * @return the short value + */ + public static short readShort(byte[] code, int curBCI) { + return (short) ByteUtils.beS2(code, curBCI + 1); + } + + /** + * Reads an unsigned byte for the current instruction (e.g. SIPUSH). The {@link Bytecodes#WIDE} + * modifier is NOT handled internally. + * + * @return the short value + */ + public static int opcode(byte[] code, int curBCI) { + // opcode validity is performed at verification time. + return ByteUtils.beU1(code, curBCI); + } + + /** + * Reads an unsigned byte for the current instruction (e.g. SIPUSH). The {@link Bytecodes#WIDE} + * modifier is NOT handled internally. It performs an opaque memory access. + */ + public static int opaqueOpcode(byte[] code, int curBCI) { + // opcode validity is performed at verification time. + return ByteUtils.opaqueBeU1(code, curBCI); + } + + public static int volatileOpcode(byte[] code, int curBCI) { + // opcode validity is performed at verification time. + return ByteUtils.volatileBeU1(code, curBCI); + } + + /** + * Gets the length of the current bytecode. It takes into account bytecodes with non-constant + * size and the {@link Bytecodes#WIDE} bytecode. + */ + private static int lengthOf(byte[] code, int curBCI) { + int opcode = opcode(code, curBCI); + int length = Bytecodes.lengthOf(opcode); + if (length == 0) { + switch (opcode) { + case Bytecodes.TABLESWITCH: { + return TableSwitch.size(code, curBCI); + } + case Bytecodes.LOOKUPSWITCH: { + return LookupSwitch.size(code, curBCI); + } + case WIDE: { + int opc = ByteUtils.beU1(code, curBCI + 1); + if (opc == Bytecodes.IINC) { + return 6; + } else { + return 4; // a load or store bytecode + } + } + default: + // Should rather be CompilerAsserts.neverPartOfCompilation() but this is + // reachable in SVM. + throw VMError.shouldNotReachHere(unknownVariableLengthBytecodeMessage(opcode)); + } + } + return length; + } + + private static String unknownVariableLengthBytecodeMessage(int opcode) { + return "unknown variable-length bytecode: " + opcode; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static void patchAppendixCPI(byte[] code, int curBCI, int appendixCPI) { + int opcode = opcode(code, curBCI); + switch (opcode) { + case INVOKEDYNAMIC: + code[curBCI + 3] = (byte) ((appendixCPI >> 8) & 0xFF); + code[curBCI + 4] = (byte) (appendixCPI & 0xFF); + break; + default: + throw VMError.shouldNotReachHereAtRuntime(); + } + } + + /** + * No CPI patching at runtime. + */ + @Platforms(Platform.HOSTED_ONLY.class) + public static void patchCPI(byte[] code, int curBCI, int newCPI) { + int opcode = opcode(code, curBCI); + switch (opcode) { + case WIDE: + throw VMError.intentionallyUnimplemented(); + case LDC: { + // Ensure LDC CPI fits in a byte. + VMError.guarantee(0 <= newCPI && newCPI <= 0xFF); + code[curBCI + 1] = (byte) newCPI; + break; + } + // @formatter:off + case INVOKEVIRTUAL: // fall-through + case INVOKEINTERFACE:// fall-through + case INVOKESTATIC: // fall-through + case INVOKESPECIAL: // fall-through + case INVOKEDYNAMIC: // fall-through + + case PUTFIELD: // fall-through + case PUTSTATIC: // fall-through + + case GETFIELD: // fall-through + case GETSTATIC: // fall-through + case LDC2_W: // fall-through + case LDC_W: // fall-through + case ANEWARRAY: // fall-through + case NEW: // fall-through + case MULTIANEWARRAY: // fall-through + case INSTANCEOF: // fall-through + case CHECKCAST: { + VMError.guarantee(0 <= newCPI && newCPI <= 0xFFFF); + code[curBCI + 1] = (byte) ((newCPI >> 8) & 0xFF); + code[curBCI + 2] = (byte) (newCPI & 0xFF); + break; + } + // @formatter:on + default: + throw VMError.intentionallyUnimplemented(); + } + } + + /** + * Updates the specified array with opaque memory order semantics. + */ + public static void patchOpcodeOpaque(byte[] code, int curBCI, int newOpcode) { + assert 0 <= newOpcode && newOpcode < Bytecodes.END; + ByteUtils.opaqueWrite(code, curBCI, (byte) newOpcode); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/Bytecodes.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/Bytecodes.java new file mode 100644 index 000000000000..dcab28120e7a --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/Bytecodes.java @@ -0,0 +1,760 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.interpreter.metadata; + +import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.ASSOCIATIVE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.BRANCH; +import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.COMMUTATIVE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.FALL_THROUGH; +import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.FIELD_READ; +import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.FIELD_WRITE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.INVOKE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.LOAD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.STOP; +import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.STORE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.Flags.TRAP; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +/** + * Definitions of the standard Java bytecodes defined by + * Java + * Virtual Machine Specification. + */ +public class Bytecodes { + + // @formatter:off + public static final int NOP = 0; // 0x00 + public static final int ACONST_NULL = 1; // 0x01 + public static final int ICONST_M1 = 2; // 0x02 + public static final int ICONST_0 = 3; // 0x03 + public static final int ICONST_1 = 4; // 0x04 + public static final int ICONST_2 = 5; // 0x05 + public static final int ICONST_3 = 6; // 0x06 + public static final int ICONST_4 = 7; // 0x07 + public static final int ICONST_5 = 8; // 0x08 + public static final int LCONST_0 = 9; // 0x09 + public static final int LCONST_1 = 10; // 0x0A + public static final int FCONST_0 = 11; // 0x0B + public static final int FCONST_1 = 12; // 0x0C + public static final int FCONST_2 = 13; // 0x0D + public static final int DCONST_0 = 14; // 0x0E + public static final int DCONST_1 = 15; // 0x0F + public static final int BIPUSH = 16; // 0x10 + public static final int SIPUSH = 17; // 0x11 + public static final int LDC = 18; // 0x12 + public static final int LDC_W = 19; // 0x13 + public static final int LDC2_W = 20; // 0x14 + public static final int ILOAD = 21; // 0x15 + public static final int LLOAD = 22; // 0x16 + public static final int FLOAD = 23; // 0x17 + public static final int DLOAD = 24; // 0x18 + public static final int ALOAD = 25; // 0x19 + public static final int ILOAD_0 = 26; // 0x1A + public static final int ILOAD_1 = 27; // 0x1B + public static final int ILOAD_2 = 28; // 0x1C + public static final int ILOAD_3 = 29; // 0x1D + public static final int LLOAD_0 = 30; // 0x1E + public static final int LLOAD_1 = 31; // 0x1F + public static final int LLOAD_2 = 32; // 0x20 + public static final int LLOAD_3 = 33; // 0x21 + public static final int FLOAD_0 = 34; // 0x22 + public static final int FLOAD_1 = 35; // 0x23 + public static final int FLOAD_2 = 36; // 0x24 + public static final int FLOAD_3 = 37; // 0x25 + public static final int DLOAD_0 = 38; // 0x26 + public static final int DLOAD_1 = 39; // 0x27 + public static final int DLOAD_2 = 40; // 0x28 + public static final int DLOAD_3 = 41; // 0x29 + public static final int ALOAD_0 = 42; // 0x2A + public static final int ALOAD_1 = 43; // 0x2B + public static final int ALOAD_2 = 44; // 0x2C + public static final int ALOAD_3 = 45; // 0x2D + public static final int IALOAD = 46; // 0x2E + public static final int LALOAD = 47; // 0x2F + public static final int FALOAD = 48; // 0x30 + public static final int DALOAD = 49; // 0x31 + public static final int AALOAD = 50; // 0x32 + public static final int BALOAD = 51; // 0x33 + public static final int CALOAD = 52; // 0x34 + public static final int SALOAD = 53; // 0x35 + public static final int ISTORE = 54; // 0x36 + public static final int LSTORE = 55; // 0x37 + public static final int FSTORE = 56; // 0x38 + public static final int DSTORE = 57; // 0x39 + public static final int ASTORE = 58; // 0x3A + public static final int ISTORE_0 = 59; // 0x3B + public static final int ISTORE_1 = 60; // 0x3C + public static final int ISTORE_2 = 61; // 0x3D + public static final int ISTORE_3 = 62; // 0x3E + public static final int LSTORE_0 = 63; // 0x3F + public static final int LSTORE_1 = 64; // 0x40 + public static final int LSTORE_2 = 65; // 0x41 + public static final int LSTORE_3 = 66; // 0x42 + public static final int FSTORE_0 = 67; // 0x43 + public static final int FSTORE_1 = 68; // 0x44 + public static final int FSTORE_2 = 69; // 0x45 + public static final int FSTORE_3 = 70; // 0x46 + public static final int DSTORE_0 = 71; // 0x47 + public static final int DSTORE_1 = 72; // 0x48 + public static final int DSTORE_2 = 73; // 0x49 + public static final int DSTORE_3 = 74; // 0x4A + public static final int ASTORE_0 = 75; // 0x4B + public static final int ASTORE_1 = 76; // 0x4C + public static final int ASTORE_2 = 77; // 0x4D + public static final int ASTORE_3 = 78; // 0x4E + public static final int IASTORE = 79; // 0x4F + public static final int LASTORE = 80; // 0x50 + public static final int FASTORE = 81; // 0x51 + public static final int DASTORE = 82; // 0x52 + public static final int AASTORE = 83; // 0x53 + public static final int BASTORE = 84; // 0x54 + public static final int CASTORE = 85; // 0x55 + public static final int SASTORE = 86; // 0x56 + public static final int POP = 87; // 0x57 + public static final int POP2 = 88; // 0x58 + public static final int DUP = 89; // 0x59 + public static final int DUP_X1 = 90; // 0x5A + public static final int DUP_X2 = 91; // 0x5B + public static final int DUP2 = 92; // 0x5C + public static final int DUP2_X1 = 93; // 0x5D + public static final int DUP2_X2 = 94; // 0x5E + public static final int SWAP = 95; // 0x5F + public static final int IADD = 96; // 0x60 + public static final int LADD = 97; // 0x61 + public static final int FADD = 98; // 0x62 + public static final int DADD = 99; // 0x63 + public static final int ISUB = 100; // 0x64 + public static final int LSUB = 101; // 0x65 + public static final int FSUB = 102; // 0x66 + public static final int DSUB = 103; // 0x67 + public static final int IMUL = 104; // 0x68 + public static final int LMUL = 105; // 0x69 + public static final int FMUL = 106; // 0x6A + public static final int DMUL = 107; // 0x6B + public static final int IDIV = 108; // 0x6C + public static final int LDIV = 109; // 0x6D + public static final int FDIV = 110; // 0x6E + public static final int DDIV = 111; // 0x6F + public static final int IREM = 112; // 0x70 + public static final int LREM = 113; // 0x71 + public static final int FREM = 114; // 0x72 + public static final int DREM = 115; // 0x73 + public static final int INEG = 116; // 0x74 + public static final int LNEG = 117; // 0x75 + public static final int FNEG = 118; // 0x76 + public static final int DNEG = 119; // 0x77 + public static final int ISHL = 120; // 0x78 + public static final int LSHL = 121; // 0x79 + public static final int ISHR = 122; // 0x7A + public static final int LSHR = 123; // 0x7B + public static final int IUSHR = 124; // 0x7C + public static final int LUSHR = 125; // 0x7D + public static final int IAND = 126; // 0x7E + public static final int LAND = 127; // 0x7F + public static final int IOR = 128; // 0x80 + public static final int LOR = 129; // 0x81 + public static final int IXOR = 130; // 0x82 + public static final int LXOR = 131; // 0x83 + public static final int IINC = 132; // 0x84 + public static final int I2L = 133; // 0x85 + public static final int I2F = 134; // 0x86 + public static final int I2D = 135; // 0x87 + public static final int L2I = 136; // 0x88 + public static final int L2F = 137; // 0x89 + public static final int L2D = 138; // 0x8A + public static final int F2I = 139; // 0x8B + public static final int F2L = 140; // 0x8C + public static final int F2D = 141; // 0x8D + public static final int D2I = 142; // 0x8E + public static final int D2L = 143; // 0x8F + public static final int D2F = 144; // 0x90 + public static final int I2B = 145; // 0x91 + public static final int I2C = 146; // 0x92 + public static final int I2S = 147; // 0x93 + public static final int LCMP = 148; // 0x94 + public static final int FCMPL = 149; // 0x95 + public static final int FCMPG = 150; // 0x96 + public static final int DCMPL = 151; // 0x97 + public static final int DCMPG = 152; // 0x98 + public static final int IFEQ = 153; // 0x99 + public static final int IFNE = 154; // 0x9A + public static final int IFLT = 155; // 0x9B + public static final int IFGE = 156; // 0x9C + public static final int IFGT = 157; // 0x9D + public static final int IFLE = 158; // 0x9E + public static final int IF_ICMPEQ = 159; // 0x9F + public static final int IF_ICMPNE = 160; // 0xA0 + public static final int IF_ICMPLT = 161; // 0xA1 + public static final int IF_ICMPGE = 162; // 0xA2 + public static final int IF_ICMPGT = 163; // 0xA3 + public static final int IF_ICMPLE = 164; // 0xA4 + public static final int IF_ACMPEQ = 165; // 0xA5 + public static final int IF_ACMPNE = 166; // 0xA6 + public static final int GOTO = 167; // 0xA7 + public static final int JSR = 168; // 0xA8 + public static final int RET = 169; // 0xA9 + public static final int TABLESWITCH = 170; // 0xAA + public static final int LOOKUPSWITCH = 171; // 0xAB + public static final int IRETURN = 172; // 0xAC + public static final int LRETURN = 173; // 0xAD + public static final int FRETURN = 174; // 0xAE + public static final int DRETURN = 175; // 0xAF + public static final int ARETURN = 176; // 0xB0 + public static final int RETURN = 177; // 0xB1 + public static final int GETSTATIC = 178; // 0xB2 + public static final int PUTSTATIC = 179; // 0xB3 + public static final int GETFIELD = 180; // 0xB4 + public static final int PUTFIELD = 181; // 0xB5 + public static final int INVOKEVIRTUAL = 182; // 0xB6 + public static final int INVOKESPECIAL = 183; // 0xB7 + public static final int INVOKESTATIC = 184; // 0xB8 + public static final int INVOKEINTERFACE = 185; // 0xB9 + public static final int INVOKEDYNAMIC = 186; // 0xBA + public static final int NEW = 187; // 0xBB + public static final int NEWARRAY = 188; // 0xBC + public static final int ANEWARRAY = 189; // 0xBD + public static final int ARRAYLENGTH = 190; // 0xBE + public static final int ATHROW = 191; // 0xBF + public static final int CHECKCAST = 192; // 0xC0 + public static final int INSTANCEOF = 193; // 0xC1 + public static final int MONITORENTER = 194; // 0xC2 + public static final int MONITOREXIT = 195; // 0xC3 + public static final int WIDE = 196; // 0xC4 + public static final int MULTIANEWARRAY = 197; // 0xC5 + public static final int IFNULL = 198; // 0xC6 + public static final int IFNONNULL = 199; // 0xC7 + public static final int GOTO_W = 200; // 0xC8 + public static final int JSR_W = 201; // 0xC9 + public static final int BREAKPOINT = 202; // 0xCA + + public static final int ILLEGAL = 255; + public static final int END = 256; + // @formatter:on + + /** + * The last opcode defined by the JVM specification. To iterate over all JVM bytecodes: + * + *
      +     * for (int opcode = 0; opcode <= Bytecodes.LAST_JVM_OPCODE; ++opcode) {
      +     *     //
      +     * }
      +     * 
      + */ + public static final int LAST_JVM_OPCODE = JSR_W; + + /** + * A collection of flags describing various bytecode attributes. + */ + static class Flags { + + /** + * Denotes an instruction that ends a basic block and does not let control flow fall through + * to its lexical successor. + */ + static final int STOP = 0x00000001; + + /** + * Denotes an instruction that ends a basic block and may let control flow fall through to + * its lexical successor. In practice this means it is a conditional branch. + */ + static final int FALL_THROUGH = 0x00000002; + + /** + * Denotes an instruction that has a 2 or 4 byte operand that is an offset to another + * instruction in the same method. This does not include the {@link Bytecodes#TABLESWITCH} + * or {@link Bytecodes#LOOKUPSWITCH} instructions. + */ + static final int BRANCH = 0x00000004; + + /** + * Denotes an instruction that reads the value of a static or instance field. + */ + static final int FIELD_READ = 0x00000008; + + /** + * Denotes an instruction that writes the value of a static or instance field. + */ + static final int FIELD_WRITE = 0x00000010; + + /** + * Denotes an instruction that can cause a trap. + */ + static final int TRAP = 0x00000080; + /** + * Denotes an instruction that is commutative. + */ + static final int COMMUTATIVE = 0x00000100; + /** + * Denotes an instruction that is associative. + */ + static final int ASSOCIATIVE = 0x00000200; + /** + * Denotes an instruction that loads an operand. + */ + static final int LOAD = 0x00000400; + /** + * Denotes an instruction that stores an operand. + */ + static final int STORE = 0x00000800; + /** + * Denotes the 4 INVOKE* instructions. + */ + static final int INVOKE = 0x00001000; + } + + // Performs a sanity check that none of the flags overlap. + static { + int allFlags = 0; + try { + for (Field field : Flags.class.getDeclaredFields()) { + int flagsFilter = Modifier.FINAL | Modifier.STATIC; + if ((field.getModifiers() & flagsFilter) == flagsFilter && !field.isSynthetic()) { + assert field.getType() == int.class : "Field is not int : " + field; + final int flag = field.getInt(null); + assert flag != 0; + assert (flag & allFlags) == 0 : field.getName() + " has a value conflicting with another flag"; + allFlags |= flag; + } + } + } catch (Exception e) { + throw new InternalError(e.toString()); + } + } + + /** + * An array that maps from a bytecode value to a {@link String} for the corresponding + * instruction mnemonic. + */ + private static final String[] nameArray = new String[256]; + + /** + * An array that maps from a bytecode value to the set of {@link Flags} for the corresponding + * instruction. + */ + private static final int[] flagsArray = new int[256]; + + /** + * An array that maps from a bytecode value to the length in bytes for the corresponding + * instruction. + */ + private static final int[] lengthArray = new int[256]; + + /** + * An array that maps from a bytecode value to the number of slots pushed on the stack by the + * corresponding instruction. + */ + private static final int[] stackEffectArray = new int[256]; + + // Checkstyle: stop + // @formatter:off + static { + def(NOP , "nop" , "b" , 0); + def(ACONST_NULL , "aconst_null" , "b" , 1); + def(ICONST_M1 , "iconst_m1" , "b" , 1); + def(ICONST_0 , "iconst_0" , "b" , 1); + def(ICONST_1 , "iconst_1" , "b" , 1); + def(ICONST_2 , "iconst_2" , "b" , 1); + def(ICONST_3 , "iconst_3" , "b" , 1); + def(ICONST_4 , "iconst_4" , "b" , 1); + def(ICONST_5 , "iconst_5" , "b" , 1); + def(LCONST_0 , "lconst_0" , "b" , 2); + def(LCONST_1 , "lconst_1" , "b" , 2); + def(FCONST_0 , "fconst_0" , "b" , 1); + def(FCONST_1 , "fconst_1" , "b" , 1); + def(FCONST_2 , "fconst_2" , "b" , 1); + def(DCONST_0 , "dconst_0" , "b" , 2); + def(DCONST_1 , "dconst_1" , "b" , 2); + def(BIPUSH , "bipush" , "bc" , 1); + def(SIPUSH , "sipush" , "bcc" , 1); + def(LDC , "ldc" , "bi" , 1, TRAP); + def(LDC_W , "ldc_w" , "bii" , 1, TRAP); + def(LDC2_W , "ldc2_w" , "bii" , 2, TRAP); + def(ILOAD , "iload" , "bi" , 1, LOAD); + def(LLOAD , "lload" , "bi" , 2, LOAD); + def(FLOAD , "fload" , "bi" , 1, LOAD); + def(DLOAD , "dload" , "bi" , 2, LOAD); + def(ALOAD , "aload" , "bi" , 1, LOAD); + def(ILOAD_0 , "iload_0" , "b" , 1, LOAD); + def(ILOAD_1 , "iload_1" , "b" , 1, LOAD); + def(ILOAD_2 , "iload_2" , "b" , 1, LOAD); + def(ILOAD_3 , "iload_3" , "b" , 1, LOAD); + def(LLOAD_0 , "lload_0" , "b" , 2, LOAD); + def(LLOAD_1 , "lload_1" , "b" , 2, LOAD); + def(LLOAD_2 , "lload_2" , "b" , 2, LOAD); + def(LLOAD_3 , "lload_3" , "b" , 2, LOAD); + def(FLOAD_0 , "fload_0" , "b" , 1, LOAD); + def(FLOAD_1 , "fload_1" , "b" , 1, LOAD); + def(FLOAD_2 , "fload_2" , "b" , 1, LOAD); + def(FLOAD_3 , "fload_3" , "b" , 1, LOAD); + def(DLOAD_0 , "dload_0" , "b" , 2, LOAD); + def(DLOAD_1 , "dload_1" , "b" , 2, LOAD); + def(DLOAD_2 , "dload_2" , "b" , 2, LOAD); + def(DLOAD_3 , "dload_3" , "b" , 2, LOAD); + def(ALOAD_0 , "aload_0" , "b" , 1, LOAD); + def(ALOAD_1 , "aload_1" , "b" , 1, LOAD); + def(ALOAD_2 , "aload_2" , "b" , 1, LOAD); + def(ALOAD_3 , "aload_3" , "b" , 1, LOAD); + def(IALOAD , "iaload" , "b" , -1, TRAP); + def(LALOAD , "laload" , "b" , 0, TRAP); + def(FALOAD , "faload" , "b" , -1, TRAP); + def(DALOAD , "daload" , "b" , 0, TRAP); + def(AALOAD , "aaload" , "b" , -1, TRAP); + def(BALOAD , "baload" , "b" , -1, TRAP); + def(CALOAD , "caload" , "b" , -1, TRAP); + def(SALOAD , "saload" , "b" , -1, TRAP); + def(ISTORE , "istore" , "bi" , -1, STORE); + def(LSTORE , "lstore" , "bi" , -2, STORE); + def(FSTORE , "fstore" , "bi" , -1, STORE); + def(DSTORE , "dstore" , "bi" , -2, STORE); + def(ASTORE , "astore" , "bi" , -1, STORE); + def(ISTORE_0 , "istore_0" , "b" , -1, STORE); + def(ISTORE_1 , "istore_1" , "b" , -1, STORE); + def(ISTORE_2 , "istore_2" , "b" , -1, STORE); + def(ISTORE_3 , "istore_3" , "b" , -1, STORE); + def(LSTORE_0 , "lstore_0" , "b" , -2, STORE); + def(LSTORE_1 , "lstore_1" , "b" , -2, STORE); + def(LSTORE_2 , "lstore_2" , "b" , -2, STORE); + def(LSTORE_3 , "lstore_3" , "b" , -2, STORE); + def(FSTORE_0 , "fstore_0" , "b" , -1, STORE); + def(FSTORE_1 , "fstore_1" , "b" , -1, STORE); + def(FSTORE_2 , "fstore_2" , "b" , -1, STORE); + def(FSTORE_3 , "fstore_3" , "b" , -1, STORE); + def(DSTORE_0 , "dstore_0" , "b" , -2, STORE); + def(DSTORE_1 , "dstore_1" , "b" , -2, STORE); + def(DSTORE_2 , "dstore_2" , "b" , -2, STORE); + def(DSTORE_3 , "dstore_3" , "b" , -2, STORE); + def(ASTORE_0 , "astore_0" , "b" , -1, STORE); + def(ASTORE_1 , "astore_1" , "b" , -1, STORE); + def(ASTORE_2 , "astore_2" , "b" , -1, STORE); + def(ASTORE_3 , "astore_3" , "b" , -1, STORE); + def(IASTORE , "iastore" , "b" , -3, TRAP); + def(LASTORE , "lastore" , "b" , -4, TRAP); + def(FASTORE , "fastore" , "b" , -3, TRAP); + def(DASTORE , "dastore" , "b" , -4, TRAP); + def(AASTORE , "aastore" , "b" , -3, TRAP); + def(BASTORE , "bastore" , "b" , -3, TRAP); + def(CASTORE , "castore" , "b" , -3, TRAP); + def(SASTORE , "sastore" , "b" , -3, TRAP); + def(POP , "pop" , "b" , -1); + def(POP2 , "pop2" , "b" , -2); + def(DUP , "dup" , "b" , 1); + def(DUP_X1 , "dup_x1" , "b" , 1); + def(DUP_X2 , "dup_x2" , "b" , 1); + def(DUP2 , "dup2" , "b" , 2); + def(DUP2_X1 , "dup2_x1" , "b" , 2); + def(DUP2_X2 , "dup2_x2" , "b" , 2); + def(SWAP , "swap" , "b" , 0); + def(IADD , "iadd" , "b" , -1, COMMUTATIVE | ASSOCIATIVE); + def(LADD , "ladd" , "b" , -2, COMMUTATIVE | ASSOCIATIVE); + def(FADD , "fadd" , "b" , -1, COMMUTATIVE | ASSOCIATIVE); + def(DADD , "dadd" , "b" , -2, COMMUTATIVE | ASSOCIATIVE); + def(ISUB , "isub" , "b" , -1); + def(LSUB , "lsub" , "b" , -2); + def(FSUB , "fsub" , "b" , -1); + def(DSUB , "dsub" , "b" , -2); + def(IMUL , "imul" , "b" , -1, COMMUTATIVE | ASSOCIATIVE); + def(LMUL , "lmul" , "b" , -2, COMMUTATIVE | ASSOCIATIVE); + def(FMUL , "fmul" , "b" , -1, COMMUTATIVE | ASSOCIATIVE); + def(DMUL , "dmul" , "b" , -2, COMMUTATIVE | ASSOCIATIVE); + def(IDIV , "idiv" , "b" , -1, TRAP); + def(LDIV , "ldiv" , "b" , -2, TRAP); + def(FDIV , "fdiv" , "b" , -1); + def(DDIV , "ddiv" , "b" , -2); + def(IREM , "irem" , "b" , -1, TRAP); + def(LREM , "lrem" , "b" , -2, TRAP); + def(FREM , "frem" , "b" , -1); + def(DREM , "drem" , "b" , -2); + def(INEG , "ineg" , "b" , 0); + def(LNEG , "lneg" , "b" , 0); + def(FNEG , "fneg" , "b" , 0); + def(DNEG , "dneg" , "b" , 0); + def(ISHL , "ishl" , "b" , -1); + def(LSHL , "lshl" , "b" , -1); + def(ISHR , "ishr" , "b" , -1); + def(LSHR , "lshr" , "b" , -1); + def(IUSHR , "iushr" , "b" , -1); + def(LUSHR , "lushr" , "b" , -1); + def(IAND , "iand" , "b" , -1, COMMUTATIVE | ASSOCIATIVE); + def(LAND , "land" , "b" , -2, COMMUTATIVE | ASSOCIATIVE); + def(IOR , "ior" , "b" , -1, COMMUTATIVE | ASSOCIATIVE); + def(LOR , "lor" , "b" , -2, COMMUTATIVE | ASSOCIATIVE); + def(IXOR , "ixor" , "b" , -1, COMMUTATIVE | ASSOCIATIVE); + def(LXOR , "lxor" , "b" , -2, COMMUTATIVE | ASSOCIATIVE); + def(IINC , "iinc" , "bic" , 0, LOAD | STORE); + def(I2L , "i2l" , "b" , 1); + def(I2F , "i2f" , "b" , 0); + def(I2D , "i2d" , "b" , 1); + def(L2I , "l2i" , "b" , -1); + def(L2F , "l2f" , "b" , -1); + def(L2D , "l2d" , "b" , 0); + def(F2I , "f2i" , "b" , 0); + def(F2L , "f2l" , "b" , 1); + def(F2D , "f2d" , "b" , 1); + def(D2I , "d2i" , "b" , -1); + def(D2L , "d2l" , "b" , 0); + def(D2F , "d2f" , "b" , -1); + def(I2B , "i2b" , "b" , 0); + def(I2C , "i2c" , "b" , 0); + def(I2S , "i2s" , "b" , 0); + def(LCMP , "lcmp" , "b" , -3); + def(FCMPL , "fcmpl" , "b" , -1); + def(FCMPG , "fcmpg" , "b" , -1); + def(DCMPL , "dcmpl" , "b" , -3); + def(DCMPG , "dcmpg" , "b" , -3); + def(IFEQ , "ifeq" , "boo" , -1, FALL_THROUGH | BRANCH); + def(IFNE , "ifne" , "boo" , -1, FALL_THROUGH | BRANCH); + def(IFLT , "iflt" , "boo" , -1, FALL_THROUGH | BRANCH); + def(IFGE , "ifge" , "boo" , -1, FALL_THROUGH | BRANCH); + def(IFGT , "ifgt" , "boo" , -1, FALL_THROUGH | BRANCH); + def(IFLE , "ifle" , "boo" , -1, FALL_THROUGH | BRANCH); + def(IF_ICMPEQ , "if_icmpeq" , "boo" , -2, COMMUTATIVE | FALL_THROUGH | BRANCH); + def(IF_ICMPNE , "if_icmpne" , "boo" , -2, COMMUTATIVE | FALL_THROUGH | BRANCH); + def(IF_ICMPLT , "if_icmplt" , "boo" , -2, FALL_THROUGH | BRANCH); + def(IF_ICMPGE , "if_icmpge" , "boo" , -2, FALL_THROUGH | BRANCH); + def(IF_ICMPGT , "if_icmpgt" , "boo" , -2, FALL_THROUGH | BRANCH); + def(IF_ICMPLE , "if_icmple" , "boo" , -2, FALL_THROUGH | BRANCH); + def(IF_ACMPEQ , "if_acmpeq" , "boo" , -2, COMMUTATIVE | FALL_THROUGH | BRANCH); + def(IF_ACMPNE , "if_acmpne" , "boo" , -2, COMMUTATIVE | FALL_THROUGH | BRANCH); + def(GOTO , "goto" , "boo" , 0, STOP | BRANCH); + def(JSR , "jsr" , "boo" , 0, STOP | BRANCH); + def(RET , "ret" , "bi" , 0, STOP); + def(TABLESWITCH , "tableswitch" , "" , -1, STOP); + def(LOOKUPSWITCH , "lookupswitch" , "" , -1, STOP); + def(IRETURN , "ireturn" , "b" , -1, TRAP | STOP); + def(LRETURN , "lreturn" , "b" , -2, TRAP | STOP); + def(FRETURN , "freturn" , "b" , -1, TRAP | STOP); + def(DRETURN , "dreturn" , "b" , -2, TRAP | STOP); + def(ARETURN , "areturn" , "b" , -1, TRAP | STOP); + def(RETURN , "return" , "b" , 0, TRAP | STOP); + def(GETSTATIC , "getstatic" , "bjj" , 1, TRAP | FIELD_READ); + def(PUTSTATIC , "putstatic" , "bjj" , -1, TRAP | FIELD_WRITE); + def(GETFIELD , "getfield" , "bjj" , 0, TRAP | FIELD_READ); + def(PUTFIELD , "putfield" , "bjj" , -2, TRAP | FIELD_WRITE); + def(INVOKEVIRTUAL , "invokevirtual" , "bjj" , -1, TRAP | INVOKE); + def(INVOKESPECIAL , "invokespecial" , "bjj" , -1, TRAP | INVOKE); + def(INVOKESTATIC , "invokestatic" , "bjj" , 0, TRAP | INVOKE); + def(INVOKEINTERFACE , "invokeinterface" , "bjja_", -1, TRAP | INVOKE); + def(INVOKEDYNAMIC , "invokedynamic" , "bjjjj", 0, TRAP | INVOKE); + def(NEW , "new" , "bii" , 1, TRAP); + def(NEWARRAY , "newarray" , "bc" , 0, TRAP); + def(ANEWARRAY , "anewarray" , "bii" , 0, TRAP); + def(ARRAYLENGTH , "arraylength" , "b" , 0, TRAP); + def(ATHROW , "athrow" , "b" , -1, TRAP | STOP); + def(CHECKCAST , "checkcast" , "bii" , 0, TRAP); + def(INSTANCEOF , "instanceof" , "bii" , 0, TRAP); + def(MONITORENTER , "monitorenter" , "b" , -1, TRAP); + def(MONITOREXIT , "monitorexit" , "b" , -1, TRAP); + def(WIDE , "wide" , "" , 0); + def(MULTIANEWARRAY , "multianewarray" , "biic" , 1, TRAP); + def(IFNULL , "ifnull" , "boo" , -1, FALL_THROUGH | BRANCH); + def(IFNONNULL , "ifnonnull" , "boo" , -1, FALL_THROUGH | BRANCH); + def(GOTO_W , "goto_w" , "boooo", 0, STOP | BRANCH); + def(JSR_W , "jsr_w" , "boooo", 0, STOP | BRANCH); + def(BREAKPOINT , "breakpoint" , "b" , 0, TRAP); + } + // @formatter:on + // Checkstyle: resume + + /** + * Gets the length of an instruction denoted by a given opcode. + * + * @param opcode an instruction opcode + * @return the length of the instruction denoted by {@code opcode}. If {@code opcode} is an + * illegal instruction or denotes a variable length instruction (e.g. + * {@link #TABLESWITCH}), then 0 is returned. + */ + public static int lengthOf(int opcode) { + return lengthArray[opcode & 0xff]; + } + + /** + * Gets the effect on the depth of the expression stack of an instruction denoted by a given + * opcode. + * + * @param opcode an instruction opcode + * @return the change in the stack caused by the instruction denoted by {@code opcode}. If + * {@code opcode} is an illegal instruction then 0 is returned. Note that invoke + * instructions may pop more arguments so this value is a minimum stack effect. + */ + public static int stackEffectOf(int opcode) { + return stackEffectArray[opcode & 0xff]; + } + + /** + * Gets the lower-case mnemonic for a given opcode. + * + * @param opcode an opcode + * @return the mnemonic for {@code opcode} or {@code ""} if + * {@code opcode} is not a legal opcode + */ + public static String nameOf(int opcode) throws IllegalArgumentException { + String name = nameArray[opcode & 0xff]; + if (name == null) { + return ""; + } + return name; + } + + /** + * Gets the opcode corresponding to a given mnemonic. + * + * @param name an opcode mnemonic + * @return the opcode corresponding to {@code mnemonic} + * @throws IllegalArgumentException if {@code name} does not denote a valid opcode + */ + public static int valueOf(String name) { + for (int opcode = 0; opcode < nameArray.length; ++opcode) { + if (name.equalsIgnoreCase(nameArray[opcode])) { + return opcode; + } + } + throw new IllegalArgumentException("No opcode for " + name); + } + + /** + * Determines if a given opcode is an instruction that has a 2 or 4 byte operand that is an + * offset to another instruction in the same method. This does not include the + * {@linkplain #TABLESWITCH switch} instructions. + * + * @param opcode an opcode to test + * @return {@code true} iff {@code opcode} is a branch instruction with a single operand + */ + public static boolean isBranch(int opcode) { + return (flagsArray[opcode & 0xff] & BRANCH) != 0; + } + + /** + * Determines if a given opcode denotes a conditional branch. + * + * @param opcode + * @return {@code true} iff {@code opcode} is a conditional branch + */ + public static boolean isConditionalBranch(int opcode) { + return (flagsArray[opcode & 0xff] & FALL_THROUGH) != 0; + } + + /** + * Determines if a given opcode denotes an invoke. + * + * @param opcode + * @return {@code true} iff {@code opcode} is an invoke + */ + public static boolean isInvoke(int opcode) { + return (flagsArray[opcode & 0xff] & INVOKE) != 0; + } + + /** + * Gets the arithmetic operator name for a given opcode. If {@code opcode} does not denote an + * arithmetic instruction, then the {@linkplain #nameOf(int) name} of the opcode is returned + * instead. + * + * @param op an opcode + * @return the arithmetic operator name + */ + public static String operator(int op) { + // Checkstyle: stop + switch (op) { + // arithmetic ops + case IADD: // fall through + case LADD: // fall through + case FADD: // fall through + case DADD: + return "+"; + case ISUB: // fall through + case LSUB: // fall through + case FSUB: // fall through + case DSUB: + return "-"; + case IMUL: // fall through + case LMUL: // fall through + case FMUL: // fall through + case DMUL: + return "*"; + case IDIV: // fall through + case LDIV: // fall through + case FDIV: // fall through + case DDIV: + return "/"; + case IREM: // fall through + case LREM: // fall through + case FREM: // fall through + case DREM: + return "%"; + // shift ops + case ISHL: // fall through + case LSHL: + return "<<"; + case ISHR: // fall through + case LSHR: + return ">>"; + case IUSHR: // fall through + case LUSHR: + return ">>>"; + // logic ops + case IAND: // fall through + case LAND: + return "&"; + case IOR: // fall through + case LOR: + return "|"; + case IXOR: // fall through + case LXOR: + return "^"; + } + // Checkstyle: resume + return nameOf(op); + } + + /** + * Defines a bytecode by entering it into the arrays that record its name, length and flags. + * + * @param name instruction name (should be lower case) + * @param format encodes the length of the instruction + */ + private static void def(int opcode, String name, String format, int stackEffect) { + def(opcode, name, format, stackEffect, 0); + } + + /** + * Defines a bytecode by entering it into the arrays that record its name, length and flags. + * + * @param name instruction name (lower case) + * @param format encodes the length of the instruction + * @param flags the set of {@link Flags} associated with the instruction + */ + private static void def(int opcode, String name, String format, int stackEffect, int flags) { + assert nameArray[opcode] == null : "opcode " + opcode + " is already bound to name " + nameArray[opcode]; + nameArray[opcode] = name; + int instructionLength = format.length(); + lengthArray[opcode] = instructionLength; + stackEffectArray[opcode] = stackEffect; + Bytecodes.flagsArray[opcode] = flags; + + assert !isConditionalBranch(opcode) || isBranch(opcode) : "a conditional branch must also be a branch"; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterConstantPool.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterConstantPool.java new file mode 100644 index 000000000000..3b3fd16a8268 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterConstantPool.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata; + +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEDYNAMIC; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.heap.UnknownObjectField; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.serialization.VisibleForSerialization; + +import jdk.vm.ci.meta.ConstantPool; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaField; +import jdk.vm.ci.meta.JavaMethod; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.Signature; + +public final class InterpreterConstantPool implements ConstantPool { + + private final InterpreterResolvedObjectType holder; + // Assigned after analysis. + @UnknownObjectField(types = Object[].class) private Object[] entries; + + Object at(int cpi) { + if (cpi == 0) { + // 0 implies unknown (!= unresolved) e.g. unknown class, field, method ... + // In this case it's not possible to even provide a name or symbolic representation for + // what's missing. + // Index 0 must be handled by the resolution methods e.g. resolveType, resolveMethod ... + // where an appropriate error should be thrown. + throw VMError.shouldNotReachHere("Cannot resolve CP entry 0"); + } + return entries[cpi]; + } + + private InterpreterConstantPool(InterpreterResolvedObjectType holder, Object[] entries) { + this.holder = MetadataUtil.requireNonNull(holder); + this.entries = MetadataUtil.requireNonNull(entries); + } + + @VisibleForSerialization + public static InterpreterConstantPool create(InterpreterResolvedObjectType holder, Object[] entries) { + return new InterpreterConstantPool(holder, entries); + } + + @Override + public int length() { + return entries.length; + } + + @Override + public JavaField lookupField(int cpi, ResolvedJavaMethod method, int opcode) { + return (JavaField) at(cpi); + } + + @Override + public JavaMethod lookupMethod(int cpi, int opcode) { + return (JavaMethod) at(cpi); + } + + @Override + public JavaMethod lookupMethod(int cpi, int opcode, ResolvedJavaMethod caller) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public JavaType lookupType(int cpi, int opcode) { + return (JavaType) at(cpi); + } + + @Override + public Object lookupConstant(int cpi) { + Object entry = at(cpi); + if (entry instanceof JavaConstant) { + return entry; + } else if (entry instanceof JavaType) { + return entry; + } + throw VMError.shouldNotReachHereAtRuntime(); + } + + @Override + public Object lookupConstant(int cpi, boolean resolve) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public JavaConstant lookupAppendix(int cpi, int opcode) { + assert opcode == INVOKEDYNAMIC; + return (JavaConstant) at(cpi); + } + + @VisibleForSerialization + @Platforms(Platform.HOSTED_ONLY.class) + public Object[] getEntries() { + return entries; + } + + public InterpreterResolvedObjectType getHolder() { + return holder; + } + + // region Unimplemented methods + + @Override + public void loadReferencedType(int cpi, int opcode) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public JavaType lookupReferencedType(int cpi, int opcode) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public String lookupUtf8(int cpi) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public Signature lookupSignature(int cpi) { + throw VMError.intentionallyUnimplemented(); + } + + // endregion Unimplemented methods +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaField.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaField.java new file mode 100644 index 000000000000..9a8ac79393cd --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaField.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata; + +import java.lang.annotation.Annotation; + +import com.oracle.svm.core.hub.DynamicHub; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.PrimitiveConstant; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.util.VMError; + +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.ResolvedJavaField; + +public final class InterpreterResolvedJavaField implements ResolvedJavaField { + + // Computed after analysis. + private int offset; + private final int modifiers; + private final String name; + + private final InterpreterResolvedJavaType type; + + private final InterpreterResolvedObjectType declaringClass; + + private JavaConstant constantValue; + + @Platforms(Platform.HOSTED_ONLY.class) private ResolvedJavaField originalField; + + /** + * Ensures that the field metadata is kept for the interpreter, without forcing it into the + * image. Reachability still depends on the declaring class and field type reachability. + * Artificial reachability is useful for fields that are reachable at build-time e.g. inlined or + * substituted. They are never accessed again at runtime in compiled code, but the interpreter + * may still access them e.g. $assertionsDisabled. + */ + @Platforms(Platform.HOSTED_ONLY.class) private boolean artificiallyReachable; + + @Platforms(Platform.HOSTED_ONLY.class) + private InterpreterResolvedJavaField(ResolvedJavaField originalField, String name, int modifiers, InterpreterResolvedJavaType type, InterpreterResolvedObjectType declaringClass, int offset, + JavaConstant constant) { + this.originalField = originalField; + this.name = MetadataUtil.requireNonNull(name); + this.modifiers = modifiers; + this.type = MetadataUtil.requireNonNull(type); + this.declaringClass = MetadataUtil.requireNonNull(declaringClass); + this.offset = offset; + this.constantValue = constant; + } + + private InterpreterResolvedJavaField(String name, int modifiers, InterpreterResolvedJavaType type, InterpreterResolvedObjectType declaringClass, int offset, JavaConstant constant) { + this.name = MetadataUtil.requireNonNull(name); + this.modifiers = modifiers; + this.type = MetadataUtil.requireNonNull(type); + this.declaringClass = MetadataUtil.requireNonNull(declaringClass); + this.offset = offset; + this.constantValue = constant; + } + + public static InterpreterResolvedJavaField create(String name, int modifiers, InterpreterResolvedJavaType type, InterpreterResolvedObjectType declaringClass, int offset, JavaConstant constant) { + return new InterpreterResolvedJavaField(name, modifiers, type, declaringClass, offset, constant); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static InterpreterResolvedJavaField create(ResolvedJavaField originalField, String name, int modifiers, InterpreterResolvedJavaType type, InterpreterResolvedObjectType declaringClass, + int offset, JavaConstant constant) { + return new InterpreterResolvedJavaField(originalField, name, modifiers, type, declaringClass, offset, constant); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public ResolvedJavaField getOriginalField() { + return originalField; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setUnmaterializedConstant(JavaConstant constant) { + assert JavaConstant.NULL_POINTER.equals(constant) || constant instanceof PrimitiveConstant || constant instanceof ReferenceConstant; + this.offset = InterpreterResolvedJavaField.FIELD_UNMATERIALIZED; + this.constantValue = constant; + } + + public static final int FIELD_UNMATERIALIZED = -10; + + public boolean isUnmaterializedConstant() { + return this.offset == FIELD_UNMATERIALIZED; + } + + /** + * A field is undefined when it is unmaterialized, and the value is not preserved for the + * interpreter. Examples of undefined fields include: {@link jdk.graal.compiler.word.Word} + * subtypes, {@link DynamicHub}'s vtable. + */ + public boolean isUndefined() { + return this.isUnmaterializedConstant() && + this.getUnmaterializedConstant().getJavaKind() == JavaKind.Illegal; + } + + public void setOffset(int offset) { + this.offset = offset; + } + + @Override + public int getModifiers() { + return modifiers; + } + + @Override + public int getOffset() { + return offset; + } + + @Override + public String getName() { + return name; + } + + @Override + public InterpreterResolvedJavaType getType() { + return type; + } + + @Override + public InterpreterResolvedObjectType getDeclaringClass() { + return declaringClass; + } + + public JavaConstant getUnmaterializedConstant() { + assert offset == FIELD_UNMATERIALIZED; + // constantValue can be "Illegal" for some folded constants, for which the value is not + // stored in the image heap. + // Also take into account WordBase types, which have an Object kind, but the constantValue + // is a long. + assert this.getType().isWordType() || + constantValue == JavaConstant.ILLEGAL || getJavaKind() == constantValue.getJavaKind(); + return constantValue; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public boolean isArtificiallyReachable() { + return artificiallyReachable; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void markAsArtificiallyReachable() { + this.artificiallyReachable = true; + } + + @Override + public String toString() { + return "InterpreterResolvedJavaField"; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof InterpreterResolvedJavaField)) { + return false; + } + InterpreterResolvedJavaField that = (InterpreterResolvedJavaField) other; + return name.equals(that.name) && declaringClass.equals(that.declaringClass) && type.equals(that.type); + } + + @Override + public int hashCode() { + int result = MetadataUtil.hashCode(name); + result = 31 * result + MetadataUtil.hashCode(declaringClass); + result = 31 * result + MetadataUtil.hashCode(type); + return result; + } + + // region Unimplemented methods + + @Override + public boolean isInternal() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public boolean isSynthetic() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public T getAnnotation(Class annotationClass) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public Annotation[] getAnnotations() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public Annotation[] getDeclaredAnnotations() { + throw VMError.intentionallyUnimplemented(); + } + + // endregion Unimplemented methods +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java new file mode 100644 index 000000000000..8c85a557ca74 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java @@ -0,0 +1,581 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata; + +import static com.oracle.svm.interpreter.metadata.Bytecodes.BREAKPOINT; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.HashSet; +import java.util.Set; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.FunctionPointerHolder; +import com.oracle.svm.core.meta.MethodPointer; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.serialization.VisibleForSerialization; + +import jdk.vm.ci.meta.Constant; +import jdk.vm.ci.meta.ExceptionHandler; +import jdk.vm.ci.meta.LineNumberTable; +import jdk.vm.ci.meta.Local; +import jdk.vm.ci.meta.LocalVariableTable; +import jdk.vm.ci.meta.ProfilingInfo; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.SpeculationLog; + +/** + * Encapsulates resolved methods used under close-world assumptions, compiled and interpretable, but + * also abstract methods for vtable calls. + */ +public final class InterpreterResolvedJavaMethod implements ResolvedJavaMethod { + + public static final LocalVariableTable EMPTY_LOCAL_VARIABLE_TABLE = new LocalVariableTable(new Local[0]); + + public static final int UNKNOWN_METHOD_ID = 0; + + // Should be final (not its contents, it can be patched with BREAKPOINT). + // These are the bytecodes executed by the interpreter e.g. can be patched with BREAKPOINT. + private byte[] interpretedCode; + private final String name; + private final int maxLocals; + private final int maxStackSize; + private final int modifiers; + + @Platforms(Platform.HOSTED_ONLY.class) // + private ResolvedJavaMethod originalMethod; + + private final InterpreterResolvedObjectType declaringClass; + private final InterpreterUnresolvedSignature signature; + + private final LineNumberTable lineNumberTable; + + private ExceptionHandler[] exceptionHandlers; + + private LocalVariableTable localVariableTable; + + private ReferenceConstant nativeEntryPoint; + + // Token set by the toggle of method enter/exit events. + private volatile Object interpreterExecToken; + + public static class InlinedBy { + public InterpreterResolvedJavaMethod holder; + public Set inliners; + + public InlinedBy(InterpreterResolvedJavaMethod holder, Set inliners) { + this.holder = holder; + this.inliners = inliners; + } + } + + protected InlinedBy inlinedBy; + + public static final int VTBL_NO_ENTRY = -1; + public static final int VTBL_ONE_IMPL = -2; + private int vtableIndex = VTBL_NO_ENTRY; + private InterpreterResolvedJavaMethod oneImplementation; + + /* slot in GOT */ + private int gotOffset; + + public static final int EST_NO_ENTRY = -5; + /* slot in EST (EnterStubTable) */ + private int enterStubOffset = EST_NO_ENTRY; + + /** + * Unique identifier for methods in compiled frames. + *

      + * Only valid if != 0, 0 means unknown. Allows to precisely extract the + * {@link InterpreterResolvedJavaMethod interpreter method instance} from a compiled frame. + */ + private int methodId; + + @Platforms(Platform.HOSTED_ONLY.class) public boolean needMethodBody; + + // Only called during universe building + @Platforms(Platform.HOSTED_ONLY.class) + private InterpreterResolvedJavaMethod(ResolvedJavaMethod originalMethod, String name, int maxLocals, int maxStackSize, int modifiers, InterpreterResolvedObjectType declaringClass, + InterpreterUnresolvedSignature signature, + byte[] code, ExceptionHandler[] exceptionHandlers, LineNumberTable lineNumberTable, LocalVariableTable localVariableTable, + ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId) { + this(name, maxLocals, maxStackSize, modifiers, declaringClass, signature, code, exceptionHandlers, lineNumberTable, localVariableTable, nativeEntryPoint, vtableIndex, gotOffset, + enterStubOffset, methodId); + this.originalMethod = originalMethod; + this.needMethodBody = false; + this.inlinedBy = new InterpreterResolvedJavaMethod.InlinedBy(this, new HashSet<>()); + } + + private InterpreterResolvedJavaMethod(String name, int maxLocals, int maxStackSize, int modifiers, InterpreterResolvedObjectType declaringClass, InterpreterUnresolvedSignature signature, + byte[] code, ExceptionHandler[] exceptionHandlers, LineNumberTable lineNumberTable, LocalVariableTable localVariableTable, + ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId) { + this.name = name; + this.maxLocals = maxLocals; + this.maxStackSize = maxStackSize; + this.modifiers = modifiers; + this.declaringClass = declaringClass; + this.signature = signature; + this.interpretedCode = code; + this.exceptionHandlers = exceptionHandlers; + this.lineNumberTable = lineNumberTable; + this.localVariableTable = localVariableTable; + + this.nativeEntryPoint = nativeEntryPoint; + this.vtableIndex = vtableIndex; + this.gotOffset = gotOffset; + this.enterStubOffset = enterStubOffset; + this.methodId = methodId; + this.inlinedBy = new InlinedBy(this, new HashSet<>()); + } + + @VisibleForSerialization + public static InterpreterResolvedJavaMethod create(String name, int maxLocals, int maxStackSize, int modifiers, InterpreterResolvedObjectType declaringClass, + InterpreterUnresolvedSignature signature, + byte[] code, ExceptionHandler[] exceptionHandlers, LineNumberTable lineNumberTable, LocalVariableTable localVariableTable, + ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId) { + return new InterpreterResolvedJavaMethod(name, maxLocals, maxStackSize, modifiers, declaringClass, signature, code, + exceptionHandlers, lineNumberTable, localVariableTable, nativeEntryPoint, vtableIndex, gotOffset, enterStubOffset, methodId); + } + + // Only called during universe building + @Platforms(Platform.HOSTED_ONLY.class) + public static InterpreterResolvedJavaMethod create(ResolvedJavaMethod originalMethod, String name, int maxLocals, int maxStackSize, int modifiers, InterpreterResolvedObjectType declaringClass, + InterpreterUnresolvedSignature signature, + byte[] code, ExceptionHandler[] exceptionHandlers, LineNumberTable lineNumberTable, LocalVariableTable localVariableTable, + ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId) { + return new InterpreterResolvedJavaMethod(originalMethod, name, maxLocals, maxStackSize, modifiers, declaringClass, signature, code, + exceptionHandlers, lineNumberTable, localVariableTable, nativeEntryPoint, vtableIndex, gotOffset, enterStubOffset, methodId); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public boolean needsMethodBody() { + return needMethodBody; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public ResolvedJavaMethod getOriginalMethod() { + return originalMethod; + } + + /** + * Returns the bytecodes executed by the interpreter, may include BREAKPOINT and other + * non-standard bytecodes used by the interpreter. For a spec-compliant, without BREAKPOINT and + * non-standard bytecodes use {@link #getCode()} + */ + public byte[] getInterpretedCode() { + return interpretedCode; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setCode(byte[] code) { + VMError.guarantee(originalCode == null); + this.interpretedCode = code; + } + + private volatile byte[] originalCode; + + public int getOriginalOpcodeAt(int bci) { + return getCode()[bci] & 0xFF; + } + + @Override + public byte[] getCode() { + if (interpretedCode == null) { + return null; + } + byte[] result = originalCode; + if (result == null) { + synchronized (this) { + result = originalCode; + if (result == null) { + originalCode = result = getInterpretedCode().clone(); + verifySanitizedCode(result); // assert + } + } + } + return result; + } + + private static void verifySanitizedCode(byte[] code) { + for (int bci = 0; bci < BytecodeStream.endBCI(code); bci = BytecodeStream.nextBCI(code, bci)) { + int currentBC = BytecodeStream.currentBC(code, bci); + VMError.guarantee(Bytecodes.BREAKPOINT != currentBC); + } + } + + @Override + public int getCodeSize() { + if (interpretedCode == null) { + return 0; + } + return interpretedCode.length; + } + + @Override + public String getName() { + return name; + } + + @Override + public InterpreterResolvedObjectType getDeclaringClass() { + return declaringClass; + } + + @Override + public InterpreterUnresolvedSignature getSignature() { + return signature; + } + + @Override + public int getMaxLocals() { + return maxLocals; + } + + @Override + public int getMaxStackSize() { + return maxStackSize; + } + + @Override + public boolean isClassInitializer() { + return "".equals(getName()) && isStatic(); + } + + @Override + public boolean isConstructor() { + return "".equals(getName()) && !isStatic(); + } + + @Override + public ExceptionHandler[] getExceptionHandlers() { + ExceptionHandler[] result = exceptionHandlers; + VMError.guarantee(result != null); + return result; + } + + @Override + public InterpreterConstantPool getConstantPool() { + return declaringClass.getConstantPool(); + } + + @Override + public LineNumberTable getLineNumberTable() { + return lineNumberTable; + } + + @Override + public LocalVariableTable getLocalVariableTable() { + return localVariableTable; + } + + @Override + public int getModifiers() { + return modifiers; + } + + @Override + public String toString() { + return "InterpreterResolvedJavaMethod"; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setExceptionHandlers(ExceptionHandler[] exceptionHandlers) { + this.exceptionHandlers = MetadataUtil.requireNonNull(exceptionHandlers); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setLocalVariableTable(LocalVariableTable localVariableTable) { + this.localVariableTable = MetadataUtil.requireNonNull(localVariableTable); + } + + private void patchOpcode(int bci, int newOpcode) { + BytecodeStream.patchOpcodeOpaque(interpretedCode, bci, newOpcode); + } + + public void ensureCanSetBreakpointAt(int bci) { + if (!hasBytecodes()) { + throw new IllegalArgumentException("Cannot set breakpoint: method " + name + " doesn't have bytecodes"); + } + if (bci < 0 || getCodeSize() <= bci) { + throw new IllegalArgumentException("Cannot set breakpoint: BCI out of bounds"); + } + if (!isValidBCI(getCode(), bci)) { + throw new IllegalArgumentException("Cannot set breakpoint: targetBCI is not a valid first opcode"); + } + } + + public void toggleBreakpoint(int bci, boolean enabled) { + ensureCanSetBreakpointAt(bci); + if (enabled) { + patchOpcode(bci, BREAKPOINT); + } else { + int originalOpcode = getOriginalOpcodeAt(bci); + patchOpcode(bci, originalOpcode); + } + } + + public static boolean isValidBCI(byte[] code, int targetBCI) { + for (int bci = 0; bci < BytecodeStream.endBCI(code); bci = BytecodeStream.nextBCI(code, bci)) { + if (bci == targetBCI) { + return true; + } + } + return false; + } + + /** + * Unique identifier for methods in compiled frames. + *

      + * Only valid if != 0, 0 means unknown. Allows to precisely extract the + * {@link InterpreterResolvedJavaMethod interpreter method instance} from a compiled frame. + */ + public int getMethodId() { + return methodId; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setMethodId(int methodId) { + assert methodId >= 0; + this.methodId = methodId; + } + + public void setGOTOffset(int gotOffset) { + this.gotOffset = gotOffset; + } + + public int getGotOffset() { + return gotOffset; + } + + public void setEnterStubOffset(int offset) { + this.enterStubOffset = offset; + } + + public int getEnterStubOffset() { + return enterStubOffset; + } + + public boolean hasNativeEntryPoint() { + return nativeEntryPoint != null; + } + + public MethodPointer getNativeEntryPoint() { + if (nativeEntryPoint == null) { + return WordFactory.nullPointer(); + } + return (MethodPointer) nativeEntryPoint.getReferent().functionPointer; + } + + public ReferenceConstant getNativeEntryPointHolderConstant() { + return nativeEntryPoint; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setNativeEntryPoint(MethodPointer nativeEntryPoint) { + if (this.nativeEntryPoint != null && nativeEntryPoint != null) { + /* already set, verify if it's the same */ + ResolvedJavaMethod setMethod = ((MethodPointer) this.nativeEntryPoint.getReferent().functionPointer).getMethod(); + VMError.guarantee(setMethod.equals(nativeEntryPoint.getMethod())); + return; + } + + if (nativeEntryPoint == null) { + this.nativeEntryPoint = null; + } else { + this.nativeEntryPoint = ReferenceConstant.createFromNonNullReference(new FunctionPointerHolder(nativeEntryPoint)); + } + } + + public int getVTableIndex() { + return vtableIndex; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setVTableIndex(int vtableIndex) { + VMError.guarantee(vtableIndex == VTBL_NO_ENTRY || (!isStatic() && !isConstructor())); + if (vtableIndex >= 0) { + VMError.guarantee(!isFinal()); + } + this.vtableIndex = vtableIndex; + } + + public boolean hasVTableIndex() { + return vtableIndex != VTBL_NO_ENTRY && vtableIndex != VTBL_ONE_IMPL; + } + + public void setOneImplementation(InterpreterResolvedJavaMethod oneImplementation) { + this.oneImplementation = oneImplementation; + } + + public InterpreterResolvedJavaMethod getOneImplementation() { + /* if VTBL_ONE_IMPL is set, oneImplementation must have an assignment */ + VMError.guarantee(vtableIndex != VTBL_ONE_IMPL || oneImplementation != null); + return oneImplementation; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof InterpreterResolvedJavaMethod)) { + return false; + } + InterpreterResolvedJavaMethod that = (InterpreterResolvedJavaMethod) other; + return name.equals(that.name) && declaringClass.equals(that.declaringClass) && signature.equals(that.signature); + } + + @Override + public int hashCode() { + int result = MetadataUtil.hashCode(name); + result = 31 * result + MetadataUtil.hashCode(declaringClass); + result = 31 * result + MetadataUtil.hashCode(signature); + return result; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public boolean isInterpreterExecutable() { + return hasBytecodes(); + } + + public Set getInlinedBy() { + return inlinedBy.inliners; + } + + public void addInliner(InterpreterResolvedJavaMethod inliner) { + inlinedBy.inliners.add(inliner); + } + + public Object getInterpreterExecToken() { + return interpreterExecToken; + } + + public void setInterpreterExecToken(Object interpreterExecToken) { + this.interpreterExecToken = interpreterExecToken; + } + + // region Unimplemented methods + + @Override + public Annotation[][] getParameterAnnotations() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public Type[] getGenericParameterTypes() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public boolean canBeInlined() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public boolean hasNeverInlineDirective() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public boolean shouldBeInlined() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public Constant getEncoding() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public boolean isInVirtualMethodTable(ResolvedJavaType resolved) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public SpeculationLog getSpeculationLog() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public T getAnnotation(Class annotationClass) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public Annotation[] getAnnotations() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public Annotation[] getDeclaredAnnotations() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public boolean isSynthetic() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public boolean isVarArgs() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public boolean isBridge() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public boolean isDefault() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public boolean canBeStaticallyBound() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public StackTraceElement asStackTraceElement(int bci) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public ProfilingInfo getProfilingInfo(boolean includeNormal, boolean includeOSR) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public void reprofile() { + throw VMError.intentionallyUnimplemented(); + } + + // endregion Unimplemented methods +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaType.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaType.java new file mode 100644 index 000000000000..4ba41bb8163a --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaType.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Modifier; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.word.WordBase; + +import com.oracle.svm.core.util.VMError; + +import jdk.vm.ci.meta.Assumptions; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + +/** + * Represents a primitive or reference resolved Java type, including additional capabilities of the + * closed world e.g. instantiable, instantiated, effectively final ... + */ +public abstract class InterpreterResolvedJavaType implements ResolvedJavaType { + + private final String name; + private final Class clazz; + private final JavaConstant clazzConstant; + private final boolean isWordType; + private volatile boolean methodEnterEventEnabled; + private volatile boolean methodExitEventEnabled; + + // Only called at build time universe creation. + @Platforms(Platform.HOSTED_ONLY.class) + protected InterpreterResolvedJavaType(String name, Class javaClass) { + this.name = MetadataUtil.requireNonNull(name); + this.clazzConstant = null; + this.clazz = MetadataUtil.requireNonNull(javaClass); + this.isWordType = WordBase.class.isAssignableFrom(javaClass); + } + + // Called by the interpreter. + protected InterpreterResolvedJavaType(String name, Class javaClass, boolean isWordType) { + this.name = MetadataUtil.requireNonNull(name); + this.clazzConstant = null; + this.clazz = MetadataUtil.requireNonNull(javaClass); + this.isWordType = isWordType; + } + + protected InterpreterResolvedJavaType(String name, JavaConstant clazzConstant, boolean isWordType) { + this.name = MetadataUtil.requireNonNull(name); + this.clazzConstant = MetadataUtil.requireNonNull(clazzConstant); + this.clazz = null; + this.isWordType = isWordType; + } + + @Override + public final String getName() { + return name; + } + + // This is only here for performance, otherwise the clazzConstant must be unwrapped every time. + public final Class getJavaClass() { + return MetadataUtil.requireNonNull(clazz); + } + + public final boolean isWordType() { + return isWordType; + } + + @Override + public final boolean isPrimitive() { + return this instanceof InterpreterResolvedPrimitiveType; + } + + @Override + public final boolean isInterface() { + return Modifier.isInterface(getModifiers()); + } + + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + if (obj instanceof InterpreterResolvedJavaType that) { + if (this.clazz != null) { + return this.clazz == that.clazz; + } + // We have no access to the classes (debugger side), only opaque constants. + assert this.clazzConstant != null && that.clazzConstant != null; + return this.clazzConstant.equals(that.clazzConstant); + } else { + return false; + } + } + + @Override + public final int hashCode() { + return getName().hashCode(); + } + + @Override + public final String toString() { + return "InterpreterResolvedJavaType<" + getName() + ">"; + } + + public void toggleMethodEnterEvent(boolean enable) { + methodEnterEventEnabled = enable; + } + + public void toggleMethodExitEvent(boolean enable) { + methodExitEventEnabled = enable; + } + + public boolean isMethodEnterEvent() { + return methodEnterEventEnabled; + } + + public boolean isMethodExitEvent() { + return methodExitEventEnabled; + } + + // region Unimplemented methods + + @Override + public final boolean hasFinalizer() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final Assumptions.AssumptionResult hasFinalizableSubclass() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final boolean isInstanceClass() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final boolean isEnum() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final boolean isInitialized() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final void initialize() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final boolean isLinked() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final boolean isInstance(JavaConstant obj) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final ResolvedJavaType getSingleImplementor() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final ResolvedJavaType findLeastCommonAncestor(ResolvedJavaType otherType) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final Assumptions.AssumptionResult findLeafConcreteSubtype() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final ResolvedJavaType resolve(ResolvedJavaType accessingClass) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final ResolvedJavaMethod resolveMethod(ResolvedJavaMethod method, ResolvedJavaType callerType) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final Assumptions.AssumptionResult findUniqueConcreteMethod(ResolvedJavaMethod method) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final ResolvedJavaField[] getInstanceFields(boolean includeSuperclasses) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final ResolvedJavaField[] getStaticFields() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final ResolvedJavaField findInstanceFieldWithOffset(long offset, JavaKind expectedKind) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final InterpreterResolvedObjectType getArrayClass() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final boolean isLocal() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final boolean isMember() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final ResolvedJavaType getEnclosingType() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final ResolvedJavaMethod[] getDeclaredConstructors() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final ResolvedJavaMethod[] getDeclaredMethods() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final ResolvedJavaMethod getClassInitializer() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final boolean isCloneableWithAllocation() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final T getAnnotation(Class annotationClass) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final Annotation[] getAnnotations() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public final Annotation[] getDeclaredAnnotations() { + throw VMError.intentionallyUnimplemented(); + } + + // endregion Unimplemented methods +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedObjectType.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedObjectType.java new file mode 100644 index 000000000000..58197d1b1c6e --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedObjectType.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.word.WordBase; + +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.serialization.VisibleForSerialization; + +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaType; + +public final class InterpreterResolvedObjectType extends InterpreterResolvedJavaType { + + private final InterpreterResolvedJavaType componentType; + private final int modifiers; + private final InterpreterResolvedObjectType superclass; + private final InterpreterResolvedObjectType[] interfaces; + + // Populated after analysis. + private InterpreterConstantPool constantPool; + + @Platforms(Platform.HOSTED_ONLY.class) private ResolvedJavaType originalType; + + private final String sourceFileName; + + public static class VTableHolder { + public InterpreterResolvedObjectType holder; + public InterpreterResolvedJavaMethod[] vtable; + + public VTableHolder(InterpreterResolvedObjectType holder, InterpreterResolvedJavaMethod[] vtable) { + this.holder = holder; + this.vtable = vtable; + } + } + + private VTableHolder vtableHolder = null; + + // Debugger side constructor, class is an opaque JavaConstant. + private InterpreterResolvedObjectType(String name, int modifiers, InterpreterResolvedJavaType componentType, InterpreterResolvedObjectType superclass, InterpreterResolvedObjectType[] interfaces, + InterpreterConstantPool constantPool, + JavaConstant clazzConstant, + boolean isWordType, String sourceFileName) { + super(name, clazzConstant, isWordType); + this.modifiers = modifiers; + this.componentType = componentType; + this.superclass = superclass; + this.interfaces = interfaces; + this.constantPool = constantPool; + this.sourceFileName = sourceFileName; + } + + // Interpreter side constructor. + private InterpreterResolvedObjectType(String name, int modifiers, InterpreterResolvedJavaType componentType, InterpreterResolvedObjectType superclass, InterpreterResolvedObjectType[] interfaces, + InterpreterConstantPool constantPool, + Class javaClass, + boolean isWordType) { + super(name, javaClass, isWordType); + assert isWordType == WordBase.class.isAssignableFrom(javaClass); + this.modifiers = modifiers; + this.superclass = superclass; + this.interfaces = interfaces; + this.componentType = componentType; + this.constantPool = constantPool; + this.sourceFileName = DynamicHub.fromClass(javaClass).getSourceFileName(); + } + + @Platforms(Platform.HOSTED_ONLY.class) + private InterpreterResolvedObjectType(ResolvedJavaType originalType, String name, int modifiers, InterpreterResolvedJavaType componentType, InterpreterResolvedObjectType superclass, + InterpreterResolvedObjectType[] interfaces, InterpreterConstantPool constantPool, + Class javaClass, + String sourceFileName) { + super(name, javaClass); + this.originalType = originalType; + this.modifiers = modifiers; + this.componentType = componentType; + this.superclass = superclass; + this.interfaces = interfaces; + this.constantPool = constantPool; + this.sourceFileName = sourceFileName; + } + + @Override + public String getSourceFileName() { + return sourceFileName; + } + + // Only used for BuildTimeInterpreterUniverse. + @Platforms(Platform.HOSTED_ONLY.class) + public static InterpreterResolvedObjectType createAtBuildTime(ResolvedJavaType originalType, String name, int modifiers, InterpreterResolvedJavaType componentType, + InterpreterResolvedObjectType superclass, InterpreterResolvedObjectType[] interfaces, InterpreterConstantPool constantPool, + Class javaClass, + String sourceFileName) { + return new InterpreterResolvedObjectType(originalType, name, modifiers, componentType, superclass, interfaces, constantPool, javaClass, sourceFileName); + } + + @VisibleForSerialization + public static InterpreterResolvedObjectType createForInterpreter(String name, int modifiers, InterpreterResolvedJavaType componentType, InterpreterResolvedObjectType superclass, + InterpreterResolvedObjectType[] interfaces, InterpreterConstantPool constantPool, + Class javaClass, + boolean isWordType) { + return new InterpreterResolvedObjectType(name, modifiers, componentType, superclass, interfaces, constantPool, javaClass, isWordType); + } + + @VisibleForSerialization + public static InterpreterResolvedObjectType createWithOpaqueClass(String name, int modifiers, InterpreterResolvedJavaType componentType, InterpreterResolvedObjectType superclass, + InterpreterResolvedObjectType[] interfaces, InterpreterConstantPool constantPool, + JavaConstant clazzConstant, + boolean isWordType, + String sourceFileName) { + return new InterpreterResolvedObjectType(name, modifiers, componentType, superclass, interfaces, constantPool, clazzConstant, isWordType, sourceFileName); + } + + public void setConstantPool(InterpreterConstantPool constantPool) { + VMError.guarantee(this == constantPool.getHolder()); + this.constantPool = MetadataUtil.requireNonNull(constantPool); + } + + public InterpreterConstantPool getConstantPool() { + assert !isArray(); + return constantPool; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public ResolvedJavaType getOriginalType() { + return originalType; + } + + @Override + public int getModifiers() { + return modifiers; + } + + @Override + public InterpreterResolvedJavaType getComponentType() { + return componentType; + } + + @Override + public JavaKind getJavaKind() { + return JavaKind.Object; + } + + @Override + public InterpreterResolvedObjectType getSuperclass() { + return this.superclass; + } + + @Override + public InterpreterResolvedObjectType[] getInterfaces() { + return this.interfaces; + } + + @Override + public boolean isAssignableFrom(ResolvedJavaType other) { + if (other instanceof InterpreterResolvedObjectType o) { + return isSubTypeOf(this, o); + } + return false; + } + + private static boolean isSubTypeOf(InterpreterResolvedObjectType superType, InterpreterResolvedObjectType subType) { + if (subType.equals(superType)) { + return true; + } + if (subType.superclass != null) { + if (isSubTypeOf(superType, subType.superclass)) { + return true; + } + } + for (InterpreterResolvedObjectType interf : subType.interfaces) { + if (isSubTypeOf(superType, interf)) { + return true; + } + } + return false; + } + + public InterpreterResolvedJavaMethod[] getVtable() { + if (vtableHolder == null) { + return null; + } + return vtableHolder.vtable; + } + + public void setVtable(InterpreterResolvedJavaMethod[] vtable) { + VMError.guarantee(vtableHolder == null); + this.vtableHolder = new VTableHolder(this, vtable); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public VTableHolder getVtableHolder() { + assert !isArray(); + return vtableHolder; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedPrimitiveType.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedPrimitiveType.java new file mode 100644 index 000000000000..23052e89d08a --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedPrimitiveType.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata; + +import java.lang.reflect.Modifier; + +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaType; + +public final class InterpreterResolvedPrimitiveType extends InterpreterResolvedJavaType { + + private final JavaKind kind; + + private InterpreterResolvedPrimitiveType(JavaKind kind) { + super(String.valueOf(kind.getTypeChar()), kind.toJavaClass()); + assert kind.isPrimitive(); + this.kind = kind; + } + + static final InterpreterResolvedPrimitiveType BOOLEAN = new InterpreterResolvedPrimitiveType(JavaKind.Boolean); + static final InterpreterResolvedPrimitiveType BYTE = new InterpreterResolvedPrimitiveType(JavaKind.Byte); + static final InterpreterResolvedPrimitiveType SHORT = new InterpreterResolvedPrimitiveType(JavaKind.Short); + static final InterpreterResolvedPrimitiveType CHAR = new InterpreterResolvedPrimitiveType(JavaKind.Char); + static final InterpreterResolvedPrimitiveType INT = new InterpreterResolvedPrimitiveType(JavaKind.Int); + static final InterpreterResolvedPrimitiveType FLOAT = new InterpreterResolvedPrimitiveType(JavaKind.Float); + static final InterpreterResolvedPrimitiveType LONG = new InterpreterResolvedPrimitiveType(JavaKind.Long); + static final InterpreterResolvedPrimitiveType DOUBLE = new InterpreterResolvedPrimitiveType(JavaKind.Double); + static final InterpreterResolvedPrimitiveType VOID = new InterpreterResolvedPrimitiveType(JavaKind.Void); + + public static InterpreterResolvedPrimitiveType fromKind(JavaKind kind) { + // @formatter:off + switch (kind) { + case Boolean : return BOOLEAN; + case Byte : return BYTE; + case Short : return SHORT; + case Char : return CHAR; + case Int : return INT; + case Float : return FLOAT; + case Long : return LONG; + case Double : return DOUBLE; + case Void : return VOID; + case Object : // fall-through + case Illegal : // fall-through + default: + throw new IllegalArgumentException(kind.toString()); + } + // @formatter:on + } + + @Override + public int getModifiers() { + return Modifier.ABSTRACT | Modifier.FINAL | Modifier.PUBLIC; + } + + @Override + public ResolvedJavaType getComponentType() { + return null; + } + + @Override + public JavaKind getJavaKind() { + return kind; + } + + @Override + public String getSourceFileName() { + return null; + } + + @Override + public ResolvedJavaType getSuperclass() { + return null; + } + + @Override + public ResolvedJavaType[] getInterfaces() { + return new ResolvedJavaType[0]; + } + + @Override + public boolean isAssignableFrom(ResolvedJavaType other) { + return this.equals(other); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterUniverse.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterUniverse.java new file mode 100644 index 000000000000..6acd297cc779 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterUniverse.java @@ -0,0 +1,64 @@ +/* + * 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.interpreter.metadata; + +import java.util.Collection; +import java.util.OptionalInt; + +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + +public interface InterpreterUniverse { + Collection getTypes(); + + Collection getFields(); + + Collection getMethods(); + + ResolvedJavaType lookupType(Class clazz); + + Class lookupClass(ResolvedJavaType type); + + Collection getAllDeclaredFields(ResolvedJavaType type); + + Collection getAllDeclaredMethods(ResolvedJavaType type); + + OptionalInt getTypeIndexFor(ResolvedJavaType type); + + OptionalInt getFieldIndexFor(ResolvedJavaField field); + + OptionalInt getMethodIndexFor(ResolvedJavaMethod method); + + ResolvedJavaType getTypeAtIndex(int index); + + ResolvedJavaField getFieldAtIndex(int index); + + ResolvedJavaMethod getMethodAtIndex(int index); + + ResolvedJavaMethod getMethodForESTOffset(int methodIndex); + + ResolvedJavaMethod getMethodFromMethodId(int methodId); +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterUniverseImpl.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterUniverseImpl.java new file mode 100644 index 000000000000..2d1022032acc --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterUniverseImpl.java @@ -0,0 +1,450 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata; + +import static com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod.EST_NO_ENTRY; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.OptionalInt; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.zip.CRC32C; + +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.serialization.SerializationContext; + +import jdk.vm.ci.meta.ResolvedJavaType; + +/** + * Classes, fields, methods and constants for the interpreter, the data structures in this universe + * are alive at runtime. + *

      + * This class can be used in SVM and HotSpot, in the resident and the server, and can load the + * interpreter universe using opaque constants even the image heap is not accessible. + */ +public final class InterpreterUniverseImpl implements InterpreterUniverse { + + static final int MAGIC = 0xE597E550; // ESPRESSO + private static final boolean LOGGING = false; + + private final List types; + private final List fields; + private final List methods; + private final Lazy, InterpreterResolvedJavaType>> classToType = // + Lazy.of(() -> getTypes().stream() + // Class -> type + .collect(Collectors.toMap(InterpreterResolvedJavaType::getJavaClass, Function.identity()))); + + private final Lazy>> allDeclaredFields = // + Lazy.of(() -> getFields().stream() + // type -> [field...] + .collect(Collectors.groupingBy(InterpreterResolvedJavaField::getDeclaringClass))); + private final Lazy>> allDeclaredMethods = // + Lazy.of(() -> getMethods().stream() + // type -> [method...] + .collect(Collectors.groupingBy(InterpreterResolvedJavaMethod::getDeclaringClass))); + + private final Lazy methodESTOffsetTable = Lazy.of(() -> createMethodTable(getMethods())); + + private final Lazy> methodInverseTable = Lazy.of(() -> createInverseTable(getMethods())); + private final Lazy> typeInverseTable = Lazy.of(() -> createInverseTable(getTypes())); + + private final Lazy> fieldInverseTable = Lazy.of(() -> createInverseTable(getFields())); + + private final Lazy methodIdMapping = Lazy.of(() -> createMethodIdMapping(getMethods())); + + public InterpreterUniverseImpl( + Collection types, + Collection fields, + Collection methods) { + this.types = List.copyOf(types); + this.fields = List.copyOf(fields); + this.methods = List.copyOf(methods); + } + + private static void consumeMagic(DataInput in) throws IOException { + int header = in.readInt(); + if (header != MAGIC) { + throw new IllegalStateException("Invalid header"); + } + } + + /** + * Serializes {@code this} {@link InterpreterUniverse} instance into the specified filePath. + */ + @Platforms(Platform.HOSTED_ONLY.class) + public void saveTo(SerializationContext.Builder builder, Path filePath) throws IOException { + + int totalBytesWritten = 0; + + long startNanos = System.nanoTime(); + try (DataOutputStream out = new DataOutputStream( + new BufferedOutputStream( + Files.newOutputStream(filePath, + StandardOpenOption.WRITE, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING)))) { + + out.writeInt(MAGIC); + + SerializationContext.Writer writer = builder.buildWriter(); + + for (InterpreterResolvedJavaType type : getTypes()) { + writer.writeValue(out, type); + } + for (InterpreterResolvedJavaField field : getFields()) { + writer.writeValue(out, field); + } + for (InterpreterResolvedJavaMethod method : getMethods()) { + writer.writeValue(out, method); + } + for (InterpreterResolvedJavaMethod method : getMethods()) { + writer.writeValue(out, method.inlinedBy); + InterpreterResolvedJavaMethod oneImplementation = method.getOneImplementation(); + writer.writeReference(out, oneImplementation); + } + + for (InterpreterResolvedJavaType type : getTypes()) { + if (type instanceof InterpreterResolvedObjectType objectType && !type.isArray()) { + writer.writeValue(out, objectType.getConstantPool()); + if (objectType.getVtableHolder() != null) { + writer.writeValue(out, objectType.getVtableHolder()); + } + } + } + totalBytesWritten = out.size(); + } + long elapsedNanos = System.nanoTime() - startNanos; + if (LOGGING) { + long shallowInterpreterMethods = getMethods().stream().filter(Predicate.not(InterpreterResolvedJavaMethod::isInterpreterExecutable)).count(); + System.err.println("Save interpreter metadata:" + + " totalBytesWritten=" + totalBytesWritten + + " types=" + getTypes().size() + + " fields=" + getFields().size() + + " methods=" + getMethods().size() + + " (shallow=" + shallowInterpreterMethods + ")" + + " elapsedMillis=" + (elapsedNanos / 1_000_000)); + } + } + + public static String toHexString(int value) { + String result = Integer.toHexString(value); + return "0".repeat(8 - result.length()) + result; + } + + public static int computeCRC32(Path filePath) throws IOException { + CRC32C checksum = new CRC32C(); + try (InputStream in = new BufferedInputStream(Files.newInputStream(filePath, StandardOpenOption.READ))) { + byte[] buf = new byte[16 * (1 << 10)]; // 16KB + int bytesRead = 0; + while ((bytesRead = in.read(buf)) > 0) { + checksum.update(buf, 0, bytesRead); + } + } + return (int) checksum.getValue(); + } + + public static InterpreterUniverseImpl loadFrom(SerializationContext.Builder builder, boolean opaqueConstants, String hashString, Path filePath) throws IOException { + long startNanos = System.nanoTime(); + if (LOGGING) { + System.err.println("Interpreter metadata hash string: " + hashString); + } + + if (hashString != null) { + String[] parts = hashString.split(":"); + String algorithm = parts[0]; + String expectedHexChecksum = parts[1]; + + /* + * TODO(peterssen): GR-46008 Use secure hash to verify interpreter metadata. This check + * is meant ONLY to improve the user experience, not for security e.g. a secure hash + * should be used, also note that this check don't guard against the file being modified + * between the checksum/hash computation and actual de-serialization. + */ + if ("crc32".equalsIgnoreCase(algorithm)) { + int crc32 = computeCRC32(filePath); + String actualHexChecksum = toHexString(crc32); + if (!expectedHexChecksum.equals(actualHexChecksum)) { + throw new IllegalArgumentException( + "Invalid " + algorithm + " verification for metadata file: " + filePath + + " expected: " + expectedHexChecksum + " actual: " + actualHexChecksum); + } + } else { + throw VMError.unimplemented("Metadata verification with: " + algorithm); + } + } + + try (DataInputStream dataInput = new DataInputStream( + new BufferedInputStream( + Files.newInputStream(filePath, StandardOpenOption.READ)))) { + List types = new ArrayList<>(); + List fields = new ArrayList<>(); + List methods = new ArrayList<>(); + + consumeMagic(dataInput); + SerializationContext.Reader reader; + + if (opaqueConstants) { + // Configure ReferenceConstant de-serialization to return opaque constants (heap + // offsets) to the main application heap. + reader = builder + .registerReader(true, ReferenceConstant.class, (context, in) -> { + boolean inNativeHeap = in.readBoolean(); + if (inNativeHeap) { + long nativeHeapOffset = in.readLong(); + return ReferenceConstant.createFromHeapOffset(nativeHeapOffset); + } else { + Object ref = context.readReference(in); + return ReferenceConstant.createFromReference(ref); + } + }) + .buildReader(); + } else { + // nothing to change, de-serialization is already configured for the interpreter + // (constants are on the same heap). + reader = builder.buildReader(); + } + + while (true) { + try { + Object value = reader.readValue(dataInput); + if (value instanceof InterpreterResolvedJavaType type) { + types.add(type); + } else if (value instanceof InterpreterResolvedJavaField field) { + fields.add(field); + } else if (value instanceof InterpreterResolvedJavaMethod method) { + methods.add(method); + } else if (value instanceof InterpreterConstantPool constantPool) { + InterpreterResolvedObjectType holder = constantPool.getHolder(); + VMError.guarantee(!holder.isArray()); + holder.setConstantPool(constantPool); + } else if (value instanceof InterpreterResolvedObjectType.VTableHolder vTableHolder) { + InterpreterResolvedObjectType holder = vTableHolder.holder; + holder.setVtable(vTableHolder.vtable); + } else if (value instanceof InterpreterResolvedJavaMethod.InlinedBy inlinedBy) { + InterpreterResolvedJavaMethod holder = inlinedBy.holder; + for (InterpreterResolvedJavaMethod m : inlinedBy.inliners) { + holder.addInliner(m); + } + Object oneImpl = reader.readReference(dataInput); + if (oneImpl instanceof InterpreterResolvedJavaMethod interpreterResolvedJavaMethod) { + holder.setOneImplementation(interpreterResolvedJavaMethod); + } else { + VMError.guarantee(oneImpl == null); + } + } else { + // skip value + } + } catch (EOFException e) { + break; + } + } + + InterpreterUniverseImpl universe = new InterpreterUniverseImpl(types, fields, methods); + long elapsedNanos = System.nanoTime() - startNanos; + + if (LOGGING) { + System.err.println("Load interpreter metadata:" + + " types=" + universe.getTypes().size() + + " fields=" + universe.getFields().size() + + " methods=" + universe.getMethods().size() + + " elapsedMillis=" + (elapsedNanos / 1_000_000)); + } + + return universe; + } + } + + @Override + public List getTypes() { + return types; + } + + @Override + public List getFields() { + return fields; + } + + @Override + public List getMethods() { + return methods; + } + + @SuppressWarnings("static-method") + @Override + public Class lookupClass(ResolvedJavaType type) { + return ((InterpreterResolvedJavaType) type).getJavaClass(); + } + + @Override + public InterpreterResolvedJavaType lookupType(Class clazz) { + Map, InterpreterResolvedJavaType> map = classToType.get(); + InterpreterResolvedJavaType type = map.get(clazz); + assert type != null; + return type; + } + + @Override + public Collection getAllDeclaredFields(ResolvedJavaType type) { + InterpreterResolvedJavaType interpreterType = (InterpreterResolvedJavaType) type; + return allDeclaredFields.get().getOrDefault(interpreterType, List.of()); + } + + @Override + public Collection getAllDeclaredMethods(ResolvedJavaType type) { + InterpreterResolvedJavaType interpreterType = (InterpreterResolvedJavaType) type; + return allDeclaredMethods.get().getOrDefault(interpreterType, List.of()); + } + + private static InterpreterResolvedJavaMethod[] createMethodTable(Collection methods) { + int maxOffset = -1; + for (InterpreterResolvedJavaMethod m : methods) { + int methodOffset = m.getEnterStubOffset(); + if (methodOffset != EST_NO_ENTRY) { + VMError.guarantee(methodOffset >= 0); + maxOffset = Math.max(maxOffset, methodOffset); + } + } + InterpreterResolvedJavaMethod[] result = new InterpreterResolvedJavaMethod[maxOffset + 1]; + for (InterpreterResolvedJavaMethod m : methods) { + int methodOffset = m.getEnterStubOffset(); + if (methodOffset != EST_NO_ENTRY) { + VMError.guarantee(result[methodOffset] == null, "duplicated interpreter method entry"); + result[methodOffset] = m; + } + } + return result; + } + + @Override + public InterpreterResolvedJavaMethod getMethodForESTOffset(int methodIndex) { + InterpreterResolvedJavaMethod method = this.methodESTOffsetTable.get()[methodIndex]; + VMError.guarantee(method != null); + return method; + } + + private static Map createInverseTable(Collection list) { + HashMap map = new HashMap<>(); + int counter = 0; + for (T e : list) { + map.put(e, counter++); + } + return map; + } + + private static InterpreterResolvedJavaMethod[] createMethodIdMapping(List methods) { + int maxMethodId = 0; + for (InterpreterResolvedJavaMethod method : methods) { + int methodId = method.getMethodId(); + assert methodId >= 0; + maxMethodId = Math.max(maxMethodId, methodId); + } + InterpreterResolvedJavaMethod[] mapping = new InterpreterResolvedJavaMethod[maxMethodId + 1]; + for (InterpreterResolvedJavaMethod method : methods) { + int methodId = method.getMethodId(); + if (methodId != 0) { + mapping[methodId] = method; + } + } + return mapping; + } + + @Override + public ResolvedJavaMethod getMethodAtIndex(int index) { + return methods.get(index); + } + + @Override + public ResolvedJavaType getTypeAtIndex(int index) { + return types.get(index); + } + + @Override + public ResolvedJavaField getFieldAtIndex(int index) { + return fields.get(index); + } + + @Override + public OptionalInt getMethodIndexFor(ResolvedJavaMethod method) { + Integer index = methodInverseTable.get().get(method); + if (index == null) { + return OptionalInt.empty(); + } + return OptionalInt.of(index); + } + + @Override + public OptionalInt getTypeIndexFor(ResolvedJavaType type) { + Integer index = typeInverseTable.get().get(type); + if (index == null) { + return OptionalInt.empty(); + } + return OptionalInt.of(index); + } + + @Override + public OptionalInt getFieldIndexFor(ResolvedJavaField field) { + Integer index = fieldInverseTable.get().get(field); + if (index == null) { + return OptionalInt.empty(); + } + return OptionalInt.of(index); + } + + @Override + public InterpreterResolvedJavaMethod getMethodFromMethodId(int methodId) { + if (methodId == 0) { + return null; + } + InterpreterResolvedJavaMethod[] mapping = methodIdMapping.get(); + if (0 <= methodId && methodId < mapping.length) { + return mapping[methodId]; + } else { + // No interpreter method known for this methodId. + return null; + } + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterUnresolvedSignature.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterUnresolvedSignature.java new file mode 100644 index 000000000000..8db477401ddf --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterUnresolvedSignature.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.interpreter.metadata.serialization.VisibleForSerialization; + +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.Signature; +import jdk.vm.ci.meta.UnresolvedJavaType; + +/** + * Unresolved signature that doesn't contain any resolved type references, except for primitive + * types. Primitive types are always and can only be resolved types. + */ +public final class InterpreterUnresolvedSignature implements Signature { + + private final JavaType returnType; + private final JavaType[] parameterTypes; + + @Platforms(Platform.HOSTED_ONLY.class) private Signature originalSignature; + + private static boolean primitiveOrUnresolved(JavaType... types) { + for (JavaType type : types) { + if (!(type instanceof InterpreterResolvedPrimitiveType || type instanceof UnresolvedJavaType)) { + return false; + } + } + return true; + } + + private InterpreterUnresolvedSignature(JavaType returnType, JavaType[] parameterTypes) { + assert primitiveOrUnresolved(returnType); + assert parameterTypes.getClass() == JavaType[].class; + assert primitiveOrUnresolved(parameterTypes); + this.returnType = returnType; + this.parameterTypes = parameterTypes; + } + + @Platforms(Platform.HOSTED_ONLY.class) + private InterpreterUnresolvedSignature(Signature originalSignature, JavaType returnType, JavaType[] parameterTypes) { + this(returnType, parameterTypes); + this.originalSignature = originalSignature; + } + + @Platforms(Platform.HOSTED_ONLY.class) + @VisibleForSerialization + public static InterpreterUnresolvedSignature create(Signature originalSignature, JavaType returnType, JavaType[] parameterTypes) { + return new InterpreterUnresolvedSignature(originalSignature, returnType, parameterTypes); + } + + @VisibleForSerialization + public static InterpreterUnresolvedSignature create(JavaType returnType, JavaType[] parameterTypes) { + return new InterpreterUnresolvedSignature(returnType, parameterTypes); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public Signature getOriginalSignature() { + return originalSignature; + } + + @Override + public int getParameterCount(boolean receiver) { + int count = parameterTypes.length; + if (receiver) { + ++count; + } + return count; + } + + public int slotsForParameters(boolean receiver) { + int slots = 0; + + if (receiver) { + ++slots; + } + + for (JavaType parameterType : parameterTypes) { + slots += parameterType.getJavaKind().getSlotCount(); + } + return slots; + } + + @Override + public JavaType getParameterType(int index, ResolvedJavaType accessingClass) { + return parameterTypes[index]; + } + + @Override + public JavaType getReturnType(ResolvedJavaType accessingClass) { + return returnType; + } + + @Override + public String toString() { + return "InterpreterUnresolvedSignature<" + toMethodDescriptor() + ">"; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof InterpreterUnresolvedSignature that) { + return returnType.equals(that.returnType) && MetadataUtil.arrayEquals(parameterTypes, that.parameterTypes); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = MetadataUtil.hashCode(returnType); + result = 31 * result + MetadataUtil.arrayHashCode(parameterTypes); + return result; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/Lazy.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/Lazy.java new file mode 100644 index 000000000000..22e8bb038050 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/Lazy.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata; + +/** + * Represents a lazily computed value. Ensures that a single thread runs the computation. + */ +public final class Lazy { + + @FunctionalInterface + public interface LazySupplier { + T get(); + } + + private volatile T ref; + private final LazySupplier supplier; + + private Lazy(LazySupplier supplier) { + this.supplier = supplier; + } + + /** + * If the supplier returns null, {@link NullPointerException} is thrown. Exceptions + * thrown by the supplier will be propagated. If the supplier returns a non-null object, it will + * be cached and the computation is considered finished. The supplier is guaranteed to run on a + * single thread. A successful computation ({@link LazySupplier#get()} returns a non-null + * object) is guaranteed to be executed only once. + * + * @return the computed object, guaranteed to be non-null + */ + public T get() { + T localRef = ref; + if (localRef == null) { + synchronized (this) { + localRef = ref; + if (localRef == null) { + localRef = MetadataUtil.requireNonNull(supplier.get()); + ref = localRef; + } + } + } + return localRef; + } + + /** + * (Not so) Lazy value that does not run a computation. + */ + public static Lazy value(T nonNullValue) { + Lazy result = new Lazy<>(null); + result.ref = MetadataUtil.requireNonNull(nonNullValue); + return result; + } + + /** + * @param supplier if the supplier returns null, {@link #get()} will throw + * {@link NullPointerException} + */ + public static Lazy of(LazySupplier supplier) { + return new Lazy<>(MetadataUtil.requireNonNull(supplier)); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/LookupSwitch.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/LookupSwitch.java new file mode 100644 index 000000000000..371148ffa85d --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/LookupSwitch.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata; + +import static com.oracle.svm.interpreter.metadata.Bytecodes.LOOKUPSWITCH; + +import com.oracle.svm.core.util.VMError; + +/** + * A utility for processing {@link Bytecodes#LOOKUPSWITCH} bytecodes. + */ +public final class LookupSwitch { + + private static final int OFFSET_TO_NUMBER_PAIRS = 4; + private static final int OFFSET_TO_FIRST_PAIR_MATCH = 8; + private static final int OFFSET_TO_FIRST_PAIR_OFFSET = 12; + private static final int PAIR_SIZE = 8; + + private LookupSwitch() { + throw VMError.shouldNotReachHereAtRuntime(); + } + + /** + * Gets the offset from the start of the switch instruction for the {@code i}'th switch target. + * + * @param i the switch target index + * @return the offset to the {@code i}'th switch target + */ + public static int offsetAt(byte[] code, int bci, int i) { + assert BytecodeStream.opcode(code, bci) == LOOKUPSWITCH; + return BytecodeStream.readInt(code, TableSwitch.getAlignedBci(bci) + OFFSET_TO_FIRST_PAIR_OFFSET + PAIR_SIZE * i); + } + + /** + * Gets the key at {@code i}'th switch target index. + * + * @param i the switch target index + * @return the key at {@code i}'th switch target index + */ + public static int keyAt(byte[] code, int bci, int i) { + assert BytecodeStream.opcode(code, bci) == LOOKUPSWITCH; + return BytecodeStream.readInt(code, TableSwitch.getAlignedBci(bci) + OFFSET_TO_FIRST_PAIR_MATCH + PAIR_SIZE * i); + } + + /** + * Gets the number of switch targets. + * + * @return the number of switch targets + */ + public static int numberOfCases(byte[] code, int bci) { + assert BytecodeStream.opcode(code, bci) == LOOKUPSWITCH; + return BytecodeStream.readInt(code, TableSwitch.getAlignedBci(bci) + OFFSET_TO_NUMBER_PAIRS); + } + + /** + * Gets the total size in bytes of the switch instruction. + * + * @return the total size in bytes of the switch instruction + */ + public static int size(byte[] code, int bci) { + assert BytecodeStream.opcode(code, bci) == LOOKUPSWITCH; + return TableSwitch.getAlignedBci(bci) + OFFSET_TO_FIRST_PAIR_MATCH + PAIR_SIZE * numberOfCases(code, bci) - bci; + } + + /** + * Gets the index of the instruction denoted by the {@code i}'th switch target. + * + * @param i index of the switch target + * @return the index of the instruction denoted by the {@code i}'th switch target + */ + public static int targetAt(byte[] code, int bci, int i) { + assert BytecodeStream.opcode(code, bci) == LOOKUPSWITCH; + return bci + offsetAt(code, bci, i); + } + + /** + * Gets the index of the instruction for the default switch target. + * + * @return the index of the instruction for the default switch target + */ + public static int defaultTarget(byte[] code, int bci) { + assert BytecodeStream.opcode(code, bci) == LOOKUPSWITCH; + return bci + defaultOffset(code, bci); + } + + /** + * Gets the offset from the start of the switch instruction to the default switch target. + * + * @return the offset to the default switch target + */ + public static int defaultOffset(byte[] code, int bci) { + assert BytecodeStream.opcode(code, bci) == LOOKUPSWITCH; + return BytecodeStream.readInt(code, TableSwitch.getAlignedBci(bci)); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/MetadataUtil.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/MetadataUtil.java new file mode 100644 index 000000000000..901aef3a3a6b --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/MetadataUtil.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata; + +import java.io.File; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Objects; + +import jdk.vm.ci.meta.JavaField; +import jdk.vm.ci.meta.JavaMethod; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.Signature; + +/** + * Duplicated utilities from {@link Objects} and {@link Arrays} to remove the dependencies on the + * standard library e.g. avoid a breakpoint on {@link Objects#requireNonNull(Object)} to the affect + * the debugger/interpreter implementation. + */ +public final class MetadataUtil { + + public static boolean equals(Object a, Object b) { + return (a == b) || (a != null && a.equals(b)); + } + + public static T requireNonNull(T obj) { + obj.getClass(); // trigger implicit NPE + return obj; + } + + public static int hashCode(Object o) { + return o != null ? o.hashCode() : 0; + } + + public static int arrayHashCode(Object[] a) { + if (a == null) { + return 0; + } + + int result = 1; + + for (Object element : a) { + result = 31 * result + (element == null ? 0 : element.hashCode()); + } + + return result; + } + + public static boolean arrayEquals(Object[] a, Object[] a2) { + if (a == a2) { + return true; + } + if (a == null || a2 == null) { + return false; + } + + int length = a.length; + if (a2.length != length) { + return false; + } + + for (int i = 0; i < length; i++) { + if (!MetadataUtil.equals(a[i], a2[i])) { + return false; + } + } + + return true; + } + + /** + * Cheap alternative to {@link String#format(String, Object...)} that only provides simple + * modifiers. + */ + // GR-55171: Consolidate into LogUtils + public static String fmt(String simpleFormat, Object... args) throws IllegalArgumentException { + StringBuilder sb = new StringBuilder(); + int index = 0; + int argIndex = 0; + while (index < simpleFormat.length()) { + char ch = simpleFormat.charAt(index++); + if (ch == '%') { + if (index >= simpleFormat.length()) { + throw new IllegalArgumentException("An unquoted '%' character cannot terminate a format specification"); + } + char specifier = simpleFormat.charAt(index++); + switch (specifier) { + case 's' -> { + if (argIndex >= args.length) { + throw new IllegalArgumentException("Too many format specifiers or not enough arguments"); + } + sb.append(args[argIndex++]); + } + case '%' -> sb.append('%'); + case 'n' -> sb.append(System.lineSeparator()); + default -> throw new IllegalArgumentException("Illegal format specifier: " + specifier); + } + } else { + sb.append(ch); + } + } + if (argIndex < args.length) { + throw new IllegalArgumentException("Not enough format specifiers or too many arguments"); + } + return sb.toString(); + } + + public static String toUniqueString(Signature signature) { + return signature.toMethodDescriptor(); + } + + public static String toUniqueString(JavaMethod method) { + return fmt("%s.%s/%s", + toUniqueString(method.getDeclaringClass()), + method.getName(), + toUniqueString(method.getSignature())); + } + + public static String toUniqueString(JavaType type) { + return type.getName(); + } + + public static String toUniqueString(JavaField field) { + return fmt("%s.%s/%s", + toUniqueString(field.getDeclaringClass()), + field.getName(), + toUniqueString(field.getType())); + } + + private static final String METADATA_SUFFIX = ".metadata"; + + public static String metadataFileName(String binaryFileName) { + assert !binaryFileName.isEmpty(); + assert !binaryFileName.endsWith(File.pathSeparator); + return binaryFileName + METADATA_SUFFIX; + } + + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "path.getParent() is never null") + public static Path metadataFilePath(Path binaryFilePath) { + String binaryFileName = binaryFilePath.getFileName().toString(); + String metadataFileName = metadataFileName(binaryFileName); + return binaryFilePath.resolveSibling(metadataFileName); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/ReferenceConstant.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/ReferenceConstant.java new file mode 100644 index 000000000000..794bb76d666f --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/ReferenceConstant.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata; + +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.svm.interpreter.metadata.serialization.VisibleForSerialization; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import java.util.Objects; + +public final class ReferenceConstant implements JavaConstant { + + private final T ref; // can be null + + // Opaque, used to identify constant on the image heap. + private final long imageHeapOffset; + + private ReferenceConstant(T ref) { + this.imageHeapOffset = 0L; + this.ref = ref; + } + + private ReferenceConstant(long heapOffset) { + this.imageHeapOffset = heapOffset; + this.ref = null; + } + + @VisibleForSerialization + public static ReferenceConstant createFromNonNullReference(T ref) { + return new ReferenceConstant<>(MetadataUtil.requireNonNull(ref)); + } + + @VisibleForSerialization + public static ReferenceConstant createFromReference(T ref) { + if (ref == null) { + // Cannot return the nullReference() singleton here since this method is + // used for de-serialization where reference "identity" must be preserved. + return new ReferenceConstant<>(null); + } + return createFromNonNullReference(ref); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static ReferenceConstant createFromImageHeapConstant(ImageHeapConstant imageHeapConstant) { + assert !imageHeapConstant.isNull() : imageHeapConstant; + return new ReferenceConstant<>(Objects.requireNonNull(imageHeapConstant)); + } + + @VisibleForSerialization + public static ReferenceConstant createFromHeapOffset(long nativeHeapOffset) { + assert nativeHeapOffset != 0L; + return new ReferenceConstant<>(nativeHeapOffset); + } + + public T getReferent() { + if (isOpaque()) { + throw new UnsupportedOperationException("Cannot extract opaque referent"); + } + return ref; + } + + @Override + public String toString() { + return "ReferenceConstant<" + ref + ">"; + } + + public boolean isOpaque() { + return imageHeapOffset != 0L; + } + + @Override + public JavaKind getJavaKind() { + return JavaKind.Object; + } + + @Override + public boolean isNull() { + return ref == null && imageHeapOffset == 0L; + } + + @Override + public boolean isDefaultForKind() { + return isNull(); + } + + @Override + public Object asBoxedPrimitive() { + throw new IllegalArgumentException(); + } + + @Override + public int asInt() { + throw new IllegalArgumentException(); + } + + @Override + public boolean asBoolean() { + throw new IllegalArgumentException(); + } + + @Override + public long asLong() { + throw new IllegalArgumentException(); + } + + @Override + public float asFloat() { + throw new IllegalArgumentException(); + } + + @Override + public double asDouble() { + throw new IllegalArgumentException(); + } + + @Override + public int hashCode() { + return System.identityHashCode(ref) ^ (int) (imageHeapOffset ^ (imageHeapOffset >>> 32)); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + return other instanceof ReferenceConstant that && this.imageHeapOffset == that.imageHeapOffset && this.ref == that.ref; + } + + private static final ReferenceConstant NULL = new ReferenceConstant<>(null); + + @SuppressWarnings("unchecked") + public static ReferenceConstant nullReference() { + return (ReferenceConstant) NULL; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/SuppressFBWarnings.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/SuppressFBWarnings.java new file mode 100644 index 000000000000..0963e274f270 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/SuppressFBWarnings.java @@ -0,0 +1,44 @@ +/* + * 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.interpreter.metadata; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Used to suppress SpotBugs warnings. + */ +@Retention(RetentionPolicy.CLASS) +public @interface SuppressFBWarnings { + /** + * @see "https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html" + */ + String[] value(); + + /** + * Reason why the warning is suppressed. Use a SpotBugs issue id where appropriate. + */ + String justification(); +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/TableSwitch.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/TableSwitch.java new file mode 100644 index 000000000000..b46bfe45c461 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/TableSwitch.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata; + +import static com.oracle.svm.interpreter.metadata.Bytecodes.TABLESWITCH; + +import com.oracle.svm.core.util.VMError; + +/** + * A utility for processing {@link Bytecodes#TABLESWITCH} bytecodes. + */ +public final class TableSwitch { + + private static final int OFFSET_TO_LOW_KEY = 4; + private static final int OFFSET_TO_HIGH_KEY = 8; + private static final int OFFSET_TO_FIRST_JUMP_OFFSET = 12; + private static final int JUMP_OFFSET_SIZE = 4; + + public static int getAlignedBci(int bci) { + return (bci + 4) & 0xfffffffc; + } + + private TableSwitch() { + throw VMError.shouldNotReachHereAtRuntime(); + } + + /** + * Gets the key at {@code i}'th switch target index. + * + * @param i the switch target index + * @return the key at {@code i}'th switch target index + */ + public static int keyAt(byte[] code, int bci, int i) { + assert BytecodeStream.opcode(code, bci) == TABLESWITCH; + return lowKey(code, bci) + i; + } + + /** + * Gets the offset from the start of the switch instruction for the {@code i}'th switch target. + * + * @param i the switch target index + * @return the offset to the {@code i}'th switch target + */ + public static int offsetAt(byte[] code, int bci, int i) { + assert BytecodeStream.opcode(code, bci) == TABLESWITCH; + return BytecodeStream.readInt(code, getAlignedBci(bci) + OFFSET_TO_FIRST_JUMP_OFFSET + JUMP_OFFSET_SIZE * i); + } + + /** + * Gets the number of switch targets. + * + * @return the number of switch targets + */ + public static int numberOfCases(byte[] code, int bci) { + assert BytecodeStream.opcode(code, bci) == TABLESWITCH; + return highKey(code, bci) - lowKey(code, bci) + 1; + } + + /** + * Gets the total size in bytes of the switch instruction. + * + * @return the total size in bytes of the switch instruction + */ + public static int size(byte[] code, int bci) { + assert BytecodeStream.opcode(code, bci) == TABLESWITCH; + return getAlignedBci(bci) + OFFSET_TO_FIRST_JUMP_OFFSET + JUMP_OFFSET_SIZE * numberOfCases(code, bci) - bci; + } + + /** + * Gets the index of the instruction denoted by the {@code i}'th switch target. + * + * @param i index of the switch target + * @return the index of the instruction denoted by the {@code i}'th switch target + */ + public static int targetAt(byte[] code, int bci, int i) { + assert BytecodeStream.opcode(code, bci) == TABLESWITCH; + return bci + offsetAt(code, bci, i); + } + + /** + * Gets the index of the instruction for the default switch target. + * + * @return the index of the instruction for the default switch target + */ + public static int defaultTarget(byte[] code, int bci) { + assert BytecodeStream.opcode(code, bci) == TABLESWITCH; + return bci + defaultOffset(code, bci); + } + + /** + * Gets the offset from the start of the switch instruction to the default switch target. + * + * @return the offset to the default switch target + */ + public static int defaultOffset(byte[] code, int bci) { + assert BytecodeStream.opcode(code, bci) == TABLESWITCH; + return BytecodeStream.readInt(code, getAlignedBci(bci)); + } + + /** + * Gets the low key of the table switch. + * + * @return the low key + */ + public static int lowKey(byte[] code, int bci) { + assert BytecodeStream.opcode(code, bci) == TABLESWITCH; + return BytecodeStream.readInt(code, getAlignedBci(bci) + OFFSET_TO_LOW_KEY); + } + + /** + * Gets the high key of the table switch. + * + * @return the high key + */ + public static int highKey(byte[] code, int bci) { + assert BytecodeStream.opcode(code, bci) == TABLESWITCH; + return BytecodeStream.readInt(code, getAlignedBci(bci) + OFFSET_TO_HIGH_KEY); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/ForkedDataOutput.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/ForkedDataOutput.java new file mode 100644 index 000000000000..5bd8fef89e5f --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/ForkedDataOutput.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata.serialization; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.util.VMError; + +@Platforms(Platform.HOSTED_ONLY.class) +final class ForkedDataOutput implements DataOutput, AutoCloseable { + private final DataOutput root; + private final ByteArrayOutputStream bytes; + private final DataOutput delegate; + + ForkedDataOutput(DataOutput root) { + this.root = getRoot(root); + this.bytes = new ByteArrayOutputStream(); + this.delegate = new DataOutputStream(bytes); + } + + private static DataOutput getRoot(DataOutput out) { + DataOutput root = out; + while (root instanceof ForkedDataOutput) { + root = ((ForkedDataOutput) root).root; + } + return root; + } + + @Override + public void close() throws IOException { + root.write(this.bytes.toByteArray()); + } + + @Override + public void write(int b) throws IOException { + delegate.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + delegate.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + delegate.write(b, off, len); + } + + @Override + public void writeBoolean(boolean v) throws IOException { + delegate.writeBoolean(v); + } + + @Override + public void writeByte(int v) throws IOException { + delegate.writeByte(v); + } + + @Override + public void writeShort(int v) throws IOException { + delegate.writeShort(v); + } + + @Override + public void writeChar(int v) throws IOException { + delegate.writeChar(v); + } + + @Override + public void writeInt(int v) throws IOException { + delegate.writeInt(v); + } + + @Override + public void writeLong(long v) throws IOException { + delegate.writeLong(v); + } + + @Override + public void writeFloat(float v) throws IOException { + delegate.writeFloat(v); + } + + @Override + public void writeDouble(double v) throws IOException { + delegate.writeDouble(v); + } + + @Override + public void writeBytes(String s) throws IOException { + throw VMError.shouldNotReachHereAtRuntime(); + } + + @Override + public void writeChars(String s) throws IOException { + throw VMError.shouldNotReachHereAtRuntime(); + } + + @Override + public void writeUTF(String s) throws IOException { + throw VMError.shouldNotReachHereAtRuntime(); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/LEB128.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/LEB128.java new file mode 100644 index 000000000000..729b4ba944fa --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/LEB128.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata.serialization; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public final class LEB128 { + + public static int readUnsignedInt(DataInput in) throws IOException { + int result = 0; + for (int i = 0;; ++i) { + byte b = in.readByte(); + result |= (b & 0x7F) << (i * 7); + // The first 4 groups of 7 bits are guaranteed to fit (4 * 7 = 28 bits). + // That leaves room for only the 4 low-order bits from the 5th group (which has index 4) + if (i == 4 && (b & 0xF0) != 0) { + throw new ArithmeticException("Value is larger than 32-bits"); + } + if ((b & 0x80) == 0) { + return result; + } + } + } + + public static void writeUnsignedInt(DataOutput out, int value) throws IOException { + int tmp = value; + do { + int b = tmp & 0x7F; + tmp >>>= 7; + if (tmp != 0) { + b |= 0x80; + } + out.writeByte(b); + } while (tmp != 0); + } + + private static int zigZagEncodeSign(int value) { + return (value << 1) ^ (value >> 31); + } + + private static int zigZagDecodeSign(int value) { + return (value >>> 1) ^ -(value & 1); + } + + public static int readSignedInt(DataInput in) throws IOException { + return zigZagDecodeSign(readUnsignedInt(in)); + } + + public static void writeSignedInt(DataOutput out, int value) throws IOException { + writeUnsignedInt(out, zigZagEncodeSign(value)); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/ReaderImpl.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/ReaderImpl.java new file mode 100644 index 000000000000..7dd88c8d5ba6 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/ReaderImpl.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata.serialization; + +import java.util.List; +import java.util.NoSuchElementException; + +final class ReaderImpl extends SerializationContextImpl implements SerializationContext.Reader { + + private final ValueReader.Resolver readerResolver; + + ReaderImpl(List> knownClasses, ValueReader.Resolver readerResolver) { + super(knownClasses); + this.readerResolver = readerResolver; + } + + @Override + public T indexToReference(int refIndex) { + if (refIndex == NULL_REFERENCE_INDEX) { + return null; + } + @SuppressWarnings("unchecked") + T ref = (T) indexToReference.get(refIndex); + if (ref == null) { + throw new IllegalStateException("Uninitialized reference at index " + refIndex); + } + return ref; + } + + @Override + public ValueReader readerFor(Class targetClass) { + if (targetClass.isPrimitive()) { + throw new IllegalArgumentException("Use in/out directly to read/write primitives"); + } + ValueReader reader = readerResolver.resolve(targetClass); + if (reader != null) { + return reader; + } + throw new NoSuchElementException("Cannot resolve ValueReader for " + targetClass); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Serialization.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Serialization.java new file mode 100644 index 000000000000..36e10cfdfad8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Serialization.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata.serialization; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.EOFException; +import java.io.IOException; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +public final class Serialization { + + @Platforms(Platform.HOSTED_ONLY.class) + public static void serializeSingle(SerializationContext.Builder builder, DataOutput out, Object object) throws IOException { + SerializationContext.Writer context = builder.buildWriter(); + context.writeValue(out, object); + } + + public static Object deserializeSingle(SerializationContext.Builder builder, DataInput in) throws IOException { + SerializationContext.Reader context = builder.buildReader(); + Object latest = null; + while (true) { + try { + latest = context.readValue(in); + } catch (EOFException e) { + break; + } + } + + return latest; + } + +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/SerializationContext.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/SerializationContext.java new file mode 100644 index 000000000000..6c9528e8997e --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/SerializationContext.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata.serialization; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.List; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +public interface SerializationContext { + + int UNKNOWN_REFERENCE_INDEX = Integer.MIN_VALUE; + int NULL_REFERENCE_INDEX = 0; // index 0 == null + int CLASS_REFERENCE_INDEX = 1; // index 1 == Class.class + + int recordReference(T value); + + interface Reader extends SerializationContext { + + T indexToReference(int refIndex); + + ValueReader readerFor(Class targetClass); + + default T readReference(DataInput in) throws IOException { + int refIndex = LEB128.readUnsignedInt(in); + return indexToReference(refIndex); + } + + default T readValue(DataInput in) throws IOException { + Class targetClass = readReference(in); + T value = readerFor(targetClass).read(this, in); + recordReference(value); + return value; + } + + } + + @Platforms(Platform.HOSTED_ONLY.class) + interface Writer extends SerializationContext { + ValueWriter writerFor(Class targetClass); + + int referenceToIndex(T value); + + default void writeReference(DataOutput out, T value) throws IOException { + int refIndex = referenceToIndex(value); + if (refIndex == UNKNOWN_REFERENCE_INDEX) { + writeValue(out, value); + refIndex = referenceToIndex(value); + if (refIndex == UNKNOWN_REFERENCE_INDEX) { + throw new IllegalStateException("Written object was not registered properly"); + } + } + LEB128.writeUnsignedInt(out, refIndex); + } + + @SuppressWarnings("unchecked") + default void writeValue(DataOutput out, T value) throws IOException { + Class targetClass = (Class) value.getClass(); + ValueWriter valueWriter = writerFor(targetClass); + try (ForkedDataOutput fork = new ForkedDataOutput(out)) { + writeReference(fork, targetClass); + valueWriter.write(this, fork, value); + } + recordReference(value); + } + } + + @Platforms(Platform.HOSTED_ONLY.class) + static Builder newBuilder() { + return new Builder(); + } + + final class Builder { + + private List> knownClasses = List.of(); + + public List> getKnownClasses() { + return knownClasses; + } + + public Builder setKnownClasses(List> knownClasses) { + this.knownClasses = knownClasses; + return this; + } + + private final EconomicMap, ValueReader> valueReaders = EconomicMap.create(); + + @Platforms(Platform.HOSTED_ONLY.class) // + private final EconomicMap, ValueWriter> valueWriters = EconomicMap.create(); + + @Platforms(Platform.HOSTED_ONLY.class) + public Builder registerSerializer(boolean overrideExisting, Class targetClass, ValueSerializer valueSerializer) { + registerReader(overrideExisting, targetClass, valueSerializer.getReader()); + registerWriter(overrideExisting, targetClass, valueSerializer.getWriter()); + return this; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public Builder registerSerializer(Class targetClass, ValueSerializer valueSerializer) { + return registerSerializer(false, targetClass, valueSerializer); + } + + public Builder registerReader(boolean overrideExisting, Class targetClass, ValueReader valueReader) { + if (!overrideExisting && valueReaders.containsKey(targetClass)) { + throw new IllegalArgumentException("ValueReader already exists for " + targetClass); + } + valueReaders.put(targetClass, valueReader); + return this; + } + + public Builder registerReader(Class targetClass, ValueReader valueReader) { + return registerReader(false, targetClass, valueReader); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public Builder registerWriter(boolean overrideExisting, Class targetClass, ValueWriter valueWriter) { + if (!overrideExisting && valueWriters.containsKey(targetClass)) { + throw new IllegalArgumentException("ValueWriter already exists for " + targetClass); + } + valueWriters.put(targetClass, valueWriter); + return this; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public Builder registerWriter(Class targetClass, ValueWriter valueWriter) { + return registerWriter(false, targetClass, valueWriter); + } + + public SerializationContext.Reader buildReader() { + return new ReaderImpl(knownClasses, new ValueReader.Resolver() { + + @SuppressWarnings("unchecked") + @Override + public ValueReader resolve(Class targetClass) { + return (ValueReader) valueReaders.get(targetClass); + } + }); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public SerializationContext.Writer buildWriter() { + return new WriterImpl(knownClasses, new ValueWriter.Resolver() { + + @SuppressWarnings("unchecked") + @Override + public ValueWriter resolve(Class targetClass) { + return (ValueWriter) valueWriters.get(targetClass); + } + }); + } + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/SerializationContextImpl.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/SerializationContextImpl.java new file mode 100644 index 000000000000..dfaaeaf4cc84 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/SerializationContextImpl.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata.serialization; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; + +abstract class SerializationContextImpl implements SerializationContext { + // The following two objects represent a bidirectional map between references and indices. + protected final List indexToReference; + protected final IdentityHashMap referenceToIndex; + + protected final List> knownClasses; + + protected SerializationContextImpl(List> knownClasses) { + if (knownClasses.contains(Class.class)) { + throw new IllegalArgumentException("Known classes cannot contain Class.class"); + } + + this.knownClasses = knownClasses; + this.referenceToIndex = new IdentityHashMap<>(); + this.indexToReference = new ArrayList<>(); + + indexToReference.add(null); // index 0 == null + + int classIndex = recordReference(Class.class); // index 1 == Class.class + assert classIndex == CLASS_REFERENCE_INDEX; + + for (Class clazz : knownClasses) { + recordReference(clazz); + } + } + + @Override + public int recordReference(T value) { + if (value == null) { + return NULL_REFERENCE_INDEX; + } + if (referenceToIndex.containsKey(value)) { + throw new IllegalStateException("Duplicated reference: " + value); + } + int refIndex = indexToReference.size(); + indexToReference.add(value); + referenceToIndex.put(value, refIndex); + return refIndex; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Serializers.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Serializers.java new file mode 100644 index 000000000000..2a2114214c61 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Serializers.java @@ -0,0 +1,771 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata.serialization; + +import java.lang.invoke.MethodType; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.List; +import java.util.function.IntFunction; +import java.util.function.ToLongFunction; + +import com.oracle.svm.interpreter.metadata.ReferenceConstant; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.FunctionPointerHolder; +import com.oracle.svm.core.snippets.KnownIntrinsics; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.InterpreterConstantPool; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedPrimitiveType; +import com.oracle.svm.interpreter.metadata.InterpreterUnresolvedSignature; + +import jdk.vm.ci.meta.ExceptionHandler; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.LineNumberTable; +import jdk.vm.ci.meta.Local; +import jdk.vm.ci.meta.LocalVariableTable; +import jdk.vm.ci.meta.PrimitiveConstant; +import jdk.vm.ci.meta.UnresolvedJavaField; +import jdk.vm.ci.meta.UnresolvedJavaMethod; +import jdk.vm.ci.meta.UnresolvedJavaType; + +/** + * Serializers for types included in the interpreter metadata. + */ +public final class Serializers { + + @VisibleForSerialization + public static ValueSerializer createSerializer(ValueReader reader, ValueWriter writer) { + return new ValueSerializer<>(reader, writer); + } + + public static final ValueSerializer BYTE_ARRAY = createSerializer( + (context, in) -> { + int length = LEB128.readUnsignedInt(in); + byte[] bytes = new byte[length]; + in.readFully(bytes); + return bytes; + }, + (context, out, value) -> { + LEB128.writeUnsignedInt(out, value.length); + out.write(value); + }); + + public static final ValueSerializer STRING = createSerializer( + (context, in) -> { + byte[] bytes = BYTE_ARRAY.getReader().read(context, in); + return new String(bytes, StandardCharsets.UTF_8); + }, + (context, out, value) -> { + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + BYTE_ARRAY.getWriter().write(context, out, bytes); + }); + + public static final ValueSerializer> CLASS_BY_NAME = createSerializer( + (context, in) -> { + boolean isPrimitive = in.readBoolean(); + if (isPrimitive) { + return JavaKind.fromPrimitiveOrVoidTypeChar(in.readChar()).toJavaClass(); + } else { + String name = STRING.getReader().read(context, in); + try { + return Class.forName(name); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + }, + (context, out, value) -> { + boolean isPrimitive = value.isPrimitive(); + out.writeBoolean(isPrimitive); + if (isPrimitive) { + out.writeChar(JavaKind.fromJavaClass(value).getTypeChar()); + } else { + STRING.getWriter().write(context, out, value.getName()); + } + }); + static final ValueSerializer BOOLEAN_ARRAY = createSerializer( + (context, in) -> { + int length = LEB128.readUnsignedInt(in); + boolean[] array = new boolean[length]; + for (int i = 0; i < length; ++i) { + array[i] = in.readBoolean(); + } + return array; + }, + (context, out, value) -> { + LEB128.writeUnsignedInt(out, value.length); + for (boolean e : value) { + out.writeBoolean(e); + } + }); + + static final ValueSerializer INT_ARRAY = createSerializer( + (context, in) -> { + int length = LEB128.readUnsignedInt(in); + int[] array = new int[length]; + for (int i = 0; i < length; ++i) { + array[i] = in.readInt(); + } + return array; + }, + (context, out, value) -> { + LEB128.writeUnsignedInt(out, value.length); + for (int e : value) { + out.writeInt(e); + } + }); + + static final ValueSerializer SHORT_ARRAY = createSerializer( + (context, in) -> { + int length = LEB128.readUnsignedInt(in); + short[] array = new short[length]; + for (int i = 0; i < length; ++i) { + array[i] = in.readShort(); + } + return array; + }, + (context, out, value) -> { + LEB128.writeUnsignedInt(out, value.length); + for (short e : value) { + out.writeShort(e); + } + }); + + static final ValueSerializer CHAR_ARRAY = createSerializer( + (context, in) -> { + int length = LEB128.readUnsignedInt(in); + char[] array = new char[length]; + for (int i = 0; i < length; ++i) { + array[i] = in.readChar(); + } + return array; + }, + (context, out, value) -> { + LEB128.writeUnsignedInt(out, value.length); + for (char e : value) { + out.writeChar(e); + } + }); + + static final ValueSerializer FLOAT_ARRAY = createSerializer( + (context, in) -> { + int length = LEB128.readUnsignedInt(in); + float[] array = new float[length]; + for (int i = 0; i < length; ++i) { + array[i] = in.readFloat(); + } + return array; + }, + (context, out, value) -> { + LEB128.writeUnsignedInt(out, value.length); + for (float e : value) { + out.writeFloat(e); + } + }); + + static final ValueSerializer DOUBLE_ARRAY = createSerializer( + (context, in) -> { + int length = LEB128.readUnsignedInt(in); + double[] array = new double[length]; + for (int i = 0; i < length; ++i) { + array[i] = in.readDouble(); + } + return array; + }, + (context, out, value) -> { + LEB128.writeUnsignedInt(out, value.length); + for (double e : value) { + out.writeDouble(e); + } + }); + + static final ValueSerializer LONG_ARRAY = createSerializer( + (context, in) -> { + int length = LEB128.readUnsignedInt(in); + long[] array = new long[length]; + for (int i = 0; i < length; ++i) { + array[i] = in.readLong(); + } + return array; + }, + (context, out, value) -> { + LEB128.writeUnsignedInt(out, value.length); + for (long e : value) { + out.writeLong(e); + } + }); + + public static > ValueSerializer ofEnum(Class enumClass) { + return createSerializer( + (context, in) -> { + String name = context.readReference(in); + return Enum.valueOf(enumClass, name); + }, + (context, out, value) -> { + context.writeReference(out, value.name()); + }); + } + + public static ValueSerializer ofReferenceArray(IntFunction allocateArray) { + return createSerializer( + (context, in) -> { + int length = LEB128.readUnsignedInt(in); + T[] array = allocateArray.apply(length); + for (int i = 0; i < length; i++) { + array[i] = context.readReference(in); + } + return array; + }, + (context, out, value) -> { + int length = value.length; + LEB128.writeUnsignedInt(out, length); + for (T e : value) { + context.writeReference(out, e); + } + }); + } + + private static final ValueSerializer AS_REFERENCE_CONSTANT = createSerializer( + (context, in) -> { + ReferenceConstant ref = context.readerFor(ReferenceConstant.class).read(context, in); + return ref.getReferent(); + }, + (context, out, value) -> { + context.writerFor(ReferenceConstant.class).write(context, out, ReferenceConstant.createFromNonNullReference(value)); + }); + + /** + * Force serialization of reference using the ReferenceConstant serializer within the context. + */ + @SuppressWarnings("unchecked") + static ValueSerializer asReferenceConstant() { + return (ValueSerializer) AS_REFERENCE_CONSTANT; + } + + public static final ValueReader> REFERENCE_CONSTANT_READER = (context, in) -> { + boolean inNativeHeap = in.readBoolean(); + if (inNativeHeap) { + long nativeHeapAddress = in.readLong(); + if (nativeHeapAddress == 0L) { + return ReferenceConstant.nullReference(); + } + Pointer heapBase = KnownIntrinsics.heapBase(); + Object ref = heapBase.add(WordFactory.unsigned(nativeHeapAddress)).toObject(); + return ReferenceConstant.createFromNonNullReference(ref); + } else { + // The reference could have been serialized despite not being on the native image heap. + // This may happen for some known types e.g. String. + // Or can be serialized as null. + Object ref = context.readReference(in); + return ReferenceConstant.createFromReference(ref); + } + }; + + @Platforms(Platform.HOSTED_ONLY.class) + public static ValueWriter> newReferenceConstantWriter(ToLongFunction getImageHeapOffset) { + return (context, out, value) -> { + Object ref = value.getReferent(); + long imageHeapOffset = ref == null + ? 0L + : getImageHeapOffset.applyAsLong(ref); + + // The reference was not included in the image heap. + boolean inImageHeap = (ref == null || imageHeapOffset != 0L); + assert !(ref == null) || inImageHeap; // ref == null => inImageHeap + out.writeBoolean(inImageHeap); + if (inImageHeap) { + out.writeLong(imageHeapOffset); + } else if (ref instanceof String) { + // Allow Strings to be serialized despite not being on the native heap. + context.writeReference(out, ref); + } else { + // Serialize as null. + context.writeReference(out, null); + } + }; + } + + public static ValueSerializer> newReferenceConstantSerializer(ToLongFunction getNativeHeapAddress) { + return createSerializer(REFERENCE_CONSTANT_READER, newReferenceConstantWriter(getNativeHeapAddress)); + } + + static final ValueSerializer JAVA_KIND = Serializers.ofEnum(JavaKind.class); + + static final ValueSerializer UNRESOLVED_TYPE = createSerializer( + (context, in) -> { + String name = context.readReference(in); + return UnresolvedJavaType.create(name); + }, + (context, out, value) -> { + context.writeReference(out, value.getName()); + }); + + static final ValueSerializer UNRESOLVED_METHOD = createSerializer( + (context, in) -> { + String name = context.readReference(in); + InterpreterUnresolvedSignature signature = context.readReference(in); + JavaType holder = context.readReference(in); + assert holder instanceof InterpreterResolvedPrimitiveType || holder instanceof UnresolvedJavaType; + return new UnresolvedJavaMethod(name, signature, holder); + }, + (context, out, value) -> { + context.writeReference(out, value.getName()); + assert value.getSignature() instanceof InterpreterUnresolvedSignature; + context.writeReference(out, value.getSignature()); + context.writeReference(out, value.getDeclaringClass()); + }); + + static final ValueSerializer UNRESOLVED_FIELD = createSerializer( + (context, in) -> { + JavaType holder = context.readReference(in); + assert holder instanceof InterpreterResolvedPrimitiveType || holder instanceof UnresolvedJavaType; + String name = context.readReference(in); + JavaType type = context.readReference(in); + assert type instanceof InterpreterResolvedPrimitiveType || type instanceof UnresolvedJavaType; + return new UnresolvedJavaField(holder, name, type); + }, + (context, out, value) -> { + context.writeReference(out, value.getDeclaringClass()); + context.writeReference(out, value.getName()); + context.writeReference(out, value.getType()); + }); + + static final ValueSerializer PRIMITIVE_TYPE = createSerializer( + (context, in) -> { + JavaKind kind = context.readReference(in); + return InterpreterResolvedPrimitiveType.fromKind(kind); + }, + (context, out, value) -> { + context.writeReference(out, value.getJavaKind()); + }); + + static final ValueSerializer UNRESOLVED_SIGNATURE = createSerializer( + (context, in) -> { + JavaType returnType = context.readReference(in); + int length = LEB128.readUnsignedInt(in); + JavaType[] parameterTypes = new JavaType[length]; + for (int i = 0; i < length; ++i) { + parameterTypes[i] = context.readReference(in); + } + return InterpreterUnresolvedSignature.create(returnType, parameterTypes); + }, + (context, out, value) -> { + JavaType returnType = value.getReturnType(null); + context.writeReference(out, returnType); + int length = value.getParameterCount(false); + LEB128.writeUnsignedInt(out, length); + for (int i = 0; i < length; ++i) { + JavaType parameterType = value.getParameterType(i, null); + context.writeReference(out, parameterType); + } + }); + + static final ValueSerializer METHOD_TYPE = createSerializer( + (context, in) -> { + Class returnType = context.readReference(in); + int length = LEB128.readUnsignedInt(in); + Class[] parameterTypes = new Class[length]; + for (int i = 0; i < length; ++i) { + parameterTypes[i] = context.readReference(in); + } + return MethodType.methodType(returnType, parameterTypes); + }, + (context, out, value) -> { + Class returnType = value.returnType(); + context.writeReference(out, returnType); + int length = value.parameterCount(); + LEB128.writeUnsignedInt(out, length); + for (int i = 0; i < length; ++i) { + Class parameterType = value.parameterType(i); + context.writeReference(out, parameterType); + } + }); + + static final ValueSerializer LOCAL = createSerializer( + (context, in) -> { + String name = context.readReference(in); + JavaType type = context.readReference(in); + int startBci = LEB128.readUnsignedInt(in); + int endBci = LEB128.readUnsignedInt(in); + int slot = LEB128.readUnsignedInt(in); + return new Local(name, type, startBci, endBci, slot); + }, + (context, out, value) -> { + context.writeReference(out, value.getName()); + context.writeReference(out, value.getType()); + LEB128.writeUnsignedInt(out, value.getStartBCI()); + LEB128.writeUnsignedInt(out, value.getEndBCI()); + LEB128.writeUnsignedInt(out, value.getSlot()); + }); + + static final ValueSerializer EXCEPTION_HANDLER = createSerializer( + (context, in) -> { + int startBCI = LEB128.readUnsignedInt(in); + int endBCI = LEB128.readUnsignedInt(in); + int catchBCI = LEB128.readUnsignedInt(in); + int catchTypeCPI = LEB128.readUnsignedInt(in); + JavaType catchType = context.readReference(in); + return new ExceptionHandler(startBCI, endBCI, catchBCI, catchTypeCPI, catchType); + }, + (context, out, value) -> { + LEB128.writeUnsignedInt(out, value.getStartBCI()); + LEB128.writeUnsignedInt(out, value.getEndBCI()); + LEB128.writeUnsignedInt(out, value.getHandlerBCI()); + LEB128.writeUnsignedInt(out, value.catchTypeCPI()); + context.writeReference(out, value.getCatchType()); + }); + + static final ValueSerializer LOCAL_VARIABLE_TABLE = createSerializer( + (context, in) -> { + Local[] locals = context.readerFor(Local[].class).read(context, in); + return new LocalVariableTable(locals); + }, + (context, out, value) -> { + context.writerFor(Local[].class).write(context, out, value.getLocals()); + }); + + static final int[] EMPTY_INT_ARRAY = new int[0]; + + static final ValueSerializer LINE_NUMBER_TABLE = createSerializer( + (context, in) -> { + int length = LEB128.readUnsignedInt(in); + if (length == 0) { + return new LineNumberTable(EMPTY_INT_ARRAY, EMPTY_INT_ARRAY); + } + int[] lineNumbers = new int[length]; + int[] bcis = new int[length]; + + int lastLineNumber = 0; + int lastBci = 0; + for (int i = 0; i < length; i++) { + int curLineNumber = lastLineNumber + LEB128.readSignedInt(in); + int curBci = lastBci + LEB128.readSignedInt(in); + lineNumbers[i] = curLineNumber; + bcis[i] = curBci; + + lastLineNumber = curLineNumber; + lastBci = curBci; + } + + return new LineNumberTable(lineNumbers, bcis); + }, + (context, out, value) -> { + int[] lines = value.getLineNumbers(); + int[] bcis = value.getBcis(); + VMError.guarantee(lines.length == bcis.length); + LEB128.writeUnsignedInt(out, lines.length); + int lastLineNumber = 0; + int lastBci = 0; + for (int i = 0; i < lines.length; i++) { + LEB128.writeSignedInt(out, lines[i] - lastLineNumber); + LEB128.writeSignedInt(out, bcis[i] - lastBci); + lastLineNumber = lines[i]; + lastBci = bcis[i]; + } + }); + + static final ValueSerializer PRIMITIVE_CONSTANT = createSerializer( + (context, in) -> { + JavaKind kind = context.readReference(in); + long rawValue = in.readLong(); + if (kind == JavaKind.Illegal) { + // JavaConstant.forPrimitive below throws for JavaKind.Illegal. + return JavaConstant.forIllegal(); + } + return JavaConstant.forPrimitive(kind, rawValue); + }, + (context, out, value) -> { + context.writeReference(out, value.getJavaKind()); + out.writeLong(value.getRawValue()); + }); + + // Register this serializer for JavaConstant.NULL_POINTER.getClass(). + static final ValueSerializer NULL_CONSTANT = createSerializer( + (context, in) -> { + return JavaConstant.NULL_POINTER; + }, + (context, out, value) -> { + // nop + }); + + static final ValueSerializer CONSTANT_POOL = createSerializer( + (context, in) -> { + InterpreterResolvedObjectType holder = context.readReference(in); + Object[] entries = context.readerFor(Object[].class).read(context, in); + return InterpreterConstantPool.create(holder, entries); + }, + (context, out, value) -> { + context.writeReference(out, value.getHolder()); + context.writerFor(Object[].class).write(context, out, value.getEntries()); + }); + + static final ValueSerializer RESOLVED_FIELD = createSerializer( + (context, in) -> { + String name = context.readReference(in); + InterpreterResolvedJavaType type = context.readReference(in); + InterpreterResolvedObjectType declaringClass = context.readReference(in); + int modifiers = LEB128.readUnsignedInt(in); + int offset = LEB128.readUnsignedInt(in); + JavaConstant constant = context.readReference(in); + return InterpreterResolvedJavaField.create(name, modifiers, type, declaringClass, offset, constant); + }, + (context, out, value) -> { + context.writeReference(out, value.getName()); + context.writeReference(out, value.getType()); + context.writeReference(out, value.getDeclaringClass()); + LEB128.writeUnsignedInt(out, value.getModifiers()); + LEB128.writeUnsignedInt(out, value.getOffset()); + if (value.isUnmaterializedConstant()) { + JavaConstant constant = value.getUnmaterializedConstant(); + context.writeReference(out, constant); + } else { + context.writeReference(out, null); + } + }); + + static final ValueSerializer OBJECT_TYPE = createSerializer( + (context, in) -> { + String name = context.readReference(in); + int modifiers = LEB128.readUnsignedInt(in); + InterpreterResolvedJavaType componentType = context.readReference(in); + + InterpreterResolvedObjectType superclass = context.readReference(in); + InterpreterResolvedObjectType[] interfaces = context.readReference(in); + /* vtable is serialized later to avoid cycle dependencies via methods */ + + InterpreterConstantPool constantPool = context.readReference(in); + ReferenceConstant> clazzConstant = context.readReference(in); + boolean isWordType = in.readBoolean(); + String sourceFileName = context.readReference(in); + if (clazzConstant.isOpaque()) { + return InterpreterResolvedObjectType.createWithOpaqueClass(name, modifiers, componentType, superclass, interfaces, constantPool, clazzConstant, isWordType, sourceFileName); + } else { + return InterpreterResolvedObjectType.createForInterpreter(name, modifiers, componentType, superclass, interfaces, constantPool, clazzConstant.getReferent(), isWordType); + } + }, + (context, out, value) -> { + String name = value.getName(); + int modifiers = value.getModifiers(); + InterpreterResolvedJavaType componentType = value.getComponentType(); + + InterpreterResolvedObjectType superclass = value.getSuperclass(); + InterpreterResolvedObjectType[] interfaces = value.getInterfaces(); + + // Constant pools are serialized separately, to break reference cycles, and + // patched after deserialization. + InterpreterConstantPool constantPool = null; + + Class javaClass = value.getJavaClass(); + ReferenceConstant> clazzConstant = ReferenceConstant.createFromNonNullReference(javaClass); + + context.writeReference(out, name); + LEB128.writeUnsignedInt(out, modifiers); + context.writeReference(out, componentType); + + context.writeReference(out, superclass); + context.writeReference(out, interfaces); + + context.writeReference(out, constantPool); + context.writeReference(out, clazzConstant); + out.writeBoolean(value.isWordType()); + context.writeReference(out, value.getSourceFileName()); + }); + + static final ValueSerializer VTABLE_HOLDER = createSerializer( + (context, in) -> { + InterpreterResolvedObjectType holder = context.readReference(in); + InterpreterResolvedJavaMethod[] vtable = context.readerFor(InterpreterResolvedJavaMethod[].class).read(context, in); + return new InterpreterResolvedObjectType.VTableHolder(holder, vtable); + }, + (context, out, value) -> { + context.writeReference(out, value.holder); + context.writerFor(InterpreterResolvedJavaMethod[].class).write(context, out, value.vtable); + }); + + static final ValueSerializer RESOLVED_METHOD = createSerializer( + (context, in) -> { + String name = context.readReference(in); + int maxLocals = LEB128.readUnsignedInt(in); + int maxStackSize = LEB128.readUnsignedInt(in); + int modifiers = LEB128.readUnsignedInt(in); + InterpreterResolvedObjectType declaringClass = context.readReference(in); + InterpreterUnresolvedSignature signature = context.readReference(in); + byte[] code = context.readReference(in); + ExceptionHandler[] exceptionHandlers = context.readReference(in); + LineNumberTable lineNumberTable = context.readReference(in); + LocalVariableTable localVariableTable = context.readReference(in); + + ReferenceConstant nativeEntryPoint = context.readReference(in); + int vtableIndex = LEB128.readUnsignedInt(in); + int gotOffset = LEB128.readUnsignedInt(in); + int enterStubOffset = LEB128.readUnsignedInt(in); + int methodId = LEB128.readUnsignedInt(in); + + return InterpreterResolvedJavaMethod.create(name, maxLocals, maxStackSize, modifiers, declaringClass, signature, code, exceptionHandlers, lineNumberTable, localVariableTable, + nativeEntryPoint, vtableIndex, gotOffset, enterStubOffset, methodId); + }, + (context, out, value) -> { + String name = value.getName(); + int maxLocals = value.getMaxLocals(); + int maxStackSize = value.getMaxStackSize(); + int modifiers = value.getModifiers(); + InterpreterResolvedObjectType declaringClass = value.getDeclaringClass(); + InterpreterUnresolvedSignature signature = value.getSignature(); + byte[] code = value.getInterpretedCode(); + ExceptionHandler[] exceptionHandlers = value.getExceptionHandlers(); + LineNumberTable lineNumberTable = value.getLineNumberTable(); + LocalVariableTable localVariableTable = value.getLocalVariableTable(); + /* + * `inlinedBy` and `oneImplementation` serialized separately to break + * reference cycle + */ + + ReferenceConstant nativeEntryPointHolder = value.getNativeEntryPointHolderConstant(); + int vtableIndex = value.getVTableIndex(); + int gotOffset = value.getGotOffset(); + int enterStubOffset = value.getEnterStubOffset(); + int methodId = value.getMethodId(); + + context.writeReference(out, name); + LEB128.writeUnsignedInt(out, maxLocals); + LEB128.writeUnsignedInt(out, maxStackSize); + LEB128.writeUnsignedInt(out, modifiers); + context.writeReference(out, declaringClass); + context.writeReference(out, signature); + context.writeReference(out, code); + context.writeReference(out, exceptionHandlers); + context.writeReference(out, lineNumberTable); + context.writeReference(out, localVariableTable); + + context.writeReference(out, nativeEntryPointHolder); + LEB128.writeUnsignedInt(out, vtableIndex); + LEB128.writeUnsignedInt(out, gotOffset); + LEB128.writeUnsignedInt(out, enterStubOffset); + LEB128.writeUnsignedInt(out, methodId); + }); + static final ValueSerializer INLINED_BY = createSerializer( + (context, in) -> { + InterpreterResolvedJavaMethod holder = context.readReference(in); + int size = LEB128.readUnsignedInt(in); + + InterpreterResolvedJavaMethod.InlinedBy ret = new InterpreterResolvedJavaMethod.InlinedBy(holder, new HashSet<>()); + while (size > 0) { + ret.inliners.add(context.readReference(in)); + size--; + } + return ret; + }, + (context, out, value) -> { + InterpreterResolvedJavaMethod holder = value.holder; + int size = value.inliners.size(); + + context.writeReference(out, holder); + LEB128.writeUnsignedInt(out, size); + for (InterpreterResolvedJavaMethod m : value.inliners) { + context.writeReference(out, m); + } + }); + + public static final List> UNIVERSE_KNOWN_CLASSES = List.of( + byte[].class, + String.class, + JavaKind.class, + UnresolvedJavaType.class, + UnresolvedJavaField.class, + UnresolvedJavaMethod.class, + InterpreterUnresolvedSignature.class, + Local.class, + Local[].class, + LocalVariableTable.class, + LineNumberTable.class, + ExceptionHandler.class, + ExceptionHandler[].class, + PrimitiveConstant.class, + JavaConstant.NULL_POINTER.getClass(), + MethodType.class, + InterpreterConstantPool.class, + Object[].class, + InterpreterResolvedObjectType[].class, + InterpreterResolvedJavaMethod[].class, + ReferenceConstant.class, + InterpreterResolvedPrimitiveType.class, + InterpreterResolvedObjectType.class, + InterpreterResolvedObjectType.VTableHolder.class, + InterpreterResolvedJavaField.class, + FunctionPointerHolder.class, + InterpreterResolvedJavaMethod.class, + InterpreterResolvedJavaMethod.InlinedBy.class); + + @Platforms(Platform.HOSTED_ONLY.class) + public static SerializationContext.Builder newBuilderForInterpreterMetadata() { + @SuppressWarnings("unchecked") + Class nullConstantClass = (Class) JavaConstant.NULL_POINTER.getClass(); + return SerializationContext.newBuilder() + .setKnownClasses(UNIVERSE_KNOWN_CLASSES) + // Only UNIVERSE_KNOWN_CLASSES can be (de-)serialized. + .registerSerializer(byte[].class, BYTE_ARRAY) + .registerSerializer(String.class, STRING) + .registerSerializer(JavaKind.class, JAVA_KIND) + .registerSerializer(UnresolvedJavaType.class, UNRESOLVED_TYPE) + .registerSerializer(UnresolvedJavaField.class, UNRESOLVED_FIELD) + .registerSerializer(UnresolvedJavaMethod.class, UNRESOLVED_METHOD) + .registerSerializer(InterpreterUnresolvedSignature.class, UNRESOLVED_SIGNATURE) + .registerSerializer(Local.class, LOCAL) + .registerSerializer(Local[].class, ofReferenceArray(Local[]::new)) + .registerSerializer(LocalVariableTable.class, LOCAL_VARIABLE_TABLE) + .registerSerializer(LineNumberTable.class, LINE_NUMBER_TABLE) + .registerSerializer(ExceptionHandler.class, EXCEPTION_HANDLER) + .registerSerializer(ExceptionHandler[].class, ofReferenceArray(ExceptionHandler[]::new)) + .registerSerializer(PrimitiveConstant.class, PRIMITIVE_CONSTANT) + .registerSerializer(nullConstantClass, NULL_CONSTANT) + .registerSerializer(MethodType.class, METHOD_TYPE) + .registerSerializer(InterpreterConstantPool.class, CONSTANT_POOL) + .registerSerializer(Object[].class, ofReferenceArray(Object[]::new)) // CP-entries + .registerSerializer(InterpreterResolvedObjectType[].class, ofReferenceArray(InterpreterResolvedObjectType[]::new)) // interfaces + .registerSerializer(InterpreterResolvedJavaMethod[].class, ofReferenceArray(InterpreterResolvedJavaMethod[]::new)) // vtable + .registerSerializer(InterpreterResolvedPrimitiveType.class, PRIMITIVE_TYPE) + .registerSerializer(InterpreterResolvedObjectType.class, OBJECT_TYPE) + .registerSerializer(InterpreterResolvedObjectType.VTableHolder.class, VTABLE_HOLDER) + .registerSerializer(InterpreterResolvedJavaField.class, RESOLVED_FIELD) + .registerSerializer(FunctionPointerHolder.class, asReferenceConstant()) + .registerSerializer(InterpreterResolvedJavaMethod.class, RESOLVED_METHOD) + .registerSerializer(InterpreterResolvedJavaMethod.InlinedBy.class, INLINED_BY) + .registerReader(ReferenceConstant.class, REFERENCE_CONSTANT_READER) + // The ReferenceConstant writer must be patched at build time. + .registerWriter(ReferenceConstant.class, (context, out, value) -> { + throw VMError.shouldNotReachHereAtRuntime(); + }); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Unsigned5.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Unsigned5.java new file mode 100644 index 000000000000..f485d5c4951c --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Unsigned5.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata.serialization; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public final class Unsigned5 { + // Math constants for the modified UNSIGNED5 coding of Pack200 + private static final int lg_H = 6; // log-base-2 of H (lg 64 == 6) + private static final int H = 1 << lg_H; // number of "high" bytes (64) + private static final int X = 1; // there is one excluded byte ('\0') + private static final int MAX_b = (1 << Byte.SIZE) - 1; // largest byte value + private static final int L = (MAX_b + 1) - X - H; + + private static final int MAX_LENGTH = 5; + + public static void writeUnsignedInt(DataOutput out, int value) throws IOException { + int sum = value; + for (int i = 0; i < MAX_LENGTH - 1 && Integer.compareUnsigned(sum, L) >= 0; ++i) { + sum -= L; + out.writeByte((byte) (X + L + (sum & 0x3F))); + sum >>>= lg_H; + } + out.writeByte((byte) (X + sum)); + } + + public static int readUnsignedInt(DataInput in) throws IOException { + int sum = 0; + for (int i = 0; i < MAX_LENGTH; ++i) { + int bi = in.readUnsignedByte(); + if (bi < X) { + throw new IllegalStateException("Invalid byte"); + } + sum += (bi - X) << (i * lg_H); + if (bi < X + L) { + break; + } + } + return sum; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/ValueReader.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/ValueReader.java new file mode 100644 index 000000000000..b160937df63d --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/ValueReader.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata.serialization; + +import java.io.DataInput; +import java.io.IOException; + +@FunctionalInterface +public interface ValueReader { + T read(SerializationContext.Reader context, DataInput in) throws IOException; + + @FunctionalInterface + interface Resolver { + ValueReader resolve(Class targetClass); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/ValueSerializer.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/ValueSerializer.java new file mode 100644 index 000000000000..e0d0e78150f5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/ValueSerializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata.serialization; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +public final class ValueSerializer { + private final ValueReader reader; + + @Platforms(Platform.HOSTED_ONLY.class) // + private final ValueWriter writer; + + @Platforms(Platform.HOSTED_ONLY.class) + public ValueSerializer(ValueReader reader, ValueWriter writer) { + this.reader = reader; + this.writer = writer; + } + + public ValueReader getReader() { + return reader; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public ValueWriter getWriter() { + return writer; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/ValueWriter.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/ValueWriter.java new file mode 100644 index 000000000000..5cb3ca883c84 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/ValueWriter.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata.serialization; + +import java.io.DataOutput; +import java.io.IOException; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +@Platforms(Platform.HOSTED_ONLY.class) +@FunctionalInterface +public interface ValueWriter { + + void write(SerializationContext.Writer context, DataOutput out, T value) throws IOException; + + @Platforms(Platform.HOSTED_ONLY.class) + @FunctionalInterface + interface Resolver { + ValueWriter resolve(Class targetClass); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/VisibleForSerialization.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/VisibleForSerialization.java new file mode 100644 index 000000000000..2d31f325d679 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/VisibleForSerialization.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata.serialization; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Denotes that the class, method or field has its visibility relaxed, so that it is more widely + * visible than otherwise necessary for serialization. + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) +public @interface VisibleForSerialization { +} diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/WriterImpl.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/WriterImpl.java new file mode 100644 index 000000000000..812768e70951 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/WriterImpl.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.metadata.serialization; + +import java.io.DataOutput; +import java.io.IOException; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +@Platforms(Platform.HOSTED_ONLY.class) +final class WriterImpl extends SerializationContextImpl implements SerializationContext.Writer { + private final Set cycleDetector = Collections.newSetFromMap(new IdentityHashMap<>()); + + private final ValueWriter.Resolver writerResolver; + + WriterImpl(List> knownClasses, ValueWriter.Resolver writerResolver) { + super(knownClasses); + this.writerResolver = writerResolver; + } + + @Override + public ValueWriter writerFor(Class targetClass) { + if (targetClass.isPrimitive()) { + throw new IllegalArgumentException("Use in/out directly to read/write primitives"); + } + ValueWriter writer = writerResolver.resolve(targetClass); + if (writer != null) { + return writer; + } + throw new NoSuchElementException("Cannot resolve ValueWriter for " + targetClass); + } + + @Override + public int referenceToIndex(T value) { + if (value == null) { + return NULL_REFERENCE_INDEX; + } + return referenceToIndex.getOrDefault(value, UNKNOWN_REFERENCE_INDEX); + } + + @Override + public void writeValue(DataOutput out, T value) throws IOException { + if (value == null) { + throw new NullPointerException(); + } + if (cycleDetector.contains(value)) { + throw new IllegalStateException("Detected reference cycle during serialization for " + value); + } + cycleDetector.add(value); + Writer.super.writeValue(out, value); + cycleDetector.remove(value); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/AArch64InterpreterStubSection.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/AArch64InterpreterStubSection.java new file mode 100644 index 000000000000..1c6b8afb656b --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/AArch64InterpreterStubSection.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.interpreter; + +import static com.oracle.objectfile.ObjectFile.RelocationKind.AARCH64_R_AARCH64_ADD_ABS_LO12_NC; +import static com.oracle.objectfile.ObjectFile.RelocationKind.AARCH64_R_AARCH64_ADR_PREL_PG_HI21; +import static com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod.EST_NO_ENTRY; + +import java.util.Collection; + +import com.oracle.objectfile.ObjectFile; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.graal.aarch64.SubstrateAArch64RegisterConfig; +import com.oracle.svm.core.graal.meta.KnownOffsets; +import com.oracle.svm.core.graal.meta.SubstrateRegisterConfig; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.graal.aarch64.AArch64InterpreterStubs; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.hosted.image.NativeImage; + +import jdk.graal.compiler.asm.Assembler; +import jdk.graal.compiler.asm.Label; +import jdk.graal.compiler.asm.aarch64.AArch64MacroAssembler; +import jdk.graal.compiler.core.common.LIRKind; +import jdk.vm.ci.code.Register; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +public class AArch64InterpreterStubSection extends InterpreterStubSection { + public AArch64InterpreterStubSection() { + this.target = ConfigurationValues.getTarget(); + this.registerConfig = new SubstrateAArch64RegisterConfig(SubstrateRegisterConfig.ConfigKind.NATIVE_TO_JAVA, null, target, true); + this.valueKindFactory = javaKind -> LIRKind.fromJavaKind(target.arch, javaKind); + } + + @Override + protected byte[] generateEnterStubs(Collection methods) { + AArch64MacroAssembler masm = new AArch64MacroAssembler(target); + + Label interpEnterStub = new Label(); + masm.bind(interpEnterStub); + + try (AArch64MacroAssembler.ScratchRegister jmpTargetRegister = masm.getScratchRegister()) { + Register jmpTarget = jmpTargetRegister.getRegister(); + + masm.setCodePatchingAnnotationConsumer(this::recordEnterStubForPatching); + /* + * use indirect jump to avoid problems around branch islands (displacement larger than + * +/-128MB) + */ + masm.adrpAdd(jmpTarget); + masm.jmp(jmpTarget); + } + + /* emit enter trampolines for each method that potentially runs in the interpreter */ + for (InterpreterResolvedJavaMethod method : methods) { + VMError.guarantee(method.getEnterStubOffset() != EST_NO_ENTRY); + InterpreterUtil.log("[svm_interp] Adding stub for %s", method); + recordEnterTrampoline(method, masm.position()); + + /* pass the method index, the reference is obtained in enterInterpreterStub */ + masm.mov(AArch64InterpreterStubs.TRAMPOLINE_ARGUMENT, method.getEnterStubOffset()); + + masm.jmp(interpEnterStub); + } + + return masm.close(true); + } + + @Override + public int getVTableStubSize() { + return 8; + } + + @Override + protected byte[] generateVtableEnterStubs(int maxVtableIndex) { + AArch64MacroAssembler masm = new AArch64MacroAssembler(target); + + Label interpEnterStub = new Label(); + masm.bind(interpEnterStub); + + try (AArch64MacroAssembler.ScratchRegister jmpTargetRegister = masm.getScratchRegister()) { + Register jmpTarget = jmpTargetRegister.getRegister(); + + masm.setCodePatchingAnnotationConsumer(this::recordEnterStubForPatching); + /* + * use indirect jump to avoid problems around branch islands (displacement larger than + * +/-128MB) + */ + masm.adrpAdd(jmpTarget); + masm.jmp(jmpTarget); + } + + masm.align(getVTableStubSize()); + + int vTableEntrySize = KnownOffsets.singleton().getVTableEntrySize(); + for (int index = 0; index < maxVtableIndex; index++) { + int stubStart = masm.position(); + int stubEnd = stubStart + getVTableStubSize(); + + int offset = index * vTableEntrySize; + + /* pass current vtable offset as hidden argument */ + masm.mov(AArch64InterpreterStubs.TRAMPOLINE_ARGUMENT, offset); + + masm.jmp(interpEnterStub); + + masm.align(getVTableStubSize()); + assert masm.position() == stubEnd; + } + + return masm.close(true); + + } + + private int resolverPatchOffset = -1; + + private void recordEnterStubForPatching(Assembler.CodeAnnotation a) { + if (resolverPatchOffset != -1) { + return; + } + + assert a instanceof AArch64MacroAssembler.AdrpAddMacroInstruction annotation; + AArch64MacroAssembler.AdrpAddMacroInstruction annotation = (AArch64MacroAssembler.AdrpAddMacroInstruction) a; + + resolverPatchOffset = annotation.instructionPosition; + } + + @Override + protected void markEnterStubPatch(ObjectFile.ProgbitsSectionImpl pltBuffer, ResolvedJavaMethod enterStub) { + pltBuffer.markRelocationSite(resolverPatchOffset, AARCH64_R_AARCH64_ADR_PREL_PG_HI21, NativeImage.localSymbolNameForMethod(enterStub), 0); + pltBuffer.markRelocationSite(resolverPatchOffset + 4, AARCH64_R_AARCH64_ADD_ABS_LO12_NC, NativeImage.localSymbolNameForMethod(enterStub), 0); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/AMD64InterpreterStubSection.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/AMD64InterpreterStubSection.java new file mode 100644 index 000000000000..79bae391b94b --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/AMD64InterpreterStubSection.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.interpreter; + +import java.util.Collection; + +import com.oracle.objectfile.ObjectFile; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.graal.amd64.AMD64InterpreterStubs; +import com.oracle.svm.core.graal.amd64.SubstrateAMD64RegisterConfig; +import com.oracle.svm.core.graal.meta.KnownOffsets; +import com.oracle.svm.core.graal.meta.SubstrateRegisterConfig; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.hosted.image.NativeImage; + +import jdk.graal.compiler.asm.Assembler; +import jdk.graal.compiler.asm.Label; +import jdk.graal.compiler.asm.amd64.AMD64BaseAssembler; +import jdk.graal.compiler.asm.amd64.AMD64MacroAssembler; +import jdk.graal.compiler.core.common.LIRKind; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +import static com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod.EST_NO_ENTRY; + +public class AMD64InterpreterStubSection extends InterpreterStubSection { + public AMD64InterpreterStubSection() { + this.target = ConfigurationValues.getTarget(); + this.registerConfig = new SubstrateAMD64RegisterConfig(SubstrateRegisterConfig.ConfigKind.NATIVE_TO_JAVA, null, target, true); + this.valueKindFactory = javaKind -> LIRKind.fromJavaKind(target.arch, javaKind); + } + + @Override + protected byte[] generateEnterStubs(Collection methods) { + AMD64MacroAssembler masm = new AMD64MacroAssembler(target); + + Label interpEnterStub = new Label(); + masm.bind(interpEnterStub); + + masm.setCodePatchingAnnotationConsumer(this::recordEnterStubForPatching); + masm.jmp(); + + /* emit enter trampolines for each method that potentially runs in the interpreter */ + for (InterpreterResolvedJavaMethod method : methods) { + VMError.guarantee(method.getEnterStubOffset() != EST_NO_ENTRY); + InterpreterUtil.log("[svm_interp] Adding stub for %s", method); + recordEnterTrampoline(method, masm.position()); + + /* pass the method index, the reference is obtained in enterInterpreterStub */ + masm.movq(AMD64InterpreterStubs.TRAMPOLINE_ARGUMENT, method.getEnterStubOffset()); + + masm.jmp(interpEnterStub); + } + + return masm.close(true); + } + + @Override + public int getVTableStubSize() { + return 16; + } + + @Override + protected byte[] generateVtableEnterStubs(int maxVtableIndex) { + AMD64MacroAssembler masm = new AMD64MacroAssembler(target); + + Label interpEnterStub = new Label(); + masm.bind(interpEnterStub); + + masm.setCodePatchingAnnotationConsumer(this::recordEnterStubForPatching); + masm.jmp(); + + masm.align(getVTableStubSize()); + + int vTableEntrySize = KnownOffsets.singleton().getVTableEntrySize(); + for (int index = 0; index < maxVtableIndex; index++) { + int stubStart = masm.position(); + int stubEnd = stubStart + getVTableStubSize(); + + int offset = index * vTableEntrySize; + + /* pass current vtable offset as hidden argument */ + masm.movq(AMD64InterpreterStubs.TRAMPOLINE_ARGUMENT, offset); + + masm.jmp(interpEnterStub); + + masm.align(getVTableStubSize()); + assert masm.position() == stubEnd; + } + + return masm.close(true); + } + + private int resolverPatchOffset; + private int resolverKindAddend; + private ObjectFile.RelocationKind resolverPatchRelocationKind = null; + + private void recordEnterStubForPatching(Assembler.CodeAnnotation a) { + if (resolverPatchRelocationKind != null) { + return; + } + + assert a instanceof AMD64BaseAssembler.OperandDataAnnotation; + AMD64BaseAssembler.OperandDataAnnotation annotation = (AMD64BaseAssembler.OperandDataAnnotation) a; + resolverPatchOffset = annotation.operandPosition; + resolverKindAddend = -annotation.operandSize; + resolverPatchRelocationKind = ObjectFile.RelocationKind.getPCRelative(annotation.operandSize); + } + + @Override + protected void markEnterStubPatch(ObjectFile.ProgbitsSectionImpl pltBuffer, ResolvedJavaMethod enterStub) { + pltBuffer.markRelocationSite(resolverPatchOffset, resolverPatchRelocationKind, NativeImage.localSymbolNameForMethod(enterStub), resolverKindAddend); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeConstantPool.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeConstantPool.java new file mode 100644 index 000000000000..1601ee015e0f --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeConstantPool.java @@ -0,0 +1,498 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter; + +import static com.oracle.svm.interpreter.metadata.Bytecodes.ANEWARRAY; +import static com.oracle.svm.interpreter.metadata.Bytecodes.CHECKCAST; +import static com.oracle.svm.interpreter.metadata.Bytecodes.GETFIELD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.GETSTATIC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INSTANCEOF; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEDYNAMIC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEINTERFACE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKESPECIAL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKESTATIC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEVIRTUAL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LDC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LDC2_W; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LDC_W; +import static com.oracle.svm.interpreter.metadata.Bytecodes.MULTIANEWARRAY; +import static com.oracle.svm.interpreter.metadata.Bytecodes.NEW; +import static com.oracle.svm.interpreter.metadata.Bytecodes.PUTFIELD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.PUTSTATIC; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.svm.interpreter.metadata.BytecodeStream; +import com.oracle.svm.interpreter.metadata.Bytecodes; +import com.oracle.svm.interpreter.metadata.InterpreterConstantPool; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType; +import com.oracle.svm.interpreter.metadata.ReferenceConstant; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; +import com.oracle.graal.pointsto.util.AnalysisError; +import com.oracle.svm.core.meta.MethodPointer; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.meta.HostedMethod; +import com.oracle.svm.hosted.meta.HostedUniverse; + +import jdk.vm.ci.meta.ConstantPool; +import jdk.vm.ci.meta.ExceptionHandler; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaField; +import jdk.vm.ci.meta.JavaMethod; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.LocalVariableTable; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.UnresolvedJavaField; +import jdk.vm.ci.meta.UnresolvedJavaMethod; +import jdk.vm.ci.meta.UnresolvedJavaType; + +/** + * Allows to incrementally build constant pools from JVMCI provided bytecodes. This constant pool + * supports appending and de-duplicating constants, classes, fields, methods ... + *

      + * Once the constant pool is finished e.g. all methods in the class are processed for the + * interpreter, it must be transformed (via {@link #snapshot()}) into a runtime-ready + * {@link InterpreterConstantPool}. + */ +@Platforms(Platform.HOSTED_ONLY.class) +final class BuildTimeConstantPool { + + private static final ExceptionHandler[] EMPTY_EXCEPTION_HANDLERS = new ExceptionHandler[0]; + + private final InterpreterResolvedObjectType holder; + + private final Map constantCPI; + private final Map appendixCPI; + private final Map fieldCPI; + private final Map typeCPI; + private final Map methodCPI; + + final ArrayList entries; + + /** + * Creates runtime-ready constant pool for the interpreter. + */ + public InterpreterConstantPool snapshot() { + return InterpreterConstantPool.create(holder, entries.toArray()); + } + + BuildTimeConstantPool(InterpreterResolvedObjectType holder) { + this.holder = holder; + this.entries = new ArrayList<>(32); + this.entries.add(null); // index 0 always contains illegal entry + this.constantCPI = new HashMap<>(); + this.fieldCPI = new HashMap<>(); + this.typeCPI = new HashMap<>(); + this.methodCPI = new HashMap<>(); + this.appendixCPI = new HashMap<>(); + } + + public int length() { + return entries.size(); + } + + public int longConstant(long value) { + return constantCPI.computeIfAbsent(value, key -> { + JavaConstant javaConstant = BuildTimeInterpreterUniverse.singleton().primitiveConstant(value); + entries.add(javaConstant); + return entries.size() - 1; + }); + } + + public int intConstant(int value) { + return constantCPI.computeIfAbsent(value, key -> { + JavaConstant javaConstant = BuildTimeInterpreterUniverse.singleton().primitiveConstant(value); + entries.add(javaConstant); + return entries.size() - 1; + }); + } + + public int floatConstant(float value) { + return constantCPI.computeIfAbsent(value, key -> { + JavaConstant javaConstant = BuildTimeInterpreterUniverse.singleton().primitiveConstant(value); + entries.add(javaConstant); + return entries.size() - 1; + }); + } + + public int doubleConstant(double value) { + return constantCPI.computeIfAbsent(value, key -> { + JavaConstant javaConstant = BuildTimeInterpreterUniverse.singleton().primitiveConstant(value); + entries.add(javaConstant); + return entries.size() - 1; + }); + } + + public int stringConstant(String value) { + return constantCPI.computeIfAbsent(value, key -> { + JavaConstant javaConstant = BuildTimeInterpreterUniverse.singleton().stringConstant(value); + entries.add(javaConstant); + return entries.size() - 1; + }); + } + + public int typeConstant(JavaType type) { + if (!(type instanceof InterpreterResolvedJavaType || type instanceof UnresolvedJavaType)) { + throw new IllegalArgumentException("Type must be either InterpreterResolvedJavaType or UnresolvedJavaType"); + } + return typeCPI.computeIfAbsent(type, key -> { + entries.add(type); + return entries.size() - 1; + }); + } + + public int method(JavaMethod method) { + if (!(method instanceof InterpreterResolvedJavaMethod || method instanceof UnresolvedJavaMethod)) { + throw new IllegalArgumentException("Type must be either InterpreterResolvedJavaMethod or UnresolvedJavaMethod"); + } + return methodCPI.computeIfAbsent(method, (key) -> { + entries.add(method); + return entries.size() - 1; + }); + } + + public int field(JavaField field) { + if (!(field instanceof InterpreterResolvedJavaField || field instanceof UnresolvedJavaField)) { + throw new IllegalArgumentException("Type must be either InterpreterResolvedJavaField or UnresolvedJavaField"); + } + return fieldCPI.computeIfAbsent(field, (key) -> { + entries.add(field); + return entries.size() - 1; + }); + } + + private int appendixConstant(JavaConstant appendix) { + assert appendix instanceof ReferenceConstant || appendix.isNull(); + return appendixCPI.computeIfAbsent(appendix, key -> { + entries.add(appendix); + return entries.size() - 1; + }); + } + + public int weakObjectConstant(ImageHeapConstant imageHeapConstant) { + return constantCPI.computeIfAbsent(imageHeapConstant, key -> { + JavaConstant javaConstant = BuildTimeInterpreterUniverse.singleton().weakObjectConstant(imageHeapConstant); + entries.add(javaConstant); + return entries.size() - 1; + }); + } + + private int ldcConstant(Object javaConstantOrType) { + if (javaConstantOrType instanceof JavaConstant javaConstant) { + switch (javaConstant.getJavaKind()) { + case Boolean, Byte, Short, Char, Int: + return intConstant(javaConstant.asInt()); + case Float: + return floatConstant(javaConstant.asFloat()); + case Long: + return longConstant(javaConstant.asLong()); + case Double: + return doubleConstant(javaConstant.asDouble()); + case Object: + if (javaConstant instanceof ImageHeapConstant imageHeapConstant) { + return weakObjectConstant(imageHeapConstant); + } + } + } else if (javaConstantOrType instanceof JavaType javaType) { + JavaType interpreterType = BuildTimeInterpreterUniverse.singleton().typeOrUnresolved(javaType); + return typeConstant(interpreterType); + } + throw VMError.shouldNotReachHereUnexpectedInput(javaConstantOrType); + } + + public static BuildTimeConstantPool create(InterpreterResolvedObjectType type) { + BuildTimeConstantPool btcp = new BuildTimeConstantPool(type); + btcp.hydrate(type); + return btcp; + } + + private ExceptionHandler[] processExceptionHandlers(ExceptionHandler[] hostExceptionHandlers) { + if (hostExceptionHandlers.length == 0) { + return EMPTY_EXCEPTION_HANDLERS; + } + ExceptionHandler[] handlers = new ExceptionHandler[hostExceptionHandlers.length]; + for (int i = 0; i < handlers.length; i++) { + ExceptionHandler host = hostExceptionHandlers[i]; + JavaType resolvedCatchType = null; + JavaType interpreterCatchType = null; + int catchTypeCPI = 0; + if (!host.isCatchAll()) { + resolvedCatchType = host.getCatchType(); + interpreterCatchType = BuildTimeInterpreterUniverse.singleton().typeOrUnresolved(resolvedCatchType); + // catchTypeCPI must be patched. + catchTypeCPI = typeConstant(interpreterCatchType); + } + + handlers[i] = BuildTimeInterpreterUniverse.singleton() + .exceptionHandler(new ExceptionHandler(host.getStartBCI(), host.getEndBCI(), host.getHandlerBCI(), catchTypeCPI, interpreterCatchType)); + } + return handlers; + } + + public static boolean weedOut(InterpreterResolvedObjectType type, HostedUniverse hUniverse) { + boolean chasingFixpoint = false; + + methodsLoop: for (InterpreterResolvedJavaMethod method : BuildTimeInterpreterUniverse.singleton().allDeclaredMethods(type)) { + if (!method.needsMethodBody() || !method.isInterpreterExecutable()) { + method.setCode(null); + } + + byte[] code = method.getInterpretedCode(); + if (code == null || code.length == 0) { + continue; + } + + ResolvedJavaMethod originalMethod = method.getOriginalMethod(); + ConstantPool originalConstantPool = originalMethod.getConstantPool(); + for (int bci = 0; bci < BytecodeStream.endBCI(code); bci = BytecodeStream.nextBCI(code, bci)) { + int bytecode = BytecodeStream.currentBC(code, bci); + switch (bytecode) { + case INVOKEINTERFACE, INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEDYNAMIC -> { + int originalCPI; + if (bytecode == INVOKEDYNAMIC) { + originalCPI = BytecodeStream.readCPI4(code, bci); + } else { + originalCPI = BytecodeStream.readCPI(code, bci); + } + JavaMethod calleeOriginalJavaMethod = null; + try { + calleeOriginalJavaMethod = originalConstantPool.lookupMethod(originalCPI, bytecode); + } catch (UnsupportedFeatureException | UserError.UserException e) { + // ignore + } + if (calleeOriginalJavaMethod != null) { + JavaMethod calleeInterpreterMethod = BuildTimeInterpreterUniverse.singleton().methodOrUnresolved(calleeOriginalJavaMethod); + if (calleeInterpreterMethod instanceof InterpreterResolvedJavaMethod calleeInterpreterResolvedJavaMethod) { + if (!calleeInterpreterResolvedJavaMethod.isInterpreterExecutable()) { + HostedMethod calleeHostedMethod = hUniverse.optionalLookup(calleeOriginalJavaMethod); + if (calleeHostedMethod.isCompiled()) { + InterpreterUtil.log("[weedout] good. Call from %s @ bci=%s (interp) to %s (compiled) possible", method, bci, calleeInterpreterResolvedJavaMethod); + } else if (calleeHostedMethod.hasVTableIndex()) { + InterpreterUtil.log("[weedout] good. Virtual call from %s @ bci=%s (interp) to %s (compiled) possible", method, bci, calleeInterpreterResolvedJavaMethod); + } else if (calleeHostedMethod.getImplementations().length == 1) { + InterpreterUtil.log("[weedout] good. Virtual call from %s @ bci=%s (interp) has exactly one implementation available %s", method, bci, + calleeHostedMethod.getImplementations()[0]); + } else { + InterpreterUtil.log("[weedout] bad. %s downgraded to non-interpreter-executable.", method); + InterpreterUtil.log(" there is no way to call a compiled version of %s or to execute it in the interpreter, but it is considered reachable", + calleeInterpreterResolvedJavaMethod); + chasingFixpoint = true; + method.setCode(null); + continue methodsLoop; + } + } else { + /* the interpreter can dispatch the callee */ + } + } else { + /* + * not reached during analysis, let it fail during runtime if this + * call-site is reached + */ + } + } else { + InterpreterUtil.log("[weedout] ??? call from %s at bci=%s does not go anywhere", method, bci); + } + } + } + } + } + return chasingFixpoint; + } + + public void hydrate(InterpreterResolvedObjectType type) { + + List allDeclaredMethods = BuildTimeInterpreterUniverse.singleton().allDeclaredMethods(type); + + // LDC (single-byte CPI) bytecodes must be processed first. + processLDC(allDeclaredMethods); + + for (InterpreterResolvedJavaMethod method : allDeclaredMethods) { + ResolvedJavaMethod originalMethod = method.getOriginalMethod(); + method.setExceptionHandlers(processExceptionHandlers(originalMethod.getExceptionHandlers())); + + LocalVariableTable hostLocalVariableTable = method.getOriginalMethod().getLocalVariableTable(); + if (hostLocalVariableTable != null) { + method.setLocalVariableTable(BuildTimeInterpreterUniverse.processLocalVariableTable(hostLocalVariableTable)); + } + + if (!method.needsMethodBody()) { + VMError.guarantee(method.getInterpretedCode() == null); + } + + byte[] code = method.getInterpretedCode(); + if (code == null || code.length == 0) { + continue; + } + + InterpreterUtil.log("[hydrate] processing method=%s", method); + + ConstantPool originalConstantPool = originalMethod.getConstantPool(); + for (int bci = 0; bci < BytecodeStream.endBCI(code); bci = BytecodeStream.nextBCI(code, bci)) { + int bytecode = BytecodeStream.currentBC(code, bci); + switch (bytecode) { + case LDC: // fall-through + case LDC_W: // fall-through + case LDC2_W: { + int originalCPI = BytecodeStream.readCPI(code, bci); + int newCPI = 0; + // GR-44571: Somehow obtain an unresolved type to print useful error + // at runtime. + try { + Object originalConstant = originalConstantPool.lookupConstant(originalCPI); + newCPI = ldcConstant(originalConstant); + } catch (UnsupportedFeatureException | AnalysisError.TypeNotFoundError e) { + // cannot resolve type, ignore + } + BytecodeStream.patchCPI(code, bci, newCPI); + break; + } + case GETSTATIC: // fall-through + case PUTSTATIC: // fall-through + case GETFIELD: // fall-through + case PUTFIELD: { + int originalCPI = BytecodeStream.readCPI(code, bci); + int newCPI = 0; + JavaField originalJavaField = null; + try { + originalJavaField = originalConstantPool.lookupField(originalCPI, originalMethod, bytecode); + } catch (UnsupportedFeatureException e) { + // ignore + } + // GR-44571: Somehow obtain an unresolved field to print useful error + // at runtime. + if (originalJavaField != null) { + JavaField interpreterField = BuildTimeInterpreterUniverse.singleton().fieldOrUnresolved(originalJavaField); + newCPI = field(interpreterField); + } + BytecodeStream.patchCPI(code, bci, newCPI); + break; + } + case ANEWARRAY: // fall-through + case MULTIANEWARRAY: // fall-through + case NEW: // fall-through + case INSTANCEOF: // fall-through + case CHECKCAST: { + int originalCPI = BytecodeStream.readCPI(code, bci); + int newCPI = 0; + JavaType originalJavaType = null; + try { + originalJavaType = originalConstantPool.lookupType(originalCPI, bytecode); + } catch (UnsupportedFeatureException | AnalysisError.TypeNotFoundError e) { + // GR-44571: Type has not been seen during analysis (e.g. path + // has not been reached). + // Will patch the CPI with 0. + } + + // GR-44571: Somehow obtain an unresolved type to print useful error + // at runtime. + if (originalJavaType != null) { + JavaType interpreterType = BuildTimeInterpreterUniverse.singleton().typeOrUnresolved(originalJavaType); + newCPI = typeConstant(interpreterType); + } + BytecodeStream.patchCPI(code, bci, newCPI); + break; + } + case INVOKEINTERFACE: // fall-through + case INVOKEVIRTUAL: // fall-through + case INVOKESPECIAL: // fall-through + case INVOKESTATIC: + case INVOKEDYNAMIC: { + int originalCPI; + if (bytecode == INVOKEDYNAMIC) { + originalCPI = BytecodeStream.readCPI4(code, bci); + } else { + originalCPI = BytecodeStream.readCPI(code, bci); + } + JavaMethod originalJavaMethod = null; + int newCPI = 0; + try { + originalJavaMethod = originalConstantPool.lookupMethod(originalCPI, bytecode); + } catch (UnsupportedFeatureException | UserError.UserException e) { + // ignore + } + // GR-44571: Somehow obtain an unresolved method to print useful + // error at runtime. + if (originalJavaMethod != null) { + JavaMethod interpreterMethod = BuildTimeInterpreterUniverse.singleton().methodOrUnresolved(originalJavaMethod); + if (interpreterMethod instanceof InterpreterResolvedJavaMethod) { + ((InterpreterResolvedJavaMethod) interpreterMethod).setNativeEntryPoint(new MethodPointer((ResolvedJavaMethod) originalJavaMethod)); + InterpreterUtil.log("[hydrate] setting method pointer for %s", interpreterMethod); + } + newCPI = method(interpreterMethod); + } + + if (bytecode == INVOKEDYNAMIC) { + int newAppendixCPI = 0; + JavaConstant appendix = originalConstantPool.lookupAppendix(originalCPI, bytecode); + if (appendix != null) { + JavaConstant interpreterAppendix = BuildTimeInterpreterUniverse.singleton().appendix(appendix); + newAppendixCPI = appendixConstant(interpreterAppendix); + } else { + // The appendix may be null, in which case a NullConstant is stored + // in the CP. + newAppendixCPI = appendixConstant(JavaConstant.NULL_POINTER); + } + BytecodeStream.patchAppendixCPI(code, bci, newAppendixCPI); + } + + BytecodeStream.patchCPI(code, bci, newCPI); + break; + } + } + } + } + } + + private void processLDC(List allDeclaredMethods) { + for (InterpreterResolvedJavaMethod method : allDeclaredMethods) { + byte[] code = method.getInterpretedCode(); + if (code == null || code.length == 0) { + continue; + } + ConstantPool originalConstantPool = method.getOriginalMethod().getConstantPool(); + for (int bci = 0; bci < BytecodeStream.endBCI(code); bci = BytecodeStream.nextBCI(code, bci)) { + if (BytecodeStream.opcode(code, bci) == Bytecodes.LDC) { + try { + Object constant = originalConstantPool.lookupConstant(BytecodeStream.readCPI(code, bci)); + ldcConstant(constant); + } catch (UnsupportedFeatureException | AnalysisError.TypeNotFoundError e) { + // constant cannot be resolved, ignore + } + } + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeInterpreterUniverse.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeInterpreterUniverse.java new file mode 100644 index 000000000000..e5dcc068f79c --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeInterpreterUniverse.java @@ -0,0 +1,890 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter; + +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEINTERFACE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKESPECIAL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKESTATIC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEVIRTUAL; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.core.util.HostedStringDeduplication; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.BytecodeStream; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedPrimitiveType; +import com.oracle.svm.interpreter.metadata.InterpreterUniverseImpl; +import com.oracle.svm.interpreter.metadata.InterpreterUnresolvedSignature; +import com.oracle.svm.interpreter.metadata.MetadataUtil; +import com.oracle.svm.interpreter.metadata.ReferenceConstant; +import com.oracle.svm.hosted.meta.HostedMethod; +import com.oracle.svm.hosted.meta.HostedUniverse; +import com.oracle.svm.hosted.pltgot.GOTEntryAllocator; +import com.oracle.svm.hosted.substitute.SubstitutionMethod; + +import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; +import jdk.vm.ci.meta.ExceptionHandler; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaField; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.JavaMethod; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.LineNumberTable; +import jdk.vm.ci.meta.Local; +import jdk.vm.ci.meta.LocalVariableTable; +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.PrimitiveConstant; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.Signature; +import jdk.vm.ci.meta.UnresolvedJavaField; +import jdk.vm.ci.meta.UnresolvedJavaMethod; +import jdk.vm.ci.meta.UnresolvedJavaType; + +/** + * Maintains the view of classes, methods and fields needed to build the InterpreterUniverse, this + * is all metadata that is required for execution in the interpreter at run-time. + * + * The view is not a 1:1 mapping to other universes. For example, some methods might not be + * executable by the interpreter and are therefore stripped from the InterpreterUniverse partially + * (e.g. bytecode array is dropped, but a method pointer to the compiled version is still held). + * + * {@link #snapshot()} persist the current view into a InterpreterUniverse at the end of an image + * build and is then serialized to an additional file. + */ +@Platforms(Platform.HOSTED_ONLY.class) +public final class BuildTimeInterpreterUniverse { + + private static BuildTimeInterpreterUniverse INSTANCE; + + public static void freshSingletonInstance() { + INSTANCE = new BuildTimeInterpreterUniverse(); + } + + private final Map types; + private final Map unresolvedTypes; + private final Map unresolvedMethods; + private final Map unresolvedFields; + + private final Map fields; + + private final Map methods; + private final Map signatures; + private final Map primitiveConstants; + private final Map> strings; + private final Map> objectConstants; + + private final Map exceptionHandlers; + + private SnippetReflectionProvider snippetReflectionProvider; + + public SnippetReflectionProvider getSnippetReflectionProvider() { + return snippetReflectionProvider; + } + + private void setSnippetReflectionProvider(SnippetReflectionProvider snippetReflectionProvider) { + this.snippetReflectionProvider = snippetReflectionProvider; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static InterpreterResolvedObjectType createResolvedObjectType(ResolvedJavaType resolvedJavaType) { + BuildTimeInterpreterUniverse universe = BuildTimeInterpreterUniverse.singleton(); + String name = universe.dedup(resolvedJavaType.getName()); + Class clazz = OriginalClassProvider.getJavaClass(resolvedJavaType); + ResolvedJavaType originalType = MetadataUtil.requireNonNull(resolvedJavaType); + int modifiers = resolvedJavaType.getModifiers(); + InterpreterResolvedJavaType componentType; + if (originalType.isArray()) { + componentType = universe.type(originalType.getComponentType()); + } else { + componentType = null; + } + String sourceFileName = universe.dedup(resolvedJavaType.getSourceFileName()); + + ResolvedJavaType originalSuperclass = resolvedJavaType.getSuperclass(); + InterpreterResolvedObjectType superclass = null; + if (originalSuperclass != null) { + superclass = (InterpreterResolvedObjectType) universe.type(originalSuperclass); + } + + ResolvedJavaType[] originalInterfaces = resolvedJavaType.getInterfaces(); + InterpreterResolvedObjectType[] interfaces = new InterpreterResolvedObjectType[originalInterfaces.length]; + for (int i = 0; i < interfaces.length; i++) { + interfaces[i] = (InterpreterResolvedObjectType) universe.type(originalInterfaces[i]); + } + + return InterpreterResolvedObjectType.createAtBuildTime(resolvedJavaType, name, modifiers, componentType, superclass, interfaces, null, clazz, sourceFileName); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static InterpreterResolvedJavaField createResolvedJavaField(ResolvedJavaField resolvedJavaField) { + ResolvedJavaField originalField = resolvedJavaField; + BuildTimeInterpreterUniverse universe = BuildTimeInterpreterUniverse.singleton(); + String name = universe.dedup(resolvedJavaField.getName()); + int modifiers = resolvedJavaField.getModifiers(); + JavaType fieldType = originalField.getType(); + + InterpreterResolvedJavaType type = universe.type((ResolvedJavaType) fieldType); + InterpreterResolvedObjectType declaringClass = universe.referenceType(originalField.getDeclaringClass()); + + return InterpreterResolvedJavaField.create(originalField, name, modifiers, type, declaringClass, 0, null); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static InterpreterResolvedJavaMethod createResolveJavaMethod(ResolvedJavaMethod originalMethod) { + assert originalMethod instanceof AnalysisMethod; + MetadataUtil.requireNonNull(originalMethod); + BuildTimeInterpreterUniverse universe = BuildTimeInterpreterUniverse.singleton(); + String name = universe.dedup(originalMethod.getName()); + int maxLocals = originalMethod.getMaxLocals(); + int maxStackSize = originalMethod.getMaxStackSize(); + int modifiers = originalMethod.getModifiers(); + InterpreterResolvedObjectType declaringClass = universe.referenceType(originalMethod.getDeclaringClass()); + InterpreterUnresolvedSignature signature = universe.unresolvedSignature(originalMethod.getSignature()); + byte[] interpretedCode = originalMethod.getCode() == null ? null : originalMethod.getCode().clone(); + + AnalysisMethod analysisMethod = (AnalysisMethod) originalMethod; + if (analysisMethod.wrapped instanceof SubstitutionMethod substitutionMethod) { + modifiers = substitutionMethod.getOriginal().getModifiers(); + if (substitutionMethod.hasBytecodes()) { + /* + * GR-53710: Keep bytecodes for substitutions, but only when there's no compiled + * entry. This is required to call Class.forName(String,boolean,ClassLoader) which + * is a substitution with no compiled entry. + */ + // Drop NATIVE flag from original method modifiers. + modifiers &= ~Modifier.NATIVE; + } + } + + LineNumberTable lineNumberTable = originalMethod.getLineNumberTable(); + return InterpreterResolvedJavaMethod.create( + originalMethod, + name, + maxLocals, + maxStackSize, + modifiers, + declaringClass, + signature, + interpretedCode, + null, + lineNumberTable, + null, + null, + InterpreterResolvedJavaMethod.VTBL_NO_ENTRY, + GOTEntryAllocator.GOT_NO_ENTRY, + InterpreterResolvedJavaMethod.EST_NO_ENTRY, + InterpreterResolvedJavaMethod.UNKNOWN_METHOD_ID); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static InterpreterUnresolvedSignature createUnresolvedSignature(Signature originalSignature) { + MetadataUtil.requireNonNull(originalSignature); + JavaType returnType = BuildTimeInterpreterUniverse.singleton().primitiveOrUnresolvedType(originalSignature.getReturnType(null)); + int parameterCount = originalSignature.getParameterCount(false); + JavaType[] parameterTypes = new JavaType[parameterCount]; + for (int i = 0; i < parameterCount; i++) { + parameterTypes[i] = BuildTimeInterpreterUniverse.singleton().primitiveOrUnresolvedType(originalSignature.getParameterType(i, null)); + } + return InterpreterUnresolvedSignature.create(originalSignature, returnType, parameterTypes); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static LocalVariableTable processLocalVariableTable(LocalVariableTable hostLocalVariableTable) { + Local[] hostLocals = hostLocalVariableTable.getLocals(); + if (hostLocals.length == 0) { + return InterpreterResolvedJavaMethod.EMPTY_LOCAL_VARIABLE_TABLE; + } + Local[] locals = new Local[hostLocals.length]; + for (int i = 0; i < locals.length; i++) { + Local host = hostLocals[i]; + JavaType hostType = host.getType(); + JavaType interpreterType = null; + if (hostType == null) { + InterpreterUtil.log("[processLocalVariableTable] BUG? host=%s. Remove local entry?", host); + } else { + interpreterType = BuildTimeInterpreterUniverse.singleton().typeOrUnresolved(hostType); + } + if (hostType instanceof AnalysisType && !((AnalysisType) hostType).isReachable()) { + /* + * Example: SecurityManager in java.lang.ThreadGroup.checkAccess. There is a + * graphbuilder plugin that makes sure getSecurityManager() always returns null, + * thus not reachable. + * + * For now, unresolved and unreachable types are not an issue since the + * LocalVariableTable attribute only keeps strictly unresolved types and primitives. + */ + } + locals[i] = BuildTimeInterpreterUniverse.singleton().local(new Local(host.getName(), interpreterType, host.getStartBCI(), host.getEndBCI(), host.getSlot())); + } + return BuildTimeInterpreterUniverse.singleton().localVariableTable(new LocalVariableTable(locals)); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static void setNeedMethodBody(InterpreterResolvedJavaMethod thiz, boolean needMethodBody, MetaAccessProvider metaAccessProvider) { + if (thiz.needMethodBody) { + // skip, already scanned + return; + } else if (!needMethodBody) { + // nothing to do + return; + } + + if (thiz.getInterpretedCode() == null) { + // nothing to scan, method is not interpreterExecutable + return; + } + + thiz.needMethodBody = true; + + for (int bci = 0; bci < BytecodeStream.endBCI(thiz.getInterpretedCode()); bci = BytecodeStream.nextBCI(thiz.getInterpretedCode(), bci)) { + int opcode = BytecodeStream.opcode(thiz.getInterpretedCode(), bci); + switch (opcode) { + /* GR-53540: Handle invokedyanmic too */ + case INVOKESPECIAL, INVOKESTATIC, INVOKEVIRTUAL, INVOKEINTERFACE -> { + int originalCPI = BytecodeStream.readCPI(thiz.getInterpretedCode(), bci); + try { + JavaMethod method = thiz.getOriginalMethod().getConstantPool().lookupMethod(originalCPI, opcode); + if (!(method instanceof ResolvedJavaMethod resolvedJavaMethod)) { + continue; + } + if (!InterpreterFeature.callableByInterpreter(resolvedJavaMethod, metaAccessProvider)) { + InterpreterUtil.log("[process invokes] cannot execute %s due to call-site (%s) @ bci=%s is not callable by interpreter%n", thiz.getName(), bci, method); + thiz.setCode(null); + thiz.needMethodBody = false; + return; + } + + if (opcode == INVOKESPECIAL || opcode == INVOKESTATIC) { + continue; + } + + BuildTimeInterpreterUniverse.singleton().method(resolvedJavaMethod, false, metaAccessProvider); + } catch (UnsupportedFeatureException | UserError.UserException e) { + InterpreterUtil.log("[process invokes] lookup in method %s failed due to:", thiz.getOriginalMethod()); + InterpreterUtil.log(e); + // ignore, call will fail at run-time if reached + } + } + } + } + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static void setUnmaterializedConstantValue(InterpreterResolvedJavaField thiz, JavaConstant constant) { + assert constant == JavaConstant.NULL_POINTER || constant instanceof PrimitiveConstant || constant instanceof ImageHeapConstant; + BuildTimeInterpreterUniverse buildTimeInterpreterUniverse = BuildTimeInterpreterUniverse.singleton(); + switch (thiz.getJavaKind()) { + case Boolean, Byte, Short, Char, Int, Float, Long, Double: + assert constant instanceof PrimitiveConstant; + thiz.setUnmaterializedConstant(buildTimeInterpreterUniverse.constant(constant)); + break; + case Object: + if (constant.isNull()) { + // The value is always null. + thiz.setUnmaterializedConstant(buildTimeInterpreterUniverse.constant(JavaConstant.NULL_POINTER)); + } else if (constant.getJavaKind() == JavaKind.Illegal) { + // Materialized field without location e.g. DynamicHub#vtable. + thiz.setUnmaterializedConstant(buildTimeInterpreterUniverse.constant(JavaConstant.ILLEGAL)); + } else if (thiz.getType().isWordType()) { + // Can be a WordType with a primitive constant value. + thiz.setUnmaterializedConstant(buildTimeInterpreterUniverse.constant(constant)); + } else if (constant instanceof ImageHeapConstant imageHeapConstant) { + // Create a WeakImageHeapReference, the referent is only preserved iff it is + // present in the native image heap. + thiz.setUnmaterializedConstant(ReferenceConstant.createFromImageHeapConstant(imageHeapConstant)); + } else { + throw VMError.shouldNotReachHere("Unsupported unmaterialized constant value: " + constant); + } + break; + default: + throw VMError.shouldNotReachHere("Invalid field kind: " + thiz.getJavaKind()); + } + if (!thiz.isUndefined()) { + if (thiz.getType().isWordType()) { + VMError.guarantee(thiz.getUnmaterializedConstant().getJavaKind() == InterpreterToVM.wordJavaKind()); + } else { + VMError.guarantee(thiz.getUnmaterializedConstant().getJavaKind() == thiz.getJavaKind()); + } + } + } + + @Platforms(Platform.HOSTED_ONLY.class) + private static final class LocalWrapper { + + final int hash; + final Local local; + + private LocalWrapper(Local local) { + this.local = MetadataUtil.requireNonNull(local); + this.hash = hashCode(this.local); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof LocalWrapper thatWrapper) { + Local that = thatWrapper.local; + return local.getName().equals(that.getName()) && local.getStartBCI() == that.getStartBCI() && local.getEndBCI() == that.getEndBCI() && local.getSlot() == that.getSlot() && + MetadataUtil.equals(local.getType(), that.getType()); + + } else { + return false; + } + } + + public static int hashCode(Local local) { + int h = MetadataUtil.hashCode(local.getName()); + h = h * 31 + local.getStartBCI(); + h = h * 31 + local.getEndBCI(); + h = h * 31 + local.getSlot(); + h = h * 31 + MetadataUtil.hashCode(local.getType()); + return h; + } + + @Override + public int hashCode() { + return hash; + } + } + + private final Map locals; + + @Platforms(Platform.HOSTED_ONLY.class) + private static final class LocalVariableTableWrapper { + final LocalVariableTable localVariableTable; + final Local[] locals; + final int hash; + + private LocalVariableTableWrapper(LocalVariableTable localVariableTable) { + this.localVariableTable = localVariableTable; + this.locals = localVariableTable.getLocals(); + int h = 0; + for (Local local : this.locals) { + h = h * 31 + LocalWrapper.hashCode(local); + } + this.hash = h; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof LocalVariableTableWrapper thatWrapper) { + if (localVariableTable == thatWrapper.localVariableTable) { + return true; + } + return Arrays.equals(locals, thatWrapper.locals); + } else { + return false; + } + } + + @Override + public int hashCode() { + return hash; + } + } + + private final Map localVariableTables; + + private final Map indyAppendices; + + public BuildTimeInterpreterUniverse() { + this.types = new HashMap<>(); + this.fields = new HashMap<>(); + this.methods = new HashMap<>(); + this.signatures = new HashMap<>(); + this.primitiveConstants = new HashMap<>(); + this.strings = new HashMap<>(); + this.objectConstants = new HashMap<>(); + this.exceptionHandlers = new HashMap<>(); + this.locals = new HashMap<>(); + this.localVariableTables = new HashMap<>(); + this.unresolvedTypes = new HashMap<>(); + this.unresolvedMethods = new HashMap<>(); + this.unresolvedFields = new HashMap<>(); + this.indyAppendices = new HashMap<>(); + } + + public static BuildTimeInterpreterUniverse singleton() { + return INSTANCE; + } + + public InterpreterResolvedObjectType referenceType(ResolvedJavaType resolvedJavaType) { + return (InterpreterResolvedObjectType) type(resolvedJavaType); + } + + @SuppressWarnings("static-method") + public String dedup(String string) { + return HostedStringDeduplication.singleton().deduplicate(string, false); + } + + public InterpreterResolvedPrimitiveType primitiveType(ResolvedJavaType resolvedJavaType) { + return (InterpreterResolvedPrimitiveType) type(resolvedJavaType); + } + + public InterpreterResolvedJavaType getType(ResolvedJavaType resolvedJavaType) { + return types.get(resolvedJavaType); + } + + public InterpreterResolvedJavaType type(ResolvedJavaType resolvedJavaType) { + assert resolvedJavaType instanceof AnalysisType; + InterpreterResolvedJavaType result = getType(resolvedJavaType); + if (result == null) { + synchronized (types) { + result = types.get(resolvedJavaType); + if (result == null) { + if (resolvedJavaType.isPrimitive()) { + result = InterpreterResolvedPrimitiveType.fromKind(JavaKind.fromPrimitiveOrVoidTypeChar(resolvedJavaType.getName().charAt(0))); + } else { + result = createResolvedObjectType(resolvedJavaType); + } + InterpreterUtil.log("[universe] Adding type '%s'", resolvedJavaType); + types.put(resolvedJavaType, result); + } + } + } + return result; + } + + public InterpreterResolvedJavaField field(ResolvedJavaField resolvedJavaField) { + assert resolvedJavaField instanceof AnalysisField; + InterpreterResolvedJavaField result = fields.get(resolvedJavaField); + if (result == null) { + synchronized (fields) { + result = fields.get(resolvedJavaField); + if (result == null) { + InterpreterUtil.log("[universe] Adding field '%s'", resolvedJavaField); + fields.put(resolvedJavaField, result = createResolvedJavaField(resolvedJavaField)); + } + } + } + return result; + } + + public InterpreterResolvedJavaMethod getMethod(ResolvedJavaMethod method) { + ResolvedJavaMethod wrapped = method; + if (wrapped instanceof HostedMethod hostedMethod) { + wrapped = hostedMethod.getWrapped(); + } + return methods.get(wrapped); + } + + public InterpreterResolvedJavaMethod method(ResolvedJavaMethod resolvedJavaMethod, boolean needsMethodBody, MetaAccessProvider metaAccessProvider) { + assert resolvedJavaMethod instanceof AnalysisMethod; + InterpreterResolvedJavaMethod result = getMethod(resolvedJavaMethod); + + if (result == null) { + synchronized (methods) { + result = methods.get(resolvedJavaMethod); + if (result == null) { + InterpreterUtil.log("[universe] Adding method '%s' with needsMethodBody=%s", resolvedJavaMethod, needsMethodBody); + methods.put(resolvedJavaMethod, result = createResolveJavaMethod(resolvedJavaMethod)); + } + } + } + + if (needsMethodBody) { + /* added explicitly, bytecodes are needed for interpretation */ + setNeedMethodBody(result, true, metaAccessProvider); + } + + return result; + } + + public JavaConstant weakObjectConstant(ImageHeapConstant imageHeapConstant) { + // Try to extract hosted references if possible. + // Some constants are stored in the interpreter metadata even if they are not included in + // the image heap e.g. String. + if (imageHeapConstant.isBackedByHostedObject()) { + Object value = snippetReflectionProvider.asObject(Object.class, imageHeapConstant.getHostedObject()); + if (value != null) { + return objectConstants.computeIfAbsent(imageHeapConstant, (key) -> ReferenceConstant.createFromNonNullReference(value)); + } + } + return objectConstants.computeIfAbsent(imageHeapConstant, (key) -> ReferenceConstant.createFromImageHeapConstant(imageHeapConstant)); + } + + public JavaConstant primitiveConstant(int value) { + return primitiveConstants.computeIfAbsent(value, (key) -> JavaConstant.forInt(value)); + } + + public JavaConstant primitiveConstant(long value) { + return primitiveConstants.computeIfAbsent(value, (key) -> JavaConstant.forLong(value)); + } + + public JavaConstant primitiveConstant(float value) { + return primitiveConstants.computeIfAbsent(value, (key) -> JavaConstant.forFloat(value)); + } + + public JavaConstant primitiveConstant(double value) { + return primitiveConstants.computeIfAbsent(value, (key) -> JavaConstant.forDouble(value)); + } + + public JavaConstant stringConstant(String value) { + return strings.computeIfAbsent(value, (key) -> ReferenceConstant.createFromNonNullReference(Objects.requireNonNull(value))); + } + + public JavaType primitiveOrUnresolvedType(JavaType type) { + // Primitives are always resolved. + if (type.getJavaKind().isPrimitive()) { + return InterpreterResolvedPrimitiveType.fromKind(type.getJavaKind()); + } + return unresolvedTypes.computeIfAbsent(dedup(MetadataUtil.toUniqueString(type)), UnresolvedJavaType::create); + } + + public JavaConstant appendix(JavaConstant appendix) { + Objects.requireNonNull(appendix); + JavaConstant result = indyAppendices.get(appendix); + if (result == null) { + synchronized (indyAppendices) { + result = indyAppendices.get(appendix); + if (result == null) { + if (appendix instanceof ImageHeapConstant imageHeapConstant) { + result = weakObjectConstant(imageHeapConstant); + } else { + VMError.shouldNotReachHere("unexpected appendix: " + appendix); + } + indyAppendices.put(appendix, result); + } + } + } + return result; + } + + public InterpreterUnresolvedSignature unresolvedSignature(Signature signature) { + return signatures.computeIfAbsent(MetadataUtil.toUniqueString(signature), key -> createUnresolvedSignature(signature)); + } + + public JavaType typeOrUnresolved(JavaType type) { + JavaType result = types.get(type); + if (result == null) { + // UnresolvedJavaType can be trusted because it only refers to type by name. + result = primitiveOrUnresolvedType(type); + } + return result; + } + + public ExceptionHandler exceptionHandler(ExceptionHandler handler) { + return exceptionHandlers.computeIfAbsent(handler, Function.identity()); + } + + public Local local(Local local) { + return locals.computeIfAbsent(new LocalWrapper(local), Function.identity()).local; + } + + public LocalVariableTable localVariableTable(LocalVariableTable localVariableTable) { + return localVariableTables.computeIfAbsent(new LocalVariableTableWrapper(localVariableTable), Function.identity()).localVariableTable; + } + + // returns InterpreterResolvedJavaField | UnresolvedJavaField + public JavaField fieldOrUnresolved(JavaField field) { + JavaField result = fields.get(field); + if (result == null) { + // Do not trust incoming UnresolvedJavaField, an unresolved field may have a resolved + // declaring type. + JavaType holder = primitiveOrUnresolvedType(field.getDeclaringClass()); + JavaType type = primitiveOrUnresolvedType(field.getType()); + result = unresolvedFields.computeIfAbsent(MetadataUtil.toUniqueString(field), key -> new UnresolvedJavaField(holder, dedup(field.getName()), type)); + } + return result; + } + + // returns InterpreterResolvedJavaMethod | UnresolvedJavaMethod + public JavaMethod methodOrUnresolved(JavaMethod method0) { + final JavaMethod method = method0 instanceof HostedMethod hostedMethod ? hostedMethod.wrapped : method0; + JavaMethod result = methods.get(method); + if (result == null) { + // Do not trust incoming unresolved method, it may have resolved holder. + JavaType holder = primitiveOrUnresolvedType(method.getDeclaringClass()); + Signature signature = unresolvedSignature(method.getSignature()); + result = unresolvedMethods.computeIfAbsent(MetadataUtil.toUniqueString(method), key -> new UnresolvedJavaMethod(dedup(method.getName()), signature, holder)); + } + return result; + } + + private Map> classToMethods; + private Map> classToFields; + + /** + * This classes cause problems when resolving constant pool entries. + */ + public void createConstantPools(HostedUniverse hUniverse) { + setSnippetReflectionProvider(hUniverse.getSnippetReflection()); + + this.classToMethods = methods.values().stream().collect(Collectors.groupingBy(InterpreterResolvedJavaMethod::getDeclaringClass)); + this.classToFields = fields.values().stream().collect(Collectors.groupingBy(InterpreterResolvedJavaField::getDeclaringClass)); + + boolean needsAnotherRound = true; + int iterations = 0; + while (needsAnotherRound) { + needsAnotherRound = false; + iterations++; + InterpreterUtil.log("[weedout] iteration %s", iterations); + for (InterpreterResolvedJavaType type : types.values()) { + if (type instanceof InterpreterResolvedObjectType referenceType) { + needsAnotherRound |= BuildTimeConstantPool.weedOut(referenceType, hUniverse); + } + } + } + + for (InterpreterResolvedJavaType type : types.values()) { + if (type instanceof InterpreterResolvedObjectType referenceType) { + BuildTimeConstantPool buildTimeConstantPool = BuildTimeConstantPool.create(referenceType); + referenceType.setConstantPool(buildTimeConstantPool.snapshot()); + } + } + } + + public List allDeclaredMethods(InterpreterResolvedObjectType type) { + return classToMethods.getOrDefault(type, Collections.emptyList()); + } + + public List allDeclaredFields(InterpreterResolvedObjectType type) { + return classToFields.getOrDefault(type, Collections.emptyList()); + } + + public Collection getFields() { + return fields.values(); + } + + public Collection getMethods() { + return methods.values(); + } + + private static boolean isReachable(InterpreterResolvedJavaType type) { + if (type instanceof InterpreterResolvedPrimitiveType) { + return true; + } + AnalysisType originalType = (AnalysisType) ((InterpreterResolvedObjectType) type).getOriginalType(); + return originalType.isReachable(); + } + + static boolean isReachable(InterpreterResolvedJavaField field) { + AnalysisField originalField = (AnalysisField) field.getOriginalField(); + // Artificial reachability ensures that the interpreter keeps the field metadata around, + // but reachability still depends on the reachability of the declaring class and field type. + return field.isArtificiallyReachable() || (originalField.isReachable() && originalField.getDeclaringClass().isReachable()); + } + + static boolean isReachable(InterpreterResolvedJavaMethod method) { + AnalysisMethod originalMethod = (AnalysisMethod) method.getOriginalMethod(); + return originalMethod.isReachable() && originalMethod.getDeclaringClass().isReachable(); + } + + public void purgeUnreachable(MetaAccessProvider metaAccessProvider) { + + List nonReachableTypes = new ArrayList<>(); + for (InterpreterResolvedJavaType type : types.values()) { + if (!isReachable(type)) { + nonReachableTypes.add((InterpreterResolvedObjectType) type); + } + } + Iterator> iteratorMethods = methods.entrySet().iterator(); + while (iteratorMethods.hasNext()) { + Map.Entry next = iteratorMethods.next(); + InterpreterResolvedJavaMethod interpreterMethod = next.getValue(); + InterpreterResolvedObjectType declaringClass = interpreterMethod.getDeclaringClass(); + if (isReachable(interpreterMethod) && interpreterMethod.isInterpreterExecutable() && !isReachable(declaringClass)) { + InterpreterUtil.log("[purge] declaring class=%s of method=%s is not reachable, which cannot be represented in the interpreter universe currently", declaringClass, interpreterMethod); + VMError.shouldNotReachHere("declaring class should be reachable"); + } + + int removeMethodReason = 0; + + if (!isReachable(interpreterMethod) || !interpreterMethod.isInterpreterExecutable()) { + /* + * we might need that method as a holder for the call-site signature and vtable + * index + */ + InterpreterUtil.log("[purge] downgrading method=%s", interpreterMethod); + setNeedMethodBody(interpreterMethod, false, metaAccessProvider); + if (!isReachable(declaringClass)) { + InterpreterUtil.log("[purge] remove declaring class=%s", declaringClass); + nonReachableTypes.add(declaringClass); + removeMethodReason |= (1 << 0); + } + } + + if (!isReachable(interpreterMethod)) { + AnalysisMethod analysisMethod = (AnalysisMethod) interpreterMethod.getOriginalMethod(); + boolean isRoot = analysisMethod.isDirectRootMethod() || analysisMethod.isVirtualRootMethod() || analysisMethod.isInvoked(); + int implementations = analysisMethod.collectMethodImplementations(true).size(); + if (!isRoot && (next.getValue().isStatic() || implementations <= 1)) { + removeMethodReason |= (1 << 1); + } + } + if (removeMethodReason > 0) { + InterpreterUtil.log("[purge] remove method '%s' with reason %s", interpreterMethod, Integer.toBinaryString(removeMethodReason)); + iteratorMethods.remove(); + } + } + Iterator> iteratorFields = fields.entrySet().iterator(); + while (iteratorFields.hasNext()) { + Map.Entry next = iteratorFields.next(); + if (!isReachable(next.getValue()) || !isReachable(next.getValue().getDeclaringClass()) || !isReachable(next.getValue().getType())) { + InterpreterUtil.log("[purge] remove field '%s'", next.getValue()); + iteratorFields.remove(); + } + } + for (InterpreterResolvedObjectType nonReachableType : nonReachableTypes) { + InterpreterUtil.log("[purge] remove type '%s'", nonReachableType); + types.remove(nonReachableType.getOriginalType()); + } + + // Verification. + for (InterpreterResolvedJavaType type : types.values()) { + VMError.guarantee(isReachable(type)); + } + + for (InterpreterResolvedJavaField field : fields.values()) { + VMError.guarantee(isReachable(field)); + } + + for (InterpreterResolvedJavaMethod method : methods.values()) { + VMError.guarantee(isReachable(method) || !method.isStatic(), "non reachable"); + } + } + + static void topSort(ResolvedJavaType type, List order, Set seen) { + if (type == null || seen.contains(type)) { + return; + } + topSort(type.getSuperclass(), order, seen); + topSort(type.getComponentType(), order, seen); + for (ResolvedJavaType interf : type.getInterfaces()) { + topSort(interf, order, seen); + } + order.add(type); + seen.add(type); + } + + static List topologicalOrder(Collection types) { + List order = new ArrayList<>(types.size()); + Set seen = Collections.newSetFromMap(new IdentityHashMap<>(types.size())); + for (ResolvedJavaType type : types) { + topSort(type, order, seen); + } + assert types.size() == order.size(); + return order; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public InterpreterUniverseImpl snapshot() { + Collection values = types.values(); + // All type dependencies must appear strictly before in the ordering for serialization. + // Superclasses, super interfaces and component type of T must appear strictly before T. + List topologicalOrder = topologicalOrder(values); + assert checkOrder(topologicalOrder); + assert topologicalOrder.stream().allMatch(type -> type instanceof InterpreterResolvedJavaType); + List result = (List) topologicalOrder; + return new InterpreterUniverseImpl(result, fields.values(), methods.values()); + } + + private static List dependencies(ResolvedJavaType type) { + List result = new ArrayList<>(Arrays.asList(type.getInterfaces())); + if (type.getSuperclass() != null) { + result.add(type.getSuperclass()); + } + if (type.getComponentType() != null) { + result.add(type.getComponentType()); + } + return result; + } + + private static boolean checkOrder(List order) { + Set seen = Collections.newSetFromMap(new IdentityHashMap<>(order.size())); + for (ResolvedJavaType type : order) { + for (ResolvedJavaType strictlyBefore : dependencies(type)) { + if (!seen.contains(strictlyBefore)) { + return false; + } + } + seen.add(type); + } + return true; + } + + /** + * Converts a {@link JavaConstant} into constant supported by the interpreter. + * + * @param constant a constant handled from analysis or hosted world. + * @return a {@link PrimitiveConstant} or a {@link JavaConstant#NULL_POINTER}. + */ + public JavaConstant constant(JavaConstant constant) { + if (constant.getClass() == PrimitiveConstant.class) { + PrimitiveConstant primitiveConstant = (PrimitiveConstant) constant; + // Dedup constants. + switch (primitiveConstant.getJavaKind()) { + case Int: + return primitiveConstant(primitiveConstant.asInt()); + case Float: + return primitiveConstant(primitiveConstant.asFloat()); + case Long: + return primitiveConstant(primitiveConstant.asLong()); + case Double: + return primitiveConstant(primitiveConstant.asDouble()); + default: + return constant; + } + } + if (constant.isNull()) { + return JavaConstant.NULL_POINTER; + } + throw VMError.shouldNotReachHere("unsupported constant: " + constant); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ConstantBytecodes.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ConstantBytecodes.java new file mode 100644 index 000000000000..682244fcba82 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ConstantBytecodes.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter; + +import jdk.graal.compiler.api.replacements.Fold; + +import com.oracle.svm.interpreter.metadata.Bytecodes; +import com.oracle.svm.core.util.VMError; + +final class ConstantBytecodes { + + private ConstantBytecodes() { + throw VMError.shouldNotReachHereAtRuntime(); + } + + /** + * Version of {@link Bytecodes#lengthOf(int)} that returns a constant. The opcode must be a + * compile-time constant. + * + * @see Bytecodes#lengthOf(int) + */ + @Fold + public static int lengthOf(int opcode) { + return Bytecodes.lengthOf(opcode); + } + + /** + * Version of {@link Bytecodes#stackEffectOf(int)} that returns a constant. The opcode must be a + * compile-time constant. + * + * @see Bytecodes#stackEffectOf(int) + */ + @Fold + public static int stackEffectOf(int opcode) { + return Bytecodes.stackEffectOf(opcode); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaFeature.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaFeature.java new file mode 100644 index 000000000000..c0e8eaeabbde --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaFeature.java @@ -0,0 +1,68 @@ +/* + * 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.interpreter; + +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.hub.RuntimeClassLoading; + +import com.oracle.svm.hosted.FeatureImpl; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.hosted.Feature; + +import java.util.Arrays; +import java.util.List; + +/** + * In this mode the interpreter is used to execute previously (= image build-time) unknown methods, + * i.e. methods that are loaded or created at run-time. + */ + +@Platforms(Platform.HOSTED_ONLY.class) +@AutomaticallyRegisteredFeature +public class CremaFeature implements InternalFeature { + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return RuntimeClassLoading.isSupported(); + } + + @Override + public List> getRequiredFeatures() { + return Arrays.asList(InterpreterFeature.class); + } + + @Override + public void afterAbstractImageCreation(AfterAbstractImageCreationAccess access) { + FeatureImpl.AfterAbstractImageCreationAccessImpl accessImpl = ((FeatureImpl.AfterAbstractImageCreationAccessImpl) access); + + /* create vtable enter stubs */ + int maxVtableIndex = 0x100; + InterpreterStubSection stubSection = ImageSingletons.lookup(InterpreterStubSection.class); + stubSection.createInterpreterVtableEnterStubSection(accessImpl.getImage(), maxVtableIndex); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/DebuggerFeature.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/DebuggerFeature.java new file mode 100644 index 000000000000..6db823fffc92 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/DebuggerFeature.java @@ -0,0 +1,757 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter; + +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEDYNAMIC; +import static com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod.EST_NO_ENTRY; +import static com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod.VTBL_NO_ENTRY; +import static com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod.VTBL_ONE_IMPL; +import static com.oracle.svm.interpreter.metadata.InterpreterUniverseImpl.toHexString; +import static com.oracle.svm.hosted.pltgot.GOTEntryAllocator.GOT_NO_ENTRY; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.word.Pointer; + +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.core.BuildArtifacts; +import com.oracle.svm.core.FunctionPointerHolder; +import com.oracle.svm.core.InvalidMethodPointerHandler; +import com.oracle.svm.core.ParsingReason; +import com.oracle.svm.core.RuntimeAssertionsSupport; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.meta.MethodPointer; +import com.oracle.svm.core.option.HostedOptionValues; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.classfile.ClassFile; +import com.oracle.svm.interpreter.metadata.BytecodeStream; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType; +import com.oracle.svm.interpreter.metadata.InterpreterUniverse; +import com.oracle.svm.interpreter.metadata.InterpreterUniverseImpl; +import com.oracle.svm.interpreter.metadata.MetadataUtil; +import com.oracle.svm.interpreter.metadata.ReferenceConstant; +import com.oracle.svm.interpreter.metadata.serialization.SerializationContext; +import com.oracle.svm.interpreter.metadata.serialization.Serializers; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.NativeImageGenerator; +import com.oracle.svm.hosted.code.CompileQueue; +import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; +import com.oracle.svm.hosted.image.NativeImageHeap; +import com.oracle.svm.hosted.meta.HostedField; +import com.oracle.svm.hosted.meta.HostedMetaAccess; +import com.oracle.svm.hosted.meta.HostedMethod; +import com.oracle.svm.hosted.meta.HostedType; +import com.oracle.svm.hosted.meta.HostedUniverse; +import com.oracle.svm.hosted.pltgot.GOTEntryAllocator; +import com.oracle.svm.hosted.pltgot.HostedPLTGOTConfiguration; +import com.oracle.svm.hosted.pltgot.IdentityMethodAddressResolverFeature; +import com.oracle.svm.hosted.pltgot.PLTGOTOptions; +import com.oracle.svm.hosted.snippets.SubstrateGraphBuilderPlugins; +import com.oracle.svm.hosted.substitute.SubstitutionMethod; +import com.oracle.svm.util.ModuleSupport; + +import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; +import jdk.graal.compiler.core.common.SuppressFBWarnings; +import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; +import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugin; +import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugins; +import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.phases.util.Providers; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.JavaMethod; +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.PrimitiveConstant; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.UnresolvedJavaMethod; + +/** + * Also known as "YellowBird". + * + * In this mode the interpreter is used as an alternative execution engine to already AOT compiled + * methods in an image. This is needed to enable bytecode level debugging for JDWP. + * + * This also implies that all methods that are AOT compiled, need their bytecodes collected at image + * build-time. + */ +@Platforms(Platform.HOSTED_ONLY.class) +@AutomaticallyRegisteredFeature +public class DebuggerFeature implements InternalFeature { + private Method enterInterpreterMethod; + private InterpreterStubTable enterStubTable = null; + private final List> classesUsedByInterpreter = new ArrayList<>(); + private Set methodsProcessedDuringAnalysis; + private InvocationPlugins invocationPlugins; + private static final String SYNTHETIC_ASSERTIONS_DISABLED_FIELD_NAME = "$assertionsDisabled"; + + public DebuggerFeature() { + if (ModuleSupport.modulePathBuild) { + /* SVM_JDWP_RESIDENT */ + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, InterpreterFeature.class, false, + "jdk.internal.vm.ci", "jdk.vm.ci.code", "jdk.vm.ci.meta"); + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, InterpreterFeature.class, false, + "java.base", "jdk.internal.misc"); + + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, InterpreterFeature.class, false, + "org.graalvm.nativeimage", "org.graalvm.nativeimage.impl"); + + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, InterpreterFeature.class, false, + "org.graalvm.nativeimage.base"); + + /* SVM_JDWP_COMMON */ + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, InterpreterUniverse.class, false, + "jdk.internal.vm.ci", "jdk.vm.ci.meta"); + } + } + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return InterpreterOptions.DebuggerWithInterpreter.getValue(); + } + + @Override + public List> getRequiredFeatures() { + return Arrays.asList( + InterpreterFeature.class, + IdentityMethodAddressResolverFeature.class); + } + + private static Class getArgumentClass(GraphBuilderContext b, ResolvedJavaMethod targetMethod, int parameterIndex, ValueNode arg) { + SubstrateGraphBuilderPlugins.checkParameterUsage(arg.isConstant(), b, targetMethod, parameterIndex, "parameter is not a compile time constant"); + return OriginalClassProvider.getJavaClass(b.getConstantReflection().asJavaType(arg.asJavaConstant())); + } + + @Override + public void registerInvocationPlugins(Providers providers, GraphBuilderConfiguration.Plugins plugins, ParsingReason reason) { + invocationPlugins = plugins.getInvocationPlugins(); + InvocationPlugins.Registration r = new InvocationPlugins.Registration(invocationPlugins, InterpreterDirectives.class); + + r.register(new InvocationPlugin.RequiredInvocationPlugin("markKlass", Class.class) { + @Override + public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode arg1) { + Class targetKlass = getArgumentClass(b, targetMethod, 1, arg1); + InterpreterUtil.log("[invocation plugin] Adding %s", targetKlass); + classesUsedByInterpreter.add(targetKlass); + + /* no-op in compiled code */ + return true; + } + }); + } + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + enterStubTable = new InterpreterStubTable(); + + VMError.guarantee(PLTGOTOptions.EnablePLTGOT.getValue()); + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + FeatureImpl.BeforeAnalysisAccessImpl accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; + + try { + enterInterpreterMethod = InterpreterStubSection.class.getMethod("enterInterpreterStub", int.class, Pointer.class); + accessImpl.registerAsRoot(enterInterpreterMethod, false, "stub for interpreter"); + + // Holds references that must be kept alive in the image heap. + access.registerAsAccessed(DebuggerSupport.class.getDeclaredField("referencesInImage")); + access.registerAsAccessed(DebuggerSupport.class.getDeclaredField("methodPointersInImage")); + + accessImpl.registerAsRoot(System.class.getDeclaredMethod("arraycopy", Object.class, int.class, Object.class, int.class, int.class), true, + "Allow interpreting methods that call System.arraycopy"); + } catch (NoSuchMethodException | NoSuchFieldException e) { + throw VMError.shouldNotReachHereAtRuntime(); + } + + registerStringConcatenation(accessImpl); + + // GR-53734: Known issues around reachability + try { + // JDK code introduced a new optional intrinsic: + // https://github.com/openjdk/jdk22u/commit/a4e9168bab1c2872ce2dbc7971a45c259270271f + // consider DualPivotQuicksort.java:268, int.class is not needed if the sort helper + // is inlined, therefore it's not needed. Still needed for interpreter execution. + access.registerAsAccessed(Integer.class.getField("TYPE")); + } catch (NoSuchFieldException e) { + throw VMError.shouldNotReachHereAtRuntime(); + } + + methodsProcessedDuringAnalysis = new HashSet<>(); + + ImageSingletons.add(DebuggerSupport.class, new DebuggerSupport()); + } + + private static void registerStringConcatenation(FeatureImpl.BeforeAnalysisAccessImpl accessImpl) { + /* + * String concatenation a0 + a1 + ... + an is compiled by the Eclipse Java Compiler (ecj) + * to: + * + * new StringBuilder(a0).append(a1).append(a2) ... append(an).toString() + * + * javac emits INVOKEDYNAMIC-based String concatenation instead. + * + * String concatenation is heavily optimized by the compiler to the point that most + * StringBuilder methods/constructor are not included if they are not explicitly used + * outside String concatenation. + * + * These registrations enable the interpreter to "interpret" StringBuilder-based String + * concatenation optimized away by the compiler. + */ + try { + List appendMethods = Arrays.stream(StringBuilder.class.getDeclaredMethods()) + .filter(m -> "append".equals(m.getName())) + .collect(Collectors.toList()); + for (Method m : appendMethods) { + accessImpl.registerAsRoot(m, false, "string concat in interpreter"); + } + for (Constructor c : StringBuilder.class.getDeclaredConstructors()) { + accessImpl.registerAsRoot(c, true, "string concat in interpreter"); + } + accessImpl.registerAsRoot(StringBuilder.class.getConstructor(), true, "string concat in interpreter"); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + private static boolean isReachable(AnalysisMethod m) { + return m.isReachable() || m.isDirectRootMethod() || m.isVirtualRootMethod(); + } + + @Override + public void duringAnalysis(DuringAnalysisAccess access) { + FeatureImpl.DuringAnalysisAccessImpl accessImpl = (FeatureImpl.DuringAnalysisAccessImpl) access; + + boolean addedIndyHelper = false; + for (AnalysisMethod m : accessImpl.getUniverse().getMethods()) { + if (isReachable(m)) { + continue; + } + if (m.getName().startsWith("invoke") && m.getDeclaringClass().getName().equals("Ljava/lang/invoke/MethodHandle;")) { + accessImpl.registerAsRoot(m, true, "method handle for interpreter"); + SubstrateCompilationDirectives.singleton().registerForcedCompilation(m); + InterpreterUtil.log("[during analysis] Force entry point for %s and mark as reachable", m); + addedIndyHelper = true; + } + } + if (addedIndyHelper) { + access.requireAnalysisIteration(); + return; + } + + if (!classesUsedByInterpreter.isEmpty()) { + access.requireAnalysisIteration(); + for (Class k : classesUsedByInterpreter) { + accessImpl.registerAsUsed(k); + Arrays.stream(k.getDeclaredMethods()).filter(m -> m.getName().startsWith("test")).forEach(m -> { + AnalysisMethod aMethod = accessImpl.getMetaAccess().lookupJavaMethod(m); + VMError.guarantee(!aMethod.isConstructor()); + accessImpl.registerAsRoot(aMethod, aMethod.isConstructor(), "reached due to interpreter directive"); + InterpreterUtil.log("[during analysis] Adding method %s", m); + }); + } + classesUsedByInterpreter.clear(); + return; + } + + DebuggerSupport supportImpl = DebuggerSupport.singleton(); + SnippetReflectionProvider snippetReflection = accessImpl.getUniverse().getSnippetReflection(); + + for (AnalysisMethod method : accessImpl.getUniverse().getMethods()) { + /* + * Hack: Add frame info for every reachable method, so we have local infos on compiled + * frames. A proper solution is to externalize this information in our metadata file and + * retrieve it at run-time on demand. + */ + SubstrateCompilationDirectives.singleton().registerFrameInformationRequired(method); + + if (method.isReachable() && !methodsProcessedDuringAnalysis.contains(method)) { + byte[] code = method.getCode(); + if (code == null) { + continue; + } + AnalysisType declaringClass = method.getDeclaringClass(); + if (!declaringClass.isReachable() && !declaringClass.getName().toLowerCase(Locale.ROOT).contains("hosted")) { + /* + * It rarely happens that a method is reachable but its declaring class is not. + * This can't be represented in the interpreter universe currently, thus we + * force the declaring class to be reached. + * + * Example: ImageSingletons.lookup(Ljava/lang/Class); + */ + InterpreterUtil.log("[during analysis] declaring class %s of method %s is not reachable, force it as root", declaringClass, method); + accessImpl.registerAsUsed(declaringClass, "interpreter needs dynamic hub at runtime for this class"); + access.requireAnalysisIteration(); + } + for (int bci = 0; bci < BytecodeStream.endBCI(code); bci = BytecodeStream.nextBCI(code, bci)) { + int bytecode = BytecodeStream.currentBC(code, bci); + if (bytecode == INVOKEDYNAMIC) { + int targetMethodCPI = BytecodeStream.readCPI4(code, bci); + JavaMethod targetMethod = method.getConstantPool().lookupMethod(targetMethodCPI, bytecode); + /* + * SVM optimizes away javac's INVOKDYNAMIC-based String concatenation e.g. + * MH.makeConcatWithConstants(...) . The CP method entry remains unresolved. + * + * Only reachable call sites should have its method and appendix included in + * the image, for now, ALL INVOKEDYNAMIC call sites of reachable methods are + * included. + */ + if (targetMethod instanceof UnresolvedJavaMethod) { + method.getConstantPool().loadReferencedType(targetMethodCPI, bytecode); + targetMethod = method.getConstantPool().lookupMethod(targetMethodCPI, bytecode); + } + if (targetMethod instanceof AnalysisMethod analysisMethod) { + accessImpl.registerAsRoot(analysisMethod, true, "forced for indy support in interpreter"); + InterpreterUtil.log("[during analysis] force %s mark as reachable", targetMethod); + } + + JavaConstant appendixConstant = method.getConstantPool().lookupAppendix(targetMethodCPI, bytecode); + if (appendixConstant instanceof ImageHeapConstant imageHeapConstant) { + supportImpl.ensureConstantIsInImageHeap(snippetReflection, imageHeapConstant); + } + } + } + methodsProcessedDuringAnalysis.add(method); + } + } + supportImpl.trimForcedReferencesInImageHeap(); + } + + @Override + public void afterAnalysis(AfterAnalysisAccess access) { + VMError.guarantee(InterpreterToVM.wordJavaKind() == JavaKind.Long || + InterpreterToVM.wordJavaKind() == JavaKind.Int); + } + + @Override + public void beforeCompilation(BeforeCompilationAccess access) { + FeatureImpl.BeforeCompilationAccessImpl accessImpl = (FeatureImpl.BeforeCompilationAccessImpl) access; + HostedUniverse hUniverse = accessImpl.getUniverse(); + HostedMetaAccess hMetaAccess = accessImpl.getMetaAccess(); + MetaAccessProvider aMetaAccess = hMetaAccess.getWrapped(); + BuildTimeInterpreterUniverse iUniverse = BuildTimeInterpreterUniverse.singleton(); + + for (HostedType hType : hUniverse.getTypes()) { + AnalysisType aType = hType.getWrapped(); + if (aType.isReachable()) { + iUniverse.type(aType); + for (ResolvedJavaField staticField : aType.getStaticFields()) { + if (staticField instanceof AnalysisField analysisStaticField && !analysisStaticField.isWritten()) { + /* + * Assertions are implemented by generating a boolean $assertionsDisabled + * static field, but native-image substitutes the field reads by a constant, + * making the field unreachable sometimes. The interpreter must artificially + * preserve the metadata without making it reachable to the analysis. In + * some cases, $assertionsDisabled is written in not-yet-executed static + * initializers, it can't be made read-only always. + */ + if (staticField.isStatic() && staticField.isSynthetic() && staticField.getName().startsWith(SYNTHETIC_ASSERTIONS_DISABLED_FIELD_NAME)) { + Class declaringClass = aType.getJavaClass(); + boolean value = !RuntimeAssertionsSupport.singleton().desiredAssertionStatus(declaringClass); + InterpreterResolvedJavaField field = iUniverse.field(staticField); + JavaConstant javaConstant = iUniverse.constant(JavaConstant.forBoolean(value)); + BuildTimeInterpreterUniverse.setUnmaterializedConstantValue(field, javaConstant); + field.markAsArtificiallyReachable(); + } + } + } + } + } + + OptionValues invocationLookupOptions = new OptionValues(EconomicMap.create()); + for (HostedMethod hMethod : hUniverse.getMethods()) { + AnalysisMethod aMethod = hMethod.getWrapped(); + if (isReachable(aMethod)) { + boolean needsMethodBody = InterpreterFeature.executableByInterpreter(aMethod); + // Test if the methods needs to be compiled for execution in the interpreter: + if (hMethod.hasBytecodes() && aMethod.getAnalyzedGraph() != null) { + if (aMethod.wrapped instanceof SubstitutionMethod subMethod && subMethod.isUserSubstitution() || + invocationPlugins.lookupInvocation(aMethod, invocationLookupOptions) != null) { + // The method is substituted, or an invocation plugin is registered + SubstrateCompilationDirectives.singleton().registerForcedCompilation(hMethod); + needsMethodBody = false; + } + } + BuildTimeInterpreterUniverse.singleton().method(aMethod, needsMethodBody, aMetaAccess); + } + } + + for (HostedField hField : accessImpl.getUniverse().getFields()) { + AnalysisField aField = hField.getWrapped(); + if (aField.isReachable()) { + BuildTimeInterpreterUniverse.singleton().field(aField); + } + } + + iUniverse.purgeUnreachable(hMetaAccess); + + for (HostedType hostedType : hUniverse.getTypes()) { + AnalysisType analysisType = hostedType.getWrapped(); + InterpreterResolvedJavaType iType = iUniverse.getType(analysisType); + + if (!(iType instanceof InterpreterResolvedObjectType objectType)) { + continue; + } + if (hostedType.getVTable().length == 0) { + continue; + } + + InterpreterResolvedJavaMethod[] iVTable = new InterpreterResolvedJavaMethod[hostedType.getVTable().length]; + + for (int i = 0; i < iVTable.length; i++) { + iVTable[i] = iUniverse.getMethod(hostedType.getVTable()[i].getWrapped()); + } + objectType.setVtable(iVTable); + } + + HostedMethod methodNotCompiledHandler = hMetaAccess.lookupJavaMethod(InvalidMethodPointerHandler.METHOD_POINTER_NOT_COMPILED_HANDLER_METHOD); + InterpreterMethodPointerHolder.setMethodNotCompiledHandler(new MethodPointer(methodNotCompiledHandler)); + + // Allow methods that call System.arraycopy to be interpreted. + try { + HostedMethod arraycopy = hMetaAccess.lookupJavaMethod( + System.class.getDeclaredMethod("arraycopy", Object.class, int.class, Object.class, int.class, int.class)); + SubstrateCompilationDirectives.singleton().registerForcedCompilation(arraycopy); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + @Override + public void afterCompilation(AfterCompilationAccess access) { + FeatureImpl.AfterCompilationAccessImpl accessImpl = (FeatureImpl.AfterCompilationAccessImpl) access; + + BuildTimeInterpreterUniverse.singleton().createConstantPools(accessImpl.getUniverse()); + + int estOffset = 0; + for (InterpreterResolvedJavaMethod interpreterMethod : BuildTimeInterpreterUniverse.singleton().getMethods()) { + HostedMethod hostedMethod = accessImpl.getUniverse().optionalLookup(interpreterMethod.getOriginalMethod()); + + CompileQueue.CompileTask compileTask = accessImpl.getCompilations().get(hostedMethod); + ResolvedJavaMethod[] inlinedMethods = compileTask == null ? null : compileTask.result.getMethods(); + + if (inlinedMethods == null) { + InterpreterUtil.log("[inlinedeps] Method %s doesn't have any inlinees", hostedMethod); + } else if (interpreterMethod.isInterpreterExecutable()) { + InterpreterUtil.log("[inlinedeps] Inlined methods for %s: %s", hostedMethod, inlinedMethods.length); + + // GR-55054: check if all inlined methods are reachable and included. + // There are a few exceptions, e.g. Substitutions + for (ResolvedJavaMethod inlinee : inlinedMethods) { + AnalysisMethod analysisMethod = ((HostedMethod) inlinee).getWrapped(); + JavaMethod inlineeJavaMethod = BuildTimeInterpreterUniverse.singleton().methodOrUnresolved(analysisMethod); + if (inlineeJavaMethod instanceof InterpreterResolvedJavaMethod inlineeInterpreterMethod) { + if (inlineeInterpreterMethod.equals(interpreterMethod)) { + InterpreterUtil.log("[inlinedeps] \t%s includes itself as an inlining dependency", interpreterMethod); + } else { + inlineeInterpreterMethod.addInliner(interpreterMethod); + InterpreterUtil.log("[inlinedeps] \t%s", inlinee); + } + } else { + InterpreterUtil.log("[inlinedeps] \tWarning: did not find interp method for %s", inlinee); + } + } + } + + if (!hostedMethod.isCompiled()) { + InterpreterUtil.log("[got] after compilation: %s is not compiled, nulling it out", hostedMethod); + interpreterMethod.setVTableIndex(VTBL_NO_ENTRY); + interpreterMethod.setNativeEntryPoint(null); + } else { + if (interpreterMethod.hasBytecodes()) { + /* only allocate stub for methods that we can actually run in the interpreter */ + interpreterMethod.setEnterStubOffset(estOffset++); + } + + interpreterMethod.setNativeEntryPoint(new MethodPointer(interpreterMethod.getOriginalMethod())); + } + + if (!interpreterMethod.isStatic() && !interpreterMethod.isConstructor()) { + if (hostedMethod.getImplementations().length > 1) { + if (!hostedMethod.hasVTableIndex()) { + InterpreterUtil.log("[vtable assignment] %s has multiple implementations but no vtable slot. This is not supported.%n", hostedMethod); + } else { + InterpreterUtil.log("[vtable assignment] Setting to Index %s for methods %s <> %s%n", hostedMethod.getVTableIndex(), interpreterMethod, hostedMethod); + interpreterMethod.setVTableIndex(hostedMethod.getVTableIndex()); + /* + * Do not null out native entry point, the method may be invoked via + * INVOKESPECIAL + */ + } + } else if (hostedMethod.getImplementations().length == 1) { + InterpreterUtil.log("[vtable assignment] Only one implementation available for %s%n", hostedMethod); + interpreterMethod.setVTableIndex(VTBL_ONE_IMPL); + + InterpreterResolvedJavaMethod oneImpl = (InterpreterResolvedJavaMethod) BuildTimeInterpreterUniverse.singleton().methodOrUnresolved(hostedMethod.getImplementations()[0]); + interpreterMethod.setOneImplementation(oneImpl); + InterpreterUtil.log("[vtable assignment] set oneImpl to -> %s%n", oneImpl); + } else { + InterpreterUtil.log("[vtable assignment] No implementation available: %s%n", hostedMethod); + interpreterMethod.setVTableIndex(VTBL_NO_ENTRY); + } + } + } + + NativeImageHeap heap = accessImpl.getHeap(); + + for (InterpreterResolvedJavaField field : BuildTimeInterpreterUniverse.singleton().getFields()) { + HostedField hostedField = accessImpl.getUniverse().optionalLookup(field.getOriginalField()); + if (!hostedField.isReachable()) { + // Field was not included in the image, so it can only be an artificially reachable + // field used only by the interpreter. These fields are read-only, thus + // unmaterialized, + // by now its value should be already computed. + VMError.guarantee(field.isArtificiallyReachable()); + VMError.guarantee(field.isUnmaterializedConstant()); + VMError.guarantee(field.getUnmaterializedConstant() != null); + } else if (hostedField.isUnmaterialized()) { + AnalysisField analysisField = (AnalysisField) field.getOriginalField(); + if (hostedField.getType().isWordType() || analysisField.getJavaKind().isPrimitive()) { + JavaConstant constant = heap.hConstantReflection.readFieldValue(hostedField, null); + assert constant instanceof PrimitiveConstant; + BuildTimeInterpreterUniverse.setUnmaterializedConstantValue(field, constant); + } else if (analysisField.isRead() || analysisField.isFolded()) { + // May or may not be in the image. + assert analysisField.getJavaKind().isObject(); + JavaConstant constantValue = heap.hConstantReflection.readFieldValue(hostedField, null); + BuildTimeInterpreterUniverse.setUnmaterializedConstantValue(field, constantValue); + } else { + // Block access. + BuildTimeInterpreterUniverse.setUnmaterializedConstantValue(field, JavaConstant.forIllegal()); + } + VMError.guarantee(field.isUnmaterializedConstant()); + } else if (!hostedField.hasLocation()) { + InterpreterUtil.log("Found materialized field without location: %s", hostedField); + BuildTimeInterpreterUniverse.setUnmaterializedConstantValue(field, JavaConstant.forIllegal()); + } else { + int fieldOffset = hostedField.getOffset(); + field.setOffset(fieldOffset); + } + } + + DebuggerSupport supportImpl = DebuggerSupport.singleton(); + for (InterpreterResolvedJavaMethod method : BuildTimeInterpreterUniverse.singleton().getMethods()) { + ReferenceConstant nativeEntryPointHolderConstant = method.getNativeEntryPointHolderConstant(); + if (nativeEntryPointHolderConstant != null) { + supportImpl.ensureMethodPointerIsInImage(nativeEntryPointHolderConstant.getReferent()); + } + } + } + + @Override + public void afterHeapLayout(AfterHeapLayoutAccess access) { + FeatureImpl.AfterHeapLayoutAccessImpl accessImpl = (FeatureImpl.AfterHeapLayoutAccessImpl) access; + NativeImageHeap heap = accessImpl.getHeap(); + + for (InterpreterResolvedJavaField field : BuildTimeInterpreterUniverse.singleton().getFields()) { + if (field.isArtificiallyReachable()) { + // Value should be already computed. + JavaConstant value = field.getUnmaterializedConstant(); + VMError.guarantee(value != null && value != JavaConstant.ILLEGAL); + continue; + } + HostedField hostedField = accessImpl.getMetaAccess().getUniverse().optionalLookup(field.getOriginalField()); + if (hostedField.isUnmaterialized()) { + AnalysisField analysisField = (AnalysisField) field.getOriginalField(); + if (hostedField.getType().isWordType()) { + // Ignore, words are stored as primitive values. + } else if ((analysisField.isFolded() && analysisField.getJavaKind().isObject())) { + JavaConstant constantValue = heap.hConstantReflection.readFieldValue(hostedField, null); + BuildTimeInterpreterUniverse.setUnmaterializedConstantValue(field, constantValue); + } + } + } + + GOTEntryAllocator gotEntryAllocator = HostedPLTGOTConfiguration.singleton().getGOTEntryAllocator(); + for (InterpreterResolvedJavaMethod interpreterMethod : BuildTimeInterpreterUniverse.singleton().getMethods()) { + HostedMethod hostedMethod = accessImpl.getMetaAccess().getUniverse().optionalLookup(interpreterMethod.getOriginalMethod()); + + int gotOffset = GOT_NO_ENTRY; + + if (interpreterMethod.isInterpreterExecutable()) { + gotOffset = gotEntryAllocator.queryGotEntry(hostedMethod); + } + + if (gotOffset == GOT_NO_ENTRY) { + InterpreterUtil.log("[got] Missing GOT offset for %s", interpreterMethod); + } else { + InterpreterUtil.log("[got] Got GOT offset=%s for %s", gotOffset, interpreterMethod); + } + interpreterMethod.setGOTOffset(gotOffset); + } + } + + @Override + public void afterAbstractImageCreation(AfterAbstractImageCreationAccess access) { + FeatureImpl.AfterAbstractImageCreationAccessImpl accessImpl = ((FeatureImpl.AfterAbstractImageCreationAccessImpl) access); + + List includedMethods = BuildTimeInterpreterUniverse.singleton().getMethods() + .stream() + .filter(m -> m.getEnterStubOffset() != EST_NO_ENTRY) + .collect(Collectors.toList()); + + /* create enter stubs */ + InterpreterStubSection stubSection = ImageSingletons.lookup(InterpreterStubSection.class); + stubSection.createInterpreterEnterStubSection(accessImpl.getImage(), includedMethods); + + /* populate EST */ + enterStubTable.installAdditionalInfoIntoImageObjectFile(accessImpl.getImage(), includedMethods); + } + + @Override + public void beforeImageWrite(BeforeImageWriteAccess access) { + FeatureImpl.BeforeImageWriteAccessImpl accessImpl = (FeatureImpl.BeforeImageWriteAccessImpl) access; + + // Serialize interpreter metadata. + NativeImageHeap heap = accessImpl.getImage().getHeap(); + + Map, DynamicHub> classToHub = new IdentityHashMap<>(); + for (NativeImageHeap.ObjectInfo info : heap.getObjects()) { + Object object = info.getObject(); + if (object instanceof DynamicHub) { + DynamicHub hub = (DynamicHub) object; + classToHub.put(hub.getHostedJavaClass(), hub); + } + } + + DebuggerSupport supportImpl = DebuggerSupport.singleton(); + SerializationContext.Builder builder = supportImpl.getUniverseSerializerBuilder() + .registerWriter(true, ReferenceConstant.class, Serializers.newReferenceConstantWriter(ref -> { + NativeImageHeap.ObjectInfo info = null; + if (ref instanceof Class) { + DynamicHub hub = classToHub.get(ref); + info = heap.getObjectInfo(hub); + } else if (ref instanceof ImageHeapConstant imageHeapConstant) { + info = heap.getConstantInfo(imageHeapConstant); + } else { + info = heap.getObjectInfo(ref); + } + + if (info == null) { + // avoid side-effects + String purgedObject = Objects.toIdentityString(ref); + InterpreterUtil.log("Constant not serialized in the image: %s", purgedObject); + return 0L; + } else { + return info.getOffset(); + } + })); + + Path destDir = NativeImageGenerator.generatedFiles(HostedOptionValues.singleton()); + + // Be explicit here: .metadata file is derived from + String imageFileName = accessImpl.getImageName(); + String extension = accessImpl.getImage().getImageKind().getFilenameSuffix(); + if (!imageFileName.endsWith(extension)) { + imageFileName += extension; + } + + String metadataFileName = MetadataUtil.metadataFileName(imageFileName); + Path metadataPath = destDir.resolve(metadataFileName); + + int crc32; + InterpreterUniverseImpl snapshot = BuildTimeInterpreterUniverse.singleton().snapshot(); + try { + snapshot.saveTo(builder, metadataPath); + crc32 = InterpreterUniverseImpl.computeCRC32(metadataPath); + } catch (IOException e) { + throw new RuntimeException(e); + } + + BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.DEBUG_INFO, metadataPath); + + String hashString = "crc32:" + toHexString(crc32); + + String dumpInterpreterClassFiles = InterpreterOptions.InterpreterDumpClassFiles.getValue(); + + if (!dumpInterpreterClassFiles.isEmpty()) { + try { + dumpInterpreterMetadataAsClassFiles(snapshot, dumpInterpreterClassFiles); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + InterpreterStubSection stubSection = ImageSingletons.lookup(InterpreterStubSection.class); + + stubSection.markEnterStubPatch(accessImpl.getHostedMetaAccess().lookupJavaMethod(enterInterpreterMethod)); + enterStubTable.writeMetadataHashString(hashString.getBytes(StandardCharsets.UTF_8)); + } + + @Platforms(Platform.HOSTED_ONLY.class) + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "path.getParent() is never null") + private static void dumpInterpreterMetadataAsClassFiles(InterpreterUniverseImpl universe, String outputFolder) throws IOException { + for (InterpreterResolvedJavaType type : universe.getTypes()) { + if (type.isPrimitive() || type.isArray()) { + continue; + } + String typeName = type.getName(); + String separator = FileSystems.getDefault().getSeparator(); + assert typeName.startsWith("L") && typeName.endsWith(";"); + String relativeFilePath = typeName.substring(1, typeName.length() - 1).replace("/", separator) + ".class"; + Path path = Path.of(outputFolder, relativeFilePath); + if (!Files.exists(path.getParent())) { + Files.createDirectories(path.getParent()); + } + byte[] bytes = ClassFile.dumpInterpreterTypeClassFile(universe, type); + Files.write(path, bytes, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/DebuggerSupport.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/DebuggerSupport.java new file mode 100644 index 000000000000..ec28340f13af --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/DebuggerSupport.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2023, 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.interpreter; + +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.svm.core.BuildPhaseProvider; +import com.oracle.svm.core.FunctionPointerHolder; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; +import com.oracle.svm.core.heap.UnknownObjectField; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; +import com.oracle.svm.interpreter.metadata.InterpreterUniverse; +import com.oracle.svm.interpreter.metadata.InterpreterUniverseImpl; +import com.oracle.svm.interpreter.metadata.Lazy; +import com.oracle.svm.interpreter.metadata.MetadataUtil; +import com.oracle.svm.interpreter.metadata.serialization.SerializationContext; +import com.oracle.svm.interpreter.metadata.serialization.Serializers; +import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.ProcessProperties; +import org.graalvm.word.Pointer; + +import java.io.IOException; +import java.lang.reflect.Executable; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Set; + +import static com.oracle.svm.interpreter.InterpreterUtil.traceInterpreter; + +public class DebuggerSupport { + public static final String IMAGE_INTERP_HASH_SYMBOL_NAME = "__svm_interp_hash"; + + public static final CGlobalData IMAGE_INTERP_HASH = CGlobalDataFactory.forSymbol(IMAGE_INTERP_HASH_SYMBOL_NAME); + + private static final SerializationContext.Builder READER_BUILDER = Serializers.newBuilderForInterpreterMetadata(); + + private ArrayList referencesInImage = new ArrayList<>(); + + @UnknownObjectField(availability = BuildPhaseProvider.AfterCompilation.class) // + private final ArrayList methodPointersInImage = new ArrayList<>(); + + private final Lazy universe; + + @SuppressWarnings("this-escape") + public DebuggerSupport() { + this.universe = Lazy.of(() -> { + logForcedReferencesHistogram(this.referencesInImage, this.methodPointersInImage); + try { + return InterpreterUniverseImpl.loadFrom(getUniverseSerializerBuilder(), false, getMetadataHashString(), getMetadataFilePath()); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (Exception e) { + if (InterpreterOptions.InterpreterTraceSupport.getValue() && InterpreterOptions.InterpreterTrace.getValue()) { + e.printStackTrace(); + } + throw VMError.shouldNotReachHere(e); + } + }); + } + + @Fold + public static boolean isEnabled() { + return ImageSingletons.contains(DebuggerSupport.class); + } + + @Fold + public static DebuggerSupport singleton() { + return ImageSingletons.lookup(DebuggerSupport.class); + } + + public static Path getMetadataFilePath() { + return MetadataUtil.metadataFilePath(Path.of(ProcessProperties.getExecutableName())); + } + + public static String getMetadataHashString() { + Pointer base = IMAGE_INTERP_HASH.get(); + int length = base.readInt(0); + byte[] bytes = new byte[length]; + for (int i = 0; i < length; ++i) { + bytes[i] = base.readByte(4 + i); + } + return new String(bytes, StandardCharsets.UTF_8); + } + + private static void logForcedReferencesHistogram(ArrayList references, ArrayList methodPointers) { + traceInterpreter("Forced constants: ").signed(references.size()).newline(); + traceInterpreter("Forced method pointers: ").signed(methodPointers.size()).newline(); + traceInterpreter("Forced constants histogram:"); + EconomicMap, Integer> histogram = EconomicMap.create(); + for (Object object : references) { + histogram.put(object.getClass(), histogram.get(object.getClass(), 0) + 1); + } + MapCursor, Integer> cursor = histogram.getEntries(); + while (cursor.advance()) { + traceInterpreter(" ").string(cursor.getKey().toString()).string(" ").string(cursor.getValue().toString()).newline(); + } + } + + /** + * Returns the interpreter "type" for a specific {@link Class}, or null if it doesn't exist or + * if the liaison wasn't registered at build time. This is an internal API meant for + * debugging and testing purposes only. + */ + // GR-55023: should be in InterpreterSupport + public static ResolvedJavaType lookupType(Class declaringClass) { + return InterpreterDirectivesSupportImpl.getInterpreterType(declaringClass); + } + + /** + * Returns the interpreter "method" for a specific {@link Executable}, or null if it doesn't + * exist or if the liaison wasn't registered at build time. This is an internal API meant for + * debugging and testing purposes only. + */ + // GR-55023: should be in InterpreterSupport + public static ResolvedJavaMethod lookupMethod(ResolvedJavaType clazz, String methodName, Class returnType, Class... parameterTypes) { + VMError.guarantee(clazz instanceof InterpreterResolvedJavaType); + return InterpreterDirectivesSupportImpl.getInterpreterMethod((InterpreterResolvedJavaType) clazz, methodName, returnType, parameterTypes); + } + + @SuppressWarnings("static-method") + public SerializationContext.Builder getUniverseSerializerBuilder() { + return READER_BUILDER; + } + + // GR-55023: should be in InterpreterSupport + public InterpreterUniverse getUniverse() { + return universe.get(); + } + + @Platforms(Platform.HOSTED_ONLY.class) + void trimForcedReferencesInImageHeap() { + Set unique = Collections.newSetFromMap(new IdentityHashMap<>()); + unique.addAll(this.referencesInImage); + this.referencesInImage = new ArrayList<>(unique); + } + + @Platforms(Platform.HOSTED_ONLY.class) + void ensureConstantIsInImageHeap(SnippetReflectionProvider snippetReflectionProvider, ImageHeapConstant imageHeapConstant) { + if (imageHeapConstant.isBackedByHostedObject()) { + Object value = snippetReflectionProvider.asObject(Object.class, imageHeapConstant.getHostedObject()); + VMError.guarantee(value != null); + referencesInImage.add(value); + } else { + throw VMError.shouldNotReachHere("Constant is not backed: " + imageHeapConstant); + } + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void ensureMethodPointerIsInImage(FunctionPointerHolder value) { + if (value != null) { + methodPointersInImage.add(value); + } + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void buildMethodIdMapping(ResolvedJavaMethod[] encodedMethods) { + assert encodedMethods[0] == null; + for (int i = 1; i < encodedMethods.length; i++) { + ResolvedJavaMethod method = encodedMethods[i]; + if (method != null) { + InterpreterResolvedJavaMethod interpreterMethod = BuildTimeInterpreterUniverse.singleton().getMethod(method); + interpreterMethod.setMethodId(i); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/EspressoFrame.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/EspressoFrame.java new file mode 100644 index 000000000000..06f6b621dd38 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/EspressoFrame.java @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.interpreter; + +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.InterpreterUnresolvedSignature; + +import jdk.vm.ci.meta.JavaKind; + +/** + * Exposes accessors to the Espresso frame e.g. operand stack, locals and current BCI. + */ +public final class EspressoFrame { + + private EspressoFrame() { + throw VMError.shouldNotReachHere("private constructor"); + } + + public static InterpreterFrame allocate(int maxLocals, int maxStackSize, Object... arguments) { + return InterpreterFrame.create(maxLocals + maxStackSize, arguments); + } + + // region Operand stack accessors + + public static void dup1(InterpreterFrame frame, int top) { + // value1 -> value1, value1 + copyStatic(frame, top - 1, top); + } + + public static void dupx1(InterpreterFrame frame, int top) { + // value2, value1 -> value1, value2, value1 + copyStatic(frame, top - 1, top); + copyStatic(frame, top - 2, top - 1); + copyStatic(frame, top, top - 2); + } + + public static void dupx2(InterpreterFrame frame, int top) { + // value3, value2, value1 -> value1, value3, value2, value1 + copyStatic(frame, top - 1, top); + copyStatic(frame, top - 2, top - 1); + copyStatic(frame, top - 3, top - 2); + copyStatic(frame, top, top - 3); + } + + public static void dup2(InterpreterFrame frame, int top) { + // {value2, value1} -> {value2, value1}, {value2, value1} + copyStatic(frame, top - 2, top); + copyStatic(frame, top - 1, top + 1); + } + + public static void swapSingle(InterpreterFrame frame, int top) { + // value2, value1 -> value1, value2 + swapStatic(frame, top); + } + + public static void dup2x1(InterpreterFrame frame, int top) { + // value3, {value2, value1} -> {value2, value1}, value3, {value2, value1} + copyStatic(frame, top - 2, top); + copyStatic(frame, top - 1, top + 1); + copyStatic(frame, top - 3, top - 1); + copyStatic(frame, top, top - 3); + copyStatic(frame, top + 1, top - 2); + } + + public static void dup2x2(InterpreterFrame frame, int top) { + // {value4, value3}, {value2, value1} -> {value2, value1}, {value4, value3}, {value2, + // value1} + copyStatic(frame, top - 1, top + 1); + copyStatic(frame, top - 2, top); + copyStatic(frame, top - 3, top - 1); + copyStatic(frame, top - 4, top - 2); + copyStatic(frame, top, top - 4); + copyStatic(frame, top + 1, top - 3); + } + + private static void swapStatic(InterpreterFrame frame, int top) { + frame.swapStatic(top - 1, top - 2); + } + + private static void copyStatic(InterpreterFrame frame, int src, int dst) { + frame.copyStatic(src, dst); + } + + public static int popInt(InterpreterFrame frame, int slot) { + int result = frame.getIntStatic(slot); + // Avoid keeping track of popped slots in FrameStates. + clearPrimitive(frame, slot); + return result; + } + + public static Object peekObject(InterpreterFrame frame, int slot) { + Object result = frame.getObjectStatic(slot); + return result; + } + + public static long peekPrimitive(InterpreterFrame frame, int slot) { + return frame.getLongStatic(slot); + } + + /** + * Reads and clear the operand stack slot. + */ + public static Object popObject(InterpreterFrame frame, int slot) { + // nulls-out the slot, use peekObject to read only + Object result = frame.getObjectStatic(slot); + clearReference(frame, slot); + assert !(result instanceof ReturnAddress); + return result; + } + + public static float popFloat(InterpreterFrame frame, int slot) { + float result = frame.getFloatStatic(slot); + // Avoid keeping track of popped slots in FrameStates. + clearPrimitive(frame, slot); + return result; + } + + public static long popLong(InterpreterFrame frame, int slot) { + long result = frame.getLongStatic(slot); + // Avoid keeping track of popped slots in FrameStates. + clearPrimitive(frame, slot); + return result; + } + + public static double popDouble(InterpreterFrame frame, int slot) { + double result = frame.getDoubleStatic(slot); + // Avoid keeping track of popped slots in FrameStates. + clearPrimitive(frame, slot); + return result; + } + + static Object popReturnAddressOrObject(InterpreterFrame frame, int slot) { + Object result = frame.getObjectStatic(slot); + clearReference(frame, slot); + return result; + } + + static void putReturnAddress(InterpreterFrame frame, int slot, int targetBCI) { + frame.setObjectStatic(slot, ReturnAddress.create(targetBCI)); + } + + public static void putObject(InterpreterFrame frame, int slot, Object value) { + frame.setObjectStatic(slot, value); + } + + public static void putInt(InterpreterFrame frame, int slot, int value) { + frame.setIntStatic(slot, value); + } + + public static void putFloat(InterpreterFrame frame, int slot, float value) { + frame.setFloatStatic(slot, value); + } + + public static void putLong(InterpreterFrame frame, int slot, long value) { + frame.setLongStatic(slot + 1, value); + } + + public static void putDouble(InterpreterFrame frame, int slot, double value) { + frame.setDoubleStatic(slot + 1, value); + } + + private static void clearReference(InterpreterFrame frame, int slot) { + frame.clearObjectStatic(slot); + } + + private static void clearPrimitive(InterpreterFrame frame, int slot) { + frame.clearPrimitiveStatic(slot); + } + + public static void clear(InterpreterFrame frame, int slot) { + frame.clearStatic(slot); + } + + // endregion Operand stack accessors + + // region Local accessors + + public static void clearLocal(InterpreterFrame frame, int localSlot) { + clear(frame, localSlot); + } + + public static void setLocalObject(InterpreterFrame frame, int localSlot, Object value) { + assert !(value instanceof ReturnAddress); + frame.setObjectStatic(localSlot, value); + } + + static void setLocalObjectOrReturnAddress(InterpreterFrame frame, int localSlot, Object value) { + frame.setObjectStatic(localSlot, value); + } + + public static void setLocalInt(InterpreterFrame frame, int localSlot, int value) { + frame.setIntStatic(localSlot, value); + } + + public static void setLocalFloat(InterpreterFrame frame, int localSlot, float value) { + frame.setFloatStatic(localSlot, value); + } + + public static void setLocalLong(InterpreterFrame frame, int localSlot, long value) { + frame.setLongStatic(localSlot, value); + } + + public static void setLocalDouble(InterpreterFrame frame, int localSlot, double value) { + frame.setDoubleStatic(localSlot, value); + } + + public static int getLocalInt(InterpreterFrame frame, int localSlot) { + return frame.getIntStatic(localSlot); + } + + public static Object getLocalObject(InterpreterFrame frame, int localSlot) { + Object result = frame.getObjectStatic(localSlot); + return result; + } + + public static Object getThis(InterpreterFrame frame) { + return getLocalObject(frame, 0); + } + + static int getLocalReturnAddress(InterpreterFrame frame, int localSlot) { + Object result = frame.getObjectStatic(localSlot); + assert result != null; + return ((ReturnAddress) result).bci(); + } + + public static float getLocalFloat(InterpreterFrame frame, int localSlot) { + return frame.getFloatStatic(localSlot); + } + + public static long getLocalLong(InterpreterFrame frame, int localSlot) { + return frame.getLongStatic(localSlot); + } + + public static double getLocalDouble(InterpreterFrame frame, int localSlot) { + return frame.getDoubleStatic(localSlot); + } + + // endregion Local accessors + + public static int startingStackOffset(int maxLocals) { + return maxLocals; + } + + public static Object[] popArguments(InterpreterFrame frame, int top, boolean hasReceiver, InterpreterUnresolvedSignature signature) { + int argCount = signature.getParameterCount(false); + + int extraParam = hasReceiver ? 1 : 0; + final Object[] args = new Object[argCount + extraParam]; + + int argAt = top - 1; + for (int i = argCount - 1; i >= 0; --i) { + JavaKind argKind = signature.getParameterKind(i); + // @formatter:off + switch (argKind) { + case Boolean: args[i + extraParam] = (popInt(frame, argAt) != 0); break; + case Byte: args[i + extraParam] = (byte) popInt(frame, argAt); break; + case Short: args[i + extraParam] = (short) popInt(frame, argAt); break; + case Char: args[i + extraParam] = (char) popInt(frame, argAt); break; + case Int: args[i + extraParam] = popInt(frame, argAt); break; + case Float: args[i + extraParam] = popFloat(frame, argAt); break; + case Long: args[i + extraParam] = popLong(frame, argAt); --argAt; break; + case Double: args[i + extraParam] = popDouble(frame, argAt); --argAt; break; + case Object: args[i + extraParam] = popObject(frame, argAt); break; + default: + throw VMError.shouldNotReachHere("implement me: " + argKind); + + } + // @formatter:on + --argAt; + } + if (hasReceiver) { + args[0] = popObject(frame, argAt); + } + return args; + } + + /** + * Puts a value in the operand stack. This method follows the JVM spec, where sub-word types (< + * int) are always treated as int. + * + * Returns the number of used slots. + * + * @param value value to push + * @param returnKind kind to push + */ + public static int putKind(InterpreterFrame frame, int top, Object value, JavaKind returnKind) { + // @formatter:off + switch (returnKind) { + case Boolean : putInt(frame, top, ((boolean) value) ? 1 : 0); break; + case Byte : putInt(frame, top, (byte) value); break; + case Short : putInt(frame, top, (short) value); break; + case Char : putInt(frame, top, (char) value); break; + case Int : putInt(frame, top, (int) value); break; + case Float : putFloat(frame, top, (float) value); break; + case Long : putLong(frame, top, (long) value); break; + case Double : putDouble(frame, top, (double) value); break; + case Object : putObject(frame, top, value); break; + case Void : /* ignore */ break; + default : + throw VMError.shouldNotReachHereAtRuntime(); + } + // @formatter:on + return returnKind.getSlotCount(); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java new file mode 100644 index 000000000000..64b9bd0a3ed4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java @@ -0,0 +1,1528 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter; + +import static com.oracle.svm.interpreter.EspressoFrame.clear; +import static com.oracle.svm.interpreter.EspressoFrame.dup1; +import static com.oracle.svm.interpreter.EspressoFrame.dup2; +import static com.oracle.svm.interpreter.EspressoFrame.dup2x1; +import static com.oracle.svm.interpreter.EspressoFrame.dup2x2; +import static com.oracle.svm.interpreter.EspressoFrame.dupx1; +import static com.oracle.svm.interpreter.EspressoFrame.dupx2; +import static com.oracle.svm.interpreter.EspressoFrame.getLocalDouble; +import static com.oracle.svm.interpreter.EspressoFrame.getLocalFloat; +import static com.oracle.svm.interpreter.EspressoFrame.getLocalInt; +import static com.oracle.svm.interpreter.EspressoFrame.getLocalLong; +import static com.oracle.svm.interpreter.EspressoFrame.getLocalObject; +import static com.oracle.svm.interpreter.EspressoFrame.getLocalReturnAddress; +import static com.oracle.svm.interpreter.EspressoFrame.peekObject; +import static com.oracle.svm.interpreter.EspressoFrame.popDouble; +import static com.oracle.svm.interpreter.EspressoFrame.popFloat; +import static com.oracle.svm.interpreter.EspressoFrame.popInt; +import static com.oracle.svm.interpreter.EspressoFrame.popLong; +import static com.oracle.svm.interpreter.EspressoFrame.popObject; +import static com.oracle.svm.interpreter.EspressoFrame.popReturnAddressOrObject; +import static com.oracle.svm.interpreter.EspressoFrame.putDouble; +import static com.oracle.svm.interpreter.EspressoFrame.putFloat; +import static com.oracle.svm.interpreter.EspressoFrame.putInt; +import static com.oracle.svm.interpreter.EspressoFrame.putLong; +import static com.oracle.svm.interpreter.EspressoFrame.putObject; +import static com.oracle.svm.interpreter.EspressoFrame.putReturnAddress; +import static com.oracle.svm.interpreter.EspressoFrame.setLocalDouble; +import static com.oracle.svm.interpreter.EspressoFrame.setLocalFloat; +import static com.oracle.svm.interpreter.EspressoFrame.setLocalInt; +import static com.oracle.svm.interpreter.EspressoFrame.setLocalLong; +import static com.oracle.svm.interpreter.EspressoFrame.setLocalObject; +import static com.oracle.svm.interpreter.EspressoFrame.setLocalObjectOrReturnAddress; +import static com.oracle.svm.interpreter.EspressoFrame.startingStackOffset; +import static com.oracle.svm.interpreter.EspressoFrame.swapSingle; +import static com.oracle.svm.interpreter.InterpreterUtil.traceInterpreter; +import static com.oracle.svm.interpreter.InterpreterToVM.nullCheck; +import static com.oracle.svm.interpreter.metadata.Bytecodes.AALOAD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.AASTORE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ACONST_NULL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ALOAD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ALOAD_0; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ALOAD_1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ALOAD_2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ALOAD_3; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ANEWARRAY; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ARETURN; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ARRAYLENGTH; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ASTORE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ASTORE_0; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ASTORE_1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ASTORE_2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ASTORE_3; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ATHROW; +import static com.oracle.svm.interpreter.metadata.Bytecodes.BALOAD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.BASTORE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.BIPUSH; +import static com.oracle.svm.interpreter.metadata.Bytecodes.BREAKPOINT; +import static com.oracle.svm.interpreter.metadata.Bytecodes.CALOAD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.CASTORE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.CHECKCAST; +import static com.oracle.svm.interpreter.metadata.Bytecodes.D2F; +import static com.oracle.svm.interpreter.metadata.Bytecodes.D2I; +import static com.oracle.svm.interpreter.metadata.Bytecodes.D2L; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DADD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DALOAD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DASTORE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DCMPG; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DCMPL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DCONST_0; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DCONST_1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DDIV; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DLOAD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DLOAD_0; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DLOAD_1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DLOAD_2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DLOAD_3; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DMUL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DNEG; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DREM; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DRETURN; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DSTORE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DSTORE_0; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DSTORE_1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DSTORE_2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DSTORE_3; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DSUB; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DUP; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DUP2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DUP2_X1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DUP2_X2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DUP_X1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.DUP_X2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.F2D; +import static com.oracle.svm.interpreter.metadata.Bytecodes.F2I; +import static com.oracle.svm.interpreter.metadata.Bytecodes.F2L; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FADD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FALOAD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FASTORE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FCMPG; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FCMPL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FCONST_0; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FCONST_1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FCONST_2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FDIV; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FLOAD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FLOAD_0; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FLOAD_1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FLOAD_2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FLOAD_3; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FMUL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FNEG; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FREM; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FRETURN; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FSTORE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FSTORE_0; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FSTORE_1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FSTORE_2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FSTORE_3; +import static com.oracle.svm.interpreter.metadata.Bytecodes.FSUB; +import static com.oracle.svm.interpreter.metadata.Bytecodes.GETFIELD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.GETSTATIC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.GOTO; +import static com.oracle.svm.interpreter.metadata.Bytecodes.GOTO_W; +import static com.oracle.svm.interpreter.metadata.Bytecodes.I2B; +import static com.oracle.svm.interpreter.metadata.Bytecodes.I2C; +import static com.oracle.svm.interpreter.metadata.Bytecodes.I2D; +import static com.oracle.svm.interpreter.metadata.Bytecodes.I2F; +import static com.oracle.svm.interpreter.metadata.Bytecodes.I2L; +import static com.oracle.svm.interpreter.metadata.Bytecodes.I2S; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IADD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IALOAD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IAND; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IASTORE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ICONST_0; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ICONST_1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ICONST_2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ICONST_3; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ICONST_4; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ICONST_5; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ICONST_M1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IDIV; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IFEQ; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IFGE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IFGT; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IFLE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IFLT; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IFNE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IFNONNULL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IFNULL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IF_ACMPEQ; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IF_ACMPNE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IF_ICMPEQ; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IF_ICMPGE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IF_ICMPGT; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IF_ICMPLE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IF_ICMPLT; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IF_ICMPNE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IINC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ILOAD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ILOAD_0; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ILOAD_1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ILOAD_2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ILOAD_3; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IMUL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INEG; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INSTANCEOF; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEDYNAMIC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEINTERFACE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKESPECIAL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKESTATIC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEVIRTUAL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IOR; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IREM; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IRETURN; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ISHL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ISHR; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ISTORE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ISTORE_0; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ISTORE_1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ISTORE_2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ISTORE_3; +import static com.oracle.svm.interpreter.metadata.Bytecodes.ISUB; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IUSHR; +import static com.oracle.svm.interpreter.metadata.Bytecodes.IXOR; +import static com.oracle.svm.interpreter.metadata.Bytecodes.JSR; +import static com.oracle.svm.interpreter.metadata.Bytecodes.JSR_W; +import static com.oracle.svm.interpreter.metadata.Bytecodes.L2D; +import static com.oracle.svm.interpreter.metadata.Bytecodes.L2F; +import static com.oracle.svm.interpreter.metadata.Bytecodes.L2I; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LADD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LALOAD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LAND; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LASTORE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LCMP; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LCONST_0; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LCONST_1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LDC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LDC2_W; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LDC_W; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LDIV; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LLOAD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LLOAD_0; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LLOAD_1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LLOAD_2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LLOAD_3; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LMUL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LNEG; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LOOKUPSWITCH; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LOR; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LREM; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LRETURN; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LSHL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LSHR; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LSTORE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LSTORE_0; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LSTORE_1; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LSTORE_2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LSTORE_3; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LSUB; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LUSHR; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LXOR; +import static com.oracle.svm.interpreter.metadata.Bytecodes.MONITORENTER; +import static com.oracle.svm.interpreter.metadata.Bytecodes.MONITOREXIT; +import static com.oracle.svm.interpreter.metadata.Bytecodes.MULTIANEWARRAY; +import static com.oracle.svm.interpreter.metadata.Bytecodes.NEW; +import static com.oracle.svm.interpreter.metadata.Bytecodes.NEWARRAY; +import static com.oracle.svm.interpreter.metadata.Bytecodes.NOP; +import static com.oracle.svm.interpreter.metadata.Bytecodes.POP; +import static com.oracle.svm.interpreter.metadata.Bytecodes.POP2; +import static com.oracle.svm.interpreter.metadata.Bytecodes.PUTFIELD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.PUTSTATIC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.RET; +import static com.oracle.svm.interpreter.metadata.Bytecodes.RETURN; +import static com.oracle.svm.interpreter.metadata.Bytecodes.SALOAD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.SASTORE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.SIPUSH; +import static com.oracle.svm.interpreter.metadata.Bytecodes.SWAP; +import static com.oracle.svm.interpreter.metadata.Bytecodes.TABLESWITCH; +import static com.oracle.svm.interpreter.metadata.Bytecodes.WIDE; + +import com.oracle.svm.interpreter.debug.DebuggerEvents; +import com.oracle.svm.interpreter.debug.EventKind; +import com.oracle.svm.interpreter.debug.SteppingControl; +import com.oracle.svm.interpreter.metadata.BytecodeStream; +import com.oracle.svm.interpreter.metadata.LookupSwitch; +import com.oracle.svm.interpreter.metadata.ReferenceConstant; +import com.oracle.svm.interpreter.metadata.TableSwitch; +import jdk.graal.compiler.api.directives.GraalDirectives; +import com.oracle.svm.interpreter.metadata.Bytecodes; +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.svm.core.StaticFieldsSupport; +import com.oracle.svm.core.jdk.InternalVMMethod; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.InterpreterConstantPool; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType; +import com.oracle.svm.interpreter.metadata.InterpreterUnresolvedSignature; +import com.oracle.svm.interpreter.metadata.MetadataUtil; + +import jdk.vm.ci.meta.ConstantPool; +import jdk.vm.ci.meta.ExceptionHandler; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaField; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.JavaMethod; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.PrimitiveConstant; +import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.UnresolvedJavaType; + +/** + * Bytecode interpreter loop. + */ +@InternalVMMethod +public final class Interpreter { + protected static final String FAILURE_CONSTANT_NOT_PART_OF_IMAGE_HEAP = "Trying to load constant that is not part of the Native Image heap"; + + public static final DebuggerEvents DEBUGGER = MetadataUtil.requireNonNull(ImageSingletons.lookup(DebuggerEvents.class)); + + private Interpreter() { + throw VMError.shouldNotReachHere("private constructor"); + } + + private static void initArguments(InterpreterFrame frame, InterpreterResolvedJavaMethod method) { + Object[] arguments = frame.getArguments(); + + boolean hasReceiver = !method.isStatic(); + int receiverSlot = hasReceiver ? 1 : 0; + int curSlot = 0; + if (hasReceiver) { + assert arguments[0] != null : "null receiver in init arguments !"; + Object receiver = arguments[0]; + setLocalObject(frame, curSlot, receiver); + curSlot += JavaKind.Object.getSlotCount(); + } + + InterpreterUnresolvedSignature methodSignature = method.getSignature(); + for (int i = 0; i < methodSignature.getParameterCount(false); ++i) { + JavaKind argType = methodSignature.getParameterKind(i); + // @formatter:off + switch (argType) { + case Boolean: setLocalInt(frame, curSlot, ((boolean) arguments[i + receiverSlot]) ? 1 : 0); break; + case Byte: setLocalInt(frame, curSlot, ((byte) arguments[i + receiverSlot])); break; + case Short: setLocalInt(frame, curSlot, ((short) arguments[i + receiverSlot])); break; + case Char: setLocalInt(frame, curSlot, ((char) arguments[i + receiverSlot])); break; + case Int: setLocalInt(frame, curSlot, (int) arguments[i + receiverSlot]); break; + case Float: setLocalFloat(frame, curSlot, (float) arguments[i + receiverSlot]); break; + case Long: setLocalLong(frame, curSlot, (long) arguments[i + receiverSlot]); ++curSlot; break; + case Double: setLocalDouble(frame, curSlot, (double) arguments[i + receiverSlot]); ++curSlot; break; + case Object: + // Reference type. + Object argument = arguments[i + receiverSlot]; + setLocalObject(frame, curSlot, argument); + break; + default : + throw VMError.shouldNotReachHereAtRuntime(); + } + // @formatter:on + ++curSlot; + } + } + + public static void initializeFrame(InterpreterFrame frame, InterpreterResolvedJavaMethod method) { + initArguments(frame, method); + } + + public static Object execute(InterpreterResolvedJavaMethod method, Object[] args) { + return execute(method, args, false); + } + + public static Object execute(InterpreterResolvedJavaMethod method, InterpreterFrame frame) { + return execute0(method, frame, false); + } + + public static Object execute(InterpreterResolvedJavaMethod method, Object[] args, boolean forceStayInInterpreter) { + InterpreterFrame frame = EspressoFrame.allocate(method.getMaxLocals(), method.getMaxStackSize(), args); + + InterpreterUtil.guarantee(!method.isNative(), "trying to interpret native method %s", method); + + initializeFrame(frame, method); + return execute0(method, frame, forceStayInInterpreter); + } + + private static Object execute0(InterpreterResolvedJavaMethod method, InterpreterFrame frame, boolean stayInInterpreter) { + try { + if (method.isSynchronized()) { + Object lockTarget = method.isStatic() + ? method.getDeclaringClass().getJavaClass() + : EspressoFrame.getThis(frame); + assert lockTarget != null; + InterpreterToVM.monitorEnter(frame, nullCheck(lockTarget)); + } + int startTop = startingStackOffset(method.getMaxLocals()); + return Root.executeBodyFromBCI(frame, method, 0, startTop, stayInInterpreter); + } finally { + InterpreterToVM.releaseInterpreterFrameLocks(frame); + } + } + + public static final ThreadLocal logIndent = ThreadLocal.withInitial(() -> 0); + + private static int getLogIndent() { + if (InterpreterOptions.InterpreterTraceSupport.getValue()) { + return logIndent.get(); + } + return 0; + } + + private static void setLogIndent(int indent) { + if (InterpreterOptions.InterpreterTraceSupport.getValue()) { + logIndent.set(indent); + } + } + + private static void traceInterpreterEnter(InterpreterResolvedJavaMethod method, int indent, int curBCI, int top) { + /* arguments to Log methods might have side-effects */ + if (!InterpreterOptions.InterpreterTraceSupport.getValue()) { + return; + } + + setLogIndent(indent + 2); + traceInterpreter(" ".repeat(indent)) + .string("[interp] Entered ") + .string(method.getDeclaringClass().getName()) + .string("::") + .string(method.getName()) + .string(method.getSignature().toMethodDescriptor()) + .string(" with bci=").unsigned(curBCI) + .string("/top=").unsigned(top).newline(); + } + + private static void traceInterpreterReturn(InterpreterResolvedJavaMethod method, int indent, int curBCI, int top) { + /* arguments to Log methods might have side-effects */ + if (!InterpreterOptions.InterpreterTraceSupport.getValue()) { + return; + } + + setLogIndent(indent); + traceInterpreter(" ".repeat(indent)); + traceInterpreter("[interp] Leave ") + .string(method.getDeclaringClass().getName()) + .string("::") + .string(method.getName()) + .string(method.getSignature().toMethodDescriptor()) + .string(" with bci=").unsigned(curBCI) + .string("/top=").unsigned(top).newline(); + } + + private static void traceInterpreterInstruction(InterpreterFrame frame, int indent, int curBCI, int top, int curOpcode) { + /* arguments to Log methods might have side-effects */ + if (!InterpreterOptions.InterpreterTraceSupport.getValue()) { + return; + } + + traceInterpreter(" ".repeat(indent)) + .string("bci=").unsigned(curBCI).string(" ") + .string(Bytecodes.nameOf(curOpcode)); + for (int slot = top - 1; slot >= 0; slot--) { + traceInterpreter(", s").unsigned(slot).string("=").hex(frame.getLongStatic(slot)).string("/").object(frame.getObjectStatic(slot)); + } + traceInterpreter("").newline(); + } + + private static void traceInterpreterException(InterpreterResolvedJavaMethod method, int indent, int curBCI, int top) { + /* arguments to Log methods might have side-effects */ + if (!InterpreterOptions.InterpreterTraceSupport.getValue()) { + return; + } + + setLogIndent(indent); + traceInterpreter(" ".repeat(indent)) + .string("[interp] Exception ") + .string(method.getDeclaringClass().getName()) + .string("::") + .string(method.getName()) + .string(method.getSignature().toMethodDescriptor()) + .string(" with bci=").unsigned(curBCI) + .string("/top=").unsigned(top).newline(); + } + + public static final class Root { + + private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterResolvedJavaMethod method, int startBCI, int startTop, + boolean forceStayInInterpreter) { + int curBCI = startBCI; + int top = startTop; + byte[] code = method.getInterpretedCode(); + + assert method.isStatic() || EspressoFrame.getThis(frame) != null; + + InterpreterUtil.guarantee(code != null, "no bytecode stream for %s", method); + + int indent = getLogIndent(); + traceInterpreterEnter(method, indent, curBCI, top); + + int debuggerEventFlags = 0; + if (DEBUGGER.isEventEnabled(Thread.currentThread(), EventKind.METHOD_ENTRY)) { + if (((InterpreterResolvedJavaType) method.getDeclaringClass()).isMethodEnterEvent()) { + debuggerEventFlags |= EventKind.METHOD_ENTRY.getFlag(); + } + } + + loop: while (true) { + /* + * Opaque read ensuring that BREAKPOINT opcodes are eventually read. Opaque == plain + * on x86/64, but on other architectures the read must be eventually guaranteed. + */ + int curOpcode = BytecodeStream.opaqueOpcode(code, curBCI); + + if (DEBUGGER.isEventEnabled(Thread.currentThread(), EventKind.SINGLE_STEP)) { + // Check that stepping "depth" and "size" are respected. + Thread currentThread = Thread.currentThread(); + SteppingControl steppingControl = DEBUGGER.getSteppingControl(currentThread); + if (steppingControl != null && steppingControl.isActiveAtCurrentFrameDepth()) { + int stepSize = steppingControl.getSize(); + if (stepSize == SteppingControl.STEP_MIN || + (stepSize == SteppingControl.STEP_LINE && !steppingControl.withinSameLine(method, curBCI))) { + debuggerEventFlags |= EventKind.SINGLE_STEP.getFlag(); + } + } + } + + if (curOpcode == BREAKPOINT) { + if (DEBUGGER.isEventEnabled(Thread.currentThread(), EventKind.BREAKPOINT)) { + debuggerEventFlags |= EventKind.BREAKPOINT.getFlag(); + } + curOpcode = method.getOriginalOpcodeAt(curBCI); + } + if (debuggerEventFlags != 0) { + // We have possibly: method enter, step before statement/expression, breakpoint + DEBUGGER.getEventHandler().onEventAt(Thread.currentThread(), method, curBCI, null, debuggerEventFlags); + debuggerEventFlags = 0; + } + + try { + traceInterpreterInstruction(frame, indent, curBCI, top, curOpcode); + + // @formatter:off + switch (curOpcode) { + case NOP: break; + case ACONST_NULL: putObject(frame, top, null); break; + + case ICONST_M1: // fall through + case ICONST_0: // fall through + case ICONST_1: // fall through + case ICONST_2: // fall through + case ICONST_3: // fall through + case ICONST_4: // fall through + case ICONST_5: putInt(frame, top, curOpcode - ICONST_0); break; + + case LCONST_0: // fall through + case LCONST_1: putLong(frame, top, curOpcode - LCONST_0); break; + + case FCONST_0: // fall through + case FCONST_1: // fall through + case FCONST_2: putFloat(frame, top, curOpcode - FCONST_0); break; + + case DCONST_0: // fall through + case DCONST_1: putDouble(frame, top, curOpcode - DCONST_0); break; + + case BIPUSH: putInt(frame, top, BytecodeStream.readByte(code, curBCI)); break; + case SIPUSH: putInt(frame, top, BytecodeStream.readShort(code, curBCI)); break; + + case LDC : loadConstant(frame, method, top, BytecodeStream.readCPI1(code, curBCI), curOpcode); break; + case LDC_W : // fall through + case LDC2_W: loadConstant(frame, method, top, BytecodeStream.readCPI2(code, curBCI), curOpcode); break; + + case ILOAD: putInt(frame, top, getLocalInt(frame, BytecodeStream.readLocalIndex1(code, curBCI))); break; + case LLOAD: putLong(frame, top, getLocalLong(frame, BytecodeStream.readLocalIndex1(code, curBCI))); break; + case FLOAD: putFloat(frame, top, getLocalFloat(frame, BytecodeStream.readLocalIndex1(code, curBCI))); break; + case DLOAD: putDouble(frame, top, getLocalDouble(frame, BytecodeStream.readLocalIndex1(code, curBCI))); break; + case ALOAD: putObject(frame, top, getLocalObject(frame, BytecodeStream.readLocalIndex1(code, curBCI))); break; + + case ILOAD_0: // fall through + case ILOAD_1: // fall through + case ILOAD_2: // fall through + case ILOAD_3: putInt(frame, top, getLocalInt(frame, curOpcode - ILOAD_0)); break; + + case LLOAD_0: // fall through + case LLOAD_1: // fall through + case LLOAD_2: // fall through + case LLOAD_3: putLong(frame, top, getLocalLong(frame, curOpcode - LLOAD_0)); break; + + case FLOAD_0: // fall through + case FLOAD_1: // fall through + case FLOAD_2: // fall through + case FLOAD_3: putFloat(frame, top, getLocalFloat(frame, curOpcode - FLOAD_0)); break; + + case DLOAD_0: // fall through + case DLOAD_1: // fall through + case DLOAD_2: // fall through + case DLOAD_3: putDouble(frame, top, getLocalDouble(frame, curOpcode - DLOAD_0)); break; + + case ALOAD_0: putObject(frame, top, getLocalObject(frame, 0)); break; + case ALOAD_1: // fall through + case ALOAD_2: // fall through + case ALOAD_3: putObject(frame, top, getLocalObject(frame, curOpcode - ALOAD_0)); break; + + case IALOAD: // fall through + case LALOAD: // fall through + case FALOAD: // fall through + case DALOAD: // fall through + case BALOAD: // fall through + case CALOAD: // fall through + case SALOAD: // fall through + case AALOAD: arrayLoad(frame, top, curOpcode); break; + + case ISTORE: setLocalInt(frame, BytecodeStream.readLocalIndex1(code, curBCI), popInt(frame, top - 1)); break; + case LSTORE: setLocalLong(frame, BytecodeStream.readLocalIndex1(code, curBCI), popLong(frame, top - 1)); break; + case FSTORE: setLocalFloat(frame, BytecodeStream.readLocalIndex1(code, curBCI), popFloat(frame, top - 1)); break; + case DSTORE: setLocalDouble(frame, BytecodeStream.readLocalIndex1(code, curBCI), popDouble(frame, top - 1)); break; + case ASTORE: setLocalObjectOrReturnAddress(frame, BytecodeStream.readLocalIndex1(code, curBCI), popReturnAddressOrObject(frame, top - 1)); break; + + case ISTORE_0: // fall through + case ISTORE_1: // fall through + case ISTORE_2: // fall through + case ISTORE_3: setLocalInt(frame, curOpcode - ISTORE_0, popInt(frame, top - 1)); break; + + case LSTORE_0: // fall through + case LSTORE_1: // fall through + case LSTORE_2: // fall through + case LSTORE_3: setLocalLong(frame, curOpcode - LSTORE_0, popLong(frame, top - 1)); break; + + case FSTORE_0: // fall through + case FSTORE_1: // fall through + case FSTORE_2: // fall through + case FSTORE_3: setLocalFloat(frame, curOpcode - FSTORE_0, popFloat(frame, top - 1)); break; + + case DSTORE_0: // fall through + case DSTORE_1: // fall through + case DSTORE_2: // fall through + case DSTORE_3: setLocalDouble(frame, curOpcode - DSTORE_0, popDouble(frame, top - 1)); break; + + case ASTORE_0: // fall through + case ASTORE_1: // fall through + case ASTORE_2: // fall through + case ASTORE_3: setLocalObjectOrReturnAddress(frame, curOpcode - ASTORE_0, popReturnAddressOrObject(frame, top - 1)); break; + + case IASTORE: // fall through + case LASTORE: // fall through + case FASTORE: // fall through + case DASTORE: // fall through + case AASTORE: // fall through + case BASTORE: // fall through + case CASTORE: // fall through + case SASTORE: arrayStore(frame, top, curOpcode); break; + + case POP2: + clear(frame, top - 1); + clear(frame, top - 2); + break; + + case POP: clear(frame, top - 1); break; + + case DUP : dup1(frame, top); break; + case DUP_X1 : dupx1(frame, top); break; + case DUP_X2 : dupx2(frame, top); break; + case DUP2 : dup2(frame, top); break; + case DUP2_X1 : dup2x1(frame, top); break; + case DUP2_X2 : dup2x2(frame, top); break; + case SWAP : swapSingle(frame, top); break; + + case IADD: putInt(frame, top - 2, popInt(frame, top - 1) + popInt(frame, top - 2)); break; + case LADD: putLong(frame, top - 4, popLong(frame, top - 1) + popLong(frame, top - 3)); break; + case FADD: putFloat(frame, top - 2, popFloat(frame, top - 1) + popFloat(frame, top - 2)); break; + case DADD: putDouble(frame, top - 4, popDouble(frame, top - 1) + popDouble(frame, top - 3)); break; + + case ISUB: putInt(frame, top - 2, popInt(frame, top - 2) - popInt(frame, top - 1)); break; + case LSUB: putLong(frame, top - 4, popLong(frame, top - 3) - popLong(frame, top - 1)); break; + case FSUB: putFloat(frame, top - 2, popFloat(frame, top - 2) - popFloat(frame, top - 1)); break; + case DSUB: putDouble(frame, top - 4, popDouble(frame, top - 3) - popDouble(frame, top - 1)); break; + + case IMUL: putInt(frame, top - 2, popInt(frame, top - 1) * popInt(frame, top - 2)); break; + case LMUL: putLong(frame, top - 4, popLong(frame, top - 1) * popLong(frame, top - 3)); break; + case FMUL: putFloat(frame, top - 2, popFloat(frame, top - 1) * popFloat(frame, top - 2)); break; + case DMUL: putDouble(frame, top - 4, popDouble(frame, top - 1) * popDouble(frame, top - 3)); break; + + case IDIV: putInt(frame, top - 2, divInt(popInt(frame, top - 1), popInt(frame, top - 2))); break; + case LDIV: putLong(frame, top - 4, divLong(popLong(frame, top - 1), popLong(frame, top - 3))); break; + case FDIV: putFloat(frame, top - 2, divFloat(popFloat(frame, top - 1), popFloat(frame, top - 2))); break; + case DDIV: putDouble(frame, top - 4, divDouble(popDouble(frame, top - 1), popDouble(frame, top - 3))); break; + + case IREM: putInt(frame, top - 2, remInt(popInt(frame, top - 1), popInt(frame, top - 2))); break; + case LREM: putLong(frame, top - 4, remLong(popLong(frame, top - 1), popLong(frame, top - 3))); break; + case FREM: putFloat(frame, top - 2, remFloat(popFloat(frame, top - 1), popFloat(frame, top - 2))); break; + case DREM: putDouble(frame, top - 4, remDouble(popDouble(frame, top - 1), popDouble(frame, top - 3))); break; + + case INEG: putInt(frame, top - 1, -popInt(frame, top - 1)); break; + case LNEG: putLong(frame, top - 2, -popLong(frame, top - 1)); break; + case FNEG: putFloat(frame, top - 1, -popFloat(frame, top - 1)); break; + case DNEG: putDouble(frame, top - 2, -popDouble(frame, top - 1)); break; + + case ISHL: putInt(frame, top - 2, shiftLeftInt(popInt(frame, top - 1), popInt(frame, top - 2))); break; + case LSHL: putLong(frame, top - 3, shiftLeftLong(popInt(frame, top - 1), popLong(frame, top - 2))); break; + case ISHR: putInt(frame, top - 2, shiftRightSignedInt(popInt(frame, top - 1), popInt(frame, top - 2))); break; + case LSHR: putLong(frame, top - 3, shiftRightSignedLong(popInt(frame, top - 1), popLong(frame, top - 2))); break; + case IUSHR: putInt(frame, top - 2, shiftRightUnsignedInt(popInt(frame, top - 1), popInt(frame, top - 2))); break; + case LUSHR: putLong(frame, top - 3, shiftRightUnsignedLong(popInt(frame, top - 1), popLong(frame, top - 2))); break; + + case IAND: putInt(frame, top - 2, popInt(frame, top - 1) & popInt(frame, top - 2)); break; + case LAND: putLong(frame, top - 4, popLong(frame, top - 1) & popLong(frame, top - 3)); break; + + case IOR: putInt(frame, top - 2, popInt(frame, top - 1) | popInt(frame, top - 2)); break; + case LOR: putLong(frame, top - 4, popLong(frame, top - 1) | popLong(frame, top - 3)); break; + + case IXOR: putInt(frame, top - 2, popInt(frame, top - 1) ^ popInt(frame, top - 2)); break; + case LXOR: putLong(frame, top - 4, popLong(frame, top - 1) ^ popLong(frame, top - 3)); break; + + case IINC: + setLocalInt(frame, BytecodeStream.readLocalIndex1(code, curBCI), getLocalInt(frame, BytecodeStream.readLocalIndex1(code, curBCI)) + BytecodeStream.readIncrement1(code, curBCI)); + break; + + case I2L: putLong(frame, top - 1, popInt(frame, top - 1)); break; + case I2F: putFloat(frame, top - 1, popInt(frame, top - 1)); break; + case I2D: putDouble(frame, top - 1, popInt(frame, top - 1)); break; + + case L2I: putInt(frame, top - 2, (int) popLong(frame, top - 1)); break; + case L2F: putFloat(frame, top - 2, popLong(frame, top - 1)); break; + case L2D: putDouble(frame, top - 2, popLong(frame, top - 1)); break; + + case F2I: putInt(frame, top - 1, (int) popFloat(frame, top - 1)); break; + case F2L: putLong(frame, top - 1, (long) popFloat(frame, top - 1)); break; + case F2D: putDouble(frame, top - 1, popFloat(frame, top - 1)); break; + + case D2I: putInt(frame, top - 2, (int) popDouble(frame, top - 1)); break; + case D2L: putLong(frame, top - 2, (long) popDouble(frame, top - 1)); break; + case D2F: putFloat(frame, top - 2, (float) popDouble(frame, top - 1)); break; + + case I2B: putInt(frame, top - 1, (byte) popInt(frame, top - 1)); break; + case I2C: putInt(frame, top - 1, (char) popInt(frame, top - 1)); break; + case I2S: putInt(frame, top - 1, (short) popInt(frame, top - 1)); break; + + case LCMP : putInt(frame, top - 4, compareLong(popLong(frame, top - 1), popLong(frame, top - 3))); break; + case FCMPL: putInt(frame, top - 2, compareFloatLess(popFloat(frame, top - 1), popFloat(frame, top - 2))); break; + case FCMPG: putInt(frame, top - 2, compareFloatGreater(popFloat(frame, top - 1), popFloat(frame, top - 2))); break; + case DCMPL: putInt(frame, top - 4, compareDoubleLess(popDouble(frame, top - 1), popDouble(frame, top - 3))); break; + case DCMPG: putInt(frame, top - 4, compareDoubleGreater(popDouble(frame, top - 1), popDouble(frame, top - 3))); break; + + // @formatter:on + case IFEQ: // fall through + case IFNE: // fall through + case IFLT: // fall through + case IFGE: // fall through + case IFGT: // fall through + case IFLE: + if (takeBranchPrimitive1(popInt(frame, top - 1), curOpcode)) { + top += ConstantBytecodes.stackEffectOf(IFLE); + curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest2(code, curBCI), top); + continue loop; + } + break; + + case IF_ICMPEQ: // fall through + case IF_ICMPNE: // fall through + case IF_ICMPLT: // fall through + case IF_ICMPGE: // fall through + case IF_ICMPGT: // fall through + case IF_ICMPLE: + if (takeBranchPrimitive2(popInt(frame, top - 1), popInt(frame, top - 2), curOpcode)) { + top += ConstantBytecodes.stackEffectOf(IF_ICMPLE); + curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest2(code, curBCI), top); + continue loop; + } + break; + + case IF_ACMPEQ: // fall through + case IF_ACMPNE: + if (takeBranchRef2(popObject(frame, top - 1), popObject(frame, top - 2), curOpcode)) { + top += ConstantBytecodes.stackEffectOf(IF_ACMPNE); + curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest2(code, curBCI), top); + continue loop; + } + break; + + case IFNULL: // fall through + case IFNONNULL: + if (takeBranchRef1(popObject(frame, top - 1), curOpcode)) { + top += ConstantBytecodes.stackEffectOf(IFNONNULL); + curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest2(code, curBCI), top); + continue loop; + } + break; + + case GOTO: + curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest2(code, curBCI), top); + continue loop; + + case GOTO_W: + curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest4(code, curBCI), top); + continue loop; + + case JSR: { + putReturnAddress(frame, top, curBCI + ConstantBytecodes.lengthOf(JSR)); + // The JSR stack effect is incorrectly set to 0 in the compiler sources. + // To keep interpreter and compiler in sync, the correct stack effect is + // hardcoded here. + int stackEffect = 1; // Bytecodes.stackEffectOf(JSR) + top += stackEffect; + curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest2(code, curBCI), top); + continue loop; + } + case JSR_W: { + putReturnAddress(frame, top, curBCI + ConstantBytecodes.lengthOf(JSR_W)); + // The JSR_W stack effect is incorrectly set to 0 in the compiler + // sources. To keep interpreter and compiler in sync, the correct stack + // effect is hardcoded here. + int stackEffect = 1; // Bytecodes.stackEffectOf(JSR_W) + top += stackEffect; + curBCI = beforeJumpChecks(frame, curBCI, BytecodeStream.readBranchDest4(code, curBCI), top); + continue loop; + } + case RET: { + top += ConstantBytecodes.stackEffectOf(RET); + curBCI = beforeJumpChecks(frame, curBCI, getLocalReturnAddress(frame, BytecodeStream.readLocalIndex1(code, curBCI)), top); + continue loop; + } + + case TABLESWITCH: { + int index = popInt(frame, top - 1); + int low = TableSwitch.lowKey(code, curBCI); + int high = TableSwitch.highKey(code, curBCI); + assert low <= high; + + int targetBCI; + if (low <= index && index <= high) { + targetBCI = TableSwitch.targetAt(code, curBCI, index - low); + } else { + targetBCI = TableSwitch.defaultTarget(code, curBCI); + } + top += ConstantBytecodes.stackEffectOf(TABLESWITCH); + curBCI = beforeJumpChecks(frame, curBCI, targetBCI, top); + continue loop; + } + case LOOKUPSWITCH: { + int key = popInt(frame, top - 1); + int low = 0; + int high = LookupSwitch.numberOfCases(code, curBCI) - 1; + while (low <= high) { + int mid = (low + high) >>> 1; + int midVal = LookupSwitch.keyAt(code, curBCI, mid); + if (midVal < key) { + low = mid + 1; + } else if (midVal > key) { + high = mid - 1; + } else { + // Key found. + int targetBCI = curBCI + LookupSwitch.offsetAt(code, curBCI, mid); + top += ConstantBytecodes.stackEffectOf(LOOKUPSWITCH); + curBCI = beforeJumpChecks(frame, curBCI, targetBCI, top); + continue loop; + } + } + + // Key not found. + int targetBCI = LookupSwitch.defaultTarget(code, curBCI); + top += ConstantBytecodes.stackEffectOf(LOOKUPSWITCH); + curBCI = beforeJumpChecks(frame, curBCI, targetBCI, top); + continue loop; + } + + case IRETURN: // fall through + case LRETURN: // fall through + case FRETURN: // fall through + case DRETURN: // fall through + case ARETURN: // fall through + case RETURN: { + Object returnValue = getReturnValueAsObject(frame, method, top); + traceInterpreterReturn(method, indent, curBCI, top); + if (DEBUGGER.isEventEnabled(Thread.currentThread(), EventKind.METHOD_EXIT)) { + if (((InterpreterResolvedJavaType) method.getDeclaringClass()).isMethodExitEvent()) { + int flags = EventKind.METHOD_EXIT.getFlag() | EventKind.METHOD_EXIT_WITH_RETURN_VALUE.getFlag(); + DEBUGGER.getEventHandler().onEventAt(Thread.currentThread(), method, curBCI, returnValue, flags); + } + } + return returnValue; + } + // @formatter:off + // Bytecodes order is shuffled. + case GETSTATIC : // fall through + case GETFIELD : top += getField(frame, top, resolveField(method, curOpcode, BytecodeStream.readCPI2(code, curBCI)), curOpcode); break; + case PUTSTATIC : // fall through + case PUTFIELD : top += putField(frame, top, resolveField(method, curOpcode, BytecodeStream.readCPI2(code, curBCI)), curOpcode); break; + + case INVOKEVIRTUAL : // fall through + case INVOKESPECIAL : // fall through + case INVOKESTATIC : // fall through + case INVOKEINTERFACE : // fall through + case INVOKEDYNAMIC : { + boolean preferStayInInterpreter = forceStayInInterpreter; + SteppingControl steppingControl = null; + boolean stepEventDisabled = false; + Thread currentThread = Thread.currentThread(); + if (DEBUGGER.isEventEnabled(currentThread, EventKind.SINGLE_STEP)) { + // Disable stepping for inner frames, except for step into, where we must force interpreter execution. + steppingControl = DEBUGGER.getSteppingControl(currentThread); + if (steppingControl != null) { + // If step events can be ignored at frame n => can be also ignored at inner frame n + 1. + steppingControl.pushFrame(); + if (!steppingControl.isActiveAtCurrentFrameDepth()) { + DEBUGGER.setEventEnabled(currentThread, EventKind.SINGLE_STEP, false); + stepEventDisabled = true; + } + if (steppingControl.getDepth() == SteppingControl.STEP_INTO) { + // For now force the callee to stay in interpreter. + // If this is not possible, the next step event will be triggered only after returning. + // From the debugger's perspective there's almost no difference between a compiled method and a native method. + preferStayInInterpreter = true; + } + } + } + + try { + top += invoke(frame, method, code, top, curBCI, curOpcode, forceStayInInterpreter, preferStayInInterpreter); + } finally { + SteppingControl newSteppingControl = DEBUGGER.getSteppingControl(currentThread); + if (newSteppingControl != null) { + if (DEBUGGER.isEventEnabled(currentThread, EventKind.SINGLE_STEP)) { + newSteppingControl.popFrame(); + } else if (steppingControl == newSteppingControl && stepEventDisabled) { + // Re-enable stepping events that could have been disabled by step outer/out into inner frames. + DEBUGGER.setEventEnabled(currentThread, EventKind.SINGLE_STEP, true); + newSteppingControl.popFrame(); + } + } + } + break; + } + + case NEW : putObject(frame, top, InterpreterToVM.createNewReference(resolveType(method, NEW, BytecodeStream.readCPI2(code, curBCI)))); break; + case NEWARRAY : putObject(frame, top - 1, InterpreterToVM.createNewPrimitiveArray(BytecodeStream.readByte(code, curBCI), popInt(frame, top - 1))); break; + case ANEWARRAY : putObject(frame, top - 1, InterpreterToVM.createNewReferenceArray(resolveType(method, ANEWARRAY, BytecodeStream.readCPI2(code, curBCI)), popInt(frame, top - 1))); break; + case ARRAYLENGTH : putInt(frame, top - 1, InterpreterToVM.arrayLength(nullCheck(popObject(frame, top - 1)))); break; + case ATHROW : + throw SemanticJavaException.raise((Throwable) nullCheck(popObject(frame, top - 1))); + + case CHECKCAST : { + Object receiver = peekObject(frame, top - 1); + // Resolve type iff receiver != null. + if (receiver != null) { + InterpreterToVM.checkCast(receiver, resolveType(method, CHECKCAST, BytecodeStream.readCPI2(code, curBCI))); + } + break; + } + case INSTANCEOF : { + Object receiver = popObject(frame, top - 1); + // Resolve type iff receiver != null. + putInt(frame, top - 1, (receiver != null && InterpreterToVM.instanceOf(receiver, resolveType(method, INSTANCEOF, BytecodeStream.readCPI2(code, curBCI)))) ? 1 : 0); + break; + } + case MONITORENTER: InterpreterToVM.monitorEnter(frame, nullCheck(popObject(frame, top - 1))); break; + case MONITOREXIT : InterpreterToVM.monitorExit(frame, nullCheck(popObject(frame, top - 1))); break; + + case WIDE: { + // The next opcode is never patched, plain access is fine. + int wideOpcode = BytecodeStream.opcode(code, curBCI + 1); + switch (wideOpcode) { + case ILOAD: putInt(frame, top, getLocalInt(frame, BytecodeStream.readLocalIndex2(code, curBCI))); break; + case LLOAD: putLong(frame, top, getLocalLong(frame, BytecodeStream.readLocalIndex2(code, curBCI))); break; + case FLOAD: putFloat(frame, top, getLocalFloat(frame, BytecodeStream.readLocalIndex2(code, curBCI))); break; + case DLOAD: putDouble(frame, top, getLocalDouble(frame, BytecodeStream.readLocalIndex2(code, curBCI))); break; + case ALOAD: putObject(frame, top, getLocalObject(frame, BytecodeStream.readLocalIndex2(code, curBCI))); break; + + case ISTORE: setLocalInt(frame, BytecodeStream.readLocalIndex2(code, curBCI), popInt(frame, top - 1)); break; + case LSTORE: setLocalLong(frame, BytecodeStream.readLocalIndex2(code, curBCI), popLong(frame, top - 1)); break; + case FSTORE: setLocalFloat(frame, BytecodeStream.readLocalIndex2(code, curBCI), popFloat(frame, top - 1)); break; + case DSTORE: setLocalDouble(frame, BytecodeStream.readLocalIndex2(code, curBCI), popDouble(frame, top - 1)); break; + case ASTORE: setLocalObjectOrReturnAddress(frame, BytecodeStream.readLocalIndex2(code, curBCI), popReturnAddressOrObject(frame, top - 1)); break; + case IINC: setLocalInt(frame, BytecodeStream.readLocalIndex2(code, curBCI), getLocalInt(frame, BytecodeStream.readLocalIndex2(code, curBCI)) + BytecodeStream.readIncrement2(code, curBCI)); break; + + case RET: { + top += ConstantBytecodes.stackEffectOf(RET); + curBCI = beforeJumpChecks(frame, curBCI, getLocalReturnAddress(frame, BytecodeStream.readLocalIndex2(code, curBCI)), top); + continue loop; + } + default: + throw VMError.shouldNotReachHere(Bytecodes.nameOf(curOpcode)); + } + top += Bytecodes.stackEffectOf(wideOpcode); + curBCI += (wideOpcode == IINC) ? 6 : /* wide store/load */ 4; + continue loop; + } + // @formatter:on + + case MULTIANEWARRAY: + top += allocateMultiArray(frame, top, resolveType(method, MULTIANEWARRAY, BytecodeStream.readCPI2(code, curBCI)), BytecodeStream.readUByte(code, curBCI + 3)); + break; + + default: + throw VMError.shouldNotReachHere(Bytecodes.nameOf(curOpcode)); + } + } catch (SemanticJavaException | OutOfMemoryError | StackOverflowError e) { + // Semantic Java exception thrown by interpreted code. + Throwable exception = e instanceof SemanticJavaException ? e.getCause() : e; + ExceptionHandler handler = resolveExceptionHandler(method, curBCI, exception); + if (handler != null) { + clearOperandStack(frame, method, top); + top = startingStackOffset(method.getMaxLocals()); + putObject(frame, top, exception); + top++; + curBCI = beforeJumpChecks(frame, curBCI, handler.getHandlerBCI(), top); + continue loop; + } else { + traceInterpreterException(method, indent, curBCI, top); + throw uncheckedThrow(exception); + } + } catch (Throwable e) { + // Exceptions other than SemanticJavaException (and OutOfMemoryError and + // StackoverflowError) are considered internal errors, a bug in the + // interpreter. + // VMError.shouldNotReachHere does not print the passed exception stack trace, + // so it's printed before panicking to help diagnose interpreter bugs. + e.printStackTrace(); + throw VMError.shouldNotReachHere("Unexpected host exception reached the interpreter", e); + } + + assert curOpcode != WIDE && curOpcode != LOOKUPSWITCH && curOpcode != TABLESWITCH; + + top += Bytecodes.stackEffectOf(curOpcode); + curBCI += Bytecodes.lengthOf(curOpcode); + } // loop + } + } + + @SuppressWarnings("unchecked") + private static RuntimeException uncheckedThrow(Throwable e) throws T { + throw (T) e; + } + + private static Object getReturnValueAsObject(InterpreterFrame frame, InterpreterResolvedJavaMethod method, int top) { + JavaKind returnType = method.getSignature().getReturnKind(); + // @formatter:off + return switch (returnType) { + case Boolean -> stackIntToBoolean(popInt(frame, top - 1)); + case Byte -> (byte) popInt(frame, top - 1); + case Short -> (short) popInt(frame, top - 1); + case Char -> (char) popInt(frame, top - 1); + case Int -> popInt(frame, top - 1); + case Long -> popLong(frame, top - 1); + case Float -> popFloat(frame, top - 1); + case Double -> popDouble(frame, top - 1); + case Void -> null; // void + case Object -> popObject(frame, top - 1); + default -> throw VMError.shouldNotReachHereAtRuntime(); + }; + // @formatter:on + } + + private static void clearOperandStack(InterpreterFrame frame, InterpreterResolvedJavaMethod method, int top) { + int stackStart = startingStackOffset(method.getMaxLocals()); + for (int slot = top - 1; slot >= stackStart; --slot) { + clear(frame, slot); + } + } + + private static boolean takeBranchRef1(Object operand, int opcode) { + assert IFNULL <= opcode && opcode <= IFNONNULL; + return switch (opcode) { + case IFNULL -> operand == null; + case IFNONNULL -> operand != null; + default -> throw VMError.shouldNotReachHereAtRuntime(); + }; + } + + private static boolean takeBranchPrimitive1(int operand, int opcode) { + assert IFEQ <= opcode && opcode <= IFLE; + return switch (opcode) { + case IFEQ -> operand == 0; + case IFNE -> operand != 0; + case IFLT -> operand < 0; + case IFGE -> operand >= 0; + case IFGT -> operand > 0; + case IFLE -> operand <= 0; + default -> throw VMError.shouldNotReachHereAtRuntime(); + }; + } + + private static boolean takeBranchPrimitive2(int operand1, int operand2, int opcode) { + assert IF_ICMPEQ <= opcode && opcode <= IF_ICMPLE; + return switch (opcode) { + case IF_ICMPEQ -> operand1 == operand2; + case IF_ICMPNE -> operand1 != operand2; + case IF_ICMPLT -> operand1 > operand2; + case IF_ICMPGE -> operand1 <= operand2; + case IF_ICMPGT -> operand1 < operand2; + case IF_ICMPLE -> operand1 >= operand2; + default -> throw VMError.shouldNotReachHereAtRuntime(); + }; + } + + private static boolean takeBranchRef2(Object operand1, Object operand2, int opcode) { + assert IF_ACMPEQ <= opcode && opcode <= IF_ACMPNE; + return switch (opcode) { + case IF_ACMPEQ -> operand1 == operand2; + case IF_ACMPNE -> operand1 != operand2; + default -> throw VMError.shouldNotReachHereAtRuntime(); + }; + } + + private static void arrayLoad(InterpreterFrame frame, int top, int loadOpcode) { + assert IALOAD <= loadOpcode && loadOpcode <= SALOAD; + int index = popInt(frame, top - 1); + Object array = nullCheck(popObject(frame, top - 2)); + switch (loadOpcode) { + case BALOAD -> putInt(frame, top - 2, InterpreterToVM.getArrayByte(index, array)); + case SALOAD -> putInt(frame, top - 2, InterpreterToVM.getArrayShort(index, (short[]) array)); + case CALOAD -> putInt(frame, top - 2, InterpreterToVM.getArrayChar(index, (char[]) array)); + case IALOAD -> putInt(frame, top - 2, InterpreterToVM.getArrayInt(index, (int[]) array)); + case FALOAD -> putFloat(frame, top - 2, InterpreterToVM.getArrayFloat(index, (float[]) array)); + case LALOAD -> putLong(frame, top - 2, InterpreterToVM.getArrayLong(index, (long[]) array)); + case DALOAD -> putDouble(frame, top - 2, InterpreterToVM.getArrayDouble(index, (double[]) array)); + case AALOAD -> putObject(frame, top - 2, InterpreterToVM.getArrayObject(index, (Object[]) array)); + default -> throw VMError.shouldNotReachHereAtRuntime(); + } + } + + private static void arrayStore(InterpreterFrame frame, int top, int storeOpcode) { + assert IASTORE <= storeOpcode && storeOpcode <= SASTORE; + int offset = (storeOpcode == LASTORE || storeOpcode == DASTORE) ? 2 : 1; + int index = popInt(frame, top - 1 - offset); + Object array = nullCheck(popObject(frame, top - 2 - offset)); + switch (storeOpcode) { + case BASTORE -> InterpreterToVM.setArrayByte((byte) popInt(frame, top - 1), index, array); + case SASTORE -> InterpreterToVM.setArrayShort((short) popInt(frame, top - 1), index, (short[]) array); + case CASTORE -> InterpreterToVM.setArrayChar((char) popInt(frame, top - 1), index, (char[]) array); + case IASTORE -> InterpreterToVM.setArrayInt(popInt(frame, top - 1), index, (int[]) array); + case FASTORE -> InterpreterToVM.setArrayFloat(popFloat(frame, top - 1), index, (float[]) array); + case LASTORE -> InterpreterToVM.setArrayLong(popLong(frame, top - 1), index, (long[]) array); + case DASTORE -> InterpreterToVM.setArrayDouble(popDouble(frame, top - 1), index, (double[]) array); + case AASTORE -> InterpreterToVM.setArrayObject(popObject(frame, top - 1), index, (Object[]) array); + default -> throw VMError.shouldNotReachHereAtRuntime(); + } + } + + @SuppressWarnings("unused") + private static int beforeJumpChecks(InterpreterFrame frame, int curBCI, int targetBCI, int top) { + if (targetBCI <= curBCI) { + // GR-55055: Safepoint poll needed? + } + return targetBCI; + } + + private static ExceptionHandler resolveExceptionHandler(InterpreterResolvedJavaMethod method, int bci, Throwable ex) { + ExceptionHandler[] handlers = method.getExceptionHandlers(); + ExceptionHandler resolved = null; + for (ExceptionHandler toCheck : handlers) { + if (bci >= toCheck.getStartBCI() && bci < toCheck.getEndBCI()) { + JavaType catchType = null; + if (!toCheck.isCatchAll()) { + // exception handlers are similar to instanceof bytecodes, so we pass instanceof + catchType = resolveTypeOrUnresolved(method, INSTANCEOF, (char) toCheck.catchTypeCPI()); + if (catchType instanceof UnresolvedJavaType) { + // Exception type is not reachable, skip handler. + continue; + } + } + if (catchType == null || InterpreterToVM.instanceOf(ex, (InterpreterResolvedObjectType) catchType)) { + // the first found exception handler is our exception handler + resolved = toCheck; + break; + } + } + } + return resolved; + } + + private static SemanticJavaException noClassDefFoundError(int opcode, JavaType javaType) { + String message = (javaType != null) + ? javaType.toJavaName() + : MetadataUtil.fmt("%s: (cpi = 0) unknown type", Bytecodes.nameOf(opcode)); + throw SemanticJavaException.raise(new NoClassDefFoundError(message)); + } + + private static SemanticJavaException noSuchMethodError(int opcode, JavaMethod javaMethod) { + String message = (javaMethod != null) + ? javaMethod.format("%H.%n(%P)") + : MetadataUtil.fmt("%s: (cpi = 0) unknown method", Bytecodes.nameOf(opcode)); + throw SemanticJavaException.raise(new NoSuchMethodError(message)); + } + + private static SemanticJavaException noSuchFieldError(int opcode, JavaField javaField) { + String message = (javaField != null) + ? javaField.format("%H.%n") + : MetadataUtil.fmt("%s: (cpi = 0) unknown field", Bytecodes.nameOf(opcode)); + throw SemanticJavaException.raise(new NoSuchFieldError(message)); + } + + private static void loadConstant(InterpreterFrame frame, InterpreterResolvedJavaMethod method, int top, char cpi, int opcode) { + assert opcode == LDC || opcode == LDC_W || opcode == LDC2_W; + if (GraalDirectives.injectBranchProbability(GraalDirectives.SLOWPATH_PROBABILITY, cpi == 0)) { + VMError.guarantee(opcode != LDC2_W); + throw noClassDefFoundError(opcode, null); + } + ConstantPool pool = getConstantPool(method); + Object constant = pool.lookupConstant(cpi); + if (constant instanceof PrimitiveConstant primitiveConstant) { + JavaKind kind = primitiveConstant.getJavaKind(); + assert !kind.needsTwoSlots() || opcode == LDC2_W; + assert kind.needsTwoSlots() || (opcode == LDC || opcode == LDC_W); + switch (kind) { + case Int -> putInt(frame, top, primitiveConstant.asInt()); + case Float -> putFloat(frame, top, primitiveConstant.asFloat()); + case Long -> putLong(frame, top, primitiveConstant.asLong()); + case Double -> putDouble(frame, top, primitiveConstant.asDouble()); + default -> throw VMError.shouldNotReachHereAtRuntime(); + } + } else if (constant instanceof JavaType) { + InterpreterResolvedJavaType resolvedType = resolveType(method, opcode, cpi); + putObject(frame, top, resolvedType.getJavaClass()); + } else if (constant instanceof ReferenceConstant referenceConstant) { + VMError.guarantee(referenceConstant.isNonNull(), FAILURE_CONSTANT_NOT_PART_OF_IMAGE_HEAP); + Object constantValue = referenceConstant.getReferent(); + putObject(frame, top, constantValue); + } else if (constant.equals(JavaConstant.NULL_POINTER)) { + putObject(frame, top, null); + } else { + throw VMError.unimplemented("LDC* constant pool type"); + } + } + + private static InterpreterConstantPool getConstantPool(InterpreterResolvedJavaMethod method) { + return method.getConstantPool(); + } + + private static int invoke(InterpreterFrame callerFrame, InterpreterResolvedJavaMethod method, byte[] code, int top, int curBCI, int opcode, boolean forceStayInInterpreter, + boolean preferStayInInterpreter) { + int invokeTop = top; + + char cpi = BytecodeStream.readCPI2(code, curBCI); + InterpreterResolvedJavaMethod seedMethod = Interpreter.resolveMethod(method, opcode, cpi); + + boolean hasReceiver = !seedMethod.isStatic(); + boolean isVirtual = opcode == INVOKEVIRTUAL || opcode == INVOKEINTERFACE; + + if (opcode == INVOKEDYNAMIC) { + int appendixCPI = BytecodeStream.readCPI4(code, curBCI) & 0xFFFF; + if (appendixCPI != 0) { + JavaConstant appendixConstant = method.getConstantPool().lookupAppendix(appendixCPI, opcode); + Object appendix; + if (JavaConstant.NULL_POINTER.equals(appendixConstant)) { + // The appendix is deliberately null. + appendix = null; + } else { + if (appendixConstant instanceof ReferenceConstant referenceConstant) { + appendix = referenceConstant.getReferent(); + } else { + throw VMError.shouldNotReachHere("Unexpected INVOKEDYNAMIC appendix constant: " + appendixConstant); + } + if (appendix == null) { + throw SemanticJavaException.raise(new IncompatibleClassChangeError("INVOKEDYNAMIC appendix was not included in the image heap")); + } + } + EspressoFrame.putObject(callerFrame, top, appendix); + invokeTop = top + 1; + } else { + throw VMError.shouldNotReachHere("Appendix-less INVOKEDYNAMIC"); + } + } + + InterpreterUnresolvedSignature seedSignature = seedMethod.getSignature(); + int resultAt = invokeTop - seedSignature.slotsForParameters(hasReceiver); + // The stack effect is wrt. the original top-of-the-stack. + int retStackEffect = resultAt - top; + + Object[] calleeArgs = EspressoFrame.popArguments(callerFrame, invokeTop, hasReceiver, seedSignature); + if (!seedMethod.isStatic()) { + nullCheck(calleeArgs[0]); + } + Object retObj = InterpreterToVM.dispatchInvocation(seedMethod, calleeArgs, isVirtual, forceStayInInterpreter, preferStayInInterpreter, opcode == INVOKEINTERFACE); + + retStackEffect += EspressoFrame.putKind(callerFrame, resultAt, retObj, seedSignature.getReturnKind()); + + /* instructions have fixed stack effect encoded */ + return retStackEffect - Bytecodes.stackEffectOf(opcode); + } + + // region Class/Method/Field resolution + + private static JavaType resolveTypeOrUnresolved(InterpreterResolvedJavaMethod method, int opcode, char cpi) { + assert opcode == INSTANCEOF || opcode == CHECKCAST || opcode == NEW || opcode == ANEWARRAY || opcode == MULTIANEWARRAY || opcode == LDC || opcode == LDC_W; + if (GraalDirectives.injectBranchProbability(GraalDirectives.SLOWPATH_PROBABILITY, cpi == 0)) { + throw noClassDefFoundError(opcode, null); + } + return getConstantPool(method).lookupType(cpi, opcode); + } + + private static InterpreterResolvedJavaType resolveType(InterpreterResolvedJavaMethod method, int opcode, char cpi) { + JavaType javaType = resolveTypeOrUnresolved(method, opcode, cpi); + if (GraalDirectives.injectBranchProbability(GraalDirectives.FASTPATH_PROBABILITY, javaType instanceof InterpreterResolvedJavaType)) { + return (InterpreterResolvedJavaType) javaType; + } + throw noClassDefFoundError(opcode, javaType); + } + + private static JavaMethod resolveMethodOrUnresolved(InterpreterResolvedJavaMethod method, int opcode, char cpi) { + assert Bytecodes.isInvoke(opcode); + if (GraalDirectives.injectBranchProbability(GraalDirectives.SLOWPATH_PROBABILITY, cpi == 0)) { + throw noSuchMethodError(opcode, null); + } + return getConstantPool(method).lookupMethod(cpi, opcode); + } + + static InterpreterResolvedJavaMethod resolveMethod(InterpreterResolvedJavaMethod method, int opcode, char cpi) { + JavaMethod javaMethod = resolveMethodOrUnresolved(method, opcode, cpi); + if (GraalDirectives.injectBranchProbability(GraalDirectives.FASTPATH_PROBABILITY, javaMethod instanceof InterpreterResolvedJavaMethod)) { + return (InterpreterResolvedJavaMethod) javaMethod; + } + throw noSuchMethodError(opcode, javaMethod); + } + + private static JavaField resolveFieldOrUnresolved(InterpreterResolvedJavaMethod method, int opcode, char cpi) { + assert opcode == GETFIELD || opcode == GETSTATIC || opcode == PUTFIELD || opcode == PUTSTATIC; + if (GraalDirectives.injectBranchProbability(GraalDirectives.SLOWPATH_PROBABILITY, cpi == 0)) { + throw noSuchFieldError(opcode, null); + } + return getConstantPool(method).lookupField(cpi, method, opcode); + } + + private static InterpreterResolvedJavaField resolveField(InterpreterResolvedJavaMethod method, int opcode, char cpi) { + JavaField javaField = resolveFieldOrUnresolved(method, opcode, cpi); + if (GraalDirectives.injectBranchProbability(GraalDirectives.FASTPATH_PROBABILITY, javaField instanceof InterpreterResolvedJavaField)) { + return (InterpreterResolvedJavaField) javaField; + } + throw noSuchFieldError(opcode, javaField); + } + + // endregion Class/Field/Method resolution + + private static int allocateMultiArray(InterpreterFrame frame, int top, ResolvedJavaType multiArrayType, int allocatedDimensions) { + assert multiArrayType.isArray() : multiArrayType; + assert allocatedDimensions > 0 : allocatedDimensions; + assert multiArrayType.getElementalType().getJavaKind() != JavaKind.Void; + int[] dimensions = new int[allocatedDimensions]; + for (int i = 0; i < allocatedDimensions; ++i) { + dimensions[i] = popInt(frame, top - allocatedDimensions + i); + } + Object value = InterpreterToVM.createMultiArray((InterpreterResolvedJavaType) multiArrayType, dimensions); + putObject(frame, top - allocatedDimensions, value); + return -allocatedDimensions; // Does not include the created (pushed) array. + } + + private static boolean stackIntToBoolean(int result) { + return (result & 1) != 0; + } + + // region Arithmetic/binary operations + + private static int divInt(int divisor, int dividend) { + try { + return dividend / divisor; + } catch (ArithmeticException e) { + throw SemanticJavaException.raise(e); + } + } + + private static long divLong(long divisor, long dividend) { + try { + return dividend / divisor; + } catch (ArithmeticException e) { + throw SemanticJavaException.raise(e); + } + } + + private static float divFloat(float divisor, float dividend) { + return dividend / divisor; + } + + private static double divDouble(double divisor, double dividend) { + return dividend / divisor; + } + + private static int remInt(int divisor, int dividend) { + try { + return dividend % divisor; + } catch (ArithmeticException e) { + throw SemanticJavaException.raise(e); + } + } + + private static long remLong(long divisor, long dividend) { + try { + return dividend % divisor; + } catch (ArithmeticException e) { + throw SemanticJavaException.raise(e); + } + } + + private static float remFloat(float divisor, float dividend) { + return dividend % divisor; + } + + private static double remDouble(double divisor, double dividend) { + return dividend % divisor; + } + + private static int shiftLeftInt(int bits, int value) { + return value << bits; + } + + private static long shiftLeftLong(int bits, long value) { + return value << bits; + } + + private static int shiftRightSignedInt(int bits, int value) { + return value >> bits; + } + + private static long shiftRightSignedLong(int bits, long value) { + return value >> bits; + } + + private static int shiftRightUnsignedInt(int bits, int value) { + return value >>> bits; + } + + private static long shiftRightUnsignedLong(int bits, long value) { + return value >>> bits; + } + + // endregion Arithmetic/binary operations + + // region Comparisons + + private static int compareLong(long y, long x) { + return Long.compare(x, y); + } + + private static int compareFloatGreater(float y, float x) { + return (x < y ? -1 : ((x == y) ? 0 : 1)); + } + + private static int compareFloatLess(float y, float x) { + return (x > y ? 1 : ((x == y) ? 0 : -1)); + } + + private static int compareDoubleGreater(double y, double x) { + return (x < y ? -1 : ((x == y) ? 0 : 1)); + } + + private static int compareDoubleLess(double y, double x) { + return (x > y ? 1 : ((x == y) ? 0 : -1)); + } + + // endregion Comparisons + + // region Field read/write + + /** + * Returns the offset adjustment, depending on how many slots are needed for the value that + * complete the {@link Bytecodes#stackEffectOf(int) stack effect} for the opcode. + * + *
      +     *   top += putField(frame, top, resolveField(...)); break; // stack effect adjust
      +     *   ...
      +     *   top += Bytecodes.stackEffectOf(curOpcode);
      +     *   // at this point `top` must have the correct value.
      +     *   curBCI = bs.next(curBCI);
      +     * 
      + */ + private static int putField(InterpreterFrame frame, int top, InterpreterResolvedJavaField field, int opcode) { + assert opcode == PUTFIELD || opcode == PUTSTATIC; + assert field.isStatic() == (opcode == PUTSTATIC); + assert !field.isUnmaterializedConstant(); + JavaKind kind = field.getJavaKind(); + assert kind != JavaKind.Illegal; + + int slotCount = kind.getSlotCount(); + Object receiver = (opcode == PUTSTATIC) + ? (kind.isPrimitive() ? StaticFieldsSupport.getStaticPrimitiveFields() : StaticFieldsSupport.getStaticObjectFields()) + : nullCheck(popObject(frame, top - slotCount - 1)); + + if (field.isStatic()) { + InterpreterToVM.ensureClassInitialized(field.getDeclaringClass()); + } + + // @formatter:off + switch (kind) { + case Boolean -> InterpreterToVM.setFieldBoolean(stackIntToBoolean(popInt(frame, top - 1)), receiver, field); + case Byte -> InterpreterToVM.setFieldByte((byte) popInt(frame, top - 1), receiver, field); + case Char -> InterpreterToVM.setFieldChar((char) popInt(frame, top - 1), receiver, field); + case Short -> InterpreterToVM.setFieldShort((short) popInt(frame, top - 1), receiver, field); + case Int -> InterpreterToVM.setFieldInt(popInt(frame, top - 1), receiver, field); + case Double -> InterpreterToVM.setFieldDouble(popDouble(frame, top - 1), receiver, field); + case Float -> InterpreterToVM.setFieldFloat(popFloat(frame, top - 1), receiver, field); + case Long -> InterpreterToVM.setFieldLong(popLong(frame, top - 1), receiver, field); + case Object -> InterpreterToVM.setFieldObject(popObject(frame, top - 1), receiver, field); + default -> throw VMError.shouldNotReachHereAtRuntime(); + } + // @formatter:on + return -slotCount + 1; + } + + /** + * Returns the offset adjustment, depending on how many slots are needed for the value that + * complete the {@link Bytecodes#stackEffectOf(int) stack effect} for the opcode. + * + *
      +     *   top += getField(frame, top, resolveField(...)); break; // stack effect adjustment that depends on the field
      +     *   ...
      +     *   top += Bytecodes.stackEffectOf(curOpcode); // minimum stack effect
      +     *   // at this point `top` must have the correct value.
      +     *   curBCI = bs.next(curBCI);
      +     * 
      + */ + private static int getField(InterpreterFrame frame, int top, InterpreterResolvedJavaField field, int opcode) { + assert opcode == GETFIELD || opcode == GETSTATIC; + assert field.isStatic() == (opcode == GETSTATIC); + JavaKind kind = field.getJavaKind(); + assert kind != JavaKind.Illegal; + + Object receiver = opcode == GETSTATIC + ? (kind.isPrimitive() ? StaticFieldsSupport.getStaticPrimitiveFields() : StaticFieldsSupport.getStaticObjectFields()) + : nullCheck(popObject(frame, top - 1)); + + if (field.isStatic()) { + InterpreterToVM.ensureClassInitialized(field.getDeclaringClass()); + } + + int resultAt = field.isStatic() ? top : (top - 1); + + // @formatter:off + switch (kind) { + case Boolean -> putInt(frame, resultAt, InterpreterToVM.getFieldBoolean(receiver, field) ? 1 : 0); + case Byte -> putInt(frame, resultAt, InterpreterToVM.getFieldByte(receiver, field)); + case Char -> putInt(frame, resultAt, InterpreterToVM.getFieldChar(receiver, field)); + case Short -> putInt(frame, resultAt, InterpreterToVM.getFieldShort(receiver, field)); + case Int -> putInt(frame, resultAt, InterpreterToVM.getFieldInt(receiver, field)); + case Double -> putDouble(frame, resultAt, InterpreterToVM.getFieldDouble(receiver, field)); + case Float -> putFloat(frame, resultAt, InterpreterToVM.getFieldFloat(receiver, field)); + case Long -> putLong(frame, resultAt, InterpreterToVM.getFieldLong(receiver, field)); + case Object -> putObject(frame, resultAt, InterpreterToVM.getFieldObject(receiver, field)); + default -> throw VMError.shouldNotReachHereAtRuntime(); + } + // @formatter:on + return kind.getSlotCount() - 1; + } + + // endregion Field read/write + +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterDirectives.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterDirectives.java new file mode 100644 index 000000000000..ca951b3a2dfc --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterDirectives.java @@ -0,0 +1,127 @@ +/* + * 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.interpreter; + +import jdk.vm.ci.meta.ResolvedJavaType; +import org.graalvm.nativeimage.ImageSingletons; + +public final class InterpreterDirectives { + /** + * Build-time marker to force inclusion of methods prefixed with "test" in its name + * for a given class. + * + * Only meant for testing purposes. + */ + public static void markKlass(Class targetKlass) { + ImageSingletons.lookup(InterpreterDirectivesSupport.class).markKlass(targetKlass); + } + + /** + * Tries to divert execution for the specified method Swaps the GOT entry for the specified + * method with an interpreter entry point. This requires for the given method to exist as a + * compiled method. + * + * Only meant for testing purposes. + * + * @param method This is an interpreter method obtained via + * {@link DebuggerSupport#lookupMethod(ResolvedJavaType, String, Class, Class[])}. + * + * @return True if it was possible to divert the execution for a given interpreter method. + */ + public static boolean forceInterpreterExecution(Object method) { + return ImageSingletons.lookup(InterpreterDirectivesSupport.class).forceInterpreterExecution(method); + } + + /** + * Undo operation to above. {@link #forceInterpreterExecution(Object)} must have + * happened before calling {@link #resetInterpreterExecution(Object)}. + * + * Only meant for testing purposes. + * + * @param method This is an interpreter method obtained via + * {@link DebuggerSupport#lookupMethod(ResolvedJavaType, String, Class, Class[])}. + */ + public static void resetInterpreterExecution(Object method) { + ImageSingletons.lookup(InterpreterDirectivesSupport.class).resetInterpreterExecution(method); + } + + /** + * Makes sure that the given method is executed in the interpreter, even if it doesn't have a + * dedicated compiled companion. For example, if the given method is practically inlined by all + * its callers, said callers will also be executed in the interpreter. + * + * @param method This is an interpreter method obtained via + * {@link DebuggerSupport#lookupMethod(ResolvedJavaType, String, Class, Class[])}. + * + * @return A token that can be used to undo the actions done by this operation. + */ + public static Object ensureInterpreterExecution(Object method) { + return ImageSingletons.lookup(InterpreterDirectivesSupport.class).ensureInterpreterExecution(method); + } + + /** + * Undo operation for a given token. + * + * Note: Undo operations must be applied in first-in last-out order. + * + * @param token Obtained by a call to {@link #ensureInterpreterExecution(Object)}, and defines + * which actions should be reverted. + * + * @throws IllegalStateException If an expired token is used. + */ + public static void undoExecutionOperation(Object token) { + ImageSingletons.lookup(InterpreterDirectivesSupport.class).undoExecutionOperation(token); + } + + /** + * Executes the given method in the interpreter with the provided arguments. + * + * Only meant for testing purposes. + */ + public static Object callIntoInterpreter(Object method, Object... args) { + return ImageSingletons.lookup(InterpreterDirectivesSupport.class).callIntoInterpreter(method, args); + } + + /** + * Similar to {@link #callIntoInterpreter(Object, Object...)}, but depending on the run-time + * state it either calls into the AOT compiled version or into the interpreter. + * + * Only meant for testing purposes. + */ + public static Object callIntoUnknown(Object method, Object... args) { + return ImageSingletons.lookup(InterpreterDirectivesSupport.class).callIntoUnknown(method, args); + } + + /** + * Returns true when executed in the interpreter, and false when used in compiled code. + * + * Only meant for testing purposes. + * + * Note: Be careful around its usage and folding done by the compiler. + */ + public static boolean inInterpreter() { + return true; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterDirectivesSupport.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterDirectivesSupport.java new file mode 100644 index 000000000000..ccbc13bcddfd --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterDirectivesSupport.java @@ -0,0 +1,42 @@ +/* + * 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.interpreter; + +public interface InterpreterDirectivesSupport { + + void markKlass(Class targetKlass); + + boolean forceInterpreterExecution(Object method); + + void resetInterpreterExecution(Object method); + + Object ensureInterpreterExecution(Object method); + + void undoExecutionOperation(Object token); + + Object callIntoInterpreter(Object method, Object... args); + + Object callIntoUnknown(Object method, Object... args); +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterDirectivesSupportImpl.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterDirectivesSupportImpl.java new file mode 100644 index 000000000000..17f130f67e27 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterDirectivesSupportImpl.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter; + +import static com.oracle.svm.interpreter.InterpreterUtil.traceInterpreter; +import static com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod.EST_NO_ENTRY; +import static com.oracle.svm.hosted.pltgot.GOTEntryAllocator.GOT_NO_ENTRY; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordBase; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.jdk.InternalVMMethod; +import com.oracle.svm.core.meta.MethodPointer; +import com.oracle.svm.core.pltgot.GOTAccess; +import com.oracle.svm.core.pltgot.GOTHeapSupport; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; +import com.oracle.svm.interpreter.metadata.InterpreterUniverse; + +import jdk.graal.compiler.debug.GraalError; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +@InternalVMMethod +final class InterpreterDirectivesSupportImpl implements InterpreterDirectivesSupport { + final Map rememberCompiledEntry = new HashMap<>(); + + @Override + public boolean forceInterpreterExecution(Object method) { + InterpreterResolvedJavaMethod interpreterMethod = getInterpreterMethod(method); + + if (interpreterMethod == null) { + return false; + } + if (interpreterMethod.getGotOffset() == GOT_NO_ENTRY) { + return false; + } + if (interpreterMethod.getEnterStubOffset() == EST_NO_ENTRY) { + return false; + } + + /* arguments to Log methods might have side-effects */ + if (InterpreterOptions.InterpreterTraceSupport.getValue()) { + traceInterpreter("[forceInterpreterExecution] ").string(interpreterMethod.toString()).newline(); + } + + int estOffset = ConfigurationValues.getTarget().wordSize * interpreterMethod.getEnterStubOffset(); + Pointer estBase = InterpreterStubTable.getBaseForEnterStubTable(); + UnsignedWord estEntry = estBase.add(estOffset).readWord(0); + + WordBase previousEntry = GOTAccess.readFromGotEntry(interpreterMethod.getGotOffset()); + rememberCompiledEntry.put(interpreterMethod, previousEntry.rawValue()); + writeGOTHelper(interpreterMethod, estEntry); + + return true; + } + + private static void writeGOTHelper(InterpreterResolvedJavaMethod interpreterMethod, UnsignedWord estEntry) { + GOTHeapSupport.get().makeGOTWritable(); + GOTAccess.writeToGotEntry(interpreterMethod.getGotOffset(), estEntry); + GOTHeapSupport.get().makeGOTReadOnly(); + } + + @Override + public void resetInterpreterExecution(Object method) { + InterpreterResolvedJavaMethod interpreterMethod = getInterpreterMethod(method); + + if (interpreterMethod == null) { + // Either forceInterpreterExecution was never called or we don't have an interpreter + // method + return; + } + + if (!rememberCompiledEntry.containsKey(interpreterMethod)) { + return; + } + long previousEntry = rememberCompiledEntry.get(interpreterMethod); + writeGOTHelper(interpreterMethod, WordFactory.pointer(previousEntry)); + } + + private class InterpreterOpToken { + List changedExecutionState; + boolean valid = true; + + InterpreterOpToken() { + changedExecutionState = new ArrayList<>(); + } + } + + @Override + public Object ensureInterpreterExecution(Object method) { + InterpreterResolvedJavaMethod interpreterMethod = getInterpreterMethod(method); + InterpreterOpToken token = new InterpreterOpToken(); + + if (interpreterMethod == null) { + return null; + } + + /* arguments to Log methods might have side-effects */ + if (InterpreterOptions.InterpreterTraceSupport.getValue()) { + traceInterpreter("[ensureInterpreterExecution] ").string(interpreterMethod.toString()).newline(); + } + + for (InterpreterResolvedJavaMethod inliner : interpreterMethod.getInlinedBy()) { + if (forceInterpreterExecution(inliner)) { + token.changedExecutionState.add(0, inliner); + } + } + + if (forceInterpreterExecution(interpreterMethod)) { + token.changedExecutionState.add(0, interpreterMethod); + } + return token; + } + + @Override + public void undoExecutionOperation(Object t) { + if (t == null) { + return; + } + InterpreterOpToken token = (InterpreterOpToken) t; + if (!token.valid) { + throw new IllegalStateException("Token is expired."); + } + VMError.guarantee(token.valid); + VMError.guarantee(token.changedExecutionState != null); + for (InterpreterResolvedJavaMethod m : token.changedExecutionState) { + VMError.guarantee(m != null); + resetInterpreterExecution(m); + } + token.valid = false; + } + + @Override + public void markKlass(Class targetKlass) { + GraalError.unimplemented("should be replaced"); + } + + @Override + public Object callIntoInterpreter(Object method, Object... args) { + InterpreterResolvedJavaMethod interpreterMethod = getInterpreterMethod(method); + return Interpreter.execute(interpreterMethod, args, true); + } + + @Override + public Object callIntoUnknown(Object method, Object... args) { + InterpreterResolvedJavaMethod interpreterMethod = getInterpreterMethod(method); + MethodPointer calleeFtnPtr = interpreterMethod.getNativeEntryPoint(); + return InterpreterStubSection.leaveInterpreter(calleeFtnPtr, interpreterMethod, interpreterMethod.getDeclaringClass(), args); + } + + private static String getDescriptor(Class returnType, Class... parameterTypes) { + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (Class param : parameterTypes) { + sb.append(param.descriptorString()); + } + sb.append(")"); + sb.append(returnType.descriptorString()); + return sb.toString(); + } + + private static InterpreterResolvedJavaMethod getInterpreterMethod(Object testMethod) { + VMError.guarantee(testMethod != null); + VMError.guarantee(testMethod instanceof InterpreterResolvedJavaMethod); + return (InterpreterResolvedJavaMethod) testMethod; + } + + static InterpreterResolvedJavaType getInterpreterType(Class clazz) { + VMError.guarantee(clazz != null); + + return findInterpreterResolvedJavaType(clazz); + } + + private static InterpreterResolvedJavaType findInterpreterResolvedJavaType(Class clazz) { + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + return (InterpreterResolvedJavaType) universe.lookupType(clazz); + } + + static InterpreterResolvedJavaMethod getInterpreterMethod(InterpreterResolvedJavaType clazz, String targetName, Class returnType, Class[] parameterTypes) { + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + + Collection allDeclaredMethods = universe.getAllDeclaredMethods(clazz); + + String targetDescriptor = returnType == null ? null : getDescriptor(returnType, parameterTypes); + for (ResolvedJavaMethod m : allDeclaredMethods) { + if (targetName.equals(m.getName()) && (targetDescriptor == null || targetDescriptor.equals(m.getSignature().toMethodDescriptor()))) { + return (InterpreterResolvedJavaMethod) m; + } + } + + return null; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterFeature.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterFeature.java new file mode 100644 index 000000000000..85251cef3364 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterFeature.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter; + +import static jdk.graal.compiler.nodeinfo.NodeCycles.CYCLES_0; +import static jdk.graal.compiler.nodeinfo.NodeSize.SIZE_0; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; + +import org.graalvm.nativeimage.AnnotationAccess; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.function.CEntryPoint; +import org.graalvm.nativeimage.c.function.CFunction; +import org.graalvm.nativeimage.c.function.CFunctionPointer; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.word.Pointer; +import org.graalvm.word.WordBase; + +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.svm.core.ParsingReason; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.interpreter.InterpreterSupport; +import com.oracle.svm.core.meta.MethodPointer; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.graal.code.InterpreterAccessStubData; +import com.oracle.svm.core.graal.aarch64.AArch64InterpreterStubs; +import com.oracle.svm.core.graal.amd64.AMD64InterpreterStubs; +import com.oracle.svm.interpreter.debug.DebuggerEventsFeature; +import com.oracle.svm.graal.hosted.DeoptimizationFeature; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; +import com.oracle.svm.hosted.meta.HostedMethod; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.core.common.type.StampFactory; +import jdk.graal.compiler.graph.NodeClass; +import jdk.graal.compiler.nodeinfo.NodeInfo; +import jdk.graal.compiler.nodes.ConstantNode; +import jdk.graal.compiler.nodes.calc.FloatingNode; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; +import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugin; +import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugins.Registration; +import jdk.graal.compiler.nodes.spi.Lowerable; +import jdk.graal.compiler.nodes.spi.LoweringTool; +import jdk.graal.compiler.phases.util.Providers; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.Local; +import jdk.vm.ci.meta.LocalVariableTable; +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.Signature; + +@Platforms(Platform.HOSTED_ONLY.class) +public class InterpreterFeature implements InternalFeature { + private AnalysisMethod leaveStub; + + static boolean executableByInterpreter(AnalysisMethod m) { + if (AnnotationAccess.getAnnotation(m, CFunction.class) != null) { + return false; + } + if (AnnotationAccess.getAnnotation(m, CEntryPoint.class) != null) { + return false; + } + Uninterruptible uninterruptible = AnnotationAccess.getAnnotation(m, Uninterruptible.class); + if (uninterruptible != null) { + if (uninterruptible.mayBeInlined() && !uninterruptible.callerMustBe()) { + /* + * this method was only annotated with @Uninterruptible so that it can be called + * from uninterruptible code, not because it has to be uninterruptible itself. + */ + } else { + return false; + } + } + + return true; + } + + public static boolean callableByInterpreter(ResolvedJavaMethod m, MetaAccessProvider metaAccess) { + if (AnnotationAccess.getAnnotation(m, Fold.class) != null) { + /* + * GR-55052: For now @Fold methods are considered not callable. The problem is that such + * methods are reachability cut-offs, so we would need to roll our own reachability + * analysis to have all the relevant methods available at run-time. Instead we should + * replace the call-site with the constant according to SVM semantics. + */ + return false; + } + + ResolvedJavaType wordBaseType = metaAccess.lookupJavaType(WordBase.class); + + if (wordBaseType.isAssignableFrom(m.getDeclaringClass())) { + return false; + } + + Signature signature = m.getSignature(); + if (wordBaseType.isAssignableFrom(signature.getReturnType(m.getDeclaringClass()).resolve(m.getDeclaringClass()))) { + return false; + } + + for (int i = 0; i < signature.getParameterCount(false); i++) { + if (wordBaseType.isAssignableFrom(signature.getParameterType(i, null).resolve(m.getDeclaringClass()))) { + return false; + } + } + + return true; + } + + @Override + public List> getRequiredFeatures() { + return Arrays.asList( + DeoptimizationFeature.class, + DebuggerEventsFeature.class); + } + + @Override + public void registerInvocationPlugins(Providers providers, GraphBuilderConfiguration.Plugins plugins, ParsingReason reason) { + Registration r = new Registration(plugins.getInvocationPlugins(), InterpreterDirectives.class); + + r.register(new InvocationPlugin.RequiredInvocationPlugin("inInterpreter") { + @Override + public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver) { + /* Dummy node so that analysis will not be able to "see through it". */ + b.addPush(JavaKind.Boolean, new InInterpreterNode()); + return true; + } + }); + } + + @NodeInfo(cycles = CYCLES_0, size = SIZE_0) + public static final class InInterpreterNode extends FloatingNode implements Lowerable { + public static final NodeClass TYPE = NodeClass.create(InInterpreterNode.class); + + public InInterpreterNode() { + super(TYPE, StampFactory.forKind(JavaKind.Boolean)); + } + + @Override + public void lower(LoweringTool tool) { + replaceAtUsagesAndDelete(graph().unique(ConstantNode.forBoolean(false))); + } + } + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + if (Platform.includedIn(Platform.AARCH64.class)) { + ImageSingletons.add(InterpreterStubSection.class, new AArch64InterpreterStubSection()); + ImageSingletons.add(InterpreterAccessStubData.class, new AArch64InterpreterStubs.AArch64InterpreterAccessStubData()); + } else if (Platform.includedIn(Platform.AMD64.class)) { + ImageSingletons.add(InterpreterStubSection.class, new AMD64InterpreterStubSection()); + ImageSingletons.add(InterpreterAccessStubData.class, new AMD64InterpreterStubs.AMD64InterpreterAccessStubData()); + } else { + VMError.unsupportedFeature("Platform not supported yet"); + } + } + + private static int findLocalSlotByName(String localName, Local[] locals) { + for (Local local : locals) { + if (localName.equals(local.getName())) { + return local.getSlot(); + } + } + throw new NoSuchElementException(localName); + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + FeatureImpl.BeforeAnalysisAccessImpl accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; + + BuildTimeInterpreterUniverse.freshSingletonInstance(); + AnalysisMethod interpreterRoot = accessImpl.getMetaAccess().lookupJavaType(Interpreter.Root.class).getDeclaredMethods(false)[0]; + + accessImpl.registerAsRoot(interpreterRoot, true, "interpreter main loop"); + LocalVariableTable interpreterVariableTable = interpreterRoot.getLocalVariableTable(); + int interpretedMethodSlot = findLocalSlotByName("method", interpreterVariableTable.getLocalsAt(0)); // parameter + int interpreterFrameSlot = findLocalSlotByName("frame", interpreterVariableTable.getLocalsAt(0)); // parameter + // Local variable, search all locals. + int bciSlot = findLocalSlotByName("curBCI", interpreterVariableTable.getLocals()); + + ImageSingletons.add(InterpreterSupport.class, new InterpreterSupportImpl(bciSlot, interpretedMethodSlot, interpreterFrameSlot)); + ImageSingletons.add(InterpreterDirectivesSupport.class, new InterpreterDirectivesSupportImpl()); + ImageSingletons.add(InterpreterMethodPointerHolder.class, new InterpreterMethodPointerHolder()); + + // Locals must be available at runtime to retrieve BCI, interpreted method and interpreter + // frame. + SubstrateCompilationDirectives.singleton().registerFrameInformationRequired(interpreterRoot); + + Method leaveMethod = ReflectionUtil.lookupMethod(InterpreterStubSection.class, "leaveInterpreterStub", CFunctionPointer.class, Pointer.class, long.class, long.class); + leaveStub = accessImpl.getMetaAccess().lookupJavaMethod(leaveMethod); + accessImpl.registerAsRoot(leaveStub, true, "low level entry point"); + } + + @Override + public void beforeCompilation(BeforeCompilationAccess access) { + FeatureImpl.BeforeCompilationAccessImpl accessImpl = (FeatureImpl.BeforeCompilationAccessImpl) access; + + /* required so that it can hold a relocatable pointer */ + accessImpl.registerAsImmutable(InterpreterMethodPointerHolder.singleton()); + accessImpl.registerAsImmutable(InterpreterSupport.singleton()); + } + + @Override + public void afterCompilation(AfterCompilationAccess access) { + FeatureImpl.AfterCompilationAccessImpl accessImpl = (FeatureImpl.AfterCompilationAccessImpl) access; + + HostedMethod hLeaveStub = accessImpl.getUniverse().lookup(leaveStub); + int leaveStubLength = accessImpl.getCompilations().get(hLeaveStub).result.getTargetCodeSize(); + + InterpreterSupport.setLeaveStubPointer(new MethodPointer(hLeaveStub), leaveStubLength); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterFrame.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterFrame.java new file mode 100644 index 000000000000..37af8235dd29 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterFrame.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter; + +import java.util.Arrays; + +import com.oracle.svm.core.monitor.MonitorSupport; +import com.oracle.svm.core.util.VMError; + +public final class InterpreterFrame { + private final long[] primitives; + private final Object[] references; + + private final Object[] arguments; + private Object[] locks; + private int lockCount; + + private static final Object[] EMPTY = new Object[0]; + + private InterpreterFrame(int slotCount, Object[] arguments) { + this.primitives = new long[slotCount]; + this.references = new Object[slotCount]; + this.arguments = arguments; + this.lockCount = 0; + this.locks = EMPTY; + } + + static InterpreterFrame create(int slotCount, Object... arguments) { + return new InterpreterFrame(slotCount, arguments); + } + + Object[] getArguments() { + return arguments; + } + + int getIntStatic(int slot) { + return (int) primitives[slot]; + } + + Object getObjectStatic(int slot) { + return references[slot]; + } + + float getFloatStatic(int slot) { + return Float.intBitsToFloat((int) primitives[slot]); + } + + long getLongStatic(int slot) { + return primitives[slot]; + } + + double getDoubleStatic(int slot) { + return Double.longBitsToDouble(primitives[slot]); + } + + void setObjectStatic(int slot, Object value) { + references[slot] = value; + } + + void setIntStatic(int slot, int value) { + primitives[slot] = value; + } + + void setFloatStatic(int slot, float value) { + primitives[slot] = Float.floatToRawIntBits(value); + } + + void setLongStatic(int slot, long value) { + primitives[slot] = value; + } + + void setDoubleStatic(int slot, double value) { + primitives[slot] = Double.doubleToRawLongBits(value); + } + + void clearObjectStatic(int slot) { + references[slot] = null; + } + + void clearPrimitiveStatic(int slot) { + primitives[slot] = 0; + } + + void clearStatic(int slot) { + clearObjectStatic(slot); + clearPrimitiveStatic(slot); + } + + void swapStatic(int src, int dst) { + long tmp = primitives[src]; + primitives[src] = primitives[dst]; + primitives[dst] = tmp; + + Object otmp = references[src]; + references[src] = references[dst]; + references[dst] = otmp; + } + + void copyStatic(int src, int dst) { + primitives[dst] = primitives[src]; + references[dst] = references[src]; + } + + private void ensureLocksCapacity(int capacity) { + int oldLength = locks.length; + Object[] newLocks = Arrays.copyOf(locks, Math.max(capacity, (oldLength * 2) + 1)); + this.locks = newLocks; + } + + void addLock(Object ref) { + assert ref != null; + assert MonitorSupport.singleton().isLockedByCurrentThread(ref); + if (lockCount >= 0) { + // Fast path, balanced locks. + if (lockCount >= locks.length) { + ensureLocksCapacity(lockCount + 1); + } + locks[lockCount++] = ref; + } else { + // Unbalanced locks, linear scan. + for (int i = 0; i < locks.length; ++i) { + if (locks[i] == null) { + locks[i] = ref; + return; + } + } + // No free slot found. + int oldLockCount = locks.length; + ensureLocksCapacity(oldLockCount + 1); + assert locks[oldLockCount] == null; + locks[oldLockCount] = ref; + } + } + + void removeLock(Object ref) { + assert ref != null; + if (lockCount > 0 && locks[lockCount - 1] == ref) { + // Fast path, balanced locks. + locks[--lockCount] = null; + } else { + lockCount = -1; + // Unbalanced locks, linear scan. + for (int i = 0; i < locks.length; ++i) { + if (locks[i] == ref) { + locks[i] = null; + return; + } + } + throw VMError.shouldNotReachHere("lock not found in interpreter frame"); + } + } + + Object[] getLocks() { + return locks; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterMethodPointerHolder.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterMethodPointerHolder.java new file mode 100644 index 000000000000..3ea10df4ef1e --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterMethodPointerHolder.java @@ -0,0 +1,58 @@ +/* + * 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.interpreter; + +import com.oracle.svm.core.BuildPhaseProvider; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.heap.UnknownPrimitiveField; + +import jdk.graal.compiler.api.replacements.Fold; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.function.CFunctionPointer; + +public class InterpreterMethodPointerHolder { + @Fold + public static InterpreterMethodPointerHolder singleton() { + return ImageSingletons.lookup(InterpreterMethodPointerHolder.class); + } + + @UnknownPrimitiveField(availability = BuildPhaseProvider.AfterCompilation.class) private CFunctionPointer methodNotCompiledFtnPtr; + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static CFunctionPointer getMethodNotCompiledHandler() { + CFunctionPointer ptr = singleton().methodNotCompiledFtnPtr; + assert ptr.rawValue() != 0; + return ptr; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static void setMethodNotCompiledHandler(CFunctionPointer ftnptr) { + assert singleton().methodNotCompiledFtnPtr == null; + singleton().methodNotCompiledFtnPtr = ftnptr; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterOptions.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterOptions.java new file mode 100644 index 000000000000..6fb516d7f80f --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterOptions.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter; + +import org.graalvm.collections.EconomicMap; + +import com.oracle.svm.core.hub.RuntimeClassLoading; +import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.option.RuntimeOptionKey; +import com.oracle.svm.hosted.pltgot.PLTGOTOptions; + +import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.options.OptionKey; +import jdk.graal.compiler.options.OptionType; + +public class InterpreterOptions { + @Option(help = "Adds support to divert execution from AOT compiled methods to the interpreter at run-time.", type = OptionType.Expert) // + public static final HostedOptionKey DebuggerWithInterpreter = new HostedOptionKey<>(false) { + @Override + protected void onValueUpdate(EconomicMap, Object> values, Boolean oldValue, Boolean newValue) { + super.onValueUpdate(values, oldValue, newValue); + if (newValue) { + PLTGOTOptions.EnablePLTGOT.update(values, true); + } + } + }; + + @Option(help = "Enables logging at build time related to the interpreter setup", type = OptionType.Expert)// + public static final HostedOptionKey InterpreterBuildTimeLogging = new HostedOptionKey<>(false); + + @Option(help = "Path to dump interpreter universe metadata as .class files", type = OptionType.Expert)// + public static final HostedOptionKey InterpreterDumpClassFiles = new HostedOptionKey<>(""); + + // GR-54939: Switch default to false, as this has roughly a 2x perf impact on interpreter + // performance. + @Option(help = "Include interpreter tracing code in image") public static final HostedOptionKey InterpreterTraceSupport = new HostedOptionKey<>(true); + + @Option(help = "Trace Interpreter execution")// + public static final RuntimeOptionKey InterpreterTrace = new RuntimeOptionKey<>(false); + + public static boolean interpreterEnabled() { + return DebuggerWithInterpreter.getValue() || RuntimeClassLoading.isSupported(); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterStubSection.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterStubSection.java new file mode 100644 index 000000000000..b64e99e037e3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterStubSection.java @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.interpreter; + +import com.oracle.objectfile.BasicProgbitsSectionImpl; +import com.oracle.objectfile.ObjectFile; +import com.oracle.objectfile.SectionName; +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.SubstrateTargetDescription; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.deopt.Deoptimizer; +import com.oracle.svm.core.graal.meta.KnownOffsets; +import com.oracle.svm.core.graal.code.InterpreterAccessStubData; +import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind; +import com.oracle.svm.core.graal.code.SubstrateCallingConventionType; +import com.oracle.svm.core.jdk.InternalVMMethod; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.image.AbstractImage; +import com.oracle.svm.hosted.image.NativeImage; +import com.oracle.svm.hosted.image.RelocatableBuffer; +import com.oracle.svm.hosted.meta.HostedMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterUnresolvedSignature; +import jdk.graal.compiler.core.common.LIRKind; +import jdk.graal.compiler.core.common.NumUtil; +import jdk.graal.compiler.word.Word; +import jdk.vm.ci.code.CallingConvention; +import jdk.vm.ci.code.RegisterConfig; +import jdk.vm.ci.code.ValueKindFactory; +import jdk.vm.ci.meta.AllocatableValue; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.function.CFunctionPointer; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; +import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import static com.oracle.svm.interpreter.EspressoFrame.setLocalDouble; +import static com.oracle.svm.interpreter.EspressoFrame.setLocalFloat; +import static com.oracle.svm.interpreter.EspressoFrame.setLocalInt; +import static com.oracle.svm.interpreter.EspressoFrame.setLocalLong; +import static com.oracle.svm.interpreter.EspressoFrame.setLocalObject; + +@InternalVMMethod +public abstract class InterpreterStubSection { + public static final SectionName SVM_INTERP = new SectionName.ProgbitsSectionName("svm_interp"); + + protected RegisterConfig registerConfig; + protected SubstrateTargetDescription target; + protected ValueKindFactory valueKindFactory; + + private ObjectFile.ProgbitsSectionImpl stubsBufferImpl; + + private final Map enterTrampolineOffsets = new HashMap<>(); + + public void createInterpreterEnterStubSection(AbstractImage image, Collection methods) { + ObjectFile objectFile = image.getObjectFile(); + byte[] stubsBlob = generateEnterStubs(methods); + + RelocatableBuffer stubsBuffer = new RelocatableBuffer(stubsBlob.length, objectFile.getByteOrder()); + stubsBufferImpl = new BasicProgbitsSectionImpl(stubsBuffer.getBackingArray()); + ObjectFile.Section stubsSection = objectFile.newProgbitsSection(SVM_INTERP.getFormatDependentName(objectFile.getFormat()), objectFile.getPageSize(), false, true, stubsBufferImpl); + + stubsBuffer.getByteBuffer().put(stubsBlob, 0, stubsBlob.length); + + boolean internalSymbolsAreGlobal = SubstrateOptions.InternalSymbolsAreGlobal.getValue(); + objectFile.createDefinedSymbol("interp_enter_trampoline", stubsSection, 0, 0, true, internalSymbolsAreGlobal); + + for (InterpreterResolvedJavaMethod method : enterTrampolineOffsets.keySet()) { + int offset = enterTrampolineOffsets.get(method); + objectFile.createDefinedSymbol(nameForInterpMethod(method), stubsSection, offset, ConfigurationValues.getTarget().wordSize, true, internalSymbolsAreGlobal); + } + } + + public static String nameForInterpMethod(InterpreterResolvedJavaMethod method) { + return "interp_enter_" + NativeImage.localSymbolNameForMethod(method); + } + + public void createInterpreterVtableEnterStubSection(AbstractImage image, int maxVtableIndex) { + ObjectFile objectFile = image.getObjectFile(); + byte[] stubsBlob = generateVtableEnterStubs(maxVtableIndex); + + RelocatableBuffer stubsBuffer = new RelocatableBuffer(stubsBlob.length, objectFile.getByteOrder()); + stubsBufferImpl = new BasicProgbitsSectionImpl(stubsBuffer.getBackingArray()); + + // TODO: if the section should be re-used, we need to respect the offsets into this section. + // or just a new dedicated section? + ObjectFile.Section stubsSection = objectFile.newProgbitsSection(SVM_INTERP.getFormatDependentName(objectFile.getFormat()), objectFile.getPageSize(), false, true, stubsBufferImpl); + + stubsBuffer.getByteBuffer().put(stubsBlob, 0, stubsBlob.length); + + boolean internalSymbolsAreGlobal = SubstrateOptions.InternalSymbolsAreGlobal.getValue(); + objectFile.createDefinedSymbol("crema_enter_trampoline", stubsSection, 0, 0, true, internalSymbolsAreGlobal); + + int vtableEntrySize = KnownOffsets.singleton().getVTableEntrySize(); + for (int index = 0; index < maxVtableIndex; index++) { + int offset = index * vtableEntrySize; + objectFile.createDefinedSymbol(nameForVtableOffset(offset), stubsSection, offset, ConfigurationValues.getTarget().wordSize, true, internalSymbolsAreGlobal); + } + } + + public static String nameForVtableOffset(int offset) { + return "crema_enter_" + String.format("%04x", offset); + } + + protected void recordEnterTrampoline(InterpreterResolvedJavaMethod m, int position) { + enterTrampolineOffsets.put(m, position); + } + + public abstract int getVTableStubSize(); + + protected abstract byte[] generateEnterStubs(Collection methods); + + protected abstract byte[] generateVtableEnterStubs(int maxVtableIndex); + + public void markEnterStubPatch(HostedMethod enterStub) { + markEnterStubPatch(stubsBufferImpl, enterStub); + } + + protected abstract void markEnterStubPatch(ObjectFile.ProgbitsSectionImpl pltBuffer, ResolvedJavaMethod enterStub); + + @Deoptimizer.DeoptStub(stubType = Deoptimizer.StubType.InterpreterEnterStub) + @NeverInline("needs ABI boundary") + public static Pointer enterInterpreterStub(int interpreterMethodESTOffset, Pointer enterData) { + InterpreterAccessStubData accessHelper = ImageSingletons.lookup(InterpreterAccessStubData.class); + InterpreterStubSection stubSection = ImageSingletons.lookup(InterpreterStubSection.class); + DebuggerSupport interpreterSupport = ImageSingletons.lookup(DebuggerSupport.class); + + InterpreterResolvedJavaMethod interpreterMethod = (InterpreterResolvedJavaMethod) interpreterSupport.getUniverse().getMethodForESTOffset(interpreterMethodESTOffset); + VMError.guarantee(interpreterMethod != null); + + InterpreterUnresolvedSignature signature = interpreterMethod.getSignature(); + VMError.guarantee(signature != null); + + int count = signature.getParameterCount(false); + + ResolvedJavaType accessingClass = interpreterMethod.getDeclaringClass(); + + JavaType thisType = interpreterMethod.hasReceiver() ? accessingClass : null; + JavaType returnType = signature.getReturnType(accessingClass); + + var kind = SubstrateCallingConventionKind.Java.toType(true); + CallingConvention callingConvention = stubSection.registerConfig.getCallingConvention(kind, returnType, signature.toParameterTypes(thisType), stubSection.valueKindFactory); + + InterpreterFrame frame = EspressoFrame.allocate(interpreterMethod.getMaxLocals(), interpreterMethod.getMaxStackSize(), new Object[0]); + + int interpSlot = 0; + int gpIdx = 0; + int fpIdx = 0; + if (interpreterMethod.hasReceiver()) { + Object receiver = ((Pointer) WordFactory.pointer(accessHelper.getGpArgumentAt(callingConvention.getArgument(gpIdx), enterData, gpIdx))).toObject(); + setLocalObject(frame, 0, receiver); + gpIdx++; + interpSlot++; + } + + for (int i = 0; i < count; i++) { + JavaKind argKind = signature.getParameterKind(i); + long arg; + AllocatableValue ccArg = callingConvention.getArgument(gpIdx + fpIdx); + switch (argKind) { + case Float: + case Double: + arg = accessHelper.getFpArgumentAt(ccArg, enterData, fpIdx); + fpIdx++; + break; + default: + arg = accessHelper.getGpArgumentAt(ccArg, enterData, gpIdx); + gpIdx++; + break; + } + + switch (argKind) { + // @formatter:off + case Boolean: setLocalInt(frame, interpSlot, (arg & 0xff) != 0 ? 1 : 0); break; + case Byte: setLocalInt(frame, interpSlot, (byte) arg); break; + case Short: setLocalInt(frame, interpSlot, (short) arg); break; + case Char: setLocalInt(frame, interpSlot, (char) arg); break; + case Int: setLocalInt(frame, interpSlot, (int) arg); break; + case Long: setLocalLong(frame, interpSlot, arg); interpSlot++; break; + case Float: setLocalFloat(frame, interpSlot, Float.intBitsToFloat((int) arg)); break; + case Double: setLocalDouble(frame, interpSlot, Double.longBitsToDouble(arg)); interpSlot++; break; + case Object: setLocalObject(frame, interpSlot, ((Pointer) WordFactory.pointer(arg)).toObject()); break; + // @formatter:on + default: + throw VMError.shouldNotReachHereAtRuntime(); + } + interpSlot++; + } + + Object retVal = Interpreter.execute(interpreterMethod, frame); + + switch (returnType.getJavaKind()) { + case Boolean: + assert retVal instanceof Boolean; + accessHelper.setGpReturn(enterData, ((Boolean) retVal) ? 1 : 0); + break; + case Byte: + assert retVal instanceof Byte; + accessHelper.setGpReturn(enterData, ((Byte) retVal).longValue()); + break; + case Short: + assert retVal instanceof Short; + accessHelper.setGpReturn(enterData, ((Short) retVal).longValue()); + break; + case Char: + assert retVal instanceof Character; + accessHelper.setGpReturn(enterData, ((Character) retVal).charValue()); + break; + case Int: + assert retVal instanceof Integer; + accessHelper.setGpReturn(enterData, ((Integer) retVal).longValue()); + break; + case Long: + assert retVal instanceof Long; + accessHelper.setGpReturn(enterData, (Long) retVal); + break; + case Float: + assert retVal instanceof Float; + accessHelper.setFpReturn(enterData, Float.floatToRawIntBits((float) retVal)); + break; + case Double: + assert retVal instanceof Double; + accessHelper.setFpReturn(enterData, Double.doubleToRawLongBits((double) retVal)); + break; + case Object: + accessHelper.setGpReturn(enterData, Word.objectToTrackedPointer(retVal).rawValue()); + break; + case Void: + break; + default: + throw VMError.shouldNotReachHereAtRuntime(); + } + return enterData; + } + + /* + * reserve four slots for: 1. base address of outgoing stack args, 2. variable stack size, 3. + * gcReferenceMap, 4. stack padding to match alignment + */ + @Deoptimizer.DeoptStub(stubType = Deoptimizer.StubType.InterpreterLeaveStub) + @NeverInline("needs ABI boundary") + @SuppressWarnings("unused") + public static Pointer leaveInterpreterStub(CFunctionPointer entryPoint, Pointer leaveData, long stackSize, long gcReferenceMap) { + return (Pointer) entryPoint; + } + + public static Object leaveInterpreter(CFunctionPointer compiledEntryPoint, InterpreterResolvedJavaMethod seedMethod, ResolvedJavaType accessingClass, Object[] args) { + InterpreterUnresolvedSignature targetSignature = seedMethod.getSignature(); + InterpreterStubSection stubSection = ImageSingletons.lookup(InterpreterStubSection.class); + + JavaType thisType = seedMethod.hasReceiver() ? seedMethod.getDeclaringClass() : null; + SubstrateCallingConventionType kind = SubstrateCallingConventionKind.Java.toType(true); + JavaType returnType = targetSignature.getReturnType(accessingClass); + + CallingConvention callingConvention = stubSection.registerConfig.getCallingConvention(kind, returnType, targetSignature.toParameterTypes(thisType), stubSection.valueKindFactory); + + InterpreterAccessStubData accessHelper = ImageSingletons.lookup(InterpreterAccessStubData.class); + Pointer leaveData = StackValue.get(1, accessHelper.allocateStubDataSize()); + + /* GR-54726: Reference map is currently limited to 64 arguments */ + long gcReferenceMap = 0; + int gpIdx = 0; + int fpIdx = 0; + if (seedMethod.hasReceiver()) { + gcReferenceMap |= accessHelper.setGpArgumentAt(callingConvention.getArgument(gpIdx), leaveData, gpIdx, Word.objectToTrackedPointer(args[0]).rawValue()); + gpIdx++; + } + + int stackSize = NumUtil.roundUp(callingConvention.getStackSize(), stubSection.target.stackAlignment); + + Pointer stackBuffer = WordFactory.nullPointer(); + if (stackSize > 0) { + stackBuffer = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(WordFactory.unsigned(stackSize)); + accessHelper.setSp(leaveData, stackSize, stackBuffer); + } + + int argCount = targetSignature.getParameterCount(false); + for (int i = 0; i < argCount; i++) { + Object arg = args[i + (seedMethod.hasReceiver() ? 1 : 0)]; + + AllocatableValue ccArg = callingConvention.getArgument(gpIdx + fpIdx); + JavaType type = targetSignature.getParameterType(i, accessingClass); + switch (type.getJavaKind()) { + case Boolean: + accessHelper.setGpArgumentAt(ccArg, leaveData, gpIdx, (boolean) arg ? 1 : 0); + gpIdx++; + break; + case Byte: + accessHelper.setGpArgumentAt(ccArg, leaveData, gpIdx, (byte) arg); + gpIdx++; + break; + case Short: + accessHelper.setGpArgumentAt(ccArg, leaveData, gpIdx, (short) arg); + gpIdx++; + break; + case Char: + accessHelper.setGpArgumentAt(ccArg, leaveData, gpIdx, (char) arg); + gpIdx++; + break; + case Int: + accessHelper.setGpArgumentAt(ccArg, leaveData, gpIdx, (int) arg); + gpIdx++; + break; + case Long: + accessHelper.setGpArgumentAt(ccArg, leaveData, gpIdx, (long) arg); + gpIdx++; + break; + case Object: + gcReferenceMap |= accessHelper.setGpArgumentAt(ccArg, leaveData, gpIdx, Word.objectToTrackedPointer(arg).rawValue()); + gpIdx++; + break; + + case Float: + accessHelper.setFpArgumentAt(ccArg, leaveData, fpIdx, Float.floatToRawIntBits((float) arg)); + fpIdx++; + break; + case Double: + accessHelper.setFpArgumentAt(ccArg, leaveData, fpIdx, Double.doubleToRawLongBits((double) arg)); + fpIdx++; + break; + + default: + throw VMError.shouldNotReachHereAtRuntime(); + } + } + + VMError.guarantee(compiledEntryPoint.isNonNull()); + + try { + // GR-55022: Stack overflow check should be done here + leaveInterpreterStub(compiledEntryPoint, leaveData, stackSize, gcReferenceMap); + } catch (Throwable e) { + // native code threw exception, wrap it + throw SemanticJavaException.raise(e); + } finally { + if (stackSize > 0) { + VMError.guarantee(stackBuffer.isNonNull()); + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(stackBuffer); + } + } + + // @formatter:off + return switch (returnType.getJavaKind()) { + case Boolean -> (accessHelper.getGpReturn(leaveData) & 0xff) != 0; + case Byte -> (byte) accessHelper.getGpReturn(leaveData); + case Short -> (short) accessHelper.getGpReturn(leaveData); + case Char -> (char) accessHelper.getGpReturn(leaveData); + case Int -> (int) accessHelper.getGpReturn(leaveData); + case Long -> accessHelper.getGpReturn(leaveData); + case Float -> Float.intBitsToFloat((int) accessHelper.getFpReturn(leaveData)); + case Double -> Double.longBitsToDouble(accessHelper.getFpReturn(leaveData)); + case Object -> ((Pointer) WordFactory.pointer(accessHelper.getGpReturn(leaveData))).toObject(); + case Void -> null; + default -> throw VMError.shouldNotReachHereAtRuntime(); + }; + // @formatter:on + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterStubTable.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterStubTable.java new file mode 100644 index 000000000000..bd70e0f95f63 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterStubTable.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter; + +import com.oracle.objectfile.BasicProgbitsSectionImpl; +import com.oracle.objectfile.ObjectFile; +import com.oracle.objectfile.SectionName; +import com.oracle.svm.core.SubstrateOptions; +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.util.VMError; +import com.oracle.svm.hosted.image.AbstractImage; +import com.oracle.svm.hosted.image.RelocatableBuffer; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import org.graalvm.word.Pointer; + +import java.nio.ByteBuffer; +import java.util.Collection; + +public class InterpreterStubTable { + final SectionName section; + private RelocatableBuffer tableBuffer; + private ObjectFile.ProgbitsSectionImpl tableBufferImpl; + private int offsetHash; + + public InterpreterStubTable() { + String sectionName = "s_intrp_ent"; + VMError.guarantee(sectionName.length() <= 16, "mach-O limitation"); + this.section = new SectionName.ProgbitsSectionName(sectionName); + } + + protected void installAdditionalInfoIntoImageObjectFile(AbstractImage image, Collection methods) { + ObjectFile objectFile = image.getObjectFile(); + + int wordSize = ConfigurationValues.getTarget().wordSize; + int hashSize = 4 /* size */ + 6 /* "crc32:" */ + 8 /* actual hash */; + assert hashSize == 18; + int size = methods.size() * wordSize + hashSize; + offsetHash = size - hashSize; + + tableBuffer = new RelocatableBuffer(size, objectFile.getByteOrder()); + tableBufferImpl = new BasicProgbitsSectionImpl(tableBuffer.getBackingArray()); + ObjectFile.Section tableSection = objectFile.newProgbitsSection(section.getFormatDependentName(objectFile.getFormat()), objectFile.getPageSize(), true, false, tableBufferImpl); + + objectFile.createDefinedSymbol(SYMBOL_NAME, tableSection, 0, 0, false, SubstrateOptions.InternalSymbolsAreGlobal.getValue()); + + // Store an additional blob of bytes to verify the interpreter metadata integrity. + objectFile.createDefinedSymbol(DebuggerSupport.IMAGE_INTERP_HASH_SYMBOL_NAME, tableSection, offsetHash, 0, true, true); + + ObjectFile.RelocationKind relocationKind = ObjectFile.RelocationKind.getDirect(wordSize); + for (InterpreterResolvedJavaMethod method : methods) { + int offset = wordSize * method.getEnterStubOffset(); + assert offset < size; + tableBufferImpl.markRelocationSite(offset, relocationKind, InterpreterStubSection.nameForInterpMethod(method), 0L); + } + } + + protected void writeMetadataHashString(byte[] metadataHash) { + assert metadataHash.length == (6 + 8) : metadataHash.length; + ByteBuffer bb = tableBuffer.getByteBuffer(); + bb.position(offsetHash); + bb.putInt(metadataHash.length); + bb.put(metadataHash); + } + + private static final String SYMBOL_NAME = "__svm_interp_enter_table"; + + private static final CGlobalData BASE = CGlobalDataFactory. forSymbol(SYMBOL_NAME); + + public static Pointer getBaseForEnterStubTable() { + return BASE.get(); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterSupportImpl.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterSupportImpl.java new file mode 100644 index 000000000000..6eeee427e546 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterSupportImpl.java @@ -0,0 +1,117 @@ +/* + * 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.interpreter; + +import com.oracle.svm.core.interpreter.InterpreterFrameSourceInfo; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.word.Pointer; +import org.graalvm.word.SignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.code.FrameInfoQueryResult; +import com.oracle.svm.core.code.FrameSourceInfo; +import com.oracle.svm.core.heap.ReferenceAccess; +import com.oracle.svm.core.interpreter.InterpreterSupport; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; + +import jdk.graal.compiler.word.Word; +import jdk.vm.ci.meta.LineNumberTable; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +public final class InterpreterSupportImpl extends InterpreterSupport { + private final int bciSlot; + private final int interpretedMethodSlot; + private final int interpretedFrameSlot; + + InterpreterSupportImpl(int bciSlot, int interpretedMethodSlot, int interpretedFrameSlot) { + this.bciSlot = bciSlot; + this.interpretedMethodSlot = interpretedMethodSlot; + this.interpretedFrameSlot = interpretedFrameSlot; + } + + @Override + public boolean isInterpreterRoot(Class clazz) { + return Interpreter.Root.class.equals(clazz); + } + + private static int readInt(Pointer addr, SignedWord offset) { + return addr.readInt(offset); + } + + @SuppressWarnings("unchecked") + private static T readObject(Pointer addr, SignedWord offset, boolean compressed) { + Word p = ((Word) addr).add(offset); + Object obj = ReferenceAccess.singleton().readObjectAt(p, compressed); + return (T) obj; + } + + private InterpreterResolvedJavaMethod readInterpretedMethod(FrameInfoQueryResult frameInfo, Pointer sp) { + FrameInfoQueryResult.ValueInfo valueInfo = frameInfo.getValueInfos()[interpretedMethodSlot]; + return readObject(sp, WordFactory.signed(valueInfo.getData()), valueInfo.isCompressedReference()); + } + + private int readBCI(FrameInfoQueryResult frameInfo, Pointer sp) { + FrameInfoQueryResult.ValueInfo valueInfo = frameInfo.getValueInfos()[bciSlot]; + return readInt(sp, WordFactory.signed(valueInfo.getData())); + } + + private InterpreterFrame readInterpreterFrame(FrameInfoQueryResult frameInfo, Pointer sp) { + FrameInfoQueryResult.ValueInfo valueInfo = frameInfo.getValueInfos()[interpretedFrameSlot]; + return readObject(sp, WordFactory.signed(valueInfo.getData()), valueInfo.isCompressedReference()); + } + + @Override + public FrameSourceInfo getInterpretedMethodFrameInfo(FrameInfoQueryResult frameInfo, Pointer sp) { + if (!isInterpreterRoot(frameInfo.getSourceClass())) { + throw VMError.shouldNotReachHereAtRuntime(); + } + InterpreterResolvedJavaMethod interpretedMethod = readInterpretedMethod(frameInfo, sp); + int bci = readBCI(frameInfo, sp); + InterpreterFrame interpreterFrame = readInterpreterFrame(frameInfo, sp); + Class interpretedClass = ((InterpreterResolvedJavaType) interpretedMethod.getDeclaringClass()).getJavaClass(); + String sourceMethodName = interpretedMethod.getName(); + LineNumberTable lineNumberTable = interpretedMethod.getLineNumberTable(); + + int sourceLineNumber = -1; // unknown + if (lineNumberTable != null) { + sourceLineNumber = lineNumberTable.getLineNumber(bci); + } + return new InterpreterFrameSourceInfo(interpretedClass, sourceMethodName, sourceLineNumber, bci, interpretedMethod, interpreterFrame); + } + + @Platforms(Platform.HOSTED_ONLY.class) + @Override + public void buildMethodIdMapping(ResolvedJavaMethod[] encodedMethods) { + if (InterpreterOptions.DebuggerWithInterpreter.getValue()) { + assert ImageSingletons.contains(DebuggerSupport.class); + ImageSingletons.lookup(DebuggerSupport.class).buildMethodIdMapping(encodedMethods); + } + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterToVM.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterToVM.java new file mode 100644 index 000000000000..65ae4e36c7d7 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterToVM.java @@ -0,0 +1,906 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.interpreter; + +import static com.oracle.svm.interpreter.InterpreterOptions.InterpreterTraceSupport; +import static com.oracle.svm.interpreter.InterpreterOptions.DebuggerWithInterpreter; +import static com.oracle.svm.interpreter.InterpreterUtil.traceInterpreter; +import static com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod.VTBL_NO_ENTRY; +import static com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod.VTBL_ONE_IMPL; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; + +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.graal.snippets.OpenTypeWorldDispatchTableSnippets; +import com.oracle.svm.core.hub.RuntimeClassLoading; +import com.oracle.svm.interpreter.metadata.ReferenceConstant; +import jdk.vm.ci.meta.PrimitiveConstant; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.MissingReflectionRegistrationError; +import org.graalvm.nativeimage.c.function.CFunctionPointer; +import org.graalvm.word.WordBase; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.graal.meta.KnownOffsets; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.jdk.InternalVMMethod; +import com.oracle.svm.core.monitor.MonitorInflationCause; +import com.oracle.svm.core.monitor.MonitorSupport; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType; + +import jdk.graal.compiler.api.directives.GraalDirectives; +import jdk.graal.compiler.core.common.SuppressFBWarnings; +import jdk.graal.compiler.nodes.java.ArrayLengthNode; +import jdk.graal.compiler.word.Word; +import jdk.internal.misc.Unsafe; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaType; + +@InternalVMMethod +public final class InterpreterToVM { + + private static final JavaKind WORD_KIND = ConfigurationValues.getTarget().wordJavaKind; + + static { + VMError.guarantee(WORD_KIND == JavaKind.Int || WORD_KIND == JavaKind.Long); + } + + public static JavaKind wordJavaKind() { + return WORD_KIND; + } + + private InterpreterToVM() { + throw VMError.shouldNotReachHereAtRuntime(); + } + + // region Get (array) operations + + public static int getArrayInt(int index, int[] array) throws SemanticJavaException { + assert array != null; + try { + return array[index]; + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static Object getArrayObject(int index, Object[] array) throws SemanticJavaException { + assert array != null; + try { + return array[index]; + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static long getArrayLong(int index, long[] array) throws SemanticJavaException { + assert array != null; + try { + return array[index]; + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static WordBase getArrayWord(int index, WordBase[] wordArray) throws SemanticJavaException { + assert wordArray != null; + try { + return wordArray[index]; + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static float getArrayFloat(int index, float[] array) throws SemanticJavaException { + assert array != null; + try { + return array[index]; + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static double getArrayDouble(int index, double[] array) throws SemanticJavaException { + assert array != null; + try { + return array[index]; + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static byte getArrayByte(int index, Object array) throws SemanticJavaException { + assert array != null; + try { + if (array instanceof byte[]) { + return ((byte[]) array)[index]; + } else { + return ((boolean[]) array)[index] ? (byte) 1 : 0; + } + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static char getArrayChar(int index, char[] array) throws SemanticJavaException { + assert array != null; + try { + return array[index]; + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static short getArrayShort(int index, short[] array) throws SemanticJavaException { + assert array != null; + try { + return array[index]; + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + // endregion + + // region Set (array) operations + + public static void setArrayInt(int value, int index, int[] array) throws SemanticJavaException { + assert array != null; + try { + array[index] = value; + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static void setArrayLong(long value, int index, long[] array) throws SemanticJavaException { + assert array != null; + try { + array[index] = value; + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static void setArrayWord(WordBase value, int index, WordBase[] array) throws SemanticJavaException { + assert array != null; + try { + array[index] = value; + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static void setArrayFloat(float value, int index, float[] array) throws SemanticJavaException { + assert array != null; + try { + array[index] = value; + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static void setArrayDouble(double value, int index, double[] array) throws SemanticJavaException { + assert array != null; + try { + array[index] = value; + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static void setArrayByte(byte value, int index, /* byte[].class or boolean[].class */ Object array) throws SemanticJavaException { + assert array != null; + try { + if (array instanceof byte[]) { + ((byte[]) array)[index] = value; + } else { + ((boolean[]) array)[index] = (value & 1) != 0; // masked from Java 9+. + } + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static void setArrayChar(char value, int index, char[] array) throws SemanticJavaException { + assert array != null; + try { + array[index] = value; + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static void setArrayShort(short value, int index, short[] array) throws SemanticJavaException { + assert array != null; + try { + array[index] = value; + } catch (ArrayIndexOutOfBoundsException e) { + throw SemanticJavaException.raise(e); + } + } + + public static void setArrayObject(Object value, int index, Object[] array) throws SemanticJavaException { + assert array != null; + try { + array[index] = value; + } catch (ArrayIndexOutOfBoundsException | ArrayStoreException e) { + throw SemanticJavaException.raise(e); + } + } + + // endregion Set (array) operations + + // region Monitor enter/exit + + public static void monitorEnter(InterpreterFrame frame, Object obj) throws SemanticJavaException { + assert obj != null; + MonitorSupport.singleton().monitorEnter(obj, MonitorInflationCause.MONITOR_ENTER); + frame.addLock(obj); + } + + @SuppressFBWarnings(value = "IMSE_DONT_CATCH_IMSE", justification = "Intentional.") + public static void monitorExit(InterpreterFrame frame, Object obj) throws SemanticJavaException { + assert obj != null; + try { + MonitorSupport.singleton().monitorExit(obj, MonitorInflationCause.VM_INTERNAL); + // GR-55049: Ensure that SVM doesn't allow non-structured locking. + frame.removeLock(obj); + } catch (IllegalMonitorStateException e) { + // GR-55050: Hide intermediate frames on exception. + throw SemanticJavaException.raise(e); + } + } + + @SuppressFBWarnings(value = "IMSE_DONT_CATCH_IMSE", justification = "Intentional.") + public static void releaseInterpreterFrameLocks(@SuppressWarnings("unused") InterpreterFrame frame) throws SemanticJavaException { + Object[] locks = frame.getLocks(); + for (int i = 0; i < locks.length; ++i) { + Object ref = locks[i]; + if (ref != null) { + try { + MonitorSupport.singleton().monitorExit(ref, MonitorInflationCause.VM_INTERNAL); + // GR-55049: Ensure that SVM doesn't allow non-structured locking. + locks[i] = null; + } catch (IllegalMonitorStateException e) { + // GR-55050: Hide intermediate frames on exception. + throw SemanticJavaException.raise(e); + } + } + } + } + + // endregion + + private static final Unsafe U = initUnsafe(); + + private static Unsafe initUnsafe() { + try { + return Unsafe.getUnsafe(); + } catch (SecurityException se) { + try { + Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + return (Unsafe) theUnsafe.get(Unsafe.class); + } catch (Exception e) { + throw new RuntimeException("Exception while trying to get Unsafe", e); + } + } + } + + public static WordBase getFieldWord(Object obj, InterpreterResolvedJavaField wordField) throws SemanticJavaException { + assert obj != null; + assert wordField.getType().isWordType(); + return switch (wordJavaKind()) { + case Long -> WordFactory.signed(getFieldLong(obj, wordField)); + case Int -> WordFactory.signed(getFieldInt(obj, wordField)); + default -> throw VMError.shouldNotReachHere("Unexpected word kind " + wordJavaKind()); + }; + } + + public static boolean getFieldBoolean(Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + if (field.isUnmaterializedConstant()) { + return field.getUnmaterializedConstant().asBoolean(); + } + if (field.isVolatile()) { + return U.getBooleanVolatile(obj, field.getOffset()); + } else { + return U.getBoolean(obj, field.getOffset()); + } + } + + public static int getFieldInt(Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + if (field.isUnmaterializedConstant()) { + return field.getUnmaterializedConstant().asInt(); + } + if (field.isVolatile()) { + return U.getIntVolatile(obj, field.getOffset()); + } else { + return U.getInt(obj, field.getOffset()); + } + } + + public static long getFieldLong(Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + if (field.isUnmaterializedConstant()) { + return field.getUnmaterializedConstant().asLong(); + } + if (field.isVolatile()) { + return U.getLongVolatile(obj, field.getOffset()); + } else { + return U.getLong(obj, field.getOffset()); + } + } + + public static byte getFieldByte(Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + if (field.isUnmaterializedConstant()) { + return (byte) field.getUnmaterializedConstant().asInt(); + } + if (field.isVolatile()) { + return U.getByteVolatile(obj, field.getOffset()); + } else { + return U.getByte(obj, field.getOffset()); + } + } + + public static short getFieldShort(Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + if (field.isUnmaterializedConstant()) { + return (short) field.getUnmaterializedConstant().asInt(); + } + if (field.isVolatile()) { + return U.getShortVolatile(obj, field.getOffset()); + } else { + return U.getShort(obj, field.getOffset()); + } + } + + public static float getFieldFloat(Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + if (field.isUnmaterializedConstant()) { + return field.getUnmaterializedConstant().asFloat(); + } + if (field.isVolatile()) { + return U.getFloatVolatile(obj, field.getOffset()); + } else { + return U.getFloat(obj, field.getOffset()); + } + } + + public static double getFieldDouble(Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + if (field.isUnmaterializedConstant()) { + return field.getUnmaterializedConstant().asDouble(); + } + if (field.isVolatile()) { + return U.getDoubleVolatile(obj, field.getOffset()); + } else { + return U.getDouble(obj, field.getOffset()); + } + } + + public static Object getFieldObject(Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + if (field.isUnmaterializedConstant()) { + JavaConstant constant = field.getUnmaterializedConstant(); + if (JavaConstant.NULL_POINTER.equals(constant)) { + return null; + } + VMError.guarantee(!constant.equals(PrimitiveConstant.ILLEGAL), Interpreter.FAILURE_CONSTANT_NOT_PART_OF_IMAGE_HEAP); + VMError.guarantee(constant.isNonNull(), Interpreter.FAILURE_CONSTANT_NOT_PART_OF_IMAGE_HEAP); + return ((ReferenceConstant) constant).getReferent(); + } + if (field.isVolatile()) { + return U.getReferenceVolatile(obj, field.getOffset()); + } else { + return U.getReference(obj, field.getOffset()); + } + } + + public static char getFieldChar(Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + if (field.isUnmaterializedConstant()) { + return (char) field.getUnmaterializedConstant().asInt(); + } + if (field.isVolatile()) { + return U.getCharVolatile(obj, field.getOffset()); + } else { + return U.getChar(obj, field.getOffset()); + } + } + + public static void setFieldBoolean(boolean value, Object obj, InterpreterResolvedJavaField field) { + if (field.isVolatile()) { + U.putBooleanVolatile(obj, field.getOffset(), value); + } else { + U.putBoolean(obj, field.getOffset(), value); + } + } + + public static void setFieldByte(byte value, Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + if (field.isVolatile()) { + U.putByteVolatile(obj, field.getOffset(), value); + } else { + U.putByte(obj, field.getOffset(), value); + } + } + + public static void setFieldChar(char value, Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + if (field.isVolatile()) { + U.putCharVolatile(obj, field.getOffset(), value); + } else { + U.putChar(obj, field.getOffset(), value); + } + } + + public static void setFieldShort(short value, Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + if (field.isVolatile()) { + U.putShortVolatile(obj, field.getOffset(), value); + } else { + U.putShort(obj, field.getOffset(), value); + } + } + + public static void setFieldInt(int value, Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + assert field.getJavaKind() == JavaKind.Int || field.getType().isWordType(); + if (field.isVolatile()) { + U.putIntVolatile(obj, field.getOffset(), value); + } else { + U.putInt(obj, field.getOffset(), value); + } + } + + public static void setFieldLong(long value, Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + assert field.getJavaKind() == JavaKind.Long || field.getType().isWordType(); + if (field.isVolatile()) { + U.putLongVolatile(obj, field.getOffset(), value); + } else { + U.putLong(obj, field.getOffset(), value); + } + } + + public static void setFieldWord(WordBase value, Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + switch (wordJavaKind()) { + case Int -> setFieldInt((int) value.rawValue(), obj, field); + case Long -> setFieldLong(value.rawValue(), obj, field); + default -> throw VMError.shouldNotReachHere("Unexpected word kind " + wordJavaKind()); + } + } + + public static void setFieldFloat(float value, Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + if (field.isVolatile()) { + U.putFloatVolatile(obj, field.getOffset(), value); + } else { + U.putFloat(obj, field.getOffset(), value); + } + } + + public static void setFieldDouble(double value, Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + if (field.isVolatile()) { + U.putDoubleVolatile(obj, field.getOffset(), value); + } else { + U.putDouble(obj, field.getOffset(), value); + } + } + + public static void setFieldObject(Object value, Object obj, InterpreterResolvedJavaField field) { + assert obj != null; + if (field.isVolatile()) { + U.putReferenceVolatile(obj, field.getOffset(), value); + } else { + U.putReference(obj, field.getOffset(), value); + } + } + + /** + * Subtyping among Array Types The following rules define the direct supertype relation among + * array types: + * + *
        + *
      • If S and T are both reference types, then S[] >1 T[] iff S >1 T. + *
      • Object >1 Object[] + *
      • Cloneable >1 Object[] + *
      • java.io.Serializable >1 Object[] + *
      • If P is a primitive type, then: Object >1 P[] Cloneable >1 P[] java.io.Serializable >1 + * P[] + *
      + */ + public static boolean instanceOf(Object instance, InterpreterResolvedJavaType typeToCheck) { + return instanceOf(instance, typeToCheck.getJavaClass()); + } + + public static boolean instanceOf(Object instance, Class classToCheck) { + if (instance == null) { + return false; + } + return classToCheck.isAssignableFrom(instance.getClass()); + } + + private static String cannotCastMsg(Object instance, Class clazz) { + return "Cannot cast " + instance.getClass().getName() + " to " + clazz.getName(); + } + + public static Object checkCast(Object instance, Class classToCheck) throws SemanticJavaException { + assert classToCheck != null; + // Avoid Class#cast since it pollutes stack traces. + if (GraalDirectives.injectBranchProbability(GraalDirectives.SLOWPATH_PROBABILITY, instance != null && !instanceOf(instance, classToCheck))) { + throw SemanticJavaException.raise(new ClassCastException(cannotCastMsg(instance, classToCheck))); + } + return instance; + } + + public static Object checkCast(Object instance, InterpreterResolvedJavaType typeToCheck) throws SemanticJavaException { + return checkCast(instance, typeToCheck.getJavaClass()); + } + + public static int arrayLength(Object array) { + assert array != null && array.getClass().isArray(); + return ArrayLengthNode.arrayLength(array); + } + + public static Object createNewReference(InterpreterResolvedJavaType klass) throws SemanticJavaException { + assert !klass.isPrimitive(); + Class clazz = klass.getJavaClass(); + ensureClassInitialized(clazz); + try { + // GR-55050: Ensure that the type can be allocated on SVM. + // At this point failing allocation should only imply OutOfMemoryError or + // StackOverflowError which are handled specially by the interpreter. + // GR-55050: Hide/remove the Unsafe#allocateInstance frame e.g. use a + // DynamicNewInstanceNode intrinsic. + return U.allocateInstance(clazz); + } catch (InstantiationException | IllegalArgumentException | MissingReflectionRegistrationError e) { + throw SemanticJavaException.raise(e); + } + } + + public static Object createNewPrimitiveArray(byte jvmPrimitiveType, int length) throws SemanticJavaException { + try { + return switch (jvmPrimitiveType) { + case 4 -> new boolean[length]; + case 5 -> new char[length]; + case 6 -> new float[length]; + case 7 -> new double[length]; + case 8 -> new byte[length]; + case 9 -> new short[length]; + case 10 -> new int[length]; + case 11 -> new long[length]; + default -> throw VMError.shouldNotReachHereAtRuntime(); + }; + } catch (NegativeArraySizeException e) { + throw SemanticJavaException.raise(e); + } + } + + public static Object createNewReferenceArray(InterpreterResolvedJavaType componentType, int length) throws SemanticJavaException { + assert componentType.getJavaKind() != JavaKind.Void; + assert !componentType.getJavaKind().isPrimitive(); + assert getDimensions(componentType) + 1 <= 255; + if (length < 0) { + throw SemanticJavaException.raise(new NegativeArraySizeException(String.valueOf(length))); + } + // GR-55050: Ensure that the array type can be allocated on SVM. + // At this point failing allocation should only imply OutOfMemoryError or + // StackOverflowError which are handled specially by the interpreter. + // GR-55050: Hide/remove the Array.newInstance (and other intermediate) frames + // e.g. use a DynamicNewArrayInstanceNode intrinsic. + return Array.newInstance(componentType.getJavaClass(), length); + } + + private static int getDimensions(ResolvedJavaType object) { + int dimensions = 0; + for (ResolvedJavaType elem = object; elem.isArray(); elem = elem.getComponentType()) { + dimensions++; + } + return dimensions; + } + + public static Object createMultiArray(InterpreterResolvedJavaType multiArrayType, int[] dimensions) throws SemanticJavaException { + assert dimensions.length > 0; + assert getDimensions(multiArrayType) >= dimensions.length; + assert getDimensions(multiArrayType) <= 255; + // GR-55050: Ensure that the array type can be allocated on SVM. + InterpreterResolvedJavaType component = multiArrayType; + for (int d : dimensions) { + if (d < 0) { + throw SemanticJavaException.raise(new NegativeArraySizeException(String.valueOf(d))); + } + component = (InterpreterResolvedJavaType) component.getComponentType(); + } + // At this point failing allocation should only imply OutOfMemoryError or + // StackOverflowError which are handled specially by the interpreter. + // GR-55050: Hide/remove the Array.newInstance (and other intermediate) frames + // e.g. use a DynamicNewArrayInstanceNode intrinsic. + return Array.newInstance(component.getJavaClass(), dimensions); + } + + public static void ensureClassInitialized(InterpreterResolvedObjectType type) { + ensureClassInitialized(type.getJavaClass()); + } + + /** + * Ensures that the given class is initialized, which may execute the static initializer of the + * given class it's superclasses/superinterfaces. Exceptions thrown by class initialization are + * propagated as a semantic/valid Java exception to the interpreter. + */ + public static void ensureClassInitialized(Class clazz) throws SemanticJavaException { + assert clazz != null; + try { + EnsureClassInitializedNode.ensureClassInitialized(clazz); + } catch (Error e) { + // Only Error is expected here. + throw SemanticJavaException.raise(e); + } + } + + static CFunctionPointer peekAtSVMVTable(Class seedClass, Class thisClass, int vTableIndex, boolean isInvokeInterface) { + DynamicHub seedHub = DynamicHub.fromClass(seedClass); + DynamicHub thisHub = DynamicHub.fromClass(thisClass); + + int vtableOffset = KnownOffsets.singleton().getVTableOffset(vTableIndex, false); + + if (SubstrateOptions.useClosedTypeWorldHubLayout()) { + vtableOffset += KnownOffsets.singleton().getVTableBaseOffset(); + } else { + VMError.guarantee(seedHub.isInterface() == isInvokeInterface); + + if (!seedHub.isInterface()) { + vtableOffset += KnownOffsets.singleton().getVTableBaseOffset(); + } else { + vtableOffset += (int) OpenTypeWorldDispatchTableSnippets.determineITableStartingOffset(thisHub, seedHub.getTypeID()); + } + } + return Word.objectToTrackedPointer(thisHub).readWord(vtableOffset); + } + + private static InterpreterResolvedJavaMethod peekAtInterpreterVTable(Class seedClass, Class thisClass, int vTableIndex, boolean isInvokeInterface) { + ResolvedJavaType thisType; + if (DebuggerWithInterpreter.getValue()) { + DebuggerSupport interpreterSupport = ImageSingletons.lookup(DebuggerSupport.class); + thisType = interpreterSupport.getUniverse().lookupType(thisClass); + } else { + assert RuntimeClassLoading.isSupported(); + throw VMError.unimplemented("obtain java type with vtable mirror"); + } + VMError.guarantee(thisType != null); + VMError.guarantee(thisType instanceof InterpreterResolvedObjectType); + + InterpreterResolvedJavaMethod[] vTable = ((InterpreterResolvedObjectType) thisType).getVtable(); + VMError.guarantee(vTable != null); + + DynamicHub seedHub = DynamicHub.fromClass(seedClass); + + if (SubstrateOptions.useClosedTypeWorldHubLayout()) { + VMError.guarantee(vTableIndex > 0 && vTableIndex < vTable.length); + return vTable[vTableIndex]; + } else { + VMError.guarantee(seedHub.isInterface() == isInvokeInterface); + + if (!seedHub.isInterface()) { + return vTable[vTableIndex]; + } else { + int iTableStartingIndex = determineITableStartingIndex(DynamicHub.fromClass(thisClass), seedHub.getTypeID()); + return vTable[iTableStartingIndex + vTableIndex]; + } + } + } + + private static int determineITableStartingIndex(DynamicHub thisHub, int interfaceID) { + /* + * iTableStartingOffset includes the initial offset to the vtable array and describes an + * offset (not index) + */ + long iTableStartingOffset = OpenTypeWorldDispatchTableSnippets.determineITableStartingOffset(thisHub, interfaceID); + + int vtableBaseOffset = KnownOffsets.singleton().getVTableBaseOffset(); + int vtableEntrySize = KnownOffsets.singleton().getVTableEntrySize(); + + return (int) (iTableStartingOffset - vtableBaseOffset) / vtableEntrySize; + } + + public static Object dispatchInvocation(InterpreterResolvedJavaMethod seedMethod, Object[] calleeArgs, boolean isVirtual0, boolean forceStayInInterpreter, boolean preferStayInInterpreter, + boolean isInvokeInterface) + throws SemanticJavaException { + boolean goThroughPLT; + boolean isVirtual = isVirtual0; + + if (forceStayInInterpreter) { + // Force execution in the interpreter, transitively, for all callees in the call + // subtree. + goThroughPLT = false; + } else { + // Not forced to transitively "stay in interpreter"; but still; it may be "preferred" to + // execute this callee (and only this one) in the interpreter, if possible e.g. Step + // Into. + goThroughPLT = !preferStayInInterpreter; + } + + InterpreterResolvedObjectType seedDeclaringClass = seedMethod.getDeclaringClass(); + if (seedMethod.isStatic()) { + InterpreterUtil.guarantee(!isVirtual, "no virtual calls for static method %s", seedMethod); + ensureClassInitialized(seedDeclaringClass); + } + + CFunctionPointer calleeFtnPtr = WordFactory.nullPointer(); + + if (goThroughPLT) { + if (seedMethod.hasNativeEntryPoint()) { + calleeFtnPtr = seedMethod.getNativeEntryPoint(); + traceInterpreter("got native entry point: ").hex(calleeFtnPtr).newline(); + } else if (seedMethod.getVTableIndex() == VTBL_NO_ENTRY) { + /* + * does not always hold. Counter example: j.io.BufferedWriter::min, because it gets + * inlined + */ + // InterpreterUtil.guarantee(!isVirtual, "leaveInterpreter is virtual %s", + // seedMethod); + goThroughPLT = false; + + /* arguments to Log methods might have side-effects */ + if (InterpreterTraceSupport.getValue()) { + traceInterpreter("fall back to interp for compile entry ").string(seedMethod.toString()).string(" because it has not been compiled.").newline(); + } + } else if (seedMethod.getVTableIndex() == VTBL_ONE_IMPL) { + goThroughPLT = seedMethod.getOneImplementation().hasNativeEntryPoint(); + } else if (!isVirtual && seedMethod.hasVTableIndex()) { + goThroughPLT = false; + /* arguments to Log methods might have side-effects */ + if (InterpreterTraceSupport.getValue()) { + traceInterpreter("invokespecial: ").string(seedMethod.toString()).newline(); + } + } else if (isVirtual && !seedMethod.hasVTableIndex()) { + VMError.shouldNotReachHere("cannot do virtual dispatch without vtable index"); + } + } + + // Arrays have no vtable. + if (isVirtual && (seedMethod.isFinalFlagSet() || calleeArgs[0].getClass().isArray() || seedDeclaringClass.isLeaf() || seedMethod.isPrivate())) { + isVirtual = false; + /* arguments to Log methods might have side-effects */ + if (InterpreterTraceSupport.getValue()) { + traceInterpreter("reverting virtual call to invokespecial: ").string(seedMethod.toString()).newline(); + } + } + + InterpreterResolvedJavaMethod targetMethod = seedMethod; + if (isVirtual && seedMethod.hasVTableIndex()) { + /* vtable dispatch */ + + VMError.guarantee(seedMethod.hasReceiver()); + + Class thisClazz = calleeArgs[0].getClass(); + Class seedClazz = seedDeclaringClass.getJavaClass(); + int vtableIndex = seedMethod.getVTableIndex(); + + if (goThroughPLT) { + // determine virtual call target via SVM vtable dispatch + calleeFtnPtr = peekAtSVMVTable(seedClazz, thisClazz, vtableIndex, isInvokeInterface); + + if (calleeFtnPtr.equal(InterpreterMethodPointerHolder.getMethodNotCompiledHandler())) { + // can happen e.g. due to devirtualization, need to stay in interpreter in + // this scenario + goThroughPLT = false; + + /* arguments to Log methods might have side-effects */ + if (InterpreterTraceSupport.getValue()) { + traceInterpreter("fall back to interp (vtable entry) for compile entry ").string(seedMethod.toString()).string(" because it has not been compiled.").newline(); + } + } + } + + /* always resolve the right target method in the interpreter universe */ + targetMethod = peekAtInterpreterVTable(seedClazz, thisClazz, vtableIndex, isInvokeInterface); + } else if (seedMethod.getVTableIndex() == VTBL_ONE_IMPL) { + targetMethod = seedMethod.getOneImplementation(); + /* arguments to Log methods might have side-effects */ + if (InterpreterTraceSupport.getValue()) { + traceInterpreter("found oneImpl: ").string(targetMethod.toString()); + if (goThroughPLT) { + calleeFtnPtr = targetMethod.getNativeEntryPoint(); + traceInterpreter(" ... with compiled entry=").hex(calleeFtnPtr); + } + traceInterpreter("").newline(); + } + VMError.guarantee(targetMethod != null, "VTBL_ONE_IMPL implies that oneImplementation is available in seedMethod"); + } + + if (!targetMethod.hasBytecodes() && !goThroughPLT && calleeFtnPtr.isNonNull()) { + goThroughPLT = true; + /* arguments to Log methods might have side-effects */ + if (InterpreterTraceSupport.getValue()) { + traceInterpreter("cannot interpret ").string(targetMethod.toString()).string(" falling back to compiled version ").hex(calleeFtnPtr).newline(); + } + } + + if (!goThroughPLT && targetMethod.isNative()) { + /* no way to execute target in interpreter, fall back to compiled code */ + /* example: MethodHandle.invokeBasic */ + VMError.guarantee(targetMethod.hasNativeEntryPoint()); + calleeFtnPtr = targetMethod.getNativeEntryPoint(); + VMError.guarantee(calleeFtnPtr.isNonNull()); + goThroughPLT = true; + } + + /* arguments to Log methods might have side-effects */ + if (InterpreterOptions.InterpreterTraceSupport.getValue()) { + traceInterpreter(" ".repeat(Interpreter.logIndent.get())) + .string(" -> calling (") + .string(goThroughPLT ? "plt" : "interp").string(") ") + .string(targetMethod.hasNativeEntryPoint() ? "(compiled entry available) " : ""); + if (targetMethod.hasNativeEntryPoint()) { + traceInterpreter("(addr: ").hex(calleeFtnPtr).string(" ) "); + } + traceInterpreter(targetMethod.getDeclaringClass().getName()) + .string("::").string(targetMethod.getName()) + .string(targetMethod.getSignature().toMethodDescriptor()) + .newline(); + } + + Object retObj = null; + if (goThroughPLT) { + VMError.guarantee(!forceStayInInterpreter); + VMError.guarantee(calleeFtnPtr.isNonNull()); + + // wrapping of exceptions is done in leaveInterpreter + retObj = InterpreterStubSection.leaveInterpreter(calleeFtnPtr, targetMethod, targetMethod.getDeclaringClass(), calleeArgs); + } else { + try { + retObj = Interpreter.execute(targetMethod, calleeArgs, forceStayInInterpreter); + } catch (Throwable e) { + // Exceptions coming from calls are valid, semantic Java exceptions. + throw SemanticJavaException.raise(e); + } + } + + return retObj; + } + + public static Object nullCheck(Object value) throws SemanticJavaException { + if (GraalDirectives.injectBranchProbability(GraalDirectives.FASTPATH_PROBABILITY, value != null)) { + return value; + } + throw SemanticJavaException.raise(new NullPointerException()); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterUtil.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterUtil.java new file mode 100644 index 000000000000..fe4d15927426 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterUtil.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter; + +import com.oracle.svm.core.log.Log; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.MetadataUtil; + +public class InterpreterUtil { + + /** + * Alternative to {@link VMError#guarantee(boolean, String, Object)} that avoids + * {@link String#format(String, Object...)} . + */ + public static void guarantee(boolean condition, String simpleFormat, Object arg1) { + if (!condition) { + VMError.guarantee(condition, MetadataUtil.fmt(simpleFormat, arg1)); + } + } + + /** + * Build time logging. + */ + @Platforms(Platform.HOSTED_ONLY.class) + public static void log(String msg) { + if (InterpreterOptions.InterpreterBuildTimeLogging.getValue()) { + System.out.println(msg); + } + } + + /** + * Build time logging. + */ + @Platforms(Platform.HOSTED_ONLY.class) + public static void log(String simpleFormat, Object arg1) { + if (InterpreterOptions.InterpreterBuildTimeLogging.getValue()) { + System.out.println(MetadataUtil.fmt(simpleFormat, arg1)); + } + } + + /** + * Build time logging. + */ + @Platforms(Platform.HOSTED_ONLY.class) + public static void log(String simpleFormat, Object arg1, Object arg2) { + if (InterpreterOptions.InterpreterBuildTimeLogging.getValue()) { + System.out.println(MetadataUtil.fmt(simpleFormat, arg1, arg2)); + } + } + + /** + * Build time logging. + */ + @Platforms(Platform.HOSTED_ONLY.class) + public static void log(String simpleFormat, Object arg1, Object arg2, Object arg3) { + if (InterpreterOptions.InterpreterBuildTimeLogging.getValue()) { + System.out.println(MetadataUtil.fmt(simpleFormat, arg1, arg2, arg3)); + } + } + + /** + * Build time logging. + */ + @Platforms(Platform.HOSTED_ONLY.class) + public static void log(Throwable t) { + if (InterpreterOptions.InterpreterBuildTimeLogging.getValue()) { + t.printStackTrace(System.out); + } + } + + public static Log traceInterpreter(String msg) { + if (InterpreterOptions.InterpreterTraceSupport.getValue()) { + if (InterpreterOptions.InterpreterTrace.getValue()) { + return Log.log().string(msg); + } + } + return Log.noopLog(); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ReturnAddress.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ReturnAddress.java new file mode 100644 index 000000000000..d38b0eb57e86 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ReturnAddress.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.interpreter; + +/** + * Represents the "returnAddress" type, used for JSR/RET bytecodes. + * + * @see The + * returnAddress Type and Values + */ +record ReturnAddress(int bci) { + static ReturnAddress create(int bci) { + return new ReturnAddress(bci); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/SemanticJavaException.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/SemanticJavaException.java new file mode 100644 index 000000000000..f3b749670982 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/SemanticJavaException.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.interpreter; + +import com.oracle.svm.core.jdk.InternalVMMethod; + +/** + * Wraps exceptions thrown by the interpreted code or by compiled code. This is a way to + * differentiate between exceptions caused by interpreter itself vs. the code it executes. + */ +@InternalVMMethod +public final class SemanticJavaException extends RuntimeException { + @java.io.Serial static final long serialVersionUID = 8271499373291031203L; + + private SemanticJavaException(Throwable cause) { + super(cause); + } + + @Override + @SuppressWarnings("sync-override") + public Throwable fillInStackTrace() { + return this; + } + + public static RuntimeException raise(Throwable cause) { + assert cause != null && !(cause instanceof SemanticJavaException); + throw new SemanticJavaException(cause); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/classfile/ClassFile.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/classfile/ClassFile.java new file mode 100644 index 000000000000..cde5a5534f84 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/classfile/ClassFile.java @@ -0,0 +1,1116 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.interpreter.classfile; + +import static com.oracle.svm.interpreter.metadata.Bytecodes.ANEWARRAY; +import static com.oracle.svm.interpreter.metadata.Bytecodes.CHECKCAST; +import static com.oracle.svm.interpreter.metadata.Bytecodes.GETFIELD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.GETSTATIC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INSTANCEOF; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEDYNAMIC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEINTERFACE; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKESPECIAL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKESTATIC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.INVOKEVIRTUAL; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LDC; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LDC2_W; +import static com.oracle.svm.interpreter.metadata.Bytecodes.LDC_W; +import static com.oracle.svm.interpreter.metadata.Bytecodes.MULTIANEWARRAY; +import static com.oracle.svm.interpreter.metadata.Bytecodes.NEW; +import static com.oracle.svm.interpreter.metadata.Bytecodes.PUTFIELD; +import static com.oracle.svm.interpreter.metadata.Bytecodes.PUTSTATIC; + +import java.lang.invoke.MethodType; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import com.oracle.svm.interpreter.metadata.ReferenceConstant; +import org.graalvm.collections.Pair; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.interpreter.metadata.InterpreterUniverse; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.BytecodeStream; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType; +import com.oracle.svm.interpreter.metadata.MetadataUtil; + +import jdk.vm.ci.meta.ConstantPool; +import jdk.vm.ci.meta.ExceptionHandler; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaField; +import jdk.vm.ci.meta.JavaMethod; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.LineNumberTable; +import jdk.vm.ci.meta.Local; +import jdk.vm.ci.meta.LocalVariableTable; +import jdk.vm.ci.meta.ModifiersProvider; +import jdk.vm.ci.meta.PrimitiveConstant; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.Signature; + +/** + * Utility to re-create a .class file representation from a {@link ResolvedJavaType}. + * + *

      + * .class files and JVMCI data structures are not equivalent, generated .class files may not be + * loadable on a JVM. + * + *

      + * Known issues: + *

        + *
      • Incorrect/hardcoded .class file version
      • + *
      • Missing attributes
      • + *
      • INVOKEDYNAMIC (missing BootstrapMethods) is not supported
      • + *
      + */ +@Platforms(Platform.HOSTED_ONLY.class) +public final class ClassFile { + + public static final int MAGIC = 0xCAFEBABE; + + // GR-55048: Find .class file version from ResolvedJavaType. + public static final int MAJOR_VERSION = 55; + public static final int MINOR_VERSION = 0; + + public static final byte CONSTANT_Utf8 = 1; + public static final byte CONSTANT_Integer = 3; + public static final byte CONSTANT_Float = 4; + public static final byte CONSTANT_Long = 5; + public static final byte CONSTANT_Double = 6; + public static final byte CONSTANT_Class = 7; + public static final byte CONSTANT_String = 8; + public static final byte CONSTANT_Fieldref = 9; + public static final byte CONSTANT_Methodref = 10; + public static final byte CONSTANT_InterfaceMethodref = 11; + public static final byte CONSTANT_NameAndType = 12; + public static final byte CONSTANT_MethodHandle = 15; + public static final byte CONSTANT_MethodType = 16; + @SuppressWarnings("unused") public static final byte CONSTANT_Dynamic = 17; + @SuppressWarnings("unused") public static final byte CONSTANT_InvokeDynamic = 18; + public static final byte CONSTANT_Module = 19; + public static final byte CONSTANT_Package = 20; + + private final OutStream classFile = new OutStream(); + private final OutStream constantPool = new OutStream(); + + private int constantPoolEntryCount; + + private final Map fieldRefCache = new HashMap<>(); + private final Map, Integer> nameAndTypeCache = new HashMap<>(); + private final Map utf8Cache = new HashMap<>(); + + private final Map intCache = new HashMap<>(); + private final Map longCache = new HashMap<>(); + + // Map on the float bit pattern to account for NaNs. + private final Map floatCache = new HashMap<>(); + + // Map on the double bit pattern to account for NaNs. + private final Map doubleCache = new HashMap<>(); + + private final Map stringCache = new HashMap<>(); + private final Map classCache = new HashMap<>(); + private final Map methodRefCache = new HashMap<>(); + private final Map interfaceMethodRefCache = new HashMap<>(); + + private final Map methodTypeCache = new HashMap<>(); + private final Map, Integer> methodHandleCache = new HashMap<>(); + + private final Map moduleCache = new HashMap<>(); + private final Map packageCache = new HashMap<>(); + + public ClassFile(InterpreterUniverse universe, Function extractConstantValue) { + this.universe = universe; + this.extractConstantValue = extractConstantValue; + } + + private int utf8(String str) { + return utf8Cache.computeIfAbsent(str, + key -> { + constantPool.writeU1(CONSTANT_Utf8); + constantPool.writeUTF(str); // writeUTF prepends the length + return ++constantPoolEntryCount; + }); + } + + private int longConstant(long value) { + return longCache.computeIfAbsent(value, + key -> { + constantPool.writeU1(CONSTANT_Long); + constantPool.writeLong(value); + // All 8-byte constants take up two entries in the constant_pool table + // of the class file.In retrospect, making 8-byte constants take two + // constant pool entries was a poor choice. + int entry = ++constantPoolEntryCount; + ++constantPoolEntryCount; + return entry; + }); + } + + private int doubleConstant(double value) { + return doubleCache.computeIfAbsent(Double.doubleToRawLongBits(value), + key -> { + constantPool.writeU1(CONSTANT_Double); + constantPool.writeDouble(value); + // All 8-byte constants take up two entries in the constant_pool table + // of the class file.In retrospect, making 8-byte constants take two + // constant pool entries was a poor choice. + int entry = ++constantPoolEntryCount; + ++constantPoolEntryCount; + return entry; + }); + } + + private int intConstant(int value) { + return intCache.computeIfAbsent(value, + key -> { + constantPool.writeU1(CONSTANT_Integer); + constantPool.writeInt(value); + return ++constantPoolEntryCount; + }); + } + + private int floatConstant(float value) { + return floatCache.computeIfAbsent(Float.floatToRawIntBits(value), + key -> { + constantPool.writeU1(CONSTANT_Float); + constantPool.writeFloat(value); + return ++constantPoolEntryCount; + }); + } + + private int string(String str) { + return stringCache.computeIfAbsent(str, + key -> { + int stringIndex = utf8(str); + constantPool.writeU1(CONSTANT_String); + constantPool.writeU2(stringIndex); + return ++constantPoolEntryCount; + }); + } + + private int ldcConstant(Object javaConstantOrType) { + if (javaConstantOrType instanceof JavaConstant) { + JavaConstant javaConstant = (JavaConstant) javaConstantOrType; + // @formatter:off + switch (javaConstant.getJavaKind()) { + case Int : return intConstant(javaConstant.asInt()); + case Float : return floatConstant(javaConstant.asFloat()); + case Long : return longConstant(javaConstant.asLong()); + case Double : return doubleConstant(javaConstant.asDouble()); + case Object : { + Object value = extractConstantValue(javaConstant); + if (value instanceof String) { + return string((String) value); + } else if (value instanceof MethodType) { + return methodType((MethodType) value); + } else { + throw VMError.unimplemented("LDC methodHandle constant"); + } + } + default: + throw VMError.shouldNotReachHereAtRuntime(); + } + // @formatter:on + } else if (javaConstantOrType instanceof JavaType) { + return classConstant((JavaType) javaConstantOrType); + } else { + throw VMError.shouldNotReachHereAtRuntime(); + } + } + + private int nameAndTypeImpl(String name, String descriptor) { + return nameAndTypeCache.computeIfAbsent(Pair.create(name, descriptor), + key -> { + int nameIndex = utf8(name); + int descriptorIndex = utf8(descriptor); + constantPool.writeU1(CONSTANT_NameAndType); + constantPool.writeU2(nameIndex); + constantPool.writeU2(descriptorIndex); + return ++constantPoolEntryCount; + }); + } + + private int nameAndType(String name, JavaType fieldDescriptor) { + String descriptor = fieldDescriptor.getName(); + return nameAndTypeImpl(name, descriptor); + } + + private int nameAndType(String name, Signature methodDescriptor) { + String descriptor = methodDescriptor.toMethodDescriptor(); + return nameAndTypeImpl(name, descriptor); + } + + private int fieldRef(JavaField field) { + return fieldRefCache.computeIfAbsent(MetadataUtil.toUniqueString(field), + key -> { + int classIndex = classConstant(field.getDeclaringClass()); + int nameAndTypeIndex = nameAndType(field.getName(), field.getType()); + constantPool.writeU1(CONSTANT_Fieldref); + constantPool.writeU2(classIndex); + constantPool.writeU2(nameAndTypeIndex); + return ++constantPoolEntryCount; + }); + } + + private int classConstant(JavaType type) { + return classCache.computeIfAbsent(MetadataUtil.toUniqueString(type), + key -> { + int nameIndex = utf8(toConstantPoolName(type)); + constantPool.writeU1(CONSTANT_Class); + constantPool.writeU2(nameIndex); + return ++constantPoolEntryCount; + }); + } + + private int methodRef(JavaMethod method) { + return methodRefCache.computeIfAbsent(MetadataUtil.toUniqueString(method), + key -> { + int classIndex = classConstant(method.getDeclaringClass()); + int nameAndTypeIndex = nameAndType(method.getName(), method.getSignature()); + constantPool.writeU1(CONSTANT_Methodref); + constantPool.writeU2(classIndex); + constantPool.writeU2(nameAndTypeIndex); + return ++constantPoolEntryCount; + }); + } + + private int interfaceMethodRef(JavaMethod method) { + return interfaceMethodRefCache.computeIfAbsent(MetadataUtil.toUniqueString(method), + key -> { + assert !(method instanceof ResolvedJavaMethod) || ((ResolvedJavaMethod) method).getDeclaringClass().isInterface() : method; + int classIndex = classConstant(method.getDeclaringClass()); + int nameAndTypeIndex = nameAndType(method.getName(), method.getSignature()); + constantPool.writeU1(CONSTANT_InterfaceMethodref); + constantPool.writeU2(classIndex); + constantPool.writeU2(nameAndTypeIndex); + return ++constantPoolEntryCount; + }); + } + + @SuppressWarnings("unused") + private int module(String moduleName) { + return moduleCache.computeIfAbsent(moduleName, + key -> { + int nameIndex = utf8(moduleName); + constantPool.writeU1(CONSTANT_Module); // u1 tag + constantPool.writeU2(nameIndex); // u2 name_index; + return ++constantPoolEntryCount; + }); + } + + @SuppressWarnings("unused") + private int packageConstant(String packageName) { + return packageCache.computeIfAbsent(packageName, + key -> { + int nameIndex = utf8(packageName); + constantPool.writeU1(CONSTANT_Package); + constantPool.writeU2(nameIndex); + return ++constantPoolEntryCount; + }); + } + + private int methodType(MethodType methodType) { + String descriptor = methodType.descriptorString(); + return methodTypeCache.computeIfAbsent(descriptor, + key -> { + int descriptorIndex = utf8(descriptor); + constantPool.writeU1(CONSTANT_MethodType); + constantPool.writeU2(descriptorIndex); + return ++constantPoolEntryCount; + }); + } + + @SuppressWarnings("unused") public static final byte REF_NONE = 0; // null value + public static final byte REF_getField = 1; + public static final byte REF_getStatic = 2; + public static final byte REF_putField = 3; + public static final byte REF_putStatic = 4; + public static final byte REF_invokeVirtual = 5; + public static final byte REF_invokeStatic = 6; + public static final byte REF_invokeSpecial = 7; + public static final byte REF_newInvokeSpecial = 8; + public static final byte REF_invokeInterface = 9; + + @SuppressWarnings("unused") public static final byte REF_LIMIT = 10; + + @SuppressWarnings("unused") + private int methodHandle(byte referenceKind, JavaField referenceField) { + VMError.guarantee(referenceKind == REF_getField || referenceKind == REF_getStatic || referenceKind == REF_putField || referenceKind == REF_putStatic); + return methodHandleCache.computeIfAbsent(Pair.create(referenceKind, referenceField), + key -> { + int referenceIndex = fieldRef(referenceField); + constantPool.writeU1(CONSTANT_MethodHandle); + constantPool.writeU1(referenceKind); + constantPool.writeU2(referenceIndex); + return ++constantPoolEntryCount; + }); + } + + @SuppressWarnings("unused") + private int methodHandle(byte referenceKind, ResolvedJavaMethod referenceMethod) { + VMError.guarantee(referenceKind == REF_invokeVirtual || referenceKind == REF_invokeStatic || referenceKind == REF_invokeSpecial || referenceKind == REF_newInvokeSpecial || + referenceKind == REF_invokeInterface); + return methodHandleCache.computeIfAbsent(Pair.create(referenceKind, referenceMethod), + key -> { + int referenceIndex; + switch (referenceKind) { + case REF_invokeVirtual: // fall-through + case REF_newInvokeSpecial: + referenceIndex = methodRef(referenceMethod); + break; + case REF_invokeStatic: // fall-through + case REF_invokeSpecial: + // GR-55048: Java version check, interfaceMethodRef + // allowed after >= 52 + if (referenceMethod.getDeclaringClass().isInterface()) { + referenceIndex = interfaceMethodRef(referenceMethod); + } else { + referenceIndex = methodRef(referenceMethod); + } + break; + case REF_invokeInterface: + referenceIndex = interfaceMethodRef(referenceMethod); + break; + default: + throw VMError.shouldNotReachHere("invalid methodHandle ref kind"); + } + constantPool.writeU1(CONSTANT_MethodHandle); + constantPool.writeU1(referenceKind); + constantPool.writeU2(referenceIndex); + return ++constantPoolEntryCount; + }); + } + + private final InterpreterUniverse universe; + private final Function extractConstantValue; + + private Object extractConstantValue(Object constant) { + return extractConstantValue.apply(constant); + } + + //@formatter:off + private static final Function EXTRACT_INTERPRETER_CONSTANT_VALUE = (constant) -> { + if (constant instanceof JavaType) { + return constant; + } + if (constant instanceof PrimitiveConstant primitiveConstant) { + return primitiveConstant.asBoxedPrimitive(); + } + if (constant instanceof ReferenceConstant) { + return ((ReferenceConstant) constant).getReferent(); + } + throw VMError.shouldNotReachHere("unexpected constant"); + }; + //@formatter:on + + private ResolvedJavaType getSuperclass(ResolvedJavaType type) { + if (type instanceof InterpreterResolvedJavaType interpreterResolvedJavaType) { + Class superclass = interpreterResolvedJavaType.getJavaClass().getSuperclass(); + if (superclass == null) { + return null; + } + return universe.lookupType(superclass); + } else { + return type.getSuperclass(); + } + } + + private ResolvedJavaType[] getInterfaces(ResolvedJavaType type) { + if (type instanceof InterpreterResolvedJavaType interpreterResolvedJavaType) { + Class[] interfaces = interpreterResolvedJavaType.getJavaClass().getInterfaces(); + ResolvedJavaType[] result = new InterpreterResolvedObjectType[interfaces.length]; + for (int i = 0; i < interfaces.length; i++) { + result[i] = universe.lookupType(interfaces[i]); + } + return result; + } else { + return type.getInterfaces(); + } + } + + private ResolvedJavaMethod getClassInitializer(ResolvedJavaType type) { + if (type instanceof InterpreterResolvedJavaType) { + return universe.getAllDeclaredMethods(type) + .stream() + .filter(ResolvedJavaMethod::isClassInitializer) + .findAny() + .orElse(null); + } else { + return type.getClassInitializer(); + } + } + + private ResolvedJavaMethod[] getDeclaredConstructors(ResolvedJavaType type) { + if (type instanceof InterpreterResolvedJavaType) { + return universe.getAllDeclaredMethods(type) + .stream() + .filter(ResolvedJavaMethod::isConstructor) + .toArray(InterpreterResolvedJavaMethod[]::new); + } else { + return type.getDeclaredConstructors(); + } + } + + private ResolvedJavaMethod[] getDeclaredMethods(ResolvedJavaType type) { + if (type instanceof InterpreterResolvedJavaType) { + return universe.getAllDeclaredMethods(type) + .stream() + .filter(method -> !method.isConstructor() && !method.isClassInitializer()) + .toArray(ResolvedJavaMethod[]::new); + } else { + return type.getDeclaredMethods(); + } + } + + private ResolvedJavaField[] getInstanceFields(ResolvedJavaType type, boolean includeSuperclasses) { + if (type instanceof InterpreterResolvedJavaType) { + if (includeSuperclasses) { + throw VMError.unimplemented("getInstanceFields with includeSuperclasses=true"); + } + return universe.getAllDeclaredFields(type) + .stream() + .filter(f -> !f.isStatic()) + .toArray(ResolvedJavaField[]::new); + } else { + return type.getInstanceFields(includeSuperclasses); + } + } + + private ResolvedJavaField[] getStaticFields(ResolvedJavaType type) { + if (type instanceof InterpreterResolvedJavaType) { + return universe.getAllDeclaredFields(type) + .stream() + .filter(ModifiersProvider::isStatic) + .toArray(ResolvedJavaField[]::new); + } else { + return type.getStaticFields(); + } + } + + static byte[] dumpResolvedJavaTypeClassFile(InterpreterUniverse universe, ResolvedJavaType type, Function extractConstantValue) { + VMError.guarantee(!type.isPrimitive() && !type.isArray()); + + ClassFile cf = new ClassFile(universe, extractConstantValue); + cf.dumpClassFileImpl(type); + OutStream ensemble = new OutStream(); + + // Header + ensemble.writeInt(MAGIC); + ensemble.writeU2(MINOR_VERSION); + ensemble.writeU2(MAJOR_VERSION); + // Constant pool + // The value of the constant_pool_count item is equal to the number of entries in the + // constant_pool table plus one. + ensemble.writeU2(cf.constantPoolEntryCount + 1); + ensemble.writeBytes(cf.constantPool.toArray()); + // Tail + ensemble.writeBytes(cf.classFile.toArray()); + + return ensemble.toArray(); + } + + public static byte[] dumpInterpreterTypeClassFile(InterpreterUniverse universe, InterpreterResolvedJavaType type) { + return dumpResolvedJavaTypeClassFile(universe, type, EXTRACT_INTERPRETER_CONSTANT_VALUE); + } + + void dumpSourceFileAttribute(String sourceFileName) { + if (sourceFileName == null) { + return; + } + // SourceFile_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u2 sourcefile_index; + // } + classFile.writeU2(utf8("SourceFile")); + classFile.writeInt(2); + classFile.writeU2(utf8(sourceFileName)); + } + + void dumpClassFileImpl(ResolvedJavaType type) { + // ClassFile { + // u4 magic; + // u2 minor_version; + // u2 major_version; + // u2 constant_pool_count; + // cp_info constant_pool[constant_pool_count-1]; + + // Constant pool is computed separately during dumping, dumping starts here: + + // u2 access_flags; + // u2 this_class; + // u2 super_class; + // u2 interfaces_count; + // u2 interfaces[interfaces_count]; + // u2 fields_count; + // field_info fields[fields_count]; + // u2 methods_count; + // method_info methods[methods_count]; + // u2 attributes_count; + // attribute_info attributes[attributes_count]; + // } + + List allDeclaredMethods = new ArrayList<>(); + + // Static initializers are not included for classes already initialized. + if (getClassInitializer(type) != null) { + allDeclaredMethods.add(getClassInitializer(type)); + } + allDeclaredMethods.addAll(Arrays.asList(getDeclaredConstructors(type))); + allDeclaredMethods.addAll(Arrays.asList(getDeclaredMethods(type))); + + // Write all 1-byte CPIs first in the constant pool. + processLDC(allDeclaredMethods); + // processINVOKEDYNAMIC(allDeclaredMethods); + + // u2 access_flags; + classFile.writeU2(type.getModifiers() & Modifier.classModifiers()); + + // u2 this_class; + classFile.writeU2(classConstant(type)); // GR-55048: Handle hidden classes. + + // u2 super_class; + if (getSuperclass(type) != null) { + classFile.writeU2(classConstant(getSuperclass(type))); + } else { + classFile.writeU2(0); // no super class + } + + // u2 interfaces_count; + classFile.writeU2(getInterfaces(type).length); + + // u2 interfaces[interfaces_count]; + for (JavaType i : getInterfaces(type)) { + classFile.writeU2(classConstant(i)); + } + + List fields = new ArrayList<>(); + fields.addAll(Arrays.asList(getStaticFields(type))); + fields.addAll(Arrays.asList(getInstanceFields(type, false))); + // u2 fields_count; + classFile.writeU2(fields.size()); + + // field_info fields[fields_count]; + for (ResolvedJavaField f : fields) { + dumpFieldInfo(f); + } + + // u2 methods_count; + classFile.writeU2(allDeclaredMethods.size()); + + // method_info methods[methods_count]; + for (ResolvedJavaMethod m : allDeclaredMethods) { + dumpMethodInfo(m); + } + + int attributeCount = 0; + if (getSourceFileName(type) != null) { + attributeCount++; + } + + // u2 attributes_count; + classFile.writeU2(attributeCount); + + // attribute_info attributes[attributes_count]; + if (getSourceFileName(type) != null) { + dumpSourceFileAttribute(getSourceFileName(type)); + } + + // SourceFile + // InnerClasses + // EnclosingMethod + // SourceDebugExtension + // BootstrapMethods + // Module + // ModulePackages + // ModuleMainClass + // NestHost + // NestMembers + // Record + // PermittedSubclasses + } + + private static String getSourceFileName(ResolvedJavaType type) { + if (type instanceof InterpreterResolvedJavaType) { + return ((InterpreterResolvedObjectType) type).getOriginalType().getSourceFileName(); + } else { + return type.getSourceFileName(); + } + } + + final class ConstantWrapper { + final Object constant; + + ConstantWrapper(Object constant) { + this.constant = constant; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ConstantWrapper)) { + return false; + } + ConstantWrapper that = (ConstantWrapper) other; + Object thisValue = extractConstantValue(this.constant); + Object thatValue = extractConstantValue(that.constant); + // Compare types by name. + if (thisValue instanceof JavaType) { + return (thatValue instanceof JavaType) && + ((JavaType) thisValue).getName().equals(((JavaType) thatValue).getName()); + } + return thisValue.equals(thatValue); + } + + @Override + public int hashCode() { + Object value = extractConstantValue(this.constant); + if (value instanceof JavaType) { + return ((JavaType) value).getName().hashCode(); + } + return value.hashCode(); + } + } + + private void processLDC(List methods) { + Set pending = new HashSet<>(); + + for (ResolvedJavaMethod m : methods) { + if (!m.hasBytecodes()) { + continue; + } + byte[] code = m.getCode(); + if (code == null || code.length == 0) { + continue; + } + for (int bci = 0; bci < BytecodeStream.endBCI(code); bci = BytecodeStream.nextBCI(code, bci)) { + if (BytecodeStream.opcode(code, bci) == LDC) { + int originalCPI = BytecodeStream.readCPI(code, bci); + if (originalCPI != 0) { + Object constant = m.getConstantPool().lookupConstant(originalCPI); + if (constant instanceof PrimitiveConstant) { + ldcConstant(constant); // push it on the constant pool + } else { + // Constant can be String, MethodType and MethodHandle. + pending.add(new ConstantWrapper(constant)); + } + } + } + } + } + + /* + * Class, String and MethodType constants are pointers to UTF8 entries. LDC doesn't refer to + * UTF8 entries directly, but these UTF8 entries may needlessly occupy slots of the 255 + * addressable by LDC, running out of addressable slots for the constants. Constant + * dependencies are written always before in the constant pool, but in this case UTF8 + * entries must be added after to ensure the constant indices remain addressable by LDC. + */ + List newEntries = new ArrayList<>(); + for (ConstantWrapper wrapper : pending) { + Object constant = wrapper.constant; + Object value = extractConstantValue(constant); + String utf8Entry = null; + if (value instanceof JavaType) { + utf8Entry = toConstantPoolName((JavaType) value); + } else if (value instanceof String) { + utf8Entry = (String) value; + } else if (value instanceof MethodType) { + MethodType methodType = (MethodType) value; + utf8Entry = methodType.toMethodDescriptorString(); + } else { + // MethodHandle is not implemented, never seen one either. + throw VMError.unimplemented("LDC methodHandle constant"); + } + VMError.guarantee(utf8Entry != null); + if (!utf8Cache.containsKey(utf8Entry)) { + newEntries.add(utf8Entry); + utf8Cache.put(utf8Entry, constantPoolEntryCount + pending.size() + newEntries.size()); + } + } + + int start = constantPoolEntryCount; + for (ConstantWrapper wrapper : pending) { + ldcConstant(wrapper.constant); + } + int end = constantPoolEntryCount; + VMError.guarantee(end - start == pending.size()); + for (String entry : newEntries) { + constantPool.writeU1(CONSTANT_Utf8); + constantPool.writeUTF(entry); + ++constantPoolEntryCount; + } + } + + private static String toConstantPoolName(JavaType type) { + return type.toJavaName().replace('.', '/'); + } + + private void dumpFieldInfo(ResolvedJavaField field) { + int accessFlags = field.getModifiers() & Modifier.fieldModifiers(); + int nameIndex = utf8(field.getName()); + int descriptorIndex = utf8(field.getType().getName()); + + // field_info { + // u2 access_flags; + // u2 name_index; + // u2 descriptor_index; + // u2 attributes_count; + // attribute_info attributes[attributes_count]; + // } + + classFile.writeU2(accessFlags); + classFile.writeU2(nameIndex); + classFile.writeU2(descriptorIndex); + + int attributeCount = 0; + classFile.writeU2(attributeCount); + + // Field attributes: + // ConstantValue + // Synthetic + // Deprecated + // Signature + // RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations + // RuntimeVisibleTypeAnnotations, RuntimeInvisibleTypeAnnotations + } + + private void dumpMethodInfo(ResolvedJavaMethod method) { + int accessFlags = method.getModifiers() & Modifier.methodModifiers(); + int nameIndex = utf8(method.getName()); + int descriptorIndex = utf8(method.getSignature().toMethodDescriptor()); + + // method_info { + // u2 access_flags; + // u2 name_index; + // u2 descriptor_index; + // u2 attributes_count; + // attribute_info attributes[attributes_count]; + // } + + classFile.writeU2(accessFlags); + classFile.writeU2(nameIndex); + classFile.writeU2(descriptorIndex); + + int attributeCount = 0; + if (method.hasBytecodes()) { + ++attributeCount; + } + + ResolvedJavaMethod.Parameter[] methodParameters = method.getParameters(); + if (methodParameters != null) { + ++attributeCount; + } + + classFile.writeU2(attributeCount); + if (method.hasBytecodes()) { + dumpCodeAttribute(method); + } + + if (methodParameters != null) { + dumpMethodParameters(methodParameters); + } + + // Method attributes: + // Code + // Exceptions + // RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations + // AnnotationDefault + // MethodParameters + // Synthetic + // Deprecated + // Signature + // RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations + // RuntimeVisibleTypeAnnotations, RuntimeInvisibleTypeAnnotations + } + + private void dumpMethodParameters(ResolvedJavaMethod.Parameter[] methodParameters) { + if (methodParameters == null) { + return; + } + + // MethodParameters_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u1 parameters_count; + // { u2 name_index; + // u2 access_flags; + // } parameters[parameters_count]; + // } + + classFile.writeU2(utf8("MethodParameters")); + int attributeLength = 1 + methodParameters.length * 4; + classFile.writeInt(attributeLength); + + classFile.writeU1(attributeLength); + for (ResolvedJavaMethod.Parameter p : methodParameters) { + classFile.writeU2(utf8(p.getName())); + classFile.writeU2(p.getModifiers()); + } + } + + private void dumpLineNumberTable(LineNumberTable lineNumberTable) { + if (lineNumberTable == null) { + return; + } + + // LineNumberTable_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u2 line_number_table_length; + // { u2 start_pc; + // u2 line_number; + // } line_number_table[line_number_table_length]; + // } + + classFile.writeU2(utf8("LineNumberTable")); + + int[] lineNumbers = lineNumberTable.getLineNumbers(); + int[] bcis = lineNumberTable.getBcis(); + + assert lineNumbers.length == bcis.length; + + int entryCount = lineNumbers.length; + + int attributeLength = 2 + entryCount * 4; + classFile.writeInt(attributeLength); + + classFile.writeU2(entryCount); + for (int i = 0; i < entryCount; ++i) { + classFile.writeU2(bcis[i]); + classFile.writeU2(lineNumbers[i]); + } + } + + private void dumpLocalVariableTable(LocalVariableTable localVariableTable) { + if (localVariableTable == null) { + return; + } + + // LocalVariableTable_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u2 local_variable_table_length; + // { u2 start_pc; + // u2 length; + // u2 name_index; + // u2 descriptor_index; + // u2 index; + // } local_variable_table[local_variable_table_length]; + // } + + classFile.writeU2(utf8("LocalVariableTable")); + Local[] locals = localVariableTable.getLocals(); + + int attributeLength = 2 + locals.length * 10; + classFile.writeInt(attributeLength); + + classFile.writeU2(locals.length); + for (Local local : locals) { + classFile.writeU2(local.getStartBCI()); + classFile.writeU2(local.getEndBCI() - local.getStartBCI()); + classFile.writeU2(utf8(local.getName())); + JavaType type = local.getType(); + if (type == null) { + classFile.writeU2(0); + } else { + classFile.writeU2(utf8(type.getName())); + } + classFile.writeU2(local.getSlot()); + } + } + + private void dumpCodeAttribute(ResolvedJavaMethod method) { + // Code_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u2 max_stack; + // u2 max_locals; + // u4 code_length; + // u1 code[code_length]; + // u2 exception_table_length; + // { u2 start_pc; + // u2 end_pc; + // u2 handler_pc; + // u2 catch_type; + // } exception_table[exception_table_length]; + // u2 attributes_count; + // attribute_info attributes[attributes_count]; + // } + + int startOffset = classFile.getOffset(); + classFile.writeU2(utf8("Code")); + classFile.writeInt(0xDEADBEEF); // placeholder + + classFile.writeU2(method.getMaxStackSize()); + classFile.writeU2(method.getMaxLocals()); + + byte[] code = method.getCode(); + if (code == null) { + classFile.writeInt(0); + // empty + } else { + classFile.writeInt(code.length); + classFile.writeBytes(recomputeConstantPoolIndices(method)); + } + + ExceptionHandler[] handlers = method.getExceptionHandlers(); + if (handlers == null || handlers.length == 0) { + classFile.writeU2(0); + // empty + } else { + classFile.writeU2(handlers.length); + for (ExceptionHandler eh : handlers) { + classFile.writeU2(eh.getStartBCI()); + classFile.writeU2(eh.getEndBCI()); + classFile.writeU2(eh.getHandlerBCI()); + classFile.writeU2(eh.catchTypeCPI()); + } + } + + int attributeCount = 0; + LineNumberTable lineNumberTable = method.getLineNumberTable(); + if (lineNumberTable != null) { + ++attributeCount; + } + LocalVariableTable localVariableTable = method.getLocalVariableTable(); + if (localVariableTable != null) { + ++attributeCount; + } + + classFile.writeU2(attributeCount); + + if (lineNumberTable != null) { + dumpLineNumberTable(lineNumberTable); + } + if (localVariableTable != null) { + dumpLocalVariableTable(localVariableTable); + } + + // Code attributes: + // LineNumberTable + // LocalVariableTable + // LocalVariableTypeTable + // StackMapTable + + int attributeLength = classFile.getOffset() - startOffset - 6; + classFile.patchAtOffset(startOffset + 2, () -> classFile.writeInt(attributeLength)); + } + + private byte[] recomputeConstantPoolIndices(ResolvedJavaMethod method) { + byte[] originalCode = method.getCode(); + if (originalCode == null || originalCode.length == 0) { + return originalCode; + } + + byte[] code = originalCode.clone(); + ConstantPool originalConstantPool = method.getConstantPool(); + + for (int bci = 0; bci < BytecodeStream.endBCI(code); bci = BytecodeStream.nextBCI(code, bci)) { + int bytecode = BytecodeStream.currentBC(code, bci); // also handles wide bytecodes + // @formatter:off + switch (bytecode) { + case CHECKCAST : // fall-through + case INSTANCEOF : // fall-through + case NEW : // fall-through + case ANEWARRAY : // fall-through + case MULTIANEWARRAY: { + int originalCPI = BytecodeStream.readCPI(code, bci); + int newCPI = 0; + if (originalCPI != 0) { + newCPI = classConstant(originalConstantPool.lookupType(originalCPI, bytecode)); + } + BytecodeStream.patchCPI(code, bci, newCPI); + break; + } + case LDC : // fall-through + case LDC_W : // fall-through + case LDC2_W: { + int originalCPI = BytecodeStream.readCPI(code, bci); + int newCPI = 0; + if (originalCPI != 0) { + Object constant = originalConstantPool.lookupConstant(BytecodeStream.readCPI(code, bci)); + newCPI = ldcConstant(constant); + } + BytecodeStream.patchCPI(code, bci, newCPI); + break; + } + case GETSTATIC : // fall-through + case PUTSTATIC : // fall-through + case GETFIELD : // fall-through + case PUTFIELD: { + int originalCPI = BytecodeStream.readCPI(code, bci); + int newCPI = 0; + if (originalCPI != 0) { + newCPI = fieldRef(originalConstantPool.lookupField(originalCPI, method, bytecode)); + } + BytecodeStream.patchCPI(code, bci, newCPI); + break; + } + + case INVOKEVIRTUAL : // fall-through + case INVOKESPECIAL : // fall-through + case INVOKESTATIC : // fall-through + case INVOKEINTERFACE: { + int originalCPI = BytecodeStream.readCPI(code, bci); + int newCPI = 0; + if (originalCPI != 0) { + newCPI = methodRef(originalConstantPool.lookupMethod(originalCPI, bytecode)); + } + BytecodeStream.patchCPI(code, bci, newCPI); + break; + } + + case INVOKEDYNAMIC: + // GR-55048: Cannot derive BootstrapMethods attribute and (Invoke)Dynamic CP entry. + // The VM already provides the resolved bootstrap method and appendix. + // Investigate how to persist the appendix (arbitrary object) on the constant pool. + BytecodeStream.patchCPI(code, bci, 0); + BytecodeStream.patchAppendixCPI(code, bci, 0); + break; + } + // @formatter:on + } + + return code; + } + +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/classfile/OutStream.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/classfile/OutStream.java new file mode 100644 index 000000000000..530b057eaeb3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/classfile/OutStream.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.interpreter.classfile; + +import java.io.UTFDataFormatException; +import java.io.UncheckedIOException; +import java.util.Arrays; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.util.VMError; + +@Platforms(Platform.HOSTED_ONLY.class) +final class OutStream { + + OutStream(int initialCapacity) { + this.bytes = new byte[initialCapacity]; + this.offset = 0; + } + + OutStream() { + this(32); + } + + private byte[] bytes; + private int offset; + + public void writeByte(byte value) { + ensureCapacity(offset + 1); + bytes[offset++] = value; + } + + public void writeU1(int value) { + VMError.guarantee(0 <= value && value <= 0xFF); + ensureCapacity(offset + 1); + bytes[offset++] = (byte) value; + } + + private void ensureCapacity(int capacity) { + if (bytes.length < capacity) { + int newCapacity = Math.max(capacity, bytes.length * 2 + 1); + this.bytes = Arrays.copyOf(bytes, newCapacity); + } + } + + public void writeShort(short value) { + writeByte((byte) (value >>> 8)); + writeByte((byte) value); + } + + public void writeU2(int value) { + VMError.guarantee(0 <= value && value <= 0xFFFF); + writeByte((byte) (value >>> 8)); + writeByte((byte) value); + } + + public void writeInt(int value) { + writeByte((byte) (value >>> 24)); + writeByte((byte) (value >>> 16)); + writeByte((byte) (value >>> 8)); + writeByte((byte) (value >>> 0)); + } + + public void writeLong(long value) { + writeByte((byte) (value >>> 56)); + writeByte((byte) (value >>> 48)); + writeByte((byte) (value >>> 40)); + writeByte((byte) (value >>> 32)); + writeByte((byte) (value >>> 24)); + writeByte((byte) (value >>> 16)); + writeByte((byte) (value >>> 8)); + writeByte((byte) (value >>> 0)); + } + + public void writeFloat(float value) { + writeInt(Float.floatToIntBits(value)); + } + + public void writeDouble(double value) { + writeLong(Double.doubleToLongBits(value)); + } + + public int writeUTF(String str) { + final int strlen = str.length(); + int utflen = strlen; // optimized for ASCII + + for (int i = 0; i < strlen; i++) { + int c = str.charAt(i); + if (c >= 0x80 || c == 0) { + utflen += (c >= 0x800) ? 2 : 1; + } + } + + if (utflen > 65535 || /* overflow */ utflen < strlen) { + throw new UncheckedIOException(new UTFDataFormatException(tooLongMsg(str, utflen))); + } + + writeByte((byte) ((utflen >>> 8) & 0xFF)); + writeByte((byte) ((utflen >>> 0) & 0xFF)); + + int i = 0; + for (i = 0; i < strlen; i++) { // optimized for initial run of ASCII + int c = str.charAt(i); + if (c >= 0x80 || c == 0) { + break; + } + writeByte((byte) c); + } + + for (; i < strlen; i++) { + int c = str.charAt(i); + if (c < 0x80 && c != 0) { + writeByte((byte) c); + } else if (c >= 0x800) { + writeByte((byte) (0xE0 | ((c >> 12) & 0x0F))); + writeByte((byte) (0x80 | ((c >> 6) & 0x3F))); + writeByte((byte) (0x80 | ((c >> 0) & 0x3F))); + } else { + writeByte((byte) (0xC0 | ((c >> 6) & 0x1F))); + writeByte((byte) (0x80 | ((c >> 0) & 0x3F))); + } + } + + return utflen + 2; + } + + private static String tooLongMsg(String s, int bits32) { + int slen = s.length(); + String head = s.substring(0, 8); + String tail = s.substring(slen - 8, slen); + // handle int overflow with max 3x expansion + long actualLength = slen + Integer.toUnsignedLong(bits32 - slen); + return "encoded string (" + head + "..." + tail + ") too long: " + actualLength + " bytes"; + } + + byte[] toArray() { + return Arrays.copyOf(bytes, offset); + } + + public void writeBytes(byte[] byteArray) { + for (byte b : byteArray) { + writeByte(b); + } + } + + int getOffset() { + return offset; + } + + void patchAtOffset(int targetOffset, Runnable action) { + int oldOffset = getOffset(); + try { + this.offset = targetOffset; + action.run(); + } finally { + this.offset = oldOffset; + } + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/Bits.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/Bits.java new file mode 100644 index 000000000000..10ef48d0039a --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/Bits.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.debug; + +final class Bits { + public static int setBit(int n, int bitIndex, boolean value) { + if (value) { + return n | (1 << bitIndex); + } else { + return n & ~(1 << bitIndex); + } + } + + public static boolean testBit(int n, int bitIndex) { + return (n & (1 << bitIndex)) != 0; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/DebuggerEvents.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/DebuggerEvents.java new file mode 100644 index 000000000000..be9a43973901 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/DebuggerEvents.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.debug; + +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + +public interface DebuggerEvents { + + /** + * Enable/disable events in a specific thread or globally. An event is generated for a + * particular thread if it is enabled either at the thread or global levels. + * + * @param thread thread where the event is enabled/disabled, or{@code null} to specify global + * scope (all threads) + * @param eventKind one of the events in {@link EventKind} + * @param enable true/false to enable/disable the event + */ + void setEventEnabled(Thread thread, EventKind eventKind, boolean enable); + + /** + * Checks if an event is enabled for a specified thread or globally. An event is generated + * for a particular thread if it is enabled either at the thread or global levels. + * + * @param thread where the event is enabled/disabled, or{@code null} to specify global * scope + * (all threads) + * @param eventKind one of the events in {@link EventKind} + */ + boolean isEventEnabled(Thread thread, EventKind eventKind); + + /** + * Enable/disable a breakpoint on the specified method and bytecode index. + * + * @throws IllegalArgumentException if the method doesn't have bytecodes, or the bytecode index + * is out of range, or if the bytecode index is not a valid b + */ + void toggleBreakpoint(ResolvedJavaMethod method, int bci, boolean enable); + + void toggleMethodEnterEvent(ResolvedJavaType clazz, boolean enable); + + void toggleMethodExitEvent(ResolvedJavaType clazz, boolean enable); + + /** + * Sets the {@link SteppingControl stepping information} associated with a thread. This enables + * stepping for the specified thread, enabling stepping is not enough, the stepping events must + * be enabled e.g. {@code Debugger.setEventEnabled(GLOBAL|threadId, EventKind.SINGLE_STEP, true} + * + * For line-stepping, the {@link SteppingControl#setStartingLocation(Location) starting location + * can be set}, otherwise, any location will raise the stepping event. + */ + void setSteppingFromLocation(Thread thread, int depth, int size, Location location); + + default void setStepping(Thread thread, int depth, int size) { + setSteppingFromLocation(thread, depth, size, null); + } + + /** + * Removes the {@link SteppingControl stepping information} associated with a thread. This + * cancels stepping for the current thread. + */ + void clearStepping(Thread thread); + + void setEventHandler(EventHandler eventHandler); + + EventHandler getEventHandler(); + + /** + * Returns the {@link SteppingControl stepping information} associated with a thread. + */ + SteppingControl getSteppingControl(Thread thread); +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/DebuggerEventsFeature.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/DebuggerEventsFeature.java new file mode 100644 index 000000000000..76681c21c576 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/DebuggerEventsFeature.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023, 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.interpreter.debug; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.option.HostedOptionKey; + +import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.options.OptionType; + +@Platforms(Platform.HOSTED_ONLY.class) +public final class DebuggerEventsFeature implements InternalFeature { + + public static final class DebuggerOptions { + @Option(help = "Enables experimental support for debugger events.", type = OptionType.Expert)// + public static final HostedOptionKey DebuggerEvents = new HostedOptionKey<>(false); + } + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + DebuggerEvents impl; + if (DebuggerOptions.DebuggerEvents.getValue()) { + impl = new DebuggerEventsImpl(); + } else { + impl = new DummyDebuggerEventsImpl(); + } + ImageSingletons.add(DebuggerEvents.class, impl); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/DebuggerEventsImpl.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/DebuggerEventsImpl.java new file mode 100644 index 000000000000..08de5af21358 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/DebuggerEventsImpl.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.debug; + +import com.oracle.svm.interpreter.DebuggerSupport; +import com.oracle.svm.interpreter.InterpreterUtil; +import jdk.graal.compiler.core.common.SuppressFBWarnings; +import org.graalvm.nativeimage.IsolateThread; + +import com.oracle.svm.interpreter.InterpreterDirectives; +import com.oracle.svm.interpreter.metadata.InterpreterUniverse; +import com.oracle.svm.core.thread.PlatformThreads; +import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; +import com.oracle.svm.core.threadlocal.FastThreadLocalInt; +import com.oracle.svm.core.threadlocal.FastThreadLocalObject; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; + +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + +public final class DebuggerEventsImpl implements DebuggerEvents { + + private static int globalEnabledEventsMask; + + private static final FastThreadLocalInt perThreadEnabledEventsMask = FastThreadLocalFactory.createInt("Debugger.perThreadEnabledEventsMask"); + + private static final FastThreadLocalObject perThreadSteppingControl = FastThreadLocalFactory.createObject(SteppingControl.class, "Debugger.perThreadSteppingControl"); + + private static EventHandler eventHandler; + + @Override + public void setEventEnabled(Thread thread, EventKind eventKind, boolean enable) { + if (thread == null) { + globalEnabledEventsMask = Bits.setBit(globalEnabledEventsMask, eventKind.ordinal(), enable); + } else { + IsolateThread isolateThread = PlatformThreads.getIsolateThreadUnsafe(thread); + int oldMask; + int newMask; + do { + oldMask = perThreadEnabledEventsMask.getVolatile(isolateThread); + newMask = Bits.setBit(oldMask, eventKind.ordinal(), enable); + } while (!perThreadEnabledEventsMask.compareAndSet(isolateThread, oldMask, newMask)); + } + } + + /** + * Returns events enabled for the specified thread, including the globally enabled events. If + * the thread is null, then returns events enabled globally. + */ + private static int enabledEventsMask(Thread thread) { + if (thread == null) { + return globalEnabledEventsMask; + } else { + IsolateThread isolateThread = PlatformThreads.getIsolateThreadUnsafe(thread); + return (globalEnabledEventsMask | perThreadEnabledEventsMask.get(isolateThread)); + } + } + + @Override + public boolean isEventEnabled(Thread thread, EventKind eventKind) { + return Bits.testBit(enabledEventsMask(thread), eventKind.ordinal()); + } + + @Override + @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", justification = "Intentional.") + public void setEventHandler(EventHandler eventHandler) { + DebuggerEventsImpl.eventHandler = eventHandler; + } + + @Override + public EventHandler getEventHandler() { + return eventHandler; + } + + @Override + public void toggleBreakpoint(ResolvedJavaMethod method, int bci, boolean enable) { + InterpreterResolvedJavaMethod interpreterMethod = (InterpreterResolvedJavaMethod) method; + interpreterMethod.ensureCanSetBreakpointAt(bci); + interpreterMethod.toggleBreakpoint(bci, enable); + InterpreterUtil.traceInterpreter(enable ? "Setting" : "Unsetting") + .string(" breakpoint for method=") + .string(interpreterMethod.toString()) + .string(" at bci=").signed(bci) + .newline(); + if (enable) { + // GR-54095: Make method and all the callers that inline it run in the + // interpreter. + // This operation cannot be nested, methods need to keep counters. + // Ignore token for now. + InterpreterDirectives.ensureInterpreterExecution(interpreterMethod); + } + } + + @Override + public void toggleMethodEnterEvent(ResolvedJavaType clazz, boolean enable) { + toggleDeclaredMethodsInterpreterExecution(clazz, enable); + ((InterpreterResolvedJavaType) clazz).toggleMethodEnterEvent(enable); + } + + @Override + public void toggleMethodExitEvent(ResolvedJavaType clazz, boolean enable) { + toggleDeclaredMethodsInterpreterExecution(clazz, enable); + ((InterpreterResolvedJavaType) clazz).toggleMethodExitEvent(enable); + } + + private static void toggleDeclaredMethodsInterpreterExecution(ResolvedJavaType clazz, boolean enable) { + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + for (ResolvedJavaMethod rMethod : universe.getAllDeclaredMethods(clazz)) { + InterpreterResolvedJavaMethod method = (InterpreterResolvedJavaMethod) rMethod; + Object token = method.getInterpreterExecToken(); + if (enable) { + if (token == null) { + token = InterpreterDirectives.ensureInterpreterExecution(method); + method.setInterpreterExecToken(token); + } + } else if (token != null) { + // GR-54095: The undoExecutionOperation() can not be nested + // InterpreterDirectives.undoExecutionOperation(token); + method.setInterpreterExecToken(null); + } + } + } + + @Override + public SteppingControl getSteppingControl(Thread thread) { + VMError.guarantee(thread != null); + + IsolateThread isolateThread = PlatformThreads.getIsolateThreadUnsafe(thread); + return perThreadSteppingControl.get(isolateThread); + } + + @Override + public void setSteppingFromLocation(Thread thread, int depth, int size, Location location) { + VMError.guarantee(thread != null); + + IsolateThread isolateThread = PlatformThreads.getIsolateThreadUnsafe(thread); + SteppingControl value = new SteppingControl(thread, depth, size); + perThreadSteppingControl.set(isolateThread, value); + + if (location != null) { + value.setStartingLocation(location); + } + } + + @Override + public void clearStepping(Thread thread) { + VMError.guarantee(thread != null); + IsolateThread isolateThread = PlatformThreads.getIsolateThreadUnsafe(thread); + perThreadSteppingControl.set(isolateThread, null); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/DummyDebuggerEventsImpl.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/DummyDebuggerEventsImpl.java new file mode 100644 index 000000000000..bfb218823303 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/DummyDebuggerEventsImpl.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.debug; + +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.Interpreter; + +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + +/** + * Allows the {@link Interpreter} to run without any debugger hooks. + */ +final class DummyDebuggerEventsImpl implements DebuggerEvents { + @Override + public void setEventEnabled(Thread thread, EventKind eventKind, boolean enable) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public boolean isEventEnabled(Thread thread, EventKind eventKind) { + return false; + } + + @Override + public void setEventHandler(EventHandler eventHandler) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public EventHandler getEventHandler() { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public void toggleBreakpoint(ResolvedJavaMethod method, int bci, boolean enable) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public void toggleMethodEnterEvent(ResolvedJavaType clazz, boolean enable) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public void toggleMethodExitEvent(ResolvedJavaType clazz, boolean enable) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public SteppingControl getSteppingControl(Thread thread) { + return null; + } + + @Override + public void setSteppingFromLocation(Thread thread, int depth, int size, Location location) { + throw VMError.intentionallyUnimplemented(); + } + + @Override + public void clearStepping(Thread thread) { + throw VMError.intentionallyUnimplemented(); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/EventHandler.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/EventHandler.java new file mode 100644 index 000000000000..c5271301be91 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/EventHandler.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.debug; + +import jdk.vm.ci.meta.ResolvedJavaMethod; + +public interface EventHandler { + /** + * An event at a program location. The bit flags may represent following event kinds: + * {@link EventKind#BREAKPOINT}, {@link EventKind#SINGLE_STEP}, {@link EventKind#METHOD_ENTRY}, + * {@link EventKind#METHOD_EXIT}, {@link EventKind#METHOD_EXIT_WITH_RETURN_VALUE}. + */ + void onEventAt(Thread thread, ResolvedJavaMethod method, int bci, Object result, int eventKindFlags); +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/EventKind.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/EventKind.java new file mode 100644 index 000000000000..7a8fcc579c17 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/EventKind.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.debug; + +/** + * The EventKind from the JDWP protocol. There is a bridge copy + * com.oracle.svm.jdwp.bridge.EventKind, the enums are exchanged via ordinal number + * ({@link EventKind#ordinal()} and {@link EventKind#fromOrdinal(int)}). + */ +public enum EventKind { + + SINGLE_STEP(1), + BREAKPOINT(2), + FRAME_POP(3), + EXCEPTION(4), + USER_DEFINED(5), + THREAD_START(6), + THREAD_DEATH(7), + CLASS_PREPARE(8), + CLASS_UNLOAD(9), + CLASS_LOAD(10), + FIELD_ACCESS(20), + FIELD_MODIFICATION(21), + EXCEPTION_CATCH(30), + METHOD_ENTRY(40), + METHOD_EXIT(41), + METHOD_EXIT_WITH_RETURN_VALUE(42), + MONITOR_CONTENDED_ENTER(43), + MONITOR_CONTENDED_ENTERED(44), + MONITOR_WAIT(45), + MONITOR_WAITED(46), + VM_START(90), + VM_DEATH(99), + VM_DISCONNECTED(100); + + EventKind(int id) { + assert 0 < id && id < 127 : id; + } + + private static final EventKind[] VALUES = EventKind.values(); + + public static EventKind fromOrdinal(int ordinal) { + return VALUES[ordinal]; + } + + /** + * Flag of the event used by + * {@link EventHandler#onEventAt(Thread, jdk.vm.ci.meta.ResolvedJavaMethod, int, Object, int)}. + * The only events on the same thread and at the same location that can be combined to bit flags + * are: {@link #BREAKPOINT}, {@link #SINGLE_STEP}, {@link #METHOD_ENTRY}, {@link #METHOD_EXIT}, + * {@link #METHOD_EXIT_WITH_RETURN_VALUE} + */ + public int getFlag() { + assert ordinal() < 32 : "Flag overflow, ordinal = " + ordinal(); + int flag = 1 << ordinal(); + return flag; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/Location.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/Location.java new file mode 100644 index 000000000000..a89986a9c8e3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/Location.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.debug; + +import jdk.vm.ci.meta.JavaMethod; +import jdk.vm.ci.meta.LineNumberTable; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +import static com.oracle.svm.core.code.FrameSourceInfo.LINENUMBER_NATIVE; +import static com.oracle.svm.core.code.FrameSourceInfo.LINENUMBER_UNKNOWN; + +public interface Location { + int LINENUMBER_MARKER = LINENUMBER_NATIVE; + + JavaMethod method(); + + int bci(); + + int lineNumber(); + + static Location create(ResolvedJavaMethod method, int bci) { + return create(method, bci, LINENUMBER_MARKER); + } + + static Location create(ResolvedJavaMethod method, int bci, int lineNo) { + return new Location() { + private int lineNumber = lineNo; + + @Override + public JavaMethod method() { + return method; + } + + @Override + public int bci() { + return bci; + } + + @Override + public int lineNumber() { + if (lineNumber != LINENUMBER_MARKER) { + return lineNumber; + } + LineNumberTable lineNumberTable = method.getLineNumberTable(); + if (lineNumberTable != null) { + lineNumber = lineNumberTable.getLineNumber(bci); + } else { + lineNumber = LINENUMBER_UNKNOWN; + } + return lineNumber; + } + }; + } + + /** + * Returns the line number associated with a BCI, or -1 (LINENUMBER_UNKNOWN) if the method is + * native or no line information is available. + */ + static int getLineNumber(ResolvedJavaMethod method, int bci) { + if (method.isNative()) { + return LINENUMBER_UNKNOWN; + } + LineNumberTable lineNumberTable = method.getLineNumberTable(); + if (lineNumberTable != null) { + return lineNumberTable.getLineNumber(bci); + } + return LINENUMBER_UNKNOWN; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/SteppingControl.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/SteppingControl.java new file mode 100644 index 000000000000..92e54c6360a3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/debug/SteppingControl.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.interpreter.debug; + +import com.oracle.svm.core.util.VMError; + +import jdk.vm.ci.meta.ResolvedJavaMethod; + +public final class SteppingControl { + + /** + * Step into any newly pushed frames. + */ + public static final int STEP_INTO = 1; + /** + * Step over any newly pushed frames. + */ + public static final int STEP_OVER = 2; + /** + * Step out of the current frame. + */ + public static final int STEP_OUT = 3; + + /** + * Step to the next available location. + */ + public static final int STEP_MIN = -1; + /** + * Step to the next location on a different line. + */ + public static final int STEP_LINE = -2; + + private final Thread thread; + private final int depth; + private final int size; + + private int currentFrameDepth; + + // Location where the stepping request was set, required only to check line numbers when + // STEP_LINE is specified. + private Location startingLocation; + + public boolean isActiveAtCurrentFrameDepth() { + switch (getDepth()) { + case STEP_OUT: + return currentFrameDepth < 0; // fire events only on outer frames + case STEP_OVER: + return currentFrameDepth <= 0; // fire events in the starting and outer frames + case STEP_INTO: + return true; // fire events in all frames + default: + throw VMError.shouldNotReachHere("unknown stepping depth"); + } + } + + public SteppingControl(Thread thread, int depth, int size) { + VMError.guarantee(size == STEP_MIN || size == STEP_LINE); + VMError.guarantee(depth == STEP_INTO || depth == STEP_OVER || depth == STEP_OUT); + this.thread = thread; + this.depth = depth; + this.size = size; + this.currentFrameDepth = 0; + } + + public int getSize() { + return size; + } + + public int getDepth() { + return depth; + } + + public Thread getThread() { + return thread; + } + + public void setStartingLocation(Location location) { + this.startingLocation = location; + } + + public boolean withinSameLine(ResolvedJavaMethod method, int bci) { + if (startingLocation == null) { + // No starting location set, consider it new line hit. + return false; + } + if (!method.equals(startingLocation.method())) { + return false; + } + // Might need to handle case when no line information is available + int startingLine = startingLocation.lineNumber(); + int currentLine = Location.getLineNumber(method, bci); + return (currentLine == startingLine); + } + + public void popFrame() { + --currentFrameDepth; + } + + public void pushFrame() { + ++currentFrameDepth; + } + +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/ArgFilesOption.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/ArgFilesOption.java new file mode 100644 index 000000000000..22f7062fc760 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/ArgFilesOption.java @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Utility to parse @argFile arguments. Copied and slightly adapted from + * {@code com.oracle.svm.driver.ArgFilesOptionPreprocessor}. + * + * GR-55150: Merge back into com.oracle.svm.driver.ArgFilesOptionPreprocessor. + * + * @see @argument + * files + */ +public final class ArgFilesOption { + + private static final String DISABLE_AT_FILES_OPTION = "--disable-@files"; + + private boolean disableAtFiles = false; + + public List process(String currentArg) { + switch (currentArg) { + case DISABLE_AT_FILES_OPTION: + disableAtFiles = true; + return List.of(); + } + + if (!disableAtFiles && currentArg.startsWith("@")) { + Path argFile = Paths.get(currentArg.substring(1)); + return readArgFile(argFile); + } + + return List.of(currentArg); + } + + // Ported from JDK11's java.base/share/native/libjli/args.c + private enum PARSER_STATE { + FIND_NEXT, + IN_COMMENT, + IN_QUOTE, + IN_ESCAPE, + SKIP_LEAD_WS, + IN_TOKEN + } + + private static class CTX_ARGS { + PARSER_STATE state; + int cptr; + int eob; + char quoteChar; + List parts; + String options; + } + + // Ported from JDK11's java.base/share/native/libjli/args.c + public List readArgFile(Path file) { + List arguments = new ArrayList<>(); + + String options = null; + try { + options = new String(Files.readAllBytes(file), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot read argument file '" + file + "'"); + } + + CTX_ARGS ctx = new CTX_ARGS(); + ctx.state = PARSER_STATE.FIND_NEXT; + ctx.parts = new ArrayList<>(4); + ctx.quoteChar = '"'; + ctx.cptr = 0; + ctx.eob = options.length(); + ctx.options = options; + + String token = nextToken(ctx); + while (token != null) { + addArg(arguments, token); + token = nextToken(ctx); + } + + // remaining partial token + if (ctx.state == PARSER_STATE.IN_TOKEN || ctx.state == PARSER_STATE.IN_QUOTE) { + if (ctx.parts.size() != 0) { + token = String.join("", ctx.parts); + addArg(arguments, token); + } + } + return arguments; + } + + private void addArg(List args, String arg) { + Objects.requireNonNull(arg); + if (DISABLE_AT_FILES_OPTION.equals(arg)) { + disableAtFiles = true; + } else { + args.add(arg); + } + } + + // Ported from JDK11's java.base/share/native/libjli/args.c + @SuppressWarnings("fallthrough") + private static String nextToken(CTX_ARGS ctx) { + int nextc = ctx.cptr; + int eob = ctx.eob; + int anchor = nextc; + String token; + + for (; nextc < eob; nextc++) { + char ch = ctx.options.charAt(nextc); + + // Skip white space characters + if (ctx.state == PARSER_STATE.FIND_NEXT || ctx.state == PARSER_STATE.SKIP_LEAD_WS) { + while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f') { + nextc++; + if (nextc >= eob) { + return null; + } + ch = ctx.options.charAt(nextc); + } + ctx.state = (ctx.state == PARSER_STATE.FIND_NEXT) ? PARSER_STATE.IN_TOKEN : PARSER_STATE.IN_QUOTE; + anchor = nextc; + // Deal with escape sequences + } else if (ctx.state == PARSER_STATE.IN_ESCAPE) { + // concatenation directive + if (ch == '\n' || ch == '\r') { + ctx.state = PARSER_STATE.SKIP_LEAD_WS; + } else { + // escaped character + String escaped; + switch (ch) { + case 'n': + escaped = "\n"; + break; + case 'r': + escaped = "\r"; + break; + case 't': + escaped = "\t"; + break; + case 'f': + escaped = "\f"; + break; + default: + escaped = String.valueOf(ch); + break; + } + ctx.parts.add(escaped); + ctx.state = PARSER_STATE.IN_QUOTE; + } + // anchor to next character + anchor = nextc + 1; + continue; + // ignore comment to EOL + } else if (ctx.state == PARSER_STATE.IN_COMMENT) { + while (ch != '\n' && ch != '\r') { + nextc++; + if (nextc >= eob) { + return null; + } + ch = ctx.options.charAt(nextc); + } + anchor = nextc + 1; + ctx.state = PARSER_STATE.FIND_NEXT; + continue; + } + + assert (ctx.state != PARSER_STATE.IN_ESCAPE); + assert (ctx.state != PARSER_STATE.FIND_NEXT); + assert (ctx.state != PARSER_STATE.SKIP_LEAD_WS); + assert (ctx.state != PARSER_STATE.IN_COMMENT); + + switch (ch) { + case ' ': + case '\t': + case '\f': + if (ctx.state == PARSER_STATE.IN_QUOTE) { + continue; + } + // fall through + case '\n': + case '\r': + if (ctx.parts.size() == 0) { + token = ctx.options.substring(anchor, nextc); + } else { + ctx.parts.add(ctx.options.substring(anchor, nextc)); + token = String.join("", ctx.parts); + ctx.parts = new ArrayList<>(); + } + ctx.cptr = nextc + 1; + ctx.state = PARSER_STATE.FIND_NEXT; + return token; + case '#': + if (ctx.state == PARSER_STATE.IN_QUOTE) { + continue; + } + ctx.state = PARSER_STATE.IN_COMMENT; + anchor = nextc + 1; + break; + case '\\': + if (ctx.state != PARSER_STATE.IN_QUOTE) { + continue; + } + ctx.parts.add(ctx.options.substring(anchor, nextc)); + ctx.state = PARSER_STATE.IN_ESCAPE; + // anchor after backslash character + anchor = nextc + 1; + break; + case '\'': + case '"': + if (ctx.state == PARSER_STATE.IN_QUOTE && ctx.quoteChar != ch) { + // not matching quote + continue; + } + // partial before quote + if (anchor != nextc) { + ctx.parts.add(ctx.options.substring(anchor, nextc)); + } + // anchor after quote character + anchor = nextc + 1; + if (ctx.state == PARSER_STATE.IN_TOKEN) { + ctx.quoteChar = ch; + ctx.state = PARSER_STATE.IN_QUOTE; + } else { + ctx.state = PARSER_STATE.IN_TOKEN; + } + break; + default: + break; + } + } + + assert (nextc == eob); + // Only need partial token, not comment or whitespaces + if (ctx.state == PARSER_STATE.IN_TOKEN || ctx.state == PARSER_STATE.IN_QUOTE) { + if (anchor < nextc) { + // not yet return until end of stream, we have part of a token. + ctx.parts.add(ctx.options.substring(anchor, nextc)); + } + } + return null; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/CheckedReader.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/CheckedReader.java new file mode 100644 index 000000000000..27ca9f338207 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/CheckedReader.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + +public final class CheckedReader { + + private final SymbolicRefs symbolicRefs; + + public CheckedReader(SymbolicRefs symbolicRefs) { + this.symbolicRefs = symbolicRefs; + } + + public ResolvedJavaType readTypeRef(Packet.Reader input) throws JDWPException { + return readTypeRef(input, false); + } + + public long readTypeRefId(Packet.Reader input, boolean allowNull) throws JDWPException { + long typeRefId = input.readLong(); + if (typeRefId == 0L) { + if (!allowNull) { + throw JDWPException.raise(ErrorCode.INVALID_CLASS); + } + return 0L; + } + symbolicRefs.toResolvedJavaType(typeRefId); // may throw JDWPException + return typeRefId; + } + + public long readMethodRefId(Packet.Reader input, boolean allowNull) throws JDWPException { + long methodRefId = input.readLong(); + if (methodRefId == 0L) { + if (!allowNull) { + throw JDWPException.raise(ErrorCode.INVALID_METHODID); + } + return 0L; + } + symbolicRefs.toResolvedJavaMethod(methodRefId); // may throw JDWPException + return methodRefId; + } + + public long readFieldRefId(Packet.Reader input, boolean allowNull) throws JDWPException { + long fieldRefId = input.readLong(); + if (fieldRefId == 0L) { + if (!allowNull) { + throw JDWPException.raise(ErrorCode.INVALID_FIELDID); + } + return 0L; + } + symbolicRefs.toResolvedJavaField(fieldRefId); // may throw JDWPException + return fieldRefId; + } + + public ResolvedJavaType readTypeRef(Packet.Reader input, boolean allowNull) throws JDWPException { + long typeRefId = readTypeRefId(input, allowNull); + if (typeRefId == 0L && allowNull) { + return null; + } + return symbolicRefs.toResolvedJavaType(typeRefId); + } + + public ResolvedJavaMethod readMethodRef(Packet.Reader input) throws JDWPException { + return readMethodRef(input, false); + } + + public ResolvedJavaMethod readMethodRef(Packet.Reader input, boolean allowNull) throws JDWPException { + long methodRefId = readMethodRefId(input, allowNull); + if (methodRefId == 0L) { + return null; + } + return symbolicRefs.toResolvedJavaMethod(methodRefId); + } + + public ResolvedJavaField readFieldRef(Packet.Reader input, boolean allowNull) throws JDWPException { + long fieldRefId = readTypeRefId(input, allowNull); + if (fieldRefId == 0L) { + return null; + } + return symbolicRefs.toResolvedJavaField(fieldRefId); + } + + public ResolvedJavaField readFieldRef(Packet.Reader input) throws JDWPException { + return readFieldRef(input, false); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/ClassStatusConstants.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/ClassStatusConstants.java new file mode 100644 index 000000000000..1f56e59188a9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/ClassStatusConstants.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +/** + * Constants to mimic the status of classes in a VM. + */ +public final class ClassStatusConstants { + public static final int LOADED = 0; + public static final int VERIFIED = 1; + public static final int PREPARED = 2; + public static final int INITIALIZED = 4; + public static final int ERROR = 8; + + private ClassStatusConstants() { + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/DebugOptions.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/DebugOptions.java new file mode 100644 index 000000000000..7c37c2e7438e --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/DebugOptions.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public final class DebugOptions { + + /** + * Supported JDWP options. + */ + public record Options(String jdwpOptions, + String additionalOptions, + boolean help, + boolean server, + boolean suspend, + boolean quiet, + int timeout, + String address, + String host, + int port, + String transport, + String mode, + String libraryPath, + String vmOptions, + boolean tracing, + Map unknownOptions) { + + public boolean hasUnknownOptions() { + return unknownOptions != null && !unknownOptions.isEmpty(); + } + } + + private static Map parseKeyValues(String text) { + Map options = new HashMap<>(); + for (int index = 0; index < text.length();) { + int equalsIndex = text.indexOf('=', index); + if (equalsIndex < 0) { + throw new IllegalArgumentException("Invalid key=value at index " + index); + } + String key = text.substring(index, equalsIndex); + int nextIndex = text.indexOf(',', equalsIndex + 1); + if (nextIndex < 0) { + nextIndex = text.length(); + } + String value = text.substring(equalsIndex + 1, nextIndex); + if (options.containsKey(key)) { + throw new IllegalArgumentException("Repeated key " + key); + } + options.put(key, value); + index = nextIndex + 1; + } + return options; + } + + private static String checkAllowedValues(String key, String value, String... allowedValues) { + for (String allowedValue : allowedValues) { + if (Objects.equals(value, allowedValue)) { + return value; + } + } + throw new IllegalArgumentException("Invalid entry " + key + "=" + value + " allowed values: " + String.join(", ", allowedValues)); + } + + private static int checkInt(String key, String value, int lowerBound, int upperBound) { + int intValue; + try { + intValue = Integer.parseInt(value); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("Invalid entry " + key + "=" + value + " not an integer"); + } + if (!(lowerBound <= intValue && intValue <= upperBound)) { + throw new IllegalArgumentException("Invalid entry " + key + "=" + value + " must be within [" + lowerBound + ", " + upperBound + "]"); + } + return intValue; + } + + public static Options parse(String jdwpOptions, boolean throwIfUnknown) { + return parse(jdwpOptions, null, throwIfUnknown, false); + } + + /** + * Parses JDWP options. This method only validates common known options e.g. doesn't check if + * required options are present. + * + * @param throwIfUnknown if true, throws {@link IllegalArgumentException} if there's an + * unknown/unsupported options + * @throws IllegalArgumentException if options cannot be parsed (malformed) + */ + public static Options parse(String jdwpOptions, String additionalOptions, boolean throwIfUnknown, boolean tracing) { + + boolean help = false; + boolean server = false; // n + boolean suspend = true; // y + boolean quiet = false; // n + int timeout = 0; // no timeout + String address = null; + String host = null; + int port = 0; + String transport = null; + String mode = "native"; + String libraryPath = null; + String vmOptions = null; + + Map unknownOptions = new HashMap<>(); + + help = "help".equals(jdwpOptions); + + String combinedOptions = help ? "" : jdwpOptions; + if (additionalOptions != null && !additionalOptions.isEmpty()) { + if (!combinedOptions.isEmpty()) { + combinedOptions += ","; + } + combinedOptions += additionalOptions; + } + Map keyValues = parseKeyValues(combinedOptions); + + for (Map.Entry entry : keyValues.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + switch (key) { + case "server" -> server = checkAllowedValues(key, value, "y", "n").equals("y"); + case "suspend" -> suspend = checkAllowedValues(key, value, "y", "n").equals("y"); + case "quiet" -> quiet = checkAllowedValues(key, value, "y", "n").equals("y"); + case "timeout" -> timeout = checkInt(key, value, 0, Integer.MAX_VALUE); + // GR-55160: Revisit adding support for dt_shmem (on Windows). + case "transport" -> transport = checkAllowedValues(key, value, "dt_socket"); + case "address" -> { + String[] parts = value.split(":"); + String inputHost = null; + String inputPort; + if (parts.length == 1) { + inputPort = parts[0]; + } else if (parts.length == 2) { + inputHost = parts[0]; + inputPort = parts[1]; + } else { + throw new IllegalArgumentException("Invalid entry " + key + "=" + value + " not a '[host:]port' pair"); + } + address = value; + host = inputHost; + port = checkInt(key, inputPort, 0, 65535); + } + case "mode" -> { + String[] parts = value.split(":", 2); + mode = checkAllowedValues(key, parts[0], "native", "jvm"); + if (parts.length > 1) { + // native:/path/to/libsvmdebugger.so + // jvm:/path/to/libjvm.so + libraryPath = parts[1]; + } else { + libraryPath = null; + } + } + case "vm.options" -> { + // VM options passed to the JVM or native isolate where the JDWP server + // runs. + vmOptions = value; + } + default -> { + if (throwIfUnknown) { + throw new IllegalArgumentException("Unknown/unsupported option: " + key); + } else { + unknownOptions.put(key, value); + } + } + } + } + + return new Options(jdwpOptions, additionalOptions, help, server, suspend, quiet, timeout, address, host, port, transport, mode, libraryPath, vmOptions, tracing, unknownOptions); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/ErrorCode.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/ErrorCode.java new file mode 100644 index 000000000000..273c85c74301 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/ErrorCode.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +//@formatter:off +public enum ErrorCode { + NONE(0, "No error has occurred."), + INVALID_THREAD(10, "The thread is null or not a valid thread."), + INVALID_THREAD_GROUP(11, "Thread group invalid."), + INVALID_PRIORITY(12, "Invalid priority."), + THREAD_NOT_SUSPENDED(13, "If the specified thread has not been suspended by an event."), + THREAD_SUSPENDED(14, "Thread already suspended."), + THREAD_NOT_ALIVE(15, "Not used."), + INVALID_OBJECT(20, "If this reference type has been unloaded and garbage collected."), + INVALID_CLASS(21, "Invalid class."), + CLASS_NOT_PREPARED(22, "Class has been loaded but not yet prepared."), + INVALID_METHODID(23, "Invalid method."), + INVALID_LOCATION(24, "Invalid location."), + INVALID_FIELDID(25, "Invalid field."), + INVALID_FRAMEID(30, "Invalid jframeID."), + NO_MORE_FRAMES(31, "There are no more Java or JNI frames on the call stack."), + OPAQUE_FRAME(32, "Information about the frame is not available (e.g. native frame) or the target VM is unable to perform an operation on the thread's current frame."), + NOT_CURRENT_FRAME(33, "Operation can only be performed on current frame."), + TYPE_MISMATCH(34, "The variable is not an appropriate type for the function used."), + INVALID_SLOT(35, "Invalid slot."), + DUPLICATE(40, "Item already set."), + NOT_FOUND(41, "Desired element not found."), + INVALID_MODULE(42, "Invalid module."), + INVALID_MONITOR(50, "Invalid monitor."), + NOT_MONITOR_OWNER(51, "This thread doesn't own the monitor."), + INTERRUPT(52, "The call has been interrupted before completion."), + INVALID_CLASS_FORMAT(60, "The virtual machine attempted to read a class file and determined that the file is malformed or otherwise cannot be interpreted as a class file."), + CIRCULAR_CLASS_DEFINITION(61, "A circularity has been detected while initializing a class."), + FAILS_VERIFICATION(62, "The verifier detected that a class file, though well formed, contained some sort of internal inconsistency or security problem."), + ADD_METHOD_NOT_IMPLEMENTED(63, "Adding methods has not been implemented."), + SCHEMA_CHANGE_NOT_IMPLEMENTED(64, "Schema change has not been implemented."), + INVALID_TYPESTATE(65, "The state of the thread has been modified, and is now inconsistent."), + HIERARCHY_CHANGE_NOT_IMPLEMENTED(66, "A direct superclass is different for the new class version, or the set of directly implemented interfaces is different and canUnrestrictedlyRedefineClasses is false."), + DELETE_METHOD_NOT_IMPLEMENTED(67, "The new class version does not declare a method declared in the old class version and canUnrestrictedlyRedefineClasses is false."), + UNSUPPORTED_VERSION(68, "A class file has a version number not supported by this VM."), + NAMES_DONT_MATCH(69, "The class name defined in the new class file is different from the name in the old class object."), + CLASS_MODIFIERS_CHANGE_NOT_IMPLEMENTED(70, "The new class version has different modifiers and canUnrestrictedlyRedefineClasses is false."), + METHOD_MODIFIERS_CHANGE_NOT_IMPLEMENTED(71, "A method in the new class version has different modifiers than its counterpart in the old class version and canUnrestrictedlyRedefineClasses is false."), + CLASS_ATTRIBUTE_CHANGE_NOT_IMPLEMENTED(72, "The new class version has a different NestHost, NestMembers, PermittedSubclasses, or Record class attribute and canUnrestrictedlyRedefineClasses is false."), + NOT_IMPLEMENTED(99, "The functionality is not implemented in this virtual machine."), + NULL_POINTER(100, "Invalid pointer."), + ABSENT_INFORMATION(101, "Desired information is not available."), + INVALID_EVENT_TYPE(102, "The specified event type id is not recognized."), + ILLEGAL_ARGUMENT(103, "Illegal argument."), + OUT_OF_MEMORY(110, "The function needed to allocate memory and no more memory was available for allocation."), + ACCESS_DENIED(111, "Debugging has not been enabled in this virtual machine. JVMTI cannot be used."), + VM_DEAD(112, "The virtual machine is not running."), + INTERNAL(113, "An unexpected internal error has occurred."), + UNATTACHED_THREAD(115, "The thread being used to call this function is not attached to the virtual machine. Calls must be made from attached threads."), + INVALID_TAG(500, "object type id or class tag."), + ALREADY_INVOKING(502, "Previous invoke not complete."), + INVALID_INDEX(503, "Index is invalid."), + INVALID_LENGTH(504, "The length is invalid."), + INVALID_STRING(506, "The string is invalid."), + INVALID_CLASS_LOADER(507, "The class loader is invalid."), + INVALID_ARRAY(508, "The array is invalid."), + TRANSPORT_LOAD(509, "Unable to load the transport."), + TRANSPORT_INIT(510, "Unable to initialize the transport."), + NATIVE_METHOD(511, ""), + INVALID_COUNT(512, "The count is invalid."); + + private final int errorCode; + private final String message; + + ErrorCode(int errorCode, String message) { + this.errorCode = errorCode; + this.message = message; + } + + public int value() { + return errorCode; + } + + public String getMessage() { + return message; + } +} +//@formatter:on diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/EventHandlerBridge.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/EventHandlerBridge.java new file mode 100644 index 000000000000..13428b540cb7 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/EventHandlerBridge.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023, 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.jdwp.bridge; + +public interface EventHandlerBridge { + void onEventAt(long threadId, long classId, byte typeTag, long methodId, int bci, byte resultTag, long resultPrimitiveOrId, int eventKindFlags); + + void onVMDeath(); + + void onThreadStart(long threadId); + + void onThreadDeath(long threadId); +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/EventKind.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/EventKind.java new file mode 100644 index 000000000000..7ad723c22059 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/EventKind.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +/** + * The EventKind from the JDWP protocol. There is a interpreter copy + * com.oracle.svm.interpreter.debug.EventKind, the enums are exchanged via ordinal + * number ({@link EventKind#ordinal()} and {@link EventKind#fromOrdinal(int)}). + */ +public enum EventKind { + + SINGLE_STEP(1), + BREAKPOINT(2), + FRAME_POP(3), + EXCEPTION(4), + USER_DEFINED(5), + THREAD_START(6), + THREAD_DEATH(7), + CLASS_PREPARE(8), + CLASS_UNLOAD(9), + CLASS_LOAD(10), + FIELD_ACCESS(20), + FIELD_MODIFICATION(21), + EXCEPTION_CATCH(30), + METHOD_ENTRY(40), + METHOD_EXIT(41), + METHOD_EXIT_WITH_RETURN_VALUE(42), + MONITOR_CONTENDED_ENTER(43), + MONITOR_CONTENDED_ENTERED(44), + MONITOR_WAIT(45), + MONITOR_WAITED(46), + VM_START(90), + VM_DEATH(99), + VM_DISCONNECTED(100); + + private final byte eventId; + + EventKind(int id) { + assert 0 < id && id < 127 : id; + this.eventId = (byte) id; + } + + private static final EventKind[] VALUES = EventKind.values(); + + public byte getEventId() { + return eventId; + } + + public static EventKind fromOrdinal(int ordinal) { + return VALUES[ordinal]; + } + + public static EventKind of(byte eventId) { + // find the EventKind using binary search: + int low = 0; + int high = VALUES.length - 1; + while (low <= high) { + int mid = (low + high) >>> 1; + EventKind midKind = VALUES[mid]; + if (midKind.eventId < eventId) { + low = mid + 1; + } else if (midKind.eventId > eventId) { + high = mid - 1; + } else { + return midKind; + } + } + return null; + } + + /** + * Test if the bit flags contain this event kind. + */ + public boolean matchesFlag(int flags) { + assert ordinal() < 32 : "Flag overflow, ordinal = " + ordinal(); + int myFlag = 1 << ordinal(); + return (flags & myFlag) != 0; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/FrameId.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/FrameId.java new file mode 100644 index 000000000000..6a098419ab08 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/FrameId.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023, 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.jdwp.bridge; + +public final class FrameId { + private static final int FRAME_DEPTH_BITS = 16; + private static final int FRAME_DEPTH_MASK = (1 << FRAME_DEPTH_BITS) - 1; + + public static int getFrameDepth(long frameId) { + return (int) (frameId & FRAME_DEPTH_MASK); + } + + public static long getFrameGeneration(long frameId) { + return frameId >>> FRAME_DEPTH_BITS; + } + + public static long createFrameId(long frameGeneration, int frameDepth) { + assert frameDepth < (1 << FRAME_DEPTH_BITS); + assert frameGeneration < (1L << (Long.SIZE - FRAME_DEPTH_BITS)); + return (frameGeneration << FRAME_DEPTH_BITS) | frameDepth; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/HSToNativeJDWPBridge.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/HSToNativeJDWPBridge.java new file mode 100644 index 000000000000..555cc687c0b9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/HSToNativeJDWPBridge.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +import com.oracle.svm.jdwp.bridge.nativebridge.NativeIsolate; +import com.oracle.svm.jdwp.bridge.nativebridge.NativeObject; + +// Originally generated with nativebridge +// @GenerateHotSpotToNativeBridge(jniConfig = JDWPJNIConfig.class, include = ResidentJDWPFeatureEnabled.class) +public abstract class HSToNativeJDWPBridge extends NativeObject implements JDWPBridge { + public HSToNativeJDWPBridge(NativeIsolate isolate, long objectHandle) { + super(isolate, objectHandle); + } + + public static HSToNativeJDWPBridge createHSToNative(NativeIsolate isolate, long objectHandle) { + return HSToNativeJDWPBridgeGen.createHSToNative(isolate, objectHandle); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/HSToNativeJDWPBridgeGen.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/HSToNativeJDWPBridgeGen.java new file mode 100644 index 000000000000..ccc2262c8271 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/HSToNativeJDWPBridgeGen.java @@ -0,0 +1,868 @@ +/* + * 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. + */ +//Checkstyle: stop +// @formatter:off +package com.oracle.svm.jdwp.bridge; + +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JByteArray; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JClass; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JIntArray; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JLongArray; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JNIEnv; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JString; +import com.oracle.svm.jdwp.bridge.jniutils.JNIMethodScope; +import com.oracle.svm.jdwp.bridge.jniutils.JNIUtil; +import com.oracle.svm.jdwp.bridge.nativebridge.BinaryInput; +import com.oracle.svm.jdwp.bridge.nativebridge.BinaryMarshaller; +import com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput; +import com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput.CCharPointerBinaryOutput; +import com.oracle.svm.jdwp.bridge.nativebridge.ForeignException; +import com.oracle.svm.jdwp.bridge.nativebridge.JNIConfig; +import com.oracle.svm.jdwp.bridge.nativebridge.NativeIsolate; +import com.oracle.svm.jdwp.bridge.nativebridge.NativeIsolateThread; +import com.oracle.svm.jdwp.bridge.nativebridge.NativeObjectHandles; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.UnmanagedMemory; +import org.graalvm.nativeimage.c.function.CEntryPoint; +import org.graalvm.nativeimage.c.function.CEntryPoint.IsolateThreadContext; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.word.WordFactory; + +/* Checkout README.md before modifying */ +final class HSToNativeJDWPBridgeGen { + + static HSToNativeJDWPBridge createHSToNative(NativeIsolate isolate, long objectHandle) { + return new HSToNativeStartPoint(isolate, objectHandle); + } + + private static final class HSToNativeStartPoint extends HSToNativeJDWPBridge { + + private static final BinaryMarshaller packetMarshaller; + private static final BinaryMarshaller stackFrameMarshaller; + private static final BinaryMarshaller throwableMarshaller; + + static { + JNIConfig config = JDWPJNIConfig.getInstance(); + packetMarshaller = config.lookupMarshaller(Packet.class); + stackFrameMarshaller = config.lookupMarshaller(StackFrame.class); + throwableMarshaller = config.lookupMarshaller(Throwable.class); + } + + HSToNativeStartPoint(NativeIsolate isolate, long objectHandle) { + super(isolate, objectHandle); + } + + @Override + public void clearStepping(long threadId) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + clearStepping0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), threadId); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public String currentWorkingDirectory() { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return currentWorkingDirectory0(nativeIsolateThread.getIsolateThreadId(), this.getHandle()); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public Packet dispatch(Packet packet) throws JDWPException { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + int marshalledParametersSizeEstimate = packetMarshaller.inferSize(packet); + BinaryOutput.ByteArrayBinaryOutput marshalledParametersOutput = BinaryOutput.ByteArrayBinaryOutput.create(marshalledParametersSizeEstimate); + packetMarshaller.write(marshalledParametersOutput, packet); + byte[] endPointResult = dispatch0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), marshalledParametersOutput.getArray()); + BinaryInput marshalledResultInput = BinaryInput.create(endPointResult); + return packetMarshaller.read(marshalledResultInput); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public int fieldRefIdToIndex(long fieldRefId) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return fieldRefIdToIndex0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), fieldRefId); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public long fieldRefIndexToId(int fieldRefIndex) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return fieldRefIndexToId0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), fieldRefIndex); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public long getCurrentThis() { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return getCurrentThis0(nativeIsolateThread.getIsolateThreadId(), this.getHandle()); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public String getSystemProperty(String key) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return getSystemProperty0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), key); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public StackFrame[] getThreadFrames(long threadId) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + byte[] endPointResult = getThreadFrames0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), threadId); + BinaryInput marshalledResultInput = BinaryInput.create(endPointResult); + int hsResultLength = marshalledResultInput.readInt(); + StackFrame[] hsResult; + if (hsResultLength != -1) { + hsResult = new StackFrame[hsResultLength]; + for(int hsResultIndex = 0; hsResultIndex < hsResultLength; hsResultIndex++) { + StackFrame hsResultElement = stackFrameMarshaller.read(marshalledResultInput); + hsResult[hsResultIndex] = hsResultElement; + } + } else { + hsResult = null; + } + return hsResult; + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public int getThreadStatus(long threadId) throws JDWPException { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return getThreadStatus0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), threadId); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public boolean isCurrentThreadVirtual() { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return isCurrentThreadVirtual0(nativeIsolateThread.getIsolateThreadId(), this.getHandle()); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public boolean isEventEnabled(long threadId, int eventKind) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return isEventEnabled0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), threadId, eventKind); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public int methodRefIdToIndex(long methodRefId) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return methodRefIdToIndex0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), methodRefId); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public long methodRefIndexToId(int methodRefIndex) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return methodRefIndexToId0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), methodRefIndex); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public void setEventEnabled(long threadId, int eventKind, boolean enable) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + setEventEnabled0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), threadId, eventKind, enable); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public void setStepping(long threadId, int depth, int size) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + setStepping0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), threadId, depth, size); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public void setSteppingFromLocation(long threadId, int depth, int size, long methodId, int bci, int lineNumber) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + setSteppingFromLocation0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), threadId, depth, size, methodId, bci, lineNumber); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public void setThreadRequest(boolean start, boolean enable) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + setThreadRequest0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), start, enable); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public long threadResume(long suspendId) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return threadResume0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), suspendId); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public long threadSuspend(long threadId) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return threadSuspend0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), threadId); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public void toggleBreakpoint(long methodId, int bci, boolean enable) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + toggleBreakpoint0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), methodId, bci, enable); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public void toggleMethodEnterEvent(long clazzId, boolean enable) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + toggleMethodEnterEvent0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), clazzId, enable); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public void toggleMethodExitEvent(long clazzId, boolean enable) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + toggleMethodExitEvent0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), clazzId, enable); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public int typeRefIdToIndex(long typeRefId) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return typeRefIdToIndex0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), typeRefId); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public long typeRefIndexToId(int typeRefIndex) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return typeRefIndexToId0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), typeRefIndex); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public int[] typeStatus(long... typeIds) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return typeStatus0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), typeIds); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public void vmResume(long[] ignoredThreadIds) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + vmResume0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), ignoredThreadIds); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + @Override + public long[] vmSuspend(long[] ignoredThreadIds) { + NativeIsolateThread nativeIsolateThread = this.getIsolate().enter(); + try { + return vmSuspend0(nativeIsolateThread.getIsolateThreadId(), this.getHandle(), ignoredThreadIds); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } finally { + nativeIsolateThread.leave(); + } + } + + private static native void clearStepping0(long isolateThread, long objectId, long threadId); + + private static native String currentWorkingDirectory0(long isolateThread, long objectId); + + private static native byte[] dispatch0(long isolateThread, long objectId, byte[] marshalledData) throws JDWPException; + + private static native int fieldRefIdToIndex0(long isolateThread, long objectId, long fieldRefId); + + private static native long fieldRefIndexToId0(long isolateThread, long objectId, int fieldRefIndex); + + private static native long getCurrentThis0(long isolateThread, long objectId); + + private static native String getSystemProperty0(long isolateThread, long objectId, String key); + + private static native byte[] getThreadFrames0(long isolateThread, long objectId, long threadId); + + private static native int getThreadStatus0(long isolateThread, long objectId, long threadId) throws JDWPException; + + private static native boolean isCurrentThreadVirtual0(long isolateThread, long objectId); + + private static native boolean isEventEnabled0(long isolateThread, long objectId, long threadId, int eventKind); + + private static native int methodRefIdToIndex0(long isolateThread, long objectId, long methodRefId); + + private static native long methodRefIndexToId0(long isolateThread, long objectId, int methodRefIndex); + + private static native void setEventEnabled0(long isolateThread, long objectId, long threadId, int eventKind, boolean enable); + + private static native void setStepping0(long isolateThread, long objectId, long threadId, int depth, int size); + + private static native void setSteppingFromLocation0(long isolateThread, long objectId, long threadId, int depth, int size, long methodId, int bci, int lineNumber); + + private static native void setThreadRequest0(long isolateThread, long objectId, boolean start, boolean enable); + + private static native long threadResume0(long isolateThread, long objectId, long suspendId); + + private static native long threadSuspend0(long isolateThread, long objectId, long threadId); + + private static native void toggleBreakpoint0(long isolateThread, long objectId, long methodId, int bci, boolean enable); + + private static native void toggleMethodEnterEvent0(long isolateThread, long objectId, long clazzId, boolean enable); + + private static native void toggleMethodExitEvent0(long isolateThread, long objectId, long clazzId, boolean enable); + + private static native int typeRefIdToIndex0(long isolateThread, long objectId, long typeRefId); + + private static native long typeRefIndexToId0(long isolateThread, long objectId, int typeRefIndex); + + private static native int[] typeStatus0(long isolateThread, long objectId, long[] typeIds); + + private static native void vmResume0(long isolateThread, long objectId, long[] ignoredThreadIds); + + private static native long[] vmSuspend0(long isolateThread, long objectId, long[] ignoredThreadIds); + } + + @SuppressWarnings("unused") + private static final class HSToNativeEndPoint { + + private static final BinaryMarshaller packetMarshaller; + private static final BinaryMarshaller stackFrameMarshaller; + private static final BinaryMarshaller throwableMarshaller; + static { + JNIConfig config = JDWPJNIConfig.getInstance(); + packetMarshaller = config.lookupMarshaller(Packet.class); + stackFrameMarshaller = config.lookupMarshaller(StackFrame.class); + throwableMarshaller = config.lookupMarshaller(Throwable.class); + } + + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_clearStepping0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static void clearStepping(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, long threadId) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::clearStepping", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + receiverObject.clearStepping(threadId); + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_currentWorkingDirectory0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static JString currentWorkingDirectory(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId) { + JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::currentWorkingDirectory", jniEnv); + try (JNIMethodScope sc = scope) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + String endPointResult = receiverObject.currentWorkingDirectory(); + scope.setObjectResult(JNIUtil.createHSString(jniEnv, endPointResult)); + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + scope.setObjectResult(WordFactory.nullPointer()); + } + return scope.getObjectResult(); + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_dispatch0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static JByteArray dispatch(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, JByteArray marshalledData) { + JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::dispatch", jniEnv); + try (JNIMethodScope sc = scope) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + CCharPointer staticMarshallBuffer = StackValue.get(256); + int marshalledDataLength = JNIUtil.GetArrayLength(jniEnv, marshalledData); + CCharPointer marshallBuffer = marshalledDataLength <= 256 ? staticMarshallBuffer : UnmanagedMemory.malloc(marshalledDataLength); + try { + JNIUtil.GetByteArrayRegion(jniEnv, marshalledData, 0, marshalledDataLength, marshallBuffer); + BinaryInput marshalledParametersInput = BinaryInput.create(marshallBuffer, marshalledDataLength); + Packet packet = packetMarshaller.read(marshalledParametersInput); + Packet endPointResult = receiverObject.dispatch(packet); + int marshalledResultSizeEstimate = packetMarshaller.inferSize(endPointResult); + int marshallBufferLength = Math.max(256, marshalledDataLength); + try (CCharPointerBinaryOutput marshalledResultOutput = marshalledResultSizeEstimate > marshallBufferLength ? CCharPointerBinaryOutput.create(marshalledResultSizeEstimate) : BinaryOutput.create(marshallBuffer, marshallBufferLength, false)) { + packetMarshaller.write(marshalledResultOutput, endPointResult); + int marshalledResultPosition = marshalledResultOutput.getPosition(); + JByteArray marshalledResult = marshalledResultPosition <= marshalledDataLength ? marshalledData : JNIUtil.NewByteArray(jniEnv, marshalledResultPosition); + JNIUtil.SetByteArrayRegion(jniEnv, marshalledResult, 0, marshalledResultPosition, marshalledResultOutput.getAddress()); + scope.setObjectResult(marshalledResult); + } + } finally { + if (marshallBuffer != staticMarshallBuffer) { + UnmanagedMemory.free(marshallBuffer); + } + } + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + scope.setObjectResult(WordFactory.nullPointer()); + } + return scope.getObjectResult(); + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_fieldRefIdToIndex0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static int fieldRefIdToIndex(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, long fieldRefId) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::fieldRefIdToIndex", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + int endPointResult = receiverObject.fieldRefIdToIndex(fieldRefId); + return endPointResult; + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + return 0; + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_fieldRefIndexToId0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static long fieldRefIndexToId(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, int fieldRefIndex) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::fieldRefIndexToId", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + long endPointResult = receiverObject.fieldRefIndexToId(fieldRefIndex); + return endPointResult; + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + return 0; + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_getCurrentThis0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static long getCurrentThis(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::getCurrentThis", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + long endPointResult = receiverObject.getCurrentThis(); + return endPointResult; + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + return 0; + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_getSystemProperty0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static JString getSystemProperty(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, JString key) { + JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::getSystemProperty", jniEnv); + try (JNIMethodScope sc = scope) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + String endPointResult = receiverObject.getSystemProperty(JNIUtil.createString(jniEnv, key)); + scope.setObjectResult(JNIUtil.createHSString(jniEnv, endPointResult)); + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + scope.setObjectResult(WordFactory.nullPointer()); + } + return scope.getObjectResult(); + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_getThreadFrames0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static JByteArray getThreadFrames(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, long threadId) { + JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::getThreadFrames", jniEnv); + try (JNIMethodScope sc = scope) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + CCharPointer staticMarshallBuffer = StackValue.get(256); + StackFrame[] endPointResult = receiverObject.getThreadFrames(threadId); + int marshalledResultSizeEstimate = Integer.BYTES + (endPointResult != null && endPointResult.length > 0 ? endPointResult.length * stackFrameMarshaller.inferSize(endPointResult[0]) : 0); + try (CCharPointerBinaryOutput marshalledResultOutput = marshalledResultSizeEstimate > 256 ? CCharPointerBinaryOutput.create(marshalledResultSizeEstimate) : BinaryOutput.create(staticMarshallBuffer, 256, false)) { + if (endPointResult != null) { + marshalledResultOutput.writeInt(endPointResult.length); + for (StackFrame endPointResultElement : endPointResult) { + stackFrameMarshaller.write(marshalledResultOutput, endPointResultElement); + } + } else { + marshalledResultOutput.writeInt(-1); + } + int marshalledResultPosition = marshalledResultOutput.getPosition(); + JByteArray marshalledResult = JNIUtil.NewByteArray(jniEnv, marshalledResultPosition); + JNIUtil.SetByteArrayRegion(jniEnv, marshalledResult, 0, marshalledResultPosition, marshalledResultOutput.getAddress()); + scope.setObjectResult(marshalledResult); + } + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + scope.setObjectResult(WordFactory.nullPointer()); + } + return scope.getObjectResult(); + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_getThreadStatus0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static int getThreadStatus(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, long threadId) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::getThreadStatus", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + int endPointResult = receiverObject.getThreadStatus(threadId); + return endPointResult; + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + return 0; + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_isCurrentThreadVirtual0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static boolean isCurrentThreadVirtual(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::isCurrentThreadVirtual", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + boolean endPointResult = receiverObject.isCurrentThreadVirtual(); + return endPointResult; + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + return false; + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_isEventEnabled0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static boolean isEventEnabled(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, long threadId, int eventKind) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::isEventEnabled", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + boolean endPointResult = receiverObject.isEventEnabled(threadId, eventKind); + return endPointResult; + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + return false; + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_methodRefIdToIndex0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static int methodRefIdToIndex(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, long methodRefId) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::methodRefIdToIndex", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + int endPointResult = receiverObject.methodRefIdToIndex(methodRefId); + return endPointResult; + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + return 0; + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_methodRefIndexToId0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static long methodRefIndexToId(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, int methodRefIndex) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::methodRefIndexToId", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + long endPointResult = receiverObject.methodRefIndexToId(methodRefIndex); + return endPointResult; + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + return 0; + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_setEventEnabled0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static void setEventEnabled(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, long threadId, int eventKind, boolean enable) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::setEventEnabled", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + receiverObject.setEventEnabled(threadId, eventKind, enable); + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_setStepping0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static void setStepping(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, long threadId, int depth, int size) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::setStepping", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + receiverObject.setStepping(threadId, depth, size); + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_setSteppingFromLocation0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static void setSteppingFromLocation(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, long threadId, int depth, int size, long methodId, int bci, int lineNumber) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::setSteppingFromLocation", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + receiverObject.setSteppingFromLocation(threadId, depth, size, methodId, bci, lineNumber); + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_setThreadRequest0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static void setThreadRequest(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, boolean start, boolean enable) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::setThreadRequest", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + receiverObject.setThreadRequest(start, enable); + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_threadResume0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static long threadResume(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, long suspendId) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::threadResume", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + long endPointResult = receiverObject.threadResume(suspendId); + return endPointResult; + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + return 0; + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_threadSuspend0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static long threadSuspend(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, long threadId) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::threadSuspend", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + long endPointResult = receiverObject.threadSuspend(threadId); + return endPointResult; + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + return 0; + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_toggleBreakpoint0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static void toggleBreakpoint(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, long methodId, int bci, boolean enable) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::toggleBreakpoint", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + receiverObject.toggleBreakpoint(methodId, bci, enable); + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_toggleMethodEnterEvent0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static void toggleMethodEnterEvent(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, long clazzId, boolean enable) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::toggleMethodEnterEvent", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + receiverObject.toggleMethodEnterEvent(clazzId, enable); + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_toggleMethodExitEvent0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static void toggleMethodExitEvent(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, long clazzId, boolean enable) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::toggleMethodExitEvent", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + receiverObject.toggleMethodExitEvent(clazzId, enable); + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_typeRefIdToIndex0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static int typeRefIdToIndex(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, long typeRefId) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::typeRefIdToIndex", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + int endPointResult = receiverObject.typeRefIdToIndex(typeRefId); + return endPointResult; + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + return 0; + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_typeRefIndexToId0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static long typeRefIndexToId(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, int typeRefIndex) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::typeRefIndexToId", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + long endPointResult = receiverObject.typeRefIndexToId(typeRefIndex); + return endPointResult; + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + return 0; + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_typeStatus0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static JIntArray typeStatus(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, JLongArray typeIds) { + JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::typeStatus", jniEnv); + try (JNIMethodScope sc = scope) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + int[] endPointResult = receiverObject.typeStatus(JNIUtil.createArray(jniEnv, typeIds)); + scope.setObjectResult(JNIUtil.createHSArray(jniEnv, endPointResult)); + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + scope.setObjectResult(WordFactory.nullPointer()); + } + return scope.getObjectResult(); + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_vmResume0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static void vmResume(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, JLongArray ignoredThreadIds) { + try (JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::vmResume", jniEnv)) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + receiverObject.vmResume(JNIUtil.createArray(jniEnv, ignoredThreadIds)); + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + } + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_HSToNativeJDWPBridgeGen_00024HSToNativeStartPoint_vmSuspend0", include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings({"try", "unused"}) + static JLongArray vmSuspend(JNIEnv jniEnv, JClass jniClazz, @IsolateThreadContext long isolateThread, long objectId, JLongArray ignoredThreadIds) { + JNIMethodScope scope = new JNIMethodScope("HSToNativeJDWPBridgeGen::vmSuspend", jniEnv); + try (JNIMethodScope sc = scope) { + JDWPBridge receiverObject = NativeObjectHandles.resolve(objectId, JDWPBridge.class); + long[] endPointResult = receiverObject.vmSuspend(JNIUtil.createArray(jniEnv, ignoredThreadIds)); + scope.setObjectResult(JNIUtil.createHSArray(jniEnv, endPointResult)); + } catch (Throwable e) { + ForeignException.forThrowable(e, throwableMarshaller).throwUsingJNI(jniEnv); + scope.setObjectResult(WordFactory.nullPointer()); + } + return scope.getObjectResult(); + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/IdTag.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/IdTag.java new file mode 100644 index 000000000000..b1d1939ec5eb --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/IdTag.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +public enum IdTag { + ARBITRARY_REFERENCE, + INITIAL_IMAGE_HEAP_REFERENCE, + THREAD, + TYPE, + METHOD, + FIELD; + + // No valid ID should be equal to this e.g. type tags cannot be == TAG_MASK. + // Tag and index of this id are invalid. + public static final long INVALID_ID = -1L; + + private static final int TAG_BITS; + private static final int TAG_MASK; + private static final IdTag[] VALUES = IdTag.values(); + + static { + int size = VALUES.length; + int bits = 0; + while (size > 0) { + size >>= 1; + bits++; + } + TAG_BITS = bits; + TAG_MASK = (1 << TAG_BITS) - 1; + } + + public long toTaggedId(long index) { + assert index >= 0; + assert index == ((index << TAG_BITS) >>> TAG_BITS) : "index information lost"; + return index << TAG_BITS | this.ordinal(); + } + + public static long fromTaggedId(long id) { + long index = id >> TAG_BITS; + assert index >= 0; + return index; + } + + public static IdTag getTag(long id) { + return VALUES[(int) (id & TAG_MASK)]; + } + +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriterHelper.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/InvokeOptions.java similarity index 62% rename from substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriterHelper.java rename to substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/InvokeOptions.java index 9a942a6ca894..a3f01b6afc3f 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriterHelper.java +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/InvokeOptions.java @@ -22,27 +22,28 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.graal.pointsto.heap; +package com.oracle.svm.jdwp.bridge; -import org.graalvm.collections.EconomicMap; +public final class InvokeOptions { -import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.graal.pointsto.meta.AnalysisType; + private InvokeOptions() { + } -public class ImageLayerWriterHelper { - protected final ImageLayerWriter imageLayerWriter; + /** + * Otherwise, all threads started. + */ + public static final int INVOKE_SINGLE_THREADED = 0x01; - public ImageLayerWriterHelper(ImageLayerWriter imageLayerWriter) { - this.imageLayerWriter = imageLayerWriter; - } + /** + * Otherwise, normal virtual invoke (instance methods only). + */ + public static final int INVOKE_NONVIRTUAL = 0x02; - @SuppressWarnings("unused") - protected void persistType(AnalysisType type, EconomicMap typeMap) { - /* No additional information to persist */ + public static boolean singleThreaded(int flags) { + return (flags & INVOKE_SINGLE_THREADED) != 0; } - @SuppressWarnings("unused") - protected void persistMethod(AnalysisMethod method, EconomicMap methodMap) { - /* No additional information to persist */ + public static boolean nonVirtual(int flags) { + return (flags & INVOKE_NONVIRTUAL) != 0; } } diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWP.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWP.java new file mode 100644 index 000000000000..8239320d9f4c --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWP.java @@ -0,0 +1,925 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +import com.oracle.svm.core.util.VMError; + +// Checkstyle: stop method name check +// Checkstyle: stop field name check + +public interface JDWP { + + int VirtualMachine = 1; + int VirtualMachine_Version = 1; + int VirtualMachine_ClassesBySignature = 2; + int VirtualMachine_AllClasses = 3; + int VirtualMachine_AllThreads = 4; + int VirtualMachine_TopLevelThreadGroups = 5; + int VirtualMachine_Dispose = 6; + int VirtualMachine_IDSizes = 7; + int VirtualMachine_Suspend = 8; + int VirtualMachine_Resume = 9; + int VirtualMachine_Exit = 10; + int VirtualMachine_CreateString = 11; + int VirtualMachine_Capabilities = 12; + int VirtualMachine_ClassPaths = 13; + int VirtualMachine_DisposeObjects = 14; + int VirtualMachine_HoldEvents = 15; + int VirtualMachine_ReleaseEvents = 16; + int VirtualMachine_CapabilitiesNew = 17; + int VirtualMachine_RedefineClasses = 18; + int VirtualMachine_SetDefaultStratum = 19; + int VirtualMachine_AllClassesWithGeneric = 20; + int VirtualMachine_InstanceCounts = 21; + int VirtualMachine_AllModules = 22; + + default Packet VirtualMachine_Version(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.Version"); + } + + default Packet VirtualMachine_ClassesBySignature(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.ClassesBySignature"); + } + + default Packet VirtualMachine_AllClasses(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.AllClasses"); + } + + default Packet VirtualMachine_AllThreads(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.AllThreads"); + } + + default Packet VirtualMachine_TopLevelThreadGroups(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.TopLevelThreadGroups"); + } + + default Packet VirtualMachine_Dispose(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.Dispose"); + } + + default Packet VirtualMachine_IDSizes(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.IDSizes"); + } + + default Packet VirtualMachine_Suspend(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.Suspend"); + } + + default Packet VirtualMachine_Resume(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.Resume"); + } + + default Packet VirtualMachine_Exit(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.Exit"); + } + + default Packet VirtualMachine_CreateString(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.CreateString"); + } + + default Packet VirtualMachine_Capabilities(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.Capabilities"); + } + + default Packet VirtualMachine_ClassPaths(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.ClassPaths"); + } + + default Packet VirtualMachine_DisposeObjects(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.DisposeObjects"); + } + + default Packet VirtualMachine_HoldEvents(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.HoldEvents"); + } + + default Packet VirtualMachine_ReleaseEvents(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.ReleaseEvents"); + } + + default Packet VirtualMachine_CapabilitiesNew(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.CapabilitiesNew"); + } + + default Packet VirtualMachine_RedefineClasses(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.RedefineClasses"); + } + + default Packet VirtualMachine_SetDefaultStratum(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.SetDefaultStratum"); + } + + default Packet VirtualMachine_AllClassesWithGeneric(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.AllClassesWithGeneric"); + } + + default Packet VirtualMachine_InstanceCounts(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.InstanceCounts"); + } + + default Packet VirtualMachine_AllModules(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("VirtualMachine.AllModules"); + } + + int ReferenceType = 2; + int ReferenceType_Signature = 1; + int ReferenceType_ClassLoader = 2; + int ReferenceType_Modifiers = 3; + int ReferenceType_Fields = 4; + int ReferenceType_Methods = 5; + int ReferenceType_GetValues = 6; + int ReferenceType_SourceFile = 7; + int ReferenceType_NestedTypes = 8; + int ReferenceType_Status = 9; + int ReferenceType_Interfaces = 10; + int ReferenceType_ClassObject = 11; + int ReferenceType_SourceDebugExtension = 12; + int ReferenceType_SignatureWithGeneric = 13; + int ReferenceType_FieldsWithGeneric = 14; + int ReferenceType_MethodsWithGeneric = 15; + int ReferenceType_Instances = 16; + int ReferenceType_ClassFileVersion = 17; + int ReferenceType_ConstantPool = 18; + int ReferenceType_Module = 19; + + default Packet ReferenceType_Signature(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.Signature"); + } + + default Packet ReferenceType_ClassLoader(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.ClassLoader"); + } + + default Packet ReferenceType_Modifiers(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.Modifiers"); + } + + default Packet ReferenceType_Fields(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.Fields"); + } + + default Packet ReferenceType_Methods(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.Methods"); + } + + default Packet ReferenceType_GetValues(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.GetValues"); + } + + default Packet ReferenceType_SourceFile(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.SourceFile"); + } + + default Packet ReferenceType_NestedTypes(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.NestedTypes"); + } + + default Packet ReferenceType_Status(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.Status"); + } + + default Packet ReferenceType_Interfaces(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.Interfaces"); + } + + default Packet ReferenceType_ClassObject(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.ClassObject"); + } + + default Packet ReferenceType_SourceDebugExtension(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.SourceDebugExtension"); + } + + default Packet ReferenceType_SignatureWithGeneric(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.SignatureWithGeneric"); + } + + default Packet ReferenceType_FieldsWithGeneric(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.FieldsWithGeneric"); + } + + default Packet ReferenceType_Instances(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.Instances"); + } + + default Packet ReferenceType_MethodsWithGeneric(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.MethodsWithGeneric"); + } + + default Packet ReferenceType_ClassFileVersion(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.ClassFileVersion"); + } + + default Packet ReferenceType_ConstantPool(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.ConstantPool"); + } + + default Packet ReferenceType_Module(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ReferenceType.Module"); + } + + int ClassType = 3; + + int ClassType_Superclass = 1; + int ClassType_SetValues = 2; + int ClassType_InvokeMethod = 3; + int ClassType_NewInstance = 4; + + default Packet ClassType_Superclass(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ClassType.Superclass"); + } + + default Packet ClassType_SetValues(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ClassType.SetValues"); + } + + default Packet ClassType_InvokeMethod(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ClassType.InvokeMethod"); + } + + default Packet ClassType_NewInstance(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ClassType.NewInstance"); + } + + int ArrayType = 4; + + int ArrayType_NewInstance = 1; + + default Packet ArrayType_NewInstance(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ArrayType.NewInstance"); + } + + int InterfaceType = 5; + int InterfaceType_InvokeMethod = 1; + + default Packet InterfaceType_InvokeMethod(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("InterfaceType.InvokeMethod"); + } + + int Method = 6; + int Method_LineTable = 1; + int Method_VariableTable = 2; + int Method_Bytecodes = 3; + int Method_IsObsolete = 4; + int Method_VariableTableWithGeneric = 5; + + default Packet Method_LineTable(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("Method.LineTable"); + } + + default Packet Method_VariableTable(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("Method.VariableTable"); + } + + default Packet Method_Bytecodes(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("Method.Bytecodes"); + } + + default Packet Method_IsObsolete(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("Method.IsObsolete"); + } + + default Packet Method_VariableTableWithGeneric(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("Method.VariableTableWithGeneric"); + } + + int Field = 8; + int ObjectReference = 9; + + int ObjectReference_ReferenceType = 1; + int ObjectReference_GetValues = 2; + int ObjectReference_SetValues = 3; + int ObjectReference_MonitorInfo = 5; + int ObjectReference_InvokeMethod = 6; + int ObjectReference_DisableCollection = 7; + int ObjectReference_EnableCollection = 8; + int ObjectReference_IsCollected = 9; + int ObjectReference_ReferringObjects = 10; + + default Packet ObjectReference_ReferenceType(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ObjectReference.ReferenceType"); + } + + default Packet ObjectReference_GetValues(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ObjectReference.GetValues"); + } + + default Packet ObjectReference_SetValues(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ObjectReference.SetValues"); + } + + default Packet ObjectReference_MonitorInfo(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ObjectReference.MonitorInfo"); + } + + default Packet ObjectReference_InvokeMethod(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ObjectReference.InvokeMethod"); + } + + default Packet ObjectReference_DisableCollection(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ObjectReference.DisableCollection"); + } + + default Packet ObjectReference_EnableCollection(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ObjectReference.EnableCollection"); + } + + default Packet ObjectReference_IsCollected(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ObjectReference.IsCollected"); + } + + default Packet ObjectReference_ReferringObjects(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ObjectReference.ReferringObjects"); + } + + int StringReference = 10; + + int StringReference_Value = 1; + + default Packet StringReference_Value(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("StringReference.Value"); + } + + int ThreadReference = 11; + + int ThreadReference_Name = 1; + int ThreadReference_Suspend = 2; + int ThreadReference_Resume = 3; + int ThreadReference_Status = 4; + int ThreadReference_ThreadGroup = 5; + int ThreadReference_Frames = 6; + int ThreadReference_FrameCount = 7; + int ThreadReference_OwnedMonitors = 8; + int ThreadReference_CurrentContendedMonitor = 9; + int ThreadReference_Stop = 10; + int ThreadReference_Interrupt = 11; + int ThreadReference_SuspendCount = 12; + int ThreadReference_OwnedMonitorsStackDepthInfo = 13; + int ThreadReference_ForceEarlyReturn = 14; + int ThreadReference_IsVirtual = 15; + + default Packet ThreadReference_Name(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadReference.Name"); + } + + default Packet ThreadReference_Suspend(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadReference.Suspend"); + } + + default Packet ThreadReference_Resume(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadReference.Resume"); + } + + default Packet ThreadReference_Status(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadReference.Status"); + } + + default Packet ThreadReference_ThreadGroup(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadReference.ThreadGroup"); + } + + default Packet ThreadReference_Frames(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadReference.Frames"); + } + + default Packet ThreadReference_FrameCount(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadReference.FrameCount"); + } + + default Packet ThreadReference_OwnedMonitors(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadReference.OwnedMonitors"); + } + + default Packet ThreadReference_CurrentContendedMonitor(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadReference.CurrentContendedMonitor"); + } + + default Packet ThreadReference_Stop(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadReference.Stop"); + } + + default Packet ThreadReference_Interrupt(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadReference.Interrupt"); + } + + default Packet ThreadReference_SuspendCount(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadReference.SuspendCount"); + } + + default Packet ThreadReference_OwnedMonitorsStackDepthInfo(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadReference.OwnedMonitorsStackDepthInfo"); + } + + default Packet ThreadReference_ForceEarlyReturn(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadReference.ForceEarlyReturn"); + } + + default Packet ThreadReference_IsVirtual(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadReference.IsVirtual"); + } + + int ThreadGroupReference = 12; + + int ThreadGroupReference_Name = 1; + int ThreadGroupReference_Parent = 2; + int ThreadGroupReference_Children = 3; + + default Packet ThreadGroupReference_Name(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadGroupReference.Name"); + } + + default Packet ThreadGroupReference_Parent(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadGroupReference.Parent"); + } + + default Packet ThreadGroupReference_Children(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ThreadGroupReference.Children"); + } + + int ArrayReference = 13; + + int ArrayReference_Length = 1; + int ArrayReference_GetValues = 2; + int ArrayReference_SetValues = 3; + + default Packet ArrayReference_Length(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ArrayReferenc.Length"); + } + + default Packet ArrayReference_GetValues(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ArrayReferenc.GetValues"); + } + + default Packet ArrayReference_SetValues(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ArrayReferenc.SetValues"); + } + + int ClassLoaderReference = 14; + + int ClassLoaderReference_VisibleClasses = 1; + + default Packet ClassLoaderReference_VisibleClasses(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ClassLoaderReference.VisibleClasses"); + } + + int EventRequest = 15; + + int EventRequest_Set = 1; + int EventRequest_Clear = 2; + int EventRequest_ClearAllBreakpoints = 3; + + default Packet EventRequest_Set(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("EventRequest.Set"); + } + + default Packet EventRequest_Clear(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("EventRequest.Clear"); + } + + default Packet EventRequest_ClearAllBreakpoints(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("EventRequest.ClearAllBreakpoints"); + } + + int StackFrame = 16; + int StackFrame_GetValues = 1; + int StackFrame_SetValues = 2; + int StackFrame_ThisObject = 3; + int StackFrame_PopFrames = 4; + + default Packet StackFrame_GetValues(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("StackFrame.GetValues"); + } + + default Packet StackFrame_SetValues(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("StackFrame.SetValues"); + } + + default Packet StackFrame_ThisObject(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("StackFrame.ThisObject"); + } + + default Packet StackFrame_PopFrames(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("StackFrame.PopFrames"); + } + + int ClassObjectReference = 17; + + int ClassObjectReference_ReflectedType = 1; + + default Packet ClassObjectReference_ReflectedType(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ClassObjectReference.ReflectedType"); + } + + int ModuleReference = 18; + + int ModuleReference_Name = 1; + int ModuleReference_ClassLoader = 2; + + default Packet ModuleReference_Name(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ModuleReference.Name"); + } + + default Packet ModuleReference_ClassLoader(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("ModuleReference.ClassLoader"); + } + + int Event = 64; + int Event_Composite = 100; + + default Packet Event_Composite(@SuppressWarnings("unused") Packet packet) throws JDWPException { + throw VMError.unimplemented("Event.Composite"); + } + + default Packet dispatch(@SuppressWarnings("unused") Packet packet) throws JDWPException { + //@formatter:off + Packet response = switch (packet.commandSet()) { + case VirtualMachine -> switch (packet.command()) { + case VirtualMachine_Version -> VirtualMachine_Version(packet); + case VirtualMachine_ClassesBySignature -> VirtualMachine_ClassesBySignature(packet); + case VirtualMachine_AllClasses -> VirtualMachine_AllClasses(packet); + case VirtualMachine_AllThreads -> VirtualMachine_AllThreads(packet); + case VirtualMachine_TopLevelThreadGroups -> VirtualMachine_TopLevelThreadGroups(packet); + case VirtualMachine_Dispose -> VirtualMachine_Dispose(packet); + case VirtualMachine_IDSizes -> VirtualMachine_IDSizes(packet); + case VirtualMachine_Suspend -> VirtualMachine_Suspend(packet); + case VirtualMachine_Resume -> VirtualMachine_Resume(packet); + case VirtualMachine_Exit -> VirtualMachine_Exit(packet); + case VirtualMachine_CreateString -> VirtualMachine_CreateString(packet); + case VirtualMachine_Capabilities -> VirtualMachine_Capabilities(packet); + case VirtualMachine_ClassPaths -> VirtualMachine_ClassPaths(packet); + case VirtualMachine_DisposeObjects -> VirtualMachine_DisposeObjects(packet); + case VirtualMachine_HoldEvents -> VirtualMachine_HoldEvents(packet); + case VirtualMachine_ReleaseEvents -> VirtualMachine_ReleaseEvents(packet); + case VirtualMachine_CapabilitiesNew -> VirtualMachine_CapabilitiesNew(packet); + case VirtualMachine_RedefineClasses -> VirtualMachine_RedefineClasses(packet); + case VirtualMachine_SetDefaultStratum -> VirtualMachine_SetDefaultStratum(packet); + case VirtualMachine_AllClassesWithGeneric -> VirtualMachine_AllClassesWithGeneric(packet); + case VirtualMachine_InstanceCounts -> VirtualMachine_InstanceCounts(packet); + case VirtualMachine_AllModules -> VirtualMachine_AllModules(packet); + default -> + throw VMError.unimplemented("VirtualMachine command " + packet.command()); + }; + case ReferenceType -> switch (packet.command()) { + case ReferenceType_Signature -> ReferenceType_Signature(packet); + case ReferenceType_ClassLoader -> ReferenceType_ClassLoader(packet); + case ReferenceType_Modifiers -> ReferenceType_Modifiers(packet); + case ReferenceType_Fields -> ReferenceType_Fields(packet); + case ReferenceType_Methods -> ReferenceType_Methods(packet); + case ReferenceType_GetValues -> ReferenceType_GetValues(packet); + case ReferenceType_SourceFile -> ReferenceType_SourceFile(packet); + case ReferenceType_NestedTypes -> ReferenceType_NestedTypes(packet); + case ReferenceType_Status -> ReferenceType_Status(packet); + case ReferenceType_Interfaces -> ReferenceType_Interfaces(packet); + case ReferenceType_ClassObject -> ReferenceType_ClassObject(packet); + case ReferenceType_SourceDebugExtension -> ReferenceType_SourceDebugExtension(packet); + case ReferenceType_SignatureWithGeneric -> ReferenceType_SignatureWithGeneric(packet); + case ReferenceType_FieldsWithGeneric -> ReferenceType_FieldsWithGeneric(packet); + case ReferenceType_MethodsWithGeneric -> ReferenceType_MethodsWithGeneric(packet); + case ReferenceType_Instances -> ReferenceType_Instances(packet); + case ReferenceType_ClassFileVersion -> ReferenceType_ClassFileVersion(packet); + case ReferenceType_ConstantPool -> ReferenceType_ConstantPool(packet); + case ReferenceType_Module -> ReferenceType_Module(packet); + default -> + throw VMError.unimplemented("ReferenceType command " + packet.command()); + }; + case ClassType -> switch (packet.command()) { + case ClassType_Superclass -> ClassType_Superclass(packet); + case ClassType_SetValues -> ClassType_SetValues(packet); + case ClassType_InvokeMethod -> ClassType_InvokeMethod(packet); + case ClassType_NewInstance -> ClassType_NewInstance(packet); + default -> + throw VMError.unimplemented("ClassType command " + packet.command()); + }; + case ArrayType -> switch (packet.command()) { + case ArrayType_NewInstance -> ArrayType_NewInstance(packet); + default -> + throw VMError.unimplemented("ArrayType command " + packet.command()); + }; + case InterfaceType -> switch (packet.command()) { + case InterfaceType_InvokeMethod -> InterfaceType_InvokeMethod(packet); + default -> + throw VMError.unimplemented("InterfaceType command " + packet.command()); + }; + case Method -> switch (packet.command()) { + case Method_LineTable -> Method_LineTable(packet); + case Method_VariableTable -> Method_VariableTable(packet); + case Method_Bytecodes -> Method_Bytecodes(packet); + case Method_IsObsolete -> Method_IsObsolete(packet); + case Method_VariableTableWithGeneric -> Method_VariableTableWithGeneric(packet); + default -> + throw VMError.unimplemented("Method command " + packet.command()); + }; + case Field -> throw VMError.unimplemented("Field command " + packet.command()); + case ObjectReference -> switch (packet.command()) { + case ObjectReference_ReferenceType -> ObjectReference_ReferenceType(packet); + case ObjectReference_GetValues -> ObjectReference_GetValues(packet); + case ObjectReference_SetValues -> ObjectReference_SetValues(packet); + case ObjectReference_MonitorInfo -> ObjectReference_MonitorInfo(packet); + case ObjectReference_InvokeMethod -> ObjectReference_InvokeMethod(packet); + case ObjectReference_DisableCollection -> ObjectReference_DisableCollection(packet); + case ObjectReference_EnableCollection -> ObjectReference_EnableCollection(packet); + case ObjectReference_IsCollected -> ObjectReference_IsCollected(packet); + case ObjectReference_ReferringObjects -> ObjectReference_ReferringObjects(packet); + default -> + throw VMError.unimplemented("ObjectReference command " + packet.command()); + }; + case StringReference -> switch (packet.command()) { + case StringReference_Value -> StringReference_Value(packet); + default -> + throw VMError.unimplemented("StringReference command " + packet.command()); + }; + case ThreadReference -> switch (packet.command()) { + case ThreadReference_Name -> ThreadReference_Name(packet); + case ThreadReference_Suspend -> ThreadReference_Suspend(packet); + case ThreadReference_Resume -> ThreadReference_Resume(packet); + case ThreadReference_Status -> ThreadReference_Status(packet); + case ThreadReference_ThreadGroup -> ThreadReference_ThreadGroup(packet); + case ThreadReference_Frames -> ThreadReference_Frames(packet); + case ThreadReference_FrameCount -> ThreadReference_FrameCount(packet); + case ThreadReference_OwnedMonitors -> ThreadReference_OwnedMonitors(packet); + case ThreadReference_CurrentContendedMonitor -> ThreadReference_CurrentContendedMonitor(packet); + case ThreadReference_Stop -> ThreadReference_Stop(packet); + case ThreadReference_Interrupt -> ThreadReference_Interrupt(packet); + case ThreadReference_SuspendCount -> ThreadReference_SuspendCount(packet); + case ThreadReference_OwnedMonitorsStackDepthInfo -> ThreadReference_OwnedMonitorsStackDepthInfo(packet); + case ThreadReference_ForceEarlyReturn -> ThreadReference_ForceEarlyReturn(packet); + case ThreadReference_IsVirtual -> ThreadReference_IsVirtual(packet); + default -> + throw VMError.unimplemented("ThreadReference command " + packet.command()); + }; + case ThreadGroupReference -> switch (packet.command()) { + case ThreadGroupReference_Name -> ThreadGroupReference_Name(packet); + case ThreadGroupReference_Parent -> ThreadGroupReference_Parent(packet); + case ThreadGroupReference_Children -> ThreadGroupReference_Children(packet); + default -> + throw VMError.unimplemented("ThreadGroupReference command " + packet.command()); + }; + case ArrayReference -> switch (packet.command()) { + case ArrayReference_Length -> ArrayReference_Length(packet); + case ArrayReference_GetValues -> ArrayReference_GetValues(packet); + case ArrayReference_SetValues -> ArrayReference_SetValues(packet); + default -> + throw VMError.unimplemented("ArrayReference command " + packet.command()); + }; + case ClassLoaderReference -> switch (packet.command()) { + case ClassLoaderReference_VisibleClasses -> ClassLoaderReference_VisibleClasses(packet); + default -> + throw VMError.unimplemented("ClassLoaderReference command " + packet.command()); + }; + case EventRequest -> switch (packet.command()) { + case EventRequest_Set -> EventRequest_Set(packet); + case EventRequest_Clear -> EventRequest_Clear(packet); + case EventRequest_ClearAllBreakpoints -> EventRequest_ClearAllBreakpoints(packet); + default -> + throw VMError.unimplemented("EventRequest command " + packet.command()); + }; + case StackFrame -> switch (packet.command()) { + case StackFrame_GetValues -> StackFrame_GetValues(packet); + case StackFrame_SetValues -> StackFrame_SetValues(packet); + case StackFrame_ThisObject -> StackFrame_ThisObject(packet); + case StackFrame_PopFrames -> StackFrame_PopFrames(packet); + default -> + throw VMError.unimplemented("StackFrame command " + packet.command()); + }; + case ClassObjectReference -> switch (packet.command()) { + case ClassObjectReference_ReflectedType -> ClassObjectReference_ReflectedType(packet); + default -> + throw VMError.unimplemented("ClassObjectReference command " + packet.command()); + }; + case ModuleReference -> switch (packet.command()) { + case ModuleReference_Name -> ModuleReference_Name(packet); + case ModuleReference_ClassLoader -> ModuleReference_ClassLoader(packet); + default -> + throw VMError.unimplemented("ModuleReference command " + packet.command()); + }; + case Event -> switch (packet.command()) { + case Event_Composite -> Event_Composite(packet); + default -> + throw VMError.unimplemented("Event command " + packet.command()); + }; + default -> + throw VMError.unimplemented("CommandSet " + packet.commandSet()); + }; + //@formatter:on + return response; + } + + static String toString(int commandSet) { + //@formatter:off + return switch (commandSet) { + case VirtualMachine -> "VirtualMachine"; + case ReferenceType -> "ReferenceType"; + case ClassType -> "ClassType"; + case ArrayType -> "ArrayType"; + case InterfaceType -> "InterfaceType"; + case Method -> "Method"; + case Field -> "Field"; + case ObjectReference -> "ObjectReference"; + case StringReference -> "StringReference"; + case ThreadReference -> "ThreadReference"; + case ThreadGroupReference -> "ThreadGroupReference"; + case ArrayReference -> "ArrayReference"; + case ClassLoaderReference -> "ClassLoaderReference"; + case EventRequest -> "EventRequest"; + case StackFrame -> "StackFrame"; + case ClassObjectReference -> "ClassObjectReference"; + case ModuleReference -> "ModuleReference"; + case Event -> "Event"; + default -> unknown(commandSet); + }; + //@formatter:on + } + + static String toString(int commandSet, int command) { + //@formatter:off + return toString(commandSet) + "." + switch (commandSet) { + case VirtualMachine -> switch (command) { + case VirtualMachine_Version -> "Version"; + case VirtualMachine_ClassesBySignature -> "ClassesBySignature"; + case VirtualMachine_AllClasses -> "AllClasses"; + case VirtualMachine_AllThreads -> "AllThreads"; + case VirtualMachine_TopLevelThreadGroups -> "TopLevelThreadGroups"; + case VirtualMachine_Dispose -> "Dispose"; + case VirtualMachine_IDSizes -> "IDSizes"; + case VirtualMachine_Suspend -> "Suspend"; + case VirtualMachine_Resume -> "Resume"; + case VirtualMachine_Exit -> "Exit"; + case VirtualMachine_CreateString -> "CreateString"; + case VirtualMachine_Capabilities -> "Capabilities"; + case VirtualMachine_ClassPaths -> "ClassPaths"; + case VirtualMachine_DisposeObjects -> "DisposeObjects"; + case VirtualMachine_HoldEvents -> "HoldEvents"; + case VirtualMachine_ReleaseEvents -> "ReleaseEvents"; + case VirtualMachine_CapabilitiesNew -> "CapabilitiesNew"; + case VirtualMachine_RedefineClasses -> "RedefineClasses"; + case VirtualMachine_SetDefaultStratum -> "SetDefaultStratum"; + case VirtualMachine_AllClassesWithGeneric -> "AllClassesWithGeneric"; + case VirtualMachine_InstanceCounts -> "InstanceCounts"; + case VirtualMachine_AllModules -> "AllModules"; + default -> unknown(command); + }; + case ReferenceType -> switch (command) { + case ReferenceType_Signature -> "Signature"; + case ReferenceType_ClassLoader -> "ClassLoader"; + case ReferenceType_Modifiers -> "Modifiers"; + case ReferenceType_Fields -> "Fields"; + case ReferenceType_Methods -> "Methods"; + case ReferenceType_GetValues -> "GetValues"; + case ReferenceType_SourceFile -> "SourceFile"; + case ReferenceType_NestedTypes -> "NestedTypes"; + case ReferenceType_Status -> "Status"; + case ReferenceType_Interfaces -> "Interfaces"; + case ReferenceType_ClassObject -> "ClassObject"; + case ReferenceType_SourceDebugExtension -> "SourceDebugExtension"; + case ReferenceType_SignatureWithGeneric -> "SignatureWithGeneric"; + case ReferenceType_FieldsWithGeneric -> "FieldsWithGeneric"; + case ReferenceType_MethodsWithGeneric -> "MethodsWithGeneric"; + case ReferenceType_Instances -> "Instances"; + case ReferenceType_ClassFileVersion -> "ClassFileVersion"; + case ReferenceType_ConstantPool -> "ConstantPool"; + case ReferenceType_Module -> "Module"; + default -> unknown(command); + }; + case ClassType -> switch (command) { + case ClassType_Superclass -> "Superclass"; + case ClassType_SetValues -> "SetValues"; + case ClassType_InvokeMethod -> "InvokeMethod"; + case ClassType_NewInstance -> "NewInstance"; + default -> unknown(command); + }; + case ArrayType -> switch (command) { + case ArrayType_NewInstance -> "NewInstance"; + default -> unknown(command); + }; + case InterfaceType -> switch (command) { + case InterfaceType_InvokeMethod -> "InvokeMethod"; + default -> unknown(command); + }; + case Method -> switch (command) { + case Method_LineTable -> "LineTable"; + case Method_VariableTable -> "VariableTable"; + case Method_Bytecodes -> "Bytecodes"; + case Method_IsObsolete -> "IsObsolete"; + case Method_VariableTableWithGeneric -> "VariableTableWithGeneric"; + default -> unknown(command); + }; + case Field -> unknown(command); + case ObjectReference -> switch (command) { + case ObjectReference_ReferenceType -> "ReferenceType"; + case ObjectReference_GetValues -> "GetValues"; + case ObjectReference_SetValues -> "SetValues"; + case ObjectReference_MonitorInfo -> "MonitorInfo"; + case ObjectReference_InvokeMethod -> "InvokeMethod"; + case ObjectReference_DisableCollection -> "DisableCollection"; + case ObjectReference_EnableCollection -> "EnableCollection"; + case ObjectReference_IsCollected -> "IsCollected"; + case ObjectReference_ReferringObjects -> "ReferringObjects"; + default -> unknown(command); + }; + case StringReference -> switch (command) { + case StringReference_Value -> "Value"; + default -> unknown(command); + }; + case ThreadReference -> switch (command) { + case ThreadReference_Name -> "Name"; + case ThreadReference_Suspend -> "Suspend"; + case ThreadReference_Resume -> "Resume"; + case ThreadReference_Status -> "Status"; + case ThreadReference_ThreadGroup -> "ThreadGroup"; + case ThreadReference_Frames -> "Frames"; + case ThreadReference_FrameCount -> "FrameCount"; + case ThreadReference_OwnedMonitors -> "OwnedMonitors"; + case ThreadReference_CurrentContendedMonitor -> "CurrentContendedMonitor"; + case ThreadReference_Stop -> "Stop"; + case ThreadReference_Interrupt -> "Interrupt"; + case ThreadReference_SuspendCount -> "SuspendCount"; + case ThreadReference_OwnedMonitorsStackDepthInfo -> "OwnedMonitorsStackDepthInfo"; + case ThreadReference_ForceEarlyReturn -> "ForceEarlyReturn"; + case ThreadReference_IsVirtual -> "IsVirtual"; + default -> unknown(command); + }; + case ThreadGroupReference -> switch (command) { + case ThreadGroupReference_Name -> "Name"; + case ThreadGroupReference_Parent -> "Parent"; + case ThreadGroupReference_Children -> "Children"; + default -> unknown(command); + }; + case ArrayReference -> switch (command) { + case ArrayReference_Length -> "Length"; + case ArrayReference_GetValues -> "GetValues"; + case ArrayReference_SetValues -> "SetValues"; + default -> unknown(command); + }; + case ClassLoaderReference -> switch (command) { + case ClassLoaderReference_VisibleClasses -> "VisibleClasses"; + default -> unknown(command); + }; + case EventRequest -> switch (command) { + case EventRequest_Set -> "Set"; + case EventRequest_Clear -> "Clear"; + case EventRequest_ClearAllBreakpoints -> "ClearAllBreakpoints"; + default -> unknown(command); + }; + case StackFrame -> switch (command) { + case StackFrame_GetValues -> "GetValues"; + case StackFrame_SetValues -> "SetValues"; + case StackFrame_ThisObject -> "ThisObject"; + case StackFrame_PopFrames -> "PopFrames"; + default -> unknown(command); + }; + case ClassObjectReference -> switch (command) { + case ClassObjectReference_ReflectedType -> "ReflectedType"; + default -> unknown(command); + }; + case ModuleReference -> switch (command) { + case ModuleReference_Name -> "Name"; + case ModuleReference_ClassLoader -> "ClassLoader"; + default -> unknown(command); + }; + case Event -> switch (command) { + case Event_Composite -> "Composite"; + default -> unknown(command); + }; + default -> + unknown(command); + }; + //@formatter:on + } + + private static String unknown(int id) { + return ""; + } + + static byte readTag(Packet.Reader reader) throws JDWPException { + byte tag = (byte) reader.readByte(); + if (TagConstants.isValidTag(tag)) { + return tag; + } + throw JDWPException.raise(ErrorCode.INVALID_TAG); + } + +} +// Checkstyle: resume method name check +// Checkstyle: resume field name check diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWPBridge.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWPBridge.java new file mode 100644 index 000000000000..576bea12baf2 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWPBridge.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +public interface JDWPBridge { + + int UNKNOWN_BCI = -1; + int UNINITIALIZED_LINE_NUMBER = -2; + + /* special thread id */ + long GLOBAL = 0; + + /** + * Enable/disable events in a specific thread or globally. An event is generated for a + * particular thread if it is enabled either at the thread or global levels. + * + * @param threadId threadId of application side, or GLOBAL to specify global scope + * @param eventKind one of the events in {@link EventKind} + * @param enable true/false to enable/disable the event + */ + void setEventEnabled(long threadId, int eventKind, boolean enable); + + /** + * Checks if an event is enabled for a specified thread or globally. An event is generated + * for a particular thread if it is enabled either at the thread or global levels. + * + * @param threadId threadId of application side, or GLOBAL to specify global scope + * @param eventKind one of the events in {@link EventKind} + */ + boolean isEventEnabled(long threadId, int eventKind); + + /** + * Enable/disable a breakpoint on the specified method and bytecode index. + * + * @throws IllegalArgumentException if the method doesn't have bytecodes, or the bytecode index + * is out of range, or if the bytecode index is not a valid b + */ + void toggleBreakpoint(long methodId, int bci, boolean enable); + + void toggleMethodEnterEvent(long clazzId, boolean enable); + + void toggleMethodExitEvent(long clazzId, boolean enable); + + /** + * Sets the stepping information associated with a thread. This enables stepping for the + * specified thread, enabling stepping is not enough, the stepping events must be enabled e.g. + * {@code Debugger.setEventEnabled(GLOBAL|threadId, EventKind.SINGLE_STEP, true} + * + * For line-stepping, the starting location can be set, otherwise, any location will raise the + * stepping event. + */ + void setSteppingFromLocation(long threadId, int depth, int size, long methodId, int bci, int lineNumber); + + default void setStepping(long threadId, int depth, int size) { + setSteppingFromLocation(threadId, depth, size, 0, UNKNOWN_BCI, UNINITIALIZED_LINE_NUMBER); + } + + /** + * Removes the stepping information associated with a thread. This cancels stepping for the + * current thread. + */ + void clearStepping(long threadId); + + Packet dispatch(Packet packet) throws JDWPException; + + /** + * Returns the ThreadStatus constant. + * + * @throws JDWPException when the {@code threadId} does not represent a {@code Thread}. + */ + int getThreadStatus(long threadId) throws JDWPException; + + long threadSuspend(long threadId); + + long threadResume(long suspendId); + + long[] vmSuspend(long[] ignoredThreadIds); + + void vmResume(long[] ignoredThreadIds); + + /** + * Request to send notifications about thread start/death. + * + * @param start true for thread start, false for thread death. + * @param enable true to enable the notifications, false to disable. + */ + void setThreadRequest(boolean start, boolean enable); + + String getSystemProperty(String key); + + long typeRefIndexToId(int typeRefIndex); + + long fieldRefIndexToId(int fieldRefIndex); + + long methodRefIndexToId(int methodRefIndex); + + int typeRefIdToIndex(long typeRefId); + + int fieldRefIdToIndex(long fieldRefId); + + int methodRefIdToIndex(long methodRefId); + + String currentWorkingDirectory(); + + int[] typeStatus(long... typeIds); + + StackFrame[] getThreadFrames(long threadId); + + boolean isCurrentThreadVirtual(); + + long getCurrentThis(); +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWPEventHandlerBridge.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWPEventHandlerBridge.java new file mode 100644 index 000000000000..e1023cfc1099 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWPEventHandlerBridge.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +public interface JDWPEventHandlerBridge extends EventHandlerBridge { + + void spawnServer(String jdwpOptions, String additionalOptions, long isolate, long initialThreadId, long jdwpBridgeHandle, String metadataHashString, String metadataPath, boolean tracing); +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWPException.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWPException.java new file mode 100644 index 000000000000..6f1ab3cebb6d --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWPException.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +import java.io.Serial; + +public final class JDWPException extends RuntimeException { + + @Serial private static final long serialVersionUID = 5939656366374581700L; + + public ErrorCode getError() { + return error; + } + + private final ErrorCode error; + + JDWPException(ErrorCode error) { + this.error = error; + } + + public static JDWPException raise(ErrorCode error) { + throw new JDWPException(error); + } + + @Override + public String getMessage() { + return "(" + error.value() + ") " + error.getMessage(); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWPJNIConfig.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWPJNIConfig.java new file mode 100644 index 000000000000..7a6857c4f7d3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWPJNIConfig.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +import com.oracle.svm.jdwp.bridge.nativebridge.BinaryInput; +import com.oracle.svm.jdwp.bridge.nativebridge.BinaryMarshaller; +import com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput; +import com.oracle.svm.jdwp.bridge.nativebridge.ForeignException; +import com.oracle.svm.jdwp.bridge.nativebridge.JNIConfig; +import com.oracle.svm.jdwp.bridge.nativebridge.MarshalledException; + +public class JDWPJNIConfig { + + private static final JNIConfig INSTANCE = createJNIConfig(); + + public static JNIConfig getInstance() { + return INSTANCE; + } + + private static JNIConfig createJNIConfig() { + JNIConfig.Builder builder = JNIConfig.newBuilder(); + builder.setAttachThreadAction(JDWPJNIConfig::attachCurrentThread); + + builder.registerMarshaller(Packet.class, new PacketMarshaller()); + EnumMarshaller errorCodeMarshaller = new EnumMarshaller<>(ErrorCode.class); + CustomThrowableMarshaller throwableMarshaller = new CustomThrowableMarshaller(new JDWPExceptionMarshaller(errorCodeMarshaller), new DefaultThrowableMarshaller()); + builder.registerMarshaller(Throwable.class, throwableMarshaller); + builder.registerMarshaller(StackFrame.class, new StackFrameMarshaller()); + + return builder.build(); + } + + private static final class PacketMarshaller implements BinaryMarshaller { + @Override + public void write(BinaryOutput output, Packet object) { + byte[] bytes = object.toByteArray(); + output.writeInt(bytes.length); + output.write(bytes, 0, bytes.length); + } + + @Override + public Packet read(BinaryInput input) { + int length = input.readInt(); + byte[] bytes = new byte[length]; + input.read(bytes, 0, bytes.length); + return UnmodifiablePacket.parseAndWrap(bytes); + } + } + + private static final class EnumMarshaller> implements BinaryMarshaller { + + private final E[] values; + + EnumMarshaller(Class enumClass) { + values = enumClass.getEnumConstants(); + if (values.length > Byte.MAX_VALUE) { + throw new IllegalArgumentException("Only " + Byte.MAX_VALUE + " enum constants are supported."); + } + } + + @Override + public E read(BinaryInput input) { + return values[input.readByte()]; + } + + @Override + public void write(BinaryOutput output, E object) { + output.writeByte(object.ordinal()); + } + + @Override + public int inferSize(E object) { + return 1; + } + } + + static final class CustomThrowableMarshaller implements BinaryMarshaller { + private final BinaryMarshaller jdwpExceptionMarshaller; + private final BinaryMarshaller fallbackThrowableMarshaller; + + private CustomThrowableMarshaller(BinaryMarshaller jdwpExceptionMarshaller, BinaryMarshaller fallbackThrowableMarshaller) { + this.jdwpExceptionMarshaller = jdwpExceptionMarshaller; + this.fallbackThrowableMarshaller = fallbackThrowableMarshaller; + } + + @Override + public void write(BinaryOutput output, Throwable object) { + boolean isJDWPException = object instanceof JDWPException; + output.writeBoolean(isJDWPException); + if (isJDWPException) { + jdwpExceptionMarshaller.write(output, (JDWPException) object); + } else { + fallbackThrowableMarshaller.write(output, object); + } + } + + @Override + public Throwable read(BinaryInput input) { + boolean isJDWPException = input.readBoolean(); + if (isJDWPException) { + return jdwpExceptionMarshaller.read(input); + } else { + return fallbackThrowableMarshaller.read(input); + } + } + + @Override + public int inferSize(Throwable object) { + if (object instanceof JDWPException jdwpException) { + return jdwpExceptionMarshaller.inferSize(jdwpException) + 1; + } else { + return fallbackThrowableMarshaller.inferSize(object) + 1; + } + } + } + + static final class DefaultThrowableMarshaller implements BinaryMarshaller { + + private static final int THROWABLE_SIZE_ESTIMATE = 1024; + private final BinaryMarshaller stackTraceMarshaller = JNIConfig.Builder.defaultStackTraceMarshaller(); + + @Override + public Throwable read(BinaryInput in) { + String foreignExceptionClassName = in.readUTF(); + String foreignExceptionMessage = (String) in.readTypedValue(); + StackTraceElement[] foreignExceptionStack = stackTraceMarshaller.read(in); + return new MarshalledException(foreignExceptionClassName, foreignExceptionMessage, ForeignException.mergeStackTrace(foreignExceptionStack)); + } + + @Override + public void write(BinaryOutput out, Throwable object) { + out.writeUTF(object instanceof MarshalledException ? ((MarshalledException) object).getForeignExceptionClassName() : object.getClass().getName()); + out.writeTypedValue(object.getMessage()); + stackTraceMarshaller.write(out, object.getStackTrace()); + } + + @Override + public int inferSize(Throwable object) { + // We don't use Throwable#getStackTrace as it allocates. + return THROWABLE_SIZE_ESTIMATE; + } + } + + private static final class JDWPExceptionMarshaller implements BinaryMarshaller { + + private final BinaryMarshaller stackTraceMarshaller; + private final BinaryMarshaller errorCodeMarshaller; + + private JDWPExceptionMarshaller(BinaryMarshaller errorCodeMarshaller) { + this.errorCodeMarshaller = errorCodeMarshaller; + this.stackTraceMarshaller = JNIConfig.Builder.defaultStackTraceMarshaller(); + } + + @Override + public void write(BinaryOutput output, JDWPException object) { + errorCodeMarshaller.write(output, object.getError()); + stackTraceMarshaller.write(output, object.getStackTrace()); + } + + @Override + public JDWPException read(BinaryInput input) { + ErrorCode error = errorCodeMarshaller.read(input); + StackTraceElement[] stackTraceElements = stackTraceMarshaller.read(input); + JDWPException jdwpException = new JDWPException(error); + jdwpException.setStackTrace(stackTraceElements); + return jdwpException; + } + + @Override + public int inferSize(JDWPException object) { + return errorCodeMarshaller.inferSize(object.getError()) + stackTraceMarshaller.inferSize(object.getStackTrace()); + } + } + + private static final class StackFrameMarshaller implements BinaryMarshaller { + + private static final int SIZE_OF_STACK_FRAME = Byte.BYTES + Long.BYTES + Long.BYTES + Integer.BYTES + Integer.BYTES; + + @Override + public void write(BinaryOutput output, StackFrame frame) { + output.writeByte(frame.typeTag()); + output.writeLong(frame.classId()); + output.writeLong(frame.methodId()); + output.writeInt(frame.bci()); + output.writeInt(frame.frameDepth()); + } + + @Override + public StackFrame read(BinaryInput input) { + byte typeTag = input.readByte(); + long classId = input.readLong(); + long methodId = input.readLong(); + int bci = input.readInt(); + int frameDepth = input.readInt(); + return new StackFrame(typeTag, classId, methodId, bci, frameDepth); + } + + @Override + public int inferSize(StackFrame frame) { + return SIZE_OF_STACK_FRAME; + } + } + + public static native long attachCurrentThread(long isolate); +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWPNativeBridgeSupport.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWPNativeBridgeSupport.java new file mode 100644 index 000000000000..ba7cb03afab5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/JDWPNativeBridgeSupport.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +import com.oracle.svm.jdwp.bridge.jniutils.NativeBridgeSupport; + +public class JDWPNativeBridgeSupport implements NativeBridgeSupport { + + @Override + public String getFeatureName() { + return "JDWPBridge"; + } + + @Override + public boolean isTracingEnabled(int level) { + return false; + } + + @Override + public void trace(String message) { + System.err.println("[" + getFeatureName() + "] " + message); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/Logger.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/Logger.java new file mode 100644 index 000000000000..d5d936c9b18b --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/Logger.java @@ -0,0 +1,117 @@ +/* + * 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.jdwp.bridge; + +import java.io.PrintStream; +import java.util.function.Supplier; + +public final class Logger { + private final boolean enabled; + private final String prefix; + private final PrintStream sink; + + public Logger(boolean enabled, String prefix, PrintStream sink) { + this.enabled = enabled; + this.prefix = prefix; + this.sink = sink; + } + + public void log(Supplier s) { + if (enabled) { + log(s.get()); + } + } + + public void log(String message) { + if (enabled) { + sink.println(prefix + " " + message); + } + } + + public void log(Throwable throwable) { + if (enabled) { + throwable.printStackTrace(new PrintStream(sink) { + @Override + public void println(Object x) { + super.println(prefix + " " + x); + } + }); + } + } + + public void log(Throwable throwable, String message) { + if (enabled) { + log(message); + log(throwable); + } + } + + public boolean isLoggable() { + assert prefix != null; + return enabled; + } + + public void log(String messageSimpleFormat, Object... args) { + if (enabled) { + log(fmt(messageSimpleFormat, args)); + } + } + + /** + * Cheap alternative to {@link String#format(String, Object...)} that only provides simple + * modifiers. + */ + private static String fmt(String simpleFormat, Object... args) throws IllegalArgumentException { + StringBuilder sb = new StringBuilder(); + int index = 0; + int argIndex = 0; + while (index < simpleFormat.length()) { + char ch = simpleFormat.charAt(index++); + if (ch == '%') { + if (index >= simpleFormat.length()) { + throw new IllegalArgumentException("An unquoted '%' character cannot terminate a format specification"); + } + char specifier = simpleFormat.charAt(index++); + switch (specifier) { + case 's' -> { + if (argIndex >= args.length) { + throw new IllegalArgumentException("Too many format specifiers or not enough arguments"); + } + sb.append(args[argIndex++]); + } + case '%' -> sb.append('%'); + case 'n' -> sb.append(System.lineSeparator()); + default -> throw new IllegalArgumentException("Illegal format specifier: " + specifier); + } + } else { + sb.append(ch); + } + } + if (argIndex < args.length) { + throw new IllegalArgumentException("Not enough format specifiers or too many arguments"); + } + return sb.toString(); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/NativeToHSJDWPEventHandlerBridge.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/NativeToHSJDWPEventHandlerBridge.java new file mode 100644 index 000000000000..c61c62d809ad --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/NativeToHSJDWPEventHandlerBridge.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +import com.oracle.svm.jdwp.bridge.jniutils.HSObject; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JNIEnv; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JObject; + +// Originally generated with nativebridge +// @GenerateNativeToHotSpotBridge(jniConfig = JDWPJNIConfig.class) +public abstract class NativeToHSJDWPEventHandlerBridge extends HSObject implements JDWPEventHandlerBridge { + + public NativeToHSJDWPEventHandlerBridge(JNIEnv env, JObject handle) { + super(env, handle); + } + + public static NativeToHSJDWPEventHandlerBridge createNativeToHS(JNIEnv env, JObject handle) { + return NativeToHSJDWPEventHandlerBridgeGen.createNativeToHS(env, handle); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/NativeToHSJDWPEventHandlerBridgeGen.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/NativeToHSJDWPEventHandlerBridgeGen.java new file mode 100644 index 000000000000..16cd5cb312e2 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/NativeToHSJDWPEventHandlerBridgeGen.java @@ -0,0 +1,226 @@ +/* + * 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. + */ +//Checkstyle: stop +// @formatter:off +package com.oracle.svm.jdwp.bridge; + +import com.oracle.svm.jdwp.bridge.nativebridge.BinaryMarshaller; +import com.oracle.svm.jdwp.bridge.nativebridge.ForeignException; +import com.oracle.svm.jdwp.bridge.nativebridge.JNIClassCache; +import com.oracle.svm.jdwp.bridge.nativebridge.JNIConfig; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JClass; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JNIEnv; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JObject; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JValue; +import com.oracle.svm.jdwp.bridge.jniutils.JNICalls.JNIMethod; +import com.oracle.svm.jdwp.bridge.jniutils.JNIEntryPoint; +import com.oracle.svm.jdwp.bridge.jniutils.JNIMethodScope; +import com.oracle.svm.jdwp.bridge.jniutils.JNIUtil; +import org.graalvm.nativeimage.StackValue; + +/* Checkout README.md before modifying */ +final class NativeToHSJDWPEventHandlerBridgeGen { + + static NativeToHSJDWPEventHandlerBridge createNativeToHS(JNIEnv env, JObject handle) { + return new StartPoint(env, handle); + } + + private static final class StartPoint extends NativeToHSJDWPEventHandlerBridge { + + private static final BinaryMarshaller throwableMarshaller; + static { + JNIConfig config = JDWPJNIConfig.getInstance(); + throwableMarshaller = config.lookupMarshaller(Throwable.class); + } + + static final class JNIData { + + static JNIData cache_; + final JClass endPointClass; + final JNIMethod onEventAtMethod; + final JNIMethod onThreadDeathMethod; + final JNIMethod onThreadStartMethod; + final JNIMethod onVMDeathMethod; + final JNIMethod spawnServerMethod; + + JNIData(JNIEnv jniEnv) { + this.endPointClass = JNIClassCache.lookupClass(jniEnv, EndPoint.class); + this.onEventAtMethod = JNIMethod.findMethod(jniEnv, endPointClass, true, "onEventAt", "(Lcom/oracle/svm/jdwp/bridge/JDWPEventHandlerBridge;JJBJIBJI)V"); + this.onThreadDeathMethod = JNIMethod.findMethod(jniEnv, endPointClass, true, "onThreadDeath", "(Lcom/oracle/svm/jdwp/bridge/JDWPEventHandlerBridge;J)V"); + this.onThreadStartMethod = JNIMethod.findMethod(jniEnv, endPointClass, true, "onThreadStart", "(Lcom/oracle/svm/jdwp/bridge/JDWPEventHandlerBridge;J)V"); + this.onVMDeathMethod = JNIMethod.findMethod(jniEnv, endPointClass, true, "onVMDeath", "(Lcom/oracle/svm/jdwp/bridge/JDWPEventHandlerBridge;)V"); + this.spawnServerMethod = JNIMethod.findMethod(jniEnv, endPointClass, true, "spawnServer", "(Lcom/oracle/svm/jdwp/bridge/JDWPEventHandlerBridge;Ljava/lang/String;Ljava/lang/String;JJJLjava/lang/String;Ljava/lang/String;Z)V"); + } + } + + final JNIData jniMethods_; + + StartPoint(JNIEnv env, JObject handle) { + super(env, handle); + JNIData localJNI = JNIData.cache_; + if (localJNI == null) { + localJNI = JNIData.cache_ = new JNIData(env); + } + this.jniMethods_ = localJNI; + } + + @Override + public void onEventAt(long threadId, long classId, byte typeTag, long methodId, int bci, byte resultTag, long resultPrimitiveOrId, int eventKindFlags) { + try { + JNIEnv jniEnv = JNIMethodScope.env(); + JValue jniArgs = StackValue.get(9, JValue.class); + jniArgs.addressOf(0).setJObject(this.getHandle()); + jniArgs.addressOf(1).setLong(threadId); + jniArgs.addressOf(2).setLong(classId); + jniArgs.addressOf(3).setByte(typeTag); + jniArgs.addressOf(4).setLong(methodId); + jniArgs.addressOf(5).setInt(bci); + jniArgs.addressOf(6).setByte(resultTag); + jniArgs.addressOf(7).setLong(resultPrimitiveOrId); + jniArgs.addressOf(8).setInt(eventKindFlags); + ForeignException.getJNICalls().callStaticVoid(jniEnv, jniMethods_.endPointClass, jniMethods_.onEventAtMethod, jniArgs); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } + } + + @Override + public void onThreadDeath(long threadId) { + try { + JNIEnv jniEnv = JNIMethodScope.env(); + JValue jniArgs = StackValue.get(2, JValue.class); + jniArgs.addressOf(0).setJObject(this.getHandle()); + jniArgs.addressOf(1).setLong(threadId); + ForeignException.getJNICalls().callStaticVoid(jniEnv, jniMethods_.endPointClass, jniMethods_.onThreadDeathMethod, jniArgs); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } + } + + @Override + public void onThreadStart(long threadId) { + try { + JNIEnv jniEnv = JNIMethodScope.env(); + JValue jniArgs = StackValue.get(2, JValue.class); + jniArgs.addressOf(0).setJObject(this.getHandle()); + jniArgs.addressOf(1).setLong(threadId); + ForeignException.getJNICalls().callStaticVoid(jniEnv, jniMethods_.endPointClass, jniMethods_.onThreadStartMethod, jniArgs); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } + } + + @Override + public void onVMDeath() { + try { + JNIEnv jniEnv = JNIMethodScope.env(); + JValue jniArgs = StackValue.get(1, JValue.class); + jniArgs.addressOf(0).setJObject(this.getHandle()); + ForeignException.getJNICalls().callStaticVoid(jniEnv, jniMethods_.endPointClass, jniMethods_.onVMDeathMethod, jniArgs); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } + } + + @Override + public void spawnServer(String jdwpOptions, String additionalOptions, long isolate, long initialThreadId, long jdwpBridgeHandle, String metadataHashString, String metadataPath, boolean tracing) { + try { + JNIEnv jniEnv = JNIMethodScope.env(); + JValue jniArgs = StackValue.get(9, JValue.class); + jniArgs.addressOf(0).setJObject(this.getHandle()); + jniArgs.addressOf(1).setJObject(JNIUtil.createHSString(jniEnv, jdwpOptions)); + jniArgs.addressOf(2).setJObject(JNIUtil.createHSString(jniEnv, additionalOptions)); + jniArgs.addressOf(3).setLong(isolate); + jniArgs.addressOf(4).setLong(initialThreadId); + jniArgs.addressOf(5).setLong(jdwpBridgeHandle); + jniArgs.addressOf(6).setJObject(JNIUtil.createHSString(jniEnv, metadataHashString)); + jniArgs.addressOf(7).setJObject(JNIUtil.createHSString(jniEnv, metadataPath)); + jniArgs.addressOf(8).setBoolean(tracing); + ForeignException.getJNICalls().callStaticVoid(jniEnv, jniMethods_.endPointClass, jniMethods_.spawnServerMethod, jniArgs); + } catch (ForeignException foreignException) { + throw foreignException.throwOriginalException(throwableMarshaller); + } + } + } + + private static class EndPoint { + + private static final BinaryMarshaller throwableMarshaller; + static { + JNIConfig config = JDWPJNIConfig.getInstance(); + throwableMarshaller = config.lookupMarshaller(Throwable.class); + } + + @SuppressWarnings({"unused"}) + @JNIEntryPoint + static void onEventAt(JDWPEventHandlerBridge receiverObject, long threadId, long classId, byte typeTag, long methodId, int bci, byte resultTag, long resultPrimitiveOrId, int eventKindFlags) { + try { + receiverObject.onEventAt(threadId, classId, typeTag, methodId, bci, resultTag, resultPrimitiveOrId, eventKindFlags); + } catch (Throwable e) { + throw ForeignException.forThrowable(e, throwableMarshaller); + } + } + + @SuppressWarnings({"unused"}) + @JNIEntryPoint + static void onThreadDeath(JDWPEventHandlerBridge receiverObject, long threadId) { + try { + receiverObject.onThreadDeath(threadId); + } catch (Throwable e) { + throw ForeignException.forThrowable(e, throwableMarshaller); + } + } + + @SuppressWarnings({"unused"}) + @JNIEntryPoint + static void onThreadStart(JDWPEventHandlerBridge receiverObject, long threadId) { + try { + receiverObject.onThreadStart(threadId); + } catch (Throwable e) { + throw ForeignException.forThrowable(e, throwableMarshaller); + } + } + + @SuppressWarnings({"unused"}) + @JNIEntryPoint + static void onVMDeath(JDWPEventHandlerBridge receiverObject) { + try { + receiverObject.onVMDeath(); + } catch (Throwable e) { + throw ForeignException.forThrowable(e, throwableMarshaller); + } + } + + @SuppressWarnings({"unused"}) + @JNIEntryPoint + static void spawnServer(JDWPEventHandlerBridge receiverObject, String jdwpOptions, String additionalOptions, long isolate, long initialThreadId, long jdwpBridgeHandle, String metadataHashString, String metadataPath, boolean tracing) { + try { + receiverObject.spawnServer(jdwpOptions, additionalOptions, isolate, initialThreadId, jdwpBridgeHandle, metadataHashString, metadataPath, tracing); + } catch (Throwable e) { + throw ForeignException.forThrowable(e, throwableMarshaller); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/Packet.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/Packet.java new file mode 100644 index 000000000000..caf817d6d8a2 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/Packet.java @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +import java.nio.charset.StandardCharsets; + +public interface Packet { + + int NO_FLAGS = 0x0; + int REPLY = 0x80; + int REPLY_NO_ERROR = 0x0; + int HEADER_SIZE = 11; + + /** + * The size, in bytes, of JDWP packet, including the {@link #HEADER_SIZE header} or not. + * + * @param includeHeader specify if the {@link #HEADER_SIZE header bytes} are included or not + */ + default int length(boolean includeHeader) { + int packetLength = dataSize(); + if (includeHeader) { + packetLength += HEADER_SIZE; + } + return packetLength; + } + + /** + * The id field is used to uniquely identify each packet command/reply pair. A reply packet has + * the same id as the command packet to which it replies. This allows asynchronous commands and + * replies to be matched. The id field must be unique among all outstanding commands sent from + * one source. (Outstanding commands originating from the debugger may use the same id as + * outstanding commands originating from the target VM.) Other than that, there are no + * requirements on the allocation of id's. + * + * A simple monotonic counter should be adequate for most implementations. It will allow 2^32 + * unique outstanding packets and is the simplest implementation alternative. + */ + int id(); + + /** + * Flags are used to alter how any command is queued and processed and to tag command packets + * that originate from the target VM. There is currently one flag bits defined; future versions + * of the protocol may define additional flags. The reply bit {@code 0x80}, when set, indicates + * that this packet is a reply. + */ + byte flags(); + + default boolean isReply() { + return (flags() & REPLY) != 0; + } + + /** + * This field is used to indicate if the command packet that is being replied to was + * successfully processed. A value of zero indicates success, a non-zero value indicates an + * error. The error code returned may be specific to each command set/command, but it is often + * mapped to a JVM TI error code. + */ + short errorCode(); + + /** + * This field is useful as a means for grouping commands in a meaningful way. The Sun defined + * command sets are used to group commands by the interfaces they support in the JDI. For + * example, all commands that support the JDI VirtualMachine interface are grouped in a + * VirtualMachine command set. The command set space is roughly divided as follows: + *
        + *
      • 0 - 63 Sets of commands sent to the target VM
      • + *
      • 64 - 127 Sets of commands sent to the debugger
      • + *
      • 128 - 256 Vendor-defined commands and extensions.
      • + *
      + */ + byte commandSet(); + + /** + * This field identifies a particular command in a command set. This field, together with the + * command set field, is used to indicate how the command packet should be processed. More + * succinctly, they tell the receiver what to do. Specific commands are presented later in this + * document. + */ + byte command(); + + /** + * The size in bytes of the payload of this packet. + */ + int dataSize(); + + /** + * Returns the byte at the specified position in this packet's payload. + * + * @param index index of the bytes to return + * @throws IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= dataSize()}) + */ + byte data(int index); + + default byte[] toByteArray() { + int length = this.length(true); + PacketWriterBuffer writer = new PacketWriterBufferImpl(length); + writer.writeInt(length); + writer.writeInt(id()); + writer.writeByte(flags()); + if (this.isReply()) { + writer.writeShort(errorCode()); + } else { + writer.writeByte(commandSet()); + writer.writeByte(command()); + } + int dataSize = this.dataSize(); + for (int i = 0; i < dataSize; ++i) { + writer.writeByte(data(i)); + } + return writer.toByteArray(); + } + + default Reader newDataReader() { + return new Reader() { + + private int position; + + @Override + public int readByte() { + return Byte.toUnsignedInt(data(position++)); + } + + @Override + public boolean isEndOfInput() { + return position >= dataSize(); + } + }; + } + + /** + * Utility to read data from JDWP packets in big-endian, avoids using JDK code to prevent the + * debugger to breakpoint itself. + */ + interface Reader { + + /** + * Reads returns a single byte as an unsigned int. To signal the end-of-input a negative + * integer can be returned or an exception thrown. + */ + int readByte(); + + default boolean readBoolean() { + return (readByte() != 0); + } + + default char readChar() { + return (char) readShort(); + } + + default short readShort() { + int b0 = readByte() & 0xff; + int b1 = readByte() & 0xff; + return (short) ((b0 << 8) | b1); + } + + default int readInt() { + int b0 = readByte() & 0xff; + int b1 = readByte() & 0xff; + int b2 = readByte() & 0xff; + int b3 = readByte() & 0xff; + return ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3); + } + + default long readLong() { + long b0 = readByte() & 0xff; + long b1 = readByte() & 0xff; + long b2 = readByte() & 0xff; + long b3 = readByte() & 0xff; + long b4 = readByte() & 0xff; + long b5 = readByte() & 0xff; + long b6 = readByte() & 0xff; + long b7 = readByte() & 0xff; + return ((b0 << 56) | (b1 << 48) | (b2 << 40) | (b3 << 32) | (b4 << 24) | (b5 << 16) | (b6 << 8) | b7); + } + + default float readFloat() { + return Float.intBitsToFloat(readInt()); + } + + default double readDouble() { + return Double.longBitsToDouble(readLong()); + } + + default void readBytes(byte[] bytes) { + readBytes(bytes, 0, bytes.length); + } + + default void readBytes(byte[] bytes, int offset, int length) { + if (offset < 0 || length < 0 || offset > bytes.length - length) { + throw new ArrayIndexOutOfBoundsException(); + } + for (int i = 0; i < length; i++) { + bytes[offset + i] = (byte) (readByte() & 0xff); + } + } + + default String readString() { + int length = readInt(); + byte[] bytes = new byte[length]; + readBytes(bytes, 0, length); + return new String(bytes, StandardCharsets.UTF_8); + } + + default boolean isEndOfInput() { + return false; + } + } + + /** + * Utility to write payloads for JDWP packets in big-endian, avoids using JDK code to prevent + * the debugger to breakpoint itself. + */ + interface Writer { + /** + * Writes a single byte, represented by the least-significant 8-bits of the given integer, + * the remaining bits are ignored. + */ + void writeByte(int value); + + default void writeBoolean(boolean value) { + writeByte(value ? 1 : 0); + } + + default void writeChar(char value) { + writeShort((short) value); + } + + default void writeShort(short value) { + writeByte(value >> 8); + writeByte(value); + } + + default void writeInt(int value) { + writeByte(value >> 24); + writeByte(value >> 16); + writeByte(value >> 8); + writeByte(value); + } + + default void writeLong(long value) { + writeByte((int) (value >> 56)); + writeByte((int) (value >> 48)); + writeByte((int) (value >> 40)); + writeByte((int) (value >> 32)); + writeByte((int) (value >> 24)); + writeByte((int) (value >> 16)); + writeByte((int) (value >> 8)); + writeByte((int) value); + } + + default void writeFloat(float value) { + writeInt(Float.floatToRawIntBits(value)); + } + + default void writeDouble(double value) { + writeLong(Double.doubleToRawLongBits(value)); + } + + default void writeBytes(byte[] bytes) { + writeBytes(bytes, 0, bytes.length); + } + + default void writeBytes(byte[] bytes, int offset, int length) { + if (offset < 0 || length < 0 || offset > bytes.length - length) { + throw new ArrayIndexOutOfBoundsException(); + } + for (int i = 0; i < length; ++i) { + writeByte(bytes[offset + i]); + } + } + + default void writeString(String value) { + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + writeInt(bytes.length); + writeBytes(bytes); + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/PacketWriterBuffer.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/PacketWriterBuffer.java new file mode 100644 index 000000000000..6eb811405a42 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/PacketWriterBuffer.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +interface PacketWriterBuffer extends Packet.Writer { + int size(); + + byte byteAt(int index); + + default byte[] toByteArray() { + int length = size(); + byte[] result = new byte[length]; + for (int i = 0; i < length; i++) { + result[i] = this.byteAt(i); + } + return result; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/PacketWriterBufferImpl.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/PacketWriterBufferImpl.java new file mode 100644 index 000000000000..8145574097a2 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/PacketWriterBufferImpl.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +import java.util.Arrays; + +final class PacketWriterBufferImpl implements PacketWriterBuffer { + private static final int INITIAL_CAPACITY = 64; + + PacketWriterBufferImpl() { + this(INITIAL_CAPACITY); + } + + PacketWriterBufferImpl(int initialCapacity) { + this.size = 0; + this.bytes = new byte[initialCapacity]; + } + + private int size; + private byte[] bytes; + + private void ensureCapacity(int capacity) { + if (capacity < bytes.length) { + return; + } + int newSize = size(); + if (newSize == 0) { + newSize = 1; + } + while (newSize < capacity) { + newSize = newSize * 2; + } + this.bytes = Arrays.copyOf(this.bytes, newSize); + } + + @Override + public void writeByte(int value) { + ensureCapacity(size + 1); + bytes[size++] = (byte) value; + } + + @Override + public int size() { + return size; + } + + @Override + public byte[] toByteArray() { + return Arrays.copyOf(this.bytes, size()); + } + + @Override + public byte byteAt(int index) { + if (index >= size()) { + throw new ArrayIndexOutOfBoundsException(); + } + return this.bytes[index]; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/README.md b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/README.md new file mode 100644 index 000000000000..f95f700db8fe --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/README.md @@ -0,0 +1,18 @@ +#### Why + +The JDWP implementation is split into two parties, (1) the resident which resides in the application image and (2) the server which is either a Native Image application itself or runs on HotSpot. Those parties communicate over JNI, and live in the same process. + +The boilerplate code for that communication was generated by the native bridge annotation processor. The same library (`sdk:nativebridge`) is also used for Polyglot Isolates in Truffle, and therefore is not part of a GraalVM but is obtained via maven or other means if needed. This means the JDWP implementation cannot use the same library, as the JDWP implementation is a component of GraalVM. + +One way to solve this would be shadowing, however that is potentially tricky as an annotation processor is involved. Since the interface isn't expected to change much anymore, we decided to check in the generated code instead and duplicate dependencies to it, see subpackages `jnuitils` and `nativebridge` in this project. + + +The sources of `jniutils` and `nativebridge` packages are a copy based on 199e453676902b74bba06581b259bb928d5f9a27 in the CE repository. + +### How to upgrade `NativeToHSJDWPEventHandlerBridgeGen` and `HSToNativeJDWPBridgeGen` + +1. Uncomment `@GenerateNativeToHotSpotBridge` annotation in `NativeToHSJDWPEventHandlerBridge` and `@GenerateHotSpotToNativeBridge` in `HSToNativeJDWPBridge` +2. Adapt imports in generated file accordingly. +3. There are a few cases where the fully qualified name to a class is used in a string, those must be updated as well. +4. Sync sources in `jniutils` and `nativebridge` if a newer revision is used. +5. Add `sdk:NATIVEBRIDGE_PROCESSOR` as `annotationProcessors` and `sdk:NATIVEBRIDGE` as dependency for this project, and build the project specifically with `mx build --target com.oracle.svm.jdwp.server`. diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/ResidentJDWPFeatureEnabled.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/ResidentJDWPFeatureEnabled.java new file mode 100644 index 000000000000..d84c4453f9ca --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/ResidentJDWPFeatureEnabled.java @@ -0,0 +1,48 @@ +/* + * 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.jdwp.bridge; + +import java.util.function.BooleanSupplier; + +import org.graalvm.nativeimage.ImageSingletons; + +/** + * A marker to detect the presence of the {@code ResidentJDWPFeature}. The + * {@code ResidentJDWPFeature} registers the {@link ResidentJDWPFeatureEnabled} instance into an + * {@link ImageSingletons}. The polyglot isolate code can use {@link ImageSingletons#contains(Class) + * ImageSingletons.contains(ResidentJDWPFeatureEnabled.class)} to prevent SubstrateVM from including + * methods that should not be reachable on the guest side. + */ +public final class ResidentJDWPFeatureEnabled implements BooleanSupplier { + + @Override + public boolean getAsBoolean() { + return isEnabled(); + } + + static boolean isEnabled() { + return ImageSingletons.contains(ResidentJDWPFeatureEnabled.class); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/ServerJDWPFeatureEnabled.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/ServerJDWPFeatureEnabled.java new file mode 100644 index 000000000000..f3103bce7b55 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/ServerJDWPFeatureEnabled.java @@ -0,0 +1,48 @@ +/* + * 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.jdwp.bridge; + +import java.util.function.BooleanSupplier; + +import org.graalvm.nativeimage.ImageSingletons; + +/** + * A marker to detect the presence of the {@code ServerJDWPFeature}. The {@code ServerJDWPFeature} + * registers the {@link ServerJDWPFeatureEnabled} instance into an {@link ImageSingletons}. The + * polyglot isolate code can use {@link ImageSingletons#contains(Class) + * ImageSingletons.contains(ServerJDWPFeatureEnabled.class)} to prevent SubstrateVM from including + * methods that should not be reachable on the guest side. + */ +public final class ServerJDWPFeatureEnabled implements BooleanSupplier { + + @Override + public boolean getAsBoolean() { + return isEnabled(); + } + + static boolean isEnabled() { + return ImageSingletons.contains(ServerJDWPFeatureEnabled.class); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/StackFrame.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/StackFrame.java new file mode 100644 index 000000000000..8278a41ea308 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/StackFrame.java @@ -0,0 +1,40 @@ +/* + * 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.jdwp.bridge; + +/** + * Symbolic representation of a frame. This class does not contain the frame id, since this is a + * concept only on the server. + */ +public record StackFrame(byte typeTag, long classId, long methodId, int bci, int frameDepth) { + public StackFrame(byte typeTag, long classId, long methodId, int bci, int frameDepth) { + this.typeTag = typeTag; + this.classId = classId; + this.methodId = methodId; + assert frameDepth >= 0; + this.frameDepth = frameDepth; + this.bci = bci; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/SteppingControlConstants.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/SteppingControlConstants.java new file mode 100644 index 000000000000..8cc7c928a0de --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/SteppingControlConstants.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +public class SteppingControlConstants { + /** + * Step into any newly pushed frames. + */ + public static final int STEP_INTO = 1; + /** + * Step over any newly pushed frames. + */ + public static final int STEP_OVER = 2; + /** + * Step out of the current frame. + */ + public static final int STEP_OUT = 3; + + /** + * Step to the next available location. + */ + public static final int STEP_MIN = -1; + /** + * Step to the next location on a different line. + */ + public static final int STEP_LINE = -2; +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/SymbolicRefs.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/SymbolicRefs.java new file mode 100644 index 000000000000..d4deabd7a27a --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/SymbolicRefs.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + +public interface SymbolicRefs { + + long NULL = 0L; + + /** + * Returns the handle/id of the given "symbolic" type reference. If the given type is + * {@code null}, then {@code 0L} is returned. + * + * @throws IllegalArgumentException if the given reference is not part of the + * interpreter/metadata universe e.g. an arbitrary {@link ResolvedJavaType} + * instance. + */ + long toTypeRef(ResolvedJavaType resolvedJavaType); + + /** + * Returns the handle/id of the given "symbolic" field reference. If the given type is + * {@code null}, then {@code 0L} is returned. + * + * @throws IllegalArgumentException if the given reference is not part of the * + * interpreter/metadata universe e.g. an arbitrary {@link ResolvedJavaField} + * instance. + */ + long toFieldRef(ResolvedJavaField resolvedJavaField); + + /** + * Returns the handle/id of the given "symbolic" method reference associated. If the given type + * is {@code null}, then {@code 0L} is returned. + * + * @throws IllegalArgumentException if the given reference is not part of the * + * interpreter/metadata universe e.g. an arbitrary {@link ResolvedJavaMethod} + * instance. + */ + long toMethodRef(ResolvedJavaMethod resolvedJavaMethod); + + /** + * Returns the "symbolic" type reference associated for the given handle/id. If the given + * handle/id is {@code 0L}, then {@code null} is returned. + * + * @throws JDWPException with {@link ErrorCode#INVALID_CLASS} is handle/id does not refer to an + * instance {@link ResolvedJavaField} part of the interpreter/metadata universe. + * @throws JDWPException with {@link ErrorCode#INVALID_OBJECT} if was unloaded or garbage + * collected + */ + ResolvedJavaType toResolvedJavaType(long typeRefId) throws JDWPException; + + /** + * Returns the "symbolic" field reference associated for the given handle/id. If the given + * handle/id is {@code 0L}, then {@code null} is returned. + * + * @throws JDWPException with {@link ErrorCode#INVALID_FIELDID} is handle/id does not refer to + * an instance {@link ResolvedJavaField} part of the interpreter/metadata universe. + * @throws JDWPException with {@link ErrorCode#INVALID_OBJECT} if was unloaded or garbage + * collected + */ + ResolvedJavaField toResolvedJavaField(long fieldRefId) throws JDWPException; + + /** + * Returns the "symbolic" method reference associated for the given handle/id. If the given + * handle/id is {@code 0L}, then {@code null} is returned. + * + * @throws JDWPException with {@link ErrorCode#INVALID_METHODID} is handle/id does not refer to + * an instance {@link ResolvedJavaMethod} part of the interpreter/metadata universe + * @throws JDWPException with {@link ErrorCode#INVALID_OBJECT} if was unloaded or garbage + * collected + */ + ResolvedJavaMethod toResolvedJavaMethod(long methodRefId) throws JDWPException; +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/TagConstants.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/TagConstants.java new file mode 100644 index 000000000000..3b9a958adf5a --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/TagConstants.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +import com.oracle.svm.core.util.VMError; + +import jdk.vm.ci.meta.JavaKind; + +public final class TagConstants { + + public static final byte ARRAY = '['; + public static final byte BYTE = 'B'; + public static final byte CHAR = 'C'; + public static final byte OBJECT = 'L'; + public static final byte FLOAT = 'F'; + public static final byte DOUBLE = 'D'; + public static final byte INT = 'I'; + public static final byte LONG = 'J'; + public static final byte SHORT = 'S'; + public static final byte VOID = 'V'; + public static final byte BOOLEAN = 'Z'; + public static final byte STRING = 's'; + public static final byte THREAD = 't'; + public static final byte THREAD_GROUP = 'g'; + public static final byte CLASS_LOADER = 'l'; + public static final byte CLASS_OBJECT = 'c'; + + private TagConstants() { + } + + public static boolean isPrimitive(byte tag) { + return tag != OBJECT && + tag != STRING && + tag != ARRAY && + tag != THREAD && + tag != THREAD_GROUP && + tag != CLASS_OBJECT && + tag != CLASS_LOADER; + } + + public static byte getTagFromPrimitive(Object boxed) { + if (boxed instanceof Integer) { + return INT; + } + if (boxed instanceof Float) { + return FLOAT; + } + if (boxed instanceof Double) { + return DOUBLE; + } + if (boxed instanceof Long) { + return LONG; + } + if (boxed instanceof Byte) { + return BYTE; + } + if (boxed instanceof Short) { + return SHORT; + } + if (boxed instanceof Character) { + return CHAR; + } + if (boxed instanceof Boolean) { + return BOOLEAN; + } + throw new RuntimeException("Boxed object: " + boxed.getClass() + " is not a primitive"); + } + + public static Class getClassOfPrimitiveTag(int tag) { + return switch (tag) { + case INT -> Integer.TYPE; + case FLOAT -> Float.TYPE; + case DOUBLE -> Double.TYPE; + case LONG -> Long.TYPE; + case BYTE -> Byte.TYPE; + case SHORT -> Short.TYPE; + case CHAR -> Character.TYPE; + case BOOLEAN -> Boolean.TYPE; + default -> throw new IllegalArgumentException(Integer.toString(tag)); + }; + } + + public static boolean isValidTag(byte tag) { + return switch (tag) { + case ARRAY, BYTE, CHAR, OBJECT, FLOAT, DOUBLE, INT, LONG, SHORT, VOID, BOOLEAN, STRING, THREAD, THREAD_GROUP, CLASS_LOADER, CLASS_OBJECT -> true; + default -> false; + }; + } + + public static byte getTagFromClass(Class clazz) { + if (clazz.isArray()) { + return ARRAY; + } + if (clazz.isPrimitive()) { + if (clazz == Integer.TYPE) { + return INT; + } + if (clazz == Float.TYPE) { + return FLOAT; + } + if (clazz == Double.TYPE) { + return DOUBLE; + } + if (clazz == Long.TYPE) { + return LONG; + } + if (clazz == Byte.TYPE) { + return BYTE; + } + if (clazz == Short.TYPE) { + return SHORT; + } + if (clazz == Character.TYPE) { + return CHAR; + } + if (clazz == Boolean.TYPE) { + return BOOLEAN; + } + if (clazz == Void.TYPE) { + return VOID; + } + throw VMError.shouldNotReachHere("Unknown primitive class: " + clazz.getName()); + } else { + if (clazz == String.class) { + return STRING; + } + if (clazz == Class.class) { + return CLASS_OBJECT; + } + // These can be sub-classed, check for subtypes. + if (Thread.class.isAssignableFrom(clazz)) { + return THREAD; + } + if (ThreadGroup.class.isAssignableFrom(clazz)) { + return THREAD_GROUP; + } + if (ClassLoader.class.isAssignableFrom(clazz)) { + return CLASS_LOADER; + } + return OBJECT; + } + } + + public static byte getTagFromReference(Object ref) { + if (ref == null) { + return OBJECT; + } + return getTagFromClass(ref.getClass()); + } + + public static JavaKind tagToKind(byte tag) { + return switch (tag) { + case BYTE -> JavaKind.Byte; + case CHAR -> JavaKind.Char; + case FLOAT -> JavaKind.Float; + case DOUBLE -> JavaKind.Double; + case INT -> JavaKind.Int; + case LONG -> JavaKind.Long; + case SHORT -> JavaKind.Short; + case VOID -> JavaKind.Void; + case BOOLEAN -> JavaKind.Boolean; + case OBJECT, ARRAY, STRING, THREAD, THREAD_GROUP, CLASS_LOADER, CLASS_OBJECT -> JavaKind.Object; + default -> throw VMError.shouldNotReachHere("unreachable"); + }; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/TypeTag.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/TypeTag.java new file mode 100644 index 000000000000..62911ba6562c --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/TypeTag.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +import com.oracle.svm.core.util.VMError; + +import jdk.vm.ci.meta.ResolvedJavaType; + +public final class TypeTag { + + public static final byte CLASS = 1; + public static final byte INTERFACE = 2; + public static final byte ARRAY = 3; + + private TypeTag() { + } + + public static byte getKind(ResolvedJavaType type) { + if (type.isArray()) { + return ARRAY; + } else if (type.isInterface()) { + return INTERFACE; + } else { + VMError.guarantee(!type.isPrimitive()); + return CLASS; + } + } + + public static boolean isValidTypeTag(byte kind) { + return kind == CLASS || kind == INTERFACE || kind == ARRAY; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/UnmodifiablePacket.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/UnmodifiablePacket.java new file mode 100644 index 000000000000..1d59b82905a8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/UnmodifiablePacket.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +public final class UnmodifiablePacket implements Packet { + + private final int id; + private final byte flags; + private final byte commandSet; + private final byte command; + private final short errorCode; + private final byte[] packetBytes; + + public static UnmodifiablePacket parseAndWrap(byte[] packetBytes) { + return new UnmodifiablePacket(packetBytes); + } + + private UnmodifiablePacket(byte[] packetBytes) { + if (packetBytes.length < HEADER_SIZE) { + throw new IllegalArgumentException(); + } + + Reader reader = new Reader() { + private int position; + + @Override + public int readByte() { + return Byte.toUnsignedInt(packetBytes[position++]); + } + + @Override + public boolean isEndOfInput() { + return position >= packetBytes.length; + } + }; + int length = reader.readInt(); + if (length != packetBytes.length) { + throw new IllegalArgumentException(); + } + + this.id = reader.readInt(); + this.flags = (byte) reader.readByte(); + + if (isReply()) { + this.errorCode = reader.readShort(); + this.commandSet = 0; + this.command = 0; + } else { + this.errorCode = REPLY_NO_ERROR; + this.commandSet = (byte) reader.readByte(); + this.command = (byte) reader.readByte(); + } + this.packetBytes = packetBytes; + } + + @Override + public int id() { + return id; + } + + @Override + public byte flags() { + return flags; + } + + @Override + public short errorCode() { + return errorCode; + } + + @Override + public byte commandSet() { + return commandSet; + } + + @Override + public byte command() { + return command; + } + + @Override + public int dataSize() { + return packetBytes.length - HEADER_SIZE; + } + + @Override + public byte data(int index) { + if (index < 0) { + throw new ArrayIndexOutOfBoundsException(); + } + return packetBytes[index + HEADER_SIZE]; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/WritablePacket.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/WritablePacket.java new file mode 100644 index 000000000000..57aae9f19714 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/WritablePacket.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.bridge; + +public final class WritablePacket implements Packet { + + private int id; + private byte flags; + private short errorCode; + private byte commandSet; + private byte command; + + private PacketWriterBuffer dataWriter = new PacketWriterBufferImpl(); + + @Override + public int id() { + return id; + } + + public WritablePacket id(int newId) { + this.id = newId; + return this; + } + + @Override + public byte flags() { + return flags; + } + + public WritablePacket flags(byte newFlags) { + this.flags = newFlags; + return this; + } + + @Override + public short errorCode() { + return errorCode; + } + + public WritablePacket errorCode(short newErrorCode) { + this.errorCode = newErrorCode; + return this; + } + + public WritablePacket errorCode(ErrorCode newErrorCode) { + this.errorCode = (short) newErrorCode.value(); + return this; + } + + @Override + public byte commandSet() { + return commandSet; + } + + public WritablePacket commandSet(byte newCommandSet) { + this.commandSet = newCommandSet; + return this; + } + + @Override + public byte command() { + return command; + } + + public WritablePacket command(byte newCommand) { + this.command = newCommand; + return this; + } + + @Override + public int dataSize() { + return dataWriter.size(); + } + + @Override + public byte data(int index) { + return dataWriter.byteAt(index); + } + + public Packet.Writer dataWriter() { + return this.dataWriter; + } + + public WritablePacket dataWriter(PacketWriterBuffer newDataWriter) { + this.dataWriter = newDataWriter; + return this; + } + + public static WritablePacket newReplyTo(Packet pkt) { + return new WritablePacket().id(pkt.id()).flags((byte) REPLY).errorCode((byte) REPLY_NO_ERROR); + } + + public static WritablePacket commandPacket() { + // Note: Command packet uses the current Packet.uID (without incrementing it?). + return new WritablePacket().commandSet((byte) JDWP.Event).command((byte) JDWP.Event_Composite); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/HSObject.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/HSObject.java new file mode 100644 index 000000000000..31a665fadd34 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/HSObject.java @@ -0,0 +1,223 @@ +/* + * 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. + */ +// Checkstyle: allow Class.getSimpleName +package com.oracle.svm.jdwp.bridge.jniutils; + +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.DeleteGlobalRef; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.DeleteWeakGlobalRef; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.NewGlobalRef; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.NewWeakGlobalRef; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JNIEnv; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JObject; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JWeak; +import org.graalvm.word.WordFactory; + +/** + * Encapsulates a JNI handle to an object in the HotSpot heap. Depending on which constructor is + * used, the handle is either local to a {@link JNIMethodScope} and thus invalid once the scope + * exits or a global JNI handle that is only released sometime after the {@link HSObject} dies. + */ +public class HSObject { + + /** + * JNI handle to the HotSpot object. + */ + private final JObject handle; + + /** + * Cleaner for Global Reference. + */ + private final Cleaner cleaner; + + /** + * Link to next the next scope local object. The head of the list is in + * {@link JNIMethodScope#locals}. The handle of a scope local object is only valid for the + * lifetime of a {@link JNIMethodScope}. A self-reference (i.e. {@code this.next == this}) + * denotes an object whose {@link JNIMethodScope} has closed. + * + * This field is {@code null} for a non-scope local object. + */ + private HSObject next; + + /** + * Creates an object encapsulating a {@code handle} whose lifetime is determined by this object. + * The created {@link HSObject} uses a JNI global reference and does not allow duplicate JNI + * references. Use {@link #HSObject(JNIEnv, JObject, boolean, boolean)} to create + * {@link HSObject} using a JNI weak reference or allowing duplicate JNI references. + */ + public HSObject(JNIEnv env, JObject handle) { + this(env, handle, false, false); + } + + /** + * Creates an object encapsulating a {@code handle} whose lifetime is determined by this object. + * The created {@link HSObject} possibly allows duplicate JNI global handles. + */ + public HSObject(JNIEnv env, JObject handle, boolean allowGlobalDuplicates, boolean weak) { + cleanHandles(env); + if (checkingGlobalDuplicates(allowGlobalDuplicates)) { + checkNonExistingGlobalReference(env, handle); + } + String name = this.getClass().getName(); + this.handle = weak ? NewWeakGlobalRef(env, handle, name) : NewGlobalRef(env, handle, name); + cleaner = new Cleaner(this, this.handle, allowGlobalDuplicates, weak); + CLEANERS.add(cleaner); + next = null; + } + + private static boolean checkingGlobalDuplicates(boolean allowGlobalDuplicates) { + return !allowGlobalDuplicates && (assertionsEnabled() || JNIUtil.tracingAt(1)); + } + + /** + * Creates an object encapsulating a {@code handle} whose lifetime is limited to {@code scope}. + * Once {@code scope.close()} is called, any attempt to {@linkplain #getHandle() use} the handle + * will result in an {@link IllegalArgumentException}. + */ + public HSObject(JNIMethodScope scope, JObject handle) { + this.handle = handle; + next = scope.locals; + scope.locals = this; + cleaner = null; + } + + /** + * Invalidates the objects in the list starting at {@code head} such that subsequently calling + * {@link #getHandle()} on any of the objects results in an {@link IllegalArgumentException}. + * + * @return the number of objects in the list + */ + static int invalidate(HSObject head) { + HSObject o = head; + int count = 0; + while (o != null) { + HSObject next = o.next; + // Makes the handle now invalid. + o.next = o; + o = next; + count++; + } + return count; + } + + public final JObject getHandle() { + if (next == this) { + throw new IllegalArgumentException("Reclaimed JNI reference: " + this); + } + return handle; + } + + @Override + public String toString() { + return String.format("%s[0x%x]", getClass().getSimpleName(), handle.rawValue()); + } + + public final void release(JNIEnv env) { + if (cleaner != null) { + assert next == null || next == this; + this.next = this; + cleaner.clean(env); + } + } + + /** + * Processes {@link #CLEANERS_QUEUE} to release any handles whose objects are now unreachable. + */ + public static void cleanHandles(JNIEnv env) { + Cleaner cleaner; + while ((cleaner = (Cleaner) CLEANERS_QUEUE.poll()) != null) { + cleaner.clean(env); + } + } + + private static void checkNonExistingGlobalReference(JNIEnv env, JObject handle) { + for (Cleaner cleaner : CLEANERS) { + synchronized (cleaner) { + if (cleaner.handle.isNonNull() && JNIUtil.IsSameObject(env, handle, cleaner.handle)) { + throw new IllegalArgumentException("Global JNI handle already exists for object referenced by " + handle.rawValue()); + } + } + } + } + + private static boolean assertionsEnabled() { + boolean res = false; + assert (res = true) == true; + return res; + } + + /** + * Strong references to the {@link PhantomReference} objects. + */ + private static final Set CLEANERS = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + /** + * Queue into which a {@link Cleaner} is enqueued when its {@link HSObject} referent becomes + * unreachable. + */ + private static final ReferenceQueue CLEANERS_QUEUE = new ReferenceQueue<>(); + + private static final class Cleaner extends PhantomReference { + + private JObject handle; + private final boolean allowGlobalDuplicates; + private final boolean weak; + + Cleaner(HSObject referent, JObject handle, boolean allowGlobalDuplicates, boolean weak) { + super(referent, CLEANERS_QUEUE); + this.handle = handle; + this.allowGlobalDuplicates = allowGlobalDuplicates; + this.weak = weak; + } + + void clean(JNIEnv env) { + if (CLEANERS.remove(this)) { + if (checkingGlobalDuplicates(allowGlobalDuplicates)) { + synchronized (this) { + delete(env); + handle = WordFactory.nullPointer(); + } + } else { + delete(env); + } + } + } + + private void delete(JNIEnv env) { + if (weak) { + DeleteWeakGlobalRef(env, (JWeak) handle); + } else { + DeleteGlobalRef(env, handle); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNI.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNI.java new file mode 100644 index 000000000000..97e677bfc23c --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNI.java @@ -0,0 +1,1111 @@ +/* + * 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.jdwp.bridge.jniutils; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.CContext; +import org.graalvm.nativeimage.c.function.CFunction.Transition; +import org.graalvm.nativeimage.c.function.CFunctionPointer; +import org.graalvm.nativeimage.c.function.InvokeCFunctionPointer; +import org.graalvm.nativeimage.c.struct.CField; +import org.graalvm.nativeimage.c.struct.CPointerTo; +import org.graalvm.nativeimage.c.struct.CStruct; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.c.type.CDoublePointer; +import org.graalvm.nativeimage.c.type.CFloatPointer; +import org.graalvm.nativeimage.c.type.CIntPointer; +import org.graalvm.nativeimage.c.type.CLongPointer; +import org.graalvm.nativeimage.c.type.CShortPointer; +import org.graalvm.nativeimage.c.type.VoidPointer; +import org.graalvm.word.PointerBase; + +public final class JNI { + + public static final int JNI_OK = 0; + public static final int JNI_ERR = -1; /* unknown error */ + public static final int JNI_EDETACHED = -2; /* thread detached from the VM */ + public static final int JNI_EVERSION = -3; /* JNI version error */ + public static final int JNI_ENOMEM = -4; /* not enough memory */ + public static final int JNI_EEXIST = -5; /* VM already created */ + public static final int JNI_EINVAL = -6; /* invalid arguments */ + public static final int JNI_VERSION_10 = 0x000a0000; + + private JNI() { + throw new IllegalStateException("No instance allowed"); + } + + public interface JMethodID extends PointerBase { + } + + public interface JFieldID extends PointerBase { + } + + public interface JObject extends PointerBase { + } + + public interface JArray extends JObject { + int MODE_WRITE_RELEASE = 0; + int MODE_WRITE = 1; + int MODE_RELEASE = 2; + } + + public interface JBooleanArray extends JArray { + } + + public interface JByteArray extends JArray { + } + + public interface JCharArray extends JArray { + } + + public interface JShortArray extends JArray { + } + + public interface JIntArray extends JArray { + } + + public interface JLongArray extends JArray { + } + + public interface JFloatArray extends JArray { + } + + public interface JDoubleArray extends JArray { + } + + public interface JObjectArray extends JArray { + } + + public interface JClass extends JObject { + } + + public interface JString extends JObject { + } + + public interface JThrowable extends JObject { + } + + public interface JWeak extends JObject { + } + + /** + * Access to the {@code jvalue} JNI union. + * + *
      +     * typedef union jvalue {
      +     *    jboolean z;
      +     *    jbyte    b;
      +     *    jchar    c;
      +     *    jshort   s;
      +     *    jint     i;
      +     *    jlong    j;
      +     *    jfloat   f;
      +     *    jdouble  d;
      +     *    jobject  l;
      +     * } jvalue;
      +     * 
      + */ + @CContext(JNIHeaderDirectives.class) + @CStruct("jvalue") + public interface JValue extends PointerBase { + // @formatter:off + @CField("z") boolean getBoolean(); + @CField("b") byte getByte(); + @CField("c") char getChar(); + @CField("s") short getShort(); + @CField("i") int getInt(); + @CField("j") long getLong(); + @CField("f") float getFloat(); + @CField("d") double getDouble(); + @CField("l") JObject getJObject(); + + @CField("z") void setBoolean(boolean b); + @CField("b") void setByte(byte b); + @CField("c") void setChar(char ch); + @CField("s") void setShort(short s); + @CField("i") void setInt(int i); + @CField("j") void setLong(long l); + @CField("f") void setFloat(float f); + @CField("d") void setDouble(double d); + @CField("l") void setJObject(JObject obj); + // @formatter:on + + /** + * Gets JValue in an array of JValues pointed to by this object. + */ + JValue addressOf(int index); + } + + @CContext(JNIHeaderDirectives.class) + @CStruct(value = "JNIEnv_", addStructKeyword = true) + public interface JNIEnv extends PointerBase { + @CField("functions") + JNINativeInterface getFunctions(); + } + + @CPointerTo(JNIEnv.class) + public interface JNIEnvPointer extends PointerBase { + JNIEnv readJNIEnv(); + + void writeJNIEnv(JNIEnv env); + } + + @CContext(JNIHeaderDirectives.class) + @CStruct(value = "JNINativeInterface_", addStructKeyword = true) + public interface JNINativeInterface extends PointerBase { + + @CField("NewString") + NewString getNewString(); + + @CField("GetStringLength") + GetStringLength getGetStringLength(); + + @CField("GetStringChars") + GetStringChars getGetStringChars(); + + @CField("ReleaseStringChars") + ReleaseStringChars getReleaseStringChars(); + + @CField("NewStringUTF") + NewStringUTF8 getNewStringUTF(); + + @CField("GetStringUTFLength") + GetStringUTFLength getGetStringUTFLength(); + + @CField("GetStringUTFChars") + GetStringUTFChars getGetStringUTFChars(); + + @CField("ReleaseStringUTFChars") + ReleaseStringUTFChars getReleaseStringUTFChars(); + + @CField("GetArrayLength") + GetArrayLength getGetArrayLength(); + + @CField("NewLocalRef") + NewLocalRef getNewLocalRef(); + + @CField("NewObjectArray") + NewObjectArray getNewObjectArray(); + + @CField("NewBooleanArray") + NewBooleanArray getNewBooleanArray(); + + @CField("NewByteArray") + NewByteArray getNewByteArray(); + + @CField("NewCharArray") + NewCharArray getNewCharArray(); + + @CField("NewShortArray") + NewShortArray getNewShortArray(); + + @CField("NewIntArray") + NewIntArray getNewIntArray(); + + @CField("NewLongArray") + NewLongArray getNewLongArray(); + + @CField("NewFloatArray") + NewFloatArray getNewFloatArray(); + + @CField("NewDoubleArray") + NewDoubleArray getNewDoubleArray(); + + @CField("GetObjectArrayElement") + GetObjectArrayElement getGetObjectArrayElement(); + + @CField("SetObjectArrayElement") + SetObjectArrayElement getSetObjectArrayElement(); + + @CField("GetBooleanArrayElements") + GetBooleanArrayElements getGetBooleanArrayElements(); + + @CField("GetByteArrayElements") + GetByteArrayElements getGetByteArrayElements(); + + @CField("GetCharArrayElements") + GetCharArrayElements getGetCharArrayElements(); + + @CField("GetShortArrayElements") + GetShortArrayElements getGetShortArrayElements(); + + @CField("GetIntArrayElements") + GetIntArrayElements getGetIntArrayElements(); + + @CField("GetLongArrayElements") + GetLongArrayElements getGetLongArrayElements(); + + @CField("GetFloatArrayElements") + GetFloatArrayElements getGetFloatArrayElements(); + + @CField("GetDoubleArrayElements") + GetDoubleArrayElements getGetDoubleArrayElements(); + + @CField("ReleaseBooleanArrayElements") + ReleaseBooleanArrayElements getReleaseBooleanArrayElements(); + + @CField("ReleaseByteArrayElements") + ReleaseByteArrayElements getReleaseByteArrayElements(); + + @CField("ReleaseCharArrayElements") + ReleaseCharArrayElements getReleaseCharArrayElements(); + + @CField("ReleaseShortArrayElements") + ReleaseShortArrayElements getReleaseShortArrayElements(); + + @CField("ReleaseIntArrayElements") + ReleaseIntArrayElements getReleaseIntArrayElements(); + + @CField("ReleaseLongArrayElements") + ReleaseLongArrayElements getReleaseLongArrayElements(); + + @CField("ReleaseFloatArrayElements") + ReleaseFloatArrayElements getReleaseFloatArrayElements(); + + @CField("ReleaseDoubleArrayElements") + ReleaseDoubleArrayElements getReleaseDoubleArrayElements(); + + @CField("GetBooleanArrayRegion") + GetBooleanArrayRegion getGetBooleanArrayRegion(); + + @CField("GetByteArrayRegion") + GetByteArrayRegion getGetByteArrayRegion(); + + @CField("GetCharArrayRegion") + GetCharArrayRegion getGetCharArrayRegion(); + + @CField("GetShortArrayRegion") + GetShortArrayRegion getGetShortArrayRegion(); + + @CField("GetIntArrayRegion") + GetIntArrayRegion getGetIntArrayRegion(); + + @CField("GetLongArrayRegion") + GetLongArrayRegion getGetLongArrayRegion(); + + @CField("GetFloatArrayRegion") + GetFloatArrayRegion getGetFloatArrayRegion(); + + @CField("GetDoubleArrayRegion") + GetDoubleArrayRegion getGetDoubleArrayRegion(); + + @CField("SetBooleanArrayRegion") + SetBooleanArrayRegion getSetBooleanArrayRegion(); + + @CField("SetByteArrayRegion") + SetByteArrayRegion getSetByteArrayRegion(); + + @CField("SetCharArrayRegion") + SetCharArrayRegion getSetCharArrayRegion(); + + @CField("SetShortArrayRegion") + SetShortArrayRegion getSetShortArrayRegion(); + + @CField("SetIntArrayRegion") + SetIntArrayRegion getSetIntArrayRegion(); + + @CField("SetLongArrayRegion") + SetLongArrayRegion getSetLongArrayRegion(); + + @CField("SetFloatArrayRegion") + SetFloatArrayRegion getSetFloatArrayRegion(); + + @CField("SetDoubleArrayRegion") + SetDoubleArrayRegion getSetDoubleArrayRegion(); + + @CField("FindClass") + FindClass getFindClass(); + + @CField("DefineClass") + DefineClass getDefineClass(); + + @CField("IsSameObject") + IsSameObject getIsSameObject(); + + @CField("GetObjectClass") + GetObjectClass getGetObjectClass(); + + @CField("NewGlobalRef") + NewGlobalRef getNewGlobalRef(); + + @CField("DeleteGlobalRef") + DeleteGlobalRef getDeleteGlobalRef(); + + @CField("NewWeakGlobalRef") + NewWeakGlobalRef getNewWeakGlobalRef(); + + @CField("DeleteWeakGlobalRef") + DeleteWeakGlobalRef getDeleteWeakGlobalRef(); + + @CField("DeleteLocalRef") + DeleteLocalRef getDeleteLocalRef(); + + @CField("PushLocalFrame") + PushLocalFrame getPushLocalFrame(); + + @CField("PopLocalFrame") + PopLocalFrame getPopLocalFrame(); + + @CField("NewObjectA") + NewObjectA getNewObjectA(); + + @CField("GetStaticMethodID") + GetStaticMethodID getGetStaticMethodID(); + + @CField("GetMethodID") + GetMethodID getGetMethodID(); + + @CField("GetStaticFieldID") + GetStaticFieldID getGetStaticFieldID(); + + @CField("GetFieldID") + GetFieldID getGetFieldID(); + + @CField("CallStaticBooleanMethodA") + CallStaticBooleanMethodA getCallStaticBooleanMethodA(); + + @CField("CallStaticIntMethodA") + CallStaticIntMethodA getCallStaticIntMethodA(); + + @CField("CallStaticVoidMethodA") + CallStaticVoidMethodA getCallStaticVoidMethodA(); + + @CField("CallStaticObjectMethodA") + CallStaticObjectMethodA getCallStaticObjectMethodA(); + + @CField("CallStaticLongMethodA") + CallStaticLongMethodA getCallStaticLongMethodA(); + + @CField("CallObjectMethodA") + CallObjectMethodA getCallObjectMethodA(); + + @CField("CallVoidMethodA") + CallVoidMethodA getCallVoidMethodA(); + + @CField("CallBooleanMethodA") + CallBooleanMethodA getCallBooleanMethodA(); + + @CField("CallShortMethodA") + CallShortMethodA getCallShortMethodA(); + + @CField("CallIntMethodA") + CallIntMethodA getCallIntMethodA(); + + @CField("CallLongMethodA") + CallLongMethodA getCallLongMethodA(); + + @CField("CallDoubleMethodA") + CallDoubleMethodA getCallDoubleMethodA(); + + @CField("CallFloatMethodA") + CallFloatMethodA getCallFloatMethodA(); + + @CField("CallByteMethodA") + CallByteMethodA getCallByteMethodA(); + + @CField("CallCharMethodA") + CallCharMethodA getCallCharMethodA(); + + @CField("GetStaticObjectField") + GetStaticObjectField getGetStaticObjectField(); + + @CField("GetIntField") + GetIntField getGetIntField(); + + @CField("GetStaticBooleanField") + GetStaticBooleanField getGetStaticBooleanField(); + + @CField("SetStaticBooleanField") + SetStaticBooleanField getSetStaticBooleanField(); + + @CField("ExceptionCheck") + ExceptionCheck getExceptionCheck(); + + @CField("ExceptionOccurred") + ExceptionOccurred getExceptionOccurred(); + + @CField("ExceptionClear") + ExceptionClear getExceptionClear(); + + @CField("ExceptionDescribe") + ExceptionDescribe getExceptionDescribe(); + + @CField("Throw") + Throw getThrow(); + + @CField("GetObjectRefType") + GetObjectRefType getGetObjectRefType(); + + @CField("GetDirectBufferAddress") + GetDirectBufferAddress getGetDirectBufferAddress(); + + @CField("IsInstanceOf") + IsInstanceOf getIsInstanceOf(); + + @CField("GetJavaVM") + GetJavaVM getGetJavaVM(); + } + + @CContext(JNIHeaderDirectives.class) + @CStruct(value = "JavaVM_", addStructKeyword = true) + public interface JavaVM extends PointerBase { + @CField("functions") + JNIInvokeInterface getFunctions(); + } + + @CPointerTo(JavaVM.class) + public interface JavaVMPointer extends PointerBase { + JavaVM readJavaVM(); + + void writeJavaVM(JavaVM javaVM); + } + + @CContext(JNIHeaderDirectives.class) + @CStruct(value = "JavaVMAttachArgs", addStructKeyword = true) + public interface JavaVMAttachArgs extends PointerBase { + @CField("version") + int getVersion(); + + @CField("version") + void setVersion(int version); + + @CField("name") + CCharPointer getName(); + + @CField("name") + void setName(CCharPointer name); + + @CField("group") + JObject getGroup(); + + @CField("group") + void setGroup(JObject group); + } + + @CContext(JNIHeaderDirectives.class) + @CStruct(value = "JNIInvokeInterface_", addStructKeyword = true) + public interface JNIInvokeInterface extends PointerBase { + @CField("AttachCurrentThread") + AttachCurrentThread getAttachCurrentThread(); + + @CField("AttachCurrentThreadAsDaemon") + AttachCurrentThreadAsDaemon getAttachCurrentThreadAsDaemon(); + + @CField("DetachCurrentThread") + DetachCurrentThread getDetachCurrentThread(); + + @CField("GetEnv") + GetEnv getGetEnv(); + } + + public interface CallStaticIntMethodA extends CFunctionPointer { + @InvokeCFunctionPointer + int call(JNIEnv env, JClass clazz, JMethodID methodID, JValue args); + } + + public interface CallStaticBooleanMethodA extends CFunctionPointer { + @InvokeCFunctionPointer + boolean call(JNIEnv env, JClass clazz, JMethodID methodID, JValue args); + } + + public interface CallStaticVoidMethodA extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JClass clazz, JMethodID methodID, JValue args); + } + + public interface CallStaticObjectMethodA extends CFunctionPointer { + @InvokeCFunctionPointer + JObject call(JNIEnv env, JClass clazz, JMethodID methodID, JValue args); + + @InvokeCFunctionPointer(transition = Transition.NO_TRANSITION) + JObject callNoTransition(JNIEnv env, JClass clazz, JMethodID methodID, JValue args); + } + + public interface CallStaticLongMethodA extends CFunctionPointer { + @InvokeCFunctionPointer + long call(JNIEnv env, JClass clazz, JMethodID methodID, JValue args); + } + + public interface CallObjectMethodA extends CFunctionPointer { + @InvokeCFunctionPointer + JObject call(JNIEnv env, JObject object, JMethodID methodID, JValue args); + } + + public interface CallVoidMethodA extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JObject o, JMethodID methodID, JValue args); + } + + public interface CallBooleanMethodA extends CFunctionPointer { + @InvokeCFunctionPointer + boolean call(JNIEnv env, JObject o, JMethodID methodID, JValue args); + } + + public interface CallShortMethodA extends CFunctionPointer { + @InvokeCFunctionPointer + short call(JNIEnv env, JObject o, JMethodID methodID, JValue args); + } + + public interface CallIntMethodA extends CFunctionPointer { + @InvokeCFunctionPointer + int call(JNIEnv env, JObject o, JMethodID methodID, JValue args); + } + + public interface CallLongMethodA extends CFunctionPointer { + @InvokeCFunctionPointer + long call(JNIEnv env, JObject o, JMethodID methodID, JValue args); + } + + public interface CallDoubleMethodA extends CFunctionPointer { + @InvokeCFunctionPointer + double call(JNIEnv env, JObject o, JMethodID methodID, JValue args); + } + + public interface CallFloatMethodA extends CFunctionPointer { + @InvokeCFunctionPointer + float call(JNIEnv env, JObject o, JMethodID methodID, JValue args); + } + + public interface CallByteMethodA extends CFunctionPointer { + @InvokeCFunctionPointer + byte call(JNIEnv env, JObject o, JMethodID methodID, JValue args); + } + + public interface CallCharMethodA extends CFunctionPointer { + @InvokeCFunctionPointer + char call(JNIEnv env, JObject o, JMethodID methodID, JValue args); + } + + public interface DeleteGlobalRef extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JObject gref); + } + + public interface DeleteWeakGlobalRef extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JWeak wref); + } + + public interface DeleteLocalRef extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JObject lref); + } + + public interface PushLocalFrame extends CFunctionPointer { + @InvokeCFunctionPointer + int call(JNIEnv env, int capacity); + } + + public interface PopLocalFrame extends CFunctionPointer { + @InvokeCFunctionPointer + JObject call(JNIEnv env, JObject result); + } + + public interface ExceptionCheck extends CFunctionPointer { + @InvokeCFunctionPointer + boolean call(JNIEnv env); + + @InvokeCFunctionPointer(transition = Transition.NO_TRANSITION) + boolean callNoTransition(JNIEnv env); + } + + public interface ExceptionClear extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env); + } + + public interface ExceptionDescribe extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env); + + @InvokeCFunctionPointer(transition = Transition.NO_TRANSITION) + void callNoTransition(JNIEnv env); + } + + public interface ExceptionOccurred extends CFunctionPointer { + @InvokeCFunctionPointer + JThrowable call(JNIEnv env); + } + + public interface FindClass extends CFunctionPointer { + @InvokeCFunctionPointer + JClass call(JNIEnv env, CCharPointer name); + + @InvokeCFunctionPointer(transition = Transition.NO_TRANSITION) + JClass callNoTransition(JNIEnv env, CCharPointer name); + } + + public interface DefineClass extends CFunctionPointer { + @InvokeCFunctionPointer + JClass call(JNIEnv env, CCharPointer name, JObject loader, CCharPointer buf, long bufLen); + } + + public interface GetArrayLength extends CFunctionPointer { + @InvokeCFunctionPointer + int call(JNIEnv env, JArray array); + } + + public interface GetBooleanArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + CCharPointer call(JNIEnv env, JBooleanArray array, JValue isCopy); + } + + public interface GetByteArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + CCharPointer call(JNIEnv env, JByteArray array, JValue isCopy); + } + + public interface GetCharArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + CShortPointer call(JNIEnv env, JCharArray array, JValue isCopy); + } + + public interface GetShortArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + CShortPointer call(JNIEnv env, JShortArray array, JValue isCopy); + } + + public interface GetIntArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + CIntPointer call(JNIEnv env, JIntArray array, JValue isCopy); + } + + public interface GetLongArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + CLongPointer call(JNIEnv env, JLongArray array, JValue isCopy); + } + + public interface GetFloatArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + CFloatPointer call(JNIEnv env, JFloatArray array, JValue isCopy); + } + + public interface GetDoubleArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + CDoublePointer call(JNIEnv env, JDoubleArray array, JValue isCopy); + } + + public interface GetMethodID extends CFunctionPointer { + @InvokeCFunctionPointer + JMethodID call(JNIEnv env, JClass clazz, CCharPointer name, CCharPointer sig); + + @InvokeCFunctionPointer(transition = Transition.NO_TRANSITION) + JMethodID callNoTransition(JNIEnv env, JClass clazz, CCharPointer name, CCharPointer sig); + } + + public interface GetObjectArrayElement extends CFunctionPointer { + @InvokeCFunctionPointer + JObject call(JNIEnv env, JObjectArray array, int index); + } + + public interface GetObjectClass extends CFunctionPointer { + @InvokeCFunctionPointer + JClass call(JNIEnv env, JObject object); + } + + public interface GetObjectRefType extends CFunctionPointer { + @InvokeCFunctionPointer + int call(JNIEnv env, JObject obj); + } + + public interface GetStaticMethodID extends CFunctionPointer { + @InvokeCFunctionPointer + JMethodID call(JNIEnv env, JClass clazz, CCharPointer name, CCharPointer sig); + + @InvokeCFunctionPointer(transition = Transition.NO_TRANSITION) + JMethodID callNoTransition(JNIEnv env, JClass clazz, CCharPointer name, CCharPointer sig); + } + + public interface GetStringChars extends CFunctionPointer { + @InvokeCFunctionPointer + CShortPointer call(JNIEnv env, JString string, JValue isCopy); + } + + public interface GetStringLength extends CFunctionPointer { + @InvokeCFunctionPointer + int call(JNIEnv env, JString string); + } + + public interface GetStringUTFChars extends CFunctionPointer { + @InvokeCFunctionPointer + CCharPointer call(JNIEnv env, JString string, JValue isCopy); + } + + public interface GetStringUTFLength extends CFunctionPointer { + @InvokeCFunctionPointer + int call(JNIEnv env, JString str); + } + + public interface IsSameObject extends CFunctionPointer { + @InvokeCFunctionPointer + boolean call(JNIEnv env, JObject ref1, JObject ref2); + } + + public interface NewBooleanArray extends CFunctionPointer { + @InvokeCFunctionPointer + JBooleanArray call(JNIEnv env, int len); + } + + public interface NewByteArray extends CFunctionPointer { + @InvokeCFunctionPointer + JByteArray call(JNIEnv env, int len); + } + + public interface NewCharArray extends CFunctionPointer { + @InvokeCFunctionPointer + JCharArray call(JNIEnv env, int len); + } + + public interface NewShortArray extends CFunctionPointer { + @InvokeCFunctionPointer + JShortArray call(JNIEnv env, int len); + } + + public interface NewIntArray extends CFunctionPointer { + @InvokeCFunctionPointer + JIntArray call(JNIEnv env, int len); + } + + public interface NewLongArray extends CFunctionPointer { + @InvokeCFunctionPointer + JLongArray call(JNIEnv env, int len); + } + + public interface NewFloatArray extends CFunctionPointer { + @InvokeCFunctionPointer + JFloatArray call(JNIEnv env, int len); + } + + public interface NewDoubleArray extends CFunctionPointer { + @InvokeCFunctionPointer + JDoubleArray call(JNIEnv env, int len); + } + + public interface NewGlobalRef extends CFunctionPointer { + @InvokeCFunctionPointer + JObject call(JNIEnv env, JObject lobj); + } + + public interface NewWeakGlobalRef extends CFunctionPointer { + @InvokeCFunctionPointer + JWeak call(JNIEnv env, JObject lobj); + } + + public interface NewObjectA extends CFunctionPointer { + @InvokeCFunctionPointer + JObject call(JNIEnv env, JClass clazz, JMethodID methodID, JValue args); + + @InvokeCFunctionPointer(transition = Transition.NO_TRANSITION) + JObject callNoTransition(JNIEnv env, JClass clazz, JMethodID methodID, JValue args); + } + + public interface NewLocalRef extends CFunctionPointer { + @InvokeCFunctionPointer + JObject call(JNIEnv env, JObject obj); + } + + public interface NewObjectArray extends CFunctionPointer { + @InvokeCFunctionPointer + JObjectArray call(JNIEnv env, int len, JClass clazz, JObject init); + } + + public interface NewString extends CFunctionPointer { + @InvokeCFunctionPointer + JString call(JNIEnv env, CShortPointer unicode, int len); + } + + public interface NewStringUTF8 extends CFunctionPointer { + @InvokeCFunctionPointer + JString call(JNIEnv env, CCharPointer bytes); + + @InvokeCFunctionPointer(transition = Transition.NO_TRANSITION) + JString callNoTransition(JNIEnv env, CCharPointer bytes); + } + + public interface ReleaseBooleanArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JBooleanArray array, CCharPointer elems, int mode); + } + + public interface ReleaseByteArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JByteArray array, CCharPointer elems, int mode); + } + + public interface ReleaseCharArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JCharArray array, CShortPointer elems, int mode); + } + + public interface ReleaseShortArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JShortArray array, CShortPointer elems, int mode); + } + + public interface ReleaseIntArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JIntArray array, CIntPointer elems, int mode); + } + + public interface ReleaseLongArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JLongArray array, CLongPointer elems, int mode); + } + + public interface ReleaseFloatArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JFloatArray array, CFloatPointer elems, int mode); + } + + public interface ReleaseDoubleArrayElements extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JDoubleArray array, CDoublePointer elems, int mode); + } + + public interface GetBooleanArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JBooleanArray array, int start, int len, CCharPointer buf); + } + + public interface GetByteArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JByteArray array, int start, int len, CCharPointer buf); + } + + public interface GetCharArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JCharArray array, int start, int len, CShortPointer buf); + } + + public interface GetShortArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JShortArray array, int start, int len, CShortPointer buf); + } + + public interface GetIntArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JIntArray array, int start, int len, CIntPointer buf); + } + + public interface GetLongArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JLongArray array, int start, int len, CLongPointer buf); + } + + public interface GetFloatArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JFloatArray array, int start, int len, CFloatPointer buf); + } + + public interface GetDoubleArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JDoubleArray array, int start, int len, CDoublePointer buf); + } + + public interface SetBooleanArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JBooleanArray array, int start, int len, CCharPointer buf); + } + + public interface SetByteArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JByteArray array, int start, int len, CCharPointer buf); + } + + public interface SetCharArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JCharArray array, int start, int len, CShortPointer buf); + } + + public interface SetShortArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JShortArray array, int start, int len, CShortPointer buf); + } + + public interface SetIntArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JIntArray array, int start, int len, CIntPointer buf); + } + + public interface SetLongArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JLongArray array, int start, int len, CLongPointer buf); + } + + public interface SetFloatArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JFloatArray array, int start, int len, CFloatPointer buf); + } + + public interface SetDoubleArrayRegion extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JDoubleArray array, int start, int len, CDoublePointer buf); + } + + public interface ReleaseStringChars extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JString string, CShortPointer chars); + } + + public interface ReleaseStringUTFChars extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JString string, CCharPointer chars); + } + + public interface SetObjectArrayElement extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JObjectArray array, int index, JObject val); + } + + public interface Throw extends CFunctionPointer { + @InvokeCFunctionPointer + int call(JNIEnv env, JThrowable throwable); + + @InvokeCFunctionPointer(transition = Transition.NO_TRANSITION) + int callNoTransition(JNIEnv env, JThrowable throwable); + } + + public interface GetDirectBufferAddress extends CFunctionPointer { + @InvokeCFunctionPointer + VoidPointer call(JNIEnv env, JObject buf); + } + + public interface IsInstanceOf extends CFunctionPointer { + @InvokeCFunctionPointer + boolean call(JNIEnv env, JObject o, JClass c); + } + + public interface GetStaticFieldID extends CFunctionPointer { + @InvokeCFunctionPointer + JFieldID call(JNIEnv env, JClass clazz, CCharPointer name, CCharPointer sig); + } + + public interface GetFieldID extends CFunctionPointer { + @InvokeCFunctionPointer + JFieldID call(JNIEnv env, JClass c, CCharPointer name, CCharPointer sig); + } + + public interface GetStaticObjectField extends CFunctionPointer { + @InvokeCFunctionPointer + JObject call(JNIEnv env, JClass clazz, JFieldID fieldID); + } + + public interface GetIntField extends CFunctionPointer { + @InvokeCFunctionPointer + int call(JNIEnv env, JObject o, JFieldID fieldId); + } + + public interface GetStaticBooleanField extends CFunctionPointer { + @InvokeCFunctionPointer + boolean call(JNIEnv env, JClass clazz, JFieldID fieldID); + } + + public interface SetStaticBooleanField extends CFunctionPointer { + @InvokeCFunctionPointer + void call(JNIEnv env, JClass clazz, JFieldID fieldID, boolean value); + } + + public interface GetJavaVM extends CFunctionPointer { + @InvokeCFunctionPointer + int call(JNIEnv env, JavaVMPointer javaVMOut); + } + + public interface AttachCurrentThread extends CFunctionPointer { + @InvokeCFunctionPointer + int call(JavaVM vm, JNIEnvPointer envOut, JavaVMAttachArgs args); + } + + public interface AttachCurrentThreadAsDaemon extends CFunctionPointer { + @InvokeCFunctionPointer + int call(JavaVM vm, JNIEnvPointer envOut, JavaVMAttachArgs args); + } + + public interface DetachCurrentThread extends CFunctionPointer { + @InvokeCFunctionPointer + int call(JavaVM vm); + } + + public interface GetEnv extends CFunctionPointer { + @InvokeCFunctionPointer + int call(JavaVM vm, JNIEnvPointer envOut, int version); + } + + static class JNIHeaderDirectives implements CContext.Directives { + private static final String[] INCLUDES = {"jni.h", "jni_md.h"}; + + @Override + public boolean isInConfiguration() { + return ImageSingletons.contains(NativeBridgeSupport.class); + } + + @Override + public List getOptions() { + return Arrays.stream(findJNIHeaders()).map((p) -> "-I" + p.getParent()).collect(Collectors.toList()); + } + + @Override + public List getHeaderFiles() { + return Arrays.stream(findJNIHeaders()).map((p) -> '<' + p.toString() + '>').collect(Collectors.toList()); + } + + private static Path[] findJNIHeaders() { + Path javaHome = Paths.get(System.getProperty("java.home")); + Path includeFolder = javaHome.resolve("include"); + if (!Files.exists(includeFolder)) { + Path parent = javaHome.getParent(); + if (parent != null) { + javaHome = parent; + } + } + includeFolder = javaHome.resolve("include"); + if (!Files.exists(includeFolder)) { + throw new IllegalStateException("Cannot find 'include' folder in JDK."); + } + Path[] res = new Path[INCLUDES.length]; + try { + for (int i = 0; i < INCLUDES.length; i++) { + String include = INCLUDES[i]; + Optional includeFile = Files.find(includeFolder, 2, (p, attrs) -> include.equals(p.getFileName().toString())).findFirst(); + if (!includeFile.isPresent()) { + throw new IllegalStateException("Include: " + res[i] + " does not exist."); + } + res[i] = includeFile.get(); + } + return res; + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNICalls.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNICalls.java new file mode 100644 index 000000000000..b3c0c55c3c26 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNICalls.java @@ -0,0 +1,351 @@ +/* + * 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.jdwp.bridge.jniutils; + +import static com.oracle.svm.jdwp.bridge.jniutils.JNIExceptionWrapper.callGetClassName; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIExceptionWrapper.wrapAndThrowPendingJNIException; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.createString; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.trace; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.tracingAt; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Objects; + +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JClass; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JMethodID; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JNIEnv; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JObject; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JValue; +import com.oracle.svm.jdwp.bridge.jniutils.JNIExceptionWrapper.ExceptionHandler; +import org.graalvm.word.WordFactory; + +/** + * Support for calling into HotSpot using JNI. In addition to calling a method using JNI, the + * {@code JNICalls} also perform JNI call tracing and exception handling. All JNI calls into HotSpot + * must use this support to correctly merge HotSpot and native image stack traces. + */ +public final class JNICalls { + + private static final JNICalls INSTANCE = new JNICalls(ExceptionHandler.DEFAULT); + + private static final ThreadLocal inTrace = ThreadLocal.withInitial(() -> false); + + private final ExceptionHandler exceptionHandler; + + private JNICalls(ExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } + + /** + * Returns a {@link JNICalls} instance with a default exception handler. The default exception + * handler rethrows any pending JNI exception as a {@link JNIExceptionWrapper}. + */ + public static JNICalls getDefault() { + return INSTANCE; + } + + /** + * Creates a new {@link JNICalls} instance with a custom exception handler. The given exception + * handler is used to handle pending JNI exceptions. + */ + public static JNICalls createWithExceptionHandler(ExceptionHandler handler) { + Objects.requireNonNull(handler, "Handler must be non null."); + return new JNICalls(handler); + } + + /** + * Performs a JNI call of a static void method. + */ + @JNICall + public void callStaticVoid(JNIEnv env, JClass clazz, JNIMethod method, JValue args) { + traceCall(env, clazz, method); + env.getFunctions().getCallStaticVoidMethodA().call(env, clazz, method.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + } + + /** + * Performs a JNI call of a static method returning {@code boolean}. + */ + @JNICall + public boolean callStaticBoolean(JNIEnv env, JClass clazz, JNIMethod method, JValue args) { + traceCall(env, clazz, method); + boolean res = env.getFunctions().getCallStaticBooleanMethodA().call(env, clazz, method.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + return res; + } + + /** + * Performs a JNI call of a static method returning {@code long}. + */ + @JNICall + public long callStaticLong(JNIEnv env, JClass clazz, JNIMethod method, JValue args) { + traceCall(env, clazz, method); + long res = env.getFunctions().getCallStaticLongMethodA().call(env, clazz, method.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + return res; + } + + /** + * Performs a JNI call of a static method returning {@code int}. + */ + @JNICall + public int callStaticInt(JNIEnv env, JClass clazz, JNIMethod method, JValue args) { + traceCall(env, clazz, method); + int res = env.getFunctions().getCallStaticIntMethodA().call(env, clazz, method.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + return res; + } + + /** + * Performs a JNI call of a static method returning {@link Object}. + */ + @SuppressWarnings("unchecked") + @JNICall + public R callStaticJObject(JNIEnv env, JClass clazz, JNIMethod method, JValue args) { + traceCall(env, clazz, method); + JObject res = env.getFunctions().getCallStaticObjectMethodA().call(env, clazz, method.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + return (R) res; + } + + /** + * Creates a new object instance using a given constructor. + */ + @JNICall + @SuppressWarnings("unchecked") + public R callNewObject(JNIEnv env, JClass clazz, JNIMethod constructor, JValue args) { + traceCall(env, clazz, constructor); + JObject res = env.getFunctions().getNewObjectA().call(env, clazz, constructor.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + return (R) res; + } + + /** + * Performs a JNI call of a void method. + */ + @JNICall + public void callVoid(JNIEnv env, JObject object, JNIMethod method, JValue args) { + traceCall(env, object, method); + env.getFunctions().getCallVoidMethodA().call(env, object, method.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + } + + /** + * Performs a JNI call of a method returning {@link Object}. + */ + @JNICall + @SuppressWarnings("unchecked") + public R callJObject(JNIEnv env, JObject object, JNIMethod method, JValue args) { + traceCall(env, object, method); + JObject res = env.getFunctions().getCallObjectMethodA().call(env, object, method.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + return (R) res; + } + + /** + * Performs a JNI call of a method returning {@code boolean}. + */ + @JNICall + public boolean callBoolean(JNIEnv env, JObject object, JNIMethod method, JValue args) { + traceCall(env, object, method); + boolean res = env.getFunctions().getCallBooleanMethodA().call(env, object, method.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + return res; + } + + /** + * Performs a JNI call of a method returning {@code short}. + */ + @JNICall + public short callShort(JNIEnv env, JObject object, JNIMethod method, JValue args) { + traceCall(env, object, method); + short res = env.getFunctions().getCallShortMethodA().call(env, object, method.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + return res; + } + + /** + * Performs a JNI call of a method returning {@code int}. + */ + @JNICall + public int callInt(JNIEnv env, JObject object, JNIMethod method, JValue args) { + traceCall(env, object, method); + int res = env.getFunctions().getCallIntMethodA().call(env, object, method.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + return res; + } + + /** + * Performs a JNI call of a method returning {@code long}. + */ + @JNICall + public long callLong(JNIEnv env, JObject object, JNIMethod method, JValue args) { + traceCall(env, object, method); + long res = env.getFunctions().getCallLongMethodA().call(env, object, method.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + return res; + } + + /** + * Performs a JNI call of a method returning {@code double}. + */ + @JNICall + public double callDouble(JNIEnv env, JObject object, JNIMethod method, JValue args) { + traceCall(env, object, method); + double res = env.getFunctions().getCallDoubleMethodA().call(env, object, method.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + return res; + } + + /** + * Performs a JNI call of a method returning {@code float}. + */ + @JNICall + public float callFloat(JNIEnv env, JObject object, JNIMethod method, JValue args) { + traceCall(env, object, method); + float res = env.getFunctions().getCallFloatMethodA().call(env, object, method.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + return res; + } + + /** + * Performs a JNI call of a method returning {@code byte}. + */ + @JNICall + public byte callByte(JNIEnv env, JObject object, JNIMethod method, JValue args) { + traceCall(env, object, method); + byte res = env.getFunctions().getCallByteMethodA().call(env, object, method.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + return res; + } + + /** + * Performs a JNI call of a method returning {@code char}. + */ + @JNICall + public char callChar(JNIEnv env, JObject object, JNIMethod method, JValue args) { + traceCall(env, object, method); + char res = env.getFunctions().getCallCharMethodA().call(env, object, method.getJMethodID(), args); + wrapAndThrowPendingJNIException(env, exceptionHandler); + return res; + } + + private static void traceCall(JNIEnv env, JClass clazz, JNIMethod method) { + if (tracingAt(1)) { + traceCallImpl(env, clazz, method); + } + } + + private static void traceCall(JNIEnv env, JObject receiver, JNIMethod method) { + if (tracingAt(1)) { + // Intentionally does not use JNIUtil. The tracing JNI usage should not be traced. + traceCallImpl(env, env.getFunctions().getGetObjectClass().call(env, receiver), method); + } + } + + private static void traceCallImpl(JNIEnv env, JClass clazz, JNIMethod method) { + // The tracing performs JNI calls to obtain name of the HotSpot entry point class. + // This call must not be traced to prevent endless recursion. + if (!inTrace.get()) { + inTrace.set(true); + try { + trace(1, "%s->HS: %s::%s", + JNIUtil.getFeatureName(), + toSimpleName(createString(env, callGetClassName(env, clazz))), + method.getDisplayName()); + } finally { + inTrace.remove(); + } + } + } + + private static String toSimpleName(String fqn) { + int separatorIndex = fqn.lastIndexOf('.'); + return separatorIndex < 0 || separatorIndex + 1 == fqn.length() ? fqn : fqn.substring(separatorIndex + 1); + } + + /** + * Represents a JNI method. + */ + public interface JNIMethod { + + /** + * Returns a method JNI {@link JMethodID}. + */ + JMethodID getJMethodID(); + + /** + * Returns a method display name used for logging. + */ + String getDisplayName(); + + /** + * Finds a {@link JNIMethod} in the given {@link JClass clazz} with the given name and + * signature. If such a method does not exist throws {@link JNIExceptionWrapper} wrapping a + * {@link NoSuchMethodError}. + */ + static JNIMethod findMethod(JNIEnv env, JClass clazz, boolean staticMethod, String methodName, String methodSignature) { + return findMethod(env, clazz, staticMethod, true, methodName, methodSignature); + } + + /** + * Finds a {@link JNIMethod} in given {@link JClass clazz} with given name and signature. If + * such a method does not exist and {@code required} is {@code true}, it throws + * {@link JNIExceptionWrapper} wrapping a {@link NoSuchMethodError}. If {@code required} is + * {@code false} it clears the pending JNI exception and returns a + * {@link WordFactory#nullPointer() C NULL pointer}. + */ + static JNIMethod findMethod(JNIEnv env, JClass clazz, boolean staticMethod, boolean required, String methodName, String methodSignature) { + JMethodID methodID = JNIUtil.findMethod(env, clazz, staticMethod, required, methodName, methodSignature); + return methodID.isNull() ? null : new JNIMethod() { + @Override + public JMethodID getJMethodID() { + return methodID; + } + + @Override + public String getDisplayName() { + return methodName; + } + + @Override + public String toString() { + return methodName + methodSignature + "[0x" + Long.toHexString(methodID.rawValue()) + ']'; + } + }; + } + } + + /** + * Marker annotation for the helper methods for calling a method in HotSpot. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + @interface JNICall { + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNIEntryPoint.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNIEntryPoint.java new file mode 100644 index 000000000000..f16807cd92bd --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNIEntryPoint.java @@ -0,0 +1,41 @@ +/* + * 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.jdwp.bridge.jniutils; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.graalvm.nativeimage.hosted.Feature; + +/** + * An annotation used to mark methods called by the JNI native interface. The annotation can be used + * by {@link Feature}s to register the annotated methods as JNI accessed. + */ +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +@Retention(RetentionPolicy.RUNTIME) +public @interface JNIEntryPoint { +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNIExceptionWrapper.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNIExceptionWrapper.java new file mode 100644 index 000000000000..8fbddb40d6c6 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNIExceptionWrapper.java @@ -0,0 +1,635 @@ +/* + * 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. + */ +// Checkstyle: allow direct annotation access +package com.oracle.svm.jdwp.bridge.jniutils; + +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.ExceptionCheck; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.ExceptionClear; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.ExceptionDescribe; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.ExceptionOccurred; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.GetObjectClass; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.GetStaticMethodID; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.IsSameObject; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.NewGlobalRef; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.Throw; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.createArray; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.createHSArray; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.createHSString; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.createString; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.encodeMethodSignature; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.findClass; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.getBinaryName; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.getJVMCIClassLoader; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.trace; +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.tracingAt; +import static org.graalvm.nativeimage.c.type.CTypeConversion.toCString; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JByteArray; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JClass; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JNIEnv; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JObject; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JString; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JThrowable; +import com.oracle.svm.jdwp.bridge.jniutils.JNICalls.JNICall; +import com.oracle.svm.jdwp.bridge.jniutils.JNICalls.JNIMethod; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.type.CTypeConversion; + +/** + * Wraps an exception thrown by a JNI call into HotSpot. If the exception propagates up to an native + * image entry point, the exception is re-thrown in HotSpot. + */ +@SuppressWarnings("serial") +public final class JNIExceptionWrapper extends RuntimeException { + + private static final String HS_ENTRYPOINTS_CLASS = "com.oracle.svm.jdwp.bridge.jniutils.JNIExceptionWrapperEntryPoints"; + private static final long serialVersionUID = 1L; + + private static final JNIMethodResolver CreateException = JNIMethodResolver.create("createException", Throwable.class, String.class); + private static final JNIMethodResolver GetClassName = JNIMethodResolver.create("getClassName", String.class, Class.class); + private static final JNIMethodResolver GetStackTrace = JNIMethodResolver.create("getStackTrace", byte[].class, Throwable.class); + private static final JNIMethodResolver GetThrowableMessage = JNIMethodResolver.create("getThrowableMessage", String.class, Throwable.class); + private static final JNIMethodResolver UpdateStackTrace = JNIMethodResolver.create("updateStackTrace", Throwable.class, Throwable.class, byte[].class); + + private static volatile JClass entryPointsClass; + + private final JThrowable throwableHandle; + private final boolean throwableRequiresStackTraceUpdate; + + private JNIExceptionWrapper(JNIEnv env, JThrowable throwableHandle) { + super(formatExceptionMessage(getClassName(env, throwableHandle), getMessage(env, throwableHandle))); + this.throwableHandle = throwableHandle; + this.throwableRequiresStackTraceUpdate = createMergedStackTrace(env); + } + + /** + * Re-throws this JNI exception in HotSpot after updating the stack trace to include the native + * image frames between the native image entry point and the call back to HotSpot that threw the + * original JNI exception. + */ + private void throwInHotSpot(JNIEnv env) { + JThrowable toThrow; + if (throwableRequiresStackTraceUpdate) { + toThrow = updateStackTrace(env, throwableHandle, getStackTrace()); + } else { + toThrow = throwableHandle; + } + Throw(env, toThrow); + } + + /** + * Creates a merged native image and HotSpot stack trace and updates this + * {@link JNIExceptionWrapper} stack trace to it. + * + * @return true if the stack trace needed a merge + */ + private boolean createMergedStackTrace(JNIEnv env) { + StackTraceElement[] hsStack = getJNIExceptionStackTrace(env, throwableHandle); + StackTraceElement[] mergedStack; + boolean res; + if (hsStack.length == 0 || containsJNIHostCall(hsStack)) { + mergedStack = hsStack; + res = false; + } else { + StackTraceElement[] nativeStack = getStackTrace(); + boolean originatedInHotSpot = !hsStack[0].isNativeMethod(); + mergedStack = mergeStackTraces(hsStack, nativeStack, 0, getIndexOfPropagateJNIExceptionFrame(nativeStack), originatedInHotSpot); + res = true; + } + setStackTrace(mergedStack); + return res; + } + + /** + * If there is a pending JNI exception, this method wraps it in a {@link JNIExceptionWrapper}, + * clears the pending exception and throws the {@link JNIExceptionWrapper} wrapper. The + * {@link JNIExceptionWrapper} message is composed of the JNI exception class name and the JNI + * exception message. For exception filtering or custom handling of JNI exceptions see + * {@link #wrapAndThrowPendingJNIException(JNIEnv, ExceptionHandler)}. + */ + public static void wrapAndThrowPendingJNIException(JNIEnv env) { + wrapAndThrowPendingJNIException(env, ExceptionHandler.DEFAULT); + } + + /** + * If there is a pending JNI exception, this method wraps it in a {@link JNIExceptionWrapper}, + * clears the pending exception and throws the {@link JNIExceptionWrapper} wrapper. The + * {@link JNIExceptionWrapper} message is composed of the JNI exception class name and the JNI + * exception message. + * + * @see ExceptionHandler + * + */ + public static void wrapAndThrowPendingJNIException(JNIEnv env, ExceptionHandler exceptionHandler) { + Objects.requireNonNull(exceptionHandler, "ExceptionHandler must be non null."); + if (ExceptionCheck(env)) { + JThrowable exception = ExceptionOccurred(env); + if (tracingAt(2) && exception.isNonNull()) { + ExceptionDescribe(env); + } + ExceptionClear(env); + exceptionHandler.handleException(new ExceptionHandlerContext(env, exception)); + } + } + + /** + * Throws an exception into HotSpot. + * + * If {@code original} is a {@link JNIExceptionWrapper} the wrapped JNI exception is thrown. + * + * Otherwise a new {@link RuntimeException} is thrown. The {@link RuntimeException} message is + * composed of {@code original.getClass().getName()} and {@code original.getMessage()}. The + * stack trace is result of merging the {@code original.getStackTrace()} with the current + * execution stack in HotSpot. + * + * @param env the {@link JNIEnv} + * @param original an exception to be thrown in HotSpot + */ + public static void throwInHotSpot(JNIEnv env, Throwable original) { + try { + trace(2, original); + if (original.getClass() == JNIExceptionWrapper.class) { + ((JNIExceptionWrapper) original).throwInHotSpot(env); + } else { + Throw(env, createHSException(env, original)); + } + } catch (Throwable t) { + // If something goes wrong when re-throwing the exception into HotSpot + // print the exception stack trace. + if (t instanceof ThreadDeath) { + throw t; + } else { + original.addSuppressed(t); + original.printStackTrace(); + } + } + } + + /** + * Crates an exception in HotSpot representing the given {@code original} exception. + * + * @param env the {@link JNIEnv} + * @param original an exception to be created in HotSpot + */ + public static JThrowable createHSException(JNIEnv env, Throwable original) { + JThrowable hsThrowable; + if (original instanceof JNIExceptionWrapper) { + JNIExceptionWrapper jniExceptionWrapper = (JNIExceptionWrapper) original; + hsThrowable = jniExceptionWrapper.throwableHandle; + if (jniExceptionWrapper.throwableRequiresStackTraceUpdate) { + hsThrowable = updateStackTrace(env, hsThrowable, jniExceptionWrapper.getStackTrace()); + } + } else { + String message = formatExceptionMessage(original.getClass().getName(), original.getMessage()); + JString hsMessage = createHSString(env, message); + hsThrowable = callCreateException(env, hsMessage); + StackTraceElement[] nativeStack = original.getStackTrace(); + if (nativeStack.length != 0) { + // Update stack trace only for exceptions which have stack trace. + // For exceptions which override fillInStackTrace merging stack traces only adds + // useless JNI calls. + StackTraceElement[] hsStack = getJNIExceptionStackTrace(env, hsThrowable); + StackTraceElement[] mergedStack = mergeStackTraces(hsStack, nativeStack, 1, + getIndexOfPropagateJNIExceptionFrame(nativeStack), false); + hsThrowable = updateStackTrace(env, hsThrowable, mergedStack); + } + } + return hsThrowable; + } + + /** + * Context for {@link ExceptionHandler}. + */ + public static final class ExceptionHandlerContext { + + private final JNIEnv env; + private final JThrowable throwable; + + ExceptionHandlerContext(JNIEnv env, JThrowable throwable) { + this.env = env; + this.throwable = throwable; + } + + /** + * Returns current thread JNIEnv. + */ + public JNIEnv getEnv() { + return env; + } + + /** + * Returns pending JNI exception. + */ + public JThrowable getThrowable() { + return throwable; + } + + /** + * Returns pending JNI exception class name. + */ + public String getThrowableClassName() { + return getClassName(env, throwable); + } + + /** + * Throws {@link JNIExceptionWrapper} for the pending JNI exception. + */ + public void throwJNIExceptionWrapper() { + throw new JNIExceptionWrapper(env, throwable); + } + } + + public interface ExceptionHandler { + + /** + * Default handler throwing {@link JNIExceptionWrapper} for the pending JNI exception. + */ + ExceptionHandler DEFAULT = new ExceptionHandler() { + @Override + public void handleException(ExceptionHandlerContext context) { + context.throwJNIExceptionWrapper(); + } + }; + + /** + * Creates an exception handler suppressing {@code allowedExceptions}. Other JNI exceptions + * are rethrown as {@link JNIExceptionWrapper}. + */ + @SafeVarargs + static ExceptionHandler allowExceptions(Class... allowedExceptions) { + return new ExceptionHandler() { + @Override + public void handleException(ExceptionHandlerContext context) { + JThrowable throwable = context.getThrowable(); + JNIEnv env = context.getEnv(); + JClass throwableClass = GetObjectClass(env, throwable); + boolean allowed = false; + for (Class allowedException : allowedExceptions) { + JClass allowedExceptionClass = findClass(env, getBinaryName(allowedException.getName())); + if (allowedExceptionClass.isNonNull() && IsSameObject(env, throwableClass, allowedExceptionClass)) { + allowed = true; + break; + } + } + if (!allowed) { + context.throwJNIExceptionWrapper(); + } + } + }; + } + + /** + * Handles the JNI pending exception. + */ + void handleException(ExceptionHandlerContext context); + } + + public static StackTraceElement[] mergeStackTraces( + StackTraceElement[] hotSpotStackTrace, + StackTraceElement[] nativeStackTrace, + boolean originatedInHotSpot) { + if (originatedInHotSpot) { + if (containsJNIHostCall(hotSpotStackTrace)) { + // Already merged + return hotSpotStackTrace; + } + } else { + if (containsJNIHostCall(nativeStackTrace)) { + // Already merged + return nativeStackTrace; + } + } + return mergeStackTraces(hotSpotStackTrace, nativeStackTrace, originatedInHotSpot ? 0 : getIndexOfTransitionToNativeFrame(hotSpotStackTrace), + getIndexOfPropagateJNIExceptionFrame(nativeStackTrace), originatedInHotSpot); + } + + /** + * Merges {@code hotSpotStackTrace} with {@code nativeStackTrace}. + * + * @param hotSpotStackTrace + * @param nativeStackTrace + * @param hotSpotStackStartIndex + * @param nativeStackStartIndex + * @param originatedInHotSpot + */ + private static StackTraceElement[] mergeStackTraces( + StackTraceElement[] hotSpotStackTrace, + StackTraceElement[] nativeStackTrace, + int hotSpotStackStartIndex, + int nativeStackStartIndex, + boolean originatedInHotSpot) { + int targetIndex = 0; + StackTraceElement[] merged = new StackTraceElement[hotSpotStackTrace.length - hotSpotStackStartIndex + nativeStackTrace.length - nativeStackStartIndex]; + boolean startingHotSpotFrame = true; + boolean startingnativeFrame = true; + boolean useHotSpotStack = originatedInHotSpot; + int hotSpotStackIndex = hotSpotStackStartIndex; + int nativeStackIndex = nativeStackStartIndex; + while (hotSpotStackIndex < hotSpotStackTrace.length || nativeStackIndex < nativeStackTrace.length) { + if (useHotSpotStack) { + while (hotSpotStackIndex < hotSpotStackTrace.length && (startingHotSpotFrame || !hotSpotStackTrace[hotSpotStackIndex].isNativeMethod())) { + startingHotSpotFrame = false; + merged[targetIndex++] = hotSpotStackTrace[hotSpotStackIndex++]; + } + startingHotSpotFrame = true; + } else { + useHotSpotStack = true; + } + while (nativeStackIndex < nativeStackTrace.length && (startingnativeFrame || !isJNIHostCall(nativeStackTrace[nativeStackIndex]))) { + startingnativeFrame = false; + merged[targetIndex++] = nativeStackTrace[nativeStackIndex++]; + } + startingnativeFrame = true; + } + return merged; + } + + /** + * Gets the stack trace from a JNI exception. + * + * @param env the {@link JNIEnv} + * @param throwableHandle the JNI exception to get the stack trace from. + * @return the stack trace + */ + private static StackTraceElement[] getJNIExceptionStackTrace(JNIEnv env, JObject throwableHandle) { + byte[] serializedStackTrace = createArray(env, (JByteArray) callGetStackTrace(env, throwableHandle)); + try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(serializedStackTrace))) { + int len = in.readInt(); + StackTraceElement[] res = new StackTraceElement[len]; + for (int i = 0; i < len; i++) { + String className = in.readUTF(); + String methodName = in.readUTF(); + String fileName = in.readUTF(); + fileName = fileName.isEmpty() ? null : fileName; + int lineNumber = in.readInt(); + res[i] = new StackTraceElement(className, methodName, fileName, lineNumber); + } + return res; + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + /** + * Determines if {@code stackTrace} contains a frame denoting a call into HotSpot. + */ + private static boolean containsJNIHostCall(StackTraceElement[] stackTrace) { + for (StackTraceElement e : stackTrace) { + if (isJNIHostCall(e)) { + return true; + } + } + return false; + } + + private static JThrowable updateStackTrace(JNIEnv env, JThrowable throwableHandle, StackTraceElement[] mergedStackTrace) { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + try (DataOutputStream out = new DataOutputStream(bout)) { + out.writeInt(mergedStackTrace.length); + for (int i = 0; i < mergedStackTrace.length; i++) { + StackTraceElement stackTraceElement = mergedStackTrace[i]; + out.writeUTF(stackTraceElement.getClassName()); + out.writeUTF(stackTraceElement.getMethodName()); + String fileName = stackTraceElement.getFileName(); + out.writeUTF(fileName == null ? "" : fileName); + out.writeInt(stackTraceElement.getLineNumber()); + } + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + return callUpdateStackTrace(env, throwableHandle, createHSArray(env, bout.toByteArray())); + } + + private static String getMessage(JNIEnv env, JThrowable throwableHandle) { + JString message = callGetThrowableMessage(env, throwableHandle); + return createString(env, message); + } + + private static String getClassName(JNIEnv env, JThrowable throwableHandle) { + JClass classHandle = GetObjectClass(env, throwableHandle); + JString className = callGetClassName(env, classHandle); + return createString(env, className); + } + + private static String formatExceptionMessage(String className, String message) { + StringBuilder builder = new StringBuilder(className); + if (message != null) { + builder.append(": ").append(message); + } + return builder.toString(); + } + + /** + * Gets the index of the first frame denoting the caller of + * {@link #wrapAndThrowPendingJNIException(JNIEnv)} or + * {@link #wrapAndThrowPendingJNIException(JNIEnv, ExceptionHandler)} in {@code stackTrace}. + * + * @return {@code 0} if no caller found + */ + private static int getIndexOfPropagateJNIExceptionFrame(StackTraceElement[] stackTrace) { + int state = 0; + for (int i = 0; i < stackTrace.length; i++) { + if (isStackFrame(stackTrace[i], JNIExceptionWrapper.class, "wrapAndThrowPendingJNIException")) { + state = 1; + } else if (state == 1) { + return i; + } + } + return 0; + } + + /** + * Gets the index of the first frame denoting the native method call. + * + * @return {@code 0} if no caller found + */ + private static int getIndexOfTransitionToNativeFrame(StackTraceElement[] stackTrace) { + for (int i = 0; i < stackTrace.length; i++) { + if (stackTrace[i].isNativeMethod()) { + return i; + } + } + return 0; + } + + private static boolean isStackFrame(StackTraceElement stackTraceElement, Class clazz, String methodName) { + return clazz.getName().equals(stackTraceElement.getClassName()) && methodName.equals(stackTraceElement.getMethodName()); + } + + // JNI calls + private static JThrowable callCreateException(JNIEnv env, JObject p0) { + JNI.JValue args = StackValue.get(1, JNI.JValue.class); + args.addressOf(0).setJObject(p0); + return JNICalls.getDefault().callStaticJObject(env, getEntryPoints(env), CreateException.resolve(env), args); + } + + private static T callUpdateStackTrace(JNIEnv env, JObject p0, JByteArray p1) { + JNI.JValue args = StackValue.get(2, JNI.JValue.class); + args.addressOf(0).setJObject(p0); + args.addressOf(1).setJObject(p1); + return JNICalls.getDefault().callStaticJObject(env, getEntryPoints(env), UpdateStackTrace.resolve(env), args); + } + + @SuppressWarnings("unchecked") + private static T callGetThrowableMessage(JNIEnv env, JObject p0) { + JNI.JValue args = StackValue.get(1, JNI.JValue.class); + args.addressOf(0).setJObject(p0); + return JNICalls.getDefault().callStaticJObject(env, getEntryPoints(env), GetThrowableMessage.resolve(env), args); + } + + @SuppressWarnings("unchecked") + static T callGetClassName(JNIEnv env, JObject p0) { + JNI.JValue args = StackValue.get(1, JNI.JValue.class); + args.addressOf(0).setJObject(p0); + return JNICalls.getDefault().callStaticJObject(env, getEntryPoints(env), GetClassName.resolve(env), args); + } + + @SuppressWarnings("unchecked") + private static T callGetStackTrace(JNIEnv env, JObject p0) { + JNI.JValue args = StackValue.get(1, JNI.JValue.class); + args.addressOf(0).setJObject(p0); + return JNICalls.getDefault().callStaticJObject(env, getEntryPoints(env), GetStackTrace.resolve(env), args); + } + + private static final class JNIMethodResolver implements JNIMethod { + + private final String methodName; + private final String methodSignature; + private volatile JNI.JMethodID methodId; + + private JNIMethodResolver(String methodName, String methodSignature) { + this.methodName = methodName; + this.methodSignature = methodSignature; + } + + JNIMethodResolver resolve(JNIEnv jniEnv) { + JNI.JMethodID res = methodId; + if (res.isNull()) { + JClass entryPointClass = getEntryPoints(jniEnv); + try (CTypeConversion.CCharPointerHolder name = toCString(methodName); CTypeConversion.CCharPointerHolder sig = toCString(methodSignature)) { + res = GetStaticMethodID(jniEnv, entryPointClass, name.get(), sig.get()); + if (res.isNull()) { + throw new InternalError("No such method: " + methodName); + } + methodId = res; + } + } + return this; + } + + @Override + public JNI.JMethodID getJMethodID() { + return methodId; + } + + @Override + public String getDisplayName() { + return methodName; + } + + static JNIMethodResolver create(String methodName, Class returnType, Class... parameterTypes) { + return new JNIMethodResolver(methodName, encodeMethodSignature(returnType, parameterTypes)); + } + } + + private static JClass getEntryPoints(JNIEnv env) { + JClass res = entryPointsClass; + if (res.isNull()) { + String binaryName = getBinaryName(HS_ENTRYPOINTS_CLASS); + JClass entryPoints = findClass(env, binaryName); + if (entryPoints.isNull()) { + // Clear the exception and try to load the entry points class using JVMCI + // classloader. + ExceptionClear(env); + JObject classLoader = getJVMCIClassLoader(env); + if (classLoader.isNonNull()) { + entryPoints = findClass(env, classLoader, binaryName); + } + } + if (entryPoints.isNull()) { + // Here we cannot use JNIExceptionWrapper. + // We failed to load HostSpot entry points for it. + ExceptionClear(env); + throw new InternalError("Failed to load " + HS_ENTRYPOINTS_CLASS); + } + synchronized (JNIExceptionWrapper.class) { + res = entryPointsClass; + if (res.isNull()) { + res = NewGlobalRef(env, entryPoints, "Class<" + HS_ENTRYPOINTS_CLASS + ">"); + entryPointsClass = res; + } + } + } + return res; + } + + /** + * Determines if {@code frame} is for a method denoting a call into HotSpot. + */ + private static boolean isJNIHostCall(StackTraceElement frame) { + return JNI_TRANSITION_CLASS.equals(frame.getClassName()) && JNI_TRANSITION_METHODS.contains(frame.getMethodName()); + } + + /** + * Names of the methods in the {@link JNICalls} class annotated by the {@link JNICall}. + */ + private static final Set JNI_TRANSITION_METHODS; + private static final String JNI_TRANSITION_CLASS; + static { + Map entryPoints = new HashMap<>(); + Map others = new HashMap<>(); + for (Method m : JNICalls.class.getDeclaredMethods()) { + if (m.getAnnotation(JNICall.class) != null) { + Method existing = entryPoints.put(m.getName(), m); + if (existing != null) { + throw new InternalError("Method annotated by " + JNICall.class.getSimpleName() + + " must have unique name: " + m + " and " + existing); + } + } else { + others.put(m.getName(), m); + } + } + for (Map.Entry e : entryPoints.entrySet()) { + Method existing = others.get(e.getKey()); + if (existing != null) { + throw new InternalError("Method annotated by " + JNICall.class.getSimpleName() + + " must have unique name: " + e.getValue() + " and " + existing); + } + } + JNI_TRANSITION_CLASS = JNICalls.class.getName(); + JNI_TRANSITION_METHODS = Set.copyOf(entryPoints.keySet()); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNIExceptionWrapperEntryPoints.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNIExceptionWrapperEntryPoints.java new file mode 100644 index 000000000000..2046eaf4f400 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNIExceptionWrapperEntryPoints.java @@ -0,0 +1,104 @@ +/* + * 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.jdwp.bridge.jniutils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Entry points in HotSpot for exception handling from a JNI native method. + */ +final class JNIExceptionWrapperEntryPoints { + + /** + * Updates an exception stack trace by decoding a stack trace from a JNI native method. + * + * @param target the {@link Throwable} to update + * @param serializedStackTrace byte serialized stack trace + * @return the updated {@link Throwable} + */ + @JNIEntryPoint + static Throwable updateStackTrace(Throwable target, byte[] serializedStackTrace) { + try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(serializedStackTrace))) { + int len = in.readInt(); + StackTraceElement[] elements = new StackTraceElement[len]; + for (int i = 0; i < len; i++) { + String className = in.readUTF(); + String methodName = in.readUTF(); + String fileName = in.readUTF(); + int lineNumber = in.readInt(); + elements[i] = new StackTraceElement(className, methodName, fileName.isEmpty() ? null : fileName, lineNumber); + } + target.setStackTrace(elements); + return target; + + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + /** + * Creates an exception used to throw native exception into Java code. + * + * @param message the exception message + * @return exception + */ + @JNIEntryPoint + static Throwable createException(String message) { + return new RuntimeException(message); + } + + @JNIEntryPoint + static byte[] getStackTrace(Throwable throwable) { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + try (DataOutputStream out = new DataOutputStream(bout)) { + StackTraceElement[] stackTraceElements = throwable.getStackTrace(); + out.writeInt(stackTraceElements.length); + for (StackTraceElement stackTraceElement : stackTraceElements) { + out.writeUTF(stackTraceElement.getClassName()); + out.writeUTF(stackTraceElement.getMethodName()); + String fileName = stackTraceElement.getFileName(); + out.writeUTF(fileName == null ? "" : fileName); + out.writeInt(stackTraceElement.getLineNumber()); + } + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + return bout.toByteArray(); + } + + @JNIEntryPoint + static String getThrowableMessage(Throwable t) { + return t.getMessage(); + } + + @JNIEntryPoint + static String getClassName(Class clz) { + return clz.getName(); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNIMethodScope.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNIMethodScope.java new file mode 100644 index 000000000000..f222386c4e75 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNIMethodScope.java @@ -0,0 +1,164 @@ +/* + * 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.jdwp.bridge.jniutils; + +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.getFeatureName; + +import java.util.Objects; + +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JNIEnv; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JObject; + +/** + * Scope of a call from HotSpot to native method. This also provides access to the {@link JNIEnv} + * value for the current thread within the native method call. + * + * If the native method call returns a non-primitive value, the return value must be + * {@linkplain #setObjectResult(JObject) set} within the try-with-resources statement and then + * {@linkplain #getObjectResult() retrieved} and returned outside the try-with-resources statement. + * This is necessary to support use of JNI local frames. + */ +public class JNIMethodScope implements AutoCloseable { + + private static final ThreadLocal topScope = new ThreadLocal<>(); + + private final JNIEnv env; + private final JNIMethodScope parent; + private JNIMethodScope leaf; + + /** + * List of scope local {@link HSObject}s that created within this scope. These are + * {@linkplain HSObject#invalidate(HSObject) invalidated} when the scope closes. + */ + HSObject locals; + + /** + * The name for this scope. + */ + private final String scopeName; + + /** + * Gets the {@link JNIEnv} value for the current thread. + */ + public static JNIEnv env() { + return scope().env; + } + + public JNIEnv getEnv() { + return env; + } + + /** + * Gets the inner most {@link JNIMethodScope} value for the current thread. + */ + public static JNIMethodScope scopeOrNull() { + JNIMethodScope scope = topScope.get(); + if (scope == null) { + return null; + } + return scope.leaf; + } + + /** + * Gets the inner most {@link JNIMethodScope} value for the current thread. + */ + public static JNIMethodScope scope() { + JNIMethodScope scope = topScope.get(); + if (scope == null) { + throw new IllegalStateException("Not in the scope of an JNI method call"); + } + return scope.leaf; + } + + /** + * Enters the scope of an native method call. + */ + @SuppressWarnings({"unchecked", "this-escape"}) + public JNIMethodScope(String scopeName, JNIEnv env) { + Objects.requireNonNull(scopeName, "ScopeName must be non null."); + this.scopeName = scopeName; + JNIMethodScope top = topScope.get(); + this.env = env; + if (top == null) { + top = this; + parent = null; + topScope.set(this); + } else { + if (top.env != this.env) { + throw new IllegalStateException("Cannot mix JNI scopes: " + this + " and " + top); + } + parent = top.leaf; + } + top.leaf = this; + JNIUtil.trace(1, "HS->%s[enter]: %s", JNIUtil.getFeatureName(), scopeName); + } + + /** + * Used to copy the handle to an object return value out of the JNI local frame. + */ + private JObject objResult; + + public void setObjectResult(JObject obj) { + objResult = obj; + } + + @SuppressWarnings("unchecked") + public R getObjectResult() { + return (R) objResult; + } + + @Override + public void close() { + JNIUtil.trace(1, "HS->%s[ exit]: %s", getFeatureName(), scopeName); + HSObject.invalidate(locals); + if (parent == null) { + if (topScope.get() != this) { + throw new IllegalStateException("Unexpected JNI scope: " + topScope.get()); + } + topScope.set(null); + } else { + JNIMethodScope top = parent; + while (top.parent != null) { + top = top.parent; + } + top.leaf = parent; + } + } + + public final int depth() { + int depth = 0; + JNIMethodScope ancestor = parent; + while (ancestor != null) { + depth++; + ancestor = ancestor.parent; + } + return depth; + } + + @Override + public String toString() { + return "JNIMethodScope[" + depth() + "]@" + Long.toHexString(env.rawValue()); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNIUtil.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNIUtil.java new file mode 100644 index 000000000000..88ac118371c1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/JNIUtil.java @@ -0,0 +1,1195 @@ +/* + * 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.jdwp.bridge.jniutils; + +import static com.oracle.svm.jdwp.bridge.jniutils.JNI.JNI_OK; +import static com.oracle.svm.jdwp.bridge.jniutils.JNI.JNI_VERSION_10; +import static org.graalvm.nativeimage.c.type.CTypeConversion.toCString; +import static org.graalvm.word.WordFactory.nullPointer; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JArray; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JBooleanArray; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JByteArray; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JCharArray; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JClass; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JDoubleArray; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JFieldID; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JFloatArray; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JIntArray; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JLongArray; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JMethodID; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JNIEnv; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JNIEnvPointer; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JObject; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JObjectArray; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JShortArray; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JString; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JThrowable; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JValue; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JWeak; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JavaVM; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JavaVMAttachArgs; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JavaVMPointer; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.UnmanagedMemory; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.c.type.CDoublePointer; +import org.graalvm.nativeimage.c.type.CFloatPointer; +import org.graalvm.nativeimage.c.type.CIntPointer; +import org.graalvm.nativeimage.c.type.CLongPointer; +import org.graalvm.nativeimage.c.type.CShortPointer; +import org.graalvm.nativeimage.c.type.CTypeConversion; +import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder; +import org.graalvm.nativeimage.c.type.VoidPointer; +import org.graalvm.word.WordFactory; + +/** + * Helpers for calling JNI functions. + */ + +public final class JNIUtil { + + private static final String[] METHOD_GET_PLATFORM_CLASS_LOADER = { + "getPlatformClassLoader", + "()Ljava/lang/ClassLoader;" + }; + private static final String[] METHOD_GET_SYSTEM_CLASS_LOADER = { + "getSystemClassLoader", + "()Ljava/lang/ClassLoader;" + }; + private static final String[] METHOD_LOAD_CLASS = { + "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;" + }; + + private static final int ARRAY_COPY_STATIC_BUFFER_SIZE = 8192; + + // Checkstyle: stop + public static boolean IsSameObject(JNIEnv env, JObject ref1, JObject ref2) { + traceJNI("IsSameObject"); + return env.getFunctions().getIsSameObject().call(env, ref1, ref2); + } + + public static void DeleteLocalRef(JNIEnv env, JObject ref) { + traceJNI("DeleteLocalRef"); + env.getFunctions().getDeleteLocalRef().call(env, ref); + } + + public static int PushLocalFrame(JNIEnv env, int capacity) { + traceJNI("PushLocalFrame"); + return env.getFunctions().getPushLocalFrame().call(env, capacity); + } + + public static JObject PopLocalFrame(JNIEnv env, JObject result) { + traceJNI("PopLocalFrame"); + return env.getFunctions().getPopLocalFrame().call(env, result); + } + + public static JClass DefineClass(JNIEnv env, CCharPointer name, JObject loader, CCharPointer buf, int bufLen) { + return env.getFunctions().getDefineClass().call(env, name, loader, buf, bufLen); + } + + public static JClass FindClass(JNIEnv env, CCharPointer name) { + traceJNI("FindClass"); + return env.getFunctions().getFindClass().call(env, name); + } + + public static JClass GetObjectClass(JNIEnv env, JObject object) { + traceJNI("GetObjectClass"); + return env.getFunctions().getGetObjectClass().call(env, object); + } + + public static JMethodID GetStaticMethodID(JNIEnv env, JClass clazz, CCharPointer name, CCharPointer sig) { + traceJNI("GetStaticMethodID"); + return env.getFunctions().getGetStaticMethodID().call(env, clazz, name, sig); + } + + public static JMethodID GetMethodID(JNIEnv env, JClass clazz, CCharPointer name, CCharPointer sig) { + traceJNI("GetMethodID"); + return env.getFunctions().getGetMethodID().call(env, clazz, name, sig); + } + + public static JFieldID GetStaticFieldID(JNIEnv env, JClass clazz, CCharPointer name, CCharPointer sig) { + traceJNI("GetStaticFieldID"); + return env.getFunctions().getGetStaticFieldID().call(env, clazz, name, sig); + } + + public static JFieldID GetFieldID(JNIEnv env, JClass clazz, CCharPointer name, CCharPointer signature) { + traceJNI("GetFieldID"); + return env.getFunctions().getGetFieldID().call(env, clazz, name, signature); + } + + public static JObject GetStaticObjectField(JNIEnv env, JClass clazz, JFieldID fieldID) { + traceJNI("GetFieldID"); + return env.getFunctions().getGetStaticObjectField().call(env, clazz, fieldID); + } + + public static int GetIntField(JNIEnv env, JObject object, JFieldID fieldID) { + traceJNI("GetIntField"); + return env.getFunctions().getGetIntField().call(env, object, fieldID); + } + + public static JObjectArray NewObjectArray(JNIEnv env, int len, JClass componentClass, JObject initialElement) { + traceJNI("NewObjectArray"); + return env.getFunctions().getNewObjectArray().call(env, len, componentClass, initialElement); + } + + public static JBooleanArray NewBooleanArray(JNIEnv env, int len) { + traceJNI("NewBooleanArray"); + return env.getFunctions().getNewBooleanArray().call(env, len); + } + + public static JByteArray NewByteArray(JNIEnv env, int len) { + traceJNI("NewByteArray"); + return env.getFunctions().getNewByteArray().call(env, len); + } + + public static JCharArray NewCharArray(JNIEnv env, int len) { + traceJNI("NewCharArray"); + return env.getFunctions().getNewCharArray().call(env, len); + } + + public static JShortArray NewShortArray(JNIEnv env, int len) { + traceJNI("NewShortArray"); + return env.getFunctions().getNewShortArray().call(env, len); + } + + public static JIntArray NewIntArray(JNIEnv env, int len) { + traceJNI("NewIntArray"); + return env.getFunctions().getNewIntArray().call(env, len); + } + + public static JLongArray NewLongArray(JNIEnv env, int len) { + traceJNI("NewLongArray"); + return env.getFunctions().getNewLongArray().call(env, len); + } + + public static JFloatArray NewFloatArray(JNIEnv env, int len) { + traceJNI("NewFloatArray"); + return env.getFunctions().getNewFloatArray().call(env, len); + } + + public static JDoubleArray NewDoubleArray(JNIEnv env, int len) { + traceJNI("NewDoubleArray"); + return env.getFunctions().getNewDoubleArray().call(env, len); + } + + public static int GetArrayLength(JNIEnv env, JArray array) { + traceJNI("GetArrayLength"); + return env.getFunctions().getGetArrayLength().call(env, array); + } + + public static void SetObjectArrayElement(JNIEnv env, JObjectArray array, int index, JObject value) { + traceJNI("SetObjectArrayElement"); + env.getFunctions().getSetObjectArrayElement().call(env, array, index, value); + } + + public static JObject GetObjectArrayElement(JNIEnv env, JObjectArray array, int index) { + traceJNI("GetObjectArrayElement"); + return env.getFunctions().getGetObjectArrayElement().call(env, array, index); + } + + public static CCharPointer GetBooleanArrayElements(JNIEnv env, JBooleanArray array, JValue isCopy) { + traceJNI("GetBooleanArrayElements"); + return env.getFunctions().getGetBooleanArrayElements().call(env, array, isCopy); + } + + public static CCharPointer GetByteArrayElements(JNIEnv env, JByteArray array, JValue isCopy) { + traceJNI("GetByteArrayElements"); + return env.getFunctions().getGetByteArrayElements().call(env, array, isCopy); + } + + public static CShortPointer GetCharArrayElements(JNIEnv env, JCharArray array, JValue isCopy) { + traceJNI("GetCharArrayElements"); + return env.getFunctions().getGetCharArrayElements().call(env, array, isCopy); + } + + public static CShortPointer GetShortArrayElements(JNIEnv env, JShortArray array, JValue isCopy) { + traceJNI("GetShortArrayElements"); + return env.getFunctions().getGetShortArrayElements().call(env, array, isCopy); + } + + public static CIntPointer GetIntArrayElements(JNIEnv env, JIntArray array, JValue isCopy) { + traceJNI("GetIntArrayElements"); + return env.getFunctions().getGetIntArrayElements().call(env, array, isCopy); + } + + public static CLongPointer GetLongArrayElements(JNIEnv env, JLongArray array, JValue isCopy) { + traceJNI("GetLongArrayElements"); + return env.getFunctions().getGetLongArrayElements().call(env, array, isCopy); + } + + public static CFloatPointer GetFloatArrayElements(JNIEnv env, JFloatArray array, JValue isCopy) { + traceJNI("GetFloatArrayElements"); + return env.getFunctions().getGetFloatArrayElements().call(env, array, isCopy); + } + + public static CDoublePointer GetDoubleArrayElements(JNIEnv env, JDoubleArray array, JValue isCopy) { + traceJNI("GetFloatArrayElements"); + return env.getFunctions().getGetDoubleArrayElements().call(env, array, isCopy); + } + + public static void ReleaseBooleanArrayElements(JNIEnv env, JBooleanArray array, CCharPointer elems, int mode) { + traceJNI("ReleaseBooleanArrayElements"); + env.getFunctions().getReleaseBooleanArrayElements().call(env, array, elems, mode); + } + + public static void ReleaseByteArrayElements(JNIEnv env, JByteArray array, CCharPointer elems, int mode) { + traceJNI("ReleaseByteArrayElements"); + env.getFunctions().getReleaseByteArrayElements().call(env, array, elems, mode); + } + + public static void ReleaseCharArrayElements(JNIEnv env, JCharArray array, CShortPointer elems, int mode) { + traceJNI("ReleaseCharArrayElements"); + env.getFunctions().getReleaseCharArrayElements().call(env, array, elems, mode); + } + + public static void ReleaseShortArrayElements(JNIEnv env, JShortArray array, CShortPointer elems, int mode) { + traceJNI("ReleaseShortArrayElements"); + env.getFunctions().getReleaseShortArrayElements().call(env, array, elems, mode); + } + + public static void ReleaseIntArrayElements(JNIEnv env, JIntArray array, CIntPointer elems, int mode) { + traceJNI("ReleaseIntArrayElements"); + env.getFunctions().getReleaseIntArrayElements().call(env, array, elems, mode); + } + + public static void ReleaseLongArrayElements(JNIEnv env, JLongArray array, CLongPointer elems, int mode) { + traceJNI("ReleaseLongArrayElements"); + env.getFunctions().getReleaseLongArrayElements().call(env, array, elems, mode); + } + + public static void ReleaseFloatArrayElements(JNIEnv env, JFloatArray array, CFloatPointer elems, int mode) { + traceJNI("ReleaseFloatArrayElements"); + env.getFunctions().getReleaseFloatArrayElements().call(env, array, elems, mode); + } + + public static void ReleaseDoubleArrayElements(JNIEnv env, JDoubleArray array, CDoublePointer elems, int mode) { + traceJNI("ReleaseDoubleArrayElements"); + env.getFunctions().getReleaseDoubleArrayElements().call(env, array, elems, mode); + } + + public static void GetBooleanArrayRegion(JNIEnv env, JBooleanArray array, int offset, int len, CCharPointer buff) { + traceJNI("GetBooleanArrayRegion"); + env.getFunctions().getGetBooleanArrayRegion().call(env, array, offset, len, buff); + } + + public static void GetByteArrayRegion(JNIEnv env, JByteArray array, int offset, int len, CCharPointer buff) { + traceJNI("GetByteArrayRegion"); + env.getFunctions().getGetByteArrayRegion().call(env, array, offset, len, buff); + } + + public static void GetCharArrayRegion(JNIEnv env, JCharArray array, int offset, int len, CShortPointer buff) { + traceJNI("GetCharArrayRegion"); + env.getFunctions().getGetCharArrayRegion().call(env, array, offset, len, buff); + } + + public static void GetShortArrayRegion(JNIEnv env, JShortArray array, int offset, int len, CShortPointer buff) { + traceJNI("GetShortArrayRegion"); + env.getFunctions().getGetShortArrayRegion().call(env, array, offset, len, buff); + } + + public static void GetIntArrayRegion(JNIEnv env, JIntArray array, int offset, int len, CIntPointer buff) { + traceJNI("GetIntArrayRegion"); + env.getFunctions().getGetIntArrayRegion().call(env, array, offset, len, buff); + } + + public static void GetLongArrayRegion(JNIEnv env, JLongArray array, int offset, int len, CLongPointer buff) { + traceJNI("GetLongArrayRegion"); + env.getFunctions().getGetLongArrayRegion().call(env, array, offset, len, buff); + } + + public static void GetFloatArrayRegion(JNIEnv env, JFloatArray array, int offset, int len, CFloatPointer buff) { + traceJNI("GetFloatArrayRegion"); + env.getFunctions().getGetFloatArrayRegion().call(env, array, offset, len, buff); + } + + public static void GetDoubleArrayRegion(JNIEnv env, JDoubleArray array, int offset, int len, CDoublePointer buff) { + traceJNI("GetDoubleArrayRegion"); + env.getFunctions().getGetDoubleArrayRegion().call(env, array, offset, len, buff); + } + + public static void SetBooleanArrayRegion(JNIEnv env, JBooleanArray array, int offset, int len, CCharPointer buff) { + traceJNI("SetBooleanArrayRegion"); + env.getFunctions().getSetBooleanArrayRegion().call(env, array, offset, len, buff); + } + + public static void SetByteArrayRegion(JNIEnv env, JByteArray array, int offset, int len, CCharPointer buff) { + traceJNI("SetByteArrayRegion"); + env.getFunctions().getSetByteArrayRegion().call(env, array, offset, len, buff); + } + + public static void SetCharArrayRegion(JNIEnv env, JCharArray array, int offset, int len, CShortPointer buff) { + traceJNI("SetCharArrayRegion"); + env.getFunctions().getSetCharArrayRegion().call(env, array, offset, len, buff); + } + + public static void SetShortArrayRegion(JNIEnv env, JShortArray array, int offset, int len, CShortPointer buff) { + traceJNI("SetShortArrayRegion"); + env.getFunctions().getSetShortArrayRegion().call(env, array, offset, len, buff); + } + + public static void SetIntArrayRegion(JNIEnv env, JIntArray array, int offset, int len, CIntPointer buff) { + traceJNI("SetIntArrayRegion"); + env.getFunctions().getSetIntArrayRegion().call(env, array, offset, len, buff); + } + + public static void SetLongArrayRegion(JNIEnv env, JLongArray array, int offset, int len, CLongPointer buff) { + traceJNI("SetLongArrayRegion"); + env.getFunctions().getSetLongArrayRegion().call(env, array, offset, len, buff); + } + + public static void SetFloatArrayRegion(JNIEnv env, JFloatArray array, int offset, int len, CFloatPointer buff) { + traceJNI("SetFloatArrayRegion"); + env.getFunctions().getSetFloatArrayRegion().call(env, array, offset, len, buff); + } + + public static void SetDoubleArrayRegion(JNIEnv env, JDoubleArray array, int offset, int len, CDoublePointer buff) { + traceJNI("SetDoubleArrayRegion"); + env.getFunctions().getSetDoubleArrayRegion().call(env, array, offset, len, buff); + } + + public static JavaVM GetJavaVM(JNIEnv env) { + traceJNI("GetJavaVM"); + JavaVMPointer javaVMPointer = StackValue.get(JavaVMPointer.class); + if (env.getFunctions().getGetJavaVM().call(env, javaVMPointer) == JNI_OK) { + return javaVMPointer.readJavaVM(); + } else { + return WordFactory.nullPointer(); + } + } + + public static JNIEnv GetEnv(JavaVM vm) { + traceJNI("GetEnv"); + JNIEnvPointer envPointer = StackValue.get(JNIEnvPointer.class); + if (vm.getFunctions().getGetEnv().call(vm, envPointer, JNI_VERSION_10) == JNI_OK) { + return envPointer.readJNIEnv(); + } else { + return WordFactory.nullPointer(); + } + } + + public static JNIEnv AttachCurrentThread(JavaVM vm, JavaVMAttachArgs args) { + traceJNI("AttachCurrentThread"); + JNIEnvPointer envPointer = StackValue.get(JNIEnvPointer.class); + if (vm.getFunctions().getAttachCurrentThread().call(vm, envPointer, args) == JNI_OK) { + return envPointer.readJNIEnv(); + } else { + return WordFactory.nullPointer(); + } + } + + public static JNIEnv AttachCurrentThreadAsDaemon(JavaVM vm, JavaVMAttachArgs args) { + traceJNI("AttachCurrentThreadAsDaemon"); + JNIEnvPointer envPointer = StackValue.get(JNIEnvPointer.class); + if (vm.getFunctions().getAttachCurrentThreadAsDaemon().call(vm, envPointer, args) == JNI_OK) { + return envPointer.readJNIEnv(); + } else { + return WordFactory.nullPointer(); + } + } + + public static boolean DetachCurrentThread(JavaVM vm) { + traceJNI("DetachCurrentThread"); + return vm.getFunctions().getDetachCurrentThread().call(vm) == JNI_OK; + } + + public static void Throw(JNIEnv env, JThrowable throwable) { + traceJNI("Throw"); + env.getFunctions().getThrow().call(env, throwable); + } + + public static boolean ExceptionCheck(JNIEnv env) { + traceJNI("ExceptionCheck"); + return env.getFunctions().getExceptionCheck().call(env); + } + + public static void ExceptionClear(JNIEnv env) { + traceJNI("ExceptionClear"); + env.getFunctions().getExceptionClear().call(env); + } + + public static void ExceptionDescribe(JNIEnv env) { + traceJNI("ExceptionDescribe"); + env.getFunctions().getExceptionDescribe().call(env); + } + + public static JThrowable ExceptionOccurred(JNIEnv env) { + traceJNI("ExceptionOccurred"); + return env.getFunctions().getExceptionOccurred().call(env); + } + + /** + * Creates a new global reference. + * + * @param env the JNIEnv + * @param ref JObject to create JNI global reference for + * @param type type of the object, used only for tracing to distinguish global references + * @return JNI global reference for given {@link JObject} + */ + @SuppressWarnings("unchecked") + public static T NewGlobalRef(JNIEnv env, T ref, String type) { + traceJNI("NewGlobalRef"); + T res = (T) env.getFunctions().getNewGlobalRef().call(env, ref); + if (tracingAt(3)) { + trace(3, "New global reference for 0x%x of type %s -> 0x%x", ref.rawValue(), type, res.rawValue()); + } + return res; + } + + public static void DeleteGlobalRef(JNIEnv env, JObject ref) { + traceJNI("DeleteGlobalRef"); + if (tracingAt(3)) { + trace(3, "Delete global reference 0x%x", ref.rawValue()); + } + env.getFunctions().getDeleteGlobalRef().call(env, ref); + } + + /** + * Creates a new weak global reference. + * + * @param env the JNIEnv + * @param ref JObject to create JNI weak global reference for + * @param type type of the object, used only for tracing to distinguish global references + * @return JNI weak global reference for given {@link JObject} + */ + public static JWeak NewWeakGlobalRef(JNIEnv env, JObject ref, String type) { + traceJNI("NewWeakGlobalRef"); + JWeak res = env.getFunctions().getNewWeakGlobalRef().call(env, ref); + if (tracingAt(3)) { + trace(3, "New weak global reference for 0x%x of type %s -> 0x%x", ref.rawValue(), type, res.rawValue()); + } + return res; + } + + public static JObject NewLocalRef(JNIEnv env, JObject ref) { + traceJNI("NewLocalRef"); + return env.getFunctions().getNewLocalRef().call(env, ref); + } + + public static void DeleteWeakGlobalRef(JNIEnv env, JWeak ref) { + traceJNI("DeleteWeakGlobalRef"); + if (tracingAt(3)) { + trace(3, "Delete weak global reference 0x%x", ref.rawValue()); + } + env.getFunctions().getDeleteWeakGlobalRef().call(env, ref); + } + + public static VoidPointer GetDirectBufferAddress(JNIEnv env, JObject buf) { + traceJNI("GetDirectBufferAddress"); + return env.getFunctions().getGetDirectBufferAddress().call(env, buf); + } + + public static boolean IsInstanceOf(JNIEnv env, JObject obj, JClass clazz) { + traceJNI("IsInstanceOf"); + return env.getFunctions().getIsInstanceOf().call(env, obj, clazz); + } + + // Checkstyle: resume + + private static void traceJNI(String function) { + trace(2, "%s->JNI: %s", getFeatureName(), function); + } + + private JNIUtil() { + } + + /** + * Decodes a string in the HotSpot heap to a local {@link String}. + */ + public static String createString(JNIEnv env, JString hsString) { + if (hsString.isNull()) { + return null; + } + int len = env.getFunctions().getGetStringLength().call(env, hsString); + CShortPointer unicode = env.getFunctions().getGetStringChars().call(env, hsString, WordFactory.nullPointer()); + try { + char[] data = new char[len]; + for (int i = 0; i < len; i++) { + data[i] = (char) unicode.read(i); + } + return new String(data); + } finally { + env.getFunctions().getReleaseStringChars().call(env, hsString, unicode); + } + } + + /** + * Creates a String in the HotSpot heap from {@code string}. + */ + public static JString createHSString(JNIEnv env, String string) { + if (string == null) { + return WordFactory.nullPointer(); + } + int len = string.length(); + CShortPointer buffer = UnmanagedMemory.malloc(len << 1); + try { + for (int i = 0; i < len; i++) { + buffer.write(i, (short) string.charAt(i)); + } + return env.getFunctions().getNewString().call(env, buffer, len); + } finally { + UnmanagedMemory.free(buffer); + } + } + + public static boolean[] createArray(JNIEnv env, JBooleanArray booleanArray) { + if (booleanArray.isNull()) { + return null; + } + int len = GetArrayLength(env, booleanArray); + boolean[] booleans = new boolean[len]; + arrayCopy(env, booleanArray, 0, booleans, 0, len); + return booleans; + } + + public static JBooleanArray createHSArray(JNIEnv jniEnv, boolean[] a) { + if (a == null) { + return WordFactory.nullPointer(); + } + JBooleanArray array = NewBooleanArray(jniEnv, a.length); + arrayCopy(jniEnv, a, 0, array, 0, a.length); + return array; + } + + public static byte[] createArray(JNIEnv env, JByteArray byteArray) { + if (byteArray.isNull()) { + return null; + } + int len = GetArrayLength(env, byteArray); + byte[] bytes = new byte[len]; + arrayCopy(env, byteArray, 0, bytes, 0, len); + return bytes; + } + + public static JByteArray createHSArray(JNIEnv jniEnv, byte[] a) { + if (a == null) { + return WordFactory.nullPointer(); + } + JByteArray array = NewByteArray(jniEnv, a.length); + arrayCopy(jniEnv, a, 0, array, 0, a.length); + return array; + } + + public static char[] createArray(JNIEnv env, JCharArray charArray) { + if (charArray.isNull()) { + return null; + } + int len = GetArrayLength(env, charArray); + char[] chars = new char[len]; + arrayCopy(env, charArray, 0, chars, 0, len); + return chars; + } + + public static JCharArray createHSArray(JNIEnv jniEnv, char[] a) { + if (a == null) { + return WordFactory.nullPointer(); + } + JCharArray array = NewCharArray(jniEnv, a.length); + arrayCopy(jniEnv, a, 0, array, 0, a.length); + return array; + } + + public static short[] createArray(JNIEnv env, JShortArray shortArray) { + if (shortArray.isNull()) { + return null; + } + int len = GetArrayLength(env, shortArray); + short[] shorts = new short[len]; + arrayCopy(env, shortArray, 0, shorts, 0, len); + return shorts; + } + + public static JShortArray createHSArray(JNIEnv jniEnv, short[] a) { + if (a == null) { + return WordFactory.nullPointer(); + } + JShortArray array = NewShortArray(jniEnv, a.length); + arrayCopy(jniEnv, a, 0, array, 0, a.length); + return array; + } + + public static int[] createArray(JNIEnv env, JIntArray intArray) { + if (intArray.isNull()) { + return null; + } + int len = GetArrayLength(env, intArray); + int[] ints = new int[len]; + arrayCopy(env, intArray, 0, ints, 0, len); + return ints; + } + + public static JIntArray createHSArray(JNIEnv jniEnv, int[] a) { + if (a == null) { + return WordFactory.nullPointer(); + } + JIntArray array = NewIntArray(jniEnv, a.length); + arrayCopy(jniEnv, a, 0, array, 0, a.length); + return array; + } + + public static long[] createArray(JNIEnv env, JLongArray longArray) { + if (longArray.isNull()) { + return null; + } + int len = GetArrayLength(env, longArray); + long[] longs = new long[len]; + arrayCopy(env, longArray, 0, longs, 0, len); + return longs; + } + + public static JLongArray createHSArray(JNIEnv jniEnv, long[] a) { + if (a == null) { + return WordFactory.nullPointer(); + } + JLongArray array = NewLongArray(jniEnv, a.length); + arrayCopy(jniEnv, a, 0, array, 0, a.length); + return array; + } + + public static float[] createArray(JNIEnv env, JFloatArray floatArray) { + if (floatArray.isNull()) { + return null; + } + int len = GetArrayLength(env, floatArray); + float[] floats = new float[len]; + arrayCopy(env, floatArray, 0, floats, 0, len); + return floats; + } + + public static JFloatArray createHSArray(JNIEnv jniEnv, float[] a) { + if (a == null) { + return WordFactory.nullPointer(); + } + JFloatArray array = NewFloatArray(jniEnv, a.length); + arrayCopy(jniEnv, a, 0, array, 0, a.length); + return array; + } + + public static double[] createArray(JNIEnv env, JDoubleArray doubleArray) { + if (doubleArray.isNull()) { + return null; + } + int len = GetArrayLength(env, doubleArray); + double[] doubles = new double[len]; + arrayCopy(env, doubleArray, 0, doubles, 0, len); + return doubles; + } + + public static JDoubleArray createHSArray(JNIEnv jniEnv, double[] a) { + if (a == null) { + return WordFactory.nullPointer(); + } + JDoubleArray array = NewDoubleArray(jniEnv, a.length); + arrayCopy(jniEnv, a, 0, array, 0, a.length); + return array; + } + + public static JObjectArray createHSArray(JNIEnv jniEnv, Object[] array, int sourcePosition, int length, String componentTypeBinaryName) { + JObjectArray hsArray; + if (array != null) { + hsArray = JNIUtil.NewObjectArray(jniEnv, length, JNIUtil.findClass(jniEnv, WordFactory.nullPointer(), componentTypeBinaryName, true), WordFactory.nullPointer()); + for (int i = 0; i < length; i++) { + HSObject element = (HSObject) array[sourcePosition + i]; + JObject hsElement = element != null ? element.getHandle() : WordFactory.nullPointer(); + JNIUtil.SetObjectArrayElement(jniEnv, hsArray, i, hsElement); + } + } else { + hsArray = WordFactory.nullPointer(); + } + return hsArray; + } + + public static void arrayCopy(JNIEnv jniEnv, JBooleanArray src, int srcPos, boolean[] dest, int destPos, int length) { + CCharPointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + CCharPointer booleanPointer = length <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(length); + try { + GetBooleanArrayRegion(jniEnv, src, srcPos, length, booleanPointer); + for (int i = 0; i < length; i++) { + dest[destPos + i] = booleanPointer.addressOf(i).read() != 0; + } + } finally { + if (booleanPointer != staticBuffer) { + UnmanagedMemory.free(booleanPointer); + } + } + } + + public static void arrayCopy(JNIEnv jniEnv, boolean[] src, int srcPos, JBooleanArray dest, int destPos, int length) { + CCharPointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + CCharPointer booleanPointer = length <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(length); + try { + for (int i = 0; i < length; i++) { + booleanPointer.write(i, src[srcPos + i] ? (byte) 1 : 0); + } + SetBooleanArrayRegion(jniEnv, dest, destPos, length, booleanPointer); + } finally { + if (booleanPointer != staticBuffer) { + UnmanagedMemory.free(booleanPointer); + } + } + } + + public static void arrayCopy(JNIEnv jniEnv, JByteArray src, int srcPos, byte[] dest, int destPos, int length) { + CCharPointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + CCharPointer bytePointer = length <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(length); + try { + GetByteArrayRegion(jniEnv, src, srcPos, length, bytePointer); + CTypeConversion.asByteBuffer(bytePointer, length).get(dest, destPos, length); + } finally { + if (bytePointer != staticBuffer) { + UnmanagedMemory.free(bytePointer); + } + } + } + + public static void arrayCopy(JNIEnv jniEnv, byte[] src, int srcPos, JByteArray dest, int destPos, int length) { + CCharPointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + CCharPointer bytePointer = length <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(length); + try { + CTypeConversion.asByteBuffer(bytePointer, length).put(src, srcPos, length); + SetByteArrayRegion(jniEnv, dest, destPos, length, bytePointer); + } finally { + if (bytePointer != staticBuffer) { + UnmanagedMemory.free(bytePointer); + } + } + } + + public static void arrayCopy(JNIEnv jniEnv, JCharArray src, int srcPos, char[] dest, int destPos, int length) { + CShortPointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + int bytesLength = length * Character.BYTES; + CShortPointer shortPointer = bytesLength <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(bytesLength); + try { + GetCharArrayRegion(jniEnv, src, srcPos, length, shortPointer); + CTypeConversion.asByteBuffer(shortPointer, bytesLength).asCharBuffer().get(dest, destPos, length); + } finally { + if (shortPointer != staticBuffer) { + UnmanagedMemory.free(shortPointer); + } + } + } + + public static void arrayCopy(JNIEnv jniEnv, char[] src, int srcPos, JCharArray dest, int destPos, int length) { + CShortPointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + int bytesLength = length * Character.BYTES; + CShortPointer shortPointer = bytesLength <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(bytesLength); + try { + CTypeConversion.asByteBuffer(shortPointer, bytesLength).asCharBuffer().put(src, srcPos, length); + SetCharArrayRegion(jniEnv, dest, destPos, length, shortPointer); + } finally { + if (shortPointer != staticBuffer) { + UnmanagedMemory.free(shortPointer); + } + } + } + + public static void arrayCopy(JNIEnv jniEnv, JShortArray src, int srcPos, short[] dest, int destPos, int length) { + CShortPointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + int bytesLength = length * Short.BYTES; + CShortPointer shortPointer = bytesLength <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(bytesLength); + try { + GetShortArrayRegion(jniEnv, src, srcPos, length, shortPointer); + CTypeConversion.asByteBuffer(shortPointer, bytesLength).asShortBuffer().get(dest, destPos, length); + } finally { + if (shortPointer != staticBuffer) { + UnmanagedMemory.free(shortPointer); + } + } + } + + public static void arrayCopy(JNIEnv jniEnv, short[] src, int srcPos, JShortArray dest, int destPos, int length) { + CShortPointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + int bytesLength = length * Short.BYTES; + CShortPointer shortPointer = bytesLength <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(bytesLength); + try { + CTypeConversion.asByteBuffer(shortPointer, bytesLength).asShortBuffer().put(src, srcPos, length); + SetShortArrayRegion(jniEnv, dest, destPos, length, shortPointer); + } finally { + if (shortPointer != staticBuffer) { + UnmanagedMemory.free(shortPointer); + } + } + } + + public static void arrayCopy(JNIEnv jniEnv, JIntArray src, int srcPos, int[] dest, int destPos, int length) { + CIntPointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + int bytesLength = length * Integer.BYTES; + CIntPointer intPointer = bytesLength <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(bytesLength); + try { + GetIntArrayRegion(jniEnv, src, srcPos, length, intPointer); + CTypeConversion.asByteBuffer(intPointer, bytesLength).asIntBuffer().get(dest, destPos, length); + } finally { + if (intPointer != staticBuffer) { + UnmanagedMemory.free(intPointer); + } + } + } + + public static void arrayCopy(JNIEnv jniEnv, int[] src, int srcPos, JIntArray dest, int destPos, int length) { + CIntPointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + int bytesLength = length * Integer.BYTES; + CIntPointer intPointer = bytesLength <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(bytesLength); + try { + CTypeConversion.asByteBuffer(intPointer, bytesLength).asIntBuffer().put(src, srcPos, length); + SetIntArrayRegion(jniEnv, dest, destPos, length, intPointer); + } finally { + if (intPointer != staticBuffer) { + UnmanagedMemory.free(intPointer); + } + } + } + + public static void arrayCopy(JNIEnv jniEnv, JLongArray src, int srcPos, long[] dest, int destPos, int length) { + CLongPointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + int bytesLength = length * Long.BYTES; + CLongPointer longPointer = bytesLength <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(bytesLength); + try { + GetLongArrayRegion(jniEnv, src, srcPos, length, longPointer); + CTypeConversion.asByteBuffer(longPointer, bytesLength).asLongBuffer().get(dest, destPos, length); + } finally { + if (longPointer != staticBuffer) { + UnmanagedMemory.free(longPointer); + } + } + } + + public static void arrayCopy(JNIEnv jniEnv, long[] src, int srcPos, JLongArray dest, int destPos, int length) { + CLongPointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + int bytesLength = length * Long.BYTES; + CLongPointer longPointer = bytesLength <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(bytesLength); + try { + CTypeConversion.asByteBuffer(longPointer, bytesLength).asLongBuffer().put(src, srcPos, length); + SetLongArrayRegion(jniEnv, dest, destPos, length, longPointer); + } finally { + if (longPointer != staticBuffer) { + UnmanagedMemory.free(longPointer); + } + } + } + + public static void arrayCopy(JNIEnv jniEnv, JFloatArray src, int srcPos, float[] dest, int destPos, int length) { + CFloatPointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + int bytesLength = length * Float.BYTES; + CFloatPointer floatPointer = bytesLength <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(bytesLength); + try { + GetFloatArrayRegion(jniEnv, src, srcPos, length, floatPointer); + CTypeConversion.asByteBuffer(floatPointer, bytesLength).asFloatBuffer().get(dest, destPos, length); + } finally { + if (floatPointer != staticBuffer) { + UnmanagedMemory.free(floatPointer); + } + } + } + + public static void arrayCopy(JNIEnv jniEnv, float[] src, int srcPos, JFloatArray dest, int destPos, int length) { + CFloatPointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + int bytesLength = length * Float.BYTES; + CFloatPointer floatPointer = bytesLength <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(bytesLength); + try { + CTypeConversion.asByteBuffer(floatPointer, bytesLength).asFloatBuffer().put(src, srcPos, length); + SetFloatArrayRegion(jniEnv, dest, destPos, length, floatPointer); + } finally { + if (floatPointer != staticBuffer) { + UnmanagedMemory.free(floatPointer); + } + } + } + + public static void arrayCopy(JNIEnv jniEnv, JDoubleArray src, int srcPos, double[] dest, int destPos, int length) { + CDoublePointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + int bytesLength = length * Double.BYTES; + CDoublePointer doublePointer = bytesLength <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(bytesLength); + try { + GetDoubleArrayRegion(jniEnv, src, srcPos, length, doublePointer); + CTypeConversion.asByteBuffer(doublePointer, bytesLength).asDoubleBuffer().get(dest, destPos, length); + } finally { + if (doublePointer != staticBuffer) { + UnmanagedMemory.free(doublePointer); + } + } + } + + public static void arrayCopy(JNIEnv jniEnv, double[] src, int srcPos, JDoubleArray dest, int destPos, int length) { + CDoublePointer staticBuffer = StackValue.get(ARRAY_COPY_STATIC_BUFFER_SIZE); + int bytesLength = length * Double.BYTES; + CDoublePointer doublePointer = bytesLength <= ARRAY_COPY_STATIC_BUFFER_SIZE ? staticBuffer : UnmanagedMemory.malloc(bytesLength); + try { + CTypeConversion.asByteBuffer(doublePointer, bytesLength).asDoubleBuffer().put(src, srcPos, length); + SetDoubleArrayRegion(jniEnv, dest, destPos, length, doublePointer); + } finally { + if (doublePointer != staticBuffer) { + UnmanagedMemory.free(doublePointer); + } + } + } + + /** + * Converts a fully qualified Java class name from Java source format (e.g. + * {@code "java.lang.getString"}) to internal format (e.g. {@code "Ljava/lang/getString;"}. + */ + public static String getInternalName(String fqn) { + return "L" + getBinaryName(fqn) + ";"; + } + + /** + * Converts a fully qualified Java class name from Java source format (e.g. + * {@code "java.lang.getString"}) to binary format (e.g. {@code "java/lang/getString"}. + */ + public static String getBinaryName(String fqn) { + return fqn.replace('.', '/'); + } + + /** + * Creates a JVM method signature as specified in the Sections 4.3.3 of the JVM Specification. + */ + public static String encodeMethodSignature(Class returnType, Class... parameterTypes) { + StringBuilder builder = new StringBuilder("("); + for (Class type : parameterTypes) { + encodeType(type, builder); + } + builder.append(")"); + encodeType(returnType, builder); + return builder.toString(); + } + + /** + * Creates a JVM field signature as specified in the Sections 4.3.2 of the JVM Specification. + */ + public static String encodeFieldSignature(Class type) { + StringBuilder res = new StringBuilder(); + encodeType(type, res); + return res.toString(); + } + + private static void encodeType(Class type, StringBuilder buf) { + String desc; + if (type == boolean.class) { + desc = "Z"; + } else if (type == byte.class) { + desc = "B"; + } else if (type == char.class) { + desc = "C"; + } else if (type == short.class) { + desc = "S"; + } else if (type == int.class) { + desc = "I"; + } else if (type == long.class) { + desc = "J"; + } else if (type == float.class) { + desc = "F"; + } else if (type == double.class) { + desc = "D"; + } else if (type == void.class) { + desc = "V"; + } else if (type.isArray()) { + buf.append('['); + encodeType(type.getComponentType(), buf); + return; + } else { + desc = "L" + type.getName().replace('.', '/') + ";"; + } + buf.append(desc); + } + + /** + * Returns a {@link JClass} for given binary name. + */ + public static JClass findClass(JNIEnv env, String binaryName) { + trace(1, "%s->HS: findClass %s", getFeatureName(), binaryName); + try (CCharPointerHolder name = CTypeConversion.toCString(binaryName)) { + return JNIUtil.FindClass(env, name.get()); + } + } + + /** + * Finds a class in HotSpot heap using a given {@code ClassLoader}. + * + * @param env the {@code JNIEnv} + * @param binaryName the class binary name + */ + public static JClass findClass(JNIEnv env, JObject classLoader, String binaryName) { + if (classLoader.isNull()) { + throw new IllegalArgumentException("ClassLoader must be non null."); + } + trace(1, "%s->HS: findClass %s", getFeatureName(), binaryName); + JMethodID findClassId = findMethod(env, JNIUtil.GetObjectClass(env, classLoader), false, false, METHOD_LOAD_CLASS[0], METHOD_LOAD_CLASS[1]); + JValue params = StackValue.get(1, JValue.class); + params.addressOf(0).setJObject(JNIUtil.createHSString(env, binaryName.replace('/', '.'))); + return (JClass) env.getFunctions().getCallObjectMethodA().call(env, classLoader, findClassId, params); + } + + /** + * Finds a class in HotSpot heap using JNI. + * + * @param env the {@code JNIEnv} + * @param classLoader the class loader to find class in or {@link WordFactory#nullPointer() NULL + * pointer}. + * @param binaryName the class binary name + * @param required if {@code true} the {@link JNIExceptionWrapper} is thrown when the class is + * not found. If {@code false} the {@code NULL pointer} is returned when the class is + * not found. + */ + public static JClass findClass(JNIEnv env, JObject classLoader, String binaryName, boolean required) { + Class allowedException = null; + try { + if (classLoader.isNonNull()) { + allowedException = required ? null : ClassNotFoundException.class; + return findClass(env, classLoader, binaryName); + } else { + allowedException = required ? null : NoClassDefFoundError.class; + return findClass(env, binaryName); + } + } finally { + JNIExceptionWrapper.wrapAndThrowPendingJNIException(env, + allowedException == null ? JNIExceptionWrapper.ExceptionHandler.DEFAULT : JNIExceptionWrapper.ExceptionHandler.allowExceptions(allowedException)); + } + } + + /** + * Returns a ClassLoader used to load the compiler classes. + */ + public static JObject getJVMCIClassLoader(JNIEnv env) { + JClass clazz; + try (CCharPointerHolder className = CTypeConversion.toCString(JNIUtil.getBinaryName(ClassLoader.class.getName()))) { + clazz = JNIUtil.FindClass(env, className.get()); + } + if (clazz.isNull()) { + throw new InternalError("No such class " + ClassLoader.class.getName()); + } + JMethodID getClassLoaderId = findMethod(env, clazz, true, true, METHOD_GET_PLATFORM_CLASS_LOADER[0], METHOD_GET_PLATFORM_CLASS_LOADER[1]); + if (getClassLoaderId.isNull()) { + throw new InternalError(String.format("Cannot find method %s in class %s.", METHOD_GET_PLATFORM_CLASS_LOADER[0], ClassLoader.class.getName())); + } + return env.getFunctions().getCallStaticObjectMethodA().call(env, clazz, getClassLoaderId, nullPointer()); + } + + public static JObject getClassLoader(JNIEnv env, JClass clazz) { + if (clazz.isNull()) { + throw new NullPointerException(); + } + // Class + JClass classClass = GetObjectClass(env, clazz); // Class + JMethodID getClassLoader = JNIUtil.findMethod(env, classClass, false, "getClassLoader", "()Ljava/lang/ClassLoader;"); + if (getClassLoader.isNull()) { + throw new NullPointerException("Not found: getClassLoader()"); + } + return env.getFunctions().getCallObjectMethodA().call(env, clazz, getClassLoader, nullPointer()); + } + + /** + * Returns the {@link ClassLoader#getSystemClassLoader()}. + */ + public static JObject getSystemClassLoader(JNIEnv env) { + JClass clazz; + try (CCharPointerHolder className = CTypeConversion.toCString(JNIUtil.getBinaryName(ClassLoader.class.getName()))) { + clazz = JNIUtil.FindClass(env, className.get()); + } + if (clazz.isNull()) { + throw new InternalError("No such class " + ClassLoader.class.getName()); + } + JMethodID getClassLoaderId = findMethod(env, clazz, true, true, METHOD_GET_SYSTEM_CLASS_LOADER[0], METHOD_GET_SYSTEM_CLASS_LOADER[1]); + if (getClassLoaderId.isNull()) { + throw new InternalError(String.format("Cannot find method %s in class %s.", METHOD_GET_SYSTEM_CLASS_LOADER[0], ClassLoader.class.getName())); + } + return env.getFunctions().getCallStaticObjectMethodA().call(env, clazz, getClassLoaderId, nullPointer()); + } + + public static JMethodID findMethod(JNIEnv env, JClass clazz, boolean staticMethod, String methodName, String methodSignature) { + return findMethod(env, clazz, staticMethod, false, methodName, methodSignature); + } + + static JMethodID findMethod(JNIEnv env, JClass clazz, boolean staticMethod, boolean required, + String methodName, String methodSignature) { + JMethodID result; + try (CCharPointerHolder name = toCString(methodName); CCharPointerHolder sig = toCString(methodSignature)) { + result = staticMethod ? GetStaticMethodID(env, clazz, name.get(), sig.get()) : GetMethodID(env, clazz, name.get(), sig.get()); + JNIExceptionWrapper.wrapAndThrowPendingJNIException(env, + required ? JNIExceptionWrapper.ExceptionHandler.DEFAULT : JNIExceptionWrapper.ExceptionHandler.allowExceptions(NoSuchMethodError.class)); + return result; + } + } + + public static JFieldID findField(JNIEnv env, JClass clazz, boolean staticField, String fieldName, String fieldSignature) { + JFieldID result; + try (CCharPointerHolder name = toCString(fieldName); CCharPointerHolder sig = toCString(fieldSignature)) { + result = staticField ? GetStaticFieldID(env, clazz, name.get(), sig.get()) : GetFieldID(env, clazz, name.get(), sig.get()); + JNIExceptionWrapper.wrapAndThrowPendingJNIException(env); + return result; + } + } + + /** + * Attaches the current C thread to a Java Thread. + * + * @param vm the {@link JavaVM} pointer. + * @param daemon if true attaches the thread as a daemon thread. + * @param name the name of the Java tread or {@code null}. + * @param threadGroup the thread group to add the thread into or C {@code NULL} pointer. + * @return the current thread {@link JNIEnv} or C {@code NULL} pointer in case of error. + */ + public static JNIEnv attachCurrentThread(JavaVM vm, boolean daemon, String name, JObject threadGroup) { + try (CCharPointerHolder cname = CTypeConversion.toCString(name)) { + JavaVMAttachArgs args = StackValue.get(JavaVMAttachArgs.class); + args.setVersion(JNI_VERSION_10); + args.setGroup(threadGroup); + args.setName(cname.get()); + return daemon ? AttachCurrentThreadAsDaemon(vm, args) : AttachCurrentThread(vm, args); + } + } + + /*----------------- TRACING ------------------*/ + + public static boolean tracingAt(int level) { + return NativeBridgeSupport.getInstance().isTracingEnabled(level); + } + + /** + * Emits a trace line composed of {@code format} and {@code args} if the tracing level equal to + * or greater than {@code level}. + */ + public static void trace(int level, String format, Object... args) { + if (tracingAt(level)) { + NativeBridgeSupport.getInstance().trace(String.format(format, args)); + } + } + + public static void trace(int level, Throwable throwable) { + if (tracingAt(level)) { + StringWriter stringWriter = new StringWriter(); + try (PrintWriter out = new PrintWriter(stringWriter)) { + throwable.printStackTrace(out); + } + trace(level, stringWriter.toString()); + } + } + + static String getFeatureName() { + return NativeBridgeSupport.getInstance().getFeatureName(); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/NativeBridgeSupport.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/NativeBridgeSupport.java new file mode 100644 index 000000000000..3c734cc6e52f --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/jniutils/NativeBridgeSupport.java @@ -0,0 +1,58 @@ +/* + * 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.jdwp.bridge.jniutils; + +import org.graalvm.nativeimage.ImageSingletons; + +/** + * Services used by the {@code com.oracle.svm.jdwp.bridge.jniutils} module. To enable the + * {@code com.oracle.svm.jdwp.bridge.jniutils} module a {@code NativeBridgeSupport} instance must be + * registered in the {@link ImageSingletons}. + */ +public interface NativeBridgeSupport { + + /** + * Returns the name of a feature using {@code com.oracle.svm.jdwp.bridge.jniutils} module. The + * feature name is used in the logging output. + */ + String getFeatureName(); + + /** + * Checks if logging at given level is enabled. + */ + boolean isTracingEnabled(int level); + + /** + * Logs the message. + */ + void trace(String message); + + /** + * Returns a {@code NativeBridgeSupport} instance registered in the {@link ImageSingletons}. + */ + static NativeBridgeSupport getInstance() { + return ImageSingletons.lookup(NativeBridgeSupport.class); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/BinaryInput.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/BinaryInput.java new file mode 100644 index 000000000000..064160e38784 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/BinaryInput.java @@ -0,0 +1,640 @@ +/* + * 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.jdwp.bridge.nativebridge; + +import static com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput.ARRAY; +import static com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput.BOOLEAN; +import static com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput.BYTE; +import static com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput.CHAR; +import static com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput.DOUBLE; +import static com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput.FLOAT; +import static com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput.INT; +import static com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput.LARGE_STRING_TAG; +import static com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput.LONG; +import static com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput.NULL; +import static com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput.SHORT; +import static com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput.STRING; +import static com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput.bufferSize; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.c.type.CTypeConversion; + +/** + * A buffer used by the {@link BinaryMarshaller} to unmarshal parameters and results passed by + * value. + * + * @see BinaryOutput + * @see BinaryMarshaller + * @see JNIConfig.Builder#registerMarshaller(Class, BinaryMarshaller) + */ +public abstract class BinaryInput { + + private static final int EOF = -1; + + private byte[] tempEncodingByteBuffer; + private char[] tempEncodingCharBuffer; + protected final int length; + protected int pos; + + private BinaryInput(int length) { + this.length = length; + } + + /** + * Reads a single byte and returns {@code true} if that byte is non-zero, {@code false} if that + * byte is zero. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read. + */ + public final boolean readBoolean() throws IndexOutOfBoundsException { + int b = read(); + if (b < 0) { + throw new IndexOutOfBoundsException(); + } + return b != 0; + } + + /** + * Reads and returns a single byte. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read. + */ + public final byte readByte() throws IndexOutOfBoundsException { + int b = read(); + if (b < 0) { + throw new IndexOutOfBoundsException(); + } + return (byte) b; + } + + /** + * Reads two bytes and returns a {@code short} value. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read. + */ + public final short readShort() throws IndexOutOfBoundsException { + int b1 = read(); + int b2 = read(); + if ((b1 | b2) < 0) { + throw new IndexOutOfBoundsException(); + } + return packShort(b1, b2); + } + + /** + * Creates a Java {@code short} from given unsigned bytes, where {@code b1} is the most + * significant byte {@code byte} and {@code b2} is the least significant {@code byte}. + */ + private static short packShort(int b1, int b2) { + return (short) ((b1 << 8) + b2); + } + + /** + * Reads two bytes and returns a {@code char} value. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read. + */ + public final char readChar() throws IndexOutOfBoundsException { + int b1 = read(); + int b2 = read(); + if ((b1 | b2) < 0) { + throw new IndexOutOfBoundsException(); + } + return packChar(b1, b2); + } + + /** + * Creates a Java {@code char} from given unsigned bytes, where {@code b1} is the most + * significant byte {@code byte} and {@code b2} is the least significant {@code byte}. + */ + private static char packChar(int b1, int b2) { + return (char) ((b1 << 8) + b2); + } + + /** + * Reads four bytes and returns an {@code int} value. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read. + */ + public final int readInt() throws IndexOutOfBoundsException { + int b1 = read(); + int b2 = read(); + int b3 = read(); + int b4 = read(); + if ((b1 | b2 | b3 | b4) < 0) { + throw new IndexOutOfBoundsException(); + } + return packInt(b1, b2, b3, b4); + } + + /** + * Creates a Java {@code int} from given unsigned bytes, where {@code b1} is the most + * significant byte {@code byte} and {@code b4} is the least significant {@code byte}. + */ + private static int packInt(int b1, int b2, int b3, int b4) { + return (b1 << 24) + (b2 << 16) + (b3 << 8) + b4; + } + + /** + * Reads eight bytes and returns a {@code long} value. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read. + */ + public final long readLong() throws IndexOutOfBoundsException { + int b1 = read(); + int b2 = read(); + int b3 = read(); + int b4 = read(); + int b5 = read(); + int b6 = read(); + int b7 = read(); + int b8 = read(); + if ((b1 | b2 | b3 | b4 | b5 | b6 | b7 | b8) < 0) { + throw new IndexOutOfBoundsException(); + } + return packLong(b1, b2, b3, b4, b5, b6, b7, b8); + } + + /** + * Creates a Java {@code long} from given unsigned bytes, where {@code b1} is the most + * significant byte {@code byte} and {@code b8} is the least significant {@code byte}. + */ + private static long packLong(int b1, int b2, int b3, int b4, int b5, int b6, int b7, int b8) { + return ((long) b1 << 56) + ((long) b2 << 48) + ((long) b3 << 40) + ((long) b4 << 32) + + ((long) b5 << 24) + ((long) b6 << 16) + ((long) b7 << 8) + b8; + } + + /** + * Reads four bytes and returns a {@code float} value. It does this by reading an {@code int} + * value and converting the {@code int} value to a {@code float} using + * {@link Float#intBitsToFloat(int)}. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read. + */ + public final float readFloat() throws IndexOutOfBoundsException { + return Float.intBitsToFloat(readInt()); + } + + /** + * Reads eight bytes and returns a {@code double} value. It does this by reading a {@code long} + * value and converting the {@code long} value to a {@code double} using + * {@link Double#longBitsToDouble(long)}. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read. + */ + public final double readDouble() throws IndexOutOfBoundsException { + return Double.longBitsToDouble(readLong()); + } + + /** + * Reads a single byte. The byte value is returned as an {@code int} in the range {@code 0} to + * {@code 255}. If no byte is available because the end of the stream has been reached, the + * value {@code -1} is returned. + */ + public abstract int read(); + + /** + * Reads {@code len} bytes into a byte array starting at offset {@code off}. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read + */ + public abstract void read(byte[] b, int off, int len) throws IndexOutOfBoundsException; + + /** + * Reads a string using a modified UTF-8 encoding in a machine-independent manner. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read. + * @throws IllegalArgumentException if the bytes do not represent a valid modified UTF-8 + * encoding of a string. + */ + public final String readUTF() throws IndexOutOfBoundsException, IllegalArgumentException { + int len; + int b1 = read(); + int b2 = read(); + if ((b1 | b2) < 0) { + throw new IndexOutOfBoundsException(); + } + if ((b1 & LARGE_STRING_TAG) == LARGE_STRING_TAG) { + int b3 = read(); + int b4 = read(); + if ((b3 | b4) < 0) { + throw new IndexOutOfBoundsException(); + } + len = ((b1 & ~LARGE_STRING_TAG) << 24) + (b2 << 16) + (b3 << 8) + b4; + } else { + len = (b1 << 8) + b2; + } + ensureBufferSize(len); + if (tempEncodingCharBuffer == null || tempEncodingCharBuffer.length < len) { + tempEncodingCharBuffer = new char[Math.max(bufferSize(0, len), 80)]; + } + + int c1; + int c2; + int c3; + int byteCount = 0; + int charCount = 0; + + read(tempEncodingByteBuffer, 0, len); + + while (byteCount < len) { + c1 = tempEncodingByteBuffer[byteCount] & 0xff; + if (c1 > 127) { + break; + } + byteCount++; + tempEncodingCharBuffer[charCount++] = (char) c1; + } + + while (byteCount < len) { + c1 = tempEncodingByteBuffer[byteCount] & 0xff; + switch (c1 >> 4) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + /* 0xxxxxxx */ + byteCount++; + tempEncodingCharBuffer[charCount++] = (char) c1; + break; + case 12: + case 13: + /* 110x xxxx 10xx xxxx */ + byteCount += 2; + if (byteCount > len) { + throw new IllegalArgumentException("Partial character at end"); + } + c2 = tempEncodingByteBuffer[byteCount - 1]; + if ((c2 & 0xC0) != 0x80) { + throw new IllegalArgumentException("Malformed input around byte " + byteCount); + } + tempEncodingCharBuffer[charCount++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F)); + break; + case 14: + /* 1110 xxxx 10xx xxxx 10xx xxxx */ + byteCount += 3; + if (byteCount > len) { + throw new IllegalArgumentException("Malformed input: partial character at end"); + } + c2 = tempEncodingByteBuffer[byteCount - 2]; + c3 = tempEncodingByteBuffer[byteCount - 1]; + if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) { + throw new IllegalArgumentException("Malformed input around byte " + (byteCount - 1)); + } + tempEncodingCharBuffer[charCount++] = (char) (((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F)); + break; + default: + /* 10xx xxxx, 1111 xxxx */ + throw new IllegalArgumentException("Malformed input around byte " + byteCount); + } + } + // The number of chars produced may be less than len + return new String(tempEncodingCharBuffer, 0, charCount); + } + + /** + * Reads a single value, using the data type encoded in the marshalled data. + * + * @return The read value, such as a boxed Java primitive, a {@link String}, a {@code null}, or + * an array of these types. + * @throws IndexOutOfBoundsException if there are not enough bytes to read. + * @throws IllegalArgumentException when the marshaled type is not supported or if the bytes do + * not represent a valid modified UTF-8 encoding of a string. + */ + public final Object readTypedValue() throws IndexOutOfBoundsException, IllegalArgumentException { + byte tag = readByte(); + switch (tag) { + case ARRAY: + int len = readInt(); + Object[] arr = new Object[len]; + for (int i = 0; i < len; i++) { + arr[i] = readTypedValue(); + } + return arr; + case NULL: + return null; + case BOOLEAN: + return readBoolean(); + case BYTE: + return readByte(); + case SHORT: + return readShort(); + case CHAR: + return readChar(); + case INT: + return readInt(); + case LONG: + return readLong(); + case FLOAT: + return readFloat(); + case DOUBLE: + return readDouble(); + case STRING: + return readUTF(); + default: + throw new IllegalArgumentException(String.format("Unknown tag %d", tag)); + } + } + + /** + * Reads {@code len} bytes into a boolean array starting at offset {@code off}. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read + */ + public final void read(boolean[] b, int off, int len) { + ensureBufferSize(len); + read(tempEncodingByteBuffer, 0, len); + int limit = off + len; + for (int i = off, j = 0; i < limit; i++) { + b[i] = tempEncodingByteBuffer[j++] != 0; + } + } + + /** + * Reads {@code len} shorts into a short array starting at offset {@code off}. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read + */ + public final void read(short[] b, int off, int len) { + int size = len * Short.BYTES; + ensureBufferSize(size); + read(tempEncodingByteBuffer, 0, size); + int limit = off + len; + for (int i = off, j = 0; i < limit; i++) { + int b1 = (tempEncodingByteBuffer[j++] & 0xff); + int b2 = (tempEncodingByteBuffer[j++] & 0xff); + b[i] = packShort(b1, b2); + } + } + + /** + * Reads {@code len} chars into a char array starting at offset {@code off}. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read + */ + public final void read(char[] b, int off, int len) { + int size = len * Character.BYTES; + ensureBufferSize(size); + read(tempEncodingByteBuffer, 0, size); + int limit = off + len; + for (int i = off, j = 0; i < limit; i++) { + int b1 = (tempEncodingByteBuffer[j++] & 0xff); + int b2 = (tempEncodingByteBuffer[j++] & 0xff); + b[i] = packChar(b1, b2); + } + } + + /** + * Reads {@code len} ints into an int array starting at offset {@code off}. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read + */ + public final void read(int[] b, int off, int len) { + int size = len * Integer.BYTES; + ensureBufferSize(size); + read(tempEncodingByteBuffer, 0, size); + int limit = off + len; + for (int i = off, j = 0; i < limit; i++) { + int b1 = (tempEncodingByteBuffer[j++] & 0xff); + int b2 = (tempEncodingByteBuffer[j++] & 0xff); + int b3 = (tempEncodingByteBuffer[j++] & 0xff); + int b4 = (tempEncodingByteBuffer[j++] & 0xff); + b[i] = packInt(b1, b2, b3, b4); + } + } + + /** + * Reads {@code len} longs into a long array starting at offset {@code off}. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read + */ + public final void read(long[] b, int off, int len) { + int size = len * Long.BYTES; + ensureBufferSize(size); + read(tempEncodingByteBuffer, 0, size); + int limit = off + len; + for (int i = off, j = 0; i < limit; i++) { + int b1 = (tempEncodingByteBuffer[j++] & 0xff); + int b2 = (tempEncodingByteBuffer[j++] & 0xff); + int b3 = (tempEncodingByteBuffer[j++] & 0xff); + int b4 = (tempEncodingByteBuffer[j++] & 0xff); + int b5 = (tempEncodingByteBuffer[j++] & 0xff); + int b6 = (tempEncodingByteBuffer[j++] & 0xff); + int b7 = (tempEncodingByteBuffer[j++] & 0xff); + int b8 = (tempEncodingByteBuffer[j++] & 0xff); + b[i] = packLong(b1, b2, b3, b4, b5, b6, b7, b8); + } + } + + /** + * Reads {@code len} floats into a float array starting at offset {@code off}. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read + */ + public final void read(float[] b, int off, int len) { + int size = len * Float.BYTES; + ensureBufferSize(size); + read(tempEncodingByteBuffer, 0, size); + int limit = off + len; + for (int i = off, j = 0; i < limit; i++) { + int b1 = (tempEncodingByteBuffer[j++] & 0xff); + int b2 = (tempEncodingByteBuffer[j++] & 0xff); + int b3 = (tempEncodingByteBuffer[j++] & 0xff); + int b4 = (tempEncodingByteBuffer[j++] & 0xff); + b[i] = Float.intBitsToFloat(packInt(b1, b2, b3, b4)); + } + } + + /** + * Reads {@code len} doubles into a double array starting at offset {@code off}. + * + * @throws IndexOutOfBoundsException if there are not enough bytes to read + */ + public final void read(double[] b, int off, int len) { + int size = len * Double.BYTES; + ensureBufferSize(size); + read(tempEncodingByteBuffer, 0, size); + int limit = off + len; + for (int i = off, j = 0; i < limit; i++) { + int b1 = (tempEncodingByteBuffer[j++] & 0xff); + int b2 = (tempEncodingByteBuffer[j++] & 0xff); + int b3 = (tempEncodingByteBuffer[j++] & 0xff); + int b4 = (tempEncodingByteBuffer[j++] & 0xff); + int b5 = (tempEncodingByteBuffer[j++] & 0xff); + int b6 = (tempEncodingByteBuffer[j++] & 0xff); + int b7 = (tempEncodingByteBuffer[j++] & 0xff); + int b8 = (tempEncodingByteBuffer[j++] & 0xff); + b[i] = Double.longBitsToDouble(packLong(b1, b2, b3, b4, b5, b6, b7, b8)); + } + } + + /** + * Returns a read only {@link ByteBuffer} backed by the {@link BinaryInput} internal buffer. The + * content of the buffer will start at the {@link BinaryInput}'s current position. The buffer's + * capacity and limit will be {@code len}, its position will be zero, its mark will be + * undefined, and its byte order will be {@link ByteOrder#BIG_ENDIAN BIG_ENDIAN}. After a + * successful call, the {@link BinaryInput}'s current position is incremented by the + * {@code len}. + * + * @throws IndexOutOfBoundsException if the BinaryInput has not enough remaining bytes. + */ + public abstract ByteBuffer asByteBuffer(int len); + + /** + * Creates a new buffer backed by a byte array. + */ + public static BinaryInput create(byte[] buffer) { + return new ByteArrayBinaryInput(buffer); + } + + /** + * Creates a new buffer backed by a byte array only up to a given length. + */ + public static BinaryInput create(byte[] buffer, int length) { + return new ByteArrayBinaryInput(buffer, length); + } + + /** + * Creates a new buffer wrapping an off-heap memory segment starting at an {@code address} + * having {@code length} bytes. + */ + public static BinaryInput create(CCharPointer address, int length) { + return new CCharPointerInput(address, length); + } + + private void ensureBufferSize(int len) { + if (tempEncodingByteBuffer == null || tempEncodingByteBuffer.length < len) { + tempEncodingByteBuffer = new byte[Math.max(bufferSize(0, len), 80)]; + } + } + + private static final class ByteArrayBinaryInput extends BinaryInput { + + private final byte[] buffer; + + ByteArrayBinaryInput(byte[] buffer) { + super(buffer.length); + this.buffer = buffer; + } + + ByteArrayBinaryInput(byte[] buffer, int length) { + super(length); + this.buffer = buffer; + } + + @Override + public int read() { + if (pos >= length) { + return EOF; + } + return (buffer[pos++] & 0xff); + } + + @Override + public void read(byte[] b, int off, int len) { + if (len < 0) { + throw new IllegalArgumentException(String.format("Len must be non negative but was %d", len)); + } + if (pos + len > length) { + throw new IndexOutOfBoundsException(); + } + System.arraycopy(buffer, pos, b, off, len); + pos += len; + } + + @Override + public ByteBuffer asByteBuffer(int len) { + ByteBuffer result = ByteBuffer.wrap(buffer, pos, len).slice().asReadOnlyBuffer(); + pos += len; + return result; + } + } + + private static final class CCharPointerInput extends BinaryInput { + + /** + * Represents the point at which the average cost of a JNI call exceeds the expense of an + * element by element copy. See {@code java.nio.Bits#JNI_COPY_TO_ARRAY_THRESHOLD}. + */ + private static final int BYTEBUFFER_COPY_TO_ARRAY_THRESHOLD = 6; + + private final CCharPointer address; + /** + * ByteBuffer view of this {@link CCharPointerInput} direct memory. The ByteBuffer is used + * for bulk data transfers, where the bulk ByteBuffer operations outperform element by + * element copying by an order of magnitude. + */ + private ByteBuffer byteBufferView; + + CCharPointerInput(CCharPointer address, int length) { + super(length); + this.address = address; + } + + @Override + public int read() { + if (pos >= length) { + return EOF; + } + return (address.read(pos++) & 0xff); + } + + @Override + public void read(byte[] b, int off, int len) { + if (len < 0) { + throw new IllegalArgumentException(String.format("Len must be non negative but was %d", len)); + } + if (pos + len > length) { + throw new IndexOutOfBoundsException(); + } + if (len > BYTEBUFFER_COPY_TO_ARRAY_THRESHOLD) { + if (byteBufferView == null) { + byteBufferView = CTypeConversion.asByteBuffer(address, length); + } + byteBufferView.position(pos); + byteBufferView.get(b, off, len); + } else { + for (int i = 0, j = pos; i < len; i++, j++) { + b[off + i] = address.read(j); + } + } + pos += len; + } + + @Override + public ByteBuffer asByteBuffer(int len) { + ByteBuffer result = CTypeConversion.asByteBuffer(address.addressOf(pos), len).order(ByteOrder.BIG_ENDIAN).asReadOnlyBuffer(); + pos += len; + return result; + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/BinaryMarshaller.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/BinaryMarshaller.java new file mode 100644 index 000000000000..69068014ee28 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/BinaryMarshaller.java @@ -0,0 +1,130 @@ +/* + * 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.jdwp.bridge.nativebridge; + +import java.util.Objects; + +/** + * A marshaller used by the native bridge processor to read or write method parameters and results + * of a custom type. Marshallers are used to support types that are not directly implemented by the + * native bridge processor. + * + * @see JNIConfig.Builder + */ +public interface BinaryMarshaller { + + /** + * Decomposes and serializes the given object passed to a foreign method. + */ + void write(BinaryOutput output, T object); + + /** + * Deserializes and recreates an object passed to a foreign method. + */ + T read(BinaryInput input); + + /** + * Estimates a size in bytes needed to marshall given object. The returned value is used to + * pre-allocate the {@link BinaryOutput}'s buffer. The accuracy of the estimate affects the + * speed of marshalling. If the estimate is too small, the pre-allocated buffer must be + * re-allocated and the already marshalled data must be copied. Too large a value may cause the + * static buffer to be unused and the dynamic buffer to be unnecessarily allocated. + */ + default int inferSize(@SuppressWarnings("unused") T object) { + return Long.BYTES; + } + + /** + * Decomposes and serializes the mutable state of a given object to support {@link Out} + * semantics. Marshallers that do not support {@link Out} parameters do not need to implement + * this method. The default implementation throws {@link UnsupportedOperationException}. To + * support {@link Out} parameters the {@link BinaryMarshaller} must implement also + * {@link #readUpdate(BinaryInput, Object)} and {@link #inferUpdateSize(Object)}. + *

      + * The {@link Out} parameters are passed in the following way: + *

        + *
      1. The start point method writes the parameter using + * {@link #write(BinaryOutput, Object)}.
      2. + *
      3. A foreign method call is made.
      4. + *
      5. The end point method reads the parameter using {@link #read(BinaryInput)}.
      6. + *
      7. The end point receiver method is called with the unmarshalled parameter.
      8. + *
      9. After calling the receiver method, the end point method writes the mutated {@link Out} + * parameter state using {@link #writeUpdate(BinaryOutput, Object)}.
      10. + *
      11. A foreign method returns.
      12. + *
      13. The state of the {@link Out} parameter is updated using + * {@link #readUpdate(BinaryInput, Object)}.
      14. + *
      + *

      + * + * @see BinaryMarshaller#readUpdate(BinaryInput, Object) + * @see BinaryMarshaller#inferUpdateSize(Object) + */ + @SuppressWarnings("unused") + default void writeUpdate(BinaryOutput output, T object) { + throw new UnsupportedOperationException(); + } + + /** + * Deserializes and updates the mutable state of a given object to support {@link Out} + * semantics. Marshallers that do not support {@link Out} parameters do not need to implement + * this method. The default implementation throws {@link UnsupportedOperationException}. To + * support {@link Out} parameters the {@link BinaryMarshaller} must implement also + * {@link #writeUpdate(BinaryOutput, Object)} and {@link #inferUpdateSize(Object)}. + * + * @see BinaryMarshaller#writeUpdate(BinaryOutput, Object) + * @see BinaryMarshaller#inferUpdateSize(Object) + */ + @SuppressWarnings("unused") + default void readUpdate(BinaryInput input, T object) { + throw new UnsupportedOperationException(); + } + + /** + * Estimates a size in bytes needed to marshall {@link Out} parameter passed back to caller from + * a foreign method call. The accuracy of the estimate affects the speed of marshalling. If the + * estimate is too small, the pre-allocated buffer must be re-allocated and the already + * marshalled data must be copied. Too large a value may cause the static buffer to be unused + * and the dynamic buffer to be unnecessarily allocated. Marshallers that do not support + * {@link Out} parameters do not need to implement this method. The default implementation + * throws {@link UnsupportedOperationException}. To support {@link Out} parameters the + * {@link BinaryMarshaller} must implement also {@link #writeUpdate(BinaryOutput, Object)} and + * {@link #readUpdate(BinaryInput, Object)}. + * + * @see BinaryMarshaller#writeUpdate(BinaryOutput, Object) + * @see BinaryMarshaller#readUpdate(BinaryInput, Object) + */ + default int inferUpdateSize(@SuppressWarnings("unused") T object) { + throw new UnsupportedOperationException(); + } + + /** + * Decorates {@code forMarshaller} by a {@link BinaryMarshaller} handling {@code null} values. + * The returned {@link BinaryMarshaller} calls the {@code forMarshaller} only non-null values. + */ + static BinaryMarshaller nullable(BinaryMarshaller forMarshaller) { + Objects.requireNonNull(forMarshaller, "ForMarshaller must be non null."); + return new NullableBinaryMarshaller<>(forMarshaller); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/BinaryOutput.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/BinaryOutput.java new file mode 100644 index 000000000000..3544f3fe5947 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/BinaryOutput.java @@ -0,0 +1,670 @@ +/* + * 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.jdwp.bridge.nativebridge; + +import java.io.Closeable; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Objects; + +import org.graalvm.nativeimage.UnmanagedMemory; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.c.type.CTypeConversion; +import org.graalvm.word.WordFactory; + +/** + * A buffer used by the {@link BinaryMarshaller} to marshall parameters and results passed by value. + * + * @see BinaryInput + * @see BinaryMarshaller + */ +public abstract class BinaryOutput { + + /** + * Maximum string length for string encoded by 4 bytes length followed be content. + */ + private static final int MAX_LENGTH = Integer.MAX_VALUE - Integer.BYTES; + /** + * Maximum string length for string encoded by 2 bytes length followed be content. + */ + private static final int MAX_SHORT_LENGTH = Short.MAX_VALUE; + /** + * Tag to distinguish between long and short string. The tag is set in the first string length + * byte. + */ + static final int LARGE_STRING_TAG = 1 << 7; + + // Type tags used by writeTypedValue + static final byte NULL = 0; + static final byte BOOLEAN = NULL + 1; + static final byte BYTE = BOOLEAN + 1; + static final byte SHORT = BYTE + 1; + static final byte CHAR = SHORT + 1; + static final byte INT = CHAR + 1; + static final byte LONG = INT + 1; + static final byte FLOAT = LONG + 1; + static final byte DOUBLE = FLOAT + 1; + static final byte STRING = DOUBLE + 1; + static final byte ARRAY = STRING + 1; + + private byte[] tempDecodingBuffer; + protected int pos; + + private BinaryOutput() { + } + + /** + * Writes a {@code boolean} as a single byte value. The value {@code true} is written as the + * value {@code (byte)1}, the value {@code false} is written as the value {@code (byte)0}. The + * buffer position is incremented by {@code 1}. + */ + public final void writeBoolean(boolean value) { + write(value ? 1 : 0); + } + + /** + * Writes a {@code byte} as a single byte value. The buffer position is incremented by + * {@code 1}. + */ + public final void writeByte(int value) { + write(value); + } + + /** + * Writes a {@code short} as two bytes, high byte first. The buffer position is incremented by + * {@code 2}. + */ + public final void writeShort(int value) { + write((value >>> 8) & 0xff); + write(value & 0xff); + } + + /** + * Writes a {@code char} as two bytes, high byte first. The buffer position is incremented by + * {@code 2}. + */ + public final void writeChar(int value) { + write((value >>> 8) & 0xff); + write(value & 0xff); + } + + /** + * Writes an {@code int} as four bytes, high byte first. The buffer position is incremented by + * {@code 4}. + */ + public final void writeInt(int value) { + write((value >>> 24) & 0xff); + write((value >>> 16) & 0xff); + write((value >>> 8) & 0xff); + write(value & 0xff); + } + + /** + * Writes a {@code long} as eight bytes, high byte first. The buffer position is incremented by + * {@code 8}. + */ + public final void writeLong(long value) { + write((int) ((value >>> 56) & 0xff)); + write((int) ((value >>> 48) & 0xff)); + write((int) ((value >>> 40) & 0xff)); + write((int) ((value >>> 32) & 0xff)); + write((int) ((value >>> 24) & 0xff)); + write((int) ((value >>> 16) & 0xff)); + write((int) ((value >>> 8) & 0xff)); + write((int) (value & 0xff)); + } + + /** + * Converts a {@code float} value to an {@code int} using the + * {@link Float#floatToIntBits(float)}, and then writes that {@code int} as four bytes, high + * byte first. The buffer position is incremented by {@code 4}. + */ + public final void writeFloat(float value) { + writeInt(Float.floatToIntBits(value)); + } + + /** + * Converts a {@code double} value to a {@code long} using the + * {@link Double#doubleToLongBits(double)}, and then writes that {@code long} as eight bytes, + * high byte first. The buffer position is incremented by {@code 8}. + */ + public final void writeDouble(double value) { + writeLong(Double.doubleToLongBits(value)); + } + + /** + * Writes the lowest byte of the argument as a single byte value. The buffer position is + * incremented by {@code 1}. + */ + public abstract void write(int b); + + /** + * Writes {@code len} bytes from the byte {@code array} starting at offset {@code off}. The + * buffer position is incremented by {@code len}. + */ + public abstract void write(byte[] array, int off, int len); + + /** + * Reserves a buffer space. The reserved space can be used for out parameters. + * + * @param numberOfBytes number of bytes to reserve. + */ + public abstract void skip(int numberOfBytes); + + /** + * Writes a string using a modified UTF-8 encoding in a machine-independent manner. + * + * @throws IllegalArgumentException if the {@code string} cannot be encoded using modified UTF-8 + * encoding. + */ + public final void writeUTF(String string) throws IllegalArgumentException { + int len = string.length(); + long utfLen = 0; + int c; + int count = 0; + + for (int i = 0; i < len; i++) { + c = string.charAt(i); + if ((c >= 0x0001) && (c <= 0x007F)) { + utfLen++; + } else if (c > 0x07FF) { + utfLen += 3; + } else { + utfLen += 2; + } + } + + if (utfLen > MAX_LENGTH) { + throw new IllegalArgumentException("String too long to encode, " + utfLen + " bytes"); + } + int headerSize; + if (utfLen > MAX_SHORT_LENGTH) { + headerSize = Integer.BYTES; + ensureBufferSize(headerSize, (int) utfLen); + tempDecodingBuffer[count++] = (byte) ((LARGE_STRING_TAG | (utfLen >>> 24)) & 0xff); + tempDecodingBuffer[count++] = (byte) ((utfLen >>> 16) & 0xFF); + } else { + headerSize = Short.BYTES; + ensureBufferSize(headerSize, (int) utfLen); + } + tempDecodingBuffer[count++] = (byte) ((utfLen >>> 8) & 0xFF); + tempDecodingBuffer[count++] = (byte) (utfLen & 0xFF); + + int i = 0; + for (; i < len; i++) { + c = string.charAt(i); + if (!((c >= 0x0001) && (c <= 0x007F))) { + break; + } + tempDecodingBuffer[count++] = (byte) c; + } + + for (; i < len; i++) { + c = string.charAt(i); + if ((c >= 0x0001) && (c <= 0x007F)) { + tempDecodingBuffer[count++] = (byte) c; + } else if (c > 0x07FF) { + tempDecodingBuffer[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F)); + tempDecodingBuffer[count++] = (byte) (0x80 | ((c >> 6) & 0x3F)); + tempDecodingBuffer[count++] = (byte) (0x80 | (c & 0x3F)); + } else { + tempDecodingBuffer[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F)); + tempDecodingBuffer[count++] = (byte) (0x80 | (c & 0x3F)); + } + } + write(tempDecodingBuffer, 0, (int) (headerSize + utfLen)); + } + + /** + * Returns this buffer's position. + */ + public int getPosition() { + return pos; + } + + /** + * Returns true if a value is a typed value writable using + * {@link #writeTypedValue(Object)}, else false. + */ + public static boolean isTypedValue(Object value) { + if (value == null) { + return true; + } + return value instanceof Object[] || value instanceof Boolean || value instanceof Byte || + value instanceof Short || value instanceof Character || value instanceof Integer || + value instanceof Long || value instanceof Float || value instanceof Double || value instanceof String; + } + + /** + * Writes the value that is represented by the given object, together with information on the + * value's data type. Supported types are boxed Java primitive types, {@link String}, + * {@code null}, and arrays of these types. + * + * @throws IllegalArgumentException when the {@code value} type is not supported or the + * {@code value} is a string which cannot be encoded using modified UTF-8 encoding. + * @see #isTypedValue(Object) to find out whether a value can be serialized. + */ + public final void writeTypedValue(Object value) throws IllegalArgumentException { + if (value instanceof Object[] arr) { + writeByte(ARRAY); + writeInt(arr.length); + for (Object arrElement : arr) { + writeTypedValue(arrElement); + } + } else if (value == null) { + writeByte(NULL); + } else if (value instanceof Boolean) { + writeByte(BOOLEAN); + writeBoolean((boolean) value); + } else if (value instanceof Byte) { + writeByte(BYTE); + writeByte((byte) value); + } else if (value instanceof Short) { + writeByte(SHORT); + writeShort((short) value); + } else if (value instanceof Character) { + writeByte(CHAR); + writeChar((char) value); + } else if (value instanceof Integer) { + writeByte(INT); + writeInt((int) value); + } else if (value instanceof Long) { + writeByte(LONG); + writeLong((long) value); + } else if (value instanceof Float) { + writeByte(FLOAT); + writeFloat((float) value); + } else if (value instanceof Double) { + writeByte(DOUBLE); + writeDouble((double) value); + } else if (value instanceof String) { + writeByte(STRING); + writeUTF((String) value); + } else { + throw new IllegalArgumentException(String.format("Unsupported type %s", value.getClass())); + } + } + + /** + * Writes {@code len} bytes from the boolean {@code array} starting at offset {@code off}. The + * value {@code true} is written as the value {@code (byte)1}, the value {@code false} is + * written as the value {@code (byte)0}. The buffer position is incremented by {@code len}. + */ + public final void write(boolean[] array, int off, int len) { + ensureBufferSize(0, len); + for (int i = 0, j = 0; i < len; i++, j++) { + tempDecodingBuffer[j] = (byte) (array[off + i] ? 1 : 0); + } + write(tempDecodingBuffer, 0, len); + } + + /** + * Writes {@code len} shorts from the {@code array} starting at offset {@code off}. The buffer + * position is incremented by {@code 2 * len}. + */ + public final void write(short[] array, int off, int len) { + int size = len * Short.BYTES; + ensureBufferSize(0, size); + for (int i = 0, j = 0; i < len; i++) { + tempDecodingBuffer[j++] = (byte) ((array[off + i] >>> 8) & 0xff); + tempDecodingBuffer[j++] = (byte) (array[off + i] & 0xff); + } + write(tempDecodingBuffer, 0, size); + } + + /** + * Writes {@code len} chars from the {@code array} starting at offset {@code off}. The buffer + * position is incremented by {@code 2 * len}. + */ + public final void write(char[] array, int off, int len) { + int size = len * Character.BYTES; + ensureBufferSize(0, size); + for (int i = 0, j = 0; i < len; i++) { + tempDecodingBuffer[j++] = (byte) ((array[off + i] >>> 8) & 0xff); + tempDecodingBuffer[j++] = (byte) (array[off + i] & 0xff); + } + write(tempDecodingBuffer, 0, size); + } + + /** + * Writes {@code len} ints from the {@code array} starting at offset {@code off}. The buffer + * position is incremented by {@code 4 * len}. + */ + public final void write(int[] array, int off, int len) { + int size = len * Integer.BYTES; + ensureBufferSize(0, size); + for (int i = 0, j = 0; i < len; i++) { + tempDecodingBuffer[j++] = (byte) ((array[off + i] >>> 24) & 0xff); + tempDecodingBuffer[j++] = (byte) ((array[off + i] >>> 16) & 0xff); + tempDecodingBuffer[j++] = (byte) ((array[off + i] >>> 8) & 0xff); + tempDecodingBuffer[j++] = (byte) (array[off + i] & 0xff); + } + write(tempDecodingBuffer, 0, size); + } + + /** + * Writes {@code len} longs from the {@code array} starting at offset {@code off}. The buffer + * position is incremented by {@code 8 * len}. + */ + public final void write(long[] array, int off, int len) { + int size = len * Long.BYTES; + ensureBufferSize(0, size); + for (int i = 0, j = 0; i < len; i++) { + tempDecodingBuffer[j++] = (byte) ((array[off + i] >>> 56) & 0xff); + tempDecodingBuffer[j++] = (byte) ((array[off + i] >>> 48) & 0xff); + tempDecodingBuffer[j++] = (byte) ((array[off + i] >>> 40) & 0xff); + tempDecodingBuffer[j++] = (byte) ((array[off + i] >>> 32) & 0xff); + tempDecodingBuffer[j++] = (byte) ((array[off + i] >>> 24) & 0xff); + tempDecodingBuffer[j++] = (byte) ((array[off + i] >>> 16) & 0xff); + tempDecodingBuffer[j++] = (byte) ((array[off + i] >>> 8) & 0xff); + tempDecodingBuffer[j++] = (byte) (array[off + i] & 0xff); + } + write(tempDecodingBuffer, 0, size); + } + + /** + * Writes {@code len} floats from the {@code array} starting at offset {@code off}. Each + * {@code float} value is converted to an {@code int} using the + * {@link Float#floatToIntBits(float)} and written as an int. The buffer position is incremented + * by {@code 4 * len}. + */ + public final void write(float[] array, int off, int len) { + int size = len * Float.BYTES; + ensureBufferSize(0, size); + for (int i = 0, j = 0; i < len; i++) { + int bits = Float.floatToIntBits(array[off + i]); + tempDecodingBuffer[j++] = (byte) ((bits >>> 24) & 0xff); + tempDecodingBuffer[j++] = (byte) ((bits >>> 16) & 0xff); + tempDecodingBuffer[j++] = (byte) ((bits >>> 8) & 0xff); + tempDecodingBuffer[j++] = (byte) (bits & 0xff); + } + write(tempDecodingBuffer, 0, size); + } + + /** + * Writes {@code len} doubles from the {@code array} starting at offset {@code off}. Each + * {@code double} value is converted to an {@code lang} using the + * {@link Double#doubleToLongBits(double)} and written as a long. The buffer position is + * incremented by {@code 8 * len}. + */ + public final void write(double[] array, int off, int len) { + int size = len * Double.BYTES; + ensureBufferSize(Integer.BYTES, size); + for (int i = 0, j = 0; i < len; i++) { + long bits = Double.doubleToLongBits(array[off + i]); + tempDecodingBuffer[j++] = (byte) ((bits >>> 56) & 0xff); + tempDecodingBuffer[j++] = (byte) ((bits >>> 48) & 0xff); + tempDecodingBuffer[j++] = (byte) ((bits >>> 40) & 0xff); + tempDecodingBuffer[j++] = (byte) ((bits >>> 32) & 0xff); + tempDecodingBuffer[j++] = (byte) ((bits >>> 24) & 0xff); + tempDecodingBuffer[j++] = (byte) ((bits >>> 16) & 0xff); + tempDecodingBuffer[j++] = (byte) ((bits >>> 8) & 0xff); + tempDecodingBuffer[j++] = (byte) (bits & 0xff); + } + write(tempDecodingBuffer, 0, size); + } + + private void ensureBufferSize(int headerSize, int dataSize) { + if (tempDecodingBuffer == null || tempDecodingBuffer.length < (headerSize + dataSize)) { + tempDecodingBuffer = new byte[bufferSize(headerSize, dataSize)]; + } + } + + /** + * Creates a new buffer backed by a byte array. + */ + public static ByteArrayBinaryOutput create() { + return new ByteArrayBinaryOutput(ByteArrayBinaryOutput.INITIAL_SIZE); + } + + /** + * Creates a new buffer wrapping the {@code initialBuffer}. If the {@code initialBuffer} + * capacity is not sufficient for writing the data, a new array is allocated. Always use + * {@link ByteArrayBinaryOutput#getArray()} to obtain the marshaled data. + */ + public static ByteArrayBinaryOutput create(byte[] initialBuffer) { + Objects.requireNonNull(initialBuffer, "InitialBuffer must be non null."); + return new ByteArrayBinaryOutput(initialBuffer); + } + + /** + * Creates a new buffer wrapping an off-heap memory segment starting at {@code address} having + * {@code length} bytes. If the capacity of an off-heap memory segment is not sufficient for + * writing the data, a new off-heap memory is allocated. Always use + * {@link CCharPointerBinaryOutput#getAddress()} to obtain the marshaled data. + * + * @param address the off-heap memory address + * @param length the off-heap memory size + * @param dynamicallyAllocated {@code true} if the memory was dynamically allocated and should + * be freed when the buffer is closed; {@code false} for the stack allocated memory. + */ + public static CCharPointerBinaryOutput create(CCharPointer address, int length, boolean dynamicallyAllocated) { + return new CCharPointerBinaryOutput(address, length, dynamicallyAllocated); + } + + static int bufferSize(int headerSize, int dataSize) { + return headerSize + (dataSize <= MAX_SHORT_LENGTH ? dataSize << 1 : dataSize); + } + + /** + * A {@link BinaryOutput} backed by a byte array. + */ + public static final class ByteArrayBinaryOutput extends BinaryOutput { + + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + static final int INITIAL_SIZE = 32; + + private byte[] buffer; + + private ByteArrayBinaryOutput(int size) { + buffer = new byte[size]; + } + + private ByteArrayBinaryOutput(byte[] initialBuffer) { + buffer = initialBuffer; + } + + @Override + public void write(int b) { + ensureCapacity(pos + 1); + buffer[pos] = (byte) b; + pos += 1; + } + + @Override + public void write(byte[] b, int off, int len) { + ensureCapacity(pos + len); + System.arraycopy(b, off, buffer, pos, len); + pos += len; + } + + @Override + public void skip(int numberOfBytes) { + ensureCapacity(pos + numberOfBytes); + pos += numberOfBytes; + } + + /** + * Returns the byte array containing the marshalled data. + */ + public byte[] getArray() { + return buffer; + } + + private void ensureCapacity(int neededCapacity) { + if (neededCapacity - buffer.length > 0) { + int newCapacity = buffer.length << 1; + if (newCapacity - neededCapacity < 0) { + newCapacity = neededCapacity; + } + if (newCapacity - MAX_ARRAY_SIZE > 0) { + throw new OutOfMemoryError(); + } + buffer = Arrays.copyOf(buffer, newCapacity); + } + } + + /** + * Creates a new buffer backed by a byte array. The buffer initial size is + * {@code initialSize}. + */ + public static ByteArrayBinaryOutput create(int initialSize) { + return new ByteArrayBinaryOutput(initialSize); + } + } + + /** + * A {@link BinaryOutput} backed by an off-heap memory. + */ + public static final class CCharPointerBinaryOutput extends BinaryOutput implements Closeable { + + /** + * Represents the point at which the average cost of a JNI call exceeds the expense of an + * element by element copy. See {@code java.nio.Bits#JNI_COPY_FROM_ARRAY_THRESHOLD}. + */ + private static final int BYTEBUFFER_COPY_FROM_ARRAY_THRESHOLD = 6; + + private CCharPointer address; + private int length; + private boolean unmanaged; + /** + * ByteBuffer view of this {@link CCharPointerBinaryOutput} direct memory. The ByteBuffer is + * used for bulk data transfers, where the bulk ByteBuffer operations outperform element by + * element copying by an order of magnitude. + */ + private ByteBuffer byteBufferView; + + private CCharPointerBinaryOutput(CCharPointer address, int length, boolean unmanaged) { + this.address = address; + this.length = length; + this.unmanaged = unmanaged; + } + + @Override + public int getPosition() { + checkClosed(); + return super.getPosition(); + } + + /** + * Returns an address of an off-heap memory segment containing the marshalled data. + */ + public CCharPointer getAddress() { + checkClosed(); + return address; + } + + @Override + public void write(int b) { + checkClosed(); + ensureCapacity(pos + 1); + address.write(pos++, (byte) b); + } + + @Override + public void write(byte[] b, int off, int len) { + checkClosed(); + if ((off | len | b.length) < 0 || b.length - off < len) { + throw new IndexOutOfBoundsException("Offset: " + off + ", length: " + len + ", array length: " + b.length); + } + ensureCapacity(pos + len); + if (len > BYTEBUFFER_COPY_FROM_ARRAY_THRESHOLD) { + if (byteBufferView == null) { + byteBufferView = CTypeConversion.asByteBuffer(address, length); + } + byteBufferView.position(pos); + byteBufferView.put(b, off, len); + } else { + for (int i = 0; i < len; i++) { + address.write(pos + i, b[off + i]); + } + } + pos += len; + } + + @Override + public void skip(int numberOfBytes) { + ensureCapacity(pos + numberOfBytes); + pos += numberOfBytes; + } + + /** + * Closes the buffer and frees off-heap allocated resources. + */ + @Override + public void close() { + if (unmanaged) { + UnmanagedMemory.free(address); + byteBufferView = null; + address = WordFactory.nullPointer(); + length = 0; + unmanaged = false; + pos = Integer.MIN_VALUE; + } + } + + private void checkClosed() { + if (pos == Integer.MIN_VALUE) { + throw new IllegalStateException("Already closed"); + } + } + + private void ensureCapacity(int neededCapacity) { + if (neededCapacity - length > 0) { + byteBufferView = null; + int newCapacity = length << 1; + if (newCapacity - neededCapacity < 0) { + newCapacity = neededCapacity; + } + if (newCapacity - Integer.MAX_VALUE > 0) { + throw new OutOfMemoryError(); + } + if (unmanaged) { + address = UnmanagedMemory.realloc(address, WordFactory.unsigned(newCapacity)); + } else { + CCharPointer newAddress = UnmanagedMemory.malloc(newCapacity); + memcpy(newAddress, address, pos); + address = newAddress; + } + length = newCapacity; + unmanaged = true; + } + } + + private static void memcpy(CCharPointer dst, CCharPointer src, int len) { + for (int i = 0; i < len; i++) { + dst.write(i, src.read(i)); + } + } + + /** + * Creates a new buffer backed by an off-heap memory segment. The buffer initial size is + * {@code initialSize}. + */ + public static CCharPointerBinaryOutput create(int initialSize) { + return new CCharPointerBinaryOutput(UnmanagedMemory.malloc(initialSize), initialSize, true); + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/DefaultStackTraceMarshaller.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/DefaultStackTraceMarshaller.java new file mode 100644 index 000000000000..241f73f0bb4a --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/DefaultStackTraceMarshaller.java @@ -0,0 +1,56 @@ +/* + * 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.jdwp.bridge.nativebridge; + +final class DefaultStackTraceMarshaller implements BinaryMarshaller { + + static final DefaultStackTraceMarshaller INSTANCE = new DefaultStackTraceMarshaller(); + + private DefaultStackTraceMarshaller() { + } + + @Override + public StackTraceElement[] read(BinaryInput in) { + int len = in.readInt(); + StackTraceElement[] res = new StackTraceElement[len]; + for (int i = 0; i < len; i++) { + res[i] = StackTraceElementMarshaller.INSTANCE.read(in); + } + return res; + } + + @Override + public void write(BinaryOutput out, StackTraceElement[] stack) { + out.writeInt(stack.length); + for (StackTraceElement stackTraceElement : stack) { + StackTraceElementMarshaller.INSTANCE.write(out, stackTraceElement); + } + } + + @Override + public int inferSize(StackTraceElement[] object) { + return object.length == 0 ? 0 : object.length * StackTraceElementMarshaller.INSTANCE.inferSize(object[0]); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/DefaultThrowableMarshaller.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/DefaultThrowableMarshaller.java new file mode 100644 index 000000000000..992afdf1b475 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/DefaultThrowableMarshaller.java @@ -0,0 +1,52 @@ +/* + * 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.jdwp.bridge.nativebridge; + +final class DefaultThrowableMarshaller implements BinaryMarshaller { + + private static final int THROWABLE_SIZE_ESTIMATE = 1024; + private final DefaultStackTraceMarshaller stackTraceMarshaller = DefaultStackTraceMarshaller.INSTANCE; + + @Override + public Throwable read(BinaryInput in) { + String foreignExceptionClassName = in.readUTF(); + String foreignExceptionMessage = (String) in.readTypedValue(); + StackTraceElement[] foreignExceptionStack = stackTraceMarshaller.read(in); + return new MarshalledException(foreignExceptionClassName, foreignExceptionMessage, ForeignException.mergeStackTrace(foreignExceptionStack)); + } + + @Override + public void write(BinaryOutput out, Throwable object) { + out.writeUTF(object instanceof MarshalledException ? ((MarshalledException) object).getForeignExceptionClassName() : object.getClass().getName()); + out.writeTypedValue(object.getMessage()); + stackTraceMarshaller.write(out, object.getStackTrace()); + } + + @Override + public int inferSize(Throwable object) { + // We don't use Throwable#getStackTrace as it allocates. + return THROWABLE_SIZE_ESTIMATE; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/ForeignException.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/ForeignException.java new file mode 100644 index 000000000000..90b4a5681bf1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/ForeignException.java @@ -0,0 +1,280 @@ +/* + * 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.jdwp.bridge.nativebridge; + +import static com.oracle.svm.jdwp.bridge.jniutils.JNIUtil.GetStaticMethodID; +import static org.graalvm.nativeimage.c.type.CTypeConversion.toCString; + +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JByteArray; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JClass; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JMethodID; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JNIEnv; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JObject; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JThrowable; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JValue; +import com.oracle.svm.jdwp.bridge.jniutils.JNICalls; +import com.oracle.svm.jdwp.bridge.jniutils.JNIExceptionWrapper; +import com.oracle.svm.jdwp.bridge.jniutils.JNIUtil; +import com.oracle.svm.jdwp.bridge.nativebridge.BinaryOutput.ByteArrayBinaryOutput; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.type.CTypeConversion; + +/** + * This exception is used to transfer a local exception over the boundary. The local exception is + * marshaled, passed over the boundary as a {@link ForeignException}, unmarshalled, and re-thrown. + */ +@SuppressWarnings("serial") +public final class ForeignException extends RuntimeException { + + static final byte UNDEFINED = 0; + static final byte HOST_TO_GUEST = 1; + static final byte GUEST_TO_HOST = 2; + private static final ThreadLocal pendingException = new ThreadLocal<>(); + private static final JNIMethodResolver CreateForeignException = new JNIMethodResolver("createForeignException", "([B)Ljava/lang/Throwable;"); + private static final JNIMethodResolver ToByteArray = new JNIMethodResolver("toByteArray", "(Lcom/oracle/svm/jdwp/bridge/nativebridge/ForeignException;)[B"); + /** + * Pre-allocated {@code ForeignException} for double failure. + */ + private static final ForeignException MARSHALLING_FAILED = new ForeignException(null, UNDEFINED, false); + private static volatile JNICalls jniCalls; + + private final byte kind; + private final byte[] rawData; + + private ForeignException(byte[] rawData, byte kind, boolean writableStackTrace) { + super(null, null, true, writableStackTrace); + this.rawData = rawData; + this.kind = kind; + } + + /** + * Re-throws this exception in host using a JNI API. This method is intended to be called by the + * code generated by the bridge processor. It's still possible to use it by the hand-written + * code, but it's recommended to use the bridge processor. + */ + public void throwUsingJNI(JNIEnv env) { + JNIUtil.Throw(env, callCreateForeignException(env, JNIUtil.createHSArray(env, rawData))); + } + + /** + * Unmarshalls the foreign exception transferred by this {@link ForeignException} and re-throws + * it. This method is intended to be called by the code generated by the bridge processor. It's + * still possible to use it by the hand-written code, but it's recommended to use the bridge + * processor. + * + * @param marshaller the marshaller to unmarshal the exception + */ + public RuntimeException throwOriginalException(BinaryMarshaller marshaller) { + try { + if (rawData == null) { + throw new RuntimeException("Failed to marshall foreign throwable."); + } + BinaryInput in = BinaryInput.create(rawData); + Throwable t = marshaller.read(in); + throw ForeignException.silenceException(RuntimeException.class, t); + } finally { + clearPendingException(); + } + } + + /** + * Merges foreign stack trace marshalled in the {@link ForeignException} with local + * {@link ForeignException}'s stack trace. This is a helper method for throwable marshallers to + * merge local and foreign stack traces. Typical usage looks like this: + * + *

      +     * final class DefaultThrowableMarshaller implements BinaryMarshaller<Throwable> {
      +     *     private final BinaryMarshaller<StackTraceElement[]> stackTraceMarshaller = MyJNIConfig.getDefault().lookupMarshaller(StackTraceElement[].class);
      +     *
      +     *     @Override
      +     *     public Throwable read(BinaryInput in) {
      +     *         String foreignExceptionClassName = in.readUTF();
      +     *         String foreignExceptionMessage = in.readUTF();
      +     *         StackTraceElement[] foreignExceptionStack = stackTraceMarshaller.read(in);
      +     *         RuntimeException exception = new RuntimeException(foreignExceptionClassName + ": " + foreignExceptionMessage);
      +     *         exception.setStackTrace(ForeignException.mergeStackTrace(foreignExceptionStack));
      +     *         return exception;
      +     *     }
      +     *
      +     *     @Override
      +     *     public void write(BinaryOutput out, Throwable object) {
      +     *         out.writeUTF(object.getClass().getName());
      +     *         out.writeUTF(object.getMessage());
      +     *         stackTraceMarshaller.write(out, object.getStackTrace());
      +     *     }
      +     * }
      +     * 
      + * + * @param foreignExceptionStack the stack trace marshalled into the {@link ForeignException} + * @return the stack trace combining both local and foreign stack trace elements + */ + public static StackTraceElement[] mergeStackTrace(StackTraceElement[] foreignExceptionStack) { + if (foreignExceptionStack.length == 0) { + // Exception has no stack trace, nothing to merge. + return foreignExceptionStack; + } + ForeignException localException = pendingException.get(); + if (localException != null) { + switch (localException.kind) { + case HOST_TO_GUEST: + return JNIExceptionWrapper.mergeStackTraces(localException.getStackTrace(), foreignExceptionStack, false); + case GUEST_TO_HOST: + return JNIExceptionWrapper.mergeStackTraces(foreignExceptionStack, localException.getStackTrace(), true); + default: + throw new IllegalStateException("Unsupported kind " + localException.kind); + } + } else { + return foreignExceptionStack; + } + } + + /** + * Creates a {@link ForeignException} by marshaling the {@code exception} using + * {@code marshaller}. This method is intended to be called by the code generated by the bridge + * processor. It's still possible to use it by the hand-written code, but it's recommended to + * use the bridge processor. + * + * @param exception the exception that should be passed over the boundary + * @param marshaller the marshaller to marshall the exception + */ + public static ForeignException forThrowable(Throwable exception, BinaryMarshaller marshaller) { + try { + ByteArrayBinaryOutput out = ByteArrayBinaryOutput.create(marshaller.inferSize(exception)); + marshaller.write(out, exception); + return new ForeignException(out.getArray(), UNDEFINED, false); + } catch (ThreadDeath td) { + throw td; + } catch (Throwable t) { + // Exception marshalling failed, prevent exception propagation from CEntryPoint that + // may cause process crash. + return MARSHALLING_FAILED; + } + } + + /** + * Returns the {@link JNICalls} transferring the {@link ForeignException} thrown in the HotSpot + * to an isolate. This method is intended to be called by the code generated by the bridge + * processor. It's still possible to use it by the hand-written code, but it's recommended to + * use the bridge processor. + */ + public static JNICalls getJNICalls() { + JNICalls res = jniCalls; + if (res == null) { + res = createJNICalls(); + jniCalls = res; + } + return res; + } + + byte[] toByteArray() { + return rawData; + } + + static ForeignException create(byte[] rawData, byte kind) { + ForeignException exception = new ForeignException(rawData, kind, true); + pendingException.set(exception); + return exception; + } + + private static void clearPendingException() { + pendingException.set(null); + } + + @SuppressWarnings({"unchecked", "unused"}) + private static T silenceException(Class type, Throwable t) throws T { + throw (T) t; + } + + private static JNICalls createJNICalls() { + return JNICalls.createWithExceptionHandler(context -> { + if (ForeignException.class.getName().equals(context.getThrowableClassName())) { + JNIEnv env = context.getEnv(); + byte[] marshalledData = JNIUtil.createArray(env, callToByteArray(env, context.getThrowable())); + throw ForeignException.create(marshalledData, ForeignException.GUEST_TO_HOST); + } else { + context.throwJNIExceptionWrapper(); + } + }); + } + + private static JThrowable callCreateForeignException(JNIEnv env, JByteArray rawValue) { + JValue args = StackValue.get(1, JValue.class); + args.addressOf(0).setJObject(rawValue); + return JNICalls.getDefault().callStaticJObject(env, CreateForeignException.getEntryPoints(env), CreateForeignException.resolve(env), args); + } + + private static JByteArray callToByteArray(JNIEnv env, JObject p0) { + JValue args = StackValue.get(1, JValue.class); + args.addressOf(0).setJObject(p0); + return JNICalls.getDefault().callStaticJObject(env, ToByteArray.getEntryPoints(env), ToByteArray.resolve(env), args); + } + + private static final class JNIMethodResolver implements JNICalls.JNIMethod { + + private final String methodName; + private final String methodSignature; + private volatile JClass entryPointsClass; + private volatile JMethodID methodId; + + JNIMethodResolver(String methodName, String methodSignature) { + this.methodName = methodName; + this.methodSignature = methodSignature; + } + + JNIMethodResolver resolve(JNIEnv jniEnv) { + JMethodID res = methodId; + if (res.isNull()) { + JClass entryPointClass = getEntryPoints(jniEnv); + try (CTypeConversion.CCharPointerHolder name = toCString(methodName); CTypeConversion.CCharPointerHolder sig = toCString(methodSignature)) { + res = GetStaticMethodID(jniEnv, entryPointClass, name.get(), sig.get()); + if (res.isNull()) { + throw new InternalError("No such method: " + methodName); + } + methodId = res; + } + } + return this; + } + + JClass getEntryPoints(JNIEnv env) { + JClass res = entryPointsClass; + if (res.isNull()) { + res = JNIClassCache.lookupClass(env, ForeignExceptionEndPoints.class); + entryPointsClass = res; + } + return res; + } + + @Override + public JMethodID getJMethodID() { + return methodId; + } + + @Override + public String getDisplayName() { + return methodName; + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/ForeignExceptionEndPoints.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/ForeignExceptionEndPoints.java new file mode 100644 index 000000000000..cfb50a5b7061 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/ForeignExceptionEndPoints.java @@ -0,0 +1,53 @@ +/* + * 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.jdwp.bridge.nativebridge; + +import com.oracle.svm.jdwp.bridge.jniutils.JNIEntryPoint; + +final class ForeignExceptionEndPoints { + + private ForeignExceptionEndPoints() { + } + + /** + * Called by JNI to create a {@link ForeignException} used to throw native exception into Java + * code. + * + * @param rawValue marshalled original exception + * @return a {@link ForeignException} instance + */ + @JNIEntryPoint + static Throwable createForeignException(byte[] rawValue) { + return ForeignException.create(rawValue, ForeignException.HOST_TO_GUEST); + } + + /** + * Called by JNI to return a marshalled exception transferred by the {@code exception}. + */ + @JNIEntryPoint + static byte[] toByteArray(ForeignException exception) { + return exception.toByteArray(); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/In.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/In.java new file mode 100644 index 000000000000..864f32f3f1f7 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/In.java @@ -0,0 +1,55 @@ +/* + * 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.jdwp.bridge.nativebridge; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Configures an array parameter as an in-parameter. For an in-parameter, the array value is copied + * over the boundary into a called method. The {@link In} is the default behavior. It's needed only + * in combination with {@link Out} for in-out parameters. + */ +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.PARAMETER) +public @interface In { + + /** + * Copy only a part of the array starting at the offset given by the + * {@code arrayOffsetParameter} method parameter. By default, the whole array is copied. The + * {@code arrayOffsetParameter} can be used to improve the performance and copy only a part of + * the array over the boundary. + */ + String arrayOffsetParameter() default ""; + + /** + * Limits copying only to many of the elements given by the {@code arrayLengthParameter} + * parameter. By default, the whole array is copied. The {@code arrayLengthParameter} can be + * used to improve the performance and copy only a part of the array over the boundary. + */ + String arrayLengthParameter() default ""; +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/JNIClassCache.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/JNIClassCache.java new file mode 100644 index 000000000000..5bbbdf0734d7 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/JNIClassCache.java @@ -0,0 +1,109 @@ +/* + * 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.jdwp.bridge.nativebridge; + +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JClass; +import com.oracle.svm.jdwp.bridge.jniutils.JNI.JNIEnv; +import com.oracle.svm.jdwp.bridge.jniutils.JNIExceptionWrapper; +import com.oracle.svm.jdwp.bridge.jniutils.JNIUtil; +import org.graalvm.word.WordFactory; + +/** + * Support class for {@link JClass} lookup. JClass instances are cached as JNI globals. The cached + * JNI globals are disposed by {@link JNIClassCache#dispose(JNIEnv)}. + */ +public final class JNIClassCache { + + private static final Map classesByName = new ConcurrentHashMap<>(); + + private JNIClassCache() { + } + + /** + * Looks up JClass using a {@link Class}. + * + * @return JNI global reference for {@link JClass} + * @throws JNIExceptionWrapper wrapping the HotSpot {@link LinkageError} is thrown when class is + * not found. + */ + public static JClass lookupClass(JNIEnv env, Class clazz) throws JNIExceptionWrapper { + return lookupClass(env, clazz.getName()); + } + + /** + * Looks up JClass using a fully qualified name. + * + * @return JNI global reference for {@link JClass} + * @throws JNIExceptionWrapper wrapping the HotSpot {@link LinkageError} is thrown when class is + * not found. + */ + public static JClass lookupClass(JNIEnv env, String className) throws JNIExceptionWrapper { + return lookupClassImpl(env, className, true); + } + + private static JClass lookupClassImpl(JNIEnv env, String className, boolean required) { + Function createClassData = new Function<>() { + @Override + public JNIClassData apply(String cn) { + JClass jClass = JNIUtil.findClass(env, WordFactory.nullPointer(), JNIUtil.getBinaryName(className), required); + if (jClass.isNull()) { + return JNIClassData.INVALID; + } + return new JNIClassData(JNIUtil.NewGlobalRef(env, jClass, "Class<" + className + ">")); + } + }; + return classesByName.computeIfAbsent(className, createClassData).jClassGlobal; + } + + /** + * Disposes cached JNI objects and frees JNI globals. The isolate should call this method before + * disposing to free host classes held by JNI global references. + */ + public static void dispose(JNIEnv jniEnv) { + for (Iterator iterator = classesByName.values().iterator(); iterator.hasNext();) { + JNIClassData classData = iterator.next(); + iterator.remove(); + if (classData != JNIClassData.INVALID) { + JNIUtil.DeleteGlobalRef(jniEnv, classData.jClassGlobal); + } + } + } + + private static final class JNIClassData { + + static final JNIClassData INVALID = new JNIClassData(null); + + private final JClass jClassGlobal; + + JNIClassData(JClass jClassGlobal) { + this.jClassGlobal = jClassGlobal; + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/JNIConfig.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/JNIConfig.java new file mode 100644 index 000000000000..b60e57b585f9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/JNIConfig.java @@ -0,0 +1,398 @@ +/* + * 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. + */ +// Checkstyle: allow direct annotation access +// Checkstyle: allow Class.getSimpleName +package com.oracle.svm.jdwp.bridge.nativebridge; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.LongBinaryOperator; +import java.util.function.LongUnaryOperator; +import java.util.function.Supplier; + +import org.graalvm.collections.Pair; + +/** + * A configuration used by the {@link NativeIsolate} and classes generated by the native bridge + * processor. + */ +public final class JNIConfig { + + private final Map> binaryMarshallers; + private final Map, List, BinaryMarshaller>>> annotationBinaryMarshallers; + private final LongUnaryOperator attachThreadAction; + private final LongUnaryOperator detachThreadAction; + private final LongBinaryOperator shutDownIsolateAction; + private final LongBinaryOperator releaseNativeObjectAction; + private final Function, ThreadLocal> threadLocalFactory; + + JNIConfig(Map> binaryMarshallers, + Map, List, BinaryMarshaller>>> annotationBinaryMarshallers, + LongUnaryOperator attachThreadAction, LongUnaryOperator detachThreadAction, + LongBinaryOperator shutDownIsolateAction, LongBinaryOperator releaseNativeObjectAction, + Function, ThreadLocal> threadLocalFactory) { + this.binaryMarshallers = binaryMarshallers; + this.annotationBinaryMarshallers = annotationBinaryMarshallers; + this.attachThreadAction = attachThreadAction; + this.detachThreadAction = detachThreadAction; + this.shutDownIsolateAction = shutDownIsolateAction; + this.releaseNativeObjectAction = releaseNativeObjectAction; + this.threadLocalFactory = threadLocalFactory; + } + + /** + * Looks up {@link BinaryMarshaller} for the {@code type} and {@code annotationTypes}. The + * method first tries to find a marshaller registered for the {@code type} and some annotation + * from {@code annotationTypes}. If no such marshaller exists, it tries to find a marshaller + * registered just for the {@code type}. If there is no such a marshaller it throws the + * {@link UnsupportedOperationException}. + * + * @param type the parameter or return type. + * @param annotationTypes parameter or method annotation types. + * @throws UnsupportedOperationException if there is no registered marshaller for the + * {@code type}. + */ + @SuppressWarnings("unchecked") + @SafeVarargs + public final BinaryMarshaller lookupMarshaller(Class type, Class... annotationTypes) { + BinaryMarshaller res = lookupBinaryMarshallerImpl(type, annotationTypes); + if (res != null) { + return (BinaryMarshaller) res; + } else { + throw unsupported(type); + } + } + + /** + * Looks up {@link BinaryMarshaller} for the {@code parameterizedType} and + * {@code annotationTypes}. The method first tries to find a marshaller registered for the + * {@code parameterizedType} and some annotation from {@code annotationTypes}. If no such + * marshaller exists, it tries to find a marshaller registered just for the + * {@code parameterizedType}. If there is no such a marshaller it throws the + * {@link UnsupportedOperationException}. + * + * @param parameterizedType the parameter or return type. + * @param annotationTypes parameter or method annotation types. + * @throws UnsupportedOperationException if there is no registered marshaller for the + * {@code parameterizedType}. + */ + @SuppressWarnings("unchecked") + @SafeVarargs + public final BinaryMarshaller lookupMarshaller(TypeLiteral parameterizedType, Class... annotationTypes) { + BinaryMarshaller res = lookupBinaryMarshallerImpl(parameterizedType.getType(), annotationTypes); + if (res != null) { + return (BinaryMarshaller) res; + } else { + throw unsupported(parameterizedType.getType()); + } + } + + Function, ThreadLocal> getThreadLocalFactory() { + return threadLocalFactory; + } + + long attachThread(long isolate) { + return attachThreadAction.applyAsLong(isolate); + } + + boolean detachThread(long isolateThread) { + return detachThreadAction.applyAsLong(isolateThread) == 0; + } + + boolean releaseNativeObject(long isolateThread, long handle) { + return releaseNativeObjectAction.applyAsLong(isolateThread, handle) == 0; + } + + boolean shutDownIsolate(long isolate, long isolateThread) { + return shutDownIsolateAction.applyAsLong(isolate, isolateThread) == 0; + } + + private static RuntimeException unsupported(Type type) { + throw new UnsupportedOperationException(String.format("Marshalling of %s is not supported", type)); + } + + @SafeVarargs + private final BinaryMarshaller lookupBinaryMarshallerImpl(Type type, Class... annotationTypes) { + for (Class annotationType : annotationTypes) { + verifyAnnotation(annotationType); + BinaryMarshaller res = lookup(annotationBinaryMarshallers, type, annotationType); + if (res != null) { + return res; + } + } + return binaryMarshallers.get(type); + } + + private static T lookup(Map, List, T>>> marshallers, Type type, Class annotationType) { + List, T>> marshallersForAnnotation = marshallers.get(annotationType); + if (marshallersForAnnotation != null) { + Class rawType = erasure(type); + for (Pair, T> marshaller : marshallersForAnnotation) { + if (marshaller.getLeft().isAssignableFrom(rawType)) { + return marshaller.getRight(); + } + } + } + return null; + } + + private static Class erasure(Type type) { + if (type instanceof Class) { + return (Class) type; + } else if (type instanceof ParameterizedType parameterizedType) { + return (Class) parameterizedType.getRawType(); + } else if (type instanceof GenericArrayType genericArrayType) { + return arrayTypeFromComponentType(erasure(genericArrayType.getGenericComponentType())); + } else { + throw new IllegalArgumentException("Unsupported type: " + type); + } + } + + private static Class arrayTypeFromComponentType(Class componentType) { + return Array.newInstance(componentType, 0).getClass(); + } + + private static void verifyAnnotation(Class annotationType) { + if (annotationType.getAnnotation(MarshallerAnnotation.class) == null) { + throw new IllegalArgumentException(String.format("The %s in not a valid marshaller annotation. The marshaller annotation must be annotated by the %s meta-annotation.", + annotationType.getSimpleName(), MarshallerAnnotation.class.getSimpleName())); + } + } + + public static Builder newBuilder() { + return new Builder(); + } + + /** + * A builder class to construct {@link JNIConfig} instances. + */ + public static final class Builder { + + private static final LongUnaryOperator ATTACH_UNSUPPORTED = (isolate) -> { + throw new UnsupportedOperationException("Attach is not supported."); + }; + private static final LongUnaryOperator DETACH_UNSUPPORTED = (isolateThread) -> { + throw new UnsupportedOperationException("Detach is not supported."); + }; + private static final LongBinaryOperator SHUTDOWN_UNSUPPORTED = (isolate, isolateThread) -> { + throw new UnsupportedOperationException("Isolate shutdown is not supported."); + }; + private static final LongBinaryOperator RELEASE_UNSUPPORTED = (isolateThread, handle) -> { + throw new UnsupportedOperationException("Native object clean up is not supported."); + }; + + private final Map> binaryMarshallers; + private final Map, List, BinaryMarshaller>>> annotationBinaryMarshallers; + private LongUnaryOperator attachThreadAction = ATTACH_UNSUPPORTED; + private LongUnaryOperator detachThreadAction = DETACH_UNSUPPORTED; + private LongBinaryOperator shutDownIsolateAction = SHUTDOWN_UNSUPPORTED; + private LongBinaryOperator releaseNativeObjectAction = RELEASE_UNSUPPORTED; + private Function, ThreadLocal> threadLocalFactory = ThreadLocal::withInitial; + + Builder() { + this.binaryMarshallers = new HashMap<>(); + this.annotationBinaryMarshallers = new HashMap<>(); + // Register default marshallers + this.binaryMarshallers.put(String.class, BinaryMarshaller.nullable(new StringMarshaller())); + this.binaryMarshallers.put(Throwable.class, new DefaultThrowableMarshaller()); + this.binaryMarshallers.put(StackTraceElement.class, StackTraceElementMarshaller.INSTANCE); + } + + /** + * Registers a {@link BinaryMarshaller} for the {@code type}. + * + * @param type the type to register {@link BinaryMarshaller} for. + * @param marshaller the marshaller to register. + */ + public Builder registerMarshaller(Class type, BinaryMarshaller marshaller) { + Objects.requireNonNull(type, "Type must be non null."); + Objects.requireNonNull(marshaller, "Marshaller must be non null."); + this.binaryMarshallers.put(type, marshaller); + return this; + } + + /** + * Registers a {@link BinaryMarshaller} for the {@code parameterizedType}. + * + * @param parameterizedType the type to register {@link BinaryMarshaller} for. + * @param marshaller the marshaller to register. + */ + public Builder registerMarshaller(TypeLiteral parameterizedType, BinaryMarshaller marshaller) { + Objects.requireNonNull(parameterizedType, "ParameterizedType must be non null."); + Objects.requireNonNull(marshaller, "Marshaller must be non null."); + this.binaryMarshallers.put(parameterizedType.getType(), marshaller); + return this; + } + + /** + * Registers a {@link BinaryMarshaller} for the {@code type} and {@code annotationType}. + * + * @param type the type to register {@link BinaryMarshaller} for. + * @param annotationType a required annotation to look up the marshaller. + * @param marshaller the marshaller to register. + * + */ + public Builder registerMarshaller(Class type, Class annotationType, BinaryMarshaller marshaller) { + Objects.requireNonNull(type, "Type must be non null."); + Objects.requireNonNull(annotationType, "AnnotationType must be non null."); + Objects.requireNonNull(marshaller, "Marshaller must be non null."); + insert(annotationBinaryMarshallers, type, annotationType, marshaller); + return this; + } + + /** + * Registers a {@link BinaryMarshaller} for the {@code parameterizedType} and + * {@code annotationType}. + * + * @param parameterizedType the type to register {@link BinaryMarshaller} for. + * @param annotationType a required annotation to look up the marshaller. + * @param marshaller the marshaller to register. + * + */ + public Builder registerMarshaller(TypeLiteral parameterizedType, Class annotationType, BinaryMarshaller marshaller) { + Objects.requireNonNull(parameterizedType, "ParameterizedType must be non null."); + Objects.requireNonNull(annotationType, "AnnotationType must be non null."); + Objects.requireNonNull(marshaller, "Marshaller must be non null."); + insert(annotationBinaryMarshallers, parameterizedType.getRawType(), annotationType, marshaller); + return this; + } + + private static void insert(Map, List, T>>> into, Class type, Class annotationType, T marshaller) { + verifyAnnotation(annotationType); + List, T>> types = into.computeIfAbsent(annotationType, (k) -> new LinkedList<>()); + Pair, T> toInsert = Pair.create(type, marshaller); + boolean inserted = false; + for (ListIterator, T>> it = types.listIterator(); it.hasNext();) { + Pair, T> current = it.next(); + if (current.getLeft().isAssignableFrom(type)) { + it.set(toInsert); + it.add(current); + inserted = true; + break; + } + } + if (!inserted) { + types.add(toInsert); + } + } + + /** + * Registers a callback used by the {@link NativeIsolate} to attach the current thread to an + * isolate. + * + * @param action a {@link LongUnaryOperator} that takes an isolate address as a parameter + * and returns the isolate thread address. + */ + public Builder setAttachThreadAction(LongUnaryOperator action) { + Objects.requireNonNull(action, "Action must be non null."); + this.attachThreadAction = action; + return this; + } + + /** + * Registers a callback used by the {@link NativeIsolate} to detach the current thread from + * an isolate. + * + * @param action a {@link LongUnaryOperator} that takes an isolate thread address as a + * parameter and returns {@code 0} on success or non-zero in case of an error. + */ + public Builder setDetachThreadAction(LongUnaryOperator action) { + Objects.requireNonNull(action, "Action must be non null."); + this.detachThreadAction = action; + return this; + } + + /** + * Registers a callback used by the {@link NativeIsolate} to tear down the isolate. + * + * @param action a {@link LongBinaryOperator} that takes an isolate address and an isolate + * thread address as parameters and returns {@code 0} on success or non-zero in + * case of an error. + */ + public Builder setShutDownIsolateAction(LongBinaryOperator action) { + Objects.requireNonNull(action, "Action must be non null."); + this.shutDownIsolateAction = action; + return this; + } + + /** + * Registers a callback used by the {@link NativeObject} to free an object in a native image + * heap referenced by the garbage-collected handle. At some point after a + * {@link NativeObject} is garbage collected, a call to the {@code action} is made to + * release the corresponding object in the native image heap. + * + * @param action a {@link LongBinaryOperator} that takes an isolate thread address and + * object handle as parameters and returns {@code 0} on success or non-zero in + * case of an error. + * + * @see NativeObject + */ + public Builder setReleaseNativeObjectAction(LongBinaryOperator action) { + Objects.requireNonNull(action, "Action must be non null."); + this.releaseNativeObjectAction = action; + return this; + } + + /** + * Registers a thread local factory whenever the default thread local handling should be + * overriden. This can be useful to install a terminating thread local using JVMCI services + * when needed. + * + * @see NativeObject + */ + public Builder setNativeThreadLocalFactory(Function, ThreadLocal> factory) { + Objects.requireNonNull(factory, "Action must be non null."); + this.threadLocalFactory = factory; + return this; + } + + /** + * Builds the {@link JNIConfig}. + */ + public JNIConfig build() { + return new JNIConfig(binaryMarshallers, annotationBinaryMarshallers, + attachThreadAction, detachThreadAction, shutDownIsolateAction, + releaseNativeObjectAction, threadLocalFactory); + } + + /** + * Returns a {@link BinaryMarshaller} for stack trace marshalling. + */ + public static BinaryMarshaller defaultStackTraceMarshaller() { + return DefaultStackTraceMarshaller.INSTANCE; + } + } + +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/MarshalledException.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/MarshalledException.java new file mode 100644 index 000000000000..92541c703c2f --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/MarshalledException.java @@ -0,0 +1,67 @@ +/* + * 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.jdwp.bridge.nativebridge; + +/** + * An exception representing an exception thrown over the isolate boundary. + */ +@SuppressWarnings("serial") +public final class MarshalledException extends RuntimeException { + + private final String foreignExceptionClassName; + + /** + * Creates a {@link MarshalledException} for foreign exception of the + * {@code foreignExceptionClassName} type with the {@code foreignExceptionMessage} message. + * + * @param foreignExceptionClassName the foreign exception class name + * @param foreignExceptionMessage the foreign exception message + * @param stackTrace the merged stack trace. + */ + public MarshalledException(String foreignExceptionClassName, String foreignExceptionMessage, StackTraceElement[] stackTrace) { + super(foreignExceptionMessage); + this.foreignExceptionClassName = foreignExceptionClassName; + setStackTrace(stackTrace); + } + + /** + * Returns the foreign exception class name. + */ + public String getForeignExceptionClassName() { + return foreignExceptionClassName; + } + + @Override + @SuppressWarnings("sync-override") + public Throwable fillInStackTrace() { + return this; + } + + @Override + public String toString() { + String message = getLocalizedMessage(); + return (message != null) ? (foreignExceptionClassName + ": " + message) : foreignExceptionClassName; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/MarshallerAnnotation.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/MarshallerAnnotation.java new file mode 100644 index 000000000000..f7ecfc99bc70 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/MarshallerAnnotation.java @@ -0,0 +1,40 @@ +/* + * 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.jdwp.bridge.nativebridge; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meta-annotation that marks an annotation to be used for marshaller lookup. An annotation intended + * for {@link JNIConfig#lookupMarshaller(Class, Class[]) marshaller lookup} must be annotated by + * this annotation. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface MarshallerAnnotation { +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NativeIsolate.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NativeIsolate.java new file mode 100644 index 000000000000..80ef465a0676 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NativeIsolate.java @@ -0,0 +1,304 @@ +/* + * 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.jdwp.bridge.nativebridge; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Represents a single native image isolate. All {@link NativeObject}s have a {@link NativeIsolate} + * context. + */ +public final class NativeIsolate { + + static final int CLOSED = -1; + private static final long NULL = 0L; + private static final Map isolates = new ConcurrentHashMap<>(); + private static final AtomicInteger UUIDS = new AtomicInteger(0); + + private final long uuid; + private final long isolateId; + private final JNIConfig config; + private final ThreadLocal attachedIsolateThread; + private final Collection threads; // Guarded by this + final Set> cleaners; + private volatile State state; // Guarded by this + + private NativeIsolate(long isolateId, JNIConfig config) { + if (isolateId == NULL) { + throw new IllegalArgumentException("Isolate address must be non NULL"); + } + this.uuid = UUIDS.incrementAndGet(); + this.isolateId = isolateId; + this.config = config; + this.cleaners = Collections.newSetFromMap(new ConcurrentHashMap<>()); + this.threads = new ArrayList<>(); + this.attachedIsolateThread = config.getThreadLocalFactory().apply(() -> null); + this.state = State.ACTIVE; + } + + /** + * Binds a native image thread to this isolate. When a thread created in the native image enters + * for the first time to the host, it must be registered to the {@link NativeIsolate} as a + * native thread. + * + * @param isolateThreadId the isolate thread to bind. + */ + public void registerNativeThread(long isolateThreadId) { + NativeIsolateThread nativeIsolateThread = attachedIsolateThread.get(); + if (nativeIsolateThread != null) { + throw new IllegalStateException(String.format("Native thread %s is already attached to isolate %s.", Thread.currentThread(), this)); + } + synchronized (this) { + if (!state.isValid()) { + throw throwClosedException(); + } + nativeIsolateThread = new NativeIsolateThread(Thread.currentThread(), this, true, isolateThreadId); + threads.add(nativeIsolateThread); + attachedIsolateThread.set(nativeIsolateThread); + } + } + + /** + * Enters this {@link NativeIsolate} on the current thread. + * + * @throws IllegalStateException when this {@link NativeObject} is already closed or being + * closed. + */ + public NativeIsolateThread enter() { + NativeIsolateThread nativeIsolateThread = getOrCreateNativeIsolateThread(); + if (nativeIsolateThread != null && nativeIsolateThread.enter()) { + return nativeIsolateThread; + } else { + throw throwClosedException(); + } + } + + /** + * Tries to enter this {@link NativeIsolate} on the current thread. + * + * @return {@link NativeIsolateThread} on success or {@code null} when this + * {@link NativeIsolate} is closed or being closed. + * @see #enter() + */ + public NativeIsolateThread tryEnter() { + NativeIsolateThread nativeIsolateThread = getOrCreateNativeIsolateThread(); + if (nativeIsolateThread != null && nativeIsolateThread.enter()) { + return nativeIsolateThread; + } else { + return null; + } + } + + /** + * Returns true if the current thread is entered to this {@link NativeIsolate}. + */ + public boolean isActive() { + NativeIsolateThread nativeIsolateThread = attachedIsolateThread.get(); + return nativeIsolateThread != null && (nativeIsolateThread.isNativeThread() || nativeIsolateThread.isActive()); + } + + /** + * Requests an isolate shutdown. If there is no host thread entered into this + * {@link NativeIsolate} the isolate is closed and the isolate heap is freed. If this + * {@link NativeIsolate} has active threads the isolate is freed by the last leaving thread. + */ + public boolean shutdown() { + NativeIsolateThread currentIsolateThread = attachedIsolateThread.get(); + if (currentIsolateThread != null && currentIsolateThread.isNativeThread()) { + return false; + } + boolean deferredClose = false; + synchronized (this) { + if (state == State.DISPOSED) { + return true; + } + state = State.DISPOSING; + for (NativeIsolateThread nativeIsolateThread : threads) { + deferredClose |= !nativeIsolateThread.invalidate(); + } + } + if (deferredClose) { + return false; + } else { + return doIsolateShutdown(); + } + } + + /** + * Returns the isolate address. + */ + public long getIsolateId() { + return isolateId; + } + + /** + * Returns the {@link JNIConfig} used by this {@link NativeIsolate}. + */ + public JNIConfig getConfig() { + return config; + } + + @Override + public String toString() { + return "NativeIsolate[" + uuid + " for 0x" + Long.toHexString(isolateId) + "]"; + } + + /** + * Gets the NativeIsolate object for the entered isolate with the specified isolate address. + * IMPORTANT: Must be used only when the isolate with the specified isolateId is entered. + * + * @param isolateId id of an entered isolate + * @return NativeIsolate object for the entered isolate with the specified isolate address + * @throws IllegalStateException when {@link NativeIsolate} does not exist for the + * {@code isolateId} + */ + public static NativeIsolate get(long isolateId) { + NativeIsolate res = isolates.get(isolateId); + if (res == null) { + throw new IllegalStateException("NativeIsolate for isolate 0x" + Long.toHexString(isolateId) + " does not exist."); + } + return res; + } + + /** + * Creates a {@link NativeIsolate} for the {@code isolateId} and {@link JNIConfig}. This method + * can be called at most once, preferably right after creating the isolate. Use the + * {@link #get(long)} method to get an existing {@link NativeIsolate} instance. + * + * @return the newly created {@link NativeIsolate} for the {@code isolateId}. + * @throws IllegalStateException when {@link NativeIsolate} for the {@code isolateId} already + * exists. + */ + public static NativeIsolate forIsolateId(long isolateId, JNIConfig config) { + NativeIsolate res = new NativeIsolate(isolateId, config); + NativeIsolate previous = isolates.put(isolateId, res); + if (previous != null && previous.state != State.DISPOSED) { + throw new IllegalStateException("NativeIsolate for isolate 0x" + Long.toHexString(isolateId) + " already exists and is not disposed."); + } + return res; + } + + /* + * Returns true if the isolate shutdown process has already begun or is finished. + */ + public boolean isDisposed() { + return state == State.DISPOSED; + } + + void lastLeave() { + synchronized (this) { + for (NativeIsolateThread nativeIsolateThread : threads) { + if (nativeIsolateThread.isActive()) { + return; + } + } + } + doIsolateShutdown(); + } + + RuntimeException throwClosedException() { + throw new IllegalStateException("Isolate 0x" + Long.toHexString(getIsolateId()) + " is already closed."); + } + + private boolean doIsolateShutdown() { + synchronized (this) { + if (state == State.DISPOSED) { + return true; + } + state = State.DISPOSED; + } + cleaners.clear(); + boolean success = false; + + NativeIsolateThread nativeIsolateThread = attachedIsolateThread.get(); + if (nativeIsolateThread == null) { + nativeIsolateThread = new NativeIsolateThread(Thread.currentThread(), this, false, config.attachThread(isolateId)); + nativeIsolateThread.invalidate(); + attachedIsolateThread.set(nativeIsolateThread); + } + try { + nativeIsolateThread.setShutDownRequest(true); + try { + success = config.shutDownIsolate(isolateId, nativeIsolateThread.isolateThread); + } finally { + nativeIsolateThread.setShutDownRequest(false); + } + } finally { + if (success) { + isolates.computeIfPresent(isolateId, (id, nativeIsolate) -> (nativeIsolate == NativeIsolate.this ? null : nativeIsolate)); + } + } + return success; + } + + private NativeIsolateThread getOrCreateNativeIsolateThread() { + NativeIsolateThread nativeIsolateThread = attachedIsolateThread.get(); + if (nativeIsolateThread == null) { + synchronized (this) { + if (!state.isValid()) { + return null; + } + long isolateThreadAddress = config.attachThread(isolateId); + nativeIsolateThread = new NativeIsolateThread(Thread.currentThread(), this, false, isolateThreadAddress); + threads.add(nativeIsolateThread); + attachedIsolateThread.set(nativeIsolateThread); + } + } + return nativeIsolateThread; + } + + public void detachCurrentThread() { + synchronized (this) { + NativeIsolateThread isolateThread = attachedIsolateThread.get(); + if (isolateThread != null) { + detachThread(isolateThread); + attachedIsolateThread.set(null); + } + } + } + + private synchronized void detachThread(NativeIsolateThread nativeIsolateThread) { + if (state.isValid() && nativeIsolateThread != null && !nativeIsolateThread.isNativeThread()) { + config.detachThread(nativeIsolateThread.isolateThread); + } + } + + private enum State { + + ACTIVE, + DISPOSING, + DISPOSED; + + boolean isValid() { + return this == ACTIVE; + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NativeIsolateThread.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NativeIsolateThread.java new file mode 100644 index 000000000000..f6dafb2618df --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NativeIsolateThread.java @@ -0,0 +1,169 @@ +/* + * 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.jdwp.bridge.nativebridge; + +import static com.oracle.svm.jdwp.bridge.nativebridge.NativeIsolate.CLOSED; + +import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Represents an entered isolate thread. + * + * @see NativeIsolate#enter() + */ +public final class NativeIsolateThread { + + private static final int CLOSING_MASK = 0b1; + + private final NativeIsolate isolate; + private final WeakReference thread; + private final AtomicInteger enteredCount; + private final boolean nativeThread; + final long isolateThread; + private boolean executesShutDown; + + NativeIsolateThread(Thread thread, NativeIsolate isolate, boolean nativeThread, long isolateThreadId) { + this.thread = new WeakReference<>(thread); + this.isolate = isolate; + this.nativeThread = nativeThread; + this.isolateThread = isolateThreadId; + this.enteredCount = new AtomicInteger(); + } + + /** + * Returns the isolate thread address. + * + * @throws IllegalStateException when the {@link NativeIsolateThread} is no more entered. + */ + public long getIsolateThreadId() { + assert verifyThread(); + if (!isActive()) { + throw new IllegalStateException("Isolate 0x" + Long.toHexString(isolate.getIsolateId()) + " is not entered."); + } + return isolateThread; + } + + /** + * Returns the isolate for this thread. + */ + public NativeIsolate getIsolate() { + return isolate; + } + + /** + * Leaves the {@link NativeIsolate} on the current thread. + */ + public void leave() { + assert verifyThread(); + decrementAttached(); + } + + boolean enter() { + assert verifyThread(); + return incrementAttached(); + } + + boolean invalidate() { + while (true) { // TERMINATION ARGUMENT: busy loop + int value = enteredCount.get(); + if (value == CLOSED) { + return true; + } + int numberOfAttached = (value >>> 1); + boolean inactive = numberOfAttached == 0; + int newValue = inactive ? CLOSED : (value | CLOSING_MASK); + if (enteredCount.compareAndSet(value, newValue)) { + return inactive; + } + } + } + + boolean isActive() { + if (executesShutDown) { + return true; + } + int value = enteredCount.get(); + return value != CLOSED && (value >>> 1) > 0; + } + + boolean isNativeThread() { + return nativeThread; + } + + void setShutDownRequest(boolean shutDown) { + this.executesShutDown = shutDown; + } + + private boolean verifyThread() { + assert thread.get() == Thread.currentThread() : String.format( + "NativeIsolateThread used by other thread. Expected thread %s, actual thread %s.", + thread.get(), + Thread.currentThread()); + return true; + } + + private boolean incrementAttached() { + while (true) { // TERMINATION ARGUMENT: busy loop + int value = enteredCount.get(); + if (value == CLOSED) { + if (executesShutDown) { + return true; + } else { + return false; + } + } + int closing = (value & CLOSING_MASK); + int newValue = (((value >>> 1) + 1) << 1) | closing; + if (enteredCount.compareAndSet(value, newValue)) { + break; + } + } + return true; + } + + private void decrementAttached() { + while (true) { // TERMINATION ARGUMENT: busy loop + int value = enteredCount.get(); + if (value == CLOSED) { + if (executesShutDown) { + return; + } else { + throw new IllegalStateException("Isolate 0x" + Long.toHexString(isolate.getIsolateId()) + " was closed while being active."); + } + } + int closing = (value & CLOSING_MASK); + int numberOfAttached = (value >>> 1) - 1; + boolean lastLeaving = closing == CLOSING_MASK && numberOfAttached == 0; + int newValue = lastLeaving ? CLOSED : ((numberOfAttached << 1) | closing); + if (enteredCount.compareAndSet(value, newValue)) { + if (lastLeaving) { + isolate.lastLeave(); + } + break; + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NativeObject.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NativeObject.java new file mode 100644 index 000000000000..a3193a357775 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NativeObject.java @@ -0,0 +1,100 @@ +/* + * 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.jdwp.bridge.nativebridge; + +/** + * Encapsulates a handle to an object in a native image heap where the object's lifetime is bound to + * the lifetime of the {@link NativeObject} instance. At some point, after a {@link NativeObject} is + * garbage collected, a call is made to release the handle, allowing the corresponding object in the + * native image heap to be collected. + */ +public class NativeObject { + + private final NativeIsolate isolate; + private final long objectHandle; + private final NativeObjectCleaner cleanup; + + /** + * Creates a new {@link NativeObject}. + * + * @param isolate an isolate in which an object referenced by the handle exists. + * @param objectHandle a handle to an object in a native image heap + */ + public NativeObject(NativeIsolate isolate, long objectHandle) { + this.isolate = isolate; + this.objectHandle = objectHandle; + this.cleanup = new NativeObjectCleanerImpl(this).register(); + } + + /** + * Returns an isolate in which an object referenced by this handle exists. + */ + public final NativeIsolate getIsolate() { + return isolate; + } + + /** + * Returns a handle to an object in the native image heap. + */ + public final long getHandle() { + return objectHandle; + } + + /** + * Explicitly releases object in the native image heap referenced by this handle. The use of + * this method should be exceptional. By default, the lifetime of the object in the native image + * heap is bound to the lifetime of the {@link NativeObject} instance. + */ + public final void release() { + if (isolate.cleaners.remove(cleanup)) { + NativeIsolateThread nativeIsolateThread = isolate.enter(); + try { + cleanup.cleanUp(nativeIsolateThread.getIsolateThreadId()); + } finally { + nativeIsolateThread.leave(); + } + } + } + + private static final class NativeObjectCleanerImpl extends NativeObjectCleaner { + + private final long handle; + + NativeObjectCleanerImpl(NativeObject nativeObject) { + super(nativeObject, nativeObject.getIsolate()); + this.handle = nativeObject.getHandle(); + } + + @Override + public void cleanUp(long isolateThread) { + isolate.getConfig().releaseNativeObject(isolateThread, handle); + } + + @Override + public String toString() { + return "NativeObject 0x" + Long.toHexString(handle); + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NativeObjectCleaner.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NativeObjectCleaner.java new file mode 100644 index 000000000000..ac3bc9a0ff2d --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NativeObjectCleaner.java @@ -0,0 +1,119 @@ +/* + * 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.jdwp.bridge.nativebridge; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; + +/** + * A weak reference performing a given action when a referent becomes weakly reachable and is + * enqueued into reference queue. The class is not active. The reference queue check is performed + * anytime a new {@link NativeObjectCleaner#register()} is called, or it can be performed explicitly + * using {@link NativeObjectCleaner#processPendingCleaners()}. + */ +public abstract class NativeObjectCleaner extends WeakReference { + + private static final Object INVALID_ISOLATE_THREAD = new Object(); + private static final ReferenceQueue cleanersQueue = new ReferenceQueue<>(); + + final NativeIsolate isolate; + + /** + * Creates a new {@link NativeObjectCleaner}. + * + * @param referent object the new weak reference will refer to + * @param isolate the native object isolate + */ + protected NativeObjectCleaner(T referent, NativeIsolate isolate) { + super(referent, cleanersQueue); + this.isolate = isolate; + } + + /** + * Registers {@link NativeObjectCleaner} for cleanup. + */ + public final NativeObjectCleaner register() { + processPendingCleaners(); + if (!isolate.isDisposed()) { + isolate.cleaners.add(this); + } + return this; + } + + /** + * At some point after a {@code referent} is garbage collected the {@link NativeIsolate} is + * entered and the {@link #cleanUp(long)} is executed with the isolate thread address parameter. + * This method should perform cleanup in the isolate heap. + * + * @param isolateThread the isolate thread address to call into isolate. + */ + protected abstract void cleanUp(long isolateThread); + + /** + * Performs an explicit clean up of enqueued {@link NativeObjectCleaner}s. + */ + public static void processPendingCleaners() { + Map enteredThreads = null; + NativeObjectCleaner cleaner; + try { + while ((cleaner = (NativeObjectCleaner) cleanersQueue.poll()) != null) { + NativeIsolate isolate = cleaner.isolate; + if (isolate.cleaners.remove(cleaner)) { + Object enteredThread; + if (enteredThreads == null) { + enteredThreads = new HashMap<>(); + enteredThread = null; + } else { + enteredThread = enteredThreads.get(isolate); + } + if (enteredThread == null) { + enteredThread = isolate.tryEnter(); + if (enteredThread == null) { + enteredThread = INVALID_ISOLATE_THREAD; + } + enteredThreads.put(isolate, enteredThread); + } + if (enteredThread != INVALID_ISOLATE_THREAD) { + cleanImpl(((NativeIsolateThread) enteredThread).getIsolateThreadId(), cleaner); + } + } + } + } finally { + if (enteredThreads != null) { + for (Object enteredThread : enteredThreads.values()) { + if (enteredThread != INVALID_ISOLATE_THREAD) { + ((NativeIsolateThread) enteredThread).leave(); + } + } + } + } + } + + private static void cleanImpl(long isolateThread, NativeObjectCleaner cleaner) { + cleaner.cleanUp(isolateThread); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NativeObjectHandles.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NativeObjectHandles.java new file mode 100644 index 000000000000..e1c623aeea73 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NativeObjectHandles.java @@ -0,0 +1,89 @@ +/* + * 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.jdwp.bridge.nativebridge; + +import org.graalvm.nativeimage.ObjectHandles; +import org.graalvm.word.WordFactory; + +/** + * A support class for mapping objects in the native image isolate to long handles. + */ +public final class NativeObjectHandles { + + private NativeObjectHandles() { + } + + /** + * Creates a handle for the {@code object}. Unless the handle is {@link #remove(long) removed} + * the {@code object} stays strongly reachable. + */ + public static long create(Object object) { + return ObjectHandles.getGlobal().create(object).rawValue(); + } + + /** + * Resolves a handle into an object. + * + * @throws InvalidHandleException if the handle is either already removed or invalid. + */ + public static T resolve(long handle, Class type) { + try { + return type.cast(ObjectHandles.getGlobal().get(WordFactory.pointer(handle))); + } catch (IllegalArgumentException iae) { + throw new InvalidHandleException(iae); + } + } + + /** + * Removes a handle. Allows an object identified by the {@code handle} to be garbage collected. + */ + public static void remove(long handle) { + try { + ObjectHandles.getGlobal().destroy(WordFactory.pointer(handle)); + } catch (IllegalArgumentException iae) { + throw new InvalidHandleException(iae); + } + } + + /** + * An exception thrown when an invalid handle is resolved. + * + * @see #resolve(long, Class) + */ + public static final class InvalidHandleException extends IllegalArgumentException { + + private static final long serialVersionUID = 1L; + + InvalidHandleException(IllegalArgumentException cause) { + super(cause.getMessage(), cause); + setStackTrace(cause.getStackTrace()); + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NullableBinaryMarshaller.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NullableBinaryMarshaller.java new file mode 100644 index 000000000000..2c51df574778 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/NullableBinaryMarshaller.java @@ -0,0 +1,104 @@ +/* + * 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.jdwp.bridge.nativebridge; + +final class NullableBinaryMarshaller implements BinaryMarshaller { + + private static final byte NULL = 0; + private static final byte NON_NULL = 1; + + private final BinaryMarshaller delegate; + + NullableBinaryMarshaller(BinaryMarshaller delegate) { + this.delegate = delegate; + } + + @Override + public T read(BinaryInput input) { + byte nullStatus = input.readByte(); + switch (nullStatus) { + case NULL: + return null; + case NON_NULL: + return delegate.read(input); + default: + throw new IllegalArgumentException("Unexpected input " + nullStatus); + } + } + + @Override + public void write(BinaryOutput output, T object) { + if (object != null) { + output.writeByte(NON_NULL); + delegate.write(output, object); + } else { + output.writeByte(NULL); + } + } + + @Override + public void readUpdate(BinaryInput input, T object) { + byte nullStatus = input.readByte(); + switch (nullStatus) { + case NULL: + assert object == null; + break; + case NON_NULL: + assert object != null; + delegate.readUpdate(input, object); + break; + default: + throw new IllegalArgumentException("Unexpected input " + nullStatus); + } + } + + @Override + public void writeUpdate(BinaryOutput output, T object) { + if (object != null) { + output.writeByte(NON_NULL); + delegate.writeUpdate(output, object); + } else { + output.writeByte(NULL); + } + } + + @Override + public int inferSize(T object) { + if (object != null) { + return 1 + delegate.inferSize(object); + } else { + return 1; + } + } + + @Override + public int inferUpdateSize(T object) { + if (object != null) { + return 1 + delegate.inferUpdateSize(object); + } else { + return 1; + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/Out.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/Out.java new file mode 100644 index 000000000000..055c0d68dd12 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/Out.java @@ -0,0 +1,68 @@ +/* + * 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.jdwp.bridge.nativebridge; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Configures an array parameter as an out-parameter. For an out-parameter, the array value is + * copied over the boundary from a called method. It may be combined with {@link In} for in-out + * parameters. Example showing the configuration for + * {@link java.io.OutputStream#write(byte[], int, int)}. + * + *
      + * @Override
      + * public abstract int read(@Out(arrayOffsetParameter = "off", arrayLengthParameter = "len", trimToResult = true) byte[] b, int off, int len);
      + * 
      + */ +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.PARAMETER) +public @interface Out { + + /** + * Copy only a part of the array starting at offset given by the {@code arrayOffsetParameter} + * method parameter. By default, the whole array is copied. The {@code arrayOffsetParameter} can + * be used to improve the performance and copy only a part of the array over the boundary. + */ + String arrayOffsetParameter() default ""; + + /** + * Limits copying only to many of the elements given by the {@code arrayLengthParameter} + * parameter. By default, the whole array is copied. The {@code arrayLengthParameter} can be + * used to improve the performance and copy only a part of the array over the boundary. + */ + String arrayLengthParameter() default ""; + + /** + * Limits copying only to method result number of elements. It can be used to further limit the + * number of copied elements in addition to {@link #arrayLengthParameter}. When used, it's still + * good to specify {@link #arrayLengthParameter} as an upper bound to limit allocated array + * size. + */ + boolean trimToResult() default false; +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/StackTraceElementMarshaller.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/StackTraceElementMarshaller.java new file mode 100644 index 000000000000..691a9871e506 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/StackTraceElementMarshaller.java @@ -0,0 +1,59 @@ +/* + * 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.jdwp.bridge.nativebridge; + +final class StackTraceElementMarshaller implements BinaryMarshaller { + + static final StackTraceElementMarshaller INSTANCE = new StackTraceElementMarshaller(); + + private StackTraceElementMarshaller() { + } + + private static final int STACK_TRACE_ELEMENT_SIZE_ESTIMATE = 100; + + @Override + public StackTraceElement read(BinaryInput in) { + String className = in.readUTF(); + String methodName = in.readUTF(); + String fileName = in.readUTF(); + fileName = fileName.isEmpty() ? null : fileName; + int lineNumber = in.readInt(); + return new StackTraceElement(className, methodName, fileName, lineNumber); + } + + @Override + public void write(BinaryOutput out, StackTraceElement stackTraceElement) { + out.writeUTF(stackTraceElement.getClassName()); + out.writeUTF(stackTraceElement.getMethodName()); + String fileName = stackTraceElement.getFileName(); + out.writeUTF(fileName == null ? "" : fileName); + out.writeInt(stackTraceElement.getLineNumber()); + } + + @Override + public int inferSize(StackTraceElement object) { + return STACK_TRACE_ELEMENT_SIZE_ESTIMATE; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/StringMarshaller.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/StringMarshaller.java new file mode 100644 index 000000000000..acf2976d56b8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/StringMarshaller.java @@ -0,0 +1,48 @@ +/* + * 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.jdwp.bridge.nativebridge; + +/** + * BinaryMarshaller used to marshall string array components. + */ +final class StringMarshaller implements BinaryMarshaller { + + private static final int STRING_SIZE_ESTIMATE = 32; + + @Override + public String read(BinaryInput in) { + return in.readUTF(); + } + + @Override + public void write(BinaryOutput out, String str) { + out.writeUTF(str); + } + + @Override + public int inferSize(String object) { + return STRING_SIZE_ESTIMATE; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/TypeLiteral.java b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/TypeLiteral.java new file mode 100644 index 000000000000..6a32a6399526 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.bridge/src/com/oracle/svm/jdwp/bridge/nativebridge/TypeLiteral.java @@ -0,0 +1,139 @@ +/* + * 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.jdwp.bridge.nativebridge; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +/** + * Represents a generic type {@code T}. Java doesn't yet provide a way to represent generic types, + * so this class does. Forces clients to create a subclass of this class which enables retrieval of + * the type information even at runtime. + * + *

      + * For example, to create a type literal for {@code List}, you can create an empty anonymous + * inner class: + * + *

      + * {@code TypeLiteral> list = new TypeLiteral>() {};} + * + */ +public abstract class TypeLiteral { + + private final Type type; + private final Class rawType; + + /** + * Constructs a new type literal. Derives represented class from type parameter. + * + *

      + * Clients create an empty anonymous subclass. Doing so embeds the type parameter in the + * anonymous class's type hierarchy so we can reconstitute it at runtime despite erasure. + * + * @since 19.0 + */ + @SuppressWarnings("unchecked") + protected TypeLiteral() { + this.type = extractLiteralType(this.getClass()); + this.rawType = (Class) extractRawType(type); + } + + private static Type extractLiteralType(@SuppressWarnings("rawtypes") Class literalClass) { + Type superType = literalClass.getGenericSuperclass(); + Type typeArgument = null; + while (true) { + if (superType instanceof ParameterizedType) { + ParameterizedType parametrizedType = (ParameterizedType) superType; + if (parametrizedType.getRawType() == TypeLiteral.class) { + // found + typeArgument = parametrizedType.getActualTypeArguments()[0]; + break; + } else { + throw new AssertionError("Unsupported type hierarchy for type literal."); + } + } else if (superType instanceof Class) { + if (superType == TypeLiteral.class) { + typeArgument = Object.class; + break; + } else { + superType = ((Class) superType).getGenericSuperclass(); + } + } else { + throw new AssertionError("Unsupported type hierarchy for type literal."); + } + } + return typeArgument; + } + + private static Class extractRawType(Type type) { + Class rawType; + if (type instanceof Class) { + rawType = (Class) type; + } else if (type instanceof ParameterizedType) { + rawType = (Class) ((ParameterizedType) type).getRawType(); + } else if (type instanceof GenericArrayType) { + rawType = arrayTypeFromComponentType(extractRawType(((GenericArrayType) type).getGenericComponentType())); + } else { + throw new IllegalArgumentException("Unsupported type: " + type); + } + return rawType; + } + + private static Class arrayTypeFromComponentType(Class componentType) { + return Array.newInstance(componentType, 0).getClass(); + } + + /** + * Returns the type literal including generic type information. + */ + public final Type getType() { + return this.type; + } + + /** + * Returns the raw class type of the literal. + */ + public final Class getRawType() { + return rawType; + } + + @Override + public final boolean equals(Object obj) { + return super.equals(obj); + } + + @Override + public final int hashCode() { + return super.hashCode(); + } + + @Override + public final String toString() { + return "TypeLiteral<" + type + ">"; + } + +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/ClassUtils.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/ClassUtils.java new file mode 100644 index 000000000000..ca7e2e1b658c --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/ClassUtils.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.resident; + +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.interpreter.DebuggerSupport; +import com.oracle.svm.jdwp.bridge.ClassStatusConstants; + +import jdk.vm.ci.meta.ResolvedJavaType; + +/** + * Utility methods for ResolvedJavaType. + */ +public final class ClassUtils { + + private ClassUtils() { + throw new UnsupportedOperationException("Do not instantiate"); + } + + /** + * Returns a String representation of the name of the class. + * + * @return the name of the class + */ + public static String getNameAsString(ResolvedJavaType type) { + return type.toClassName(); + } + + /** + * Returns the String representation of the type of the class. + * + * @return the class type descriptor name + */ + public static String getTypeAsString(ResolvedJavaType type) { + return type.getName(); // toJavaName(); + } + + /** + * Returns the String representation of the generic type of the class. + * + * @return the generic class type descriptor name + */ + public static String getGenericTypeAsString(@SuppressWarnings("unused") ResolvedJavaType type) { + // TODO(peterssen): GR-55068 The JDWP debugger should provide generic type names and + // signatures. + // An empty string means no information available. + return ""; + } + + /** + * @return the status according to ClassStatusConstants of the class + */ + public static int getStatus(@SuppressWarnings("unused") ResolvedJavaType type) { + Class clazz = DebuggerSupport.singleton().getUniverse().lookupClass(type); + + // Classes are always prepared and verified under closed-world assumptions. + int status = ClassStatusConstants.PREPARED | ClassStatusConstants.VERIFIED; + + DynamicHub hub = DynamicHub.fromClass(clazz); + if (hub.isLoaded()) { + // Just for completeness since ClassStatusConstants.LOADED is 0. + status |= ClassStatusConstants.LOADED; + } + if (hub.isInitialized()) { + status |= ClassStatusConstants.INITIALIZED; + } + if (hub.getClassInitializationInfo().isInErrorState()) { + status |= ClassStatusConstants.ERROR; + } + + return status; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/CopyNativeJDWPLibraryFeature.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/CopyNativeJDWPLibraryFeature.java new file mode 100644 index 000000000000..a7e381f11b0c --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/CopyNativeJDWPLibraryFeature.java @@ -0,0 +1,88 @@ +/* + * 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.jdwp.resident; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import jdk.graal.compiler.core.common.SuppressFBWarnings; +import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.options.OptionType; + +import com.oracle.svm.core.BuildArtifacts; +import com.oracle.svm.core.OS; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.option.HostedOptionKey; + +/** + * Copies {@code lib:svmjdwp} from the GraalVM native libraries, if it exists, to the native-image + * destination directory. + * + *

      + * This is an optional feature to improve usability; so that native images compiled with + * {@link JDWPOptions#JDWP} support are ready to be debugged right out-of-the-box. + * + *

      + * Disable with {@link Options#CopyNativeJDWPLibrary -H:-CopyNativeJDWPLibrary} to build + * {@code lib:svmjdwp} with JDWP support, otherwise it gets overwritten. + */ +@AutomaticallyRegisteredFeature +final class CopyNativeJDWPLibraryFeature implements InternalFeature { + + public static class Options { + @Option(help = "Copy lib:svmjdwp, if available, to the native image destination directory", type = OptionType.Expert)// + public static final HostedOptionKey CopyNativeJDWPLibrary = new HostedOptionKey<>(true); + } + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return JDWPOptions.JDWP.getValue() && Options.CopyNativeJDWPLibrary.getValue(); + } + + @Override + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "imagePath.getParent() is never null") + public void afterImageWrite(AfterImageWriteAccess access) { + Path javaHome = Paths.get(System.getProperty("java.home")); + String svmjdwpLibraryName = System.mapLibraryName("svmjdwp"); + Path svmjdwpLibraryPath = javaHome + .resolve(OS.WINDOWS.isCurrent() ? "bin" : "lib") + .resolve(svmjdwpLibraryName); + + if (Files.isRegularFile(svmjdwpLibraryPath)) { + Path imagePath = access.getImagePath(); + Path targetPath = imagePath.getParent().resolve(svmjdwpLibraryName); + try { + Files.copy(svmjdwpLibraryPath, targetPath, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new RuntimeException(e); + } + BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.SHARED_LIBRARY, targetPath); + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/DebuggingOnDemandHandler.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/DebuggingOnDemandHandler.java new file mode 100644 index 000000000000..b15b8f28f312 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/DebuggingOnDemandHandler.java @@ -0,0 +1,556 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.resident; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import com.oracle.svm.core.threadlocal.FastThreadLocalWord; +import com.oracle.svm.interpreter.DebuggerSupport; +import com.oracle.svm.jdwp.bridge.Logger; +import com.oracle.svm.jdwp.bridge.nativebridge.NativeObjectHandles; +import com.oracle.svm.jdwp.resident.impl.ResidentJDWP; +import jdk.graal.compiler.core.common.SuppressFBWarnings; +import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.nativeimage.Isolate; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.ProcessProperties; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.function.CEntryPoint; +import org.graalvm.nativeimage.c.function.CFunctionPointer; +import org.graalvm.nativeimage.c.function.InvokeCFunctionPointer; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.c.type.CTypeConversion; +import org.graalvm.word.PointerBase; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.OS; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.c.function.CEntryPointOptions; +import com.oracle.svm.core.headers.LibC; +import com.oracle.svm.core.jdk.PlatformNativeLibrarySupport; +import com.oracle.svm.core.jni.headers.JNIEnvironment; +import com.oracle.svm.core.jni.headers.JNIEnvironmentPointer; +import com.oracle.svm.core.jni.headers.JNIErrors; +import com.oracle.svm.core.jni.headers.JNIJavaVMInitArgs; +import com.oracle.svm.core.jni.headers.JNIJavaVMOption; +import com.oracle.svm.core.jni.headers.JNIJavaVMPointer; +import com.oracle.svm.core.jni.headers.JNIMethodId; +import com.oracle.svm.core.jni.headers.JNIObjectHandle; +import com.oracle.svm.core.jni.headers.JNIVersion; +import com.oracle.svm.core.log.Log; +import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.Interpreter; +import com.oracle.svm.interpreter.InterpreterOptions; +import com.oracle.svm.jdwp.bridge.jniutils.JNI; +import com.oracle.svm.jdwp.bridge.jniutils.JNIMethodScope; +import com.oracle.svm.jdwp.bridge.ArgFilesOption; +import com.oracle.svm.jdwp.bridge.DebugOptions.Options; +import com.oracle.svm.jdwp.bridge.JDWPEventHandlerBridge; +import com.oracle.svm.jdwp.bridge.NativeToHSJDWPEventHandlerBridge; +import com.oracle.svm.jdwp.bridge.ResidentJDWPFeatureEnabled; + +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.internal.misc.Signal; + +public class DebuggingOnDemandHandler implements Signal.Handler { + + private final Options options; + private static volatile Thread suspendThread; + + private static final int MAX_DEBUGGER_VM_OPTIONS = 32; + + public DebuggingOnDemandHandler(Options options) { + this.options = options; + } + + boolean first = true; + + private static JNI.JavaVM debuggerServerJavaVM; // Java VM running the debugger server + + private static final FastThreadLocalWord jniEnvPerThread = FastThreadLocalFactory.createWord("jniEnvPerThread"); + + public static JNI.JNIEnv currentThreadJniEnv() { + JNI.JNIEnv currentJniEnv = jniEnvPerThread.get(); + if (currentJniEnv.isNull()) { + JNI.JNIEnvPointer attachedJniEnvPointer = StackValue.get(JNI.JNIEnvPointer.class); + // Enter the debugger server (HotSpot or isolate) as a daemon thread. + debuggerServerJavaVM.getFunctions().getAttachCurrentThreadAsDaemon().call(debuggerServerJavaVM, attachedJniEnvPointer, WordFactory.nullPointer()); + currentJniEnv = attachedJniEnvPointer.readJNIEnv(); + jniEnvPerThread.set(currentJniEnv); + } + return currentJniEnv; + } + + @Override + public void handle(Signal sig) { + if (!first) { + ResidentJDWP.LOGGER.log("[DebuggingOnDemand] received USR2 signal, but JDWP server already spawned?"); + return; + } + first = false; + ResidentJDWP.LOGGER.log("[DebuggingOnDemand] received USR2 signal, spawning JDWP server"); + spawnJDWPThread(); + } + + private static class JDWPServerThread extends Thread { + private final Path libraryPath; + private final long initialThreadId; + private final Options options; + + JDWPServerThread(Path libraryPath, long initialThreadId, Options options) { + super("jdwp-server"); + this.libraryPath = libraryPath; + this.initialThreadId = initialThreadId; + this.options = options; + } + + @Override + public void run() { + DebuggingOnDemandHandler.spawnJDWPServer(libraryPath, initialThreadId, options); + ThreadStartDeathSupport.get().setDebuggerThreadServer(null); + } + } + + @SuppressFBWarnings(value = {"UW_UNCOND_WAIT", "WA_NOT_IN_LOOP"}, justification = "Intentional.") + public void spawnJDWPThread() { + long initialThreadId = JDWPBridgeImpl.getIds().getIdOrCreateWeak(Thread.currentThread()); + + // Try to find native library before spawning the JDWP server thread, to fail early in the + // case it cannot be found. + Path libraryPath = DebuggingOnDemandHandler.findLibraryPath(options); + assert Files.isRegularFile(libraryPath); + + Thread jdwpServerThread = new JDWPServerThread(libraryPath, initialThreadId, options); + ThreadStartDeathSupport.get().setDebuggerThreadServer(jdwpServerThread); + jdwpServerThread.setDaemon(true); + jdwpServerThread.start(); + if (options.suspend()) { + suspendThread = Thread.currentThread(); + synchronized (DebuggingOnDemandHandler.class) { + try { + DebuggingOnDemandHandler.class.wait(); + } catch (InterruptedException e) { + ResidentJDWP.LOGGER.log("[DebuggingOnDemand] Interrupted initial suspend."); + } + } + } + } + + public interface JNICreateJavaVMPointer extends CFunctionPointer { + @InvokeCFunctionPointer + int call(JNIJavaVMPointer jvmptr, JNIEnvironmentPointer env, JNIJavaVMInitArgs args); + } + + public interface CallStaticVoidMethodFunctionPointer extends CFunctionPointer { + @InvokeCFunctionPointer + void invoke(JNIEnvironment env, JNIObjectHandle objOrClass, JNIMethodId methodId); + } + + public interface CallStaticObjectMethodFunctionPointer extends CFunctionPointer { + @InvokeCFunctionPointer + JNI.JObject invoke(JNIEnvironment env, JNIObjectHandle objOrClass, JNIMethodId methodId); + } + + @SuppressWarnings("try") + public static void spawnJDWPServer(Path libraryPath, long initialThreadId, Options jdwpOptions) { + assert libraryPath != null; + ResidentJDWP.LOGGER = new Logger(jdwpOptions.tracing(), "[ResidentJDWP]", System.err); + + String debuggerArgs = jdwpOptions.vmOptions(); + List vmOptions = new ArrayList<>(); + if (debuggerArgs != null && !debuggerArgs.isBlank()) { + ArgFilesOption parser = new ArgFilesOption(); + for (String arg : debuggerArgs.split("(\\R|\\s)+")) { + vmOptions.addAll(parser.process(arg)); + } + } else { + if ("jvm".equals(jdwpOptions.mode())) { + throw new IllegalArgumentException( + "The JDWP 'vm.options' option is not set or empty. It must contain at least the -Djava.class.path for the debugger, note that it also supports @argFiles arguments."); + } + } + + if (vmOptions.size() > MAX_DEBUGGER_VM_OPTIONS) { + throw VMError.shouldNotReachHere("'vm.options' contains more than " + MAX_DEBUGGER_VM_OPTIONS + " elements (including @argFile expansion)"); + } + + try (CTypeConversion.CCharPointerHolder declaringClass = CTypeConversion.toCString("com/oracle/svm/jdwp/server/JDWPServer"); + CTypeConversion.CCharPointerHolder hsMethodName = CTypeConversion.toCString("createInstance"); + CTypeConversion.CCharPointerHolder hsSignature = CTypeConversion.toCString("()Lcom/oracle/svm/jdwp/bridge/JDWPEventHandlerBridge;"); + CTypeConversion.CCharPointerPointerHolder hsVMOptions = CTypeConversion.toCStrings(vmOptions.toArray(new String[0]))) { + + ResidentJDWP.LOGGER.log("Loading libraryPath=" + libraryPath); + ResidentJDWP.LOGGER.log("jdwpOptions=" + jdwpOptions.jdwpOptions()); + ResidentJDWP.LOGGER.log("additionalOptions=" + jdwpOptions.additionalOptions()); + + JNIJavaVMInitArgs args = StackValue.get(JNIJavaVMInitArgs.class); + + JNIJavaVMOption options; + args.setNOptions(vmOptions.size()); + // The number of elements must still be constant for the stack allocation. + options = StackValue.get(MAX_DEBUGGER_VM_OPTIONS, JNIJavaVMOption.class); + + for (int i = 0; i < vmOptions.size(); i++) { + CCharPointer option = hsVMOptions.get().read(i); + options.addressOf(i).setOptionString(option); + } + + args.setOptions(options); + args.setIgnoreUnrecognized(false); + + args.setVersion(JNIVersion.JNI_VERSION_10()); + args.setIgnoreUnrecognized(true); + + JNIJavaVMPointer jvmptr = StackValue.get(JNIJavaVMPointer.class); + JNIEnvironmentPointer dbgEnv = StackValue.get(JNIEnvironmentPointer.class); + + PlatformNativeLibrarySupport.NativeLibrary library = PlatformNativeLibrarySupport.singleton().createLibrary(libraryPath.toString(), false); + library.load(); + + DebuggingOnDemandHandler.JNICreateJavaVMPointer symCreateJavaVM = (DebuggingOnDemandHandler.JNICreateJavaVMPointer) library.findSymbol("JNI_CreateJavaVM"); + + int result = symCreateJavaVM.call(jvmptr, dbgEnv, args); + + if (result != JNIErrors.JNI_OK()) { + Log.log().string("CreateJavaVM failed: ").signed(result).newline(); + LibC.exit(LibC.EXIT_CODE_ABORT); + } + + debuggerServerJavaVM = (JNI.JavaVM) jvmptr.read(); + + JNIObjectHandle jhJDWPServerClass = dbgEnv.read().getFunctions().getFindClass().invoke(dbgEnv.read(), declaringClass.get()); + abortOnJNIException(dbgEnv); + + JNIMethodId jdwpCreateInstance = dbgEnv.read().getFunctions().getGetStaticMethodID().invoke(dbgEnv.read(), jhJDWPServerClass, hsMethodName.get(), hsSignature.get()); + abortOnJNIException(dbgEnv); + + DebuggingOnDemandHandler.CallStaticObjectMethodFunctionPointer entryPointFunctionPointer = (DebuggingOnDemandHandler.CallStaticObjectMethodFunctionPointer) dbgEnv.read().getFunctions() + .getCallStaticObjectMethod(); + JNI.JObject handle = entryPointFunctionPointer.invoke(dbgEnv.read(), jhJDWPServerClass, jdwpCreateInstance); + abortOnJNIException(dbgEnv); + + JDWPEventHandlerBridge jdwpEventHandler = NativeToHSJDWPEventHandlerBridge.createNativeToHS((JNI.JNIEnv) dbgEnv.read(), handle); + abortOnJNIException(dbgEnv); + + long isolate = CurrentIsolate.getIsolate().rawValue(); + + // Ensures that the callbacks To HotSpot have a JNIEnv available via JNIMethodScope. + Interpreter.DEBUGGER.setEventHandler(new EnterHSEventHandler(jdwpEventHandler)); + + ThreadStartDeathSupport.get().setListener(new ThreadStartDeathSupport.Listener() { + @Override + public void threadStarted() { + IsolateThread isolateThread = CurrentIsolate.getCurrentThread(); + Thread thread = ThreadStartDeathSupport.get().filterAppThread(isolateThread); + if (thread == null) { + return; + } + long threadId = JDWPBridgeImpl.getIds().toId(thread); + ResidentJDWP.LOGGER.log("[StartDeathSupport] threadStarted[" + threadId + "](" + thread.getName() + ")"); + try (JNIMethodScope ignored = new JNIMethodScope("JDWPServer::onThreadStart", currentThreadJniEnv())) { + jdwpEventHandler.onThreadStart(threadId); + } + } + + @Override + @Uninterruptible(reason = "Used in ThreadListenerSupport.", calleeMustBe = false) + public void threadDied() { + IsolateThread isolateThread = CurrentIsolate.getCurrentThread(); + // We need to inform the server through JNI, interruptible code is called + threadDied(isolateThread); + } + + private void threadDied(IsolateThread isolateThread) { + Thread thread = ThreadStartDeathSupport.get().filterAppThread(isolateThread); + if (thread == null) { + return; + } + long threadId = JDWPBridgeImpl.getIds().toId(thread); + ResidentJDWP.LOGGER.log("[StartDeathSupport] threadDied[" + threadId + "](" + thread.getName() + ")"); + try (JNIMethodScope ignored = new JNIMethodScope("JDWPServer::onThreadDeath", currentThreadJniEnv())) { + jdwpEventHandler.onThreadDeath(threadId); + } + } + + @Override + public void vmDied() { + ResidentJDWP.LOGGER.log("[StartDeathSupport] vmDied()"); + try (JNIMethodScope ignored = new JNIMethodScope("JDWPServer::onVMDeath", currentThreadJniEnv())) { + jdwpEventHandler.onVMDeath(); + } + } + + }); + + assert Interpreter.DEBUGGER.getEventHandler() != null; + Path metadataPath = DebuggerSupport.getMetadataFilePath(); + String metadataHashString = DebuggerSupport.getMetadataHashString(); + try (JNIMethodScope ignored = new JNIMethodScope("JDWPServer::spawnServer", currentThreadJniEnv())) { + long jdwpBridgeHandle = createJDWPBridgeHandle(); + jdwpEventHandler.spawnServer( + jdwpOptions.jdwpOptions(), + jdwpOptions.additionalOptions(), + isolate, + initialThreadId, + jdwpBridgeHandle, + metadataHashString, + metadataPath.toString(), + jdwpOptions.tracing()); + } + } + } + + /** + * Finds a Java home from environment variables. + * + * @return path to Java home, or {@code null} if cannot be found + */ + static Path findJavaHome() { + String javaHome = System.getenv("JDWP_SERVER_JAVA_HOME"); + if (javaHome == null) { + javaHome = System.getenv("GRAALVM_HOME"); + if (javaHome == null) { + javaHome = System.getenv("JAVA_HOME"); + } + } + return (javaHome != null) + ? Path.of(javaHome) + : null; + } + + /** + * Finds the given {@link System#mapLibraryName(String) library name} (file name), in the given + * search paths. The search paths are inspected in the given order, search paths can point to + * regular files or directories. + * + *

      + * A file search path is valid, if it exists and have the same file name as the specified + * library name. A directory search is valid, if it exists contains the specified library file + * name. + * + * @param libraryName file name of the library, platform-dependent, as given by + * {@link System#mapLibraryName(String)} + * @param throwIfNotFound flag to throw {@link IllegalArgumentException} if the library is not + * found + * @param searchPaths paths to search, files of directories + * @return the path of the library, guaranteed to be an existing regular file, or null if the + * library was not found (and {@code throwIfNotFound} is false). + * + * @throws IllegalArgumentException if {@code throwIfNotFound} and the library was not found in + * the given search paths + */ + private static Path findLibrary(String libraryName, boolean throwIfNotFound, List searchPaths) { + for (Path path : searchPaths) { + Path fileName = path.getFileName(); + if (Files.isRegularFile(path) && fileName != null && libraryName.equals(fileName.toString())) { + return path; + } else if (Files.isDirectory(path)) { + Path libraryPath = path.resolve(libraryName); + if (Files.isRegularFile(libraryPath)) { + return libraryPath; + } + } + } + + if (throwIfNotFound) { + throw new IllegalArgumentException(libraryName + " not found in search path: " + searchPaths); + } + + return null; + } + + /** + * Returns the platform-dependent path (Linux/Mac/Windows) to the native libraries directory in + * a Java home. + * + * @return path to the native libraries directory, or {@code null} if the Java home is + * {@code null} + */ + private static Path librariesInJavaHome(Path javaHome) { + if (javaHome == null) { + return null; + } + return javaHome.resolve(OS.WINDOWS.isCurrent() ? "bin" : "lib"); + } + + /** + * Returns the platform-dependent path (Linux/Mac/Windows) to {@code lib:jvm} library within a + * Java home. A Java home may contain several implementations of {@code lib:jvm} e.g. + * {@code "server" | "client" | "truffle"}, use {@code jvmSubDirectory} to select which one. + * + * @return path to the lib:jvm shared library, or {@code null} if the Java home is {@code null} + */ + private static Path jvmLibraryInJavaHome(Path javaHome, String jvmSubDirectory) { + Path libraries = librariesInJavaHome(javaHome); + if (libraries == null) { + return null; + } + return libraries.resolve(jvmSubDirectory); + } + + /** + * Returns the platform-dependent path (Linux/Mac/Windows) to {@code lib:jvm} library within a + * Java home. Picks the {@ocde lib:jvm} "server" configuration. + * + * @return path to the lib:jvm shared library, or {@code null} if the Java home is {@code null} + */ + private static Path jvmLibraryInJavaHome(Path javaHome) { + return jvmLibraryInJavaHome(javaHome, "server"); + } + + private static List filterValidPaths(Path... paths) { + List validPaths = new ArrayList<>(); + for (Path path : paths) { + if (path != null && Files.exists(path)) { + validPaths.add(path); + } + } + return validPaths; + } + + /** + * Finds the native library path used launch the JDWP server. Supports both + * {@link Options#mode() native or jvm modes}. + * + *

      + * In {@link Options#mode() native mode}, the search paths are as follows: + *

        + *
      1. The specified {@link Options#libraryPath() library path} + *
      2. The native executable directory, NOT the current working directory
      3. + *
      4. Assumes {@link Options#libraryPath() library path} is a Java home, search for + * {@code lib:svmjdwp} there. + *
      5. Finds a Java home from environment variables, search for {@code lib:svmjdwp} there. + *
      + * + *

      + * In {@link Options#mode() jvm mode}, the search paths are as follows: + *

        + *
      1. The specified {@link Options#libraryPath() library path} + *
      2. Assumes {@link Options#libraryPath() library path} is a Java home, search for + * {@code lib:jvm} there. + *
      3. Finds a Java home from environment variables, search for {@code lib:jvm} there. + *
      + * + * @throws IllegalArgumentException if the native library cannot be found + * + * @return path the to native library used to run the JDWP server, {@code lib:svmjdwp} or + * {@code lib:jvm} depending on the {@link Options#mode()} + */ + private static Path findLibraryPath(Options jdwpOptions) { + Path libraryPath = null; + if (jdwpOptions.libraryPath() != null) { + libraryPath = Path.of(jdwpOptions.libraryPath()); + } + + String libraryName; + List searchPaths; + if ("jvm".equals(jdwpOptions.mode())) { + libraryName = System.mapLibraryName("jvm"); + searchPaths = filterValidPaths( + libraryPath, + jvmLibraryInJavaHome(libraryPath), + jvmLibraryInJavaHome(findJavaHome())); + } else { + assert "native".equals(jdwpOptions.mode()); + libraryName = System.mapLibraryName("svmjdwp"); + searchPaths = filterValidPaths( + libraryPath, + Path.of(ProcessProperties.getExecutableName()).getParent(), + librariesInJavaHome(libraryPath), + librariesInJavaHome(findJavaHome())); + } + + return findLibrary(libraryName, true, searchPaths); + } + + private static long createJDWPBridgeHandle() { + VMError.guarantee(JDWPOptions.JDWP.getValue()); + VMError.guarantee(InterpreterOptions.DebuggerWithInterpreter.getValue()); + return NativeObjectHandles.create(new JDWPBridgeImpl()); + } + + static long toPrimitiveOrId(JavaKind kind, Object object) { + return switch (kind) { + case Boolean -> JavaConstant.forBoolean((boolean) object).getRawValue(); + case Byte -> JavaConstant.forByte((byte) object).getRawValue(); + case Short -> JavaConstant.forShort((short) object).getRawValue(); + case Char -> JavaConstant.forChar((char) object).getRawValue(); + case Int -> JavaConstant.forInt((int) object).getRawValue(); + case Float -> JavaConstant.forFloat((float) object).getRawValue(); + case Long -> JavaConstant.forLong((long) object).getRawValue(); + case Double -> JavaConstant.forDouble((double) object).getRawValue(); + case Object -> JDWPBridgeImpl.getIds().toId(object); + case Void -> { + assert object == null; + yield 0L; // null + } + case Illegal -> throw VMError.shouldNotReachHere("illegal return kind"); + }; + } + + private static void abortOnJNIException(JNIEnvironmentPointer dbgEnv) { + if (dbgEnv.read().getFunctions().getExceptionCheck().invoke(dbgEnv.read())) { + dbgEnv.read().getFunctions().getExceptionDescribe().invoke(dbgEnv.read()); + LibC.exit(LibC.EXIT_CODE_ABORT); + } + } + + public static boolean suspendDoneOnShellSide() { + Thread t = suspendThread; + suspendThread = null; + if (t != null) { + t.interrupt(); + return true; + } else { + return false; + } + } + + /* + * GR-55105: This shouldn't be needed. Only inner classes are processed by + * com.oracle.svm.hosted.ImageClassLoader#findSystemElements for some reason. + */ + @SuppressWarnings("unused") + private static class EntryPointHolder { + @Uninterruptible(reason = "Dummy symbol to make HotSpot's native method linking to look for symbols in the main executable. Required to run the JDWP server (debugger) on HotSpot") + @CEntryPoint(name = "JNI_OnLoad_DEFAULT_NAMESPACE", include = ResidentJDWPFeatureEnabled.class) + @CEntryPointOptions(prologue = CEntryPointOptions.NoPrologue.class, epilogue = CEntryPointOptions.NoEpilogue.class) + @SuppressWarnings("unused") + public static int onLoadDefaultNamespace(JNI.JavaVM vm, PointerBase reserved) { + return JNIVersion.JNI_VERSION_10(); + } + + @CEntryPoint(name = "Java_com_oracle_svm_jdwp_bridge_JDWPJNIConfig_attachCurrentThread", // + builtin = CEntryPoint.Builtin.ATTACH_THREAD, // + include = ResidentJDWPFeatureEnabled.class) + @SuppressWarnings("unused") + public static native IsolateThread attachCurrentThread(JNI.JNIEnv jniEnv, JNI.JClass clazz, Isolate isolate); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/DebuggingOnDemandHook.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/DebuggingOnDemandHook.java new file mode 100644 index 000000000000..913c745d30af --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/DebuggingOnDemandHook.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.resident; + +import com.oracle.svm.core.jdk.RuntimeSupport; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.MetadataUtil; +import com.oracle.svm.jdwp.bridge.DebugOptions; + +public class DebuggingOnDemandHook implements RuntimeSupport.Hook { + + static final String ADDITIONAL_JDWP_OPTIONS_ENV_VARIABLE = "_NATIVE_JDWP_OPTIONS"; + + @Override + public void execute(boolean isFirstIsolate) { + // TODO(peterssen): GR-55057 Support attaching the JDWP debugger to any isolate, not only + // the first. + if (isFirstIsolate) { + + String jdwpOptions = JDWPOptions.JDWPOptions.getValue(); + if (jdwpOptions == null) { + // Debugger not requested. + return; + } + + // Options can be added externally via this environment variable. Anything contained in + // it will get a comma prepended to it (if needed), then it will be added to the end of + // the JDWP options. + // This mimics the _JAVA_JDWP_OPTIONS env. variable used in HotSpot. + // Note that a different variable name is used here to avoid collisions with options + // meant for the HotSpot JDWP agent. + String nativeJDWPOptions = System.getenv(ADDITIONAL_JDWP_OPTIONS_ENV_VARIABLE); + DebugOptions.Options options = null; + try { + options = DebugOptions.parse(jdwpOptions, nativeJDWPOptions, true, JDWPOptions.JDWPTrace.getValue()); + } catch (IllegalArgumentException e) { + String errorMessage = MetadataUtil.fmt("ERROR: JDWP option syntax error: %s %s=%s", + jdwpOptions, + ADDITIONAL_JDWP_OPTIONS_ENV_VARIABLE, + nativeJDWPOptions); + System.err.println(errorMessage); + System.exit(1); + } + + // TODO(peterssen): GR-55061 Add support for starting JDWP debugging on signal. + // Debug on signal (USR2) allowing to attach to a running executable, + // but it's not clear to me how this is supposed to work and what's the tooling + // involved. + // Signal.handle(new Signal("USR2"), new DebuggingOnDemandHandler(options)); + new DebuggingOnDemandHandler(options).spawnJDWPThread(); + } else { + VMError.shouldNotReachHere("debugging only works in main isolate, for now"); + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/EnterHSEventHandler.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/EnterHSEventHandler.java new file mode 100644 index 000000000000..b8b3128b7fcb --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/EnterHSEventHandler.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023, 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.jdwp.resident; + +import com.oracle.svm.interpreter.debug.EventHandler; +import com.oracle.svm.jdwp.bridge.jniutils.JNIMethodScope; +import com.oracle.svm.jdwp.bridge.EventHandlerBridge; +import com.oracle.svm.jdwp.bridge.TagConstants; +import com.oracle.svm.jdwp.bridge.TypeTag; + +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + +public class EnterHSEventHandler implements EventHandler { + private final EventHandlerBridge jdwpEventHandler; + + public EnterHSEventHandler(EventHandlerBridge jdwpEventHandler) { + this.jdwpEventHandler = jdwpEventHandler; + } + + @SuppressWarnings("try") + @Override + public void onEventAt(Thread thread, ResolvedJavaMethod method, int bci, Object returnValue, int eventKindFlags) { + try (JNIMethodScope ignored = new JNIMethodScope("JDWPServer::onEventAt", DebuggingOnDemandHandler.currentThreadJniEnv())) { + long threadId = JDWPBridgeImpl.getIds().toId(thread); + long methodId = JDWPBridgeImpl.getIds().toId(method); + ResolvedJavaType declaringType = method.getDeclaringClass(); + long classId = JDWPBridgeImpl.getIds().toId(declaringType); + byte typeTag = TypeTag.getKind(declaringType); + JavaKind returnKind = method.getSignature().getReturnKind(); + long returnPrimitiveOrId; + byte returnTag; + if (returnValue == null) { + returnPrimitiveOrId = 0; + if (JavaKind.Void == returnKind) { + returnTag = TagConstants.VOID; + } else { + returnTag = TagConstants.OBJECT; + } + } else { + returnPrimitiveOrId = DebuggingOnDemandHandler.toPrimitiveOrId(returnKind, returnValue); + Class returnClass = switch (returnKind) { + case Byte -> Byte.TYPE; + case Boolean -> Boolean.TYPE; + case Char -> Character.TYPE; + case Short -> Short.TYPE; + case Int -> Integer.TYPE; + case Float -> Float.TYPE; + case Long -> Long.TYPE; + case Double -> Double.TYPE; + case Void -> Void.TYPE; + default -> returnValue.getClass(); + }; + returnTag = TagConstants.getTagFromClass(returnClass); + } + assert TagConstants.isValidTag(returnTag) : returnTag; + jdwpEventHandler.onEventAt(threadId, classId, typeTag, methodId, bci, returnTag, returnPrimitiveOrId, eventKindFlags); + } + } + +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/JDWPBridgeImpl.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/JDWPBridgeImpl.java new file mode 100644 index 000000000000..fc836e993406 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/JDWPBridgeImpl.java @@ -0,0 +1,408 @@ +/* + * Copyright (c) 2023, 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.jdwp.resident; + +import com.oracle.svm.core.code.FrameInfoQueryResult; +import com.oracle.svm.core.code.FrameSourceInfo; +import com.oracle.svm.core.heap.VMOperationInfos; +import com.oracle.svm.core.interpreter.InterpreterFrameSourceInfo; +import com.oracle.svm.core.thread.JavaVMOperation; +import com.oracle.svm.core.thread.PlatformThreads; +import com.oracle.svm.core.thread.ThreadSuspendSupport; +import com.oracle.svm.core.thread.VMOperation; +import com.oracle.svm.core.thread.VMThreads; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.DebuggerSupport; +import com.oracle.svm.interpreter.debug.DebuggerEvents; +import com.oracle.svm.interpreter.debug.EventKind; +import com.oracle.svm.interpreter.debug.Location; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterUniverse; +import com.oracle.svm.interpreter.metadata.MetadataUtil; +import com.oracle.svm.jdwp.bridge.ErrorCode; +import com.oracle.svm.jdwp.bridge.JDWP; +import com.oracle.svm.jdwp.bridge.JDWPBridge; +import com.oracle.svm.jdwp.bridge.JDWPException; +import com.oracle.svm.jdwp.bridge.Packet; +import com.oracle.svm.jdwp.bridge.StackFrame; +import com.oracle.svm.jdwp.bridge.TypeTag; +import com.oracle.svm.jdwp.resident.impl.AllJavaFramesVisitor; +import com.oracle.svm.jdwp.resident.impl.ResidentJDWP; +import com.oracle.svm.jdwp.resident.impl.SafeStackWalker; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.IsolateThread; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.OptionalInt; + +public final class JDWPBridgeImpl implements JDWPBridge { + + JDWP impl = new ResidentJDWP(); + + private static final ObjectIdMap IDS = new ObjectIdMap(); + + public static ObjectIdMap getIds() { + return IDS; + } + + private static final DebuggerEvents DEBUGGER = MetadataUtil.requireNonNull(ImageSingletons.lookup(DebuggerEvents.class)); + + @Override + public void setEventEnabled(long threadId, int eventKind, boolean enable) { + DEBUGGER.setEventEnabled(getIds().toObject(threadId, Thread.class), EventKind.fromOrdinal(eventKind), enable); + } + + @Override + public boolean isEventEnabled(long threadId, int eventKind) { + return DEBUGGER.isEventEnabled(getIds().toObject(threadId, Thread.class), EventKind.fromOrdinal(eventKind)); + } + + @Override + public void toggleBreakpoint(long methodId, int bci, boolean enable) { + DEBUGGER.toggleBreakpoint(getIds().toObject(methodId, ResolvedJavaMethod.class), bci, enable); + } + + @Override + public void toggleMethodEnterEvent(long clazzId, boolean enable) { + DEBUGGER.toggleMethodEnterEvent(getIds().toObject(clazzId, ResolvedJavaType.class), enable); + } + + @Override + public void toggleMethodExitEvent(long clazzId, boolean enable) { + DEBUGGER.toggleMethodExitEvent(getIds().toObject(clazzId, ResolvedJavaType.class), enable); + } + + @Override + public void setSteppingFromLocation(long threadId, int depth, int size, long methodId, int bci, int lineNumber) { + DEBUGGER.setSteppingFromLocation(getIds().toObject(threadId, Thread.class), depth, size, Location.create(getIds().toObject(methodId, ResolvedJavaMethod.class), bci, lineNumber)); + } + + @Override + public void clearStepping(long threadId) { + DEBUGGER.clearStepping(getIds().toObject(threadId, Thread.class)); + } + + @Override + public Packet dispatch(Packet packet) throws JDWPException { + return impl.dispatch(packet); + } + + @Override + public int getThreadStatus(long threadId) { + Thread thread = ResidentJDWP.getThread(threadId); + return JDWPThreadStatus.getThreadStatus(thread); + } + + @Override + public long threadSuspend(long threadId) { + return threadSuspendOrResume(true, threadId); + } + + @Override + public long threadResume(long threadId) { + return threadSuspendOrResume(false, threadId); + } + + private static long threadSuspendOrResume(boolean suspend, long threadId) { + Thread thread = ResidentJDWP.getThread(threadId); + if (!thread.isAlive()) { + // An invalid thread (zombie) + return -1; + } + if (suspend) { + ThreadSuspendSupport.suspend(thread); + } else { + ThreadSuspendSupport.resume(thread); + } + return 1; + } + + @Override + public void setThreadRequest(boolean start, boolean enable) { + ThreadStartDeathSupport.get().setListeningOn(start, enable); + } + + @Override + public long[] vmSuspend(long[] ignoredThreadIds) { + SuspendAllOperation operation = new SuspendAllOperation(ignoredThreadIds); + operation.enqueue(); + return operation.getSuspendedThreadIds(); + } + + @Override + public void vmResume(long[] resumeThreadIds) { + if (!DebuggingOnDemandHandler.suspendDoneOnShellSide()) { + ResumeAllOperation operation = new ResumeAllOperation(resumeThreadIds); + operation.enqueue(); + } + } + + private static class SuspendAllOperation extends JavaVMOperation { + + private final Thread[] ignoredThreads; + private Thread[] suspendedThreads; + private int suspendedThreadsCount; + + SuspendAllOperation(long[] ignoredThreadIds) { + super(VMOperationInfos.get(SuspendAllOperation.class, "All Threads Suspend", VMOperation.SystemEffect.SAFEPOINT)); + ignoredThreads = new Thread[ignoredThreadIds.length]; + for (int i = 0; i < ignoredThreadIds.length; i++) { + ignoredThreads[i] = JDWPBridgeImpl.getIds().toObject(ignoredThreadIds[i], Thread.class); + } + } + + @Override + protected void operate() { + Thread[] threads = new Thread[10]; + int i = 0; + threadsLoop: for (IsolateThread thread = VMThreads.firstThreadUnsafe(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { + Thread t = ThreadStartDeathSupport.get().filterAppThread(thread); + if (t == null || PlatformThreads.getThreadStatus(t) == com.oracle.svm.core.thread.ThreadStatus.TERMINATED) { + continue; + } + for (Thread ignoredThread : ignoredThreads) { + if (ignoredThread == t) { + continue threadsLoop; + } + } + ThreadSuspendSupport.suspend(t); + if (i >= threads.length) { + threads = Arrays.copyOf(threads, i + i / 2); + } + threads[i++] = t; + } + suspendedThreads = threads; + suspendedThreadsCount = i; + } + + long[] getSuspendedThreadIds() { + long[] ids = new long[suspendedThreadsCount]; + for (int i = 0; i < ids.length; i++) { + ids[i] = JDWPBridgeImpl.getIds().getIdOrCreateWeak(suspendedThreads[i]); + } + return ids; + } + } + + private static class ResumeAllOperation extends JavaVMOperation { + + private final Thread[] resumeThreads; + + ResumeAllOperation(long[] resumeThreadIds) { + super(VMOperationInfos.get(ResumeAllOperation.class, "All Threads Resume", VMOperation.SystemEffect.SAFEPOINT)); + resumeThreads = new Thread[resumeThreadIds.length]; + for (int i = 0; i < resumeThreadIds.length; i++) { + resumeThreads[i] = JDWPBridgeImpl.getIds().toObject(resumeThreadIds[i], Thread.class); + } + } + + @Override + protected void operate() { + for (Thread thread : resumeThreads) { + if (thread == null) { + continue; + } + ThreadSuspendSupport.resume(thread); + } + } + } + + @Override + public String getSystemProperty(String key) { + return System.getProperty(key); + } + + @Override + public long typeRefIndexToId(int typeRefIndex) { + ResolvedJavaType typeAtIndex = DebuggerSupport.singleton().getUniverse().getTypeAtIndex(typeRefIndex); + return getIds().getIdOrCreateWeak(typeAtIndex); + } + + @Override + public long fieldRefIndexToId(int fieldRefIndex) { + ResolvedJavaField fieldAtIndex = DebuggerSupport.singleton().getUniverse().getFieldAtIndex(fieldRefIndex); + return getIds().getIdOrCreateWeak(fieldAtIndex); + } + + @Override + public long methodRefIndexToId(int methodRefIndex) { + ResolvedJavaMethod methodAtIndex = DebuggerSupport.singleton().getUniverse().getMethodAtIndex(methodRefIndex); + return getIds().getIdOrCreateWeak(methodAtIndex); + } + + @Override + public int typeRefIdToIndex(long typeRefId) { + ResolvedJavaType resolvedJavaType = null; + try { + resolvedJavaType = getIds().toObject(typeRefId, ResolvedJavaType.class); + } catch (ClassCastException e) { + throw JDWPException.raise(ErrorCode.INVALID_CLASS); + } + if (resolvedJavaType == null) { + throw JDWPException.raise(ErrorCode.INVALID_OBJECT); + } + OptionalInt methodRefIndex = DebuggerSupport.singleton().getUniverse().getTypeIndexFor(resolvedJavaType); + return methodRefIndex.orElseThrow(() -> JDWPException.raise(ErrorCode.INVALID_CLASS)); + } + + @Override + public int fieldRefIdToIndex(long fieldRefId) { + ResolvedJavaField resolvedJavaField = null; + try { + resolvedJavaField = getIds().toObject(fieldRefId, ResolvedJavaField.class); + } catch (ClassCastException e) { + throw JDWPException.raise(ErrorCode.INVALID_FIELDID); + } + if (resolvedJavaField == null) { + throw JDWPException.raise(ErrorCode.INVALID_OBJECT); + } + OptionalInt typeRefIndex = DebuggerSupport.singleton().getUniverse().getFieldIndexFor(resolvedJavaField); + return typeRefIndex.orElseThrow(() -> JDWPException.raise(ErrorCode.INVALID_FIELDID)); + } + + @Override + public int methodRefIdToIndex(long methodRefId) { + ResolvedJavaMethod resolvedJavaMethod = null; + try { + resolvedJavaMethod = getIds().toObject(methodRefId, ResolvedJavaMethod.class); + } catch (ClassCastException e) { + throw JDWPException.raise(ErrorCode.INVALID_METHODID); + } + if (resolvedJavaMethod == null) { + throw JDWPException.raise(ErrorCode.INVALID_OBJECT); + } + OptionalInt methodRefIndex = DebuggerSupport.singleton().getUniverse().getMethodIndexFor(resolvedJavaMethod); + return methodRefIndex.orElseThrow(() -> JDWPException.raise(ErrorCode.INVALID_METHODID)); + } + + @Override + public String currentWorkingDirectory() { + return Path.of("").toAbsolutePath().toString(); + } + + @Override + public int[] typeStatus(long... typeIds) { + int[] result = new int[typeIds.length]; + for (int i = 0; i < typeIds.length; i++) { + long typeId = typeIds[i]; + Object type = getIds().getObject(typeId); + if (type instanceof ResolvedJavaType resolvedJavaType) { + // Check that the type is part of the interpreter universe, e.g. arbitrary + // ResolvedJavaType instances are not valid. + OptionalInt typeIndex = DebuggerSupport.singleton().getUniverse().getTypeIndexFor(resolvedJavaType); + if (typeIndex.isEmpty()) { + throw JDWPException.raise(ErrorCode.INVALID_CLASS); + } + result[i] = ClassUtils.getStatus(resolvedJavaType); + } else { + throw JDWPException.raise(ErrorCode.INVALID_CLASS); + } + } + return result; + } + + @Override + public StackFrame[] getThreadFrames(long threadId) { + Thread targetThread = JDWPBridgeImpl.getIds().toObject(threadId, Thread.class); + AllJavaFramesVisitor getAllFrames = new AllJavaFramesVisitor(true); + SafeStackWalker.safeStackWalk(targetThread, getAllFrames); + + List frames = getAllFrames.getFrames(); + + StackFrame[] stackFrames = new StackFrame[frames.size()]; + for (int i = 0; i < stackFrames.length; i++) { + FrameSourceInfo frameInfo = frames.get(i); + + Class sourceClass = frameInfo.getSourceClass(); + ResolvedJavaType sourceType = DebuggerSupport.singleton().getUniverse().lookupType(sourceClass); + ResolvedJavaMethod sourceMethod = findSourceMethod(sourceType, frameInfo); + + byte typeTag = TypeTag.getKind(sourceType); + long classId = JDWPBridgeImpl.getIds().getIdOrCreateWeak(sourceType); + long methodId = JDWPBridgeImpl.getIds().getIdOrCreateWeak(sourceMethod); + int bci = frameInfo.getBci(); + + // Workaround for JDI crash, causes by illegal BCI on stack traces. + // SVM native C API may produce non-native methods that have no bytecodes, but report + // BCI=1 on stack traces. + // Ensure that methods without bytecodes always report unknown BCI in stack traces. + if (!sourceMethod.hasBytecodes()) { + bci = -1; + } + + int frameDepth = i; + stackFrames[i] = new StackFrame(typeTag, classId, methodId, bci, frameDepth); + } + + return stackFrames; + } + + @Override + public long getCurrentThis() { + Object thisObject = ResidentJDWP.getThis(Thread.currentThread(), 0); + return JDWPBridgeImpl.getIds().getIdOrCreateWeak(thisObject); + } + + @Override + public boolean isCurrentThreadVirtual() { + return Thread.currentThread().isVirtual(); + } + + /** + * Returns the {@link InterpreterResolvedJavaMethod} associated with the given stack frame. + * + *

      + * Interpreter frames contains a direct reference to the associated + * {@link InterpreterResolvedJavaMethod}. Compiled frames contains a + * {@link FrameInfoQueryResult#getSourceMethodId()} that can be used to obtain the interpreter + * method via {@link InterpreterUniverse#getMethodFromMethodId(int)}. + */ + public static ResolvedJavaMethod findSourceMethod(ResolvedJavaType sourceType, FrameSourceInfo frameInfo) { + if (frameInfo instanceof InterpreterFrameSourceInfo interpreterFrameSourceInfo) { + // Interpreter frames contain the interpreted method. + assert sourceType.equals(interpreterFrameSourceInfo.getInterpretedMethod().getDeclaringClass()); + return interpreterFrameSourceInfo.getInterpretedMethod(); + } else if (frameInfo instanceof FrameInfoQueryResult compiledFrameInfo) { + // Compiled frames have a methodId. + int sourceMethodId = compiledFrameInfo.getSourceMethodId(); + if (sourceMethodId != 0) { + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + ResolvedJavaMethod interpreterMethod = universe.getMethodFromMethodId(sourceMethodId); + if (interpreterMethod != null) { + assert sourceType.equals(interpreterMethod.getDeclaringClass()); + assert interpreterMethod.getName().equals(frameInfo.getSourceMethodName()); + return interpreterMethod; + } + } + } + + throw VMError.shouldNotReachHere("Cannot find method " + frameInfo.getSourceMethodName() + " in class " + frameInfo.getSourceClassName() + " at line " + frameInfo.getSourceLineNumber()); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/JDWPOptions.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/JDWPOptions.java new file mode 100644 index 000000000000..90dad8cb5f08 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/JDWPOptions.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.resident; + +import com.oracle.svm.interpreter.InterpreterOptions; + +import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.option.RuntimeOptionKey; + +import com.oracle.svm.interpreter.debug.DebuggerEventsFeature; +import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.options.OptionKey; +import jdk.graal.compiler.options.OptionType; +import org.graalvm.collections.EconomicMap; + +public final class JDWPOptions { + + @Option(help = "Include JDWP support in the native executable", type = OptionType.Expert)// + public static final HostedOptionKey JDWP = new HostedOptionKey<>(false) { + + @Override + protected void onValueUpdate(EconomicMap, Object> values, Boolean oldValue, Boolean newValue) { + super.onValueUpdate(values, oldValue, newValue); + if (newValue) { + InterpreterOptions.DebuggerWithInterpreter.update(values, true); + DebuggerEventsFeature.DebuggerOptions.DebuggerEvents.update(values, true); + } + } + }; + + @Option(help = "Specify JDWP options")// + public static final RuntimeOptionKey JDWPOptions = new RuntimeOptionKey<>(null); + + @Option(help = "Enable JDWP specifc logging", type = OptionType.Expert) // + public static final RuntimeOptionKey JDWPTrace = new RuntimeOptionKey<>(false); +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/JDWPThreadStatus.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/JDWPThreadStatus.java new file mode 100644 index 000000000000..b133d5ac793c --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/JDWPThreadStatus.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.resident; + +import com.oracle.svm.core.thread.PlatformThreads; +import com.oracle.svm.core.thread.ThreadStatus; + +public enum JDWPThreadStatus { + + ZOMBIE(0), + RUNNING(1), + SLEEPING(2), + MONITOR(3), + WAIT(4), + NOT_STARTED(5); + + public final int value; + + JDWPThreadStatus(int value) { + this.value = value; + } + + public static int getThreadStatus(Thread thread) { + int state = PlatformThreads.getThreadStatus(thread); + JDWPThreadStatus status = switch (state) { + case ThreadStatus.NEW -> NOT_STARTED; + case ThreadStatus.BLOCKED_ON_MONITOR_ENTER -> MONITOR; + case ThreadStatus.IN_OBJECT_WAIT, ThreadStatus.IN_OBJECT_WAIT_TIMED, ThreadStatus.PARKED, ThreadStatus.PARKED_TIMED -> WAIT; + case ThreadStatus.SLEEPING -> SLEEPING; + case ThreadStatus.TERMINATED -> ZOMBIE; + default -> RUNNING; + }; + return status.value; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/ObjectIdMap.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/ObjectIdMap.java new file mode 100644 index 000000000000..557bc8bd4123 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/ObjectIdMap.java @@ -0,0 +1,888 @@ +/* + * Copyright (c) 2023, 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.jdwp.resident; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; + +import jdk.internal.misc.Unsafe; + +import jdk.graal.compiler.core.common.SuppressFBWarnings; +import org.graalvm.nativeimage.ImageSingletons; + +/** + * Mapping of objects to unique IDs and back. Allows concurrent access and automatically disposes + * mapping of GCed values. IDs are linearly increasing long values. + *

      + * This is a lock-free implementation. The {@link LockFreeHashMap} accesses the hash table via an + * 'accessFlag', which assures exclusive resize of the table. The resize is done by prepending a + * bigger hash table to the table chain. After resize, new objects are always put to the first and + * the biggest table. + *

      + * Objects are stored in arrays of {@link HashNode} nodes. The arrays must not be muted to assure + * data consistency. When nodes need to be changed, a new array is created and is safely (CAS) + * replaced in the table index. + *

      + * Every inserted object gets a unique ID assigned. The ID is generated only after the object's node + * is stored in a table to assure uniqueness of IDs. See + * {@link HashNode#finalizeId(ObjectIdMap.LockFreeHashMap, ObjectIdMap.LockFreeHashMap.HashingTable)}. + */ +public final class ObjectIdMap { + + private static final int INITIAL_SIZE_BITS = 10; // The first table has size 2^INITIAL_SIZE_BITS + // An attemt to resize when the current resizeCount % (table.length/RESIZE_ATTEMPT) == 0 + private static final int RESIZE_ATTEMPT = 16; + + private volatile LockFreeHashMap map; + private static final long mapOffset = Unsafe.getUnsafe().objectFieldOffset(ObjectIdMap.class, "map"); + + private static long getObjectArrayByteOffset(long index) { + int offset = Unsafe.getUnsafe().arrayBaseOffset(Object[].class); + int scale = Unsafe.getUnsafe().arrayIndexScale(Object[].class); + try { + return Math.addExact(offset, Math.multiplyExact(index, scale)); + } catch (ArithmeticException ex) { + throw new IndexOutOfBoundsException(index); + } + } + + @SuppressWarnings("unchecked") + private static T getElementVolatile(T[] array, long index) { + long arrayByteOffset = getObjectArrayByteOffset(index); + return (T) Unsafe.getUnsafe().getReferenceVolatile(array, arrayByteOffset); + } + + private static boolean compareAndSetElement(Object[] array, long index, Object existingElement, Object newElement) { + long arrayByteOffset = getObjectArrayByteOffset(index); + return Unsafe.getUnsafe().compareAndSetReference(array, arrayByteOffset, existingElement, newElement); + } + + private LockFreeHashMap getMap() { + LockFreeHashMap theMap = map; + if (theMap == null) { + // We have no map yet + theMap = new LockFreeHashMap(); + LockFreeHashMap oldMap = (LockFreeHashMap) Unsafe.getUnsafe().compareAndExchangeReference(this, mapOffset, null, theMap); + if (oldMap != null) { + // It was set already + theMap = oldMap; + } + } + return theMap; + } + + /** + * Returns the ID, or -1 when the object is not tracked. + */ + public long getIdExisting(Object obj) { + if (obj == null) { + return 0; + } + return getMap().getIdExisting(obj); + } + + public long getIdOrCreateWeak(Object obj) { + return getMap().getIdOrCreateWeak(obj); + } + + public Object getObject(long id) { + if (id == 0) { + return null; + } + return getMap().getObject(id); + } + + /** + * Decreases the hold count by one, replaces HashNodeStrong with HashNodeWeak when holdCount is + * zero. + * + * @param id The object id + * @return true when the object reference exists. + */ + public boolean enableCollection(long id) { + return enableCollection(id, 1, false); + } + + /** + * Decreases the hold count by {@code disposeIfNotHold} and when holdCount is zero then either + * dispose the node, or replace HashNodeStrong with HashNodeWeak. + * + * @param id The object id + * @param refCount the count to decrease the hold count by. + * @param disposeIfNotHold whether to dispose the object ID when hold count decrements to zero. + * @return true when the object reference existed. + */ + public boolean enableCollection(long id, int refCount, boolean disposeIfNotHold) { + return getMap().enableCollection(id, refCount, disposeIfNotHold); + } + + /** + * Increases the hold count by one, replaces HashNodeWeak with HashNodeStrong when holdCount is + * zero. + * + * @param id The object id + * @return true when the object reference exists. + */ + public boolean disableCollection(long id) { + return getMap().disableCollection(id); + } + + /** + * Check if the object was collected. + * + * @param id The object id. + * @return true when the object was collected, false when the object + * still exists in memory, null when the id never referenced an object. + */ + @SuppressFBWarnings(value = "NP_BOOLEAN_RETURN_NULL", justification = "Intentional.") + public Boolean isCollected(long id) { + return getMap().isCollected(id); + } + + public void reset() { + LockFreeHashMap theMap; + do { + theMap = map; + if (theMap == null) { + return; + } + } while (!Unsafe.getUnsafe().compareAndSetReference(this, mapOffset, theMap, null)); + // Reset 'theMap', it will not be used any more + theMap.reset(); + } + + public T toObject(long objectId, Class targetClass) { + Object object = getObject(objectId); + return targetClass.cast(object); + } + + public long toId(Object object) { + return getIdOrCreateWeak(object); + } + + private static class LockFreeHashMap { + + private volatile TableAccessFlag accessFlag; + private static final long accessFlagOffset = Unsafe.getUnsafe().objectFieldOffset(LockFreeHashMap.class, "accessFlag"); + private final ReferenceQueue refQueue = new ReferenceQueue<>(); + private volatile Thread refQueueThread; + private volatile long lastId; + private static final long lastIdOffset = Unsafe.getUnsafe().objectFieldOffset(LockFreeHashMap.class, "lastId"); + + LockFreeHashMap() { + } + + long getNextId() { + long id = lastId; + do { + long witnessId = Unsafe.getUnsafe().compareAndExchangeLong(this, lastIdOffset, id, id + 1); + if (witnessId != id) { + // Try again + id = witnessId; + } else { + // id + 1 was written successfully + return id + 1; + } + } while (true); + } + + private Thread startCleanupThread() { + Thread queueThread = Thread.ofPlatform().name("JDWP Object map cleanup queue").unstarted(() -> { + while (true) { + try { + Reference ref = refQueue.remove(); + HashNode node = (HashNodeWeak) ref; + dispose(node); + } catch (InterruptedException ex) { + break; + } + } + }); + if (ImageSingletons.contains(ThreadStartDeathSupport.class)) { + ThreadStartDeathSupport.get().setDebuggerThreadObjectQueue(queueThread); + } + queueThread.setDaemon(true); + queueThread.start(); + return queueThread; + } + + void reset() { + Thread queueThread = refQueueThread; + if (queueThread != null) { + queueThread.interrupt(); + if (ImageSingletons.contains(ThreadStartDeathSupport.class)) { + ThreadStartDeathSupport.get().setDebuggerThreadObjectQueue(null); + } + } + // GC of this object and the ReferenceQueue will make all its elements eligible for GC. + if (queueThread != null) { + try { // The queue thread should finish eventually. + queueThread.join(); + } catch (InterruptedException e) { + // The join was interrupted, we give up + } + } + } + + private TableAccessFlag getTableAccess() { + TableAccessFlag flag = accessFlag; + if (flag == null) { + // No table was created yet, create the first one: + HashingTable table = new HashingTable(1 << INITIAL_SIZE_BITS, null); + flag = new TableAccessFlag(0, table); + TableAccessFlag oldFlag = (TableAccessFlag) Unsafe.getUnsafe().compareAndExchangeReference(this, accessFlagOffset, null, flag); + if (oldFlag == null) { + // We have successfully set the first table + refQueueThread = startCleanupThread(); + } else { + // It was set already + flag = oldFlag; + } + } + return flag; + } + + private boolean setNewTableAccess(TableAccessFlag oldFlag, TableAccessFlag newFlag) { + return Unsafe.getUnsafe().compareAndSetReference(this, accessFlagOffset, oldFlag, newFlag); + } + + /** + * Returns the ID, or -1 when the object is not tracked. + */ + public long getIdExisting(Object obj) { + if (obj == null) { + return 0; + } + TableAccessFlag flag = accessFlag; + if (flag == null) { + return -1; // Have no tables yet + } + HashingTable table = flag.table(); + int hash = System.identityHashCode(obj); + HashNode node = getIdExisting(table, obj, hash); + if (node != null) { + return node.getId(); + } else { + return -1; + } + } + + private static HashNode getIdExisting(HashingTable table, Object obj, int hash) { + for (HashingTable t = table; t != null; t = t.getNext()) { + HashNode node = t.getIdExisting(obj, hash); + if (node != null) { + return node; + } + } + return null; + } + + public long getIdOrCreateWeak(Object obj) { + long id = getIdExisting(obj); + if (id != -1) { + return id; + } + + int hash = System.identityHashCode(obj); + HashNode node = null; + boolean[] needsFinalizeId = new boolean[]{false}; + do { + TableAccessFlag tableAccess = getTableAccess(); + HashingTable table = tableAccess.table(); + boolean needsResize = table.needsResize(); + if (needsResize) { + int hashTableLength = table.hashToObjectTable.length; + if (tableAccess.resizeCount % (hashTableLength / RESIZE_ATTEMPT) == 0) { + // Let's try to do resize + int newSize = table.hashToObjectTable.length << 1; + HashingTable newTable = new HashingTable(newSize, table); + TableAccessFlag newTableAccess = new TableAccessFlag(0, newTable); + if (!setNewTableAccess(tableAccess, newTableAccess)) { + // The resize was not successful + continue; + } else { + // We're resized + table = newTable; + tableAccess = newTableAccess; + } + } else { + // Just increase the resize request count + TableAccessFlag newTableAccess = new TableAccessFlag(accessFlag.resizeCount + 1, table); + if (!setNewTableAccess(tableAccess, newTableAccess)) { + // Try next time + continue; + } + } + } + // Write the value to the table + node = table.getIdOrCreateWeak(obj, hash, needsFinalizeId); + if (needsFinalizeId[0]) { + // A new node was written to the table. + // We need to verify that the table wasn't resized in between + if (tableAccess.table == accessFlag.table) { + // We wrote it into the current table, great. + // Assign a new ID: + node.finalizeId(this, table); + } else { + // The table was resized in between. We can not be sure + // whether it wasn't put into the new table already + continue; + } + } + // We have the object's node. We exit the loop if the ID is set. + assert node != null; + } while (node == null || node.getId() < 0); + + return node.getId(); + } + + public Object getObject(long id) { + if (id == 0) { + return null; + } + TableAccessFlag flag = accessFlag; + if (flag == null) { + return null; // Have no tables yet + } + for (HashingTable table = flag.table(); table != null; table = table.getNext()) { + Object obj = table.getObject(id); + if (obj != null) { + return obj; + } + } + return null; + } + + private void dispose(HashNode node) { + TableAccessFlag flag = accessFlag; + if (flag == null) { + return; // Have no tables yet + } + disposeAll(flag.table(), node); + } + + private static void disposeAll(HashingTable table, HashNode node) { + for (HashingTable t = table; t != null; t = t.getNext()) { + t.dispose(node); + } + } + + boolean enableCollection(long id, int refCount, boolean disposeIfNotHold) { + if (refCount < 0) { + throw new IllegalArgumentException("Negative refCount not permitted: " + refCount); + } + TableAccessFlag flag = accessFlag; + if (flag == null) { + return false; // Have no tables yet + } + for (HashingTable table = flag.table(); table != null; table = table.getNext()) { + boolean sucess = table.enableCollection(id, refCount, disposeIfNotHold); + if (sucess) { + return sucess; + } + } + return false; + } + + public boolean disableCollection(long id) { + TableAccessFlag flag = accessFlag; + if (flag == null) { + return false; // Have no tables yet + } + for (HashingTable table = flag.table(); table != null; table = table.getNext()) { + boolean sucess = table.disableCollection(id); + if (sucess) { + return sucess; + } + } + return false; + } + + @SuppressFBWarnings(value = "NP_BOOLEAN_RETURN_NULL", justification = "Intentional.") + public Boolean isCollected(long id) { + if (id <= 0 || id > lastId) { + // Non-existing object + return null; + } + Object obj = getObject(id); + return null == obj; + } + + /** + * This class contains the hashing table that stores the hash -> object mapping and also + * the ID -> hash code mapping for lookups by the ID. + *

      + * hashToObjectTable is an array indexed by object's system hash code and + * contains arrays of HashNode.
      + * idToHashTable is an array indexed by unique IDs and contains lists of + * object's hash codes. + */ + final class HashingTable { + + private final HashNode[][] hashToObjectTable; // object table by a hash code index + private final HashListNode[] idToHashTable; // hash values by ID index + private final HashingTable next; + private volatile int size; + private static final long sizeOffset = Unsafe.getUnsafe().objectFieldOffset(HashingTable.class, "size"); + + HashingTable(int size, HashingTable next) { + idToHashTable = new HashListNode[size]; + hashToObjectTable = new HashNode[size][]; + this.next = next; + } + + HashingTable getNext() { + return next; + } + + HashNode getIdExisting(Object obj, int hash) { + int index = (hashToObjectTable.length - 1) & hash; + HashNode[] chain = getElementVolatile(hashToObjectTable, index); + if (chain != null) { + for (HashNode node : chain) { + if (obj == node.getObject()) { + return node; + } + } + } + return null; + } + + private void incrementSize() { + changeSize(+1); + } + + private void decrementSize() { + changeSize(-1); + } + + private void changeSize(int increment) { + int oldSize = size; + int s; + do { + s = Unsafe.getUnsafe().compareAndExchangeInt(this, sizeOffset, oldSize, oldSize + increment); + if (s != oldSize) { + // Try again + oldSize = s; + } else { + break; + } + } while (true); + } + + boolean needsResize() { + return size > hashToObjectTable.length && (hashToObjectTable.length << 1) > 0; + } + + private HashNode getIdOrCreateWeak(Object obj, int hash, boolean[] needsFinalizeId) { + int index = (hashToObjectTable.length - 1) & hash; + HashNode[] oldChain; + HashNode[] newChain; + do { + oldChain = getElementVolatile(hashToObjectTable, index); + if (oldChain != null) { + // Search the old node for the object + for (HashNode node : oldChain) { + if (node.getObject() == obj) { + // The node is there already + needsFinalizeId[0] = false; + return node; + } + } + newChain = new HashNode[oldChain.length + 1]; + System.arraycopy(oldChain, 0, newChain, 1, oldChain.length); + } else { + newChain = new HashNode[1]; + } + newChain[0] = new HashNodeWeak(hash, obj, refQueue); + } while (!compareAndSetElement(hashToObjectTable, index, oldChain, newChain)); + incrementSize(); + // A new node with uninitialized ID + needsFinalizeId[0] = true; + return newChain[0]; + } + + void newId(HashNode node) { + // A node got a new ID assigned. + // We need to add that to our 'hashes' table. + long newId = node.getId(); + int hash = node.getHash(); + int hashIndex = (int) ((idToHashTable.length - 1) & newId); + HashListNode oldList; + HashListNode newList; + do { + oldList = getElementVolatile(idToHashTable, hashIndex); + newList = new HashListNode(hash, oldList); + } while (!compareAndSetElement(idToHashTable, hashIndex, oldList, newList)); + } + + public Object getObject(long id) { + if (id == 0) { + return null; + } + int hashIndex = (int) ((idToHashTable.length - 1) & id); + HashListNode list = getElementVolatile(idToHashTable, hashIndex); + while (list != null) { + int hash = list.hash(); + int index = (hashToObjectTable.length - 1) & hash; + HashNode[] chain = getElementVolatile(hashToObjectTable, index); + if (chain != null) { + for (HashNode node : chain) { + if (id == node.getId()) { + return node.getObject(); + } + } + } + list = list.next(); + } + return null; + } + + private void dispose(HashNode node) { + long id = node.getId(); + int hash = node.getHash(); + int index = (hashToObjectTable.length - 1) & hash; + retryLoop: do { + HashNode[] oldChain = getElementVolatile(hashToObjectTable, index); + if (oldChain != null) { + for (int i = 0; i < oldChain.length; i++) { + if (oldChain[i].getId() == id) { + // Remove 'i' element from the oldChain, forming a newChain + HashNode[] newChain; + if (oldChain.length == 1) { + newChain = null; + } else { + // Copy chain, skipping the removed node at position 'i' + newChain = removeNode(oldChain, i); + } + if (!compareAndSetElement(hashToObjectTable, index, oldChain, newChain)) { + // We failed to write the new chain, try again + continue retryLoop; + } else { + // Successfully removed. + // Node with the given ID is in the table just once. + disposeId(id, hash); + decrementSize(); + break; + } + } + } + } + // not found + break; + } while (true); + } + + private static HashNode[] removeNode(HashNode[] oldChain, int index) { + HashNode[] newChain = new HashNode[oldChain.length - 1]; + if (index > 0) { + System.arraycopy(oldChain, 0, newChain, 0, index); + } + if (index < oldChain.length) { + System.arraycopy(oldChain, index + 1, newChain, index, newChain.length - index); + } + return newChain; + } + + private static HashNode[] replaceNode(HashNode[] oldChain, int index, HashNode newNode) { + if (oldChain.length == 1) { + return new HashNode[]{newNode}; + } + HashNode[] newChain = new HashNode[oldChain.length]; + // Copy it all for simplicity + System.arraycopy(oldChain, 0, newChain, 0, oldChain.length); + newChain[index] = newNode; + return newChain; + } + + private void disposeId(long id, int hash) { + int hashIndex = (int) ((idToHashTable.length - 1) & id); + HashListNode oldList; + retryLoop: do { + oldList = getElementVolatile(idToHashTable, hashIndex); + HashListNode nPrev = null; + for (HashListNode n = oldList; n != null; nPrev = n, n = n.next()) { + if (n.hash() == hash) { + // Remove 'n' from the chain + HashListNode newList; + if (nPrev == null) { + newList = n.next(); + } else { + HashListNode end = n.next(); + while (nPrev != null) { + HashListNode nn = new HashListNode(nPrev.hash(), end); + end = nn; + HashListNode lastPrev = nPrev; + // The chains are short, find the previous + nPrev = null; + for (HashListNode on = oldList; on != lastPrev; on = on.next()) { + nPrev = on; + } + } + newList = end; + } + if (compareAndSetElement(idToHashTable, hashIndex, oldList, newList)) { + // Disposed. + return; + } else { + // We failed to write the new chain, try again + continue retryLoop; + } + } + } + // not found + break; + } while (true); + } + + boolean enableCollection(long id, int refCount, boolean disposeIfNotHold) { + int hashIndex = (int) ((idToHashTable.length - 1) & id); + HashListNode list = getElementVolatile(idToHashTable, hashIndex); + for (; list != null; list = list.next()) { + int index = (hashToObjectTable.length - 1) & list.hash(); + retryLoop: do { + HashNode[] chain = getElementVolatile(hashToObjectTable, index); + if (chain != null) { + for (int i = 0; i < chain.length; i++) { + HashNode node = chain[i]; + if (id == node.getId()) { + if (node instanceof HashNodeStrong strongNode) { + int holdCount = strongNode.changeHoldCount(-refCount); + if (holdCount == 0 || holdCount == Integer.MIN_VALUE) { + // The node needs to be replaced with a weak one. + if (disposeIfNotHold) { + disposeAll(this, node); + } else { + // Replace the strong node with a weak one + HashNode weak = new HashNodeWeak(node.getHash(), node.getObject(), refQueue, node.getId()); + HashNode[] newChain = replaceNode(chain, i, weak); + if (compareAndSetElement(hashToObjectTable, index, chain, newChain)) { + // We changed the node. We must wipe out any + // occurrences of this ID from other tables + if (this.next != null) { + disposeAll(this.next, node); + } + } else { + continue retryLoop; + } + } + } + } else if (disposeIfNotHold) { + // We're weak already + disposeAll(this, node); + } + return true; // We found the ID + } + } + } + break; // not found + } while (true); + } + return false; + } + + public boolean disableCollection(long id) { + int hashIndex = (int) ((idToHashTable.length - 1) & id); + HashListNode list = getElementVolatile(idToHashTable, hashIndex); + for (; list != null; list = list.next()) { + int index = (hashToObjectTable.length - 1) & list.hash(); + retryLoop: do { + HashNode[] chain = getElementVolatile(hashToObjectTable, index); + if (chain != null) { + for (int i = 0; i < chain.length; i++) { + HashNode node = chain[i]; + if (id == node.getId()) { + if (node instanceof HashNodeStrong strongNode) { + strongNode.changeHoldCount(+1); + } else { + Object obj = node.getObject(); + if (obj == null) { + // GCed + return false; + } + // Replace node with HashNodeStrong + HashNode strong = new HashNodeStrong(node.getHash(), obj, node.getId()); + HashNode[] newChain = replaceNode(chain, i, strong); + if (compareAndSetElement(hashToObjectTable, index, chain, newChain)) { + // We changed the node. We must wipe out any occurrences + // of this ID from other tables + if (this.next != null) { + disposeAll(this.next, node); + } + } else { + continue retryLoop; + } + } + return true; // We found the ID + } + } + } + break; // not found + } while (true); + } + return false; + } + + } + + private record TableAccessFlag(int resizeCount, HashingTable table) { + + } + + } + + private sealed interface HashNode permits HashNodeWeak, HashNodeStrong { + + /** + * Get a unique ID, or -1 if it was not assigned yet. + */ + long getId(); + + /** + * Assign a new unique ID. + */ + long finalizeId(LockFreeHashMap map, LockFreeHashMap.HashingTable table); + + /** + * Get the object's hash code. + */ + int getHash(); + + /** + * Get the object, or {@code null} when collected. + */ + Object getObject(); + + } + + private static final class HashNodeWeak extends WeakReference implements HashNode { + + private volatile long id; + private final int hash; + + HashNodeWeak(int hash, Object referent, ReferenceQueue refQueue) { + this(hash, referent, refQueue, -1); + } + + HashNodeWeak(int hash, Object referent, ReferenceQueue refQueue, long id) { + super(referent, refQueue); + this.id = id; + this.hash = hash; + } + + @Override + public long getId() { + return id; + } + + @Override + public long finalizeId(LockFreeHashMap map, LockFreeHashMap.HashingTable table) { + long theId = id; + if (theId <= -1) { + theId = map.getNextId(); + // Only the node's creator will finalize, pure set is safe + id = theId; + } + table.newId(this); + return theId; + } + + @Override + public int getHash() { + return hash; + } + + @Override + public Object getObject() { + return get(); + } + + } + + private static final class HashNodeStrong implements HashNode { + + private final long id; + private final int hash; + private final Object object; + private volatile int holdCount = 1; + private static final long holdCountOffset = Unsafe.getUnsafe().objectFieldOffset(HashNodeStrong.class, "holdCount"); + + HashNodeStrong(int hash, Object object, long id) { + this.id = id; + this.hash = hash; + this.object = object; + } + + @Override + public long getId() { + return id; + } + + @Override + public long finalizeId(LockFreeHashMap map, LockFreeHashMap.HashingTable table) { + // The strong nodes are used as a replacement of existing weak nodes, + // which have the ID initialized already. + throw new UnsupportedOperationException(); + } + + int changeHoldCount(int increment) { + int oldCount = holdCount; + if (oldCount == 0) { + // Locked when reached 0, the node is replaced with a weak one. + return Integer.MIN_VALUE; + } + int count; + do { + int newCount = oldCount + increment; + if (newCount < 0) { + newCount = 0; + } + count = Unsafe.getUnsafe().compareAndExchangeInt(this, holdCountOffset, oldCount, newCount); + if (count != oldCount) { + // Try again + oldCount = count; + } else { + return newCount; + } + } while (true); + } + + @Override + public int getHash() { + return hash; + } + + @Override + public Object getObject() { + return object; + } + + } + + private record HashListNode(int hash, HashListNode next) { + } + +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/ResidentJDWPFeature.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/ResidentJDWPFeature.java new file mode 100644 index 000000000000..4be25fab7807 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/ResidentJDWPFeature.java @@ -0,0 +1,72 @@ +/* + * 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.jdwp.resident; + +import java.util.List; + +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.jdwp.bridge.jniutils.NativeBridgeSupport; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.jdk.RuntimeSupport; +import com.oracle.svm.interpreter.InterpreterFeature; +import com.oracle.svm.interpreter.debug.DebuggerEventsFeature; +import com.oracle.svm.jdwp.bridge.JDWPNativeBridgeSupport; +import com.oracle.svm.jdwp.bridge.ResidentJDWPFeatureEnabled; + +@Platforms(Platform.HOSTED_ONLY.class) +@AutomaticallyRegisteredFeature +final class ResidentJDWPFeature implements InternalFeature { + + @Override + public String getDescription() { + return "Support debugging native images via JDWP, using standard Java tooling"; + } + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + registerStartupHook(); + ImageSingletons.add(NativeBridgeSupport.class, new JDWPNativeBridgeSupport()); + ImageSingletons.add(ResidentJDWPFeatureEnabled.class, new ResidentJDWPFeatureEnabled()); + } + + private static void registerStartupHook() { + RuntimeSupport.getRuntimeSupport().addStartupHook(new DebuggingOnDemandHook()); + } + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return JDWPOptions.JDWP.getValue(); + } + + @Override + public List> getRequiredFeatures() { + return List.of(InterpreterFeature.class, DebuggerEventsFeature.class); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/ThreadStartDeathSupport.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/ThreadStartDeathSupport.java new file mode 100644 index 000000000000..16e226e1b3e5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/ThreadStartDeathSupport.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.resident; + +import com.oracle.svm.core.Uninterruptible; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.jdk.RuntimeSupport; +import com.oracle.svm.core.thread.PlatformThreads; +import com.oracle.svm.core.thread.ThreadListener; +import com.oracle.svm.core.thread.ThreadListenerSupport; +import com.oracle.svm.core.thread.VMOperationControl; + +import jdk.graal.compiler.api.replacements.Fold; + +/** + * Support for Thread start/death events. + */ +@AutomaticallyRegisteredImageSingleton +public final class ThreadStartDeathSupport implements ThreadListener { + + /** + * Debugger-related threads that should not be visible to clients. + */ + private final Thread[] debuggerThreads = new Thread[2]; + private static final int DEBUGGER_THREAD_INDEX_SERVER = 0; + private static final int DEBUGGER_THREAD_INDEX_OBJECTS_QUEUE = 1; + + private volatile boolean start; + private volatile boolean death; + private volatile Listener listener; + + @Platforms(Platform.HOSTED_ONLY.class) + ThreadStartDeathSupport() { + ThreadListenerSupport.get().register(this); + RuntimeSupport.getRuntimeSupport().addShutdownHook(new RuntimeSupport.Hook() { + @Override + public void execute(boolean isFirstIsolate) { + Listener l = listener; + if (l != null) { + l.vmDied(); + } + } + }); + } + + @Fold + public static ThreadStartDeathSupport get() { + return ImageSingletons.lookup(ThreadStartDeathSupport.class); + } + + void setDebuggerThreadServer(Thread serverThread) { + debuggerThreads[DEBUGGER_THREAD_INDEX_SERVER] = serverThread; + } + + void setDebuggerThreadObjectQueue(Thread serverThread) { + debuggerThreads[DEBUGGER_THREAD_INDEX_OBJECTS_QUEUE] = serverThread; + } + + /** + * Filter application-related threads and convert IsolateThread to Thread. + * + * @return a converted application Thread, or {@code null}. + */ + public Thread filterAppThread(IsolateThread isolateThread) { + if (VMOperationControl.isDedicatedVMOperationThread(isolateThread)) { + return null; + } + Thread thread = PlatformThreads.fromVMThread(isolateThread); + if (thread == null) { + return null; + } + for (Thread t : debuggerThreads) { + if (t == thread) { + return null; + } + } + // TODO(peterssen): GR-55071 Identify external threads that entered via JNI. + if (thread.getName().startsWith("System-")) { + return null; + } + return thread; + } + + void setListener(Listener listener) { + this.listener = listener; + } + + void setListeningOn(boolean startOrDeath, boolean enable) { + if (startOrDeath) { + start = enable; + } else { + death = enable; + } + } + + @Override + public void beforeThreadRun() { + Listener l = listener; + if (l != null && start) { + l.threadStarted(); + } + } + + @Override + @Uninterruptible(reason = "Only uninterruptible because we need to prevent stack overflow errors.") + public void afterThreadRun() { + Listener l = listener; + if (l != null && death) { + l.threadDied(); + } + } + + interface Listener { + + void threadStarted(); + + void threadDied(); + + void vmDied(); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/api/StackframeDescriptor.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/api/StackframeDescriptor.java new file mode 100644 index 000000000000..37285d1dbcd3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/api/StackframeDescriptor.java @@ -0,0 +1,34 @@ +/* + * 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.jdwp.resident.api; + +import com.oracle.svm.core.code.FrameSourceInfo; +import org.graalvm.word.Pointer; + +public interface StackframeDescriptor { + FrameSourceInfo getFrameSourceInfo(); + + Pointer getStackPointer(); +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/AbstractJDWPJavaFrameInfoVisitor.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/AbstractJDWPJavaFrameInfoVisitor.java new file mode 100644 index 000000000000..c32f7880867e --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/AbstractJDWPJavaFrameInfoVisitor.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.resident.impl; + +import com.oracle.svm.core.code.FrameSourceInfo; +import com.oracle.svm.core.jdk.StackTraceUtils; +import com.oracle.svm.core.stack.JavaStackFrameVisitor; +import org.graalvm.word.Pointer; + +abstract class AbstractJDWPJavaFrameInfoVisitor extends JavaStackFrameVisitor { + private final boolean filterExceptions; + int currentFrameDepth; + + AbstractJDWPJavaFrameInfoVisitor(boolean filterExceptions) { + this.filterExceptions = filterExceptions; + this.currentFrameDepth = 0; + } + + protected boolean ignoreFrame(FrameSourceInfo frameInfo) { + if (!StackTraceUtils.shouldShowFrame(frameInfo, false, true, false)) { + /* Always ignore the frame. It is an internal frame of the VM. */ + return true; + + } else if (filterExceptions && currentFrameDepth == 0 && Throwable.class.isAssignableFrom(frameInfo.getSourceClass())) { + /* + * We are still in the constructor invocation chain at the beginning of the stack trace, + * which is also filtered by the Java HotSpot VM. + */ + return true; + } + + String sourceClassName = null; + Class sourceClass = frameInfo.getSourceClass(); + if (sourceClass != null) { + sourceClassName = sourceClass.getName(); + } + if (currentFrameDepth == 0 && (sourceClassName != null && sourceClassName.startsWith("com.oracle.svm.jdwp."))) { + /* + * Ignore frames used by the debugger to spawn events, but only if they would be + * reported as top-most frames. + */ + return true; + } + + return false; + } + + @Override + public boolean visitFrame(FrameSourceInfo frameInfo, Pointer sp) { + if (ignoreFrame(frameInfo)) { + return true; + } + return processFrame(frameInfo, sp); + } + + protected boolean processFrame(@SuppressWarnings("unused") FrameSourceInfo frameInfo, @SuppressWarnings("unused") Pointer sp) { + ++currentFrameDepth; + return true; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/AllJavaFramesVisitor.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/AllJavaFramesVisitor.java new file mode 100644 index 000000000000..60ea9002406f --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/AllJavaFramesVisitor.java @@ -0,0 +1,52 @@ +/* + * 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.jdwp.resident.impl; + +import com.oracle.svm.core.code.FrameSourceInfo; +import org.graalvm.word.Pointer; + +import java.util.ArrayList; +import java.util.List; + +public final class AllJavaFramesVisitor extends AbstractJDWPJavaFrameInfoVisitor { + + private final List frames; + + public AllJavaFramesVisitor(boolean filterExceptions) { + super(filterExceptions); + this.frames = new ArrayList<>(); + } + + @Override + protected boolean processFrame(FrameSourceInfo frameInfo, Pointer sp) { + super.processFrame(frameInfo, sp); + frames.add(frameInfo); + return true; + } + + public List getFrames() { + return frames; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/GetStackframeDescriptorAtDepthVisitor.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/GetStackframeDescriptorAtDepthVisitor.java new file mode 100644 index 000000000000..2e7e94ee8f62 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/GetStackframeDescriptorAtDepthVisitor.java @@ -0,0 +1,64 @@ +/* + * 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.jdwp.resident.impl; + +import com.oracle.svm.core.code.FrameSourceInfo; +import com.oracle.svm.jdwp.resident.api.StackframeDescriptor; +import org.graalvm.word.Pointer; + +public final class GetStackframeDescriptorAtDepthVisitor extends AbstractJDWPJavaFrameInfoVisitor implements StackframeDescriptor { + private final int frameDepth; + private FrameSourceInfo frameSourceInfo; + private Pointer stackPointer; + + @Override + public FrameSourceInfo getFrameSourceInfo() { + return frameSourceInfo; + } + + @Override + public Pointer getStackPointer() { + return stackPointer; + } + + GetStackframeDescriptorAtDepthVisitor(boolean filterExceptions, int frameDepth) { + super(filterExceptions); + this.frameDepth = frameDepth; + } + + @Override + protected boolean processFrame(FrameSourceInfo frameInfo, Pointer sp) { + super.processFrame(frameInfo, sp); + // currentFrameDepth was incremented in super.processFrame(frameInfo, sp). + + if (currentFrameDepth - 1 == frameDepth) { + this.frameSourceInfo = frameInfo; + this.stackPointer = sp; + return false; + } + + return true; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/ResidentJDWP.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/ResidentJDWP.java new file mode 100644 index 000000000000..a6318ff66cb0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/ResidentJDWP.java @@ -0,0 +1,2149 @@ +/* + * Copyright (c) 2023, 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.jdwp.resident.impl; + +import com.oracle.svm.core.StaticFieldsSupport; +import com.oracle.svm.core.code.FrameInfoQueryResult; +import com.oracle.svm.core.code.FrameSourceInfo; +import com.oracle.svm.core.deopt.DeoptState; +import com.oracle.svm.core.hub.ClassForNameSupport; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.interpreter.InterpreterFrameSourceInfo; +import com.oracle.svm.interpreter.DebuggerSupport; +import com.oracle.svm.interpreter.metadata.InterpreterUniverse; +import com.oracle.svm.core.locks.VMMutex; +import com.oracle.svm.core.log.Log; +import com.oracle.svm.core.meta.SubstrateObjectConstant; +import com.oracle.svm.core.thread.VMThreads; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.EspressoFrame; +import com.oracle.svm.interpreter.InterpreterFrame; +import com.oracle.svm.interpreter.InterpreterToVM; +import com.oracle.svm.interpreter.SemanticJavaException; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType; +import com.oracle.svm.interpreter.metadata.MetadataUtil; +import com.oracle.svm.jdwp.bridge.ErrorCode; +import com.oracle.svm.jdwp.bridge.FrameId; +import com.oracle.svm.jdwp.bridge.InvokeOptions; +import com.oracle.svm.jdwp.bridge.JDWP; +import com.oracle.svm.jdwp.bridge.JDWPException; +import com.oracle.svm.jdwp.bridge.Logger; +import com.oracle.svm.jdwp.bridge.Packet; +import com.oracle.svm.jdwp.bridge.SymbolicRefs; +import com.oracle.svm.jdwp.bridge.TagConstants; +import com.oracle.svm.jdwp.bridge.TypeTag; +import com.oracle.svm.jdwp.bridge.WritablePacket; +import com.oracle.svm.jdwp.resident.JDWPBridgeImpl; +import com.oracle.svm.jdwp.resident.ThreadStartDeathSupport; +import com.oracle.svm.jdwp.resident.ClassUtils; +import com.oracle.svm.jdwp.resident.api.StackframeDescriptor; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.Local; +import jdk.vm.ci.meta.LocalVariableTable; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.word.Pointer; +import org.graalvm.word.WordBase; +import org.graalvm.word.WordFactory; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.OptionalInt; +import java.util.Set; + +public final class ResidentJDWP implements JDWP { + + private static final boolean LOGGING = false; + public static Logger LOGGER = new Logger(LOGGING, "[ResidentJDWP]", System.err); + + private final SymbolicRefs symbolicRefs = new ResidentSymbolicRefs(); + + public ResidentJDWP() { + } + + /** + * Reads a reference, can be null. + * + * @throws JDWPException {@link ErrorCode#INVALID_OBJECT} if the object was collected or the + * object id is invalid + */ + private static Object readReferenceOrNull(Packet.Reader reader) throws JDWPException { + long objectId = reader.readLong(); + Object value = JDWPBridgeImpl.getIds().getObject(objectId); + if (objectId != 0 && value == null) { + throw JDWPException.raise(ErrorCode.INVALID_OBJECT); + } + return value; + } + + /** + * Reads an {@link Class#isArray() array}. + * + * @throws JDWPException {@link ErrorCode#INVALID_ARRAY} if the reference is null, + * {@link ErrorCode#INVALID_OBJECT} if the object id was collected or is invalid + */ + private static Object readArray(Packet.Reader reader) throws JDWPException { + Object array = readReferenceOrNull(reader); + if (array == null || !array.getClass().isArray()) { + throw JDWPException.raise(ErrorCode.INVALID_ARRAY); + } + return array; + } + + /** + * Reads an object of the given class. If the object is {@code null} or not and instance of the + * given class, this method throws {@link JDWPException} with the provided error code is thrown. + * + * @throws JDWPException {@link ErrorCode#INVALID_OBJECT} if the object id was collected or is + * invalid + */ + private static T readTypedObject(Packet.Reader reader, Class clazz, ErrorCode errorCode) throws JDWPException { + Object object = readReferenceOrNull(reader); + if (clazz.isInstance(object)) { + return clazz.cast(object); + } + throw JDWPException.raise(errorCode); + } + + /** + * Reads a {@link String} reference. If the object is {@code null} or not an instance of + * {@link String}, this method throws {@link JDWPException} with + * {@link ErrorCode#INVALID_STRING}. + * + * @throws JDWPException {@link ErrorCode#INVALID_OBJECT} if the object id was collected or is + * invalid + */ + private static String readStringObject(Packet.Reader reader) throws JDWPException { + return readTypedObject(reader, String.class, ErrorCode.INVALID_STRING); + } + + /** + * Reads an {@link InterpreterResolvedJavaType}. If the object is {@code null} or not an + * instance of {@link InterpreterResolvedJavaType}, or it is NOT part of the + * {@link DebuggerSupport#getUniverse() interpreter universe}, this method throws + * {@link JDWPException} with {@link ErrorCode#INVALID_CLASS}. + * + * @throws JDWPException {@link ErrorCode#INVALID_OBJECT} if the object id was collected or is + * invalid + */ + private static InterpreterResolvedJavaType readType(Packet.Reader reader) throws JDWPException { + Object object = readReferenceOrNull(reader); + if (object instanceof InterpreterResolvedJavaType interpreterResolvedJavaType) { + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + OptionalInt knownInUniverse = universe.getTypeIndexFor(interpreterResolvedJavaType); + if (knownInUniverse.isPresent()) { + return interpreterResolvedJavaType; + } + } + throw JDWPException.raise(ErrorCode.INVALID_CLASS); // cannot be null + } + + /** + * Reads an {@link InterpreterResolvedJavaField}. If the object is {@code null} or not an + * instance of {@link InterpreterResolvedJavaField}, or it is NOT part of the + * {@link DebuggerSupport#getUniverse() interpreter universe}, this method throws + * {@link JDWPException} with {@link ErrorCode#INVALID_FIELDID}. + * + * @throws JDWPException {@link ErrorCode#INVALID_OBJECT} if the object id was collected or is + * invalid + */ + private static InterpreterResolvedJavaField readField(Packet.Reader reader) throws JDWPException { + Object object = readReferenceOrNull(reader); + if (object instanceof InterpreterResolvedJavaField interpreterResolvedJavaField) { + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + OptionalInt knownInUniverse = universe.getFieldIndexFor(interpreterResolvedJavaField); + if (knownInUniverse.isPresent()) { + return interpreterResolvedJavaField; + } + } + throw JDWPException.raise(ErrorCode.INVALID_FIELDID); // cannot be null + } + + /** + * Reads an {@link InterpreterResolvedJavaMethod}. If the object is {@code null} or not an + * instance of {@link InterpreterResolvedJavaMethod}, or it is NOT part of the + * {@link DebuggerSupport#getUniverse() interpreter universe}, this method throws + * {@link JDWPException} with {@link ErrorCode#INVALID_METHODID}. + * + * @throws JDWPException {@link ErrorCode#INVALID_OBJECT} if the object id was collected or is + * invalid + */ + @SuppressWarnings("unused") + private static InterpreterResolvedJavaMethod readMethod(Packet.Reader reader) throws JDWPException { + Object object = readReferenceOrNull(reader); + if (object instanceof InterpreterResolvedJavaMethod interpreterResolvedJavaMethod) { + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + OptionalInt knownInUniverse = universe.getMethodIndexFor(interpreterResolvedJavaMethod); + if (knownInUniverse.isPresent()) { + return interpreterResolvedJavaMethod; + } + } + throw JDWPException.raise(ErrorCode.INVALID_METHODID); // cannot be null + } + + /** + * Reads a {@link ThreadGroup}. If the object is {@code null} or not an instance of + * {@link ThreadGroup} this method throws {@link JDWPException} with + * {@link ErrorCode#INVALID_THREAD_GROUP}. + * + * @throws JDWPException {@link ErrorCode#INVALID_OBJECT} if the object id was collected or is + * invalid + */ + private static ThreadGroup readThreadGroup(Packet.Reader reader) throws JDWPException { + return readTypedObject(reader, ThreadGroup.class, ErrorCode.INVALID_THREAD_GROUP); + } + + /** + * Reads a {@link Module}. If the object is {@code null} or not an instance of {@link Module} + * this method throws {@link JDWPException} with {@link ErrorCode#INVALID_MODULE}. + * + * @throws JDWPException {@link ErrorCode#INVALID_OBJECT} if the object id was collected or is + * invalid + */ + private static Module readModule(Packet.Reader reader) throws JDWPException { + return readTypedObject(reader, Module.class, ErrorCode.INVALID_MODULE); + } + + /** + * Reads a {@link Class}. If the object is {@code null} or not an instance of {@link Class} this + * method throws {@link JDWPException} with {@link ErrorCode#INVALID_CLASS}. + * + * @throws JDWPException {@link ErrorCode#INVALID_OBJECT} if the object id was collected or is + * invalid + */ + private static Class readClassObject(Packet.Reader reader) throws JDWPException { + return readTypedObject(reader, Class.class, ErrorCode.INVALID_CLASS); + } + + /** + * Reads a {@link ClassLoader}. If the object is {@code null} or not an instance of + * {@link Class} this method throws {@link JDWPException} with + * {@link ErrorCode#INVALID_CLASS_LOADER}. + * + * @throws JDWPException {@link ErrorCode#INVALID_OBJECT} if the object id was collected or is + * invalid + */ + @SuppressWarnings("unused") + private static ClassLoader readClassLoader(Packet.Reader reader) throws JDWPException { + return readTypedObject(reader, ClassLoader.class, ErrorCode.INVALID_CLASS_LOADER); + } + + /** + * Verify that the given object id is valid and not collected. An object id can be + * {@link SymbolicRefs#NULL null}, which is valid in this method, thus the {@code ...orNull} + * suffix. + * + * @throws JDWPException {@link ErrorCode#INVALID_OBJECT} if the object id was collected or is + * invalid + */ + private static Object verifyObjectIdOrNull(long objectId) throws JDWPException { + Object object = JDWPBridgeImpl.getIds().getObject(objectId); + if (object != null || objectId == SymbolicRefs.NULL) { + return object; + } + // Reference was collected, or is unknown. + throw JDWPException.raise(ErrorCode.INVALID_OBJECT); + } + + /** + * Verify that the given object id is a valid {@link InterpreterResolvedJavaType type}, not + * null, not collected and part of the {@link DebuggerSupport#getUniverse() interpreter + * universe}; otherwise it throws {@link JDWPException} with {@link ErrorCode#INVALID_CLASS}. + * + * @throws JDWPException {@link ErrorCode#INVALID_OBJECT} if the object id was collected or is + * invalid + */ + @SuppressWarnings("unused") + private static ResolvedJavaType verifyRefType(long refTypeId) throws JDWPException { + Object object = verifyObjectIdOrNull(refTypeId); + if (object instanceof InterpreterResolvedJavaType interpreterResolvedJavaType) { + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + OptionalInt knownInUniverse = universe.getTypeIndexFor(interpreterResolvedJavaType); + if (knownInUniverse.isPresent()) { + return interpreterResolvedJavaType; + } + } + throw JDWPException.raise(ErrorCode.INVALID_CLASS); // cannot be null + } + + /** + * Verify that the given object id is a valid {@link InterpreterResolvedJavaField field}, not + * null, not collected and part of the {@link DebuggerSupport#getUniverse() interpreter + * universe}; otherwise it throws {@link JDWPException} with {@link ErrorCode#INVALID_FIELDID}. + * + * @throws JDWPException {@link ErrorCode#INVALID_OBJECT} if the object id was collected or is + * invalid + */ + @SuppressWarnings("unused") + private static ResolvedJavaField verifyRefField(long fieldId) throws JDWPException { + Object object = verifyObjectIdOrNull(fieldId); + if (object instanceof InterpreterResolvedJavaField interpreterResolvedJavaField) { + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + OptionalInt knownInUniverse = universe.getFieldIndexFor(interpreterResolvedJavaField); + if (knownInUniverse.isPresent()) { + return interpreterResolvedJavaField; + } + } + throw JDWPException.raise(ErrorCode.INVALID_FIELDID); // cannot be null + } + + /** + * Verify that the given object id is a valid {@link InterpreterResolvedJavaMethod method}, not + * null, not collected and part of the {@link DebuggerSupport#getUniverse() interpreter + * universe}; otherwise it throws {@link JDWPException} with {@link ErrorCode#INVALID_METHODID}. + * + * @throws JDWPException {@link ErrorCode#INVALID_OBJECT} if the object id was collected or is + * invalid + */ + @SuppressWarnings("unused") + private static ResolvedJavaMethod verifyRefMethod(long methodId) throws JDWPException { + Object object = verifyObjectIdOrNull(methodId); + if (object instanceof InterpreterResolvedJavaMethod interpreterResolvedJavaMethod) { + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + OptionalInt knownInUniverse = universe.getMethodIndexFor(interpreterResolvedJavaMethod); + if (knownInUniverse.isPresent()) { + return interpreterResolvedJavaMethod; + } + } + throw JDWPException.raise(ErrorCode.INVALID_METHODID); // cannot be null + } + + /** + * Writes a tagged-object, as specified in the JDWP spec. The {@link TagConstants tag} is + * derived from the given object. {@code null} is tagged as {@link TagConstants#OBJECT}. + */ + private static void writeTaggedObject(Packet.Writer writer, Object value) { + writer.writeByte(TagConstants.getTagFromReference(value)); + writer.writeLong(JDWPBridgeImpl.getIds().getIdOrCreateWeak(value)); + } + + @Override + public Packet VirtualMachine_AllThreads(Packet packet) throws JDWPException { + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer data = reply.dataWriter(); + + long[] allThreads = getAllThreadIds(); + data.writeInt(allThreads.length); + for (long threadId : allThreads) { + data.writeLong(threadId); + } + + return reply; + } + + private static VMMutex lockThreads() { + VMMutex mutex; + try { + Field mutexField = VMThreads.class.getDeclaredField("THREAD_MUTEX"); + mutexField.setAccessible(true); + mutex = (VMMutex) mutexField.get(null); + } catch (IllegalAccessException | IllegalArgumentException | NoSuchFieldException | SecurityException ex) { + ex.printStackTrace(); + throw JDWPException.raise(ErrorCode.INTERNAL); + } + mutex.lock(); + return mutex; + } + + private static long[] getAllThreadIds() { + long[] ids = new long[10]; + int i = 0; + VMMutex mutex = lockThreads(); + try { + for (IsolateThread thread = VMThreads.firstThreadUnsafe(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { + Thread t = ThreadStartDeathSupport.get().filterAppThread(thread); + if (t == null) { + continue; + } + if (i >= ids.length) { + ids = Arrays.copyOf(ids, i + i / 2); + } + ids[i++] = JDWPBridgeImpl.getIds().getIdOrCreateWeak(t); + } + } finally { + mutex.unlock(); + } + ids = Arrays.copyOf(ids, i); + Log.log().string("getAllThreadIds(): " + Arrays.toString(ids)).newline(); + return ids; + } + + @Override + public Packet VirtualMachine_AllClassesWithGeneric(Packet packet) throws JDWPException { + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer data = reply.dataWriter(); + + ResolvedJavaType[] allClasses = allReferenceTypes(); + + data.writeInt(allClasses.length); + for (ResolvedJavaType type : allClasses) { + data.writeByte(TypeTag.getKind(type)); + data.writeLong(JDWPBridgeImpl.getIds().getIdOrCreateWeak(type)); + data.writeString(ClassUtils.getTypeAsString(type)); + data.writeString(ClassUtils.getGenericTypeAsString(type)); + data.writeInt(ClassUtils.getStatus(type)); + } + + return reply; + } + + @Override + public Packet VirtualMachine_Dispose(Packet packet) throws JDWPException { + JDWPBridgeImpl.getIds().reset(); + return WritablePacket.newReplyTo(packet); + } + + @Override + public Packet VirtualMachine_DisposeObjects(Packet packet) throws JDWPException { + Packet.Reader input = packet.newDataReader(); + int numRequests = input.readInt(); + + for (int i = 0; i < numRequests; i++) { + long objectId = input.readLong(); + int refCount = input.readInt(); + JDWPBridgeImpl.getIds().enableCollection(objectId, refCount, true); + } + + return WritablePacket.newReplyTo(packet); + } + + @Override + public Packet ObjectReference_DisableCollection(Packet packet) throws JDWPException { + Packet.Reader input = packet.newDataReader(); + long objectId = input.readLong(); + + boolean success = JDWPBridgeImpl.getIds().disableCollection(objectId); + if (!success) { + throw JDWPException.raise(ErrorCode.INVALID_OBJECT); + } + return WritablePacket.newReplyTo(packet); + } + + @Override + public Packet ObjectReference_EnableCollection(Packet packet) throws JDWPException { + Packet.Reader input = packet.newDataReader(); + long objectId = input.readLong(); + + boolean success = JDWPBridgeImpl.getIds().enableCollection(objectId); + if (!success) { + throw JDWPException.raise(ErrorCode.INVALID_OBJECT); + } + return WritablePacket.newReplyTo(packet); + } + + @Override + public Packet ObjectReference_IsCollected(Packet packet) throws JDWPException { + Packet.Reader input = packet.newDataReader(); + long objectId = input.readLong(); + + Boolean collected = JDWPBridgeImpl.getIds().isCollected(objectId); + if (collected == null) { + throw JDWPException.raise(ErrorCode.INVALID_OBJECT); + } + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer data = reply.dataWriter(); + + data.writeBoolean(collected); + + return reply; + } + + @Override + public Packet ThreadReference_Name(Packet packet) throws JDWPException { + Packet.Reader input = packet.newDataReader(); + + Thread thread = readThread(input); + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer data = reply.dataWriter(); + + // The thread name. + data.writeString(thread.getName()); + + return reply; + } + + @Override + public Packet ThreadReference_ThreadGroup(Packet packet) throws JDWPException { + Packet.Reader input = packet.newDataReader(); + + Thread thread = readThread(input); + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer data = reply.dataWriter(); + + // The thread group. + ThreadGroup threadGroup = thread.getThreadGroup(); + if (threadGroup == null) { + // Thread has terminated + throw JDWPException.raise(ErrorCode.INVALID_THREAD); + } + long id = JDWPBridgeImpl.getIds().getIdOrCreateWeak(threadGroup); + data.writeLong(id); + + return reply; + } + + private static Thread readThread(Packet.Reader reader) throws JDWPException { + return readTypedObject(reader, Thread.class, ErrorCode.INVALID_THREAD); + } + + public static Thread getThread(long threadId) { + Thread thread; + try { + thread = JDWPBridgeImpl.getIds().toObject(threadId, Thread.class); + } catch (ClassCastException e) { + throw JDWPException.raise(ErrorCode.INVALID_THREAD); + } + if (thread == null) { + if (threadId == 0) { + // A null thread is invalid + throw JDWPException.raise(ErrorCode.INVALID_THREAD); + } else { + // Unknown ID + throw JDWPException.raise(ErrorCode.INVALID_OBJECT); + } + } + return thread; + } + + @Override + public Packet ThreadGroupReference_Name(Packet packet) throws JDWPException { + Packet.Reader input = packet.newDataReader(); + + ThreadGroup threadGroup = readThreadGroup(input); + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer data = reply.dataWriter(); + + // The thread group name. + data.writeString(threadGroup.getName()); + + return reply; + } + + @Override + public Packet ThreadGroupReference_Children(Packet packet) throws JDWPException { + Packet.Reader input = packet.newDataReader(); + + ThreadGroup threadGroup = readThreadGroup(input); + long[] threadIds = new long[10]; + long[] threadGroupIds = new long[10]; + int ti = 0; + int tgi = 0; + VMMutex mutex = lockThreads(); + try { + // Find child threads and child groups: + for (IsolateThread thread = VMThreads.firstThreadUnsafe(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { + Thread t = ThreadStartDeathSupport.get().filterAppThread(thread); + if (t == null) { + continue; + } + ThreadGroup tg = t.getThreadGroup(); + if (tg == threadGroup) { + // A direct child thread + if (ti >= threadIds.length) { + threadIds = Arrays.copyOf(threadIds, ti + ti / 2); + } + threadIds[ti++] = JDWPBridgeImpl.getIds().getIdOrCreateWeak(t); + } + if (tg != null && tg.getParent() == threadGroup) { + // A direct child thread group + long id = JDWPBridgeImpl.getIds().getIdOrCreateWeak(tg); + boolean contains = false; + for (int i = 0; i < tgi; i++) { + if (threadGroupIds[i] == id) { + contains = true; + break; + } + } + if (!contains) { + if (tgi >= threadGroupIds.length) { + threadGroupIds = Arrays.copyOf(threadGroupIds, tgi + tgi / 2); + } + threadGroupIds[tgi++] = id; + } + } + } + } finally { + mutex.unlock(); + } + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer data = reply.dataWriter(); + + data.writeInt(ti); + for (int i = 0; i < ti; i++) { + data.writeLong(threadIds[i]); + } + data.writeInt(tgi); + for (int i = 0; i < tgi; i++) { + data.writeLong(threadGroupIds[i]); + } + + return reply; + + } + + @Override + public Packet ThreadGroupReference_Parent(Packet packet) throws JDWPException { + Packet.Reader input = packet.newDataReader(); + ThreadGroup threadGroup = readThreadGroup(input); + assert input.isEndOfInput(); + + ThreadGroup parentGroup = threadGroup.getParent(); + long parentId = JDWPBridgeImpl.getIds().getIdOrCreateWeak(parentGroup); + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer data = reply.dataWriter(); + data.writeLong(parentId); + + return reply; + } + + @Override + public Packet VirtualMachine_TopLevelThreadGroups(Packet packet) throws JDWPException { + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer data = reply.dataWriter(); + + long[] threadGroupIds = new long[5]; + int tgi = 0; + VMMutex mutex = lockThreads(); + try { + // Find all top thread groups: + for (IsolateThread thread = VMThreads.firstThreadUnsafe(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { + Thread t = ThreadStartDeathSupport.get().filterAppThread(thread); + if (t == null) { + continue; + } + ThreadGroup tg = t.getThreadGroup(); + if (tg != null) { + ThreadGroup rootGroup = tg; + while ((tg = tg.getParent()) != null) { + rootGroup = tg; + } + long id = JDWPBridgeImpl.getIds().getIdOrCreateWeak(rootGroup); + boolean contains = false; + for (int i = 0; i < tgi; i++) { + if (threadGroupIds[i] == id) { + contains = true; + break; + } + } + if (!contains) { + // A new top-level group + if (tgi >= threadGroupIds.length) { + threadGroupIds = Arrays.copyOf(threadGroupIds, tgi + tgi / 2); + } + threadGroupIds[tgi++] = id; + } + } + } + } finally { + mutex.unlock(); + } + + data.writeInt(tgi); + for (int i = 0; i < tgi; i++) { + data.writeLong(threadGroupIds[i]); + } + + return reply; + } + + @Override + public Packet VirtualMachine_AllClasses(Packet packet) throws JDWPException { + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer data = reply.dataWriter(); + + ResolvedJavaType[] allClasses = allReferenceTypes(); + + data.writeInt(allClasses.length); + for (ResolvedJavaType type : allClasses) { + data.writeByte(TypeTag.getKind(type)); + data.writeLong(JDWPBridgeImpl.getIds().getIdOrCreateWeak(type)); + data.writeString(ClassUtils.getTypeAsString(type)); + data.writeInt(ClassUtils.getStatus(type)); + } + + return reply; + } + + private static ResolvedJavaType[] allReferenceTypes() { + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + return universe.getTypes() + .stream() + // AllClasses cannot return primitive types. + .filter(type -> !type.isPrimitive()) + .toArray(ResolvedJavaType[]::new); + } + + @Override + public Packet ObjectReference_ReferenceType(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + Object object = readReferenceOrNull(reader); + assert reader.isEndOfInput(); + if (object == null) { + throw JDWPException.raise(ErrorCode.INVALID_OBJECT); + } + Class c = object.getClass(); + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + ResolvedJavaType type = universe.lookupType(c); + long typeId = JDWPBridgeImpl.getIds().getIdOrCreateWeak(type); + byte typeKind = TypeTag.getKind(type); + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer data = reply.dataWriter(); + + data.writeByte(typeKind); + data.writeLong(typeId); + + return reply; + } + + @Override + public Packet VirtualMachine_CreateString(Packet packet) throws JDWPException { + assert packet.commandSet() == JDWP.VirtualMachine; + assert packet.command() == JDWP.VirtualMachine_CreateString; + + Packet.Reader reader = packet.newDataReader(); + String str = reader.readString(); + assert reader.isEndOfInput(); + long stringId = JDWPBridgeImpl.getIds().getIdOrCreateWeak(str); + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + writer.writeLong(stringId); + return reply; + } + + @Override + public Packet StringReference_Value(Packet packet) throws JDWPException { + assert packet.commandSet() == JDWP.StringReference; + assert packet.command() == JDWP.StringReference_Value; + + Packet.Reader reader = packet.newDataReader(); + String string = readStringObject(reader); + assert reader.isEndOfInput(); + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + writer.writeString(string); + return reply; + } + + @Override + public Packet ReferenceType_ClassObject(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + InterpreterResolvedJavaType type = readType(reader); + assert reader.isEndOfInput(); + + Class javaClass = type.getJavaClass(); + long classId = JDWPBridgeImpl.getIds().getIdOrCreateWeak(javaClass); + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + writer.writeLong(classId); + + return reply; + } + + @Override + public Packet ClassObjectReference_ReflectedType(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + Class classObject = readClassObject(reader); + assert reader.isEndOfInput(); + + ResolvedJavaType type = DebuggerSupport.lookupType(classObject); + assert type != null; + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + long typeId = symbolicRefs.toTypeRef(type); + writer.writeByte(TypeTag.getKind(type)); + writer.writeLong(typeId); + return reply; + } + + @Override + public Packet ReferenceType_ClassLoader(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + InterpreterResolvedJavaType type = readType(reader); + assert reader.isEndOfInput(); + + Class javaClass = type.getJavaClass(); + ClassLoader classLoader = javaClass.getClassLoader(); + long classLoaderId = JDWPBridgeImpl.getIds().getIdOrCreateWeak(classLoader); + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + writer.writeLong(classLoaderId); + + return reply; + } + + @Override + public Packet ReferenceType_Module(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + InterpreterResolvedJavaType type = readType(reader); + assert reader.isEndOfInput(); + + Class javaClass = type.getJavaClass(); + Module module = javaClass.getModule(); + long moduleId = JDWPBridgeImpl.getIds().getIdOrCreateWeak(module); + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + writer.writeLong(moduleId); + + return reply; + } + + @Override + public Packet ModuleReference_Name(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + Module module = readModule(reader); + assert reader.isEndOfInput(); + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + + String moduleName = module.getName(); + // From HotSpot: The JDWP converts null into an empty string. + if (moduleName == null) { + moduleName = ""; + } + writer.writeString(moduleName); + return reply; + } + + /** + * This method return all the modules in the native image. Note that + * {@code ModuleLayer.boot().modules()} does not include unnamed modules. + */ + private static Module[] allModules() { + Set allModules = new HashSet<>(ModuleLayer.boot().modules()); + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + for (ResolvedJavaType type : universe.getTypes()) { + if (type instanceof InterpreterResolvedObjectType objectType) { + Class javaClass = objectType.getJavaClass(); + if (javaClass != null) { + Module module = javaClass.getModule(); + if (module != null) { + allModules.add(module); + } + } + } + } + return allModules.toArray(Module[]::new); + } + + @Override + public Packet VirtualMachine_AllModules(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + assert reader.isEndOfInput(); + + Module[] allModules = allModules(); + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + + writer.writeInt(allModules.length); + for (Module module : allModules) { + long moduleId = JDWPBridgeImpl.getIds().getIdOrCreateWeak(module); + writer.writeLong(moduleId); + } + + return reply; + } + + @Override + public Packet ModuleReference_ClassLoader(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + Module module = readModule(reader); + assert reader.isEndOfInput(); + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + ClassLoader classLoader = module.getClassLoader(); + long classLoaderId = JDWPBridgeImpl.getIds().getIdOrCreateWeak(classLoader); + writer.writeLong(classLoaderId); + return reply; + } + + @Override + public Packet ReferenceType_NestedTypes(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + InterpreterResolvedJavaType type = readType(reader); + assert reader.isEndOfInput(); + + Class javaClass = type.getJavaClass(); + Class[] declaredClasses = javaClass.getDeclaredClasses(); + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + writer.writeInt(declaredClasses.length); + for (Class declaredClass : declaredClasses) { + ResolvedJavaType declaredType = DebuggerSupport.lookupType(declaredClass); + long declaredTypeId = JDWPBridgeImpl.getIds().getIdOrCreateWeak(declaredType); + writer.writeLong(declaredTypeId); + } + + return reply; + } + + @Override + public Packet ThreadReference_IsVirtual(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + Thread thread = readThread(reader); + assert reader.isEndOfInput(); + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + writer.writeBoolean(thread.isVirtual()); + return reply; + } + + @Override + public Packet ArrayReference_Length(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + Object array = readArray(reader); + assert array.getClass().isArray(); + assert reader.isEndOfInput(); + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + int arrayLength = InterpreterToVM.arrayLength(array); + writer.writeInt(arrayLength); + return reply; + } + + private static void validateArrayRegion(int firstIndex, int length, int arrayLength) { + if (firstIndex < 0 || firstIndex >= arrayLength) { + throw JDWPException.raise(ErrorCode.INVALID_INDEX); + } + if (length < 0 || firstIndex > arrayLength - length) { + throw JDWPException.raise(ErrorCode.INVALID_LENGTH); + } + } + + @Override + public Packet ArrayReference_GetValues(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + Object array = readArray(reader); + + int firstIndex = reader.readInt(); + int length = reader.readInt(); + + assert reader.isEndOfInput(); + + int arrayLength = InterpreterToVM.arrayLength(array); + if (length == -1) { + // Read all remaining values. + // Not in the JDWP spec, but included in HotSpot's JDWP implementation. + length = arrayLength - firstIndex; + } + validateArrayRegion(firstIndex, length, arrayLength); + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + + Class componentType = array.getClass().getComponentType(); + boolean isWordTypeComponent = WordBase.class.isAssignableFrom(componentType); + + byte componentStorageTag; + if (isWordTypeComponent) { + componentStorageTag = switch (InterpreterToVM.wordJavaKind()) { + case Int -> TagConstants.INT; + case Long -> TagConstants.LONG; + default -> + throw VMError.shouldNotReachHere("Unexpected word kind " + InterpreterToVM.wordJavaKind()); + }; + } else { + componentStorageTag = TagConstants.getTagFromClass(componentType); + } + + // The first byte is a signature byte which is used to identify the type. + writer.writeByte(componentStorageTag); + + // Next is a four-byte integer indicating the number of values in the sequence. + writer.writeInt(length); + + assert firstIndex >= 0; + assert firstIndex < arrayLength; + assert firstIndex <= arrayLength - length; + + // This is followed by the values themselves. + if (isWordTypeComponent) { + for (int i = firstIndex; i - firstIndex < length; ++i) { + WordBase value = InterpreterToVM.getArrayWord(i, (WordBase[]) array); + switch (InterpreterToVM.wordJavaKind()) { + case Int -> writer.writeInt((int) value.rawValue()); + case Long -> writer.writeLong(value.rawValue()); + default -> + throw VMError.shouldNotReachHere("Unexpected word kind " + InterpreterToVM.wordJavaKind()); + } + } + } else if (componentType.isPrimitive()) { + // Primitive values are encoded as a sequence of untagged-values. + for (int i = firstIndex; i - firstIndex < length; ++i) { + switch (componentStorageTag) { + case TagConstants.INT -> { + int value = InterpreterToVM.getArrayInt(i, (int[]) array); + writer.writeInt(value); + } + case TagConstants.FLOAT -> { + float value = InterpreterToVM.getArrayFloat(i, (float[]) array); + writer.writeFloat(value); + } + case TagConstants.DOUBLE -> { + double value = InterpreterToVM.getArrayDouble(i, (double[]) array); + writer.writeDouble(value); + } + case TagConstants.LONG -> { + long value = InterpreterToVM.getArrayLong(i, (long[]) array); + writer.writeLong(value); + } + case TagConstants.BYTE -> { + byte value = InterpreterToVM.getArrayByte(i, array); + writer.writeByte(value); + } + case TagConstants.SHORT -> { + short value = InterpreterToVM.getArrayShort(i, (short[]) array); + writer.writeShort(value); + } + case TagConstants.CHAR -> { + char value = InterpreterToVM.getArrayChar(i, (char[]) array); + writer.writeChar(value); + } + case TagConstants.BOOLEAN -> { + byte value = InterpreterToVM.getArrayByte(i, array); + writer.writeBoolean(value != 0); + } + default -> throw VMError.shouldNotReachHere("Illegal primitive component tag: " + componentStorageTag); + } + } + } else { + // Object values are encoded as a sequence of values. + for (int i = firstIndex; i - firstIndex < length; ++i) { + Object value = InterpreterToVM.getArrayObject(i, (Object[]) array); + writeTaggedObject(writer, value); + } + } + + return reply; + } + + @Override + public Packet ArrayReference_SetValues(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + Object array = readArray(reader); + + int firstIndex = reader.readInt(); + int length = reader.readInt(); + int arrayLength = InterpreterToVM.arrayLength(array); + validateArrayRegion(firstIndex, length, arrayLength); + + Class componentType = array.getClass().getComponentType(); + byte componentStorageTag; + boolean isWordTypeComponent = WordBase.class.isAssignableFrom(componentType); + if (isWordTypeComponent) { + componentStorageTag = switch (InterpreterToVM.wordJavaKind()) { + case Int -> TagConstants.INT; + case Long -> TagConstants.LONG; + default -> + throw VMError.shouldNotReachHere("Unexpected word kind " + InterpreterToVM.wordJavaKind()); + }; + } else { + componentStorageTag = TagConstants.getTagFromClass(componentType); + } + + assert firstIndex >= 0; + assert firstIndex < arrayLength; + assert firstIndex <= arrayLength - length; + + // This is followed by the values themselves. + if (isWordTypeComponent) { + for (int i = firstIndex; i - firstIndex < length; ++i) { + switch (InterpreterToVM.wordJavaKind()) { + case Int -> { + WordBase value = WordFactory.signed(reader.readInt()); + InterpreterToVM.setArrayWord(value, i, (WordBase[]) array); + } + case Long -> { + WordBase value = WordFactory.signed(reader.readLong()); + InterpreterToVM.setArrayWord(value, i, (WordBase[]) array); + } + default -> + throw VMError.shouldNotReachHere("Unexpected word kind " + InterpreterToVM.wordJavaKind()); + } + } + } else if (componentType.isPrimitive()) { + // For primitive values, each value's type must match the array component type exactly. + // Primitive values are encoded as a sequence of untagged-values. + for (int i = firstIndex; i - firstIndex < length; ++i) { + switch (componentStorageTag) { + case TagConstants.INT -> { + int value = reader.readInt(); + InterpreterToVM.setArrayInt(value, i, (int[]) array); + } + case TagConstants.FLOAT -> { + float value = reader.readFloat(); + InterpreterToVM.setArrayFloat(value, i, (float[]) array); + } + case TagConstants.DOUBLE -> { + double value = reader.readDouble(); + InterpreterToVM.setArrayDouble(value, i, (double[]) array); + } + case TagConstants.LONG -> { + long value = reader.readLong(); + InterpreterToVM.setArrayLong(value, i, (long[]) array); + } + case TagConstants.BYTE -> { + byte value = (byte) reader.readByte(); + InterpreterToVM.setArrayByte(value, i, array); + } + case TagConstants.SHORT -> { + short value = reader.readShort(); + InterpreterToVM.setArrayShort(value, i, (short[]) array); + } + case TagConstants.CHAR -> { + char value = reader.readChar(); + InterpreterToVM.setArrayChar(value, i, (char[]) array); + } + case TagConstants.BOOLEAN -> { + boolean value = reader.readBoolean(); + InterpreterToVM.setArrayByte(value ? (byte) 1 : (byte) 0, i, array); + } + default -> throw VMError.shouldNotReachHere("Illegal primitive component tag: " + componentStorageTag); + } + } + } else { + // For object values, there must be a widening reference conversion from the value's + // type to the array component type and the array component type must be loaded. + // Object values are encoded as a sequence of untagged-values. + for (int i = firstIndex; i - firstIndex < length; ++i) { + Object value = readReferenceOrNull(reader); + if (value != null && !componentType.isInstance(value)) { + throw JDWPException.raise(ErrorCode.TYPE_MISMATCH); + } + InterpreterToVM.setArrayObject(value, i, (Object[]) array); + } + } + + assert reader.isEndOfInput(); + + return WritablePacket.newReplyTo(packet); // empty reply + } + + @Override + public Packet ArrayType_NewInstance(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + ResolvedJavaType arrayType = readType(reader); + if (!arrayType.isArray()) { + throw JDWPException.raise(ErrorCode.TYPE_MISMATCH); + } + + int length = reader.readInt(); + if (length < 0) { + throw JDWPException.raise(ErrorCode.INVALID_LENGTH); + } + assert reader.isEndOfInput(); + + ResolvedJavaType componentType = arrayType.getComponentType(); + Object array; + try { + if (componentType.isPrimitive()) { + assert componentType.getJavaKind() != JavaKind.Void; + array = InterpreterToVM.createNewPrimitiveArray((byte) componentType.getJavaKind().getBasicType(), length); + } else { + array = InterpreterToVM.createNewReferenceArray((InterpreterResolvedJavaType) componentType, length); + } + } catch (OutOfMemoryError e) { + throw JDWPException.raise(ErrorCode.OUT_OF_MEMORY); + } + + assert array != null; + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + writeTaggedObject(writer, array); + return reply; + } + + private static StackframeDescriptor getStackframeDescriptor(Thread thread, int frameDepth) { + assert !thread.isVirtual(); + GetStackframeDescriptorAtDepthVisitor visitor = new GetStackframeDescriptorAtDepthVisitor(false, frameDepth); + SafeStackWalker.safeStackWalk(thread, visitor); + return visitor; + } + + public static Object getThis(Thread thread, int frameDepth) { + StackframeDescriptor stackframeDescriptor = getStackframeDescriptor(thread, frameDepth); + FrameSourceInfo frameSourceInfo = stackframeDescriptor.getFrameSourceInfo(); + require(frameSourceInfo != null, ErrorCode.INVALID_FRAMEID, + "Frame depth %s not found for thread.threadId()=%s", frameDepth, thread.threadId()); + + /* + * Return null for static or native methods; otherwise, the JVM spec guarantees that "this" + * is in slot 0. + */ + Object thisObject; + if (frameSourceInfo instanceof InterpreterFrameSourceInfo interpreterJavaFrameInfo) { + ResolvedJavaMethod method = interpreterJavaFrameInfo.getInterpretedMethod(); + if (method.isStatic() || method.isNative()) { + thisObject = null; + } else { + InterpreterFrame interpreterFrame = (InterpreterFrame) interpreterJavaFrameInfo.getInterpreterFrame(); + thisObject = EspressoFrame.getThis(interpreterFrame); + } + } else { + FrameInfoQueryResult frameInfoQueryResult = (FrameInfoQueryResult) frameSourceInfo; + + if (frameInfoQueryResult.isNativeMethod()) { + thisObject = null; + } else { + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + ResolvedJavaType type = universe.lookupType(frameInfoQueryResult.getSourceClass()); + ResolvedJavaMethod method = JDWPBridgeImpl.findSourceMethod(type, frameInfoQueryResult); + + assert !method.isNative(); + + if (method.isStatic()) { + thisObject = null; + } else { + DeoptState deoptState = new DeoptState(stackframeDescriptor.getStackPointer(), WordFactory.zero()); + JavaConstant javaConstant = deoptState.readLocalVariable(0, frameInfoQueryResult); + thisObject = SubstrateObjectConstant.asObject(javaConstant); + } + } + } + return thisObject; + } + + @Override + public Packet StackFrame_ThisObject(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + Thread thread = readThread(reader); + + // The server ensures that the thread is suspended/parked. + // A thread may be parked or even in a RUNNABLE state, but still be considered suspended by + // the debugger. + assert !thread.isVirtual(); + long frameId = reader.readLong(); + // frameId is validated on the server. + int frameDepth = FrameId.getFrameDepth(frameId); + assert reader.isEndOfInput(); + + Object thisObject = getThis(thread, frameDepth); + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + writeTaggedObject(writer, thisObject); + return reply; + } + + @Override + public Packet StackFrame_GetValues(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + Thread thread = readThread(reader); + assert !thread.isVirtual(); + + long frameId = reader.readLong(); + int frameDepth = FrameId.getFrameDepth(frameId); + assert frameDepth >= 0; + + int slots = reader.readInt(); + assert slots >= 0; + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + + StackframeDescriptor stackframeDescriptor = getStackframeDescriptor(thread, frameDepth); + FrameSourceInfo frameSourceInfo = stackframeDescriptor.getFrameSourceInfo(); + require(frameSourceInfo != null, ErrorCode.INVALID_FRAMEID, + "Frame depth %s not found for thread.threadId()=%s", frameDepth, thread.threadId()); + + // The number of values retrieved, always equal to slots, the number of values to get. + writer.writeInt(slots); + + Pointer stackPointer = stackframeDescriptor.getStackPointer(); + for (int i = 0; i < slots; i++) { + // The local variable's index in the frame. + int slot = reader.readInt(); + byte tag = JDWP.readTag(reader); + + if (frameSourceInfo instanceof InterpreterFrameSourceInfo interpreterJavaFrameInfo) { + readLocalFromInterpreterFrame(tag, interpreterJavaFrameInfo, slot, writer); + } else { + FrameInfoQueryResult frameInfoQueryResult = (FrameInfoQueryResult) frameSourceInfo; + readLocalFromCompiledFrame(tag, frameInfoQueryResult, stackPointer, slot, writer); + } + } + + assert reader.isEndOfInput(); + + return reply; + } + + private static void sharedReadField(Packet.Writer writer, Object typeOrReceiver, InterpreterResolvedJavaField field) { + + Object receiver; + JavaKind fieldKind = field.getJavaKind(); + if (field.isStatic()) { + assert typeOrReceiver instanceof InterpreterResolvedJavaType; + // typeOrReceiver is ignored, all static fields are grouped together. + receiver = (fieldKind.isPrimitive() || field.getType().isWordType()) + ? StaticFieldsSupport.getStaticPrimitiveFields() + : StaticFieldsSupport.getStaticObjectFields(); + } else { + receiver = typeOrReceiver; + assert receiver != null; + } + + if (field.isUndefined()) { + writer.writeByte(TagConstants.VOID); + return; + } + + assert !field.isUndefined() : "Cannot read undefined field " + field; + + if (field.getType().isWordType()) { + switch (InterpreterToVM.wordJavaKind()) { + case Int -> { + writer.writeByte(TagConstants.INT); + writer.writeInt((int) InterpreterToVM.getFieldWord(receiver, field).rawValue()); + } + case Long -> { + writer.writeByte(TagConstants.LONG); + writer.writeLong(InterpreterToVM.getFieldWord(receiver, field).rawValue()); + } + } + return; + } + + switch (fieldKind) { + case Boolean -> { + writer.writeByte(TagConstants.BOOLEAN); + writer.writeBoolean(InterpreterToVM.getFieldBoolean(receiver, field)); + } + case Byte -> { + writer.writeByte(TagConstants.BYTE); + writer.writeByte(InterpreterToVM.getFieldByte(receiver, field)); + } + case Short -> { + writer.writeByte(TagConstants.SHORT); + writer.writeShort(InterpreterToVM.getFieldShort(receiver, field)); + } + case Char -> { + writer.writeByte(TagConstants.CHAR); + writer.writeChar(InterpreterToVM.getFieldChar(receiver, field)); + } + case Int -> { + writer.writeByte(TagConstants.INT); + writer.writeInt(InterpreterToVM.getFieldInt(receiver, field)); + } + case Float -> { + writer.writeByte(TagConstants.FLOAT); + writer.writeFloat(InterpreterToVM.getFieldFloat(receiver, field)); + } + case Long -> { + writer.writeByte(TagConstants.LONG); + writer.writeLong(InterpreterToVM.getFieldLong(receiver, field)); + } + case Double -> { + writer.writeByte(TagConstants.DOUBLE); + writer.writeDouble(InterpreterToVM.getFieldDouble(receiver, field)); + } + case Object -> { + Object value = InterpreterToVM.getFieldObject(receiver, field); + writeTaggedObject(writer, value); + } + default -> throw JDWPException.raise(ErrorCode.INVALID_FIELDID); + } + } + + @Override + public Packet ObjectReference_GetValues(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + Object object = readReferenceOrNull(reader); + if (object == null) { + throw JDWPException.raise(ErrorCode.INVALID_OBJECT); + } + + int length = reader.readInt(); + assert length >= 0; + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + writer.writeInt(length); + + for (int i = 0; i < length; i++) { + InterpreterResolvedJavaField field = readField(reader); + if (field.isStatic() || !InterpreterToVM.instanceOf(object, field.getDeclaringClass())) { + // Field is static or not present in the given object. + throw JDWPException.raise(ErrorCode.INVALID_FIELDID); + } + sharedReadField(writer, object, field); + } + + assert reader.isEndOfInput(); + + return reply; + } + + @Override + public Packet ReferenceType_GetValues(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + InterpreterResolvedJavaType type = readType(reader); + int length = reader.readInt(); + assert length >= 0; + + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + writer.writeInt(length); + + for (int i = 0; i < length; i++) { + InterpreterResolvedJavaField field = readField(reader); + if (!field.isStatic() || + !field.getDeclaringClass().getJavaClass().isAssignableFrom(type.getJavaClass())) { + // Instance field or field is not included in superclasses, superinterfaces, or + // implemented interfaces. + throw JDWPException.raise(ErrorCode.INVALID_FIELDID); + } + sharedReadField(writer, type, field); + } + + assert reader.isEndOfInput(); + + return reply; + } + + @Override + public Packet ClassLoaderReference_VisibleClasses(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + // The boot class loader (null) is accepted. + Object object = readReferenceOrNull(reader); + if (object != null && !(object instanceof ClassLoader)) { + throw JDWPException.raise(ErrorCode.INVALID_CLASS_LOADER); + } + assert reader.isEndOfInput(); + + ClassLoader classLoader = (ClassLoader) object; + List visibleTypes = classesWithInitiatingClassLoader(classLoader); + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + writer.writeInt(visibleTypes.size()); + for (ResolvedJavaType type : visibleTypes) { + writer.writeByte(TypeTag.getKind(type)); + writer.writeLong(JDWPBridgeImpl.getIds().getIdOrCreateWeak(type)); + } + + return reply; + } + + private static Class getElementalClass(Class clazz) { + Class elemental = clazz; + while (elemental.isArray()) { + elemental = elemental.getComponentType(); + } + return elemental; + } + + /** + * Returns the set of types reachable by the given class loader e.g. all classes for which the + * given class loader is an "initiating" class loader. + */ + private static List classesWithInitiatingClassLoader(ClassLoader classLoader) { + List visibleTypes = new ArrayList<>(); + // Traverse all types in the universe and check if the class is reachable + // by name, without resolution; this can be slow. + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + for (ResolvedJavaType type : universe.getTypes()) { + if (type.isPrimitive()) { + continue; + } + Class javaClass = ((InterpreterResolvedJavaType) type).getJavaClass(); + // On SVM, some classes may not be loaded yet, the Class instance is present but + // doesn't have a class loader associated with it yet. + if (!DynamicHub.fromClass(javaClass).isLoaded()) { + continue; + } + // Do not include hidden classes or interfaces or array classes whose element type is a + // hidden class or interface as they cannot be discovered by any class loader. + if (getElementalClass(javaClass).isHidden()) { + continue; + } + + ClassLoader bootLoader = null; + ClassLoader platformLoader = ClassLoader.getPlatformClassLoader(); + ClassLoader appLoader = ClassLoader.getSystemClassLoader(); + + ClassLoader loader = javaClass.getClassLoader(); + + // TODO(peterssen): GR-55067 SVM's ClassLoader#findLoadedClass doesn't respect + // class-loader hierarchy. + // SVM's ClassLoader#findLoadedClass is out-of spec e.g. the platform + // class loader can "find" classes defined by the application class loader. + // This is an attempt to fix the issue for known class loaders. + // These checks enforce the following class loader delegation hierarchy: + // System/application -> Platform -> Boot (null). + boolean isClassVisible = false; + if (classLoader == bootLoader) { + isClassVisible = (loader == bootLoader); + } else if (classLoader == platformLoader) { + isClassVisible = (loader == bootLoader || loader == platformLoader); + } else if (classLoader == appLoader) { + isClassVisible = (loader == bootLoader || loader == platformLoader || loader == appLoader); + } else { + // SVM equivalent to ClassLoader#findLoadedClass. + Class forNameClass = ClassForNameSupport.forNameOrNull(type.toClassName(), classLoader); + if (javaClass == forNameClass) { + isClassVisible = true; + } + } + + if (isClassVisible) { + visibleTypes.add(type); + } + } + + return visibleTypes; + } + + private static void readLocalFromCompiledFrame(byte tag, FrameInfoQueryResult frame, Pointer stackPointer, int slot, Packet.Writer writer) throws JDWPException { + if (!(slot >= 0 && slot <= frame.getNumLocals())) { + throw JDWPException.raise(ErrorCode.INVALID_SLOT); + } + + if (frame.getValueInfos() == null) { + /* missing locals info for this method */ + // ABSENT_INFORMATION not expected for this command. + // IDE's should deal with the error code, reporting that the information is missing + // is better than reporting nothing at all. + throw JDWPException.raise(ErrorCode.ABSENT_INFORMATION); + } + + IsolateThread targetThread = WordFactory.zero(); + DeoptState deoptState = new DeoptState(stackPointer, targetThread); + JavaConstant javaConstant = deoptState.readLocalVariable(slot, frame); + + if (javaConstant.getJavaKind().equals(JavaKind.Illegal)) { + /* either value was not encoded or it is unknown */ + // ABSENT_INFORMATION not expected for this command. + // IDE's should deal with the error code, reporting that the information is missing + // is better than reporting nothing at all. + throw JDWPException.raise(ErrorCode.ABSENT_INFORMATION); + } else { + switch (tag) { + case TagConstants.BYTE -> { + expectKind(javaConstant, JavaKind.Byte.getStackKind()); + writer.writeByte(TagConstants.BYTE); + writer.writeByte((byte) javaConstant.asInt()); + } + case TagConstants.BOOLEAN -> { + expectKind(javaConstant, JavaKind.Boolean.getStackKind()); + writer.writeByte(TagConstants.BOOLEAN); + writer.writeBoolean(javaConstant.asInt() != 0); + } + case TagConstants.SHORT -> { + expectKind(javaConstant, JavaKind.Short.getStackKind()); + writer.writeByte(TagConstants.SHORT); + writer.writeShort((short) javaConstant.asInt()); + } + case TagConstants.CHAR -> { + expectKind(javaConstant, JavaKind.Char.getStackKind()); + writer.writeByte(TagConstants.CHAR); + writer.writeChar((char) javaConstant.asInt()); + } + case TagConstants.INT -> { + expectKind(javaConstant, JavaKind.Int); + writer.writeByte(TagConstants.INT); + writer.writeInt(javaConstant.asInt()); + } + case TagConstants.LONG -> { + expectKind(javaConstant, JavaKind.Long); + writer.writeByte(TagConstants.LONG); + writer.writeLong(javaConstant.asLong()); + } + case TagConstants.FLOAT -> { + expectKind(javaConstant, JavaKind.Float); + writer.writeByte(TagConstants.FLOAT); + writer.writeFloat(javaConstant.asFloat()); + } + case TagConstants.DOUBLE -> { + expectKind(javaConstant, JavaKind.Double); + writer.writeByte(TagConstants.DOUBLE); + writer.writeDouble(javaConstant.asDouble()); + } + case TagConstants.VOID -> { + // Should this be unreachable instead? + writer.writeByte(TagConstants.VOID); + } + default -> { + expectKind(javaConstant, JavaKind.Object); + Object value = SubstrateObjectConstant.asObject(javaConstant); + byte valueTag = TagConstants.getTagFromReference(value); + // Value tag overrides provided tag. + writer.writeByte(valueTag); + writer.writeLong(JDWPBridgeImpl.getIds().getIdOrCreateWeak(value)); + } + } + } + } + + private static void expectKind(JavaConstant jc, JavaKind expectedKind) { + JavaKind givenKind = jc.getJavaKind(); + if (givenKind != expectedKind) { + throw JDWPException.raise(ErrorCode.INVALID_TAG); + } + } + + private static void readLocalFromInterpreterFrame(byte tag, InterpreterFrameSourceInfo interpreterJavaFrameInfo, int slot, Packet.Writer writer) throws JDWPException { + LocalVariableTable localVariableTable = interpreterJavaFrameInfo.getInterpretedMethod().getLocalVariableTable(); + /* + * Even if local variable information is not available, values can be retrieved if the + * front-end is able to determine the correct local variable index. Typically, this index + * can be determined for method arguments from the method signature without access to the + * local variable table information. + */ + if (localVariableTable != null) { + Local local = localVariableTable.getLocal(slot, interpreterJavaFrameInfo.getBci()); + if (local == null) { + throw JDWPException.raise(ErrorCode.INVALID_SLOT); + } + JavaKind localKind = local.getType().getJavaKind(); + JavaKind tagKind = TagConstants.tagToKind(tag); + if (localKind != tagKind) { + throw JDWPException.raise(ErrorCode.INVALID_TAG); + } + } + InterpreterFrame interpreterFrame = (InterpreterFrame) interpreterJavaFrameInfo.getInterpreterFrame(); + switch (tag) { + case TagConstants.BYTE -> { + int value = EspressoFrame.getLocalInt(interpreterFrame, slot); + writer.writeByte(TagConstants.BYTE); + writer.writeByte((byte) value); + } + case TagConstants.BOOLEAN -> { + int value = EspressoFrame.getLocalInt(interpreterFrame, slot); + writer.writeByte(TagConstants.BOOLEAN); + writer.writeBoolean(value != 0); + } + case TagConstants.SHORT -> { + int value = EspressoFrame.getLocalInt(interpreterFrame, slot); + writer.writeByte(TagConstants.SHORT); + writer.writeShort((short) value); + } + case TagConstants.CHAR -> { + int value = EspressoFrame.getLocalInt(interpreterFrame, slot); + writer.writeByte(TagConstants.CHAR); + writer.writeChar((char) value); + } + case TagConstants.INT -> { + int value = EspressoFrame.getLocalInt(interpreterFrame, slot); + writer.writeByte(TagConstants.INT); + writer.writeInt(value); + } + case TagConstants.LONG -> { + long value = EspressoFrame.getLocalLong(interpreterFrame, slot); + writer.writeByte(TagConstants.LONG); + writer.writeLong(value); + } + case TagConstants.FLOAT -> { + float value = EspressoFrame.getLocalFloat(interpreterFrame, slot); + writer.writeByte(TagConstants.FLOAT); + writer.writeFloat(value); + } + case TagConstants.DOUBLE -> { + double value = EspressoFrame.getLocalDouble(interpreterFrame, slot); + writer.writeByte(TagConstants.DOUBLE); + writer.writeDouble(value); + } + case TagConstants.VOID -> { + writer.writeByte(TagConstants.VOID); + // Write nothing here. + } + default -> { + Object value = EspressoFrame.getLocalObject(interpreterFrame, slot); + writeTaggedObject(writer, value); + } + } + } + + @Override + public Packet ClassType_SetValues(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + InterpreterResolvedJavaType type = readType(reader); + + if (!DynamicHub.fromClass(type.getJavaClass()).isLoaded()) { + throw JDWPException.raise(ErrorCode.CLASS_NOT_PREPARED); + } + + int fieldCount = reader.readInt(); + assert fieldCount >= 0; + for (int i = 0; i < fieldCount; i++) { + InterpreterResolvedJavaField field = readField(reader); + InterpreterResolvedJavaType fieldType = field.getType(); + if (!field.isStatic()) { + throw JDWPException.raise(ErrorCode.ILLEGAL_ARGUMENT); + } + if (field.isUndefined() || fieldType.isWordType() || field.isUnmaterializedConstant()) { + throw JDWPException.raise(ErrorCode.ILLEGAL_ARGUMENT); + } + sharedWriteField(reader, type, field); + } + + assert reader.isEndOfInput(); + + // Empty response. + return WritablePacket.newReplyTo(packet); + } + + @Override + public Packet ObjectReference_SetValues(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + Object receiver = readReferenceOrNull(reader); + if (receiver == null) { + throw JDWPException.raise(ErrorCode.ILLEGAL_ARGUMENT); + } + int fieldCount = reader.readInt(); + assert fieldCount >= 0; + for (int i = 0; i < fieldCount; i++) { + InterpreterResolvedJavaField field = readField(reader); + InterpreterResolvedJavaType fieldType = field.getType(); + if (field.isStatic()) { + throw JDWPException.raise(ErrorCode.ILLEGAL_ARGUMENT); + } + if (field.isUndefined() || fieldType.isWordType() || field.isUnmaterializedConstant()) { + throw JDWPException.raise(ErrorCode.ILLEGAL_ARGUMENT); + } + sharedWriteField(reader, receiver, field); + } + + assert reader.isEndOfInput(); + + // Empty response. + return WritablePacket.newReplyTo(packet); + } + + private static void sharedWriteField(Packet.Reader reader, Object typeOrReceiver, InterpreterResolvedJavaField field) { + Object receiver; + JavaKind fieldKind = field.getJavaKind(); + if (field.isStatic()) { + assert typeOrReceiver instanceof InterpreterResolvedJavaType; + // typeOrReceiver is ignored, all static fields are grouped together. + receiver = (fieldKind.isPrimitive() || field.getType().isWordType()) + ? StaticFieldsSupport.getStaticPrimitiveFields() + : StaticFieldsSupport.getStaticObjectFields(); + } else { + receiver = typeOrReceiver; + assert receiver != null; + } + + if (field.isUndefined() || field.isUnmaterializedConstant()) { + throw JDWPException.raise(ErrorCode.ILLEGAL_ARGUMENT); + } + + assert !field.isUndefined() && !field.isUnmaterializedConstant() // + : "Cannot write undefined or unmaterialized field " + field; + + if (field.getType().isWordType()) { + switch (InterpreterToVM.wordJavaKind()) { + case Int -> + InterpreterToVM.setFieldWord(WordFactory.signed(reader.readInt()), receiver, field); + case Long -> + InterpreterToVM.setFieldWord(WordFactory.signed(reader.readLong()), receiver, field); + default -> + throw VMError.shouldNotReachHere("Unexpected word kind " + InterpreterToVM.wordJavaKind()); + } + return; + } + + // @formatter:off + switch (fieldKind) { + case Boolean -> InterpreterToVM.setFieldBoolean(reader.readBoolean(), receiver, field); + case Byte -> InterpreterToVM.setFieldByte((byte) reader.readByte(), receiver, field); + case Short -> InterpreterToVM.setFieldShort(reader.readShort(), receiver, field); + case Char -> InterpreterToVM.setFieldChar(reader.readChar(), receiver, field); + case Int -> InterpreterToVM.setFieldInt(reader.readInt(), receiver, field); + case Float -> InterpreterToVM.setFieldFloat(reader.readFloat(), receiver, field); + case Long -> InterpreterToVM.setFieldLong(reader.readLong(), receiver, field); + case Double -> InterpreterToVM.setFieldDouble(reader.readDouble(), receiver, field); + case Object -> { + assert !field.getType().isWordType() : field; // handled above + Object value = readReferenceOrNull(reader); + if (value != null && !field.getType().getJavaClass().isInstance(value)) { + throw JDWPException.raise(ErrorCode.TYPE_MISMATCH); + } + InterpreterToVM.setFieldObject(value, receiver, field); + } + default -> throw JDWPException.raise(ErrorCode.INVALID_FIELDID); + } + // @formatter:on + } + + @Override + public Packet StackFrame_SetValues(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + Thread thread = readThread(reader); + assert !thread.isVirtual(); + + long frameId = reader.readLong(); + int frameDepth = FrameId.getFrameDepth(frameId); + assert frameDepth >= 0; + + // The number of values to set. + int slotValues = reader.readInt(); + assert slotValues >= 0; + + StackframeDescriptor stackframeDescriptor = getStackframeDescriptor(thread, frameDepth); + FrameSourceInfo frameSourceInfo = stackframeDescriptor.getFrameSourceInfo(); + require(frameSourceInfo != null, ErrorCode.INVALID_FRAMEID, + "Frame depth %s not found for thread.threadId()=%s", frameDepth, thread.threadId()); + + if (!(frameSourceInfo instanceof InterpreterFrameSourceInfo)) { + // GR-55013: Add support for writing locals in compiled frames. + throw JDWPException.raise(ErrorCode.NOT_IMPLEMENTED); + } + + for (int i = 0; i < slotValues; i++) { + // The local variable's index in the frame. + int slot = reader.readInt(); + byte tag = JDWP.readTag(reader); + InterpreterFrameSourceInfo interpreterJavaFrameInfo = (InterpreterFrameSourceInfo) frameSourceInfo; + writeLocalToInterpreterFrame(tag, interpreterJavaFrameInfo, slot, reader); + } + + assert reader.isEndOfInput(); + + // Empty response. + return WritablePacket.newReplyTo(packet); + } + + private static void writeLocalToInterpreterFrame(byte tag, InterpreterFrameSourceInfo interpreterJavaFrameInfo, int slot, Packet.Reader reader) { + LocalVariableTable localVariableTable = interpreterJavaFrameInfo.getInterpretedMethod().getLocalVariableTable(); + /* + * Even if local variable information is not available, values can be retrieved if the + * front-end is able to determine the correct local variable index. Typically, this index + * can be determined for method arguments from the method signature without access to the + * local variable table information. + */ + if (localVariableTable != null) { + Local local = localVariableTable.getLocal(slot, interpreterJavaFrameInfo.getBci()); + if (local == null) { + throw JDWPException.raise(ErrorCode.INVALID_SLOT); + } + JavaKind localKind = local.getType().getJavaKind(); + JavaKind tagKind = TagConstants.tagToKind(tag); + if (localKind != tagKind) { + throw JDWPException.raise(ErrorCode.INVALID_TAG); + } + } + InterpreterFrame interpreterFrame = (InterpreterFrame) interpreterJavaFrameInfo.getInterpreterFrame(); + // @formatter:off + switch (tag) { + case TagConstants.BYTE -> EspressoFrame.setLocalInt(interpreterFrame, slot, (byte) reader.readByte()); + case TagConstants.BOOLEAN -> EspressoFrame.setLocalInt(interpreterFrame, slot, reader.readBoolean() ? 1 : 0); + case TagConstants.SHORT -> EspressoFrame.setLocalInt(interpreterFrame, slot, reader.readShort()); + case TagConstants.CHAR -> EspressoFrame.setLocalInt(interpreterFrame, slot, reader.readChar()); + case TagConstants.INT -> EspressoFrame.setLocalInt(interpreterFrame, slot, reader.readInt()); + case TagConstants.LONG -> EspressoFrame.setLocalLong(interpreterFrame, slot, reader.readLong()); + case TagConstants.FLOAT -> EspressoFrame.setLocalFloat(interpreterFrame, slot, reader.readFloat()); + case TagConstants.DOUBLE -> EspressoFrame.setLocalDouble(interpreterFrame, slot, reader.readDouble()); + case TagConstants.VOID -> { } // nothing + default -> EspressoFrame.setLocalObject(interpreterFrame, slot, readReferenceOrNull(reader)); + } + // @formatter:on + } + + private static Object[] readArguments(Packet.Reader reader) { + int argCount = reader.readInt(); + assert argCount >= 0; + + Object[] args = new Object[argCount]; + for (int i = 0; i < argCount; i++) { + byte tag = JDWP.readTag(reader); + switch (tag) { + case TagConstants.BYTE -> args[i] = (byte) reader.readByte(); + case TagConstants.BOOLEAN -> args[i] = reader.readBoolean(); + case TagConstants.SHORT -> args[i] = reader.readShort(); + case TagConstants.CHAR -> args[i] = reader.readChar(); + case TagConstants.INT -> args[i] = reader.readInt(); + case TagConstants.LONG -> args[i] = reader.readLong(); + case TagConstants.FLOAT -> args[i] = reader.readFloat(); + case TagConstants.DOUBLE -> args[i] = reader.readDouble(); + case TagConstants.VOID -> { + // Read nothing. + } + default -> args[i] = readReferenceOrNull(reader); + } + } + + return args; + } + + record Result(Object value, Throwable throwable) { + + static Result fromValue(Object value) { + return new Result(value, null); + } + + static Result fromThrowable(Throwable throwable) { + return new Result(null, MetadataUtil.requireNonNull(throwable)); + } + + static Result ofInvoke(boolean isVirtual, InterpreterResolvedJavaMethod method, Object... args) { + try { + return fromValue(InterpreterToVM.dispatchInvocation(method, args, isVirtual, false, false, false)); + } catch (SemanticJavaException e) { + return fromThrowable(e.getCause()); + } catch (StackOverflowError | OutOfMemoryError error) { + return fromThrowable(error); + } + } + } + + private static void writeTaggedValue(Packet.Writer writer, Object value, JavaKind valueKind) { + switch (valueKind) { + case Boolean -> { + writer.writeByte(TagConstants.BOOLEAN); + writer.writeBoolean((boolean) value); + } + case Byte -> { + writer.writeByte(TagConstants.BYTE); + writer.writeByte((byte) value); + } + case Short -> { + writer.writeByte(TagConstants.SHORT); + writer.writeShort((short) value); + } + case Char -> { + writer.writeByte(TagConstants.CHAR); + writer.writeChar((char) value); + } + case Int -> { + writer.writeByte(TagConstants.INT); + writer.writeInt((int) value); + } + case Float -> { + writer.writeByte(TagConstants.FLOAT); + writer.writeFloat((float) value); + } + case Long -> { + writer.writeByte(TagConstants.LONG); + writer.writeLong((long) value); + } + case Double -> { + writer.writeByte(TagConstants.DOUBLE); + writer.writeDouble((double) value); + } + case Object -> { + writeTaggedObject(writer, value); + } + case Void -> { + writer.writeByte(TagConstants.VOID); + // write nothing + } + default -> + throw VMError.shouldNotReachHere("unexpected kind " + valueKind); + } + } + + /** + * Ensures that a given condition is true, throwing a {@link JDWPException} with the provided + * {@link ErrorCode error code} otherwise. Before throwing the {@link JDWPException exception}, + * the message is {@link Logger#log(String, Object...) logged}. + * + * @param logMessageSimpleFormat a "simple" format string + * @param args arguments referenced by the "simple" format string + */ + private static void require(boolean condition, ErrorCode errorCode, String logMessageSimpleFormat, Object... args) throws JDWPException { + if (!condition) { + LOGGER.log(logMessageSimpleFormat, args); + throw JDWPException.raise(errorCode); + } + } + + /** + * Ensures that a given condition is true, throwing a {@link JDWPException} with the provided + * {@link ErrorCode error code} otherwise. Before throwing the {@link JDWPException exception}, + * the {@link ErrorCode#getMessage() error message} associated with the {@link ErrorCode error + * code} is {@link Logger#log(String) logged}. + */ + @SuppressWarnings("unused") + private static void require(boolean condition, ErrorCode errorCode) throws JDWPException { + if (!condition) { + throw JDWPException.raise(errorCode); + } + } + + static Packet invokeReply(Packet packet, Result invokeResult, JavaKind returnValueKind) { + return invokeReply(packet, invokeResult.value(), invokeResult.throwable(), returnValueKind); + } + + static Packet invokeReply(Packet packet, Object value, Throwable throwable, JavaKind returnValueKind) { + WritablePacket reply = WritablePacket.newReplyTo(packet); + Packet.Writer writer = reply.dataWriter(); + /* + * The JDWP spec states that both, the receiver and the exception must be written. If an + * exception was thrown, write a 'null' return value. + * + * In case of exception, cannot reply with a return value of type/tag void since the vanilla + * JDI implementation expects the ClassType.NewInstance command to always return a value of + * type/tag object, regardless of exceptions and that methods actually returns void. + */ + if (throwable != null) { + writeTaggedObject(writer, null); + } else { + writeTaggedValue(writer, value, returnValueKind); + } + writeTaggedObject(writer, throwable); + return reply; + } + + @Override + public Packet ClassType_InvokeMethod(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + InterpreterResolvedJavaType type = readType(reader); + Thread thread = readThread(reader); + InterpreterResolvedJavaMethod method = readMethod(reader); + Object[] args = readArguments(reader); + @SuppressWarnings("unused") + int options = reader.readInt(); + assert reader.isEndOfInput(); + + require(thread == Thread.currentThread(), ErrorCode.ILLEGAL_ARGUMENT, "method invocation only supports current/same thread"); + require(method.isStatic(), ErrorCode.ILLEGAL_ARGUMENT, "method must be static %s", method); + require(type.equals(method.getDeclaringClass()), ErrorCode.ILLEGAL_ARGUMENT, "method declaring type %s and type %s differ", method.getDeclaringClass(), type); + require(!thread.isVirtual(), ErrorCode.ILLEGAL_ARGUMENT, "virtual threads not supported"); + // InvokeOptions.INVOKE_NONVIRTUAL is ignored. + + return invokeReply(packet, Result.ofInvoke(false, method, args), method.getSignature().getReturnKind()); + } + + @Override + public Packet InterfaceType_InvokeMethod(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + InterpreterResolvedJavaType type = readType(reader); + Thread thread = readThread(reader); + InterpreterResolvedJavaMethod method = readMethod(reader); + Object[] args = readArguments(reader); + @SuppressWarnings("unused") + int options = reader.readInt(); + assert reader.isEndOfInput(); + + require(!method.isClassInitializer(), ErrorCode.ILLEGAL_ARGUMENT, "method cannot be a static initializer %s", method); + require(method.isStatic(), ErrorCode.ILLEGAL_ARGUMENT, "method must be be static %s", method); + require(type.equals(method.getDeclaringClass()), ErrorCode.ILLEGAL_ARGUMENT, "method declaring type %s and type %s differ", method.getDeclaringClass(), type); + require(type.isInterface(), ErrorCode.ILLEGAL_ARGUMENT, "type %s is not an interface"); + require(type.equals(method.getDeclaringClass()), ErrorCode.ILLEGAL_ARGUMENT, "method %s is not a member of the interface type %s", method, type); + require(!thread.isVirtual(), ErrorCode.ILLEGAL_ARGUMENT, "virtual threads not supported"); + // InvokeOptions.INVOKE_NONVIRTUAL is ignored. + + return invokeReply(packet, Result.ofInvoke(false, method, args), method.getSignature().getReturnKind()); + } + + @Override + public Packet ObjectReference_InvokeMethod(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + Object receiver = readReferenceOrNull(reader); + Thread thread = readThread(reader); + @SuppressWarnings("unused") + InterpreterResolvedJavaType type = readType(reader); + InterpreterResolvedJavaMethod method = readMethod(reader); + Object[] argsWithoutReceiver = readArguments(reader); + int options = reader.readInt(); + assert reader.isEndOfInput(); + + require(receiver != null, ErrorCode.ILLEGAL_ARGUMENT, "receiver is null"); + require(!method.isStatic(), ErrorCode.ILLEGAL_ARGUMENT, "method cannot be static %s", method); + require(method.getDeclaringClass().isAssignableFrom(type), ErrorCode.ILLEGAL_ARGUMENT, + "method %s is not declared in type %s nor any of its super types (or super interfaces)", method, type); + require(method.getDeclaringClass().getJavaClass().isInstance(receiver), ErrorCode.ILLEGAL_ARGUMENT, + "method %s is not declared in the receiver type %s nor any of its super types (or super interfaces)", method, receiver.getClass()); + require(!thread.isVirtual(), ErrorCode.ILLEGAL_ARGUMENT, "virtual threads not supported"); + + Object[] args = prepend(receiver, argsWithoutReceiver); + boolean isVirtual = !InvokeOptions.nonVirtual(options); + return invokeReply(packet, Result.ofInvoke(isVirtual, method, args), method.getSignature().getReturnKind()); + } + + @Override + public Packet ClassType_NewInstance(Packet packet) throws JDWPException { + Packet.Reader reader = packet.newDataReader(); + InterpreterResolvedJavaType type = readType(reader); + Thread thread = readThread(reader); + InterpreterResolvedJavaMethod method = readMethod(reader); + Object[] argsWithoutReceiver = readArguments(reader); + @SuppressWarnings("unused") + int options = reader.readInt(); + assert reader.isEndOfInput(); + + require(!type.isPrimitive(), ErrorCode.ILLEGAL_ARGUMENT, "invalid primitive type %s", type); + require(!type.isArray(), ErrorCode.ILLEGAL_ARGUMENT, "invalid array type %s", type); + require(!type.isAbstract(), ErrorCode.ILLEGAL_ARGUMENT, "invalid abstract type %s", type); + require(method.isConstructor(), ErrorCode.ILLEGAL_ARGUMENT, "method is not a constructor %s", method); + require(!method.isStatic(), ErrorCode.ILLEGAL_ARGUMENT, "constructor cannot be static %s", method); + require(type.equals(method.getDeclaringClass()), ErrorCode.ILLEGAL_ARGUMENT, "constructor %s is not a member of the given type %s", method, type); + require(!thread.isVirtual(), ErrorCode.ILLEGAL_ARGUMENT, "virtual threads not supported"); + + Object instance; + try { + instance = InterpreterToVM.createNewReference(type); + assert instance != null; + } catch (SemanticJavaException e) { + return invokeReply(packet, null, e.getCause(), JavaKind.Object); + } + + Object[] args = prepend(instance, argsWithoutReceiver); + return invokeReply(packet, instance, Result.ofInvoke(false, method, args).throwable(), JavaKind.Object); + } + + private static Object[] prepend(Object newFirst, Object[] array) { + Object[] newArray = new Object[array.length + 1]; + newArray[0] = newFirst; + System.arraycopy(array, 0, newArray, 1, array.length); + return newArray; + } + + @Override + public Packet dispatch(Packet packet) throws JDWPException { + try { + return JDWP.super.dispatch(packet); + } catch (JDWPException e) { + ResidentJDWP.LOGGER.log(e, "JDWP exception"); + throw e; + } catch (Throwable t) { + ResidentJDWP.LOGGER.log(t, "Internal error"); + throw t; + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/ResidentSymbolicRefs.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/ResidentSymbolicRefs.java new file mode 100644 index 000000000000..7ffd2a315a4a --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/ResidentSymbolicRefs.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.resident.impl; + +import java.util.OptionalInt; + +import com.oracle.svm.interpreter.DebuggerSupport; +import com.oracle.svm.interpreter.metadata.InterpreterUniverse; +import com.oracle.svm.jdwp.bridge.ErrorCode; +import com.oracle.svm.jdwp.bridge.JDWPException; +import com.oracle.svm.jdwp.bridge.SymbolicRefs; +import com.oracle.svm.jdwp.resident.JDWPBridgeImpl; + +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + +public final class ResidentSymbolicRefs implements SymbolicRefs { + + @Override + public long toTypeRef(ResolvedJavaType resolvedJavaType) { + if (resolvedJavaType == null) { + return 0L; + } + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + OptionalInt typeIndexFor = universe.getTypeIndexFor(resolvedJavaType); + int typeIndex = typeIndexFor.orElseThrow(IllegalArgumentException::new); + return JDWPBridgeImpl.getIds().getIdOrCreateWeak(universe.getTypeAtIndex(typeIndex)); + } + + @Override + public long toFieldRef(ResolvedJavaField resolvedJavaField) { + if (resolvedJavaField == null) { + return 0L; + } + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + OptionalInt fieldIndexFor = universe.getFieldIndexFor(resolvedJavaField); + int fieldIndex = fieldIndexFor.orElseThrow(IllegalArgumentException::new); + return JDWPBridgeImpl.getIds().getIdOrCreateWeak(universe.getFieldAtIndex(fieldIndex)); + } + + @Override + public long toMethodRef(ResolvedJavaMethod resolvedJavaMethod) { + if (resolvedJavaMethod == null) { + return 0L; + } + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + OptionalInt methodIndexFor = universe.getMethodIndexFor(resolvedJavaMethod); + int methodIndex = methodIndexFor.orElseThrow(IllegalArgumentException::new); + return JDWPBridgeImpl.getIds().getIdOrCreateWeak(universe.getMethodAtIndex(methodIndex)); + } + + @Override + public ResolvedJavaType toResolvedJavaType(long typeRefId) throws JDWPException { + if (typeRefId == 0) { + return null; + } + ResolvedJavaType resolvedJavaType; + try { + resolvedJavaType = JDWPBridgeImpl.getIds().toObject(typeRefId, ResolvedJavaType.class); + } catch (ClassCastException e) { + throw JDWPException.raise(ErrorCode.INVALID_CLASS); + } + if (resolvedJavaType == null) { + throw JDWPException.raise(ErrorCode.INVALID_OBJECT); + } + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + OptionalInt typeIndexFor = universe.getTypeIndexFor(resolvedJavaType); + if (typeIndexFor.isEmpty()) { + throw JDWPException.raise(ErrorCode.INVALID_CLASS); + } + return resolvedJavaType; + } + + @Override + public ResolvedJavaField toResolvedJavaField(long fieldRefId) throws JDWPException { + if (fieldRefId == 0) { + return null; + } + ResolvedJavaField resolvedJavaField; + try { + resolvedJavaField = JDWPBridgeImpl.getIds().toObject(fieldRefId, ResolvedJavaField.class); + } catch (ClassCastException e) { + throw JDWPException.raise(ErrorCode.INVALID_FIELDID); + } + if (resolvedJavaField == null) { + throw JDWPException.raise(ErrorCode.INVALID_OBJECT); + } + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + OptionalInt typeIndexFor = universe.getFieldIndexFor(resolvedJavaField); + if (typeIndexFor.isEmpty()) { + throw JDWPException.raise(ErrorCode.INVALID_FIELDID); + } + return resolvedJavaField; + } + + @Override + public ResolvedJavaMethod toResolvedJavaMethod(long methodRefId) throws JDWPException { + if (methodRefId == 0) { + return null; + } + ResolvedJavaMethod resolvedJavaMethod; + try { + resolvedJavaMethod = JDWPBridgeImpl.getIds().toObject(methodRefId, ResolvedJavaMethod.class); + } catch (ClassCastException e) { + throw JDWPException.raise(ErrorCode.INVALID_METHODID); + } + if (resolvedJavaMethod == null) { + throw JDWPException.raise(ErrorCode.INVALID_OBJECT); + } + InterpreterUniverse universe = DebuggerSupport.singleton().getUniverse(); + OptionalInt methodIndexFor = universe.getMethodIndexFor(resolvedJavaMethod); + if (methodIndexFor.isEmpty()) { + throw JDWPException.raise(ErrorCode.INVALID_METHODID); + } + return resolvedJavaMethod; + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/SafeStackWalker.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/SafeStackWalker.java new file mode 100644 index 000000000000..5c147bf500d9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/SafeStackWalker.java @@ -0,0 +1,69 @@ +/* + * 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.jdwp.resident.impl; + +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.heap.VMOperationInfos; +import com.oracle.svm.core.jdk.InternalVMMethod; +import com.oracle.svm.core.snippets.KnownIntrinsics; +import com.oracle.svm.core.stack.JavaStackWalker; +import com.oracle.svm.core.stack.StackFrameVisitor; +import com.oracle.svm.core.thread.JavaVMOperation; +import com.oracle.svm.core.thread.PlatformThreads; +import org.graalvm.nativeimage.IsolateThread; + +public final class SafeStackWalker { + @NeverInline("Starting a stack walk in the caller frame") + public static void safeStackWalk(Thread targetThread, StackFrameVisitor visitor) { + assert targetThread != null; + if (targetThread == Thread.currentThread()) { + JavaStackWalker.walkCurrentThread(KnownIntrinsics.readCallerStackPointer(), visitor); + } else { + // Stack-walking another (suspended) thread requires a safepoint. + StackWalkOperation stackWalkOp = new StackWalkOperation(targetThread, visitor); + stackWalkOp.enqueue(); + } + } + + @InternalVMMethod + private static final class StackWalkOperation extends JavaVMOperation { + private final Thread thread; + private final StackFrameVisitor visitor; + + StackWalkOperation(Thread thread, StackFrameVisitor visitor) { + super(VMOperationInfos.get(StackWalkOperation.class, "Stack walking", SystemEffect.SAFEPOINT)); + this.thread = thread; + this.visitor = visitor; + } + + @Override + @NeverInline("Starting a stack walk.") + protected void operate() { + assert thread.isAlive(); + IsolateThread isolateThread = PlatformThreads.getIsolateThreadUnsafe(thread); + JavaStackWalker.walkThread(isolateThread, visitor); + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.server/src/com/oracle/svm/jdwp/server/ClassUtils.java b/substratevm/src/com.oracle.svm.jdwp.server/src/com/oracle/svm/jdwp/server/ClassUtils.java new file mode 100644 index 000000000000..7f524f188218 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.server/src/com/oracle/svm/jdwp/server/ClassUtils.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.server; + +import com.oracle.svm.interpreter.metadata.InterpreterUniverse; + +/** + * Utility methods for ResolvedJavaType. + */ +public final class ClassUtils { + + public static InterpreterUniverse UNIVERSE = null; + + private ClassUtils() { + throw new UnsupportedOperationException("Do not instantiate"); + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.server/src/com/oracle/svm/jdwp/server/JDWPHandler.java b/substratevm/src/com.oracle.svm.jdwp.server/src/com/oracle/svm/jdwp/server/JDWPHandler.java new file mode 100644 index 000000000000..46d8e0d4404e --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.server/src/com/oracle/svm/jdwp/server/JDWPHandler.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.server; + +import java.io.IOException; +import java.net.ConnectException; +import java.util.ArrayList; +import java.util.Collection; + +import com.oracle.svm.jdwp.bridge.DebugOptions; +import com.oracle.svm.jdwp.bridge.Packet; +import com.oracle.svm.jdwp.server.api.ConnectionController; +import com.oracle.svm.jdwp.server.impl.DebuggerConnection; +import com.oracle.svm.jdwp.server.impl.DebuggerController; +import com.oracle.svm.jdwp.server.impl.HandshakeController; +import com.oracle.svm.jdwp.server.impl.SocketConnection; + +public final class JDWPHandler implements Runnable { + + static final String HOST = "*"; + static final int PORT = 8000; + + private final ConnectionControllerImpl connectionController; + private volatile DebuggerController debuggerController; + + JDWPHandler(long initialThreadId) { + connectionController = new ConnectionControllerImpl(initialThreadId); + } + + public DebuggerController getController() { + return debuggerController; + } + + void doConnect(DebugOptions.Options options) { + connectionController.doConnect(options); + } + + @Override + public void run() { + DebugOptions.Options dummyOptions = DebugOptions.parse("transport=dt_socket,server=y,suspend=n,address=" + HOST + ":" + PORT, false); + doConnect(dummyOptions); + } + + private static void handleConnectException(ConnectException ex) { + System.err.println("ERROR: transport error 202: connect failed: " + ex.getMessage()); + System.err.println("ERROR: JDWP Transport dt_socket failed to initialize, TRANSPORT_INIT(510)"); + System.err.println("JDWP exit error AGENT_ERROR_TRANSPORT_INIT(197): No transports initialized"); + } + + private final class ConnectionControllerImpl implements ConnectionController { + + private long initialThreadId; + private DebugOptions.Options lastOptions; + private volatile DebuggerConnection lastConnection; + + private ConnectionControllerImpl(long initialThreadId) { + this.initialThreadId = initialThreadId; + } + + @Override + public void dispose(Packet replyPacket) { + DebuggerConnection connection = lastConnection; + lastConnection = null; // no sync, dispose() and restart() do not run in parallel + if (connection != null) { + connection.queuePacket(replyPacket); + connection.close(); + } + } + + @Override + public void restart() { + if (lastOptions != null && lastOptions.server()) { + doConnect(lastOptions); + } + } + + void doConnect(DebugOptions.Options options) { + lastOptions = options; + + SocketConnection socketConnection; + + Collection activeThreads = new ArrayList<>(); + try (HandshakeController hsController = new HandshakeController(options)) { + socketConnection = hsController.createSocketConnection(activeThreads); + } catch (ConnectException ex) { + handleConnectException(ex); + return; + } catch (IOException ioex) { + System.err.println("Critical failure in establishing JDWP connection: " + ioex.getLocalizedMessage()); + return; + } + + // connection established with handshake. Prepare to process commands from debugger + DebuggerController controller = new DebuggerController(initialThreadId, connectionController); + debuggerController = controller; + DebuggerConnection connection = new DebuggerConnection(socketConnection, controller, activeThreads); + controller.getEventListener().setConnection(socketConnection); + // The VM started event must be sent when we're ready to process commands + // doProcessCommands method will control when events can be fired without + // causing races, so pass on a Callable + Runnable vmStartedJob = () -> controller.getEventListener().vmStarted(options.suspend()); + lastConnection = connection; + connection.doProcessCommands(options.suspend(), vmStartedJob); + initialThreadId = 0; // no initial thread for further connections + } + } +} diff --git a/substratevm/src/com.oracle.svm.jdwp.server/src/com/oracle/svm/jdwp/server/JDWPServer.java b/substratevm/src/com.oracle.svm.jdwp.server/src/com/oracle/svm/jdwp/server/JDWPServer.java new file mode 100644 index 000000000000..1bee8c3d5c81 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jdwp.server/src/com/oracle/svm/jdwp/server/JDWPServer.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jdwp.server; + +import java.io.IOException; +import java.nio.file.Path; + +import com.oracle.svm.jdwp.bridge.nativebridge.NativeIsolate; +import org.graalvm.nativeimage.ImageInfo; +import org.graalvm.nativeimage.VMRuntime; + +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.interpreter.metadata.InterpreterUniverseImpl; +import com.oracle.svm.interpreter.metadata.serialization.SerializationContext; +import com.oracle.svm.interpreter.metadata.serialization.Serializers; +import com.oracle.svm.jdwp.bridge.DebugOptions; +import com.oracle.svm.jdwp.bridge.HSToNativeJDWPBridge; +import com.oracle.svm.jdwp.bridge.JDWPEventHandlerBridge; +import com.oracle.svm.jdwp.bridge.JDWPJNIConfig; +import com.oracle.svm.jdwp.server.impl.ServerJDWP; + +@SuppressWarnings("unused") +public class JDWPServer implements JDWPEventHandlerBridge { + + public static final String DEBUGGER_HELP_MESSAGE = """ + Native Image JDWP Debugger + -------------------------- + + (See the "VM Invocation Options" section of the JPDA + "Connection and Invocation Details" document for more information.) + + jdwp usage: foobar -XX:JDWPOptions=[help]|[