compiler: create temporary allocas with appropriate lifetimes
Make sure all allocas are created in the entry block and are given the right lifetimes. This is good for code quality: * Moving allocas to the entry block makes sure they are always allocated statically (avoiding the need for a frame pointer) and do not grow the stack on each new alloca instruction. This is especially useful in loops where it could otherwise lead to a stack overflow even though there is no recursion. * Adding lifetime markers allows LLVM to reuse stack areas for different allocas as long as their lifetimes do not overlap. All in all, this reduces code size in all tested cases for the BBC micro:bit, and reduces code size for most cases for WebAssembly.
Этот коммит содержится в:
родитель
de032cddd2
коммит
7b6ef65fe7
5 изменённых файлов: 81 добавлений и 56 удалений
|
@ -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
|
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
|
||||||
// channel send operation during goroutine lowering.
|
// channel send operation during goroutine lowering.
|
||||||
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
|
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
|
||||||
valueType := c.getLLVMType(instr.X.Type())
|
|
||||||
ch := c.getValue(frame, instr.Chan)
|
ch := c.getValue(frame, instr.Chan)
|
||||||
chanValue := c.getValue(frame, instr.X)
|
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
|
// store value-to-send
|
||||||
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
valueType := c.getLLVMType(instr.X.Type())
|
||||||
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
|
||||||
c.builder.SetInsertPointBefore(coroutine)
|
|
||||||
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
|
|
||||||
c.builder.CreateStore(chanValue, valueAlloca)
|
c.builder.CreateStore(chanValue, valueAlloca)
|
||||||
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
|
||||||
|
|
||||||
// Do the send.
|
// 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}, "")
|
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
|
||||||
|
|
||||||
// Make sure CoroSplit includes the alloca in the coroutine frame.
|
// End the lifetime of the alloca.
|
||||||
// This is a bit dirty, but it works (at least in LLVM 8).
|
// This also works around a bug in CoroSplit, at least in LLVM 8:
|
||||||
valueSizeI64 := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(chanValue.Type()), false)
|
// https://bugs.llvm.org/show_bug.cgi?id=41742
|
||||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSizeI64, valueAllocaCast}, "")
|
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
|
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
|
||||||
// actual channel receive operation during goroutine lowering.
|
// actual channel receive operation during goroutine lowering.
|
||||||
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
|
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
|
||||||
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
|
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)
|
ch := c.getValue(frame, unop.X)
|
||||||
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
|
||||||
|
|
||||||
// Allocate memory to receive into.
|
// Allocate memory to receive into.
|
||||||
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
|
||||||
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")
|
|
||||||
|
|
||||||
// Do the receive.
|
// 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}, "")
|
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
|
||||||
received := c.builder.CreateLoad(valueAlloca, "chan.received")
|
received := c.builder.CreateLoad(valueAlloca, "chan.received")
|
||||||
|
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||||
|
|
||||||
if unop.CommaOk {
|
if unop.CommaOk {
|
||||||
commaOk := c.createRuntimeCall("getTaskPromiseData", []llvm.Value{coroutine}, "chan.commaOk.wide")
|
commaOk := c.createRuntimeCall("getTaskPromiseData", []llvm.Value{coroutine}, "chan.commaOk.wide")
|
||||||
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
|
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
|
||||||
|
|
|
@ -1369,7 +1369,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||||
switch expr := expr.(type) {
|
switch expr := expr.(type) {
|
||||||
case *ssa.Alloc:
|
case *ssa.Alloc:
|
||||||
typ := c.getLLVMType(expr.Type().Underlying().(*types.Pointer).Elem())
|
typ := c.getLLVMType(expr.Type().Underlying().(*types.Pointer).Elem())
|
||||||
var buf llvm.Value
|
|
||||||
if expr.Heap {
|
if expr.Heap {
|
||||||
size := c.targetData.TypeAllocSize(typ)
|
size := c.targetData.TypeAllocSize(typ)
|
||||||
// Calculate ^uintptr(0)
|
// Calculate ^uintptr(0)
|
||||||
|
@ -1380,15 +1379,16 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||||
}
|
}
|
||||||
// TODO: escape analysis
|
// TODO: escape analysis
|
||||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
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), "")
|
buf = c.builder.CreateBitCast(buf, llvm.PointerType(typ, 0), "")
|
||||||
|
return buf, nil
|
||||||
} else {
|
} else {
|
||||||
buf = c.builder.CreateAlloca(typ, expr.Comment)
|
buf := c.createEntryBlockAlloca(typ, expr.Comment)
|
||||||
if c.targetData.TypeAllocSize(typ) != 0 {
|
if c.targetData.TypeAllocSize(typ) != 0 {
|
||||||
c.builder.CreateStore(c.getZeroValue(typ), buf) // zero-initialize var
|
c.builder.CreateStore(c.getZeroValue(typ), buf) // zero-initialize var
|
||||||
}
|
}
|
||||||
|
return buf, nil
|
||||||
}
|
}
|
||||||
return buf, nil
|
|
||||||
case *ssa.BinOp:
|
case *ssa.BinOp:
|
||||||
x := c.getValue(frame, expr.X)
|
x := c.getValue(frame, expr.X)
|
||||||
y := c.getValue(frame, expr.Y)
|
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
|
// This could be done directly, but as this is a very infrequent
|
||||||
// operation it's much easier to bitcast it through an alloca.
|
// operation it's much easier to bitcast it through an alloca.
|
||||||
resultType := c.getLLVMType(expr.Type())
|
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)
|
c.builder.CreateStore(value, alloca)
|
||||||
bitcast := c.builder.CreateBitCast(alloca, llvm.PointerType(resultType, 0), "")
|
bitcast := c.builder.CreateBitCast(alloca, llvm.PointerType(resultType, 0), "union.bitcast")
|
||||||
return c.builder.CreateLoad(bitcast, ""), nil
|
result := c.builder.CreateLoad(bitcast, "union.result")
|
||||||
|
c.emitLifetimeEnd(allocaPtr, allocaSize)
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
result := c.builder.CreateExtractValue(value, expr.Field, "")
|
result := c.builder.CreateExtractValue(value, expr.Field, "")
|
||||||
return result, nil
|
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
|
// Can't load directly from array (as index is non-constant), so have to
|
||||||
// do it using an alloca+gep+load.
|
// 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)
|
c.builder.CreateStore(array, alloca)
|
||||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
ptr := c.builder.CreateInBoundsGEP(alloca, []llvm.Value{zero, index}, "index.gep")
|
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:
|
case *ssa.IndexAddr:
|
||||||
val := c.getValue(frame, expr.X)
|
val := c.getValue(frame, expr.X)
|
||||||
index := c.getValue(frame, expr.Index)
|
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())
|
llvmKeyType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Key())
|
||||||
llvmValueType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Elem())
|
llvmValueType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Elem())
|
||||||
|
|
||||||
mapKeyAlloca := c.builder.CreateAlloca(llvmKeyType, "range.key")
|
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(llvmKeyType, "range.key")
|
||||||
mapKeyPtr := c.builder.CreateBitCast(mapKeyAlloca, c.i8ptrType, "range.keyptr")
|
mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "range.value")
|
||||||
mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "range.value")
|
|
||||||
mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "range.valueptr")
|
|
||||||
ok := c.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyPtr, mapValuePtr}, "range.next")
|
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 := 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, ok, 0, "")
|
||||||
tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapKeyAlloca, ""), 1, "")
|
tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapKeyAlloca, ""), 1, "")
|
||||||
tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapValueAlloca, ""), 2, "")
|
tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapValueAlloca, ""), 2, "")
|
||||||
|
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
|
||||||
|
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
|
||||||
return tuple, nil
|
return tuple, nil
|
||||||
}
|
}
|
||||||
case *ssa.Phi:
|
case *ssa.Phi:
|
||||||
|
@ -1684,7 +1688,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||||
default:
|
default:
|
||||||
panic("unknown type in range: " + typ.String())
|
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)
|
c.builder.CreateStore(c.getZeroValue(iteratorType), it)
|
||||||
return it, nil
|
return it, nil
|
||||||
case *ssa.Select:
|
case *ssa.Select:
|
||||||
|
|
|
@ -24,23 +24,41 @@ func getUses(value llvm.Value) []llvm.Value {
|
||||||
|
|
||||||
// createEntryBlockAlloca creates a new alloca in the entry block, even though
|
// createEntryBlockAlloca creates a new alloca in the entry block, even though
|
||||||
// the IR builder is located elsewhere. It assumes that the insert point is
|
// 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
|
// at the end of the current block.
|
||||||
// information to the IR signalling that the alloca won't be used before this
|
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) llvm.Value {
|
||||||
// point.
|
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
|
// This is useful for creating temporary allocas for intrinsics. Don't forget to
|
||||||
// end the lifetime after you're done with it.
|
// end the lifetime using emitLifetimeEnd after you're done with it.
|
||||||
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
||||||
currentBlock := c.builder.GetInsertBlock()
|
alloca = c.createEntryBlockAlloca(t, name)
|
||||||
c.builder.SetInsertPointBefore(currentBlock.Parent().EntryBasicBlock().FirstInstruction())
|
|
||||||
alloca = c.builder.CreateAlloca(t, name)
|
|
||||||
c.builder.SetInsertPointAtEnd(currentBlock)
|
|
||||||
bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
|
bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
|
||||||
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
|
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
|
||||||
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
|
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
|
||||||
return
|
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
|
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
|
||||||
// first if it doesn't exist yet.
|
// first if it doesn't exist yet.
|
||||||
func (c *Compiler) getLifetimeStartFunc() llvm.Value {
|
func (c *Compiler) getLifetimeStartFunc() llvm.Value {
|
||||||
|
|
|
@ -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
|
// 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
|
// will be zeroed by the hashmap get implementation if the key is not
|
||||||
// present in the map.
|
// 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.
|
// Do the lookup. How it is done depends on the key type.
|
||||||
var commaOkValue llvm.Value
|
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
|
// key can be compared with runtime.memequal
|
||||||
// Store the key in an alloca, in the entry block to avoid dynamic stack
|
// Store the key in an alloca, in the entry block to avoid dynamic stack
|
||||||
// growth.
|
// growth.
|
||||||
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
|
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||||
c.builder.CreateStore(key, mapKeyAlloca)
|
c.builder.CreateStore(key, mapKeyAlloca)
|
||||||
// Fetch the value from the hashmap.
|
// Fetch the value from the hashmap.
|
||||||
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
|
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
|
||||||
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
||||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapKeySize, mapKeyPtr}, "")
|
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
|
||||||
} else {
|
} else {
|
||||||
// Not trivially comparable using memcmp.
|
// 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())
|
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
|
// Load the resulting value from the hashmap. The value is set to the zero
|
||||||
// value if the key doesn't exist in the hashmap.
|
// value if the key doesn't exist in the hashmap.
|
||||||
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
|
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
|
||||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapValueSize, mapValuePtr}, "")
|
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
|
||||||
|
|
||||||
if commaOk {
|
if commaOk {
|
||||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
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) {
|
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)
|
c.builder.CreateStore(value, valueAlloca)
|
||||||
keyType = keyType.Underlying()
|
keyType = keyType.Underlying()
|
||||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
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, "")
|
c.createRuntimeCall("hashmapStringSet", params, "")
|
||||||
} else if hashmapIsBinaryKey(keyType) {
|
} else if hashmapIsBinaryKey(keyType) {
|
||||||
// key can be compared with runtime.memequal
|
// 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)
|
c.builder.CreateStore(key, keyAlloca)
|
||||||
params := []llvm.Value{m, keyPtr, valuePtr}
|
params := []llvm.Value{m, keyPtr, valuePtr}
|
||||||
c.createRuntimeCall("hashmapBinarySet", params, "")
|
c.createRuntimeCall("hashmapBinarySet", params, "")
|
||||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
|
c.emitLifetimeEnd(keyPtr, keySize)
|
||||||
} else {
|
} else {
|
||||||
c.addError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
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 {
|
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, "")
|
c.createRuntimeCall("hashmapStringDelete", params, "")
|
||||||
return nil
|
return nil
|
||||||
} else if hashmapIsBinaryKey(keyType) {
|
} 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)
|
c.builder.CreateStore(key, keyAlloca)
|
||||||
params := []llvm.Value{m, keyPtr}
|
params := []llvm.Value{m, keyPtr}
|
||||||
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
||||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
|
c.emitLifetimeEnd(keyPtr, keySize)
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||||
|
|
|
@ -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
|
// 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).
|
// it in an alloca first for bitcasting (store+bitcast+load).
|
||||||
packedAlloc = c.builder.CreateAlloca(packedType, "")
|
packedAlloc, _, _ = c.createTemporaryAlloca(packedType, "")
|
||||||
} else {
|
} else {
|
||||||
// Packed data is bigger than a pointer, so allocate it on the heap.
|
// Packed data is bigger than a pointer, so allocate it on the heap.
|
||||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||||
|
@ -54,7 +54,11 @@ func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||||
if packedHeapAlloc.IsNil() {
|
if packedHeapAlloc.IsNil() {
|
||||||
// Load value (as *i8) from the alloca.
|
// Load value (as *i8) from the alloca.
|
||||||
packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "")
|
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 {
|
} else {
|
||||||
// Get the original heap allocation pointer, which already is an *i8.
|
// Get the original heap allocation pointer, which already is an *i8.
|
||||||
return packedHeapAlloc
|
return packedHeapAlloc
|
||||||
|
@ -66,7 +70,7 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l
|
||||||
packedType := c.ctx.StructType(valueTypes, false)
|
packedType := c.ctx.StructType(valueTypes, false)
|
||||||
|
|
||||||
// Get a correctly-typed pointer to the packed data.
|
// Get a correctly-typed pointer to the packed data.
|
||||||
var packedAlloc llvm.Value
|
var packedAlloc, packedRawAlloc llvm.Value
|
||||||
size := c.targetData.TypeAllocSize(packedType)
|
size := c.targetData.TypeAllocSize(packedType)
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
// No data to unpack.
|
// 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")}
|
return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
|
||||||
}
|
}
|
||||||
// Fallback: load it using an alloca.
|
// 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")
|
packedRawValue := c.builder.CreateBitCast(ptr, llvm.PointerType(c.i8ptrType, 0), "unpack.raw.value")
|
||||||
c.builder.CreateStore(packedRawValue, packedRawAlloc)
|
c.builder.CreateStore(packedRawValue, packedRawAlloc)
|
||||||
packedAlloc = c.builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
|
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, "")
|
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
||||||
values[i] = c.builder.CreateLoad(gep, "")
|
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
|
return values
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче