diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index eeeb56f07135..aec0909b324c 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -2,6 +2,9 @@ This changelog summarizes major changes between GraalVM SDK versions. The main focus is on APIs exported by GraalVM SDK. +## Version 26.0.0 +* GR-65048: GR-65048: Introduced the `-Dpolyglot.engine.allowUnsupportedPlatform=true` system property to enable Truffle to run on unsupported platforms. If this property is enabled then the failure will be suppressed. Please see follow-up errors and warnings for instructions on how to continue. Note that using an unsupported platform will also force the fallback runtime without runtime optimization. + ## 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. * GR-51664 Improved `PolyglotException#toString` and `PolyglotException#printStackTrace`. 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 278e1cc7b281..962127a92713 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 @@ -781,7 +781,7 @@ static Map readOptionsFromSystemProperties(Map o for (Object systemKey : properties.keySet()) { String key = (String) systemKey; if ("polyglot.engine.AllowExperimentalOptions".equals(key) || key.equals("polyglot.engine.resourcePath") || key.startsWith("polyglot.engine.resourcePath.") || - key.equals("polyglot.engine.userResourceCache")) { + key.equals("polyglot.engine.userResourceCache") || key.equals("polyglot.engine.allowUnsupportedPlatform")) { continue; } if (key.startsWith(systemPropertyPrefix)) { diff --git a/truffle/CHANGELOG.md b/truffle/CHANGELOG.md index d92c17d26cb1..fb24be6b330d 100644 --- a/truffle/CHANGELOG.md +++ b/truffle/CHANGELOG.md @@ -2,6 +2,9 @@ This changelog summarizes major changes between Truffle versions relevant to languages implementors building upon the Truffle framework. The main focus is on APIs exported by Truffle. +## Version 26.0 +* GR-65048: Introduced `InternalResource.OS.UNSUPPORTED` and `InternalResource.CPUArchitecture.UNSUPPORTED` to represent unsupported platforms. Execution on unsupported platforms must be explicitly enabled using the system property `-Dpolyglot.engine.allowUnsupportedPlatform=true`. If this property is not set, calls to `OS.getCurrent()` or `CPUArchitecture.getCurrent()` will throw an `IllegalStateException` when running on an unsupported platform. `InternalResource` implementations should handle the unsupported platform and describe possible steps in the error message on how to proceed. + ## Version 25.0 * GR-31495 Added ability to specify language and instrument specific options using `Source.Builder.option(String, String)`. Languages may describe available source options by implementing `TruffleLanguage.getSourceOptionDescriptors()` and `TruffleInstrument.getSourceOptionDescriptors()` respectively. * GR-61493 Added `RootNode.prepareForCall` which allows root nodes to prepare themselves for use as a call target (or to validate whether they can be used as a call target). diff --git a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/SubprocessTestUtils.java b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/SubprocessTestUtils.java index b5f8a9fe75ed..1e88dfc3bcbb 100644 --- a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/SubprocessTestUtils.java +++ b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/SubprocessTestUtils.java @@ -66,6 +66,7 @@ import java.util.Collections; import java.util.EnumSet; import java.util.Formatter; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -74,6 +75,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -87,6 +89,8 @@ import javax.management.remote.JMXServiceURL; import org.graalvm.nativeimage.ImageInfo; +import org.graalvm.options.OptionDescriptor; +import org.graalvm.options.OptionDescriptors; import org.junit.Assert; import org.junit.Assume; import org.junit.Test; @@ -188,11 +192,11 @@ private static Method findTestMethod(Class testClass) { } private static Subprocess execute(Method testMethod, boolean failOnNonZeroExitCode, List prefixVMOptions, - List postfixVmOptions, Duration timeout, Consumer onStart) throws IOException, InterruptedException { + List postfixVmOptions, boolean removeOptimizedRuntimeOptions, Duration timeout, Consumer onStart) throws IOException, InterruptedException { String enclosingElement = testMethod.getDeclaringClass().getName(); String testName = testMethod.getName(); Subprocess subprocess = javaHelper( - configure(getVmArgs(), prefixVMOptions, postfixVmOptions), + configure(getVmArgs(), prefixVMOptions, postfixVmOptions, removeOptimizedRuntimeOptions), null, null, List.of("com.oracle.mxtool.junit.MxJUnitWrapper", String.format("%s#%s", enclosingElement, testName)), timeout, onStart); @@ -202,8 +206,9 @@ private static Subprocess execute(Method testMethod, boolean failOnNonZeroExitCo return subprocess; } - private static List configure(List vmArgs, List prefixVMOptions, List postfixVmOptions) { + private static List configure(List vmArgs, List prefixVMOptions, List postfixVmOptions, boolean removeOptimizedRuntimeOptions) { List newVmArgs = new ArrayList<>(); + Predicate optimizedRuntimeFilter = removeOptimizedRuntimeOptions ? new OptimizedRuntimeOptionsFilter() : (s) -> true; newVmArgs.addAll(vmArgs.stream().filter(vmArg -> { for (String toRemove : getForbiddenVmOptions()) { if (vmArg.startsWith(toRemove)) { @@ -221,14 +226,14 @@ private static List configure(List vmArgs, List prefixVM } } return true; - }).collect(Collectors.toList())); + }).filter(optimizedRuntimeFilter).toList()); for (String additionalVmOption : prefixVMOptions) { - if (!additionalVmOption.startsWith(TO_REMOVE_PREFIX)) { + if (!additionalVmOption.startsWith(TO_REMOVE_PREFIX) && optimizedRuntimeFilter.test(additionalVmOption)) { newVmArgs.add(1, additionalVmOption); } } for (String additionalVmOption : postfixVmOptions) { - if (!additionalVmOption.startsWith(TO_REMOVE_PREFIX)) { + if (!additionalVmOption.startsWith(TO_REMOVE_PREFIX) && optimizedRuntimeFilter.test(additionalVmOption)) { newVmArgs.add(additionalVmOption); } } @@ -466,6 +471,7 @@ public static final class Builder { private Duration timeout; private Consumer onExit; private Consumer onStart; + private boolean removeOptimizedRuntimeOptions; private Builder(Class testClass, Runnable run) { this.testClass = testClass; @@ -496,6 +502,17 @@ public Builder postfixVmOption(String... options) { return this; } + /** + * Removes all {@code OptimizedRuntimeOptions} from the command line. + *

+ * This method is useful in tests that require fallback to the default Truffle runtime, + * which does not support optimized runtime options. + */ + public Builder removeOptimizedRuntimeOptions(boolean value) { + removeOptimizedRuntimeOptions = value; + return this; + } + /** * Disables assertions in {@code forClass}. */ @@ -535,7 +552,7 @@ public void run() throws IOException, InterruptedException { if (isSubprocess()) { runnable.run(); } else { - Subprocess process = execute(findTestMethod(testClass), failOnNonZeroExit, prefixVmArgs, postfixVmArgs, timeout, onStart); + Subprocess process = execute(findTestMethod(testClass), failOnNonZeroExit, prefixVmArgs, postfixVmArgs, removeOptimizedRuntimeOptions, timeout, onStart); if (onExit != null) { try { onExit.accept(process); @@ -1175,4 +1192,34 @@ private void skipWhite() throws IOException { } } } + + private static final class OptimizedRuntimeOptionsFilter implements Predicate { + + private final Set toRemove; + + OptimizedRuntimeOptionsFilter() { + Set optionNames; + try { + Class options = Class.forName("com.oracle.truffle.runtime.OptimizedRuntimeOptions"); + OptionDescriptors descriptors = (OptionDescriptors) ReflectionUtils.invokeStatic(options, "getDescriptors"); + optionNames = new HashSet<>(); + for (OptionDescriptor descriptor : descriptors) { + optionNames.add(String.format("-Dpolyglot.%s", descriptor.getName())); + } + } catch (ClassNotFoundException e) { + optionNames = Set.of(); + } + toRemove = optionNames; + } + + @Override + public boolean test(String s) { + int index = s.lastIndexOf("="); + if (index > 0) { + String key = s.substring(0, index); + return !toRemove.contains(key); + } + return true; + } + } } diff --git a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/InternalResourceTest.java b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/InternalResourceTest.java index e28988c2bc8c..15f81d05afea 100644 --- a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/InternalResourceTest.java +++ b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/InternalResourceTest.java @@ -59,6 +59,7 @@ import java.nio.file.attribute.FileTime; import java.time.Instant; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -72,10 +73,13 @@ import com.oracle.truffle.api.instrumentation.TruffleInstrument; import com.oracle.truffle.api.test.OSUtils; import com.oracle.truffle.api.test.ReflectionUtils; +import com.oracle.truffle.api.test.SubprocessTestUtils; import com.oracle.truffle.api.test.common.TestUtils; import com.oracle.truffle.tck.tests.TruffleTestAssumptions; +import org.graalvm.nativeimage.ImageInfo; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Engine; +import org.graalvm.polyglot.PolyglotException; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -91,6 +95,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static com.oracle.truffle.api.test.polyglot.AbstractPolyglotTest.assertFails; +import static org.junit.Assume.assumeFalse; public class InternalResourceTest { @@ -1308,6 +1313,83 @@ protected Object execute(RootNode node, Env env, Object[] contextArguments, Obje } } + @Test + public void testUnsupportedPlatformDisabled() throws IOException, InterruptedException { + assumeFalse(ImageInfo.inImageCode()); + SubprocessTestUtils.newBuilder(InternalResourceTest.class, () -> { + var prevProperties = setUnsupportedArchitecture(); + try { + AbstractPolyglotTest.assertFails(() -> Context.create(), PolyglotException.class, (e) -> { + assertEquals("java.lang.IllegalStateException: Unsupported operating system: 'z/os'. " + + "If you want to continue using this unsupported platform, set the system property '-Dpolyglot.engine.allowUnsupportedPlatform=true'. " + + "Note that unsupported platforms require additional command line options to be functional.", e.getMessage()); + }); + } finally { + restoreArchitecture(prevProperties); + } + }).removeOptimizedRuntimeOptions(true).run(); + } + + @Test + public void testUnsupportedPlatformEnabled() throws IOException, InterruptedException { + assumeFalse(ImageInfo.inImageCode()); + SubprocessTestUtils.newBuilder(InternalResourceTest.class, () -> { + var prevProperties = setUnsupportedArchitecture(); + try { + AbstractPolyglotTest.assertFails(() -> Context.create(), PolyglotException.class, (e) -> { + assertEquals("java.lang.IllegalStateException: Truffle is running on an unsupported platform. " + + "On unsupported platforms, you must explicitly set the default cache directory " + + "using the system property '-Dpolyglot.engine.userResourceCache='.", e.getMessage()); + }); + } finally { + restoreArchitecture(prevProperties); + } + }).removeOptimizedRuntimeOptions(true).prefixVmOption("-Dpolyglot.engine.allowUnsupportedPlatform=true").run(); + } + + @Test + public void testUnsupportedPlatformEnabledWithExplicitCacheFolder() throws IOException, InterruptedException { + assumeFalse(ImageInfo.inImageCode()); + Path tmp = SubprocessTestUtils.isSubprocess() ? null : Files.createTempDirectory("test_cache_root").toAbsolutePath(); + try { + SubprocessTestUtils.newBuilder(InternalResourceTest.class, () -> { + var prevProperties = setUnsupportedArchitecture(); + try { + Context context = Context.create(); + context.close(); + } finally { + restoreArchitecture(prevProperties); + } + }).// + prefixVmOption("-Dpolyglot.engine.allowUnsupportedPlatform=true").// + prefixVmOption("-Dpolyglot.engine.userResourceCache=" + tmp).// + removeOptimizedRuntimeOptions(true).// + onExit((subprocess) -> { + assertTrue(subprocess.output.stream().anyMatch((l) -> l.contains("Truffle is running on an unsupported platform where the TruffleAttach library is unavailable."))); + }).// + run(); + } finally { + if (tmp != null) { + delete(tmp); + } + } + } + + private static Map setUnsupportedArchitecture() { + Map prev = new HashMap<>(); + prev.put("os.name", System.getProperty("os.name")); + prev.put("os.arch", System.getProperty("os.arch")); + System.setProperty("os.name", "z/os"); + System.setProperty("os.arch", "s390x"); + return prev; + } + + private static void restoreArchitecture(Map properties) { + for (var entry : properties.entrySet()) { + System.setProperty(entry.getKey(), entry.getValue()); + } + } + private static String findLine(String pattern, String[] lines) { Pattern p = Pattern.compile(pattern); for (String line : lines) { diff --git a/truffle/src/com.oracle.truffle.api/snapshot.sigtest b/truffle/src/com.oracle.truffle.api/snapshot.sigtest index b074be77c529..fd285c4d8eed 100644 --- a/truffle/src/com.oracle.truffle.api/snapshot.sigtest +++ b/truffle/src/com.oracle.truffle.api/snapshot.sigtest @@ -181,6 +181,7 @@ CLSS public final static !enum com.oracle.truffle.api.InternalResource$CPUArchit outer com.oracle.truffle.api.InternalResource fld public final static com.oracle.truffle.api.InternalResource$CPUArchitecture AARCH64 fld public final static com.oracle.truffle.api.InternalResource$CPUArchitecture AMD64 +fld public final static com.oracle.truffle.api.InternalResource$CPUArchitecture UNSUPPORTED meth public java.lang.String toString() meth public static com.oracle.truffle.api.InternalResource$CPUArchitecture getCurrent() meth public static com.oracle.truffle.api.InternalResource$CPUArchitecture valueOf(java.lang.String) @@ -213,13 +214,14 @@ CLSS public final static !enum com.oracle.truffle.api.InternalResource$OS outer com.oracle.truffle.api.InternalResource fld public final static com.oracle.truffle.api.InternalResource$OS DARWIN fld public final static com.oracle.truffle.api.InternalResource$OS LINUX +fld public final static com.oracle.truffle.api.InternalResource$OS UNSUPPORTED fld public final static com.oracle.truffle.api.InternalResource$OS WINDOWS meth public java.lang.String toString() meth public static com.oracle.truffle.api.InternalResource$OS getCurrent() meth public static com.oracle.truffle.api.InternalResource$OS valueOf(java.lang.String) meth public static com.oracle.truffle.api.InternalResource$OS[] values() supr java.lang.Enum -hfds id +hfds PROPERTY_ALLOW_UNSUPPORTED_PLATFORM,allowsUnsupportedPlatformValue,id CLSS public com.oracle.truffle.api.OptimizationFailedException cons public init(java.lang.Throwable,com.oracle.truffle.api.RootCallTarget) diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/InternalResource.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/InternalResource.java index 15c65a2bd299..5929b1f104bb 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/InternalResource.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/InternalResource.java @@ -484,7 +484,43 @@ enum OS { * * @since 23.1 */ - WINDOWS("windows"); + WINDOWS("windows"), + + /** + * Represents an unsupported operating system. + *

+ * To enable execution on unsupported platforms, the system property + * {@code -Dpolyglot.engine.allowUnsupportedPlatform=true} must be explicitly set. If this + * property is not set and the platform is unsupported, the {@link #getCurrent()} method + * will throw an {@link IllegalStateException}. + * + * @since 26.0 + */ + UNSUPPORTED("unsupported"); + + private static final String PROPERTY_ALLOW_UNSUPPORTED_PLATFORM = "polyglot.engine.allowUnsupportedPlatform"; + + private static volatile Boolean allowsUnsupportedPlatformValue; + + private static boolean allowsUnsupportedPlatform() { + Boolean res = allowsUnsupportedPlatformValue; + if (res == null) { + synchronized (OS.class) { + res = allowsUnsupportedPlatformValue; + if (res == null) { + res = Boolean.getBoolean(OS.PROPERTY_ALLOW_UNSUPPORTED_PLATFORM); + if (!ImageInfo.inImageBuildtimeCode()) { + /* + * Avoid caching the property value during image build time, as it would + * require resetting it in the image heap later. + */ + allowsUnsupportedPlatformValue = res; + } + } + } + } + return res; + } private final String id; @@ -517,8 +553,12 @@ public static OS getCurrent() { return DARWIN; } else if (os.toLowerCase().startsWith("windows")) { return WINDOWS; + } else if (allowsUnsupportedPlatform()) { + return UNSUPPORTED; } else { - throw CompilerDirectives.shouldNotReachHere("Unsupported OS name " + os); + throw new IllegalStateException(String.format("Unsupported operating system: '%s'. " + + "If you want to continue using this unsupported platform, set the system property '-D%s=true'. " + + "Note that unsupported platforms require additional command line options to be functional.", os, PROPERTY_ALLOW_UNSUPPORTED_PLATFORM)); } } } @@ -542,7 +582,19 @@ enum CPUArchitecture { * * @since 23.1 */ - AMD64("amd64"); + AMD64("amd64"), + + /** + * Represents an unsupported architecture. + *

+ * To enable execution on unsupported platforms, the system property + * {@code -Dpolyglot.engine.allowUnsupportedPlatform=true} must be explicitly set. If this + * property is not set and the platform is unsupported, the {@link #getCurrent()} method + * will throw an {@link IllegalStateException}. + * + * @since 26.0 + */ + UNSUPPORTED("unsupported"); private final String id; @@ -573,7 +625,15 @@ public static CPUArchitecture getCurrent() { return switch (arch) { case "amd64", "x86_64" -> AMD64; case "aarch64", "arm64" -> AARCH64; - default -> throw CompilerDirectives.shouldNotReachHere("Unsupported CPU architecture " + arch); + default -> { + if (OS.allowsUnsupportedPlatform()) { + yield UNSUPPORTED; + } else { + throw new IllegalStateException(String.format("Unsupported CPU architecture: '%s'. " + + "If you want to allow unsupported CPU architectures (which will cause the fallback Truffle runtime to be used and may disable some language features), " + + "set the system property '-D%s=true'.", arch, OS.PROPERTY_ALLOW_UNSUPPORTED_PLATFORM)); + } + } }; } } diff --git a/truffle/src/com.oracle.truffle.nfi.backend.panama.jdk22/src/com/oracle/truffle/nfi/backend/panama/ErrorContext.java b/truffle/src/com.oracle.truffle.nfi.backend.panama.jdk22/src/com/oracle/truffle/nfi/backend/panama/ErrorContext.java index 36966797af36..f8ab9e2e637a 100644 --- a/truffle/src/com.oracle.truffle.nfi.backend.panama.jdk22/src/com/oracle/truffle/nfi/backend/panama/ErrorContext.java +++ b/truffle/src/com.oracle.truffle.nfi.backend.panama.jdk22/src/com/oracle/truffle/nfi/backend/panama/ErrorContext.java @@ -55,6 +55,7 @@ public class ErrorContext extends AbstractErrorContext { case DARWIN -> "__error"; case LINUX -> "__errno_location"; case WINDOWS -> "_errno"; + case UNSUPPORTED -> throw new IllegalStateException("NFI is not supported on unsupported platforms."); }; public static final VarHandle INT_VAR_HANDLE = ValueLayout.JAVA_INT.varHandle(); diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/InternalResourceRoots.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/InternalResourceRoots.java index de3fda5ee1d2..f21819dece4d 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/InternalResourceRoots.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/InternalResourceRoots.java @@ -339,6 +339,9 @@ private static ResolvedCacheFolder findCacheRootDefault() { yield userCacheDir; } case WINDOWS -> new ResolvedCacheFolder(userHome.resolve(Path.of("AppData", "Local")), "user home", userHome); + case UNSUPPORTED -> throw new IllegalStateException(String.format("Truffle is running on an unsupported platform. " + + "On unsupported platforms, you must explicitly set the default cache directory using the system property " + + "'-D%s='.", PROPERTY_USER_RESOURCE_CACHE)); }; return container.resolve("org.graalvm.polyglot"); } diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/JDKSupport.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/JDKSupport.java index 3721e0f6d92a..7e0d45cb73e5 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/JDKSupport.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/JDKSupport.java @@ -70,6 +70,8 @@ import java.util.stream.Stream; import com.oracle.truffle.api.InternalResource; +import com.oracle.truffle.api.InternalResource.OS; +import com.oracle.truffle.api.InternalResource.CPUArchitecture; import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.impl.Accessor; import com.oracle.truffle.api.impl.Accessor.JavaLangSupport; @@ -112,6 +114,10 @@ final class JDKSupport { private static ModulesAccessor initializeModuleAccessor() { String attachLibPath = System.getProperty("truffle.attach.library"); if (attachLibPath == null) { + if (isUnsupportedPlatform()) { + performTruffleAttachLoadFailureAction("Truffle is running on an unsupported platform where the TruffleAttach library is unavailable.", null); + return null; + } try { Path truffleAttachRoot = InternalResourceCache.installRuntimeResource(new LibTruffleAttachResource()); Path libAttach = truffleAttachRoot.resolve("bin").resolve(System.mapLibraryName("truffleattach")); @@ -148,6 +154,19 @@ private static ModulesAccessor initializeModuleAccessor() { } } + private static boolean isUnsupportedPlatform() { + try { + return OS.getCurrent() == OS.UNSUPPORTED || CPUArchitecture.getCurrent() == CPUArchitecture.UNSUPPORTED; + } catch (IllegalStateException ise) { + /* + * We suppress this error in the JDKSupport static initializer to prevent it from being + * obscured by a long and unreadable chain of exception causes. If this error occurs + * here, it will be handled later during engine creation. + */ + return true; + } + } + private static native void addExports0(Module m1, String pn, Module m2); private static void performTruffleAttachLoadFailureAction(String reason, Throwable t) {