From 27824e3c80adf74d79629287c92d2a76cd7599d2 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 17 Jan 2023 22:49:16 +0100 Subject: [PATCH] compiler: move some llvmutil code into the compiler This is purely a refactor, to make the next change simpler. The wordpack functionality used to be necessary in transform passes, but now it isn't anymore so let's not complicate this more than necessary. --- compiler/goroutine.go | 34 ++++--- compiler/llvm.go | 154 +++++++++++++++++++++++++++- compiler/llvmutil/wordpack.go | 182 ---------------------------------- 3 files changed, 170 insertions(+), 200 deletions(-) delete mode 100644 compiler/llvmutil/wordpack.go diff --git a/compiler/goroutine.go b/compiler/goroutine.go index ba8ec0f4..da4e32ae 100644 --- a/compiler/goroutine.go +++ b/compiler/goroutine.go @@ -7,7 +7,6 @@ import ( "go/token" "go/types" - "github.com/tinygo-org/tinygo/compiler/llvmutil" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -145,8 +144,11 @@ func (b *builder) createGo(instr *ssa.Go) { func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value { var wrapper llvm.Value - builder := c.ctx.NewBuilder() - defer builder.Dispose() + b := &builder{ + compilerContext: c, + Builder: c.ctx.NewBuilder(), + } + defer b.Dispose() var deadlock llvm.Value var deadlockType llvm.Type @@ -170,7 +172,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm. wrapper.SetUnnamedAddr(true) wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name)) entry := c.ctx.AddBasicBlock(wrapper, "entry") - builder.SetInsertPointAtEnd(entry) + b.SetInsertPointAtEnd(entry) if c.Debug { pos := c.program.Fset.Position(pos) @@ -191,7 +193,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm. Optimized: true, }) wrapper.SetSubprogram(difunc) - builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) + b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } // Create the list of params for the call. @@ -199,16 +201,16 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm. if !hasContext { paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter } - params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes) + params := b.emitPointerUnpack(wrapper.Param(0), paramTypes) if !hasContext { params = append(params, llvm.Undef(c.i8ptrType)) // add dummy context parameter } // Create the call. - builder.CreateCall(fnType, fn, params, "") + b.CreateCall(fnType, fn, params, "") if c.Scheduler == "asyncify" { - builder.CreateCall(deadlockType, deadlock, []llvm.Value{ + b.CreateCall(deadlockType, deadlock, []llvm.Value{ llvm.Undef(c.i8ptrType), }, "") } @@ -239,7 +241,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm. wrapper.SetUnnamedAddr(true) wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", "")) entry := c.ctx.AddBasicBlock(wrapper, "entry") - builder.SetInsertPointAtEnd(entry) + b.SetInsertPointAtEnd(entry) if c.Debug { pos := c.program.Fset.Position(pos) @@ -260,23 +262,23 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm. Optimized: true, }) wrapper.SetSubprogram(difunc) - builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) + b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } // Get the list of parameters, with the extra parameters at the end. paramTypes := fnType.ParamTypes() paramTypes = append(paramTypes, fn.Type()) // the last element is the function pointer - params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes) + params := b.emitPointerUnpack(wrapper.Param(0), paramTypes) // Get the function pointer. fnPtr := params[len(params)-1] params = params[:len(params)-1] // Create the call. - builder.CreateCall(fnType, fnPtr, params, "") + b.CreateCall(fnType, fnPtr, params, "") if c.Scheduler == "asyncify" { - builder.CreateCall(deadlockType, deadlock, []llvm.Value{ + b.CreateCall(deadlockType, deadlock, []llvm.Value{ llvm.Undef(c.i8ptrType), }, "") } @@ -284,13 +286,13 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm. if c.Scheduler == "asyncify" { // The goroutine was terminated via deadlock. - builder.CreateUnreachable() + b.CreateUnreachable() } else { // Finish the function. Every basic block must end in a terminator, and // because goroutines never return a value we can simply return void. - builder.CreateRetVoid() + b.CreateRetVoid() } // Return a ptrtoint of the wrapper, not the function itself. - return builder.CreatePtrToInt(wrapper, c.uintptrType, "") + return b.CreatePtrToInt(wrapper, c.uintptrType, "") } diff --git a/compiler/llvm.go b/compiler/llvm.go index 37bc74da..0dddb330 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -51,13 +51,163 @@ func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) { // 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. +// If the values are all constants, they are be stored in a constant global and +// deduplicated. func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value { - return llvmutil.EmitPointerPack(b.Builder, b.mod, b.pkg.Path(), b.NeedsStackObjects, values) + valueTypes := make([]llvm.Type, len(values)) + for i, value := range values { + valueTypes[i] = value.Type() + } + packedType := b.ctx.StructType(valueTypes, false) + + // Allocate memory for the packed data. + size := b.targetData.TypeAllocSize(packedType) + if size == 0 { + return llvm.ConstPointerNull(b.i8ptrType) + } else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind { + return b.CreateBitCast(values[0], b.i8ptrType, "pack.ptr") + } else if size <= b.targetData.TypeAllocSize(b.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 b.CreateIntToPtr(values[0], b.i8ptrType, "pack.int") + } + + // Because packedType is a struct and we have to cast it to a *i8, store + // it in a *i8 alloca first and load the *i8 value from there. This is + // effectively a bitcast. + packedAlloc, _, _ := b.createTemporaryAlloca(b.i8ptrType, "") + + if size < b.targetData.TypeAllocSize(b.i8ptrType) { + // The alloca is bigger than the value that will be stored in it. + // To avoid having some bits undefined, zero the alloca first. + // Hopefully this will get optimized away. + b.CreateStore(llvm.ConstNull(b.i8ptrType), packedAlloc) + } + + // Store all values in the alloca. + packedAllocCast := b.CreateBitCast(packedAlloc, llvm.PointerType(packedType, 0), "") + for i, value := range values { + indices := []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), + } + gep := b.CreateInBoundsGEP(packedType, packedAllocCast, indices, "") + b.CreateStore(value, gep) + } + + // Load value (the *i8) from the alloca. + result := b.CreateLoad(b.i8ptrType, packedAlloc, "") + + // End the lifetime of the alloca, to help the optimizer. + packedPtr := b.CreateBitCast(packedAlloc, b.i8ptrType, "") + packedSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(packedAlloc.Type()), false) + b.emitLifetimeEnd(packedPtr, packedSize) + + return result + } else { + // Check if the values are all constants. + constant := true + for _, v := range values { + if !v.IsConstant() { + constant = false + break + } + } + + if constant { + // The data is known at compile time, so store it in a constant global. + // The global address is marked as unnamed, which allows LLVM to merge duplicates. + global := llvm.AddGlobal(b.mod, packedType, b.pkg.Path()+"$pack") + global.SetInitializer(b.ctx.ConstStruct(values, false)) + global.SetGlobalConstant(true) + global.SetUnnamedAddr(true) + global.SetLinkage(llvm.InternalLinkage) + return llvm.ConstBitCast(global, b.i8ptrType) + } + + // Packed data is bigger than a pointer, so allocate it on the heap. + sizeValue := llvm.ConstInt(b.uintptrType, size, false) + alloc := b.mod.NamedFunction("runtime.alloc") + packedHeapAlloc := b.CreateCall(alloc.GlobalValueType(), alloc, []llvm.Value{ + sizeValue, + llvm.ConstNull(b.i8ptrType), + llvm.Undef(b.i8ptrType), // unused context parameter + }, "") + if b.NeedsStackObjects { + trackPointer := b.mod.NamedFunction("runtime.trackPointer") + b.CreateCall(trackPointer.GlobalValueType(), trackPointer, []llvm.Value{ + packedHeapAlloc, + llvm.Undef(b.i8ptrType), // unused context parameter + }, "") + } + packedAlloc := b.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "") + + // Store all values in the heap pointer. + for i, value := range values { + indices := []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), + } + gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "") + b.CreateStore(value, gep) + } + + // Return the original heap allocation pointer, which already is an *i8. + return packedHeapAlloc + } } // emitPointerUnpack extracts a list of values packed using emitPointerPack. func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value { - return llvmutil.EmitPointerUnpack(b.Builder, b.mod, ptr, valueTypes) + packedType := b.ctx.StructType(valueTypes, false) + + // Get a correctly-typed pointer to the packed data. + var packedAlloc, packedRawAlloc llvm.Value + size := b.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{b.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")} + } else if size <= b.targetData.TypeAllocSize(b.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{b.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")} + } + // Fallback: load it using an alloca. + packedRawAlloc, _, _ = b.createTemporaryAlloca(llvm.PointerType(b.i8ptrType, 0), "unpack.raw.alloc") + packedRawValue := b.CreateBitCast(ptr, llvm.PointerType(b.i8ptrType, 0), "unpack.raw.value") + b.CreateStore(packedRawValue, packedRawAlloc) + packedAlloc = b.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 = b.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 b.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(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), + } + gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "") + values[i] = b.CreateLoad(valueType, gep, "") + } + if !packedRawAlloc.IsNil() { + allocPtr := b.CreateBitCast(packedRawAlloc, b.i8ptrType, "") + allocSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(b.uintptrType), false) + b.emitLifetimeEnd(allocPtr, allocSize) + } + return values } // makeGlobalArray creates a new LLVM global with the given name and integers as diff --git a/compiler/llvmutil/wordpack.go b/compiler/llvmutil/wordpack.go deleted file mode 100644 index 2a4607c8..00000000 --- a/compiler/llvmutil/wordpack.go +++ /dev/null @@ -1,182 +0,0 @@ -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 ( - "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. -// If the values are all constants, they are be stored in a constant global and deduplicated. -func EmitPointerPack(builder llvm.Builder, mod llvm.Module, prefix string, needsStackObjects bool, values []llvm.Value) llvm.Value { - ctx := mod.Context() - targetData := llvm.NewTargetData(mod.DataLayout()) - defer targetData.Dispose() - i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) - uintptrType := ctx.IntType(targetData.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. - 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 a *i8 alloca first and load the *i8 value from there. This is - // effectively a bitcast. - packedAlloc, _, _ := CreateTemporaryAlloca(builder, mod, i8ptrType, "") - - if size < targetData.TypeAllocSize(i8ptrType) { - // The alloca is bigger than the value that will be stored in it. - // To avoid having some bits undefined, zero the alloca first. - // Hopefully this will get optimized away. - builder.CreateStore(llvm.ConstNull(i8ptrType), packedAlloc) - } - - // Store all values in the alloca. - packedAllocCast := builder.CreateBitCast(packedAlloc, llvm.PointerType(packedType, 0), "") - for i, value := range values { - indices := []llvm.Value{ - llvm.ConstInt(ctx.Int32Type(), 0, false), - llvm.ConstInt(ctx.Int32Type(), uint64(i), false), - } - gep := builder.CreateInBoundsGEP(packedType, packedAllocCast, indices, "") - builder.CreateStore(value, gep) - } - - // Load value (the *i8) from the alloca. - result := builder.CreateLoad(i8ptrType, packedAlloc, "") - - // End the lifetime of the alloca, to help the optimizer. - packedPtr := builder.CreateBitCast(packedAlloc, i8ptrType, "") - packedSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(packedAlloc.Type()), false) - EmitLifetimeEnd(builder, mod, packedPtr, packedSize) - - return result - } else { - // Check if the values are all constants. - constant := true - for _, v := range values { - if !v.IsConstant() { - constant = false - break - } - } - - if constant { - // The data is known at compile time, so store it in a constant global. - // The global address is marked as unnamed, which allows LLVM to merge duplicates. - global := llvm.AddGlobal(mod, packedType, prefix+"$pack") - global.SetInitializer(ctx.ConstStruct(values, false)) - global.SetGlobalConstant(true) - global.SetUnnamedAddr(true) - global.SetLinkage(llvm.InternalLinkage) - return llvm.ConstBitCast(global, i8ptrType) - } - - // 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.GlobalValueType(), alloc, []llvm.Value{ - sizeValue, - llvm.ConstNull(i8ptrType), - llvm.Undef(i8ptrType), // unused context parameter - }, "") - if needsStackObjects { - trackPointer := mod.NamedFunction("runtime.trackPointer") - builder.CreateCall(trackPointer.GlobalValueType(), trackPointer, []llvm.Value{ - packedHeapAlloc, - llvm.Undef(i8ptrType), // unused context parameter - }, "") - } - packedAlloc := builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "") - - // Store all values in the 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(packedType, packedAlloc, indices, "") - builder.CreateStore(value, gep) - } - - // Return 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()) - defer targetData.Dispose() - i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) - uintptrType := ctx.IntType(targetData.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(packedType, packedAlloc, indices, "") - values[i] = builder.CreateLoad(valueType, 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 -}