
It does the same thing but should be more complete, and it probably is faster as well (just one CGo call instead of several).
120 строки
5,1 КиБ
Go
120 строки
5,1 КиБ
Go
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
|
|
}
|