mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-13 23:43:40 +00:00
compiler: refactor alloca/lifetime/wordpack code into separate package
This code is required by transformation passes which are being moved into a separate package, but is too complicated to simply copy. Therefore, I decided to move them into a new package.
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user