aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--compileopts/config.go15
-rw-r--r--compiler/channel.go3
-rw-r--r--compiler/compiler.go5
-rw-r--r--compiler/defer.go2
-rw-r--r--compiler/gc.go15
-rw-r--r--compiler/goroutine-lowering.go5
-rw-r--r--compiler/llvm.go67
-rw-r--r--compiler/llvmutil/llvm.go96
-rw-r--r--compiler/llvmutil/wordpack.go133
-rw-r--r--compiler/wordpack.go120
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
-}