runtime: use uint32 for the channel state and select index

This uses uint32 instead of uint64. The reason for this is that uint64
atomic operations aren't universally available (especially on 32-bit
architectures). We could also use uintptr, but that seems needlessly
complicated: it's unlikely real-world programs will use more than a
billion select states (2^30).
This commit is contained in:
Ayke van Laethem
2024-11-27 11:15:15 +01:00
committed by Ron Evans
parent ee6fcd76f4
commit 392a709b77
3 changed files with 45 additions and 17 deletions
+16
View File
@@ -4,7 +4,9 @@ package compiler
// or pseudo-operations that are lowered during goroutine lowering.
import (
"fmt"
"go/types"
"math"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
@@ -124,6 +126,20 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
}
}
const maxSelectStates = math.MaxUint32 >> 2
if len(expr.States) > maxSelectStates {
// The runtime code assumes that the number of state must fit in 30 bits
// (so the select index can be stored in a uint32 with two bits reserved
// for other purposes). It seems unlikely that a real program would have
// that many states, but we check for this case anyway to be sure.
// We use a uint32 (and not a uintptr or uint64) to avoid 64-bit atomic
// operations which aren't available everywhere.
b.addError(expr.Pos(), fmt.Sprintf("too many select states: got %d but the maximum supported number is %d", len(expr.States), maxSelectStates))
// Continue as usual (we'll generate broken code but the error will
// prevent the compilation to complete).
}
// This code create a (stack-allocated) slice containing all the select
// cases and then calls runtime.chanSelect to perform the actual select
// statement.
+12
View File
@@ -26,6 +26,18 @@ type Task struct {
DeferFrame unsafe.Pointer
}
// DataUint32 returns the Data field as a uint32. The value is only valid after
// setting it through SetDataUint32.
func (t *Task) DataUint32() uint32 {
return *(*uint32)(unsafe.Pointer(&t.Data))
}
// SetDataUint32 updates the uint32 portion of the Data field (which could be
// the first 4 or last 4 bytes depending on the architecture endianness).
func (t *Task) SetDataUint32(val uint32) {
*(*uint32)(unsafe.Pointer(&t.Data)) = val
}
// getGoroutineStackSize is a compiler intrinsic that returns the stack size for
// the given function and falls back to the default stack size. It is replaced
// with a load from a special section just before codegen.
+17 -17
View File
@@ -83,7 +83,7 @@ func (q *chanQueue) push(node *channelOp) {
// waiting (for example, when they're part of a select operation) will be
// skipped.
// This function must be called with interrupts disabled.
func (q *chanQueue) pop(chanOp uint64) *channelOp {
func (q *chanQueue) pop(chanOp uint32) *channelOp {
for {
if q.first == nil {
return nil
@@ -96,11 +96,11 @@ func (q *chanQueue) pop(chanOp uint64) *channelOp {
// The new value for the 'data' field will be a combination of the
// channel operation and the select index. (The select index is 0 for
// non-select channel operations).
newDataValue := chanOp | uint64(popped.index<<2)
newDataValue := chanOp | popped.index<<2
// Try to be the first to proceed with this goroutine.
if popped.task.Data == chanOperationWaiting {
popped.task.Data = newDataValue
if popped.task.DataUint32() == chanOperationWaiting {
popped.task.SetDataUint32(newDataValue)
return popped
}
}
@@ -123,7 +123,7 @@ func (q *chanQueue) remove(remove *channelOp) {
type channelOp struct {
next *channelOp
task *task.Task
index uintptr // select index, 0 for non-select operation
index uint32 // select index, 0 for non-select operation
value unsafe.Pointer // if this is a sender, this is the value to send
}
@@ -239,7 +239,7 @@ func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) {
// Can't proceed. Add us to the list of senders and wait until we're awoken.
t := task.Current()
t.Data = chanOperationWaiting
t.SetDataUint32(chanOperationWaiting)
op.task = t
op.index = 0
op.value = value
@@ -251,7 +251,7 @@ func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) {
// Check whether the sent happened normally (not because the channel was
// closed while sending).
if t.Data == chanOperationClosed {
if t.DataUint32() == chanOperationClosed {
// Oops, this channel was closed while sending!
runtimePanic("send on closed channel")
}
@@ -313,7 +313,7 @@ func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool {
// until we're awoken.
t := task.Current()
t.Ptr = value
t.Data = chanOperationWaiting
t.SetDataUint32(chanOperationWaiting)
op.task = t
op.index = 0
ch.receivers.push(op)
@@ -323,7 +323,7 @@ func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool {
task.Pause()
// Return whether the receive happened from a closed channel.
return t.Data != chanOperationClosed
return t.DataUint32() != chanOperationClosed
}
// chanClose closes the given channel. If this channel has a receiver or is
@@ -375,10 +375,10 @@ func chanClose(ch *channel) {
// chanSelect implements blocking or non-blocking select operations.
// The 'ops' slice must be set if (and only if) this is a blocking select.
func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelOp) (uintptr, bool) {
func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelOp) (uint32, bool) {
mask := interrupt.Disable()
const selectNoIndex = ^uintptr(0)
const selectNoIndex = ^uint32(0)
selectIndex := selectNoIndex
selectOk := true
@@ -393,13 +393,13 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO
if state.value == nil { // chan receive
if received, ok := state.ch.tryRecv(recvbuf); received {
selectIndex = uintptr(i)
selectIndex = uint32(i)
selectOk = ok
break
}
} else { // chan send
if state.ch.trySend(state.value) {
selectIndex = uintptr(i)
selectIndex = uint32(i)
break
}
}
@@ -421,14 +421,14 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO
// will be able to "take" this select operation.
t := task.Current()
t.Ptr = recvbuf
t.Data = chanOperationWaiting
t.SetDataUint32(chanOperationWaiting)
for i, state := range states {
if state.ch == nil {
continue
}
op := &ops[i]
op.task = t
op.index = uintptr(i)
op.index = uint32(i)
if state.value == nil { // chan receive
state.ch.receivers.push(op)
} else { // chan send
@@ -460,8 +460,8 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO
}
// Pull the return values out of t.Data (which contains two bitfields).
selectIndex = uintptr(t.Data) >> 2
selectOk = t.Data&chanOperationMask != chanOperationClosed
selectIndex = t.DataUint32() >> 2
selectOk = t.DataUint32()&chanOperationMask != chanOperationClosed
return selectIndex, selectOk
}