From 7ca5fcdf70acd182ef7a6572497ab037341803f4 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Tue, 11 Feb 2025 14:44:21 +0100 Subject: [PATCH 1/5] Use VMOperation.guaranteeInProgressAtSafepoint() instead of VMOperation.guaranteeInProgress(); --- .../oracle/svm/core/genscavenge/GCImpl.java | 2 +- .../genscavenge/ThreadLocalAllocation.java | 2 +- .../com/oracle/svm/core/Uninterruptible.java | 13 +-------- .../oracle/svm/core/deopt/Deoptimizer.java | 9 +++---- .../oracle/svm/core/thread/VMOperation.java | 27 ++++++++++++------- 5 files changed, 24 insertions(+), 29 deletions(-) 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 cb1b06eb987d..0de98cefb465 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 @@ -31,7 +31,6 @@ import java.lang.ref.Reference; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; @@ -105,6 +104,7 @@ import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.word.Word; /** * Garbage collector (incremental or complete) for {@link HeapImpl}. diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java index c725b3e80395..2fbf977c55f7 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java @@ -455,7 +455,7 @@ private static void guaranteeZeroed(Pointer memory, UnsignedWord size) { } static void disableAndFlushForAllThreads() { - VMOperation.guaranteeInProgress("ThreadLocalAllocation.disableAndFlushForAllThreads"); + VMOperation.guaranteeInProgressAtSafepoint("ThreadLocalAllocation.disableAndFlushForAllThreads"); for (IsolateThread vmThread = VMThreads.firstThread(); vmThread.isNonNull(); vmThread = VMThreads.nextThread(vmThread)) { disableAndFlushForThread(vmThread); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Uninterruptible.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Uninterruptible.java index 80f7139777aa..fe34f9b48c56 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Uninterruptible.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Uninterruptible.java @@ -32,7 +32,6 @@ import java.lang.reflect.Executable; import java.util.Objects; -import jdk.graal.compiler.api.replacements.Fold; import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -41,10 +40,10 @@ import org.graalvm.word.WordBase; import com.oracle.svm.core.snippets.KnownIntrinsics; -import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ReflectionUtil; +import jdk.graal.compiler.api.replacements.Fold; import jdk.vm.ci.meta.ResolvedJavaMethod; /** @@ -88,16 +87,6 @@ * is so simple that it can always be inlined into interruptible code, the method can be annotated * with {@link #mayBeInlined "mayBeInlined = true"}. Uninterruptible methods can always be inlined * into other uninterruptible methods. - *
- * Some alternatives to annotation: - *
Code called from snippets
- *
Snippet code is always inlined and runs to completion. Methods called only from snippets need - * not be annotated.
- *
Code called from VMOperations
- *
VMOperation code runs single-threaded to completion. Public entry points that should only run - * in VMOperations can be guarded with a call to - * {@linkplain VMOperation#guaranteeInProgress(String)}.
- *
*/ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java index 2d4d16f722aa..ea236826b53f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java @@ -467,7 +467,7 @@ public static void deoptimizeInRange(CodePointer fromIp, CodePointer toIp, boole "Note that we could start the stack frame also further down the stack, because VM operation frames never need deoptimization. " + "But we don't store stack frame information for the first frame we would need to process.") private static void deoptimizeInRangeOperation(CodePointer fromIp, CodePointer toIp, boolean deoptAll) { - VMOperation.guaranteeInProgress("Deoptimizer.deoptimizeInRangeOperation, but not in VMOperation."); + VMOperation.guaranteeInProgressAtSafepoint("Deoptimizer.deoptimizeInRangeOperation, but not in VMOperation."); /* Handle my own thread specially, because I do not have a JavaFrameAnchor. */ Pointer sp = KnownIntrinsics.readCallerStackPointer(); @@ -573,7 +573,6 @@ private static class DeoptimizeFrameOperation extends JavaVMOperation { @Override protected void operate() { - VMOperation.guaranteeInProgress("doDeoptimizeFrame"); CodePointer ip = FrameAccess.singleton().readReturnAddress(targetThread, sourceSp); deoptimizeFrame(targetThread, sourceSp, ip, ignoreNonDeoptimizable, speculation, deoptEagerly); } @@ -997,7 +996,7 @@ static int savedBasePointerSize() { * @param pc A code address inside the source method (= the method to deoptimize) */ public void deoptSourceFrameLazily(CodePointer pc, boolean ignoreNonDeoptimizable) { - assert VMOperation.isInProgress(); + assert VMOperation.isInProgressAtSafepoint(); if (!Options.LazyDeoptimization.getValue()) { deoptSourceFrameEagerly(pc, ignoreNonDeoptimizable); return; @@ -1043,7 +1042,7 @@ public DeoptimizedFrame deoptSourceFrameEagerly(CodePointer pc, boolean ignoreNo @Uninterruptible(reason = "Prevent stack walks from seeing an inconsistent stack.") private static void installLazyDeoptStubReturnAddress(boolean returnValueIsObject, Pointer sourceSp, IsolateThread targetThread) { assert Options.LazyDeoptimization.getValue(); - assert VMOperation.isInProgress(); + assert VMOperation.isInProgressAtSafepoint(); CodePointer oldReturnAddress = FrameAccess.singleton().readReturnAddress(targetThread, sourceSp); // Replace the return address to the deoptimized method with a pointer to the lazyDeoptStub. @@ -1059,7 +1058,7 @@ private static void installLazyDeoptStubReturnAddress(boolean returnValueIsObjec @Uninterruptible(reason = "Prevent stack walks from seeing an inconsistent stack.") private static void uninstallLazyDeoptStubReturnAddress(Pointer sourceSp, IsolateThread thread) { assert Options.LazyDeoptimization.getValue(); - assert VMOperation.isInProgress(); + assert VMOperation.isInProgressAtSafepoint(); CodePointer oldReturnAddress = sourceSp.readWord(0); assert oldReturnAddress.isNonNull(); // Clear the old return address from the deopt slot diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperation.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperation.java index 6f36bec40a25..dc90fb95afaa 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperation.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperation.java @@ -117,7 +117,8 @@ protected final void execute(NativeVMOperationData data) { } /** - * Returns true if the current thread is currently executing a VM operation. + * Returns true if the current thread is in the middle of executing a VM operation. Note that + * this includes VM operations that do not need a safepoint. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isInProgress() { @@ -126,7 +127,7 @@ public static boolean isInProgress() { } /** - * Returns true if the current thread is currently executing a VM operation that causes a + * Returns true if the current thread is in the middle of executing a VM operation that needs a * safepoint. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -135,31 +136,36 @@ public static boolean isInProgressAtSafepoint() { return isInProgress(inProgress) && inProgress.operation.getCausesSafepoint(); } + /** + * Returns true if the current thread is in the middle of executing a VM operation. Note that + * this includes VM operations that do not need a safepoint. + */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) static boolean isInProgress(OpInProgress inProgress) { return inProgress.getExecutingThread() == CurrentIsolate.getCurrentThread(); } + /** Returns true if the current thread is in the middle of performing a garbage collection. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isGCInProgress() { VMOperation op = VMOperationControl.get().getInProgress().getOperation(); return op != null && op.isGC(); } - /** Check that there is a VMOperation in progress. */ - public static void guaranteeInProgress(String message) { - if (!isInProgress()) { - throw VMError.shouldNotReachHere(message); - } - } - - /** Check that there is not a VMOperation in progress. */ + /** + * Throws a fatal error if the current thread is in the middle of executing a VM operation. Note + * that this includes VM operations that do not need a safepoint. + */ public static void guaranteeNotInProgress(String message) { if (isInProgress()) { throw VMError.shouldNotReachHere(message); } } + /** + * Verifies that the current thread is in the middle of executing a VM operation that needs a + * safepoint. + */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void guaranteeInProgressAtSafepoint(String message) { if (!isInProgressAtSafepoint()) { @@ -167,6 +173,7 @@ public static void guaranteeInProgressAtSafepoint(String message) { } } + /** Verifies that the current thread is in the middle of performing a garbage collection. */ public static void guaranteeGCInProgress(String message) { if (!isGCInProgress()) { throw VMError.shouldNotReachHere(message); From fb5d21068d78a39bf8f7709fe3bdae04850fe3db Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Thu, 13 Feb 2025 17:38:05 +0100 Subject: [PATCH 2/5] Small fixes regarding @Uninterruptible. --- .../oracle/svm/core/posix/PosixPlatformTimeUtils.java | 11 +---------- .../svm/core/windows/WindowsPlatformTimeUtils.java | 11 ++++++++--- .../src/com/oracle/svm/core/CPUFeatureAccess.java | 4 +++- .../com/oracle/svm/core/util/PlatformTimeUtils.java | 11 +++++++++++ 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixPlatformTimeUtils.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixPlatformTimeUtils.java index 155b27115fd5..13e2a7f7e906 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixPlatformTimeUtils.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixPlatformTimeUtils.java @@ -42,15 +42,6 @@ public SecondsNanos javaTimeSystemUTC() { Time.timespec ts = StackValue.get(Time.timespec.class); int status = PosixUtils.clock_gettime(Time.CLOCK_REALTIME(), ts); PosixUtils.checkStatusIs0(status, "javaTimeSystemUTC: clock_gettime(CLOCK_REALTIME) failed."); - return allocateSecondsNanos0(ts.tv_sec(), ts.tv_nsec()); - } - - @Uninterruptible(reason = "Wrap the now safe call to interruptibly allocate a SecondsNanos object.", calleeMustBe = false) - private static SecondsNanos allocateSecondsNanos0(long seconds, long nanos) { - return allocateSecondsNanos(seconds, nanos); - } - - private static SecondsNanos allocateSecondsNanos(long seconds, long nanos) { - return new SecondsNanos(seconds, nanos); + return allocateSecondsNanosInterruptibly(ts.tv_sec(), ts.tv_nsec()); } } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformTimeUtils.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformTimeUtils.java index 15dca5f55343..ab88c1c91942 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformTimeUtils.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformTimeUtils.java @@ -24,16 +24,19 @@ */ package com.oracle.svm.core.windows; +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; import static com.oracle.svm.core.windows.headers.SysinfoAPI.GetSystemTimeAsFileTime; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; +import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.core.util.PlatformTimeUtils; import com.oracle.svm.core.windows.headers.WinBase.FILETIME; +import jdk.graal.compiler.word.Word; + @AutomaticallyRegisteredImageSingleton(PlatformTimeUtils.class) public final class WindowsPlatformTimeUtils extends PlatformTimeUtils { @@ -41,12 +44,14 @@ public final class WindowsPlatformTimeUtils extends PlatformTimeUtils { private static final long OFFSET = 116444736000000000L; @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+3/src/hotspot/os/windows/os_windows.cpp#L1153-L1155") + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private static long offset() { return OFFSET; } /* Returns time ticks in (10th of micro seconds) */ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+3/src/hotspot/os/windows/os_windows.cpp#L1158-L1161") + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private static long windowsToTimeTicks(FILETIME wt) { long a = Word.unsigned(wt.dwHighDateTime()).shiftLeft(32).or(Word.unsigned(wt.dwLowDateTime())).rawValue(); return (a - offset()); @@ -54,13 +59,13 @@ private static long windowsToTimeTicks(FILETIME wt) { @Override @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+3/src/hotspot/os/windows/os_windows.cpp#L1198-L1205") + @Uninterruptible(reason = "Must not migrate platform threads when executing on a virtual thread.") public SecondsNanos javaTimeSystemUTC() { FILETIME wt = StackValue.get(FILETIME.class); GetSystemTimeAsFileTime(wt); long ticks = windowsToTimeTicks(wt); // 10th of micros long secs = ticks / 10000000L; // 10000 * 1000 long nanos = (ticks - (secs * 10000000L)) * 100L; - return new SecondsNanos(secs, nanos); + return allocateSecondsNanosInterruptibly(secs, nanos); } - } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/CPUFeatureAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/CPUFeatureAccess.java index 6f9ba44571ea..37b52e9f3629 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/CPUFeatureAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/CPUFeatureAccess.java @@ -26,9 +26,9 @@ import java.util.EnumSet; -import jdk.graal.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; +import jdk.graal.compiler.api.replacements.Fold; import jdk.vm.ci.code.Architecture; public interface CPUFeatureAccess { @@ -37,8 +37,10 @@ static CPUFeatureAccess singleton() { return ImageSingletons.lookup(CPUFeatureAccess.class); } + @Uninterruptible(reason = "Thread state not set up yet.") int verifyHostSupportsArchitectureEarly(); + @Uninterruptible(reason = "Thread state not set up yet.") void verifyHostSupportsArchitectureEarlyOrExit(); void enableFeatures(Architecture architecture); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/PlatformTimeUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/PlatformTimeUtils.java index bc1a30bf8567..9dea252eedc1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/PlatformTimeUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/PlatformTimeUtils.java @@ -28,6 +28,8 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import com.oracle.svm.core.Uninterruptible; + import jdk.graal.compiler.api.replacements.Fold; /** @@ -49,6 +51,15 @@ protected PlatformTimeUtils() { public record SecondsNanos(long seconds, long nanos) { } + @Uninterruptible(reason = "Wrap the now safe call to interruptibly allocate a SecondsNanos object.", calleeMustBe = false) + protected static SecondsNanos allocateSecondsNanosInterruptibly(long seconds, long nanos) { + return allocateSecondsNanos0(seconds, nanos); + } + + private static SecondsNanos allocateSecondsNanos0(long seconds, long nanos) { + return new SecondsNanos(seconds, nanos); + } + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+5/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp#L38-L52") public long nanosNow() { // Use same clock source as Instant.now() to ensure From 369d8a741b45e2ca19cd0cdbef3864335b8e0bc3 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Thu, 13 Feb 2025 17:47:28 +0100 Subject: [PATCH 3/5] Add pinned object count to serial GC chunks. --- .../nativeimage/impl/PinnedObjectSupport.java | 4 +- .../oracle/svm/core/genscavenge/GCImpl.java | 69 ++----- .../svm/core/genscavenge/HeapChunk.java | 7 + .../core/genscavenge/HeapChunkLogging.java | 2 +- .../oracle/svm/core/genscavenge/HeapImpl.java | 9 - .../core/genscavenge/PinnedObjectImpl.java | 156 -------------- .../genscavenge/PinnedObjectSupportImpl.java | 65 ++++++ .../graal/GenScavengeGCFeature.java | 3 + .../heap/AbstractPinnedObjectSupport.java | 192 ++++++++++++++++++ .../oracle/svm/core/thread/VMOperation.java | 3 + 10 files changed, 294 insertions(+), 216 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/PinnedObjectImpl.java create mode 100644 substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/PinnedObjectSupportImpl.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/AbstractPinnedObjectSupport.java diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/PinnedObjectSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/PinnedObjectSupport.java index 6e645a78e9ec..f035aa4eb57d 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/PinnedObjectSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/PinnedObjectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -44,6 +44,4 @@ public interface PinnedObjectSupport { PinnedObject create(Object object); - - boolean isPinned(Object object); } 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 0de98cefb465..4efa8d16b213 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 @@ -66,9 +66,12 @@ import com.oracle.svm.core.genscavenge.UnalignedHeapChunk.UnalignedHeader; import com.oracle.svm.core.genscavenge.remset.RememberedSet; import com.oracle.svm.core.graal.RuntimeCompilation; +import com.oracle.svm.core.heap.AbstractPinnedObjectSupport; +import com.oracle.svm.core.heap.AbstractPinnedObjectSupport.PinnedObjectImpl; import com.oracle.svm.core.heap.CodeReferenceMapDecoder; import com.oracle.svm.core.heap.GC; import com.oracle.svm.core.heap.GCCause; +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; @@ -754,18 +757,15 @@ private void promoteChunksWithPinnedObjects() { Timer promotePinnedObjectsTimer = timers.promotePinnedObjects.open(); try { // Remove closed pinned objects from the global list. This code needs to use write - // barriers as the PinnedObjectImpls are a linked list and we don't know in which + // barriers as the PinnedObjectImpls are a linked list, and we don't know in which // generation each individual PinnedObjectImpl lives. So, the card table will be // modified. - PinnedObjectImpl pinnedObjects = removeClosedPinnedObjects(PinnedObjectImpl.getPinnedObjects()); - PinnedObjectImpl.setPinnedObjects(pinnedObjects); + PinnedObjectImpl cur = AbstractPinnedObjectSupport.singleton().removeClosedObjectsAndGetFirstOpenObject(); // Promote all chunks that contain pinned objects. The card table of the promoted chunks // will be cleaned. - PinnedObjectImpl cur = pinnedObjects; while (cur != null) { - assert cur.isOpen(); - promotePinnedObject(cur); + promotePinnedObject(cur.getObject()); cur = cur.getNext(); } } finally { @@ -773,32 +773,6 @@ private void promoteChunksWithPinnedObjects() { } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static PinnedObjectImpl removeClosedPinnedObjects(PinnedObjectImpl list) { - PinnedObjectImpl firstOpen = null; - PinnedObjectImpl lastOpen = null; - - PinnedObjectImpl cur = list; - while (cur != null) { - if (cur.isOpen()) { - if (firstOpen == null) { - assert lastOpen == null; - firstOpen = cur; - lastOpen = cur; - } else { - lastOpen.setNext(cur); - lastOpen = cur; - } - } - cur = cur.getNext(); - } - - if (lastOpen != null) { - lastOpen.setNext(null); - } - return firstOpen; - } - @NeverInline("Starting a stack walk in the caller frame. " + "Note that we could start the stack frame also further down the stack, because GC stack frames must not access any objects that are processed by the GC. " + "But we don't store stack frame information for the first frame we would need to process.") @@ -1082,25 +1056,26 @@ private static Header getChunk(Object obj, boolean isAligned) { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private void promotePinnedObject(PinnedObjectImpl pinned) { + private void promotePinnedObject(Object pinned) { + assert pinned != null; + assert !Heap.getHeap().isInImageHeap(pinned); + assert HeapChunk.getEnclosingHeapChunk(pinned).getPinnedObjectCount() > 0; + HeapImpl heap = HeapImpl.getHeapImpl(); - Object referent = pinned.getObject(); - if (referent != null && !heap.isInImageHeap(referent)) { - boolean isAligned = ObjectHeaderImpl.isAlignedObject(referent); - Header originalChunk = getChunk(referent, isAligned); - Space originalSpace = HeapChunk.getSpace(originalChunk); - if (originalSpace.isFromSpace() || (originalSpace.isCompactingOldSpace() && completeCollection)) { - boolean promoted = false; - if (!completeCollection && originalSpace.getNextAgeForPromotion() < policy.getTenuringAge()) { - promoted = heap.getYoungGeneration().promotePinnedObject(referent, originalChunk, isAligned, originalSpace); - if (!promoted) { - accounting.onSurvivorOverflowed(); - } - } + boolean isAligned = ObjectHeaderImpl.isAlignedObject(pinned); + Header originalChunk = getChunk(pinned, isAligned); + Space originalSpace = HeapChunk.getSpace(originalChunk); + if (originalSpace.isFromSpace() || (originalSpace.isCompactingOldSpace() && completeCollection)) { + boolean promoted = false; + if (!completeCollection && originalSpace.getNextAgeForPromotion() < policy.getTenuringAge()) { + promoted = heap.getYoungGeneration().promotePinnedObject(pinned, originalChunk, isAligned, originalSpace); if (!promoted) { - heap.getOldGeneration().promotePinnedObject(referent, originalChunk, isAligned, originalSpace); + accounting.onSurvivorOverflowed(); } } + if (!promoted) { + heap.getOldGeneration().promotePinnedObject(pinned, originalChunk, isAligned, originalSpace); + } } } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunk.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunk.java index ab6b889ef5b6..e8d2bfc102ea 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunk.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunk.java @@ -27,6 +27,7 @@ import java.util.function.IntUnaryOperator; import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawFieldAddress; import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.nativeimage.c.struct.UniqueLocationIdentity; import org.graalvm.word.ComparableWord; @@ -166,6 +167,12 @@ public interface Header> extends HeaderPadding { @RawField void setIdentityHashSalt(UnsignedWord value, LocationIdentity identity); + + @RawField + int getPinnedObjectCount(); + + @RawFieldAddress + Pointer addressOfPinnedObjectCount(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkLogging.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkLogging.java index bebcbc09af16..c3ebc9db1a45 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkLogging.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkLogging.java @@ -85,7 +85,7 @@ private static void logChunk(Log log, HeapChunk.Header chunk, Pointer bottom, log.string("|").unsigned(usedPercent, 3, RIGHT_ALIGN).string("%"); log.string("|").string(shortSpaceName, 3, RIGHT_ALIGN); log.string("|").string(isAligned ? "A" : "U"); - log.string("|").string(isToSpace ? "T" : ""); + log.string("|").string(isToSpace ? "T" : " "); log.newline(); } } 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 17f57d86f5a9..f5245caa8afa 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 @@ -70,7 +70,6 @@ import com.oracle.svm.core.heap.VMOperationInfos; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; -import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicReference; import com.oracle.svm.core.jfr.JfrTicks; import com.oracle.svm.core.jfr.events.SystemGCEvent; import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; @@ -124,9 +123,6 @@ public final class HeapImpl extends Heap { /** Total number of times when threads waiting for a pending reference list were interrupted. */ private volatile long refListWaiterWakeUpCounter; - /** Head of the linked list of object pins. */ - private final AtomicReference pinHead = new AtomicReference<>(); - /** A cached list of all the classes, if someone asks for it. */ private List> classList; @@ -279,11 +275,6 @@ public OldGeneration getOldGeneration() { return oldGeneration; } - @Fold - AtomicReference getPinHead() { - return pinHead; - } - void logUsage(Log log) { youngGeneration.logUsage(log); oldGeneration.logUsage(log); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/PinnedObjectImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/PinnedObjectImpl.java deleted file mode 100644 index c7f887109577..000000000000 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/PinnedObjectImpl.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2013, 2017, 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 jdk.graal.compiler.word.Word; -import org.graalvm.nativeimage.PinnedObject; -import org.graalvm.nativeimage.impl.PinnedObjectSupport; -import org.graalvm.word.Pointer; -import org.graalvm.word.PointerBase; -import org.graalvm.word.UnsignedWord; - -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; -import com.oracle.svm.core.heap.ObjectHeader; -import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.hub.LayoutEncoding; -import com.oracle.svm.core.jdk.UninterruptibleUtils; -import com.oracle.svm.core.thread.VMOperation; - -/** Support for pinning objects to a memory address with {@link PinnedObject}. */ -final class PinnedObjectImpl implements PinnedObject { - - @AutomaticallyRegisteredImageSingleton(value = PinnedObjectSupport.class, onlyWith = UseSerialOrEpsilonGC.class) - static class PinnedObjectSupportImpl implements PinnedObjectSupport { - @Override - public PinnedObject create(Object object) { - PinnedObjectImpl result = new PinnedObjectImpl(object); - PinnedObjectImpl.pushPinnedObject(result); - return result; - } - - @Override - public boolean isPinned(Object object) { - PinnedObjectImpl pin = HeapImpl.getHeapImpl().getPinHead().get(); - while (pin != null) { - if (pin.open && pin.referent == object) { - return true; - } - pin = pin.next; - } - return false; - } - } - - static void pushPinnedObject(PinnedObjectImpl newHead) { - // To avoid ABA problems, the application may only push data. All other operations may only - // be executed by the GC. - HeapImpl heap = HeapImpl.getHeapImpl(); - UninterruptibleUtils.AtomicReference pinHead = heap.getPinHead(); - PinnedObjectImpl sampleHead; - do { - sampleHead = pinHead.get(); - newHead.next = sampleHead; - } while (!pinHead.compareAndSet(sampleHead, newHead)); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - static PinnedObjectImpl getPinnedObjects() { - assert VMOperation.isGCInProgress(); - UninterruptibleUtils.AtomicReference pinHead = HeapImpl.getHeapImpl().getPinHead(); - return pinHead.get(); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - static void setPinnedObjects(PinnedObjectImpl list) { - assert VMOperation.isGCInProgress(); - UninterruptibleUtils.AtomicReference pinHead = HeapImpl.getHeapImpl().getPinHead(); - pinHead.set(list); - } - - private final Object referent; - - private volatile boolean open = true; - - /** Successor on the singly-linked list maintained by {@linkplain #pushPinnedObject}. */ - private PinnedObjectImpl next; - - PinnedObjectImpl(Object object) { - this.referent = object; - } - - @Override - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public void close() { - assert open : "Should not call close() on a closed PinnedObject."; - open = false; - } - - @Override - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public Object getObject() { - assert open : "Should not call getObject() on a closed PinnedObject."; - return referent; - } - - @Override - public Pointer addressOfObject() { - if (!SubstrateOptions.PinnedObjectAddressing.getValue()) { - throw new UnsupportedOperationException("Pinned object addressing has been disabled."); - } - assert open : "Should not call addressOfObject() on a closed PinnedObject."; - return Word.objectToUntrackedPointer(referent); - } - - @Override - @SuppressWarnings("unchecked") - public T addressOfArrayElement(int index) { - if (referent == null) { - throw new NullPointerException("PinnedObject is missing a referent"); - } - DynamicHub hub = ObjectHeader.readDynamicHubFromObject(referent); - UnsignedWord offsetOfArrayElement = LayoutEncoding.getArrayElementOffset(hub.getLayoutEncoding(), index); - return (T) addressOfObject().add(offsetOfArrayElement); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public boolean isOpen() { - return open; - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public PinnedObjectImpl getNext() { - return next; - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - void setNext(PinnedObjectImpl value) { - // Avoid useless writes as those would dirty the card table unnecessarily. - if (value != next) { - this.next = value; - } - } -} diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/PinnedObjectSupportImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/PinnedObjectSupportImpl.java new file mode 100644 index 000000000000..456a8916be3d --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/PinnedObjectSupportImpl.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2013, 2025, 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.PinnedObject; +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.AbstractPinnedObjectSupport; + +import jdk.graal.compiler.nodes.NamedLocationIdentity; + +/** Support for pinning objects to a memory address with {@link PinnedObject}. */ +public final class PinnedObjectSupportImpl extends AbstractPinnedObjectSupport { + @Platforms(Platform.HOSTED_ONLY.class) + public PinnedObjectSupportImpl() { + } + + @Override + @Uninterruptible(reason = "Ensure that pinned object counts and PinnedObjects are consistent.", callerMustBe = true) + protected void pinObject(Object object) { + modifyPinnedObjectCount(object, 1); + } + + @Override + @Uninterruptible(reason = "Ensure that pinned object counts and PinnedObjects are consistent.", callerMustBe = true) + protected void unpinObject(Object object) { + modifyPinnedObjectCount(object, -1); + } + + @Uninterruptible(reason = "Ensure that pinned object counts and PinnedObjects are consistent.", callerMustBe = true) + private static void modifyPinnedObjectCount(Object object, int delta) { + Pointer pinnedObjectCount = HeapChunk.getEnclosingHeapChunk(object).addressOfPinnedObjectCount(); + int oldValue; + do { + oldValue = pinnedObjectCount.readInt(0); + } while (!pinnedObjectCount.logicCompareAndSwapInt(0, oldValue, oldValue + delta, NamedLocationIdentity.OFF_HEAP_LOCATION)); + + assert oldValue < Integer.MAX_VALUE; + } +} diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeGCFeature.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeGCFeature.java index 0fcee811d5d4..cd185c767764 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeGCFeature.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeGCFeature.java @@ -30,6 +30,7 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.impl.PinnedObjectSupport; import com.oracle.svm.core.GCRelatedMXBeans; import com.oracle.svm.core.SubstrateGCOptions; @@ -42,6 +43,7 @@ import com.oracle.svm.core.genscavenge.HeapImpl; import com.oracle.svm.core.genscavenge.HeapVerifier; import com.oracle.svm.core.genscavenge.ImageHeapInfo; +import com.oracle.svm.core.genscavenge.PinnedObjectSupportImpl; import com.oracle.svm.core.genscavenge.SerialGCOptions; import com.oracle.svm.core.genscavenge.jvmstat.EpsilonGCPerfData; import com.oracle.svm.core.genscavenge.jvmstat.SerialGCPerfData; @@ -97,6 +99,7 @@ public void duringSetup(DuringSetupAccess access) { ImageSingletons.add(Heap.class, new HeapImpl()); ImageSingletons.add(ImageHeapInfo.class, new ImageHeapInfo()); ImageSingletons.add(GCAllocationSupport.class, new GenScavengeAllocationSupport()); + ImageSingletons.add(PinnedObjectSupport.class, new PinnedObjectSupportImpl()); if (ImageSingletons.contains(PerfManager.class)) { ImageSingletons.lookup(PerfManager.class).register(createPerfData()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/AbstractPinnedObjectSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/AbstractPinnedObjectSupport.java new file mode 100644 index 000000000000..89e069965848 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/AbstractPinnedObjectSupport.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2013, 2025, 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; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.PinnedObject; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.PinnedObjectSupport; +import org.graalvm.word.Pointer; +import org.graalvm.word.PointerBase; +import org.graalvm.word.UnsignedWord; + +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.LayoutEncoding; +import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicReference; +import com.oracle.svm.core.thread.VMOperation; + +import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.word.Word; + +public abstract class AbstractPinnedObjectSupport implements PinnedObjectSupport { + private final AtomicReference pinnedObjects = new AtomicReference<>(); + + @Platforms(Platform.HOSTED_ONLY.class) + public AbstractPinnedObjectSupport() { + } + + @Fold + public static AbstractPinnedObjectSupport singleton() { + return (AbstractPinnedObjectSupport) ImageSingletons.lookup(PinnedObjectSupport.class); + } + + @Override + public PinnedObject create(Object object) { + PinnedObjectImpl result = new PinnedObjectImpl(object); + if (needsPinning(object)) { + pushPinnedObject(result); + } + return result; + } + + @Uninterruptible(reason = "Ensure that pinned object counts and PinnedObjects are consistent.") + private void pushPinnedObject(PinnedObjectImpl pinned) { + /* + * To avoid ABA problems, the application may only push data. Other operations may only be + * performed during a GC. + */ + PinnedObjectImpl head; + do { + head = pinnedObjects.get(); + pinned.next = head; + } while (!pinnedObjects.compareAndSet(head, pinned)); + + pinObject(pinned.referent); + } + + @Uninterruptible(reason = "Ensure that pinned object counts and PinnedObjects are consistent.", callerMustBe = true) + protected abstract void pinObject(Object object); + + @Uninterruptible(reason = "Ensure that pinned object counts and PinnedObjects are consistent.", callerMustBe = true) + protected abstract void unpinObject(Object object); + + /** + * Removes all closed pinned objects. Returns the first open {@link PinnedObject} or null if + * there are no more open pinned objects. Note that this method may only be called by the GC. + */ + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public PinnedObjectImpl removeClosedObjectsAndGetFirstOpenObject() { + VMOperation.guaranteeGCInProgress("would cause various race conditions otherwise"); + PinnedObjectImpl cur = pinnedObjects.get(); + PinnedObjectImpl lastOpen = null; + PinnedObjectImpl newHead = null; + + while (cur != null) { + if (cur.open) { + if (newHead == null) { + newHead = cur; + } else if (lastOpen.next != cur) { + /* Avoid useless writes as those would dirty the card table unnecessarily. */ + lastOpen.next = cur; + } + lastOpen = cur; + } + cur = cur.next; + } + + if (lastOpen != null) { + lastOpen.next = null; + } + + pinnedObjects.set(newHead); + return newHead; + } + + public boolean isPinnedSlow(Object object) { + PinnedObjectImpl pin = pinnedObjects.get(); + while (pin != null) { + if (pin.open && pin.referent == object) { + return true; + } + pin = pin.next; + } + return false; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static boolean needsPinning(Object object) { + return !SubstrateOptions.useEpsilonGC() && object != null && !Heap.getHeap().isInImageHeap(object); + } + + public static class PinnedObjectImpl implements PinnedObject { + private Object referent; + private boolean open; + private PinnedObjectImpl next; + + PinnedObjectImpl(Object object) { + this.referent = object; + this.open = true; + this.next = null; + } + + @Override + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public Object getObject() { + assert open : "Should not call getObject() on a closed PinnedObject."; + return referent; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public PinnedObjectImpl getNext() { + return next; + } + + @Override + @Uninterruptible(reason = "Ensure that pinned object counts and PinnedObjects are consistent.") + public void close() { + assert open : "Should not call close() on a closed PinnedObject."; + open = false; + if (needsPinning(referent)) { + AbstractPinnedObjectSupport.singleton().unpinObject(referent); + } + referent = null; + } + + @Override + public Pointer addressOfObject() { + if (!SubstrateOptions.PinnedObjectAddressing.getValue()) { + throw new UnsupportedOperationException("Pinned object addressing has been disabled."); + } + assert open : "Should not call addressOfObject() on a closed PinnedObject."; + return Word.objectToUntrackedPointer(referent); + } + + @Override + @SuppressWarnings("unchecked") + public T addressOfArrayElement(int index) { + if (referent == null) { + throw new NullPointerException("PinnedObject is missing a referent"); + } + DynamicHub hub = ObjectHeader.readDynamicHubFromObject(referent); + UnsignedWord offsetOfArrayElement = LayoutEncoding.getArrayElementOffset(hub.getLayoutEncoding(), index); + return (T) addressOfObject().add(offsetOfArrayElement); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperation.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperation.java index dc90fb95afaa..6558b3721900 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperation.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperation.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.core.thread; +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.IsolateThread; @@ -174,6 +176,7 @@ public static void guaranteeInProgressAtSafepoint(String message) { } /** Verifies that the current thread is in the middle of performing a garbage collection. */ + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public static void guaranteeGCInProgress(String message) { if (!isGCInProgress()) { throw VMError.shouldNotReachHere(message); From 883de154f397440abd8184bb8c842ba4a794a494 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Thu, 13 Feb 2025 17:57:22 +0100 Subject: [PATCH 4/5] Improve crash log output. --- .../genscavenge/CompactingOldGeneration.java | 5 + .../genscavenge/CopyingOldGeneration.java | 5 + .../oracle/svm/core/genscavenge/GCImpl.java | 2 +- .../svm/core/genscavenge/Generation.java | 3 - .../core/genscavenge/HeapChunkLogging.java | 15 +- .../oracle/svm/core/genscavenge/HeapImpl.java | 115 ++++--- .../svm/core/genscavenge/OldGeneration.java | 5 + .../oracle/svm/core/genscavenge/Space.java | 30 ++ .../svm/core/genscavenge/YoungGeneration.java | 38 ++- .../svm/core/posix/linux/DumpLinuxOSInfo.java | 4 +- .../oracle/svm/core/SubstrateDiagnostics.java | 26 +- .../oracle/svm/core/code/CodeInfoAccess.java | 16 +- .../svm/core/stack/ThreadStackPrinter.java | 288 ++++++++++-------- 13 files changed, 348 insertions(+), 204 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CompactingOldGeneration.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CompactingOldGeneration.java index 1b12444bdb88..83a10ffa3591 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CompactingOldGeneration.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CompactingOldGeneration.java @@ -434,6 +434,11 @@ boolean isInSpace(Pointer ptr) { return space.contains(ptr); } + @Override + boolean printLocationInfo(Log log, Pointer ptr) { + return space.printLocationInfo(log, ptr); + } + @Override public boolean walkObjects(ObjectVisitor visitor) { return space.walkObjects(visitor); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CopyingOldGeneration.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CopyingOldGeneration.java index 47e1cfe80779..27e389973e3f 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CopyingOldGeneration.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CopyingOldGeneration.java @@ -165,6 +165,11 @@ boolean isInSpace(Pointer ptr) { return fromSpace.contains(ptr) || toSpace.contains(ptr); } + @Override + boolean printLocationInfo(Log log, Pointer ptr) { + return fromSpace.printLocationInfo(log, ptr) || toSpace.printLocationInfo(log, ptr); + } + @Override boolean verifyRememberedSets() { boolean success = true; 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 4efa8d16b213..8c0c2bbf3fa8 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 @@ -432,7 +432,7 @@ private void printGCAfter(GCCause cause) { } if (SerialGCOptions.TraceHeapChunks.getValue()) { - HeapImpl.getHeapImpl().logChunks(Log.log()); + HeapImpl.getHeapImpl().logChunks(Log.log(), false); } } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Generation.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Generation.java index 11cdc4d7b10f..6a811a62e5eb 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Generation.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Generation.java @@ -56,9 +56,6 @@ public String getName() { /** Print some heap statistics to a log. */ public abstract void logUsage(Log log); - /** Print some information about the chunks to the log. */ - public abstract void logChunks(Log log); - /** * Promote an Object to this Generation, typically by copying and leaving a forwarding pointer * to the new Object in place of the original Object. If the object cannot be promoted due to diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkLogging.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkLogging.java index c3ebc9db1a45..11250c4e4ee5 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkLogging.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkLogging.java @@ -77,15 +77,20 @@ public static void logChunks(Log log, UnalignedHeapChunk.UnalignedHeader firstCh } private static void logChunk(Log log, HeapChunk.Header chunk, Pointer bottom, Pointer top, Pointer end, boolean isAligned, String shortSpaceName, boolean isToSpace) { - UnsignedWord used = top.subtract(bottom); - UnsignedWord capacity = end.subtract(bottom); - UnsignedWord usedPercent = used.multiply(100).unsignedDivide(capacity); - log.string("|").zhex(chunk).string("|").zhex(bottom).string(", ").zhex(top).string(", ").zhex(end); - log.string("|").unsigned(usedPercent, 3, RIGHT_ALIGN).string("%"); + log.string("|"); + if (top.isNonNull()) { + UnsignedWord used = top.subtract(bottom); + UnsignedWord capacity = end.subtract(bottom); + UnsignedWord usedPercent = used.multiply(100).unsignedDivide(capacity); + log.unsigned(usedPercent, 3, RIGHT_ALIGN).string("%"); + } else { + log.spaces(3).string("?"); + } log.string("|").string(shortSpaceName, 3, RIGHT_ALIGN); log.string("|").string(isAligned ? "A" : "U"); log.string("|").string(isToSpace ? "T" : " "); + log.string("|").signed(chunk.getPinnedObjectCount()); log.newline(); } } 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 f5245caa8afa..d1b8f65038f6 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 @@ -28,7 +28,6 @@ import java.util.ArrayList; import java.util.List; -import com.oracle.svm.core.hub.LayoutEncoding; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; @@ -49,7 +48,6 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.genscavenge.AlignedHeapChunk.AlignedHeader; import com.oracle.svm.core.genscavenge.ThreadLocalAllocation.Descriptor; import com.oracle.svm.core.genscavenge.UnalignedHeapChunk.UnalignedHeader; @@ -69,6 +67,7 @@ import com.oracle.svm.core.heap.RuntimeCodeInfoGCSupport; import com.oracle.svm.core.heap.VMOperationInfos; import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.LayoutEncoding; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.core.jfr.JfrTicks; import com.oracle.svm.core.jfr.events.SystemGCEvent; @@ -135,7 +134,9 @@ public HeapImpl() { HeapParameters.initialize(); DiagnosticThunkRegistry.singleton().add(new DumpHeapSettingsAndStatistics()); DiagnosticThunkRegistry.singleton().add(new DumpHeapUsage()); - DiagnosticThunkRegistry.singleton().add(new DumpChunkInformation()); + DiagnosticThunkRegistry.singleton().add(new DumpGCPolicy()); + DiagnosticThunkRegistry.singleton().add(new DumpImageHeapInfo()); + DiagnosticThunkRegistry.singleton().add(new DumpChunkInfo()); } @Fold @@ -280,30 +281,12 @@ void logUsage(Log log) { oldGeneration.logUsage(log); } - void logChunks(Log log) { - getYoungGeneration().logChunks(log); + void logChunks(Log log, boolean allowUnsafe) { + getYoungGeneration().logChunks(log, allowUnsafe); getOldGeneration().logChunks(log); getChunkProvider().logFreeChunks(log); } - @SuppressWarnings("static-method") - void logImageHeapPartitionBoundaries(Log log) { - log.string("Native image heap boundaries:").indent(true); - for (ImageHeapInfo info : HeapImpl.getImageHeapInfos()) { - info.print(log); - } - log.indent(false); - - if (AuxiliaryImageHeap.isPresent()) { - ImageHeapInfo auxHeapInfo = AuxiliaryImageHeap.singleton().getImageHeapInfo(); - if (auxHeapInfo != null) { - log.string("Auxiliary image heap boundaries:").indent(true); - auxHeapInfo.print(log); - log.indent(false); - } - } - } - /** Log the zap values to make it easier to search for them. */ static void logZapValues(Log log) { if (HeapParameters.getZapProducedHeapChunks() || HeapParameters.getZapConsumedHeapChunks()) { @@ -776,11 +759,9 @@ private boolean printLocationInfo(Log log, Pointer ptr, boolean allowJavaHeapAcc if (allowJavaHeapAccess) { // Accessing spaces and chunks is safe if we prevent a GC. - if (youngGeneration.isInSpace(ptr)) { - log.string("points into the young generation"); + if (youngGeneration.printLocationInfo(log, ptr)) { return true; - } else if (oldGeneration.isInSpace(ptr)) { - log.string("points into the old generation"); + } else if (oldGeneration.printLocationInfo(log, ptr)) { return true; } } @@ -810,10 +791,10 @@ private static boolean printTlabInfo(Log log, Pointer ptr, IsolateThread thread) ThreadLocalAllocation.Descriptor tlab = getTlabUnsafe(thread); AlignedHeader aChunk = tlab.getAlignedChunk(); while (aChunk.isNonNull()) { - Pointer dataStart = AlignedHeapChunk.getObjectsStart(aChunk); - Pointer dataEnd = AlignedHeapChunk.getObjectsEnd(aChunk); - if (ptr.aboveOrEqual(dataStart) && ptr.belowThan(dataEnd)) { - log.string("points into an aligned TLAB chunk of thread ").zhex(thread); + if (HeapChunk.asPointer(aChunk).belowOrEqual(ptr) && ptr.belowThan(HeapChunk.getEndPointer(aChunk))) { + /* top may be null for a thread's current aligned allocation chunk. */ + boolean unusablePart = HeapChunk.getTopPointer(aChunk).isNonNull() && ptr.aboveOrEqual(HeapChunk.getTopPointer(aChunk)); + printTlabChunkInfo(log, thread, aChunk, "aligned", unusablePart); return true; } aChunk = HeapChunk.getNext(aChunk); @@ -821,10 +802,9 @@ private static boolean printTlabInfo(Log log, Pointer ptr, IsolateThread thread) UnalignedHeader uChunk = tlab.getUnalignedChunk(); while (uChunk.isNonNull()) { - Pointer dataStart = UnalignedHeapChunk.getObjectStart(uChunk); - Pointer dataEnd = UnalignedHeapChunk.getObjectEnd(uChunk); - if (ptr.aboveOrEqual(dataStart) && ptr.belowThan(dataEnd)) { - log.string("points into an unaligned TLAB chunk of thread ").zhex(thread); + if (HeapChunk.asPointer(uChunk).belowOrEqual(ptr) && ptr.belowThan(HeapChunk.getEndPointer(uChunk))) { + boolean unusablePart = ptr.aboveOrEqual(HeapChunk.getTopPointer(uChunk)); + printTlabChunkInfo(log, thread, uChunk, "unaligned", unusablePart); return true; } uChunk = HeapChunk.getNext(uChunk); @@ -833,8 +813,14 @@ private static boolean printTlabInfo(Log log, Pointer ptr, IsolateThread thread) return false; } + private static void printTlabChunkInfo(Log log, IsolateThread thread, HeapChunk.Header chunk, String chunkType, boolean unusablePart) { + String unusable = unusablePart ? "unusable part of " : ""; + log.string("points into ").string(unusable).string(chunkType).string(" chunk ").zhex(chunk).spaces(1); + log.string("(TLAB of thread ").zhex(thread).string(")"); + } + @Uninterruptible(reason = "This whole method is unsafe, so it is only uninterruptible to satisfy the checks.") - private static Descriptor getTlabUnsafe(IsolateThread thread) { + static Descriptor getTlabUnsafe(IsolateThread thread) { assert SubstrateDiagnostics.isFatalErrorHandlingThread() : "can cause crashes, so it may only be used while printing diagnostics"; return ThreadLocalAllocation.getTlab(thread); } @@ -880,9 +866,6 @@ public int maxInvocationCount() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.") public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) { log.string("Heap settings and statistics:").indent(true); - log.string("Supports isolates: ").bool(SubstrateOptions.SpawnIsolates.getValue()).newline(); - log.string("Heap base: ").zhex(KnownIntrinsics.heapBase()).newline(); - log.string("Object reference size: ").signed(ConfigurationValues.getObjectLayout().getReferenceSize()).newline(); log.string("Reserved object header bits: 0b").number(Heap.getHeap().getObjectHeader().getReservedBitsMask(), 2, false).newline(); log.string("Aligned chunk size: ").unsigned(HeapParameters.getAlignedHeapChunkSize()).newline(); @@ -913,7 +896,29 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev } } - private static final class DumpChunkInformation extends DiagnosticThunk { + private static final class DumpGCPolicy extends DiagnosticThunk { + @Override + public int maxInvocationCount() { + return 1; + } + + @Override + @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.") + public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) { + CollectionPolicy policy = GCImpl.getPolicy(); + + log.string("GC policy:").indent(true); + log.string("Name: ").string(policy.getName()).newline(); + log.string("Max eden size: ").unsigned(policy.getMaximumEdenSize()).newline(); + log.string("Max survivor size: ").unsigned(policy.getMaximumSurvivorSize()).newline(); + log.string("Max young size: ").unsigned(policy.getMaximumYoungGenerationSize()).newline(); + log.string("Max old size: ").unsigned(policy.getMaximumOldSize()).newline(); + log.string("Max heap size: ").unsigned(policy.getMaximumHeapSize()).newline(); + log.indent(false); + } + } + + private static final class DumpImageHeapInfo extends DiagnosticThunk { @Override public int maxInvocationCount() { return 1; @@ -922,11 +927,35 @@ public int maxInvocationCount() { @Override @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.") public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) { - HeapImpl heap = HeapImpl.getHeapImpl(); - heap.logImageHeapPartitionBoundaries(log); + log.string("Image heap boundaries:").indent(true); + for (ImageHeapInfo info : HeapImpl.getImageHeapInfos()) { + info.print(log); + } + log.indent(false); + + if (AuxiliaryImageHeap.isPresent()) { + ImageHeapInfo auxHeapInfo = AuxiliaryImageHeap.singleton().getImageHeapInfo(); + if (auxHeapInfo != null) { + log.string("Auxiliary image heap boundaries:").indent(true); + auxHeapInfo.print(log); + log.indent(false); + } + } + } + } + private static final class DumpChunkInfo extends DiagnosticThunk { + @Override + public int maxInvocationCount() { + return 2; + } + + @Override + @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.") + public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) { log.string("Heap chunks: E=eden, S=survivor, O=old, F=free; A=aligned chunk, U=unaligned chunk; T=to space").indent(true); - heap.logChunks(log); + boolean allowUnsafe = invocationCount == 1 && SubstrateDiagnostics.DiagnosticLevel.unsafeOperationsAllowed(maxDiagnosticLevel); + HeapImpl.getHeapImpl().logChunks(log, allowUnsafe); log.indent(false); } } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/OldGeneration.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/OldGeneration.java index f31a461a6059..fb88d87e5430 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/OldGeneration.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/OldGeneration.java @@ -33,6 +33,7 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.genscavenge.GCImpl.ChunkReleaser; import com.oracle.svm.core.genscavenge.remset.RememberedSet; +import com.oracle.svm.core.log.Log; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.util.VMError; @@ -63,6 +64,10 @@ public abstract class OldGeneration extends Generation { abstract boolean isInSpace(Pointer ptr); + abstract boolean printLocationInfo(Log log, Pointer ptr); + + abstract void logChunks(Log log); + abstract void appendChunk(AlignedHeapChunk.AlignedHeader hdr); abstract boolean verifyRememberedSets(); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Space.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Space.java index 233f4030b3b0..679701947b59 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Space.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Space.java @@ -617,4 +617,34 @@ boolean contains(Pointer p) { } return false; } + + public boolean printLocationInfo(Log log, Pointer p) { + AlignedHeapChunk.AlignedHeader aChunk = getFirstAlignedHeapChunk(); + while (aChunk.isNonNull()) { + if (HeapChunk.asPointer(aChunk).belowOrEqual(p) && p.belowThan(HeapChunk.getEndPointer(aChunk))) { + boolean unusablePart = p.aboveOrEqual(HeapChunk.getTopPointer(aChunk)); + printChunkInfo(log, aChunk, "aligned", unusablePart); + return true; + } + aChunk = HeapChunk.getNext(aChunk); + } + + UnalignedHeapChunk.UnalignedHeader uChunk = getFirstUnalignedHeapChunk(); + while (uChunk.isNonNull()) { + if (HeapChunk.asPointer(uChunk).belowOrEqual(p) && p.belowThan(HeapChunk.getEndPointer(uChunk))) { + boolean unusablePart = p.aboveOrEqual(HeapChunk.getTopPointer(uChunk)); + printChunkInfo(log, uChunk, "unaligned", unusablePart); + return true; + } + uChunk = HeapChunk.getNext(uChunk); + } + return false; + } + + private void printChunkInfo(Log log, HeapChunk.Header chunk, String chunkType, boolean unusablePart) { + String toSpace = isToSpace ? "-T" : ""; + String unusable = unusablePart ? "unusable part of " : ""; + log.string("points into ").string(unusable).string(chunkType).string(" chunk ").zhex(chunk).spaces(1); + log.string("(").string(getShortName()).string(toSpace).string(")"); + } } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/YoungGeneration.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/YoungGeneration.java index f70b053e279f..7115e06f2ca7 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/YoungGeneration.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/YoungGeneration.java @@ -24,7 +24,7 @@ */ package com.oracle.svm.core.genscavenge; -import jdk.graal.compiler.word.Word; +import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; @@ -37,6 +37,9 @@ import com.oracle.svm.core.heap.ObjectVisitor; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.thread.VMOperation; +import com.oracle.svm.core.thread.VMThreads; + +import jdk.graal.compiler.word.Word; public final class YoungGeneration extends Generation { private final Space eden; @@ -106,8 +109,13 @@ public void logUsage(Log log) { } } - @Override - public void logChunks(Log log) { + public void logChunks(Log log, boolean allowUnsafe) { + if (allowUnsafe) { + for (IsolateThread thread = VMThreads.firstThreadUnsafe(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { + logTlabChunks(log, thread); + } + } + getEden().logChunks(log); for (int i = 0; i < maxSurvivorSpaces; i++) { this.survivorFromSpaces[i].logChunks(log); @@ -115,6 +123,15 @@ public void logChunks(Log log) { } } + private void logTlabChunks(Log log, IsolateThread thread) { + ThreadLocalAllocation.Descriptor tlab = HeapImpl.getTlabUnsafe(thread); + AlignedHeapChunk.AlignedHeader aChunk = tlab.getAlignedChunk(); + HeapChunkLogging.logChunks(log, aChunk, eden.getShortName(), false); + + UnalignedHeapChunk.UnalignedHeader uChunk = tlab.getUnalignedChunk(); + HeapChunkLogging.logChunks(log, uChunk, eden.getShortName(), false); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) Space getEden() { return eden; @@ -351,4 +368,19 @@ boolean isInSpace(Pointer ptr) { } return false; } + + public boolean printLocationInfo(Log log, Pointer ptr) { + if (getEden().printLocationInfo(log, ptr)) { + return true; + } + for (int i = 0; i < getMaxSurvivorSpaces(); i++) { + if (getSurvivorFromSpaceAt(i).printLocationInfo(log, ptr)) { + return true; + } + if (getSurvivorToSpaceAt(i).printLocationInfo(log, ptr)) { + return true; + } + } + return false; + } } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/DumpLinuxOSInfo.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/DumpLinuxOSInfo.java index 7f7e9cd76071..c3da72746499 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/DumpLinuxOSInfo.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/DumpLinuxOSInfo.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.posix.linux; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.word.Pointer; @@ -42,6 +41,7 @@ import com.oracle.svm.core.os.RawFileOperationSupport; import jdk.graal.compiler.core.common.NumUtil; +import jdk.graal.compiler.word.Word; class DumpLinuxOSInfo extends SubstrateDiagnostics.DiagnosticThunk { private static final CGlobalData MAX_THREADS_PATH = CGlobalDataFactory.createCString("/proc/sys/kernel/threads-max"); @@ -107,7 +107,7 @@ class DumpLinuxOSInfoFeature implements InternalFeature { @Override public void afterRegistration(AfterRegistrationAccess access) { if (!SubstrateOptions.AsyncSignalSafeDiagnostics.getValue()) { - DiagnosticThunkRegistry.singleton().addAfter(new DumpLinuxOSInfo(), SubstrateDiagnostics.DumpMachineInfo.class); + DiagnosticThunkRegistry.singleton().addAfter(new DumpLinuxOSInfo(), SubstrateDiagnostics.DumpRuntimeInfo.class); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java index 327147b3b880..6b1875d20059 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java @@ -78,13 +78,12 @@ import com.oracle.svm.core.log.Log; import com.oracle.svm.core.option.RuntimeOptionKey; import com.oracle.svm.core.os.VirtualMemoryProvider; +import com.oracle.svm.core.snippets.KnownIntrinsics; import com.oracle.svm.core.stack.JavaFrameAnchor; import com.oracle.svm.core.stack.JavaFrameAnchors; import com.oracle.svm.core.stack.JavaStackWalker; import com.oracle.svm.core.stack.ThreadStackPrinter; import com.oracle.svm.core.stack.ThreadStackPrinter.StackFramePrintVisitor; -import com.oracle.svm.core.stack.ThreadStackPrinter.Stage0StackFramePrintVisitor; -import com.oracle.svm.core.stack.ThreadStackPrinter.Stage1StackFramePrintVisitor; import com.oracle.svm.core.thread.PlatformThreads; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VMOperationControl; @@ -115,8 +114,7 @@ public class SubstrateDiagnostics { private static final FastThreadLocalBytes threadOnlyAttachedForCrashHandler = FastThreadLocalFactory.createBytes(() -> 1, "SubstrateDiagnostics.threadOnlyAttachedForCrashHandler"); private static final ImageCodeLocationInfoPrinter imageCodeLocationInfoPrinter = new ImageCodeLocationInfoPrinter(); - private static final Stage0StackFramePrintVisitor[] printVisitors = new Stage0StackFramePrintVisitor[]{new StackFramePrintVisitor(), new Stage1StackFramePrintVisitor(), - new Stage0StackFramePrintVisitor()}; + private static final StackFramePrintVisitor stackFramePrinter = new StackFramePrintVisitor(); @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void setOnlyAttachedForCrashHandler(IsolateThread thread) { @@ -850,7 +848,7 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev } } - static class DumpVMInfo extends DiagnosticThunk { + static class DumpBuildTimeInfo extends DiagnosticThunk { @Override public int maxInvocationCount() { return 1; @@ -867,7 +865,9 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev Platform platform = ImageSingletons.lookup(Platform.class); log.string("Platform: ").string(platform.getOS()).string("/").string(platform.getArchitecture()).newline(); log.string("Page size: ").unsigned(SubstrateOptions.getPageSize()).newline(); + log.string("Supports isolates: ").bool(SubstrateOptions.SpawnIsolates.getValue()).newline(); log.string("Containerized: ").string(String.valueOf(Container.singleton().isContainerized())).newline(); + log.string("Object reference size: ").signed(ConfigurationValues.getObjectLayout().getReferenceSize()).newline(); log.string("CPU features used for AOT compiled code: ").string(getBuildTimeCpuFeatures()).newline(); log.indent(false); } @@ -878,7 +878,7 @@ static String getBuildTimeCpuFeatures() { } } - public static class DumpMachineInfo extends DiagnosticThunk { + public static class DumpRuntimeInfo extends DiagnosticThunk { @Override public int maxInvocationCount() { return 1; @@ -888,6 +888,8 @@ public int maxInvocationCount() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while printing diagnostics.") public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLevel, int invocationCount) { log.string("Runtime information:").indent(true); + log.string("Isolate id: ").signed(Isolates.getIsolateId()).newline(); + log.string("Heap base: ").zhex(KnownIntrinsics.heapBase()).newline(); if (Container.singleton().isContainerized()) { log.string("CPU cores (container): "); @@ -1024,8 +1026,8 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev Pointer sp = context.getStackPointer(); CodePointer ip = context.getInstructionPointer(); - log.string("Stacktrace for the failing thread ").zhex(CurrentIsolate.getCurrentThread()).string(" (A=AOT compiled, J=JIT compiled, D=deoptimized, i=inlined):").indent(true); - boolean success = ThreadStackPrinter.printStacktrace(CurrentIsolate.getCurrentThread(), sp, ip, printVisitors[invocationCount - 1].reset(), log); + log.string("Stacktrace for the failing thread ").zhex(CurrentIsolate.getCurrentThread()).string(" (A=AOT compiled, J=JIT compiled, D=deoptimized, i=inlined, C=native):").indent(true); + boolean success = ThreadStackPrinter.printStacktrace(CurrentIsolate.getCurrentThread(), sp, ip, stackFramePrinter.reset(invocationCount), log); if (!success && DiagnosticLevel.unsafeOperationsAllowed(maxDiagnosticLevel)) { /* @@ -1056,7 +1058,7 @@ private static void startStackWalkInMostLikelyCaller(Log log, int invocationCoun Pointer sp = returnAddressPos.add(FrameAccess.returnAddressSize()); log.newline(); log.string("Starting the stack walk in a possible caller (sp + ").unsigned(sp.subtract(originalSp)).string("):").newline(); - ThreadStackPrinter.printStacktrace(CurrentIsolate.getCurrentThread(), sp, possibleIp, printVisitors[invocationCount - 1].reset(), log); + ThreadStackPrinter.printStacktrace(CurrentIsolate.getCurrentThread(), sp, possibleIp, stackFramePrinter.reset(invocationCount), log); } } @@ -1103,7 +1105,7 @@ private static void printFrameAnchors(Log log, IsolateThread vmThread) { private static void printStackTrace(Log log, IsolateThread vmThread, int invocationCount) { log.string("Stacktrace:").indent(true); - JavaStackWalker.walkThread(vmThread, printVisitors[invocationCount - 1].reset(), log); + JavaStackWalker.walkThread(vmThread, stackFramePrinter.reset(invocationCount), log); log.redent(false); } } @@ -1279,8 +1281,8 @@ public static synchronized DiagnosticThunkRegistry singleton() { thunks.add(new DumpCurrentVMOperation()); thunks.add(new DumpVMOperationHistory()); thunks.add(new VMLockSupport.DumpVMMutexes()); - thunks.add(new DumpVMInfo()); - thunks.add(new DumpMachineInfo()); + thunks.add(new DumpBuildTimeInfo()); + thunks.add(new DumpRuntimeInfo()); if (ImageSingletons.contains(JavaMainWrapper.JavaMainSupport.class)) { thunks.add(new DumpCommandLine()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java index db9ca4965620..102e0a347dca 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.code; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.UnsignedWord; @@ -45,6 +44,7 @@ import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.word.Word; /** * Provides functionality to query information about a unit of compiled code from a {@link CodeInfo} @@ -424,6 +424,20 @@ public static boolean isAOTImageCode(CodeInfo info) { return cast(info).getIsAOTImageCode(); } + /** + * In some callers, we can't necessarily assume that we have a valid {@link CodeInfo} object. + * So, this method explicitly avoids any {@link CodeInfo} field accesses. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean isAOTImageCodeSlow(CodeInfo info) { + for (CodeInfo imageCodeInfo = CodeInfoTable.getFirstImageCodeInfo(); imageCodeInfo.isNonNull(); imageCodeInfo = getNextImageCodeInfo(imageCodeInfo)) { + if (info == imageCodeInfo) { + return true; + } + } + return false; + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static CodeInfo getNextImageCodeInfo(CodeInfo info) { assert isAOTImageCode(info); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java index 25511da2d42b..0745a68178be 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.stack; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.function.CodePointer; @@ -43,85 +42,77 @@ import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.log.Log; -public class ThreadStackPrinter { - private static final int MAX_STACK_FRAMES_PER_THREAD_TO_PRINT = 100_000; +import jdk.graal.compiler.word.Word; - public static class StackFramePrintVisitor extends Stage1StackFramePrintVisitor { - private final CodeInfoDecoder.FrameInfoCursor frameInfoCursor = new CodeInfoDecoder.FrameInfoCursor(); +public class ThreadStackPrinter { + @Uninterruptible(reason = "Prevent deoptimization of stack frames while in this method.") + public static boolean printStacktrace(IsolateThread thread, Pointer initialSP, CodePointer initialIP, StackFramePrintVisitor printVisitor, Log log) { + Pointer sp = initialSP; + CodePointer ip = initialIP; - public StackFramePrintVisitor() { - } + /* Don't start the stack walk in a non-Java frame, even if the crash happened there. */ + UntetheredCodeInfo info = CodeInfoTable.lookupCodeInfo(ip); + if (info.isNull()) { + logFrame(log, sp, ip); - @Override - protected void logFrame(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptFrame) { - if (deoptFrame != null) { - logVirtualFrames(log, sp, ip, codeInfo, deoptFrame); - return; + JavaFrameAnchor anchor = JavaFrameAnchors.getFrameAnchor(thread); + if (anchor.isNonNull()) { + sp = anchor.getLastJavaSP(); + ip = anchor.getLastJavaIP(); + } else { + return false; } + } - boolean isFirst = true; - frameInfoCursor.initialize(codeInfo, ip, false); - while (frameInfoCursor.advance()) { - if (printedFrames >= MAX_STACK_FRAMES_PER_THREAD_TO_PRINT) { - log.string("... (truncated)").newline(); - break; - } - - if (!isFirst) { - log.newline(); - } - - boolean compilationRoot = !frameInfoCursor.hasCaller(); - printFrameIdentifier(log, codeInfo, null, compilationRoot); - logFrameRaw(log, sp, ip, codeInfo); - - String codeInfoName = DeoptimizationSupport.enabled() ? CodeInfoAccess.getName(codeInfo) : null; - logFrameInfo(log, frameInfoCursor.get(), codeInfoName); - isFirst = false; - printedFrames++; - } + JavaStackWalk walk = StackValue.get(JavaStackWalker.sizeOfJavaStackWalk()); + JavaStackWalker.initialize(walk, thread, sp, ip); + return JavaStackWalker.doWalk(walk, thread, printVisitor, log); + } - if (isFirst) { - /* We don't have any metadata, so print less detailed information. */ - super.logFrame(log, sp, ip, codeInfo, null); - log.string("missing metadata"); - } - } + @Uninterruptible(reason = "IP is not within Java code, so there is no risk that it gets invalidated.", calleeMustBe = false) + private static void logFrame(Log log, Pointer sp, CodePointer ip) { + StackFramePrintVisitor.logSPAndIP(log, sp, ip); + log.string(" IP is not within Java code. Trying frame anchor of last Java frame instead.").newline(); } - public static class Stage0StackFramePrintVisitor extends ParameterizedStackFrameVisitor { - protected int printedFrames; + /** + * With every retry, the output is reduced a bit. + *
    + *
  • 1st invocation: maximum details for AOT and JIT compiled code
  • + *
  • 2nd invocation: reduced details for JIT compiled code
  • + *
  • 3rd invocation: minimal information for both AOT and JIT compiled code
  • + *
+ */ + public static class StackFramePrintVisitor extends ParameterizedStackFrameVisitor { + private static final int MAX_STACK_FRAMES_PER_THREAD_TO_PRINT = 100_000; - public Stage0StackFramePrintVisitor() { + private final CodeInfoDecoder.FrameInfoCursor frameInfoCursor = new CodeInfoDecoder.FrameInfoCursor(); + private int invocationCount; + private int printedFrames; + private Pointer expectedSP; + + public StackFramePrintVisitor() { } - public Stage0StackFramePrintVisitor reset() { - printedFrames = 0; + @SuppressWarnings("hiding") + public StackFramePrintVisitor reset(int invocationCount) { + assert invocationCount >= 1 && invocationCount <= 3; + this.invocationCount = invocationCount; + this.printedFrames = 0; + this.expectedSP = Word.nullPointer(); return this; } @Override - @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Provide allocation-free StackFrameVisitor") + @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Used for crash log") protected final boolean visitRegularFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, Object data) { return visitFrame(sp, ip, codeInfo, null, (Log) data); } @Override - @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Provide allocation-free StackFrameVisitor") + @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Used for crash log") protected boolean visitDeoptimizedFrame(Pointer originalSP, CodePointer deoptStubIP, DeoptimizedFrame deoptFrame, Object data) { - CodeInfo imageCodeInfo = CodeInfoTable.lookupImageCodeInfo(deoptStubIP); - return visitFrame(originalSP, deoptStubIP, imageCodeInfo, deoptFrame, (Log) data); - } - - private boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptFrame, Log log) { - if (printedFrames >= MAX_STACK_FRAMES_PER_THREAD_TO_PRINT) { - log.string("... (truncated)").newline(); - return false; - } - - logFrame(log, sp, ip, codeInfo, deoptFrame); - log.newline(); - return true; + return visitFrame(originalSP, deoptStubIP, Word.nullPointer(), deoptFrame, (Log) data); } @Override @@ -129,123 +120,152 @@ protected final boolean unknownFrame(Pointer sp, CodePointer ip, Object data) { Log log = (Log) data; logFrameRaw(log, sp, ip, Word.nullPointer()); log.string(" IP is not within Java code. Aborting stack trace printing.").newline(); - printedFrames++; return false; } - @SuppressWarnings("unused") - protected void logFrame(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptFrame) { - logFrameRaw(log, sp, ip, codeInfo); - printedFrames++; - } - - protected static void logFrameRaw(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo) { - log.string("SP ").zhex(sp); - log.string(" IP ").zhex(ip); - log.string(" size="); - if (codeInfo.isNonNull()) { - long frameSize = CodeInfoAccess.lookupTotalFrameSize(codeInfo, ip); - log.signed(frameSize, 4, Log.LEFT_ALIGN); - } else { - log.string("unknown"); + private boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptFrame, Log log) { + if (printedFrames >= MAX_STACK_FRAMES_PER_THREAD_TO_PRINT) { + log.string("... (truncated)").newline(); + return false; } - } - } - - public static class Stage1StackFramePrintVisitor extends Stage0StackFramePrintVisitor { - public Stage1StackFramePrintVisitor() { - } - @Override - protected void logFrame(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptFrame) { - if (deoptFrame != null) { - logVirtualFrames(log, sp, ip, codeInfo, deoptFrame); + if (invocationCount >= 3) { + logJavaFrameMinimalInfo(log, sp, ip, codeInfo, deoptFrame, true); + log.string("CodeInfo ").zhex(codeInfo).string(", "); + log.string("DeoptimizedFrame ").zhex(Word.objectToUntrackedPointer(deoptFrame)); } else { - logStackFrame(log, sp, ip, codeInfo); + if (expectedSP.isNonNull() && sp != expectedSP) { + logNativeFrame(log, expectedSP, ip, sp.subtract(expectedSP).rawValue()); + log.newline(); + } + + if (deoptFrame != null) { + logDeoptimizedJavaFrame(log, sp, ip, deoptFrame); + } else { + logRegularJavaFrame(log, sp, ip, codeInfo); + } } + log.newline(); + return true; } - protected void logVirtualFrames(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptFrame) { + private void logDeoptimizedJavaFrame(Log log, Pointer sp, CodePointer ip, DeoptimizedFrame deoptFrame) { for (DeoptimizedFrame.VirtualFrame frame = deoptFrame.getTopFrame(); frame != null; frame = frame.getCaller()) { if (printedFrames >= MAX_STACK_FRAMES_PER_THREAD_TO_PRINT) { log.string("... (truncated)").newline(); break; } - boolean compilationRoot = frame.getCaller() == null; - printFrameIdentifier(log, Word.nullPointer(), deoptFrame, compilationRoot); - logFrameRaw(log, sp, ip, codeInfo); + boolean isCompilationRoot = frame.getCaller() == null; + printFrameIdentifier(log, Word.nullPointer(), deoptFrame, isCompilationRoot, false); + logFrameRaw(log, sp, ip, deoptFrame.getSourceTotalFrameSize()); logFrameInfo(log, frame.getFrameInfo(), ImageCodeInfo.CODE_INFO_NAME + ", deopt"); - if (!compilationRoot) { + if (!isCompilationRoot) { log.newline(); } - printedFrames++; } } - private void logStackFrame(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo) { - printFrameIdentifier(log, codeInfo, null, true); + private void logRegularJavaFrame(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo) { + if (invocationCount == 1 || CodeInfoAccess.isAOTImageCodeSlow(codeInfo)) { + boolean isFirst = true; + frameInfoCursor.initialize(codeInfo, ip, false); + while (frameInfoCursor.advance()) { + if (printedFrames >= MAX_STACK_FRAMES_PER_THREAD_TO_PRINT) { + log.string("... (truncated)").newline(); + break; + } + + if (!isFirst) { + log.newline(); + } + + boolean isCompilationRoot = !frameInfoCursor.hasCaller(); + logVirtualFrame(log, sp, ip, codeInfo, frameInfoCursor.get(), isCompilationRoot); + isFirst = false; + } + + if (isFirst) { + /* We don't have any metadata, so print less detailed information. */ + logJavaFrameMinimalInfo(log, sp, ip, codeInfo, null, true); + log.string("missing metadata"); + } + } else { + /* Print less details for JIT compiled code if printing already failed once. */ + logJavaFrameMinimalInfo(log, sp, ip, codeInfo, null, true); + log.string("CodeInfo ").zhex(codeInfo); + } + } + + private void logVirtualFrame(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo, FrameInfoQueryResult frameInfo, boolean isCompilationRoot) { + logJavaFrameMinimalInfo(log, sp, ip, codeInfo, null, isCompilationRoot); + + String codeInfoName = DeoptimizationSupport.enabled() ? CodeInfoAccess.getName(codeInfo) : null; + logFrameInfo(log, frameInfo, codeInfoName); + } + + private void logJavaFrameMinimalInfo(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptFrame, boolean isCompilationRoot) { + printFrameIdentifier(log, codeInfo, deoptFrame, isCompilationRoot, false); logFrameRaw(log, sp, ip, codeInfo); - log.spaces(2); - if (DeoptimizationSupport.enabled()) { - log.string("[").string(CodeInfoAccess.getName(codeInfo)).string("] "); + } + + private void logNativeFrame(Log log, Pointer sp, CodePointer ip, long frameSize) { + printFrameIdentifier(log, Word.nullPointer(), null, true, true); + logFrameRaw(log, sp, ip, frameSize); + log.string("unknown"); + } + + private void logFrameRaw(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo) { + long frameSize = -1; + if (codeInfo.isNonNull()) { + frameSize = CodeInfoAccess.lookupTotalFrameSize(codeInfo, ip); } + logFrameRaw(log, sp, ip, frameSize); + } + + private void logFrameRaw(Log log, Pointer sp, CodePointer ip, long frameSize) { + logSPAndIP(log, sp, ip); + log.string(" size="); + if (frameSize >= 0) { + log.signed(frameSize, 4, Log.LEFT_ALIGN); + expectedSP = sp.add(Word.unsigned(frameSize)); + } else { + log.string("?").spaces(3); + expectedSP = Word.nullPointer(); + } + log.spaces(2); printedFrames++; } - protected static void logFrameInfo(Log log, FrameInfoQueryResult frameInfo, String runtimeMethodInfoName) { - log.string(" "); + private static void logSPAndIP(Log log, Pointer sp, CodePointer ip) { + log.string("SP ").zhex(sp).spaces(1); + log.string("IP ").zhex(ip); + } + + private static void logFrameInfo(Log log, FrameInfoQueryResult frameInfo, String runtimeMethodInfoName) { if (runtimeMethodInfoName != null) { log.string("[").string(runtimeMethodInfoName).string("] "); } frameInfo.log(log); } - protected static void printFrameIdentifier(Log log, CodeInfo codeInfo, DeoptimizedFrame deoptFrame, boolean isCompilationRoot) { - char ch = getFrameIdentifier(codeInfo, deoptFrame, isCompilationRoot); + private static void printFrameIdentifier(Log log, CodeInfo codeInfo, DeoptimizedFrame deoptFrame, boolean isCompilationRoot, boolean isNative) { + char ch = getFrameIdentifier(codeInfo, deoptFrame, isCompilationRoot, isNative); log.character(ch).spaces(2); } - private static char getFrameIdentifier(CodeInfo codeInfo, DeoptimizedFrame deoptFrame, boolean isCompilationRoot) { - if (deoptFrame != null) { + private static char getFrameIdentifier(CodeInfo codeInfo, DeoptimizedFrame deoptFrame, boolean isCompilationRoot, boolean isNative) { + if (isNative) { + return 'C'; + } else if (deoptFrame != null) { return 'D'; } else if (!isCompilationRoot) { return 'i'; - } else if (codeInfo.isNonNull() && CodeInfoAccess.isAOTImageCode(codeInfo)) { + } else if (codeInfo.isNonNull() && CodeInfoAccess.isAOTImageCodeSlow(codeInfo)) { return 'A'; } else { return 'J'; } } } - - @Uninterruptible(reason = "Prevent deoptimization of stack frames while in this method.") - public static boolean printStacktrace(IsolateThread thread, Pointer initialSP, CodePointer initialIP, Stage0StackFramePrintVisitor printVisitor, Log log) { - Pointer sp = initialSP; - CodePointer ip = initialIP; - - /* Don't start the stack walk in a non-Java frame, even if the crash happened there. */ - UntetheredCodeInfo info = CodeInfoTable.lookupCodeInfo(ip); - if (info.isNull()) { - logFrame(log, sp, ip); - - JavaFrameAnchor anchor = JavaFrameAnchors.getFrameAnchor(thread); - if (anchor.isNonNull()) { - sp = anchor.getLastJavaSP(); - ip = anchor.getLastJavaIP(); - } else { - return false; - } - } - - JavaStackWalk walk = StackValue.get(JavaStackWalker.sizeOfJavaStackWalk()); - JavaStackWalker.initialize(walk, thread, sp, ip); - return JavaStackWalker.doWalk(walk, thread, printVisitor, log); - } - - @Uninterruptible(reason = "IP is not within Java code, so there is no risk that it gets invalidated.", calleeMustBe = false) - private static void logFrame(Log log, Pointer sp, CodePointer ip) { - Stage0StackFramePrintVisitor.logFrameRaw(log, sp, ip, Word.nullPointer()); - log.string(" IP is not within Java code. Trying frame anchor of last Java frame instead.").newline(); - } } From f818b86aab0afed1908d5380fd6243e052154903 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Thu, 13 Feb 2025 18:02:09 +0100 Subject: [PATCH 5/5] Add basic assertion for reference map. --- .../src/com/oracle/svm/hosted/meta/UniverseBuilder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java index 5c854ef3afc1..d4eb4a910310 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java @@ -906,6 +906,9 @@ private void buildHubs() { ReferenceMapEncoder.Input referenceMap = referenceMaps.get(type); assert referenceMap != null; assert ((SubstrateReferenceMap) referenceMap).hasNoDerivedOffsets(); + ReferenceMapEncoder.OffsetIterator iter = referenceMap.getOffsets(); + assert !iter.hasNext() || iter.nextInt() >= ConfigurationValues.getObjectLayout().getFirstFieldOffset(); + long referenceMapIndex = referenceMapEncoder.lookupEncoding(referenceMap); DynamicHub hub = type.getHub(); @@ -968,7 +971,6 @@ private void buildHubs() { private static ReferenceMapEncoder.Input createReferenceMap(HostedType type) { HostedField[] fields = type.getInstanceFields(true); - SubstrateReferenceMap referenceMap = new SubstrateReferenceMap(); for (HostedField field : fields) { if (field.getType().getStorageKind() == JavaKind.Object && field.hasLocation() && !excludeFromReferenceMap(field)) {