diff --git a/compiler/channel.go b/compiler/channel.go index 8f1a8603..b5bcd95b 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -23,47 +23,41 @@ func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) { // 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) { - valueType := c.getLLVMType(instr.X.Type()) ch := c.getValue(frame, instr.Chan) chanValue := c.getValue(frame, instr.X) - valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false) - coroutine := c.createRuntimeCall("getCoroutine", nil, "") // store value-to-send - c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction()) - valueAlloca := c.builder.CreateAlloca(valueType, "chan.value") - c.builder.SetInsertPointBefore(coroutine) - c.builder.SetInsertPointAtEnd(coroutine.InstructionParent()) + valueType := c.getLLVMType(instr.X.Type()) + valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value") c.builder.CreateStore(chanValue, valueAlloca) - valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr") // Do the send. + coroutine := c.createRuntimeCall("getCoroutine", nil, "") + valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false) c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "") - // Make sure CoroSplit includes the alloca in the coroutine frame. - // This is a bit dirty, but it works (at least in LLVM 8). - valueSizeI64 := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(chanValue.Type()), false) - c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSizeI64, 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) } // emitChanRecv 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()) - valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false) ch := c.getValue(frame, unop.X) - coroutine := c.createRuntimeCall("getCoroutine", nil, "") // Allocate memory to receive into. - c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction()) - valueAlloca := c.builder.CreateAlloca(valueType, "chan.value") - c.builder.SetInsertPointBefore(coroutine) - c.builder.SetInsertPointAtEnd(coroutine.InstructionParent()) - valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr") + valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value") // Do the receive. + coroutine := c.createRuntimeCall("getCoroutine", nil, "") + valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false) c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "") received := c.builder.CreateLoad(valueAlloca, "chan.received") + c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) + if unop.CommaOk { commaOk := c.createRuntimeCall("getTaskPromiseData", []llvm.Value{coroutine}, "chan.commaOk.wide") commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk") diff --git a/compiler/compiler.go b/compiler/compiler.go index 5f09e54a..aebea4c6 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1369,7 +1369,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { switch expr := expr.(type) { case *ssa.Alloc: typ := c.getLLVMType(expr.Type().Underlying().(*types.Pointer).Elem()) - var buf llvm.Value if expr.Heap { size := c.targetData.TypeAllocSize(typ) // Calculate ^uintptr(0) @@ -1380,15 +1379,16 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { } // TODO: escape analysis sizeValue := llvm.ConstInt(c.uintptrType, size, false) - buf = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, expr.Comment) + buf := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, expr.Comment) buf = c.builder.CreateBitCast(buf, llvm.PointerType(typ, 0), "") + return buf, nil } else { - buf = c.builder.CreateAlloca(typ, expr.Comment) + buf := c.createEntryBlockAlloca(typ, expr.Comment) if c.targetData.TypeAllocSize(typ) != 0 { c.builder.CreateStore(c.getZeroValue(typ), buf) // zero-initialize var } + return buf, nil } - return buf, nil case *ssa.BinOp: x := c.getValue(frame, expr.X) y := c.getValue(frame, expr.Y) @@ -1451,10 +1451,12 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { // This could be done directly, but as this is a very infrequent // operation it's much easier to bitcast it through an alloca. resultType := c.getLLVMType(expr.Type()) - alloca := c.builder.CreateAlloca(value.Type(), "") + alloca, allocaPtr, allocaSize := c.createTemporaryAlloca(value.Type(), "union.alloca") c.builder.CreateStore(value, alloca) - bitcast := c.builder.CreateBitCast(alloca, llvm.PointerType(resultType, 0), "") - return c.builder.CreateLoad(bitcast, ""), nil + bitcast := c.builder.CreateBitCast(alloca, llvm.PointerType(resultType, 0), "union.bitcast") + result := c.builder.CreateLoad(bitcast, "union.result") + c.emitLifetimeEnd(allocaPtr, allocaSize) + return result, nil } result := c.builder.CreateExtractValue(value, expr.Field, "") return result, nil @@ -1494,11 +1496,13 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { // Can't load directly from array (as index is non-constant), so have to // do it using an alloca+gep+load. - alloca := c.builder.CreateAlloca(array.Type(), "index.alloca") + alloca, allocaPtr, allocaSize := c.createTemporaryAlloca(array.Type(), "index.alloca") c.builder.CreateStore(array, alloca) zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) ptr := c.builder.CreateInBoundsGEP(alloca, []llvm.Value{zero, index}, "index.gep") - return c.builder.CreateLoad(ptr, "index.load"), nil + result := c.builder.CreateLoad(ptr, "index.load") + c.emitLifetimeEnd(allocaPtr, allocaSize) + return result, nil case *ssa.IndexAddr: val := c.getValue(frame, expr.X) index := c.getValue(frame, expr.Index) @@ -1658,16 +1662,16 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { llvmKeyType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Key()) llvmValueType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Elem()) - mapKeyAlloca := c.builder.CreateAlloca(llvmKeyType, "range.key") - mapKeyPtr := c.builder.CreateBitCast(mapKeyAlloca, c.i8ptrType, "range.keyptr") - mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "range.value") - mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "range.valueptr") + mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(llvmKeyType, "range.key") + mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "range.value") ok := c.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyPtr, mapValuePtr}, "range.next") tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{c.ctx.Int1Type(), llvmKeyType, llvmValueType}, false)) tuple = c.builder.CreateInsertValue(tuple, ok, 0, "") tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapKeyAlloca, ""), 1, "") tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapValueAlloca, ""), 2, "") + c.emitLifetimeEnd(mapKeyPtr, mapKeySize) + c.emitLifetimeEnd(mapValuePtr, mapValueSize) return tuple, nil } case *ssa.Phi: @@ -1684,7 +1688,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { default: panic("unknown type in range: " + typ.String()) } - it := c.builder.CreateAlloca(iteratorType, "range.it") + it, _, _ := c.createTemporaryAlloca(iteratorType, "range.it") c.builder.CreateStore(c.getZeroValue(iteratorType), it) return it, nil case *ssa.Select: diff --git a/compiler/llvm.go b/compiler/llvm.go index a4957345..fb52b006 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -24,23 +24,41 @@ func getUses(value llvm.Value) []llvm.Value { // createEntryBlockAlloca creates a new alloca in the entry block, even though // the IR builder is located elsewhere. It assumes that the insert point is -// after the last instruction in the current block. Also, it adds lifetime -// information to the IR signalling that the alloca won't be used before this -// point. +// at the end of the current block. +func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) llvm.Value { + currentBlock := c.builder.GetInsertBlock() + entryBlock := currentBlock.Parent().EntryBasicBlock() + if entryBlock.FirstInstruction().IsNil() { + c.builder.SetInsertPointAtEnd(entryBlock) + } else { + c.builder.SetInsertPointBefore(entryBlock.FirstInstruction()) + } + alloca := c.builder.CreateAlloca(t, name) + c.builder.SetInsertPointAtEnd(currentBlock) + return alloca +} + +// 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 after you're done with it. -func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) { - currentBlock := c.builder.GetInsertBlock() - c.builder.SetInsertPointBefore(currentBlock.Parent().EntryBasicBlock().FirstInstruction()) - alloca = c.builder.CreateAlloca(t, name) - c.builder.SetInsertPointAtEnd(currentBlock) +// end the lifetime using emitLifetimeEnd after you're done with it. +func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) { + alloca = c.createEntryBlockAlloca(t, name) bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast") size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false) c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "") return } +// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the +// llvm.lifetime.end intrinsic. It is commonly used together with +// createTemporaryAlloca. +func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) { + c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{size, ptr}, "") +} + // getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it // first if it doesn't exist yet. func (c *Compiler) getLifetimeStartFunc() llvm.Value { diff --git a/compiler/map.go b/compiler/map.go index da8e6874..3f0d9523 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -15,7 +15,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu // Allocate the memory for the resulting type. Do not zero this memory: it // will be zeroed by the hashmap get implementation if the key is not // present in the map. - mapValueAlloca, mapValuePtr, mapValueSize := c.createEntryBlockAlloca(llvmValueType, "hashmap.value") + mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "hashmap.value") // Do the lookup. How it is done depends on the key type. var commaOkValue llvm.Value @@ -27,12 +27,12 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu // key can be compared with runtime.memequal // Store the key in an alloca, in the entry block to avoid dynamic stack // growth. - mapKeyAlloca, mapKeyPtr, mapKeySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key") + mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key") c.builder.CreateStore(key, mapKeyAlloca) // Fetch the value from the hashmap. params := []llvm.Value{m, mapKeyPtr, mapValuePtr} commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "") - c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapKeySize, mapKeyPtr}, "") + c.emitLifetimeEnd(mapKeyPtr, mapKeySize) } else { // Not trivially comparable using memcmp. return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String()) @@ -41,7 +41,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu // Load the resulting value from the hashmap. The value is set to the zero // value if the key doesn't exist in the hashmap. mapValue := c.builder.CreateLoad(mapValueAlloca, "") - c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapValueSize, mapValuePtr}, "") + c.emitLifetimeEnd(mapValuePtr, mapValueSize) if commaOk { tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false)) @@ -54,7 +54,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu } func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { - valueAlloca, valuePtr, valueSize := c.createEntryBlockAlloca(value.Type(), "hashmap.value") + valueAlloca, valuePtr, valueSize := c.createTemporaryAlloca(value.Type(), "hashmap.value") c.builder.CreateStore(value, valueAlloca) keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { @@ -63,15 +63,15 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p c.createRuntimeCall("hashmapStringSet", params, "") } else if hashmapIsBinaryKey(keyType) { // key can be compared with runtime.memequal - keyAlloca, keyPtr, keySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key") + keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key") c.builder.CreateStore(key, keyAlloca) params := []llvm.Value{m, keyPtr, valuePtr} c.createRuntimeCall("hashmapBinarySet", params, "") - c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "") + c.emitLifetimeEnd(keyPtr, keySize) } else { c.addError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String()) } - c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSize, valuePtr}, "") + c.emitLifetimeEnd(valuePtr, valueSize) } func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error { @@ -82,11 +82,11 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke c.createRuntimeCall("hashmapStringDelete", params, "") return nil } else if hashmapIsBinaryKey(keyType) { - keyAlloca, keyPtr, keySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key") + keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key") c.builder.CreateStore(key, keyAlloca) params := []llvm.Value{m, keyPtr} c.createRuntimeCall("hashmapBinaryDelete", params, "") - c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "") + c.emitLifetimeEnd(keyPtr, keySize) return nil } else { return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String()) diff --git a/compiler/wordpack.go b/compiler/wordpack.go index caeb7956..b76bf6a3 100644 --- a/compiler/wordpack.go +++ b/compiler/wordpack.go @@ -34,7 +34,7 @@ func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value { } // Because packedType is a struct and we have to cast it to a *i8, store // it in an alloca first for bitcasting (store+bitcast+load). - packedAlloc = c.builder.CreateAlloca(packedType, "") + packedAlloc, _, _ = c.createTemporaryAlloca(packedType, "") } else { // Packed data is bigger than a pointer, so allocate it on the heap. sizeValue := llvm.ConstInt(c.uintptrType, size, false) @@ -54,7 +54,11 @@ func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value { if packedHeapAlloc.IsNil() { // Load value (as *i8) from the alloca. packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "") - return c.builder.CreateLoad(packedAlloc, "") + result := c.builder.CreateLoad(packedAlloc, "") + packedPtr := c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "") + packedSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(packedAlloc.Type()), false) + c.emitLifetimeEnd(packedPtr, packedSize) + return result } else { // Get the original heap allocation pointer, which already is an *i8. return packedHeapAlloc @@ -66,7 +70,7 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l packedType := c.ctx.StructType(valueTypes, false) // Get a correctly-typed pointer to the packed data. - var packedAlloc llvm.Value + var packedAlloc, packedRawAlloc llvm.Value size := c.targetData.TypeAllocSize(packedType) if size == 0 { // No data to unpack. @@ -80,7 +84,7 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")} } // Fallback: load it using an alloca. - packedRawAlloc := c.builder.CreateAlloca(llvm.PointerType(c.i8ptrType, 0), "unpack.raw.alloc") + packedRawAlloc, _, _ = c.createTemporaryAlloca(llvm.PointerType(c.i8ptrType, 0), "unpack.raw.alloc") packedRawValue := c.builder.CreateBitCast(ptr, llvm.PointerType(c.i8ptrType, 0), "unpack.raw.value") c.builder.CreateStore(packedRawValue, packedRawAlloc) packedAlloc = c.builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc") @@ -104,5 +108,10 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "") values[i] = c.builder.CreateLoad(gep, "") } + if !packedRawAlloc.IsNil() { + allocPtr := c.builder.CreateBitCast(packedRawAlloc, c.i8ptrType, "") + allocSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(c.uintptrType), false) + c.emitLifetimeEnd(allocPtr, allocSize) + } return values }