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 }