mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-04 02:57:46 +00:00
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:
committed by
Ron Evans
parent
ee6fcd76f4
commit
392a709b77
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user