github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/compiler/channel.go (about)

     1  package compiler
     2  
     3  // This file lowers channel operations (make/send/recv/close) to runtime calls
     4  // or pseudo-operations that are lowered during goroutine lowering.
     5  
     6  import (
     7  	"go/types"
     8  
     9  	"github.com/tinygo-org/tinygo/compiler/llvmutil"
    10  	"golang.org/x/tools/go/ssa"
    11  	"tinygo.org/x/go-llvm"
    12  )
    13  
    14  func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
    15  	elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem()))
    16  	elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false)
    17  	bufSize := b.getValue(expr.Size, getPos(expr))
    18  	b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
    19  	if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
    20  		bufSize = b.CreateZExt(bufSize, b.uintptrType, "")
    21  	} else if bufSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
    22  		bufSize = b.CreateTrunc(bufSize, b.uintptrType, "")
    23  	}
    24  	return b.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
    25  }
    26  
    27  // createChanSend emits a pseudo chan send operation. It is lowered to the
    28  // actual channel send operation during goroutine lowering.
    29  func (b *builder) createChanSend(instr *ssa.Send) {
    30  	ch := b.getValue(instr.Chan, getPos(instr))
    31  	chanValue := b.getValue(instr.X, getPos(instr))
    32  
    33  	// store value-to-send
    34  	valueType := b.getLLVMType(instr.X.Type())
    35  	isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
    36  	var valueAlloca, valueAllocaSize llvm.Value
    37  	if isZeroSize {
    38  		valueAlloca = llvm.ConstNull(b.dataPtrType)
    39  	} else {
    40  		valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
    41  		b.CreateStore(chanValue, valueAlloca)
    42  	}
    43  
    44  	// Allocate blockedlist buffer.
    45  	channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
    46  	channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
    47  
    48  	// Do the send.
    49  	b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "")
    50  
    51  	// End the lifetime of the allocas.
    52  	// This also works around a bug in CoroSplit, at least in LLVM 8:
    53  	// https://bugs.llvm.org/show_bug.cgi?id=41742
    54  	b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize)
    55  	if !isZeroSize {
    56  		b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
    57  	}
    58  }
    59  
    60  // createChanRecv emits a pseudo chan receive operation. It is lowered to the
    61  // actual channel receive operation during goroutine lowering.
    62  func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
    63  	valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem())
    64  	ch := b.getValue(unop.X, getPos(unop))
    65  
    66  	// Allocate memory to receive into.
    67  	isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
    68  	var valueAlloca, valueAllocaSize llvm.Value
    69  	if isZeroSize {
    70  		valueAlloca = llvm.ConstNull(b.dataPtrType)
    71  	} else {
    72  		valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
    73  	}
    74  
    75  	// Allocate blockedlist buffer.
    76  	channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
    77  	channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
    78  
    79  	// Do the receive.
    80  	commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "")
    81  	var received llvm.Value
    82  	if isZeroSize {
    83  		received = llvm.ConstNull(valueType)
    84  	} else {
    85  		received = b.CreateLoad(valueType, valueAlloca, "chan.received")
    86  		b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
    87  	}
    88  	b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize)
    89  
    90  	if unop.CommaOk {
    91  		tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
    92  		tuple = b.CreateInsertValue(tuple, received, 0, "")
    93  		tuple = b.CreateInsertValue(tuple, commaOk, 1, "")
    94  		return tuple
    95  	} else {
    96  		return received
    97  	}
    98  }
    99  
   100  // createChanClose closes the given channel.
   101  func (b *builder) createChanClose(ch llvm.Value) {
   102  	b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
   103  }
   104  
   105  // createSelect emits all IR necessary for a select statements. That's a
   106  // non-trivial amount of code because select is very complex to implement.
   107  func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
   108  	if len(expr.States) == 0 {
   109  		// Shortcuts for some simple selects.
   110  		llvmType := b.getLLVMType(expr.Type())
   111  		if expr.Blocking {
   112  			// Blocks forever:
   113  			//     select {}
   114  			b.createRuntimeCall("deadlock", nil, "")
   115  			return llvm.Undef(llvmType)
   116  		} else {
   117  			// No-op:
   118  			//     select {
   119  			//     default:
   120  			//     }
   121  			retval := llvm.Undef(llvmType)
   122  			retval = b.CreateInsertValue(retval, llvm.ConstInt(b.intType, 0xffffffffffffffff, true), 0, "")
   123  			return retval // {-1, false}
   124  		}
   125  	}
   126  
   127  	// This code create a (stack-allocated) slice containing all the select
   128  	// cases and then calls runtime.chanSelect to perform the actual select
   129  	// statement.
   130  	// Simple selects (blocking and with just one case) are already transformed
   131  	// into regular chan operations during SSA construction so we don't have to
   132  	// optimize such small selects.
   133  
   134  	// Go through all the cases. Create the selectStates slice and and
   135  	// determine the receive buffer size and alignment.
   136  	recvbufSize := uint64(0)
   137  	recvbufAlign := 0
   138  	var selectStates []llvm.Value
   139  	chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
   140  	for _, state := range expr.States {
   141  		ch := b.getValue(state.Chan, state.Pos)
   142  		selectState := llvm.ConstNull(chanSelectStateType)
   143  		selectState = b.CreateInsertValue(selectState, ch, 0, "")
   144  		switch state.Dir {
   145  		case types.RecvOnly:
   146  			// Make sure the receive buffer is big enough and has the correct alignment.
   147  			llvmType := b.getLLVMType(state.Chan.Type().Underlying().(*types.Chan).Elem())
   148  			if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize {
   149  				recvbufSize = size
   150  			}
   151  			if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
   152  				recvbufAlign = align
   153  			}
   154  		case types.SendOnly:
   155  			// Store this value in an alloca and put a pointer to this alloca
   156  			// in the send state.
   157  			sendValue := b.getValue(state.Send, state.Pos)
   158  			alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
   159  			b.CreateStore(sendValue, alloca)
   160  			selectState = b.CreateInsertValue(selectState, alloca, 1, "")
   161  		default:
   162  			panic("unreachable")
   163  		}
   164  		selectStates = append(selectStates, selectState)
   165  	}
   166  
   167  	// Create a receive buffer, where the received value will be stored.
   168  	recvbuf := llvm.Undef(b.dataPtrType)
   169  	if recvbufSize != 0 {
   170  		allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
   171  		recvbufAlloca, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
   172  		recvbufAlloca.SetAlignment(recvbufAlign)
   173  		recvbuf = b.CreateGEP(allocaType, recvbufAlloca, []llvm.Value{
   174  			llvm.ConstInt(b.ctx.Int32Type(), 0, false),
   175  			llvm.ConstInt(b.ctx.Int32Type(), 0, false),
   176  		}, "select.recvbuf")
   177  	}
   178  
   179  	// Create the states slice (allocated on the stack).
   180  	statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
   181  	statesAlloca, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
   182  	for i, state := range selectStates {
   183  		// Set each slice element to the appropriate channel.
   184  		gep := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{
   185  			llvm.ConstInt(b.ctx.Int32Type(), 0, false),
   186  			llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
   187  		}, "")
   188  		b.CreateStore(state, gep)
   189  	}
   190  	statesPtr := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{
   191  		llvm.ConstInt(b.ctx.Int32Type(), 0, false),
   192  		llvm.ConstInt(b.ctx.Int32Type(), 0, false),
   193  	}, "select.states")
   194  	statesLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
   195  
   196  	// Do the select in the runtime.
   197  	var results llvm.Value
   198  	if expr.Blocking {
   199  		// Stack-allocate operation structures.
   200  		// If these were simply created as a slice, they would heap-allocate.
   201  		chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
   202  		chBlockAlloca, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
   203  		chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
   204  		chBlockPtr := b.CreateGEP(chBlockAllocaType, chBlockAlloca, []llvm.Value{
   205  			llvm.ConstInt(b.ctx.Int32Type(), 0, false),
   206  			llvm.ConstInt(b.ctx.Int32Type(), 0, false),
   207  		}, "select.block")
   208  
   209  		results = b.createRuntimeCall("chanSelect", []llvm.Value{
   210  			recvbuf,
   211  			statesPtr, statesLen, statesLen, // []chanSelectState
   212  			chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
   213  		}, "select.result")
   214  
   215  		// Terminate the lifetime of the operation structures.
   216  		b.emitLifetimeEnd(chBlockAlloca, chBlockSize)
   217  	} else {
   218  		results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
   219  			recvbuf,
   220  			statesPtr, statesLen, statesLen, // []chanSelectState
   221  		}, "select.result")
   222  	}
   223  
   224  	// Terminate the lifetime of the states alloca.
   225  	b.emitLifetimeEnd(statesAlloca, statesSize)
   226  
   227  	// The result value does not include all the possible received values,
   228  	// because we can't load them in advance. Instead, the *ssa.Extract
   229  	// instruction will treat a *ssa.Select specially and load it there inline.
   230  	// Store the receive alloca in a sidetable until we hit this extract
   231  	// instruction.
   232  	if b.selectRecvBuf == nil {
   233  		b.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
   234  	}
   235  	b.selectRecvBuf[expr] = recvbuf
   236  
   237  	return results
   238  }
   239  
   240  // getChanSelectResult returns the special values from a *ssa.Extract expression
   241  // when extracting a value from a select statement (*ssa.Select). Because
   242  // *ssa.Select cannot load all values in advance, it does this later in the
   243  // *ssa.Extract expression.
   244  func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
   245  	if expr.Index == 0 {
   246  		// index
   247  		value := b.getValue(expr.Tuple, getPos(expr))
   248  		index := b.CreateExtractValue(value, expr.Index, "")
   249  		if index.Type().IntTypeWidth() < b.intType.IntTypeWidth() {
   250  			index = b.CreateSExt(index, b.intType, "")
   251  		}
   252  		return index
   253  	} else if expr.Index == 1 {
   254  		// comma-ok
   255  		value := b.getValue(expr.Tuple, getPos(expr))
   256  		return b.CreateExtractValue(value, expr.Index, "")
   257  	} else {
   258  		// Select statements are (index, ok, ...) where ... is a number of
   259  		// received values, depending on how many receive statements there
   260  		// are. They are all combined into one alloca (because only one
   261  		// receive can proceed at a time) so we'll get that alloca, bitcast
   262  		// it to the correct type, and dereference it.
   263  		recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
   264  		typ := b.getLLVMType(expr.Type())
   265  		return b.CreateLoad(typ, recvbuf, "")
   266  	}
   267  }