diff options
-rw-r--r-- | compileopts/config.go | 15 | ||||
-rw-r--r-- | compiler/channel.go | 3 | ||||
-rw-r--r-- | compiler/compiler.go | 5 | ||||
-rw-r--r-- | compiler/defer.go | 2 | ||||
-rw-r--r-- | compiler/gc.go | 15 | ||||
-rw-r--r-- | compiler/goroutine-lowering.go | 5 | ||||
-rw-r--r-- | compiler/llvm.go | 67 | ||||
-rw-r--r-- | compiler/llvmutil/llvm.go | 96 | ||||
-rw-r--r-- | compiler/llvmutil/wordpack.go | 133 | ||||
-rw-r--r-- | compiler/wordpack.go | 120 |
10 files changed, 264 insertions, 197 deletions
diff --git a/compileopts/config.go b/compileopts/config.go index 240f14c97..197253e42 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -77,6 +77,21 @@ func (c *Config) GC() string { return "conservative" } +// NeedsStackObjects returns true if the compiler should insert stack objects +// that can be traced by the garbage collector. +func (c *Config) NeedsStackObjects() bool { + if c.GC() != "conservative" { + return false + } + for _, tag := range c.BuildTags() { + if tag == "baremetal" { + return false + } + } + + return true +} + // Scheduler returns the scheduler implementation. Valid values are "coroutines" // and "tasks". func (c *Config) Scheduler() string { diff --git a/compiler/channel.go b/compiler/channel.go index 803075f1b..ac16510c6 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -6,6 +6,7 @@ package compiler import ( "go/types" + "github.com/tinygo-org/tinygo/compiler/llvmutil" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -122,7 +123,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value { // Store this value in an alloca and put a pointer to this alloca // in the send state. sendValue := c.getValue(frame, state.Send) - alloca := c.createEntryBlockAlloca(sendValue.Type(), "select.send.value") + 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, "") diff --git a/compiler/compiler.go b/compiler/compiler.go index 625ef28f3..e95a0f908 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -15,6 +15,7 @@ import ( "strings" "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/ir" "github.com/tinygo-org/tinygo/loader" @@ -978,7 +979,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { frame.locals[instr] = llvm.Undef(c.getLLVMType(instr.Type())) } else { frame.locals[instr] = value - if len(*instr.Referrers()) != 0 && c.needsStackObjects() { + if len(*instr.Referrers()) != 0 && c.NeedsStackObjects() { c.trackExpr(frame, instr, value) } } @@ -1386,7 +1387,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { buf = c.builder.CreateBitCast(buf, llvm.PointerType(typ, 0), "") return buf, nil } else { - buf := c.createEntryBlockAlloca(typ, expr.Comment) + buf := llvmutil.CreateEntryBlockAlloca(c.builder, typ, expr.Comment) if c.targetData.TypeAllocSize(typ) != 0 { c.builder.CreateStore(llvm.ConstNull(typ), buf) // zero-initialize var } diff --git a/compiler/defer.go b/compiler/defer.go index 00664cfe8..bb501d862 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -130,7 +130,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) { // Put this struct in an alloca. alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca") c.builder.CreateStore(deferFrame, alloca) - if c.needsStackObjects() { + if c.NeedsStackObjects() { c.trackPointer(alloca) } diff --git a/compiler/gc.go b/compiler/gc.go index 85ec2d521..3a87b1153 100644 --- a/compiler/gc.go +++ b/compiler/gc.go @@ -11,21 +11,6 @@ import ( "tinygo.org/x/go-llvm" ) -// needsStackObjects returns true if the compiler should insert stack objects -// that can be traced by the garbage collector. -func (c *Compiler) needsStackObjects() bool { - if c.GC() != "conservative" { - return false - } - for _, tag := range c.BuildTags() { - if tag == "baremetal" { - return false - } - } - - return true -} - // trackExpr inserts pointer tracking intrinsics for the GC if the expression is // one of the expressions that need this. func (c *Compiler) trackExpr(frame *Frame, expr ssa.Value, value llvm.Value) { diff --git a/compiler/goroutine-lowering.go b/compiler/goroutine-lowering.go index b34ac8a6d..7e7be7ad3 100644 --- a/compiler/goroutine-lowering.go +++ b/compiler/goroutine-lowering.go @@ -108,6 +108,7 @@ import ( "fmt" "strings" + "github.com/tinygo-org/tinygo/compiler/llvmutil" "tinygo.org/x/go-llvm" ) @@ -534,7 +535,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { var retvalAlloca llvm.Value if callee.Type().ElementType().ReturnType().TypeKind() != llvm.VoidTypeKind { // allocate return value buffer - retvalAlloca = c.createInstructionAlloca(callee.Type().ElementType().ReturnType(), inst, "coro.retvalAlloca") + retvalAlloca = llvmutil.CreateInstructionAlloca(c.builder, c.mod, callee.Type().ElementType().ReturnType(), inst, "coro.retvalAlloca") // call before function c.builder.SetInsertPointBefore(inst) @@ -770,7 +771,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) { size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr") } data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data") - if c.needsStackObjects() { + if c.NeedsStackObjects() { c.trackPointer(data) } diff --git a/compiler/llvm.go b/compiler/llvm.go index 2df6d2768..3bd9eeee1 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -3,6 +3,7 @@ package compiler import ( "reflect" + "github.com/tinygo-org/tinygo/compiler/llvmutil" "tinygo.org/x/go-llvm" ) @@ -24,22 +25,6 @@ func getUses(value llvm.Value) []llvm.Value { return uses } -// createEntryBlockAlloca creates a new alloca in the entry block, even though -// the IR builder is located elsewhere. It assumes that the insert point is -// 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. @@ -47,56 +32,26 @@ func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) llvm.Value { // 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 (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 -} - -// createInstructionAlloca creates an alloca in the entry block, and places lifetime control intrinsics around the instruction -func (c *Compiler) createInstructionAlloca(t llvm.Type, inst llvm.Value, name string) llvm.Value { - alloca := c.createEntryBlockAlloca(t, name) - c.builder.SetInsertPointBefore(inst) - 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}, "") - if next := llvm.NextInstruction(inst); !next.IsNil() { - c.builder.SetInsertPointBefore(next) - } else { - c.builder.SetInsertPointAtEnd(inst.InstructionParent()) - } - c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{size, bitcast}, "") - return alloca + return llvmutil.CreateTemporaryAlloca(c.builder, c.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. func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) { - c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{size, ptr}, "") + llvmutil.EmitLifetimeEnd(c.builder, c.mod, ptr, size) } -// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it -// first if it doesn't exist yet. -func (c *Compiler) getLifetimeStartFunc() llvm.Value { - fn := c.mod.NamedFunction("llvm.lifetime.start.p0i8") - if fn.IsNil() { - fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false) - fn = llvm.AddFunction(c.mod, "llvm.lifetime.start.p0i8", fnType) - } - return fn +// 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. +func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value { + return llvmutil.EmitPointerPack(c.builder, c.mod, c.Config, values) } -// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it -// first if it doesn't exist yet. -func (c *Compiler) getLifetimeEndFunc() llvm.Value { - fn := c.mod.NamedFunction("llvm.lifetime.end.p0i8") - if fn.IsNil() { - fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false) - fn = llvm.AddFunction(c.mod, "llvm.lifetime.end.p0i8", fnType) - } - return fn +// emitPointerUnpack extracts a list of values packed using emitPointerPack. +func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value { + return llvmutil.EmitPointerUnpack(c.builder, c.mod, ptr, valueTypes) } // splitBasicBlock splits a LLVM basic block into two parts. All instructions diff --git a/compiler/llvmutil/llvm.go b/compiler/llvmutil/llvm.go new file mode 100644 index 000000000..ad2d28717 --- /dev/null +++ b/compiler/llvmutil/llvm.go @@ -0,0 +1,96 @@ +// Package llvmutil contains utility functions used across multiple compiler +// packages. For example, they may be used by both the compiler pacakge and +// transformation packages. +// +// Normally, utility packages are avoided. However, in this case, the utility +// functions are non-trivial and hard to get right. Copying them to multiple +// places would be a big risk if only one of them is updated. +package llvmutil + +import "tinygo.org/x/go-llvm" + +// CreateEntryBlockAlloca creates a new alloca in the entry block, even though +// the IR builder is located elsewhere. It assumes that the insert point is +// at the end of the current block. +func CreateEntryBlockAlloca(builder llvm.Builder, t llvm.Type, name string) llvm.Value { + currentBlock := builder.GetInsertBlock() + entryBlock := currentBlock.Parent().EntryBasicBlock() + if entryBlock.FirstInstruction().IsNil() { + builder.SetInsertPointAtEnd(entryBlock) + } else { + builder.SetInsertPointBefore(entryBlock.FirstInstruction()) + } + alloca := builder.CreateAlloca(t, name) + 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 using emitLifetimeEnd after you're done with it. +func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, name string) (alloca, bitcast, size llvm.Value) { + ctx := t.Context() + targetData := llvm.NewTargetData(mod.DataLayout()) + i8ptrType := llvm.PointerType(ctx.Int8Type(), 0) + alloca = CreateEntryBlockAlloca(builder, t, name) + bitcast = builder.CreateBitCast(alloca, i8ptrType, name+".bitcast") + size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false) + builder.CreateCall(getLifetimeStartFunc(mod), []llvm.Value{size, bitcast}, "") + return +} + +// CreateInstructionAlloca creates an alloca in the entry block, and places lifetime control intrinsics around the instruction +func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, inst llvm.Value, name string) llvm.Value { + ctx := mod.Context() + targetData := llvm.NewTargetData(mod.DataLayout()) + i8ptrType := llvm.PointerType(ctx.Int8Type(), 0) + + alloca := CreateEntryBlockAlloca(builder, t, name) + builder.SetInsertPointBefore(inst) + bitcast := builder.CreateBitCast(alloca, i8ptrType, name+".bitcast") + size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false) + builder.CreateCall(getLifetimeStartFunc(mod), []llvm.Value{size, bitcast}, "") + if next := llvm.NextInstruction(inst); !next.IsNil() { + builder.SetInsertPointBefore(next) + } else { + builder.SetInsertPointAtEnd(inst.InstructionParent()) + } + builder.CreateCall(getLifetimeEndFunc(mod), []llvm.Value{size, bitcast}, "") + return alloca +} + +// EmitLifetimeEnd signals the end of an (alloca) lifetime by calling the +// llvm.lifetime.end intrinsic. It is commonly used together with +// createTemporaryAlloca. +func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) { + builder.CreateCall(getLifetimeEndFunc(mod), []llvm.Value{size, ptr}, "") +} + +// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it +// first if it doesn't exist yet. +func getLifetimeStartFunc(mod llvm.Module) llvm.Value { + fn := mod.NamedFunction("llvm.lifetime.start.p0i8") + ctx := mod.Context() + i8ptrType := llvm.PointerType(ctx.Int8Type(), 0) + if fn.IsNil() { + fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false) + fn = llvm.AddFunction(mod, "llvm.lifetime.start.p0i8", fnType) + } + return fn +} + +// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it +// first if it doesn't exist yet. +func getLifetimeEndFunc(mod llvm.Module) llvm.Value { + fn := mod.NamedFunction("llvm.lifetime.end.p0i8") + ctx := mod.Context() + i8ptrType := llvm.PointerType(ctx.Int8Type(), 0) + if fn.IsNil() { + fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false) + fn = llvm.AddFunction(mod, "llvm.lifetime.end.p0i8", fnType) + } + return fn +} diff --git a/compiler/llvmutil/wordpack.go b/compiler/llvmutil/wordpack.go new file mode 100644 index 000000000..1cbada7ee --- /dev/null +++ b/compiler/llvmutil/wordpack.go @@ -0,0 +1,133 @@ +package llvmutil + +// This file contains utility functions to pack and unpack sets of values. It +// can take in a list of values and tries to store it efficiently in the pointer +// itself if possible and legal. + +import ( + "github.com/tinygo-org/tinygo/compileopts" + "tinygo.org/x/go-llvm" +) + +// 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. +func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.Config, values []llvm.Value) llvm.Value { + ctx := mod.Context() + targetData := llvm.NewTargetData(mod.DataLayout()) + i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) + uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8) + + valueTypes := make([]llvm.Type, len(values)) + for i, value := range values { + valueTypes[i] = value.Type() + } + packedType := ctx.StructType(valueTypes, false) + + // Allocate memory for the packed data. + var packedAlloc, packedHeapAlloc llvm.Value + size := targetData.TypeAllocSize(packedType) + if size == 0 { + return llvm.ConstPointerNull(i8ptrType) + } else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind { + return builder.CreateBitCast(values[0], i8ptrType, "pack.ptr") + } else if size <= targetData.TypeAllocSize(i8ptrType) { + // Packed data fits in a pointer, so store it directly inside the + // pointer. + if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind { + // Try to keep this cast in SSA form. + return builder.CreateIntToPtr(values[0], i8ptrType, "pack.int") + } + // 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, _, _ = CreateTemporaryAlloca(builder, mod, packedType, "") + } else { + // Packed data is bigger than a pointer, so allocate it on the heap. + sizeValue := llvm.ConstInt(uintptrType, size, false) + alloc := mod.NamedFunction("runtime.alloc") + packedHeapAlloc = builder.CreateCall(alloc, []llvm.Value{sizeValue}, "") + if config.NeedsStackObjects() { + trackPointer := mod.NamedFunction("runtime.trackPointer") + builder.CreateCall(trackPointer, []llvm.Value{packedHeapAlloc}, "") + } + packedAlloc = builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "") + } + // Store all values in the alloca or heap pointer. + for i, value := range values { + indices := []llvm.Value{ + llvm.ConstInt(ctx.Int32Type(), 0, false), + llvm.ConstInt(ctx.Int32Type(), uint64(i), false), + } + gep := builder.CreateInBoundsGEP(packedAlloc, indices, "") + builder.CreateStore(value, gep) + } + + if packedHeapAlloc.IsNil() { + // Load value (as *i8) from the alloca. + packedAlloc = builder.CreateBitCast(packedAlloc, llvm.PointerType(i8ptrType, 0), "") + result := builder.CreateLoad(packedAlloc, "") + packedPtr := builder.CreateBitCast(packedAlloc, i8ptrType, "") + packedSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(packedAlloc.Type()), false) + EmitLifetimeEnd(builder, mod, packedPtr, packedSize) + return result + } else { + // Get the original heap allocation pointer, which already is an *i8. + return packedHeapAlloc + } +} + +// EmitPointerUnpack extracts a list of values packed using EmitPointerPack. +func EmitPointerUnpack(builder llvm.Builder, mod llvm.Module, ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value { + ctx := mod.Context() + targetData := llvm.NewTargetData(mod.DataLayout()) + i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) + uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8) + + packedType := ctx.StructType(valueTypes, false) + + // Get a correctly-typed pointer to the packed data. + var packedAlloc, packedRawAlloc llvm.Value + size := targetData.TypeAllocSize(packedType) + if size == 0 { + // No data to unpack. + } else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind { + // A single pointer is always stored directly. + return []llvm.Value{builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")} + } else if size <= targetData.TypeAllocSize(i8ptrType) { + // Packed data stored directly in pointer. + if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind { + // Keep this cast in SSA form. + return []llvm.Value{builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")} + } + // Fallback: load it using an alloca. + packedRawAlloc, _, _ = CreateTemporaryAlloca(builder, mod, llvm.PointerType(i8ptrType, 0), "unpack.raw.alloc") + packedRawValue := builder.CreateBitCast(ptr, llvm.PointerType(i8ptrType, 0), "unpack.raw.value") + builder.CreateStore(packedRawValue, packedRawAlloc) + packedAlloc = builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc") + } else { + // Packed data stored on the heap. Bitcast the passed-in pointer to the + // correct pointer type. + packedAlloc = builder.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr") + } + // Load each value from the packed data. + values := make([]llvm.Value, len(valueTypes)) + for i, valueType := range valueTypes { + if targetData.TypeAllocSize(valueType) == 0 { + // This value has length zero, so there's nothing to load. + values[i] = llvm.ConstNull(valueType) + continue + } + indices := []llvm.Value{ + llvm.ConstInt(ctx.Int32Type(), 0, false), + llvm.ConstInt(ctx.Int32Type(), uint64(i), false), + } + gep := builder.CreateInBoundsGEP(packedAlloc, indices, "") + values[i] = builder.CreateLoad(gep, "") + } + if !packedRawAlloc.IsNil() { + allocPtr := builder.CreateBitCast(packedRawAlloc, i8ptrType, "") + allocSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(uintptrType), false) + EmitLifetimeEnd(builder, mod, allocPtr, allocSize) + } + return values +} diff --git a/compiler/wordpack.go b/compiler/wordpack.go deleted file mode 100644 index 513970a1e..000000000 --- a/compiler/wordpack.go +++ /dev/null @@ -1,120 +0,0 @@ -package compiler - -// This file contains utility functions to pack and unpack sets of values. It -// can take in a list of values and tries to store it efficiently in the pointer -// itself if possible and legal. - -import ( - "tinygo.org/x/go-llvm" -) - -// 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. -func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value { - valueTypes := make([]llvm.Type, len(values)) - for i, value := range values { - valueTypes[i] = value.Type() - } - packedType := c.ctx.StructType(valueTypes, false) - - // Allocate memory for the packed data. - var packedAlloc, packedHeapAlloc llvm.Value - size := c.targetData.TypeAllocSize(packedType) - if size == 0 { - return llvm.ConstPointerNull(c.i8ptrType) - } else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind { - return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr") - } else if size <= c.targetData.TypeAllocSize(c.i8ptrType) { - // Packed data fits in a pointer, so store it directly inside the - // pointer. - if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind { - // Try to keep this cast in SSA form. - return c.builder.CreateIntToPtr(values[0], c.i8ptrType, "pack.int") - } - // 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.createTemporaryAlloca(packedType, "") - } else { - // Packed data is bigger than a pointer, so allocate it on the heap. - sizeValue := llvm.ConstInt(c.uintptrType, size, false) - packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "") - if c.needsStackObjects() { - c.trackPointer(packedHeapAlloc) - } - packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "") - } - // Store all values in the alloca or heap pointer. - for i, value := range values { - indices := []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false), - } - gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "") - c.builder.CreateStore(value, gep) - } - - if packedHeapAlloc.IsNil() { - // Load value (as *i8) from the alloca. - packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "") - 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 - } -} - -// emitPointerUnpack extracts a list of values packed using emitPointerPack. -func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value { - packedType := c.ctx.StructType(valueTypes, false) - - // Get a correctly-typed pointer to the packed data. - var packedAlloc, packedRawAlloc llvm.Value - size := c.targetData.TypeAllocSize(packedType) - if size == 0 { - // No data to unpack. - } else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind { - // A single pointer is always stored directly. - return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")} - } else if size <= c.targetData.TypeAllocSize(c.i8ptrType) { - // Packed data stored directly in pointer. - if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind { - // Keep this cast in SSA form. - return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")} - } - // Fallback: load it using an alloca. - 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") - } else { - // Packed data stored on the heap. Bitcast the passed-in pointer to the - // correct pointer type. - packedAlloc = c.builder.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr") - } - // Load each value from the packed data. - values := make([]llvm.Value, len(valueTypes)) - for i, valueType := range valueTypes { - if c.targetData.TypeAllocSize(valueType) == 0 { - // This value has length zero, so there's nothing to load. - values[i] = llvm.ConstNull(valueType) - continue - } - indices := []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false), - } - 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 -} |