diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
new file mode 100644
index 000000000000..50541c51b511
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.core.posix;
+
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.IsolateThread;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+import org.graalvm.nativeimage.StackValue;
+import org.graalvm.nativeimage.c.function.CEntryPoint;
+import org.graalvm.nativeimage.c.function.CEntryPointLiteral;
+import org.graalvm.nativeimage.c.struct.SizeOf;
+import org.graalvm.nativeimage.c.type.VoidPointer;
+import org.graalvm.nativeimage.hosted.Feature;
+import org.graalvm.word.UnsignedWord;
+import org.graalvm.word.WordFactory;
+
+import com.oracle.svm.core.annotate.AutomaticFeature;
+import com.oracle.svm.core.annotate.RestrictHeapAccess;
+import com.oracle.svm.core.annotate.Uninterruptible;
+import com.oracle.svm.core.c.function.CEntryPointOptions;
+import com.oracle.svm.core.headers.LibC;
+import com.oracle.svm.core.posix.headers.Pthread;
+import com.oracle.svm.core.posix.headers.Signal;
+import com.oracle.svm.core.posix.headers.Time;
+import com.oracle.svm.core.sampler.SubstrateSigprofHandler;
+
+@AutomaticFeature
+@SuppressWarnings("unused")
+class PosixSubstrateSigprofHandlerFeature implements Feature {
+ @Override
+ public void afterRegistration(AfterRegistrationAccess access) {
+ ImageSingletons.add(SubstrateSigprofHandler.class, new PosixSubstrateSigprofHandler());
+ }
+}
+
+public class PosixSubstrateSigprofHandler extends SubstrateSigprofHandler {
+
+ public static final long INTERVAL_S = 0;
+ public static final long INTERVAL_uS = 20_000;
+
+ @Platforms(Platform.HOSTED_ONLY.class)
+ public PosixSubstrateSigprofHandler() {
+ }
+
+ /** The address of the signal handler for signals handled by Java code, below. */
+ private static final CEntryPointLiteral advancedSignalDispatcher = CEntryPointLiteral.create(PosixSubstrateSigprofHandler.class,
+ "dispatch", int.class, Signal.siginfo_t.class, Signal.ucontext_t.class);
+
+ @SuppressWarnings("unused")
+ @CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
+ @CEntryPointOptions(prologue = CEntryPointOptions.NoPrologue.class, epilogue = CEntryPointOptions.NoEpilogue.class)
+ @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate in sigprof signal handler.")
+ @Uninterruptible(reason = "Signal handler may only execute uninterruptible code.")
+ private static void dispatch(@SuppressWarnings("unused") int signalNumber, @SuppressWarnings("unused") Signal.siginfo_t sigInfo, Signal.ucontext_t uContext) {
+ tryEnterIsolateAndDoWalk(uContext);
+ }
+
+ private static void registerSigprofSignal() {
+ int structSigActionSize = SizeOf.get(Signal.sigaction.class);
+ Signal.sigaction structSigAction = StackValue.get(structSigActionSize);
+ LibC.memset(structSigAction, WordFactory.signed(0), WordFactory.unsigned(structSigActionSize));
+
+ /* Register sa_sigaction signal handler */
+ structSigAction.sa_flags(Signal.SA_SIGINFO() | Signal.SA_NODEFER());
+ structSigAction.sa_sigaction(advancedSignalDispatcher.getFunctionPointer());
+ Signal.sigaction(Signal.SignalEnum.SIGPROF.getCValue(), structSigAction, WordFactory.nullPointer());
+ }
+
+ private static int callSetitimer() {
+ /* Call setitimer to start profiling. */
+ Time.itimerval newValue = StackValue.get(Time.itimerval.class);
+ Time.itimerval oldValue = StackValue.get(Time.itimerval.class);
+
+ newValue.it_value().set_tv_sec(INTERVAL_S);
+ newValue.it_value().set_tv_usec(INTERVAL_uS);
+ newValue.it_interval().set_tv_sec(INTERVAL_S);
+ newValue.it_interval().set_tv_usec(INTERVAL_uS);
+
+ return Time.NoTransitions.setitimer(Time.TimerTypeEnum.ITIMER_PROF, newValue, oldValue);
+ }
+
+ @Override
+ protected void install0() {
+ registerSigprofSignal();
+ PosixUtils.checkStatusIs0(callSetitimer(), "setitimer(which, newValue, oldValue): wrong arguments.");
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected UnsignedWord createThreadLocalKey() {
+ Pthread.pthread_key_tPointer key = StackValue.get(Pthread.pthread_key_tPointer.class);
+ PosixUtils.checkStatusIs0(Pthread.pthread_key_create(key, WordFactory.nullPointer()), "pthread_key_create(key, keyDestructor): failed.");
+ return key.read();
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected void deleteThreadLocalKey(UnsignedWord key) {
+ int resultCode = Pthread.pthread_key_delete((Pthread.pthread_key_t) key);
+ PosixUtils.checkStatusIs0(resultCode, "pthread_key_delete(key): failed.");
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected void setThreadLocalKeyValue(UnsignedWord key, IsolateThread value) {
+ int resultCode = Pthread.pthread_setspecific((Pthread.pthread_key_t) key, (VoidPointer) value);
+ PosixUtils.checkStatusIs0(resultCode, "pthread_setspecific(key, value): wrong arguments.");
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected IsolateThread getThreadLocalKeyValue(UnsignedWord key) {
+ /*
+ * Although this method is not async-signal-safe in general we rely on
+ * implementation-specific behavior here.
+ */
+ return (IsolateThread) Pthread.pthread_getspecific((Pthread.pthread_key_t) key);
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Pthread.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Pthread.java
index fb447ac4ad57..c3163b6c2540 100644
--- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Pthread.java
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Pthread.java
@@ -177,6 +177,9 @@ public interface pthread_key_tPointer extends PointerBase {
@CFunction(transition = Transition.NO_TRANSITION)
public static native int pthread_key_create(pthread_key_tPointer key, PointerBase keyDestructor);
+ @CFunction(transition = Transition.NO_TRANSITION)
+ public static native int pthread_key_delete(pthread_key_t key);
+
@CFunction(transition = Transition.NO_TRANSITION)
public static native int pthread_setspecific(pthread_key_t key, VoidPointer value);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java
index ed4c2d76447b..c2bf212c4c8a 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java
@@ -62,8 +62,19 @@ public void afterCreateIsolate(Isolate isolate) {
}
}
+ @Uninterruptible(reason = "The isolate teardown is in progress.")
+ public void onIsolateTeardown() {
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].onIsolateTeardown();
+ }
+ }
+
public interface IsolateListener {
@Uninterruptible(reason = "Thread state not yet set up.")
void afterCreateIsolate(Isolate isolate);
+
+ @Uninterruptible(reason = "The isolate teardown is in progress.")
+ default void onIsolateTeardown() {
+ }
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/SubstrateThreadMXBean.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/SubstrateThreadMXBean.java
index ecc54763cd03..322a5487b911 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/SubstrateThreadMXBean.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/SubstrateThreadMXBean.java
@@ -115,6 +115,7 @@ public boolean isCurrentThreadCpuTimeSupported() {
}
@Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public int getThreadCount() {
return threadCount.get();
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java
index 5d13e056578c..a67410ee653f 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java
@@ -73,14 +73,13 @@ public interface JfrNativeEventWriterData extends PointerBase {
void setCurrentPos(Pointer value);
/**
- * Returns the end position for the current event write. Writing of data cannot exceed this
- * position.
+ * Returns the position where the buffer ends. Writing of data cannot exceed this position.
*/
@RawField
Pointer getEndPos();
/**
- * Sets the end position for the current event write.
+ * Sets the position where the buffer ends.
*/
@RawField
void setEndPos(Pointer value);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java
index 7fe7efb266bb..2c6d918d12c6 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java
@@ -261,7 +261,9 @@ public long getThreadId(Thread thread) {
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public long getThreadId(IsolateThread isolateThread) {
- return threadLocal.getTraceId(isolateThread);
+ long threadId = threadLocal.getTraceId(isolateThread);
+ VMError.guarantee(threadId > 0);
+ return threadId;
}
/** See {@link JVM#storeMetadataDescriptor}. */
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java
new file mode 100644
index 000000000000..5f1f57f15cbc
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.core.sampler;
+
+import org.graalvm.nativeimage.c.struct.RawField;
+import org.graalvm.nativeimage.c.struct.RawStructure;
+import org.graalvm.word.Pointer;
+import org.graalvm.word.PointerBase;
+import org.graalvm.word.UnsignedWord;
+
+/**
+ * A {@link SamplerBuffer} is a block of native memory into which the results of stack walks are
+ * written.
+ */
+@RawStructure
+interface SamplerBuffer extends PointerBase {
+
+ /**
+ * Returns the buffer that is next in the {@link SamplerBufferStack}, otherwise null.
+ */
+ @RawField
+ SamplerBuffer getNext();
+
+ /**
+ * Sets the successor to this buffer in the {@link SamplerBufferStack}.
+ */
+ @RawField
+ void setNext(SamplerBuffer buffer);
+
+ /**
+ * Returns the JFR id of the thread that owns this buffer.
+ */
+ @RawField
+ long getOwner();
+
+ /**
+ * Sets the JFR id of the thread that owns this buffer.
+ */
+ @RawField
+ void setOwner(long threadId);
+
+ /**
+ * Returns the current position. Any data before this position is valid sample data.
+ */
+ @RawField
+ Pointer getPos();
+
+ /**
+ * Sets the current position.
+ */
+ @RawField
+ void setPos(Pointer pos);
+
+ /**
+ * Returns the size of the buffer. This excludes the header of the buffer.
+ */
+ @RawField
+ UnsignedWord getSize();
+
+ /**
+ * Sets the size of the buffer.
+ */
+ @RawField
+ void setSize(UnsignedWord value);
+
+ /**
+ * Should this buffer be freed after processing the data in it.
+ */
+ @RawField
+ boolean getFreeable();
+
+ /**
+ * Sets the freeable status of the buffer.
+ */
+ @RawField
+ void setFreeable(boolean freeable);
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java
new file mode 100644
index 000000000000..f8021d4ee885
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.core.sampler;
+
+import org.graalvm.compiler.api.replacements.Fold;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.c.struct.SizeOf;
+import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
+import org.graalvm.word.Pointer;
+import org.graalvm.word.UnsignedWord;
+import org.graalvm.word.WordFactory;
+
+import com.oracle.svm.core.annotate.Uninterruptible;
+import com.oracle.svm.core.config.ConfigurationValues;
+import com.oracle.svm.core.util.UnsignedUtils;
+
+/**
+ * Used to access the raw memory of a {@link SamplerBufferAccess}.
+ */
+final class SamplerBufferAccess {
+
+ private SamplerBufferAccess() {
+ }
+
+ @Fold
+ public static UnsignedWord getHeaderSize() {
+ return UnsignedUtils.roundUp(SizeOf.unsigned(SamplerBuffer.class), WordFactory.unsigned(ConfigurationValues.getTarget().wordSize));
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static SamplerBuffer allocate(UnsignedWord dataSize) {
+ UnsignedWord headerSize = SamplerBufferAccess.getHeaderSize();
+ SamplerBuffer result = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(headerSize.add(dataSize));
+ if (result.isNonNull()) {
+ result.setSize(dataSize);
+ result.setFreeable(false);
+ reinitialize(result);
+ }
+ return result;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static void free(SamplerBuffer buffer) {
+ ImageSingletons.lookup(UnmanagedMemorySupport.class).free(buffer);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static void reinitialize(SamplerBuffer buffer) {
+ Pointer dataStart = getDataStart(buffer);
+ buffer.setPos(dataStart);
+ buffer.setOwner(0L);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static Pointer getDataStart(SamplerBuffer buffer) {
+ return ((Pointer) buffer).add(getHeaderSize());
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static boolean isEmpty(SamplerBuffer buffer) {
+ return getDataStart(buffer).equal(buffer.getPos());
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static Pointer getDataEnd(SamplerBuffer buffer) {
+ return getDataStart(buffer).add(buffer.getSize());
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java
new file mode 100644
index 000000000000..96af063d0e6b
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.core.sampler;
+
+import org.graalvm.word.WordFactory;
+
+import com.oracle.svm.core.annotate.Uninterruptible;
+import com.oracle.svm.core.locks.VMMutex;
+import com.oracle.svm.core.util.VMError;
+
+import jdk.jfr.internal.Options;
+
+/**
+ * The pool that maintains the desirable number of buffers in the system by allocating/releasing
+ * extra buffers.
+ */
+class SamplerBufferPool {
+
+ private static final long THREAD_BUFFER_SIZE = Options.getThreadBufferSize();
+
+ private static final VMMutex mutex = new VMMutex("SamplerBufferPool");
+
+ private static long bufferCount;
+
+ @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true)
+ public static void releaseBufferAndAdjustCount(SamplerBuffer threadLocalBuffer) {
+ adjustBufferCount0(threadLocalBuffer);
+ }
+
+ @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true)
+ public static void adjustBufferCount() {
+ adjustBufferCount0(WordFactory.nullPointer());
+ }
+
+ @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true)
+ private static void adjustBufferCount0(SamplerBuffer threadLocalBuffer) {
+ mutex.lockNoTransition();
+ try {
+ releaseThreadLocalBuffer(threadLocalBuffer);
+ long diff = diff();
+ if (diff > 0) {
+ for (int i = 0; i < diff; i++) {
+ if (!allocateAndPush()) {
+ break;
+ }
+ }
+ } else {
+ for (long i = diff; i < 0; i++) {
+ if (!popAndFree()) {
+ break;
+ }
+ }
+ }
+ } finally {
+ mutex.unlock();
+ }
+ }
+
+ @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true)
+ private static void releaseThreadLocalBuffer(SamplerBuffer buffer) {
+ /*
+ * buffer is null if the thread is not running yet, or we did not perform the stack walk for
+ * this thread during the run.
+ */
+ if (buffer.isNonNull()) {
+ if (SamplerBufferAccess.isEmpty(buffer)) {
+ /* We can free it right away. */
+ SamplerBufferAccess.free(buffer);
+ } else {
+ /* Put it in the stack with other unprocessed buffers. */
+ buffer.setFreeable(true);
+ SubstrateSigprofHandler.singleton().fullBuffers().pushBuffer(buffer);
+ }
+ VMError.guarantee(bufferCount > 0);
+ bufferCount--;
+ }
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static boolean allocateAndPush() {
+ VMError.guarantee(bufferCount >= 0);
+ SamplerBuffer buffer = SamplerBufferAccess.allocate(WordFactory.unsigned(THREAD_BUFFER_SIZE));
+ if (buffer.isNonNull()) {
+ SubstrateSigprofHandler.singleton().availableBuffers().pushBuffer(buffer);
+ bufferCount++;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static boolean popAndFree() {
+ VMError.guarantee(bufferCount > 0);
+ SamplerBuffer buffer = SubstrateSigprofHandler.singleton().availableBuffers().popBuffer();
+ if (buffer.isNonNull()) {
+ SamplerBufferAccess.free(buffer);
+ bufferCount--;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static long diff() {
+ double diffD = SubstrateSigprofHandler.singleton().substrateThreadMXBean().getThreadCount() * 1.5 - bufferCount;
+ return (long) (diffD + 0.5);
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java
new file mode 100644
index 000000000000..229e942b6892
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.core.sampler;
+
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+import org.graalvm.word.WordFactory;
+
+import com.oracle.svm.core.annotate.Uninterruptible;
+
+/**
+ * The linked-list implementation of the stack that holds a sequence of native memory buffers.
+ *
+ * The stack uses spin-lock to protect itself from races with competing pop operations (ABA
+ * problem).
+ *
+ * @see SamplerSpinLock
+ */
+class SamplerBufferStack {
+
+ private SamplerBuffer head;
+ private final SamplerSpinLock spinLock;
+
+ @Platforms(Platform.HOSTED_ONLY.class)
+ SamplerBufferStack() {
+ this.spinLock = new SamplerSpinLock();
+ }
+
+ /**
+ * Push the buffer into the linked-list.
+ */
+ @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.")
+ public void pushBuffer(SamplerBuffer buffer) {
+ spinLock.lock();
+ try {
+ buffer.setNext(head);
+ head = buffer;
+ } finally {
+ spinLock.unlock();
+ }
+ }
+
+ /**
+ * Pop the buffer from the linked-list. Returns {@code null} if the list is empty.
+ */
+ @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.")
+ public SamplerBuffer popBuffer() {
+ spinLock.lock();
+ try {
+ SamplerBuffer result = head;
+ if (result.isNonNull()) {
+ head = head.getNext();
+ result.setNext(WordFactory.nullPointer());
+ }
+ return result;
+ } finally {
+ spinLock.unlock();
+ }
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public boolean isLockedByCurrentThread() {
+ return spinLock.isOwner();
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerIsolateLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerIsolateLocal.java
new file mode 100644
index 000000000000..bd25d3101b71
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerIsolateLocal.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.core.sampler;
+
+import org.graalvm.nativeimage.CurrentIsolate;
+import org.graalvm.nativeimage.Isolate;
+import org.graalvm.word.LocationIdentity;
+import org.graalvm.word.Pointer;
+import org.graalvm.word.UnsignedWord;
+import org.graalvm.word.WordFactory;
+
+import com.oracle.svm.core.IsolateListenerSupport;
+import com.oracle.svm.core.annotate.Uninterruptible;
+import com.oracle.svm.core.c.CGlobalData;
+import com.oracle.svm.core.c.CGlobalDataFactory;
+
+class SamplerIsolateLocal implements IsolateListenerSupport.IsolateListener {
+
+ /** Stores the address of the first isolate created. */
+ private static final CGlobalData firstIsolate = CGlobalDataFactory.createWord();
+
+ /** Stores the isolate-specific key. */
+ private static UnsignedWord key = WordFactory.unsigned(0);
+
+ @Override
+ @Uninterruptible(reason = "Thread state not yet set up.")
+ public void afterCreateIsolate(Isolate isolate) {
+ if (SubstrateSigprofHandler.isProfilingSupported()) {
+ if (firstIsolate.get().logicCompareAndSwapWord(0, WordFactory.zero(), isolate, LocationIdentity.ANY_LOCATION)) {
+ key = SubstrateSigprofHandler.singleton().createThreadLocalKey();
+ }
+ }
+ }
+
+ @Override
+ @Uninterruptible(reason = "The isolate teardown is in progress.")
+ public void onIsolateTeardown() {
+ if (SubstrateSigprofHandler.singleton().isProfilingEnabled() && isKeySet()) {
+ /* Invalidate the isolate-specific key. */
+ UnsignedWord oldKey = key;
+ key = WordFactory.unsigned(0);
+
+ /* Manually disable sampling for the current thread (no other threads are remaining). */
+ SamplerThreadLocal.teardown(CurrentIsolate.getCurrentThread());
+
+ /* Now, it's safe to delete the isolate-specific key. */
+ SubstrateSigprofHandler.singleton().deleteThreadLocalKey(oldKey);
+ }
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static Isolate getIsolate() {
+ return firstIsolate.get().readWord(0);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static UnsignedWord getKey() {
+ return key;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static boolean isKeySet() {
+ return key.aboveThan(0);
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java
new file mode 100644
index 000000000000..7290fea6c6c6
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.core.sampler;
+
+import org.graalvm.word.UnsignedWord;
+import org.graalvm.word.WordFactory;
+
+import com.oracle.svm.core.UnmanagedMemoryUtil;
+import com.oracle.svm.core.annotate.Uninterruptible;
+import com.oracle.svm.core.util.VMError;
+
+final class SamplerSampleWriter {
+
+ private static final int END_MARKER_SIZE = Long.BYTES;
+ private static final long END_MARKER = -1;
+
+ private SamplerSampleWriter() {
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static boolean putLong(SamplerSampleWriterData data, long value) {
+ if (ensureSize(data, Long.BYTES)) {
+ data.getCurrentPos().writeLong(0, value);
+ increaseCurrentPos(data, WordFactory.unsigned(Long.BYTES));
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static void commit(SamplerSampleWriterData data) {
+ SamplerBuffer buffer = data.getSamplerBuffer();
+ /*
+ * put END_MARKER should not fail as ensureSize takes end marker size in consideration.
+ */
+ VMError.guarantee(getAvailableSize(data).aboveOrEqual(END_MARKER_SIZE));
+ data.getCurrentPos().writeLong(0, END_MARKER);
+ increaseCurrentPos(data, WordFactory.unsigned(Long.BYTES));
+
+ buffer.setPos(data.getCurrentPos());
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static boolean ensureSize(SamplerSampleWriterData data, int requested) {
+ assert requested > 0;
+ int totalRequested = requested + END_MARKER_SIZE;
+ if (getAvailableSize(data).belowThan(totalRequested)) {
+ if (!accommodate(data, getUncommittedSize(data))) {
+ return false;
+ }
+ }
+ assert getAvailableSize(data).aboveOrEqual(totalRequested);
+ return true;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static boolean accommodate(SamplerSampleWriterData data, UnsignedWord uncommitted) {
+ if (SamplerBufferAccess.isEmpty(data.getSamplerBuffer())) {
+ /*
+ * Sample is too big to fit into the size of one buffer i.e. we want to do
+ * accommodations while nothing is committed into buffer.
+ */
+ SamplerThreadLocal.increaseMissedSamples();
+ return false;
+ }
+
+ /* Pop first free buffer from the pool. */
+ SamplerBuffer newBuffer = SubstrateSigprofHandler.singleton().availableBuffers().popBuffer();
+ if (newBuffer.isNull()) {
+ /* No available buffers on the pool. Fallback! */
+ SamplerThreadLocal.increaseMissedSamples();
+ return false;
+ }
+ SamplerThreadLocal.setThreadLocalBuffer(newBuffer);
+
+ /* Copy the uncommitted content of old buffer into new one. */
+ UnmanagedMemoryUtil.copy(data.getStartPos(), SamplerBufferAccess.getDataStart(newBuffer), uncommitted);
+
+ /* Put in the stack with other unprocessed buffers. */
+ SamplerBuffer oldBuffer = data.getSamplerBuffer();
+ SubstrateSigprofHandler.singleton().fullBuffers().pushBuffer(oldBuffer);
+
+ /* Reinitialize data structure. */
+ data.setSamplerBuffer(newBuffer);
+ reset(data);
+ increaseCurrentPos(data, uncommitted);
+ return true;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static UnsignedWord getAvailableSize(SamplerSampleWriterData data) {
+ return data.getEndPos().subtract(data.getCurrentPos());
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static UnsignedWord getUncommittedSize(SamplerSampleWriterData data) {
+ return data.getCurrentPos().subtract(data.getStartPos());
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static void increaseCurrentPos(SamplerSampleWriterData data, UnsignedWord delta) {
+ data.setCurrentPos(data.getCurrentPos().add(delta));
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static void reset(SamplerSampleWriterData data) {
+ SamplerBuffer buffer = data.getSamplerBuffer();
+ data.setStartPos(buffer.getPos());
+ data.setCurrentPos(buffer.getPos());
+ data.setEndPos(SamplerBufferAccess.getDataEnd(buffer));
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterData.java
new file mode 100644
index 000000000000..49cdaaf18635
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterData.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.core.sampler;
+
+import org.graalvm.nativeimage.c.struct.RawField;
+import org.graalvm.nativeimage.c.struct.RawStructure;
+import org.graalvm.word.Pointer;
+import org.graalvm.word.PointerBase;
+
+/**
+ * A data structure that holds the mutable state of a {@link SamplerSampleWriter}. Typically, it is
+ * allocated on the stack.
+ */
+@RawStructure
+interface SamplerSampleWriterData extends PointerBase {
+ /**
+ * Gets the buffer that data will be written to.
+ */
+ @RawField
+ SamplerBuffer getSamplerBuffer();
+
+ /**
+ * Sets the buffer that data will be written to.
+ */
+ @RawField
+ void setSamplerBuffer(SamplerBuffer buffer);
+
+ /**
+ * Gets the start position for the current sample write.
+ */
+ @RawField
+ Pointer getStartPos();
+
+ /**
+ * Sets the start position for the current sample write.
+ */
+ @RawField
+ void setStartPos(Pointer value);
+
+ /**
+ * Gets the current position of the sample write. This position is moved forward as data is
+ * written for a sample.
+ */
+ @RawField
+ Pointer getCurrentPos();
+
+ /**
+ * Sets the current position of the sample write.
+ */
+ @RawField
+ void setCurrentPos(Pointer value);
+
+ /**
+ * Returns the position where the buffer ends. Writing of data cannot exceed this position.
+ */
+ @RawField
+ Pointer getEndPos();
+
+ /**
+ * Sets the position where the buffer ends.
+ */
+ @RawField
+ void setEndPos(Pointer value);
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java
new file mode 100644
index 000000000000..85dc89e26577
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.core.sampler;
+
+import org.graalvm.compiler.nodes.PauseNode;
+import org.graalvm.nativeimage.CurrentIsolate;
+import org.graalvm.nativeimage.IsolateThread;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+import org.graalvm.word.WordFactory;
+
+import com.oracle.svm.core.annotate.Uninterruptible;
+import com.oracle.svm.core.jdk.UninterruptibleUtils;
+import com.oracle.svm.core.util.VMError;
+
+/**
+ * The custom implementation of spin lock that is async signal safe.
+ *
+ * In some specific situations, the signal handler can interrupt execution while the same thread
+ * already has the lock. This implementation will check and fatally fail while other spin locks
+ * implementations can deadlock in this case. So it is essential to check if the current thread is
+ * the owner of the lock, before acquiring it.
+ */
+class SamplerSpinLock {
+ private final UninterruptibleUtils.AtomicPointer owner;
+
+ @Platforms(Platform.HOSTED_ONLY.class)
+ SamplerSpinLock() {
+ this.owner = new UninterruptibleUtils.AtomicPointer<>();
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public boolean isOwner() {
+ return owner.get().equal(CurrentIsolate.getCurrentThread());
+ }
+
+ @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true)
+ public void lock() {
+ VMError.guarantee(!isOwner(), "The current thread already has the lock!");
+ IsolateThread currentThread = CurrentIsolate.getCurrentThread();
+ while (!owner.compareAndSet(WordFactory.nullPointer(), currentThread)) {
+ PauseNode.pause();
+ }
+ }
+
+ @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true)
+ public void unlock() {
+ VMError.guarantee(isOwner(), "The current thread doesn't have the lock!");
+ owner.compareAndSet(CurrentIsolate.getCurrentThread(), WordFactory.nullPointer());
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackWalkVisitor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackWalkVisitor.java
new file mode 100644
index 000000000000..3998a5b550d4
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackWalkVisitor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.core.sampler;
+
+import org.graalvm.nativeimage.c.function.CodePointer;
+import org.graalvm.word.Pointer;
+
+import com.oracle.svm.core.annotate.Uninterruptible;
+import com.oracle.svm.core.code.CodeInfo;
+import com.oracle.svm.core.deopt.DeoptimizedFrame;
+import com.oracle.svm.core.stack.ParameterizedStackFrameVisitor;
+
+final class SamplerStackWalkVisitor extends ParameterizedStackFrameVisitor {
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptimizedFrame, Void voidData) {
+ return SamplerSampleWriter.putLong(SamplerThreadLocal.getWriterData(), ip.rawValue());
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected boolean unknownFrame(Pointer sp, CodePointer ip, DeoptimizedFrame deoptimizedFrame, Void data) {
+ SamplerThreadLocal.increaseUnparseableStacks();
+ return false;
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java
new file mode 100644
index 000000000000..6cd132cda1fe
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.core.sampler;
+
+import org.graalvm.nativeimage.CurrentIsolate;
+import org.graalvm.nativeimage.IsolateThread;
+import org.graalvm.word.UnsignedWord;
+import org.graalvm.word.WordFactory;
+
+import com.oracle.svm.core.annotate.Uninterruptible;
+import com.oracle.svm.core.jfr.SubstrateJVM;
+import com.oracle.svm.core.thread.ThreadListener;
+import com.oracle.svm.core.threadlocal.FastThreadLocalFactory;
+import com.oracle.svm.core.threadlocal.FastThreadLocalLong;
+import com.oracle.svm.core.threadlocal.FastThreadLocalWord;
+
+class SamplerThreadLocal implements ThreadListener {
+
+ private static final FastThreadLocalWord localBuffer = FastThreadLocalFactory.createWord("SamplerThreadLocal.localBuffer");
+ private static final FastThreadLocalLong missedSamples = FastThreadLocalFactory.createLong("SamplerThreadLocal.missedSamples");
+ private static final FastThreadLocalLong unparseableStacks = FastThreadLocalFactory.createLong("SamplerThreadLocal.unparseableStacks");
+ /**
+ * The data that we are using during the stack walk, allocated on the stack.
+ */
+ private static final FastThreadLocalWord writerData = FastThreadLocalFactory.createWord("SamplerThreadLocal.writerData");
+
+ @Override
+ @Uninterruptible(reason = "Only uninterruptible code may be executed before Thread.run.")
+ public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) {
+ if (SubstrateSigprofHandler.singleton().isProfilingEnabled()) {
+ initialize(isolateThread);
+ }
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ static void initialize(IsolateThread isolateThread) {
+ if (SamplerIsolateLocal.isKeySet()) {
+ /* Adjust the number of buffers. */
+ SamplerBufferPool.adjustBufferCount();
+
+ /*
+ * Save isolate thread in thread-local area.
+ *
+ * Once this value is set, the signal handler may interrupt this thread at any time. So,
+ * it is essential that this value is set at the very end of this method.
+ */
+ UnsignedWord key = SamplerIsolateLocal.getKey();
+ SubstrateSigprofHandler.singleton().setThreadLocalKeyValue(key, isolateThread);
+ }
+ }
+
+ @Override
+ @Uninterruptible(reason = "Only uninterruptible code may be executed after Thread.exit.")
+ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) {
+ if (SubstrateSigprofHandler.singleton().isProfilingEnabled() && SamplerIsolateLocal.isKeySet()) {
+ teardown(isolateThread);
+ }
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ static void teardown(IsolateThread isolateThread) {
+ /*
+ * Invalidate thread-local area.
+ *
+ * Once this value is set to null, the signal handler can't interrupt this thread anymore.
+ * So, it is essential that this value is set at the very beginning of this method i.e.
+ * before doing cleanup.
+ */
+ UnsignedWord key = SamplerIsolateLocal.getKey();
+ SubstrateSigprofHandler.singleton().setThreadLocalKeyValue(key, WordFactory.nullPointer());
+
+ /* Adjust the number of buffers (including the thread-local buffer). */
+ SamplerBuffer threadLocalBuffer = localBuffer.get(isolateThread);
+ SamplerBufferPool.releaseBufferAndAdjustCount(threadLocalBuffer);
+ localBuffer.set(isolateThread, WordFactory.nullPointer());
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static SamplerBuffer getThreadLocalBuffer() {
+ return localBuffer.get();
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static void setThreadLocalBuffer(SamplerBuffer buffer) {
+ buffer.setOwner(SubstrateJVM.get().getThreadId(CurrentIsolate.getCurrentThread()));
+ localBuffer.set(buffer);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static void increaseMissedSamples() {
+ missedSamples.set(getMissedSamples() + 1);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static long getMissedSamples() {
+ return missedSamples.get();
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static void increaseUnparseableStacks() {
+ unparseableStacks.set(getUnparseableStacks() + 1);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static long getUnparseableStacks() {
+ return unparseableStacks.get();
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static void setWriterData(SamplerSampleWriterData data) {
+ writerData.set(data);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static SamplerSampleWriterData getWriterData() {
+ return writerData.get();
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java
new file mode 100644
index 000000000000..b3751bce98dc
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.core.sampler;
+
+import java.lang.management.ManagementFactory;
+import java.util.Arrays;
+import java.util.List;
+
+import org.graalvm.compiler.api.replacements.Fold;
+import org.graalvm.compiler.options.Option;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.Isolate;
+import org.graalvm.nativeimage.IsolateThread;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+import org.graalvm.nativeimage.StackValue;
+import org.graalvm.nativeimage.c.function.CodePointer;
+import org.graalvm.nativeimage.hosted.Feature;
+import org.graalvm.word.Pointer;
+import org.graalvm.word.UnsignedWord;
+
+import com.oracle.svm.core.IsolateListenerSupport;
+import com.oracle.svm.core.RegisterDumper;
+import com.oracle.svm.core.VMInspectionOptions;
+import com.oracle.svm.core.annotate.AutomaticFeature;
+import com.oracle.svm.core.annotate.Uninterruptible;
+import com.oracle.svm.core.code.CodeInfo;
+import com.oracle.svm.core.code.CodeInfoAccess;
+import com.oracle.svm.core.code.CodeInfoTable;
+import com.oracle.svm.core.graal.nodes.WriteCurrentVMThreadNode;
+import com.oracle.svm.core.graal.nodes.WriteHeapBaseNode;
+import com.oracle.svm.core.heap.VMOperationInfos;
+import com.oracle.svm.core.jdk.RuntimeSupport;
+import com.oracle.svm.core.jdk.management.ManagementFeature;
+import com.oracle.svm.core.jdk.management.SubstrateThreadMXBean;
+import com.oracle.svm.core.jfr.JfrFeature;
+import com.oracle.svm.core.option.RuntimeOptionKey;
+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.thread.JavaVMOperation;
+import com.oracle.svm.core.thread.ThreadListenerFeature;
+import com.oracle.svm.core.thread.ThreadListenerSupport;
+import com.oracle.svm.core.thread.VMThreads;
+
+@AutomaticFeature
+@SuppressWarnings("unused")
+class SubstrateSigprofHandlerFeature implements Feature {
+
+ @Override
+ public boolean isInConfiguration(IsInConfigurationAccess access) {
+ return VMInspectionOptions.AllowVMInspection.getValue() && SubstrateSigprofHandler.isProfilingSupported();
+ }
+
+ @Override
+ public List> getRequiredFeatures() {
+ return Arrays.asList(ThreadListenerFeature.class, JfrFeature.class, ManagementFeature.class);
+ }
+
+ @Override
+ public void beforeAnalysis(BeforeAnalysisAccess access) {
+ if (!ImageSingletons.contains(SubstrateSigprofHandler.class)) {
+ /* No sigprof handler. */
+ return;
+ }
+
+ /* Create stack visitor. */
+ ImageSingletons.add(SamplerStackWalkVisitor.class, new SamplerStackWalkVisitor());
+
+ /* Add listeners. */
+ ThreadListenerSupport.get().register(new SamplerThreadLocal());
+ IsolateListenerSupport.singleton().register(new SamplerIsolateLocal());
+
+ /* Add startup hook. */
+ RuntimeSupport.getRuntimeSupport().addStartupHook(new SubstrateSigprofHandlerStartupHook());
+ }
+}
+
+final class SubstrateSigprofHandlerStartupHook implements RuntimeSupport.Hook {
+ @Override
+ public void execute(boolean isFirstIsolate) {
+ if (isFirstIsolate) {
+ SubstrateSigprofHandler.singleton().install();
+ }
+ }
+}
+
+/**
+ *
+ * The core class of low overhead asynchronous sampling based profiler.
+ * {@link SubstrateSigprofHandler} handles the periodic signal generated by the OS with a given
+ * time-frequency. The asynchronous nature of the signal means that the OS could invoke the signal
+ * handler at any time (could be during GC, VM operation, uninterruptible code) and that the signal
+ * handler can only execute specific code i.e. the calls that are asynchronous signal safe.
+ *
+ *
+ *
+ * The signal handler is divided into three part: restore isolate, isolate-thread, stack and
+ * instruction pointers, prepare everything necessary for stack walk, do a stack walk and write IPs
+ * into buffer.
+ *
+ *
+ *
+ * The signal handler is as a producer. On the other side of relation is
+ * {@link com.oracle.svm.core.jfr.JfrRecorderThread} that is consumer. The
+ * {@link SamplerBuffer} that we are using in this consumer-producer communication is allocated
+ * eagerly, in a part of the heap that is not accessible via GC, and there will always be more
+ * available buffers that threads.
+ *
+ *
+ *
+ * The communication between consumer and producer goes as follows:
+ *
+ * - Signal handler (producer): pops the buffer from the pool of available buffers (the buffer now
+ * becomes thread-local), writes the IPs into buffer, if the buffer is full and moves it to a pool
+ * with buffers that awaits processing.
+ * - Recorder thread (consumer): pops the buffer from the pool of full buffers, reconstructs the
+ * stack walk based on IPs and pushes the buffer into pool of available buffers.
+ *
+ * NOTE: The producer and the consumer are always accessing different buffers.
+ *
+ *
+ *
+ * In some rare cases, the profiling is impossible e.g. no available buffers in the pool, unknown IP
+ * during stack walk, the thread holds the pool's lock when the signal arrives, etc.
+ *
+ *
+ * @see SamplerSpinLock
+ * @see SamplerBufferStack
+ */
+public abstract class SubstrateSigprofHandler {
+
+ public static class Options {
+ @Option(help = "Allow sampling-based profiling. Default: disabled in execution.")//
+ static final RuntimeOptionKey SamplingBasedProfiling = new RuntimeOptionKey<>(Boolean.FALSE);
+ }
+
+ private boolean enabled;
+ private final SamplerBufferStack availableBuffers;
+ private final SamplerBufferStack fullBuffers;
+ private SubstrateThreadMXBean threadMXBean;
+
+ @Platforms(Platform.HOSTED_ONLY.class)
+ protected SubstrateSigprofHandler() {
+ this.availableBuffers = new SamplerBufferStack();
+ this.fullBuffers = new SamplerBufferStack();
+ }
+
+ @Fold
+ static SubstrateSigprofHandler singleton() {
+ return ImageSingletons.lookup(SubstrateSigprofHandler.class);
+ }
+
+ @Fold
+ static SamplerStackWalkVisitor visitor() {
+ return ImageSingletons.lookup(SamplerStackWalkVisitor.class);
+ }
+
+ @Fold
+ static boolean isProfilingSupported() {
+ return Platform.includedIn(Platform.LINUX.class);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ boolean isProfilingEnabled() {
+ return enabled;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ SamplerBufferStack availableBuffers() {
+ return availableBuffers;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ SamplerBufferStack fullBuffers() {
+ return fullBuffers;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ SubstrateThreadMXBean substrateThreadMXBean() {
+ return threadMXBean;
+ }
+
+ /**
+ * Installs the platform dependent sigprof handler.
+ */
+ void install() {
+ if (Options.SamplingBasedProfiling.getValue()) {
+ threadMXBean = (SubstrateThreadMXBean) ManagementFactory.getThreadMXBean();
+ /* Call VM operation to initialize the profiler and the threads. */
+ InitializeProfilerOperation initializeProfilerOperation = new InitializeProfilerOperation();
+ initializeProfilerOperation.enqueue();
+
+ /* After the VM operations finishes. Install handler and start profiling. */
+ install0();
+ }
+ }
+
+ protected abstract void install0();
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected abstract UnsignedWord createThreadLocalKey();
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected abstract void deleteThreadLocalKey(UnsignedWord key);
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected abstract void setThreadLocalKeyValue(UnsignedWord key, IsolateThread value);
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected abstract IsolateThread getThreadLocalKeyValue(UnsignedWord key);
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static boolean isIPInJavaCode(RegisterDumper.Context uContext) {
+ Pointer ip = (Pointer) RegisterDumper.singleton().getIP(uContext);
+ CodeInfo codeInfo = CodeInfoTable.getImageCodeInfo();
+ Pointer codeStart = (Pointer) CodeInfoAccess.getCodeStart(codeInfo);
+ UnsignedWord codeSize = CodeInfoAccess.getCodeSize(codeInfo);
+ return ip.aboveOrEqual(codeStart) && ip.belowOrEqual(codeStart.add(codeSize));
+ }
+
+ @Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true)
+ private static void doUninterruptibleStackWalk(RegisterDumper.Context uContext, boolean isIPInJavaCode) {
+ CodePointer ip;
+ Pointer sp;
+ if (isIPInJavaCode) {
+ ip = (CodePointer) RegisterDumper.singleton().getIP(uContext);
+ sp = (Pointer) RegisterDumper.singleton().getSP(uContext);
+ } else {
+ JavaFrameAnchor anchor = JavaFrameAnchors.getFrameAnchor();
+ if (anchor.isNull()) {
+ /*
+ * The anchor is still null if the function is interrupted during prologue. See:
+ * com.oracle.svm.core.graal.snippets.CFunctionSnippets.prologueSnippet
+ */
+ return;
+ }
+
+ ip = anchor.getLastJavaIP();
+ sp = anchor.getLastJavaSP();
+
+ if (ip.isNull() || sp.isNull()) {
+ /*
+ * It can happen that anchor is in list of all anchors, but its IP and SP are not
+ * filled yet.
+ */
+ return;
+ }
+ }
+
+ /* Initialize stack walk. */
+ SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class);
+ if (prepareStackWalk(data)) {
+ /* Walk the stack. */
+ if (JavaStackWalker.walkCurrentThread(sp, ip, visitor())) {
+ SamplerSampleWriter.commit(data);
+ }
+ }
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static boolean prepareStackWalk(SamplerSampleWriterData data) {
+ if (singleton().availableBuffers().isLockedByCurrentThread() || singleton().fullBuffers().isLockedByCurrentThread()) {
+ /*
+ * The current thread already holds the stack lock, so we can't access it. It's way
+ * better to lose one sample, then potentially the whole buffer.
+ */
+ SamplerThreadLocal.increaseMissedSamples();
+ return false;
+ }
+
+ SamplerBuffer buffer = SamplerThreadLocal.getThreadLocalBuffer();
+ if (buffer.isNull()) {
+ /* Pop first free buffer from the pool. */
+ buffer = singleton().availableBuffers().popBuffer();
+ if (buffer.isNull()) {
+ /* No available buffers on the pool. Fallback! */
+ SamplerThreadLocal.increaseMissedSamples();
+ return false;
+ }
+ SamplerThreadLocal.setThreadLocalBuffer(buffer);
+ }
+
+ /* Initialize the buffer. */
+ data.setSamplerBuffer(buffer);
+ data.setStartPos(buffer.getPos());
+ data.setCurrentPos(buffer.getPos());
+ data.setEndPos(SamplerBufferAccess.getDataEnd(buffer));
+ SamplerThreadLocal.setWriterData(data);
+ return true;
+ }
+
+ /** Called from the platform dependent sigprof handler to enter isolate. */
+ @Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true)
+ protected static void tryEnterIsolateAndDoWalk(RegisterDumper.Context uContext) {
+ Isolate isolate = SamplerIsolateLocal.getIsolate();
+ if (isolate.isNull()) {
+ /* It may happen that the initial isolate exited. */
+ return;
+ }
+
+ /* Write isolate pointer (heap base) into register. */
+ WriteHeapBaseNode.writeCurrentVMHeapBase(isolate);
+
+ /* We are keeping reference to isolate thread inside OS thread local area. */
+ if (!SamplerIsolateLocal.isKeySet()) {
+ /* The key becomes invalid during initial isolate teardown. */
+ return;
+ }
+ UnsignedWord key = SamplerIsolateLocal.getKey();
+ IsolateThread thread = singleton().getThreadLocalKeyValue(key);
+ if (thread.isNull()) {
+ /* Thread is not yet initialized or already detached from isolate. */
+ return;
+ }
+
+ /* Write isolate thread pointer into register. */
+ WriteCurrentVMThreadNode.writeCurrentVMThread(thread);
+
+ /* Check if the instruction pointer (IP) is inside native or java code. */
+ boolean isIPInJavaCode = isIPInJavaCode(uContext);
+
+ /* Walk the stack. */
+ doUninterruptibleStackWalk(uContext, isIPInJavaCode);
+ }
+
+ private class InitializeProfilerOperation extends JavaVMOperation {
+
+ protected InitializeProfilerOperation() {
+ super(VMOperationInfos.get(InitializeProfilerOperation.class, "Initialize Profiler", SystemEffect.SAFEPOINT));
+ }
+
+ @Override
+ protected void operate() {
+ initialize();
+ }
+
+ /**
+ * We need to ensure that all threads are properly initialized at a moment when we start a
+ * profiling.
+ */
+ @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.")
+ private void initialize() {
+ /*
+ * Iterate all over all thread and initialize the thread-local storage of each thread.
+ */
+ for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {
+ SamplerThreadLocal.initialize(thread);
+ }
+ enabled = true;
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java
index 2436aaf19898..a0283ea6b14b 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java
@@ -65,7 +65,7 @@ public void beforeThreadStart(IsolateThread isolateThread, Thread javaThread) {
@Uninterruptible(reason = "Force that all listeners are uninterruptible.")
public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) {
- for (int i = 0; i < listeners.length; i++) {
+ for (int i = listeners.length - 1; i >= 0; i--) {
listeners[i].afterThreadExit(isolateThread, javaThread);
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java
index fd596ed88cca..6a28ba4f18ef 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java
@@ -24,6 +24,7 @@
*/
package com.oracle.svm.core.thread;
+import com.oracle.svm.core.IsolateListenerSupport;
import org.graalvm.compiler.api.directives.GraalDirectives;
import org.graalvm.compiler.api.replacements.Fold;
import org.graalvm.compiler.replacements.ReplacementsUtil;
@@ -450,6 +451,7 @@ public void tearDown() {
VMOperationControl.shutdownAndDetachVMOperationThread();
}
// At this point, it is guaranteed that all other threads were detached.
+ IsolateListenerSupport.singleton().onIsolateTeardown();
waitUntilLastOsThreadExited();
}