From 7bb5681c00c57cf8f7cf5bfc38897722a9baaf35 Mon Sep 17 00:00:00 2001 From: Tom Shull Date: Thu, 30 May 2024 18:10:08 +0200 Subject: [PATCH] Introduce ApplicationLayerOnly and MultiLayer Image Singletons. --- .../pointsto/heap/ImageHeapConstant.java | 4 + .../pointsto/heap/ImageHeapObjectArray.java | 8 + .../graal/pointsto/heap/ImageLayerLoader.java | 2 +- .../graal/nodes/LoadImageSingletonNode.java | 106 +++ .../imagelayer/ImageLayerBuildingSupport.java | 21 +- .../imagelayer/LastImageBuildPredicate.java | 38 ++ .../imagelayer/LoadImageSingletonFactory.java | 63 ++ .../ApplicationLayerOnlyImageSingleton.java | 28 + .../ImageSingletonLoader.java | 4 + .../ImageSingletonWriter.java | 4 + .../LayeredImageSingleton.java | 41 ++ .../LayeredImageSingletonBuilderFlags.java | 33 +- .../LayeredImageSingletonSupport.java | 6 + .../MultiLayeredImageSingleton.java | 35 + .../RuntimeOnlyImageSingleton.java | 2 +- .../oracle/svm/core/thread/JavaThreads.java | 44 +- .../svm/core/thread/JavaThreadsFeature.java | 8 - .../core/thread/Target_java_lang_Thread.java | 5 +- .../hosted/ImageSingletonsSupportImpl.java | 55 +- .../svm/hosted/NativeImageGenerator.java | 1 - .../flow/SVMMethodTypeFlowBuilder.java | 19 + .../svm/hosted/heap/SVMImageLayerLoader.java | 17 + .../svm/hosted/heap/SVMImageLayerWriter.java | 29 +- .../svm/hosted/image/NativeImageHeap.java | 5 + .../imagelayer/ImageLayerSectionFeature.java | 45 +- .../imagelayer/LoadImageSingletonFeature.java | 622 ++++++++++++++++++ .../SubstrateGraphBuilderPlugins.java | 21 +- .../thread/HostedJavaThreadsFeature.java | 77 ++- 28 files changed, 1264 insertions(+), 79 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/nodes/LoadImageSingletonNode.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/LastImageBuildPredicate.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/LoadImageSingletonFactory.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/ApplicationLayerOnlyImageSingleton.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/MultiLayeredImageSingleton.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadImageSingletonFeature.java 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 bfa0428d3fac..7f95b1f88e6d 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 @@ -206,6 +206,10 @@ public boolean isBackedByHostedObject() { return constantData.hostedObject != null; } + public static int getConstantID(ImageHeapConstant constant) { + return constant.getConstantData().id; + } + @Override public JavaKind getJavaKind() { return JavaKind.Object; 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 f4d3560c4c99..e87e85a3508f 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 @@ -75,6 +75,10 @@ private ObjectArrayData(AnalysisType type, JavaConstant hostedObject, Object[] a super(new ObjectArrayData(type, hostedObject, null, length, identityHashCode), false); } + ImageHeapObjectArray(AnalysisType type, JavaConstant hostedObject, Object[] arrayElementValues, int identityHashCode) { + super(new ObjectArrayData(type, hostedObject, arrayElementValues, arrayElementValues.length, identityHashCode), false); + } + ImageHeapObjectArray(AnalysisType type, int length) { super(new ObjectArrayData(type, null, new Object[length], length, -1), false); } @@ -93,6 +97,10 @@ void setElementValues(Object[] elementValues) { AnalysisError.guarantee(success, "Unexpected field values reference for constant %s", this); } + public static ImageHeapObjectArray createUnbackedImageHeapArray(AnalysisType type, Object[] elementValues) { + return new ImageHeapObjectArray(type, null, elementValues, -1); + } + /** * {@link ObjectArrayData#arrayElementValues} are only set once, in * {@link #setElementValues(Object[])} and shouldn't be accessed before set, i.e., read access 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 index e3760b1704cc..5292f771ee97 100644 --- 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 @@ -933,7 +933,7 @@ protected ImageHeapConstant getValueForObject(Object object) { throw AnalysisError.shouldNotReachHere("The constant was not in the persisted heap."); } - protected ImageHeapConstant getOrCreateConstant(int id) { + public ImageHeapConstant getOrCreateConstant(int id) { return getOrCreateConstant(get(jsonMap, CONSTANTS_TAG), id, null); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/nodes/LoadImageSingletonNode.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/nodes/LoadImageSingletonNode.java new file mode 100644 index 000000000000..d5c66d0420d3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/nodes/LoadImageSingletonNode.java @@ -0,0 +1,106 @@ +/* + * 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.heap.ReferenceAccess; +import com.oracle.svm.core.imagelayer.LoadImageSingletonFactory.LoadImageSingletonData; + +import jdk.graal.compiler.core.common.memory.BarrierType; +import jdk.graal.compiler.core.common.memory.MemoryOrderMode; +import jdk.graal.compiler.core.common.type.AbstractObjectStamp; +import jdk.graal.compiler.core.common.type.Stamp; +import jdk.graal.compiler.core.common.type.StampFactory; +import jdk.graal.compiler.core.common.type.TypeReference; +import jdk.graal.compiler.graph.Node; +import jdk.graal.compiler.graph.NodeClass; +import jdk.graal.compiler.nodeinfo.NodeCycles; +import jdk.graal.compiler.nodeinfo.NodeInfo; +import jdk.graal.compiler.nodeinfo.NodeSize; +import jdk.graal.compiler.nodes.CompressionNode; +import jdk.graal.compiler.nodes.ConstantNode; +import jdk.graal.compiler.nodes.FixedWithNextNode; +import jdk.graal.compiler.nodes.NamedLocationIdentity; +import jdk.graal.compiler.nodes.NodeView; +import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.nodes.memory.ReadNode; +import jdk.graal.compiler.nodes.memory.address.AddressNode; +import jdk.graal.compiler.nodes.memory.address.OffsetAddressNode; +import jdk.graal.compiler.nodes.spi.Canonicalizable; +import jdk.graal.compiler.nodes.spi.CanonicalizerTool; +import jdk.graal.compiler.nodes.spi.Lowerable; +import jdk.graal.compiler.nodes.spi.LoweringTool; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.MetaAccessProvider; + +@NodeInfo(cycles = NodeCycles.CYCLES_4, size = NodeSize.SIZE_2) +public class LoadImageSingletonNode extends FixedWithNextNode implements Lowerable, Canonicalizable { + public static final NodeClass TYPE = NodeClass.create(LoadImageSingletonNode.class); + + private final LoadImageSingletonData singletonInfo; + + protected LoadImageSingletonNode(LoadImageSingletonData singletonInfo, Stamp stamp) { + super(TYPE, stamp); + this.singletonInfo = singletonInfo; + } + + public static LoadImageSingletonNode createLoadImageSingleton(LoadImageSingletonData singletonInfo, MetaAccessProvider metaAccess) { + return new LoadImageSingletonNode(singletonInfo, StampFactory.objectNonNull(TypeReference.createExactTrusted(metaAccess.lookupJavaType(singletonInfo.getLoadType())))); + } + + @Override + public Node canonical(CanonicalizerTool tool) { + if (tool.allUsagesAvailable() && hasNoUsages()) { + // can remove this load if it is never used. + return null; + } + + return this; + } + + @Override + public void lower(LoweringTool tool) { + StructuredGraph graph = graph(); + var singletonAccessInfo = singletonInfo.getAccessInfo(); + + /* + * Load the starting address of the singleton table. + */ + + CGlobalDataLoadAddressNode baseAddress = graph.unique(new CGlobalDataLoadAddressNode(singletonAccessInfo.tableBase())); + + /* + * Read from the appropriate offset of the singleton table. + */ + + AddressNode address = graph.unique(new OffsetAddressNode(baseAddress, ConstantNode.forIntegerKind(JavaKind.Long, singletonAccessInfo.offset(), graph))); + var tableReadStamp = SubstrateNarrowOopStamp.compressed((AbstractObjectStamp) stamp(NodeView.DEFAULT), ReferenceAccess.singleton().getCompressEncoding()); + ReadNode tableRead = graph.add(new ReadNode(address, NamedLocationIdentity.FINAL_LOCATION, tableReadStamp, BarrierType.NONE, MemoryOrderMode.PLAIN)); + + CompressionNode uncompress = SubstrateCompressionNode.uncompress(graph(), tableRead, ReferenceAccess.singleton().getCompressEncoding()); + + replaceAtUsages(uncompress); + graph.replaceFixed(this, tableRead); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerBuildingSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerBuildingSupport.java index f720002043a5..6b2e134a9bef 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerBuildingSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/ImageLayerBuildingSupport.java @@ -24,14 +24,11 @@ */ package com.oracle.svm.core.imagelayer; -import java.util.EnumSet; - import org.graalvm.nativeimage.ImageSingletons; -import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags; -import com.oracle.svm.core.layeredimagesingleton.UnsavedSingleton; - import jdk.graal.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; /** * Support for tracking the image layer stage of this native-image build. When image layers are @@ -58,8 +55,10 @@ * | (D) Application Layer | * |------------------------| * + * + * Note this is intentionally not a LayeredImageSingleton itself to prevent circular dependencies. */ -public abstract class ImageLayerBuildingSupport implements UnsavedSingleton { +public abstract class ImageLayerBuildingSupport { protected final boolean buildingImageLayer; private final boolean buildingInitialLayer; private final boolean buildingApplicationLayer; @@ -74,6 +73,11 @@ private static ImageLayerBuildingSupport singleton() { return ImageSingletons.lookup(ImageLayerBuildingSupport.class); } + @Platforms(Platform.HOSTED_ONLY.class) + public static boolean lastImageBuild() { + return !buildingImageLayer() || buildingApplicationLayer(); + } + @Fold public static boolean buildingImageLayer() { return singleton().buildingImageLayer; @@ -98,9 +102,4 @@ public static boolean buildingExtensionLayer() { public static boolean buildingSharedLayer() { return singleton().buildingImageLayer && !singleton().buildingApplicationLayer; } - - @Override - public final EnumSet getImageBuilderFlags() { - return LayeredImageSingletonBuilderFlags.ALL_ACCESS_ALLOW_FOLDING; - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/LastImageBuildPredicate.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/LastImageBuildPredicate.java new file mode 100644 index 000000000000..7a524e6fa87a --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/LastImageBuildPredicate.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.core.imagelayer; + +import java.util.function.BooleanSupplier; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +@Platforms(Platform.HOSTED_ONLY.class) +public class LastImageBuildPredicate implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + return ImageLayerBuildingSupport.lastImageBuild(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/LoadImageSingletonFactory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/LoadImageSingletonFactory.java new file mode 100644 index 000000000000..c4a50f6b9392 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/LoadImageSingletonFactory.java @@ -0,0 +1,63 @@ +/* + * 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.imagelayer; + +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.svm.core.graal.code.CGlobalDataInfo; +import com.oracle.svm.core.graal.nodes.LoadImageSingletonNode; + +import jdk.vm.ci.meta.MetaAccessProvider; + +public abstract class LoadImageSingletonFactory { + + public record SingletonAccessInfo(CGlobalDataInfo tableBase, int offset) { + + } + + /** + * Provides compiler-relevant information about the value which will be loaded. + */ + public interface LoadImageSingletonData { + + Class getLoadType(); + + SingletonAccessInfo getAccessInfo(); + } + + protected abstract LoadImageSingletonData getApplicationLayerOnlyImageSingletonInfo(Class clazz); + + protected abstract LoadImageSingletonData getLayeredImageSingletonInfo(Class clazz); + + public static LoadImageSingletonNode loadApplicationOnlyImageSingleton(Class clazz, MetaAccessProvider metaAccess) { + LoadImageSingletonData singletonInfo = ImageSingletons.lookup(LoadImageSingletonFactory.class).getApplicationLayerOnlyImageSingletonInfo(clazz); + return LoadImageSingletonNode.createLoadImageSingleton(singletonInfo, metaAccess); + } + + public static LoadImageSingletonNode loadLayeredImageSingleton(Class clazz, MetaAccessProvider metaAccess) { + LoadImageSingletonData singletonInfo = ImageSingletons.lookup(LoadImageSingletonFactory.class).getLayeredImageSingletonInfo(clazz); + return LoadImageSingletonNode.createLoadImageSingleton(singletonInfo, metaAccess); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/ApplicationLayerOnlyImageSingleton.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/ApplicationLayerOnlyImageSingleton.java new file mode 100644 index 000000000000..53c0beb9e502 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/ApplicationLayerOnlyImageSingleton.java @@ -0,0 +1,28 @@ +/* + * 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.layeredimagesingleton; + +public interface ApplicationLayerOnlyImageSingleton extends LayeredImageSingleton { +} 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 7558e4935130..fc97c2303c87 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 @@ -31,5 +31,9 @@ public interface ImageSingletonLoader { List readIntList(String keyName); + long readLong(String keyName); + String readString(String keyName); + + List readStringList(String keyName); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/ImageSingletonWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/ImageSingletonWriter.java index 2ee302e399a9..f9d471ab1eb7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/ImageSingletonWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/ImageSingletonWriter.java @@ -31,5 +31,9 @@ public interface ImageSingletonWriter { void writeIntList(String keyName, List value); + void writeLong(String keyName, long value); + void writeString(String keyName, String value); + + void writeStringList(String keyName, List value); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingleton.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingleton.java index 54e444d96185..c7af0691248c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingleton.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingleton.java @@ -26,6 +26,41 @@ import java.util.EnumSet; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +/** + * In additional to the traditional singleton model, i.e. a key-value map whose lookups are constant + * folded within generated code, we provide two additional options: + * + *
    + *
  • {@link ApplicationLayerOnlyImageSingleton}: Instead of having a per-layer singleton, all + * {@link ImageSingletons#lookup} calls refer to a single singleton which will be created in the + * application layer.
  • + * + *
  • {@link MultiLayeredImageSingleton}: {@link ImageSingletons#lookup} calls continue to refer to + * the appropriate per layer image singleton, but there is also an additional method + * {@link MultiLayeredImageSingleton#getAllLayers} which returns an array with the image singletons + * corresponding to this key in all layers they were created.
  • + *
+ * + * Note the unique behavior of {@link ApplicationLayerOnlyImageSingleton} and + * {@link MultiLayeredImageSingleton} apply only when building a layered image. During a traditional + * build these flags do not have an impact. + * + * Currently, when using these special singleton types there are additional restrictions: + * + *
    + *
  1. The key class type must match the implementation type
  2. + *
  3. The same object cannot be mapped into multiple keys, i.e. there is a one-to-one mapping + * between Class<->singleton object
  4. + *
  5. {@link ImageSingletons#add} must be called before the analysis phase (i.e. these image + * singletons cannot be added at a later point)
  6. + *
  7. {@link ApplicationLayerOnlyImageSingleton}s can only be installed in the application + * layer
  8. + *
+ */ public interface LayeredImageSingleton { enum PersistFlags { @@ -46,8 +81,14 @@ enum PersistFlags { CREATE } + /* + * Returns how this singleton should be handled for the current build. The returned value must + * not change throughout execution (i.e., the returned results can be cached). + */ + @Platforms(Platform.HOSTED_ONLY.class) EnumSet getImageBuilderFlags(); + @Platforms(Platform.HOSTED_ONLY.class) PersistFlags preparePersist(ImageSingletonWriter writer); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingletonBuilderFlags.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingletonBuilderFlags.java index bceeda445b3b..7a231fa0a302 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingletonBuilderFlags.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingletonBuilderFlags.java @@ -42,19 +42,7 @@ public enum LayeredImageSingletonBuilderFlags { /** * This singleton should not have been created. Throw error if it is created. */ - UNSUPPORTED, - /** - * This singleton should only be created in the initial layer. - */ - INITIAL_LAYER_ONLY, - /** - * Allow the singleton to be constant-folded within the generated code. - */ - ALLOW_CONSTANT_FOLDING, - /** - * Prevent the singleton to be constant-folded within the generated code. - */ - PREVENT_CONSTANT_FOLDING; + UNSUPPORTED; /* * Below are some common flag patterns. @@ -62,11 +50,9 @@ public enum LayeredImageSingletonBuilderFlags { public static final EnumSet BUILDTIME_ACCESS_ONLY = EnumSet.of(BUILDTIME_ACCESS); - public static final EnumSet RUNTIME_ACCESS_ONLY_ALLOW_FOLDING = EnumSet.of(RUNTIME_ACCESS, - ALLOW_CONSTANT_FOLDING); + public static final EnumSet RUNTIME_ACCESS_ONLY = EnumSet.of(RUNTIME_ACCESS); - public static final EnumSet ALL_ACCESS_ALLOW_FOLDING = EnumSet.of(RUNTIME_ACCESS, - BUILDTIME_ACCESS, ALLOW_CONSTANT_FOLDING); + public static final EnumSet ALL_ACCESS = EnumSet.of(RUNTIME_ACCESS, BUILDTIME_ACCESS); public static boolean verifyImageBuilderFlags(LayeredImageSingleton singleton) { EnumSet flags = singleton.getImageBuilderFlags(); @@ -79,16 +65,13 @@ public static boolean verifyImageBuilderFlags(LayeredImageSingleton singleton) { assert flags.equals(EnumSet.of(UNSUPPORTED)) : "Unsupported should be the only flag set " + flags; } - if (flags.contains(RUNTIME_ACCESS)) { - assert !flags.containsAll(EnumSet.of(PREVENT_CONSTANT_FOLDING, ALLOW_CONSTANT_FOLDING)) : String.format("Must set one of %s or %s. flags: %s", PREVENT_CONSTANT_FOLDING, - ALLOW_CONSTANT_FOLDING, flags); - assert flags.contains(PREVENT_CONSTANT_FOLDING) || flags.contains(ALLOW_CONSTANT_FOLDING) : String.format("Must set one of %s or %s. flags: %s", PREVENT_CONSTANT_FOLDING, - ALLOW_CONSTANT_FOLDING, flags); + if (singleton instanceof MultiLayeredImageSingleton || singleton instanceof ApplicationLayerOnlyImageSingleton) { + assert flags.contains(RUNTIME_ACCESS) : String.format("%s must be set when implementing either %s or %s: %s", RUNTIME_ACCESS, MultiLayeredImageSingleton.class, + ApplicationLayerOnlyImageSingleton.class, singleton); } - if (flags.contains(BUILDTIME_ACCESS) && !flags.contains(RUNTIME_ACCESS)) { - assert !(flags.contains(PREVENT_CONSTANT_FOLDING) || flags.contains(ALLOW_CONSTANT_FOLDING)) : "Constant folding is only applicable for runtime accesses " + flags; - } + assert !(singleton instanceof MultiLayeredImageSingleton && singleton instanceof ApplicationLayerOnlyImageSingleton) : String.format("%s can only implement one of %s or %s", singleton, + MultiLayeredImageSingleton.class, ApplicationLayerOnlyImageSingleton.class); return true; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingletonSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingletonSupport.java index 9ecc481ed01f..4d5855c72419 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingletonSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingletonSupport.java @@ -26,6 +26,8 @@ import org.graalvm.nativeimage.ImageSingletons; +import java.util.Collection; + public interface LayeredImageSingletonSupport { static LayeredImageSingletonSupport singleton() { @@ -33,4 +35,8 @@ static LayeredImageSingletonSupport singleton() { } T runtimeLookup(Class key); + + Collection> getMultiLayeredImageSingletonKeys(); + + void freezeMultiLayeredImageSingletons(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/MultiLayeredImageSingleton.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/MultiLayeredImageSingleton.java new file mode 100644 index 000000000000..5c49781f91b4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/MultiLayeredImageSingleton.java @@ -0,0 +1,35 @@ +/* + * 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.layeredimagesingleton; + +import com.oracle.svm.core.util.VMError; + +public interface MultiLayeredImageSingleton extends LayeredImageSingleton { + + @SuppressWarnings("unused") + static T[] getAllLayers(Class key) { + throw VMError.shouldNotReachHere("This can only be called during runtime"); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/RuntimeOnlyImageSingleton.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/RuntimeOnlyImageSingleton.java index 3775ecbd8f44..dd477d96f28f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/RuntimeOnlyImageSingleton.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/RuntimeOnlyImageSingleton.java @@ -30,7 +30,7 @@ public interface RuntimeOnlyImageSingleton extends LayeredImageSingleton { @Override default EnumSet getImageBuilderFlags() { - return LayeredImageSingletonBuilderFlags.RUNTIME_ACCESS_ONLY_ALLOW_FOLDING; + return LayeredImageSingletonBuilderFlags.RUNTIME_ACCESS_ONLY; } @Override 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 7213c8e1faf5..113558c128ff 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 @@ -27,11 +27,14 @@ import java.lang.Thread.UncaughtExceptionHandler; import java.security.AccessControlContext; import java.security.AccessController; +import java.util.EnumSet; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.impl.InternalPlatform; import org.graalvm.word.Pointer; @@ -41,7 +44,12 @@ import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.imagelayer.LastImageBuildPredicate; import com.oracle.svm.core.jdk.StackTraceUtils; +import com.oracle.svm.core.layeredimagesingleton.ApplicationLayerOnlyImageSingleton; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags; +import com.oracle.svm.core.layeredimagesingleton.UnsavedSingleton; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.snippets.KnownIntrinsics; import com.oracle.svm.core.stack.StackFrameVisitor; @@ -84,10 +92,34 @@ public final class JavaThreads { */ static final FastThreadLocalLong currentVThreadId = FastThreadLocalFactory.createLong("JavaThreads.currentVThreadId").setMaxOffset(FastThreadLocal.BYTE_OFFSET); - /** For Thread.nextThreadID(). */ - static final AtomicLong threadSeqNumber = new AtomicLong(); - /** For Thread.nextThreadNum(). */ - static final AtomicInteger threadInitNumber = new AtomicInteger(); + @AutomaticallyRegisteredImageSingleton(onlyWith = LastImageBuildPredicate.class) + public static class JavaThreadNumberSingleton implements ApplicationLayerOnlyImageSingleton, UnsavedSingleton { + + public static JavaThreadNumberSingleton singleton() { + return ImageSingletons.lookup(JavaThreadNumberSingleton.class); + } + + /** For Thread.nextThreadID(). */ + final AtomicLong threadSeqNumber; + /** For Thread.nextThreadNum(). */ + final AtomicInteger threadInitNumber; + + public JavaThreadNumberSingleton() { + this.threadSeqNumber = new AtomicLong(); + this.threadInitNumber = new AtomicInteger(); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setThreadNumberInfo(long seqNumber, int initNumber) { + this.threadSeqNumber.set(seqNumber); + this.threadInitNumber.set(initNumber); + } + + @Override + public EnumSet getImageBuilderFlags() { + return LayeredImageSingletonBuilderFlags.ALL_ACCESS; + } + } private JavaThreads() { } @@ -105,12 +137,12 @@ static Target_java_lang_Thread toTarget(Thread thread) { @Platforms(InternalPlatform.NATIVE_ONLY.class) static long nextThreadID() { - return JavaThreads.threadSeqNumber.incrementAndGet(); + return JavaThreadNumberSingleton.singleton().threadSeqNumber.incrementAndGet(); } @Platforms(InternalPlatform.NATIVE_ONLY.class) static int nextThreadNum() { - return JavaThreads.threadInitNumber.incrementAndGet(); + return JavaThreadNumberSingleton.singleton().threadInitNumber.incrementAndGet(); } /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreadsFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreadsFeature.java index 4503e4308685..c76db9be11cd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreadsFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreadsFeature.java @@ -34,12 +34,4 @@ protected static long threadId(Thread thread) { } return JavaThreads.getThreadId(thread); } - - protected static void setThreadSeqNumber(long num) { - JavaThreads.threadSeqNumber.set(num); - } - - protected static void setThreadInitNumber(int num) { - JavaThreads.threadInitNumber.set(num); - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java index f20227afe554..2d02205cc2ff 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java @@ -238,7 +238,8 @@ private Target_java_lang_Thread( @Substitute static String genThreadName() { - return "Thread-" + JavaThreads.threadInitNumber.incrementAndGet(); + int threadNum = JavaThreads.JavaThreadNumberSingleton.singleton().threadInitNumber.incrementAndGet(); + return "Thread-" + threadNum; } /** This constructor is called only by {@code VirtualThread}. */ @@ -583,7 +584,7 @@ final class Target_java_lang_Thread_FieldHolder { final class Target_java_lang_Thread_ThreadIdentifiers { @Substitute// static long next() { - return JavaThreads.threadSeqNumber.incrementAndGet(); + return JavaThreads.JavaThreadNumberSingleton.singleton().threadSeqNumber.incrementAndGet(); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageSingletonsSupportImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageSingletonsSupportImpl.java index 0d466847db9b..3c54460a3895 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageSingletonsSupportImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageSingletonsSupportImpl.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.hosted; +import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.Map; @@ -33,11 +34,14 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.impl.ImageSingletonsSupport; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.layeredimagesingleton.ApplicationLayerOnlyImageSingleton; import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton.PersistFlags; import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags; import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonSupport; import com.oracle.svm.core.layeredimagesingleton.LoadedLayeredImageSingletonInfo; +import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; import com.oracle.svm.core.layeredimagesingleton.RuntimeOnlyWrapper; import com.oracle.svm.core.util.UserError; import com.oracle.svm.hosted.heap.SVMImageLayerLoader; @@ -60,6 +64,16 @@ public T runtimeLookup(Class key) { return HostedManagement.getAndAssertExists().doLookup(key, true); } + @Override + public Collection> getMultiLayeredImageSingletonKeys() { + return HostedManagement.getAndAssertExists().getMultiLayeredImageSingletonKeys(); + } + + @Override + public void freezeMultiLayeredImageSingletons() { + HostedManagement.getAndAssertExists().freezeMultiLayeredImageSingletons(); + } + @Override public boolean contains(Class key) { HostedManagement hm = HostedManagement.get(); @@ -111,6 +125,15 @@ public static void install(HostedManagement vmConfig) { public static void install(HostedManagement vmConfig, HostedImageLayerBuildingSupport support) { UserError.guarantee(singletonDuringImageBuild == null, "Only one native image build can run at a time"); singletonDuringImageBuild = vmConfig; + + if (support != null) { + /* + * Note we are intentionally adding this singleton early as build flags may depend + * on it. We also intentionally do not mark this singleton as a LayerImageSingleton + * to prevent circular dependency complications. + */ + singletonDuringImageBuild.doAddInternal(ImageLayerBuildingSupport.class, support); + } if (support != null && support.getLoader() != null) { /* * Note eventually this may need to be moved to a later point after the Options @@ -142,13 +165,15 @@ public static void clear() { } public static void persist() { - var list = singletonDuringImageBuild.configObjects.entrySet().stream().filter(e -> e.getValue() instanceof LayeredImageSingleton).sorted(Comparator.comparing(e -> e.getKey().getName())) + var list = singletonDuringImageBuild.configObjects.entrySet().stream().filter(e -> e.getValue() instanceof LayeredImageSingleton || e.getValue() instanceof RuntimeOnlyWrapper) + .sorted(Comparator.comparing(e -> e.getKey().getName())) .toList(); HostedImageLayerBuildingSupport.singleton().getWriter().writeImageSingletonInfo(list); } private final Map, Object> configObjects; private final boolean checkUnsupported; + private Set> multiLayeredImageSingletonKeys; public HostedManagement() { this(false); @@ -156,6 +181,7 @@ public HostedManagement() { public HostedManagement(boolean checkUnsupported) { this.configObjects = new ConcurrentHashMap<>(); + this.multiLayeredImageSingletonKeys = ConcurrentHashMap.newKeySet(); this.checkUnsupported = checkUnsupported; } @@ -177,6 +203,25 @@ private void doAddInternal(Class key, Object value) { throw UserError.abort("Unsupported image singleton is being installed %s %s", key.getTypeName(), singleton); } + if (singleton instanceof MultiLayeredImageSingleton || singleton instanceof ApplicationLayerOnlyImageSingleton) { + + if (!key.equals(singleton.getClass())) { + throw UserError.abort("The implementation class must be the same as the key class. key: %s, singleton: %s", key, singleton); + } + + if (singleton instanceof MultiLayeredImageSingleton && singleton instanceof ApplicationLayerOnlyImageSingleton) { + throw UserError.abort("Singleton cannot implement both %s and %s. singleton: %s", MultiLayeredImageSingleton.class, ApplicationLayerOnlyImageSingleton.class, singleton); + } + + if (singleton instanceof ApplicationLayerOnlyImageSingleton && !ImageLayerBuildingSupport.lastImageBuild()) { + throw UserError.abort("Application layer only image singleton can only be installed in the final layer: %s", singleton); + } + + if (singleton instanceof MultiLayeredImageSingleton) { + multiLayeredImageSingletonKeys.add(key); + } + } + if (!singleton.getImageBuilderFlags().contains(LayeredImageSingletonBuilderFlags.BUILDTIME_ACCESS)) { storedValue = new RuntimeOnlyWrapper(singleton); } @@ -189,6 +234,14 @@ private void doAddInternal(Class key, Object value) { } } + Collection> getMultiLayeredImageSingletonKeys() { + return multiLayeredImageSingletonKeys; + } + + void freezeMultiLayeredImageSingletons() { + multiLayeredImageSingletonKeys = Set.copyOf(multiLayeredImageSingletonKeys); + } + T doLookup(Class key, boolean stripRuntimeOnly) { checkKey(key); Object result = configObjects.get(key); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index ac1eebb17350..ff8bc81cf50d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -536,7 +536,6 @@ public void run(Map entryPoints, imageLayerSupport); ImageSingletons.add(LayeredImageSingletonSupport.class, (LayeredImageSingletonSupport) ImageSingletonsSupportImpl.get()); - ImageSingletons.add(ImageLayerBuildingSupport.class, imageLayerSupport); ImageSingletons.add(ProgressReporter.class, reporter); ImageSingletons.add(DeadlockWatchdog.class, loader.watchdog); ImageSingletons.add(TimerCollection.class, timerCollection); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/flow/SVMMethodTypeFlowBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/flow/SVMMethodTypeFlowBuilder.java index 38bc4b175194..1318134cc529 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/flow/SVMMethodTypeFlowBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/flow/SVMMethodTypeFlowBuilder.java @@ -27,6 +27,7 @@ import com.oracle.graal.pointsto.AbstractAnalysisEngine; import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.flow.AllInstantiatedTypeFlow; import com.oracle.graal.pointsto.flow.MethodFlowsGraph; import com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder; import com.oracle.graal.pointsto.flow.TypeFlow; @@ -35,6 +36,7 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; import com.oracle.svm.core.graal.nodes.InlinedInvokeArgumentsNode; +import com.oracle.svm.core.graal.nodes.LoadImageSingletonNode; import com.oracle.svm.core.graal.thread.CompareAndSetVMThreadLocalNode; import com.oracle.svm.core.graal.thread.StoreVMThreadLocalNode; import com.oracle.svm.core.util.UserError.UserException; @@ -152,6 +154,9 @@ protected boolean delegateNodeProcessing(FixedNode n, TypeFlowsOfNodes state) { } else if (n instanceof InlinedInvokeArgumentsNode node) { processInlinedInvokeArgumentsNode(state, node); return true; + } else if (n instanceof LoadImageSingletonNode node) { + processLoadImageSingleton(state, node); + return true; } return super.delegateNodeProcessing(n, state); } @@ -183,6 +188,20 @@ private void processInlinedInvokeArgumentsNode(TypeFlowsOfNodes state, InlinedIn processMethodInvocation(state, node, invokeKind, targetMethod, node.getArguments(), true, getInvokePosition(node), true); } + private void processLoadImageSingleton(TypeFlowsOfNodes state, LoadImageSingletonNode node) { + /* + * When processing a load image singleton node, we do not know the exact constant that will + * be introduced in a later layer. Hence, we must represent this node via an + * AllTypesInstantiated flow for the returned type. + */ + AnalysisType singletonType = (AnalysisType) ((ObjectStamp) node.stamp(NodeView.DEFAULT)).type(); + var singletonTypeFlow = TypeFlowBuilder.create(bb, node, AllInstantiatedTypeFlow.class, () -> { + singletonType.registerAsInstantiated(AbstractAnalysisEngine.sourcePosition(node)); + return ((AllInstantiatedTypeFlow) singletonType.getTypeFlow(bb, false)); + }); + state.add(node, singletonTypeFlow); + } + @Override protected void processImplicitNonNull(ValueNode node, ValueNode source, TypeFlowsOfNodes state) { // GR-49362 - remove after improving non-runtime graphs 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 index 81d9c0a2df8d..23a8f53f71ed 100644 --- 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 @@ -298,6 +298,15 @@ public List readIntList(String keyName) { 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)); @@ -305,4 +314,12 @@ public String readString(String keyName) { 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)); + } } 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 index 073e9623fb7d..26afdb2117ec 100644 --- 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 @@ -57,6 +57,7 @@ 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; @@ -230,7 +231,12 @@ public void writeImageSingletonInfo(List, Object>> layeredIma Map singletonInfoMap = new HashMap<>(); int nextID = 1; for (var singletonInfo : layeredImageSingletons) { - LayeredImageSingleton singleton = (LayeredImageSingleton) singletonInfo.getValue(); + 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(); @@ -264,16 +270,31 @@ EconomicMap getKeyValueStore() { @Override public void writeInt(String keyName, int value) { - keyValueStore.put(keyName, List.of("I", value)); + var previous = keyValueStore.put(keyName, List.of("I", value)); + assert previous == null : previous; } @Override public void writeIntList(String keyName, List value) { - keyValueStore.put(keyName, List.of("I(", value)); + var previous = keyValueStore.put(keyName, List.of("I(", value)); + assert previous == null : previous; + } + + @Override + public void writeLong(String keyName, long value) { + var previous = keyValueStore.put(keyName, List.of("L", value)); + assert previous == null : previous; } @Override public void writeString(String keyName, String value) { - keyValueStore.put(keyName, List.of("S", value)); + var previous = keyValueStore.put(keyName, List.of("S", value)); + assert previous == null : previous; + } + + @Override + public void writeStringList(String keyName, List value) { + var previous = keyValueStore.put(keyName, List.of("S(", value)); + assert previous == null : previous; } } 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 06e1e07990b5..47dbc521a8ba 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 @@ -76,6 +76,7 @@ import com.oracle.svm.hosted.config.DynamicHubLayout; import com.oracle.svm.hosted.config.HybridLayout; import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; +import com.oracle.svm.hosted.imagelayer.LoadImageSingletonFeature; import com.oracle.svm.hosted.meta.HostedArrayClass; import com.oracle.svm.hosted.meta.HostedClass; import com.oracle.svm.hosted.meta.HostedConstantReflectionProvider; @@ -217,6 +218,10 @@ public void addInitialObjects() { addObjectsPhase.allow(); internStringsPhase.allow(); + if (ImageSingletons.contains(LoadImageSingletonFeature.class)) { + ImageSingletons.lookup(LoadImageSingletonFeature.class).addInitialObjects(this, hUniverse); + } + addStaticFields(); } 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 6bb1002c9812..97aecacfde27 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 @@ -27,8 +27,10 @@ import static org.graalvm.word.WordFactory.signed; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.EnumSet; import java.util.List; +import java.util.Map; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.type.WordPointer; @@ -53,10 +55,14 @@ import com.oracle.svm.core.layeredimagesingleton.FeatureSingleton; import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags; import com.oracle.svm.core.layeredimagesingleton.UnsavedSingleton; +import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.c.CGlobalDataFeature; import com.oracle.svm.hosted.code.ObjectFileTransformer; +import jdk.graal.compiler.core.common.CompressEncoding; +import jdk.graal.compiler.core.common.NumUtil; import jdk.vm.ci.code.Architecture; +import jdk.vm.ci.meta.JavaConstant; /** * Creates a section with image information specific to the current layer. @@ -74,11 +80,13 @@ * | 40 | heap writable begin | * | 48 | heap writable end | * | 56 | next layer section (0 if final layer) | + * | 64 | cross-layer singleton table start | * -------------------------------------------------------- * */ @AutomaticallyRegisteredFeature public final class ImageLayerSectionFeature implements InternalFeature, FeatureSingleton, UnsavedSingleton, ObjectFileTransformer { + private static final SectionName SVM_LAYER_SECTION = new SectionName.ProgbitsSectionName("svm_layer"); private static final int HEAP_BEGIN_OFFSET = 0; @@ -89,7 +97,7 @@ public final class ImageLayerSectionFeature implements InternalFeature, FeatureS private static final int HEAP_WRITABLE_BEGIN_OFFSET = 40; private static final int HEAP_WRITABLE_END_OFFSET = 48; private static final int NEXT_SECTION_OFFSET = 56; - private static final int SECTION_SIZE = 64; + private static final int STATIC_SECTION_SIZE = 64; 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"; @@ -103,7 +111,7 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { @Override public List> getRequiredFeatures() { - return List.of(HostedDynamicLayerInfoFeature.class); + return List.of(HostedDynamicLayerInfoFeature.class, LoadImageSingletonFeature.class); } @Override @@ -154,6 +162,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { } private ObjectFile.ProgbitsSectionImpl layeredSectionData; + private ByteBuffer layeredSectionDataByteBuffer; /** * Creates the SVM layer section and define all symbols within the section. Note it is necessary @@ -162,8 +171,9 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { */ @Override public void afterAbstractImageCreation(ObjectFile objectFile) { - byte[] sectionBytes = new byte[SECTION_SIZE]; - layeredSectionData = new BasicProgbitsSectionImpl(sectionBytes); + int numSingletonSlots = ImageSingletons.lookup(LoadImageSingletonFeature.class).getConstantToTableSlotMap().size(); + layeredSectionDataByteBuffer = ByteBuffer.wrap(new byte[STATIC_SECTION_SIZE + (ConfigurationValues.getObjectLayout().getReferenceSize() * numSingletonSlots)]).order(ByteOrder.LITTLE_ENDIAN); + layeredSectionData = new BasicProgbitsSectionImpl(layeredSectionDataByteBuffer.array()); // since relocations are present the section it is considered writable ObjectFile.Section layeredImageSection = objectFile.newProgbitsSection(SVM_LAYER_SECTION.getFormatDependentName(objectFile.getFormat()), objectFile.getPageSize(), true, false, @@ -184,6 +194,11 @@ public void afterAbstractImageCreation(ObjectFile objectFile) { // this symbol must be global when it will be read by the prior section objectFile.createDefinedSymbol(getLayerName(DynamicImageLayerInfo.singleton().layerNumber), layeredImageSection, 0, 0, false, ImageLayerBuildingSupport.buildingExtensionLayer()); + + if (numSingletonSlots != 0) { + assert ImageLayerBuildingSupport.buildingApplicationLayer() : "Currently only application layer is supported"; + objectFile.createDefinedSymbol(LoadImageSingletonFeature.CROSS_LAYER_SINGLETON_TABLE_SYMBOL, layeredImageSection, STATIC_SECTION_SIZE, 0, false, true); + } } @Override @@ -199,6 +214,28 @@ public void beforeImageWrite(BeforeImageWriteAccess access) { 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); + + var config = (FeatureImpl.BeforeImageWriteAccessImpl) access; + + Map singletonToSlotMap = ImageSingletons.lookup(LoadImageSingletonFeature.class).getConstantToTableSlotMap(); + long[] singletonTableInfo = new long[singletonToSlotMap.size()]; + int shift = ImageSingletons.lookup(CompressEncoding.class).getShift(); + for (var entry : singletonToSlotMap.entrySet()) { + var objectInfo = config.getImage().getHeap().getConstantInfo(entry.getKey()); + singletonTableInfo[entry.getValue()] = objectInfo.getOffset() >>> shift; + } + + int referenceSize = ConfigurationValues.getObjectLayout().getReferenceSize(); + int singletonTableOffset = STATIC_SECTION_SIZE; + for (long imageSingletonOffset : singletonTableInfo) { + if (referenceSize == 4) { + layeredSectionDataByteBuffer.putInt(singletonTableOffset, NumUtil.safeToInt(imageSingletonOffset)); + } else { + assert referenceSize == 8 : referenceSize; + layeredSectionDataByteBuffer.putLong(singletonTableOffset, imageSingletonOffset); + } + singletonTableOffset += referenceSize; + } } private static class ImageLayerSectionImpl extends ImageLayerSection implements UnsavedSingleton { 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 new file mode 100644 index 000000000000..a22aa7201a88 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadImageSingletonFeature.java @@ -0,0 +1,622 @@ +/* + * 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.LoadImageSingletonFeature.CROSS_LAYER_SINGLETON_TABLE_SYMBOL; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.word.Pointer; + +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.graal.pointsto.heap.ImageHeapObjectArray; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.core.ParsingReason; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.graal.code.CGlobalDataInfo; +import com.oracle.svm.core.graal.nodes.LoadImageSingletonNode; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.imagelayer.LoadImageSingletonFactory; +import com.oracle.svm.core.layeredimagesingleton.ApplicationLayerOnlyImageSingleton; +import com.oracle.svm.core.layeredimagesingleton.FeatureSingleton; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonSupport; +import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; +import com.oracle.svm.core.layeredimagesingleton.UnsavedSingleton; +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; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; +import jdk.graal.compiler.nodes.ConstantNode; +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.phases.util.Providers; +import jdk.vm.ci.code.BytecodeFrame; +import jdk.vm.ci.code.BytecodePosition; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +/** + * Tracks metdata {@link MultiLayeredImageSingleton} and {@link ApplicationLayerOnlyImageSingleton} + * singletons so that they can be properly referenced as needed. + */ +@AutomaticallyRegisteredFeature +public class LoadImageSingletonFeature implements InternalFeature, FeatureSingleton, UnsavedSingleton { + public static final String CROSS_LAYER_SINGLETON_TABLE_SYMBOL = "__layered_singleton_table_start"; + + private static CrossLayerSingletonMappingInfo getCrossLayerSingletonMappingInfo() { + return (CrossLayerSingletonMappingInfo) ImageSingletons.lookup(LoadImageSingletonFactory.class); + } + + /* + * Cache for objects created by the calls to getAllLayers within the application layer. + */ + private final Map, JavaConstant> keyToMultiLayerConstantMap = new ConcurrentHashMap<>(); + /* + * We need to cache this for the invocation plugin. + */ + private SVMImageLayerLoader loader; + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return ImageLayerBuildingSupport.buildingImageLayer(); + } + + @Override + public void registerInvocationPlugins(Providers providers, GraphBuilderConfiguration.Plugins plugins, ParsingReason reason) { + InvocationPlugins.Registration r = new InvocationPlugins.Registration(plugins.getInvocationPlugins(), MultiLayeredImageSingleton.class); + r.register(new InvocationPlugin.RequiredInvocationPlugin("getAllLayers", Class.class) { + + @Override + public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver unused, ValueNode classNode) { + + Class key = b.getSnippetReflection().asObject(Class.class, classNode.asJavaConstant()); + + if (ImageLayerBuildingSupport.buildingSharedLayer()) { + /* + * Load reference to the proper slot within the cross-layer singleton table. + */ + LoadImageSingletonNode layeredSingleton = LoadImageSingletonFactory.loadLayeredImageSingleton(key, b.getMetaAccess()); + b.addPush(JavaKind.Object, layeredSingleton); + return true; + } else { + /* + * Can directly load the array of all objects + */ + JavaConstant multiLayerArray = keyToMultiLayerConstantMap.computeIfAbsent(key, + k -> createMultiLayerArray(key, (AnalysisType) b.getMetaAccess().lookupJavaType(k.arrayType()), b.getSnippetReflection())); + var node = ConstantNode.forConstant(multiLayerArray, b.getMetaAccess()); + b.addPush(JavaKind.Object, node); + return true; + } + } + }); + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + var config = (FeatureImpl.BeforeAnalysisAccessImpl) access; + loader = (SVMImageLayerLoader) config.getUniverse().getImageLayerLoader(); + + LayeredImageSingletonSupport layeredImageSingletonSupport = LayeredImageSingletonSupport.singleton(); + layeredImageSingletonSupport.freezeMultiLayeredImageSingletons(); + + Consumer multiLayerEmbeddedRootsRegistration = (objArray) -> { + var method = config.getMetaAccess().lookupJavaMethod(ReflectionUtil.lookupMethod(MultiLayeredImageSingleton.class, "getAllLayers", Class.class)); + var javaConstant = config.getUniverse().getSnippetReflection().forObject(objArray); + config.getUniverse().registerEmbeddedRoot(javaConstant, new BytecodePosition(null, method, BytecodeFrame.UNKNOWN_BCI)); + }; + + if (ImageLayerBuildingSupport.buildingSharedLayer()) { + /* + * We must register all multi layered image singletons within shared layers as embedded. + * Even if they are not referred to in this layer, it is possible for them to be + * accessed via a multi-layer lookup in a subsequent layer, so they must be installed in + * the heap. + */ + Object[] multiLayeredSingletons = LayeredImageSingletonSupport.singleton().getMultiLayeredImageSingletonKeys().stream().map(layeredImageSingletonSupport::runtimeLookup).toArray(); + if (multiLayeredSingletons.length != 0) { + multiLayerEmbeddedRootsRegistration.accept(multiLayeredSingletons); + } + } + + if (ImageLayerBuildingSupport.buildingInitialLayer()) { + ImageSingletons.add(LoadImageSingletonFactory.class, new CrossLayerSingletonMappingInfo()); + + } else if (ImageLayerBuildingSupport.buildingApplicationLayer()) { + + ArrayList applicationLayerEmbeddedRoots = new ArrayList<>(); + ArrayList multiLayerEmbeddedRoots = new ArrayList<>(); + for (var slotInfo : getCrossLayerSingletonMappingInfo().getPriorKeyToSlotInfoMap().values()) { + switch (slotInfo.recordKind()) { + case APPLICATION_LAYER_SINGLETON -> { + /* + * We must register the current image application only singleton keys to + * ensure their types are part of the current analysis. + */ + Class key = slotInfo.keyClass(); + var singleton = layeredImageSingletonSupport.runtimeLookup(key); + assert singleton.getClass().equals(key) : String.format("We currently require %s to match their key. Key %s, Singleton: %s", ApplicationLayerOnlyImageSingleton.class, key, + singleton); + applicationLayerEmbeddedRoots.add(singleton); + } + case MULTI_LAYERED_SINGLETON -> { + /* + * Register multi-layer singletons of this layer if a getAllLayers call + * exists in a prior layer. + */ + Class key = slotInfo.keyClass(); + if (ImageSingletons.contains(key)) { + multiLayerEmbeddedRoots.add(layeredImageSingletonSupport.runtimeLookup(key)); + } + /* + * Within the application layer there will be an array created to hold all + * multi-layered image singletons. We must record this type is in the heap. + */ + config.registerAsInHeap(slotInfo.keyClass().arrayType()); + if (!getCrossLayerSingletonMappingInfo().getPriorLayerObjectIDs(slotInfo.keyClass()).isEmpty()) { + /* + * We also must ensure the type is registered as instantiated in this + * heap if we know the array will refer to a prior object. + */ + config.registerAsInHeap(slotInfo.keyClass()); + } + } + } + } + + if (!applicationLayerEmbeddedRoots.isEmpty()) { + var method = config.getMetaAccess().lookupJavaMethod(ReflectionUtil.lookupMethod(ImageSingletons.class, "lookup", Class.class)); + var javaConstant = config.getUniverse().getSnippetReflection().forObject(applicationLayerEmbeddedRoots.toArray()); + config.getUniverse().registerEmbeddedRoot(javaConstant, new BytecodePosition(null, method, BytecodeFrame.UNKNOWN_BCI)); + } + + if (!multiLayerEmbeddedRoots.isEmpty()) { + multiLayerEmbeddedRootsRegistration.accept(multiLayerEmbeddedRoots.toArray()); + } + } + } + + @Override + public void beforeCompilation(BeforeCompilationAccess access) { + var config = (FeatureImpl.BeforeCompilationAccessImpl) access; + getCrossLayerSingletonMappingInfo().assignSlots(config.getMetaAccess()); + } + + ImageHeapObjectArray createMultiLayerArray(Class key, AnalysisType arrayType, SnippetReflectionProvider snippetReflectionProvider) { + List priorIds = getCrossLayerSingletonMappingInfo().getPriorLayerObjectIDs(key); + Stream values = priorIds.stream().map(priorId -> loader.getOrCreateConstant(priorId)); + + if (ImageSingletons.contains(key)) { + var singleton = LayeredImageSingletonSupport.singleton().runtimeLookup(key); + JavaConstant singletonConstant = snippetReflectionProvider.forObject(singleton); + values = Stream.concat(values, Stream.of(singletonConstant)); + } + + Object[] elements = values.toArray(); + return ImageHeapObjectArray.createUnbackedImageHeapArray(arrayType, elements); + } + + /** + * Holds the values which need to be stored in the cross-layer singleton table. + */ + private Map constantToTableSlotMap; + + public Map getConstantToTableSlotMap() { + assert constantToTableSlotMap != null; + return constantToTableSlotMap; + } + + /** + * Ensure all objects needed for {@link MultiLayeredImageSingleton}s and + * {@link ApplicationLayerOnlyImageSingleton}s are installed in the heap. + */ + public void addInitialObjects(NativeImageHeap heap, HostedUniverse hUniverse) { + String addReason = "Read via the layered image singleton support"; + + /* + * We must add to the heap and record the id of all multilayered image singletons so that if + * needed a table can be created of all layer references later. + */ + LayeredImageSingletonSupport layeredImageSingletonSupport = LayeredImageSingletonSupport.singleton(); + for (var keyClass : layeredImageSingletonSupport.getMultiLayeredImageSingletonKeys()) { + var singleton = layeredImageSingletonSupport.runtimeLookup(keyClass); + ImageHeapConstant singletonConstant = (ImageHeapConstant) hUniverse.getSnippetReflection().forObject(singleton); + heap.addConstant(singletonConstant, false, addReason); + int id = ImageHeapConstant.getConstantID(singletonConstant); + + getCrossLayerSingletonMappingInfo().recordConstantID(keyClass, id); + } + + if (ImageLayerBuildingSupport.buildingApplicationLayer()) { + Map mappingInfo = new HashMap<>(); + for (var slotInfo : getCrossLayerSingletonMappingInfo().getCurrentKeyToSlotInfoMap().values()) { + + JavaConstant createdConstant = switch (slotInfo.recordKind()) { + case APPLICATION_LAYER_SINGLETON -> { + /* + * Need to install the singleton. + */ + var singleton = layeredImageSingletonSupport.runtimeLookup(slotInfo.keyClass()); + JavaConstant singletonConstant = hUniverse.getSnippetReflection().forObject(singleton); + heap.addConstant(singletonConstant, false, addReason); + + yield singletonConstant; + } + case MULTI_LAYERED_SINGLETON -> { + /* + * Check if we already created this object via an intrinsification. + */ + JavaConstant multiLayerArray = keyToMultiLayerConstantMap.get(slotInfo.keyClass()); + if (multiLayerArray == null) { + /* + * Need to install the array which points to all installed singletons. + */ + heap.hMetaAccess.lookupJavaType(slotInfo.keyClass()); + ImageHeapObjectArray imageHeapArray = createMultiLayerArray(slotInfo.keyClass(), heap.hMetaAccess.lookupJavaType(slotInfo.keyClass()).getWrapped(), + hUniverse.getSnippetReflection()); + + heap.addConstant(imageHeapArray, true, addReason); + + multiLayerArray = imageHeapArray; + } + + yield multiLayerArray; + } + }; + + var previous = mappingInfo.put(createdConstant, slotInfo.slotNum()); + assert previous == null : previous; + } + constantToTableSlotMap = Map.copyOf(mappingInfo); + + } else { + constantToTableSlotMap = Map.of(); + } + + } +} + +enum SlotRecordKind { + APPLICATION_LAYER_SINGLETON, + MULTI_LAYERED_SINGLETON + +} + +record SlotInfo(Class keyClass, + int slotNum, + SlotRecordKind recordKind) { + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + + SlotInfo slotInfo = (SlotInfo) object; + return slotNum == slotInfo.slotNum && Objects.equals(keyClass, slotInfo.keyClass) && recordKind == slotInfo.recordKind; + } + + @Override + public int hashCode() { + return Objects.hash(keyClass, slotNum, recordKind); + } +} + +class CrossLayerSingletonMappingInfo extends LoadImageSingletonFactory implements LayeredImageSingleton { + /** + * Map of slot infos created in prior layers. + */ + private final Map, SlotInfo> priorKeyToSlotInfoMap; + + /** + * Map of all MultiLayer objects created in prior layers. + */ + private final Map, List> priorKeyToSingletonObjectIDsMap; + + /** + * Map of all slot infos (past & present). Is created in {@link #assignSlots}. + */ + private Map, SlotInfo> currentKeyToSlotInfoMap; + + /** + * Map of constant identifiers for MultiLayer objects installed in this layer. + */ + private final Map, Integer> layerKeyToObjectIDMap = new HashMap<>(); + + /** + * Cache for created LoadImageSingletonDataImpl objects within the current layer. + */ + private final Map, LoadImageSingletonDataImpl> layerKeyToSingletonDataMap = new ConcurrentHashMap<>(); + + boolean sealedSingletonLookup = false; + private CGlobalData singletonTableStart; + int referenceSize = 0; + + @Override + public EnumSet getImageBuilderFlags() { + return LayeredImageSingletonBuilderFlags.BUILDTIME_ACCESS_ONLY; + } + + CrossLayerSingletonMappingInfo() { + priorKeyToSlotInfoMap = Map.of(); + priorKeyToSingletonObjectIDsMap = Map.of(); + } + + CrossLayerSingletonMappingInfo(Map, SlotInfo> priorKeyToSlotInfoMap, Map, List> priorKeyToSingletonObjectIDsMap) { + this.priorKeyToSlotInfoMap = priorKeyToSlotInfoMap; + this.priorKeyToSingletonObjectIDsMap = priorKeyToSingletonObjectIDsMap; + } + + void recordConstantID(Class keyClass, int objectID) { + var previous = layerKeyToObjectIDMap.put(keyClass, objectID); + assert previous == null : previous; + } + + Map, SlotInfo> getCurrentKeyToSlotInfoMap() { + assert currentKeyToSlotInfoMap != null; + return currentKeyToSlotInfoMap; + } + + Map, SlotInfo> getPriorKeyToSlotInfoMap() { + return priorKeyToSlotInfoMap; + } + + List getPriorLayerObjectIDs(Class keyClass) { + return priorKeyToSingletonObjectIDsMap.getOrDefault(keyClass, List.of()); + } + + private LoadImageSingletonData getImageSingletonInfo(Class keyClass, SlotRecordKind kind) { + assert !sealedSingletonLookup; + assert !ImageLayerBuildingSupport.buildingApplicationLayer() : "Singletons can always be directly folded in the application layer"; + + /* + * First check to see if something is already cached. + */ + LoadImageSingletonDataImpl result = layerKeyToSingletonDataMap.get(keyClass); + if (result != null) { + return result; + } + + LoadImageSingletonDataImpl newInfo = new LoadImageSingletonDataImpl(keyClass, kind); + result = layerKeyToSingletonDataMap.computeIfAbsent(keyClass, k -> newInfo); + if (result != newInfo) { + /* + * A different thread added this singleton in the meantime. + */ + return result; + } + + SlotInfo priorSlotInfo = priorKeyToSlotInfoMap.get(keyClass); + if (priorSlotInfo != null && priorSlotInfo.recordKind() != kind) { + VMError.shouldNotReachHere("A singleton cannot implement both %s and %s", priorSlotInfo.recordKind(), kind); + } + + return newInfo; + } + + @Override + protected LoadImageSingletonData getApplicationLayerOnlyImageSingletonInfo(Class keyClass) { + assert !ImageLayerBuildingSupport.buildingApplicationLayer() : "In the application layer one can directly load the constant"; + return getImageSingletonInfo(keyClass, SlotRecordKind.APPLICATION_LAYER_SINGLETON); + } + + @Override + protected LoadImageSingletonData getLayeredImageSingletonInfo(Class keyClass) { + return getImageSingletonInfo(keyClass, SlotRecordKind.MULTI_LAYERED_SINGLETON); + } + + void assignSlots(HostedMetaAccess metaAccess) { + sealedSingletonLookup = true; + + if (ImageLayerBuildingSupport.buildingSharedLayer()) { + // within the application layer we directly load the constant + referenceSize = ConfigurationValues.getObjectLayout().getReferenceSize(); + singletonTableStart = CGlobalDataFactory.forSymbol(CROSS_LAYER_SINGLETON_TABLE_SYMBOL); + } + + var priorMax = priorKeyToSlotInfoMap.values().stream().mapToInt(SlotInfo::slotNum).max(); + + int nextFreeSlot = priorMax.isPresent() ? priorMax.getAsInt() + 1 : 0; + currentKeyToSlotInfoMap = new HashMap<>(priorKeyToSlotInfoMap); + var sortedEntries = layerKeyToSingletonDataMap.entrySet().stream().sorted(Comparator.comparing(e -> e.getKey().getName())).toList(); + for (var entry : sortedEntries) { + int slotAssignment; + LoadImageSingletonDataImpl info = entry.getValue(); + var hType = metaAccess.lookupJavaType(info.getLoadType()); + if (hType.isInstantiated()) { + Class keyClass = entry.getKey(); + SlotInfo slotInfo = priorKeyToSlotInfoMap.get(entry.getKey()); + + if (slotInfo == null) { + /* + * Singleton was not assigned a slot in the prior layer. Need to assign + * singleton a slot now. + */ + slotAssignment = nextFreeSlot++; + slotInfo = new SlotInfo(keyClass, slotAssignment, info.kind); + } + var prior = currentKeyToSlotInfoMap.put(keyClass, slotInfo); + assert prior == null : prior; + } + } + } + + private static String getKeyClassName(Class clazz) { + return clazz.getName(); + } + + @Override + public PersistFlags preparePersist(ImageSingletonWriter writer) { + /* + * Write out all relevant information. + */ + List keyClasses = new ArrayList<>(); + List slotAssignments = new ArrayList<>(); + List slotKinds = new ArrayList<>(); + + /* + * Write out information about the assigned slots + */ + for (var info : currentKeyToSlotInfoMap.values()) { + String keyName = getKeyClassName(info.keyClass()); + + keyClasses.add(keyName); + slotAssignments.add(info.slotNum()); + slotKinds.add(info.recordKind().name()); + } + + writer.writeStringList("keyClasses", keyClasses); + writer.writeIntList("slotAssignments", slotAssignments); + writer.writeStringList("slotKinds", slotKinds); + + /* + * Write out all multi-layered image singletons seen. + */ + + Map, List> currentKeyToSingletonObjectIDsMap = new HashMap<>(priorKeyToSingletonObjectIDsMap); + for (var keyClass : LayeredImageSingletonSupport.singleton().getMultiLayeredImageSingletonKeys()) { + Integer id = layerKeyToObjectIDMap.get(keyClass); + assert id != null : "Missing multiLayerKey " + keyClass; + currentKeyToSingletonObjectIDsMap.compute(keyClass, (k, v) -> { + if (v == null) { + return List.of(id); + } else { + // don't want to affect the list created before + var newList = new ArrayList<>(v); + newList.add(id); + return newList; + } + }); + } + + List multiLayerKeyNames = new ArrayList<>(); + List multiLayerKeyClasses = new ArrayList<>(); + int count = 0; + for (var entry : currentKeyToSingletonObjectIDsMap.entrySet()) { + String keyClassName = getKeyClassName(entry.getKey()); + + String idListKey = String.format("priorObjectIds-%s", count++); + writer.writeIntList(idListKey, entry.getValue()); + + multiLayerKeyNames.add(idListKey); + multiLayerKeyClasses.add(keyClassName); + } + + writer.writeStringList("multiLayerClassNames", multiLayerKeyClasses); + writer.writeStringList("multiLayerKeyNames", multiLayerKeyNames); + + return PersistFlags.CREATE; + } + + @SuppressWarnings("unused") + public static Object createFromLoader(ImageSingletonLoader loader) { + Iterator keyClasses = loader.readStringList("keyClasses").iterator(); + Iterator slotAssignments = loader.readIntList("slotAssignments").iterator(); + Iterator slotKinds = loader.readStringList("slotKinds").iterator(); + + Map, SlotInfo> keyClassToSlotInfoMap = new HashMap<>(); + + while (keyClasses.hasNext()) { + String keyName = keyClasses.next(); + Class keyClass = ReflectionUtil.lookupClass(false, keyName); + int slotAssignment = slotAssignments.next(); + SlotRecordKind slotKind = SlotRecordKind.valueOf(slotKinds.next()); + + Object previous = keyClassToSlotInfoMap.put(keyClass, new SlotInfo(keyClass, slotAssignment, slotKind)); + assert previous == null : previous; + } + + Map, List> keyClassToObjectIDListMap = new HashMap<>(); + keyClasses = loader.readStringList("multiLayerClassNames").iterator(); + Iterator idKeyNames = loader.readStringList("multiLayerKeyNames").iterator(); + while (keyClasses.hasNext()) { + String keyClassName = keyClasses.next(); + Class keyClass = ReflectionUtil.lookupClass(false, keyClassName); + String idKeyName = idKeyNames.next(); + var list = loader.readIntList(idKeyName); + assert list != null; + Object previous = keyClassToObjectIDListMap.put(keyClass, list); + assert previous == null; + } + + return new CrossLayerSingletonMappingInfo(Map.copyOf(keyClassToSlotInfoMap), Map.copyOf(keyClassToObjectIDListMap)); + } + + class LoadImageSingletonDataImpl implements LoadImageSingletonData { + + private final Class key; + private final SlotRecordKind kind; + + LoadImageSingletonDataImpl(Class key, SlotRecordKind kind) { + this.key = key; + this.kind = kind; + } + + @Override + public Class getLoadType() { + return kind == SlotRecordKind.APPLICATION_LAYER_SINGLETON ? key : key.arrayType(); + } + + @Override + public SingletonAccessInfo getAccessInfo() { + assert singletonTableStart != null; + CGlobalDataInfo cglobal = CGlobalDataFeature.singleton().registerAsAccessedOrGet(singletonTableStart); + int slotNum = currentKeyToSlotInfoMap.get(key).slotNum(); + return new SingletonAccessInfo(cglobal, slotNum * referenceSize); + } + } +} 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 0ea475c0ec83..63a4c33e97fa 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 @@ -85,7 +85,10 @@ import com.oracle.svm.core.heap.ReferenceAccessImpl; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.identityhashcode.SubstrateIdentityHashCodeNode; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.imagelayer.LoadImageSingletonFactory; import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; +import com.oracle.svm.core.layeredimagesingleton.ApplicationLayerOnlyImageSingleton; import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags; import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonSupport; @@ -1130,18 +1133,28 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec @Override public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver unused, ValueNode classNode) { Class key = constantObjectParameter(b, targetMethod, 0, Class.class, classNode); - Object result = LayeredImageSingletonSupport.singleton().runtimeLookup(key); - if (result instanceof LayeredImageSingleton layeredSingleton && !layeredSingleton.getImageBuilderFlags().contains(LayeredImageSingletonBuilderFlags.RUNTIME_ACCESS)) { + + if (ApplicationLayerOnlyImageSingleton.class.isAssignableFrom(key) && ImageLayerBuildingSupport.buildingSharedLayer()) { + /* + * This singleton is only installed in the application layer heap. All other + * layers looks refer to this singleton. + */ + b.addPush(JavaKind.Object, LoadImageSingletonFactory.loadApplicationOnlyImageSingleton(key, b.getMetaAccess())); + return true; + } + + Object singleton = LayeredImageSingletonSupport.singleton().runtimeLookup(key); + if (singleton instanceof LayeredImageSingleton layeredSingleton && !layeredSingleton.getImageBuilderFlags().contains(LayeredImageSingletonBuilderFlags.RUNTIME_ACCESS)) { /* * Runtime compilation installs many singletons into the image which are * otherwise hosted only. Note the platform checks still apply and can be used * to ensure certain singleton are not installed into the image. */ if (!RuntimeCompilation.isEnabled()) { - throw b.bailout("Layered image singleton without runtime access is in runtime graph: " + result); + throw b.bailout("Layered image singleton without runtime access is in runtime graph: " + singleton); } } - b.addPush(JavaKind.Object, ConstantNode.forConstant(b.getSnippetReflection().forObject(result), b.getMetaAccess())); + b.addPush(JavaKind.Object, ConstantNode.forConstant(b.getSnippetReflection().forObject(singleton), b.getMetaAccess())); return true; } }); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/HostedJavaThreadsFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/HostedJavaThreadsFeature.java index 17da926c95d4..a2011cf5210d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/HostedJavaThreadsFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/HostedJavaThreadsFeature.java @@ -25,20 +25,26 @@ package com.oracle.svm.hosted.thread; import java.util.Arrays; +import java.util.EnumSet; import java.util.Map; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags; +import com.oracle.svm.core.thread.JavaThreads; import com.oracle.svm.core.thread.JavaThreadsFeature; import com.oracle.svm.core.thread.PlatformThreads; import com.oracle.svm.core.util.ConcurrentIdentityHashMap; import com.oracle.svm.core.util.UserError; import com.oracle.svm.hosted.FeatureImpl; -import com.oracle.svm.hosted.FeatureImpl.AfterAnalysisAccessImpl; import com.oracle.svm.util.ClassUtil; import com.oracle.svm.util.ReflectionUtil; @@ -164,7 +170,6 @@ public void duringAnalysis(DuringAnalysisAccess access) { @Override public void afterAnalysis(AfterAnalysisAccess a) { - AfterAnalysisAccessImpl access = (AfterAnalysisAccessImpl) a; /* * No more changes to the reachable threads and thread groups are allowed after the * analysis. @@ -178,16 +183,27 @@ public void afterAnalysis(AfterAnalysisAccess a) { * id or autonumber of a thread that the user created during image generation would be an * intrusion with unpredictable side effects. */ - long maxThreadId = 0; - int maxAutonumber = -1; + long maxThreadId = HostedJavaThreadsMetadata.singleton().maxThreadId; + int maxAutonumber = HostedJavaThreadsMetadata.singleton().maxAutonumber; for (Thread thread : reachableThreads.keySet()) { maxThreadId = Math.max(maxThreadId, threadId(thread)); maxAutonumber = Math.max(maxAutonumber, autonumberOf(thread)); } - // GR-52413: load maxThreadId from base layer - assert access.getBigBang().getHostVM().useBaseLayer() || maxThreadId >= 1 : "main thread with id 1 must always be found"; - setThreadSeqNumber(maxThreadId); - setThreadInitNumber(maxAutonumber); + + assert maxThreadId >= 1 : "main thread with id 1 must always be found"; + + if (ImageLayerBuildingSupport.buildingSharedLayer()) { + /* + * Update max numbers seen + */ + HostedJavaThreadsMetadata.singleton().maxThreadId = maxThreadId; + HostedJavaThreadsMetadata.singleton().maxAutonumber = maxAutonumber; + } else { + /* + * Store the values within the application layer singleton. + */ + JavaThreads.JavaThreadNumberSingleton.singleton().setThreadNumberInfo(maxThreadId, maxAutonumber); + } } private static final String AUTONUMBER_PREFIX = "Thread-"; @@ -207,7 +223,6 @@ static int autonumberOf(Thread thread) { } } -@Platforms(Platform.HOSTED_ONLY.class) class ReachableThreadGroup { int ngroups; ThreadGroup[] groups; @@ -223,3 +238,43 @@ synchronized void add(ThreadGroup g) { ngroups++; } } + +@AutomaticallyRegisteredImageSingleton +class HostedJavaThreadsMetadata implements LayeredImageSingleton { + long maxThreadId; + int maxAutonumber; + + HostedJavaThreadsMetadata() { + this.maxThreadId = 0; + this.maxAutonumber = -1; + } + + static HostedJavaThreadsMetadata singleton() { + return ImageSingletons.lookup(HostedJavaThreadsMetadata.class); + } + + private HostedJavaThreadsMetadata(long maxThreadId, int maxAutonumber) { + this.maxThreadId = maxThreadId; + this.maxAutonumber = maxAutonumber; + } + + @Override + public EnumSet getImageBuilderFlags() { + return LayeredImageSingletonBuilderFlags.BUILDTIME_ACCESS_ONLY; + } + + @Override + public PersistFlags preparePersist(ImageSingletonWriter writer) { + writer.writeLong("maxThreadId", maxThreadId); + writer.writeInt("maxAutonumber", maxAutonumber); + return PersistFlags.CREATE; + } + + @SuppressWarnings("unused") + public static Object createFromLoader(ImageSingletonLoader loader) { + long maxThreadId = loader.readLong("maxThreadId"); + int maxAutonumber = loader.readInt("maxAutonumber"); + + return new HostedJavaThreadsMetadata(maxThreadId, maxAutonumber); + } +}