Skip to content

Commit 392a709

Browse files
aykevldeadprogram
authored andcommitted
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).
1 parent ee6fcd7 commit 392a709

File tree

3 files changed

+45
-17
lines changed

3 files changed

+45
-17
lines changed

compiler/channel.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ package compiler
44
// or pseudo-operations that are lowered during goroutine lowering.
55

66
import (
7+
"fmt"
78
"go/types"
9+
"math"
810

911
"github.com/tinygo-org/tinygo/compiler/llvmutil"
1012
"golang.org/x/tools/go/ssa"
@@ -124,6 +126,20 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
124126
}
125127
}
126128

129+
const maxSelectStates = math.MaxUint32 >> 2
130+
if len(expr.States) > maxSelectStates {
131+
// The runtime code assumes that the number of state must fit in 30 bits
132+
// (so the select index can be stored in a uint32 with two bits reserved
133+
// for other purposes). It seems unlikely that a real program would have
134+
// that many states, but we check for this case anyway to be sure.
135+
// We use a uint32 (and not a uintptr or uint64) to avoid 64-bit atomic
136+
// operations which aren't available everywhere.
137+
b.addError(expr.Pos(), fmt.Sprintf("too many select states: got %d but the maximum supported number is %d", len(expr.States), maxSelectStates))
138+
139+
// Continue as usual (we'll generate broken code but the error will
140+
// prevent the compilation to complete).
141+
}
142+
127143
// This code create a (stack-allocated) slice containing all the select
128144
// cases and then calls runtime.chanSelect to perform the actual select
129145
// statement.

src/internal/task/task.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ type Task struct {
2626
DeferFrame unsafe.Pointer
2727
}
2828

29+
// DataUint32 returns the Data field as a uint32. The value is only valid after
30+
// setting it through SetDataUint32.
31+
func (t *Task) DataUint32() uint32 {
32+
return *(*uint32)(unsafe.Pointer(&t.Data))
33+
}
34+
35+
// SetDataUint32 updates the uint32 portion of the Data field (which could be
36+
// the first 4 or last 4 bytes depending on the architecture endianness).
37+
func (t *Task) SetDataUint32(val uint32) {
38+
*(*uint32)(unsafe.Pointer(&t.Data)) = val
39+
}
40+
2941
// getGoroutineStackSize is a compiler intrinsic that returns the stack size for
3042
// the given function and falls back to the default stack size. It is replaced
3143
// with a load from a special section just before codegen.

src/runtime/chan.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func (q *chanQueue) push(node *channelOp) {
8383
// waiting (for example, when they're part of a select operation) will be
8484
// skipped.
8585
// This function must be called with interrupts disabled.
86-
func (q *chanQueue) pop(chanOp uint64) *channelOp {
86+
func (q *chanQueue) pop(chanOp uint32) *channelOp {
8787
for {
8888
if q.first == nil {
8989
return nil
@@ -96,11 +96,11 @@ func (q *chanQueue) pop(chanOp uint64) *channelOp {
9696
// The new value for the 'data' field will be a combination of the
9797
// channel operation and the select index. (The select index is 0 for
9898
// non-select channel operations).
99-
newDataValue := chanOp | uint64(popped.index<<2)
99+
newDataValue := chanOp | popped.index<<2
100100

101101
// Try to be the first to proceed with this goroutine.
102-
if popped.task.Data == chanOperationWaiting {
103-
popped.task.Data = newDataValue
102+
if popped.task.DataUint32() == chanOperationWaiting {
103+
popped.task.SetDataUint32(newDataValue)
104104
return popped
105105
}
106106
}
@@ -123,7 +123,7 @@ func (q *chanQueue) remove(remove *channelOp) {
123123
type channelOp struct {
124124
next *channelOp
125125
task *task.Task
126-
index uintptr // select index, 0 for non-select operation
126+
index uint32 // select index, 0 for non-select operation
127127
value unsafe.Pointer // if this is a sender, this is the value to send
128128
}
129129

@@ -239,7 +239,7 @@ func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) {
239239

240240
// Can't proceed. Add us to the list of senders and wait until we're awoken.
241241
t := task.Current()
242-
t.Data = chanOperationWaiting
242+
t.SetDataUint32(chanOperationWaiting)
243243
op.task = t
244244
op.index = 0
245245
op.value = value
@@ -251,7 +251,7 @@ func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) {
251251

252252
// Check whether the sent happened normally (not because the channel was
253253
// closed while sending).
254-
if t.Data == chanOperationClosed {
254+
if t.DataUint32() == chanOperationClosed {
255255
// Oops, this channel was closed while sending!
256256
runtimePanic("send on closed channel")
257257
}
@@ -313,7 +313,7 @@ func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool {
313313
// until we're awoken.
314314
t := task.Current()
315315
t.Ptr = value
316-
t.Data = chanOperationWaiting
316+
t.SetDataUint32(chanOperationWaiting)
317317
op.task = t
318318
op.index = 0
319319
ch.receivers.push(op)
@@ -323,7 +323,7 @@ func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool {
323323
task.Pause()
324324

325325
// Return whether the receive happened from a closed channel.
326-
return t.Data != chanOperationClosed
326+
return t.DataUint32() != chanOperationClosed
327327
}
328328

329329
// chanClose closes the given channel. If this channel has a receiver or is
@@ -375,10 +375,10 @@ func chanClose(ch *channel) {
375375

376376
// chanSelect implements blocking or non-blocking select operations.
377377
// The 'ops' slice must be set if (and only if) this is a blocking select.
378-
func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelOp) (uintptr, bool) {
378+
func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelOp) (uint32, bool) {
379379
mask := interrupt.Disable()
380380

381-
const selectNoIndex = ^uintptr(0)
381+
const selectNoIndex = ^uint32(0)
382382
selectIndex := selectNoIndex
383383
selectOk := true
384384

@@ -393,13 +393,13 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO
393393

394394
if state.value == nil { // chan receive
395395
if received, ok := state.ch.tryRecv(recvbuf); received {
396-
selectIndex = uintptr(i)
396+
selectIndex = uint32(i)
397397
selectOk = ok
398398
break
399399
}
400400
} else { // chan send
401401
if state.ch.trySend(state.value) {
402-
selectIndex = uintptr(i)
402+
selectIndex = uint32(i)
403403
break
404404
}
405405
}
@@ -421,14 +421,14 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO
421421
// will be able to "take" this select operation.
422422
t := task.Current()
423423
t.Ptr = recvbuf
424-
t.Data = chanOperationWaiting
424+
t.SetDataUint32(chanOperationWaiting)
425425
for i, state := range states {
426426
if state.ch == nil {
427427
continue
428428
}
429429
op := &ops[i]
430430
op.task = t
431-
op.index = uintptr(i)
431+
op.index = uint32(i)
432432
if state.value == nil { // chan receive
433433
state.ch.receivers.push(op)
434434
} else { // chan send
@@ -460,8 +460,8 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO
460460
}
461461

462462
// Pull the return values out of t.Data (which contains two bitfields).
463-
selectIndex = uintptr(t.Data) >> 2
464-
selectOk = t.Data&chanOperationMask != chanOperationClosed
463+
selectIndex = t.DataUint32() >> 2
464+
selectOk = t.DataUint32()&chanOperationMask != chanOperationClosed
465465

466466
return selectIndex, selectOk
467467
}

0 commit comments

Comments
 (0)