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  	"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