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