diff --git a/compiler/channel.go b/compiler/channel.go index 2e802f0f..3686c98f 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -11,79 +11,79 @@ import ( "tinygo.org/x/go-llvm" ) -func (c *Compiler) emitMakeChan(frame *Frame, expr *ssa.MakeChan) llvm.Value { - elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem())) - elementSizeValue := llvm.ConstInt(c.uintptrType, elementSize, false) - bufSize := frame.getValue(expr.Size) - frame.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos()) - if bufSize.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() { - bufSize = c.builder.CreateZExt(bufSize, c.uintptrType, "") - } else if bufSize.Type().IntTypeWidth() > c.uintptrType.IntTypeWidth() { - bufSize = c.builder.CreateTrunc(bufSize, c.uintptrType, "") +func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value { + elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().(*types.Chan).Elem())) + elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false) + bufSize := b.getValue(expr.Size) + b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos()) + if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() { + bufSize = b.CreateZExt(bufSize, b.uintptrType, "") + } else if bufSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() { + bufSize = b.CreateTrunc(bufSize, b.uintptrType, "") } - return c.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "") + return b.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "") } -// emitChanSend emits a pseudo chan send operation. It is lowered to the actual -// channel send operation during goroutine lowering. -func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) { - ch := frame.getValue(instr.Chan) - chanValue := frame.getValue(instr.X) +// createChanSend emits a pseudo chan send operation. It is lowered to the +// actual channel send operation during goroutine lowering. +func (b *builder) createChanSend(instr *ssa.Send) { + ch := b.getValue(instr.Chan) + chanValue := b.getValue(instr.X) // store value-to-send - valueType := c.getLLVMType(instr.X.Type()) - valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value") - c.builder.CreateStore(chanValue, valueAlloca) + valueType := b.getLLVMType(instr.X.Type()) + valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value") + b.CreateStore(chanValue, valueAlloca) // Do the send. - c.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "") + b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "") // End the lifetime of the alloca. // This also works around a bug in CoroSplit, at least in LLVM 8: // https://bugs.llvm.org/show_bug.cgi?id=41742 - c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) + b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) } -// emitChanRecv emits a pseudo chan receive operation. It is lowered to the +// createChanRecv emits a pseudo chan receive operation. It is lowered to the // actual channel receive operation during goroutine lowering. -func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value { - valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem()) - ch := frame.getValue(unop.X) +func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { + valueType := b.getLLVMType(unop.X.Type().(*types.Chan).Elem()) + ch := b.getValue(unop.X) // Allocate memory to receive into. - valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value") + valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value") // Do the receive. - commaOk := c.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "") - received := c.builder.CreateLoad(valueAlloca, "chan.received") - c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) + commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "") + received := b.CreateLoad(valueAlloca, "chan.received") + b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) if unop.CommaOk { - tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false)) - tuple = c.builder.CreateInsertValue(tuple, received, 0, "") - tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") + tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)) + tuple = b.CreateInsertValue(tuple, received, 0, "") + tuple = b.CreateInsertValue(tuple, commaOk, 1, "") return tuple } else { return received } } -// emitChanClose closes the given channel. -func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) { - ch := frame.getValue(param) - c.createRuntimeCall("chanClose", []llvm.Value{ch}, "") +// createChanClose closes the given channel. +func (b *builder) createChanClose(param ssa.Value) { + ch := b.getValue(param) + b.createRuntimeCall("chanClose", []llvm.Value{ch}, "") } -// emitSelect emits all IR necessary for a select statements. That's a +// createSelect emits all IR necessary for a select statements. That's a // non-trivial amount of code because select is very complex to implement. -func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value { +func (b *builder) createSelect(expr *ssa.Select) llvm.Value { if len(expr.States) == 0 { // Shortcuts for some simple selects. - llvmType := c.getLLVMType(expr.Type()) + llvmType := b.getLLVMType(expr.Type()) if expr.Blocking { // Blocks forever: // select {} - c.createRuntimeCall("deadlock", nil, "") + b.createRuntimeCall("deadlock", nil, "") return llvm.Undef(llvmType) } else { // No-op: @@ -91,7 +91,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value { // default: // } retval := llvm.Undef(llvmType) - retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "") + retval = b.CreateInsertValue(retval, llvm.ConstInt(b.intType, 0xffffffffffffffff, true), 0, "") return retval // {-1, false} } } @@ -109,30 +109,30 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value { recvbufAlign := 0 hasReceives := false var selectStates []llvm.Value - chanSelectStateType := c.getLLVMRuntimeType("chanSelectState") + chanSelectStateType := b.getLLVMRuntimeType("chanSelectState") for _, state := range expr.States { - ch := frame.getValue(state.Chan) + ch := b.getValue(state.Chan) selectState := llvm.ConstNull(chanSelectStateType) - selectState = c.builder.CreateInsertValue(selectState, ch, 0, "") + selectState = b.CreateInsertValue(selectState, ch, 0, "") switch state.Dir { case types.RecvOnly: // Make sure the receive buffer is big enough and has the correct alignment. - llvmType := c.getLLVMType(state.Chan.Type().(*types.Chan).Elem()) - if size := c.targetData.TypeAllocSize(llvmType); size > recvbufSize { + llvmType := b.getLLVMType(state.Chan.Type().(*types.Chan).Elem()) + if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize { recvbufSize = size } - if align := c.targetData.ABITypeAlignment(llvmType); align > recvbufAlign { + if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign { recvbufAlign = align } hasReceives = true case types.SendOnly: // Store this value in an alloca and put a pointer to this alloca // in the send state. - sendValue := frame.getValue(state.Send) - alloca := llvmutil.CreateEntryBlockAlloca(c.builder, sendValue.Type(), "select.send.value") - c.builder.CreateStore(sendValue, alloca) - ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "") - selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "") + sendValue := b.getValue(state.Send) + alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value") + b.CreateStore(sendValue, alloca) + ptr := b.CreateBitCast(alloca, b.i8ptrType, "") + selectState = b.CreateInsertValue(selectState, ptr, 1, "") default: panic("unreachable") } @@ -140,74 +140,74 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value { } // Create a receive buffer, where the received value will be stored. - recvbuf := llvm.Undef(c.i8ptrType) + recvbuf := llvm.Undef(b.i8ptrType) if hasReceives { - allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize)) - recvbufAlloca, _, _ := c.createTemporaryAlloca(allocaType, "select.recvbuf.alloca") + allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize)) + recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca") recvbufAlloca.SetAlignment(recvbufAlign) - recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), 0, false), + recvbuf = b.CreateGEP(recvbufAlloca, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), 0, false), }, "select.recvbuf") } // Create the states slice (allocated on the stack). statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates)) - statesAlloca, statesI8, statesSize := c.createTemporaryAlloca(statesAllocaType, "select.states.alloca") + statesAlloca, statesI8, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca") for i, state := range selectStates { // Set each slice element to the appropriate channel. - gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false), + gep := b.CreateGEP(statesAlloca, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), }, "") - c.builder.CreateStore(state, gep) + b.CreateStore(state, gep) } - statesPtr := c.builder.CreateGEP(statesAlloca, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), 0, false), + statesPtr := b.CreateGEP(statesAlloca, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), 0, false), }, "select.states") - statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false) + statesLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false) // Do the select in the runtime. var results llvm.Value if expr.Blocking { // Stack-allocate operation structures. // If these were simply created as a slice, they would heap-allocate. - chBlockAllocaType := llvm.ArrayType(c.getLLVMRuntimeType("channelBlockedList"), len(selectStates)) - chBlockAlloca, chBlockAllocaPtr, chBlockSize := c.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca") - chBlockLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false) - chBlockPtr := c.builder.CreateGEP(chBlockAlloca, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), 0, false), + chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates)) + chBlockAlloca, chBlockAllocaPtr, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca") + chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false) + chBlockPtr := b.CreateGEP(chBlockAlloca, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), 0, false), }, "select.block") - results = c.createRuntimeCall("chanSelect", []llvm.Value{ + results = b.createRuntimeCall("chanSelect", []llvm.Value{ recvbuf, statesPtr, statesLen, statesLen, // []chanSelectState chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList }, "select.result") // Terminate the lifetime of the operation structures. - c.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize) + b.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize) } else { - results = c.createRuntimeCall("tryChanSelect", []llvm.Value{ + results = b.createRuntimeCall("tryChanSelect", []llvm.Value{ recvbuf, statesPtr, statesLen, statesLen, // []chanSelectState }, "select.result") } // Terminate the lifetime of the states alloca. - c.emitLifetimeEnd(statesI8, statesSize) + b.emitLifetimeEnd(statesI8, statesSize) // The result value does not include all the possible received values, // because we can't load them in advance. Instead, the *ssa.Extract // instruction will treat a *ssa.Select specially and load it there inline. // Store the receive alloca in a sidetable until we hit this extract // instruction. - if frame.selectRecvBuf == nil { - frame.selectRecvBuf = make(map[*ssa.Select]llvm.Value) + if b.selectRecvBuf == nil { + b.selectRecvBuf = make(map[*ssa.Select]llvm.Value) } - frame.selectRecvBuf[expr] = recvbuf + b.selectRecvBuf[expr] = recvbuf return results } @@ -216,28 +216,28 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value { // when extracting a value from a select statement (*ssa.Select). Because // *ssa.Select cannot load all values in advance, it does this later in the // *ssa.Extract expression. -func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Value { +func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value { if expr.Index == 0 { // index - value := frame.getValue(expr.Tuple) - index := c.builder.CreateExtractValue(value, expr.Index, "") - if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() { - index = c.builder.CreateSExt(index, c.intType, "") + value := b.getValue(expr.Tuple) + index := b.CreateExtractValue(value, expr.Index, "") + if index.Type().IntTypeWidth() < b.intType.IntTypeWidth() { + index = b.CreateSExt(index, b.intType, "") } return index } else if expr.Index == 1 { // comma-ok - value := frame.getValue(expr.Tuple) - return c.builder.CreateExtractValue(value, expr.Index, "") + value := b.getValue(expr.Tuple) + return b.CreateExtractValue(value, expr.Index, "") } else { // Select statements are (index, ok, ...) where ... is a number of // received values, depending on how many receive statements there // are. They are all combined into one alloca (because only one // receive can proceed at a time) so we'll get that alloca, bitcast // it to the correct type, and dereference it. - recvbuf := frame.selectRecvBuf[expr.Tuple.(*ssa.Select)] - typ := llvm.PointerType(c.getLLVMType(expr.Type()), 0) - ptr := c.builder.CreateBitCast(recvbuf, typ, "") - return c.builder.CreateLoad(ptr, "") + recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)] + typ := llvm.PointerType(b.getLLVMType(expr.Type()), 0) + ptr := b.CreateBitCast(recvbuf, typ, "") + return b.CreateLoad(ptr, "") } } diff --git a/compiler/compiler.go b/compiler/compiler.go index 1ad55453..71e80eec 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1131,7 +1131,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { case *ssa.RunDefers: c.emitRunDefers(frame) case *ssa.Send: - c.emitChanSend(frame, instr) + frame.createChanSend(instr) case *ssa.Store: llvmAddr := frame.getValue(instr.Addr) llvmVal := frame.getValue(instr.Val) @@ -1188,7 +1188,7 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string, } return llvmCap, nil case "close": - c.emitChanClose(frame, args[0]) + frame.createChanClose(args[0]) return llvm.Value{}, nil case "complex": r := frame.getValue(args[0]) @@ -1510,7 +1510,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { return c.parseConvert(expr.X.Type(), expr.Type(), x, expr.Pos()) case *ssa.Extract: if _, ok := expr.Tuple.(*ssa.Select); ok { - return c.getChanSelectResult(frame, expr), nil + return frame.getChanSelectResult(expr), nil } value := frame.getValue(expr.Tuple) return c.builder.CreateExtractValue(value, expr.Index, ""), nil @@ -1626,7 +1626,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { panic("unknown lookup type: " + expr.String()) } case *ssa.MakeChan: - return c.emitMakeChan(frame, expr), nil + return frame.createMakeChan(expr), nil case *ssa.MakeClosure: return c.parseMakeClosure(frame, expr) case *ssa.MakeInterface: @@ -1726,7 +1726,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { c.builder.CreateStore(llvm.ConstNull(iteratorType), it) return it, nil case *ssa.Select: - return c.emitSelect(frame, expr), nil + return frame.createSelect(expr), nil case *ssa.Slice: value := frame.getValue(expr.X) @@ -2566,7 +2566,7 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) { case token.XOR: // ^x, toggle all bits in integer return c.builder.CreateXor(x, llvm.ConstInt(x.Type(), ^uint64(0), false), ""), nil case token.ARROW: // <-x, receive from channel - return c.emitChanRecv(frame, unop), nil + return frame.createChanRecv(unop), nil default: return llvm.Value{}, c.makeError(unop.Pos(), "todo: unknown unop") } diff --git a/compiler/llvm.go b/compiler/llvm.go index 30a96f42..2a9f6c9d 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -33,6 +33,16 @@ func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitc return llvmutil.CreateTemporaryAlloca(c.builder, c.mod, t, name) } +// createTemporaryAlloca creates a new alloca in the entry block and adds +// lifetime start infromation in the IR signalling that the alloca won't be used +// before this point. +// +// This is useful for creating temporary allocas for intrinsics. Don't forget to +// end the lifetime using emitLifetimeEnd after you're done with it. +func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) { + return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name) +} + // emitLifetimeEnd signals the end of an (alloca) lifetime by calling the // llvm.lifetime.end intrinsic. It is commonly used together with // createTemporaryAlloca. @@ -40,6 +50,13 @@ func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) { llvmutil.EmitLifetimeEnd(c.builder, c.mod, ptr, size) } +// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the +// llvm.lifetime.end intrinsic. It is commonly used together with +// createTemporaryAlloca. +func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) { + llvmutil.EmitLifetimeEnd(b.Builder, b.mod, ptr, size) +} + // emitPointerPack packs the list of values into a single pointer value using // bitcasts, or else allocates a value on the heap if it cannot be packed in the // pointer value directly. It returns the pointer with the packed data.