From fd2dfe611747da957388e7187dec1e41cafc4111 Mon Sep 17 00:00:00 2001 From: Ivan Ristovic Date: Wed, 14 May 2025 08:50:17 +0200 Subject: [PATCH 1/2] Allow cancellation of image-heap laying out --- .../genscavenge/ChunkedImageHeapLayouter.java | 21 ++++++---- .../ChunkedImageHeapPartition.java | 17 ++++---- .../svm/core/image/ImageHeapLayouter.java | 41 ++++++++++++++++++- .../oracle/svm/hosted/image/NativeImage.java | 3 +- .../wasm/codegen/WebImageWasmLMCodeGen.java | 3 +- .../wasmgc/codegen/WasmGCHeapWriter.java | 3 +- .../wasmgc/image/WasmGCHeapLayouter.java | 2 +- 7 files changed, 70 insertions(+), 20 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapLayouter.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapLayouter.java index 4a25f7d4c9ff..0d457213e0b2 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapLayouter.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapLayouter.java @@ -39,8 +39,8 @@ import com.oracle.svm.core.genscavenge.remset.RememberedSet; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.image.ImageHeap; -import com.oracle.svm.core.image.ImageHeapLayoutInfo; import com.oracle.svm.core.image.ImageHeapLayouter; +import com.oracle.svm.core.image.ImageHeapLayoutInfo; import com.oracle.svm.core.image.ImageHeapObject; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.UserError; @@ -156,11 +156,12 @@ private Error reportHugeObjectError(ImageHeapObject info, String objectTypeMsg, } @Override - public ImageHeapLayoutInfo layout(ImageHeap imageHeap, int pageSize) { + public ImageHeapLayoutInfo layout(ImageHeap imageHeap, int pageSize, ImageHeapLayouterCallback callback) { + ImageHeapLayouterControl control = new ImageHeapLayouterControl(callback); int objectAlignment = ConfigurationValues.getObjectLayout().getAlignment(); assert pageSize % objectAlignment == 0 : "Page size does not match object alignment"; - ImageHeapLayoutInfo layoutInfo = doLayout(imageHeap, pageSize); + ImageHeapLayoutInfo layoutInfo = doLayout(imageHeap, pageSize, control); for (ChunkedImageHeapPartition partition : getPartitions()) { assert partition.getStartOffset() % partition.getStartAlignment() == 0 : partition; @@ -170,15 +171,16 @@ public ImageHeapLayoutInfo layout(ImageHeap imageHeap, int pageSize) { return layoutInfo; } - private ImageHeapLayoutInfo doLayout(ImageHeap imageHeap, int pageSize) { + private ImageHeapLayoutInfo doLayout(ImageHeap imageHeap, int pageSize, ImageHeapLayouterControl control) { allocator = new ChunkedImageHeapAllocator(imageHeap, startOffset); for (ChunkedImageHeapPartition partition : getPartitions()) { - partition.layout(allocator); + control.poll(); + partition.layout(allocator, control); } - return populateInfoObjects(imageHeap.countAndVerifyDynamicHubs(), pageSize); + return populateInfoObjects(imageHeap.countAndVerifyDynamicHubs(), pageSize, control); } - private ImageHeapLayoutInfo populateInfoObjects(int dynamicHubCount, int pageSize) { + private ImageHeapLayoutInfo populateInfoObjects(int dynamicHubCount, int pageSize, ImageHeapLayouterControl control) { // Determine writable start boundary from chunks: a chunk that contains writable objects // must also have a writable card table long offsetOfFirstWritableAlignedChunk = -1; @@ -188,6 +190,8 @@ private ImageHeapLayoutInfo populateInfoObjects(int dynamicHubCount, int pageSiz break; // (chunks are in ascending memory order) } } + control.poll(); + VMError.guarantee(offsetOfFirstWritableAlignedChunk >= 0 && offsetOfFirstWritableAlignedChunk % pageSize == 0, "Start of the writable part is assumed to be page-aligned"); long offsetOfFirstWritableUnalignedChunk = -1; long offsetOfLastWritableUnalignedChunk = -1; @@ -200,6 +204,7 @@ private ImageHeapLayoutInfo populateInfoObjects(int dynamicHubCount, int pageSiz } offsetOfLastWritableUnalignedChunk = chunk.getBegin(); } + control.poll(); heapInfo.initialize(getReadOnlyRegular().firstObject, getReadOnlyRegular().lastObject, getReadOnlyRelocatable().firstObject, getReadOnlyRelocatable().lastObject, getWritablePatched().firstObject, getWritablePatched().lastObject, @@ -207,6 +212,8 @@ private ImageHeapLayoutInfo populateInfoObjects(int dynamicHubCount, int pageSiz getReadOnlyHuge().firstObject, getReadOnlyHuge().lastObject, offsetOfFirstWritableAlignedChunk, offsetOfFirstWritableUnalignedChunk, offsetOfLastWritableUnalignedChunk, dynamicHubCount); + control.poll(); + long writableEnd = getWritableHuge().getStartOffset() + getWritableHuge().getSize(); long writableSize = writableEnd - offsetOfFirstWritableAlignedChunk; long imageHeapSize = getReadOnlyHuge().getStartOffset() + getReadOnlyHuge().getSize() - startOffset; diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapPartition.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapPartition.java index 3c33faace645..da89c5b39392 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapPartition.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapPartition.java @@ -32,6 +32,7 @@ import java.util.TreeMap; import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.image.ImageHeapLayouter.ImageHeapLayouterControl; import com.oracle.svm.core.image.ImageHeapObject; import com.oracle.svm.core.image.ImageHeapPartition; @@ -72,44 +73,46 @@ void assign(ImageHeapObject obj) { objects.add(obj); } - void layout(ChunkedImageHeapAllocator allocator) { + void layout(ChunkedImageHeapAllocator allocator, ImageHeapLayouterControl control) { if (hugeObjects) { - layoutInUnalignedChunks(allocator); + layoutInUnalignedChunks(allocator, control); } else { - layoutInAlignedChunks(allocator); + layoutInAlignedChunks(allocator, control); } } - private void layoutInUnalignedChunks(ChunkedImageHeapAllocator allocator) { + private void layoutInUnalignedChunks(ChunkedImageHeapAllocator allocator, ImageHeapLayouterControl control) { allocator.finishAlignedChunk(); allocator.alignBetweenChunks(getStartAlignment()); startOffset = allocator.getPosition(); for (ImageHeapObject info : objects) { // No need to sort by size appendAllocatedObject(info, allocator.allocateUnalignedChunkForObject(info, isWritable())); + control.poll(); } allocator.alignBetweenChunks(getEndAlignment()); endOffset = allocator.getPosition(); } - private void layoutInAlignedChunks(ChunkedImageHeapAllocator allocator) { + private void layoutInAlignedChunks(ChunkedImageHeapAllocator allocator, ImageHeapLayouterControl control) { allocator.maybeStartAlignedChunk(); allocator.alignInAlignedChunk(getStartAlignment()); startOffset = allocator.getPosition(); - allocateObjectsInAlignedChunks(allocator); + allocateObjectsInAlignedChunks(allocator, control); allocator.alignInAlignedChunk(getEndAlignment()); endOffset = allocator.getPosition(); } - private void allocateObjectsInAlignedChunks(ChunkedImageHeapAllocator allocator) { + private void allocateObjectsInAlignedChunks(ChunkedImageHeapAllocator allocator, ImageHeapLayouterControl control) { NavigableMap> sortedObjects = createSortedObjectsMap(); while (!sortedObjects.isEmpty()) { ImageHeapObject info = dequeueBestFit(sortedObjects, allocator.getRemainingBytesInAlignedChunk()); if (info == null) { allocator.startNewAlignedChunk(); + control.poll(); } else { appendAllocatedObject(info, allocator.allocateObjectInAlignedChunk(info, isWritable())); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeapLayouter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeapLayouter.java index 844559e5c7de..cee0fc0b0baa 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeapLayouter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeapLayouter.java @@ -43,9 +43,11 @@ public interface ImageHeapLayouter { void assignObjectToPartition(ImageHeapObject info, boolean immutable, boolean references, boolean relocatable, boolean patched); /** - * Places all heap partitions and assigns objects their final offsets. + * Places all heap partitions and assigns objects their final offsets. The layout operation can + * be cancelled through the {@link ImageHeapLayouterCallback}. If the layout is cancelled, an + * instance of {@link ImageHeapLayoutCancelledException} is thrown. */ - ImageHeapLayoutInfo layout(ImageHeap imageHeap, int pageSize); + ImageHeapLayoutInfo layout(ImageHeap imageHeap, int pageSize, ImageHeapLayouterCallback callback); /** Hook to run tasks after heap layout is finished. */ @SuppressWarnings("unused") @@ -63,4 +65,39 @@ default void afterLayout(ImageHeap imageHeap) { * same offset in the given buffer, the same offset must be specified to this method. */ void writeMetadata(ByteBuffer imageHeapBytes, long imageHeapOffsetInBuffer); + + /** + * Facilitates {@link ImageHeapLayouter#layout} cancellation through an + * {@link ImageHeapLayouterCallback} instance. + */ + class ImageHeapLayouterControl { + protected final ImageHeapLayouterCallback callback; + + public ImageHeapLayouterControl(ImageHeapLayouterCallback callback) { + this.callback = callback; + } + + public void poll() throws ImageHeapLayoutCancelledException { + if (callback.shouldCancel()) { + throw new ImageHeapLayoutCancelledException(); + } + } + } + + interface ImageHeapLayouterCallback { + + ImageHeapLayouterCallback NONE = () -> false; + + /** + * Called periodically to determine whether the operation should be canceled. + */ + boolean shouldCancel(); + } + + class ImageHeapLayoutCancelledException extends RuntimeException { + private static final long serialVersionUID = 1017980175582546348L; + + public ImageHeapLayoutCancelledException() { + } + } } 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 6928eea7bdf3..a2c4793124db 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 @@ -94,6 +94,7 @@ import com.oracle.svm.core.graal.nodes.TLABObjectHeaderConstant; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.image.ImageHeapLayoutInfo; +import com.oracle.svm.core.image.ImageHeapLayouter.ImageHeapLayouterCallback; import com.oracle.svm.core.image.ImageHeapPartition; import com.oracle.svm.core.imagelayer.DynamicImageLayerInfo; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; @@ -436,7 +437,7 @@ public void build(String imageName, DebugContext debug) { long roSectionSize = codeCache.getAlignedConstantsSize(); long rwSectionSize = ConfigurationValues.getObjectLayout().alignUp(cGlobals.getSize()); - heapLayout = heap.getLayouter().layout(heap, objectFile.getPageSize()); + heapLayout = heap.getLayouter().layout(heap, objectFile.getPageSize(), ImageHeapLayouterCallback.NONE); // after this point, the layout is final and must not be changed anymore assert !hasDuplicatedObjects(heap.getObjects()) : "heap.getObjects() must not contain any duplicates"; diff --git a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/WebImageWasmLMCodeGen.java b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/WebImageWasmLMCodeGen.java index 64555fe13bb3..cb741f427f0b 100644 --- a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/WebImageWasmLMCodeGen.java +++ b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/WebImageWasmLMCodeGen.java @@ -36,6 +36,7 @@ import com.oracle.svm.core.c.CGlobalData; import com.oracle.svm.core.graal.code.CGlobalDataReference; import com.oracle.svm.core.image.ImageHeapLayoutInfo; +import com.oracle.svm.core.image.ImageHeapLayouter.ImageHeapLayouterCallback; import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.hosted.image.NativeImageHeap; import com.oracle.svm.hosted.image.NativeImageHeapWriter; @@ -90,7 +91,7 @@ public long getImageHeapSize() { */ @Override protected void writeImageHeap() { - ImageHeapLayoutInfo layout = codeCache.nativeImageHeap.getLayouter().layout(codeCache.nativeImageHeap, WasmUtil.PAGE_SIZE); + ImageHeapLayoutInfo layout = codeCache.nativeImageHeap.getLayouter().layout(codeCache.nativeImageHeap, WasmUtil.PAGE_SIZE, ImageHeapLayouterCallback.NONE); setLayout(layout); afterHeapLayout(); diff --git a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/codegen/WasmGCHeapWriter.java b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/codegen/WasmGCHeapWriter.java index b1265797ad76..fd1581ac0198 100644 --- a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/codegen/WasmGCHeapWriter.java +++ b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/codegen/WasmGCHeapWriter.java @@ -49,6 +49,7 @@ import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.Hybrid; +import com.oracle.svm.core.image.ImageHeapLayouter.ImageHeapLayouterCallback; import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.core.meta.SubstrateMethodPointerConstant; import com.oracle.svm.core.util.VMError; @@ -273,7 +274,7 @@ public WasmGCHeapWriter(WebImageCodeCache codeCache, WebImageWasmGCProviders pro public WasmGCImageHeapLayoutInfo layout() { collectObjectData(); - return (WasmGCImageHeapLayoutInfo) heap.getLayouter().layout(heap, WasmUtil.PAGE_SIZE); + return (WasmGCImageHeapLayoutInfo) heap.getLayouter().layout(heap, WasmUtil.PAGE_SIZE, ImageHeapLayouterCallback.NONE); } public void write(WasmGCImageHeapLayoutInfo layout, WasmModule module) { diff --git a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/image/WasmGCHeapLayouter.java b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/image/WasmGCHeapLayouter.java index 0e276badb865..899864ff09e3 100644 --- a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/image/WasmGCHeapLayouter.java +++ b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/image/WasmGCHeapLayouter.java @@ -73,7 +73,7 @@ public void assignObjectToPartition(ImageHeapObject info, boolean immutable, boo } @Override - public WasmGCImageHeapLayoutInfo layout(ImageHeap imageHeap, int pageSize) { + public WasmGCImageHeapLayoutInfo layout(ImageHeap imageHeap, int pageSize, ImageHeapLayouterCallback callback) { layoutPseudoPartition(); doLayout(); From 5cef0fd98a1491b23e7c661e6aae0524ce1590fe Mon Sep 17 00:00:00 2001 From: Christian Humer Date: Thu, 12 Jun 2025 18:57:55 +0200 Subject: [PATCH 2/2] Implement Engine.storeCache(...) method to persist engine caches manually. --- sdk/CHANGELOG.md | 1 + sdk/src/org.graalvm.polyglot/snapshot.sigtest | 2 + .../src/org/graalvm/polyglot/Engine.java | 72 +++++++++++++++++++ .../polyglot/impl/AbstractPolyglotImpl.java | 3 + .../api/test/wrapper/HostEngineDispatch.java | 8 +++ .../com/oracle/truffle/api/impl/Accessor.java | 7 +- .../api/impl/DefaultRuntimeAccessor.java | 7 ++ .../truffle/polyglot/EngineAccessor.java | 10 ++- .../polyglot/PolyglotEngineDispatch.java | 9 +++ .../truffle/polyglot/PolyglotEngineImpl.java | 37 +++++++++- .../oracle/truffle/polyglot/PolyglotImpl.java | 1 + .../truffle/runtime/CompilationTask.java | 8 ++- .../truffle/runtime/EngineCacheSupport.java | 7 ++ .../oracle/truffle/runtime/EngineData.java | 17 ++++- .../runtime/OptimizedRuntimeSupport.java | 7 ++ .../runtime/OptimizedTruffleRuntime.java | 18 +++++ 16 files changed, 206 insertions(+), 8 deletions(-) diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index eeeb56f07135..f61060952837 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -14,6 +14,7 @@ This changelog summarizes major changes between GraalVM SDK versions. The main f * GR-64488 FileSystem implementations can now provide disk-related metadata, including [total space](https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/io/FileSystem.html##getFileStoreTotalSpace(java.nio.file.Path)), [usable space](https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/io/FileSystem.html#getFileStoreUsableSpace(java.nio.file.Path)), [unallocated space](https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/io/FileSystem.html#getFileStoreUnallocatedSpace(java.nio.file.Path)), [block size](https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/io/FileSystem.html#getFileStoreBlockSize(java.nio.file.Path)), and [read-only status](https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/io/FileSystem.html#isFileStoreReadOnly(java.nio.file.Path)). * GR-22699(EE-only) Added the ability to spawn `Engine` or `Context` isolated in a separate process by setting `Context.Builder.option("engine.IsolateMode", "external").`. * GR-64087 Removed the dependency on `org.graalvm.truffle:truffle-enterprise` from all language and tool POM artifacts. As a result, the community Maven artifacts (those with an artifact ID ending in `-community`) are now identical to their corresponding non-community artifacts. Consequently, all community language and tool POM artifacts have been deprecated. The only exception is `org.graalvm.truffle:java-community` vs. `org.graalvm.truffle:java`, which differ in the bundled Java runtime. Embedders using auxiliary engine caching, polyglot isolates, a isolated/untrusted sandbox policy, or the sandbox resource limits must now explicitly add the `org.graalvm.truffle:truffle-enterprise` Maven artifact to the classpath or module path. +* GR-64947 Added `Engine.storeCache(Path)` to manually store [auxiliary engine caches](https://github.com/oracle/graal/blob/master/truffle/docs/AuxiliaryEngineCachingEnterprise.md) when needed. ## 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. diff --git a/sdk/src/org.graalvm.polyglot/snapshot.sigtest b/sdk/src/org.graalvm.polyglot/snapshot.sigtest index adb3d4f8044c..f9f95bc54ccd 100644 --- a/sdk/src/org.graalvm.polyglot/snapshot.sigtest +++ b/sdk/src/org.graalvm.polyglot/snapshot.sigtest @@ -210,6 +210,8 @@ intf java.lang.AutoCloseable meth public !varargs static boolean copyResources(java.nio.file.Path,java.lang.String[]) throws java.io.IOException meth public !varargs static org.graalvm.polyglot.Engine create(java.lang.String[]) meth public !varargs static org.graalvm.polyglot.Engine$Builder newBuilder(java.lang.String[]) +meth public boolean storeCache(java.nio.file.Path) +meth public boolean storeCache(java.nio.file.Path,org.graalvm.nativeimage.c.type.WordPointer) meth public java.lang.String getImplementationName() meth public java.lang.String getVersion() meth public java.util.Map getInstruments() 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..06b78e3f4ba8 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 @@ -80,6 +80,7 @@ import java.util.Properties; import java.util.ServiceLoader; import java.util.Set; +import java.util.concurrent.CancellationException; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; @@ -90,6 +91,7 @@ import org.graalvm.home.HomeFinder; import org.graalvm.home.Version; import org.graalvm.nativeimage.ImageInfo; +import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.options.OptionDescriptor; import org.graalvm.options.OptionDescriptors; import org.graalvm.polyglot.HostAccess.MutableTargetMapping; @@ -126,6 +128,8 @@ import org.graalvm.polyglot.proxy.ProxyObject; import org.graalvm.polyglot.proxy.ProxyTime; import org.graalvm.polyglot.proxy.ProxyTimeZone; +import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; /** * An execution engine for Graal {@linkplain Language guest languages} that allows to inspect the @@ -316,6 +320,74 @@ public void close() { close(false); } + /** + * Stores the auxiliary engine cache to the targetFile without cancellation. + * + * @see #storeCache(Path, Pointer) + * @throws UnsupportedOperationException if this engine or the host virtual machine does not + * support storing the cache. + * @since 25.0 + */ + public boolean storeCache(Path targetFile) throws UnsupportedOperationException { + return dispatch.storeCache(receiver, targetFile, WordFactory.nullPointer()); + } + + /** + * Stores the auxiliary engine cache to the {@code targetFile}. If it already exists, the file + * will be overwritten. The option engine.CacheStoreEnabled must be set to + * true to use this feature. Stored caches may be loaded by specifying the path + * using the engine.CacheLoad option. + *

+ * Note that this feature is experimental and only supported on native-image hosts with + * Truffle's enterprise extensions. + *

+ * + *

Basic Usage:

+ * + *
+     * // Store the engine cache into a file
+     * Path store = Files.createTempFile("cache", "engine");
+     * try (Engine e = Engine.newBuilder().allowExperimentalOptions(true).option("engine.CacheStoreEnabled", "true").build()) {
+     *     try (Context c = Context.newBuilder().engine(e).build()) {
+     *         // Evaluate sources, run application
+     *     }
+     *     e.storeCache(store);
+     * }
+     *
+     * // Load the engine cache from a file
+     * try (Engine e = Engine.newBuilder().allowExperimentalOptions(true).option("engine.CacheLoad", store.toAbsolutePath().toString()).build()) {
+     *     try (Context c = Context.newBuilder().engine(e).build()) {
+     *         // The context should be able to use
+     *         // the existing code cache.
+     *     }
+     * }
+     * 
+ * + *

+ * See the + * documentation on auxiliary engine caching for further details. + *

+ * + * @param targetFile the file to which the cache is stored + * @param cancelledWord a native pointer; if set to a non-zero value, the operation is + * cancelled. Allows cancellation of the cache store operation through a + * cancelled control word. The memory {@code address} pointing to the + * control word is polled periodically during storage without guaranteed frequency + * and may be delayed by safepoints such as garbage collection. A control word value + * of zero must be maintained for the duration of the operation. If a non-zero value + * is detected, the operation will be cancelled. + * @return true if the file was written; otherwise, false + * @throws CancellationException if the storeCache operation was cancelled via the + * cancelled pointer + * @throws UnsupportedOperationException if this engine or host virtual machine does not support + * cache storage + * @since 25.0 + */ + public boolean storeCache(Path targetFile, WordPointer cancelledWord) throws CancellationException, UnsupportedOperationException { + return dispatch.storeCache(receiver, targetFile, cancelledWord); + } + /** * Gets a human-readable name of the polyglot implementation (for example, "Default Truffle * Engine" or "Graal Truffle Engine"). The returned value may change without notice. The value 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 ab8589197c64..7f58e300f32c 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 @@ -74,6 +74,7 @@ import java.util.function.Predicate; import java.util.logging.LogRecord; +import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.options.OptionDescriptors; import org.graalvm.options.OptionValues; import org.graalvm.polyglot.Context; @@ -850,6 +851,8 @@ public abstract Object attachExecutionListener(Object engine, Consumer o public abstract void onEngineCollected(Object engineReceiver); + public abstract boolean storeCache(Object engineReceiver, Path targetFile, WordPointer cancelledWord); + } public abstract static class AbstractExceptionDispatch extends AbstractDispatchClass { diff --git a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/wrapper/HostEngineDispatch.java b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/wrapper/HostEngineDispatch.java index 3c74cca91a25..9c26b2ec9344 100644 --- a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/wrapper/HostEngineDispatch.java +++ b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/wrapper/HostEngineDispatch.java @@ -43,12 +43,14 @@ import java.io.InputStream; import java.io.OutputStream; import java.lang.ref.Reference; +import java.nio.file.Path; import java.time.ZoneId; import java.util.Map; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; +import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.options.OptionDescriptors; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Engine; @@ -188,4 +190,10 @@ public void onEngineCollected(Object receiver) { dispatch.onEngineCollected(engineReceiver); hostToGuest.shutdown(engine.remoteEngine); } + + @Override + public boolean storeCache(Object engineReceiver, Path targetFile, WordPointer cancelWord) { + throw new UnsupportedOperationException(); + } + } diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java index 3617d6d5c964..11599039238f 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java @@ -73,6 +73,7 @@ import org.graalvm.collections.Pair; import org.graalvm.nativeimage.ImageInfo; +import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.options.OptionDescriptors; import org.graalvm.options.OptionKey; import org.graalvm.options.OptionValues; @@ -692,7 +693,9 @@ public abstract Iterator mergeHostGuestFrames(Object polyglotEngine, S public abstract void preinitializeContext(Object polyglotEngine); - public abstract void finalizeStore(Object polyglotEngine); + public abstract Object finalizeStore(Object polyglotEngine); + + public abstract void restoreStore(Object polyglotEngine, Object finalizationResult); public abstract Object getEngineLock(Object polyglotEngine); @@ -1295,6 +1298,8 @@ public final FrameExtensions getFrameExtensionsUnsafe() { public abstract boolean onEngineClosing(Object runtimeData); + public abstract boolean onStoreCache(Object runtimeData, Path targetPath, WordPointer cancelledWord); + public abstract void onEngineClosed(Object runtimeData); public abstract boolean isOSRRootNode(RootNode rootNode); diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultRuntimeAccessor.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultRuntimeAccessor.java index ddebfb34f607..60df3193549d 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultRuntimeAccessor.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultRuntimeAccessor.java @@ -40,10 +40,12 @@ */ package com.oracle.truffle.api.impl; +import java.nio.file.Path; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.options.OptionDescriptors; import org.graalvm.options.OptionValues; import org.graalvm.polyglot.SandboxPolicy; @@ -255,6 +257,11 @@ public boolean onEngineClosing(Object runtimeData) { return false; } + @Override + public boolean onStoreCache(Object runtimeData, Path targetPath, WordPointer cancelledWord) { + throw new UnsupportedOperationException("Persisting an engine is not supported with the the Truffle fallback runtime. It is only supported on native-image hosts."); + } + @Override public boolean isOSRRootNode(RootNode rootNode) { return false; diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java index ae5a8e33d193..b0877641910d 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java @@ -130,6 +130,7 @@ import com.oracle.truffle.api.source.SourceSection; import com.oracle.truffle.polyglot.FileSystems.ResetablePath; import com.oracle.truffle.polyglot.PolyglotContextConfig.FileSystemConfig; +import com.oracle.truffle.polyglot.PolyglotEngineImpl.FinalizationResult; import com.oracle.truffle.polyglot.PolyglotImpl.EmbedderFileSystemContext; import com.oracle.truffle.polyglot.PolyglotImpl.VMObject; import com.oracle.truffle.polyglot.PolyglotLocals.InstrumentContextLocal; @@ -1323,8 +1324,13 @@ public void preinitializeContext(Object polyglotEngine) { } @Override - public void finalizeStore(Object polyglotEngine) { - ((PolyglotEngineImpl) polyglotEngine).finalizeStore(); + public Object finalizeStore(Object polyglotEngine) { + return ((PolyglotEngineImpl) polyglotEngine).finalizeStore(); + } + + @Override + public void restoreStore(Object polyglotEngine, Object finalizationResult) { + ((PolyglotEngineImpl) polyglotEngine).restoreStore((FinalizationResult) finalizationResult); } @Override diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineDispatch.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineDispatch.java index bb44a0916c0e..e29208676468 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineDispatch.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineDispatch.java @@ -43,6 +43,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.lang.ref.Reference; +import java.nio.file.Path; import java.time.ZoneId; import java.util.ArrayList; import java.util.HashMap; @@ -52,6 +53,7 @@ import java.util.function.Consumer; import java.util.function.Predicate; +import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.options.OptionDescriptors; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Engine; @@ -327,4 +329,11 @@ public SandboxPolicy getSandboxPolicy(Object engineReceiver) { public void onEngineCollected(Object engineReceiver) { ((PolyglotEngineImpl) engineReceiver).onEngineCollected(); } + + @Override + public boolean storeCache(Object engineReceiver, Path targetFile, WordPointer cancelledWord) { + PolyglotEngineImpl engine = ((PolyglotEngineImpl) engineReceiver); + return engine.storeCache(targetFile, cancelledWord); + } + } diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java index 879dfe4f64d3..174ef6699061 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java @@ -88,6 +88,7 @@ import org.graalvm.collections.EconomicSet; import org.graalvm.collections.Equivalence; import org.graalvm.home.HomeFinder; +import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.options.OptionDescriptor; import org.graalvm.options.OptionDescriptors; import org.graalvm.polyglot.Context; @@ -1242,6 +1243,28 @@ > PolyglotLanguage getLanguage(Class languageCla return foundLanguage; } + boolean storeCache(Path targetPath, WordPointer cancelledWord) { + if (!TruffleOptions.AOT) { + throw new UnsupportedOperationException("Storing the engine cache is only supported on native-image hosts."); + } + + synchronized (this.lock) { + if (closingThread != null || closed) { + throw new IllegalStateException("The engine is already closed and cannot be cancelled or persisted."); + } + if (!storeEngine) { + throw new IllegalStateException( + "In order to store the cache the option 'engine.CacheStoreEnabled' must be set to 'true'."); + } + List localContexts = collectAliveContexts(); + if (!localContexts.isEmpty()) { + throw new IllegalStateException("There are still alive contexts that need to be closed or cancelled before the engine can be persisted."); + } + + return RUNTIME.onStoreCache(this.runtimeData, targetPath, cancelledWord); + } + } + void ensureClosed(boolean force, boolean initiatedByContext) { synchronized (this.lock) { Thread currentThread = Thread.currentThread(); @@ -1515,12 +1538,15 @@ void preInitialize() { } } + record FinalizationResult(DispatchOutputStream out, DispatchOutputStream err, InputStream in) { + } + /** * Invoked when the context is closing to prepare an engine to be stored. */ - void finalizeStore() { + FinalizationResult finalizeStore() { assert Thread.holdsLock(this.lock); - + FinalizationResult result = new FinalizationResult(out, err, in); this.out = null; this.err = null; this.in = null; @@ -1534,6 +1560,13 @@ void finalizeStore() { if (hostLanguageService != null) { hostLanguageService.release(); } + return result; + } + + void restoreStore(FinalizationResult result) { + this.out = result.out; + this.err = result.err; + this.in = result.in; } @TruffleBoundary diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotImpl.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotImpl.java index 5e7e1447809f..f69bd9f13760 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotImpl.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotImpl.java @@ -116,6 +116,7 @@ public final class PolyglotImpl extends AbstractPolyglotImpl { "engine.Cache", "engine.CacheLoad", "engine.CacheStore", + "engine.CacheStoreEnabled", "engine.DebugCacheLoad", "engine.DebugCacheStore", "engine.SpawnIsolate"); diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/CompilationTask.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/CompilationTask.java index 25d7aeb36452..752a9b0c5286 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/CompilationTask.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/CompilationTask.java @@ -50,6 +50,7 @@ import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.BooleanSupplier; import java.util.function.Consumer; import com.oracle.truffle.api.Truffle; @@ -70,6 +71,7 @@ public void accept(CompilationTask task) { } } }; + final WeakReference targetRef; private final BackgroundCompileQueue.Priority priority; private final long id; @@ -77,6 +79,7 @@ public void accept(CompilationTask task) { private final EngineData engineData; private volatile Future future; private volatile boolean cancelled; + volatile BooleanSupplier cancelledPredicate; private volatile boolean started; // Traversing queue related private int lastCount; @@ -105,10 +108,12 @@ private CompilationTask(BackgroundCompileQueue.Priority priority, WeakReference< lastCount = Integer.MIN_VALUE; engineData = null; isOSR = false; + cancelledPredicate = null; } else { lastCount = target.getCallAndLoopCount(); engineData = target.engine; isOSR = target.isOSR(); + cancelledPredicate = target.engine.cancelledPredicate; } } @@ -170,7 +175,8 @@ public synchronized boolean start() { @Override public boolean isCancelled() { - return cancelled; + BooleanSupplier cancelPredicate = cancelledPredicate; + return cancelled || (cancelPredicate != null && cancelPredicate.getAsBoolean()); } @Override diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/EngineCacheSupport.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/EngineCacheSupport.java index 0e51145fb2e1..70c673d498af 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/EngineCacheSupport.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/EngineCacheSupport.java @@ -40,8 +40,10 @@ */ package com.oracle.truffle.runtime; +import java.nio.file.Path; import java.util.function.Function; +import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.options.OptionDescriptors; import org.graalvm.options.OptionValues; @@ -55,6 +57,11 @@ public interface EngineCacheSupport extends OptimizedRuntimeServiceProvider { boolean onEngineClosing(EngineData e); + @SuppressWarnings("unused") + default boolean onStoreCache(EngineData e, Path path, WordPointer cancelledWord) { + throw new UnsupportedOperationException("Engine persist ist not yet supported on this JDK. Please update to resolve this problem."); + } + void onEngineClosed(EngineData e); boolean isStoreEnabled(OptionValues options); diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/EngineData.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/EngineData.java index ad1d735268b5..961b65f05fa5 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/EngineData.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/EngineData.java @@ -83,6 +83,7 @@ import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.TraversingQueueWeightingBothTiers; import static com.oracle.truffle.runtime.OptimizedTruffleRuntime.getRuntime; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -90,10 +91,12 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BooleanSupplier; import java.util.function.Function; import java.util.logging.Level; import org.graalvm.collections.Pair; +import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.options.OptionValues; import org.graalvm.polyglot.SandboxPolicy; @@ -173,6 +176,8 @@ public final class EngineData { @CompilationFinal public int callAndLoopThresholdInFirstTier; @CompilationFinal public long interpreterCallStackHeadRoom; + public BooleanSupplier cancelledPredicate; + // Cached parsed CompileOnly includes and excludes private volatile Pair, List> parsedCompileOnly; private Map compilerOptions; @@ -215,8 +220,12 @@ public void preinitializeContext() { OptimizedRuntimeAccessor.ENGINE.preinitializeContext(this.polyglotEngine); } - public void finalizeStore() { - OptimizedRuntimeAccessor.ENGINE.finalizeStore(this.polyglotEngine); + public Object finalizeStore() { + return OptimizedRuntimeAccessor.ENGINE.finalizeStore(this.polyglotEngine); + } + + public void restoreStore(Object finalizationResult) { + OptimizedRuntimeAccessor.ENGINE.restoreStore(this.polyglotEngine, finalizationResult); } public Object getEngineLock() { @@ -291,6 +300,10 @@ void onEngineClosed() { this.polyglotEngine = null; } + public boolean onStoreCache(Path targetPath, WordPointer cancelledWord) { + return getRuntime().getEngineCacheSupport().onStoreCache(this, targetPath, cancelledWord); + } + private void loadOptions(OptionValues options, SandboxPolicy sandboxPolicy) { this.engineOptions = options; diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedRuntimeSupport.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedRuntimeSupport.java index 137fe9241bf0..9836a0c4e364 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedRuntimeSupport.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedRuntimeSupport.java @@ -40,10 +40,12 @@ */ package com.oracle.truffle.runtime; +import java.nio.file.Path; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.options.OptionDescriptors; import org.graalvm.options.OptionValues; import org.graalvm.polyglot.SandboxPolicy; @@ -360,6 +362,11 @@ public boolean onEngineClosing(Object runtimeData) { return ((EngineData) runtimeData).onEngineClosing(); } + @Override + public boolean onStoreCache(Object runtimeData, Path targetPath, WordPointer cancelledWord) { + return ((EngineData) runtimeData).onStoreCache(targetPath, cancelledWord); + } + @Override public void onEngineClosed(Object runtimeData) { ((EngineData) runtimeData).onEngineClosed(); diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntime.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntime.java index 267ab7897f7e..7573f0d39dc2 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntime.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntime.java @@ -63,6 +63,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.logging.Level; @@ -980,7 +981,24 @@ public void waitForCompilation(OptimizedCallTarget optimizedCallTarget, long tim // ignore interrupted } } + } + public void waitForCompilation(OptimizedCallTarget optimizedCallTarget, long timeout, BooleanSupplier cancelledPredicate) throws ExecutionException, TimeoutException { + CompilationTask task = optimizedCallTarget.getCompilationTask(); + if (task != null) { + BooleanSupplier prev = task.cancelledPredicate; + try { + task.cancelledPredicate = cancelledPredicate; + try { + task.awaitCompletion(timeout, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + // ignore interrupted + } + } finally { + task.cancelledPredicate = prev; + } + } } public int getCompilationQueueSize() {