diff --git a/compiler/compiler.go b/compiler/compiler.go index 397cb661..33ad6308 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -743,38 +743,18 @@ func (c *Compiler) parseFunc(frame *Frame) { context.SetName("context") } if len(frame.fn.FreeVars) != 0 { - // Determine the context type. It's a struct containing all variables. - freeVarTypes := make([]llvm.Type, 0, len(frame.fn.FreeVars)) - for _, freeVar := range frame.fn.FreeVars { - freeVarTypes = append(freeVarTypes, c.getLLVMType(freeVar.Type())) - } - contextType := c.ctx.StructType(freeVarTypes, false) - - // Get a correctly-typed pointer to the context. - contextAlloc := llvm.Value{} - if c.targetData.TypeAllocSize(contextType) <= c.targetData.TypeAllocSize(c.i8ptrType) { - // Context stored directly in pointer. Load it using an alloca. - contextRawAlloc := c.builder.CreateAlloca(llvm.PointerType(c.i8ptrType, 0), "context.raw.alloc") - contextRawValue := c.builder.CreateBitCast(context, llvm.PointerType(c.i8ptrType, 0), "context.raw.value") - c.builder.CreateStore(contextRawValue, contextRawAlloc) - contextAlloc = c.builder.CreateBitCast(contextRawAlloc, llvm.PointerType(contextType, 0), "context.alloc") - } else { - // Context stored in the heap. Bitcast the passed-in pointer to the - // correct pointer type. - contextAlloc = c.builder.CreateBitCast(context, llvm.PointerType(contextType, 0), "context.raw.ptr") + // Get a list of all variable types in the context. + freeVarTypes := make([]llvm.Type, len(frame.fn.FreeVars)) + for i, freeVar := range frame.fn.FreeVars { + freeVarTypes[i] = c.getLLVMType(freeVar.Type()) } - // Load each free variable from the context. + // Load each free variable from the context pointer. // A free variable is always a pointer when this is a closure, but it // can be another type when it is a wrapper for a bound method (these // wrappers are generated by the ssa package). - for i, freeVar := range frame.fn.FreeVars { - indices := []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false), - } - gep := c.builder.CreateInBoundsGEP(contextAlloc, indices, "") - frame.locals[freeVar] = c.builder.CreateLoad(gep, "") + for i, val := range c.emitPointerUnpack(context, freeVarTypes) { + frame.locals[frame.fn.FreeVars[i]] = val } } diff --git a/compiler/func.go b/compiler/func.go index 3bee9112..2a2af954 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -186,53 +186,16 @@ func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.V f := c.ir.GetFunction(expr.Fn.(*ssa.Function)) // Collect all bound variables. - boundVars := make([]llvm.Value, 0, len(expr.Bindings)) - boundVarTypes := make([]llvm.Type, 0, len(expr.Bindings)) - for _, binding := range expr.Bindings { + boundVars := make([]llvm.Value, len(expr.Bindings)) + for i, binding := range expr.Bindings { // The context stores the bound variables. llvmBoundVar := c.getValue(frame, binding) - boundVars = append(boundVars, llvmBoundVar) - boundVarTypes = append(boundVarTypes, llvmBoundVar.Type()) - } - contextType := c.ctx.StructType(boundVarTypes, false) - - // Allocate memory for the context. - contextAlloc := llvm.Value{} - contextHeapAlloc := llvm.Value{} - if c.targetData.TypeAllocSize(contextType) <= c.targetData.TypeAllocSize(c.i8ptrType) { - // Context fits in a pointer - e.g. when it is a pointer. Store it - // directly in the stack after a convert. - // Because contextType is a struct and we have to cast it to a *i8, - // store it in an alloca first for bitcasting (store+bitcast+load). - contextAlloc = c.builder.CreateAlloca(contextType, "") - } else { - // Context is bigger than a pointer, so allocate it on the heap. - size := c.targetData.TypeAllocSize(contextType) - sizeValue := llvm.ConstInt(c.uintptrType, size, false) - contextHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "") - contextAlloc = c.builder.CreateBitCast(contextHeapAlloc, llvm.PointerType(contextType, 0), "") + boundVars[i] = llvmBoundVar } - // Store all bound variables in the alloca or heap pointer. - for i, boundVar := range boundVars { - indices := []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false), - } - gep := c.builder.CreateInBoundsGEP(contextAlloc, indices, "") - c.builder.CreateStore(boundVar, gep) - } - - context := llvm.Value{} - if c.targetData.TypeAllocSize(contextType) <= c.targetData.TypeAllocSize(c.i8ptrType) { - // Load value (as *i8) from the alloca. - contextAlloc = c.builder.CreateBitCast(contextAlloc, llvm.PointerType(c.i8ptrType, 0), "") - context = c.builder.CreateLoad(contextAlloc, "") - } else { - // Get the original heap allocation pointer, which already is an - // *i8. - context = contextHeapAlloc - } + // Store the bound variables in a single object, allocating it on the heap + // if necessary. + context := c.emitPointerPack(boundVars) // Create the closure. return c.createFuncValue(f.LLVMFn, context, f.Signature), nil diff --git a/compiler/interface.go b/compiler/interface.go index c9624f16..6bc85806 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -23,37 +23,7 @@ import ( // // An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact. func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) (llvm.Value, error) { - var itfValue llvm.Value - size := c.targetData.TypeAllocSize(val.Type()) - if size > c.targetData.TypeAllocSize(c.i8ptrType) { - // Allocate on the heap and put a pointer in the interface. - // TODO: escape analysis. - sizeValue := llvm.ConstInt(c.uintptrType, size, false) - alloc := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "makeinterface.alloc") - itfValueCast := c.builder.CreateBitCast(alloc, llvm.PointerType(val.Type(), 0), "makeinterface.cast.value") - c.builder.CreateStore(val, itfValueCast) - itfValue = c.builder.CreateBitCast(itfValueCast, c.i8ptrType, "makeinterface.cast.i8ptr") - } else if size == 0 { - itfValue = llvm.ConstPointerNull(c.i8ptrType) - } else { - // Directly place the value in the interface. - switch val.Type().TypeKind() { - case llvm.IntegerTypeKind: - itfValue = c.builder.CreateIntToPtr(val, c.i8ptrType, "makeinterface.cast.int") - case llvm.PointerTypeKind: - itfValue = c.builder.CreateBitCast(val, c.i8ptrType, "makeinterface.cast.ptr") - case llvm.StructTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind: - // A bitcast would be useful here, but bitcast doesn't allow - // aggregate types. So we'll bitcast it using an alloca. - // Hopefully this will get optimized away. - mem := c.builder.CreateAlloca(c.i8ptrType, "makeinterface.cast.struct") - memStructPtr := c.builder.CreateBitCast(mem, llvm.PointerType(val.Type(), 0), "makeinterface.cast.struct.cast") - c.builder.CreateStore(val, memStructPtr) - itfValue = c.builder.CreateLoad(mem, "makeinterface.cast.load") - default: - return llvm.Value{}, c.makeError(pos, "todo: makeinterface: cast small type to i8*") - } - } + itfValue := c.emitPointerPack([]llvm.Value{val}) itfTypeCodeGlobal := c.getTypeCode(typ) itfMethodSetGlobal, err := c.getTypeMethodSet(typ) if err != nil { @@ -329,30 +299,7 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu // Type assert on concrete type. Extract the underlying type from // the interface (but only after checking it matches). valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr") - size := c.targetData.TypeAllocSize(assertedType) - if size > c.targetData.TypeAllocSize(c.i8ptrType) { - // Value was stored in an allocated buffer, load it from there. - valuePtrCast := c.builder.CreateBitCast(valuePtr, llvm.PointerType(assertedType, 0), "") - valueOk = c.builder.CreateLoad(valuePtrCast, "typeassert.value.ok") - } else if size == 0 { - valueOk = c.getZeroValue(assertedType) - } else { - // Value was stored directly in the interface. - switch assertedType.TypeKind() { - case llvm.IntegerTypeKind: - valueOk = c.builder.CreatePtrToInt(valuePtr, assertedType, "typeassert.value.ok") - case llvm.PointerTypeKind: - valueOk = c.builder.CreateBitCast(valuePtr, assertedType, "typeassert.value.ok") - default: // struct, float, etc. - // A bitcast would be useful here, but bitcast doesn't allow - // aggregate types. So we'll bitcast it using an alloca. - // Hopefully this will get optimized away. - mem := c.builder.CreateAlloca(c.i8ptrType, "") - c.builder.CreateStore(valuePtr, mem) - memCast := c.builder.CreateBitCast(mem, llvm.PointerType(assertedType, 0), "") - valueOk = c.builder.CreateLoad(memCast, "typeassert.value.ok") - } - } + valueOk = c.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0] } c.builder.CreateBr(nextBlock) @@ -472,28 +419,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) { block := c.ctx.AddBasicBlock(wrapper, "entry") c.builder.SetInsertPointAtEnd(block) - var receiverPtr llvm.Value - if c.targetData.TypeAllocSize(receiverType) > c.targetData.TypeAllocSize(c.i8ptrType) { - // The receiver is passed in using a pointer. We have to load it here - // and pass it by value to the real function. - - // Load the underlying value. - receiverPtrType := llvm.PointerType(receiverType, 0) - receiverPtr = c.builder.CreateBitCast(wrapper.Param(0), receiverPtrType, "receiver.ptr") - } else { - // The value is stored in the interface, but it is of type struct which - // is expanded to multiple parameters (e.g. {i8, i8}). So we have to - // receive the struct as parameter, expand it, and pass it on to the - // real function. - - // Cast the passed-in i8* to the struct value (using an alloca) and - // extract its values. - alloca := c.builder.CreateAlloca(c.i8ptrType, "receiver.alloca") - c.builder.CreateStore(wrapper.Param(0), alloca) - receiverPtr = c.builder.CreateBitCast(alloca, llvm.PointerType(receiverType, 0), "receiver.ptr") - } - - receiverValue := c.builder.CreateLoad(receiverPtr, "receiver") + receiverValue := c.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0] params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...) if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind { c.builder.CreateCall(fn.LLVMFn, params, "") diff --git a/compiler/wordpack.go b/compiler/wordpack.go new file mode 100644 index 00000000..caeb7956 --- /dev/null +++ b/compiler/wordpack.go @@ -0,0 +1,108 @@ +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.builder.CreateAlloca(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}, "") + 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), "") + return c.builder.CreateLoad(packedAlloc, "") + } 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 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.builder.CreateAlloca(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] = c.getZeroValue(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, "") + } + return values +}