github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/runtime/chan.go (about)

     1  package runtime
     2  
     3  // This file implements the 'chan' type and send/receive/select operations.
     4  
     5  // A channel can be in one of the following states:
     6  //     empty:
     7  //       No goroutine is waiting on a send or receive operation. The 'blocked'
     8  //       member is nil.
     9  //     recv:
    10  //       A goroutine tries to receive from the channel. This goroutine is stored
    11  //       in the 'blocked' member.
    12  //     send:
    13  //       The reverse of send. A goroutine tries to send to the channel. This
    14  //       goroutine is stored in the 'blocked' member.
    15  //     closed:
    16  //       The channel is closed. Sends will panic, receives will get a zero value
    17  //       plus optionally the indication that the channel is zero (with the
    18  //       comma-ok value in the task).
    19  //
    20  // A send/recv transmission is completed by copying from the data element of the
    21  // sending task to the data element of the receiving task, and setting
    22  // the 'comma-ok' value to true.
    23  // A receive operation on a closed channel is completed by zeroing the data
    24  // element of the receiving task and setting the 'comma-ok' value to false.
    25  
    26  import (
    27  	"internal/task"
    28  	"runtime/interrupt"
    29  	"unsafe"
    30  )
    31  
    32  func chanDebug(ch *channel) {
    33  	if schedulerDebug {
    34  		if ch.bufSize > 0 {
    35  			println("--- channel update:", ch, ch.state.String(), ch.bufSize, ch.bufUsed)
    36  		} else {
    37  			println("--- channel update:", ch, ch.state.String())
    38  		}
    39  	}
    40  }
    41  
    42  // channelBlockedList is a list of channel operations on a specific channel which are currently blocked.
    43  type channelBlockedList struct {
    44  	// next is a pointer to the next blocked channel operation on the same channel.
    45  	next *channelBlockedList
    46  
    47  	// t is the task associated with this channel operation.
    48  	// If this channel operation is not part of a select, then the pointer field of the state holds the data buffer.
    49  	// If this channel operation is part of a select, then the pointer field of the state holds the receive buffer.
    50  	// If this channel operation is a receive, then the data field should be set to zero when resuming due to channel closure.
    51  	t *task.Task
    52  
    53  	// s is a pointer to the channel select state corresponding to this operation.
    54  	// This will be nil if and only if this channel operation is not part of a select statement.
    55  	// If this is a send operation, then the send buffer can be found in this select state.
    56  	s *chanSelectState
    57  
    58  	// allSelectOps is a slice containing all of the channel operations involved with this select statement.
    59  	// Before resuming the task, all other channel operations on this select statement should be canceled by removing them from their corresponding lists.
    60  	allSelectOps []channelBlockedList
    61  }
    62  
    63  // remove takes the current list of blocked channel operations and removes the specified operation.
    64  // This returns the resulting list, or nil if the resulting list is empty.
    65  // A nil receiver is treated as an empty list.
    66  func (b *channelBlockedList) remove(old *channelBlockedList) *channelBlockedList {
    67  	if b == old {
    68  		return b.next
    69  	}
    70  	c := b
    71  	for ; c != nil && c.next != old; c = c.next {
    72  	}
    73  	if c != nil {
    74  		c.next = old.next
    75  	}
    76  	return b
    77  }
    78  
    79  // detatch removes all other channel operations that are part of the same select statement.
    80  // If the input is not part of a select statement, this is a no-op.
    81  // This must be called before resuming any task blocked on a channel operation in order to ensure that it is not placed on the runqueue twice.
    82  func (b *channelBlockedList) detach() {
    83  	if b.allSelectOps == nil {
    84  		// nothing to do
    85  		return
    86  	}
    87  	for i, v := range b.allSelectOps {
    88  		// cancel all other channel operations that are part of this select statement
    89  		switch {
    90  		case &b.allSelectOps[i] == b:
    91  			// This entry is the one that was already detatched.
    92  			continue
    93  		case v.t == nil:
    94  			// This entry is not used (nil channel).
    95  			continue
    96  		}
    97  		v.s.ch.blocked = v.s.ch.blocked.remove(&b.allSelectOps[i])
    98  		if v.s.ch.blocked == nil {
    99  			if v.s.value == nil {
   100  				// recv operation
   101  				if v.s.ch.state != chanStateClosed {
   102  					v.s.ch.state = chanStateEmpty
   103  				}
   104  			} else {
   105  				// send operation
   106  				if v.s.ch.bufUsed == 0 {
   107  					// unbuffered channel
   108  					v.s.ch.state = chanStateEmpty
   109  				} else {
   110  					// buffered channel
   111  					v.s.ch.state = chanStateBuf
   112  				}
   113  			}
   114  		}
   115  		chanDebug(v.s.ch)
   116  	}
   117  }
   118  
   119  type channel struct {
   120  	elementSize uintptr // the size of one value in this channel
   121  	bufSize     uintptr // size of buffer (in elements)
   122  	state       chanState
   123  	blocked     *channelBlockedList
   124  	bufHead     uintptr        // head index of buffer (next push index)
   125  	bufTail     uintptr        // tail index of buffer (next pop index)
   126  	bufUsed     uintptr        // number of elements currently in buffer
   127  	buf         unsafe.Pointer // pointer to first element of buffer
   128  }
   129  
   130  // chanMake creates a new channel with the given element size and buffer length in number of elements.
   131  // This is a compiler intrinsic.
   132  func chanMake(elementSize uintptr, bufSize uintptr) *channel {
   133  	return &channel{
   134  		elementSize: elementSize,
   135  		bufSize:     bufSize,
   136  		buf:         alloc(elementSize*bufSize, nil),
   137  	}
   138  }
   139  
   140  // Return the number of entries in this chan, called from the len builtin.
   141  // A nil chan is defined as having length 0.
   142  //
   143  //go:inline
   144  func chanLen(c *channel) int {
   145  	if c == nil {
   146  		return 0
   147  	}
   148  	return int(c.bufUsed)
   149  }
   150  
   151  // wrapper for use in reflect
   152  func chanLenUnsafePointer(p unsafe.Pointer) int {
   153  	c := (*channel)(p)
   154  	return chanLen(c)
   155  }
   156  
   157  // Return the capacity of this chan, called from the cap builtin.
   158  // A nil chan is defined as having capacity 0.
   159  //
   160  //go:inline
   161  func chanCap(c *channel) int {
   162  	if c == nil {
   163  		return 0
   164  	}
   165  	return int(c.bufSize)
   166  }
   167  
   168  // wrapper for use in reflect
   169  func chanCapUnsafePointer(p unsafe.Pointer) int {
   170  	c := (*channel)(p)
   171  	return chanCap(c)
   172  }
   173  
   174  // resumeRX resumes the next receiver and returns the destination pointer.
   175  // If the ok value is true, then the caller is expected to store a value into this pointer.
   176  func (ch *channel) resumeRX(ok bool) unsafe.Pointer {
   177  	// pop a blocked goroutine off the stack
   178  	var b *channelBlockedList
   179  	b, ch.blocked = ch.blocked, ch.blocked.next
   180  
   181  	// get destination pointer
   182  	dst := b.t.Ptr
   183  
   184  	if !ok {
   185  		// the result value is zero
   186  		memzero(dst, ch.elementSize)
   187  		b.t.Data = 0
   188  	}
   189  
   190  	if b.s != nil {
   191  		// tell the select op which case resumed
   192  		b.t.Ptr = unsafe.Pointer(b.s)
   193  
   194  		// detach associated operations
   195  		b.detach()
   196  	}
   197  
   198  	// push task onto runqueue
   199  	runqueue.Push(b.t)
   200  
   201  	return dst
   202  }
   203  
   204  // resumeTX resumes the next sender and returns the source pointer.
   205  // The caller is expected to read from the value in this pointer before yielding.
   206  func (ch *channel) resumeTX() unsafe.Pointer {
   207  	// pop a blocked goroutine off the stack
   208  	var b *channelBlockedList
   209  	b, ch.blocked = ch.blocked, ch.blocked.next
   210  
   211  	// get source pointer
   212  	src := b.t.Ptr
   213  
   214  	if b.s != nil {
   215  		// use state's source pointer
   216  		src = b.s.value
   217  
   218  		// tell the select op which case resumed
   219  		b.t.Ptr = unsafe.Pointer(b.s)
   220  
   221  		// detach associated operations
   222  		b.detach()
   223  	}
   224  
   225  	// push task onto runqueue
   226  	runqueue.Push(b.t)
   227  
   228  	return src
   229  }
   230  
   231  // push value to end of channel if space is available
   232  // returns whether there was space for the value in the buffer
   233  func (ch *channel) push(value unsafe.Pointer) bool {
   234  	// immediately return false if the channel is not buffered
   235  	if ch.bufSize == 0 {
   236  		return false
   237  	}
   238  
   239  	// ensure space is available
   240  	if ch.bufUsed == ch.bufSize {
   241  		return false
   242  	}
   243  
   244  	// copy value to buffer
   245  	memcpy(
   246  		unsafe.Add(ch.buf, // pointer to the base of the buffer + offset = pointer to destination element
   247  			ch.elementSize*ch.bufHead), // element size * equivalent slice index = offset
   248  		value,
   249  		ch.elementSize,
   250  	)
   251  
   252  	// update buffer state
   253  	ch.bufUsed++
   254  	ch.bufHead++
   255  	if ch.bufHead == ch.bufSize {
   256  		ch.bufHead = 0
   257  	}
   258  
   259  	return true
   260  }
   261  
   262  // pop value from channel buffer if one is available
   263  // returns whether a value was popped or not
   264  // result is stored into value pointer
   265  func (ch *channel) pop(value unsafe.Pointer) bool {
   266  	// channel is empty
   267  	if ch.bufUsed == 0 {
   268  		return false
   269  	}
   270  
   271  	// compute address of source
   272  	addr := unsafe.Add(ch.buf, (ch.elementSize * ch.bufTail))
   273  
   274  	// copy value from buffer
   275  	memcpy(
   276  		value,
   277  		addr,
   278  		ch.elementSize,
   279  	)
   280  
   281  	// zero buffer element to allow garbage collection of value
   282  	memzero(
   283  		addr,
   284  		ch.elementSize,
   285  	)
   286  
   287  	// update buffer state
   288  	ch.bufUsed--
   289  
   290  	// move tail up
   291  	ch.bufTail++
   292  	if ch.bufTail == ch.bufSize {
   293  		ch.bufTail = 0
   294  	}
   295  
   296  	return true
   297  }
   298  
   299  // try to send a value to a channel, without actually blocking
   300  // returns whether the value was sent
   301  // will panic if channel is closed
   302  func (ch *channel) trySend(value unsafe.Pointer) bool {
   303  	if ch == nil {
   304  		// send to nil channel blocks forever
   305  		// this is non-blocking, so just say no
   306  		return false
   307  	}
   308  
   309  	i := interrupt.Disable()
   310  
   311  	switch ch.state {
   312  	case chanStateEmpty, chanStateBuf:
   313  		// try to dump the value directly into the buffer
   314  		if ch.push(value) {
   315  			ch.state = chanStateBuf
   316  			interrupt.Restore(i)
   317  			return true
   318  		}
   319  		interrupt.Restore(i)
   320  		return false
   321  	case chanStateRecv:
   322  		// unblock receiver
   323  		dst := ch.resumeRX(true)
   324  
   325  		// copy value to receiver
   326  		memcpy(dst, value, ch.elementSize)
   327  
   328  		// change state to empty if there are no more receivers
   329  		if ch.blocked == nil {
   330  			ch.state = chanStateEmpty
   331  		}
   332  
   333  		interrupt.Restore(i)
   334  		return true
   335  	case chanStateSend:
   336  		// something else is already waiting to send
   337  		interrupt.Restore(i)
   338  		return false
   339  	case chanStateClosed:
   340  		interrupt.Restore(i)
   341  		runtimePanic("send on closed channel")
   342  	default:
   343  		interrupt.Restore(i)
   344  		runtimePanic("invalid channel state")
   345  	}
   346  
   347  	interrupt.Restore(i)
   348  	return false
   349  }
   350  
   351  // try to receive a value from a channel, without really blocking
   352  // returns whether a value was received
   353  // second return is the comma-ok value
   354  func (ch *channel) tryRecv(value unsafe.Pointer) (bool, bool) {
   355  	if ch == nil {
   356  		// receive from nil channel blocks forever
   357  		// this is non-blocking, so just say no
   358  		return false, false
   359  	}
   360  
   361  	i := interrupt.Disable()
   362  
   363  	switch ch.state {
   364  	case chanStateBuf, chanStateSend:
   365  		// try to pop the value directly from the buffer
   366  		if ch.pop(value) {
   367  			// unblock next sender if applicable
   368  			if ch.blocked != nil {
   369  				src := ch.resumeTX()
   370  
   371  				// push sender's value into buffer
   372  				ch.push(src)
   373  
   374  				if ch.blocked == nil {
   375  					// last sender unblocked - update state
   376  					ch.state = chanStateBuf
   377  				}
   378  			}
   379  
   380  			if ch.bufUsed == 0 {
   381  				// channel empty - update state
   382  				ch.state = chanStateEmpty
   383  			}
   384  
   385  			interrupt.Restore(i)
   386  			return true, true
   387  		} else if ch.blocked != nil {
   388  			// unblock next sender if applicable
   389  			src := ch.resumeTX()
   390  
   391  			// copy sender's value
   392  			memcpy(value, src, ch.elementSize)
   393  
   394  			if ch.blocked == nil {
   395  				// last sender unblocked - update state
   396  				ch.state = chanStateEmpty
   397  			}
   398  
   399  			interrupt.Restore(i)
   400  			return true, true
   401  		}
   402  		interrupt.Restore(i)
   403  		return false, false
   404  	case chanStateRecv, chanStateEmpty:
   405  		// something else is already waiting to receive
   406  		interrupt.Restore(i)
   407  		return false, false
   408  	case chanStateClosed:
   409  		if ch.pop(value) {
   410  			interrupt.Restore(i)
   411  			return true, true
   412  		}
   413  
   414  		// channel closed - nothing to receive
   415  		memzero(value, ch.elementSize)
   416  		interrupt.Restore(i)
   417  		return true, false
   418  	default:
   419  		runtimePanic("invalid channel state")
   420  	}
   421  
   422  	runtimePanic("unreachable")
   423  	return false, false
   424  }
   425  
   426  type chanState uint8
   427  
   428  const (
   429  	chanStateEmpty  chanState = iota // nothing in channel, no senders/receivers
   430  	chanStateRecv                    // nothing in channel, receivers waiting
   431  	chanStateSend                    // senders waiting, buffer full if present
   432  	chanStateBuf                     // buffer not empty, no senders waiting
   433  	chanStateClosed                  // channel closed
   434  )
   435  
   436  func (s chanState) String() string {
   437  	switch s {
   438  	case chanStateEmpty:
   439  		return "empty"
   440  	case chanStateRecv:
   441  		return "recv"
   442  	case chanStateSend:
   443  		return "send"
   444  	case chanStateBuf:
   445  		return "buffered"
   446  	case chanStateClosed:
   447  		return "closed"
   448  	default:
   449  		return "invalid"
   450  	}
   451  }
   452  
   453  // chanSelectState is a single channel operation (send/recv) in a select
   454  // statement. The value pointer is either nil (for receives) or points to the
   455  // value to send (for sends).
   456  type chanSelectState struct {
   457  	ch    *channel
   458  	value unsafe.Pointer
   459  }
   460  
   461  // chanSend sends a single value over the channel.
   462  // This operation will block unless a value is immediately available.
   463  // May panic if the channel is closed.
   464  func chanSend(ch *channel, value unsafe.Pointer, blockedlist *channelBlockedList) {
   465  	i := interrupt.Disable()
   466  
   467  	if ch.trySend(value) {
   468  		// value immediately sent
   469  		chanDebug(ch)
   470  		interrupt.Restore(i)
   471  		return
   472  	}
   473  
   474  	if ch == nil {
   475  		// A nil channel blocks forever. Do not schedule this goroutine again.
   476  		interrupt.Restore(i)
   477  		deadlock()
   478  	}
   479  
   480  	// wait for receiver
   481  	sender := task.Current()
   482  	ch.state = chanStateSend
   483  	sender.Ptr = value
   484  	*blockedlist = channelBlockedList{
   485  		next: ch.blocked,
   486  		t:    sender,
   487  	}
   488  	ch.blocked = blockedlist
   489  	chanDebug(ch)
   490  	interrupt.Restore(i)
   491  	task.Pause()
   492  	sender.Ptr = nil
   493  }
   494  
   495  // chanRecv receives a single value over a channel.
   496  // It blocks if there is no available value to receive.
   497  // The received value is copied into the value pointer.
   498  // Returns the comma-ok value.
   499  func chanRecv(ch *channel, value unsafe.Pointer, blockedlist *channelBlockedList) bool {
   500  	i := interrupt.Disable()
   501  
   502  	if rx, ok := ch.tryRecv(value); rx {
   503  		// value immediately available
   504  		chanDebug(ch)
   505  		interrupt.Restore(i)
   506  		return ok
   507  	}
   508  
   509  	if ch == nil {
   510  		// A nil channel blocks forever. Do not schedule this goroutine again.
   511  		interrupt.Restore(i)
   512  		deadlock()
   513  	}
   514  
   515  	// wait for a value
   516  	receiver := task.Current()
   517  	ch.state = chanStateRecv
   518  	receiver.Ptr, receiver.Data = value, 1
   519  	*blockedlist = channelBlockedList{
   520  		next: ch.blocked,
   521  		t:    receiver,
   522  	}
   523  	ch.blocked = blockedlist
   524  	chanDebug(ch)
   525  	interrupt.Restore(i)
   526  	task.Pause()
   527  	ok := receiver.Data == 1
   528  	receiver.Ptr, receiver.Data = nil, 0
   529  	return ok
   530  }
   531  
   532  // chanClose closes the given channel. If this channel has a receiver or is
   533  // empty, it closes the channel. Else, it panics.
   534  func chanClose(ch *channel) {
   535  	if ch == nil {
   536  		// Not allowed by the language spec.
   537  		runtimePanic("close of nil channel")
   538  	}
   539  	i := interrupt.Disable()
   540  	switch ch.state {
   541  	case chanStateClosed:
   542  		// Not allowed by the language spec.
   543  		interrupt.Restore(i)
   544  		runtimePanic("close of closed channel")
   545  	case chanStateSend:
   546  		// This panic should ideally on the sending side, not in this goroutine.
   547  		// But when a goroutine tries to send while the channel is being closed,
   548  		// that is clearly invalid: the send should have been completed already
   549  		// before the close.
   550  		interrupt.Restore(i)
   551  		runtimePanic("close channel during send")
   552  	case chanStateRecv:
   553  		// unblock all receivers with the zero value
   554  		ch.state = chanStateClosed
   555  		for ch.blocked != nil {
   556  			ch.resumeRX(false)
   557  		}
   558  	case chanStateEmpty, chanStateBuf:
   559  		// Easy case. No available sender or receiver.
   560  	}
   561  	ch.state = chanStateClosed
   562  	interrupt.Restore(i)
   563  	chanDebug(ch)
   564  }
   565  
   566  // chanSelect is the runtime implementation of the select statement. This is
   567  // perhaps the most complicated statement in the Go spec. It returns the
   568  // selected index and the 'comma-ok' value.
   569  //
   570  // TODO: do this in a round-robin fashion (as specified in the Go spec) instead
   571  // of picking the first one that can proceed.
   572  func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelBlockedList) (uintptr, bool) {
   573  	istate := interrupt.Disable()
   574  
   575  	if selected, ok := tryChanSelect(recvbuf, states); selected != ^uintptr(0) {
   576  		// one channel was immediately ready
   577  		interrupt.Restore(istate)
   578  		return selected, ok
   579  	}
   580  
   581  	// construct blocked operations
   582  	for i, v := range states {
   583  		if v.ch == nil {
   584  			// A nil channel receive will never complete.
   585  			// A nil channel send would have panicked during tryChanSelect.
   586  			ops[i] = channelBlockedList{}
   587  			continue
   588  		}
   589  
   590  		ops[i] = channelBlockedList{
   591  			next:         v.ch.blocked,
   592  			t:            task.Current(),
   593  			s:            &states[i],
   594  			allSelectOps: ops,
   595  		}
   596  		v.ch.blocked = &ops[i]
   597  		if v.value == nil {
   598  			// recv
   599  			switch v.ch.state {
   600  			case chanStateEmpty:
   601  				v.ch.state = chanStateRecv
   602  			case chanStateRecv:
   603  				// already in correct state
   604  			default:
   605  				interrupt.Restore(istate)
   606  				runtimePanic("invalid channel state")
   607  			}
   608  		} else {
   609  			// send
   610  			switch v.ch.state {
   611  			case chanStateEmpty:
   612  				v.ch.state = chanStateSend
   613  			case chanStateSend:
   614  				// already in correct state
   615  			case chanStateBuf:
   616  				// already in correct state
   617  			default:
   618  				interrupt.Restore(istate)
   619  				runtimePanic("invalid channel state")
   620  			}
   621  		}
   622  		chanDebug(v.ch)
   623  	}
   624  
   625  	// expose rx buffer
   626  	t := task.Current()
   627  	t.Ptr = recvbuf
   628  	t.Data = 1
   629  
   630  	// wait for one case to fire
   631  	interrupt.Restore(istate)
   632  	task.Pause()
   633  
   634  	// figure out which one fired and return the ok value
   635  	return (uintptr(t.Ptr) - uintptr(unsafe.Pointer(&states[0]))) / unsafe.Sizeof(chanSelectState{}), t.Data != 0
   636  }
   637  
   638  // tryChanSelect is like chanSelect, but it does a non-blocking select operation.
   639  func tryChanSelect(recvbuf unsafe.Pointer, states []chanSelectState) (uintptr, bool) {
   640  	istate := interrupt.Disable()
   641  
   642  	// See whether we can receive from one of the channels.
   643  	for i, state := range states {
   644  		if state.value == nil {
   645  			// A receive operation.
   646  			if rx, ok := state.ch.tryRecv(recvbuf); rx {
   647  				chanDebug(state.ch)
   648  				interrupt.Restore(istate)
   649  				return uintptr(i), ok
   650  			}
   651  		} else {
   652  			// A send operation: state.value is not nil.
   653  			if state.ch.trySend(state.value) {
   654  				chanDebug(state.ch)
   655  				interrupt.Restore(istate)
   656  				return uintptr(i), true
   657  			}
   658  		}
   659  	}
   660  
   661  	interrupt.Restore(istate)
   662  	return ^uintptr(0), false
   663  }