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: + *

+ *

+ */ +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 { /**