diff --git a/espresso/mx.espresso/native-image.properties b/espresso/mx.espresso/native-image.properties index 888e00addac0..9aa23ff9f5df 100644 --- a/espresso/mx.espresso/native-image.properties +++ b/espresso/mx.espresso/native-image.properties @@ -3,4 +3,5 @@ Requires = language:nfi Args = -H:MaxRuntimeCompileMethods=7000 \ -H:ReflectionConfigurationFiles=${.}/reflectconfig.json \ -H:+TruffleCheckBlockListMethods \ + --initialize-at-build-time=com.oracle.truffle.espresso \ --features=com.oracle.truffle.espresso.ref.FinalizationFeature diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/EspressoLanguage.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/EspressoLanguage.java index 1d0b15842535..8f268948829b 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/EspressoLanguage.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/EspressoLanguage.java @@ -22,9 +22,13 @@ */ package com.oracle.truffle.espresso; +import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.logging.Level; +import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; +import com.oracle.truffle.espresso.meta.EspressoError; +import com.oracle.truffle.espresso.runtime.JavaVersion; import org.graalvm.home.Version; import org.graalvm.options.OptionDescriptors; @@ -95,15 +99,17 @@ public final class EspressoLanguage extends TruffleLanguage { private static final StaticProperty ARRAY_PROPERTY = new DefaultStaticProperty("array"); // This field should be static final, but until we move the static object model we cannot have a // SubstrateVM feature which will allow us to set the right field offsets at image build time. - @CompilerDirectives.CompilationFinal // + @CompilationFinal // private static StaticShape arrayShape; private static final StaticProperty FOREIGN_PROPERTY = new DefaultStaticProperty("foreignObject"); // This field should be static final, but until we move the static object model we cannot have a // SubstrateVM feature which will allow us to set the right field offsets at image build time. - @CompilerDirectives.CompilationFinal // + @CompilationFinal // private static StaticShape foreignShape; + @CompilationFinal private JavaVersion javaVersion; + private final ContextThreadLocal threadLocalState = createContextThreadLocal((context, thread) -> new EspressoThreadLocalState(context)); public EspressoLanguage() { @@ -289,4 +295,21 @@ private StaticShape initializeForeignShape() { public static EspressoLanguage get(Node node) { return REFERENCE.get(node); } + + public JavaVersion getJavaVersion() { + return javaVersion; + } + + public void tryInitializeJavaVersion(JavaVersion version) { + JavaVersion ref = this.javaVersion; + if (ref == null) { + synchronized (this) { + ref = this.javaVersion; + if (ref == null) { + this.javaVersion = ref = Objects.requireNonNull(version); + } + } + } + EspressoError.guarantee(version.equals(ref), "incompatible Java versions"); + } } 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 85f633ffaef5..c2637ff74b00 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 @@ -501,8 +501,12 @@ private void spawnVM() { // Spawn JNI first, then the VM. try (DebugCloseable vmInit = VM_INIT.scope(timers)) { - this.vm = VM.create(getJNI()); // Mokapot is loaded + this.jniEnv = JniEnv.create(this); // libnespresso + this.vm = VM.create(this.jniEnv); // libjvm vm.attachThread(Thread.currentThread()); + // The Java version is extracted from libjava and is available after this line. + vm.loadAndInitializeJavaLibrary(vmProperties.bootLibraryPath()); // libjava + EspressoError.guarantee(getJavaVersion() != null, "Java version"); } if (getJavaVersion().modulesEnabled()) { @@ -735,7 +739,7 @@ public JImageHelper createJImageHelper(String jimagePath) { } public JavaVersion getJavaVersion() { - return vm.getJavaVersion(); + return getLanguage().getJavaVersion(); } public boolean advancedRedefinitionEnabled() { diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/JavaVersion.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/JavaVersion.java index b37c52e00b2d..1b8902d7cdc7 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/JavaVersion.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/JavaVersion.java @@ -29,7 +29,24 @@ * * Makes it harder to access the raw int version: please add new predicates instead. */ -public final class JavaVersion { +public enum JavaVersion { + + JAVA_4(4), + JAVA_5(5), + JAVA_6(6), + JAVA_7(7), + JAVA_8(8), + JAVA_9(9), + JAVA_10(10), + JAVA_11(11), + JAVA_12(12), + JAVA_13(13), + JAVA_14(14), + JAVA_15(15), + JAVA_16(16), + JAVA_17(17), + JAVA_18(18); + public static final class VersionRange { public static final VersionRange VERSION_8_OR_LOWER = lower(8); public static final VersionRange VERSION_9_TO_11 = new VersionRange(9, 11); @@ -68,10 +85,19 @@ public boolean contains(JavaVersion version) { private final int version; - public JavaVersion(int version) { + JavaVersion(int version) { this.version = version; } + public static JavaVersion forVersion(int version) { + int lowest = values()[0].version; + return values()[version - lowest]; + } + + public static JavaVersion latestSupported() { + return forVersion(LATEST_SUPPORTED); + } + public boolean java8OrEarlier() { return version <= 8; } 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 8eae4864133c..2812583320da 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 @@ -184,8 +184,6 @@ public final class VM extends NativeEnv implements ContextAccess { private final long rtldDefaultValue; private final long processHandleValue; - private final JavaVersion javaVersion; - private final Structs structs; private final JniEnv jniEnv; @@ -193,9 +191,7 @@ public final class VM extends NativeEnv implements ContextAccess { private final JVMTI jvmti; private @Pointer TruffleObject mokapotEnvPtr; - - // libjava must be loaded after mokapot. - private final @Pointer TruffleObject javaLibrary; + private @Pointer TruffleObject javaLibrary; private static String stringify(List paths) { StringJoiner joiner = new StringJoiner(File.pathSeparator); @@ -225,6 +221,10 @@ public Management getManagement() { return management; } + public @Pointer TruffleObject getJavaLibrary() { + return javaLibrary; + } + public static final class GlobalFrameIDs { private static final AtomicLong id = new AtomicLong(); @@ -233,15 +233,6 @@ public static long getID() { } } - @Override - public JavaVersion getJavaVersion() { - return javaVersion; - } - - public @Pointer TruffleObject getJavaLibrary() { - return javaLibrary; - } - private @Pointer TruffleObject loadJavaLibrary(List bootLibraryPath) { // Comment from HotSpot: // Try to load verify dll first. In 1.3 java dll depends on it and is not @@ -252,14 +243,15 @@ public JavaVersion getJavaVersion() { return getNativeAccess().loadLibrary(bootLibraryPath, "java", true); } - private void libJavaOnLoad(TruffleObject libJava) { + private void initializeJavaLibrary(TruffleObject libJava) { + // HotSpot calls libjava's JNI_OnLoad only on 8. if (getJavaVersion().java8OrEarlier()) { - // The JNI_OnLoad handling is normally done by method load in - // java.lang.ClassLoader$NativeLibrary, but the VM loads the base library - // explicitly so we have to check for JNI_OnLoad as well - // libjava is initialized after libjvm (Espresso VM native context). - - // TODO(peterssen): Use JVM_FindLibraryEntry. + /* + * The JNI_OnLoad handling is normally done by method load in + * java.lang.ClassLoader$NativeLibrary, but the VM loads the base library explicitly so + * we have to check for JNI_OnLoad as well. + */ + EspressoError.guarantee(getVM() != null, "The VM must be initialized before libjava's JNI_OnLoad"); TruffleObject jniOnLoad = getNativeAccess().lookupAndBindSymbol(libJava, "JNI_OnLoad", NativeSignature.create(NativeType.INT, NativeType.POINTER, NativeType.POINTER)); if (jniOnLoad != null) { try { @@ -288,17 +280,25 @@ private JavaVersion findJavaVersion(TruffleObject libJava) { if (major == 1) { // Version 1.X int minor = (versionInfo & 0x00FF0000) >> 16; - return new JavaVersion(minor); + return JavaVersion.forVersion(minor); } else { // Version X.Y - return new JavaVersion(major); + return JavaVersion.forVersion(major); } } else { // JDK 14+ - return new JavaVersion(JavaVersion.LATEST_SUPPORTED); + return JavaVersion.latestSupported(); } } + public void loadAndInitializeJavaLibrary(List searchPaths) { + assert javaLibrary == null : "java library already initialized"; + this.javaLibrary = loadJavaLibrary(searchPaths); + JavaVersion javaVersion = findJavaVersion(this.javaLibrary); + getEspressoLanguage().tryInitializeJavaVersion(javaVersion); + initializeJavaLibrary(this.javaLibrary); + } + private VM(JniEnv jniEnv) { this.jniEnv = jniEnv; try { @@ -359,19 +359,9 @@ private VM(JniEnv jniEnv) { getLogger().finest(() -> String.format("Got RTLD_DEFAULT=0x%016x and ProcessHandle=0x%016x", rtldDefaultValue, processHandleValue)); assert getUncached().isPointer(this.mokapotEnvPtr); assert !getUncached().isNull(this.mokapotEnvPtr); - - javaLibrary = loadJavaLibrary(props.bootLibraryPath()); - javaVersion = findJavaVersion(javaLibrary); - libJavaOnLoad(javaLibrary); - } catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) { throw EspressoError.shouldNotReachHere(e); } - if (getJavaVersion().java9OrLater()) { - stackWalk = new StackWalk(); - } else { - stackWalk = null; - } } @Override @@ -3606,7 +3596,21 @@ public static long JVM_MaxObjectInspectionAge() { // region stackwalk - private final StackWalk stackWalk; + private volatile StackWalk stackWalk; + + private StackWalk getStackWalk() { + StackWalk ref = stackWalk; + if (ref == null) { + EspressoError.guarantee(getJavaVersion().java9OrLater(), "Stack-Walking API requires Java 9+"); + synchronized (this) { + ref = stackWalk; + if (ref == null) { + stackWalk = ref = new StackWalk(); + } + } + } + return ref; + } @VmImpl(isJni = true) @SuppressWarnings("unused") @@ -3708,7 +3712,7 @@ private void checkStackWalkArguments(int batchSize, int startIndex, @JavaType(Ob int batchSize, int startIndex, @JavaType(Object[].class) StaticObject frames) { checkStackWalkArguments(batchSize, startIndex, frames); - return stackWalk.fetchFirstBatch(stackStream, mode, skipframes, batchSize, startIndex, frames, getMeta()); + return getStackWalk().fetchFirstBatch(stackStream, mode, skipframes, batchSize, startIndex, frames, getMeta()); } @VmImpl(isJni = true) @@ -3718,7 +3722,7 @@ public int JVM_MoreStackWalk( int batchSize, int startIndex, @JavaType(Object[].class) StaticObject frames) { checkStackWalkArguments(batchSize, startIndex, frames); - return stackWalk.fetchNextBatch(stackStream, mode, anchor, batchSize, startIndex, frames, getMeta()); + return getStackWalk().fetchNextBatch(stackStream, mode, anchor, batchSize, startIndex, frames, getMeta()); } // endregion stackwalk