From e2c0a65427c645951eac2a53057056085fdc5c30 Mon Sep 17 00:00:00 2001
From: jovanstevanovic
Date: Thu, 1 Feb 2024 11:44:41 +0100
Subject: [PATCH] Replace setitimer with timer_create method. Implement
isHidden entry in JfrMethod.
---
.../posix/PosixSubstrateSigprofHandler.java | 79 +++++------
.../darwin/DarwinSubstrateSigprofHandler.java | 90 ++++++++++++
.../oracle/svm/core/posix/headers/Signal.java | 15 ++
.../core/posix/headers/linux/LinuxTime.java | 33 +++++
.../linux/LinuxSubstrateSigprofHandler.java | 133 ++++++++++++++++++
.../oracle/svm/core/code/CodeInfoAccess.java | 4 +-
.../com/oracle/svm/core/hub/DynamicHub.java | 3 +
.../oracle/svm/core/jdk/BacktraceDecoder.java | 2 +-
.../svm/core/jdk/JavaLangSubstitutions.java | 31 ++--
.../oracle/svm/core/jdk/StackTraceUtils.java | 53 +++++--
.../svm/core/jdk/UninterruptibleUtils.java | 75 +++++++++-
.../svm/core/jfr/JfrMethodRepository.java | 4 +-
.../core/os/BufferedFileOperationSupport.java | 4 +-
.../core/sampler/SubstrateSigprofHandler.java | 17 ++-
.../src/com/oracle/svm/util/StringUtil.java | 4 +
15 files changed, 454 insertions(+), 93 deletions(-)
create mode 100644 substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinSubstrateSigprofHandler.java
create mode 100644 substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxSubstrateSigprofHandler.java
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
index f805d421c92b5..237e462cb06f1 100644
--- 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
@@ -32,13 +32,11 @@
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
-import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.c.function.CEntryPoint;
import org.graalvm.nativeimage.c.function.CEntryPointLiteral;
import org.graalvm.nativeimage.c.function.CodePointer;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.word.Pointer;
-import org.graalvm.word.WordFactory;
import com.oracle.svm.core.IsolateListenerSupport;
import com.oracle.svm.core.IsolateListenerSupportFeature;
@@ -48,31 +46,47 @@
import com.oracle.svm.core.c.function.CEntryPointOptions;
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
import com.oracle.svm.core.feature.InternalFeature;
-import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue;
import com.oracle.svm.core.heap.RestrictHeapAccess;
import com.oracle.svm.core.jfr.JfrExecutionSamplerSupported;
import com.oracle.svm.core.jfr.JfrFeature;
import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.option.SubstrateOptionsParser;
+import com.oracle.svm.core.posix.darwin.DarwinSubstrateSigprofHandler;
import com.oracle.svm.core.posix.headers.Signal;
-import com.oracle.svm.core.posix.headers.Time;
+import com.oracle.svm.core.posix.linux.LinuxSubstrateSigprofHandler;
import com.oracle.svm.core.sampler.SubstrateSigprofHandler;
import com.oracle.svm.core.thread.ThreadListenerSupport;
import com.oracle.svm.core.thread.ThreadListenerSupportFeature;
-import com.oracle.svm.core.util.TimeUtils;
import com.oracle.svm.core.util.UserError;
+import com.oracle.svm.core.util.VMError;
import jdk.graal.compiler.options.Option;
-public final class PosixSubstrateSigprofHandler extends SubstrateSigprofHandler {
+/**
+ *
+ * This class serves as the core for POSIX-based SIGPROF signal handlers.
+ *
+ *
+ *
+ * POSIX supports two types of timers: the global timer and per-thread timer. Both timers can
+ * interrupt threads that are blocked. This may result in situations where the VM operation changes
+ * unexpectedly while a thread executes signal handler code:
+ *
+ * - Thread A requests a safepoint.
+ *
- Thread B is blocked because of the safepoint but the VM did not start executing the VM
+ * operation yet (i.e., there is no VM operation in progress).
+ *
- Thread B receives a SIGPROF signal and starts executing the signal handler.
+ *
- The VM reaches a safepoint and thread A starts executing the VM operation.
+ *
- Thread B continues executing the signal handler while the VM operation is now suddenly in
+ * progress.
+ *
+ *
+ */
+public abstract class PosixSubstrateSigprofHandler extends SubstrateSigprofHandler {
private static final CEntryPointLiteral advancedSignalDispatcher = CEntryPointLiteral.create(PosixSubstrateSigprofHandler.class,
"dispatch", int.class, Signal.siginfo_t.class, Signal.ucontext_t.class);
- @Platforms(Platform.HOSTED_ONLY.class)
- public PosixSubstrateSigprofHandler() {
- }
-
@SuppressWarnings("unused")
@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
@CEntryPointOptions(prologue = CEntryPointOptions.NoPrologue.class, epilogue = CEntryPointOptions.NoEpilogue.class)
@@ -87,40 +101,9 @@ private static void dispatch(@SuppressWarnings("unused") int signalNumber, @Supp
}
}
- private static void registerSigprofSignal(Signal.AdvancedSignalDispatcher dispatcher) {
- PosixUtils.installSignalHandler(Signal.SignalEnum.SIGPROF, dispatcher, Signal.SA_RESTART());
- }
-
- @Override
- protected void updateInterval() {
- updateInterval(TimeUtils.millisToMicros(newIntervalMillis));
- }
-
- public static void updateInterval(long us) {
- Time.itimerval newValue = UnsafeStackValue.get(Time.itimerval.class);
- newValue.it_value().set_tv_sec(us / TimeUtils.microsPerSecond);
- newValue.it_value().set_tv_usec(us % TimeUtils.microsPerSecond);
- newValue.it_interval().set_tv_sec(us / TimeUtils.microsPerSecond);
- newValue.it_interval().set_tv_usec(us % TimeUtils.microsPerSecond);
-
- int status = Time.NoTransitions.setitimer(Time.TimerTypeEnum.ITIMER_PROF, newValue, WordFactory.nullPointer());
- PosixUtils.checkStatusIs0(status, "setitimer(which, newValue, oldValue): wrong arguments.");
- }
-
@Override
protected void installSignalHandler() {
- registerSigprofSignal(advancedSignalDispatcher.getFunctionPointer());
- updateInterval();
- }
-
- @Override
- protected void uninstallSignalHandler() {
- /*
- * Only disable the sampling but do not replace the signal handler with the default one
- * because a signal might be pending for some thread (the default signal handler would print
- * "Profiling timer expired" to the output).
- */
- updateInterval(0);
+ PosixUtils.installSignalHandler(Signal.SignalEnum.SIGPROF, advancedSignalDispatcher.getFunctionPointer(), Signal.SA_RESTART());
}
static boolean isSignalHandlerBasedExecutionSamplerEnabled() {
@@ -159,7 +142,7 @@ public List> getRequiredFeatures() {
@Override
public void afterRegistration(AfterRegistrationAccess access) {
if (JfrExecutionSamplerSupported.isSupported() && isSignalHandlerBasedExecutionSamplerEnabled()) {
- SubstrateSigprofHandler sampler = new PosixSubstrateSigprofHandler();
+ SubstrateSigprofHandler sampler = makeNewSigprofHandler();
ImageSingletons.add(JfrExecutionSampler.class, sampler);
ImageSingletons.add(SubstrateSigprofHandler.class, sampler);
@@ -167,4 +150,14 @@ public void afterRegistration(AfterRegistrationAccess access) {
IsolateListenerSupport.singleton().register(sampler);
}
}
+
+ private static SubstrateSigprofHandler makeNewSigprofHandler() {
+ if (Platform.includedIn(Platform.LINUX.class)) {
+ return new LinuxSubstrateSigprofHandler();
+ } else if (Platform.includedIn(Platform.DARWIN.class)) {
+ return new DarwinSubstrateSigprofHandler();
+ } else {
+ throw VMError.shouldNotReachHere("The JFR-based sampler is not supported on this platform.");
+ }
+ }
}
diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinSubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinSubstrateSigprofHandler.java
new file mode 100644
index 0000000000000..2398a7c783679
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinSubstrateSigprofHandler.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.core.posix.darwin;
+
+import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE;
+
+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.Uninterruptible;
+import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue;
+import com.oracle.svm.core.posix.PosixSubstrateSigprofHandler;
+import com.oracle.svm.core.posix.PosixUtils;
+import com.oracle.svm.core.posix.headers.Time;
+import com.oracle.svm.core.util.TimeUtils;
+
+/**
+ * Darwin supports only global timer from POSIX (see {@link PosixSubstrateSigprofHandler}).
+ */
+public final class DarwinSubstrateSigprofHandler extends PosixSubstrateSigprofHandler {
+
+ @Platforms(Platform.HOSTED_ONLY.class)
+ public DarwinSubstrateSigprofHandler() {
+ }
+
+ @Override
+ protected void updateInterval() {
+ updateInterval(TimeUtils.millisToMicros(newIntervalMillis));
+ }
+
+ private static void updateInterval(long us) {
+ Time.itimerval newValue = UnsafeStackValue.get(Time.itimerval.class);
+ newValue.it_value().set_tv_sec(us / TimeUtils.microsPerSecond);
+ newValue.it_value().set_tv_usec(us % TimeUtils.microsPerSecond);
+ newValue.it_interval().set_tv_sec(us / TimeUtils.microsPerSecond);
+ newValue.it_interval().set_tv_usec(us % TimeUtils.microsPerSecond);
+
+ int status = Time.NoTransitions.setitimer(Time.TimerTypeEnum.ITIMER_PROF, newValue, WordFactory.nullPointer());
+ PosixUtils.checkStatusIs0(status, "setitimer(which, newValue, oldValue): wrong arguments.");
+ }
+
+ @Override
+ protected void installSignalHandler() {
+ super.installSignalHandler();
+ updateInterval();
+ }
+
+ @Override
+ protected void uninstallSignalHandler() {
+ /* Disable sampling. */
+ updateInterval(0);
+ }
+
+ @Override
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ protected void install0(IsolateThread thread) {
+ /* The timer is global, so nothing to do here. */
+ }
+
+ @Override
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ protected void uninstall0(IsolateThread thread) {
+ /* The timer is global, so nothing to do here. */
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Signal.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Signal.java
index 59accc941d087..229cdb631918e 100644
--- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Signal.java
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Signal.java
@@ -68,6 +68,9 @@ public class Signal {
@CConstant
public static native int SIG_SETMASK();
+ @CConstant
+ public static native int SIGEV_SIGNAL();
+
@CFunction
public static native int sigprocmask(int how, sigset_tPointer set, sigset_tPointer oldset);
@@ -179,10 +182,22 @@ public interface sigaction extends PointerBase {
sigset_tPointer sa_mask();
}
+ @CStruct(addStructKeyword = true)
+ public interface sigevent extends PointerBase {
+ @CField
+ void sigev_notify(int value);
+
+ @CField
+ void sigev_signo(int value);
+ }
+
/** Don't call this function directly, see {@link PosixUtils#sigaction}. */
@CFunction
public static native int sigaction(int signum, sigaction act, sigaction oldact);
+ @CConstant
+ public static native int SIGPROF();
+
@CEnum
@CContext(PosixDirectives.class)
public enum SignalEnum {
diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/linux/LinuxTime.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/linux/LinuxTime.java
index 7197fca6d2c2b..44b2a5122e6f5 100644
--- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/linux/LinuxTime.java
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/linux/LinuxTime.java
@@ -28,8 +28,13 @@
import org.graalvm.nativeimage.c.constant.CConstant;
import org.graalvm.nativeimage.c.function.CFunction;
import org.graalvm.nativeimage.c.function.CLibrary;
+import org.graalvm.nativeimage.c.struct.CFieldAddress;
+import org.graalvm.nativeimage.c.struct.CPointerTo;
+import org.graalvm.nativeimage.c.struct.CStruct;
+import org.graalvm.word.PointerBase;
import com.oracle.svm.core.posix.headers.PosixDirectives;
+import com.oracle.svm.core.posix.headers.Signal;
import com.oracle.svm.core.posix.headers.Time;
// Checkstyle: stop
@@ -39,6 +44,25 @@
*/
@CContext(PosixDirectives.class)
public class LinuxTime extends Time {
+
+ @CStruct
+ public interface timer_t extends PointerBase {
+ }
+
+ @CPointerTo(timer_t.class)
+ public interface timer_tPointer extends PointerBase {
+ timer_t read();
+ }
+
+ @CStruct(addStructKeyword = true)
+ public interface itimerspec extends PointerBase {
+ @CFieldAddress
+ Time.timespec it_interval();
+
+ @CFieldAddress
+ Time.timespec it_value();
+ }
+
@CConstant
public static native int CLOCK_MONOTONIC();
@@ -50,5 +74,14 @@ public static class NoTransitions {
@CFunction(transition = CFunction.Transition.NO_TRANSITION)
@CLibrary("rt")
public static native int clock_gettime(int clock_id, timespec tp);
+
+ @CFunction(transition = CFunction.Transition.NO_TRANSITION)
+ public static native int timer_create(int clockid, Signal.sigevent sevp, timer_tPointer timerid);
+
+ @CFunction(transition = CFunction.Transition.NO_TRANSITION)
+ public static native int timer_settime(timer_t timerid, int flags, itimerspec newValue, itimerspec oldValue);
+
+ @CFunction(transition = CFunction.Transition.NO_TRANSITION)
+ public static native int timer_delete(timer_t timerid);
}
}
diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxSubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxSubstrateSigprofHandler.java
new file mode 100644
index 0000000000000..4341a02b59df9
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxSubstrateSigprofHandler.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.core.posix.linux;
+
+import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE;
+
+import org.graalvm.nativeimage.CurrentIsolate;
+import org.graalvm.nativeimage.IsolateThread;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+import org.graalvm.nativeimage.StackValue;
+import org.graalvm.word.WordFactory;
+
+import com.oracle.svm.core.Uninterruptible;
+import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue;
+import com.oracle.svm.core.posix.PosixSubstrateSigprofHandler;
+import com.oracle.svm.core.posix.PosixUtils;
+import com.oracle.svm.core.posix.headers.Signal;
+import com.oracle.svm.core.posix.headers.linux.LinuxTime;
+import com.oracle.svm.core.thread.VMOperation;
+import com.oracle.svm.core.thread.VMThreads;
+import com.oracle.svm.core.threadlocal.FastThreadLocalFactory;
+import com.oracle.svm.core.threadlocal.FastThreadLocalWord;
+import com.oracle.svm.core.util.TimeUtils;
+
+/**
+ * Linux supports both types of timers (see {@link PosixSubstrateSigprofHandler}). We use the
+ * per-thread timer as it increases the number of recorded samples and provides more reliable
+ * sampling.
+ */
+public final class LinuxSubstrateSigprofHandler extends PosixSubstrateSigprofHandler {
+
+ private static final int INITIAL_SAMPLER_TIMER_ID = -1;
+ private static final FastThreadLocalWord samplerTimerId = FastThreadLocalFactory.createWord("LinuxSubstrateSigprofHandler.samplerTimerId");
+
+ @Platforms(Platform.HOSTED_ONLY.class)
+ public LinuxSubstrateSigprofHandler() {
+ }
+
+ @Override
+ @Uninterruptible(reason = "Prevent VM operations that modify thread-local execution sampler state.")
+ public void beforeThreadStart(IsolateThread isolateThread, Thread javaThread) {
+ setSamplerTimerId(isolateThread, WordFactory.signed(INITIAL_SAMPLER_TIMER_ID));
+ }
+
+ @Override
+ protected void updateInterval() {
+ for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {
+ updateInterval(thread);
+ }
+ }
+
+ @Override
+ @Uninterruptible(reason = "Prevent VM operations that modify thread-local execution sampler state.")
+ protected void install0(IsolateThread thread) {
+ assert !isSamplerTimerSet(thread);
+
+ Signal.sigevent sigevent = StackValue.get(Signal.sigevent.class);
+ sigevent.sigev_notify(Signal.SIGEV_SIGNAL());
+ sigevent.sigev_signo(Signal.SIGPROF());
+ LinuxTime.timer_tPointer timerPointer = StackValue.get(LinuxTime.timer_tPointer.class);
+
+ int status = LinuxTime.NoTransitions.timer_create(LinuxTime.CLOCK_MONOTONIC(), sigevent, timerPointer);
+ PosixUtils.checkStatusIs0(status, "timer_create(clockid, sevp, timerid): wrong arguments.");
+ setSamplerTimerId(thread, timerPointer.read());
+ updateInterval(thread);
+ }
+
+ @Override
+ @Uninterruptible(reason = "Prevent VM operations that modify thread-local execution sampler state.")
+ protected void uninstall0(IsolateThread thread) {
+ assert isSamplerTimerSet(thread);
+
+ int status = LinuxTime.NoTransitions.timer_delete(getSamplerTimerId(thread));
+ PosixUtils.checkStatusIs0(status, "timer_delete(clockid): wrong arguments.");
+ setSamplerTimerId(thread, WordFactory.signed(INITIAL_SAMPLER_TIMER_ID));
+ }
+
+ @Uninterruptible(reason = "Prevent VM operations that modify thread-local execution sampler state.")
+ private void updateInterval(IsolateThread thread) {
+ assert isSamplerTimerSet(thread);
+
+ long ns = TimeUtils.millisToNanos(newIntervalMillis);
+ LinuxTime.itimerspec newTimerSpec = UnsafeStackValue.get(LinuxTime.itimerspec.class);
+ newTimerSpec.it_value().set_tv_sec(ns / TimeUtils.nanosPerSecond);
+ newTimerSpec.it_value().set_tv_nsec(ns % TimeUtils.nanosPerSecond);
+ newTimerSpec.it_interval().set_tv_sec(ns / TimeUtils.nanosPerSecond);
+ newTimerSpec.it_interval().set_tv_nsec(ns % TimeUtils.nanosPerSecond);
+
+ int status = LinuxTime.NoTransitions.timer_settime(getSamplerTimerId(thread), 0, newTimerSpec, WordFactory.nullPointer());
+ PosixUtils.checkStatusIs0(status, "timer_settime(timerid, flags, newTimerSpec, oldValue): wrong arguments.");
+ }
+
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ private static boolean isSamplerTimerSet(IsolateThread thread) {
+ return getSamplerTimerId(thread).notEqual(WordFactory.signed(INITIAL_SAMPLER_TIMER_ID));
+ }
+
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ private static LinuxTime.timer_t getSamplerTimerId(IsolateThread thread) {
+ assert CurrentIsolate.getCurrentThread() == thread || VMOperation.isInProgressAtSafepoint();
+ return samplerTimerId.get(thread);
+ }
+
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ private static void setSamplerTimerId(IsolateThread thread, LinuxTime.timer_t timerId) {
+ assert CurrentIsolate.getCurrentThread() == thread || VMOperation.isInProgressAtSafepoint();
+ samplerTimerId.set(thread, timerId);
+ }
+}
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 bfb2d6194adb8..51d55720e2cce 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
@@ -93,7 +93,7 @@ public static Object acquireTether(UntetheredCodeInfo info) {
* Do not interact with the tether object during GCs, as the reference might be forwarded
* and therefore not safe to access. Tethering is not needed then, either.
*/
- assert VMOperation.isGCInProgress() || ((CodeInfoTether) tether).incrementCount() > 0;
+ assert info.equal(CodeInfoTable.getImageCodeInfo()) || VMOperation.isGCInProgress() || ((CodeInfoTether) tether).incrementCount() > 0;
return tether;
}
@@ -101,7 +101,7 @@ public static Object acquireTether(UntetheredCodeInfo info) {
@NeverInline("Prevent elimination of object reference in caller.")
public static void releaseTether(UntetheredCodeInfo info, Object tether) {
assert VMOperation.isGCInProgress() || UntetheredCodeInfoAccess.getTetherUnsafe(info) == null || UntetheredCodeInfoAccess.getTetherUnsafe(info) == tether;
- assert VMOperation.isGCInProgress() || ((CodeInfoTether) tether).decrementCount() >= 0;
+ assert info.equal(CodeInfoTable.getImageCodeInfo()) || VMOperation.isGCInProgress() || ((CodeInfoTether) tether).decrementCount() >= 0;
}
/**
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java
index d4eb2f201b455..b2686b0286343 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java
@@ -25,6 +25,7 @@
package com.oracle.svm.core.hub;
import static com.oracle.svm.core.MissingRegistrationUtils.throwMissingRegistrationErrors;
+import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE;
import static com.oracle.svm.core.annotate.TargetElement.CONSTRUCTOR_NAME;
import static com.oracle.svm.core.reflect.ReflectionMetadataDecoder.NO_DATA;
import static com.oracle.svm.core.reflect.target.ReflectionMetadataDecoderImpl.ALL_CLASSES_FLAG;
@@ -976,10 +977,12 @@ public boolean isSealed() {
return isFlagSet(flags, IS_SEALED_FLAG_BIT);
}
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
public boolean isVMInternal() {
return isFlagSet(flags, IS_VM_INTERNAL_FLAG_BIT);
}
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
public boolean isLambdaFormHidden() {
return isFlagSet(flags, IS_LAMBDA_FORM_HIDDEN_BIT);
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BacktraceDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BacktraceDecoder.java
index 0799bb84caae3..63eac3eaa6ec5 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BacktraceDecoder.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BacktraceDecoder.java
@@ -118,7 +118,7 @@ private int visitFrame(CodePointer ip, CodeInfo tetheredCodeInfo, int oldFramesD
frameInfoCursor.initialize(tetheredCodeInfo, ip, true);
while (frameInfoCursor.advance()) {
FrameInfoQueryResult frameInfo = frameInfoCursor.get();
- if (!StackTraceUtils.shouldShowFrame(frameInfo, false, true, false)) {
+ if (!StackTraceUtils.shouldShowFrame(frameInfo)) {
/* Always ignore the frame. It is an internal frame of the VM. */
continue;
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java
index 122f97ba0045e..cb1d13bd5d2a1 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java
@@ -157,6 +157,15 @@ private static Enum> valueOf(Class> enumType, String name) {
@TargetClass(java.lang.String.class)
final class Target_java_lang_String {
+ @Alias //
+ @TargetElement(name = "COMPACT_STRINGS") //
+ @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.None, isFinal = true) //
+ public static boolean COMPACT;
+
+ @Alias //
+ @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.None, isFinal = true) //
+ public static byte LATIN1;
+
@Substitute
public String intern() {
String thisStr = SubstrateUtil.cast(this, String.class);
@@ -180,7 +189,7 @@ public String intern() {
native byte coder();
@Alias @RecomputeFieldValue(kind = Kind.None, isFinal = true) //
- byte[] value;
+ public byte[] value;
@Alias //
int hash;
@@ -689,26 +698,6 @@ public Object transform(Object receiver, Object originalValue) {
/** Dummy class to have a class with the file's name. */
public final class JavaLangSubstitutions {
- public static final class StringUtil {
- /**
- * Returns a character from a string at {@code index} position based on the encoding format.
- */
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static char charAt(String string, int index) {
- Target_java_lang_String str = SubstrateUtil.cast(string, Target_java_lang_String.class);
- byte[] value = str.value;
- if (str.isLatin1()) {
- return Target_java_lang_StringLatin1.getChar(value, index);
- } else {
- return Target_java_lang_StringUTF16.getChar(value, index);
- }
- }
-
- public static byte coder(String string) {
- return SubstrateUtil.cast(string, Target_java_lang_String.class).coder();
- }
- }
-
public static final class ClassValueSupport {
/**
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java
index c53458b1b01a2..4fd9a706343e8 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java
@@ -24,6 +24,7 @@
*/
package com.oracle.svm.core.jdk;
+import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE;
import static com.oracle.svm.core.snippets.KnownIntrinsics.readCallerStackPointer;
import java.security.AccessControlContext;
@@ -42,6 +43,7 @@
import com.oracle.svm.core.NeverInline;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.SubstrateUtil;
+import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.code.CodeInfo;
import com.oracle.svm.core.code.CodeInfoAccess;
import com.oracle.svm.core.code.CodeInfoQueryResult;
@@ -143,18 +145,37 @@ public static Class> getCallerClass(Pointer startSP, boolean showLambdaFrames,
return visitor.result;
}
+ /**
+ * Indicates whether the frame should be displayed in the context of Java backtracing, returning
+ * true if so, and false otherwise.
+ */
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ public static boolean shouldShowFrame(Class> clazz, String method) {
+ return shouldShowFrame(clazz, method, false, true, false);
+ }
+
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ public static boolean shouldShowFrame(FrameInfoQueryResult frameInfo) {
+ return shouldShowFrame(frameInfo.getSourceClass(), frameInfo.getSourceMethodName());
+ }
+
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ public static boolean shouldShowFrame(FrameInfoQueryResult frameInfo, boolean showLambdaFrames, boolean showReflectFrames, boolean showHiddenFrames) {
+ return shouldShowFrame(frameInfo.getSourceClass(), frameInfo.getSourceMethodName(), showLambdaFrames, showReflectFrames, showHiddenFrames);
+ }
+
/*
* Note that this method is duplicated below to work on compiler metadata. Make sure to always
* keep both versions in sync, otherwise intrinsifications by the compiler will return different
* results than stack walking at run time.
*/
- public static boolean shouldShowFrame(FrameInfoQueryResult frameInfo, boolean showLambdaFrames, boolean showReflectFrames, boolean showHiddenFrames) {
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ public static boolean shouldShowFrame(Class> clazz, String methodName, boolean showLambdaFrames, boolean showReflectFrames, boolean showHiddenFrames) {
if (showHiddenFrames) {
/* No filtering, all frames including internal frames are shown. */
return true;
}
- Class> clazz = frameInfo.getSourceClass();
if (clazz == null) {
/*
* We don't have a Java class. This must be an internal frame. This path mostly exists
@@ -172,20 +193,22 @@ public static boolean shouldShowFrame(FrameInfoQueryResult frameInfo, boolean sh
return false;
}
- if (!showReflectFrames && ((clazz == java.lang.reflect.Method.class && "invoke".equals(frameInfo.getSourceMethodName())) ||
- (clazz == java.lang.reflect.Constructor.class && "newInstance".equals(frameInfo.getSourceMethodName())) ||
- (clazz == java.lang.Class.class && "newInstance".equals(frameInfo.getSourceMethodName())))) {
- /*
- * Ignore a reflective method / constructor invocation frame. Note that the classes
- * cannot be annotated with @InternalFrame because 1) they are JDK classes and 2) only
- * one method of each class is affected.
- */
- return false;
+ if (!showReflectFrames) {
+ if (clazz == java.lang.reflect.Method.class && UninterruptibleUtils.String.equals("invoke", methodName)) {
+ /*
+ * Ignore a reflective method invocation frame. Note that the classes cannot be
+ * annotated with @InternalFrame because 1) they are JDK classes and 2) only one
+ * method of each class is affected.
+ */
+ return false;
+ } else if ((clazz == java.lang.reflect.Constructor.class || clazz == java.lang.Class.class) && UninterruptibleUtils.String.equals("newInstance", methodName)) {
+ /* Ignore a constructor invocation frame (see the comment above). */
+ return false;
+ }
}
if (clazz == Target_jdk_internal_vm_Continuation.class) {
- String name = frameInfo.getSourceMethodName();
- if (name.startsWith("enter") || name.startsWith("yield")) {
+ if (UninterruptibleUtils.String.startsWith(methodName, "enter") || UninterruptibleUtils.String.startsWith(methodName, "yield")) {
return false;
}
}
@@ -392,7 +415,7 @@ private void visitAOTFrame(CodePointer ip) {
}
private boolean visitFrameInfo(FrameInfoQueryResult frameInfo) {
- if (!StackTraceUtils.shouldShowFrame(frameInfo, false, true, false)) {
+ if (!StackTraceUtils.shouldShowFrame(frameInfo)) {
/* Always ignore the frame. It is an internal frame of the VM. */
return true;
@@ -615,7 +638,7 @@ class BuildStackTraceVisitor extends JavaStackFrameVisitor {
@Override
public boolean visitFrame(FrameInfoQueryResult frameInfo) {
- if (!StackTraceUtils.shouldShowFrame(frameInfo, false, true, false)) {
+ if (!StackTraceUtils.shouldShowFrame(frameInfo)) {
/* Always ignore the frame. It is an internal frame of the VM. */
return true;
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java
index 2645c1f7cab04..d94596a5ec7f6 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java
@@ -24,16 +24,19 @@
*/
package com.oracle.svm.core.jdk;
+import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE;
+
import org.graalvm.word.Pointer;
import org.graalvm.word.PointerBase;
import org.graalvm.word.UnsignedWord;
import org.graalvm.word.WordBase;
import org.graalvm.word.WordFactory;
+import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.Uninterruptible;
-import com.oracle.svm.core.jdk.JavaLangSubstitutions.StringUtil;
import com.oracle.svm.core.util.VMError;
+import jdk.graal.compiler.core.common.SuppressFBWarnings;
import jdk.internal.misc.Unsafe;
/**
@@ -592,7 +595,7 @@ public static int modifiedUTF8Length(java.lang.String string, boolean addNullTer
public static int modifiedUTF8Length(java.lang.String string, boolean addNullTerminator, CharReplacer replacer) {
int result = 0;
for (int index = 0; index < string.length(); index++) {
- char ch = StringUtil.charAt(string, index);
+ char ch = charAt(string, index);
if (replacer != null) {
ch = replacer.replace(ch);
}
@@ -623,7 +626,7 @@ public static Pointer toModifiedUTF8(java.lang.String string, Pointer buffer, Po
public static Pointer toModifiedUTF8(java.lang.String string, int stringLength, Pointer buffer, Pointer bufferEnd, boolean addNullTerminator, CharReplacer replacer) {
Pointer pos = buffer;
for (int index = 0; index < stringLength; index++) {
- char ch = StringUtil.charAt(string, index);
+ char ch = charAt(string, index);
if (replacer != null) {
ch = replacer.replace(ch);
}
@@ -637,6 +640,72 @@ public static Pointer toModifiedUTF8(java.lang.String string, int stringLength,
VMError.guarantee(pos.belowOrEqual(bufferEnd), "Must not write out of bounds.");
return pos;
}
+
+ /**
+ * Returns a character from a string at {@code index} position based on the encoding format.
+ */
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ public static char charAt(java.lang.String string, int index) {
+ Target_java_lang_String str = SubstrateUtil.cast(string, Target_java_lang_String.class);
+ byte[] value = str.value;
+ if (str.isLatin1()) {
+ return Target_java_lang_StringLatin1.getChar(value, index);
+ } else {
+ return Target_java_lang_StringUTF16.getChar(value, index);
+ }
+ }
+
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ public static byte coder(java.lang.String string) {
+ return SubstrateUtil.cast(string, Target_java_lang_String.class).coder();
+ }
+
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ public static byte[] value(java.lang.String string) {
+ return SubstrateUtil.cast(string, Target_java_lang_String.class).value;
+ }
+
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ public static boolean startsWith(java.lang.String string, java.lang.String prefix) {
+ if (prefix.length() > string.length()) {
+ return false;
+ }
+ byte coder = coder(string);
+ if (coder != coder(prefix) && coder == Target_java_lang_String.LATIN1) {
+ /* string.coder == LATIN1 && prefix.coder == UTF16 */
+ return false;
+ }
+ return compare(string, prefix, prefix.length());
+ }
+
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ @SuppressFBWarnings(value = "", justification = "The string comparison by reference is fine in this case.")
+ public static boolean equals(java.lang.String a, java.lang.String b) {
+ return a == b || (!Target_java_lang_String.COMPACT || coder(a) == coder(b)) && equals0(value(a), value(b));
+ }
+
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ private static boolean equals0(byte[] value, byte[] other) {
+ if (value.length == other.length) {
+ for (int i = 0; i < value.length; i++) {
+ if (value[i] != other[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
+ private static boolean compare(java.lang.String a, java.lang.String b, int length) {
+ for (int index = 0; index < length; index++) {
+ if (charAt(a, index) != charAt(b, index)) {
+ return false;
+ }
+ }
+ return true;
+ }
}
@FunctionalInterface
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java
index 3be73ab2bd80c..0547052b268d5 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java
@@ -30,6 +30,7 @@
import org.graalvm.word.WordFactory;
import com.oracle.svm.core.Uninterruptible;
+import com.oracle.svm.core.jdk.StackTraceUtils;
import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch;
import com.oracle.svm.core.jfr.utils.JfrVisited;
import com.oracle.svm.core.jfr.utils.JfrVisitedTable;
@@ -90,8 +91,7 @@ public long getMethodId(Class> clazz, String methodName, int methodId) {
JfrNativeEventWriter.putLong(data, symbolRepo.getSymbolId("()V", false));
/* Dummy value for modifiers. */
JfrNativeEventWriter.putShort(data, (short) 0);
- /* Dummy value for isHidden. */
- JfrNativeEventWriter.putBoolean(data, false);
+ JfrNativeEventWriter.putBoolean(data, !StackTraceUtils.shouldShowFrame(clazz, methodName));
if (!JfrNativeEventWriter.commit(data)) {
return methodId;
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java
index c96df4dd61cb7..b7c8bdd5789fe 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java
@@ -43,7 +43,7 @@
import com.oracle.svm.core.feature.InternalFeature;
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.hub.LayoutEncoding;
-import com.oracle.svm.core.jdk.JavaLangSubstitutions;
+import com.oracle.svm.core.jdk.UninterruptibleUtils;
import com.oracle.svm.core.memory.NullableNativeMemory;
import com.oracle.svm.core.nmt.NmtCategory;
import com.oracle.svm.core.os.BufferedFileOperationSupport.BufferedFileOperationSupportHolder;
@@ -354,7 +354,7 @@ public boolean writeDouble(BufferedFile f, double v) {
public boolean writeUTF8(BufferedFile f, String string) {
boolean success = true;
for (int index = 0; index < string.length() && success; index++) {
- success &= writeUTF8(f, JavaLangSubstitutions.StringUtil.charAt(string, index));
+ success &= writeUTF8(f, UninterruptibleUtils.String.charAt(string, index));
}
return success;
}
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
index 530bcb29bedec..57e613b32a2ce 100644
--- 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
@@ -106,9 +106,6 @@ protected void startSampling() {
}
}
- @Override
- protected abstract void updateInterval();
-
@Override
protected void stopSampling() {
assert VMOperation.isInProgressAtSafepoint();
@@ -152,7 +149,17 @@ public void beforeThreadRun() {
protected abstract void installSignalHandler();
- protected abstract void uninstallSignalHandler();
+ protected void uninstallSignalHandler() {
+ /*
+ * Do not replace the signal handler with the default one because a signal might be pending
+ * for some thread (the default signal handler would print "Profiling timer expired" to the
+ * output).
+ */
+ }
+
+ protected abstract void install0(IsolateThread thread);
+
+ protected abstract void uninstall0(IsolateThread thread);
@Uninterruptible(reason = "Prevent VM operations that modify thread-local execution sampler state.")
private static void install(IsolateThread thread) {
@@ -160,6 +167,7 @@ private static void install(IsolateThread thread) {
if (ExecutionSamplerInstallation.isAllowed(thread)) {
ExecutionSamplerInstallation.installed(thread);
+ singleton().install0(thread);
}
}
@@ -175,6 +183,7 @@ protected void uninstall(IsolateThread thread) {
*/
storeIsolateThreadInNativeThreadLocal(WordFactory.nullPointer());
ExecutionSamplerInstallation.uninstalled(thread);
+ uninstall0(thread);
}
}
diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/StringUtil.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/StringUtil.java
index d8c339153c632..a33fd56db616c 100644
--- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/StringUtil.java
+++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/StringUtil.java
@@ -30,6 +30,10 @@
import java.util.List;
import java.util.Objects;
+/**
+ * This is an interruptible wrapper around some of the {@link String} methods that we usually use to
+ * avoid pulling regular expression support into small Native Images (such as HelloWorld).
+ */
public class StringUtil {
/**