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 }