mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-16 18:53:29 +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.
|
// or pseudo-operations that are lowered during goroutine lowering.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"go/types"
|
"go/types"
|
||||||
|
"math"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||||
"golang.org/x/tools/go/ssa"
|
"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
|
// This code create a (stack-allocated) slice containing all the select
|
||||||
// cases and then calls runtime.chanSelect to perform the actual select
|
// cases and then calls runtime.chanSelect to perform the actual select
|
||||||
// statement.
|
// statement.
|
||||||
|
|||||||
@@ -26,6 +26,18 @@ type Task struct {
|
|||||||
DeferFrame unsafe.Pointer
|
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
|
// 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
|
// the given function and falls back to the default stack size. It is replaced
|
||||||
// with a load from a special section just before codegen.
|
// 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
|
// waiting (for example, when they're part of a select operation) will be
|
||||||
// skipped.
|
// skipped.
|
||||||
// This function must be called with interrupts disabled.
|
// This function must be called with interrupts disabled.
|
||||||
func (q *chanQueue) pop(chanOp uint64) *channelOp {
|
func (q *chanQueue) pop(chanOp uint32) *channelOp {
|
||||||
for {
|
for {
|
||||||
if q.first == nil {
|
if q.first == nil {
|
||||||
return 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
|
// 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
|
// channel operation and the select index. (The select index is 0 for
|
||||||
// non-select channel operations).
|
// 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.
|
// Try to be the first to proceed with this goroutine.
|
||||||
if popped.task.Data == chanOperationWaiting {
|
if popped.task.DataUint32() == chanOperationWaiting {
|
||||||
popped.task.Data = newDataValue
|
popped.task.SetDataUint32(newDataValue)
|
||||||
return popped
|
return popped
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ func (q *chanQueue) remove(remove *channelOp) {
|
|||||||
type channelOp struct {
|
type channelOp struct {
|
||||||
next *channelOp
|
next *channelOp
|
||||||
task *task.Task
|
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
|
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.
|
// Can't proceed. Add us to the list of senders and wait until we're awoken.
|
||||||
t := task.Current()
|
t := task.Current()
|
||||||
t.Data = chanOperationWaiting
|
t.SetDataUint32(chanOperationWaiting)
|
||||||
op.task = t
|
op.task = t
|
||||||
op.index = 0
|
op.index = 0
|
||||||
op.value = value
|
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
|
// Check whether the sent happened normally (not because the channel was
|
||||||
// closed while sending).
|
// closed while sending).
|
||||||
if t.Data == chanOperationClosed {
|
if t.DataUint32() == chanOperationClosed {
|
||||||
// Oops, this channel was closed while sending!
|
// Oops, this channel was closed while sending!
|
||||||
runtimePanic("send on closed channel")
|
runtimePanic("send on closed channel")
|
||||||
}
|
}
|
||||||
@@ -313,7 +313,7 @@ func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool {
|
|||||||
// until we're awoken.
|
// until we're awoken.
|
||||||
t := task.Current()
|
t := task.Current()
|
||||||
t.Ptr = value
|
t.Ptr = value
|
||||||
t.Data = chanOperationWaiting
|
t.SetDataUint32(chanOperationWaiting)
|
||||||
op.task = t
|
op.task = t
|
||||||
op.index = 0
|
op.index = 0
|
||||||
ch.receivers.push(op)
|
ch.receivers.push(op)
|
||||||
@@ -323,7 +323,7 @@ func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool {
|
|||||||
task.Pause()
|
task.Pause()
|
||||||
|
|
||||||
// Return whether the receive happened from a closed channel.
|
// 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
|
// 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.
|
// chanSelect implements blocking or non-blocking select operations.
|
||||||
// The 'ops' slice must be set if (and only if) this is a blocking select.
|
// 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()
|
mask := interrupt.Disable()
|
||||||
|
|
||||||
const selectNoIndex = ^uintptr(0)
|
const selectNoIndex = ^uint32(0)
|
||||||
selectIndex := selectNoIndex
|
selectIndex := selectNoIndex
|
||||||
selectOk := true
|
selectOk := true
|
||||||
|
|
||||||
@@ -393,13 +393,13 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO
|
|||||||
|
|
||||||
if state.value == nil { // chan receive
|
if state.value == nil { // chan receive
|
||||||
if received, ok := state.ch.tryRecv(recvbuf); received {
|
if received, ok := state.ch.tryRecv(recvbuf); received {
|
||||||
selectIndex = uintptr(i)
|
selectIndex = uint32(i)
|
||||||
selectOk = ok
|
selectOk = ok
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else { // chan send
|
} else { // chan send
|
||||||
if state.ch.trySend(state.value) {
|
if state.ch.trySend(state.value) {
|
||||||
selectIndex = uintptr(i)
|
selectIndex = uint32(i)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -421,14 +421,14 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO
|
|||||||
// will be able to "take" this select operation.
|
// will be able to "take" this select operation.
|
||||||
t := task.Current()
|
t := task.Current()
|
||||||
t.Ptr = recvbuf
|
t.Ptr = recvbuf
|
||||||
t.Data = chanOperationWaiting
|
t.SetDataUint32(chanOperationWaiting)
|
||||||
for i, state := range states {
|
for i, state := range states {
|
||||||
if state.ch == nil {
|
if state.ch == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
op := &ops[i]
|
op := &ops[i]
|
||||||
op.task = t
|
op.task = t
|
||||||
op.index = uintptr(i)
|
op.index = uint32(i)
|
||||||
if state.value == nil { // chan receive
|
if state.value == nil { // chan receive
|
||||||
state.ch.receivers.push(op)
|
state.ch.receivers.push(op)
|
||||||
} else { // chan send
|
} 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).
|
// Pull the return values out of t.Data (which contains two bitfields).
|
||||||
selectIndex = uintptr(t.Data) >> 2
|
selectIndex = t.DataUint32() >> 2
|
||||||
selectOk = t.Data&chanOperationMask != chanOperationClosed
|
selectOk = t.DataUint32()&chanOperationMask != chanOperationClosed
|
||||||
|
|
||||||
return selectIndex, selectOk
|
return selectIndex, selectOk
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user