diff --git a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/NumUtil.java b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/NumUtil.java index 0384e195ff41..15b935770a25 100644 --- a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/NumUtil.java +++ b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/NumUtil.java @@ -126,6 +126,11 @@ public static short safeToShort(int v) { return (short) v; } + public static int safeToUInt(long v) { + assert isUInt(v); + return (int) v; + } + public static int safeToInt(long v) { assert isInt(v); return (int) v; diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java index 653da0c3dddc..fc8419d5a49a 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java @@ -90,6 +90,7 @@ import com.oracle.svm.core.thread.PlatformThreads; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VMThreads; +import com.oracle.svm.core.threadlocal.VMThreadLocalMTSupport; import com.oracle.svm.core.util.TimeUtils; import com.oracle.svm.core.util.VMError; @@ -179,7 +180,6 @@ boolean collectWithoutAllocating(GCCause cause, boolean forceFullGC) { int size = SizeOf.get(CollectionVMOperationData.class); CollectionVMOperationData data = StackValue.get(size); UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); - data.setNativeVMOperation(collectOperation); data.setCauseId(cause.getId()); data.setRequestingEpoch(getCollectionEpoch()); data.setRequestingNanoTime(System.nanoTime()); @@ -947,7 +947,9 @@ private void walkThreadLocals() { if (SubstrateOptions.MultiThreaded.getValue()) { Timer walkThreadLocalsTimer = timers.walkThreadLocals.open(); try { - ThreadLocalMTWalker.walk(greyToBlackObjRefVisitor); + for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { + VMThreadLocalMTSupport.singleton().walk(isolateThread, greyToBlackObjRefVisitor); + } } finally { walkThreadLocalsTimer.close(); } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java index 0fb8fecdbf65..c25fd61ea8ac 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java @@ -457,6 +457,7 @@ public boolean walkImageHeapObjects(ObjectVisitor visitor) { @Override public boolean walkCollectedHeapObjects(ObjectVisitor visitor) { VMOperation.guaranteeInProgressAtSafepoint("Must only be called at a safepoint"); + ThreadLocalAllocation.disableAndFlushForAllThreads(); return getYoungGeneration().walkObjects(visitor) && getOldGeneration().walkObjects(visitor); } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/RuntimeCodeCacheWalker.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/RuntimeCodeCacheWalker.java index 5440ef78c39d..2ae2bbacc974 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/RuntimeCodeCacheWalker.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/RuntimeCodeCacheWalker.java @@ -29,13 +29,13 @@ import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.SubstrateGCOptions; -import com.oracle.svm.core.util.DuplicatedInNativeCode; import com.oracle.svm.core.code.CodeInfo; import com.oracle.svm.core.code.CodeInfoAccess; import com.oracle.svm.core.code.RuntimeCodeCache.CodeInfoVisitor; import com.oracle.svm.core.code.RuntimeCodeInfoAccess; import com.oracle.svm.core.code.UntetheredCodeInfoAccess; import com.oracle.svm.core.heap.ObjectReferenceVisitor; +import com.oracle.svm.core.util.DuplicatedInNativeCode; /** * References from the runtime compiled code to the Java heap must be considered either strong or @@ -58,7 +58,7 @@ final class RuntimeCodeCacheWalker implements CodeInfoVisitor { @Override @DuplicatedInNativeCode - public boolean visitCode(T codeInfo) { + public boolean visitCode(CodeInfo codeInfo) { if (RuntimeCodeInfoAccess.areAllObjectsOnImageHeap(codeInfo)) { return true; } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalMTWalker.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalMTWalker.java deleted file mode 100644 index 122336af1c18..000000000000 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalMTWalker.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.core.genscavenge; - -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.IsolateThread; -import org.graalvm.word.Pointer; - -import com.oracle.svm.core.c.NonmovableArray; -import com.oracle.svm.core.c.NonmovableArrays; -import com.oracle.svm.core.heap.GC; -import com.oracle.svm.core.heap.InstanceReferenceMapDecoder; -import com.oracle.svm.core.heap.ObjectReferenceVisitor; -import com.oracle.svm.core.thread.VMThreads; -import com.oracle.svm.core.threadlocal.VMThreadLocalMTSupport; - -/** - * The class is registered with the {@link GC} to process VM thread local variables of type - * {@link Object}. - */ -final class ThreadLocalMTWalker { - static void walk(ObjectReferenceVisitor referenceVisitor) { - VMThreadLocalMTSupport threadLocals = ImageSingletons.lookup(VMThreadLocalMTSupport.class); - NonmovableArray threadRefMapEncoding = NonmovableArrays.fromImageHeap(threadLocals.vmThreadReferenceMapEncoding); - for (IsolateThread vmThread = VMThreads.firstThread(); vmThread.isNonNull(); vmThread = VMThreads.nextThread(vmThread)) { - InstanceReferenceMapDecoder.walkOffsetsFromPointer((Pointer) vmThread, threadRefMapEncoding, - threadLocals.vmThreadReferenceMapIndex, referenceVisitor, null); - } - } -} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java index fe39bc674048..7fe1c3aae1f1 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java @@ -55,6 +55,16 @@ public PosixRawFileOperationSupport(boolean useNativeByteOrder) { super(useNativeByteOrder); } + @Override + public RawFileDescriptor create(File file, FileCreationMode creationMode, FileAccessMode accessMode) { + String path = file.getPath(); + int flags = parseMode(creationMode) | parseMode(accessMode); + + try (CTypeConversion.CCharPointerHolder cPath = CTypeConversion.toCString(path)) { + return WordFactory.signed(Fcntl.NoTransitions.open(cPath.get(), flags, DEFAULT_PERMISSIONS)); + } + } + @Override public RawFileDescriptor open(File file, FileAccessMode mode) { String path = file.getPath(); @@ -83,24 +93,24 @@ public boolean close(RawFileDescriptor fd) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override - public SignedWord size(RawFileDescriptor fd) { + public long size(RawFileDescriptor fd) { int posixFd = getPosixFileDescriptor(fd); return PosixStat.getSize(posixFd); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override - public SignedWord position(RawFileDescriptor fd) { + public long position(RawFileDescriptor fd) { int posixFd = getPosixFileDescriptor(fd); - return Unistd.NoTransitions.lseek(posixFd, WordFactory.signed(0), Unistd.SEEK_CUR()); + return Unistd.NoTransitions.lseek(posixFd, WordFactory.signed(0), Unistd.SEEK_CUR()).rawValue(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override - public boolean seek(RawFileDescriptor fd, SignedWord position) { + public boolean seek(RawFileDescriptor fd, long position) { int posixFd = getPosixFileDescriptor(fd); - SignedWord newPos = Unistd.NoTransitions.lseek(posixFd, position, Unistd.SEEK_SET()); - return position.equal(newPos); + SignedWord newPos = Unistd.NoTransitions.lseek(posixFd, WordFactory.signed(position), Unistd.SEEK_SET()); + return position == newPos.rawValue(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -127,7 +137,7 @@ public boolean write(RawFileDescriptor fd, Pointer data, UnsignedWord size) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override - public SignedWord read(RawFileDescriptor fd, Pointer buffer, UnsignedWord bufferSize) { + public long read(RawFileDescriptor fd, Pointer buffer, UnsignedWord bufferSize) { int posixFd = getPosixFileDescriptor(fd); SignedWord readBytes; @@ -135,7 +145,7 @@ public SignedWord read(RawFileDescriptor fd, Pointer buffer, UnsignedWord buffer readBytes = Unistd.NoTransitions.read(posixFd, buffer, bufferSize); } while (readBytes.equal(-1) && LibC.errno() == Errno.EINTR()); - return readBytes; + return readBytes.rawValue(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -145,14 +155,25 @@ private static int getPosixFileDescriptor(RawFileDescriptor fd) { return result; } + private static int parseMode(FileCreationMode mode) { + switch (mode) { + case CREATE: + return Fcntl.O_CREAT() | Fcntl.O_EXCL(); + case CREATE_OR_REPLACE: + return Fcntl.O_CREAT() | Fcntl.O_TRUNC(); + default: + throw VMError.shouldNotReachHere(); + } + } + private static int parseMode(FileAccessMode mode) { switch (mode) { case READ: return Fcntl.O_RDONLY(); case READ_WRITE: - return Fcntl.O_RDWR() | Fcntl.O_CREAT(); + return Fcntl.O_RDWR(); case WRITE: - return Fcntl.O_WRONLY() | Fcntl.O_CREAT(); + return Fcntl.O_WRONLY(); default: throw VMError.shouldNotReachHere(); } @@ -162,14 +183,14 @@ private static int parseMode(FileAccessMode mode) { @AutomaticallyRegisteredFeature class PosixRawFileOperationFeature implements InternalFeature { @Override - public void beforeAnalysis(BeforeAnalysisAccess access) { + public void afterRegistration(AfterRegistrationAccess access) { ByteOrder nativeByteOrder = ByteOrder.nativeOrder(); assert nativeByteOrder == ByteOrder.LITTLE_ENDIAN || nativeByteOrder == ByteOrder.BIG_ENDIAN; - PosixRawFileOperationSupport littleEndianSupport = new PosixRawFileOperationSupport(ByteOrder.LITTLE_ENDIAN == nativeByteOrder); - PosixRawFileOperationSupport bigEndianSupport = new PosixRawFileOperationSupport(ByteOrder.BIG_ENDIAN == nativeByteOrder); - PosixRawFileOperationSupport nativeByteOrderSupport = nativeByteOrder == ByteOrder.LITTLE_ENDIAN ? littleEndianSupport : bigEndianSupport; + PosixRawFileOperationSupport littleEndian = new PosixRawFileOperationSupport(ByteOrder.LITTLE_ENDIAN == nativeByteOrder); + PosixRawFileOperationSupport bigEndian = new PosixRawFileOperationSupport(ByteOrder.BIG_ENDIAN == nativeByteOrder); + PosixRawFileOperationSupport nativeOrder = nativeByteOrder == ByteOrder.LITTLE_ENDIAN ? littleEndian : bigEndian; - ImageSingletons.add(RawFileOperationSupportHolder.class, new RawFileOperationSupportHolder(littleEndianSupport, bigEndianSupport, nativeByteOrderSupport)); + ImageSingletons.add(RawFileOperationSupportHolder.class, new RawFileOperationSupportHolder(littleEndian, bigEndian, nativeOrder)); } } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixStat.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixStat.java index 8ad151c02cbc..5120ba660ef4 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixStat.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixStat.java @@ -26,8 +26,6 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.SignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; @@ -54,7 +52,7 @@ public static boolean isOpen(int fd) { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static SignedWord getSize(int fd) { + public static long getSize(int fd) { long size = -1; if (Platform.includedIn(Platform.LINUX.class)) { LinuxStat.stat64 stat = UnsafeStackValue.get(LinuxStat.stat64.class); @@ -67,7 +65,7 @@ public static SignedWord getSize(int fd) { size = stat.st_size(); } } - return WordFactory.signed(size); + return size; } @Platforms(Platform.HOSTED_ONLY.class) diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Fcntl.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Fcntl.java index b140697794ab..10ce569238f5 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Fcntl.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Fcntl.java @@ -50,6 +50,12 @@ public class Fcntl { @CConstant public static native int O_CREAT(); + @CConstant + public static native int O_TRUNC(); + + @CConstant + public static native int O_EXCL(); + public static class NoTransitions { @CFunction(value = "openSII", transition = Transition.NO_TRANSITION) public static native int open(CCharPointer pathname, int flags, int mode); diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/heapdump/AllocationFreeFileOutputStreamPosix.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/heapdump/AllocationFreeFileOutputStreamPosix.java index a21e6ece449d..64298d6cc20e 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/heapdump/AllocationFreeFileOutputStreamPosix.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/heapdump/AllocationFreeFileOutputStreamPosix.java @@ -41,6 +41,8 @@ import com.oracle.svm.core.util.VMError; /** + * Legacy implementation, only used by other legacy code (see GR-44538). + * * Posix implementation of allocation-free output stream created from FileOutputStream. * * The limitation to Linux and Darwin is necessary because the implementation currently uses diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpHeapOnSignalFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpHeapOnSignalFeature.java index 9125f8f32f45..0eb2753f1445 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpHeapOnSignalFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpHeapOnSignalFeature.java @@ -33,10 +33,10 @@ import org.graalvm.nativeimage.ProcessProperties; import org.graalvm.nativeimage.VMRuntime; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.jdk.RuntimeSupport; import com.oracle.svm.core.log.Log; -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import sun.misc.Signal; import sun.misc.SignalHandler; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java index b02fb7c24b59..e05ac0ac79c1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java @@ -142,6 +142,15 @@ private static int runCore() { */ private static int runCore0() { try { + if (SubstrateOptions.ParseRuntimeOptions.getValue()) { + /* + * When options are not parsed yet, it is also too early to run the startup hooks + * because they often depend on option values. The user is expected to manually run + * the startup hooks after setting all option values. + */ + VMRuntime.initialize(); + } + if (SubstrateOptions.DumpHeapAndExit.getValue()) { if (VMInspectionOptions.hasHeapDumpSupport()) { String absoluteHeapDumpPath = SubstrateOptions.getHeapDumpPath(SubstrateOptions.Name.getValue() + ".hprof"); @@ -149,20 +158,12 @@ private static int runCore0() { System.out.println("Heap dump created at '" + absoluteHeapDumpPath + "'."); return 0; } else { - System.err.println("Unable to dump heap. Heap dumping is only supported for native executables built with `" + VMInspectionOptions.getHeapdumpsCommandArgument() + "`."); + System.err.println("Unable to dump heap. Heap dumping is only supported on Linux and MacOS for native executables built with `" + + VMInspectionOptions.getHeapdumpsCommandArgument() + "`."); return 1; } } - if (SubstrateOptions.ParseRuntimeOptions.getValue()) { - /* - * When options are not parsed yet, it is also too early to run the startup hooks - * because they often depend on option values. The user is expected to manually run - * the startup hooks after setting all option values. - */ - VMRuntime.initialize(); - } - ThreadListenerSupport.get().beforeThreadRun(); // Ensure that native code using JNI_GetCreatedJavaVMs finds this isolate. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java index c6dd49651f4d..a249ed5fcbf6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java @@ -35,6 +35,7 @@ import org.graalvm.compiler.options.OptionType; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platform.WINDOWS; +import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.option.APIOption; import com.oracle.svm.core.option.HostedOptionKey; @@ -61,6 +62,7 @@ public final class VMInspectionOptions { public static final HostedOptionKey EnableMonitoringFeatures = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter(), VMInspectionOptions::validateEnableMonitoringFeatures); + @Platforms(Platform.HOSTED_ONLY.class) public static void validateEnableMonitoringFeatures(@SuppressWarnings("unused") OptionKey optionKey) { Set enabledFeatures = getEnabledMonitoringFeatures(); if (enabledFeatures.contains(MONITORING_DEFAULT_NAME)) { @@ -77,6 +79,7 @@ public static void validateEnableMonitoringFeatures(@SuppressWarnings("unused") } } + @Platforms(Platform.HOSTED_ONLY.class) private static String getDefaultMonitoringCommandArgument() { return SubstrateOptionsParser.commandArgument(EnableMonitoringFeatures, MONITORING_DEFAULT_NAME); } @@ -86,7 +89,7 @@ public static String getHeapdumpsCommandArgument() { return SubstrateOptionsParser.commandArgument(EnableMonitoringFeatures, MONITORING_HEAPDUMP_NAME); } - public static Set getEnabledMonitoringFeatures() { + private static Set getEnabledMonitoringFeatures() { return new HashSet<>(EnableMonitoringFeatures.getValue().values()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeCache.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeCache.java index 761b404a6c7d..6b3ca74d0e8c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeCache.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeCache.java @@ -40,16 +40,16 @@ import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; +import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; -import com.oracle.svm.core.NeverInline; -import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.NonmovableArray; import com.oracle.svm.core.c.NonmovableArrays; import com.oracle.svm.core.deopt.DeoptimizedFrame; import com.oracle.svm.core.deopt.Deoptimizer; import com.oracle.svm.core.deopt.SubstrateInstalledCode; +import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.option.RuntimeOptionKey; import com.oracle.svm.core.stack.JavaStackWalker; import com.oracle.svm.core.stack.StackFrameVisitor; @@ -322,6 +322,6 @@ public interface CodeInfoVisitor { * continue, else false. */ @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while visiting code.") - boolean visitCode(T codeInfo); + boolean visitCode(CodeInfo codeInfo); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java index 3529dd3e1665..332d10d9eb59 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java @@ -201,7 +201,7 @@ private void rehashAfterUnregisterAt(int index) { // from IdentityHashMap: Knuth } } - public boolean walkRuntimeMethodsDuringGC(CodeInfoVisitor visitor) { + public void walkRuntimeMethodsDuringGC(CodeInfoVisitor visitor) { assert VMOperation.isGCInProgress() : "otherwise, we would need to make sure that the CodeInfo is not freeded by the GC"; if (table.isNonNull()) { int length = NonmovableArrays.lengthOf(table); @@ -221,22 +221,30 @@ public boolean walkRuntimeMethodsDuringGC(CodeInfoVisitor visitor) { } } } - return true; } - @Uninterruptible(reason = "Must prevent the GC from freeing the CodeInfo object.") - public boolean walkRuntimeMethodsUninterruptibly(CodeInfoVisitor visitor) { + @Uninterruptible(reason = "Prevent the GC from freeing the CodeInfo object until it is tethered.") + public void walkRuntimeMethods(CodeInfoVisitor visitor) { if (table.isNonNull()) { int length = NonmovableArrays.lengthOf(table); - for (int i = 0; i < length;) { + for (int i = 0; i < length; i++) { UntetheredCodeInfo info = NonmovableArrays.getWord(table, i); if (info.isNonNull()) { - visitor.visitCode(CodeInfoAccess.convert(info)); + Object tether = CodeInfoAccess.acquireTether(info); + try { + callVisitor(visitor, info, tether); + } finally { + CodeInfoAccess.releaseTether(info, tether); + } + assert info == NonmovableArrays.getWord(table, i); } - assert info == NonmovableArrays.getWord(table, i); } } - return true; + } + + @Uninterruptible(reason = "Call the visitor, which may execute interruptible code.", calleeMustBe = false) + private static void callVisitor(CodeInfoVisitor visitor, UntetheredCodeInfo info, Object tether) { + visitor.visitCode(CodeInfoAccess.convert(info, tether)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArray.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArray.java new file mode 100644 index 000000000000..a9fc178e9b17 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArray.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.collections; + +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.word.PointerBase; + +/** + * Growable array with word-sized elements. The {@link #getData() array elements} are allocated on + * the C heap. The functions in {@link GrowableWordArrayAccess} should be used to access and modify + * this data structure. + */ +@RawStructure +public interface GrowableWordArray extends PointerBase { + @RawField + int getSize(); + + @RawField + void setSize(int value); + + @RawField + int getCapacity(); + + @RawField + void setCapacity(int value); + + @RawField + WordPointer getData(); + + @RawField + void setData(WordPointer value); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArrayAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArrayAccess.java new file mode 100644 index 000000000000..f1a190f7fcc1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArrayAccess.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.collections; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.compiler.word.Word; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; +import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.UnmanagedMemoryUtil; +import com.oracle.svm.core.config.ConfigurationValues; + +public class GrowableWordArrayAccess { + private static final int INITIAL_CAPACITY = 10; + + public static void initialize(GrowableWordArray array) { + array.setSize(0); + array.setCapacity(0); + array.setData(WordFactory.nullPointer()); + } + + public static Word get(GrowableWordArray array, int i) { + assert i >= 0 && i < array.getSize(); + return array.getData().addressOf(i).read(); + } + + public static boolean add(GrowableWordArray array, Word element) { + if (array.getSize() == array.getCapacity() && !grow(array)) { + return false; + } + + array.getData().addressOf(array.getSize()).write(element); + array.setSize(array.getSize() + 1); + return true; + } + + public static void freeData(GrowableWordArray array) { + if (array.isNonNull()) { + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(array.getData()); + array.setData(WordFactory.nullPointer()); + array.setSize(0); + array.setCapacity(0); + } + } + + private static boolean grow(GrowableWordArray array) { + int newCapacity = computeNewCapacity(array); + if (newCapacity < 0) { + /* Overflow. */ + return false; + } + + assert newCapacity >= INITIAL_CAPACITY; + WordPointer oldData = array.getData(); + WordPointer newData = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(WordFactory.unsigned(newCapacity).multiply(wordSize())); + if (newData.isNull()) { + return false; + } + + UnmanagedMemoryUtil.copyForward((Pointer) oldData, (Pointer) newData, WordFactory.unsigned(array.getSize()).multiply(wordSize())); + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(oldData); + + array.setData(newData); + array.setCapacity(newCapacity); + return true; + } + + private static int computeNewCapacity(GrowableWordArray array) { + int oldCapacity = array.getCapacity(); + if (oldCapacity == 0) { + return INITIAL_CAPACITY; + } else { + return oldCapacity * 2; + } + } + + @Fold + static int wordSize() { + return ConfigurationValues.getTarget().wordSize; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/AbstractMemoryMXBean.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/AbstractMemoryMXBean.java index f067c5c3ecc7..0e2d5e97fd33 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/AbstractMemoryMXBean.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/AbstractMemoryMXBean.java @@ -68,7 +68,7 @@ public int getObjectPendingFinalizationCount() { @Override public MemoryUsage getNonHeapMemoryUsage() { codeInfoVisitor.reset(); - RuntimeCodeInfoMemory.singleton().walkRuntimeMethodsUninterruptibly(codeInfoVisitor); + RuntimeCodeInfoMemory.singleton().walkRuntimeMethods(codeInfoVisitor); long used = codeInfoVisitor.getRuntimeCodeInfoSize().rawValue(); return new MemoryUsage(UNDEFINED_MEMORY_USAGE, used, used, UNDEFINED_MEMORY_USAGE); } @@ -104,9 +104,9 @@ public void reset() { runtimeCodeInfoSize = WordFactory.zero(); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override - public boolean visitCode(T codeInfo) { + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean visitCode(CodeInfo codeInfo) { runtimeCodeInfoSize = runtimeCodeInfoSize.add(CodeInfoAccess.getCodeAndDataMemorySize(codeInfo)).add(CodeInfoAccess.getNativeMetadataSize(codeInfo)); return true; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/RuntimeCodeCacheCleaner.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/RuntimeCodeCacheCleaner.java index 00b782993543..af6ca78c2898 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/RuntimeCodeCacheCleaner.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/RuntimeCodeCacheCleaner.java @@ -31,13 +31,12 @@ import com.oracle.svm.core.code.CodeInfoAccess; import com.oracle.svm.core.code.CodeInfoTable; import com.oracle.svm.core.code.RuntimeCodeCache.CodeInfoVisitor; -import com.oracle.svm.core.graal.meta.SharedRuntimeMethod; - -import jdk.vm.ci.meta.SpeculationLog.SpeculationReason; - import com.oracle.svm.core.code.RuntimeCodeInfoAccess; import com.oracle.svm.core.code.RuntimeCodeInfoHistory; import com.oracle.svm.core.code.RuntimeCodeInfoMemory; +import com.oracle.svm.core.graal.meta.SharedRuntimeMethod; + +import jdk.vm.ci.meta.SpeculationLog.SpeculationReason; /** * Cleans the code cache and frees the unmanaged memory that is used by {@link CodeInfo} objects. @@ -62,7 +61,7 @@ public RuntimeCodeCacheCleaner() { } @Override - public boolean visitCode(T codeInfo) { + public boolean visitCode(CodeInfo codeInfo) { if (RuntimeCodeInfoAccess.areAllObjectsOnImageHeap(codeInfo)) { return true; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfSubRecord.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfSubRecord.java new file mode 100644 index 000000000000..ae9714454e3f --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfSubRecord.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.heap.dump; + +import org.graalvm.compiler.core.common.NumUtil; + +/* Enum of all relevant HPROF sub-records (see enum hprofTag in HotSpot). */ +public enum HProfSubRecord { + GC_ROOT_UNKNOWN(0xFF), + GC_ROOT_JNI_GLOBAL(0x01), + GC_ROOT_JNI_LOCAL(0x02), + GC_ROOT_JAVA_FRAME(0x03), + GC_ROOT_NATIVE_STACK(0x04), + GC_ROOT_STICKY_CLASS(0x05), + GC_ROOT_THREAD_BLOCK(0x06), + GC_ROOT_MONITOR_USED(0x07), + GC_ROOT_THREAD_OBJ(0x08), + GC_CLASS_DUMP(0x20), + GC_INSTANCE_DUMP(0x21), + GC_OBJ_ARRAY_DUMP(0x22), + GC_PRIM_ARRAY_DUMP(0x23); + + private final byte value; + + HProfSubRecord(int value) { + this.value = NumUtil.safeToUByte(value); + } + + public byte getValue() { + return value; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfTopLevelRecord.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfTopLevelRecord.java new file mode 100644 index 000000000000..09a737071269 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfTopLevelRecord.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.heap.dump; + +import org.graalvm.compiler.core.common.NumUtil; + +/* Enum of all relevant HPROF top-level records (see enum hprofTag in HotSpot). */ +public enum HProfTopLevelRecord { + UTF8(0x01), + LOAD_CLASS(0x02), + UNLOAD_CLASS(0x03), + FRAME(0x04), + TRACE(0x05), + ALLOC_SITES(0x06), + HEAP_SUMMARY(0x07), + START_THREAD(0x0A), + END_THREAD(0x0B), + HEAP_DUMP(0x0C), + CPU_SAMPLES(0x0D), + CONTROL_SETTINGS(0x0E), + HEAP_DUMP_SEGMENT(0x1C), + HEAP_DUMP_END(0x2C); + + private final byte value; + + HProfTopLevelRecord(int value) { + this.value = NumUtil.safeToUByte(value); + } + + public byte getValue() { + return value; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfType.java new file mode 100644 index 000000000000..3f0e870d94cc --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfType.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.heap.dump; + +import org.graalvm.compiler.core.common.NumUtil; + +import com.oracle.svm.core.config.ConfigurationValues; + +/* Enum of all relevant HPROF types (see enum hprofTag in HotSpot). */ +public enum HProfType { + NORMAL_OBJECT(0x2, 0), + BOOLEAN(0x4, 1), + CHAR(0x5, 2), + FLOAT(0x6, 4), + DOUBLE(0x7, 8), + BYTE(0x8, 1), + SHORT(0x9, 2), + INT(0xA, 4), + LONG(0xB, 8); + + private static final HProfType[] TYPES = HProfType.values(); + + private final byte value; + private final int size; + + HProfType(int value, int size) { + this.value = NumUtil.safeToUByte(value); + this.size = size; + } + + public static HProfType get(byte value) { + return TYPES[value]; + } + + public byte getValue() { + return value; + } + + public int getSize() { + if (size == 0) { + return ConfigurationValues.getTarget().wordSize; + } + return size; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java new file mode 100644 index 000000000000..32fef73016e2 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.heap.dump; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawPointerTo; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; +import org.graalvm.word.Pointer; +import org.graalvm.word.PointerBase; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.c.NonmovableArrays; +import com.oracle.svm.core.c.struct.PinnedObjectField; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.heap.ObjectVisitor; +import com.oracle.svm.core.heap.UnknownObjectField; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.util.coder.ByteStream; +import com.oracle.svm.core.util.coder.ByteStreamAccess; +import com.oracle.svm.core.util.coder.NativeCoder; +import com.oracle.svm.core.util.coder.Pack200Coder; + +/** + * Provides access to the encoded heap dump metadata that was prepared at image build-time. + * + *
+ * |----------------------------|
+ * | metadata byte[]            |
+ * |----------------------------|
+ * | s4 totalFieldCount         |
+ * | s4 classCount              |
+ * | s4 fieldNameCount          |
+ * | uv maxTypeId               |
+ * | (class information)*       |
+ * | (field names)*             |
+ * |----------------------------|
+ *
+ * |----------------------------|
+ * | information per class      |
+ * |----------------------------|
+ * | uv typeId                  |
+ * | uv instanceFieldCount      |
+ * | uv staticFieldCount        |
+ * | (instance field)*          |
+ * | (static field)*            |
+ * |----------------------------|
+ *
+ * |----------------------------|
+ * | information per field      |
+ * |----------------------------|
+ * | u1 type             |
+ * | uv fieldNameIndex          |
+ * | uv location                |
+ * |----------------------------|
+ *
+ * |----------------------------|
+ * | information per field name |
+ * |----------------------------|
+ * | uv lengthInBytes           |
+ * | (s1 utf8 character)*       |
+ * |----------------------------|
+ * 
+ */ +public class HeapDumpMetadata { + private final ComputeHubDataVisitor computeHubDataVisitor; + @UnknownObjectField(types = {byte[].class}) private byte[] data; + + private int fieldNameCount; + private int classInfoCount; + private ClassInfo classInfos; + private FieldInfoPointer fieldInfoTable; + private FieldNamePointer fieldNameTable; + + @Platforms(Platform.HOSTED_ONLY.class) + public HeapDumpMetadata() { + computeHubDataVisitor = new ComputeHubDataVisitor(); + } + + @Fold + public static HeapDumpMetadata singleton() { + return ImageSingletons.lookup(HeapDumpMetadata.class); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setData(byte[] value) { + this.data = value; + } + + public boolean initialize() { + assert classInfos.isNull() && fieldInfoTable.isNull() && fieldNameTable.isNull(); + + Pointer start = NonmovableArrays.getArrayBase(NonmovableArrays.fromImageHeap(data)); + Pointer end = start.add(data.length); + + ByteStream stream = StackValue.get(ByteStream.class); + ByteStreamAccess.initialize(stream, start); + + /* Read the header. */ + int totalFieldCount = NativeCoder.readInt(stream); + int classCount = NativeCoder.readInt(stream); + fieldNameCount = NativeCoder.readInt(stream); + int maxTypeId = Pack200Coder.readUVAsInt(stream); + classInfoCount = maxTypeId + 1; + + /* + * Precompute a few small data structures so that the heap dumping code can access the + * encoded data more efficiently. At first, we allocate some memory for those data + * structures so that we can abort right away in case that an allocation fails. + */ + UnsignedWord classInfosSize = WordFactory.unsigned(classInfoCount).multiply(SizeOf.get(ClassInfo.class)); + classInfos = ImageSingletons.lookup(UnmanagedMemorySupport.class).calloc(classInfosSize); + if (classInfos.isNull()) { + return false; + } + + UnsignedWord fieldStartsSize = WordFactory.unsigned(totalFieldCount).multiply(SizeOf.get(FieldInfoPointer.class)); + fieldInfoTable = ImageSingletons.lookup(UnmanagedMemorySupport.class).calloc(fieldStartsSize); + if (fieldInfoTable.isNull()) { + return false; + } + + UnsignedWord fieldNameTableSize = WordFactory.unsigned(fieldNameCount).multiply(SizeOf.get(FieldNamePointer.class)); + fieldNameTable = ImageSingletons.lookup(UnmanagedMemorySupport.class).calloc(fieldNameTableSize); + if (fieldNameTable.isNull()) { + return false; + } + + /* Read the classes and fields. */ + int fieldIndex = 0; + for (int i = 0; i < classCount; i++) { + int typeId = Pack200Coder.readUVAsInt(stream); + + ClassInfo classInfo = getClassInfo(typeId); + + int numInstanceFields = Pack200Coder.readUVAsInt(stream); + classInfo.setInstanceFieldCount(numInstanceFields); + + int numStaticFields = Pack200Coder.readUVAsInt(stream); + classInfo.setStaticFieldCount(numStaticFields); + + classInfo.setInstanceFields(fieldInfoTable.addressOf(fieldIndex)); + for (int j = 0; j < numInstanceFields; j++) { + Pointer fieldInfo = (Pointer) fieldInfoTable.addressOf(fieldIndex); + fieldInfo.writeWord(0, stream.getPosition()); + FieldInfoAccess.skipFieldInfo(stream); + fieldIndex++; + } + + classInfo.setStaticFields(fieldInfoTable.addressOf(fieldIndex)); + for (int j = 0; j < numStaticFields; j++) { + Pointer fieldInfo = (Pointer) fieldInfoTable.addressOf(fieldIndex); + fieldInfo.writeWord(0, stream.getPosition()); + FieldInfoAccess.skipFieldInfo(stream); + fieldIndex++; + } + } + + /* Fill the symbol table. */ + for (int i = 0; i < fieldNameCount; i++) { + Pointer fieldName = (Pointer) fieldNameTable.addressOf(i); + fieldName.writeWord(0, stream.getPosition()); + int length = Pack200Coder.readUVAsInt(stream); + stream.setPosition(stream.getPosition().add(length)); + } + assert stream.getPosition().equal(end); + + /* Store the DynamicHubs in their corresponding ClassInfo structs. */ + computeHubDataVisitor.initialize(); + Heap.getHeap().walkImageHeapObjects(computeHubDataVisitor); + + /* Compute the size of the instance fields per class. */ + for (int i = 0; i < classInfoCount; i++) { + ClassInfo classInfo = getClassInfo(i); + if (ClassInfoAccess.isValid(classInfo)) { + computeInstanceFieldsDumpSize(classInfo); + } + } + return true; + } + + /** + * Must always be called, regardless if {@link #initialize} returned true or false. + */ + public void teardown() { + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(classInfos); + classInfos = WordFactory.nullPointer(); + + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(fieldInfoTable); + fieldInfoTable = WordFactory.nullPointer(); + + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(fieldNameTable); + fieldNameTable = WordFactory.nullPointer(); + } + + public int getClassInfoCount() { + return classInfoCount; + } + + public ClassInfo getClassInfo(Class clazz) { + if (clazz == null) { + return WordFactory.nullPointer(); + } + return getClassInfo(DynamicHub.fromClass(clazz)); + } + + public ClassInfo getClassInfo(DynamicHub hub) { + if (hub == null) { + return WordFactory.nullPointer(); + } + return getClassInfo(hub.getTypeID()); + } + + public ClassInfo getClassInfo(int typeId) { + return classInfos.addressOf(typeId); + } + + public int getFieldNameCount() { + return fieldNameCount; + } + + public FieldName getFieldName(int index) { + return fieldNameTable.addressOf(index).read(); + } + + /** + * Get the size in bytes that is required for the fields of the given class when an object of + * that class is written to the heap dump file. Note that this is not the same as the object + * size in the heap. The size in the heap will include the object header, padding/alignment, and + * the size of object references may be different. + */ + private int computeInstanceFieldsDumpSize(ClassInfo classInfo) { + /* Check if this class was already processed earlier. */ + if (classInfo.getInstanceFieldsDumpSize() != -1) { + return classInfo.getInstanceFieldsDumpSize(); + } + + /* Compute the size of the immediate fields. */ + int result = computeFieldsDumpSize(classInfo.getInstanceFields(), classInfo.getInstanceFieldCount()); + + /* Add the size of all inherited fields. */ + DynamicHub superHub = classInfo.getHub().getSuperHub(); + if (superHub != null) { + result += computeInstanceFieldsDumpSize(getClassInfo(superHub)); + } + classInfo.setInstanceFieldsDumpSize(result); + return result; + } + + static int computeFieldsDumpSize(FieldInfoPointer fields, int fieldCount) { + int result = 0; + for (int i = 0; i < fieldCount; i++) { + FieldInfo field = fields.addressOf(i).read(); + HProfType type = FieldInfoAccess.getType(field); + result += type.getSize(); + } + return result; + } + + @RawStructure + public interface ClassInfo extends PointerBase { + @RawField + @PinnedObjectField + DynamicHub getHub(); + + @RawField + @PinnedObjectField + void setHub(DynamicHub value); + + @RawField + int getSerialNum(); + + @RawField + void setSerialNum(int value); + + @RawField + int getInstanceFieldsDumpSize(); + + @RawField + void setInstanceFieldsDumpSize(int value); + + @RawField + int getInstanceFieldCount(); + + @RawField + void setInstanceFieldCount(int value); + + @RawField + FieldInfoPointer getInstanceFields(); + + @RawField + void setInstanceFields(FieldInfoPointer value); + + @RawField + int getStaticFieldCount(); + + @RawField + void setStaticFieldCount(int value); + + @RawField + FieldInfoPointer getStaticFields(); + + @RawField + void setStaticFields(FieldInfoPointer value); + + ClassInfo addressOf(int index); + } + + public static class ClassInfoAccess { + /** + * When iterating over all {@link ClassInfo} objects, it is possible to encounter invalid + * {@link ClassInfo} objects because {@link DynamicHub#getTypeID()} is not necessarily + * continuous (i.e., there may be more type ids than {@link DynamicHub}s). This method can + * be used to determine if a {@link ClassInfo} object is valid. + */ + static boolean isValid(ClassInfo classInfo) { + return classInfo.getHub() != null; + } + } + + /** + * Data structure has a variable size. + */ + @RawStructure + public interface FieldInfo extends PointerBase { + // u1 type + // uv fieldNameIndex + // uv location + } + + public static class FieldInfoAccess { + static HProfType getType(FieldInfo field) { + return HProfType.get(((Pointer) field).readByte(0)); + } + + static FieldName getFieldName(FieldInfo field) { + int fieldNameIndex = Pack200Coder.readUVAsInt(getFieldNameIndexAddress(field)); + return HeapDumpMetadata.singleton().getFieldName(fieldNameIndex); + } + + static int getLocation(FieldInfo field) { + Pointer fieldNameIndex = getFieldNameIndexAddress(field); // skip type + ByteStream stream = StackValue.get(ByteStream.class); + ByteStreamAccess.initialize(stream, fieldNameIndex); + Pack200Coder.readUVAsInt(stream); // skip field name index + return Pack200Coder.readUVAsInt(stream); + } + + static void skipFieldInfo(ByteStream stream) { + NativeCoder.readByte(stream); // type + Pack200Coder.readUVAsInt(stream); // field name index + Pack200Coder.readUVAsInt(stream); // location + } + + private static Pointer getFieldNameIndexAddress(FieldInfo field) { + return ((Pointer) field).add(1); + } + } + + @RawPointerTo(FieldInfo.class) + public interface FieldInfoPointer extends PointerBase { + FieldInfoPointer addressOf(int index); + + FieldInfo read(); + } + + /** + * Data structure has a variable size. + */ + @RawStructure + public interface FieldName extends PointerBase { + // uv lengthInBytes + // (s1 utf8 character)* + } + + public static class FieldNameAccess { + static int getLength(FieldName fieldName) { + return Pack200Coder.readUVAsInt((Pointer) fieldName); + } + + static CCharPointer getChars(FieldName fieldName) { + ByteStream data = StackValue.get(ByteStream.class); + ByteStreamAccess.initialize(data, (Pointer) fieldName); + Pack200Coder.readUV(data); // skip length + return (CCharPointer) data.getPosition(); + } + } + + @RawPointerTo(FieldName.class) + public interface FieldNamePointer extends PointerBase { + FieldNamePointer addressOf(int index); + + FieldName read(); + } + + private static class ComputeHubDataVisitor implements ObjectVisitor { + private int classSerialNum; + + public void initialize() { + this.classSerialNum = 0; + } + + @Override + public boolean visitObject(Object o) { + if (o instanceof DynamicHub hub) { + ClassInfo classInfo = HeapDumpMetadata.singleton().getClassInfo(hub.getTypeID()); + assert classInfo.getHub() == null; + classInfo.setHub(hub); + classInfo.setSerialNum(++classSerialNum); + classInfo.setInstanceFieldsDumpSize(-1); + } + return true; + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java new file mode 100644 index 000000000000..33201d0cc54d --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.heap.dump; + +import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION; +import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.UNRESTRICTED; + +import java.io.IOException; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.impl.HeapDumpSupport; +import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.UnmanagedMemoryUtil; +import com.oracle.svm.core.heap.RestrictHeapAccess; +import com.oracle.svm.core.heap.VMOperationInfos; +import com.oracle.svm.core.log.Log; +import com.oracle.svm.core.os.RawFileOperationSupport; +import com.oracle.svm.core.os.RawFileOperationSupport.FileCreationMode; +import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor; +import com.oracle.svm.core.thread.NativeVMOperation; +import com.oracle.svm.core.thread.NativeVMOperationData; +import com.oracle.svm.core.thread.VMOperation; + +public class HeapDumpSupportImpl implements HeapDumpSupport { + private final HeapDumpWriter writer; + private final HeapDumpOperation heapDumpOperation; + + @Platforms(Platform.HOSTED_ONLY.class) + public HeapDumpSupportImpl(HeapDumpMetadata metadata) { + this.writer = new HeapDumpWriter(metadata); + this.heapDumpOperation = new HeapDumpOperation(); + } + + @Override + public void dumpHeap(String filename, boolean gcBefore) throws IOException { + RawFileDescriptor fd = getFileSupport().create(filename, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE); + if (!getFileSupport().isValid(fd)) { + throw new IOException("Could not create the heap dump file: " + filename); + } + + try { + writeHeapTo(fd, gcBefore); + } finally { + getFileSupport().close(fd); + } + } + + public void writeHeapTo(RawFileDescriptor fd, boolean gcBefore) throws IOException { + int size = SizeOf.get(HeapDumpVMOperationData.class); + HeapDumpVMOperationData data = StackValue.get(size); + UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); + + data.setGCBefore(gcBefore); + data.setRawFileDescriptor(fd); + heapDumpOperation.enqueue(data); + + if (!data.getSuccess()) { + throw new IOException("An error occurred while writing the heap dump."); + } + } + + @Fold + static RawFileOperationSupport getFileSupport() { + return RawFileOperationSupport.bigEndian(); + } + + @RawStructure + private interface HeapDumpVMOperationData extends NativeVMOperationData { + @RawField + boolean getGCBefore(); + + @RawField + void setGCBefore(boolean value); + + @RawField + RawFileDescriptor getRawFileDescriptor(); + + @RawField + void setRawFileDescriptor(RawFileDescriptor fd); + + @RawField + boolean getSuccess(); + + @RawField + void setSuccess(boolean value); + } + + private class HeapDumpOperation extends NativeVMOperation { + @Platforms(Platform.HOSTED_ONLY.class) + HeapDumpOperation() { + super(VMOperationInfos.get(HeapDumpOperation.class, "Write heap dump", VMOperation.SystemEffect.SAFEPOINT)); + } + + @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") + protected void operate(NativeVMOperationData d) { + HeapDumpVMOperationData data = (HeapDumpVMOperationData) d; + if (data.getGCBefore()) { + System.gc(); + } + + try { + boolean success = writer.dumpHeap(data.getRawFileDescriptor()); + data.setSuccess(success); + } catch (Throwable e) { + reportError(e); + } + } + + @RestrictHeapAccess(access = UNRESTRICTED, reason = "Error reporting may allocate.") + private void reportError(Throwable e) { + Log.log().string("An exception occurred during heap dumping. The data in the heap dump file may be corrupt.").newline().string(e.getClass().getName()).string(": ") + .string(e.getMessage()); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java new file mode 100644 index 000000000000..fed71c167c92 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java @@ -0,0 +1,1413 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.heap.dump; + +import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.compiler.core.common.NumUtil; +import org.graalvm.compiler.nodes.java.ArrayLengthNode; +import org.graalvm.compiler.word.ObjectAccess; +import org.graalvm.compiler.word.Word; +import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.function.CodePointer; +import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordBase; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.StaticFieldsSupport; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.c.NonmovableArray; +import com.oracle.svm.core.code.CodeInfo; +import com.oracle.svm.core.code.CodeInfoAccess; +import com.oracle.svm.core.code.CodeInfoDecoder; +import com.oracle.svm.core.code.CodeInfoTable; +import com.oracle.svm.core.code.FrameInfoQueryResult; +import com.oracle.svm.core.code.RuntimeCodeCache; +import com.oracle.svm.core.code.RuntimeCodeInfoAccess; +import com.oracle.svm.core.code.RuntimeCodeInfoMemory; +import com.oracle.svm.core.code.SimpleCodeInfoQueryResult; +import com.oracle.svm.core.collections.GrowableWordArray; +import com.oracle.svm.core.collections.GrowableWordArrayAccess; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.deopt.DeoptimizedFrame; +import com.oracle.svm.core.heap.CodeReferenceMapDecoder; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.heap.NoAllocationVerifier; +import com.oracle.svm.core.heap.ObjectReferenceVisitor; +import com.oracle.svm.core.heap.ObjectVisitor; +import com.oracle.svm.core.heap.ReferenceAccess; +import com.oracle.svm.core.heap.ReferenceMapIndex; +import com.oracle.svm.core.heap.RestrictHeapAccess; +import com.oracle.svm.core.heap.dump.HeapDumpMetadata.ClassInfo; +import com.oracle.svm.core.heap.dump.HeapDumpMetadata.ClassInfoAccess; +import com.oracle.svm.core.heap.dump.HeapDumpMetadata.FieldInfo; +import com.oracle.svm.core.heap.dump.HeapDumpMetadata.FieldInfoAccess; +import com.oracle.svm.core.heap.dump.HeapDumpMetadata.FieldInfoPointer; +import com.oracle.svm.core.heap.dump.HeapDumpMetadata.FieldName; +import com.oracle.svm.core.heap.dump.HeapDumpMetadata.FieldNameAccess; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.LayoutEncoding; +import com.oracle.svm.core.log.Log; +import com.oracle.svm.core.os.BufferedFileOperationSupport; +import com.oracle.svm.core.os.BufferedFileOperationSupport.BufferedFile; +import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor; +import com.oracle.svm.core.snippets.KnownIntrinsics; +import com.oracle.svm.core.stack.JavaStackWalker; +import com.oracle.svm.core.stack.StackFrameVisitor; +import com.oracle.svm.core.thread.PlatformThreads; +import com.oracle.svm.core.thread.ThreadingSupportImpl; +import com.oracle.svm.core.thread.VMOperation; +import com.oracle.svm.core.thread.VMThreads; +import com.oracle.svm.core.threadlocal.VMThreadLocalMTSupport; +import com.oracle.svm.core.util.VMError; + +/** + * This class dumps the image heap and the Java heap into a file (HPROF binary format), similar to + * the HotSpot classes in {@code heapDumper.cpp}. The heap dumping needs additional metadata that is + * encoded during the image build and that is stored in the image heap in a compact binary format + * (see {@code HeapDumpFeature} and {@link HeapDumpMetadata}). + * + * The actual heap dumping logic is executed in a VM operation, so it is safe to iterate over all + * live threads. The heap dumper is implemented as a singleton and only a single heap dumping + * operation can be in progress at a given time. + * + * The heap dumping code must not modify the Java heap in any way as this would pollute or corrupt + * the heap dump. Therefore, no Java datastructures can be used and all data structures must be + * allocated using native memory instead. This also guarantees that the heap dumping works in case + * that Native Image is completely out of Java heap memory (see option + * {@code -XX:HeapDumpOnOutOfMemoryError}). + * + * The heap dump is stored in the HPROF binary format (big endian byte order). The high-level + * structure of the file is roughly as follows: + * + *
+ *   file header
+ *   ([top level record] [sub record]*)+
+ *   end marker
+ * 
+ * + * Other relevant file format aspects: + *
    + *
  • References to objects or symbols are encoded using their word-sized address. This is done + * regardless if compressed references are enabled or not. Tools (such as VisualVM) that read HPROF + * files, therefore use heuristics to detect if compressed references were used at run-time.
  • + *
  • Symbols such as class or method names are encoded as UTF8.
  • + *
  • The size of individual records is limited to {@link #MAX_UNSIGNED_INT}. So, very large arrays + * that have a larger size than {@link #MAX_UNSIGNED_INT} need to be truncated.
  • + *
+ * + * HPROF file format in detail (mostly copied from {@code heapDumper.cpp}): + * + *
+ * HPROF binary format:
+ *
+ * header     "JAVA PROFILE 1.0.2" (0-terminated)
+ *
+ * u4         size of identifiers. Identifiers are used to represent
+ *            UTF8 strings, objects, stack traces, etc. They usually
+ *            have the same size as host pointers.
+ * u4         high word
+ * u4         low word    number of milliseconds since 0:00 GMT, 1/1/70
+ * [record]*  a sequence of records.
+ *
+ *
+ * Record format:
+ *
+ * u1         a TAG denoting the type of the record
+ * u4         number of *microseconds* since the time stamp in the
+ *            header. (wraps around in a little more than an hour)
+ * u4         number of bytes *remaining* in the record. Note that
+ *            this number excludes the tag and the length field itself.
+ * [u1]*      BODY of the record (a sequence of bytes)
+ *
+ *
+ * The following TAGs are supported:
+ *
+ * TAG           BODY       notes
+ *----------------------------------------------------------
+ * HPROF_UTF8               a UTF8-encoded name
+ *
+ *               id         name ID
+ *               [u1]*      UTF8 characters (no trailing zero)
+ *
+ * HPROF_LOAD_CLASS         a newly loaded class
+ *
+ *                u4        class serial number (> 0)
+ *                id        class object ID
+ *                u4        stack trace serial number
+ *                id        class name ID
+ *
+ * HPROF_UNLOAD_CLASS       an unloading class
+ *
+ *                u4        class serial_number
+ *
+ * HPROF_FRAME              a Java stack frame
+ *
+ *                id        stack frame ID
+ *                id        method name ID
+ *                id        method signature ID
+ *                id        source file name ID
+ *                u4        class serial number
+ *                i4        line number. >0: normal
+ *                                       -1: unknown
+ *                                       -2: compiled method
+ *                                       -3: native method
+ *
+ * HPROF_TRACE              a Java stack trace
+ *
+ *               u4         stack trace serial number
+ *               u4         thread serial number
+ *               u4         number of frames
+ *               [id]*      stack frame IDs
+ *
+ *
+ * HPROF_ALLOC_SITES        a set of heap allocation sites, obtained after GC
+ *
+ *               u2         flags 0x0001: incremental vs. complete
+ *                                0x0002: sorted by allocation vs. live
+ *                                0x0004: whether to force a GC
+ *               u4         cutoff ratio
+ *               u4         total live bytes
+ *               u4         total live instances
+ *               u8         total bytes allocated
+ *               u8         total instances allocated
+ *               u4         number of sites that follow
+ *               [u1        is_array: 0:  normal object
+ *                                    2:  object array
+ *                                    4:  boolean array
+ *                                    5:  char array
+ *                                    6:  float array
+ *                                    7:  double array
+ *                                    8:  byte array
+ *                                    9:  short array
+ *                                    10: int array
+ *                                    11: long array
+ *                u4        class serial number (may be zero during startup)
+ *                u4        stack trace serial number
+ *                u4        number of bytes alive
+ *                u4        number of instances alive
+ *                u4        number of bytes allocated
+ *                u4]*      number of instance allocated
+ *
+ * HPROF_START_THREAD       a newly started thread.
+ *
+ *               u4         thread serial number (> 0)
+ *               id         thread object ID
+ *               u4         stack trace serial number
+ *               id         thread name ID
+ *               id         thread group name ID
+ *               id         thread group parent name ID
+ *
+ * HPROF_END_THREAD         a terminating thread.
+ *
+ *               u4         thread serial number
+ *
+ * HPROF_HEAP_SUMMARY       heap summary
+ *
+ *               u4         total live bytes
+ *               u4         total live instances
+ *               u8         total bytes allocated
+ *               u8         total instances allocated
+ *
+ * HPROF_HEAP_DUMP          denote a heap dump
+ *
+ *               [heap dump sub-records]*
+ *
+ *                          There are four kinds of heap dump sub-records:
+ *
+ *               u1         sub-record type
+ *
+ *               HPROF_GC_ROOT_UNKNOWN         unknown root
+ *
+ *                          id         object ID
+ *
+ *               HPROF_GC_ROOT_THREAD_OBJ      thread object
+ *
+ *                          id         thread object ID  (may be 0 for a
+ *                                     thread newly attached through JNI)
+ *                          u4         thread sequence number
+ *                          u4         stack trace sequence number
+ *
+ *               HPROF_GC_ROOT_JNI_GLOBAL      JNI global ref root
+ *
+ *                          id         object ID
+ *                          id         JNI global ref ID
+ *
+ *               HPROF_GC_ROOT_JNI_LOCAL       JNI local ref
+ *
+ *                          id         object ID
+ *                          u4         thread serial number
+ *                          u4         frame # in stack trace (-1 for empty)
+ *
+ *               HPROF_GC_ROOT_JAVA_FRAME      Java stack frame
+ *
+ *                          id         object ID
+ *                          u4         thread serial number
+ *                          u4         frame # in stack trace (-1 for empty)
+ *
+ *               HPROF_GC_ROOT_NATIVE_STACK    Native stack
+ *
+ *                          id         object ID
+ *                          u4         thread serial number
+ *
+ *               HPROF_GC_ROOT_STICKY_CLASS    System class
+ *
+ *                          id         object ID
+ *
+ *               HPROF_GC_ROOT_THREAD_BLOCK    Reference from thread block
+ *
+ *                          id         object ID
+ *                          u4         thread serial number
+ *
+ *               HPROF_GC_ROOT_MONITOR_USED    Busy monitor
+ *
+ *                          id         object ID
+ *
+ *               HPROF_GC_CLASS_DUMP           dump of a class object
+ *
+ *                          id         class object ID
+ *                          u4         stack trace serial number
+ *                          id         super class object ID
+ *                          id         class loader object ID
+ *                          id         signers object ID
+ *                          id         protection domain object ID
+ *                          id         reserved
+ *                          id         reserved
+ *
+ *                          u4         instance size (in bytes)
+ *
+ *                          u2         size of constant pool
+ *                          [u2,       constant pool index,
+ *                           ty,       type
+ *                                     2:  object
+ *                                     4:  boolean
+ *                                     5:  char
+ *                                     6:  float
+ *                                     7:  double
+ *                                     8:  byte
+ *                                     9:  short
+ *                                     10: int
+ *                                     11: long
+ *                           vl]*      and value
+ *
+ *                          u2         number of static fields
+ *                          [id,       static field name,
+ *                           ty,       type,
+ *                           vl]*      and value
+ *
+ *                          u2         number of inst. fields (not inc. super)
+ *                          [id,       instance field name,
+ *                           ty]*      type
+ *
+ *               HPROF_GC_INSTANCE_DUMP        dump of a normal object
+ *
+ *                          id         object ID
+ *                          u4         stack trace serial number
+ *                          id         class object ID
+ *                          u4         number of bytes that follow
+ *                          [vl]*      instance field values (class, followed
+ *                                     by super, super's super ...)
+ *
+ *               HPROF_GC_OBJ_ARRAY_DUMP       dump of an object array
+ *
+ *                          id         array object ID
+ *                          u4         stack trace serial number
+ *                          u4         number of elements
+ *                          id         array class ID
+ *                          [id]*      elements
+ *
+ *               HPROF_GC_PRIM_ARRAY_DUMP      dump of a primitive array
+ *
+ *                          id         array object ID
+ *                          u4         stack trace serial number
+ *                          u4         number of elements
+ *                          u1         element type
+ *                                     4:  boolean array
+ *                                     5:  char array
+ *                                     6:  float array
+ *                                     7:  double array
+ *                                     8:  byte array
+ *                                     9:  short array
+ *                                     10: int array
+ *                                     11: long array
+ *                          [u1]*      elements
+ *
+ * HPROF_CPU_SAMPLES        a set of sample traces of running threads
+ *
+ *                u4        total number of samples
+ *                u4        # of traces
+ *               [u4        # of samples
+ *                u4]*      stack trace serial number
+ *
+ * HPROF_CONTROL_SETTINGS   the settings of on/off switches
+ *
+ *                u4        0x00000001: alloc traces on/off
+ *                          0x00000002: cpu sampling on/off
+ *                u2        stack trace depth
+ *
+ *
+ * When the header is "JAVA PROFILE 1.0.2" a heap dump can optionally
+ * be generated as a sequence of heap dump segments. This sequence is
+ * terminated by an end record. The additional tags allowed by format
+ * "JAVA PROFILE 1.0.2" are:
+ *
+ * HPROF_HEAP_DUMP_SEGMENT  denote a heap dump segment
+ *
+ *               [heap dump sub-records]*
+ *               The same sub-record types allowed by HPROF_HEAP_DUMP
+ *
+ * HPROF_HEAP_DUMP_END      denotes the end of a heap dump
+ * 
+ */ +public class HeapDumpWriter { + private static final long MAX_UNSIGNED_INT = (1L << 32) - 1; + + private static final int DUMMY_STACK_TRACE_ID = 1; + private static final int LARGE_OBJECT_THRESHOLD = 1 * 1024 * 1024; + private static final int HEAP_DUMP_SEGMENT_TARGET_SIZE = 1 * 1024 * 1024; + + private final NoAllocationVerifier noAllocationVerifier = NoAllocationVerifier.factory("HeapDumpWriter", false); + private final DumpStackFrameVisitor dumpStackFrameVisitor = new DumpStackFrameVisitor(); + private final DumpObjectsVisitor dumpObjectsVisitor = new DumpObjectsVisitor(); + private final CodeMetadataVisitor codeMetadataVisitor = new CodeMetadataVisitor(); + private final ThreadLocalsVisitor threadLocalsVisitor = new ThreadLocalsVisitor(); + private final HeapDumpMetadata metadata; + + private BufferedFile f; + private long topLevelRecordBegin = -1; + private long subRecordBegin = -1; + private boolean error; + + @Platforms(Platform.HOSTED_ONLY.class) + public HeapDumpWriter(HeapDumpMetadata metadata) { + this.metadata = metadata; + } + + public boolean dumpHeap(RawFileDescriptor fd) { + assert VMOperation.isInProgressAtSafepoint(); + assert ThreadingSupportImpl.isRecurringCallbackPaused(); + + noAllocationVerifier.open(); + try { + Heap.getHeap().suspendAllocation(); + return dumpHeap0(fd); + } finally { + noAllocationVerifier.close(); + } + } + + private boolean dumpHeap0(RawFileDescriptor fd) { + boolean initialized = initialize(fd); + try { + if (initialized) { + return writeHeapDump(); + } else { + Log.log().string("An error occurred while initializing the heap dump infrastructure. No heap data will be dumped.").newline(); + return false; + } + } finally { + /* teardown must always be executed, even if the initialization failed. */ + teardown(); + } + } + + private boolean initialize(RawFileDescriptor fd) { + assert topLevelRecordBegin == -1 && subRecordBegin == -1 && !error; + + this.f = file().allocate(fd); + if (f.isNull()) { + return false; + } + return metadata.initialize(); + } + + private void teardown() { + metadata.teardown(); + + assert f.isNull() || error || file().getUnflushedDataSize(f) == 0; + file().free(f); + this.f = WordFactory.nullPointer(); + + this.topLevelRecordBegin = -1; + this.subRecordBegin = -1; + this.error = false; + } + + @NeverInline("Starting a stack walk in the caller frame.") + private boolean writeHeapDump() { + /* + * Only read the stack pointer for the current thread once. This ensures consistency for all + * the information that we dump about the stack of the current thread. + */ + Pointer currentThreadSp = KnownIntrinsics.readCallerStackPointer(); + + writeHeader(); + writeClassNames(); // UTF-8 symbols + writeFieldNames(); // UTF-8 symbols + writeLoadedClasses(); // LOAD_CLASS + writeStackTraces(currentThreadSp); // FRAME and TRACE + + /* 1..n HEAP_DUMP_SEGMENT records */ + startTopLevelRecord(HProfTopLevelRecord.HEAP_DUMP_SEGMENT); + writeClasses(); // GC_CLASS_DUMP + writeThreads(currentThreadSp); // GC_ROOT_THREAD_OBJ, GC_ROOT_JAVA_FRAME, GC_ROOT_JNI_LOCAL + writeJNIGlobals(); // GC_ROOT_JNI_GLOBAL + writeStickyClasses(); // GC_ROOT_STICKY_CLASS + writeObjects(); // GC_INSTANCE_DUMP, GC_OBJ_ARRAY_DUMP, GC_PRIM_ARRAY_DUMP + endTopLevelRecord(); + + startTopLevelRecord(HProfTopLevelRecord.HEAP_DUMP_END); + endTopLevelRecord(); + + flush(); + + if (error) { + Log.log().string("An error occurred while writing the heap dump data. The data in the heap dump file may be corrupt.").newline(); + return false; + } + return true; + } + + private void writeHeader() { + writeUTF8("JAVA PROFILE 1.0.2"); + writeByte((byte) 0); + writeInt(wordSize()); + writeLong(System.currentTimeMillis()); + } + + private void startTopLevelRecord(HProfTopLevelRecord tag) { + assert topLevelRecordBegin == -1; + writeByte(tag.getValue()); + writeInt(0); // timestamp + writeInt(0); // length (patched later on) + topLevelRecordBegin = getPosition(); + } + + private void endTopLevelRecord() { + assert topLevelRecordBegin > 0; + long currentPosition = getPosition(); + setPosition(topLevelRecordBegin - Integer.BYTES); + writeInt(NumUtil.safeToUInt(currentPosition - topLevelRecordBegin)); + setPosition(currentPosition); + topLevelRecordBegin = -1; + } + + private void startSubRecord(HProfSubRecord tag, long size) { + assert topLevelRecordBegin > 0 : "must be within a HEAP_DUMP_SEGMENT"; + long heapDumpSegmentSize = getPosition() - topLevelRecordBegin; + if (heapDumpSegmentSize > 0 && heapDumpSegmentSize + size > HEAP_DUMP_SEGMENT_TARGET_SIZE) { + endTopLevelRecord(); + startTopLevelRecord(HProfTopLevelRecord.HEAP_DUMP_SEGMENT); + } + + subRecordBegin = getPosition(); + writeByte(tag.getValue()); + } + + private void endSubRecord(long recordSize) { + assert subRecordBegin > 0; + assert subRecordBegin + recordSize == getPosition(); + subRecordBegin = -1; + } + + private void writeClassNames() { + for (int i = 0; i < metadata.getClassInfoCount(); i++) { + ClassInfo classInfo = metadata.getClassInfo(i); + if (ClassInfoAccess.isValid(classInfo)) { + writeSymbol(classInfo.getHub().getName()); + } + } + } + + private void writeSymbol(String value) { + startTopLevelRecord(HProfTopLevelRecord.UTF8); + writeObjectId(value); + writeUTF8(value); + endTopLevelRecord(); + } + + private void writeSymbol(FieldName fieldName) { + startTopLevelRecord(HProfTopLevelRecord.UTF8); + writeFieldNameId(fieldName); + write((Pointer) FieldNameAccess.getChars(fieldName), WordFactory.unsigned(FieldNameAccess.getLength(fieldName))); + endTopLevelRecord(); + } + + private void writeFieldNames() { + for (int i = 0; i < metadata.getFieldNameCount(); i++) { + FieldName fieldName = metadata.getFieldName(i); + writeSymbol(fieldName); + } + } + + private void writeLoadedClasses() { + for (int i = 0; i < metadata.getClassInfoCount(); i++) { + ClassInfo classInfo = metadata.getClassInfo(i); + if (ClassInfoAccess.isValid(classInfo)) { + DynamicHub hub = classInfo.getHub(); + if (hub.isLoaded()) { + startTopLevelRecord(HProfTopLevelRecord.LOAD_CLASS); + writeInt(classInfo.getSerialNum()); + writeClassId(hub); + writeInt(DUMMY_STACK_TRACE_ID); + writeObjectId(hub.getName()); + endTopLevelRecord(); + } + } + } + } + + private void writeStackTraces(Pointer currentThreadSp) { + writeDummyStackTrace(); + + /* Write the stack traces of all threads. */ + long nextFrameId = 1; + int threadSerialNum = 1; + for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { + /* Write FRAME records. */ + int writtenFrames = dumpStackData(isolateThread, currentThreadSp, threadSerialNum, nextFrameId, false); + + /* Write TRACE record that references the FRAME records. */ + startTopLevelRecord(HProfTopLevelRecord.TRACE); + int stackSerialNum = threadSerialNum + DUMMY_STACK_TRACE_ID; + writeInt(stackSerialNum); + writeInt(threadSerialNum); + writeInt(writtenFrames); + for (int i = 0; i < writtenFrames; i++) { + writeFrameId(nextFrameId++); + } + endTopLevelRecord(); + + threadSerialNum++; + } + } + + private int dumpStackData(IsolateThread isolateThread, Pointer currentThreadSp, int threadSerialNum, long nextFrameId, boolean markGCRoots) { + dumpStackFrameVisitor.initialize(threadSerialNum, nextFrameId, markGCRoots); + if (isolateThread == CurrentIsolate.getCurrentThread()) { + JavaStackWalker.walkCurrentThread(currentThreadSp, dumpStackFrameVisitor); + } else { + JavaStackWalker.walkThread(isolateThread, dumpStackFrameVisitor); + } + return dumpStackFrameVisitor.getWrittenFrames(); + } + + /** Writes an empty TRACE record that can be used in other records. */ + private void writeDummyStackTrace() { + startTopLevelRecord(HProfTopLevelRecord.TRACE); + writeInt(DUMMY_STACK_TRACE_ID); + writeInt(0); // thread serial number + writeInt(0); // number of frames + endTopLevelRecord(); + } + + private void writeClasses() { + for (int i = 0; i < metadata.getClassInfoCount(); i++) { + ClassInfo classInfo = metadata.getClassInfo(i); + if (ClassInfoAccess.isValid(classInfo)) { + if (classInfo.getHub().isLoaded()) { + writeClassDumpRecord(classInfo); + } + } + } + } + + private void writeClassDumpRecord(ClassInfo classInfo) { + int staticFieldsCount = classInfo.getStaticFieldCount(); + int staticFieldsSize = staticFieldsCount * (wordSize() + 1) + HeapDumpMetadata.computeFieldsDumpSize(classInfo.getStaticFields(), classInfo.getStaticFieldCount()); + int instanceFieldsCount = classInfo.getInstanceFieldCount(); + int instanceFieldsSize = instanceFieldsCount * (wordSize() + 1); + int recordSize = 1 + wordSize() + 4 + 6 * wordSize() + 4 + 2 + 2 + staticFieldsSize + 2 + instanceFieldsSize; + + Class clazz = DynamicHub.toClass(classInfo.getHub()); + startSubRecord(HProfSubRecord.GC_CLASS_DUMP, recordSize); + writeClassId(clazz); + writeInt(DUMMY_STACK_TRACE_ID); + writeClassId(clazz.getSuperclass()); + writeObjectId(getClassLoader(clazz)); + writeObjectId(clazz.getSigners()); + writeObjectId(null); // protection domain + writeObjectId(null); // reserved field + writeObjectId(null); // reserved field + writeInt(getObjectSizeInHeap(clazz)); + writeShort((short) 0); // size of constant pool + writeFieldDescriptors(staticFieldsCount, classInfo.getStaticFields(), true); + writeFieldDescriptors(instanceFieldsCount, classInfo.getInstanceFields(), false); + endSubRecord(recordSize); + } + + private void writeFieldDescriptors(int fieldCount, FieldInfoPointer fieldInfos, boolean isStatic) { + writeShort(NumUtil.safeToUShort(fieldCount)); + for (int i = 0; i < fieldCount; i++) { + FieldInfo field = fieldInfos.addressOf(i).read(); + writeFieldNameId(FieldInfoAccess.getFieldName(field)); + HProfType type = FieldInfoAccess.getType(field); + writeType(type); + + if (isStatic) { + /* For static fields, write the field value to the heap dump as well. */ + Object dataHolder = getStaticFieldDataHolder(type); + writeFieldData(dataHolder, field); + } + } + } + + private static Object getStaticFieldDataHolder(HProfType type) { + if (type == HProfType.NORMAL_OBJECT) { + return StaticFieldsSupport.getStaticObjectFields(); + } else { + return StaticFieldsSupport.getStaticPrimitiveFields(); + } + } + + private void writeFieldData(Object dataHolder, FieldInfo field) { + Pointer p = Word.objectToUntrackedPointer(dataHolder); + int location = FieldInfoAccess.getLocation(field); + HProfType type = FieldInfoAccess.getType(field); + switch (type) { + case BOOLEAN, BYTE -> writeByte(p.readByte(location)); + case CHAR -> writeChar(p.readChar(location)); + case SHORT -> writeShort(p.readShort(location)); + case INT -> writeInt(p.readInt(location)); + case LONG -> writeLong(p.readLong(location)); + case FLOAT -> writeFloat(p.readFloat(location)); + case DOUBLE -> writeDouble(p.readDouble(location)); + case NORMAL_OBJECT -> writeObjectId(ReferenceAccess.singleton().readObjectAt(p.add(location), true)); + default -> throw VMError.shouldNotReachHere("Unexpected type."); + } + } + + private void writeThreads(Pointer currentThreadSp) { + long nextFrameId = 1; + int threadSerialNum = 1; + for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { + int stackTraceSerialNum = threadSerialNum + DUMMY_STACK_TRACE_ID; + /* + * If a thread is not fully initialized yet, then the java.lang.Thread object may still + * be null. In this case, a thread object id of 0 is written, which is fine according to + * the HPROF specification. + */ + Thread thread = PlatformThreads.fromVMThread(isolateThread); + writeThread(thread, threadSerialNum, stackTraceSerialNum); + nextFrameId += dumpStackData(isolateThread, currentThreadSp, threadSerialNum, nextFrameId, true); + + writeThreadLocals(isolateThread, threadSerialNum); + threadSerialNum++; + } + } + + private void writeThread(Thread threadObj, int threadSerialNum, int stackTraceSerialNum) { + int recordSize = 1 + wordSize() + 4 + 4; + startSubRecord(HProfSubRecord.GC_ROOT_THREAD_OBJ, recordSize); + writeObjectId(threadObj); + writeInt(threadSerialNum); + writeInt(stackTraceSerialNum); + endSubRecord(recordSize); + } + + private void writeThreadLocals(IsolateThread isolateThread, int threadSerialNum) { + if (SubstrateOptions.MultiThreaded.getValue()) { + threadLocalsVisitor.initialize(threadSerialNum); + VMThreadLocalMTSupport.singleton().walk(isolateThread, threadLocalsVisitor); + } + } + + private void writeJNIGlobals() { + /* All objects that are referenced by the runtime code cache are GC roots. */ + RuntimeCodeInfoMemory.singleton().walkRuntimeMethods(codeMetadataVisitor); + } + + private void writeStickyClasses() { + for (int i = 0; i < metadata.getClassInfoCount(); i++) { + ClassInfo classInfo = metadata.getClassInfo(i); + if (ClassInfoAccess.isValid(classInfo)) { + int recordSize = 1 + wordSize(); + startSubRecord(HProfSubRecord.GC_ROOT_STICKY_CLASS, recordSize); + writeClassId(classInfo.getHub()); + endSubRecord(recordSize); + } + } + } + + private void writeObjects() { + GrowableWordArray largeObjects = StackValue.get(GrowableWordArray.class); + GrowableWordArrayAccess.initialize(largeObjects); + try { + dumpObjectsVisitor.initialize(largeObjects); + Heap.getHeap().walkImageHeapObjects(dumpObjectsVisitor); + + dumpObjectsVisitor.initialize(largeObjects); + Heap.getHeap().walkCollectedHeapObjects(dumpObjectsVisitor); + + /* Large objects are collected and written separately. */ + writeLargeObjects(largeObjects); + } finally { + GrowableWordArrayAccess.freeData(largeObjects); + largeObjects = WordFactory.nullPointer(); + } + } + + private void writeLargeObjects(GrowableWordArray largeObjects) { + int count = largeObjects.getSize(); + for (int i = 0; i < count; i++) { + Word rawObj = GrowableWordArrayAccess.get(largeObjects, i); + writeObject(rawObj.toObject()); + } + } + + private static ClassLoader getClassLoader(Class clazz) { + Class c = clazz; + while (c.isArray()) { + c = c.getComponentType(); + } + return c.getClassLoader(); + } + + private static int getObjectSizeInHeap(Class cls) { + DynamicHub hub = DynamicHub.fromClass(cls); + int encoding = hub.getLayoutEncoding(); + if (LayoutEncoding.isPureInstance(encoding)) { + /* + * May underestimate the object size if the identity hashcode field is optional. This is + * the best that what can do because the HPROF format does not support that instances of + * one class have different object sizes. + */ + return (int) LayoutEncoding.getPureInstanceAllocationSize(encoding).rawValue(); + } else if (LayoutEncoding.isHybrid(encoding)) { + /* For hybrid objects, return the size of the fields. */ + return LayoutEncoding.getArrayBaseOffsetAsInt(encoding); + } else { + /* Variable size. */ + return 0; + } + } + + private void writeObject(Object obj) { + DynamicHub hub = KnownIntrinsics.readHub(obj); + int layoutEncoding = hub.getLayoutEncoding(); + if (LayoutEncoding.isArray(layoutEncoding)) { + if (LayoutEncoding.isPrimitiveArray(layoutEncoding)) { + writePrimitiveArray(obj, layoutEncoding); + } else { + writeObjectArray(obj); + } + } else { + /* + * Hybrid objects are handled here as well. This means that the array part of hybrid + * objects is currently skipped. Eventually, we should probably dump the array part as a + * separate object. + */ + writeInstance(obj); + } + + if (Heap.getHeap().isInImageHeap(obj)) { + markImageHeapObjectAsGCRoot(obj); + } + + /* + * Ideally, we would model Java monitors as instance fields as they are only reachable if + * the object that owns the monitor is reachable. However, that is not possible because the + * synthetic monitor field could overlap or collide with a normal field of a subclass. + * Therefore, we simply mark all monitors as GC roots. + */ + int monitorOffset = hub.getMonitorOffset(); + if (monitorOffset != 0) { + Object monitor = ObjectAccess.readObject(obj, monitorOffset); + if (monitor != null) { + markMonitorAsGCRoot(monitor); + } + } + } + + private void markMonitorAsGCRoot(Object monitor) { + int recordSize = 1 + wordSize(); + startSubRecord(HProfSubRecord.GC_ROOT_MONITOR_USED, recordSize); + writeObjectId(monitor); + endSubRecord(recordSize); + } + + /** We mark image heap objects as GC_ROOT_JNI_GLOBAL. */ + private void markImageHeapObjectAsGCRoot(Object obj) { + assert Heap.getHeap().isInImageHeap(obj); + markAsJniGlobalGCRoot(obj); + } + + private void markAsJniGlobalGCRoot(Object obj) { + int recordSize = 1 + 2 * wordSize(); + startSubRecord(HProfSubRecord.GC_ROOT_JNI_GLOBAL, recordSize); + writeObjectId(obj); + writeObjectId(null); // global ref ID + endSubRecord(recordSize); + } + + private void writeInstance(Object obj) { + ClassInfo classInfo = metadata.getClassInfo(obj.getClass()); + int instanceFieldsSize = classInfo.getInstanceFieldsDumpSize(); + int recordSize = 1 + wordSize() + 4 + wordSize() + 4 + instanceFieldsSize; + + startSubRecord(HProfSubRecord.GC_INSTANCE_DUMP, recordSize); + writeObjectId(obj); + writeInt(DUMMY_STACK_TRACE_ID); + writeClassId(obj.getClass()); + writeInt(instanceFieldsSize); + + /* Write the field data. */ + do { + int instanceFieldCount = classInfo.getInstanceFieldCount(); + FieldInfoPointer instanceFields = classInfo.getInstanceFields(); + for (int i = 0; i < instanceFieldCount; i++) { + FieldInfo field = instanceFields.addressOf(i).read(); + writeFieldData(obj, field); + } + classInfo = metadata.getClassInfo(classInfo.getHub().getSuperHub()); + } while (classInfo.isNonNull()); + + endSubRecord(recordSize); + } + + private void writePrimitiveArray(Object array, int layoutEncoding) { + int arrayBaseOffset = LayoutEncoding.getArrayBaseOffsetAsInt(layoutEncoding); + int elementSize = LayoutEncoding.getArrayIndexScale(layoutEncoding); + + int recordHeaderSize = 1 + wordSize() + 2 * 4 + 1; + int length = calculateMaxArrayLength(array, elementSize, recordHeaderSize); + long recordSize = recordHeaderSize + ((long) length) * elementSize; + + startSubRecord(HProfSubRecord.GC_PRIM_ARRAY_DUMP, recordSize); + writeObjectId(array); + writeInt(DUMMY_STACK_TRACE_ID); + writeInt(length); + + /* The file is big endian, so we need to read & write the data element-wise. */ + if (array instanceof boolean[]) { + writeType(HProfType.BOOLEAN); + writeU1ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof byte[]) { + writeType(HProfType.BYTE); + writeU1ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof short[]) { + writeType(HProfType.SHORT); + writeU2ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof char[]) { + writeType(HProfType.CHAR); + writeU2ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof int[]) { + writeType(HProfType.INT); + writeU4ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof float[]) { + writeType(HProfType.FLOAT); + writeU4ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof long[]) { + writeType(HProfType.LONG); + writeU8ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof double[]) { + writeType(HProfType.DOUBLE); + writeU8ArrayData(array, length, arrayBaseOffset); + } else { + /* Word arrays are primitive arrays as well */ + assert WordBase.class.isAssignableFrom(array.getClass().getComponentType()); + assert elementSize == wordSize(); + writeWordArray(array, length, arrayBaseOffset); + } + endSubRecord(recordSize); + } + + private void writeObjectArray(Object array) { + int recordHeaderSize = 1 + 2 * 4 + 2 * wordSize(); + /* In the heap dump, object array elements are always uncompressed. */ + int length = calculateMaxArrayLength(array, wordSize(), recordHeaderSize); + long recordSize = recordHeaderSize + ((long) length) * wordSize(); + + startSubRecord(HProfSubRecord.GC_OBJ_ARRAY_DUMP, recordSize); + writeObjectId(array); + writeInt(DUMMY_STACK_TRACE_ID); + writeInt(length); + writeClassId(array.getClass()); + + Object[] data = (Object[]) array; + for (int i = 0; i < length; i++) { + writeObjectId(data[i]); + } + endSubRecord(recordSize); + } + + /* + * The size of individual HPROF records is limited to {@link #MAX_UNSIGNED_INT}, which means + * that very large arrays need to truncated. + */ + private static int calculateMaxArrayLength(Object array, int elementSize, int recordHeaderSize) { + int length = ArrayLengthNode.arrayLength(array); + UnsignedWord lengthInBytes = WordFactory.unsigned(length).multiply(elementSize); + UnsignedWord maxBytes = WordFactory.unsigned(MAX_UNSIGNED_INT).subtract(recordHeaderSize); + + if (lengthInBytes.belowOrEqual(maxBytes)) { + return length; + } + + UnsignedWord newLength = maxBytes.unsignedDivide(elementSize); + Log.log().string("Cannot dump very large arrays. Array is truncated to ").unsigned(newLength).string(" elements.").newline(); + return NumUtil.safeToInt(newLength.rawValue()); + } + + private void writeWordArray(Object array, int length, int arrayBaseOffset) { + if (wordSize() == 8) { + writeType(HProfType.LONG); + writeU8ArrayData(array, length, arrayBaseOffset); + } else { + assert wordSize() == 4; + writeType(HProfType.INT); + writeU4ArrayData(array, length, arrayBaseOffset); + } + } + + private void writeU1ArrayData(Object array, int length, int arrayBaseOffset) { + Pointer data = getArrayData(array, arrayBaseOffset); + write(data, WordFactory.unsigned(length)); + } + + private void writeU2ArrayData(Object array, int length, int arrayBaseOffset) { + Pointer cur = getArrayData(array, arrayBaseOffset); + for (int i = 0; i < length; i++) { + writeChar(cur.readChar(0)); + cur = cur.add(2); + } + } + + private void writeU4ArrayData(Object array, int length, int arrayBaseOffset) { + Pointer cur = getArrayData(array, arrayBaseOffset); + for (int i = 0; i < length; i++) { + writeInt(cur.readInt(0)); + cur = cur.add(4); + } + } + + private void writeU8ArrayData(Object array, int length, int arrayBaseOffset) { + Pointer cur = getArrayData(array, arrayBaseOffset); + for (int i = 0; i < length; i++) { + writeLong(cur.readLong(0)); + cur = cur.add(8); + } + } + + private static Pointer getArrayData(Object array, int arrayBaseOffset) { + return Word.objectToUntrackedPointer(array).add(arrayBaseOffset); + } + + private void writeByte(byte value) { + boolean success = file().writeByte(f, value); + handleError(success); + } + + private void writeShort(short value) { + boolean success = file().writeShort(f, value); + handleError(success); + } + + private void writeChar(char value) { + boolean success = file().writeChar(f, value); + handleError(success); + } + + private void writeInt(int value) { + boolean success = file().writeInt(f, value); + handleError(success); + } + + private void writeLong(long value) { + boolean success = file().writeLong(f, value); + handleError(success); + } + + private void writeFloat(float value) { + boolean success = file().writeFloat(f, value); + handleError(success); + } + + private void writeDouble(double value) { + boolean success = file().writeDouble(f, value); + handleError(success); + } + + private void writeType(HProfType type) { + writeByte(type.getValue()); + } + + private void writeObjectId(Object obj) { + writeId0(Word.objectToUntrackedPointer(obj).rawValue()); + } + + private void writeClassId(Class clazz) { + writeClassId(DynamicHub.fromClass(clazz)); + } + + private void writeClassId(DynamicHub hub) { + /* + * HotSpot writes Class objects only as GC_CLASS_DUMP and never as GC_INSTANCE_DUMP records. + * It also always uses the address of the mirror class as the class id. This has the effect + * that the heap dump only contains a very limited set of information for class objects. + * + * It is handy to have detailed information about the DynamicHub in the heap dump. Ideally, + * we would just write both a GC_CLASS_DUMP and a GC_INSTANCE_DUMP record with the same id + * but that breaks VisualVM in a weird way. Therefore, we are using different ids for the + * GC_CLASS_DUMP and GC_INSTANCE_DUMP records. + */ + Word hubAddress = Word.objectToUntrackedPointer(hub); + if (hubAddress.isNonNull()) { + hubAddress = hubAddress.add(1); + } + writeId0(hubAddress.rawValue()); + } + + private void writeFieldNameId(FieldName fieldName) { + writeId0(fieldName.rawValue()); + } + + private void writeFrameId(long frameId) { + writeId0(frameId); + } + + private void writeId0(long value) { + boolean success; + if (wordSize() == 8) { + success = file().writeLong(f, value); + } else { + assert wordSize() == 4; + success = file().writeInt(f, (int) value); + } + handleError(success); + } + + private void writeUTF8(String value) { + boolean success = file().writeUTF8(f, value); + handleError(success); + } + + private void write(Pointer data, UnsignedWord size) { + boolean success = file().write(f, data, size); + handleError(success); + } + + private long getPosition() { + long result = file().position(f); + handleError(result >= 0); + return result; + } + + private void setPosition(long newPos) { + boolean success = file().seek(f, newPos); + handleError(success); + } + + private void flush() { + boolean success = file().flush(f); + handleError(success); + } + + private void handleError(boolean success) { + if (!success) { + error = true; + } + } + + @Fold + static BufferedFileOperationSupport file() { + return BufferedFileOperationSupport.bigEndian(); + } + + @Fold + static int wordSize() { + return ConfigurationValues.getTarget().wordSize; + } + + /** + * This class is used for different purposes: + *
    + *
  • Write a {@link HProfTopLevelRecord#FRAME} top-level record for every frame on the + * stack.
  • + *
  • Write a GC_ROOT sub-record for every deoptimized frame and for every reference that is on + * the stack.
  • + *
+ * + * Unfortunately, it is not possible to write all the information in a single pass because the + * data needs to end up in different HPROF records (top-level vs. sub-records). The data from + * the different passes needs to match, so it is easier to implement both passes in one class + * and to use a {@link #markGCRoots field} to determine which data should be written. + */ + private class DumpStackFrameVisitor extends StackFrameVisitor implements ObjectReferenceVisitor { + private static final int LINE_NUM_NATIVE_METHOD = -3; + + private final CodeInfoDecoder.FrameInfoCursor frameInfoCursor = new CodeInfoDecoder.FrameInfoCursor(); + + private int threadSerialNum; + private long initialNextFrameId; + private long nextFrameId; + private boolean markGCRoots; + + @Platforms(Platform.HOSTED_ONLY.class) + DumpStackFrameVisitor() { + } + + @SuppressWarnings("hiding") + public void initialize(int threadSerialNum, long nextFrameId, boolean markGCRoots) { + assert nextFrameId > 0; + assert threadSerialNum > 0; + assert nextFrameId > 0; + + this.threadSerialNum = threadSerialNum; + this.initialNextFrameId = nextFrameId; + this.nextFrameId = nextFrameId; + this.markGCRoots = markGCRoots; + } + + public int getWrittenFrames() { + return NumUtil.safeToInt(nextFrameId - initialNextFrameId); + } + + @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") + protected boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptimizedFrame) { + if (deoptimizedFrame != null) { + markAsGCRoot(deoptimizedFrame); + + for (DeoptimizedFrame.VirtualFrame frame = deoptimizedFrame.getTopFrame(); frame != null; frame = frame.getCaller()) { + visitFrame(frame.getFrameInfo()); + nextFrameId++; + } + } else { + /* + * All references that are on the stack need to be marked as GC roots. Our + * information is not necessarily precise enough to identify the exact Java-level + * stack frame to which a reference belongs. Therefore, we just dump the data in a + * way that it gets associated with the deepest inlined Java-level stack frame of + * each compilation unit. + */ + markStackValuesAsGCRoots(sp, ip, codeInfo); + + frameInfoCursor.initialize(codeInfo, ip); + while (frameInfoCursor.advance()) { + FrameInfoQueryResult frame = frameInfoCursor.get(); + visitFrame(frame); + nextFrameId++; + } + } + return true; + } + + private void markAsGCRoot(DeoptimizedFrame frame) { + if (markGCRoots) { + markAsJniGlobalGCRoot(frame); + } + } + + private void markStackValuesAsGCRoots(Pointer sp, CodePointer ip, CodeInfo codeInfo) { + if (markGCRoots) { + SimpleCodeInfoQueryResult queryResult = StackValue.get(SimpleCodeInfoQueryResult.class); + CodeInfoAccess.lookupCodeInfo(codeInfo, CodeInfoAccess.relativeIP(codeInfo, ip), queryResult); + + NonmovableArray referenceMapEncoding = CodeInfoAccess.getStackReferenceMapEncoding(codeInfo); + long referenceMapIndex = queryResult.getReferenceMapIndex(); + if (referenceMapIndex == ReferenceMapIndex.NO_REFERENCE_MAP) { + throw CodeInfoTable.reportNoReferenceMap(sp, ip, codeInfo); + } + CodeReferenceMapDecoder.walkOffsetsFromPointer(sp, referenceMapEncoding, referenceMapIndex, this, null); + } + } + + @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") + public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { + assert markGCRoots; + + Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); + if (obj != null) { + int recordSize = 1 + wordSize() + 4 + 4; + startSubRecord(HProfSubRecord.GC_ROOT_JAVA_FRAME, recordSize); + writeObjectId(obj); + writeInt(threadSerialNum); + /* Position of the stack frame in the stack trace. */ + writeInt(getWrittenFrames()); + } + return true; + } + + private void visitFrame(FrameInfoQueryResult frame) { + if (!markGCRoots) { + /* + * Write all UTF-8 symbols that are needed when writing the frame. Ideally, we would + * de-duplicate the symbols, but doing so is not crucial. We also don't support the + * method signature at the moment. + */ + String methodName = frame.getSourceMethodName(); + String methodSignature = ""; + String sourceFileName = getSourceFileName(frame); + writeSymbol(methodName); + writeSymbol(methodSignature); + writeSymbol(sourceFileName); + + /* Write the FRAME record. */ + ClassInfo classInfo = metadata.getClassInfo(frame.getSourceClass()); + int lineNumber = getLineNumber(frame); + writeFrame(classInfo.getSerialNum(), lineNumber, methodName, methodSignature, sourceFileName); + } + } + + private void writeFrame(int classSerialNum, int lineNumber, String methodName, String methodSignature, String sourceFileName) { + assert !markGCRoots; + + startTopLevelRecord(HProfTopLevelRecord.FRAME); + writeFrameId(nextFrameId); + writeObjectId(methodName); + writeObjectId(methodSignature); + writeObjectId(sourceFileName); + writeInt(classSerialNum); + writeInt(lineNumber); + endTopLevelRecord(); + } + + private String getSourceFileName(FrameInfoQueryResult frame) { + String sourceFileName = frame.getSourceFileName(); + if (sourceFileName == null || sourceFileName.isEmpty()) { + sourceFileName = "Unknown Source"; + } + return sourceFileName; + } + + private int getLineNumber(FrameInfoQueryResult frame) { + if (frame.isNativeMethod()) { + return LINE_NUM_NATIVE_METHOD; + } + return frame.getSourceLineNumber(); + } + } + + private class DumpObjectsVisitor implements ObjectVisitor { + private GrowableWordArray largeObjects; + + @Platforms(Platform.HOSTED_ONLY.class) + DumpObjectsVisitor() { + } + + @SuppressWarnings("hiding") + public void initialize(GrowableWordArray largeObjects) { + this.largeObjects = largeObjects; + } + + @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") + public boolean visitObject(Object obj) { + if (isLarge(obj)) { + boolean added = GrowableWordArrayAccess.add(largeObjects, Word.objectToUntrackedPointer(obj)); + if (!added) { + Log.log().string("Failed to add an element to the large object list. Heap dump will be incomplete.").newline(); + } + } else { + writeObject(obj); + } + return true; + } + + private boolean isLarge(Object obj) { + return getObjectSize(obj).aboveThan(LARGE_OBJECT_THRESHOLD); + } + + private UnsignedWord getObjectSize(Object obj) { + int layoutEncoding = KnownIntrinsics.readHub(obj).getLayoutEncoding(); + if (LayoutEncoding.isArray(layoutEncoding)) { + int elementSize; + if (LayoutEncoding.isPrimitiveArray(layoutEncoding)) { + elementSize = LayoutEncoding.getArrayIndexScale(layoutEncoding); + } else { + elementSize = wordSize(); + } + int length = ArrayLengthNode.arrayLength(obj); + return WordFactory.unsigned(length).multiply(elementSize); + } else { + ClassInfo classInfo = metadata.getClassInfo(obj.getClass()); + return WordFactory.unsigned(classInfo.getInstanceFieldsDumpSize()); + } + } + } + + private class CodeMetadataVisitor implements RuntimeCodeCache.CodeInfoVisitor, ObjectReferenceVisitor { + @Platforms(Platform.HOSTED_ONLY.class) + CodeMetadataVisitor() { + } + + @Override + public boolean visitCode(CodeInfo info) { + RuntimeCodeInfoAccess.walkObjectFields(info, this); + return true; + } + + @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") + public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { + Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); + if (obj != null) { + markAsJniGlobalGCRoot(obj); + } + return true; + } + } + + private class ThreadLocalsVisitor implements ObjectReferenceVisitor { + private int threadSerialNum; + + @Platforms(Platform.HOSTED_ONLY.class) + ThreadLocalsVisitor() { + } + + @SuppressWarnings("hiding") + public void initialize(int threadSerialNum) { + this.threadSerialNum = threadSerialNum; + } + + @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") + public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { + Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); + if (obj != null) { + markThreadLocalAsGCRoot(obj); + } + return true; + } + + private void markThreadLocalAsGCRoot(Object obj) { + int recordSize = 1 + wordSize() + 4 + 4; + startSubRecord(HProfSubRecord.GC_ROOT_JNI_LOCAL, recordSize); + writeObjectId(obj); + writeInt(threadSerialNum); + writeInt(-1); // empty stack + endSubRecord(recordSize); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/AllocationFreeOutputStream.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/AllocationFreeOutputStream.java index fe10df6adff1..46248182c3f4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/AllocationFreeOutputStream.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/AllocationFreeOutputStream.java @@ -27,8 +27,9 @@ import java.io.IOException; /** - * Simple interface for writing heap dump to arbitrary data source. + * Legacy implementation, only used by other legacy code (see GR-44538). * + * Simple interface for writing heap dump to arbitrary data source. */ public interface AllocationFreeOutputStream { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpFeature.java deleted file mode 100644 index 0b0336c6014d..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpFeature.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2022, 2022, 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.heapdump; - -import java.io.FileOutputStream; - -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.impl.HeapDumpSupport; - -import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; - -@AutomaticallyRegisteredFeature -public final class HeapDumpFeature implements InternalFeature { - @Override - public void afterRegistration(AfterRegistrationAccess access) { - ImageSingletons.add(HeapDumpSupport.class, new HeapDumpSupportImpl()); - ImageSingletons.add(HeapDumpUtils.class, new HeapDumpUtils()); - if (Platform.includedIn(Platform.WINDOWS.class)) { - ImageSingletons.add(HeapDumpWriter.class, new UnimplementedHeapDumpWriter("Currently not supported for " + ImageSingletons.lookup(Platform.class))); - } else { - ImageSingletons.add(HeapDumpWriter.class, new HeapDumpWriterImpl()); - } - } -} - -final class HeapDumpSupportImpl implements HeapDumpSupport { - @Override - public void dumpHeap(String outputFile, boolean live) throws java.io.IOException { - try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) { - HeapDumpWriter.singleton().writeHeapTo(fileOutputStream, live); - } - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/UnimplementedHeapDumpWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java similarity index 65% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/UnimplementedHeapDumpWriter.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java index 9356d05ddb8c..1ac7046f8550 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/UnimplementedHeapDumpWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,24 +25,15 @@ package com.oracle.svm.core.heapdump; import java.io.FileOutputStream; -import java.io.IOException; -import com.oracle.svm.core.util.VMError; - -public final class UnimplementedHeapDumpWriter extends HeapDumpWriter { - private final String message; - - public UnimplementedHeapDumpWriter(String message) { - this.message = message; - } - - @Override - public void writeHeapTo(FileOutputStream fileOutputStream, boolean gcBefore) throws IOException { - throw VMError.unimplemented(message); - } +import org.graalvm.nativeimage.impl.HeapDumpSupport; +/* Legacy implementation, only used by other legacy code (see GR-44538). */ +public class HeapDumpSupportImpl implements HeapDumpSupport { @Override - public void writeHeapTo(AllocationFreeOutputStream fileOutputStream, boolean gcBefore) throws IOException { - throw VMError.unimplemented(message); + public void dumpHeap(String outputFile, boolean live) throws java.io.IOException { + try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) { + com.oracle.svm.core.heapdump.HeapDumpWriter.singleton().writeHeapTo(fileOutputStream, live); + } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpUtils.java index 752c9f25e9eb..b359f9aaa5b4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpUtils.java @@ -57,7 +57,11 @@ import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.VMThreads; -/** A collection of utilities that might assist heap dumps. */ +/** + * Legacy implementation, only used by other legacy code (see GR-44538). + * + * A collection of utilities that might assist heap dumps. + */ public class HeapDumpUtils { @UnknownObjectField(types = {byte[].class}) private byte[] fieldsMap; @@ -66,7 +70,7 @@ public class HeapDumpUtils { private final TestingBackDoor testingBackDoor; /** Constructor. */ - HeapDumpUtils() { + public HeapDumpUtils() { this.testingBackDoor = new TestingBackDoor(this); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java index 84449a1c073a..176776c12a03 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java @@ -29,6 +29,7 @@ import org.graalvm.nativeimage.ImageSingletons; +/** Legacy implementation, only used by other legacy code (see GR-44538). */ public abstract class HeapDumpWriter { /** * Writes heap in hprof format to ordinary file. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriterImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriterImpl.java index b9f239d614f9..0d8cb6f85de6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriterImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriterImpl.java @@ -60,6 +60,8 @@ import com.oracle.svm.core.util.VMError; /* + * Legacy implementation, only used by other legacy code (see GR-44538). + * * This class writes Java heap in hprof binary format. The class is heavily * influenced by 'HeapHprofBinWriter' implementation. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index 11412eb47e76..f319d4a66d00 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -768,8 +768,10 @@ public InputStream getResourceAsStream(String resourceName) { @KeepOriginal private native boolean isOpenToCaller(String resourceName, Class caller); - @KeepOriginal - private native ClassLoader getClassLoader(); + @Substitute + public ClassLoader getClassLoader() { + return companion.getClassLoader(); + } @KeepOriginal private native ClassLoader getClassLoader0(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java index 2225aec0f4c1..2050038a41e8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java @@ -32,7 +32,6 @@ import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; @@ -42,6 +41,8 @@ import com.oracle.svm.core.jfr.sampler.JfrRecurringCallbackExecutionSampler; import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; import com.oracle.svm.core.os.RawFileOperationSupport; +import com.oracle.svm.core.os.RawFileOperationSupport.FileAccessMode; +import com.oracle.svm.core.os.RawFileOperationSupport.FileCreationMode; import com.oracle.svm.core.sampler.SamplerBuffersAccess; import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.ThreadingSupportImpl; @@ -123,7 +124,7 @@ public boolean openFile(String outputFile) { chunkStartNanos = JfrTicks.currentTimeNanos(); chunkStartTicks = JfrTicks.elapsedTicks(); filename = outputFile; - fd = getFileSupport().open(filename, RawFileOperationSupport.FileAccessMode.READ_WRITE); + fd = getFileSupport().create(filename, FileCreationMode.CREATE_OR_REPLACE, FileAccessMode.READ_WRITE); writeFileHeader(); return true; } @@ -142,7 +143,7 @@ public boolean write(JfrBuffer buffer) { // We lost some data because the write failed. return false; } - return getFileSupport().position(fd).greaterThan(WordFactory.signed(notificationThreshold)); + return getFileSupport().position(fd) > notificationThreshold; } /** @@ -163,8 +164,8 @@ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories) * data structures of the new epoch. This guarantees that the data in the old epoch can be * persisted to a file without a safepoint. */ - SignedWord constantPoolPosition = writeCheckpointEvent(repositories); - SignedWord metadataPosition = writeMetadataEvent(metadataDescriptor); + long constantPoolPosition = writeCheckpointEvent(repositories); + long metadataPosition = writeMetadataEvent(metadataDescriptor); patchFileHeader(constantPoolPosition, metadataPosition); getFileSupport().close(fd); @@ -177,7 +178,7 @@ private void writeFileHeader() { getFileSupport().write(fd, FILE_MAGIC); getFileSupport().writeShort(fd, JFR_VERSION_MAJOR); getFileSupport().writeShort(fd, JFR_VERSION_MINOR); - assert getFileSupport().position(fd).equal(CHUNK_SIZE_OFFSET); + assert getFileSupport().position(fd) == CHUNK_SIZE_OFFSET; getFileSupport().writeLong(fd, 0L); // chunk size getFileSupport().writeLong(fd, 0L); // last checkpoint offset getFileSupport().writeLong(fd, 0L); // metadata position @@ -188,30 +189,30 @@ private void writeFileHeader() { getFileSupport().writeInt(fd, compressedInts ? 1 : 0); } - public void patchFileHeader(SignedWord constantPoolPosition, SignedWord metadataPosition) { - long chunkSize = getFileSupport().position(fd).rawValue(); + public void patchFileHeader(long constantPoolPosition, long metadataPosition) { + long chunkSize = getFileSupport().position(fd); long durationNanos = JfrTicks.currentTimeNanos() - chunkStartNanos; - getFileSupport().seek(fd, WordFactory.signed(CHUNK_SIZE_OFFSET)); + getFileSupport().seek(fd, CHUNK_SIZE_OFFSET); getFileSupport().writeLong(fd, chunkSize); - getFileSupport().writeLong(fd, constantPoolPosition.rawValue()); - getFileSupport().writeLong(fd, metadataPosition.rawValue()); + getFileSupport().writeLong(fd, constantPoolPosition); + getFileSupport().writeLong(fd, metadataPosition); getFileSupport().writeLong(fd, chunkStartNanos); getFileSupport().writeLong(fd, durationNanos); } - private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories) { - SignedWord start = beginEvent(); + private long writeCheckpointEvent(JfrConstantPool[] repositories) { + long start = beginEvent(); writeCompressedLong(CONSTANT_POOL_TYPE_ID); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration writeCompressedLong(0); // deltaToNext writeBoolean(true); // flush - SignedWord poolCountPos = getFileSupport().position(fd); + long poolCountPos = getFileSupport().position(fd); getFileSupport().writeInt(fd, 0); // We'll patch this later. JfrConstantPool[] serializers = JfrSerializerSupport.get().getSerializers(); int poolCount = writeConstantPools(serializers) + writeConstantPools(repositories); - SignedWord currentPos = getFileSupport().position(fd); + long currentPos = getFileSupport().position(fd); getFileSupport().seek(fd, poolCountPos); getFileSupport().writeInt(fd, makePaddedInt(poolCount)); getFileSupport().seek(fd, currentPos); @@ -229,8 +230,8 @@ private int writeConstantPools(JfrConstantPool[] constantPools) { return count; } - private SignedWord writeMetadataEvent(byte[] metadataDescriptor) { - SignedWord start = beginEvent(); + private long writeMetadataEvent(byte[] metadataDescriptor) { + long start = beginEvent(); writeCompressedLong(METADATA_TYPE_ID); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration @@ -242,21 +243,21 @@ private SignedWord writeMetadataEvent(byte[] metadataDescriptor) { public boolean shouldRotateDisk() { assert lock.isHeldByCurrentThread(); - return getFileSupport().isValid(fd) && getFileSupport().size(fd).greaterThan(WordFactory.signed(notificationThreshold)); + return getFileSupport().isValid(fd) && getFileSupport().size(fd) > notificationThreshold; } - public SignedWord beginEvent() { - SignedWord start = getFileSupport().position(fd); + public long beginEvent() { + long start = getFileSupport().position(fd); // Write a placeholder for the size. Will be patched by endEvent, getFileSupport().writeInt(fd, 0); return start; } - public void endEvent(SignedWord start) { - SignedWord end = getFileSupport().position(fd); - SignedWord writtenBytes = end.subtract(start); + public void endEvent(long start) { + long end = getFileSupport().position(fd); + long writtenBytes = end - start; getFileSupport().seek(fd, start); - getFileSupport().writeInt(fd, makePaddedInt(writtenBytes.rawValue())); + getFileSupport().writeInt(fd, makePaddedInt(writtenBytes)); getFileSupport().seek(fd, end); } @@ -362,7 +363,7 @@ public enum StringEncoding { CHAR_ARRAY(4), LATIN1_BYTE_ARRAY(5); - public byte byteValue; + public final byte byteValue; StringEncoding(int byteValue) { this.byteValue = (byte) byteValue; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractRawFileOperationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractRawFileOperationSupport.java index 9145879b9976..215f4add6a52 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractRawFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractRawFileOperationSupport.java @@ -50,8 +50,13 @@ protected AbstractRawFileOperationSupport(boolean useNativeByteOrder) { } @Override - public RawFileDescriptor open(String filename, FileAccessMode mode) { - return open(new File(filename), mode); + public RawFileDescriptor create(String filename, FileCreationMode creationMode, FileAccessMode accessMode) { + return create(new File(filename), creationMode, accessMode); + } + + @Override + public RawFileDescriptor open(String filename, FileAccessMode accessMode) { + return open(new File(filename), accessMode); } @Override @@ -114,34 +119,48 @@ public boolean writeLong(RawFileDescriptor fd, long data) { return write(fd, dataPtr, WordFactory.unsigned(sizeInBytes)); } + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeFloat(RawFileDescriptor fd, float data) { + return writeInt(fd, Float.floatToIntBits(data)); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeDouble(RawFileDescriptor fd, double data) { + return writeLong(fd, Double.doubleToLongBits(data)); + } + public static class RawFileOperationSupportHolder { private final RawFileOperationSupport littleEndian; private final RawFileOperationSupport bigEndian; - private final RawFileOperationSupport nativeByteOrder; + private final RawFileOperationSupport nativeOrder; @Platforms(Platform.HOSTED_ONLY.class) - public RawFileOperationSupportHolder(RawFileOperationSupport littleEndian, RawFileOperationSupport bigEndian, RawFileOperationSupport nativeByteOrder) { + public RawFileOperationSupportHolder(RawFileOperationSupport littleEndian, RawFileOperationSupport bigEndian, RawFileOperationSupport nativeOrder) { this.littleEndian = littleEndian; this.bigEndian = bigEndian; - this.nativeByteOrder = nativeByteOrder; + this.nativeOrder = nativeOrder; + } + + @Fold + static RawFileOperationSupportHolder singleton() { + return ImageSingletons.lookup(RawFileOperationSupportHolder.class); } @Fold public static RawFileOperationSupport getLittleEndian() { - RawFileOperationSupportHolder holder = ImageSingletons.lookup(RawFileOperationSupportHolder.class); - return holder.littleEndian; + return singleton().littleEndian; } @Fold public static RawFileOperationSupport getBigEndian() { - RawFileOperationSupportHolder holder = ImageSingletons.lookup(RawFileOperationSupportHolder.class); - return holder.bigEndian; + return singleton().bigEndian; } @Fold public static RawFileOperationSupport getNativeByteOrder() { - RawFileOperationSupportHolder holder = ImageSingletons.lookup(RawFileOperationSupportHolder.class); - return holder.nativeByteOrder; + return singleton().nativeOrder; } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java new file mode 100644 index 000000000000..0e99a69c14b1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.os; + +import java.nio.ByteOrder; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.compiler.word.Word; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; +import org.graalvm.word.Pointer; +import org.graalvm.word.PointerBase; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.UnmanagedMemoryUtil; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.LayoutEncoding; +import com.oracle.svm.core.jdk.JavaLangSubstitutions; +import com.oracle.svm.core.os.BufferedFileOperationSupport.BufferedFileOperationSupportHolder; +import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor; +import com.oracle.svm.core.snippets.KnownIntrinsics; + +/** + * Provides buffered, OS-independent operations on files. Most of the code is implemented in a way + * that it can be used from uninterruptible code. + */ +public class BufferedFileOperationSupport { + /** + * Returns a {@link BufferedFileOperationSupport} singleton that uses little endian byte order. + */ + @Fold + public static BufferedFileOperationSupport littleEndian() { + return BufferedFileOperationSupportHolder.singleton().littleEndian; + } + + /** + * Returns a {@link BufferedFileOperationSupport} singleton that uses big endian byte order. + */ + @Fold + public static BufferedFileOperationSupport bigEndian() { + return BufferedFileOperationSupportHolder.singleton().bigEndian; + } + + /** + * Returns a {@link BufferedFileOperationSupport} singleton that uses the native byte order of + * the underlying architecture. + */ + @Fold + public static BufferedFileOperationSupport nativeByteOrder() { + return BufferedFileOperationSupportHolder.singleton().nativeOrder; + } + + private static final int BUFFER_SIZE = 4 * 1024; + private static final int LARGE_DATA_THRESHOLD = 1024; + + private final boolean useNativeByteOrder; + + @Platforms(Platform.HOSTED_ONLY.class) + protected BufferedFileOperationSupport(boolean useNativeByteOrder) { + this.useNativeByteOrder = useNativeByteOrder; + } + + /** + * Allocate a {@link BufferedFile} for a {@link RawFileDescriptor}. + * + * @return a {@link BufferedFile} if the {@link RawFileDescriptor} was + * {@link RawFileOperationSupport#isValid valid} and if the allocation was successful. + * Returns a null pointer otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public BufferedFile allocate(RawFileDescriptor fd) { + if (!rawFiles().isValid(fd)) { + return WordFactory.nullPointer(); + } + long filePosition = rawFiles().position(fd); + if (filePosition < 0) { + return WordFactory.nullPointer(); + } + + /* Use a single allocation for the struct and the corresponding buffer. */ + UnsignedWord totalSize = SizeOf.unsigned(BufferedFile.class).add(WordFactory.unsigned(BUFFER_SIZE)); + BufferedFile result = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(totalSize); + if (result.isNull()) { + return WordFactory.nullPointer(); + } + + result.setFileDescriptor(fd); + result.setFilePosition(filePosition); + result.setBufferPos(getBufferStart(result)); + return result; + } + + /** + * Free the {@link BufferedFile} and its corresponding buffer. Be aware that this operation does + * neither flush pending data nor close the underlying {@link RawFileDescriptor}. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void free(BufferedFile f) { + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(f); + } + + /** + * Flush the buffered data to the file. + * + * @return true if the data was flushed successful or there was no pending data that needed + * flushing. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean flush(BufferedFile f) { + int unflushed = getUnflushedDataSize(f); + if (unflushed == 0) { + return true; + } + + boolean success = rawFiles().write(f.getFileDescriptor(), getBufferStart(f), WordFactory.unsigned(unflushed)); + if (success) { + f.setBufferPos(getBufferStart(f)); + f.setFilePosition(f.getFilePosition() + unflushed); + assert f.getFilePosition() == rawFiles().position(f.getFileDescriptor()); + } + return success; + } + + /** + * Gets the current position within a file. + * + * @return If the operation is successful, it returns the current file position. Otherwise, it + * returns a value less than 0. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public long position(BufferedFile f) { + return f.getFilePosition() + getUnflushedDataSize(f); + } + + /** + * Sets the current position within a file. As a side effect of this operation, pending data may + * be flushed. + * + * @return true if the file position was updated to the given value, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean seek(BufferedFile f, long position) { + if (position >= 0 && flush(f) && rawFiles().seek(f.getFileDescriptor(), position)) { + f.setFilePosition(position); + return true; + } + return false; + } + + /** + * Writes data to the current file position and advances the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean write(BufferedFile f, Pointer data, UnsignedWord size) { + /* Large data is written directly to the file without any buffering. */ + if (size.aboveOrEqual(LARGE_DATA_THRESHOLD)) { + if (flush(f) && rawFiles().write(f.getFileDescriptor(), data, size)) { + f.setFilePosition(f.getFilePosition() + size.rawValue()); + assert f.getFilePosition() == rawFiles().position(f.getFileDescriptor()); + assert f.getBufferPos() == getBufferStart(f); + return true; + } + return false; + } + + /* Try to write the data to the buffer. */ + assert (int) size.rawValue() == size.rawValue(); + if (!ensureBufferSpace(f, (int) size.rawValue())) { + return false; + } + + Pointer pos = f.getBufferPos(); + UnmanagedMemoryUtil.copy(data, pos, size); + f.setBufferPos(pos.add(size)); + return true; + } + + /** + * Writes data to the current file position and advances the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Array must not move.") + public boolean write(BufferedFile f, byte[] data) { + DynamicHub hub = KnownIntrinsics.readHub(data); + UnsignedWord baseOffset = LayoutEncoding.getArrayBaseOffset(hub.getLayoutEncoding()); + Pointer dataPtr = Word.objectToUntrackedPointer(data).add(baseOffset); + return write(f, dataPtr, WordFactory.unsigned(data.length)); + } + + /** + * Writes data to the current file position and advances the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeBoolean(BufferedFile f, boolean data) { + return writeByte(f, (byte) (data ? 1 : 0)); + } + + /** + * Writes data to the current file position and advances the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeByte(BufferedFile f, byte data) { + if (!ensureBufferSpace(f, Byte.BYTES)) { + return false; + } + + Pointer pos = f.getBufferPos(); + pos.writeByte(0, data); + f.setBufferPos(pos.add(Byte.BYTES)); + return true; + } + + /** + * Writes a short value in the specified byte order to the current file position and advances + * the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeShort(BufferedFile f, short data) { + if (!ensureBufferSpace(f, Short.BYTES)) { + return false; + } + + Pointer pos = f.getBufferPos(); + pos.writeShort(0, useNativeByteOrder ? data : Short.reverseBytes(data)); + f.setBufferPos(pos.add(Short.BYTES)); + return true; + } + + /** + * Writes a char value in the specified byte order to the current file position and advances the + * file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeChar(BufferedFile f, char data) { + if (!ensureBufferSpace(f, Character.BYTES)) { + return false; + } + + Pointer pos = f.getBufferPos(); + pos.writeChar(0, useNativeByteOrder ? data : Character.reverseBytes(data)); + f.setBufferPos(pos.add(Character.BYTES)); + return true; + } + + /** + * Writes an integer value in the specified byte order to the current file position and advances + * the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeInt(BufferedFile f, int data) { + if (!ensureBufferSpace(f, Integer.BYTES)) { + return false; + } + + Pointer pos = f.getBufferPos(); + pos.writeInt(0, useNativeByteOrder ? data : Integer.reverseBytes(data)); + f.setBufferPos(pos.add(Integer.BYTES)); + return true; + } + + /** + * Writes a long value in the specified byte order to the current file position and advances the + * file position. + * + * @return true if the v was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeLong(BufferedFile f, long v) { + if (!ensureBufferSpace(f, Long.BYTES)) { + return false; + } + + Pointer pos = f.getBufferPos(); + pos.writeLong(0, useNativeByteOrder ? v : Long.reverseBytes(v)); + f.setBufferPos(pos.add(Long.BYTES)); + return true; + } + + /** + * Writes a float value in the specified byte order to the current file position and advances + * the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeFloat(BufferedFile f, float v) { + return writeInt(f, Float.floatToIntBits(v)); + } + + /** + * Writes a double value in the specified byte order to the current file position and advances + * the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeDouble(BufferedFile f, double v) { + return writeLong(f, Double.doubleToLongBits(v)); + } + + /** + * Writes the String characters encoded as UTF8 to the current file position and advances the + * file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeUTF8(BufferedFile f, String string) { + boolean success = true; + for (int index = 0; index < string.length() && success; index++) { + success &= writeUTF8(f, JavaLangSubstitutions.StringUtil.charAt(string, index)); + } + return success; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private boolean writeUTF8(BufferedFile f, char c) { + boolean success; + if (c <= 0x007F) { + success = writeByte(f, (byte) c); + } else if (c <= 0x07FF) { + success = writeByte(f, (byte) (0xC0 | (c >> 6))); + success = success && writeByte(f, (byte) (0x80 | (c & 0x3F))); + } else { + success = writeByte(f, (byte) (0xE0 | (c >> 12))); + success = success && writeByte(f, (byte) (0x80 | ((c >> 6) & 0x3F))); + success = success && writeByte(f, (byte) (0x80 | (c & 0x3F))); + } + return success; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int getUnflushedDataSize(BufferedFile f) { + UnsignedWord result = f.getBufferPos().subtract(getBufferStart(f)); + assert result.belowOrEqual(BUFFER_SIZE); + return (int) result.rawValue(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static Pointer getBufferStart(BufferedFile f) { + return ((Pointer) f).add(SizeOf.unsigned(BufferedFile.class)); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private boolean ensureBufferSpace(BufferedFile f, int size) { + assert size <= BUFFER_SIZE : "only called for small data"; + if (getUnflushedDataSize(f) + size >= BUFFER_SIZE) { + return flush(f); + } + return true; + } + + @Fold + static RawFileOperationSupport rawFiles() { + /* The byte order does not matter because we only use byte-order-independent methods. */ + return RawFileOperationSupport.nativeByteOrder(); + } + + @RawStructure + public interface BufferedFile extends PointerBase { + @RawField + RawFileDescriptor getFileDescriptor(); + + @RawField + void setFileDescriptor(RawFileDescriptor value); + + @RawField + Pointer getBufferPos(); + + @RawField + void setBufferPos(Pointer value); + + @RawField + long getFilePosition(); + + @RawField + void setFilePosition(long value); + } + + public static class BufferedFileOperationSupportHolder { + private final BufferedFileOperationSupport littleEndian; + private final BufferedFileOperationSupport bigEndian; + private final BufferedFileOperationSupport nativeOrder; + + @Platforms(Platform.HOSTED_ONLY.class) + public BufferedFileOperationSupportHolder() { + ByteOrder nativeByteOrder = ByteOrder.nativeOrder(); + assert nativeByteOrder == ByteOrder.LITTLE_ENDIAN || nativeByteOrder == ByteOrder.BIG_ENDIAN; + + this.littleEndian = new BufferedFileOperationSupport(ByteOrder.LITTLE_ENDIAN == nativeByteOrder); + this.bigEndian = new BufferedFileOperationSupport(ByteOrder.BIG_ENDIAN == nativeByteOrder); + this.nativeOrder = nativeByteOrder == ByteOrder.LITTLE_ENDIAN ? littleEndian : bigEndian; + } + + @Fold + static BufferedFileOperationSupportHolder singleton() { + return ImageSingletons.lookup(BufferedFileOperationSupportHolder.class); + } + } +} + +@AutomaticallyRegisteredFeature +class BufferedFileOperationFeature implements InternalFeature { + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + if (RawFileOperationSupport.isPresent()) { + ImageSingletons.add(BufferedFileOperationSupportHolder.class, new BufferedFileOperationSupportHolder()); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java index 96e092db6906..b0d4aa0b720d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java @@ -27,8 +27,8 @@ import java.io.File; import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.word.Pointer; -import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; @@ -36,12 +36,17 @@ import com.oracle.svm.core.os.AbstractRawFileOperationSupport.RawFileOperationSupportHolder; /** - * Provides an OS-independent abstraction for operations on files. Most of the code is implemented - * in a way that it can be used from uninterruptible code. + * Provides unbuffered, OS-independent operations on files. Most of the code is implemented in a way + * that it can be used from uninterruptible code. */ public interface RawFileOperationSupport { + @Fold + static boolean isPresent() { + return ImageSingletons.contains(RawFileOperationSupportHolder.class); + } + /** - * Returns a {@link RawFileOperationSupport} singleton that uses little endian byte ordering. + * Returns a {@link RawFileOperationSupport} singleton that uses little endian byte order. */ @Fold static RawFileOperationSupport littleEndian() { @@ -49,7 +54,7 @@ static RawFileOperationSupport littleEndian() { } /** - * Returns a {@link RawFileOperationSupport} singleton that uses big endian byte ordering. + * Returns a {@link RawFileOperationSupport} singleton that uses big endian byte order. */ @Fold static RawFileOperationSupport bigEndian() { @@ -57,7 +62,7 @@ static RawFileOperationSupport bigEndian() { } /** - * Returns a {@link RawFileOperationSupport} singleton that uses the native byte ordering of the + * Returns a {@link RawFileOperationSupport} singleton that uses the native byte order of the * underlying architecture. */ @Fold @@ -66,20 +71,38 @@ static RawFileOperationSupport nativeByteOrder() { } /** - * Opens or creates a file with the specified {@link FileAccessMode access mode}. + * Creates a file with the specified {@link FileCreationMode creation} and {@link FileAccessMode + * access modes}. + * + * @return If the operation is successful, it returns the file descriptor. Otherwise, it returns + * a value where {@link #isValid} will return false. + */ + RawFileDescriptor create(String filename, FileCreationMode creationMode, FileAccessMode accessMode); + + /** + * Creates a file with the specified {@link FileCreationMode creation} and {@link FileAccessMode + * access modes}. * * @return If the operation is successful, it returns the file descriptor. Otherwise, it returns * a value where {@link #isValid} will return false. */ - RawFileDescriptor open(String filename, FileAccessMode mode); + RawFileDescriptor create(File file, FileCreationMode creationMode, FileAccessMode accessMode); /** - * Opens or creates a file with the specified {@link FileAccessMode access mode}. + * Opens a file with the specified {@link FileAccessMode access mode}. * * @return If the operation is successful, it returns the file descriptor. Otherwise, it returns * a value where {@link #isValid} will return false. */ - RawFileDescriptor open(File file, FileAccessMode mode); + RawFileDescriptor open(String filename, FileAccessMode accessMode); + + /** + * Opens a file with the specified {@link FileAccessMode access mode}. + * + * @return If the operation is successful, it returns the file descriptor. Otherwise, it returns + * a value where {@link #isValid} will return false. + */ + RawFileDescriptor open(File file, FileAccessMode accessMode); /** * Checks if a file descriptor is valid or if it represents an error value. @@ -104,7 +127,7 @@ static RawFileOperationSupport nativeByteOrder() { * returns a value less than 0. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - SignedWord size(RawFileDescriptor fd); + long size(RawFileDescriptor fd); /** * Gets the current file position within a file. @@ -113,7 +136,7 @@ static RawFileOperationSupport nativeByteOrder() { * returns a value less than 0. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - SignedWord position(RawFileDescriptor fd); + long position(RawFileDescriptor fd); /** * Sets the current file position within a file. @@ -121,7 +144,7 @@ static RawFileOperationSupport nativeByteOrder() { * @return true if the file position was updated to the given value, false otherwise. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - boolean seek(RawFileDescriptor fd, SignedWord position); + boolean seek(RawFileDescriptor fd, long position); /** * Writes data to the current file position and advances the file position. @@ -156,7 +179,7 @@ static RawFileOperationSupport nativeByteOrder() { boolean writeByte(RawFileDescriptor fd, byte data); /** - * Writes a short value in the specified byte ordering to the current file position and advances + * Writes a short value in the specified byte order to the current file position and advances * the file position. * * @return true if the data was written, false otherwise. @@ -165,8 +188,8 @@ static RawFileOperationSupport nativeByteOrder() { boolean writeShort(RawFileDescriptor fd, short data); /** - * Writes a char value in the specified byte ordering to the current file position and advances - * the file position. + * Writes a char value in the specified byte order to the current file position and advances the + * file position. * * @return true if the data was written, false otherwise. */ @@ -174,8 +197,8 @@ static RawFileOperationSupport nativeByteOrder() { boolean writeChar(RawFileDescriptor fd, char data); /** - * Writes an integer value in the specified byte ordering to the current file position and - * advances the file position. + * Writes an integer value in the specified byte order to the current file position and advances + * the file position. * * @return true if the data was written, false otherwise. */ @@ -183,14 +206,32 @@ static RawFileOperationSupport nativeByteOrder() { boolean writeInt(RawFileDescriptor fd, int data); /** - * Writes a long value in the specified byte ordering to the current file position and advances - * the file position. + * Writes a long value in the specified byte order to the current file position and advances the + * file position. * * @return true if the data was written, false otherwise. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) boolean writeLong(RawFileDescriptor fd, long data); + /** + * Writes a float value in the specified byte order to the current file position and advances + * the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + boolean writeFloat(RawFileDescriptor fd, float data); + + /** + * Writes a double value in the specified byte order to the current file position and advances + * the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + boolean writeDouble(RawFileDescriptor fd, double data); + /** * Reads up to bufferSize bytes of data from to the current file position and advances the file * position. @@ -199,7 +240,7 @@ static RawFileOperationSupport nativeByteOrder() { * returns a negative value. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - SignedWord read(RawFileDescriptor fd, Pointer buffer, UnsignedWord bufferSize); + long read(RawFileDescriptor fd, Pointer buffer, UnsignedWord bufferSize); /** * OS-specific signed value that represents a file descriptor. It is OS-specific which values @@ -209,12 +250,16 @@ static RawFileOperationSupport nativeByteOrder() { interface RawFileDescriptor extends WordBase { } - /** - * The file access modes that can be used when opening/creating a file. - */ + enum FileCreationMode { + /** Create the file if it doesn't exist. Fail if it already exists. */ + CREATE, + /** Create the file if it doesn't exist. If it already exists, then truncate the file. */ + CREATE_OR_REPLACE, + } + enum FileAccessMode { READ, READ_WRITE, - WRITE + WRITE, } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java index 46f7cadc0953..0d0a07730d0c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java @@ -45,7 +45,7 @@ protected NativeVMOperation(VMOperationInfo info) { } public void enqueue(NativeVMOperationData data) { - assert data.getNativeVMOperation() == this; + data.setNativeVMOperation(this); VMOperationControl.get().enqueue(data); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java index 637da5dd77e8..8c318480b048 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java @@ -276,6 +276,7 @@ void enqueue(JavaVMOperation operation) { } void enqueue(NativeVMOperationData data) { + assert data.getNativeVMOperation() != null; enqueue(data.getNativeVMOperation(), data); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalMTSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalMTSupport.java index 93dcadaa2851..bb192cc5a5b2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalMTSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalMTSupport.java @@ -24,14 +24,20 @@ */ package com.oracle.svm.core.threadlocal; +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.word.Pointer; import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.heap.UnknownObjectField; -import com.oracle.svm.core.heap.UnknownPrimitiveField; import com.oracle.svm.core.c.NonmovableArray; import com.oracle.svm.core.c.NonmovableArrays; +import com.oracle.svm.core.heap.InstanceReferenceMapDecoder; +import com.oracle.svm.core.heap.ObjectReferenceVisitor; +import com.oracle.svm.core.heap.UnknownObjectField; +import com.oracle.svm.core.heap.UnknownPrimitiveField; public class VMThreadLocalMTSupport { @UnknownPrimitiveField public int vmThreadSize = -1; @@ -42,6 +48,11 @@ public class VMThreadLocalMTSupport { public VMThreadLocalMTSupport() { } + @Fold + public static VMThreadLocalMTSupport singleton() { + return ImageSingletons.lookup(VMThreadLocalMTSupport.class); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public NonmovableArray getThreadLocalsReferenceMap() { assert vmThreadReferenceMapEncoding != null; @@ -55,4 +66,9 @@ public int getThreadLocalsReferenceMapIndex() { assert (int) result == result; return (int) result; } + + public void walk(IsolateThread isolateThread, ObjectReferenceVisitor referenceVisitor) { + NonmovableArray threadRefMapEncoding = NonmovableArrays.fromImageHeap(vmThreadReferenceMapEncoding); + InstanceReferenceMapDecoder.walkOffsetsFromPointer((Pointer) isolateThread, threadRefMapEncoding, vmThreadReferenceMapIndex, referenceVisitor, null); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStream.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStream.java new file mode 100644 index 000000000000..61080d02319b --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStream.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.util.coder; + +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.word.Pointer; +import org.graalvm.word.PointerBase; + +@RawStructure +public interface ByteStream extends PointerBase { + @RawField + Pointer getPosition(); + + @RawField + void setPosition(Pointer value); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStreamAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStreamAccess.java new file mode 100644 index 000000000000..da76ccba9289 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStreamAccess.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.util.coder; + +import org.graalvm.word.Pointer; + +import com.oracle.svm.core.Uninterruptible; + +public class ByteStreamAccess { + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void initialize(ByteStream stream, Pointer data) { + stream.setPosition(data); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/NativeCoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/NativeCoder.java new file mode 100644 index 000000000000..b11eeff2a0aa --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/NativeCoder.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.util.coder; + +import org.graalvm.word.Pointer; + +import com.oracle.svm.core.Uninterruptible; + +/** Uses the native, architecture-specific byte order to access {@link ByteStream} data. */ +public class NativeCoder { + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static byte readByte(ByteStream data) { + Pointer position = data.getPosition(); + byte result = position.readByte(0); + data.setPosition(position.add(Byte.BYTES)); + return result; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static int readInt(ByteStream data) { + Pointer position = data.getPosition(); + int result = position.readInt(0); + data.setPosition(position.add(Integer.BYTES)); + return result; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/Pack200Coder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/Pack200Coder.java new file mode 100644 index 000000000000..ac7cf239ea80 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/Pack200Coder.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.util.coder; + +import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.word.Pointer; + +import com.oracle.svm.core.Uninterruptible; + +/** Read Pack200 encoded values from a {@link ByteStream} or a raw {@link Pointer}. */ +public class Pack200Coder { + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static long readUV(Pointer data) { + ByteStream stream = StackValue.get(ByteStream.class); + ByteStreamAccess.initialize(stream, data); + return readUV(stream); + } + + /** This code is adapted from TypeReader.getUV(). */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static long readUV(ByteStream data) { + Pointer pos = data.getPosition(); + long result = pos.readByte(0) & 0xFF; + pos = pos.add(1); + if (result >= UnsafeArrayTypeWriter.NUM_LOW_CODES) { + long shift = UnsafeArrayTypeWriter.HIGH_WORD_SHIFT; + for (int i = 2;; i++) { + long b = pos.readByte(0) & 0xFF; + pos = pos.add(1); + result += b << shift; + if (b < UnsafeArrayTypeWriter.NUM_LOW_CODES || i == UnsafeArrayTypeWriter.MAX_BYTES) { + break; + } + shift += UnsafeArrayTypeWriter.HIGH_WORD_SHIFT; + } + } + data.setPosition(pos); + return result; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static int readUVAsInt(Pointer data) { + long result = readUV(data); + assert (int) result == result; + return (int) result; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static int readUVAsInt(ByteStream data) { + long result = readUV(data); + assert (int) result == result; + return (int) result; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java new file mode 100644 index 000000000000..7b19650c7f32 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.heap; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; +import org.graalvm.compiler.core.common.util.TypeConversion; +import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.impl.HeapDumpSupport; + +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.heap.dump.HProfType; +import com.oracle.svm.core.heap.dump.HeapDumpMetadata; +import com.oracle.svm.core.heap.dump.HeapDumpSupportImpl; +import com.oracle.svm.core.heap.dump.HeapDumpWriter; +import com.oracle.svm.core.heapdump.HeapDumpUtils; +import com.oracle.svm.core.heapdump.HeapDumpWriterImpl; +import com.oracle.svm.core.meta.SharedField; +import com.oracle.svm.core.meta.SharedType; +import com.oracle.svm.core.os.RawFileOperationSupport; +import com.oracle.svm.core.util.ByteArrayReader; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.FeatureImpl.AfterCompilationAccessImpl; + +import jdk.vm.ci.meta.ResolvedJavaField; + +/** + * Heap dumping on Native Image needs some extra metadata about all the classes and fields that are + * present in the image. The necessary information is encoded as binary data at image build time + * (see {@link #encodeMetadata}}). When the heap dumping is triggered at run-time, the metadata is + * decoded on the fly (see {@link com.oracle.svm.core.heap.dump.HeapDumpMetadata}) and used for + * writing the heap dump (see {@link HeapDumpWriter}). + */ +@AutomaticallyRegisteredFeature +public class HeapDumpFeature implements InternalFeature { + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + /* + * Include the feature unconditionally on Linux and macOS. The code and all its data are + * only present in the final image if the heap dumping infrastructure is actually called by + * any code (e.g., VMRuntime.dumpHeap(...) or --enable-monitoring=heapdump). + */ + return Platform.includedIn(Platform.LINUX.class) || Platform.includedIn(Platform.DARWIN.class); + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + if (useLegacyImplementation()) { + ImageSingletons.add(HeapDumpSupport.class, new com.oracle.svm.core.heapdump.HeapDumpSupportImpl()); + ImageSingletons.add(HeapDumpUtils.class, new HeapDumpUtils()); + ImageSingletons.add(com.oracle.svm.core.heapdump.HeapDumpWriter.class, new HeapDumpWriterImpl()); + } else { + HeapDumpMetadata metadata = new HeapDumpMetadata(); + ImageSingletons.add(HeapDumpMetadata.class, metadata); + ImageSingletons.add(HeapDumpSupport.class, new HeapDumpSupportImpl(metadata)); + } + } + + public static boolean useLegacyImplementation() { + /* See GR-44538. */ + return !RawFileOperationSupport.isPresent(); + } + + @Override + public void afterCompilation(Feature.AfterCompilationAccess access) { + AfterCompilationAccessImpl accessImpl = (AfterCompilationAccessImpl) access; + if (useLegacyImplementation()) { + byte[] fieldMap = HeapDumpHostedUtils.dumpFieldsMap(accessImpl.getTypes()); + + HeapDumpUtils.getHeapDumpUtils().setFieldsMap(fieldMap); + access.registerAsImmutable(fieldMap); + } else { + byte[] metadata = encodeMetadata(accessImpl.getTypes()); + HeapDumpMetadata.singleton().setData(metadata); + access.registerAsImmutable(metadata); + } + } + + /** + * This method writes the metadata that is needed for heap dumping into one large byte[] (see + * {@link com.oracle.svm.core.heap.dump.HeapDumpMetadata} for more details). + */ + private static byte[] encodeMetadata(Collection types) { + int maxTypeId = types.stream().mapToInt(t -> t.getHub().getTypeID()).max().orElse(0); + assert maxTypeId > 0; + + UnsafeArrayTypeWriter output = UnsafeArrayTypeWriter.create(ByteArrayReader.supportsUnalignedMemoryAccess()); + encodeMetadata(output, types, maxTypeId); + + int length = TypeConversion.asS4(output.getBytesWritten()); + return output.toArray(new byte[length]); + } + + private static void encodeMetadata(UnsafeArrayTypeWriter output, Collection types, int maxTypeId) { + /* Write header. */ + long totalFieldCountOffset = output.getBytesWritten(); + output.putS4(0); // total field count (patched later on) + long classCountOffset = output.getBytesWritten(); + output.putS4(0); // class count (patched later on) + long fieldNameCountOffset = output.getBytesWritten(); + output.putS4(0); // field name count (patched later on) + + output.putUV(maxTypeId); + + /* Write the class and field information. */ + int totalFieldCount = 0; + int classCount = 0; + EconomicMap fieldNames = EconomicMap.create(); + for (SharedType type : types) { + if (type.isInstanceClass()) { + ArrayList instanceFields = collectFields(type.getInstanceFields(false)); + ArrayList staticFields = collectFields(type.getStaticFields()); + if (instanceFields.size() == 0 && staticFields.size() == 0) { + continue; + } + + classCount++; + totalFieldCount += instanceFields.size() + staticFields.size(); + + output.putUV(type.getHub().getTypeID()); + output.putUV(instanceFields.size()); + output.putUV(staticFields.size()); + + /* Write direct instance fields. */ + for (SharedField field : instanceFields) { + encodeField(field, output, fieldNames); + } + + /* Write static fields. */ + for (SharedField field : staticFields) { + encodeField(field, output, fieldNames); + } + } + } + + /* Patch the header. */ + output.patchS4(totalFieldCount, totalFieldCountOffset); + output.patchS4(classCount, classCountOffset); + output.patchS4(fieldNames.size(), fieldNameCountOffset); + + /* Write the field names. */ + int index = 0; + MapCursor cursor = fieldNames.getEntries(); + while (cursor.advance()) { + assert cursor.getValue() == index; + byte[] utf8 = cursor.getKey().getBytes(StandardCharsets.UTF_8); + output.putUV(utf8.length); + for (byte b : utf8) { + output.putS1(b); + } + index++; + } + } + + private static ArrayList collectFields(ResolvedJavaField[] input) { + /* Collect all fields that have a location. */ + ArrayList result = new ArrayList<>(); + for (ResolvedJavaField f : input) { + if (f instanceof SharedField field) { + if (field.getLocation() >= 0) { + result.add(field); + } + } + } + + /* Sort fields by their location. */ + result.sort(Comparator.comparingInt(SharedField::getLocation)); + return result; + } + + private static void encodeField(SharedField field, UnsafeArrayTypeWriter output, EconomicMap fieldNames) { + int location = field.getLocation(); + assert location >= 0; + output.putU1(getType(field).ordinal()); + output.putUV(addFieldName(field.getName(), fieldNames)); + output.putUV(location); + } + + private static int addFieldName(String fieldName, EconomicMap fieldNames) { + Integer fieldNameIndex = fieldNames.get(fieldName); + if (fieldNameIndex != null) { + return fieldNameIndex; + } + + int result = fieldNames.size(); + fieldNames.put(fieldName, result); + return result; + } + + private static HProfType getType(SharedField field) { + return switch (field.getStorageKind()) { + case Object -> HProfType.NORMAL_OBJECT; + case Boolean -> HProfType.BOOLEAN; + case Char -> HProfType.CHAR; + case Float -> HProfType.FLOAT; + case Double -> HProfType.DOUBLE; + case Byte -> HProfType.BYTE; + case Short -> HProfType.SHORT; + case Int -> HProfType.INT; + case Long -> HProfType.LONG; + default -> throw VMError.shouldNotReachHere("Unexpected storage kind."); + }; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFieldsMapFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpHostedUtils.java similarity index 87% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFieldsMapFeature.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpHostedUtils.java index aaff568446ad..32f4f7320eac 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFieldsMapFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpHostedUtils.java @@ -31,19 +31,15 @@ import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.hosted.Feature; -import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.heapdump.HeapDumpUtils; import com.oracle.svm.core.meta.SharedField; import com.oracle.svm.core.meta.SharedType; -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.util.ByteArrayReader; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.FeatureImpl.AfterCompilationAccessImpl; import jdk.vm.ci.meta.ResolvedJavaField; +/** Legacy implementation, only used by other legacy code (see GR-44538). */ class HeapDumpHostedUtils { @Platforms(Platform.HOSTED_ONLY.class) @@ -143,20 +139,3 @@ private static void writeString(UnsafeArrayTypeWriter writeBuffer, String name) } } } - -@AutomaticallyRegisteredFeature -class HeapDumpFieldsMapFeature implements InternalFeature { - - /** - * Write out fields info and their offsets. - */ - @Override - @Platforms(Platform.HOSTED_ONLY.class) - public void afterCompilation(Feature.AfterCompilationAccess access) { - AfterCompilationAccessImpl accessImpl = (AfterCompilationAccessImpl) access; - byte[] fieldMap = HeapDumpHostedUtils.dumpFieldsMap(accessImpl.getTypes()); - - HeapDumpUtils.getHeapDumpUtils().setFieldsMap(fieldMap); - access.registerAsImmutable(fieldMap); - } -}