All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jdk.internal.thread.ThreadNative Maven / Gradle / Ivy

package jdk.internal.thread;

import static jdk.internal.sys.linux.Futex.*;
import static jdk.internal.sys.posix.Errno.*;
import static jdk.internal.sys.posix.PThread.*;
import static jdk.internal.sys.posix.Time.*;
import static org.qbicc.runtime.CNative.*;
import static org.qbicc.runtime.stdc.Errno.*;
import static org.qbicc.runtime.stdc.Stdint.*;
import static org.qbicc.runtime.stdc.Stdlib.*;
import static org.qbicc.runtime.stdc.Time.*;
import static org.qbicc.runtime.unwind.LibUnwind.*;
import static org.qbicc.runtime.unwind.Unwind.*;

import java.util.concurrent.locks.LockSupport;

import org.qbicc.runtime.AutoQueued;
import org.qbicc.runtime.Build;
import org.qbicc.runtime.Hidden;
import org.qbicc.runtime.Inline;
import org.qbicc.runtime.InlineCondition;
import org.qbicc.runtime.NoSafePoint;
import org.qbicc.runtime.NoSideEffects;
import org.qbicc.runtime.NoThrow;
import org.qbicc.runtime.ThreadScoped;

/**
 * Native aspects of threads which are shared in the JDK.
 */
public final class ThreadNative {
    /**
     * Get the system thread group object.
     *
     * @return the system thread group (not {@code null})
     */
    public static native ThreadGroup getSystemThreadGroup();

    /**
     * Mutex that protects the running thread doubly-linked list.
     */
    @SuppressWarnings("unused")
    public static final pthread_mutex_t thread_list_mutex = zero();
    /**
     * The special thread list terminus node.
     */
    public static final thread_native thread_list_terminus = zero();
    // JVMTI flag definitions; this is our thread state. Here are the constraints:
    // - no more than one of STATE_WAITING, STATE_BLOCKED_ON_MONITOR_ENTER, or STATE_RUNNABLE may be set at once
    // - no more than one of STATE_WAITING_INDEFINITELY or STATE_WAITING_WITH_TIMEOUT may be set at once
    // - no more than one of STATE_IN_OBJECT_WAIT, STATE_PARKED, or STATE_SLEEPING may be set at once
    // - if any of STATE_INTERRUPTED or STATE_IN_NATIVE is set, then STATE_ALIVE is set
    // - no more than one of STATE_ALIVE or STATE_TERMINATED may be set at once

    // ==================================
    //  State Flags
    //    These flag bits align with the
    //    corresponding JVMTI bit numbers.
    // ==================================

    // ----------------------------------
    //  Liveness status flags
    //    0 or 1 of these may be set
    // ----------------------------------

    /**
     * Thread is alive (has been started).
     * Mutually exclusive with {@link #STATE_TERMINATED}.
     */
    public static final int STATE_ALIVE = 1 << 0;
    /**
     * Thread is terminated (has exited after being started).
     * Mutually exclusive with {@link #STATE_ALIVE}.
     */
    public static final int STATE_TERMINATED = 1 << 1;

    // ----------------------------------
    //  Terminated state flags
    //    0 or 1 of these may be set
    //    (STATE_TERMINATED is set)
    // ----------------------------------

    /**
     * Thread has exited.
     * May only be set while {@link #STATE_TERMINATED} is set.
     */
    public static final int STATE_EXITED = 1 << 27;

    // ----------------------------------
    //  Alive state flags
    //    Exactly 1 must be set
    //    (STATE_ALIVE is set)
    // ----------------------------------

    /**
     * Thread is runnable.
     * May only be set while {@link #STATE_ALIVE} is set.
     * Mutually exclusive with {@link #STATE_WAITING} and {@link #STATE_BLOCKED_ON_MONITOR_ENTER}.
     */
    public static final int STATE_RUNNABLE = 1 << 2;
    /**
     * The thread is waiting for something (is not currently runnable).
     * May only be set while {@link #STATE_ALIVE} is set.
     * Mutually exclusive with {@link #STATE_RUNNABLE} and {@link #STATE_BLOCKED_ON_MONITOR_ENTER}.
     */
    public static final int STATE_WAITING = 1 << 7;

    // ----------------------------------
    //  Waiting reason flags
    //    0 or 1 may be set while WAITING
    // ----------------------------------

    /**
     * The thread is blocked waiting to acquire or reacquire an object monitor.
     * May only be set while {@link #STATE_ALIVE} is set.
     * Mutually exclusive with {@link #STATE_RUNNABLE} and {@link #STATE_WAITING}.
     */
    public static final int STATE_BLOCKED_ON_MONITOR_ENTER = 1 << 10;
    /**
     * The thread is waiting without a timeout.
     * May only be set while {@link #STATE_WAITING} is set.
     * Mutually exclusive with {@link #STATE_WAITING_WITH_TIMEOUT}.
     */
    public static final int STATE_WAITING_INDEFINITELY = 1 << 4;
    /**
     * The thread is waiting with a timeout.
     * May only be set while {@link #STATE_WAITING} is set.
     * Mutually exclusive with {@link #STATE_WAITING_INDEFINITELY}.
     */
    public static final int STATE_WAITING_WITH_TIMEOUT = 1 << 5;
    /**
     * The thread is asleep (i.e. in {@link Thread#sleep(long)} or {@link Thread#sleep(long, int)}).
     * May only be set while {@link #STATE_WAITING} is set.
     * Mutually exclusive with {@link #STATE_IN_OBJECT_WAIT} and {@link #STATE_PARKED}.
     */
    public static final int STATE_SLEEPING = 1 << 6;
    /**
     * The thread is waiting on an object's monitor.
     * May only be set while {@link #STATE_WAITING} is set.
     * Mutually exclusive with {@link #STATE_SLEEPING} and {@link #STATE_PARKED}.
     */
    public static final int STATE_IN_OBJECT_WAIT = 1 << 8;
    /**
     * The thread is waiting in one of the {@link LockSupport} park methods.
     * May only be set while {@link #STATE_WAITING} is set.
     * Mutually exclusive with {@link #STATE_SLEEPING} and {@link #STATE_IN_OBJECT_WAIT}.
     */
    public static final int STATE_PARKED = 1 << 9;
    // STATE_SUSPENDED = 1 << 20;

    // ----------------------------------
    //  Run-time notification flags
    //    0 or more may be set while ALIVE
    //    Affects running thread state
    // ----------------------------------

    /**
     * The unpark permit.
     * If set, then a {@code LockSupport.park()} will consume the permit rather than park.
     * Other forms of {@code park} and other blocking operations are unaffected.
     * May only be set while {@link #STATE_ALIVE} is set.
     * Normally clear.
     * This is an inbound-notification flag.
     * This bit is unused by JVMTI.
     */
    public static final int STATE_UNPARK = 1 << 11; // unused by JVMTI
    /**
     * The interrupt flag.
     * If set, then interruptible blocking operations will return immediately or throw an interruption exception.
     * Other blocking operations are unaffected.
     * May only be set while {@link #STATE_ALIVE} is set.
     * Normally clear.
     * This is an inbound-notification flag.
     */
    public static final int STATE_INTERRUPTED = 1 << 21;

    // ----------------------------------
    //  Thread safepoint state flags
    // ----------------------------------

    /**
     * The in-native flag.
     * If set, then a native method is running.
     * Native methods may also run without setting this flag.
     * May only be set while {@link #STATE_IN_SAFEPOINT} is set.
     * Normally clear.
     * No notifications are associated with this flag.
     */
    public static final int STATE_IN_NATIVE = 1 << 22;
    /**
     * The thread state bit mask which reflects the bits that are valid to JVMTI.
     */
    public static final int JVMTI_STATE_BITS = 0
        | STATE_ALIVE
        | STATE_TERMINATED
        | STATE_RUNNABLE
        | STATE_WAITING_INDEFINITELY
        | STATE_WAITING_WITH_TIMEOUT
        | STATE_SLEEPING
        | STATE_WAITING
        | STATE_IN_OBJECT_WAIT
        | STATE_PARKED
        | STATE_BLOCKED_ON_MONITOR_ENTER
     // | STATE_SUSPENDED
        | STATE_INTERRUPTED
        | STATE_IN_NATIVE
        ;
    /**
     * The in-safepoint flag.
     * If set, then the thread is in a safepoint.
     * A thread may cooperatively enter a safepoint at any time.
     * A thread may not exit the safepoint state until the {@link #STATE_SAFEPOINT_REQUEST} flag is clear.
     * May only be set while {@link #STATE_ALIVE} is set.
     * This is an outbound-notification flag.
     * This bit corresponds to the JVMTI "vendor 1" bit.
     */
    public static final int STATE_IN_SAFEPOINT = 1 << 28;

    // ----------------------------------
    //  Thread safepoint request flags
    // ----------------------------------

    // STATE_SAFEPOINT_REQUEST_SUSPEND = 1 << ??;
    /**
     * The safepoint-request flag for GC.
     * If set, then the thread is participating in garbage collection and may not exit a safepoint.
     * Normally clear.
     * The thread may atomically set its own request-GC flag and park in a safepoint if it cannot complete an allocation.
     * The GC thread(s) may set this flag and perform GC work from within a safepoint.
     * May only be set while {@link #STATE_ALIVE} is set.
     * No notifications are associated with this flag.
     * This bit corresponds to the JVMTI "vendor 2" bit.
     */
    public static final int STATE_SAFEPOINT_REQUEST_GC = 1 << 29;
    /**
     * The safepoint-request flag for thread stack walking.
     * If set, then the thread is participating in stack capture and may not exit a safepoint.
     * This flag must be owned exclusively by one thread to prevent premature safepoint exits.
     * Use a monitor to ensure exclusive access.
     * Normally clear.
     * May only be set while {@link #STATE_ALIVE} is set.
     * No notifications are associated with this flag.
     * This bit corresponds to the JVMTI "vendor 3" bit.
     */
    public static final int STATE_SAFEPOINT_REQUEST_STACK = 1 << 30;
    /**
     * A mask containing the bits of all the specific safepoint-request flags.
     */
    public static final int STATE_SAFEPOINT_REQUEST_ANY = STATE_SAFEPOINT_REQUEST_GC | STATE_SAFEPOINT_REQUEST_STACK;
    /**
     * The general safepoint-request flag.
     * This flag must be set whenever any of the other {@code #PARK_SAFEPOINT_REQUEST_*} flags are set.
     * This is because CPUs generally have a single instruction to detect a negative number, making polling more efficient.
     * May only be set while {@link #STATE_ALIVE} is set.
     * This is an inbound-notification flag.
     * This bit is unused by JVMTI.
     */
    public static final int STATE_SAFEPOINT_REQUEST = 1 << 31;
    /**
     * Thread number counter for generation of thread names.
     */
    @SuppressWarnings("unused")
    public static volatile int threadNumberCounter;
    /* For generating thread ID */
    @SuppressWarnings("unused")
    public static volatile long threadSeqNumber;
    /**
     * The number of non-daemon threads; when this reaches zero, we exit.
     */
    @SuppressWarnings("unused")
    public static volatile int nonDaemonThreadCount;
    @SuppressWarnings("unused")
    public static volatile int shutdownInitiated;
    /**
     * Internal holder for the current thread.
     */
    @export
    @ThreadScoped
    public static ptr _qbicc_bound_java_thread;

    private ThreadNative() {}

    static {
        thread_list_terminus.next = addr_of(thread_list_terminus);
        thread_list_terminus.prev = addr_of(thread_list_terminus);
    }

    @NoSafePoint
    @NoSideEffects
    @NoThrow
    @Hidden
    public static native ptr currentThreadNativePtr();

    public static int nextThreadNum() {
        return addr_of(threadNumberCounter).getAndAdd(word(1)).intValue();
    }

    public static void notifySafePointOutbound(final ptr threadNativePtr) {
        if (Build.Target.isWasi()) {
            // TODO
            //  memory.atomic.notify(statusPtr, 1)
            abort();
        } else if (Build.Target.isPosix()) {
            if (pthread_cond_signal(addr_of(deref(threadNativePtr).outbound_cond)).isNonZero()) abort();
        }
    }

    public static void monitorEnter(Object obj) {
        if (obj == null) {
            throw new NullPointerException();
        }
        // todo: stack-locking or similar
        ObjectAccess oa = cast(obj);
        oa.monitorEnter();
    }

    public static void monitorExit(Object obj) {
        if (obj == null) {
            throw new NullPointerException();
        }
        // todo: stack-locking or similar
        ObjectAccess oa = cast(obj);
        oa.monitorExit();
    }

    /**
     * A pointer to this function is passed to pthread_create by start0.
     * The role of this wrapper is to transition a newly started Thread from C to Java calling
     * conventions and then invoke the Thread's run0 method.
     * @param threadParam a reference stuffed into a pointer
     * @return {@code null} always
     */
    @export(withScope = ExportScope.LOCAL)
    @Hidden
    public static ptr runThreadBody(ptr threadParam) {
        pthread_detach(pthread_self());
        ptr threadNativePtr = threadParam.cast();
        _qbicc_bound_java_thread = threadNativePtr;
        bind(threadNativePtr);
        // not reachable
        abort();
        return null;
    }

    /**
     * Attach an unstarted thread and run the given method body under the thread.
     *
     * @param thread the thread to attach
     */
    @export
    @NoThrow
    @Hidden
    public static void thread_attach(Thread thread) {
        ThreadAccess ta = cast(thread);
        ptr threadNativePtr = ta.threadNativePtr;
        if (threadNativePtr.isNonNull()) {
            // we cannot recover
            abort();
        }
        threadNativePtr = malloc(sizeof(thread_native.class));
        if (threadNativePtr.isNull()) {
            // not enough memory to start
            abort();
        }
        // zero-init the whole structure
        threadNativePtr.storeUnshared(zero());
        if (Build.Target.isPosix()) {
            if (! Build.Target.isWasm()) {
                if (pthread_mutex_init(addr_of(deref(threadNativePtr).mutex), zero()).isNonZero()) abort();
                if (pthread_cond_init(addr_of(deref(threadNativePtr).inbound_cond), zero()).isNonZero()) abort();
                if (pthread_cond_init(addr_of(deref(threadNativePtr).outbound_cond), zero()).isNonZero()) abort();
            }
        }
        deref(threadNativePtr).ref = reference.of(thread);
        // register it on to this thread *before* the safepoint
        if (addr_of(deref(refToPtr(ta)).threadNativePtr).compareAndSwap(zero(), threadNativePtr).isNonNull()) {
            // lost the race :(
            // release the mutex and conditions
            if (Build.Target.isPosix()) {
                if (! Build.Target.isWasm()) {
                    if (pthread_cond_destroy(addr_of(deref(threadNativePtr).outbound_cond)).isNonZero()) abort();
                    if (pthread_cond_destroy(addr_of(deref(threadNativePtr).inbound_cond)).isNonZero()) abort();
                    if (pthread_mutex_destroy(addr_of(deref(threadNativePtr).mutex)).isNonZero()) abort();
                }
            }
            free(threadNativePtr);
            abort();
        }
        _qbicc_bound_java_thread = threadNativePtr;

        // initialize status
        deref(threadNativePtr).state = word(STATE_ALIVE);

        int oldVal;
        int newVal;
        int witness;

        // lock in the config
        final ptr configPtr = addr_of(deref(refToPtr(ta)).config);
        oldVal = configPtr.loadSingleAcquire().intValue();
        for (;;) {
            if ((oldVal & ThreadAccess.CONFIG_LOCKED) != 0) {
                // should not be possible, but it's a valid state
                if ((oldVal & ThreadAccess.CONFIG_DAEMON) != 0) {
                    // the main thread may not be a daemon thread
                    abort();
                }
                break;
            }
            newVal = oldVal | ThreadAccess.CONFIG_LOCKED;
            witness = configPtr.compareAndSwapRelease(word(oldVal), word(newVal)).intValue();
            if (witness == oldVal) {
                break;
            }
            oldVal = witness;
        }
        // manually register the thread count
        addr_of(nonDaemonThreadCount).getAndAdd(word(1));

        // add thread to linked list; note that unlike `start`, we do not safepoint here
        // acquire lock
        if (Build.Target.isPosix()) {
            if (pthread_mutex_lock(addr_of(thread_list_mutex)).isNonZero()) abort();
        } else {
            // ???
            abort();
        }
        // we hold the big list lock; do ye olde linked list insertion
        deref(thread_list_terminus.prev).next = threadNativePtr;
        deref(threadNativePtr).prev = thread_list_terminus.prev;
        thread_list_terminus.prev = threadNativePtr;
        deref(threadNativePtr).next = addr_of(thread_list_terminus);
        // release lock
        if (Build.Target.isPosix()) {
            if (pthread_mutex_unlock(addr_of(thread_list_mutex)).isNonZero()) abort();
        } else {
            // ???
            abort();
        }

        // The thread is now alive; use the normal execution methodology from now on
        bind(threadNativePtr);
        // (not reachable)
        abort();
    }

    @NoSafePoint
    @NoThrow
    public static ptr getThreadNativePtr(Thread thread) {
        ThreadAccess ta = cast(thread);
        return ta.threadNativePtr;
    }

    public static void exitNonDaemonThread() {
        // todo: put the shutdown bit right on the count word?
        int cnt = addr_of(nonDaemonThreadCount).getAndAdd(word(- 1)).intValue();
        if (cnt == 1) {
            boolean shouldShutdown = addr_of(shutdownInitiated).compareAndSet(word(0), word(1));
            if (shouldShutdown) {
                // start a new exiter thread to avoid blocking Thread.start()
                new Thread(() -> ShutdownAccess.exit(0), "Exit thread").start();
            }
        }
    }

    @NoSafePoint
    public static void freeThreadNative(ptr threadNativePtr) {
        // assert threadNativePtr != null;
        if (Build.Target.isPosix() && ! Build.Target.isWasm()) {
            // destroy our conditions and mutex
            pthread_cond_destroy(addr_of(deref(threadNativePtr).inbound_cond));
            pthread_cond_destroy(addr_of(deref(threadNativePtr).outbound_cond));
            pthread_mutex_destroy(addr_of(deref(threadNativePtr).mutex));
        }
        // release the memory
        free(threadNativePtr);
    }

    // intrinsic, but implies Hidden & NoThrow & NoReturn
    public static native void bind(ptr threadPtr);

    @NoSafePoint
    public static long nextThreadID() {
        return addr_of(threadSeqNumber).getAndAdd(word(1)).longValue();
    }

    /**
     * Park in a safepoint for some amount of time.
     *
     * @param millis the number of milliseconds to park for
     * @param nanos the number of nanos to park for
     * @param setBits the bits to set while parking
     * @param clearBits the bits to clear while parking
     * @param wakeBits the set of bits, any of which should cause the park to return early
     * @param clearWakeBits the set of bits to clear on unpark and cause the park to return early
     */
    @SuppressWarnings("ConstantConditions")
    @NoSafePoint
    @Hidden
    public static void park(long millis, int nanos, int setBits, int clearBits, int wakeBits, int clearWakeBits) {
        if (millis < 0 || nanos < 0 || nanos > 999_999) {
            throw new IllegalArgumentException();
        }
        // assert
        final ptr threadNativePtr = currentThreadNativePtr();
        final ptr statePtr = addr_of(deref(threadNativePtr).state);
        // check to see if we should just exit right away
        if ((statePtr.loadSingleAcquire().intValue() & wakeBits) != 0) {
            return;
        }
        if (Build.Target.isPosix()) {
            // POSIX park uses seconds not millis...
            long posixSeconds = millis / 1_000L;
            int posixNanos = nanos + ((int)millis % 1_000) * 1_000_000;
            parkPosix(posixSeconds, posixNanos, setBits, clearBits, wakeBits, clearWakeBits);
        } else {
            // todo
            abort();
        }
    }

    @SuppressWarnings("ConstantConditions")
    @NoSafePoint
    @NoThrow
    @Hidden
    public static void parkPosix(long seconds, int nanos, int setBits, int clearBits, int wakeBits, int clearWakeBits) {
        struct_timespec ts = auto();
        struct_timespec now = auto();
        final ptr threadNativePtr = currentThreadNativePtr();
        final ptr statePtr = addr_of(deref(threadNativePtr).state);
        enterSafePoint(threadNativePtr, setBits, clearBits);
        // we are now ready to park

        // acquire the mutex
        if (pthread_mutex_lock(addr_of(deref(threadNativePtr).mutex)).isNonZero()) abort();
        if (seconds != 0 || nanos != 0) {
            // get the start time; both Linux and not-linux are absolute but the clock used varies
            clock_gettime(Build.Target.isLinux() ? CLOCK_MONOTONIC : CLOCK_REALTIME, addr_of(now));
            // add the base time to the duration
            nanos += now.tv_nsec.intValue();
            if (nanos > 1_000_000_000) {
                // carry the one
                seconds ++;
                nanos -= 1_000_000_000;
            }
            seconds += now.tv_sec.longValue();
            // store it back
            ts.tv_sec = word(seconds);
            ts.tv_nsec = word(nanos);
        }
        wakeBits |= clearWakeBits;
        int oldVal = statePtr.loadSingleAcquire().intValue();
        while ((oldVal & wakeBits) == 0) {
            // wait
            if (seconds != 0 || nanos != 0) {
                final c_int res = pthread_cond_timedwait(addr_of(deref(threadNativePtr).inbound_cond), addr_of(deref(threadNativePtr).mutex), addr_of(ts));
                if (res.isNonZero() && res != ETIMEDOUT) abort();
            } else {
                if (pthread_cond_wait(addr_of(deref(threadNativePtr).inbound_cond), addr_of(deref(threadNativePtr).mutex)).isNonZero()) abort();
            }
            // check the bits
            oldVal = statePtr.loadSingleAcquire().intValue();
            if ((oldVal & wakeBits) != 0) {
                // done (awoken)
                break;
            }
            if (seconds != 0 || nanos != 0) {
                // check the time
                clock_gettime(Build.Target.isLinux() ? CLOCK_MONOTONIC : CLOCK_REALTIME, addr_of(now));
                if (now.tv_sec.longValue() > ts.tv_sec.longValue() || now.tv_sec == ts.tv_sec && now.tv_nsec.intValue() >= ts.tv_nsec.intValue()) {
                    // done (timeout)
                    break;
                }
            }
        }
        // release the mutex
        if (pthread_mutex_unlock(addr_of(deref(threadNativePtr).mutex)).isNonZero()) abort();
        // todo: this reacquires the mutex right away; maybe introduce a "locked" version to avoid this
        exitSafePoint(threadNativePtr, clearBits, setBits | clearWakeBits);
    }

    /**
     * Request that a thread enter into a safepoint.
     * Within a safepoint, a thread may not access the VM heap in any way (including allocation).
     * On return, the thread was requested to enter a safepoint.
     *
     * @param setBits the {@code STATE_*} flag that indicates the reason for the safepoint
     */
    @NoSafePoint
    @Hidden
    @NoThrow
    public static void requestSafePoint(ptr threadNativePtr, int setBits) {
        // assert Integer.bitCount(reason) == 1;
        // assert Thread.currentThread() != threadNativePtr->ref
        final ptr statusPtr = addr_of(deref(threadNativePtr).state);
        setBits |= STATE_SAFEPOINT_REQUEST;
        int witness;
        int oldVal = statusPtr.loadVolatile().intValue();
        for (;;) {
            if ((oldVal & setBits) == setBits) {
                // already set
                return;
            }
            witness = statusPtr.compareAndSwap(word(oldVal), word(oldVal | setBits)).intValue();
            if (witness == oldVal) {
                // done; wake it up if it is sleeping
                if (Build.Target.isWasi()) {
                    // TODO
                    //  memory.atomic.notify(statusPtr, 1)
                    abort();
                } else if (Build.Target.isPosix()) {
                    if (pthread_cond_broadcast(addr_of(deref(threadNativePtr).inbound_cond)).isNonZero()) abort();
                }
                return;
            }
            oldVal = witness;
        }
    }

    /**
     * Release a safepoint for another thread.
     * The bits given should be the same as the one given to {@link #requestSafePoint}.
     *
     * @param clearBits the {@code STATE_*} flag that indicates the reason for the safepoint
     */
    @NoSafePoint
    @Hidden
    @NoThrow
    public static void releaseSafePoint(final ptr threadNativePtr, int clearBits) {
        // assert Integer.bitCount(reason) == 1;
        final ptr statusPtr = addr_of(deref(threadNativePtr).state);
        int witness;
        int oldVal = statusPtr.loadVolatile().intValue();
        int newVal;
        for (;;) {
            newVal = oldVal & ~clearBits;
            if ((newVal & STATE_SAFEPOINT_REQUEST_ANY) == 0) {
                // case 1: we were the last reason for safepoint
                newVal &= ~STATE_SAFEPOINT_REQUEST;
                witness = statusPtr.compareAndSwap(word(oldVal), word(newVal)).intValue();
                if (witness == oldVal) {
                    // done; now we must signal the thread so it can exit the safepoint
                    if (Build.Target.isWasi()) {
                        // TODO
                        //  memory.atomic.notify(statusPtr, 1)
                        abort();
                    } else if (Build.Target.isPosix()) {
                        if (pthread_cond_broadcast(addr_of(deref(threadNativePtr).inbound_cond)).isNonZero()) abort();
                    }
                    return;
                }
            } else {
                // case 2: other safepoint reasons are still in effect; just clear the bit and return
                witness = statusPtr.compareAndSwap(word(oldVal), word(newVal)).intValue();
                if (witness == oldVal) {
                    // done
                    return;
                }
            }
            oldVal = witness;
        }
    }

    /**
     * Wait until the given thread is in a safepoint.
     * Must not be called from within the same thread.
     * May only be called within a safepoint.
     */
    @NoSafePoint
    @Hidden
    @NoThrow
    public static void awaitSafePoint(ptr threadNative) {
        awaitStatusWord(threadNative, STATE_IN_SAFEPOINT);
    }

    @NoSafePoint
    @Hidden
    @NoThrow
    public static void awaitStatusWord(ptr threadNative, int bits) {
        awaitStatusWord(threadNative, bits, bits);
    }

    @NoSafePoint
    @Hidden
    @NoThrow
    public static int awaitStatusWord(ptr threadNative, int mask, int bits) {
        // assert isSafePoint();
        final ptr statusPtr = addr_of(deref(threadNative).state);
        int state = statusPtr.loadVolatile().intValue();
        while ((state & mask) != bits) {
            if (Build.Target.isWasi()) {
                // TODO
                //  memory.atomic.wait32(statusPtr, state & mask | bits, -1)
                abort();
            } else if (Build.Target.isPosix()) {
                // double-checked pattern, but using pthread_mutex
                int res = pthread_mutex_lock(addr_of(deref(threadNative).mutex)).intValue();
                if (res != 0) {
                    // fatal error
                    abort();
                }
                state = statusPtr.loadVolatile().intValue();
                while ((state & mask) != bits) {
                    res = pthread_cond_wait(addr_of(deref(threadNative).outbound_cond), addr_of(deref(threadNative).mutex)).intValue();
                    if (res != 0) {
                        // fatal error
                        abort();
                    }
                    state = statusPtr.loadVolatile().intValue();
                }
                res = pthread_mutex_unlock(addr_of(deref(threadNative).mutex)).intValue();
                if (res != 0) {
                    // fatal error
                    abort();
                }
                // done by interior loop
                return state;
            }
            state = statusPtr.loadVolatile().intValue();
        }
        return state;
    }

    /**
     * Poll for a safepoint.
     */
    @NoSafePoint
    @Hidden
    @Inline(InlineCondition.ALWAYS)
    @NoThrow
    @AutoQueued
    public static void pollSafePoint() {
        final ptr threadNativePtr = currentThreadNativePtr();
        final int threadStatus = addr_of(deref(threadNativePtr).state).loadAcquire().intValue();
        if (threadStatus < 0) {
            externalSafePoint(threadNativePtr);
        }
    }

    /**
     * Do the work of an externally-triggered safepoint.
     */
    @NoSafePoint
    @Hidden
    @Inline(InlineCondition.NEVER)
    @NoThrow
    public static void externalSafePoint(final ptr threadNativePtr) {
        enterSafePoint(threadNativePtr, 0, 0);
        exitSafePoint(threadNativePtr, 0, 0);
    }

    /**
     * Enter a safepoint voluntarily from the current thread.
     * While in a safepoint, the thread execution state will not change.
     * However, the interrupt or unpark flags may be asynchronously set on a safepointed thread.
     * The {@code reason} parameter must either be zero or a combination of {@link #STATE_SAFEPOINT_REQUEST} and
     * one or more {@code PARK_SAFEPOINT_REQUEST_*} flags; otherwise, the thread will be trapped in a safepoint indefinitely.
     *
     * @param threadNativePtr the pointer to the thread's native structure (must not be {@code null})
     * @param setBits an optional set of bits to add to the status
     * @param clearBits an optional set of bits to remove from the status
     */
    @NoSafePoint
    @Hidden
    @NoThrow
    public static void enterSafePoint(ptr threadNativePtr, int setBits, int clearBits) {
        // XXX WARNING: Thread.currentThread() is INVALID in this method XXX
        // the reason is that GC may relocate it, but this method is not in the captured context so stack walk will miss it

        final ptr statusPtr = addr_of(deref(threadNativePtr).state);

        // capture our state
        // todo: if (Build.Target.isUnwContext) ...
        if (unw_getcontext(addr_of(deref(threadNativePtr).saved_context)).isNonZero()) abort();

        // now, indicate that we are in a safepoint
        int witness = statusPtr.loadVolatile().intValue();
        int oldVal, newVal;
        do {
            oldVal = witness;
            // no need to check oldVal, since we cannot be in a safepoint
            // assert (oldVal & (STATE_EXITED | STATE_IN_SAFEPOINT)) == 0;
            newVal = (oldVal & ~clearBits) | STATE_IN_SAFEPOINT | setBits;
            witness = statusPtr.compareAndSwap(word(oldVal), word(newVal)).intValue();
        } while (witness != oldVal);
        // notify waiters (if any)
        if (Build.Target.isWasi()) {
            // TODO
            //  memory.atomic.notify(statusPtr, Integer.MAX_VALUE)
            abort();
        } else if (Build.Target.isPosix()) {
            // signal after transition
            if (pthread_cond_broadcast(addr_of(deref(threadNativePtr).outbound_cond)).intValue() != 0) abort();
        }
    }

    @NoSafePoint
    @Hidden
    public static int exitSafePoint(ptr threadNativePtr, int setBits, int clearBits) {
        // XXX WARNING: Thread.currentThread() is INVALID in this method XXX
        // the reason is that GC may relocate it, but this method is not in the captured context so stack walk will miss it

        final ptr statusPtr = addr_of(deref(threadNativePtr).state);
        // wait until it's OK to exit the safepoint
        // assert isSafePoint();
        if (Build.Target.isPosix() && ! Build.Target.isWasi()) {
            // double-checked pattern, but using pthread_mutex; also, we need the lock for waiting
            int res = pthread_mutex_lock(addr_of(deref(threadNativePtr).mutex)).intValue();
            if (res != 0) {
                // fatal error
                abort();
            }
        }
        int oldVal = statusPtr.loadVolatile().intValue();
        for (;;) {
            while ((oldVal & STATE_SAFEPOINT_REQUEST) != 0) {
                if (Build.Target.isWasi()) {
                    // TODO
                    //  memory.atomic.wait32(statusPtr, state & ~STATE_SAFEPOINT_REQUEST, -1)
                    abort();
                } else if (Build.Target.isPosix()) {
                    if (pthread_cond_wait(addr_of(deref(threadNativePtr).inbound_cond), addr_of(deref(threadNativePtr).mutex)).isNonZero()) abort();
                }
                oldVal = statusPtr.loadVolatile().intValue();
            }
            // the request is cleared; now, attempt to clear the safepoint state and notify waiters
            int witness = statusPtr.compareAndSwap(word(oldVal), word(oldVal & ~STATE_IN_SAFEPOINT & ~clearBits | setBits)).intValue();
            if (witness == oldVal) {
                // success!
                break;
            }
            // try again
            oldVal = witness;
        }
        // done! notify waiters
        if (Build.Target.isWasi()) {
            // TODO
            //  memory.atomic.notify(statusPtr, Integer.MAX_VALUE)
            abort();
        } else if (Build.Target.isPosix()) {
            if (pthread_cond_broadcast(addr_of(deref(threadNativePtr).outbound_cond)).isNonZero()) abort();
            if (pthread_mutex_unlock(addr_of(deref(threadNativePtr).mutex)).isNonZero()) abort();
        }
        return oldVal;
    }

    // todo: move this to main?
    @constructor
    @export
    public static void init_thread_list_mutex() {
        // initialize the mutex at early run time
        pthread_mutex_init(addr_of(thread_list_mutex), zero());
    }

    /**
     * Structure containing thread-specific items which cannot be moved in memory.
     * A doubly-linked list, protected by {@link ThreadNative#thread_list_mutex}, is maintained by
     * the {@code next} and {@code prev} pointers, which are always non-{@code null}.
     * The list link pointers may only be accessed (for read or write) under the mutex.
     * The special node {@link ThreadNative#thread_list_terminus} indicates the start and end of the list.
     * The {@code next} and {@code prev} pointers of that node contain the actual ends of the list.
     * 

* This structure is allocated on thread start and freed some time after termination. * Thus, it should normally only be accessed from the same thread or when thread state {@code STATE_ACTIVE} is set. */ @internal public static class thread_native extends object { // TODO // ptr top_of_stack; // basis for compressed stack refs /** * The next (newer) thread link. * On the terminus node, this is the first (least recent) node in the list. */ public ptr next; /** * The previous (older) thread link. * On the terminus node, this is the last (most recent) node in the list. * New threads are added here. */ public ptr prev; /** * The POSIX thread identifier. */ @incomplete(unless = Build.Target.IsPThreads.class) public pthread_t thread; /** * Reference to the actual Java thread. Must be updated during GC. */ public reference ref; // Fallback park/unpark mutex /** * Protects {@link #state} (double-checked path) and the two conditions on platforms without lockless waiting. */ // todo: unless = Build.Target.HasIntegerWait or similar @incomplete(when = { Build.Target.IsLinux.class, Build.Target.IsWasi.class}, unless = Build.Target.IsPosix.class) public pthread_mutex_t mutex; /** * Other threads signal this waiting thread. */ @incomplete(when = { Build.Target.IsLinux.class, Build.Target.IsWasi.class}, unless = Build.Target.IsPosix.class) public pthread_cond_t inbound_cond; /** * This thread signals other waiting threads. */ @incomplete(when = { Build.Target.IsLinux.class, Build.Target.IsWasi.class}, unless = Build.Target.IsPosix.class) public pthread_cond_t outbound_cond; /** * The thread state. */ public uint32_t state; // safepoint state @incomplete(when = Build.Target.IsWasi.class) public unw_context_t saved_context; // exception info // @incomplete(unless = Build.Target.IsUnwind.class) public struct__Unwind_Exception unwindException; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy