From 7a177114df3e26f4362378e907a869c4fbbf38b7 Mon Sep 17 00:00:00 2001
From: Rhys Hiltner <rhys.hiltner@gmail.com>
Date: Fri, 14 Mar 2025 12:38:34 -0700
Subject: [PATCH] runtime: commit to spinbitmutex GOEXPERIMENT

Use the "spinbit" mutex implementation always (including on platforms
that need to emulate atomic.Xchg8), and delete the prior "tristate"
implementations.

The exception is GOARCH=wasm, where the Go runtime does not use multiple
threads.

For #68578

Change-Id: Ifc29bbfa05071d776c23a19ae185891a03a82417
Reviewed-on: https://go-review.googlesource.com/c/go/+/658456
Auto-Submit: Rhys Hiltner <rhys.hiltner@gmail.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
---
 src/internal/buildcfg/exp.go                  |   3 -
 .../goexperiment/exp_spinbitmutex_off.go      |   8 -
 .../goexperiment/exp_spinbitmutex_on.go       |   8 -
 src/internal/goexperiment/flags.go            |   4 -
 src/runtime/lock_futex_tristate.go            | 138 ----------------
 src/runtime/lock_sema_tristate.go             | 148 ------------------
 src/runtime/lock_spinbit.go                   |   2 +-
 src/runtime/runtime2.go                       |   7 +-
 8 files changed, 4 insertions(+), 314 deletions(-)
 delete mode 100644 src/internal/goexperiment/exp_spinbitmutex_off.go
 delete mode 100644 src/internal/goexperiment/exp_spinbitmutex_on.go
 delete mode 100644 src/runtime/lock_futex_tristate.go
 delete mode 100644 src/runtime/lock_sema_tristate.go

diff --git a/src/internal/buildcfg/exp.go b/src/internal/buildcfg/exp.go
index ccdf465ffbb..e36ec08a5b0 100644
--- a/src/internal/buildcfg/exp.go
+++ b/src/internal/buildcfg/exp.go
@@ -67,8 +67,6 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (*ExperimentFlags, error) {
 		regabiSupported = true
 	}
 
-	haveThreads := goarch != "wasm"
-
 	// Older versions (anything before V16) of dsymutil don't handle
 	// the .debug_rnglists section in DWARF5. See
 	// https://github.com/golang/go/issues/26379#issuecomment-2677068742
@@ -85,7 +83,6 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (*ExperimentFlags, error) {
 		RegabiArgs:      regabiSupported,
 		AliasTypeParams: true,
 		SwissMap:        true,
-		SpinbitMutex:    haveThreads,
 		SyncHashTrieMap: true,
 		Dwarf5:          dwarf5Supported,
 	}
diff --git a/src/internal/goexperiment/exp_spinbitmutex_off.go b/src/internal/goexperiment/exp_spinbitmutex_off.go
deleted file mode 100644
index 776b0dc77ff..00000000000
--- a/src/internal/goexperiment/exp_spinbitmutex_off.go
+++ /dev/null
@@ -1,8 +0,0 @@
-// Code generated by mkconsts.go. DO NOT EDIT.
-
-//go:build !goexperiment.spinbitmutex
-
-package goexperiment
-
-const SpinbitMutex = false
-const SpinbitMutexInt = 0
diff --git a/src/internal/goexperiment/exp_spinbitmutex_on.go b/src/internal/goexperiment/exp_spinbitmutex_on.go
deleted file mode 100644
index 8468030c769..00000000000
--- a/src/internal/goexperiment/exp_spinbitmutex_on.go
+++ /dev/null
@@ -1,8 +0,0 @@
-// Code generated by mkconsts.go. DO NOT EDIT.
-
-//go:build goexperiment.spinbitmutex
-
-package goexperiment
-
-const SpinbitMutex = true
-const SpinbitMutexInt = 1
diff --git a/src/internal/goexperiment/flags.go b/src/internal/goexperiment/flags.go
index 271fdc2b3cf..0961764bee6 100644
--- a/src/internal/goexperiment/flags.go
+++ b/src/internal/goexperiment/flags.go
@@ -115,10 +115,6 @@ type Flags struct {
 	// SwissMap enables the SwissTable-based map implementation.
 	SwissMap bool
 
-	// SpinbitMutex enables the new "spinbit" mutex implementation on supported
-	// platforms. See https://go.dev/issue/68578.
-	SpinbitMutex bool
-
 	// SyncHashTrieMap enables the HashTrieMap sync.Map implementation.
 	SyncHashTrieMap bool
 
diff --git a/src/runtime/lock_futex_tristate.go b/src/runtime/lock_futex_tristate.go
deleted file mode 100644
index b7df18c86c0..00000000000
--- a/src/runtime/lock_futex_tristate.go
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build (dragonfly || freebsd || linux) && !goexperiment.spinbitmutex
-
-package runtime
-
-import (
-	"internal/runtime/atomic"
-)
-
-// This implementation depends on OS-specific implementations of
-//
-//	futexsleep(addr *uint32, val uint32, ns int64)
-//		Atomically,
-//			if *addr == val { sleep }
-//		Might be woken up spuriously; that's allowed.
-//		Don't sleep longer than ns; ns < 0 means forever.
-//
-//	futexwakeup(addr *uint32, cnt uint32)
-//		If any procs are sleeping on addr, wake up at most cnt.
-
-const (
-	mutex_unlocked = 0
-	mutex_locked   = 1
-	mutex_sleeping = 2
-
-	active_spin     = 4
-	active_spin_cnt = 30
-	passive_spin    = 1
-)
-
-// Possible lock states are mutex_unlocked, mutex_locked and mutex_sleeping.
-// mutex_sleeping means that there is presumably at least one sleeping thread.
-// Note that there can be spinning threads during all states - they do not
-// affect mutex's state.
-
-type mWaitList struct{}
-
-func lockVerifyMSize() {}
-
-func mutexContended(l *mutex) bool {
-	return atomic.Load(key32(&l.key)) > mutex_locked
-}
-
-func lock(l *mutex) {
-	lockWithRank(l, getLockRank(l))
-}
-
-func lock2(l *mutex) {
-	gp := getg()
-
-	if gp.m.locks < 0 {
-		throw("runtime·lock: lock count")
-	}
-	gp.m.locks++
-
-	// Speculative grab for lock.
-	v := atomic.Xchg(key32(&l.key), mutex_locked)
-	if v == mutex_unlocked {
-		return
-	}
-
-	// wait is either MUTEX_LOCKED or MUTEX_SLEEPING
-	// depending on whether there is a thread sleeping
-	// on this mutex. If we ever change l->key from
-	// MUTEX_SLEEPING to some other value, we must be
-	// careful to change it back to MUTEX_SLEEPING before
-	// returning, to ensure that the sleeping thread gets
-	// its wakeup call.
-	wait := v
-
-	timer := &lockTimer{lock: l}
-	timer.begin()
-	// On uniprocessors, no point spinning.
-	// On multiprocessors, spin for ACTIVE_SPIN attempts.
-	spin := 0
-	if ncpu > 1 {
-		spin = active_spin
-	}
-	for {
-		// Try for lock, spinning.
-		for i := 0; i < spin; i++ {
-			for l.key == mutex_unlocked {
-				if atomic.Cas(key32(&l.key), mutex_unlocked, wait) {
-					timer.end()
-					return
-				}
-			}
-			procyield(active_spin_cnt)
-		}
-
-		// Try for lock, rescheduling.
-		for i := 0; i < passive_spin; i++ {
-			for l.key == mutex_unlocked {
-				if atomic.Cas(key32(&l.key), mutex_unlocked, wait) {
-					timer.end()
-					return
-				}
-			}
-			osyield()
-		}
-
-		// Sleep.
-		v = atomic.Xchg(key32(&l.key), mutex_sleeping)
-		if v == mutex_unlocked {
-			timer.end()
-			return
-		}
-		wait = mutex_sleeping
-		futexsleep(key32(&l.key), mutex_sleeping, -1)
-	}
-}
-
-func unlock(l *mutex) {
-	unlockWithRank(l)
-}
-
-func unlock2(l *mutex) {
-	v := atomic.Xchg(key32(&l.key), mutex_unlocked)
-	if v == mutex_unlocked {
-		throw("unlock of unlocked lock")
-	}
-	if v == mutex_sleeping {
-		futexwakeup(key32(&l.key), 1)
-	}
-
-	gp := getg()
-	gp.m.mLockProfile.recordUnlock(l)
-	gp.m.locks--
-	if gp.m.locks < 0 {
-		throw("runtime·unlock: lock count")
-	}
-	if gp.m.locks == 0 && gp.preempt { // restore the preemption request in case we've cleared it in newstack
-		gp.stackguard0 = stackPreempt
-	}
-}
diff --git a/src/runtime/lock_sema_tristate.go b/src/runtime/lock_sema_tristate.go
deleted file mode 100644
index 4375791d46f..00000000000
--- a/src/runtime/lock_sema_tristate.go
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build (aix || darwin || netbsd || openbsd || plan9 || solaris || windows) && !goexperiment.spinbitmutex
-
-package runtime
-
-import (
-	"internal/runtime/atomic"
-	"unsafe"
-)
-
-// This implementation depends on OS-specific implementations of
-//
-//	func semacreate(mp *m)
-//		Create a semaphore for mp, if it does not already have one.
-//
-//	func semasleep(ns int64) int32
-//		If ns < 0, acquire m's semaphore and return 0.
-//		If ns >= 0, try to acquire m's semaphore for at most ns nanoseconds.
-//		Return 0 if the semaphore was acquired, -1 if interrupted or timed out.
-//
-//	func semawakeup(mp *m)
-//		Wake up mp, which is or will soon be sleeping on its semaphore.
-const (
-	active_spin     = 4
-	active_spin_cnt = 30
-	passive_spin    = 1
-)
-
-// mWaitList is part of the M struct, and holds the list of Ms that are waiting
-// for a particular runtime.mutex.
-//
-// When an M is unable to immediately obtain a lock, it adds itself to the list
-// of Ms waiting for the lock. It does that via this struct's next field,
-// forming a singly-linked list with the mutex's key field pointing to the head
-// of the list.
-type mWaitList struct {
-	next muintptr // next m waiting for lock
-}
-
-func lockVerifyMSize() {}
-
-func mutexContended(l *mutex) bool {
-	return atomic.Loaduintptr(&l.key) > locked
-}
-
-func lock(l *mutex) {
-	lockWithRank(l, getLockRank(l))
-}
-
-func lock2(l *mutex) {
-	gp := getg()
-	if gp.m.locks < 0 {
-		throw("runtime·lock: lock count")
-	}
-	gp.m.locks++
-
-	// Speculative grab for lock.
-	if atomic.Casuintptr(&l.key, 0, locked) {
-		return
-	}
-	semacreate(gp.m)
-
-	timer := &lockTimer{lock: l}
-	timer.begin()
-	// On uniprocessor's, no point spinning.
-	// On multiprocessors, spin for ACTIVE_SPIN attempts.
-	spin := 0
-	if ncpu > 1 {
-		spin = active_spin
-	}
-Loop:
-	for i := 0; ; i++ {
-		v := atomic.Loaduintptr(&l.key)
-		if v&locked == 0 {
-			// Unlocked. Try to lock.
-			if atomic.Casuintptr(&l.key, v, v|locked) {
-				timer.end()
-				return
-			}
-			i = 0
-		}
-		if i < spin {
-			procyield(active_spin_cnt)
-		} else if i < spin+passive_spin {
-			osyield()
-		} else {
-			// Someone else has it.
-			// l.key points to a linked list of M's waiting
-			// for this lock, chained through m.mWaitList.next.
-			// Queue this M.
-			for {
-				gp.m.mWaitList.next = muintptr(v &^ locked)
-				if atomic.Casuintptr(&l.key, v, uintptr(unsafe.Pointer(gp.m))|locked) {
-					break
-				}
-				v = atomic.Loaduintptr(&l.key)
-				if v&locked == 0 {
-					continue Loop
-				}
-			}
-			if v&locked != 0 {
-				// Queued. Wait.
-				semasleep(-1)
-				i = 0
-			}
-		}
-	}
-}
-
-func unlock(l *mutex) {
-	unlockWithRank(l)
-}
-
-// We might not be holding a p in this code.
-//
-//go:nowritebarrier
-func unlock2(l *mutex) {
-	gp := getg()
-	var mp *m
-	for {
-		v := atomic.Loaduintptr(&l.key)
-		if v == locked {
-			if atomic.Casuintptr(&l.key, locked, 0) {
-				break
-			}
-		} else {
-			// Other M's are waiting for the lock.
-			// Dequeue an M.
-			mp = muintptr(v &^ locked).ptr()
-			if atomic.Casuintptr(&l.key, v, uintptr(mp.mWaitList.next)) {
-				// Dequeued an M.  Wake it.
-				semawakeup(mp) // no use of mp after this point; it's awake
-				break
-			}
-		}
-	}
-	gp.m.mLockProfile.recordUnlock(l)
-	gp.m.locks--
-	if gp.m.locks < 0 {
-		throw("runtime·unlock: lock count")
-	}
-	if gp.m.locks == 0 && gp.preempt { // restore the preemption request in case we've cleared it in newstack
-		gp.stackguard0 = stackPreempt
-	}
-}
diff --git a/src/runtime/lock_spinbit.go b/src/runtime/lock_spinbit.go
index 7b7bc651ad9..9bca4bd8243 100644
--- a/src/runtime/lock_spinbit.go
+++ b/src/runtime/lock_spinbit.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || plan9 || solaris || windows) && goexperiment.spinbitmutex
+//go:build !wasm
 
 package runtime
 
diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go
index 27d14b890bd..f7371c450bd 100644
--- a/src/runtime/runtime2.go
+++ b/src/runtime/runtime2.go
@@ -8,7 +8,6 @@ import (
 	"internal/abi"
 	"internal/chacha8rand"
 	"internal/goarch"
-	"internal/goexperiment"
 	"internal/runtime/atomic"
 	"internal/runtime/sys"
 	"unsafe"
@@ -628,9 +627,9 @@ type mPadded struct {
 
 	// Size the runtime.m structure so it fits in the 2048-byte size class, and
 	// not in the next-smallest (1792-byte) size class. That leaves the 11 low
-	// bits of muintptr values available for flags, as required for
-	// GOEXPERIMENT=spinbitmutex.
-	_ [goexperiment.SpinbitMutexInt * (2048 - mallocHeaderSize - mRedZoneSize - unsafe.Sizeof(m{}))]byte
+	// bits of muintptr values available for flags, as required by
+	// lock_spinbit.go.
+	_ [(1 - goarch.IsWasm) * (2048 - mallocHeaderSize - mRedZoneSize - unsafe.Sizeof(m{}))]byte
 }
 
 type p struct {
-- 
GitLab