channel.go raw
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 "moxie/loader"
13 "golang.org/x/tools/go/ssa"
14 "tinygo.org/x/go-llvm"
15 )
16
17 func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
18 elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem()))
19 elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false)
20 bufSize := b.getValue(expr.Size, getPos(expr))
21 b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
22 if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
23 bufSize = b.CreateZExt(bufSize, b.uintptrType, "")
24 } else if bufSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
25 bufSize = b.CreateTrunc(bufSize, b.uintptrType, "")
26 }
27 ch := b.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
28 if b.PrintAllocs != nil {
29 b.emitLogAlloc(expr.Pos())
30 }
31 return ch
32 }
33
34 // createChanSend emits a pseudo chan send operation. It is lowered to the
35 // actual channel send operation during goroutine lowering.
36 func (b *builder) createChanSend(instr *ssa.Send) {
37 // Wasm spawn-boundary SAB channel: bypass runtime, call bridge directly.
38 if b.isWasmTarget() {
39 if handle, ok := b.sabChannels[instr.Chan]; ok {
40 b.createWasmSABChanSend(instr, handle)
41 return
42 }
43 }
44 // Native: any chan whose element type implements moxie.Codec routes
45 // through ChanSendDual at runtime, which dispatches on ch.pipeBoundFd.
46 // This preserves correctness through array / slice / struct-field
47 // indirection where the compiler's per-SSA-value tracking can't see
48 // that the chan was originally passed to spawn().
49 if b.elemTypeImplementsCodec(instr.X.Type()) {
50 b.createCodecChanSend(instr)
51 return
52 }
53
54 ch := b.getValue(instr.Chan, getPos(instr))
55 chanValue := b.getValue(instr.X, getPos(instr))
56
57 // store value-to-send
58 valueType := b.getLLVMType(instr.X.Type())
59 isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
60 var valueAlloca, valueAllocaSize llvm.Value
61 if isZeroSize {
62 valueAlloca = llvm.ConstNull(b.dataPtrType)
63 } else {
64 valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
65 b.CreateStore(chanValue, valueAlloca)
66 }
67
68 // Allocate buffer for the channel operation.
69 channelOp := b.getLLVMRuntimeType("channelOp")
70 channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
71
72 // Do the send.
73 b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
74
75 // End the lifetime of the allocas.
76 // This also works around a bug in CoroSplit, at least in LLVM 8:
77 // https://bugs.llvm.org/show_bug.cgi?id=41742
78 b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
79 if !isZeroSize {
80 b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
81 }
82 }
83
84 // createChanRecv emits a pseudo chan receive operation. It is lowered to the
85 // actual channel receive operation during goroutine lowering.
86 func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
87 // Wasm spawn-boundary SAB channel.
88 if b.isWasmTarget() {
89 if handle, ok := b.sabChannels[unop.X]; ok {
90 return b.createWasmSABChanRecv(unop, handle)
91 }
92 }
93 // Native: dual-path runtime dispatch for Codec-implementing types.
94 chanType := unop.X.Type().Underlying().(*types.Chan)
95 if b.elemTypeImplementsCodec(chanType.Elem()) {
96 return b.createCodecChanRecv(unop)
97 }
98
99 valueType := b.getLLVMType(chanType.Elem())
100 ch := b.getValue(unop.X, getPos(unop))
101
102 // Allocate memory to receive into.
103 isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
104 var valueAlloca, valueAllocaSize llvm.Value
105 if isZeroSize {
106 valueAlloca = llvm.ConstNull(b.dataPtrType)
107 } else {
108 valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
109 }
110
111 // Allocate buffer for the channel operation.
112 channelOp := b.getLLVMRuntimeType("channelOp")
113 channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
114
115 // Do the receive.
116 commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
117 var received llvm.Value
118 if isZeroSize {
119 received = llvm.ConstNull(valueType)
120 } else {
121 received = b.CreateLoad(valueType, valueAlloca, "chan.received")
122 b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
123 }
124 b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
125
126 if unop.CommaOk {
127 tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
128 tuple = b.CreateInsertValue(tuple, received, 0, "")
129 tuple = b.CreateInsertValue(tuple, commaOk, 1, "")
130 return tuple
131 } else {
132 return received
133 }
134 }
135
136 // createChanClose closes the given channel.
137 func (b *builder) createChanClose(ch llvm.Value) {
138 b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
139 }
140
141 // createWasmSABChanSend emits a bridge.channel_send call for spawn-boundary channels.
142 func (b *builder) createWasmSABChanSend(instr *ssa.Send, handle llvm.Value) {
143 chanValue := b.getValue(instr.X, getPos(instr))
144 valueType := b.getLLVMType(instr.X.Type())
145 sz := b.targetData.TypeAllocSize(valueType)
146 i32 := b.ctx.Int32Type()
147
148 if sz == 0 {
149 b.emitBridgeCall("channel_send", []llvm.Value{
150 handle,
151 llvm.ConstNull(b.dataPtrType),
152 llvm.ConstInt(i32, 0, false),
153 }, b.ctx.VoidType())
154 return
155 }
156 alloca, allocaSize := b.createTemporaryAlloca(valueType, "sab.send.val")
157 b.CreateStore(chanValue, alloca)
158 b.emitBridgeCall("channel_send", []llvm.Value{
159 handle,
160 alloca,
161 llvm.ConstInt(i32, sz, false),
162 }, b.ctx.VoidType())
163 b.emitLifetimeEnd(alloca, allocaSize)
164 }
165
166 // createWasmSABChanRecv emits a bridge.channel_recv call for spawn-boundary channels.
167 func (b *builder) createWasmSABChanRecv(unop *ssa.UnOp, handle llvm.Value) llvm.Value {
168 valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem())
169 sz := b.targetData.TypeAllocSize(valueType)
170 i32 := b.ctx.Int32Type()
171
172 alloca, allocaSize := b.createTemporaryAlloca(valueType, "sab.recv.val")
173 result := b.emitBridgeCall("channel_recv", []llvm.Value{
174 handle,
175 alloca,
176 llvm.ConstInt(i32, sz, false),
177 }, i32)
178
179 var received llvm.Value
180 if sz == 0 {
181 received = llvm.ConstNull(valueType)
182 } else {
183 received = b.CreateLoad(valueType, alloca, "sab.received")
184 b.emitLifetimeEnd(alloca, allocaSize)
185 }
186
187 if unop.CommaOk {
188 // result == -1 (all bits set as int32) means closed.
189 isOpen := b.CreateICmp(llvm.IntNE, result, llvm.ConstInt(i32, ^uint64(0), true), "sab.ok")
190 tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
191 tuple = b.CreateInsertValue(tuple, received, 0, "")
192 tuple = b.CreateInsertValue(tuple, isOpen, 1, "")
193 return tuple
194 }
195 return received
196 }
197
198 // createSelect emits all IR necessary for a select statements. That's a
199 // non-trivial amount of code because select is very complex to implement.
200 func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
201 if len(expr.States) == 0 {
202 // Shortcuts for some simple selects.
203 llvmType := b.getLLVMType(expr.Type())
204 if expr.Blocking {
205 // Blocks forever:
206 // select {}
207 b.createRuntimeCall("deadlock", nil, "")
208 return llvm.Undef(llvmType)
209 } else {
210 // No-op:
211 // select {
212 // default:
213 // }
214 retval := llvm.Undef(llvmType)
215 retval = b.CreateInsertValue(retval, llvm.ConstInt(b.intType, 0xffffffffffffffff, true), 0, "")
216 return retval // {-1, false}
217 }
218 }
219
220 const maxSelectStates = math.MaxUint32 >> 2
221 if len(expr.States) > maxSelectStates {
222 // The runtime code assumes that the number of state must fit in 30 bits
223 // (so the select index can be stored in a uint32 with two bits reserved
224 // for other purposes). It seems unlikely that a real program would have
225 // that many states, but we check for this case anyway to be sure.
226 // We use a uint32 (and not a uintptr or uint64) to avoid 64-bit atomic
227 // operations which aren't available everywhere.
228 b.addError(expr.Pos(), fmt.Sprintf("too many select states: got %d but the maximum supported number is %d", len(expr.States), maxSelectStates))
229
230 // Continue as usual (we'll generate broken code but the error will
231 // prevent the compilation to complete).
232 }
233
234 // This code create a (stack-allocated) slice containing all the select
235 // cases and then calls runtime.chanSelect to perform the actual select
236 // statement.
237 // Simple selects (blocking and with just one case) are already transformed
238 // into regular chan operations during SSA construction so we don't have to
239 // optimize such small selects.
240
241 // Go through all the cases. Create the selectStates slice and and
242 // determine the receive buffer size and alignment.
243 recvbufSize := uint64(0)
244 recvbufAlign := 0
245 var selectStates []llvm.Value
246 chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
247 for _, state := range expr.States {
248 ch := b.getValue(state.Chan, state.Pos)
249 selectState := llvm.ConstNull(chanSelectStateType)
250 selectState = b.CreateInsertValue(selectState, ch, 0, "")
251 switch state.Dir {
252 case types.RecvOnly:
253 // Make sure the receive buffer is big enough and has the correct alignment.
254 llvmType := b.getLLVMType(state.Chan.Type().Underlying().(*types.Chan).Elem())
255 if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize {
256 recvbufSize = size
257 }
258 if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
259 recvbufAlign = align
260 }
261 case types.SendOnly:
262 // Store this value in an alloca and put a pointer to this alloca
263 // in the send state.
264 sendValue := b.getValue(state.Send, state.Pos)
265 alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
266 b.CreateStore(sendValue, alloca)
267 selectState = b.CreateInsertValue(selectState, alloca, 1, "")
268 default:
269 panic("unreachable")
270 }
271 selectStates = append(selectStates, selectState)
272 }
273
274 // Create a receive buffer, where the received value will be stored.
275 recvbuf := llvm.Undef(b.dataPtrType)
276 if recvbufSize != 0 {
277 allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
278 recvbufAlloca, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
279 recvbufAlloca.SetAlignment(recvbufAlign)
280 recvbuf = b.CreateGEP(allocaType, recvbufAlloca, []llvm.Value{
281 llvm.ConstInt(b.ctx.Int32Type(), 0, false),
282 llvm.ConstInt(b.ctx.Int32Type(), 0, false),
283 }, "select.recvbuf")
284 }
285
286 // Create the states slice (allocated on the stack).
287 statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
288 statesAlloca, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
289 for i, state := range selectStates {
290 // Set each slice element to the appropriate channel.
291 gep := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{
292 llvm.ConstInt(b.ctx.Int32Type(), 0, false),
293 llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
294 }, "")
295 b.CreateStore(state, gep)
296 }
297 statesPtr := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{
298 llvm.ConstInt(b.ctx.Int32Type(), 0, false),
299 llvm.ConstInt(b.ctx.Int32Type(), 0, false),
300 }, "select.states")
301 statesLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
302
303 // Do the select in the runtime.
304 var results llvm.Value
305 if expr.Blocking {
306 // Stack-allocate operation structures.
307 // If these were simply created as a slice, they would heap-allocate.
308 opsAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelOp"), len(selectStates))
309 opsAlloca, opsSize := b.createTemporaryAlloca(opsAllocaType, "select.block.alloca")
310 opsLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
311 opsPtr := b.CreateGEP(opsAllocaType, opsAlloca, []llvm.Value{
312 llvm.ConstInt(b.ctx.Int32Type(), 0, false),
313 llvm.ConstInt(b.ctx.Int32Type(), 0, false),
314 }, "select.block")
315
316 results = b.createRuntimeCall("chanSelect", []llvm.Value{
317 recvbuf,
318 statesPtr, statesLen, statesLen, // []chanSelectState
319 opsPtr, opsLen, opsLen, // []channelOp
320 }, "select.result")
321
322 // Terminate the lifetime of the operation structures.
323 b.emitLifetimeEnd(opsAlloca, opsSize)
324 } else {
325 opsPtr := llvm.ConstNull(b.dataPtrType)
326 opsLen := llvm.ConstInt(b.uintptrType, 0, false)
327 results = b.createRuntimeCall("chanSelect", []llvm.Value{
328 recvbuf,
329 statesPtr, statesLen, statesLen, // []chanSelectState
330 opsPtr, opsLen, opsLen, // []channelOp (nil slice)
331 }, "select.result")
332 }
333
334 // Terminate the lifetime of the states alloca.
335 b.emitLifetimeEnd(statesAlloca, statesSize)
336
337 // The result value does not include all the possible received values,
338 // because we can't load them in advance. Instead, the *ssa.Extract
339 // instruction will treat a *ssa.Select specially and load it there inline.
340 // Store the receive alloca in a sidetable until we hit this extract
341 // instruction.
342 if b.selectRecvBuf == nil {
343 b.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
344 }
345 b.selectRecvBuf[expr] = recvbuf
346
347 return results
348 }
349
350 // getChanSelectResult returns the special values from a *ssa.Extract expression
351 // when extracting a value from a select statement (*ssa.Select). Because
352 // *ssa.Select cannot load all values in advance, it does this later in the
353 // *ssa.Extract expression.
354 func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
355 if expr.Index == 0 {
356 // index
357 value := b.getValue(expr.Tuple, getPos(expr))
358 index := b.CreateExtractValue(value, expr.Index, "")
359 if index.Type().IntTypeWidth() < b.intType.IntTypeWidth() {
360 index = b.CreateSExt(index, b.intType, "")
361 }
362 return index
363 } else if expr.Index == 1 {
364 // comma-ok
365 value := b.getValue(expr.Tuple, getPos(expr))
366 return b.CreateExtractValue(value, expr.Index, "")
367 } else {
368 // Select statements are (index, ok, ...) where ... is a number of
369 // received values, depending on how many receive statements there
370 // are. They are all combined into one alloca (because only one
371 // receive can proceed at a time) so we'll get that alloca, bitcast
372 // it to the correct type, and dereference it.
373 recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
374 typ := b.getLLVMType(expr.Type())
375 return b.CreateLoad(typ, recvbuf, "")
376 }
377 }
378
379 // createNativePipeChanSend emits the native spawn-boundary chan send
380 // path. Wraps the value as a moxie.Codec interface (with *T as the
381 // underlying value so both value- and pointer-receiver methods are
382 // dispatchable) and calls runtime.PipeChanSendCodec, which invokes
383 // the user-defined EncodeTo to serialize correctly across the fork.
384 //
385 // Codec replaces raw-memcpy: types containing slices, maps, or other
386 // indirect data now serialize their byte payload, not just their
387 // pointer headers. The compiler's stack allocas stay T-sized (no
388 // quadratic codegen blowup from huge inline allocas) because EncodeTo
389 // emits bytes incrementally.
390 // createCodecChanSend emits the dual-path chan send for any chan whose
391 // element type implements moxie.Codec. The runtime helper ChanSendDual
392 // branches on ch.pipeBoundFd: if pipe-bound, EncodeTo then PipeSend; if
393 // local, the existing chanSend in-runtime queue path.
394 //
395 // Compile-time emission shape:
396 // alloca v: T // store the value-to-send
397 // alloca op: channelOp // for local-queue path
398 // codec := iface{*T, &v}
399 // ChanSendDual(ch, codec, &v, &op)
400 func (b *builder) createCodecChanSend(instr *ssa.Send) {
401 ch := b.getValue(instr.Chan, getPos(instr))
402 chanValue := b.getValue(instr.X, getPos(instr))
403 elemType := instr.X.Type()
404 elemLLVM := b.getLLVMType(elemType)
405
406 valAlloca, valAllocaSize := b.createTemporaryAlloca(elemLLVM, "chan.value")
407 b.CreateStore(chanValue, valAlloca)
408 codecIface := b.createPipeChanCodecIface(elemType, valAlloca)
409
410 channelOpType := b.getLLVMRuntimeType("channelOp")
411 opAlloca, opAllocaSize := b.createTemporaryAlloca(channelOpType, "chan.op")
412
413 runtimePkg := b.program.ImportedPackage("runtime")
414 sendFn := runtimePkg.Members["ChanSendDual"].(*ssa.Function)
415 sendType, sendLLVM := b.getFunction(sendFn)
416
417 args := []llvm.Value{ch}
418 args = append(args, b.expandFormalParam(codecIface)...)
419 args = append(args, valAlloca, opAlloca)
420 args = append(args, llvm.Undef(b.dataPtrType))
421 b.createCall(sendType, sendLLVM, args, "")
422
423 b.emitLifetimeEnd(opAlloca, opAllocaSize)
424 b.emitLifetimeEnd(valAlloca, valAllocaSize)
425 }
426
427 // createCodecChanRecv emits the dual-path chan recv counterpart of
428 // createCodecChanSend.
429 func (b *builder) createCodecChanRecv(unop *ssa.UnOp) llvm.Value {
430 elemType := unop.X.Type().Underlying().(*types.Chan).Elem()
431 elemLLVM := b.getLLVMType(elemType)
432 ch := b.getValue(unop.X, getPos(unop))
433
434 valAlloca, valAllocaSize := b.createTemporaryAlloca(elemLLVM, "chan.value")
435 b.CreateStore(llvm.ConstNull(elemLLVM), valAlloca)
436 codecIface := b.createPipeChanCodecIface(elemType, valAlloca)
437
438 channelOpType := b.getLLVMRuntimeType("channelOp")
439 opAlloca, opAllocaSize := b.createTemporaryAlloca(channelOpType, "chan.op")
440
441 runtimePkg := b.program.ImportedPackage("runtime")
442 recvFn := runtimePkg.Members["ChanRecvDual"].(*ssa.Function)
443 recvType, recvLLVM := b.getFunction(recvFn)
444
445 args := []llvm.Value{ch}
446 args = append(args, b.expandFormalParam(codecIface)...)
447 args = append(args, valAlloca, opAlloca)
448 args = append(args, llvm.Undef(b.dataPtrType))
449 okBool := b.createCall(recvType, recvLLVM, args, "chan.recv.ok")
450
451 received := b.CreateLoad(elemLLVM, valAlloca, "chan.received")
452 b.emitLifetimeEnd(opAlloca, opAllocaSize)
453 b.emitLifetimeEnd(valAlloca, valAllocaSize)
454
455 if unop.CommaOk {
456 tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{elemLLVM, b.ctx.Int1Type()}, false))
457 tuple = b.CreateInsertValue(tuple, received, 0, "")
458 tuple = b.CreateInsertValue(tuple, okBool, 1, "")
459 return tuple
460 }
461 return received
462 }
463
464 // elemTypeImplementsCodec reports whether the given chan element type
465 // satisfies the moxie.Codec interface (or *T does — covers value- and
466 // pointer-receiver methods). Used to decide whether to emit the dual-
467 // path chan op or the local-only path.
468 func (b *builder) elemTypeImplementsCodec(t types.Type) bool {
469 if ch, ok := t.Underlying().(*types.Chan); ok {
470 t = ch.Elem()
471 }
472 moxiePkg := b.program.ImportedPackage("moxie")
473 if moxiePkg == nil {
474 return false
475 }
476 codecObj := moxiePkg.Pkg.Scope().Lookup("Codec")
477 if codecObj == nil {
478 return false
479 }
480 codecIface, ok := codecObj.Type().Underlying().(*types.Interface)
481 if !ok {
482 return false
483 }
484 return loader.ImplementsCodecIface(t, codecIface)
485 }
486
487 // createPipeChanCodecIface builds a moxie.Codec interface value whose
488 // typecode is *T (so value-receiver and pointer-receiver methods on T
489 // both dispatch correctly) and whose valuePtr points at the supplied
490 // alloca holding (or to be filled with) a T value.
491 //
492 // Avoids createMakeInterface — that would heap-allocate the value via
493 // emitPointerPack for non-pointer-typed inputs. Here valPtr is already
494 // a pointer; we splice it directly into the _interface struct.
495 func (b *builder) createPipeChanCodecIface(elemType types.Type, valPtr llvm.Value) llvm.Value {
496 ptrType := types.NewPointer(elemType)
497 typecode := b.getTypeCode(ptrType)
498 itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
499 itf = b.CreateInsertValue(itf, typecode, 0, "")
500 itf = b.CreateInsertValue(itf, valPtr, 1, "")
501 return itf
502 }
503