diff --git a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/util/AbstractTypeReader.java b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/util/AbstractTypeReader.java index 2336face3b7c..9f138ca51ddc 100644 --- a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/util/AbstractTypeReader.java +++ b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/util/AbstractTypeReader.java @@ -35,7 +35,7 @@ public long getUV() { return read(); } - public static long decodeSign(long value) { + private static long decodeSign(long value) { return (value >>> 1) ^ -(value & 1); } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixLibCSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixLibCSupport.java index 9d5cc4366b9f..48b7da2ee12a 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixLibCSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixLibCSupport.java @@ -41,6 +41,12 @@ public T memcpy(T dest, PointerBase src, UnsignedWord n) return PosixLibC.memcpy(dest, src, n); } + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int memcmp(T s1, T s2, UnsignedWord n) { + return PosixLibC.memcmp(s1, s2, n); + } + @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public T memmove(T dest, PointerBase src, UnsignedWord n) { diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixVMSemaphoreSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixVMSemaphoreSupport.java new file mode 100644 index 000000000000..a215d671f876 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixVMSemaphoreSupport.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.posix; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.locks.VMSemaphore; + +public abstract class PosixVMSemaphoreSupport { + + @Fold + public static PosixVMSemaphoreSupport singleton() { + return ImageSingletons.lookup(PosixVMSemaphoreSupport.class); + } + + /** + * Must be called once early during startup, before any semaphore is used. + */ + @Uninterruptible(reason = "Called from uninterruptible code. Too early for safepoints.") + public abstract boolean initialize(); + + /** + * Must be called during isolate teardown. + */ + @Uninterruptible(reason = "The isolate teardown is in progress.") + public abstract void destroy(); + + public abstract VMSemaphore[] getSemaphores(); + +} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinVMSemaphoreFeature.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinVMSemaphoreFeature.java new file mode 100644 index 000000000000..03255e4ad710 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinVMSemaphoreFeature.java @@ -0,0 +1,134 @@ +/* + * 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.darwin; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.heap.UnknownObjectField; +import com.oracle.svm.core.locks.ClassInstanceReplacer; +import com.oracle.svm.core.locks.VMSemaphore; +import com.oracle.svm.core.posix.PosixVMSemaphoreSupport; +import com.oracle.svm.core.util.VMError; + +/** + * Support of {@link VMSemaphore} in multithreaded environments on DARWIN. + */ +@AutomaticallyRegisteredFeature +final class DarwinVMSemaphoreFeature implements InternalFeature { + + private final ClassInstanceReplacer semaphoreReplacer = new ClassInstanceReplacer<>(VMSemaphore.class) { + @Override + protected VMSemaphore createReplacement(VMSemaphore source) { + return new DarwinVMSemaphore(); + } + }; + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return SubstrateOptions.MultiThreaded.getValue(); + } + + @Override + public void duringSetup(DuringSetupAccess access) { + ImageSingletons.add(PosixVMSemaphoreSupport.class, new DarwinVMSemaphoreSupport()); + access.registerObjectReplacer(semaphoreReplacer); + } + + @Override + public void beforeCompilation(BeforeCompilationAccess access) { + DarwinVMSemaphoreSupport semaphoreSupport = (DarwinVMSemaphoreSupport) PosixVMSemaphoreSupport.singleton(); + semaphoreSupport.semaphores = semaphoreReplacer.getReplacements().toArray(new DarwinVMSemaphore[0]); + } +} + +final class DarwinVMSemaphoreSupport extends PosixVMSemaphoreSupport { + + /** All semaphores, so that we can initialize them at run time when the VM starts. */ + @UnknownObjectField(types = DarwinVMSemaphore[].class)// + DarwinVMSemaphore[] semaphores; + + @Override + @Uninterruptible(reason = "Called from uninterruptible code. Too early for safepoints.") + public boolean initialize() { + for (DarwinVMSemaphore semaphore : semaphores) { + if (semaphore.init() != 0) { + return false; + } + } + + return true; + } + + @Override + @Uninterruptible(reason = "The isolate teardown is in progress.") + public void destroy() { + for (DarwinVMSemaphore semaphore : semaphores) { + semaphore.destroy(); + } + } + + @Override + public DarwinVMSemaphore[] getSemaphores() { + return semaphores; + } +} + +final class DarwinVMSemaphore extends VMSemaphore { + + @Platforms(Platform.HOSTED_ONLY.class) + DarwinVMSemaphore() { + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected int init() { + /* sem_init method is now deprecated on DARWIN and do nothing. */ + return 0; + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected void destroy() { + /* sem_destroy method is now deprecated on DARWIN and do nothing. */ + } + + @Override + public void await() { + VMError.shouldNotReachHere("Unnamed semaphores are not supported on DARWIN."); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void signal() { + VMError.shouldNotReachHere("Unnamed semaphores are not supported on DARWIN."); + } +} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/PosixDirectives.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/PosixDirectives.java index 8c877c6328ee..51bb598caa7c 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/PosixDirectives.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/PosixDirectives.java @@ -41,6 +41,7 @@ public class PosixDirectives implements CContext.Directives { "", "", "", + "", "", "", "", diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/PosixLibC.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/PosixLibC.java index f68539b055d5..36bfdcfeac15 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/PosixLibC.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/PosixLibC.java @@ -47,6 +47,9 @@ public class PosixLibC { @CFunction(value = "memmove", transition = CFunction.Transition.NO_TRANSITION) public static native T memcpy(T dest, PointerBase src, UnsignedWord n); + @CFunction(transition = CFunction.Transition.NO_TRANSITION) + public static native int memcmp(T s1, T s2, UnsignedWord n); + @CFunction(transition = CFunction.Transition.NO_TRANSITION) public static native T memmove(T dest, PointerBase src, UnsignedWord n); diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Semaphore.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Semaphore.java new file mode 100644 index 000000000000..ecc1df261265 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Semaphore.java @@ -0,0 +1,61 @@ +/* + * 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.headers; + +import org.graalvm.nativeimage.c.CContext; +import org.graalvm.nativeimage.c.function.CFunction; +import org.graalvm.nativeimage.c.struct.CStruct; +import org.graalvm.word.PointerBase; +import org.graalvm.word.SignedWord; +import org.graalvm.word.UnsignedWord; + +// Checkstyle: stop + +/** + * Manually translated definitions from the C header file semaphore.h. + */ +@CContext(PosixDirectives.class) +public class Semaphore { + + @CStruct + public interface sem_t extends PointerBase { + } + + @CFunction(transition = CFunction.Transition.TO_NATIVE) + public static native int sem_wait(sem_t sem); + + public static class NoTransitions { + + @CFunction(transition = CFunction.Transition.NO_TRANSITION) + public static native int sem_init(sem_t sem, SignedWord pshared, UnsignedWord value); + + @CFunction(transition = CFunction.Transition.NO_TRANSITION) + public static native int sem_destroy(sem_t sem); + + @CFunction(transition = CFunction.Transition.NO_TRANSITION) + public static native int sem_post(sem_t sem); + } +} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxVMSemaphoreFeature.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxVMSemaphoreFeature.java new file mode 100644 index 000000000000..6d277ca419de --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxVMSemaphoreFeature.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.posix.linux; + +import org.graalvm.compiler.core.common.NumUtil; +import org.graalvm.compiler.word.Word; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.config.ObjectLayout; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.heap.UnknownObjectField; +import com.oracle.svm.core.locks.ClassInstanceReplacer; +import com.oracle.svm.core.locks.VMSemaphore; +import com.oracle.svm.core.posix.PosixVMSemaphoreSupport; +import com.oracle.svm.core.posix.headers.Semaphore; +import com.oracle.svm.core.posix.pthread.PthreadVMLockSupport; + +import jdk.vm.ci.meta.JavaKind; + +/** + * Support of {@link VMSemaphore} in multithreaded environments on LINUX. + */ +@AutomaticallyRegisteredFeature +final class LinuxVMSemaphoreFeature implements InternalFeature { + + private final ClassInstanceReplacer semaphoreReplacer = new ClassInstanceReplacer<>(VMSemaphore.class) { + @Override + protected VMSemaphore createReplacement(VMSemaphore source) { + return new LinuxVMSemaphore(); + } + }; + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return SubstrateOptions.MultiThreaded.getValue(); + } + + @Override + public void duringSetup(DuringSetupAccess access) { + ImageSingletons.add(PosixVMSemaphoreSupport.class, new LinuxVMSemaphoreSupport()); + access.registerObjectReplacer(semaphoreReplacer); + } + + @Override + public void beforeCompilation(BeforeCompilationAccess access) { + final int wordSize = ConfigurationValues.getTarget().wordSize; + + // `alignment` should actually be: `max(alignof(pthread_mutex_t), alignof(pthread_cond_t))`. + // + // Until `alignof()` can be queried from the C compiler, we hard-code this alignment to: + // - One word on 64-bit architectures. + // - Two words on 32-bit architectures. + // + // This split is arbitrary. Actual alignment requirements depend on the architecture, + // the Pthread library implementation, and the C compiler. + // These hard-coded values will need to be adjusted to higher values if we find out + // that `pthread_mutex_t` or `pthread_cond_t` have higher alignment requirements on some + // particular architecture. + assert wordSize == 8 || wordSize == 4 : "Unsupported architecture bit width"; + final int alignment = (wordSize == 8) ? wordSize : (2 * wordSize); + + ObjectLayout layout = ConfigurationValues.getObjectLayout(); + final int baseOffset = layout.getArrayBaseOffset(JavaKind.Byte); + + // Align the first element to word boundary. + int nextIndex = NumUtil.roundUp(baseOffset, alignment) - baseOffset; + + LinuxVMSemaphore[] semaphores = semaphoreReplacer.getReplacements().toArray(new LinuxVMSemaphore[0]); + int semaphoreSize = NumUtil.roundUp(SizeOf.get(Semaphore.sem_t.class), alignment); + for (LinuxVMSemaphore semaphore : semaphores) { + semaphore.structOffset = WordFactory.unsigned(layout.getArrayElementOffset(JavaKind.Byte, nextIndex)); + nextIndex += semaphoreSize; + } + + LinuxVMSemaphoreSupport semaphoreSupport = (LinuxVMSemaphoreSupport) PosixVMSemaphoreSupport.singleton(); + semaphoreSupport.semaphores = semaphores; + semaphoreSupport.semaphoreStructs = new byte[nextIndex]; + } +} + +final class LinuxVMSemaphoreSupport extends PosixVMSemaphoreSupport { + + /** All semaphores, so that we can initialize them at run time when the VM starts. */ + @UnknownObjectField(types = LinuxVMSemaphore[].class)// + LinuxVMSemaphore[] semaphores; + + /** + * Raw memory for the semaphore lock structures. The offset into this array is stored in + * {@link LinuxVMSemaphore#structOffset}. + */ + @UnknownObjectField(types = byte[].class)// + byte[] semaphoreStructs; + + @Override + @Uninterruptible(reason = "Called from uninterruptible code. Too early for safepoints.") + public boolean initialize() { + for (LinuxVMSemaphore semaphore : semaphores) { + if (semaphore.init() != 0) { + return false; + } + } + + return true; + } + + @Override + @Uninterruptible(reason = "The isolate teardown is in progress.") + public void destroy() { + for (LinuxVMSemaphore semaphore : semaphores) { + semaphore.destroy(); + } + } + + @Override + public LinuxVMSemaphore[] getSemaphores() { + return semaphores; + } +} + +final class LinuxVMSemaphore extends VMSemaphore { + UnsignedWord structOffset; + + @Platforms(Platform.HOSTED_ONLY.class) + LinuxVMSemaphore() { + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private Semaphore.sem_t getStructPointer() { + LinuxVMSemaphoreSupport semaphoreSupport = (LinuxVMSemaphoreSupport) PosixVMSemaphoreSupport.singleton(); + return (Semaphore.sem_t) Word.objectToUntrackedPointer(semaphoreSupport.semaphoreStructs).add(structOffset); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected int init() { + return Semaphore.NoTransitions.sem_init(getStructPointer(), WordFactory.signed(0), WordFactory.unsigned(0)); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected void destroy() { + PthreadVMLockSupport.checkResult(Semaphore.NoTransitions.sem_destroy(getStructPointer()), "sem_destroy"); + } + + @Override + public void await() { + PthreadVMLockSupport.checkResult(Semaphore.sem_wait(getStructPointer()), "sem_wait"); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void signal() { + PthreadVMLockSupport.checkResult(Semaphore.NoTransitions.sem_post(getStructPointer()), "sem_post"); + } +} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadVMLockSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadVMLockSupport.java index 2e4afc10c7c8..52f962f09a26 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadVMLockSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadVMLockSupport.java @@ -51,7 +51,9 @@ import com.oracle.svm.core.locks.VMCondition; import com.oracle.svm.core.locks.VMLockSupport; import com.oracle.svm.core.locks.VMMutex; +import com.oracle.svm.core.locks.VMSemaphore; import com.oracle.svm.core.log.Log; +import com.oracle.svm.core.posix.PosixVMSemaphoreSupport; import com.oracle.svm.core.posix.headers.Errno; import com.oracle.svm.core.posix.headers.Pthread; import com.oracle.svm.core.posix.headers.Time; @@ -142,11 +144,11 @@ public void beforeCompilation(BeforeCompilationAccess access) { public final class PthreadVMLockSupport extends VMLockSupport { /** All mutexes, so that we can initialize them at run time when the VM starts. */ @UnknownObjectField(types = PthreadVMMutex[].class)// - protected PthreadVMMutex[] mutexes; + PthreadVMMutex[] mutexes; /** All conditions, so that we can initialize them at run time when the VM starts. */ @UnknownObjectField(types = PthreadVMCondition[].class)// - protected PthreadVMCondition[] conditions; + PthreadVMCondition[] conditions; /** * Raw memory for the pthread lock structures. Since we know that native image objects are never @@ -155,7 +157,7 @@ public final class PthreadVMLockSupport extends VMLockSupport { * {@link PthreadVMCondition#structOffset}. */ @UnknownObjectField(types = byte[].class)// - protected byte[] pthreadStructs; + byte[] pthreadStructs; @Fold public static PthreadVMLockSupport singleton() { @@ -180,12 +182,12 @@ public static boolean initialize() { } } - return true; + return PosixVMSemaphoreSupport.singleton().initialize(); } @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false) @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Must not allocate in fatal error handling.") - protected static void checkResult(int result, String functionName) { + public static void checkResult(int result, String functionName) { if (result != 0) { /* * Functions are called very early and late during our execution, so there is not much @@ -208,19 +210,24 @@ public PthreadVMMutex[] getMutexes() { public PthreadVMCondition[] getConditions() { return conditions; } + + @Override + public VMSemaphore[] getSemaphores() { + return PosixVMSemaphoreSupport.singleton().getSemaphores(); + } } final class PthreadVMMutex extends VMMutex { - protected UnsignedWord structOffset; + UnsignedWord structOffset; @Platforms(Platform.HOSTED_ONLY.class) - protected PthreadVMMutex(String name) { + PthreadVMMutex(String name) { super(name); } @Uninterruptible(reason = "Called from uninterruptible code.") - protected Pthread.pthread_mutex_t getStructPointer() { + Pthread.pthread_mutex_t getStructPointer() { return (Pthread.pthread_mutex_t) Word.objectToUntrackedPointer(PthreadVMLockSupport.singleton().pthreadStructs).add(structOffset); } @@ -270,15 +277,15 @@ public void unlockWithoutChecks() { final class PthreadVMCondition extends VMCondition { - protected UnsignedWord structOffset; + UnsignedWord structOffset; @Platforms(Platform.HOSTED_ONLY.class) - protected PthreadVMCondition(PthreadVMMutex mutex) { + PthreadVMCondition(PthreadVMMutex mutex) { super(mutex); } @Uninterruptible(reason = "Called from uninterruptible code.") - protected Pthread.pthread_cond_t getStructPointer() { + Pthread.pthread_cond_t getStructPointer() { return (Pthread.pthread_cond_t) Word.objectToUntrackedPointer(PthreadVMLockSupport.singleton().pthreadStructs).add(structOffset); } @@ -311,14 +318,14 @@ public long block(long waitNanos) { PthreadConditionUtils.delayNanosToDeadlineTimespec(waitNanos, deadlineTimespec); mutex.clearCurrentThreadOwner(); - final int timedwaitResult = Pthread.pthread_cond_timedwait(getStructPointer(), ((PthreadVMMutex) getMutex()).getStructPointer(), deadlineTimespec); + final int timedWaitResult = Pthread.pthread_cond_timedwait(getStructPointer(), ((PthreadVMMutex) getMutex()).getStructPointer(), deadlineTimespec); mutex.setOwnerToCurrentThread(); /* If the timed wait timed out, then I am done blocking. */ - if (timedwaitResult == Errno.ETIMEDOUT()) { + if (timedWaitResult == Errno.ETIMEDOUT()) { return 0L; } /* Check for other errors from the timed wait. */ - PthreadVMLockSupport.checkResult(timedwaitResult, "pthread_cond_timedwait"); + PthreadVMLockSupport.checkResult(timedWaitResult, "pthread_cond_timedwait"); return PthreadConditionUtils.deadlineTimespecToDelayNanos(deadlineTimespec); } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsLibCSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsLibCSupport.java index f4dd38084dee..179a459e7fbb 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsLibCSupport.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsLibCSupport.java @@ -31,8 +31,8 @@ import org.graalvm.word.UnsignedWord; import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.headers.LibCSupport; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.headers.LibCSupport; import com.oracle.svm.core.windows.headers.WindowsLibC; @AutomaticallyRegisteredImageSingleton(LibCSupport.class) @@ -55,6 +55,12 @@ public T memcpy(T dest, PointerBase src, UnsignedWord n) return WindowsLibC.memcpy(dest, src, n); } + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int memcmp(T s1, T s2, UnsignedWord n) { + return WindowsLibC.memcmp(s1, s2, n); + } + @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public T memmove(T dest, PointerBase src, UnsignedWord n) { diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVMLockSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVMLockSupport.java index 2689143c969d..c8dc1e125eeb 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVMLockSupport.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsVMLockSupport.java @@ -49,6 +49,7 @@ import com.oracle.svm.core.locks.VMCondition; import com.oracle.svm.core.locks.VMLockSupport; import com.oracle.svm.core.locks.VMMutex; +import com.oracle.svm.core.locks.VMSemaphore; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.stack.StackOverflowCheck; import com.oracle.svm.core.thread.VMThreads.SafepointBehavior; @@ -59,8 +60,8 @@ import jdk.vm.ci.meta.JavaKind; /** - * Support of {@link VMMutex} and {@link VMCondition} in multi-threaded environments. Locking is - * implemented via Windows locking primitives. + * Support of {@link VMMutex}, {@link VMCondition} and {@link VMSemaphore} in multithreaded + * environments. Locking is implemented via Windows locking primitives. */ @AutomaticallyRegisteredFeature @Platforms(Platform.WINDOWS.class) @@ -80,6 +81,13 @@ protected WindowsVMCondition createReplacement(VMCondition source) { } }; + private final ClassInstanceReplacer semaphoreReplacer = new ClassInstanceReplacer<>(VMSemaphore.class) { + @Override + protected VMSemaphore createReplacement(VMSemaphore source) { + return new WindowsVMSemaphore(); + } + }; + @Override public boolean isInConfiguration(IsInConfigurationAccess access) { return SubstrateOptions.MultiThreaded.getValue(); @@ -90,6 +98,7 @@ public void duringSetup(DuringSetupAccess access) { ImageSingletons.add(VMLockSupport.class, new WindowsVMLockSupport()); access.registerObjectReplacer(mutexReplacer); access.registerObjectReplacer(conditionReplacer); + access.registerObjectReplacer(semaphoreReplacer); } @Override @@ -114,6 +123,7 @@ public void beforeCompilation(BeforeCompilationAccess access) { WindowsVMLockSupport lockSupport = WindowsVMLockSupport.singleton(); lockSupport.mutexes = mutexes; lockSupport.conditions = conditions; + lockSupport.semaphores = semaphoreReplacer.getReplacements().toArray(new WindowsVMSemaphore[0]); lockSupport.syncStructs = new byte[nextIndex]; } } @@ -127,6 +137,10 @@ public final class WindowsVMLockSupport extends VMLockSupport { @UnknownObjectField(types = WindowsVMCondition[].class)// WindowsVMCondition[] conditions; + /** All semaphores, so that we can initialize them at run time when the VM starts. */ + @UnknownObjectField(types = WindowsVMSemaphore[].class)// + WindowsVMSemaphore[] semaphores; + /** * Raw memory for the Condition Variable structures. Since we know that native image objects are * never moved, we can safely hand out pointers into the middle of this array to C code. The @@ -148,12 +162,15 @@ public static WindowsVMLockSupport singleton() { public static void initialize() { WindowsVMLockSupport support = WindowsVMLockSupport.singleton(); for (WindowsVMMutex mutex : support.mutexes) { - // critical sections on windows always support recursive locking + // critical sections on Windows always support recursive locking Process.NoTransitions.InitializeCriticalSection(mutex.getStructPointer()); } for (WindowsVMCondition condition : support.conditions) { Process.NoTransitions.InitializeConditionVariable(condition.getStructPointer()); } + for (WindowsVMSemaphore semaphore : support.semaphores) { + semaphore.init(); + } } @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false) @@ -182,6 +199,11 @@ public VMMutex[] getMutexes() { public VMCondition[] getConditions() { return conditions; } + + @Override + public VMSemaphore[] getSemaphores() { + return semaphores; + } } final class WindowsVMMutex extends VMMutex { @@ -189,7 +211,7 @@ final class WindowsVMMutex extends VMMutex { UnsignedWord structOffset; @Platforms(Platform.HOSTED_ONLY.class) - protected WindowsVMMutex(String name) { + WindowsVMMutex(String name) { super(name); } @@ -339,3 +361,32 @@ public void broadcast() { Process.NoTransitions.WakeAllConditionVariable(getStructPointer()); } } + +final class WindowsVMSemaphore extends VMSemaphore { + + private WinBase.HANDLE hSemaphore; + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected int init() { + hSemaphore = WinBase.CreateSemaphoreA(WordFactory.nullPointer(), 0, Integer.MAX_VALUE, WordFactory.nullPointer()); + return hSemaphore.isNonNull() ? 0 : 1; + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected void destroy() { + WinBase.CloseHandle(hSemaphore); + } + + @Override + public void await() { + WindowsVMLockSupport.checkResult(SynchAPI.WaitForSingleObject(hSemaphore, SynchAPI.INFINITE()), "WaitForSingleObject"); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void signal() { + WindowsVMLockSupport.checkResult(SynchAPI.NoTransitions.ReleaseSemaphore(hSemaphore, 1, WordFactory.nullPointer()), "ReleaseSemaphore"); + } +} diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/SynchAPI.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/SynchAPI.java index ea17acd8a7a0..df7f1c2fc53c 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/SynchAPI.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/SynchAPI.java @@ -30,6 +30,7 @@ import org.graalvm.nativeimage.c.constant.CConstant; import org.graalvm.nativeimage.c.function.CFunction; import org.graalvm.nativeimage.c.function.CFunction.Transition; +import org.graalvm.nativeimage.c.type.CIntPointer; import org.graalvm.word.PointerBase; //Checkstyle: stop @@ -79,5 +80,8 @@ public static class NoTransitions { @CFunction(transition = Transition.NO_TRANSITION) public static native int WaitForSingleObject(WinBase.HANDLE hEvent, int dwMilliseconds); + + @CFunction(transition = Transition.NO_TRANSITION) + public static native int ReleaseSemaphore(WinBase.HANDLE hSemaphore, int lReleaseCount, CIntPointer lpPreviousCount); } } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WinBase.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WinBase.java index 0dea09ef68aa..0ef4799bf7df 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WinBase.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WinBase.java @@ -33,6 +33,7 @@ import org.graalvm.nativeimage.c.struct.CStruct; import org.graalvm.nativeimage.c.type.CIntPointer; import org.graalvm.nativeimage.c.type.CLongPointer; +import org.graalvm.nativeimage.c.type.VoidPointer; import org.graalvm.word.PointerBase; import com.oracle.svm.core.windows.headers.WindowsLibC.WCharPointer; @@ -125,4 +126,8 @@ public static native int DuplicateHandle(HANDLE hSourceProcessHandle, HANDLE hSo @CFunction(transition = Transition.NO_TRANSITION) public static native int GetUserProfileDirectoryW(HANDLE hToken, WCharPointer lpProfileDir, CIntPointer lpcchSize); + + @CFunction(transition = Transition.NO_TRANSITION) + public static native WinBase.HANDLE CreateSemaphoreA(VoidPointer lpSemaphoreAttributes, int lInitialCount, int lMaximumCount, VoidPointer lpName); + } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsLibC.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsLibC.java index e8b916457c44..828d67152965 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsLibC.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsLibC.java @@ -47,6 +47,9 @@ public class WindowsLibC { @CFunction(transition = CFunction.Transition.NO_TRANSITION) public static native T memcpy(T dest, PointerBase src, UnsignedWord n); + @CFunction(transition = CFunction.Transition.NO_TRANSITION) + public static native int memcmp(T s1, T s2, UnsignedWord n); + @CFunction(transition = CFunction.Transition.NO_TRANSITION) public static native T memmove(T dest, PointerBase src, UnsignedWord n); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java index bcbae86a9c87..540b5912123d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java @@ -60,9 +60,9 @@ import com.oracle.svm.core.code.CodeInfoAccess.SingleShotFrameInfoQueryResultAllocator; import com.oracle.svm.core.code.CodeInfoTable; import com.oracle.svm.core.code.FrameInfoQueryResult; -import com.oracle.svm.core.code.ReusableTypeReader; import com.oracle.svm.core.code.RuntimeCodeInfoHistory; import com.oracle.svm.core.code.RuntimeCodeInfoMemory; +import com.oracle.svm.core.code.UninterruptibleReusableTypeReader; import com.oracle.svm.core.code.UntetheredCodeInfo; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.deopt.DeoptimizationSupport; @@ -957,7 +957,7 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev } private static class ImageCodeLocationInfoPrinter { - private final ReusableTypeReader frameInfoReader = new ReusableTypeReader(); + private final UninterruptibleReusableTypeReader frameInfoReader = new UninterruptibleReusableTypeReader(); private final SingleShotFrameInfoQueryResultAllocator singleShotFrameInfoQueryResultAllocator = new SingleShotFrameInfoQueryResultAllocator(); private final DummyValueInfoAllocator dummyValueInfoAllocator = new DummyValueInfoAllocator(); private final FrameInfoState frameInfoState = new FrameInfoState(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java index 7f94e2035960..b95e10816760 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java @@ -53,6 +53,7 @@ import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.StringUtil; @@ -397,6 +398,14 @@ public static boolean isHiddenClass(Class javaClass) { return false; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean isHiddenClass(DynamicHub hub) { + if (JavaVersionUtil.JAVA_SPEC >= 17) { + return hub.isHidden(); + } + return false; + } + public static int arrayTypeDimension(Class clazz) { int dimension = 0; Class componentType = clazz; 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 b80a5b89d8f6..1838b639819a 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 @@ -245,7 +245,7 @@ public static CodePointer absoluteIP(CodeInfo info, long relativeIP) { return (CodePointer) ((UnsignedWord) cast(info).getCodeStart()).add(WordFactory.unsigned(relativeIP)); } - public static void initFrameInfoReader(CodeInfo info, CodePointer ip, ReusableTypeReader frameInfoReader, FrameInfoState state) { + public static void initFrameInfoReader(CodeInfo info, CodePointer ip, UninterruptibleReusableTypeReader frameInfoReader, FrameInfoState state) { long entryOffset = CodeInfoDecoder.lookupCodeInfoEntryOffset(info, relativeIP(info, ip)); state.entryOffset = entryOffset; if (entryOffset >= 0) { @@ -255,7 +255,7 @@ public static void initFrameInfoReader(CodeInfo info, CodePointer ip, ReusableTy } } - public static FrameInfoQueryResult nextFrameInfo(CodeInfo info, ReusableTypeReader frameInfoReader, + public static FrameInfoQueryResult nextFrameInfo(CodeInfo info, UninterruptibleReusableTypeReader frameInfoReader, FrameInfoDecoder.FrameInfoQueryResultAllocator resultAllocator, ValueInfoAllocator valueInfoAllocator, FrameInfoState state) { int entryFlags = CodeInfoDecoder.loadEntryFlags(info, state.entryOffset); boolean isDeoptEntry = CodeInfoDecoder.extractFI(entryFlags) == CodeInfoDecoder.FI_DEOPT_ENTRY_INDEX_S4; @@ -296,6 +296,11 @@ public static void lookupCodeInfo(CodeInfo info, long ip, CodeInfoQueryResult co CodeInfoDecoder.lookupCodeInfo(info, ip, codeInfoQueryResult); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void lookupCodeInfoUninterruptible(CodeInfo info, long ip, CodeInfoQueryResult codeInfoQueryResult) { + CodeInfoDecoder.lookupCodeInfoUninterruptible(info, ip, codeInfoQueryResult); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void lookupCodeInfo(CodeInfo info, long ip, SimpleCodeInfoQueryResult codeInfoQueryResult) { CodeInfoDecoder.lookupCodeInfo(info, ip, codeInfoQueryResult); @@ -364,18 +369,22 @@ public static NonmovableArray getCodeInfoEncodings(CodeInfo info) { return cast(info).getCodeInfoEncodings(); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static NonmovableArray getFrameInfoEncodings(CodeInfo info) { return cast(info).getFrameInfoEncodings(); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static NonmovableObjectArray getFrameInfoObjectConstants(CodeInfo info) { return cast(info).getFrameInfoObjectConstants(); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static NonmovableObjectArray> getFrameInfoSourceClasses(CodeInfo info) { return cast(info).getFrameInfoSourceClasses(); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static NonmovableObjectArray getFrameInfoSourceMethodNames(CodeInfo info) { return cast(info).getFrameInfoSourceMethodNames(); } @@ -454,6 +463,7 @@ public FrameInfoState() { reset(); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public FrameInfoState reset() { entryOffset = -1; isFirstFrame = true; @@ -465,10 +475,11 @@ public FrameInfoState reset() { } public static class SingleShotFrameInfoQueryResultAllocator implements FrameInfoDecoder.FrameInfoQueryResultAllocator { - private static FrameInfoQueryResult frameInfoQueryResult = new FrameInfoQueryResult(); + private static final FrameInfoQueryResult frameInfoQueryResult = new FrameInfoQueryResult(); private boolean fired; + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public SingleShotFrameInfoQueryResultAllocator reload() { fired = false; return this; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java index 8f283a6783c9..16f727cf0e80 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java @@ -89,6 +89,16 @@ static long lookupCodeInfoEntryOffset(CodeInfo info, long ip) { } static void lookupCodeInfo(CodeInfo info, long ip, CodeInfoQueryResult codeInfoQueryResult) { + lookupCodeInfo0(info, ip, codeInfoQueryResult, FrameInfoDecoder.HeapBasedFrameInfoQueryResultLoader); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + static void lookupCodeInfoUninterruptible(CodeInfo info, long ip, CodeInfoQueryResult codeInfoQueryResult) { + lookupCodeInfo0(info, ip, codeInfoQueryResult, FrameInfoDecoder.UninterruptibleFrameInfoQueryResultLoader); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static void lookupCodeInfo0(CodeInfo info, long ip, CodeInfoQueryResult codeInfoQueryResult, FrameInfoDecoder.FrameInfoQueryResultLoader loader) { long sizeEncoding = initialSizeEncoding(); long entryIP = lookupEntryIP(ip); long entryOffset = loadEntryOffset(info, ip); @@ -99,7 +109,7 @@ static void lookupCodeInfo(CodeInfo info, long ip, CodeInfoQueryResult codeInfoQ codeInfoQueryResult.encodedFrameSize = sizeEncoding; codeInfoQueryResult.exceptionOffset = loadExceptionOffset(info, entryOffset, entryFlags); codeInfoQueryResult.referenceMapIndex = loadReferenceMapIndex(info, entryOffset, entryFlags); - codeInfoQueryResult.frameInfo = loadFrameInfo(info, entryOffset, entryFlags); + codeInfoQueryResult.frameInfo = loadFrameInfo(info, entryOffset, entryFlags, loader); return; } @@ -172,7 +182,7 @@ static long lookupDeoptimizationEntrypoint(CodeInfo info, long method, long enco codeInfo.encodedFrameSize = sizeEncoding; codeInfo.exceptionOffset = loadExceptionOffset(info, entryOffset, entryFlags); codeInfo.referenceMapIndex = loadReferenceMapIndex(info, entryOffset, entryFlags); - codeInfo.frameInfo = loadFrameInfo(info, entryOffset, entryFlags); + codeInfo.frameInfo = loadFrameInfo(info, entryOffset, entryFlags, FrameInfoDecoder.HeapBasedFrameInfoQueryResultLoader); assert codeInfo.frameInfo.isDeoptEntry() && codeInfo.frameInfo.getCaller() == null : "Deoptimization entry must not have inlined frames"; return entryIP; } @@ -339,7 +349,7 @@ private static boolean isDeoptEntryPoint(CodeInfo info, long entryOffset, int en } } - static boolean initFrameInfoReader(CodeInfo info, long entryOffset, ReusableTypeReader frameInfoReader) { + static boolean initFrameInfoReader(CodeInfo info, long entryOffset, UninterruptibleReusableTypeReader frameInfoReader) { int entryFlags = loadEntryFlags(info, entryOffset); int frameInfoIndex = NonmovableByteArrayReader.getS4(CodeInfoAccess.getCodeInfoEncodings(info), offsetFI(entryOffset, entryFlags)); frameInfoReader.setByteIndex(frameInfoIndex); @@ -347,8 +357,8 @@ static boolean initFrameInfoReader(CodeInfo info, long entryOffset, ReusableType return extractFI(entryFlags) != FI_NO_DEOPT; } - private static FrameInfoQueryResult loadFrameInfo(CodeInfo info, long entryOffset, int entryFlags) { - + @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false) + private static FrameInfoQueryResult loadFrameInfo(CodeInfo info, long entryOffset, int entryFlags, FrameInfoDecoder.FrameInfoQueryResultLoader loader) { boolean isDeoptEntry; switch (extractFI(entryFlags)) { case FI_NO_DEOPT: @@ -363,8 +373,7 @@ private static FrameInfoQueryResult loadFrameInfo(CodeInfo info, long entryOffse throw shouldNotReachHere(); } int frameInfoIndex = NonmovableByteArrayReader.getS4(CodeInfoAccess.getCodeInfoEncodings(info), offsetFI(entryOffset, entryFlags)); - return FrameInfoDecoder.decodeFrameInfo(isDeoptEntry, new ReusableTypeReader(CodeInfoAccess.getFrameInfoEncodings(info), frameInfoIndex), info, - FrameInfoDecoder.HeapBasedFrameInfoQueryResultAllocator, FrameInfoDecoder.HeapBasedValueInfoAllocator); + return loader.load(info, isDeoptEntry, frameInfoIndex); } @AlwaysInline("Make IP-lookup loop call free") @@ -466,6 +475,7 @@ static int extractRM(int entryFlags) { return (entryFlags & RM_MASK_IN_PLACE) >> RM_SHIFT; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) static int extractFI(int entryFlags) { return (entryFlags & FI_MASK_IN_PLACE) >> FI_SHIFT; } @@ -498,6 +508,7 @@ private static long offsetRM(long entryOffset, int entryFlags) { return entryOffset + getU1(RM_OFFSET, entryFlags); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static long offsetFI(long entryOffset, int entryFlags) { assert extractFI(entryFlags) != FI_NO_DEOPT; return entryOffset + getU1(FI_OFFSET, entryFlags); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoQueryResult.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoQueryResult.java index 9cbb94329080..eb162b8aa7a2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoQueryResult.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoQueryResult.java @@ -122,6 +122,7 @@ public long getReferenceMapIndex() { * Stack frame information used, e.g., for deoptimization and printing of stack frames in debug * builds. */ + @Uninterruptible(reason = "called from uninterruptible code", mayBeInlined = true) public FrameInfoQueryResult getFrameInfo() { return frameInfo; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java index a80887a3e9ac..7fc87fba86a1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java @@ -110,6 +110,17 @@ public static CodeInfoQueryResult lookupCodeInfoQueryResult(CodeInfo info, CodeP return result; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static CodeInfoQueryResult lookupCodeInfoQueryResultUninterruptible(CodeInfo info, CodePointer absoluteIP, CodeInfoQueryResult result) { + counters().lookupCodeInfoCount.inc(); + if (info.isNull()) { + return null; + } + result.ip = absoluteIP; + CodeInfoAccess.lookupCodeInfoUninterruptible(info, CodeInfoAccess.relativeIP(info, absoluteIP), result); + return result; + } + public static CodeInfoQueryResult lookupDeoptimizationEntrypoint(int deoptOffsetInImage, long encodedBci) { counters().lookupDeoptimizationEntrypointCount.inc(); /* Deoptimization entry points are always in the image, i.e., never compiled at run time. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java index 24c88661ff0f..b290c8c23e6a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java @@ -32,12 +32,14 @@ import org.graalvm.compiler.core.common.util.TypeReader; import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.heap.RestrictHeapAccess; +import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.NonmovableArray; import com.oracle.svm.core.c.NonmovableArrays; import com.oracle.svm.core.c.NonmovableObjectArray; import com.oracle.svm.core.code.FrameInfoQueryResult.ValueInfo; import com.oracle.svm.core.code.FrameInfoQueryResult.ValueType; +import com.oracle.svm.core.heap.RestrictHeapAccess; +import com.oracle.svm.core.jdk.UninterruptibleUtils; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.meta.SharedMethod; import com.oracle.svm.core.meta.SubstrateObjectConstant; @@ -104,6 +106,40 @@ public FrameInfoQueryResult newFrameInfoQueryResult() { static final HeapBasedFrameInfoQueryResultAllocator HeapBasedFrameInfoQueryResultAllocator = new HeapBasedFrameInfoQueryResultAllocator(); + public interface FrameInfoQueryResultLoader { + FrameInfoQueryResult load(CodeInfo info, boolean isDeoptEntry, long frameInfoIndex); + } + + static class HeapBasedFrameInfoQueryResultLoader implements FrameInfoQueryResultLoader { + @Override + public FrameInfoQueryResult load(CodeInfo info, boolean isDeoptEntry, long frameInfoIndex) { + return FrameInfoDecoder.decodeFrameInfo(isDeoptEntry, new UninterruptibleReusableTypeReader(CodeInfoAccess.getFrameInfoEncodings(info), frameInfoIndex), info, + FrameInfoDecoder.HeapBasedFrameInfoQueryResultAllocator, FrameInfoDecoder.HeapBasedValueInfoAllocator, new CodeInfoAccess.FrameInfoState()); + } + } + + static final HeapBasedFrameInfoQueryResultLoader HeapBasedFrameInfoQueryResultLoader = new HeapBasedFrameInfoQueryResultLoader(); + + static class UninterruptibleFrameInfoQueryResultLoader implements FrameInfoQueryResultLoader { + private static final CodeInfoAccess.FrameInfoState frameInfoState = new CodeInfoAccess.FrameInfoState(); + private static final UninterruptibleReusableTypeReader uninterruptibleFrameInfoReader = new UninterruptibleReusableTypeReader(); + private static final CodeInfoAccess.DummyValueInfoAllocator dummyValueInfoAllocator = new CodeInfoAccess.DummyValueInfoAllocator(); + private static final CodeInfoAccess.SingleShotFrameInfoQueryResultAllocator singleShotFrameInfoQueryResultAllocator = new CodeInfoAccess.SingleShotFrameInfoQueryResultAllocator(); + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public FrameInfoQueryResult load(CodeInfo info, boolean isDeoptEntry, long frameInfoIndex) { + frameInfoState.reset(); + singleShotFrameInfoQueryResultAllocator.reload(); + uninterruptibleFrameInfoReader.setByteIndex(frameInfoIndex); + uninterruptibleFrameInfoReader.setData(CodeInfoAccess.getFrameInfoEncodings(info)); + return FrameInfoDecoder.decodeFrameInfo(isDeoptEntry, uninterruptibleFrameInfoReader, info, singleShotFrameInfoQueryResultAllocator, + dummyValueInfoAllocator, frameInfoState); + } + } + + static final UninterruptibleFrameInfoQueryResultLoader UninterruptibleFrameInfoQueryResultLoader = new UninterruptibleFrameInfoQueryResultLoader(); + public interface ValueInfoAllocator { ValueInfo newValueInfo(); @@ -175,6 +211,7 @@ private static class CompressedFrameDecoderHelper { * Differentiates between compressed and uncompressed frame slices. Uncompressed frame * slices start with {@link #UNCOMPRESSED_FRAME_SLICE_MARKER}. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static boolean isCompressedFrameSlice(int firstValue) { return firstValue != UNCOMPRESSED_FRAME_SLICE_MARKER; } @@ -183,6 +220,7 @@ private static boolean isCompressedFrameSlice(int firstValue) { * Determines whether a value is a pointer to a shared frame index. See * FrameInfoEncoder.encodeCompressedFirstEntry for more details. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static boolean isSharedFramePointer(int value) { return value < 0; } @@ -191,6 +229,7 @@ private static boolean isSharedFramePointer(int value) { * Complement of FrameInfoEncoder.encodeCompressedFirstEntry when a shared frame index is * encoded. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static int decodeSharedFrameIndex(int value) { VMError.guarantee(value < UNCOMPRESSED_FRAME_SLICE_MARKER); @@ -202,6 +241,7 @@ private static int decodeSharedFrameIndex(int value) { * a uniqueSharedFrameSuccessor. See FrameInfoEncoder.encodeCompressedMethodIndex for more * details. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static boolean hasEncodedUniqueSharedFrameSuccessor(int encodedSourceMethodNameIndex) { return encodedSourceMethodNameIndex < 0; } @@ -209,6 +249,7 @@ private static boolean hasEncodedUniqueSharedFrameSuccessor(int encodedSourceMet /** * Complement of FrameInfoEncoder.encodeCompressedMethodIndex. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static int decodeMethodIndex(int methodIndex) { if (methodIndex < 0) { return -(methodIndex + COMPRESSED_UNIQUE_SUCCESSOR_ADDEND); @@ -220,6 +261,7 @@ private static int decodeMethodIndex(int methodIndex) { /** * See FrameInfoEncoder.encodeCompressedSourceLineNumber for details. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static boolean isSliceEnd(int encodedSourceLineNumber) { return encodedSourceLineNumber < 0; } @@ -227,21 +269,18 @@ private static boolean isSliceEnd(int encodedSourceLineNumber) { /** * Complement of FrameInfoEncode.encodeCompressedSourceLineNumber. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static int decodeSourceLineNumber(int sourceLineNumber) { - return Math.abs(sourceLineNumber) - COMPRESSED_SOURCE_LINE_ADDEND; + return UninterruptibleUtils.Math.abs(sourceLineNumber) - COMPRESSED_SOURCE_LINE_ADDEND; } } - protected static FrameInfoQueryResult decodeFrameInfo(boolean isDeoptEntry, TypeReader readBuffer, CodeInfo info, - FrameInfoQueryResultAllocator resultAllocator, ValueInfoAllocator valueInfoAllocator) { - return decodeFrameInfo(isDeoptEntry, readBuffer, info, resultAllocator, valueInfoAllocator, new CodeInfoAccess.FrameInfoState()); - } - + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) protected static FrameInfoQueryResult decodeFrameInfo(boolean isDeoptEntry, TypeReader readBuffer, CodeInfo info, FrameInfoQueryResultAllocator resultAllocator, ValueInfoAllocator valueInfoAllocator, CodeInfoAccess.FrameInfoState state) { if (state.isFirstFrame) { - state.firstValue = readBuffer.getSVInt(); + state.firstValue = getSVInt(readBuffer); } FrameInfoQueryResult result; @@ -256,16 +295,17 @@ protected static FrameInfoQueryResult decodeFrameInfo(boolean isDeoptEntry, Type } /* - * See (FrameInfoEncoder.CompressedFrameInfoEncodingMedata) for more information about the + * See (FrameInfoEncoder.CompressedFrameInfoEncodingMetadata) for more information about the * compressed encoding format. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static FrameInfoQueryResult decodeCompressedFrameInfo(boolean isDeoptEntry, TypeReader readBuffer, CodeInfo info, FrameInfoQueryResultAllocator resultAllocator, CodeInfoAccess.FrameInfoState state) { FrameInfoQueryResult result = null; FrameInfoQueryResult prev = null; while (!state.isDone) { - FrameInfoQueryResult cur = resultAllocator.newFrameInfoQueryResult(); + FrameInfoQueryResult cur = newFrameInfoQueryResult(resultAllocator); if (cur == null) { return result; } @@ -276,15 +316,15 @@ private static FrameInfoQueryResult decodeCompressedFrameInfo(boolean isDeoptEnt long bufferIndexToRestore = -1; if (state.successorIndex != NO_SUCCESSOR_INDEX_MARKER) { - bufferIndexToRestore = readBuffer.getByteIndex(); - readBuffer.setByteIndex(state.successorIndex); + bufferIndexToRestore = getByteIndex(readBuffer); + setByteIndex(readBuffer, state.successorIndex); } final int firstEntry; if (state.isFirstFrame) { firstEntry = state.firstValue; } else { - firstEntry = readBuffer.getSVInt(); + firstEntry = getSVInt(readBuffer); assert !isDeoptEntry : "Deoptimization entry must not have inlined frames"; } @@ -293,24 +333,24 @@ private static FrameInfoQueryResult decodeCompressedFrameInfo(boolean isDeoptEnt long sharedFrameByteIndex = CompressedFrameDecoderHelper.decodeSharedFrameIndex(firstEntry); // save current buffer index - bufferIndexToRestore = readBuffer.getByteIndex(); + bufferIndexToRestore = getByteIndex(readBuffer); // jump to shared frame index - readBuffer.setByteIndex(sharedFrameByteIndex); + setByteIndex(readBuffer, sharedFrameByteIndex); - int sourceClassIndex = readBuffer.getSVInt(); + int sourceClassIndex = getSVInt(readBuffer); VMError.guarantee(!CompressedFrameDecoderHelper.isSharedFramePointer(sourceClassIndex)); decodeCompressedFrameData(readBuffer, info, state, sourceClassIndex, cur); // jump back to frame slice information - readBuffer.setByteIndex(bufferIndexToRestore); + setByteIndex(readBuffer, bufferIndexToRestore); bufferIndexToRestore = -1; } else { decodeCompressedFrameData(readBuffer, info, state, firstEntry, cur); } if (bufferIndexToRestore != -1) { - readBuffer.setByteIndex(bufferIndexToRestore); + setByteIndex(readBuffer, bufferIndexToRestore); } if (prev == null) { @@ -327,11 +367,13 @@ private static FrameInfoQueryResult decodeCompressedFrameInfo(boolean isDeoptEnt return result; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static void decodeCompressedFrameData(TypeReader readBuffer, CodeInfo info, CodeInfoAccess.FrameInfoState state, int sourceClassIndex, FrameInfoQueryResult queryResult) { - int encodedSourceMethodNameIndex = readBuffer.getSVInt(); + int encodedSourceMethodNameIndex = getSVInt(readBuffer); int sourceMethodNameIndex = CompressedFrameDecoderHelper.decodeMethodIndex(encodedSourceMethodNameIndex); - int encodedSourceLineNumber = readBuffer.getSVInt(); + int encodedSourceLineNumber = getSVInt(readBuffer); int sourceLineNumber = CompressedFrameDecoderHelper.decodeSourceLineNumber(encodedSourceLineNumber); + int methodId = getSVInt(readBuffer); queryResult.sourceClassIndex = sourceClassIndex; queryResult.sourceMethodNameIndex = sourceMethodNameIndex; @@ -339,9 +381,10 @@ private static void decodeCompressedFrameData(TypeReader readBuffer, CodeInfo in queryResult.sourceClass = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceClasses(info), sourceClassIndex); queryResult.sourceMethodName = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceMethodNames(info), sourceMethodNameIndex); queryResult.sourceLineNumber = sourceLineNumber; + queryResult.methodId = methodId; if (CompressedFrameDecoderHelper.hasEncodedUniqueSharedFrameSuccessor(encodedSourceMethodNameIndex)) { - state.successorIndex = readBuffer.getSVInt(); + state.successorIndex = getSVInt(readBuffer); } else { state.successorIndex = NO_SUCCESSOR_INDEX_MARKER; } @@ -351,6 +394,7 @@ private static void decodeCompressedFrameData(TypeReader readBuffer, CodeInfo in assert !state.isDone || state.successorIndex == NO_SUCCESSOR_INDEX_MARKER; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static FrameInfoQueryResult decodeUncompressedFrameInfo(boolean isDeoptEntry, TypeReader readBuffer, CodeInfo info, FrameInfoQueryResultAllocator resultAllocator, ValueInfoAllocator valueInfoAllocator, CodeInfoAccess.FrameInfoState state) { FrameInfoQueryResult result = null; @@ -358,12 +402,12 @@ private static FrameInfoQueryResult decodeUncompressedFrameInfo(boolean isDeoptE ValueInfo[][] virtualObjects = null; while (true) { - FrameInfoQueryResult cur = resultAllocator.newFrameInfoQueryResult(); + FrameInfoQueryResult cur = newFrameInfoQueryResult(resultAllocator); if (cur == null) { return result; } - int encodedBci = readBuffer.getSVInt(); + int encodedBci = getSVInt(readBuffer); if (encodedBci == NO_CALLER_BCI) { return result; } @@ -376,35 +420,35 @@ private static FrameInfoQueryResult decodeUncompressedFrameInfo(boolean isDeoptE final boolean needLocalValues = encodedBci != NO_LOCAL_INFO_BCI; if (needLocalValues) { - cur.numLocks = readBuffer.getUVInt(); - cur.numLocals = readBuffer.getUVInt(); - cur.numStack = readBuffer.getUVInt(); + cur.numLocks = getUVInt(readBuffer); + cur.numLocals = getUVInt(readBuffer); + cur.numStack = getUVInt(readBuffer); /* * We either encode a reference to the target method (for runtime compilations) or * just the start offset of the target method (for native image methods, because we * do not want to include unnecessary method metadata in the native image. */ - int deoptMethodIndex = readBuffer.getSVInt(); + int deoptMethodIndex = getSVInt(readBuffer); if (deoptMethodIndex < 0) { /* Negative number is a reference to the target method. */ cur.deoptMethod = (SharedMethod) NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoObjectConstants(info), -1 - deoptMethodIndex); - cur.deoptMethodOffset = cur.deoptMethod.getDeoptOffsetInImage(); + cur.deoptMethodOffset = getDeoptOffsetInImage(cur.deoptMethod); } else { /* Positive number is a directly encoded method offset. */ cur.deoptMethodOffset = deoptMethodIndex; } - int curValueInfosLength = readBuffer.getUVInt(); + int curValueInfosLength = getUVInt(readBuffer); cur.valueInfos = decodeValues(valueInfoAllocator, curValueInfosLength, readBuffer, CodeInfoAccess.getFrameInfoObjectConstants(info)); } if (state.isFirstFrame && needLocalValues) { /* This is the first frame, i.e., the top frame that will be returned. */ - int numVirtualObjects = readBuffer.getUVInt(); - virtualObjects = valueInfoAllocator.newValueInfoArrayArray(numVirtualObjects); + int numVirtualObjects = getUVInt(readBuffer); + virtualObjects = newValueInfoArrayArray(valueInfoAllocator, numVirtualObjects); for (int i = 0; i < numVirtualObjects; i++) { - int numValues = readBuffer.getUVInt(); + int numValues = getUVInt(readBuffer); ValueInfo[] decodedValues = decodeValues(valueInfoAllocator, numValues, readBuffer, CodeInfoAccess.getFrameInfoObjectConstants(info)); if (virtualObjects != null) { virtualObjects[i] = decodedValues; @@ -414,10 +458,10 @@ private static FrameInfoQueryResult decodeUncompressedFrameInfo(boolean isDeoptE cur.virtualObjects = virtualObjects; if (encodeSourceReferences()) { - final int sourceClassIndex = readBuffer.getSVInt(); - final int sourceMethodNameIndex = readBuffer.getSVInt(); - final int sourceLineNumber = readBuffer.getSVInt(); - final int sourceMethodID = readBuffer.getUVInt(); + final int sourceClassIndex = getSVInt(readBuffer); + final int sourceMethodNameIndex = getSVInt(readBuffer); + final int sourceLineNumber = getSVInt(readBuffer); + final int sourceMethodId = getUVInt(readBuffer); cur.sourceClassIndex = sourceClassIndex; cur.sourceMethodNameIndex = sourceMethodNameIndex; @@ -425,7 +469,7 @@ private static FrameInfoQueryResult decodeUncompressedFrameInfo(boolean isDeoptE cur.sourceClass = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceClasses(info), sourceClassIndex); cur.sourceMethodName = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceMethodNames(info), sourceMethodNameIndex); cur.sourceLineNumber = sourceLineNumber; - cur.methodID = sourceMethodID; + cur.methodId = sourceMethodId; } if (prev == null) { @@ -440,16 +484,17 @@ private static FrameInfoQueryResult decodeUncompressedFrameInfo(boolean isDeoptE } } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static ValueInfo[] decodeValues(ValueInfoAllocator valueInfoAllocator, int numValues, TypeReader readBuffer, NonmovableObjectArray frameInfoObjectConstants) { - ValueInfo[] valueInfos = valueInfoAllocator.newValueInfoArray(numValues); + ValueInfo[] valueInfos = newValueInfoArray(valueInfoAllocator, numValues); for (int i = 0; i < numValues; i++) { - ValueInfo valueInfo = valueInfoAllocator.newValueInfo(); + ValueInfo valueInfo = newValueInfo(valueInfoAllocator); if (valueInfos != null) { valueInfos[i] = valueInfo; } - int flags = readBuffer.getU1(); + int flags = getU1(readBuffer); ValueType valueType = extractType(flags); if (valueInfo != null) { valueInfo.type = valueType; @@ -458,23 +503,26 @@ private static ValueInfo[] decodeValues(ValueInfoAllocator valueInfoAllocator, i valueInfo.isEliminatedMonitor = extractIsEliminatedMonitor(flags); } if (valueType.hasData) { - long valueInfoData = readBuffer.getSV(); + long valueInfoData = getSV(readBuffer); if (valueInfo != null) { valueInfo.data = valueInfoData; } } - - valueInfoAllocator.decodeConstant(valueInfo, frameInfoObjectConstants); + decodeConstant(valueInfoAllocator, valueInfo, frameInfoObjectConstants); } return valueInfos; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) protected static boolean encodeSourceReferences() { return SubstrateOptions.StackTrace.getValue(); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) protected static int decodeBci(long encodedBci) { - return TypeConversion.asS4(encodedBci >> BCI_SHIFT); + long value = encodedBci >> BCI_SHIFT; + assert value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE; + return (int) value; } protected static boolean decodeDuringCall(long encodedBci) { @@ -530,19 +578,85 @@ public static void logReadableBci(Log log, long encodedBci) { /* Allow allocation-free access to ValueType values */ private static final ValueType[] ValueTypeValues = ValueType.values(); + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static ValueType extractType(int flags) { return ValueTypeValues[(flags & TYPE_MASK_IN_PLACE) >> TYPE_SHIFT]; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static JavaKind extractKind(int flags) { return KIND_VALUES[(flags & KIND_MASK_IN_PLACE) >> KIND_SHIFT]; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static boolean extractIsCompressedReference(int flags) { return (flags & IS_COMPRESSED_REFERENCE_MASK_IN_PLACE) != 0; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static boolean extractIsEliminatedMonitor(int flags) { return ((flags & KIND_MASK_IN_PLACE) >> KIND_SHIFT) == IS_ELIMINATED_MONITOR_KIND_VALUE; } + + /* Uninterruptible wrapper methods. */ + + @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false) + private static int getDeoptOffsetInImage(SharedMethod sharedMethod) { + return sharedMethod.getDeoptOffsetInImage(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false) + private static long getSV(TypeReader typeReader) { + return typeReader.getSV(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false) + private static int getU1(TypeReader typeReader) { + return typeReader.getU1(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false) + private static int getSVInt(TypeReader typeReader) { + return typeReader.getSVInt(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false) + private static int getUVInt(TypeReader typeReader) { + return typeReader.getUVInt(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false) + private static long getByteIndex(TypeReader typeReader) { + return typeReader.getByteIndex(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false) + private static void setByteIndex(TypeReader typeReader, long byteIndex) { + typeReader.setByteIndex(byteIndex); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false) + private static FrameInfoQueryResult newFrameInfoQueryResult(FrameInfoQueryResultAllocator frameInfoQueryResultAllocator) { + return frameInfoQueryResultAllocator.newFrameInfoQueryResult(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false) + private static ValueInfo newValueInfo(ValueInfoAllocator valueInfoAllocator) { + return valueInfoAllocator.newValueInfo(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false) + private static ValueInfo[] newValueInfoArray(ValueInfoAllocator valueInfoAllocator, int len) { + return valueInfoAllocator.newValueInfoArray(len); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false) + private static ValueInfo[][] newValueInfoArrayArray(ValueInfoAllocator valueInfoAllocator, int len) { + return valueInfoAllocator.newValueInfoArrayArray(len); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false) + private static void decodeConstant(ValueInfoAllocator valueInfoAllocator, ValueInfo valueInfo, NonmovableObjectArray frameInfoObjectConstants) { + valueInfoAllocator.decodeConstant(valueInfo, frameInfoObjectConstants); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java index 213901472156..933100d5ba78 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java @@ -129,6 +129,7 @@ protected void fillSourceFields(BytecodeFrame bytecodeFrame, FrameInfoQueryResul */ resultFrameInfo.sourceMethodName = stringTable.deduplicate(source.getMethodName(), true); resultFrameInfo.sourceLineNumber = source.getLineNumber(); + resultFrameInfo.methodId = ImageSingletons.lookup(FrameInfoMethodData.class).getMethodId(method); } protected abstract Class getDeclaringJavaClass(ResolvedJavaMethod method); @@ -147,6 +148,7 @@ protected void fillSourceFields(BytecodeFrame bytecodeFrame, FrameInfoQueryResul resultFrameInfo.sourceClass = targetFrameInfo.sourceClass; resultFrameInfo.sourceMethodName = targetFrameInfo.sourceMethodName; resultFrameInfo.sourceLineNumber = targetFrameInfo.sourceLineNumber; + resultFrameInfo.methodId = targetFrameInfo.methodId; } } } @@ -167,12 +169,14 @@ private static class CompressedFrameData { final Class sourceClass; final String sourceMethodName; final int sourceLineNumber; + final int methodId; final boolean isSliceEnd; - CompressedFrameData(Class sourceClass, String sourceMethodName, int sourceLineNumber, boolean isSliceEnd) { + CompressedFrameData(Class sourceClass, String sourceMethodName, int sourceLineNumber, int methodId, boolean isSliceEnd) { this.sourceClass = sourceClass; this.sourceMethodName = sourceMethodName; this.sourceLineNumber = sourceLineNumber; + this.methodId = methodId; this.isSliceEnd = isSliceEnd; } @@ -185,12 +189,13 @@ public boolean equals(Object o) { return false; } CompressedFrameData that = (CompressedFrameData) o; - return sourceLineNumber == that.sourceLineNumber && isSliceEnd == that.isSliceEnd && sourceClass.equals(that.sourceClass) && sourceMethodName.equals(that.sourceMethodName); + return sourceLineNumber == that.sourceLineNumber && methodId == that.methodId && isSliceEnd == that.isSliceEnd && sourceClass.equals(that.sourceClass) && + sourceMethodName.equals(that.sourceMethodName); } @Override public int hashCode() { - return Objects.hash(sourceClass, sourceMethodName, sourceLineNumber, isSliceEnd); + return Objects.hash(sourceClass, sourceMethodName, sourceLineNumber, methodId, isSliceEnd); } } @@ -396,6 +401,7 @@ private static void encodeCompressedFrame(UnsafeArrayTypeWriter encodingBuffer, boolean encodeUniqueSuccessor = uniqueSuccessorIndex != NO_SUCCESSOR_INDEX_MARKER; encodingBuffer.putSV(encodeCompressedMethodIndex(methodIndex, encodeUniqueSuccessor)); encodingBuffer.putSV(encodeCompressedSourceLineNumber(frame.sourceLineNumber, frame.isSliceEnd)); + encodingBuffer.putSV(frame.methodId); if (encodeUniqueSuccessor) { encodingBuffer.putSV(uniqueSuccessorIndex); } @@ -422,7 +428,7 @@ boolean writeFrameVerificationInfo(FrameData data, Encoders encoders) { cur.sourceClassIndex = encoders.sourceClasses.getIndex(cur.sourceClass); cur.sourceMethodNameIndex = encoders.sourceMethodNames.getIndex(cur.sourceMethodName); boolean isSliceEnd = cur.caller == null; - CompressedFrameData frame = new CompressedFrameData(cur.sourceClass, cur.sourceMethodName, cur.sourceLineNumber, isSliceEnd); + CompressedFrameData frame = new CompressedFrameData(cur.sourceClass, cur.sourceMethodName, cur.sourceLineNumber, cur.methodId, isSliceEnd); assert frame.equals(slice.get(curIdx)); curIdx++; } @@ -477,7 +483,8 @@ protected FrameData addDebugInfo(ResolvedJavaMethod method, Infopoint infopoint, assert !resultFrame.hasLocalValueInfo(); final boolean isSliceEnd = resultFrame.caller == null; final int sourceLineNumber = resultFrame.sourceLineNumber; - CompressedFrameData frame = new CompressedFrameData(sourceClass, sourceMethodName, sourceLineNumber, isSliceEnd); + final int methodId = resultFrame.methodId; + CompressedFrameData frame = new CompressedFrameData(sourceClass, sourceMethodName, sourceLineNumber, methodId, isSliceEnd); frameSlice.add(frame); } @@ -531,8 +538,8 @@ private FrameInfoQueryResult addFrame(FrameData data, BytecodeFrame frame, boole if (needLocalValues) { SharedMethod method = (SharedMethod) frame.getMethod(); if (ImageSingletons.contains(CallStackFrameMethodData.class)) { - result.methodID = ImageSingletons.lookup(CallStackFrameMethodData.class).getMethodId(method); - ImageSingletons.lookup(CallStackFrameMethodInfo.class).addMethodInfo(method, result.methodID); + result.methodId = ImageSingletons.lookup(CallStackFrameMethodData.class).getMethodId(method); + ImageSingletons.lookup(CallStackFrameMethodInfo.class).addMethodInfo(method, result.methodId); } if (customization.storeDeoptTargetMethod()) { @@ -915,7 +922,7 @@ private void encodeUncompressedFrameData(FrameData data, UnsafeArrayTypeWriter e encodingBuffer.putSV(classIndex); encodingBuffer.putSV(methodIndex); encodingBuffer.putSV(cur.sourceLineNumber); - encodingBuffer.putUV(cur.methodID); + encodingBuffer.putUV(cur.methodId); } } encodingBuffer.putSV(FrameInfoDecoder.NO_CALLER_BCI); @@ -1005,9 +1012,7 @@ private static int encodeCompressedSourceLineNumber(int sourceLineNumber, boolea void verifyEncoding(CodeInfo info) { for (FrameData expectedData : allDebugInfos) { - FrameInfoQueryResult actualFrame = FrameInfoDecoder.decodeFrameInfo(expectedData.frame.isDeoptEntry, - new ReusableTypeReader(CodeInfoAccess.getFrameInfoEncodings(info), expectedData.encodedFrameInfoIndex), - info, FrameInfoDecoder.HeapBasedFrameInfoQueryResultAllocator, FrameInfoDecoder.HeapBasedValueInfoAllocator); + FrameInfoQueryResult actualFrame = FrameInfoDecoder.HeapBasedFrameInfoQueryResultLoader.load(info, expectedData.frame.isDeoptEntry, expectedData.encodedFrameInfoIndex); FrameInfoVerifier.verifyFrames(expectedData, expectedData.frame, actualFrame); } } @@ -1038,6 +1043,7 @@ protected static void verifyFrames(FrameInfoEncoder.FrameData expectedData, Fram assert Objects.equals(expectedFrame.sourceClass, actualFrame.sourceClass); assert Objects.equals(expectedFrame.sourceMethodName, actualFrame.sourceMethodName); assert expectedFrame.sourceLineNumber == actualFrame.sourceLineNumber; + assert expectedFrame.methodId == actualFrame.methodId; assert expectedFrame.sourceClassIndex == actualFrame.sourceClassIndex; assert expectedFrame.sourceMethodNameIndex == actualFrame.sourceMethodNameIndex; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoMethodData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoMethodData.java new file mode 100644 index 000000000000..c304fc562d58 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoMethodData.java @@ -0,0 +1,38 @@ +/* + * 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.code; + +import jdk.vm.ci.meta.ResolvedJavaMethod; + +/** + * Encode method data to identify methods in {@link FrameInfoQueryResult}. + */ +public interface FrameInfoMethodData { + /** + * Returns the unique identification number for the method. + */ + int getMethodId(ResolvedJavaMethod method); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java index 55e828785053..e394e7ffbb65 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java @@ -31,7 +31,9 @@ import com.oracle.svm.core.CalleeSavedRegisters; import com.oracle.svm.core.ReservedRegisters; +import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.jdk.JavaLangSubstitutions; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.meta.SharedMethod; @@ -89,7 +91,7 @@ public enum ValueType { */ VirtualObject(true); - protected final boolean hasData; + final boolean hasData; ValueType(boolean hasData) { this.hasData = hasData; @@ -165,7 +167,7 @@ public JavaConstant getValue() { protected Class sourceClass; protected String sourceMethodName; protected int sourceLineNumber; - protected int methodID; + protected int methodId; // Index of sourceClass in CodeInfoDecoder.frameInfoSourceClasses protected int sourceClassIndex; @@ -238,6 +240,7 @@ public long getEncodedBci() { /** * Returns the bytecode index. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public int getBci() { return FrameInfoDecoder.decodeBci(encodedBci); } @@ -312,6 +315,7 @@ public ValueInfo[][] getVirtualObjects() { return virtualObjects; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public Class getSourceClass() { return sourceClass; } @@ -320,6 +324,7 @@ public String getSourceClassName() { return sourceClass != null ? sourceClass.getName() : ""; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public String getSourceMethodName() { return sourceMethodName; } @@ -327,14 +332,17 @@ public String getSourceMethodName() { /** * Returns the unique identification number for the method. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public int getMethodID() { - return methodID; + return methodId; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public String getSourceFileName() { return sourceClass != null ? DynamicHub.fromClass(sourceClass).getSourceFileName() : null; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public int getSourceLineNumber() { return sourceLineNumber; } @@ -367,6 +375,15 @@ public boolean isNativeMethod() { return sourceLineNumber == -2; } + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int hashCode() { + int result = 31 * sourceClass.hashCode() + JavaLangSubstitutions.StringUtil.hashCode(sourceMethodName); + result = 31 * result + JavaLangSubstitutions.StringUtil.hashCode(getSourceFileName()); + result = 31 * result + sourceLineNumber; + return result; + } + public Log log(Log log) { String className = sourceClass != null ? sourceClass.getName() : ""; String methodName = sourceMethodName != null ? sourceMethodName : ""; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ReusableTypeReader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ReusableTypeReader.java deleted file mode 100644 index 6d68cc00dfc7..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ReusableTypeReader.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2017, 2017, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.core.code; - -import org.graalvm.compiler.core.common.util.AbstractTypeReader; - -import com.oracle.svm.core.c.NonmovableArray; -import com.oracle.svm.core.c.NonmovableArrays; -import com.oracle.svm.core.util.NonmovableByteArrayReader; -import com.oracle.svm.core.util.VMError; - -/** - * Custom TypeReader that allows reusing the same instance over and over again. Only getSV(), - * getSVInt(), getUV(), getUVInt() are implemented. - */ -public final class ReusableTypeReader extends AbstractTypeReader { - - private NonmovableArray data; - private long byteIndex = -1; - - public ReusableTypeReader() { - } - - public ReusableTypeReader(NonmovableArray data, long byteIndex) { - this.data = data; - this.byteIndex = byteIndex; - } - - public ReusableTypeReader reset() { - data = NonmovableArrays.nullArray(); - byteIndex = -1; - return this; - } - - public boolean isValid() { - return data != null && byteIndex >= 0; - } - - @Override - public long getByteIndex() { - return byteIndex; - } - - @Override - public void setByteIndex(long byteIndex) { - this.byteIndex = byteIndex; - } - - public NonmovableArray getData() { - return data; - } - - public void setData(NonmovableArray data) { - this.data = data; - } - - @Override - public int getS1() { - throw VMError.unimplemented(); - } - - @Override - public int getU1() { - int result = NonmovableByteArrayReader.getU1(data, byteIndex); - byteIndex += Byte.BYTES; - return result; - } - - @Override - public int getS2() { - throw VMError.unimplemented(); - } - - @Override - public int getU2() { - throw VMError.unimplemented(); - } - - @Override - public int getS4() { - throw VMError.unimplemented(); - } - - @Override - public long getU4() { - throw VMError.unimplemented(); - } - - @Override - public long getS8() { - throw VMError.unimplemented(); - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/UninterruptibleReusableTypeReader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/UninterruptibleReusableTypeReader.java new file mode 100644 index 000000000000..56b1c309bdd7 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/UninterruptibleReusableTypeReader.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2017, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.code; + +import org.graalvm.compiler.core.common.util.AbstractTypeReader; +import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.c.NonmovableArray; +import com.oracle.svm.core.c.NonmovableArrays; +import com.oracle.svm.core.util.NonmovableByteArrayReader; +import com.oracle.svm.core.util.VMError; + +/** + * Custom uninterruptible TypeReader that allows reusing the same instance over and over again. Only + * getSV(), getSVInt(), getUV(), getUVInt() needs implementation. + */ +public class UninterruptibleReusableTypeReader extends AbstractTypeReader { + private NonmovableArray data; + private long byteIndex = -1; + + public UninterruptibleReusableTypeReader() { + } + + public UninterruptibleReusableTypeReader(NonmovableArray data, long byteIndex) { + this.data = data; + this.byteIndex = byteIndex; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public UninterruptibleReusableTypeReader reset() { + data = NonmovableArrays.nullArray(); + byteIndex = -1; + return this; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean isValid() { + return data != null && byteIndex >= 0; + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public long getByteIndex() { + return byteIndex; + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void setByteIndex(long byteIndex) { + this.byteIndex = byteIndex; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public NonmovableArray getData() { + return data; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void setData(NonmovableArray data) { + this.data = data; + } + + @Override + public int getS1() { + throw VMError.unimplemented(); + } + + @Override + public int getS2() { + throw VMError.unimplemented(); + } + + @Override + public int getU2() { + throw VMError.unimplemented(); + } + + @Override + public int getS4() { + throw VMError.unimplemented(); + } + + @Override + public long getU4() { + throw VMError.unimplemented(); + } + + @Override + public long getS8() { + throw VMError.unimplemented(); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int getU1() { + int result = NonmovableByteArrayReader.getU1(data, byteIndex); + byteIndex += Byte.BYTES; + return result; + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int getUVInt() { + return asS4(getUV()); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int getSVInt() { + return asS4(getSV()); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public long getSV() { + return decodeSign(read()); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public long getUV() { + return read(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static long decodeSign(long value) { + return (value >>> 1) ^ -(value & 1); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private long read() { + int b0 = getU1(); + if (b0 < UnsafeArrayTypeWriter.NUM_LOW_CODES) { + return b0; + } else { + return readPacked(b0); + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private long readPacked(int b0) { + assert b0 >= UnsafeArrayTypeWriter.NUM_LOW_CODES; + long sum = b0; + long shift = UnsafeArrayTypeWriter.HIGH_WORD_SHIFT; + for (int i = 2;; i++) { + long b = getU1(); + sum += b << shift; + if (b < UnsafeArrayTypeWriter.NUM_LOW_CODES || i == UnsafeArrayTypeWriter.MAX_BYTES) { + return sum; + } + shift += UnsafeArrayTypeWriter.HIGH_WORD_SHIFT; + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static boolean isS4(long value) { + return value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static int asS4(long value) { + assert isS4(value); + return (int) value; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java index 8702d8fcf893..8c6a7fb88464 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java @@ -52,6 +52,11 @@ public static T memcpy(T dest, PointerBase src, Unsigned return libc().memcpy(dest, src, n); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static int memcmp(T s1, T s2, UnsignedWord n) { + return libc().memcmp(s1, s2, n); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static T memmove(T dest, PointerBase src, UnsignedWord n) { return libc().memmove(dest, src, n); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibCSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibCSupport.java index d7c252b53767..e0fbd5e2b139 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibCSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibCSupport.java @@ -42,6 +42,9 @@ public interface LibCSupport { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) T memcpy(T dest, PointerBase src, UnsignedWord n); + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + int memcmp(T s1, T s2, UnsignedWord n); + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) T memmove(T dest, PointerBase src, UnsignedWord n); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/CodeReferenceMapDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/CodeReferenceMapDecoder.java index e8df6667ddf6..a9dc8c213de6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/CodeReferenceMapDecoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/CodeReferenceMapDecoder.java @@ -31,12 +31,12 @@ import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; -import com.oracle.svm.core.FrameAccess; import com.oracle.svm.core.AlwaysInline; -import com.oracle.svm.core.util.DuplicatedInNativeCode; +import com.oracle.svm.core.FrameAccess; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.NonmovableArray; import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.util.DuplicatedInNativeCode; import com.oracle.svm.core.util.NonmovableByteArrayReader; @DuplicatedInNativeCode @@ -69,7 +69,8 @@ public static boolean walkOffsetsFromPointer(PointerBase baseAddress, Nonmovable * The following code is copied from TypeReader.getUV() and .getSV() because we cannot * allocate a TypeReader here which, in addition to returning the read variable-sized * values, can keep track of the index in the byte array. Even with an instance of - * ReusableTypeReader, we would need to worry about this method being reentrant. + * UninterruptibleReusableTypeReader, we would need to worry about this method being + * reentrant. */ // Size of gap in bytes (negative means the next pointer has derived pointers) 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 dd58a36ce7bd..e5d259759c72 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 @@ -367,6 +367,7 @@ private static int makeFlag(int flagBit, boolean value) { return value ? flagMask : 0; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static boolean isFlagSet(byte flags, int flagBit) { int flagMask = 1 << flagBit; return (flags & flagMask) != 0; @@ -516,6 +517,7 @@ public SharedType getMetaType() { return metaType; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public String getSourceFileName() { return sourceFileName; } @@ -762,6 +764,7 @@ void setClassLoaderAtRuntime(ClassLoader loader) { @Substitute @TargetElement(onlyWith = JDK17OrLater.class) + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean isHidden() { return isFlagSet(flags, IS_HIDDEN_FLAG_BIT); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AbstractUninterruptibleHashtable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AbstractUninterruptibleHashtable.java index 1d1f0dc892d1..8057bcb7a43b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AbstractUninterruptibleHashtable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AbstractUninterruptibleHashtable.java @@ -126,7 +126,7 @@ public UninterruptibleEntry getOrPut(UninterruptibleEntry valueOnStack) { UninterruptibleEntry entry = get(valueOnStack); if (entry.isNonNull()) { - return WordFactory.nullPointer(); + return entry; } else { return insertEntry(valueOnStack); } 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 ef634c85a31a..90c07aba65b2 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 @@ -188,6 +188,21 @@ public String intern() { @Alias // byte[] value; + + @Alias // + int hash; +} + +@TargetClass(className = "java.lang.StringLatin1") +final class Target_java_lang_StringLatin1 { + + @AnnotateOriginal + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + static native char getChar(byte[] val, int index); + + @AnnotateOriginal + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + static native int hashCode(byte[] value); } @TargetClass(className = "java.lang.StringUTF16") @@ -196,6 +211,10 @@ final class Target_java_lang_StringUTF16 { @AnnotateOriginal @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) static native char getChar(byte[] val, int index); + + @AnnotateOriginal + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + static native int hashCode(byte[] value); } @TargetClass(java.lang.Throwable.class) @@ -721,7 +740,7 @@ 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 (char) (value[index] & 0xFF); + return Target_java_lang_StringLatin1.getChar(value, index); } else { return Target_java_lang_StringUTF16.getChar(value, index); } @@ -730,6 +749,26 @@ public static char charAt(String string, int index) { public static byte coder(String string) { return SubstrateUtil.cast(string, Target_java_lang_String.class).coder(); } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static int hashCode(java.lang.String string) { + return string != null ? hashCode0(string) : 0; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static int hashCode0(java.lang.String string) { + Target_java_lang_String str = SubstrateUtil.cast(string, Target_java_lang_String.class); + byte[] value = str.value; + if (str.hash == 0 && value.length > 0) { + boolean isLatin1 = str.isLatin1(); + if (isLatin1) { + str.hash = Target_java_lang_StringLatin1.hashCode(value); + } else { + str.hash = Target_java_lang_StringUTF16.hashCode(value); + } + } + return str.hash; + } } public static final class ClassValueSupport { 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 71d777660be0..f9caff2a2821 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,7 +24,6 @@ */ package com.oracle.svm.core.jdk; -import com.oracle.svm.core.jdk.JavaLangSubstitutions.StringUtil; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; @@ -32,6 +31,7 @@ import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jdk.JavaLangSubstitutions.StringUtil; import com.oracle.svm.core.util.VMError; import jdk.internal.misc.Unsafe; @@ -45,6 +45,41 @@ */ public class UninterruptibleUtils { + public static class AtomicBoolean { + + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + private static final long VALUE_OFFSET; + + static { + try { + VALUE_OFFSET = UNSAFE.objectFieldOffset(AtomicBoolean.class.getDeclaredField("value")); + } catch (Throwable ex) { + throw VMError.shouldNotReachHere(ex); + } + } + + private volatile boolean value; + + public AtomicBoolean(boolean value) { + this.value = value; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean get() { + return value; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void set(boolean newValue) { + value = newValue; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean compareAndSet(boolean expected, boolean update) { + return UNSAFE.compareAndSetBoolean(this, VALUE_OFFSET, expected, update); + } + } + public static class AtomicInteger { private static final Unsafe UNSAFE = Unsafe.getUnsafe(); @@ -384,6 +419,11 @@ public static int clamp(int value, int min, int max) { return min(max(value, min), max); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static int abs(int a) { + return (a < 0) ? -a : a; + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static long abs(long a) { return (a < 0) ? -a : a; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java index 4a7876fcd088..3321e245aa99 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java @@ -52,6 +52,12 @@ public static UnsignedWord getHeaderSize() { return UnsignedUtils.roundUp(SizeOf.unsigned(JfrBuffer.class), WordFactory.unsigned(ConfigurationValues.getTarget().wordSize)); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static JfrBuffer allocate(JfrBufferType bufferType) { + JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal(); + return allocate(WordFactory.unsigned(jfrThreadLocal.getThreadLocalBufferSize()), bufferType); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static JfrBuffer allocate(UnsignedWord dataSize, JfrBufferType bufferType) { UnsignedWord headerSize = JfrBufferAccess.getHeaderSize(); 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 79bf96233350..9bf46de5cb3e 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -24,20 +24,143 @@ */ package com.oracle.svm.core.jfr; +import com.oracle.svm.core.Uninterruptible; 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.SubstrateUtil; +import com.oracle.svm.core.code.FrameInfoQueryResult; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; +import com.oracle.svm.core.jfr.utils.JfrVisited; +import com.oracle.svm.core.jfr.utils.JfrVisitedTable; +import com.oracle.svm.core.locks.VMMutex; + +/** + * Repository that collects and writes used methods. + */ public class JfrMethodRepository implements JfrConstantPool { + private final VMMutex mutex; + private final JfrMethodEpochData epochData0; + private final JfrMethodEpochData epochData1; @Platforms(Platform.HOSTED_ONLY.class) public JfrMethodRepository() { + this.epochData0 = new JfrMethodEpochData(); + this.epochData1 = new JfrMethodEpochData(); + this.mutex = new VMMutex("jfrMethodRepository"); } + @Uninterruptible(reason = "Releasing repository buffers.") public void teardown() { + epochData0.teardown(); + epochData1.teardown(); + } + + @Uninterruptible(reason = "Epoch must not change while in this method.") + public long getMethodId(FrameInfoQueryResult stackTraceElement) { + JfrMethodEpochData epochData = getEpochData(false); + mutex.lockNoTransition(); + try { + return getMethodId0(stackTraceElement, epochData); + } finally { + mutex.unlock(); + } + } + + @Uninterruptible(reason = "Epoch must not change while in this method.") + private static long getMethodId0(FrameInfoQueryResult stackTraceElement, JfrMethodEpochData epochData) { + if (epochData.methodBuffer.isNull()) { + // This will happen only on the first call. + epochData.methodBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); + } + + JfrVisited jfrVisited = StackValue.get(JfrVisited.class); + int methodId = stackTraceElement.getMethodID(); + jfrVisited.setId(methodId); + jfrVisited.setHash(stackTraceElement.hashCode()); + if (!epochData.visitedMethods.putIfAbsent(jfrVisited)) { + return methodId; + } + + JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository(); + JfrTypeRepository typeRepo = SubstrateJVM.getTypeRepository(); + + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initialize(data, epochData.methodBuffer); + + /* JFR Method id. */ + JfrNativeEventWriter.putLong(data, methodId); + /* Class. */ + JfrNativeEventWriter.putLong(data, typeRepo.getClassId(stackTraceElement.getSourceClass())); + /* Method name. */ + JfrNativeEventWriter.putLong(data, symbolRepo.getSymbolId(stackTraceElement.getSourceMethodName(), false)); + /* Method description. */ + JfrNativeEventWriter.putLong(data, symbolRepo.getSymbolId("()V", false)); + /* Method modifier. */ + JfrNativeEventWriter.putInt(data, 0); + /* Is hidden class? */ + JfrNativeEventWriter.putBoolean(data, SubstrateUtil.isHiddenClass(DynamicHub.fromClass(stackTraceElement.getSourceClass()))); + JfrNativeEventWriter.commit(data); + + // Maybe during writing, the thread buffer was replaced with a new (larger) one, so we + // need to update the repository pointer as well. + epochData.methodBuffer = data.getJfrBuffer(); + return methodId; } @Override public int write(JfrChunkWriter writer) { - return EMPTY; + JfrMethodEpochData epochData = getEpochData(true); + int count = writeMethods(writer, epochData); + epochData.clear(); + return count; + } + + private static int writeMethods(JfrChunkWriter writer, JfrMethodEpochData epochData) { + int numberOfMethods = epochData.visitedMethods.getSize(); + if (numberOfMethods == 0) { + return EMPTY; + } + + writer.writeCompressedLong(JfrType.Method.getId()); + writer.writeCompressedInt(numberOfMethods); + writer.write(epochData.methodBuffer); + + return NON_EMPTY; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private JfrMethodEpochData getEpochData(boolean previousEpoch) { + boolean epoch = previousEpoch ? JfrTraceIdEpoch.getInstance().previousEpoch() : JfrTraceIdEpoch.getInstance().currentEpoch(); + return epoch ? epochData0 : epochData1; + } + + private static class JfrMethodEpochData { + private JfrBuffer methodBuffer; + private final JfrVisitedTable visitedMethods; + + @Platforms(Platform.HOSTED_ONLY.class) + JfrMethodEpochData() { + this.visitedMethods = new JfrVisitedTable(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + void teardown() { + visitedMethods.teardown(); + if (methodBuffer.isNonNull()) { + JfrBufferAccess.free(methodBuffer); + } + methodBuffer = WordFactory.nullPointer(); + } + + void clear() { + visitedMethods.clear(); + if (methodBuffer.isNonNull()) { + JfrBufferAccess.reinitialize(methodBuffer); + } + } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java index cb47d42e1151..9a400e8a7028 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java @@ -275,7 +275,7 @@ private static void cancel(JfrNativeEventWriterData data) { @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true) private static boolean accommodate(JfrNativeEventWriterData data, UnsignedWord uncommitted, int requested) { - JfrBuffer newBuffer = accomodate0(data, uncommitted, requested); + JfrBuffer newBuffer = accommodate0(data, uncommitted, requested); if (newBuffer.isNull()) { cancel(data); return false; @@ -288,7 +288,7 @@ private static boolean accommodate(JfrNativeEventWriterData data, UnsignedWord u } @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true) - private static JfrBuffer accomodate0(JfrNativeEventWriterData data, UnsignedWord uncommitted, int requested) { + private static JfrBuffer accommodate0(JfrNativeEventWriterData data, UnsignedWord uncommitted, int requested) { JfrBuffer oldBuffer = data.getJfrBuffer(); switch (oldBuffer.getBufferType()) { case THREAD_LOCAL_NATIVE: diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java index 42b7a74d65ea..8197d2730632 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java @@ -31,6 +31,9 @@ */ public final class JfrNativeEventWriterDataAccess { + private JfrNativeEventWriterDataAccess() { + } + /** * Initialize the {@link JfrNativeEventWriterData data} so that it uses the given buffer. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java index 142ce7888ecb..79ef4ddd89de 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java @@ -25,35 +25,52 @@ package com.oracle.svm.core.jfr; import org.graalvm.compiler.core.common.SuppressFBWarnings; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jdk.UninterruptibleUtils; import com.oracle.svm.core.locks.VMCondition; import com.oracle.svm.core.locks.VMMutex; +import com.oracle.svm.core.locks.VMSemaphore; +import com.oracle.svm.core.sampler.SamplerBuffer; +import com.oracle.svm.core.sampler.SamplerBuffersAccess; +import com.oracle.svm.core.sampler.SubstrateSigprofHandler; import com.oracle.svm.core.util.VMError; /** * A daemon thread that is created during JFR startup and torn down by - * {@link SubstrateJVM#destroyJFR}. It is used for persisting the {@link JfrGlobalMemory} buffers to - * a file. + * {@link SubstrateJVM#destroyJFR}. + * + * It is used for persisting the {@link JfrGlobalMemory} buffers to a file and for processing the + * pool of {@link SamplerBuffer}s collected in signal handler (see {@link SubstrateSigprofHandler}). + * With that in mind, the thread is using {@link VMSemaphore} for a synchronization between threads + * as it is async signal safe. */ public class JfrRecorderThread extends Thread { private static final int BUFFER_FULL_ENOUGH_PERCENTAGE = 50; private final JfrGlobalMemory globalMemory; private final JfrUnlockedChunkWriter unlockedChunkWriter; + + private final VMSemaphore semaphore; + private final UninterruptibleUtils.AtomicBoolean atomicNotify; + private final VMMutex mutex; private final VMCondition condition; - private volatile boolean notified; private volatile boolean stopped; + @Platforms(Platform.HOSTED_ONLY.class) public JfrRecorderThread(JfrGlobalMemory globalMemory, JfrUnlockedChunkWriter unlockedChunkWriter) { super("JFR recorder"); this.globalMemory = globalMemory; this.unlockedChunkWriter = unlockedChunkWriter; this.mutex = new VMMutex("jfrRecorder"); this.condition = new VMCondition(mutex); + this.semaphore = new VMSemaphore(); + this.atomicNotify = new UninterruptibleUtils.AtomicBoolean(false); setDaemon(true); } @@ -65,15 +82,8 @@ public void setStopped(boolean value) { public void run() { try { while (!stopped) { - waitForNotification(); - - JfrChunkWriter chunkWriter = unlockedChunkWriter.lock(); - try { - if (chunkWriter.hasOpenFile()) { - persistBuffers(chunkWriter); - } - } finally { - chunkWriter.unlock(); + if (await()) { + run0(); } } } catch (Throwable e) { @@ -81,15 +91,38 @@ public void run() { } } - private void waitForNotification() { - mutex.lock(); + private boolean await() { + if (Platform.includedIn(Platform.DARWIN.class)) { + /* + * DARWIN is not supporting unnamed semaphores, therefore we must use VMLock and + * VMConditional for synchronization. + */ + mutex.lock(); + try { + while (!notified) { + condition.block(); + } + notified = false; + } finally { + mutex.unlock(); + } + return true; + } else { + semaphore.await(); + return atomicNotify.compareAndSet(true, false); + } + } + + private void run0() { + /* Process all unprocessed sampler buffers. */ + SamplerBuffersAccess.processSamplerBuffers(); + JfrChunkWriter chunkWriter = unlockedChunkWriter.lock(); try { - while (!notified) { - condition.block(); + if (chunkWriter.hasOpenFile()) { + persistBuffers(chunkWriter); } - notified = false; } finally { - mutex.unlock(); + chunkWriter.unlock(); } } @@ -129,8 +162,17 @@ private static boolean persistBuffer(JfrChunkWriter chunkWriter, JfrBuffer buffe */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void signal() { - notified = true; - condition.broadcast(); + if (Platform.includedIn(Platform.DARWIN.class)) { + /* + * DARWIN is not supporting unnamed semaphores, therefore we must use VMConditional for + * signaling. + */ + notified = true; + condition.broadcast(); + } else { + atomicNotify.set(true); + semaphore.signal(); + } } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java index f8a225bf2533..6e560f421790 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -26,39 +26,257 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.c.type.CIntPointer; +import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.UnmanagedMemoryUtil; +import com.oracle.svm.core.code.FrameInfoQueryResult; +import com.oracle.svm.core.headers.LibC; +import com.oracle.svm.core.jdk.AbstractUninterruptibleHashtable; +import com.oracle.svm.core.jdk.UninterruptibleEntry; +import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; +import com.oracle.svm.core.jfr.utils.JfrVisited; +import com.oracle.svm.core.locks.VMMutex; +import com.oracle.svm.core.sampler.SamplerBuffer; +import com.oracle.svm.core.sampler.SamplerBufferAccess; /** * Repository that collects all metadata about stacktraces. */ public class JfrStackTraceRepository implements JfrConstantPool { + private int maxDepth = SubstrateOptions.MaxJavaStackTraceDepth.getValue(); - private int depth = SubstrateOptions.MaxJavaStackTraceDepth.getValue(); + private final VMMutex mutex; + private final JfrStackTraceEpochData epochData0; + private final JfrStackTraceEpochData epochData1; @Platforms(Platform.HOSTED_ONLY.class) JfrStackTraceRepository() { + this.epochData0 = new JfrStackTraceEpochData(); + this.epochData1 = new JfrStackTraceEpochData(); + this.mutex = new VMMutex("jfrStackTraceRepository"); } + public void setStackTraceDepth(int depth) { + if (depth < 0 || depth > SubstrateOptions.MaxJavaStackTraceDepth.getValue()) { + throw new IllegalArgumentException("StackTrace depth (" + depth + ") is not in a valid range!"); + } + this.maxDepth = depth; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void acquireLock() { + mutex.lockNoTransition(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void releaseLock() { + mutex.unlock(); + } + + @Uninterruptible(reason = "Releasing repository buffers.") public void teardown() { + epochData0.teardown(); + epochData1.teardown(); } @Uninterruptible(reason = "Epoch must not change while in this method.") - public long getStackTraceId(@SuppressWarnings("unused") int skipCount, @SuppressWarnings("unused") boolean previousEpoch) { - assert depth >= 0; + public long getStackTraceId(@SuppressWarnings("unused") int skipCount) { + assert maxDepth >= 0; return 0; } - public void setStackTraceDepth(int depth) { - if (depth < 0 || depth > SubstrateOptions.MaxJavaStackTraceDepth.getValue()) { - throw new IllegalArgumentException("StackTrace depth (" + depth + ") is not in a valid range!"); + @Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.") + public long getStackTraceId(Pointer start, Pointer end, int hashCode, CIntPointer isRecorded) { + JfrStackTraceEpochData epochData = getEpochData(false); + JfrStackTraceTableEntry entry = StackValue.get(JfrStackTraceTableEntry.class); + + UnsignedWord size = end.subtract(start); + entry.setHash(hashCode); + entry.setSize((int) size.rawValue()); + /* Do not copy stacktrace into new entry unless it is necessary. */ + entry.setStackTrace(start); + + JfrStackTraceTableEntry result = (JfrStackTraceTableEntry) epochData.visitedStackTraces.get(entry); + if (result.isNonNull()) { + isRecorded.write(1); + return result.getId(); + } else { + /* Replace the previous pointer with new one (entry size and hash remains the same). */ + SamplerBuffer buffer = SamplerBufferAccess.allocate(size); + Pointer to = SamplerBufferAccess.getDataStart(buffer); + entry.setStackTrace(to); + /* Copy the stacktrace into separate native memory entry in hashtable. */ + UnmanagedMemoryUtil.copy(start, to, size); + + JfrStackTraceTableEntry newEntry = (JfrStackTraceTableEntry) epochData.visitedStackTraces.getOrPut(entry); + isRecorded.write(0); + return newEntry.getId(); } - this.depth = depth; + } + + @Uninterruptible(reason = "Epoch must not change while in this method.") + public void serializeStackTraceHeader(long stackTraceId, boolean isTruncated, int stackTraceLength) { + JfrStackTraceEpochData epochData = getEpochData(false); + if (epochData.stackTraceBuffer.isNull()) { + // This will happen only on the first call. + epochData.stackTraceBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); + } + + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initialize(data, epochData.stackTraceBuffer); + + /* JFR Stacktrace id. */ + JfrNativeEventWriter.putLong(data, stackTraceId); + /* Is truncated. */ + JfrNativeEventWriter.putBoolean(data, isTruncated); + /* Stacktrace size. */ + JfrNativeEventWriter.putInt(data, stackTraceLength); + + JfrNativeEventWriter.commit(data); + + /* + * Maybe during writing, the thread buffer was replaced with a new (larger) one, so we need + * to update the repository pointer as well. + */ + epochData.stackTraceBuffer = data.getJfrBuffer(); + } + + @Uninterruptible(reason = "Epoch must not change while in this method.") + public void serializeUnknownStackTraceElement() { + serializeStackTraceElement0(0, -1, -1); + } + + @Uninterruptible(reason = "Epoch must not change while in this method.") + public void serializeStackTraceElement(FrameInfoQueryResult stackTraceElement) { + serializeStackTraceElement0(SubstrateJVM.getMethodRepo().getMethodId(stackTraceElement), stackTraceElement.getSourceLineNumber(), stackTraceElement.getBci()); + } + + @Uninterruptible(reason = "Epoch must not change while in this method.") + private void serializeStackTraceElement0(long methodId, int sourceLineNumber, int bci) { + JfrStackTraceEpochData epochData = getEpochData(false); + assert epochData.stackTraceBuffer.isNonNull(); + + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initialize(data, epochData.stackTraceBuffer); + + /* Method id. */ + JfrNativeEventWriter.putLong(data, methodId); + /* Line number. */ + JfrNativeEventWriter.putInt(data, sourceLineNumber); + /* Bytecode index. */ + JfrNativeEventWriter.putInt(data, bci); + /* Frame type id. */ + JfrNativeEventWriter.putLong(data, JfrFrameType.FRAME_AOT_COMPILED.getId()); + + JfrNativeEventWriter.commit(data); + + /* + * Maybe during writing, the thread buffer was replaced with a new (larger) one, so we need + * to update the repository pointer as well. + */ + epochData.stackTraceBuffer = data.getJfrBuffer(); } @Override - public int write(@SuppressWarnings("unused") JfrChunkWriter writer) { - return EMPTY; + public int write(JfrChunkWriter writer) { + JfrStackTraceEpochData epochData = getEpochData(true); + int count = writeStackTraces(writer, epochData); + epochData.clear(); + return count; + } + + private static int writeStackTraces(JfrChunkWriter writer, JfrStackTraceEpochData epochData) { + int stackTraceCount = epochData.visitedStackTraces.getSize(); + if (stackTraceCount == 0) { + return EMPTY; + } + + writer.writeCompressedLong(JfrType.StackTrace.getId()); + writer.writeCompressedInt(stackTraceCount); + writer.write(epochData.stackTraceBuffer); + + return NON_EMPTY; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private JfrStackTraceEpochData getEpochData(boolean previousEpoch) { + boolean epoch = previousEpoch ? JfrTraceIdEpoch.getInstance().previousEpoch() : JfrTraceIdEpoch.getInstance().currentEpoch(); + return epoch ? epochData0 : epochData1; + } + + @RawStructure + public interface JfrStackTraceTableEntry extends JfrVisited { + @RawField + Pointer getStackTrace(); + + @RawField + void setStackTrace(Pointer pointer); + + @RawField + int getSize(); + + @RawField + void setSize(int size); + } + + public static final class JfrStackTraceTable extends AbstractUninterruptibleHashtable { + private long nextId; + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected JfrStackTraceTableEntry[] createTable(int size) { + return new JfrStackTraceTableEntry[size]; + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected boolean isEqual(UninterruptibleEntry a, UninterruptibleEntry b) { + JfrStackTraceTableEntry entry1 = (JfrStackTraceTableEntry) a; + JfrStackTraceTableEntry entry2 = (JfrStackTraceTableEntry) b; + return entry1.getSize() == entry2.getSize() && LibC.memcmp(entry1.getStackTrace(), entry2.getStackTrace(), WordFactory.unsigned(entry1.getSize())) == 0; + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected UninterruptibleEntry copyToHeap(UninterruptibleEntry valueOnStack) { + JfrStackTraceTableEntry result = (JfrStackTraceTableEntry) copyToHeap(valueOnStack, SizeOf.unsigned(JfrStackTraceTableEntry.class)); + result.setId(++nextId); + return result; + } + } + + private static class JfrStackTraceEpochData { + private JfrBuffer stackTraceBuffer; + private final JfrStackTraceTable visitedStackTraces; + + @Platforms(Platform.HOSTED_ONLY.class) + JfrStackTraceEpochData() { + this.visitedStackTraces = new JfrStackTraceTable(); + } + + void clear() { + visitedStackTraces.clear(); + if (stackTraceBuffer.isNonNull()) { + JfrBufferAccess.reinitialize(stackTraceBuffer); + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + void teardown() { + visitedStackTraces.teardown(); + if (stackTraceBuffer.isNonNull()) { + JfrBufferAccess.free(stackTraceBuffer); + } + stackTraceBuffer = WordFactory.nullPointer(); + } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java index 3615b59985ef..5dbcb2c2f8c8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java @@ -106,7 +106,6 @@ public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) { @Uninterruptible(reason = "Accesses a JFR buffer.") @Override public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { - // Emit ThreadEnd event after thread.run() finishes. ThreadEndEvent.emit(isolateThread); @@ -146,6 +145,11 @@ public long getParentThreadId(IsolateThread isolateThread) { return parentThreadId.get(isolateThread); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public long getThreadLocalBufferSize() { + return threadLocalBufferSize; + } + public Target_jdk_jfr_internal_EventWriter getEventWriter() { return javaEventWriter.get(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index b28e56a577d0..3b5010986f3f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -29,15 +29,12 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; -import org.graalvm.nativeimage.c.struct.RawField; -import org.graalvm.nativeimage.c.struct.RawStructure; -import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.jdk.AbstractUninterruptibleHashtable; -import com.oracle.svm.core.jdk.UninterruptibleEntry; import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; +import com.oracle.svm.core.jfr.utils.JfrVisited; +import com.oracle.svm.core.jfr.utils.JfrVisitedTable; import com.oracle.svm.core.locks.VMMutex; import com.oracle.svm.core.thread.JavaLangThreadGroupSubstitutions; import com.oracle.svm.core.thread.JavaThreads; @@ -46,14 +43,10 @@ import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.util.VMError; -import jdk.jfr.internal.Options; - /** * Repository that collects all metadata about threads and thread groups. */ public final class JfrThreadRepository implements JfrConstantPool { - private static final long INITIAL_BUFFER_SIZE = Options.getThreadBufferSize(); - private final VMMutex mutex; private final JfrThreadEpochData epochData0; private final JfrThreadEpochData epochData1; @@ -103,7 +96,7 @@ private void registerThread0(Thread thread) { JfrThreadEpochData epochData = getEpochData(false); if (epochData.threadBuffer.isNull()) { // This will happen only on the first call. - epochData.threadBuffer = JfrBufferAccess.allocate(WordFactory.unsigned(INITIAL_BUFFER_SIZE), JfrBufferType.C_HEAP); + epochData.threadBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); } JfrVisited visitedThread = StackValue.get(JfrVisited.class); @@ -160,7 +153,7 @@ private void registerThreadGroup(long threadGroupId, ThreadGroup threadGroup) { JfrThreadEpochData epochData = getEpochData(false); if (epochData.threadGroupBuffer.isNull()) { // This will happen only on the first call. - epochData.threadGroupBuffer = JfrBufferAccess.allocate(WordFactory.unsigned(INITIAL_BUFFER_SIZE), JfrBufferType.C_HEAP); + epochData.threadGroupBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP); } JfrVisited jfrVisited = StackValue.get(JfrVisited.class); @@ -237,44 +230,6 @@ public void teardown() { epochData1.teardown(); } - @RawStructure - interface JfrVisited extends UninterruptibleEntry { - @RawField - long getId(); - - @RawField - void setId(long value); - } - - private static class JfrVisitedTable extends AbstractUninterruptibleHashtable { - - @Override - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - protected JfrVisited[] createTable(int size) { - return new JfrVisited[size]; - } - - @Override - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public JfrVisited[] getTable() { - return (JfrVisited[]) super.getTable(); - } - - @Override - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - protected boolean isEqual(UninterruptibleEntry v0, UninterruptibleEntry v1) { - JfrVisited a = (JfrVisited) v0; - JfrVisited b = (JfrVisited) v1; - return a.getId() == b.getId(); - } - - @Override - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - protected UninterruptibleEntry copyToHeap(UninterruptibleEntry visitedOnStack) { - return copyToHeap(visitedOnStack, SizeOf.unsigned(JfrVisited.class)); - } - } - private static class JfrThreadEpochData { /* * We need to keep track of the threads because it is not guaranteed that registerThread is 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 429eed66684c..d1ebc70da7fb 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 @@ -166,6 +166,11 @@ public static JfrMethodRepository getMethodRepo() { return get().methodRepo; } + @Fold + public static JfrStackTraceRepository getStackTraceRepo() { + return get().stackTraceRepo; + } + @Fold public static JfrLogging getJfrLogging() { return get().jfrLogging; @@ -254,7 +259,7 @@ public long getStackTraceId(long eventTypeId, int skipCount) { /** See {@link JVM#getStackTraceId}. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public long getStackTraceId(int skipCount) { - return stackTraceRepo.getStackTraceId(skipCount, false); + return stackTraceRepo.getStackTraceId(skipCount); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java index 1267e822d17f..cddcb8748cab 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java @@ -60,28 +60,31 @@ public static void setSamplingInterval(long intervalMillis) { } @Uninterruptible(reason = "Accesses a JFR buffer.") - public static void writeExecutionSample(IsolateThread isolateThread, Thread.State threadState) { + public static void writeExecutionSample(long elapsedTicks, long threadId, long stackTraceId, long threadState) { SubstrateJVM svm = SubstrateJVM.get(); if (SubstrateJVM.isRecording() && svm.isEnabled(JfrEvent.ExecutionSample)) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ExecutionSample); - JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks()); - JfrNativeEventWriter.putThread(data, isolateThread); - JfrNativeEventWriter.putLong(data, svm.getStackTraceId(JfrEvent.ExecutionSample, 0)); - JfrNativeEventWriter.putLong(data, JfrThreadState.getId(threadState)); + JfrNativeEventWriter.putLong(data, elapsedTicks); + JfrNativeEventWriter.putLong(data, threadId); + JfrNativeEventWriter.putLong(data, stackTraceId); + JfrNativeEventWriter.putLong(data, threadState); JfrNativeEventWriter.endSmallEvent(data); } } private static final class ExecutionSampleEventCallback implements Threading.RecurringCallback { - @Override public void run(Threading.RecurringCallbackAccess access) { IsolateThread isolateThread = CurrentIsolate.getCurrentThread(); Thread javaThread = PlatformThreads.fromVMThread(isolateThread); - ExecutionSampleEvent.writeExecutionSample(isolateThread, javaThread.getState()); + ExecutionSampleEvent.writeExecutionSample( + JfrTicks.elapsedTicks(), + SubstrateJVM.getThreadId(isolateThread), + SubstrateJVM.get().getStackTraceId(JfrEvent.ExecutionSample.getId(), 0), + JfrThreadState.getId(javaThread.getState())); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisited.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisited.java new file mode 100644 index 000000000000..12e37cf17caf --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisited.java @@ -0,0 +1,39 @@ +/* + * 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.jfr.utils; + +import com.oracle.svm.core.jdk.UninterruptibleEntry; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; + +@RawStructure +public interface JfrVisited extends UninterruptibleEntry { + @RawField + long getId(); + + @RawField + void setId(long value); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisitedTable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisitedTable.java new file mode 100644 index 000000000000..6e25fbe9206c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrVisitedTable.java @@ -0,0 +1,61 @@ +/* + * 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.jfr.utils; + +import com.oracle.svm.core.Uninterruptible; +import org.graalvm.nativeimage.c.struct.SizeOf; + +import com.oracle.svm.core.jdk.AbstractUninterruptibleHashtable; +import com.oracle.svm.core.jdk.UninterruptibleEntry; + +public final class JfrVisitedTable extends AbstractUninterruptibleHashtable { + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected JfrVisited[] createTable(int size) { + return new JfrVisited[size]; + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public JfrVisited[] getTable() { + return (JfrVisited[]) super.getTable(); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected boolean isEqual(UninterruptibleEntry v0, UninterruptibleEntry v1) { + JfrVisited a = (JfrVisited) v0; + JfrVisited b = (JfrVisited) v1; + return a.getId() == b.getId(); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected UninterruptibleEntry copyToHeap(UninterruptibleEntry visitedOnStack) { + return copyToHeap(visitedOnStack, SizeOf.unsigned(JfrVisited.class)); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/SingleThreadedVMLockSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/SingleThreadedVMLockSupport.java index 8d6cb8b326cd..b809095e182d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/SingleThreadedVMLockSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/SingleThreadedVMLockSupport.java @@ -35,8 +35,8 @@ import com.oracle.svm.core.util.VMError; /** - * Support of {@link VMMutex} and {@link VMCondition} in single-threaded environments. No real - * locking is necessary. + * Support of {@link VMMutex}, {@link VMCondition} and {@link VMSemaphore} in single-threaded + * environments. No real locking is necessary. */ final class SingleThreadedVMLockSupport extends VMLockSupport { @Override @@ -48,6 +48,11 @@ public VMMutex[] getMutexes() { public VMCondition[] getConditions() { return null; } + + @Override + public VMSemaphore[] getSemaphores() { + return null; + } } @AutomaticallyRegisteredFeature @@ -67,6 +72,13 @@ protected VMCondition createReplacement(VMCondition source) { } }; + private final ClassInstanceReplacer semaphoreReplacer = new ClassInstanceReplacer<>(VMSemaphore.class) { + @Override + protected VMSemaphore createReplacement(VMSemaphore source) { + return new SingleThreadedVMSemaphore(); + } + }; + @Override public boolean isInConfiguration(IsInConfigurationAccess access) { return !SubstrateOptions.MultiThreaded.getValue(); @@ -77,6 +89,7 @@ public void duringSetup(DuringSetupAccess access) { ImageSingletons.add(VMLockSupport.class, new SingleThreadedVMLockSupport()); access.registerObjectReplacer(mutexReplacer); access.registerObjectReplacer(conditionReplacer); + access.registerObjectReplacer(semaphoreReplacer); } @Override @@ -84,12 +97,13 @@ public void beforeCompilation(BeforeCompilationAccess access) { /* Seal the lists. */ mutexReplacer.getReplacements(); conditionReplacer.getReplacements(); + semaphoreReplacer.getReplacements(); } } final class SingleThreadedVMMutex extends VMMutex { @Platforms(Platform.HOSTED_ONLY.class) - protected SingleThreadedVMMutex(String name) { + SingleThreadedVMMutex(String name) { super(name); } @@ -177,3 +191,30 @@ public void broadcast() { /* Nothing to do. */ } } + +final class SingleThreadedVMSemaphore extends VMSemaphore { + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected int init() { + /* Nothing to do here. */ + return 0; + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected void destroy() { + /* Nothing to do here. */ + } + + @Override + public void await() { + VMError.shouldNotReachHere("Cannot wait in a single-threaded environment, because there is no other thread that could signal."); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void signal() { + /* Nothing to do here. */ + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMLockSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMLockSupport.java index 0c8e4fb27b37..9a640c058e81 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMLockSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMLockSupport.java @@ -45,6 +45,12 @@ public abstract class VMLockSupport { */ public abstract VMCondition[] getConditions(); + /** + * Returns an array that contains all {@link VMSemaphore} objects that are present in the image + * or null if that information is not available. + */ + public abstract VMSemaphore[] getSemaphores(); + public static class DumpVMMutexes extends DiagnosticThunk { @Override public int maxInvocationCount() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMSemaphore.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMSemaphore.java new file mode 100644 index 000000000000..e3fb3cf895eb --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/locks/VMSemaphore.java @@ -0,0 +1,112 @@ +/* + * 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.locks; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.util.VMError; + +/** + *

+ * A semaphore that has minimal requirements on Java code. The implementation does not perform + * memory allocation, exception unwinding, or other complicated operations. This allows it to be + * used in early startup and shutdown phases of the VM, as well as to coordinate garbage collection. + *

+ * + *

+ * Higher-level code that does not have these restrictions should use regular semaphores from the + * JDK instead, i.e., implementations of {@link java.util.concurrent.Semaphore}. + *

+ * + *

+ * It is not possible to allocate new VM semaphores at run time. All VM semaphores must be allocated + * during image generation. + *

+ * + *

+ * This class is almost an abstract base class for VMSemaphore. Subclasses replace instances of + * VMSemaphore with platform-specific implementations. + *

+ */ +public class VMSemaphore { + + /** + * The function that initializes the semaphore. + * + * @return The error code. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected int init() { + throw VMError.shouldNotReachHere("Semaphore cannot be used during native image generation."); + } + + /** + * The function that destroys the semaphore. + * + *

+ * Only a semaphore that has been initialized by {@link #init()} should be destroyed using this + * function. + *

+ * + *

+ * Destroying a semaphore that other threads are currently blocked on (in {@link #await()}) + * produces undefined behavior. + *

+ */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected void destroy() { + throw VMError.shouldNotReachHere("Semaphore cannot be used during native image generation."); + } + + /** + * The function that decrements the semaphore with thread status transitions. If the semaphore's + * value is greater than zero, then the decrement proceeds, and the function returns, + * immediately. If the semaphore currently has the value zero, then the call blocks until it + * becomes possible to perform the decrement (i.e., the semaphore value rises above zero). + */ + public void await() { + throw VMError.shouldNotReachHere("Semaphore cannot be used during native image generation."); + } + + /** + * The function that increments the semaphore. + * + *

+ * If the semaphore value resulting from this operation is positive, then no threads were + * blocked waiting for the semaphore to become available; the semaphore value is simply + * incremented. + *

+ * + *

+ * If the value of the semaphore resulting from this operation is zero, then one of the threads + * blocked waiting for the semaphore shall be allowed to return successfully from its call to + * {@link #await()}. + *

+ */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void signal() { + throw VMError.shouldNotReachHere("Semaphore cannot be used during native image generation."); + } +} 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 index 5f1f57f15cbc..df1d7a150a7a 100644 --- 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 @@ -36,7 +36,7 @@ * written. */ @RawStructure -interface SamplerBuffer extends PointerBase { +public interface SamplerBuffer extends PointerBase { /** * Returns the buffer that is next in the {@link SamplerBufferStack}, otherwise null. 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 index 6f504541e57e..cce40c004189 100644 --- 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 @@ -38,9 +38,9 @@ import com.oracle.svm.core.util.UnsignedUtils; /** - * Used to access the raw memory of a {@link SamplerBufferAccess}. + * Used to access the raw memory of a {@link SamplerBuffer}. */ -final class SamplerBufferAccess { +public final class SamplerBufferAccess { private SamplerBufferAccess() { } 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 index a7ff8928b420..45df346c78c9 100644 --- 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 @@ -28,21 +28,18 @@ import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jfr.JfrThreadLocal; +import com.oracle.svm.core.jfr.SubstrateJVM; 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) @@ -102,7 +99,8 @@ private static void releaseThreadLocalBuffer(SamplerBuffer buffer) { @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)); + JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal(); + SamplerBuffer buffer = SamplerBufferAccess.allocate(WordFactory.unsigned(jfrThreadLocal.getThreadLocalBufferSize())); if (buffer.isNonNull()) { SubstrateSigprofHandler.singleton().availableBuffers().pushBuffer(buffer); bufferCount++; 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 index f3548df312c6..5889fe59eb95 100644 --- 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 @@ -39,7 +39,7 @@ * * @see SamplerSpinLock */ -class SamplerBufferStack { +public class SamplerBufferStack { private SamplerBuffer head; private final SamplerSpinLock spinLock; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java new file mode 100644 index 000000000000..aa50d1c8b047 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java @@ -0,0 +1,161 @@ +/* + * 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.StackValue; +import org.graalvm.nativeimage.c.function.CodePointer; +import org.graalvm.nativeimage.c.type.CIntPointer; +import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; + +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; +import com.oracle.svm.core.code.CodeInfoTable; +import com.oracle.svm.core.code.FrameInfoQueryResult; +import com.oracle.svm.core.code.UntetheredCodeInfo; +import com.oracle.svm.core.jfr.JfrStackTraceRepository; +import com.oracle.svm.core.jfr.SubstrateJVM; +import com.oracle.svm.core.jfr.events.ExecutionSampleEvent; +import com.oracle.svm.core.util.VMError; + +/** + * Used to access the pool of {@link SamplerBuffer}s. + */ +public final class SamplerBuffersAccess { + private static final CodeInfoQueryResult CODE_INFO_QUERY_RESULT = new CodeInfoQueryResult(); + + private SamplerBuffersAccess() { + } + + @Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.") + public static void processSamplerBuffers() { + while (true) { + /* Pop top buffer from stack of full buffers. */ + SamplerBuffer buffer = SubstrateSigprofHandler.singleton().fullBuffers().popBuffer(); + if (buffer.isNull()) { + /* No buffers to process. */ + return; + } + + /* Process the buffer. */ + processSamplerBuffer(buffer); + if (buffer.getFreeable()) { + SamplerBufferAccess.free(buffer); + } else { + SubstrateSigprofHandler.singleton().availableBuffers().pushBuffer(buffer); + } + } + } + + @Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.", callerMustBe = true) + private static void processSamplerBuffer(SamplerBuffer buffer) { + Pointer end = buffer.getPos(); + Pointer current = SamplerBufferAccess.getDataStart(buffer); + while (current.belowThan(end)) { + /* Sample hash. */ + int sampleHash = current.readInt(0); + current = current.add(Integer.BYTES); + + /* Is truncated. */ + boolean isTruncated = current.readInt(0) == 1; + current = current.add(Integer.BYTES); + + /* Sample size. */ + int sampleSize = current.readInt(0); + current = current.add(Integer.BYTES); + + /* Tick. */ + long sampleTick = current.readLong(0); + current = current.add(Long.BYTES); + + /* Thread state. */ + long threadState = current.readLong(0); + current = current.add(Long.BYTES); + + CIntPointer isRecorded = StackValue.get(CIntPointer.class); + JfrStackTraceRepository stackTraceRepo = SubstrateJVM.getStackTraceRepo(); + stackTraceRepo.acquireLock(); + try { + long stackTraceId = stackTraceRepo.getStackTraceId(current, current.add(sampleSize), sampleHash, isRecorded); + if (isRecorded.read() == 1) { + ExecutionSampleEvent.writeExecutionSample(sampleTick, buffer.getOwner(), stackTraceId, threadState); + /* Sample is already there, skip the rest of sample plus END_MARK symbol. */ + current = current.add(sampleSize).add(SamplerSampleWriter.END_MARKER_SIZE); + } else { + /* Sample is not there. Start walking a stacktrace. */ + stackTraceRepo.serializeStackTraceHeader(stackTraceId, isTruncated, sampleSize / Long.BYTES); + while (current.belowThan(end)) { + long ip = current.readLong(0); + if (ip == SamplerSampleWriter.END_MARKER) { + ExecutionSampleEvent.writeExecutionSample(sampleTick, buffer.getOwner(), stackTraceId, threadState); + current = current.add(SamplerSampleWriter.END_MARKER_SIZE); + break; + } else { + visitFrame(ip); + current = current.add(Long.BYTES); + } + } + } + } finally { + stackTraceRepo.releaseLock(); + } + } + } + + @Uninterruptible(reason = "The handle should only be accessed from uninterruptible code to prevent that the GC frees the CodeInfo.", callerMustBe = true) + private static void visitFrame(long address) { + CodePointer ip = WordFactory.pointer(address); + UntetheredCodeInfo untetheredInfo = CodeInfoTable.lookupCodeInfo(ip); + if (untetheredInfo.isNull()) { + /* Unknown frame. May happen for various reasons. */ + VMError.shouldNotReachHere("Stack walk must walk only frames of known code."); + } + + Object tether = CodeInfoAccess.acquireTether(untetheredInfo); + CodeInfo tetheredCodeInfo = CodeInfoAccess.convert(untetheredInfo, tether); + try { + /* Now the value of code info can be passed to interruptible code safely. */ + visitFrame0(tetheredCodeInfo, ip); + } finally { + CodeInfoAccess.releaseTether(untetheredInfo, tether); + } + } + + @Uninterruptible(reason = "The handle should only be accessed from uninterruptible code to prevent that the GC frees the CodeInfo.", callerMustBe = true) + private static void visitFrame0(CodeInfo codeInfo, CodePointer ip) { + CodeInfoQueryResult queryResult = CodeInfoTable.lookupCodeInfoQueryResultUninterruptible(codeInfo, ip, CODE_INFO_QUERY_RESULT); + VMError.guarantee(queryResult != null); + FrameInfoQueryResult frameInfoQueryResult = queryResult.getFrameInfo(); + if (frameInfoQueryResult != null) { + SubstrateJVM.getStackTraceRepo().serializeStackTraceElement(frameInfoQueryResult); + } else { + /* We don't have information about native code. */ + SubstrateJVM.getStackTraceRepo().serializeUnknownStackTraceElement(); + } + } +} 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 index d09a72a7ea24..5982b7a159e9 100644 --- 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 @@ -25,26 +25,73 @@ package com.oracle.svm.core.sampler; +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; -import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.UnmanagedMemoryUtil; +import com.oracle.svm.core.jfr.JfrThreadState; +import com.oracle.svm.core.jfr.JfrTicks; +import com.oracle.svm.core.jfr.SubstrateJVM; -final class SamplerSampleWriter { +public final class SamplerSampleWriter { - private static final int END_MARKER_SIZE = Long.BYTES; - private static final long END_MARKER = -1; + public static final long END_MARKER = -1; + public static final int END_MARKER_SIZE = Long.BYTES; private SamplerSampleWriter() { } + @Fold + public static int getHeaderSize() { + /* sample hash + is truncated + sample size + tick + thread state. */ + return Integer.BYTES + Integer.BYTES + Integer.BYTES + Long.BYTES + Long.BYTES; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void begin(SamplerSampleWriterData data) { + assert isValid(data); + assert getUncommittedSize(data).equal(0); + + /* Sample hash. (will be patched later) */ + SamplerSampleWriter.putInt(data, 0); + /* Is truncated? (will be patched later) */ + SamplerSampleWriter.putInt(data, 0); + /* Sample size. (will be patched later) */ + SamplerSampleWriter.putInt(data, 0); + /* Tick. */ + SamplerSampleWriter.putLong(data, JfrTicks.elapsedTicks()); + /* Thread state. */ + SamplerSampleWriter.putLong(data, JfrThreadState.getId(Thread.State.RUNNABLE)); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void end(SamplerSampleWriterData data) { + assert isValid(data); + + UnsignedWord sampleSize = getSampleSize(data); + /* Put end marker. */ + putUncheckedLong(data, END_MARKER); + + Pointer currentPos = data.getCurrentPos(); + data.setCurrentPos(data.getStartPos()); + /* Patch sample hash. */ + putUncheckedInt(data, data.getHashCode()); + /* Patch is truncated. */ + putUncheckedInt(data, data.getTruncated() ? 1 : 0); + /* Patch sample size. */ + putUncheckedInt(data, (int) sampleSize.rawValue()); + data.setCurrentPos(currentPos); + + commit(data); + } + @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)); + putUncheckedLong(data, value); return true; } else { return false; @@ -52,14 +99,37 @@ public static boolean putLong(SamplerSampleWriterData data, long value) { } @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); + private static void putUncheckedLong(SamplerSampleWriterData data, long value) { + /* This method is only called if ensureSize() succeeded earlier. */ + assert getAvailableSize(data).aboveOrEqual(Long.BYTES); + data.getCurrentPos().writeLong(0, value); increaseCurrentPos(data, WordFactory.unsigned(Long.BYTES)); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean putInt(SamplerSampleWriterData data, int value) { + if (ensureSize(data, Integer.BYTES)) { + putUncheckedInt(data, value); + return true; + } else { + return false; + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static void putUncheckedInt(SamplerSampleWriterData data, int value) { + /* This method is only called if ensureSize() succeeded earlier. */ + assert getAvailableSize(data).aboveOrEqual(Integer.BYTES); + data.getCurrentPos().writeInt(0, value); + increaseCurrentPos(data, WordFactory.unsigned(Integer.BYTES)); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static void commit(SamplerSampleWriterData data) { + SamplerBuffer buffer = data.getSamplerBuffer(); + assert isValid(data); + assert buffer.getPos().equal(data.getStartPos()); + assert SamplerBufferAccess.getDataEnd(data.getSamplerBuffer()).equal(data.getEndPos()); buffer.setPos(data.getCurrentPos()); } @@ -67,9 +137,14 @@ public static void commit(SamplerSampleWriterData data) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static boolean ensureSize(SamplerSampleWriterData data, int requested) { assert requested > 0; + if (!isValid(data)) { + return false; + } + int totalRequested = requested + END_MARKER_SIZE; if (getAvailableSize(data).belowThan(totalRequested)) { if (!accommodate(data, getUncommittedSize(data))) { + assert !isValid(data); return false; } } @@ -100,9 +175,10 @@ private static boolean accommodate(SamplerSampleWriterData data, UnsignedWord un /* 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. */ + /* Put in the stack with other unprocessed buffers and send a signal to the JFR recorder. */ SamplerBuffer oldBuffer = data.getSamplerBuffer(); SubstrateSigprofHandler.singleton().fullBuffers().pushBuffer(oldBuffer); + SubstrateJVM.getRecorderThread().signal(); /* Reinitialize data structure. */ data.setSamplerBuffer(newBuffer); @@ -111,6 +187,19 @@ private static boolean accommodate(SamplerSampleWriterData data, UnsignedWord un return true; } + @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)); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static boolean isValid(SamplerSampleWriterData data) { + return data.getEndPos().isNonNull(); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static UnsignedWord getAvailableSize(SamplerSampleWriterData data) { return data.getEndPos().subtract(data.getCurrentPos()); @@ -122,15 +211,12 @@ private static UnsignedWord getUncommittedSize(SamplerSampleWriterData data) { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static void increaseCurrentPos(SamplerSampleWriterData data, UnsignedWord delta) { - data.setCurrentPos(data.getCurrentPos().add(delta)); + private static UnsignedWord getSampleSize(SamplerSampleWriterData data) { + return getUncommittedSize(data).subtract(getHeaderSize()); } @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)); + private static void increaseCurrentPos(SamplerSampleWriterData data, UnsignedWord delta) { + data.setCurrentPos(data.getCurrentPos().add(delta)); } } 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 index 49cdaaf18635..625bd9ef4ddc 100644 --- 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 @@ -35,7 +35,7 @@ * allocated on the stack. */ @RawStructure -interface SamplerSampleWriterData extends PointerBase { +public interface SamplerSampleWriterData extends PointerBase { /** * Gets the buffer that data will be written to. */ @@ -84,4 +84,64 @@ interface SamplerSampleWriterData extends PointerBase { */ @RawField void setEndPos(Pointer value); + + /** + * Returns the hash code of sample. + */ + @RawField + int getHashCode(); + + /** + * Sets the hash code of sample. + */ + @RawField + void setHashCode(int value); + + /** + * Returns the number of frames that should be skipped during stack walk. + */ + @RawField + int getSkipCount(); + + /** + * Sets the number of frames that should be skipped during stack walk. + */ + @RawField + void setSkipCount(int value); + + /** + * Returns the max depth of stack walk. + */ + @RawField + int getMaxDepth(); + + /** + * Sets the max depth of stack walk. + */ + @RawField + void setMaxDepth(int value); + + /** + * Returns the number of frames. + */ + @RawField + int getNumFrames(); + + /** + * Sets the number of frames. + */ + @RawField + void setNumFrames(int value); + + /** + * Returns {@code true} if the stack size exceeds {@link #getMaxDepth()}. + */ + @RawField + boolean getTruncated(); + + /** + * Sets the truncation status of stack walking. + */ + @RawField + void setTruncated(boolean value); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java new file mode 100644 index 000000000000..495773aa96ac --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java @@ -0,0 +1,81 @@ +/* + * 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 com.oracle.svm.core.Uninterruptible; + +/** + * Helper class that holds methods related to {@link SamplerSampleWriterData}. + */ +public final class SamplerSampleWriterDataAccess { + + private SamplerSampleWriterDataAccess() { + } + + /** + * Initialize the {@link SamplerSampleWriterData data} so that it uses the current thread's + * native buffer. + */ + @Uninterruptible(reason = "Accesses a sampler buffer", callerMustBe = true) + public static boolean initialize(SamplerSampleWriterData data, int skipCount, int maxDepth) { + SamplerBuffer buffer = SamplerThreadLocal.getThreadLocalBuffer(); + if (buffer.isNull()) { + /* Pop first free buffer from the pool. */ + buffer = SubstrateSigprofHandler.singleton().availableBuffers().popBuffer(); + if (buffer.isNull()) { + /* No available buffers on the pool. Fallback! */ + SamplerThreadLocal.increaseMissedSamples(); + return false; + } + SamplerThreadLocal.setThreadLocalBuffer(buffer); + } + initialize(data, buffer, skipCount, maxDepth); + return true; + } + + /** + * Initialize the {@link SamplerSampleWriterData data} so that it uses the given buffer. + */ + @Uninterruptible(reason = "Accesses a sampler buffer", callerMustBe = true) + public static void initialize(SamplerSampleWriterData data, SamplerBuffer buffer, int skipCount, int maxDepth) { + assert buffer.isNonNull(); + + /* Initialize the writer data. */ + data.setSamplerBuffer(buffer); + data.setStartPos(buffer.getPos()); + data.setCurrentPos(buffer.getPos()); + data.setEndPos(SamplerBufferAccess.getDataEnd(buffer)); + + data.setHashCode(1); + data.setMaxDepth(maxDepth); + data.setTruncated(false); + data.setSkipCount(skipCount); + data.setNumFrames(0); + + /* Set the writer data as thread local. */ + SamplerThreadLocal.setWriterData(data); + } +} 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 index d6339a9ef24a..9d94b4252868 100644 --- 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 @@ -37,7 +37,37 @@ 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()); + SamplerSampleWriterData writerData = SamplerThreadLocal.getWriterData(); + boolean shouldSkipFrame = shouldSkipFrame(writerData); + boolean shouldContinueWalk = shouldContinueWalk(writerData); + if (!shouldSkipFrame && shouldContinueWalk) { + writerData.setHashCode(computeHash(writerData.getHashCode(), ip.rawValue())); + shouldContinueWalk = SamplerSampleWriter.putLong(writerData, ip.rawValue()); + } + return shouldContinueWalk; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static boolean shouldContinueWalk(SamplerSampleWriterData data) { + if (data.getNumFrames() >= data.getMaxDepth()) { + /* The stack size exceeds given depth. Stop walk! */ + data.setTruncated(true); + return false; + } else { + return true; + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static boolean shouldSkipFrame(SamplerSampleWriterData data) { + data.setNumFrames(data.getNumFrames() + 1); + return data.getNumFrames() <= data.getSkipCount(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static int computeHash(int oldHash, long ip) { + int hash = (int) (ip ^ (ip >>> 32)); + return 31 * oldHash + hash; } @Override 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 687a72c0527d..57646ca21ef1 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 @@ -28,8 +28,11 @@ import java.util.Arrays; import java.util.List; +import org.graalvm.collections.EconomicMap; import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.options.Option; +import org.graalvm.compiler.options.OptionKey; +import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.ImageInfo; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Isolate; @@ -159,7 +162,26 @@ 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); + static final RuntimeOptionKey SamplingBasedProfiling = new RuntimeOptionKey<>(Boolean.FALSE) { + @Override + protected void onValueUpdate(EconomicMap, Object> values, Boolean oldValue, Boolean newValue) { + if (newValue) { + /* Enabling sampling-based profiling requires to enabled JFR as well. */ + SubstrateOptions.FlightRecorder.update(values, true); + } + } + }; + + @Option(help = "Start sampling-based profiling with options.")// + public static final RuntimeOptionKey StartSamplingBasedProfiling = new RuntimeOptionKey<>("") { + @Override + protected void onValueUpdate(EconomicMap, Object> values, String oldValue, String newValue) { + if (!newValue.isEmpty()) { + /* Starting sampling-based profiling requires to start JFR as well. */ + SubstrateOptions.StartFlightRecording.update(values, newValue); + } + } + }; } private boolean enabled; @@ -174,32 +196,40 @@ protected SubstrateSigprofHandler() { } @Fold - static SubstrateSigprofHandler singleton() { + public static SubstrateSigprofHandler singleton() { return ImageSingletons.lookup(SubstrateSigprofHandler.class); } @Fold - static SamplerStackWalkVisitor visitor() { + public static SamplerStackWalkVisitor visitor() { return ImageSingletons.lookup(SamplerStackWalkVisitor.class); } @Fold static boolean isProfilingSupported() { + return isOSSupported() && isJDKSupported(); + } + + private static boolean isOSSupported() { return Platform.includedIn(Platform.LINUX.class); } + private static boolean isJDKSupported() { + return JavaVersionUtil.JAVA_SPEC < 19; + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) boolean isProfilingEnabled() { return enabled; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - SamplerBufferStack availableBuffers() { + public SamplerBufferStack availableBuffers() { return availableBuffers; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - SamplerBufferStack fullBuffers() { + public SamplerBufferStack fullBuffers() { return fullBuffers; } @@ -274,46 +304,30 @@ protected static void doUninterruptibleStackWalk(RegisterDumper.Context uContext } } - /* 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()) { + if (SubstrateSigprofHandler.singleton().availableBuffers().isLockedByCurrentThread() || SubstrateSigprofHandler.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; + return; } - 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; + /* Initialize stack walk. */ + SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class); + /* Buffer size constrains stack walk size. */ + if (SamplerSampleWriterDataAccess.initialize(data, 0, Integer.MAX_VALUE)) { + SamplerSampleWriter.begin(data); + /* + * Walk the stack. + * + * We should commit the sample if: the stack walk was done successfully or the stack + * walk was interrupted because stack size exceeded given depth. + */ + if (JavaStackWalker.walkCurrentThread(sp, ip, visitor()) || data.getTruncated()) { + SamplerSampleWriter.end(data); } - 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. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java index 90b2c684915e..20540d11605b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java @@ -29,7 +29,6 @@ import org.graalvm.word.Pointer; import com.oracle.svm.core.NeverInline; -import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.code.CodeInfo; import com.oracle.svm.core.code.CodeInfoAccess; @@ -39,16 +38,17 @@ import com.oracle.svm.core.code.CodeInfoTable; import com.oracle.svm.core.code.FrameInfoQueryResult; import com.oracle.svm.core.code.ImageCodeInfo; -import com.oracle.svm.core.code.ReusableTypeReader; +import com.oracle.svm.core.code.UninterruptibleReusableTypeReader; import com.oracle.svm.core.deopt.DeoptimizationSupport; import com.oracle.svm.core.deopt.DeoptimizedFrame; +import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.log.Log; public class ThreadStackPrinter { private static final int MAX_STACK_FRAMES_PER_THREAD_TO_PRINT = 100_000; public static class StackFramePrintVisitor extends Stage1StackFramePrintVisitor { - private static final ReusableTypeReader frameInfoReader = new ReusableTypeReader(); + private static final UninterruptibleReusableTypeReader frameInfoReader = new UninterruptibleReusableTypeReader(); private static final SingleShotFrameInfoQueryResultAllocator singleShotFrameInfoQueryResultAllocator = new SingleShotFrameInfoQueryResultAllocator(); private static final DummyValueInfoAllocator dummyValueInfoAllocator = new DummyValueInfoAllocator(); private static final FrameInfoState frameInfoState = new FrameInfoState(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FrameInfoHostedMethodData.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FrameInfoHostedMethodData.java new file mode 100644 index 000000000000..e1b6d9582ff5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FrameInfoHostedMethodData.java @@ -0,0 +1,44 @@ +/* + * 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.hosted.code; + +import com.oracle.svm.core.code.FrameInfoMethodData; +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.hosted.meta.HostedMethod; + +import jdk.vm.ci.meta.ResolvedJavaMethod; + +/** + * The concrete implementation of {@link FrameInfoMethodData}. + */ +@AutomaticallyRegisteredImageSingleton(FrameInfoMethodData.class) +final class FrameInfoHostedMethodData implements FrameInfoMethodData { + @Override + public int getMethodId(ResolvedJavaMethod method) { + assert method instanceof HostedMethod; + return ((HostedMethod) method).wrapped.getId(); + } +}