From d89009f5fcd2040cb92a7f6be50799a0661ac43b Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 21 Nov 2019 14:20:12 +0100 Subject: [PATCH 01/21] compiler: refactor IR generation This is the first commit in a series to refactor the compiler. The intention is to make sure every function to be compiled eventually has its own IR builder. This will make it much easier to do other refactorings in the future: * Most code won't depend (directly) on the central Compiler object, perhaps making it possible to eliminate it in the future. Right now it's embedded in the `builder` struct but individual fields from the `Compiler` can easily be moved into the `builder` object. * Some functions are not directly exposed in Go SSA, they are wrapper functions for something. At the moment they are included in the list of functions to be compiled with the reachability analysis (SimpleDCE) in the ir package, but eventually this reachability analys will be removed. At that point, it would be very convenient to be able to simply build a function with a new IR builder. The `compilerContext` struct makes sure that it is not possible for `builder` methods to accidentally use global state such as the global IR builder. It is a transitional mechanism and may be removed when finished. --- compiler/calls.go | 12 +-- compiler/compiler.go | 165 +++++++++++++++++++++++++----------------- compiler/errors.go | 5 +- compiler/func.go | 8 +- compiler/interface.go | 2 +- 5 files changed, 115 insertions(+), 77 deletions(-) diff --git a/compiler/calls.go b/compiler/calls.go index 40d9443aa3..e185d3c396 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -43,10 +43,10 @@ func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llv // Expand an argument type to a list that can be used in a function call // parameter list. -func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type { +func expandFormalParamType(t llvm.Type) []llvm.Type { switch t.TypeKind() { case llvm.StructTypeKind: - fields := c.flattenAggregateType(t) + fields := flattenAggregateType(t) if len(fields) <= MaxFieldsPerParam { return fields } else { @@ -82,7 +82,7 @@ func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 { func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value { switch v.Type().TypeKind() { case llvm.StructTypeKind: - fieldTypes := c.flattenAggregateType(v.Type()) + fieldTypes := flattenAggregateType(v.Type()) if len(fieldTypes) <= MaxFieldsPerParam { fields := c.flattenAggregate(v) if len(fields) != len(fieldTypes) { @@ -101,12 +101,12 @@ func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value { // Try to flatten a struct type to a list of types. Returns a 1-element slice // with the passed in type if this is not possible. -func (c *Compiler) flattenAggregateType(t llvm.Type) []llvm.Type { +func flattenAggregateType(t llvm.Type) []llvm.Type { switch t.TypeKind() { case llvm.StructTypeKind: fields := make([]llvm.Type, 0, t.StructElementTypesCount()) for _, subfield := range t.StructElementTypes() { - subfields := c.flattenAggregateType(subfield) + subfields := flattenAggregateType(subfield) fields = append(fields, subfields...) } return fields @@ -166,7 +166,7 @@ func (c *Compiler) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Va func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) { switch t.TypeKind() { case llvm.StructTypeKind: - if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam { + if len(flattenAggregateType(t)) <= MaxFieldsPerParam { value := llvm.ConstNull(t) for i, subtyp := range t.StructElementTypes() { structField, remaining := c.collapseFormalParamInternal(subtyp, fields) diff --git a/compiler/compiler.go b/compiler/compiler.go index 63b1dc9205..030e673430 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -34,29 +34,43 @@ func init() { // The TinyGo import path. const tinygoPath = "github.com/tinygo-org/tinygo" -type Compiler struct { +// compilerContext contains function-independent data that should still be +// available while compiling every function. It is not strictly read-only, but +// must not contain function-dependent data such as an IR builder. +type compilerContext struct { *compileopts.Config - mod llvm.Module - ctx llvm.Context + mod llvm.Module + ctx llvm.Context + dibuilder *llvm.DIBuilder + cu llvm.Metadata + difiles map[string]llvm.Metadata + ditypes map[types.Type]llvm.Metadata + machine llvm.TargetMachine + targetData llvm.TargetData + intType llvm.Type + i8ptrType llvm.Type // for convenience + funcPtrAddrSpace int + uintptrType llvm.Type + ir *ir.Program + diagnostics []error +} + +type Compiler struct { + compilerContext builder llvm.Builder - dibuilder *llvm.DIBuilder - cu llvm.Metadata - difiles map[string]llvm.Metadata - ditypes map[types.Type]llvm.Metadata - machine llvm.TargetMachine - targetData llvm.TargetData - intType llvm.Type - i8ptrType llvm.Type // for convenience - funcPtrAddrSpace int - uintptrType llvm.Type initFuncs []llvm.Value interfaceInvokeWrappers []interfaceInvokeWrapper - ir *ir.Program - diagnostics []error astComments map[string]*ast.CommentGroup } type Frame struct { + builder +} + +// builder contains all information relevant to build a single function. +type builder struct { + *compilerContext + llvm.Builder fn *ir.Function locals map[ssa.Value]llvm.Value // local variables blockEntries map[*ssa.BasicBlock]llvm.BasicBlock // a *ssa.BasicBlock may be split up @@ -81,9 +95,11 @@ type Phi struct { func NewCompiler(pkgName string, config *compileopts.Config) (*Compiler, error) { c := &Compiler{ - Config: config, - difiles: make(map[string]llvm.Metadata), - ditypes: make(map[types.Type]llvm.Metadata), + compilerContext: compilerContext{ + Config: config, + difiles: make(map[string]llvm.Metadata), + ditypes: make(map[types.Type]llvm.Metadata), + }, } target, err := llvm.GetTargetFromTriple(config.Triple()) @@ -252,6 +268,20 @@ func (c *Compiler) Compile(mainPath string) []error { c.loadASTComments(lprogram) + // Declare runtime types. + // TODO: lazily create runtime types in getLLVMRuntimeType when they are + // needed. Eventually this will be required anyway, when packages are + // compiled independently (and the runtime types are not available). + for _, member := range c.ir.Program.ImportedPackage("runtime").Members { + if member, ok := member.(*ssa.Type); ok { + if typ, ok := member.Type().(*types.Named); ok { + if _, ok := typ.Underlying().(*types.Struct); ok { + c.getLLVMType(typ) + } + } + } + } + // Declare all functions. for _, f := range c.ir.Functions { frames = append(frames, c.parseFuncDecl(f)) @@ -367,23 +397,23 @@ func (c *Compiler) Compile(mainPath string) []error { return c.diagnostics } -// getRuntimeType obtains a named type from the runtime package and returns it -// as a Go type. -func (c *Compiler) getRuntimeType(name string) types.Type { - return c.ir.Program.ImportedPackage("runtime").Type(name).Type() -} - // getLLVMRuntimeType obtains a named type from the runtime package and returns // it as a LLVM type, creating it if necessary. It is a shorthand for // getLLVMType(getRuntimeType(name)). -func (c *Compiler) getLLVMRuntimeType(name string) llvm.Type { - return c.getLLVMType(c.getRuntimeType(name)) +func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type { + fullName := "runtime." + name + typ := c.mod.GetTypeByName(fullName) + if typ.IsNil() { + println(c.mod.String()) + panic("could not find runtime type: " + fullName) + } + return typ } // getLLVMType creates and returns a LLVM type for a Go type. In the case of // named struct types (or Go types implemented as named LLVM structs such as // strings) it also creates it first if necessary. -func (c *Compiler) getLLVMType(goType types.Type) llvm.Type { +func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type { switch typ := goType.(type) { case *types.Array: elemType := c.getLLVMType(typ.Elem()) @@ -705,11 +735,15 @@ func (c *Compiler) getLocalVariable(frame *Frame, variable *types.Var) llvm.Meta func (c *Compiler) parseFuncDecl(f *ir.Function) *Frame { frame := &Frame{ - fn: f, - locals: make(map[ssa.Value]llvm.Value), - dilocals: make(map[*types.Var]llvm.Metadata), - blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock), - blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock), + builder: builder{ + compilerContext: &c.compilerContext, + Builder: c.builder, // TODO: use a separate builder per function + fn: f, + locals: make(map[ssa.Value]llvm.Value), + dilocals: make(map[*types.Var]llvm.Metadata), + blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock), + blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock), + }, } var retType llvm.Type @@ -728,7 +762,7 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) *Frame { var paramTypes []llvm.Type for _, param := range f.Params { paramType := c.getLLVMType(param.Type()) - paramTypeFragments := c.expandFormalParamType(paramType) + paramTypeFragments := expandFormalParamType(paramType) paramTypes = append(paramTypes, paramTypeFragments...) } @@ -871,7 +905,7 @@ func (c *Compiler) parseFunc(frame *Frame) { for _, param := range frame.fn.Params { llvmType := c.getLLVMType(param.Type()) fields := make([]llvm.Value, 0, 1) - for range c.expandFormalParamType(llvmType) { + for range expandFormalParamType(llvmType) { fields = append(fields, frame.fn.LLVMFn.Param(llvmParamIndex)) llvmParamIndex++ } @@ -1368,7 +1402,7 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e func (c *Compiler) getValue(frame *Frame, expr ssa.Value) llvm.Value { switch expr := expr.(type) { case *ssa.Const: - return c.parseConst(frame.fn.LinkName(), expr) + return frame.createConst(frame.fn.LinkName(), expr) case *ssa.Function: fn := c.ir.GetFunction(expr) if fn.IsExported() { @@ -2211,10 +2245,11 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p } } -func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value { +// createConst creates a LLVM constant value from a Go constant. +func (b *builder) createConst(prefix string, expr *ssa.Const) llvm.Value { switch typ := expr.Type().Underlying().(type) { case *types.Basic: - llvmType := c.getLLVMType(typ) + llvmType := b.getLLVMType(typ) if typ.Info()&types.IsBoolean != 0 { b := constant.BoolVal(expr.Value) n := uint64(0) @@ -2224,23 +2259,23 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value { return llvm.ConstInt(llvmType, n, false) } else if typ.Info()&types.IsString != 0 { str := constant.StringVal(expr.Value) - strLen := llvm.ConstInt(c.uintptrType, uint64(len(str)), false) + strLen := llvm.ConstInt(b.uintptrType, uint64(len(str)), false) objname := prefix + "$string" - global := llvm.AddGlobal(c.mod, llvm.ArrayType(c.ctx.Int8Type(), len(str)), objname) - global.SetInitializer(c.ctx.ConstString(str, false)) + global := llvm.AddGlobal(b.mod, llvm.ArrayType(b.ctx.Int8Type(), len(str)), objname) + global.SetInitializer(b.ctx.ConstString(str, false)) global.SetLinkage(llvm.InternalLinkage) global.SetGlobalConstant(true) global.SetUnnamedAddr(true) - zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) - strPtr := c.builder.CreateInBoundsGEP(global, []llvm.Value{zero, zero}, "") - strObj := llvm.ConstNamedStruct(c.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen}) + zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) + strPtr := b.CreateInBoundsGEP(global, []llvm.Value{zero, zero}, "") + strObj := llvm.ConstNamedStruct(b.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen}) return strObj } else if typ.Kind() == types.UnsafePointer { if !expr.IsNil() { value, _ := constant.Uint64Val(expr.Value) - return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, value, false), c.i8ptrType) + return llvm.ConstIntToPtr(llvm.ConstInt(b.uintptrType, value, false), b.i8ptrType) } - return llvm.ConstNull(c.i8ptrType) + return llvm.ConstNull(b.i8ptrType) } else if typ.Info()&types.IsUnsigned != 0 { n, _ := constant.Uint64Val(expr.Value) return llvm.ConstInt(llvmType, n, false) @@ -2251,18 +2286,18 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value { n, _ := constant.Float64Val(expr.Value) return llvm.ConstFloat(llvmType, n) } else if typ.Kind() == types.Complex64 { - r := c.parseConst(prefix, ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float32])) - i := c.parseConst(prefix, ssa.NewConst(constant.Imag(expr.Value), types.Typ[types.Float32])) - cplx := llvm.Undef(c.ctx.StructType([]llvm.Type{c.ctx.FloatType(), c.ctx.FloatType()}, false)) - cplx = c.builder.CreateInsertValue(cplx, r, 0, "") - cplx = c.builder.CreateInsertValue(cplx, i, 1, "") + r := b.createConst(prefix, ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float32])) + i := b.createConst(prefix, ssa.NewConst(constant.Imag(expr.Value), types.Typ[types.Float32])) + cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.FloatType(), b.ctx.FloatType()}, false)) + cplx = b.CreateInsertValue(cplx, r, 0, "") + cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx } else if typ.Kind() == types.Complex128 { - r := c.parseConst(prefix, ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float64])) - i := c.parseConst(prefix, ssa.NewConst(constant.Imag(expr.Value), types.Typ[types.Float64])) - cplx := llvm.Undef(c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false)) - cplx = c.builder.CreateInsertValue(cplx, r, 0, "") - cplx = c.builder.CreateInsertValue(cplx, i, 1, "") + r := b.createConst(prefix, ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float64])) + i := b.createConst(prefix, ssa.NewConst(constant.Imag(expr.Value), types.Typ[types.Float64])) + cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.DoubleType(), b.ctx.DoubleType()}, false)) + cplx = b.CreateInsertValue(cplx, r, 0, "") + cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx } else { panic("unknown constant of basic type: " + expr.String()) @@ -2271,35 +2306,35 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value { if expr.Value != nil { panic("expected nil chan constant") } - return llvm.ConstNull(c.getLLVMType(expr.Type())) + return llvm.ConstNull(b.getLLVMType(expr.Type())) case *types.Signature: if expr.Value != nil { panic("expected nil signature constant") } - return llvm.ConstNull(c.getLLVMType(expr.Type())) + return llvm.ConstNull(b.getLLVMType(expr.Type())) case *types.Interface: if expr.Value != nil { panic("expected nil interface constant") } // Create a generic nil interface with no dynamic type (typecode=0). fields := []llvm.Value{ - llvm.ConstInt(c.uintptrType, 0, false), - llvm.ConstPointerNull(c.i8ptrType), + llvm.ConstInt(b.uintptrType, 0, false), + llvm.ConstPointerNull(b.i8ptrType), } - return llvm.ConstNamedStruct(c.getLLVMRuntimeType("_interface"), fields) + return llvm.ConstNamedStruct(b.getLLVMRuntimeType("_interface"), fields) case *types.Pointer: if expr.Value != nil { panic("expected nil pointer constant") } - return llvm.ConstPointerNull(c.getLLVMType(typ)) + return llvm.ConstPointerNull(b.getLLVMType(typ)) case *types.Slice: if expr.Value != nil { panic("expected nil slice constant") } - elemType := c.getLLVMType(typ.Elem()) + elemType := b.getLLVMType(typ.Elem()) llvmPtr := llvm.ConstPointerNull(llvm.PointerType(elemType, 0)) - llvmLen := llvm.ConstInt(c.uintptrType, 0, false) - slice := c.ctx.ConstStruct([]llvm.Value{ + llvmLen := llvm.ConstInt(b.uintptrType, 0, false) + slice := b.ctx.ConstStruct([]llvm.Value{ llvmPtr, // backing array llvmLen, // len llvmLen, // cap @@ -2310,7 +2345,7 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value { // I believe this is not allowed by the Go spec. panic("non-nil map constant") } - llvmType := c.getLLVMType(typ) + llvmType := b.getLLVMType(typ) return llvm.ConstNull(llvmType) default: panic("unknown constant: " + expr.String()) diff --git a/compiler/errors.go b/compiler/errors.go index b2d8cc766d..37205f01f6 100644 --- a/compiler/errors.go +++ b/compiler/errors.go @@ -1,5 +1,7 @@ package compiler +// This file contains some utility functions related to error handling. + import ( "go/scanner" "go/token" @@ -9,7 +11,8 @@ import ( "tinygo.org/x/go-llvm" ) -func (c *Compiler) makeError(pos token.Pos, msg string) types.Error { +// makeError makes it easy to create an error from a token.Pos with a message. +func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error { return types.Error{ Fset: c.ir.Program.Fset, Pos: pos, diff --git a/compiler/func.go b/compiler/func.go index b79b17f822..b55d5240db 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -76,7 +76,7 @@ func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) ( } // getFuncType returns the type of a func value given a signature. -func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type { +func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type { switch c.FuncImplementation() { case compileopts.FuncValueDoubleword: rawPtr := c.getRawFuncType(typ) @@ -89,7 +89,7 @@ func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type { } // getRawFuncType returns a LLVM function pointer type for a given signature. -func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type { +func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type { // Get the return type. var returnType llvm.Type switch typ.Results().Len() { @@ -119,11 +119,11 @@ func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type { // The receiver is not an interface, but a i8* type. recv = c.i8ptrType } - paramTypes = append(paramTypes, c.expandFormalParamType(recv)...) + paramTypes = append(paramTypes, expandFormalParamType(recv)...) } for i := 0; i < typ.Params().Len(); i++ { subType := c.getLLVMType(typ.Params().At(i).Type()) - paramTypes = append(paramTypes, c.expandFormalParamType(subType)...) + paramTypes = append(paramTypes, expandFormalParamType(subType)...) } // All functions take these parameters at the end. paramTypes = append(paramTypes, c.i8ptrType) // context diff --git a/compiler/interface.go b/compiler/interface.go index 761a45c5ca..f1599cd20b 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -445,7 +445,7 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { // Get the expanded receiver type. receiverType := c.getLLVMType(f.Params[0].Type()) - expandedReceiverType := c.expandFormalParamType(receiverType) + expandedReceiverType := expandFormalParamType(receiverType) // Does this method even need any wrapping? if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind { From 54e0bfedc80a61218a701824eb4a5581a0196859 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 22 Nov 2019 19:53:16 +0100 Subject: [PATCH 02/21] compiler: refactor createBinOp This commit unfortunately introduces a significant amount of code duplication. However, all that duplicate code should be removed once this refactor is done. --- compiler/calls.go | 64 ++++++++++++ compiler/compiler.go | 230 ++++++++++++++++++++++--------------------- compiler/func.go | 6 ++ 3 files changed, 188 insertions(+), 112 deletions(-) diff --git a/compiler/calls.go b/compiler/calls.go index e185d3c396..b59fb30746 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -31,6 +31,18 @@ func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name stri return c.createCall(fn.LLVMFn, args, name) } +// createCall creates a new call to runtime. with the given arguments. +func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value { + fullName := "runtime." + fnName + fn := b.mod.NamedFunction(fullName) + if fn.IsNil() { + panic("trying to call non-existent function: " + fullName) + } + args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter + args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle + return b.createCall(fn, args, name) +} + // Create a call to the given function with the arguments possibly expanded. func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value { expanded := make([]llvm.Value, 0, len(args)) @@ -41,6 +53,17 @@ func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llv return c.builder.CreateCall(fn, expanded, name) } +// createCall creates a call to the given function with the arguments possibly +// expanded. +func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value { + expanded := make([]llvm.Value, 0, len(args)) + for _, arg := range args { + fragments := b.expandFormalParam(arg) + expanded = append(expanded, fragments...) + } + return b.CreateCall(fn, expanded, name) +} + // Expand an argument type to a list that can be used in a function call // parameter list. func expandFormalParamType(t llvm.Type) []llvm.Type { @@ -99,6 +122,30 @@ func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value { } } +// expandFormalParam splits a formal param value into pieces, so it can be +// passed directly as part of a function call. For example, it splits up small +// structs into individual fields. It is the equivalent of expandFormalParamType +// for parameter values. +func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value { + switch v.Type().TypeKind() { + case llvm.StructTypeKind: + fieldTypes := flattenAggregateType(v.Type()) + if len(fieldTypes) <= MaxFieldsPerParam { + fields := b.flattenAggregate(v) + if len(fields) != len(fieldTypes) { + panic("type and value param lowering don't match") + } + return fields + } else { + // failed to lower + return []llvm.Value{v} + } + default: + // TODO: split small arrays + return []llvm.Value{v} + } +} + // Try to flatten a struct type to a list of types. Returns a 1-element slice // with the passed in type if this is not possible. func flattenAggregateType(t llvm.Type) []llvm.Type { @@ -153,6 +200,23 @@ func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value { } } +// flattenAggregate breaks down a struct into its elementary values for argument +// passing. It is the value equivalent of flattenAggregateType +func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value { + switch v.Type().TypeKind() { + case llvm.StructTypeKind: + fields := make([]llvm.Value, 0, v.Type().StructElementTypesCount()) + for i := range v.Type().StructElementTypes() { + subfield := b.CreateExtractValue(v, i, "") + subfields := b.flattenAggregate(subfield) + fields = append(fields, subfields...) + } + return fields + default: + return []llvm.Value{v} + } +} + // Collapse a list of fields into its original value. func (c *Compiler) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value { param, remaining := c.collapseFormalParamInternal(t, fields) diff --git a/compiler/compiler.go b/compiler/compiler.go index 030e673430..05540ab6ed 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1460,7 +1460,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { case *ssa.BinOp: x := c.getValue(frame, expr.X) y := c.getValue(frame, expr.Y) - return c.parseBinOp(expr.Op, expr.X.Type(), x, y, expr.Pos()) + return frame.createBinOp(expr.Op, expr.X.Type(), x, y, expr.Pos()) case *ssa.Call: // Passing the current task here to the subroutine. It is only used when // the subroutine is blocking. @@ -1905,7 +1905,13 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { } } -func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, pos token.Pos) (llvm.Value, error) { +// createBinOp creates a LLVM binary operation (add, sub, mul, etc) for a Go +// binary operation. This is almost a direct mapping, but there are some subtle +// differences such as the requirement in LLVM IR that both sides must have the +// same type, even for bitshifts. Also, signedness in Go is encoded in the type +// and is encoded in the operation in LLVM IR: this is important for some +// operations such as divide. +func (b *builder) createBinOp(op token.Token, typ types.Type, x, y llvm.Value, pos token.Pos) (llvm.Value, error) { switch typ := typ.Underlying().(type) { case *types.Basic: if typ.Info()&types.IsInteger != 0 { @@ -1913,87 +1919,87 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p signed := typ.Info()&types.IsUnsigned == 0 switch op { case token.ADD: // + - return c.builder.CreateAdd(x, y, ""), nil + return b.CreateAdd(x, y, ""), nil case token.SUB: // - - return c.builder.CreateSub(x, y, ""), nil + return b.CreateSub(x, y, ""), nil case token.MUL: // * - return c.builder.CreateMul(x, y, ""), nil + return b.CreateMul(x, y, ""), nil case token.QUO: // / if signed { - return c.builder.CreateSDiv(x, y, ""), nil + return b.CreateSDiv(x, y, ""), nil } else { - return c.builder.CreateUDiv(x, y, ""), nil + return b.CreateUDiv(x, y, ""), nil } case token.REM: // % if signed { - return c.builder.CreateSRem(x, y, ""), nil + return b.CreateSRem(x, y, ""), nil } else { - return c.builder.CreateURem(x, y, ""), nil + return b.CreateURem(x, y, ""), nil } case token.AND: // & - return c.builder.CreateAnd(x, y, ""), nil + return b.CreateAnd(x, y, ""), nil case token.OR: // | - return c.builder.CreateOr(x, y, ""), nil + return b.CreateOr(x, y, ""), nil case token.XOR: // ^ - return c.builder.CreateXor(x, y, ""), nil + return b.CreateXor(x, y, ""), nil case token.SHL, token.SHR: - sizeX := c.targetData.TypeAllocSize(x.Type()) - sizeY := c.targetData.TypeAllocSize(y.Type()) + sizeX := b.targetData.TypeAllocSize(x.Type()) + sizeY := b.targetData.TypeAllocSize(y.Type()) if sizeX > sizeY { // x and y must have equal sizes, make Y bigger in this case. // y is unsigned, this has been checked by the Go type checker. - y = c.builder.CreateZExt(y, x.Type(), "") + y = b.CreateZExt(y, x.Type(), "") } else if sizeX < sizeY { // What about shifting more than the integer width? // I'm not entirely sure what the Go spec is on that, but as // Intel CPUs have undefined behavior when shifting more // than the integer width I'm assuming it is also undefined // in Go. - y = c.builder.CreateTrunc(y, x.Type(), "") + y = b.CreateTrunc(y, x.Type(), "") } switch op { case token.SHL: // << - return c.builder.CreateShl(x, y, ""), nil + return b.CreateShl(x, y, ""), nil case token.SHR: // >> if signed { - return c.builder.CreateAShr(x, y, ""), nil + return b.CreateAShr(x, y, ""), nil } else { - return c.builder.CreateLShr(x, y, ""), nil + return b.CreateLShr(x, y, ""), nil } default: panic("unreachable") } case token.EQL: // == - return c.builder.CreateICmp(llvm.IntEQ, x, y, ""), nil + return b.CreateICmp(llvm.IntEQ, x, y, ""), nil case token.NEQ: // != - return c.builder.CreateICmp(llvm.IntNE, x, y, ""), nil + return b.CreateICmp(llvm.IntNE, x, y, ""), nil case token.AND_NOT: // &^ // Go specific. Calculate "and not" with x & (~y) - inv := c.builder.CreateNot(y, "") // ~y - return c.builder.CreateAnd(x, inv, ""), nil + inv := b.CreateNot(y, "") // ~y + return b.CreateAnd(x, inv, ""), nil case token.LSS: // < if signed { - return c.builder.CreateICmp(llvm.IntSLT, x, y, ""), nil + return b.CreateICmp(llvm.IntSLT, x, y, ""), nil } else { - return c.builder.CreateICmp(llvm.IntULT, x, y, ""), nil + return b.CreateICmp(llvm.IntULT, x, y, ""), nil } case token.LEQ: // <= if signed { - return c.builder.CreateICmp(llvm.IntSLE, x, y, ""), nil + return b.CreateICmp(llvm.IntSLE, x, y, ""), nil } else { - return c.builder.CreateICmp(llvm.IntULE, x, y, ""), nil + return b.CreateICmp(llvm.IntULE, x, y, ""), nil } case token.GTR: // > if signed { - return c.builder.CreateICmp(llvm.IntSGT, x, y, ""), nil + return b.CreateICmp(llvm.IntSGT, x, y, ""), nil } else { - return c.builder.CreateICmp(llvm.IntUGT, x, y, ""), nil + return b.CreateICmp(llvm.IntUGT, x, y, ""), nil } case token.GEQ: // >= if signed { - return c.builder.CreateICmp(llvm.IntSGE, x, y, ""), nil + return b.CreateICmp(llvm.IntSGE, x, y, ""), nil } else { - return c.builder.CreateICmp(llvm.IntUGE, x, y, ""), nil + return b.CreateICmp(llvm.IntUGE, x, y, ""), nil } default: panic("binop on integer: " + op.String()) @@ -2002,58 +2008,58 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p // Operations on floats switch op { case token.ADD: // + - return c.builder.CreateFAdd(x, y, ""), nil + return b.CreateFAdd(x, y, ""), nil case token.SUB: // - - return c.builder.CreateFSub(x, y, ""), nil + return b.CreateFSub(x, y, ""), nil case token.MUL: // * - return c.builder.CreateFMul(x, y, ""), nil + return b.CreateFMul(x, y, ""), nil case token.QUO: // / - return c.builder.CreateFDiv(x, y, ""), nil + return b.CreateFDiv(x, y, ""), nil case token.EQL: // == - return c.builder.CreateFCmp(llvm.FloatUEQ, x, y, ""), nil + return b.CreateFCmp(llvm.FloatUEQ, x, y, ""), nil case token.NEQ: // != - return c.builder.CreateFCmp(llvm.FloatUNE, x, y, ""), nil + return b.CreateFCmp(llvm.FloatUNE, x, y, ""), nil case token.LSS: // < - return c.builder.CreateFCmp(llvm.FloatULT, x, y, ""), nil + return b.CreateFCmp(llvm.FloatULT, x, y, ""), nil case token.LEQ: // <= - return c.builder.CreateFCmp(llvm.FloatULE, x, y, ""), nil + return b.CreateFCmp(llvm.FloatULE, x, y, ""), nil case token.GTR: // > - return c.builder.CreateFCmp(llvm.FloatUGT, x, y, ""), nil + return b.CreateFCmp(llvm.FloatUGT, x, y, ""), nil case token.GEQ: // >= - return c.builder.CreateFCmp(llvm.FloatUGE, x, y, ""), nil + return b.CreateFCmp(llvm.FloatUGE, x, y, ""), nil default: panic("binop on float: " + op.String()) } } else if typ.Info()&types.IsComplex != 0 { - r1 := c.builder.CreateExtractValue(x, 0, "r1") - r2 := c.builder.CreateExtractValue(y, 0, "r2") - i1 := c.builder.CreateExtractValue(x, 1, "i1") - i2 := c.builder.CreateExtractValue(y, 1, "i2") + r1 := b.CreateExtractValue(x, 0, "r1") + r2 := b.CreateExtractValue(y, 0, "r2") + i1 := b.CreateExtractValue(x, 1, "i1") + i2 := b.CreateExtractValue(y, 1, "i2") switch op { case token.EQL: // == - req := c.builder.CreateFCmp(llvm.FloatOEQ, r1, r2, "") - ieq := c.builder.CreateFCmp(llvm.FloatOEQ, i1, i2, "") - return c.builder.CreateAnd(req, ieq, ""), nil + req := b.CreateFCmp(llvm.FloatOEQ, r1, r2, "") + ieq := b.CreateFCmp(llvm.FloatOEQ, i1, i2, "") + return b.CreateAnd(req, ieq, ""), nil case token.NEQ: // != - req := c.builder.CreateFCmp(llvm.FloatOEQ, r1, r2, "") - ieq := c.builder.CreateFCmp(llvm.FloatOEQ, i1, i2, "") - neq := c.builder.CreateAnd(req, ieq, "") - return c.builder.CreateNot(neq, ""), nil + req := b.CreateFCmp(llvm.FloatOEQ, r1, r2, "") + ieq := b.CreateFCmp(llvm.FloatOEQ, i1, i2, "") + neq := b.CreateAnd(req, ieq, "") + return b.CreateNot(neq, ""), nil case token.ADD, token.SUB: var r, i llvm.Value switch op { case token.ADD: - r = c.builder.CreateFAdd(r1, r2, "") - i = c.builder.CreateFAdd(i1, i2, "") + r = b.CreateFAdd(r1, r2, "") + i = b.CreateFAdd(i1, i2, "") case token.SUB: - r = c.builder.CreateFSub(r1, r2, "") - i = c.builder.CreateFSub(i1, i2, "") + r = b.CreateFSub(r1, r2, "") + i = b.CreateFSub(i1, i2, "") default: panic("unreachable") } - cplx := llvm.Undef(c.ctx.StructType([]llvm.Type{r.Type(), i.Type()}, false)) - cplx = c.builder.CreateInsertValue(cplx, r, 0, "") - cplx = c.builder.CreateInsertValue(cplx, i, 1, "") + cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{r.Type(), i.Type()}, false)) + cplx = b.CreateInsertValue(cplx, r, 0, "") + cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil case token.MUL: // Complex multiplication follows the current implementation in @@ -2068,11 +2074,11 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p // http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf#page=549 // See https://github.com/golang/go/issues/29846 for a related // discussion. - r := c.builder.CreateFSub(c.builder.CreateFMul(r1, r2, ""), c.builder.CreateFMul(i1, i2, ""), "") - i := c.builder.CreateFAdd(c.builder.CreateFMul(r1, i2, ""), c.builder.CreateFMul(i1, r2, ""), "") - cplx := llvm.Undef(c.ctx.StructType([]llvm.Type{r.Type(), i.Type()}, false)) - cplx = c.builder.CreateInsertValue(cplx, r, 0, "") - cplx = c.builder.CreateInsertValue(cplx, i, 1, "") + r := b.CreateFSub(b.CreateFMul(r1, r2, ""), b.CreateFMul(i1, i2, ""), "") + i := b.CreateFAdd(b.CreateFMul(r1, i2, ""), b.CreateFMul(i1, r2, ""), "") + cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{r.Type(), i.Type()}, false)) + cplx = b.CreateInsertValue(cplx, r, 0, "") + cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil case token.QUO: // Complex division. @@ -2080,9 +2086,9 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p // inline. switch r1.Type().TypeKind() { case llvm.FloatTypeKind: - return c.createRuntimeCall("complex64div", []llvm.Value{x, y}, ""), nil + return b.createRuntimeCall("complex64div", []llvm.Value{x, y}, ""), nil case llvm.DoubleTypeKind: - return c.createRuntimeCall("complex128div", []llvm.Value{x, y}, ""), nil + return b.createRuntimeCall("complex128div", []llvm.Value{x, y}, ""), nil default: panic("unexpected complex type") } @@ -2093,9 +2099,9 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p // Operations on booleans switch op { case token.EQL: // == - return c.builder.CreateICmp(llvm.IntEQ, x, y, ""), nil + return b.CreateICmp(llvm.IntEQ, x, y, ""), nil case token.NEQ: // != - return c.builder.CreateICmp(llvm.IntNE, x, y, ""), nil + return b.CreateICmp(llvm.IntNE, x, y, ""), nil default: panic("binop on bool: " + op.String()) } @@ -2103,9 +2109,9 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p // Operations on pointers switch op { case token.EQL: // == - return c.builder.CreateICmp(llvm.IntEQ, x, y, ""), nil + return b.CreateICmp(llvm.IntEQ, x, y, ""), nil case token.NEQ: // != - return c.builder.CreateICmp(llvm.IntNE, x, y, ""), nil + return b.CreateICmp(llvm.IntNE, x, y, ""), nil default: panic("binop on pointer: " + op.String()) } @@ -2113,27 +2119,27 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p // Operations on strings switch op { case token.ADD: // + - return c.createRuntimeCall("stringConcat", []llvm.Value{x, y}, ""), nil + return b.createRuntimeCall("stringConcat", []llvm.Value{x, y}, ""), nil case token.EQL: // == - return c.createRuntimeCall("stringEqual", []llvm.Value{x, y}, ""), nil + return b.createRuntimeCall("stringEqual", []llvm.Value{x, y}, ""), nil case token.NEQ: // != - result := c.createRuntimeCall("stringEqual", []llvm.Value{x, y}, "") - return c.builder.CreateNot(result, ""), nil + result := b.createRuntimeCall("stringEqual", []llvm.Value{x, y}, "") + return b.CreateNot(result, ""), nil case token.LSS: // < - return c.createRuntimeCall("stringLess", []llvm.Value{x, y}, ""), nil + return b.createRuntimeCall("stringLess", []llvm.Value{x, y}, ""), nil case token.LEQ: // <= - result := c.createRuntimeCall("stringLess", []llvm.Value{y, x}, "") - return c.builder.CreateNot(result, ""), nil + result := b.createRuntimeCall("stringLess", []llvm.Value{y, x}, "") + return b.CreateNot(result, ""), nil case token.GTR: // > - result := c.createRuntimeCall("stringLess", []llvm.Value{x, y}, "") - return c.builder.CreateNot(result, ""), nil + result := b.createRuntimeCall("stringLess", []llvm.Value{x, y}, "") + return b.CreateNot(result, ""), nil case token.GEQ: // >= - return c.createRuntimeCall("stringLess", []llvm.Value{y, x}, ""), nil + return b.createRuntimeCall("stringLess", []llvm.Value{y, x}, ""), nil default: panic("binop on string: " + op.String()) } } else { - return llvm.Value{}, c.makeError(pos, "todo: unknown basic type in binop: "+typ.String()) + return llvm.Value{}, b.makeError(pos, "todo: unknown basic type in binop: "+typ.String()) } case *types.Signature: // Get raw scalars from the function value and compare those. @@ -2141,26 +2147,26 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p // have some way of getting a scalar value identifying the function. // This is safe: function pointers are generally not comparable // against each other, only against nil. So one of these has to be nil. - x = c.extractFuncScalar(x) - y = c.extractFuncScalar(y) + x = b.extractFuncScalar(x) + y = b.extractFuncScalar(y) switch op { case token.EQL: // == - return c.builder.CreateICmp(llvm.IntEQ, x, y, ""), nil + return b.CreateICmp(llvm.IntEQ, x, y, ""), nil case token.NEQ: // != - return c.builder.CreateICmp(llvm.IntNE, x, y, ""), nil + return b.CreateICmp(llvm.IntNE, x, y, ""), nil default: - return llvm.Value{}, c.makeError(pos, "binop on signature: "+op.String()) + return llvm.Value{}, b.makeError(pos, "binop on signature: "+op.String()) } case *types.Interface: switch op { case token.EQL, token.NEQ: // ==, != - result := c.createRuntimeCall("interfaceEqual", []llvm.Value{x, y}, "") + result := b.createRuntimeCall("interfaceEqual", []llvm.Value{x, y}, "") if op == token.NEQ { - result = c.builder.CreateNot(result, "") + result = b.CreateNot(result, "") } return result, nil default: - return llvm.Value{}, c.makeError(pos, "binop on interface: "+op.String()) + return llvm.Value{}, b.makeError(pos, "binop on interface: "+op.String()) } case *types.Chan, *types.Map, *types.Pointer: // Maps are in general not comparable, but can be compared against nil @@ -2170,78 +2176,78 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p // are created with the same call to make or if both are nil. switch op { case token.EQL: // == - return c.builder.CreateICmp(llvm.IntEQ, x, y, ""), nil + return b.CreateICmp(llvm.IntEQ, x, y, ""), nil case token.NEQ: // != - return c.builder.CreateICmp(llvm.IntNE, x, y, ""), nil + return b.CreateICmp(llvm.IntNE, x, y, ""), nil default: - return llvm.Value{}, c.makeError(pos, "todo: binop on pointer: "+op.String()) + return llvm.Value{}, b.makeError(pos, "todo: binop on pointer: "+op.String()) } case *types.Slice: // Slices are in general not comparable, but can be compared against // nil. Assume at least one of them is nil to make the code easier. - xPtr := c.builder.CreateExtractValue(x, 0, "") - yPtr := c.builder.CreateExtractValue(y, 0, "") + xPtr := b.CreateExtractValue(x, 0, "") + yPtr := b.CreateExtractValue(y, 0, "") switch op { case token.EQL: // == - return c.builder.CreateICmp(llvm.IntEQ, xPtr, yPtr, ""), nil + return b.CreateICmp(llvm.IntEQ, xPtr, yPtr, ""), nil case token.NEQ: // != - return c.builder.CreateICmp(llvm.IntNE, xPtr, yPtr, ""), nil + return b.CreateICmp(llvm.IntNE, xPtr, yPtr, ""), nil default: - return llvm.Value{}, c.makeError(pos, "todo: binop on slice: "+op.String()) + return llvm.Value{}, b.makeError(pos, "todo: binop on slice: "+op.String()) } case *types.Array: // Compare each array element and combine the result. From the spec: // Array values are comparable if values of the array element type // are comparable. Two array values are equal if their corresponding // elements are equal. - result := llvm.ConstInt(c.ctx.Int1Type(), 1, true) + result := llvm.ConstInt(b.ctx.Int1Type(), 1, true) for i := 0; i < int(typ.Len()); i++ { - xField := c.builder.CreateExtractValue(x, i, "") - yField := c.builder.CreateExtractValue(y, i, "") - fieldEqual, err := c.parseBinOp(token.EQL, typ.Elem(), xField, yField, pos) + xField := b.CreateExtractValue(x, i, "") + yField := b.CreateExtractValue(y, i, "") + fieldEqual, err := b.createBinOp(token.EQL, typ.Elem(), xField, yField, pos) if err != nil { return llvm.Value{}, err } - result = c.builder.CreateAnd(result, fieldEqual, "") + result = b.CreateAnd(result, fieldEqual, "") } switch op { case token.EQL: // == return result, nil case token.NEQ: // != - return c.builder.CreateNot(result, ""), nil + return b.CreateNot(result, ""), nil default: - return llvm.Value{}, c.makeError(pos, "unknown: binop on struct: "+op.String()) + return llvm.Value{}, b.makeError(pos, "unknown: binop on struct: "+op.String()) } case *types.Struct: // Compare each struct field and combine the result. From the spec: // Struct values are comparable if all their fields are comparable. // Two struct values are equal if their corresponding non-blank // fields are equal. - result := llvm.ConstInt(c.ctx.Int1Type(), 1, true) + result := llvm.ConstInt(b.ctx.Int1Type(), 1, true) for i := 0; i < typ.NumFields(); i++ { if typ.Field(i).Name() == "_" { // skip blank fields continue } fieldType := typ.Field(i).Type() - xField := c.builder.CreateExtractValue(x, i, "") - yField := c.builder.CreateExtractValue(y, i, "") - fieldEqual, err := c.parseBinOp(token.EQL, fieldType, xField, yField, pos) + xField := b.CreateExtractValue(x, i, "") + yField := b.CreateExtractValue(y, i, "") + fieldEqual, err := b.createBinOp(token.EQL, fieldType, xField, yField, pos) if err != nil { return llvm.Value{}, err } - result = c.builder.CreateAnd(result, fieldEqual, "") + result = b.CreateAnd(result, fieldEqual, "") } switch op { case token.EQL: // == return result, nil case token.NEQ: // != - return c.builder.CreateNot(result, ""), nil + return b.CreateNot(result, ""), nil default: - return llvm.Value{}, c.makeError(pos, "unknown: binop on struct: "+op.String()) + return llvm.Value{}, b.makeError(pos, "unknown: binop on struct: "+op.String()) } default: - return llvm.Value{}, c.makeError(pos, "todo: binop type: "+typ.String()) + return llvm.Value{}, b.makeError(pos, "todo: binop type: "+typ.String()) } } diff --git a/compiler/func.go b/compiler/func.go index b55d5240db..c64c52c981 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -51,6 +51,12 @@ func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value { return c.builder.CreateExtractValue(funcValue, 1, "") } +// extractFuncScalar returns some scalar that can be used in comparisons. It is +// a cheap operation. +func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value { + return b.CreateExtractValue(funcValue, 1, "") +} + // extractFuncContext extracts the context pointer from this function value. It // is a cheap operation. func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value { From ab21d56b23b2b4b61f1806c1cd9d9178473a2bde Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 23 Nov 2019 00:01:20 +0100 Subject: [PATCH 03/21] compiler: rename Compiler.getValue -> builder.getValue This is a fairly big commit, but it actually changes very little. getValue should really be a property of the builder (or frame), where the previously created instructions are kept. --- compiler/channel.go | 18 +++--- compiler/compiler.go | 138 +++++++++++++++++++++--------------------- compiler/defer.go | 10 +-- compiler/errors.go | 2 +- compiler/func.go | 18 +++++- compiler/inlineasm.go | 10 +-- compiler/interface.go | 10 +-- compiler/interrupt.go | 2 +- compiler/llvm.go | 2 +- compiler/map.go | 2 +- compiler/symbol.go | 4 +- compiler/syscall.go | 10 +-- compiler/volatile.go | 6 +- 13 files changed, 122 insertions(+), 110 deletions(-) diff --git a/compiler/channel.go b/compiler/channel.go index de7b12e291..bdfdf9b91e 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -14,7 +14,7 @@ import ( func (c *Compiler) emitMakeChan(frame *Frame, expr *ssa.MakeChan) llvm.Value { elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem())) elementSizeValue := llvm.ConstInt(c.uintptrType, elementSize, false) - bufSize := c.getValue(frame, expr.Size) + bufSize := frame.getValue(expr.Size) c.emitChanBoundsCheck(frame, elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos()) if bufSize.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() { bufSize = c.builder.CreateZExt(bufSize, c.uintptrType, "") @@ -27,8 +27,8 @@ func (c *Compiler) emitMakeChan(frame *Frame, expr *ssa.MakeChan) llvm.Value { // emitChanSend emits a pseudo chan send operation. It is lowered to the actual // channel send operation during goroutine lowering. func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) { - ch := c.getValue(frame, instr.Chan) - chanValue := c.getValue(frame, instr.X) + ch := frame.getValue(instr.Chan) + chanValue := frame.getValue(instr.X) // store value-to-send valueType := c.getLLVMType(instr.X.Type()) @@ -48,7 +48,7 @@ func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) { // actual channel receive operation during goroutine lowering. func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value { valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem()) - ch := c.getValue(frame, unop.X) + ch := frame.getValue(unop.X) // Allocate memory to receive into. valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value") @@ -70,7 +70,7 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value { // emitChanClose closes the given channel. func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) { - ch := c.getValue(frame, param) + ch := frame.getValue(param) c.createRuntimeCall("chanClose", []llvm.Value{ch}, "") } @@ -111,7 +111,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value { var selectStates []llvm.Value chanSelectStateType := c.getLLVMRuntimeType("chanSelectState") for _, state := range expr.States { - ch := c.getValue(frame, state.Chan) + ch := frame.getValue(state.Chan) selectState := llvm.ConstNull(chanSelectStateType) selectState = c.builder.CreateInsertValue(selectState, ch, 0, "") switch state.Dir { @@ -128,7 +128,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value { case types.SendOnly: // Store this value in an alloca and put a pointer to this alloca // in the send state. - sendValue := c.getValue(frame, state.Send) + sendValue := frame.getValue(state.Send) alloca := llvmutil.CreateEntryBlockAlloca(c.builder, sendValue.Type(), "select.send.value") c.builder.CreateStore(sendValue, alloca) ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "") @@ -219,7 +219,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value { func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Value { if expr.Index == 0 { // index - value := c.getValue(frame, expr.Tuple) + value := frame.getValue(expr.Tuple) index := c.builder.CreateExtractValue(value, expr.Index, "") if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() { index = c.builder.CreateSExt(index, c.intType, "") @@ -227,7 +227,7 @@ func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Val return index } else if expr.Index == 1 { // comma-ok - value := c.getValue(frame, expr.Tuple) + value := frame.getValue(expr.Tuple) return c.builder.CreateExtractValue(value, expr.Index, "") } else { // Select statements are (index, ok, ...) where ... is a number of diff --git a/compiler/compiler.go b/compiler/compiler.go index 05540ab6ed..db2ca66fb5 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -53,6 +53,7 @@ type compilerContext struct { uintptrType llvm.Type ir *ir.Program diagnostics []error + astComments map[string]*ast.CommentGroup } type Compiler struct { @@ -60,7 +61,6 @@ type Compiler struct { builder llvm.Builder initFuncs []llvm.Value interfaceInvokeWrappers []interfaceInvokeWrapper - astComments map[string]*ast.CommentGroup } type Frame struct { @@ -513,7 +513,7 @@ func isPointer(typ types.Type) bool { } // Get the DWARF type for this Go type. -func (c *Compiler) getDIType(typ types.Type) llvm.Metadata { +func (c *compilerContext) getDIType(typ types.Type) llvm.Metadata { if md, ok := c.ditypes[typ]; ok { return md } @@ -524,7 +524,7 @@ func (c *Compiler) getDIType(typ types.Type) llvm.Metadata { // createDIType creates a new DWARF type. Don't call this function directly, // call getDIType instead. -func (c *Compiler) createDIType(typ types.Type) llvm.Metadata { +func (c *compilerContext) createDIType(typ types.Type) llvm.Metadata { llvmType := c.getLLVMType(typ) sizeInBytes := c.targetData.TypeAllocSize(llvmType) switch typ := typ.(type) { @@ -836,7 +836,7 @@ func (c *Compiler) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix, // getDIFile returns a DIFile metadata node for the given filename. It tries to // use one that was already created, otherwise it falls back to creating a new // one. -func (c *Compiler) getDIFile(filename string) llvm.Metadata { +func (c *compilerContext) getDIFile(filename string) llvm.Metadata { if _, ok := c.difiles[filename]; !ok { dir, file := filepath.Split(filename) if dir != "" { @@ -988,7 +988,7 @@ func (c *Compiler) parseFunc(frame *Frame) { } dbgVar := c.getLocalVariable(frame, variable) pos := c.ir.Program.Fset.Position(instr.Pos()) - c.dibuilder.InsertValueAtEnd(c.getValue(frame, instr.X), dbgVar, c.dibuilder.CreateExpression(nil), llvm.DebugLoc{ + c.dibuilder.InsertValueAtEnd(frame.getValue(instr.X), dbgVar, c.dibuilder.CreateExpression(nil), llvm.DebugLoc{ Line: uint(pos.Line), Col: uint(pos.Column), Scope: frame.difunc, @@ -1013,7 +1013,7 @@ func (c *Compiler) parseFunc(frame *Frame) { for _, phi := range frame.phis { block := phi.ssa.Block() for i, edge := range phi.ssa.Edges { - llvmVal := c.getValue(frame, edge) + llvmVal := frame.getValue(edge) llvmBlock := frame.blockExits[block.Preds[i]] phi.llvm.AddIncoming([]llvm.Value{llvmVal}, []llvm.BasicBlock{llvmBlock}) } @@ -1051,7 +1051,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { // Get all function parameters to pass to the goroutine. var params []llvm.Value for _, param := range instr.Call.Args { - params = append(params, c.getValue(frame, param)) + params = append(params, frame.getValue(param)) } // Start a new goroutine. @@ -1067,7 +1067,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { case *ssa.MakeClosure: // A goroutine call on a func value, but the callee is trivial to find. For // example: immediately applied functions. - funcValue := c.getValue(frame, value) + funcValue := frame.getValue(value) context = c.extractFuncContext(funcValue) default: panic("StaticCallee returned an unexpected value") @@ -1080,7 +1080,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { // goroutine: // * The function context, for closures. // * The function pointer (for tasks). - funcPtr, context := c.decodeFuncValue(c.getValue(frame, instr.Call.Value), instr.Call.Value.Type().(*types.Signature)) + funcPtr, context := c.decodeFuncValue(frame.getValue(instr.Call.Value), instr.Call.Value.Type().(*types.Signature)) params = append(params, context) // context parameter switch c.Scheduler() { case "none", "coroutines": @@ -1096,7 +1096,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { c.addError(instr.Pos(), "todo: go on interface call") } case *ssa.If: - cond := c.getValue(frame, instr.Cond) + cond := frame.getValue(instr.Cond) block := instr.Block() blockThen := frame.blockEntries[block.Succs[0]] blockElse := frame.blockEntries[block.Succs[1]] @@ -1105,25 +1105,25 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { blockJump := frame.blockEntries[instr.Block().Succs[0]] c.builder.CreateBr(blockJump) case *ssa.MapUpdate: - m := c.getValue(frame, instr.Map) - key := c.getValue(frame, instr.Key) - value := c.getValue(frame, instr.Value) + m := frame.getValue(instr.Map) + key := frame.getValue(instr.Key) + value := frame.getValue(instr.Value) mapType := instr.Map.Type().Underlying().(*types.Map) c.emitMapUpdate(mapType.Key(), m, key, value, instr.Pos()) case *ssa.Panic: - value := c.getValue(frame, instr.X) + value := frame.getValue(instr.X) c.createRuntimeCall("_panic", []llvm.Value{value}, "") c.builder.CreateUnreachable() case *ssa.Return: if len(instr.Results) == 0 { c.builder.CreateRetVoid() } else if len(instr.Results) == 1 { - c.builder.CreateRet(c.getValue(frame, instr.Results[0])) + c.builder.CreateRet(frame.getValue(instr.Results[0])) } else { // Multiple return values. Put them all in a struct. retVal := llvm.ConstNull(frame.fn.LLVMFn.Type().ElementType().ReturnType()) for i, result := range instr.Results { - val := c.getValue(frame, result) + val := frame.getValue(result) retVal = c.builder.CreateInsertValue(retVal, val, i, "") } c.builder.CreateRet(retVal) @@ -1133,8 +1133,8 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { case *ssa.Send: c.emitChanSend(frame, instr) case *ssa.Store: - llvmAddr := c.getValue(frame, instr.Addr) - llvmVal := c.getValue(frame, instr.Val) + llvmAddr := frame.getValue(instr.Addr) + llvmVal := frame.getValue(instr.Val) c.emitNilCheck(frame, llvmAddr, "store") if c.targetData.TypeAllocSize(llvmVal.Type()) == 0 { // nothing to store @@ -1149,8 +1149,8 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string, pos token.Pos) (llvm.Value, error) { switch callName { case "append": - src := c.getValue(frame, args[0]) - elems := c.getValue(frame, args[1]) + src := frame.getValue(args[0]) + elems := frame.getValue(args[1]) srcBuf := c.builder.CreateExtractValue(src, 0, "append.srcBuf") srcPtr := c.builder.CreateBitCast(srcBuf, c.i8ptrType, "append.srcPtr") srcLen := c.builder.CreateExtractValue(src, 1, "append.srcLen") @@ -1171,7 +1171,7 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string, newSlice = c.builder.CreateInsertValue(newSlice, newCap, 2, "") return newSlice, nil case "cap": - value := c.getValue(frame, args[0]) + value := frame.getValue(args[0]) var llvmCap llvm.Value switch args[0].Type().(type) { case *types.Chan: @@ -1191,8 +1191,8 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string, c.emitChanClose(frame, args[0]) return llvm.Value{}, nil case "complex": - r := c.getValue(frame, args[0]) - i := c.getValue(frame, args[1]) + r := frame.getValue(args[0]) + i := frame.getValue(args[1]) t := args[0].Type().Underlying().(*types.Basic) var cplx llvm.Value switch t.Kind() { @@ -1207,8 +1207,8 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string, cplx = c.builder.CreateInsertValue(cplx, i, 1, "") return cplx, nil case "copy": - dst := c.getValue(frame, args[0]) - src := c.getValue(frame, args[1]) + dst := frame.getValue(args[0]) + src := frame.getValue(args[1]) dstLen := c.builder.CreateExtractValue(dst, 1, "copy.dstLen") srcLen := c.builder.CreateExtractValue(src, 1, "copy.srcLen") dstBuf := c.builder.CreateExtractValue(dst, 0, "copy.dstArray") @@ -1219,14 +1219,14 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string, elemSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(elemType), false) return c.createRuntimeCall("sliceCopy", []llvm.Value{dstBuf, srcBuf, dstLen, srcLen, elemSize}, "copy.n"), nil case "delete": - m := c.getValue(frame, args[0]) - key := c.getValue(frame, args[1]) + m := frame.getValue(args[0]) + key := frame.getValue(args[1]) return llvm.Value{}, c.emitMapDelete(args[1].Type(), m, key, pos) case "imag": - cplx := c.getValue(frame, args[0]) + cplx := frame.getValue(args[0]) return c.builder.CreateExtractValue(cplx, 1, "imag"), nil case "len": - value := c.getValue(frame, args[0]) + value := frame.getValue(args[0]) var llvmLen llvm.Value switch args[0].Type().Underlying().(type) { case *types.Basic, *types.Slice: @@ -1250,7 +1250,7 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string, if i >= 1 && callName == "println" { c.createRuntimeCall("printspace", nil, "") } - value := c.getValue(frame, arg) + value := frame.getValue(arg) typ := arg.Type().Underlying() switch typ := typ.(type) { case *types.Basic: @@ -1303,13 +1303,13 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string, } return llvm.Value{}, nil // print() or println() returns void case "real": - cplx := c.getValue(frame, args[0]) + cplx := frame.getValue(args[0]) return c.builder.CreateExtractValue(cplx, 0, "real"), nil case "recover": return c.createRuntimeCall("_recover", nil, ""), nil case "ssa:wrapnilchk": // TODO: do an actual nil check? - return c.getValue(frame, args[0]), nil + return frame.getValue(args[0]), nil default: return llvm.Value{}, c.makeError(pos, "todo: builtin: "+callName) } @@ -1318,7 +1318,7 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string, func (c *Compiler) parseFunctionCall(frame *Frame, args []ssa.Value, llvmFn, context llvm.Value, exported bool) llvm.Value { var params []llvm.Value for _, param := range args { - params = append(params, c.getValue(frame, param)) + params = append(params, frame.getValue(param)) } if !exported { @@ -1375,7 +1375,7 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e case *ssa.MakeClosure: // A call on a func value, but the callee is trivial to find. For // example: immediately applied functions. - funcValue := c.getValue(frame, value) + funcValue := frame.getValue(value) context = c.extractFuncContext(funcValue) default: panic("StaticCallee returned an unexpected value") @@ -1388,7 +1388,7 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e case *ssa.Builtin: return c.parseBuiltin(frame, instr.Args, call.Name(), instr.Pos()) default: // function pointer - value := c.getValue(frame, instr.Value) + value := frame.getValue(instr.Value) // This is a func value, which cannot be called directly. We have to // extract the function pointer and context first from the func value. funcPtr, context := c.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature)) @@ -1399,27 +1399,27 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e // getValue returns the LLVM value of a constant, function value, global, or // already processed SSA expression. -func (c *Compiler) getValue(frame *Frame, expr ssa.Value) llvm.Value { +func (b *builder) getValue(expr ssa.Value) llvm.Value { switch expr := expr.(type) { case *ssa.Const: - return frame.createConst(frame.fn.LinkName(), expr) + return b.createConst(b.fn.LinkName(), expr) case *ssa.Function: - fn := c.ir.GetFunction(expr) + fn := b.ir.GetFunction(expr) if fn.IsExported() { - c.addError(expr.Pos(), "cannot use an exported function as value: "+expr.String()) - return llvm.Undef(c.getLLVMType(expr.Type())) + b.addError(expr.Pos(), "cannot use an exported function as value: "+expr.String()) + return llvm.Undef(b.getLLVMType(expr.Type())) } - return c.createFuncValue(fn.LLVMFn, llvm.Undef(c.i8ptrType), fn.Signature) + return b.createFuncValue(fn.LLVMFn, llvm.Undef(b.i8ptrType), fn.Signature) case *ssa.Global: - value := c.getGlobal(expr) + value := b.getGlobal(expr) if value.IsNil() { - c.addError(expr.Pos(), "global not found: "+expr.RelString(nil)) - return llvm.Undef(c.getLLVMType(expr.Type())) + b.addError(expr.Pos(), "global not found: "+expr.RelString(nil)) + return llvm.Undef(b.getLLVMType(expr.Type())) } return value default: // other (local) SSA value - if value, ok := frame.locals[expr]; ok { + if value, ok := b.locals[expr]; ok { return value } else { // indicates a compiler bug @@ -1458,8 +1458,8 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { return buf, nil } case *ssa.BinOp: - x := c.getValue(frame, expr.X) - y := c.getValue(frame, expr.Y) + x := frame.getValue(expr.X) + y := frame.getValue(expr.Y) return frame.createBinOp(expr.Op, expr.X.Type(), x, y, expr.Pos()) case *ssa.Call: // Passing the current task here to the subroutine. It is only used when @@ -1472,12 +1472,12 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { // This is different from how the official Go compiler works, because of // heap allocation and because it's easier to implement, see: // https://research.swtch.com/interfaces - return c.getValue(frame, expr.X), nil + return frame.getValue(expr.X), nil case *ssa.ChangeType: // This instruction changes the type, but the underlying value remains // the same. This is often a no-op, but sometimes we have to change the // LLVM type as well. - x := c.getValue(frame, expr.X) + x := frame.getValue(expr.X) llvmType := c.getLLVMType(expr.Type()) if x.Type() == llvmType { // Different Go type but same LLVM type (for example, named int). @@ -1506,20 +1506,20 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { case *ssa.Const: panic("const is not an expression") case *ssa.Convert: - x := c.getValue(frame, expr.X) + x := frame.getValue(expr.X) return c.parseConvert(expr.X.Type(), expr.Type(), x, expr.Pos()) case *ssa.Extract: if _, ok := expr.Tuple.(*ssa.Select); ok { return c.getChanSelectResult(frame, expr), nil } - value := c.getValue(frame, expr.Tuple) + value := frame.getValue(expr.Tuple) return c.builder.CreateExtractValue(value, expr.Index, ""), nil case *ssa.Field: - value := c.getValue(frame, expr.X) + value := frame.getValue(expr.X) result := c.builder.CreateExtractValue(value, expr.Field, "") return result, nil case *ssa.FieldAddr: - val := c.getValue(frame, expr.X) + val := frame.getValue(expr.X) // Check for nil pointer before calculating the address, from the spec: // > For an operand x of type T, the address operation &x generates a // > pointer of type *T to x. [...] If the evaluation of x would cause a @@ -1536,8 +1536,8 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { case *ssa.Global: panic("global is not an expression") case *ssa.Index: - array := c.getValue(frame, expr.X) - index := c.getValue(frame, expr.Index) + array := frame.getValue(expr.X) + index := frame.getValue(expr.Index) // Check bounds. arrayLen := expr.X.Type().(*types.Array).Len() @@ -1554,8 +1554,8 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { c.emitLifetimeEnd(allocaPtr, allocaSize) return result, nil case *ssa.IndexAddr: - val := c.getValue(frame, expr.X) - index := c.getValue(frame, expr.Index) + val := frame.getValue(expr.X) + index := frame.getValue(expr.Index) // Get buffer pointer and length var bufptr, buflen llvm.Value @@ -1599,8 +1599,8 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { panic("unreachable") } case *ssa.Lookup: - value := c.getValue(frame, expr.X) - index := c.getValue(frame, expr.Index) + value := frame.getValue(expr.X) + index := frame.getValue(expr.Index) switch xType := expr.X.Type().Underlying().(type) { case *types.Basic: // Value type must be a string, which is a basic type. @@ -1630,13 +1630,13 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { case *ssa.MakeClosure: return c.parseMakeClosure(frame, expr) case *ssa.MakeInterface: - val := c.getValue(frame, expr.X) + val := frame.getValue(expr.X) return c.parseMakeInterface(val, expr.X.Type(), expr.Pos()), nil case *ssa.MakeMap: return c.emitMakeMap(frame, expr) case *ssa.MakeSlice: - sliceLen := c.getValue(frame, expr.Len) - sliceCap := c.getValue(frame, expr.Cap) + sliceLen := frame.getValue(expr.Len) + sliceCap := frame.getValue(expr.Cap) sliceType := expr.Type().Underlying().(*types.Slice) llvmElemType := c.getLLVMType(sliceType.Elem()) elemSize := c.targetData.TypeAllocSize(llvmElemType) @@ -1688,8 +1688,8 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { return slice, nil case *ssa.Next: rangeVal := expr.Iter.(*ssa.Range).X - llvmRangeVal := c.getValue(frame, rangeVal) - it := c.getValue(frame, expr.Iter) + llvmRangeVal := frame.getValue(rangeVal) + it := frame.getValue(expr.Iter) if expr.IsString { return c.createRuntimeCall("stringNext", []llvm.Value{llvmRangeVal, it}, "range.next"), nil } else { // map @@ -1728,14 +1728,14 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { case *ssa.Select: return c.emitSelect(frame, expr), nil case *ssa.Slice: - value := c.getValue(frame, expr.X) + value := frame.getValue(expr.X) var lowType, highType, maxType *types.Basic var low, high, max llvm.Value if expr.Low != nil { lowType = expr.Low.Type().Underlying().(*types.Basic) - low = c.getValue(frame, expr.Low) + low = frame.getValue(expr.Low) if low.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() { if lowType.Info()&types.IsUnsigned != 0 { low = c.builder.CreateZExt(low, c.uintptrType, "") @@ -1750,7 +1750,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { if expr.High != nil { highType = expr.High.Type().Underlying().(*types.Basic) - high = c.getValue(frame, expr.High) + high = frame.getValue(expr.High) if high.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() { if highType.Info()&types.IsUnsigned != 0 { high = c.builder.CreateZExt(high, c.uintptrType, "") @@ -1764,7 +1764,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { if expr.Max != nil { maxType = expr.Max.Type().Underlying().(*types.Basic) - max = c.getValue(frame, expr.Max) + max = frame.getValue(expr.Max) if max.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() { if maxType.Info()&types.IsUnsigned != 0 { max = c.builder.CreateZExt(max, c.uintptrType, "") @@ -2524,7 +2524,7 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p } func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) { - x := c.getValue(frame, unop.X) + x := frame.getValue(unop.X) switch unop.Op { case token.NOT: // !x return c.builder.CreateNot(x, ""), nil diff --git a/compiler/defer.go b/compiler/defer.go index bc82603200..a2d878a482 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -91,12 +91,12 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) { // Collect all values to be put in the struct (starting with // runtime._defer fields, followed by the call parameters). - itf := c.getValue(frame, instr.Call.Value) // interface + itf := frame.getValue(instr.Call.Value) // interface receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver") values = []llvm.Value{callback, next, receiverValue} valueTypes = append(valueTypes, c.i8ptrType) for _, arg := range instr.Call.Args { - val := c.getValue(frame, arg) + val := frame.getValue(arg) values = append(values, val) valueTypes = append(valueTypes, val.Type()) } @@ -115,7 +115,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) { // runtime._defer fields). values = []llvm.Value{callback, next} for _, param := range instr.Call.Args { - llvmParam := c.getValue(frame, param) + llvmParam := frame.getValue(param) values = append(values, llvmParam) valueTypes = append(valueTypes, llvmParam.Type()) } @@ -127,7 +127,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) { // pointer. // TODO: ignore this closure entirely and put pointers to the free // variables directly in the defer struct, avoiding a memory allocation. - closure := c.getValue(frame, instr.Call.Value) + closure := frame.getValue(instr.Call.Value) context := c.builder.CreateExtractValue(closure, 0, "") // Get the callback number. @@ -143,7 +143,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) { // context pointer). values = []llvm.Value{callback, next} for _, param := range instr.Call.Args { - llvmParam := c.getValue(frame, param) + llvmParam := frame.getValue(param) values = append(values, llvmParam) valueTypes = append(valueTypes, llvmParam.Type()) } diff --git a/compiler/errors.go b/compiler/errors.go index 37205f01f6..85de4d161e 100644 --- a/compiler/errors.go +++ b/compiler/errors.go @@ -20,7 +20,7 @@ func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error { } } -func (c *Compiler) addError(pos token.Pos, msg string) { +func (c *compilerContext) addError(pos token.Pos, msg string) { c.diagnostics = append(c.diagnostics, c.makeError(pos, msg)) } diff --git a/compiler/func.go b/compiler/func.go index c64c52c981..69fb78bce5 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -14,6 +14,18 @@ import ( // createFuncValue creates a function value from a raw function pointer with no // context. func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value { + return c.compilerContext.createFuncValue(c.builder, funcPtr, context, sig) +} + +// createFuncValue creates a function value from a raw function pointer with no +// context. +func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value { + return b.compilerContext.createFuncValue(b.Builder, funcPtr, context, sig) +} + +// createFuncValue creates a function value from a raw function pointer with no +// context. +func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value { var funcValueScalar llvm.Value switch c.FuncImplementation() { case compileopts.FuncValueDoubleword: @@ -40,8 +52,8 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa } funcValueType := c.getFuncType(sig) funcValue := llvm.Undef(funcValueType) - funcValue = c.builder.CreateInsertValue(funcValue, context, 0, "") - funcValue = c.builder.CreateInsertValue(funcValue, funcValueScalar, 1, "") + funcValue = builder.CreateInsertValue(funcValue, context, 0, "") + funcValue = builder.CreateInsertValue(funcValue, funcValueScalar, 1, "") return funcValue } @@ -151,7 +163,7 @@ func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.V boundVars := make([]llvm.Value, len(expr.Bindings)) for i, binding := range expr.Bindings { // The context stores the bound variables. - llvmBoundVar := c.getValue(frame, binding) + llvmBoundVar := frame.getValue(binding) boundVars[i] = llvmBoundVar } diff --git a/compiler/inlineasm.go b/compiler/inlineasm.go index e9f093a7b7..77e1af96a2 100644 --- a/compiler/inlineasm.go +++ b/compiler/inlineasm.go @@ -77,7 +77,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, } key := constant.StringVal(r.Key.(*ssa.Const).Value) //println("value:", r.Value.(*ssa.MakeInterface).X.String()) - registers[key] = c.getValue(frame, r.Value.(*ssa.MakeInterface).X) + registers[key] = frame.getValue(r.Value.(*ssa.MakeInterface).X) case *ssa.Call: if r.Common() == instr { break @@ -150,7 +150,7 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error } else { constraints += ",{r" + strconv.Itoa(i) + "}" } - llvmValue := c.getValue(frame, arg) + llvmValue := frame.getValue(arg) llvmArgs = append(llvmArgs, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } @@ -190,19 +190,19 @@ func (c *Compiler) emitCSROperation(frame *Frame, call *ssa.CallCommon) (llvm.Va fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.uintptrType}, false) asm := fmt.Sprintf("csrw %d, $0", csr) target := llvm.InlineAsm(fnType, asm, "r", true, false, 0) - return c.builder.CreateCall(target, []llvm.Value{c.getValue(frame, call.Args[1])}, ""), nil + return c.builder.CreateCall(target, []llvm.Value{frame.getValue(call.Args[1])}, ""), nil case "SetBits": // Note: it may be possible to optimize this to csrrsi in many cases. fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{c.uintptrType}, false) asm := fmt.Sprintf("csrrs $0, %d, $1", csr) target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0) - return c.builder.CreateCall(target, []llvm.Value{c.getValue(frame, call.Args[1])}, ""), nil + return c.builder.CreateCall(target, []llvm.Value{frame.getValue(call.Args[1])}, ""), nil case "ClearBits": // Note: it may be possible to optimize this to csrrci in many cases. fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{c.uintptrType}, false) asm := fmt.Sprintf("csrrc $0, %d, $1", csr) target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0) - return c.builder.CreateCall(target, []llvm.Value{c.getValue(frame, call.Args[1])}, ""), nil + return c.builder.CreateCall(target, []llvm.Value{frame.getValue(call.Args[1])}, ""), nil default: return llvm.Value{}, c.makeError(call.Pos(), "unknown CSR operation: "+name) } diff --git a/compiler/interface.go b/compiler/interface.go index f1599cd20b..7904df726f 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -44,7 +44,7 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token. // getTypeCode returns a reference to a type code. // It returns a pointer to an external global which should be replaced with the // real type in the interface lowering pass. -func (c *Compiler) getTypeCode(typ types.Type) llvm.Value { +func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { globalName := "reflect/types.type:" + getTypeCodeName(typ) global := c.mod.NamedGlobal(globalName) if global.IsNil() { @@ -91,7 +91,7 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value { // makeStructTypeFields creates a new global that stores all type information // related to this struct type, and returns the resulting global. This global is // actually an array of all the fields in the structs. -func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value { +func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value { // The global is an array of runtime.structField structs. runtimeStructField := c.getLLVMRuntimeType("structField") structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields()) @@ -315,7 +315,7 @@ func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value { // Type asserts on concrete types are trivial: just compare type numbers. Type // asserts on interfaces are more difficult, see the comments in the function. func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Value { - itf := c.getValue(frame, expr.X) + itf := frame.getValue(expr.X) assertedType := c.getLLVMType(expr.AssertedType) actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type") @@ -395,7 +395,7 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu // interface call. It can be used in a call or defer instruction. func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value) { // Call an interface method with dynamic dispatch. - itf := c.getValue(frame, instr.Value) // interface + itf := frame.getValue(instr.Value) // interface llvmFnType := c.getRawFuncType(instr.Method.Type().(*types.Signature)) @@ -411,7 +411,7 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu args := []llvm.Value{receiverValue} for _, arg := range instr.Args { - args = append(args, c.getValue(frame, arg)) + args = append(args, frame.getValue(arg)) } // Add the context parameter. An interface call never takes a context but we // have to supply the parameter anyway. diff --git a/compiler/interrupt.go b/compiler/interrupt.go index 67cb8eca62..7f4fd77ac4 100644 --- a/compiler/interrupt.go +++ b/compiler/interrupt.go @@ -24,7 +24,7 @@ func (c *Compiler) emitInterruptGlobal(frame *Frame, instr *ssa.CallCommon) (llv // Note that bound functions are allowed if the function has a pointer // receiver and is a global. This is rather strict but still allows for // idiomatic Go code. - funcValue := c.getValue(frame, instr.Args[1]) + funcValue := frame.getValue(instr.Args[1]) if funcValue.IsAConstant().IsNil() { // Try to determine the cause of the non-constantness for a nice error // message. diff --git a/compiler/llvm.go b/compiler/llvm.go index 53d52bbaef..ff2b4c60c2 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -56,7 +56,7 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l // contents, and returns the global. // Note that it is left with the default linkage etc., you should set // linkage/constant/etc properties yourself. -func (c *Compiler) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value { +func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value { globalType := llvm.ArrayType(elementType, len(buf)) global := llvm.AddGlobal(c.mod, globalType, name) value := llvm.Undef(globalType) diff --git a/compiler/map.go b/compiler/map.go index 5faf7aedf2..6f93b88821 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -34,7 +34,7 @@ func (c *Compiler) emitMakeMap(frame *Frame, expr *ssa.MakeMap) (llvm.Value, err llvmValueSize := llvm.ConstInt(c.ctx.Int8Type(), valueSize, false) sizeHint := llvm.ConstInt(c.uintptrType, 8, false) if expr.Reserve != nil { - sizeHint = c.getValue(frame, expr.Reserve) + sizeHint = frame.getValue(expr.Reserve) var err error sizeHint, err = c.parseConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos()) if err != nil { diff --git a/compiler/symbol.go b/compiler/symbol.go index ae853f1886..716fc86b65 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -56,7 +56,7 @@ func (c *Compiler) loadASTComments(lprogram *loader.Program) { // getGlobal returns a LLVM IR global value for a Go SSA global. It is added to // the LLVM IR if it has not been added already. -func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value { +func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value { info := c.getGlobalInfo(g) llvmGlobal := c.mod.NamedGlobal(info.linkName) if llvmGlobal.IsNil() { @@ -104,7 +104,7 @@ func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value { } // getGlobalInfo returns some information about a specific global. -func (c *Compiler) getGlobalInfo(g *ssa.Global) globalInfo { +func (c *compilerContext) getGlobalInfo(g *ssa.Global) globalInfo { info := globalInfo{} if strings.HasPrefix(g.Name(), "C.") { // Created by CGo: such a name cannot be created by regular C code. diff --git a/compiler/syscall.go b/compiler/syscall.go index 8dbc81928e..074a88caf2 100644 --- a/compiler/syscall.go +++ b/compiler/syscall.go @@ -13,7 +13,7 @@ import ( // emitSyscall emits an inline system call instruction, depending on the target // OS/arch. func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) { - num := c.getValue(frame, call.Args[0]) + num := frame.getValue(call.Args[0]) var syscallResult llvm.Value switch { case c.GOARCH() == "amd64": @@ -50,7 +50,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, "{r12}", "{r13}", }[i] - llvmValue := c.getValue(frame, arg) + llvmValue := frame.getValue(arg) args = append(args, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } @@ -77,7 +77,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, "{edi}", "{ebp}", }[i] - llvmValue := c.getValue(frame, arg) + llvmValue := frame.getValue(arg) args = append(args, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } @@ -102,7 +102,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, "{r5}", "{r6}", }[i] - llvmValue := c.getValue(frame, arg) + llvmValue := frame.getValue(arg) args = append(args, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } @@ -132,7 +132,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, "{x4}", "{x5}", }[i] - llvmValue := c.getValue(frame, arg) + llvmValue := frame.getValue(arg) args = append(args, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } diff --git a/compiler/volatile.go b/compiler/volatile.go index 7ee5b168f1..ef957d9ab5 100644 --- a/compiler/volatile.go +++ b/compiler/volatile.go @@ -9,7 +9,7 @@ import ( ) func (c *Compiler) emitVolatileLoad(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) { - addr := c.getValue(frame, instr.Args[0]) + addr := frame.getValue(instr.Args[0]) c.emitNilCheck(frame, addr, "deref") val := c.builder.CreateLoad(addr, "") val.SetVolatile(true) @@ -17,8 +17,8 @@ func (c *Compiler) emitVolatileLoad(frame *Frame, instr *ssa.CallCommon) (llvm.V } func (c *Compiler) emitVolatileStore(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) { - addr := c.getValue(frame, instr.Args[0]) - val := c.getValue(frame, instr.Args[1]) + addr := frame.getValue(instr.Args[0]) + val := frame.getValue(instr.Args[1]) c.emitNilCheck(frame, addr, "deref") store := c.builder.CreateStore(val, addr) store.SetVolatile(true) From 8df3e8e059fd9c040872e554dd86ab0067a6ba84 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 23 Nov 2019 00:09:13 +0100 Subject: [PATCH 04/21] compiler: refactor parseTypeAssert Move to the builder object, and rename to createTypeAssert. --- compiler/compiler.go | 2 +- compiler/interface.go | 52 +++++++++++++++++++++---------------------- compiler/llvm.go | 5 +++++ 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index db2ca66fb5..8608e2eaa4 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1897,7 +1897,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { return llvm.Value{}, c.makeError(expr.Pos(), "unknown slice type: "+typ.String()) } case *ssa.TypeAssert: - return c.parseTypeAssert(frame, expr), nil + return frame.createTypeAssert(expr), nil case *ssa.UnOp: return c.parseUnOp(frame, expr) default: diff --git a/compiler/interface.go b/compiler/interface.go index 7904df726f..66a3b9a4a7 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -271,7 +271,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value { // getInterfaceMethodSet returns a global variable with the method set of the // given named interface type. This method set is used by the interface lowering // pass. -func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value { +func (c *compilerContext) getInterfaceMethodSet(typ *types.Named) llvm.Value { global := c.mod.NamedGlobal(typ.String() + "$interface") zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) if !global.IsNil() { @@ -297,7 +297,7 @@ func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value { // getMethodSignature returns a global variable which is a reference to an // external *i8 indicating the indicating the signature of this method. It is // used during the interface lowering pass. -func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value { +func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value { signature := ir.MethodSignature(method) signatureGlobal := c.mod.NamedGlobal("func " + signature) if signatureGlobal.IsNil() { @@ -307,18 +307,18 @@ func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value { return signatureGlobal } -// parseTypeAssert will emit the code for a typeassert, used in if statements +// createTypeAssert will emit the code for a typeassert, used in if statements // and in type switches (Go SSA does not have type switches, only if/else // chains). Note that even though the Go SSA does not contain type switches, // LLVM will recognize the pattern and make it a real switch in many cases. // // Type asserts on concrete types are trivial: just compare type numbers. Type // asserts on interfaces are more difficult, see the comments in the function. -func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Value { - itf := frame.getValue(expr.X) - assertedType := c.getLLVMType(expr.AssertedType) +func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value { + itf := b.getValue(expr.X) + assertedType := b.getLLVMType(expr.AssertedType) - actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type") + actualTypeNum := b.CreateExtractValue(itf, 0, "interface.type") commaOk := llvm.Value{} if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok { // Type assert on interface type. @@ -329,15 +329,15 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu // the main Go compiler, where the runtime checks whether the type // implements each method of the interface. See: // https://research.swtch.com/interfaces - methodSet := c.getInterfaceMethodSet(expr.AssertedType.(*types.Named)) - commaOk = c.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "") + methodSet := b.getInterfaceMethodSet(expr.AssertedType.(*types.Named)) + commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "") } else { // Type assert on concrete type. // Call runtime.typeAssert, which will be lowered to a simple icmp or // const false in the interface lowering pass. - assertedTypeCodeGlobal := c.getTypeCode(expr.AssertedType) - commaOk = c.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode") + assertedTypeCodeGlobal := b.getTypeCode(expr.AssertedType) + commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode") } // Add 2 new basic blocks (that should get optimized away): one for the @@ -351,15 +351,15 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu // typeassert should return a zero value, not an incorrectly casted // value. - prevBlock := c.builder.GetInsertBlock() - okBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.ok") - nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.next") - frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes - c.builder.CreateCondBr(commaOk, okBlock, nextBlock) + prevBlock := b.GetInsertBlock() + okBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.ok") + nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.next") + b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes + b.CreateCondBr(commaOk, okBlock, nextBlock) // Retrieve the value from the interface if the type assert was // successful. - c.builder.SetInsertPointAtEnd(okBlock) + b.SetInsertPointAtEnd(okBlock) var valueOk llvm.Value if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok { // Type assert on interface type. Easy: just return the same @@ -368,25 +368,25 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu } else { // 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") - valueOk = c.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0] + valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr") + valueOk = b.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0] } - c.builder.CreateBr(nextBlock) + b.CreateBr(nextBlock) // Continue after the if statement. - c.builder.SetInsertPointAtEnd(nextBlock) - phi := c.builder.CreatePHI(assertedType, "typeassert.value") + b.SetInsertPointAtEnd(nextBlock) + phi := b.CreatePHI(assertedType, "typeassert.value") phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock}) if expr.CommaOk { - tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple - tuple = c.builder.CreateInsertValue(tuple, phi, 0, "") // insert value - tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean + tuple := b.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(b.ctx.Int1Type())}, false) // create empty tuple + tuple = b.CreateInsertValue(tuple, phi, 0, "") // insert value + tuple = b.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean return tuple } else { // This is kind of dirty as the branch above becomes mostly useless, // but hopefully this gets optimized away. - c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "") + b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "") return phi } } diff --git a/compiler/llvm.go b/compiler/llvm.go index ff2b4c60c2..30a96f427d 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -52,6 +52,11 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l return llvmutil.EmitPointerUnpack(c.builder, c.mod, ptr, valueTypes) } +// 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) +} + // makeGlobalArray creates a new LLVM global with the given name and integers as // contents, and returns the global. // Note that it is left with the default linkage etc., you should set From 3466828ee55c3b41f035674ddf46bd892f303686 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 23 Nov 2019 00:15:06 +0100 Subject: [PATCH 05/21] compiler: refactor asserts --- compiler/asserts.go | 102 +++++++++++++++++++++---------------------- compiler/channel.go | 2 +- compiler/compiler.go | 26 +++++------ compiler/volatile.go | 4 +- 4 files changed, 67 insertions(+), 67 deletions(-) diff --git a/compiler/asserts.go b/compiler/asserts.go index daeecadb9c..6b25478177 100644 --- a/compiler/asserts.go +++ b/compiler/asserts.go @@ -11,11 +11,11 @@ import ( "tinygo.org/x/go-llvm" ) -// emitLookupBoundsCheck emits a bounds check before doing a lookup into a +// createLookupBoundsCheck emits a bounds check before doing a lookup into a // slice. This is required by the Go language spec: an index out of bounds must // cause a panic. -func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Value, indexType types.Type) { - if frame.fn.IsNoBounds() { +func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) { + if b.fn.IsNoBounds() { // The //go:nobounds pragma was added to the function to avoid bounds // checking. return @@ -25,29 +25,29 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu // Sometimes, the index can be e.g. an uint8 or int8, and we have to // correctly extend that type. if indexType.Underlying().(*types.Basic).Info()&types.IsUnsigned == 0 { - index = c.builder.CreateZExt(index, arrayLen.Type(), "") + index = b.CreateZExt(index, arrayLen.Type(), "") } else { - index = c.builder.CreateSExt(index, arrayLen.Type(), "") + index = b.CreateSExt(index, arrayLen.Type(), "") } } else if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() { // The index is bigger than the array length type, so extend it. - arrayLen = c.builder.CreateZExt(arrayLen, index.Type(), "") + arrayLen = b.CreateZExt(arrayLen, index.Type(), "") } // Now do the bounds check: index >= arrayLen - outOfBounds := c.builder.CreateICmp(llvm.IntUGE, index, arrayLen, "") - c.createRuntimeAssert(frame, outOfBounds, "lookup", "lookupPanic") + outOfBounds := b.CreateICmp(llvm.IntUGE, index, arrayLen, "") + b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic") } -// emitSliceBoundsCheck emits a bounds check before a slicing operation to make +// createSliceBoundsCheck emits a bounds check before a slicing operation to make // sure it is within bounds. // // This function is both used for slicing a slice (low and high have their // normal meaning) and for creating a new slice, where 'capacity' means the // biggest possible slice capacity, 'low' means len and 'high' means cap. The // logic is the same in both cases. -func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) { - if frame.fn.IsNoBounds() { +func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) { + if b.fn.IsNoBounds() { // The //go:nobounds pragma was added to the function to avoid bounds // checking. return @@ -65,45 +65,45 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l capacityType = max.Type() } if capacityType != capacity.Type() { - capacity = c.builder.CreateZExt(capacity, capacityType, "") + capacity = b.CreateZExt(capacity, capacityType, "") } // Extend low and high to be the same size as capacity. if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() { if lowType.Info()&types.IsUnsigned != 0 { - low = c.builder.CreateZExt(low, capacityType, "") + low = b.CreateZExt(low, capacityType, "") } else { - low = c.builder.CreateSExt(low, capacityType, "") + low = b.CreateSExt(low, capacityType, "") } } if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() { if highType.Info()&types.IsUnsigned != 0 { - high = c.builder.CreateZExt(high, capacityType, "") + high = b.CreateZExt(high, capacityType, "") } else { - high = c.builder.CreateSExt(high, capacityType, "") + high = b.CreateSExt(high, capacityType, "") } } if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() { if maxType.Info()&types.IsUnsigned != 0 { - max = c.builder.CreateZExt(max, capacityType, "") + max = b.CreateZExt(max, capacityType, "") } else { - max = c.builder.CreateSExt(max, capacityType, "") + max = b.CreateSExt(max, capacityType, "") } } // Now do the bounds check: low > high || high > capacity - outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh") - outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax") - outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap") - outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax") - outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap") - c.createRuntimeAssert(frame, outOfBounds, "slice", "slicePanic") + outOfBounds1 := b.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh") + outOfBounds2 := b.CreateICmp(llvm.IntUGT, high, max, "slice.highmax") + outOfBounds3 := b.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap") + outOfBounds := b.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax") + outOfBounds = b.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap") + b.createRuntimeAssert(outOfBounds, "slice", "slicePanic") } -// emitChanBoundsCheck emits a bounds check before creating a new channel to +// createChanBoundsCheck creates a bounds check before creating a new channel to // check that the value is not too big for runtime.chanMake. -func (c *Compiler) emitChanBoundsCheck(frame *Frame, elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) { - if frame.fn.IsNoBounds() { +func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) { + if b.fn.IsNoBounds() { // The //go:nobounds pragma was added to the function to avoid bounds // checking. return @@ -111,23 +111,23 @@ func (c *Compiler) emitChanBoundsCheck(frame *Frame, elementSize uint64, bufSize // Check whether the bufSize parameter must be cast to a wider integer for // comparison. - if bufSize.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() { + if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() { if bufSizeType.Info()&types.IsUnsigned != 0 { // Unsigned, so zero-extend to uint type. bufSizeType = types.Typ[types.Uint] - bufSize = c.builder.CreateZExt(bufSize, c.intType, "") + bufSize = b.CreateZExt(bufSize, b.intType, "") } else { // Signed, so sign-extend to int type. bufSizeType = types.Typ[types.Int] - bufSize = c.builder.CreateSExt(bufSize, c.intType, "") + bufSize = b.CreateSExt(bufSize, b.intType, "") } } // Calculate (^uintptr(0)) >> 1, which is the max value that fits in an // uintptr if uintptrs were signed. - maxBufSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(c.uintptrType, 0, false)), llvm.ConstInt(c.uintptrType, 1, false)) + maxBufSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false)) if elementSize > maxBufSize.ZExtValue() { - c.addError(pos, fmt.Sprintf("channel element type is too big (%v bytes)", elementSize)) + b.addError(pos, fmt.Sprintf("channel element type is too big (%v bytes)", elementSize)) return } // Avoid divide-by-zero. @@ -136,7 +136,7 @@ func (c *Compiler) emitChanBoundsCheck(frame *Frame, elementSize uint64, bufSize } // Make the maxBufSize actually the maximum allowed value (in number of // elements in the channel buffer). - maxBufSize = llvm.ConstUDiv(maxBufSize, llvm.ConstInt(c.uintptrType, elementSize, false)) + maxBufSize = llvm.ConstUDiv(maxBufSize, llvm.ConstInt(b.uintptrType, elementSize, false)) // Make sure maxBufSize has the same type as bufSize. if maxBufSize.Type() != bufSize.Type() { @@ -144,14 +144,14 @@ func (c *Compiler) emitChanBoundsCheck(frame *Frame, elementSize uint64, bufSize } // Do the check for a too large (or negative) buffer size. - bufSizeTooBig := c.builder.CreateICmp(llvm.IntUGE, bufSize, maxBufSize, "") - c.createRuntimeAssert(frame, bufSizeTooBig, "chan", "chanMakePanic") + bufSizeTooBig := b.CreateICmp(llvm.IntUGE, bufSize, maxBufSize, "") + b.createRuntimeAssert(bufSizeTooBig, "chan", "chanMakePanic") } -// emitNilCheck checks whether the given pointer is nil, and panics if it is. It -// has no effect in well-behaved programs, but makes sure no uncaught nil +// createNilCheck checks whether the given pointer is nil, and panics if it is. +// It has no effect in well-behaved programs, but makes sure no uncaught nil // pointer dereferences exist in valid Go code. -func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) { +func (b *builder) createNilCheck(ptr llvm.Value, blockPrefix string) { // Check whether we need to emit this check at all. if !ptr.IsAGlobalValue().IsNil() { return @@ -167,22 +167,22 @@ func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string // https://reviews.llvm.org/D60047 for details. Pointer capturing // unfortunately breaks escape analysis, so we use this trick to let the // functionattr pass know that this pointer doesn't really escape. - ptr = c.builder.CreateBitCast(ptr, c.i8ptrType, "") - isnil = c.createRuntimeCall("isnil", []llvm.Value{ptr}, "") + ptr = b.CreateBitCast(ptr, b.i8ptrType, "") + isnil = b.createRuntimeCall("isnil", []llvm.Value{ptr}, "") } else { // Do the nil check using a regular icmp. This can happen with function // pointers on AVR, which don't benefit from escape analysis anyway. nilptr := llvm.ConstPointerNull(ptr.Type()) - isnil = c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "") + isnil = b.CreateICmp(llvm.IntEQ, ptr, nilptr, "") } // Emit the nil check in IR. - c.createRuntimeAssert(frame, isnil, blockPrefix, "nilPanic") + b.createRuntimeAssert(isnil, blockPrefix, "nilPanic") } // createRuntimeAssert is a common function to create a new branch on an assert // bool, calling an assert func if the assert value is true (1). -func (c *Compiler) createRuntimeAssert(frame *Frame, assert llvm.Value, blockPrefix, assertFunc string) { +func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) { // Check whether we can resolve this check at compile time. if !assert.IsAConstantInt().IsNil() { val := assert.ZExtValue() @@ -193,18 +193,18 @@ func (c *Compiler) createRuntimeAssert(frame *Frame, assert llvm.Value, blockPre } } - faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".throw") - nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next") - frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes + faultBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".throw") + nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".next") + b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes // Now branch to the out-of-bounds or the regular block. - c.builder.CreateCondBr(assert, faultBlock, nextBlock) + b.CreateCondBr(assert, faultBlock, nextBlock) // Fail: the assert triggered so panic. - c.builder.SetInsertPointAtEnd(faultBlock) - c.createRuntimeCall(assertFunc, nil, "") - c.builder.CreateUnreachable() + b.SetInsertPointAtEnd(faultBlock) + b.createRuntimeCall(assertFunc, nil, "") + b.CreateUnreachable() // Ok: assert didn't trigger so continue normally. - c.builder.SetInsertPointAtEnd(nextBlock) + b.SetInsertPointAtEnd(nextBlock) } diff --git a/compiler/channel.go b/compiler/channel.go index bdfdf9b91e..2e802f0f92 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -15,7 +15,7 @@ func (c *Compiler) emitMakeChan(frame *Frame, expr *ssa.MakeChan) llvm.Value { elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem())) elementSizeValue := llvm.ConstInt(c.uintptrType, elementSize, false) bufSize := frame.getValue(expr.Size) - c.emitChanBoundsCheck(frame, elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos()) + frame.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos()) if bufSize.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() { bufSize = c.builder.CreateZExt(bufSize, c.uintptrType, "") } else if bufSize.Type().IntTypeWidth() > c.uintptrType.IntTypeWidth() { diff --git a/compiler/compiler.go b/compiler/compiler.go index 8608e2eaa4..1ad55453bd 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -347,7 +347,7 @@ func (c *Compiler) Compile(mainPath string) []error { c.mod.NamedFunction("runtime.alloc").AddAttributeAtIndex(0, getAttr(attrName)) } - // See emitNilCheck in asserts.go. + // See createNilCheck in asserts.go. c.mod.NamedFunction("runtime.isnil").AddAttributeAtIndex(1, nocapture) // On *nix systems, the "abort" functuion in libc is used to handle fatal panics. @@ -1135,7 +1135,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { case *ssa.Store: llvmAddr := frame.getValue(instr.Addr) llvmVal := frame.getValue(instr.Val) - c.emitNilCheck(frame, llvmAddr, "store") + frame.createNilCheck(llvmAddr, "store") if c.targetData.TypeAllocSize(llvmVal.Type()) == 0 { // nothing to store return @@ -1392,7 +1392,7 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e // This is a func value, which cannot be called directly. We have to // extract the function pointer and context first from the func value. funcPtr, context := c.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature)) - c.emitNilCheck(frame, funcPtr, "fpcall") + frame.createNilCheck(funcPtr, "fpcall") return c.parseFunctionCall(frame, instr.Args, funcPtr, context, false), nil } } @@ -1524,7 +1524,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { // > For an operand x of type T, the address operation &x generates a // > pointer of type *T to x. [...] If the evaluation of x would cause a // > run-time panic, then the evaluation of &x does too. - c.emitNilCheck(frame, val, "gep") + frame.createNilCheck(val, "gep") // Do a GEP on the pointer to get the field address. indices := []llvm.Value{ llvm.ConstInt(c.ctx.Int32Type(), 0, false), @@ -1542,7 +1542,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { // Check bounds. arrayLen := expr.X.Type().(*types.Array).Len() arrayLenLLVM := llvm.ConstInt(c.uintptrType, uint64(arrayLen), false) - c.emitLookupBoundsCheck(frame, arrayLenLLVM, index, expr.Index.Type()) + frame.createLookupBoundsCheck(arrayLenLLVM, index, expr.Index.Type()) // Can't load directly from array (as index is non-constant), so have to // do it using an alloca+gep+load. @@ -1572,7 +1572,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { // > generates a pointer of type *T to x. [...] If the // > evaluation of x would cause a run-time panic, then the // > evaluation of &x does too. - c.emitNilCheck(frame, bufptr, "gep") + frame.createNilCheck(bufptr, "gep") default: return llvm.Value{}, c.makeError(expr.Pos(), "todo: indexaddr: "+typ.String()) } @@ -1584,7 +1584,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { } // Bounds check. - c.emitLookupBoundsCheck(frame, buflen, index, expr.Index.Type()) + frame.createLookupBoundsCheck(buflen, index, expr.Index.Type()) switch expr.X.Type().Underlying().(type) { case *types.Pointer: @@ -1610,7 +1610,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { // Bounds check. length := c.builder.CreateExtractValue(value, 1, "len") - c.emitLookupBoundsCheck(frame, length, index, expr.Index.Type()) + frame.createLookupBoundsCheck(length, index, expr.Index.Type()) // Lookup byte buf := c.builder.CreateExtractValue(value, 0, "") @@ -1654,7 +1654,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { // Bounds checking. lenType := expr.Len.Type().(*types.Basic) capType := expr.Cap.Type().(*types.Basic) - c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType) + frame.createSliceBoundsCheck(maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType) // Allocate the backing array. sliceCapCast, err := c.parseConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) @@ -1792,7 +1792,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { low, } - c.emitSliceBoundsCheck(frame, llvmLen, low, high, max, lowType, highType, maxType) + frame.createSliceBoundsCheck(llvmLen, low, high, max, lowType, highType, maxType) // Truncate ints bigger than uintptr. This is after the bounds // check so it's safe. @@ -1832,7 +1832,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { max = oldCap } - c.emitSliceBoundsCheck(frame, oldCap, low, high, max, lowType, highType, maxType) + frame.createSliceBoundsCheck(oldCap, low, high, max, lowType, highType, maxType) // Truncate ints bigger than uintptr. This is after the bounds // check so it's safe. @@ -1875,7 +1875,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { high = oldLen } - c.emitSliceBoundsCheck(frame, oldLen, low, high, high, lowType, highType, maxType) + frame.createSliceBoundsCheck(oldLen, low, high, high, lowType, highType, maxType) // Truncate ints bigger than uintptr. This is after the bounds // check so it's safe. @@ -2559,7 +2559,7 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) { } return c.builder.CreateBitCast(fn, c.i8ptrType, ""), nil } else { - c.emitNilCheck(frame, x, "deref") + frame.createNilCheck(x, "deref") load := c.builder.CreateLoad(x, "") return load, nil } diff --git a/compiler/volatile.go b/compiler/volatile.go index ef957d9ab5..237d554619 100644 --- a/compiler/volatile.go +++ b/compiler/volatile.go @@ -10,7 +10,7 @@ import ( func (c *Compiler) emitVolatileLoad(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) { addr := frame.getValue(instr.Args[0]) - c.emitNilCheck(frame, addr, "deref") + frame.createNilCheck(addr, "deref") val := c.builder.CreateLoad(addr, "") val.SetVolatile(true) return val, nil @@ -19,7 +19,7 @@ func (c *Compiler) emitVolatileLoad(frame *Frame, instr *ssa.CallCommon) (llvm.V func (c *Compiler) emitVolatileStore(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) { addr := frame.getValue(instr.Args[0]) val := frame.getValue(instr.Args[1]) - c.emitNilCheck(frame, addr, "deref") + frame.createNilCheck(addr, "deref") store := c.builder.CreateStore(val, addr) store.SetVolatile(true) return llvm.Value{}, nil From 504f749d6f1257ff19f553fd45251aa56376e32b Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 23 Nov 2019 00:27:18 +0100 Subject: [PATCH 06/21] compiler: refactor creating of channel operations --- compiler/channel.go | 180 +++++++++++++++++++++---------------------- compiler/compiler.go | 12 +-- compiler/llvm.go | 17 ++++ 3 files changed, 113 insertions(+), 96 deletions(-) diff --git a/compiler/channel.go b/compiler/channel.go index 2e802f0f92..3686c98f37 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -11,79 +11,79 @@ import ( "tinygo.org/x/go-llvm" ) -func (c *Compiler) emitMakeChan(frame *Frame, expr *ssa.MakeChan) llvm.Value { - elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem())) - elementSizeValue := llvm.ConstInt(c.uintptrType, elementSize, false) - bufSize := frame.getValue(expr.Size) - frame.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos()) - if bufSize.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() { - bufSize = c.builder.CreateZExt(bufSize, c.uintptrType, "") - } else if bufSize.Type().IntTypeWidth() > c.uintptrType.IntTypeWidth() { - bufSize = c.builder.CreateTrunc(bufSize, c.uintptrType, "") +func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value { + elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().(*types.Chan).Elem())) + elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false) + bufSize := b.getValue(expr.Size) + b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos()) + if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() { + bufSize = b.CreateZExt(bufSize, b.uintptrType, "") + } else if bufSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() { + bufSize = b.CreateTrunc(bufSize, b.uintptrType, "") } - return c.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "") + return b.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "") } -// emitChanSend emits a pseudo chan send operation. It is lowered to the actual -// channel send operation during goroutine lowering. -func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) { - ch := frame.getValue(instr.Chan) - chanValue := frame.getValue(instr.X) +// createChanSend emits a pseudo chan send operation. It is lowered to the +// actual channel send operation during goroutine lowering. +func (b *builder) createChanSend(instr *ssa.Send) { + ch := b.getValue(instr.Chan) + chanValue := b.getValue(instr.X) // store value-to-send - valueType := c.getLLVMType(instr.X.Type()) - valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value") - c.builder.CreateStore(chanValue, valueAlloca) + valueType := b.getLLVMType(instr.X.Type()) + valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value") + b.CreateStore(chanValue, valueAlloca) // Do the send. - c.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "") + b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "") // End the lifetime of the alloca. // This also works around a bug in CoroSplit, at least in LLVM 8: // https://bugs.llvm.org/show_bug.cgi?id=41742 - c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) + b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) } -// emitChanRecv emits a pseudo chan receive operation. It is lowered to the +// createChanRecv emits a pseudo chan receive operation. It is lowered to the // actual channel receive operation during goroutine lowering. -func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value { - valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem()) - ch := frame.getValue(unop.X) +func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { + valueType := b.getLLVMType(unop.X.Type().(*types.Chan).Elem()) + ch := b.getValue(unop.X) // Allocate memory to receive into. - valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value") + valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value") // Do the receive. - commaOk := c.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "") - received := c.builder.CreateLoad(valueAlloca, "chan.received") - c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) + commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "") + received := b.CreateLoad(valueAlloca, "chan.received") + b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) if unop.CommaOk { - tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false)) - tuple = c.builder.CreateInsertValue(tuple, received, 0, "") - tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") + tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)) + tuple = b.CreateInsertValue(tuple, received, 0, "") + tuple = b.CreateInsertValue(tuple, commaOk, 1, "") return tuple } else { return received } } -// emitChanClose closes the given channel. -func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) { - ch := frame.getValue(param) - c.createRuntimeCall("chanClose", []llvm.Value{ch}, "") +// createChanClose closes the given channel. +func (b *builder) createChanClose(param ssa.Value) { + ch := b.getValue(param) + b.createRuntimeCall("chanClose", []llvm.Value{ch}, "") } -// emitSelect emits all IR necessary for a select statements. That's a +// createSelect emits all IR necessary for a select statements. That's a // non-trivial amount of code because select is very complex to implement. -func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value { +func (b *builder) createSelect(expr *ssa.Select) llvm.Value { if len(expr.States) == 0 { // Shortcuts for some simple selects. - llvmType := c.getLLVMType(expr.Type()) + llvmType := b.getLLVMType(expr.Type()) if expr.Blocking { // Blocks forever: // select {} - c.createRuntimeCall("deadlock", nil, "") + b.createRuntimeCall("deadlock", nil, "") return llvm.Undef(llvmType) } else { // No-op: @@ -91,7 +91,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value { // default: // } retval := llvm.Undef(llvmType) - retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "") + retval = b.CreateInsertValue(retval, llvm.ConstInt(b.intType, 0xffffffffffffffff, true), 0, "") return retval // {-1, false} } } @@ -109,30 +109,30 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value { recvbufAlign := 0 hasReceives := false var selectStates []llvm.Value - chanSelectStateType := c.getLLVMRuntimeType("chanSelectState") + chanSelectStateType := b.getLLVMRuntimeType("chanSelectState") for _, state := range expr.States { - ch := frame.getValue(state.Chan) + ch := b.getValue(state.Chan) selectState := llvm.ConstNull(chanSelectStateType) - selectState = c.builder.CreateInsertValue(selectState, ch, 0, "") + selectState = b.CreateInsertValue(selectState, ch, 0, "") switch state.Dir { case types.RecvOnly: // Make sure the receive buffer is big enough and has the correct alignment. - llvmType := c.getLLVMType(state.Chan.Type().(*types.Chan).Elem()) - if size := c.targetData.TypeAllocSize(llvmType); size > recvbufSize { + llvmType := b.getLLVMType(state.Chan.Type().(*types.Chan).Elem()) + if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize { recvbufSize = size } - if align := c.targetData.ABITypeAlignment(llvmType); align > recvbufAlign { + if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign { recvbufAlign = align } hasReceives = true case types.SendOnly: // Store this value in an alloca and put a pointer to this alloca // in the send state. - sendValue := frame.getValue(state.Send) - 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, "") + sendValue := b.getValue(state.Send) + alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value") + b.CreateStore(sendValue, alloca) + ptr := b.CreateBitCast(alloca, b.i8ptrType, "") + selectState = b.CreateInsertValue(selectState, ptr, 1, "") default: panic("unreachable") } @@ -140,74 +140,74 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value { } // Create a receive buffer, where the received value will be stored. - recvbuf := llvm.Undef(c.i8ptrType) + recvbuf := llvm.Undef(b.i8ptrType) if hasReceives { - allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize)) - recvbufAlloca, _, _ := c.createTemporaryAlloca(allocaType, "select.recvbuf.alloca") + allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize)) + recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca") recvbufAlloca.SetAlignment(recvbufAlign) - recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), 0, false), + recvbuf = b.CreateGEP(recvbufAlloca, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), 0, false), }, "select.recvbuf") } // Create the states slice (allocated on the stack). statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates)) - statesAlloca, statesI8, statesSize := c.createTemporaryAlloca(statesAllocaType, "select.states.alloca") + statesAlloca, statesI8, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca") for i, state := range selectStates { // Set each slice element to the appropriate channel. - gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false), + gep := b.CreateGEP(statesAlloca, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), }, "") - c.builder.CreateStore(state, gep) + b.CreateStore(state, gep) } - statesPtr := c.builder.CreateGEP(statesAlloca, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), 0, false), + statesPtr := b.CreateGEP(statesAlloca, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), 0, false), }, "select.states") - statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false) + statesLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false) // Do the select in the runtime. var results llvm.Value if expr.Blocking { // Stack-allocate operation structures. // If these were simply created as a slice, they would heap-allocate. - chBlockAllocaType := llvm.ArrayType(c.getLLVMRuntimeType("channelBlockedList"), len(selectStates)) - chBlockAlloca, chBlockAllocaPtr, chBlockSize := c.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca") - chBlockLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false) - chBlockPtr := c.builder.CreateGEP(chBlockAlloca, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), 0, false), + chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates)) + chBlockAlloca, chBlockAllocaPtr, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca") + chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false) + chBlockPtr := b.CreateGEP(chBlockAlloca, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), 0, false), }, "select.block") - results = c.createRuntimeCall("chanSelect", []llvm.Value{ + results = b.createRuntimeCall("chanSelect", []llvm.Value{ recvbuf, statesPtr, statesLen, statesLen, // []chanSelectState chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList }, "select.result") // Terminate the lifetime of the operation structures. - c.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize) + b.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize) } else { - results = c.createRuntimeCall("tryChanSelect", []llvm.Value{ + results = b.createRuntimeCall("tryChanSelect", []llvm.Value{ recvbuf, statesPtr, statesLen, statesLen, // []chanSelectState }, "select.result") } // Terminate the lifetime of the states alloca. - c.emitLifetimeEnd(statesI8, statesSize) + b.emitLifetimeEnd(statesI8, statesSize) // The result value does not include all the possible received values, // because we can't load them in advance. Instead, the *ssa.Extract // instruction will treat a *ssa.Select specially and load it there inline. // Store the receive alloca in a sidetable until we hit this extract // instruction. - if frame.selectRecvBuf == nil { - frame.selectRecvBuf = make(map[*ssa.Select]llvm.Value) + if b.selectRecvBuf == nil { + b.selectRecvBuf = make(map[*ssa.Select]llvm.Value) } - frame.selectRecvBuf[expr] = recvbuf + b.selectRecvBuf[expr] = recvbuf return results } @@ -216,28 +216,28 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value { // when extracting a value from a select statement (*ssa.Select). Because // *ssa.Select cannot load all values in advance, it does this later in the // *ssa.Extract expression. -func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Value { +func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value { if expr.Index == 0 { // index - value := frame.getValue(expr.Tuple) - index := c.builder.CreateExtractValue(value, expr.Index, "") - if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() { - index = c.builder.CreateSExt(index, c.intType, "") + value := b.getValue(expr.Tuple) + index := b.CreateExtractValue(value, expr.Index, "") + if index.Type().IntTypeWidth() < b.intType.IntTypeWidth() { + index = b.CreateSExt(index, b.intType, "") } return index } else if expr.Index == 1 { // comma-ok - value := frame.getValue(expr.Tuple) - return c.builder.CreateExtractValue(value, expr.Index, "") + value := b.getValue(expr.Tuple) + return b.CreateExtractValue(value, expr.Index, "") } else { // Select statements are (index, ok, ...) where ... is a number of // received values, depending on how many receive statements there // are. They are all combined into one alloca (because only one // receive can proceed at a time) so we'll get that alloca, bitcast // it to the correct type, and dereference it. - recvbuf := frame.selectRecvBuf[expr.Tuple.(*ssa.Select)] - typ := llvm.PointerType(c.getLLVMType(expr.Type()), 0) - ptr := c.builder.CreateBitCast(recvbuf, typ, "") - return c.builder.CreateLoad(ptr, "") + recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)] + typ := llvm.PointerType(b.getLLVMType(expr.Type()), 0) + ptr := b.CreateBitCast(recvbuf, typ, "") + return b.CreateLoad(ptr, "") } } diff --git a/compiler/compiler.go b/compiler/compiler.go index 1ad55453bd..71e80eecad 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1131,7 +1131,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { case *ssa.RunDefers: c.emitRunDefers(frame) case *ssa.Send: - c.emitChanSend(frame, instr) + frame.createChanSend(instr) case *ssa.Store: llvmAddr := frame.getValue(instr.Addr) llvmVal := frame.getValue(instr.Val) @@ -1188,7 +1188,7 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string, } return llvmCap, nil case "close": - c.emitChanClose(frame, args[0]) + frame.createChanClose(args[0]) return llvm.Value{}, nil case "complex": r := frame.getValue(args[0]) @@ -1510,7 +1510,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { return c.parseConvert(expr.X.Type(), expr.Type(), x, expr.Pos()) case *ssa.Extract: if _, ok := expr.Tuple.(*ssa.Select); ok { - return c.getChanSelectResult(frame, expr), nil + return frame.getChanSelectResult(expr), nil } value := frame.getValue(expr.Tuple) return c.builder.CreateExtractValue(value, expr.Index, ""), nil @@ -1626,7 +1626,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { panic("unknown lookup type: " + expr.String()) } case *ssa.MakeChan: - return c.emitMakeChan(frame, expr), nil + return frame.createMakeChan(expr), nil case *ssa.MakeClosure: return c.parseMakeClosure(frame, expr) case *ssa.MakeInterface: @@ -1726,7 +1726,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { c.builder.CreateStore(llvm.ConstNull(iteratorType), it) return it, nil case *ssa.Select: - return c.emitSelect(frame, expr), nil + return frame.createSelect(expr), nil case *ssa.Slice: value := frame.getValue(expr.X) @@ -2566,7 +2566,7 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) { case token.XOR: // ^x, toggle all bits in integer return c.builder.CreateXor(x, llvm.ConstInt(x.Type(), ^uint64(0), false), ""), nil case token.ARROW: // <-x, receive from channel - return c.emitChanRecv(frame, unop), nil + return frame.createChanRecv(unop), nil default: return llvm.Value{}, c.makeError(unop.Pos(), "todo: unknown unop") } diff --git a/compiler/llvm.go b/compiler/llvm.go index 30a96f427d..2a9f6c9d91 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -33,6 +33,16 @@ func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitc return llvmutil.CreateTemporaryAlloca(c.builder, c.mod, t, name) } +// 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 (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) { + return llvmutil.CreateTemporaryAlloca(b.Builder, b.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. @@ -40,6 +50,13 @@ func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) { llvmutil.EmitLifetimeEnd(c.builder, c.mod, ptr, size) } +// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the +// llvm.lifetime.end intrinsic. It is commonly used together with +// createTemporaryAlloca. +func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) { + llvmutil.EmitLifetimeEnd(b.Builder, b.mod, ptr, size) +} + // 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. From afaa3136b5064b9fba8d28dd61e672b3bb4aa568 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 23 Nov 2019 00:34:38 +0100 Subject: [PATCH 07/21] compiler: refactor parseUnOp --- compiler/compiler.go | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 71e80eecad..a786eaeab8 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1899,7 +1899,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { case *ssa.TypeAssert: return frame.createTypeAssert(expr), nil case *ssa.UnOp: - return c.parseUnOp(frame, expr) + return frame.createUnOp(expr) default: return llvm.Value{}, c.makeError(expr.Pos(), "todo: unknown expression: "+expr.String()) } @@ -2523,26 +2523,30 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p } } -func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) { - x := frame.getValue(unop.X) +// createUnOp creates LLVM IR for a given Go unary operation. +// Most unary operators are pretty simple, such as the not and minus operator +// which can all be directly lowered to IR. However, there is also the channel +// receive operator which is handled in the runtime directly. +func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) { + x := b.getValue(unop.X) switch unop.Op { case token.NOT: // !x - return c.builder.CreateNot(x, ""), nil + return b.CreateNot(x, ""), nil case token.SUB: // -x if typ, ok := unop.X.Type().Underlying().(*types.Basic); ok { if typ.Info()&types.IsInteger != 0 { - return c.builder.CreateSub(llvm.ConstInt(x.Type(), 0, false), x, ""), nil + return b.CreateSub(llvm.ConstInt(x.Type(), 0, false), x, ""), nil } else if typ.Info()&types.IsFloat != 0 { - return c.builder.CreateFSub(llvm.ConstFloat(x.Type(), 0.0), x, ""), nil + return b.CreateFSub(llvm.ConstFloat(x.Type(), 0.0), x, ""), nil } else { - return llvm.Value{}, c.makeError(unop.Pos(), "todo: unknown basic type for negate: "+typ.String()) + return llvm.Value{}, b.makeError(unop.Pos(), "todo: unknown basic type for negate: "+typ.String()) } } else { - return llvm.Value{}, c.makeError(unop.Pos(), "todo: unknown type for negate: "+unop.X.Type().Underlying().String()) + return llvm.Value{}, b.makeError(unop.Pos(), "todo: unknown type for negate: "+unop.X.Type().Underlying().String()) } case token.MUL: // *x, dereference pointer unop.X.Type().Underlying().(*types.Pointer).Elem() - if c.targetData.TypeAllocSize(x.Type().ElementType()) == 0 { + if b.targetData.TypeAllocSize(x.Type().ElementType()) == 0 { // zero-length data return llvm.ConstNull(x.Type().ElementType()), nil } else if strings.HasSuffix(unop.X.String(), "$funcaddr") { @@ -2551,24 +2555,24 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) { // var C.add unsafe.Pointer // Instead of a load from the global, create a bitcast of the // function pointer itself. - globalName := c.getGlobalInfo(unop.X.(*ssa.Global)).linkName + globalName := b.getGlobalInfo(unop.X.(*ssa.Global)).linkName name := globalName[:len(globalName)-len("$funcaddr")] - fn := c.mod.NamedFunction(name) + fn := b.mod.NamedFunction(name) if fn.IsNil() { - return llvm.Value{}, c.makeError(unop.Pos(), "cgo function not found: "+name) + return llvm.Value{}, b.makeError(unop.Pos(), "cgo function not found: "+name) } - return c.builder.CreateBitCast(fn, c.i8ptrType, ""), nil + return b.CreateBitCast(fn, b.i8ptrType, ""), nil } else { - frame.createNilCheck(x, "deref") - load := c.builder.CreateLoad(x, "") + b.createNilCheck(x, "deref") + load := b.CreateLoad(x, "") return load, nil } case token.XOR: // ^x, toggle all bits in integer - return c.builder.CreateXor(x, llvm.ConstInt(x.Type(), ^uint64(0), false), ""), nil + return b.CreateXor(x, llvm.ConstInt(x.Type(), ^uint64(0), false), ""), nil case token.ARROW: // <-x, receive from channel - return frame.createChanRecv(unop), nil + return b.createChanRecv(unop), nil default: - return llvm.Value{}, c.makeError(unop.Pos(), "todo: unknown unop") + return llvm.Value{}, b.makeError(unop.Pos(), "todo: unknown unop") } } From 6b2dd968d9f7e27dd0bbe22fce361e0c75ff38f3 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 23 Nov 2019 12:31:53 +0100 Subject: [PATCH 08/21] compiler: refactor parseConvert --- compiler/compiler.go | 97 ++++++++++++++++++++++---------------------- compiler/map.go | 2 +- 2 files changed, 50 insertions(+), 49 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index a786eaeab8..07ce42140f 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1507,7 +1507,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { panic("const is not an expression") case *ssa.Convert: x := frame.getValue(expr.X) - return c.parseConvert(expr.X.Type(), expr.Type(), x, expr.Pos()) + return frame.createConvert(expr.X.Type(), expr.Type(), x, expr.Pos()) case *ssa.Extract: if _, ok := expr.Tuple.(*ssa.Select); ok { return frame.getChanSelectResult(expr), nil @@ -1657,7 +1657,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { frame.createSliceBoundsCheck(maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType) // Allocate the backing array. - sliceCapCast, err := c.parseConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) + sliceCapCast, err := frame.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) if err != nil { return llvm.Value{}, err } @@ -1667,11 +1667,11 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { // Extend or truncate if necessary. This is safe as we've already done // the bounds check. - sliceLen, err = c.parseConvert(expr.Len.Type(), types.Typ[types.Uintptr], sliceLen, expr.Pos()) + sliceLen, err = frame.createConvert(expr.Len.Type(), types.Typ[types.Uintptr], sliceLen, expr.Pos()) if err != nil { return llvm.Value{}, err } - sliceCap, err = c.parseConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) + sliceCap, err = frame.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) if err != nil { return llvm.Value{}, err } @@ -2358,15 +2358,16 @@ func (b *builder) createConst(prefix string, expr *ssa.Const) llvm.Value { } } -func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, pos token.Pos) (llvm.Value, error) { +// createConvert creates a Go type conversion instruction. +func (b *builder) createConvert(typeFrom, typeTo types.Type, value llvm.Value, pos token.Pos) (llvm.Value, error) { llvmTypeFrom := value.Type() - llvmTypeTo := c.getLLVMType(typeTo) + llvmTypeTo := b.getLLVMType(typeTo) // Conversion between unsafe.Pointer and uintptr. isPtrFrom := isPointer(typeFrom.Underlying()) isPtrTo := isPointer(typeTo.Underlying()) if isPtrFrom && !isPtrTo { - return c.builder.CreatePtrToInt(value, llvmTypeTo, ""), nil + return b.CreatePtrToInt(value, llvmTypeTo, ""), nil } else if !isPtrFrom && isPtrTo { if !value.IsABinaryOperator().IsNil() && value.InstructionOpcode() == llvm.Add { // This is probably a pattern like the following: @@ -2381,7 +2382,7 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p } if !ptr.IsAPtrToIntInst().IsNil() { origptr := ptr.Operand(0) - if origptr.Type() == c.i8ptrType { + if origptr.Type() == b.i8ptrType { // This pointer can be calculated from the original // ptrtoint instruction with a GEP. The leftover inttoptr // instruction is trivial to optimize away. @@ -2389,21 +2390,21 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p // create a GEP that is not in bounds. However, we're // talking about unsafe code here so the programmer has to // be careful anyway. - return c.builder.CreateInBoundsGEP(origptr, []llvm.Value{index}, ""), nil + return b.CreateInBoundsGEP(origptr, []llvm.Value{index}, ""), nil } } } - return c.builder.CreateIntToPtr(value, llvmTypeTo, ""), nil + return b.CreateIntToPtr(value, llvmTypeTo, ""), nil } // Conversion between pointers and unsafe.Pointer. if isPtrFrom && isPtrTo { - return c.builder.CreateBitCast(value, llvmTypeTo, ""), nil + return b.CreateBitCast(value, llvmTypeTo, ""), nil } switch typeTo := typeTo.Underlying().(type) { case *types.Basic: - sizeFrom := c.targetData.TypeAllocSize(llvmTypeFrom) + sizeFrom := b.targetData.TypeAllocSize(llvmTypeFrom) if typeTo.Info()&types.IsString != 0 { switch typeFrom := typeFrom.Underlying().(type) { @@ -2413,47 +2414,47 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p // Cast to an i32 value as expected by // runtime.stringFromUnicode. if sizeFrom > 4 { - value = c.builder.CreateTrunc(value, c.ctx.Int32Type(), "") + value = b.CreateTrunc(value, b.ctx.Int32Type(), "") } else if sizeFrom < 4 && typeTo.Info()&types.IsUnsigned != 0 { - value = c.builder.CreateZExt(value, c.ctx.Int32Type(), "") + value = b.CreateZExt(value, b.ctx.Int32Type(), "") } else if sizeFrom < 4 { - value = c.builder.CreateSExt(value, c.ctx.Int32Type(), "") + value = b.CreateSExt(value, b.ctx.Int32Type(), "") } - return c.createRuntimeCall("stringFromUnicode", []llvm.Value{value}, ""), nil + return b.createRuntimeCall("stringFromUnicode", []llvm.Value{value}, ""), nil case *types.Slice: switch typeFrom.Elem().(*types.Basic).Kind() { case types.Byte: - return c.createRuntimeCall("stringFromBytes", []llvm.Value{value}, ""), nil + return b.createRuntimeCall("stringFromBytes", []llvm.Value{value}, ""), nil case types.Rune: - return c.createRuntimeCall("stringFromRunes", []llvm.Value{value}, ""), nil + return b.createRuntimeCall("stringFromRunes", []llvm.Value{value}, ""), nil default: - return llvm.Value{}, c.makeError(pos, "todo: convert to string: "+typeFrom.String()) + return llvm.Value{}, b.makeError(pos, "todo: convert to string: "+typeFrom.String()) } default: - return llvm.Value{}, c.makeError(pos, "todo: convert to string: "+typeFrom.String()) + return llvm.Value{}, b.makeError(pos, "todo: convert to string: "+typeFrom.String()) } } typeFrom := typeFrom.Underlying().(*types.Basic) - sizeTo := c.targetData.TypeAllocSize(llvmTypeTo) + sizeTo := b.targetData.TypeAllocSize(llvmTypeTo) if typeFrom.Info()&types.IsInteger != 0 && typeTo.Info()&types.IsInteger != 0 { // Conversion between two integers. if sizeFrom > sizeTo { - return c.builder.CreateTrunc(value, llvmTypeTo, ""), nil + return b.CreateTrunc(value, llvmTypeTo, ""), nil } else if typeFrom.Info()&types.IsUnsigned != 0 { // if unsigned - return c.builder.CreateZExt(value, llvmTypeTo, ""), nil + return b.CreateZExt(value, llvmTypeTo, ""), nil } else { // if signed - return c.builder.CreateSExt(value, llvmTypeTo, ""), nil + return b.CreateSExt(value, llvmTypeTo, ""), nil } } if typeFrom.Info()&types.IsFloat != 0 && typeTo.Info()&types.IsFloat != 0 { // Conversion between two floats. if sizeFrom > sizeTo { - return c.builder.CreateFPTrunc(value, llvmTypeTo, ""), nil + return b.CreateFPTrunc(value, llvmTypeTo, ""), nil } else if sizeFrom < sizeTo { - return c.builder.CreateFPExt(value, llvmTypeTo, ""), nil + return b.CreateFPExt(value, llvmTypeTo, ""), nil } else { return value, nil } @@ -2462,46 +2463,46 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p if typeFrom.Info()&types.IsFloat != 0 && typeTo.Info()&types.IsInteger != 0 { // Conversion from float to int. if typeTo.Info()&types.IsUnsigned != 0 { // if unsigned - return c.builder.CreateFPToUI(value, llvmTypeTo, ""), nil + return b.CreateFPToUI(value, llvmTypeTo, ""), nil } else { // if signed - return c.builder.CreateFPToSI(value, llvmTypeTo, ""), nil + return b.CreateFPToSI(value, llvmTypeTo, ""), nil } } if typeFrom.Info()&types.IsInteger != 0 && typeTo.Info()&types.IsFloat != 0 { // Conversion from int to float. if typeFrom.Info()&types.IsUnsigned != 0 { // if unsigned - return c.builder.CreateUIToFP(value, llvmTypeTo, ""), nil + return b.CreateUIToFP(value, llvmTypeTo, ""), nil } else { // if signed - return c.builder.CreateSIToFP(value, llvmTypeTo, ""), nil + return b.CreateSIToFP(value, llvmTypeTo, ""), nil } } if typeFrom.Kind() == types.Complex128 && typeTo.Kind() == types.Complex64 { // Conversion from complex128 to complex64. - r := c.builder.CreateExtractValue(value, 0, "real.f64") - i := c.builder.CreateExtractValue(value, 1, "imag.f64") - r = c.builder.CreateFPTrunc(r, c.ctx.FloatType(), "real.f32") - i = c.builder.CreateFPTrunc(i, c.ctx.FloatType(), "imag.f32") - cplx := llvm.Undef(c.ctx.StructType([]llvm.Type{c.ctx.FloatType(), c.ctx.FloatType()}, false)) - cplx = c.builder.CreateInsertValue(cplx, r, 0, "") - cplx = c.builder.CreateInsertValue(cplx, i, 1, "") + r := b.CreateExtractValue(value, 0, "real.f64") + i := b.CreateExtractValue(value, 1, "imag.f64") + r = b.CreateFPTrunc(r, b.ctx.FloatType(), "real.f32") + i = b.CreateFPTrunc(i, b.ctx.FloatType(), "imag.f32") + cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.FloatType(), b.ctx.FloatType()}, false)) + cplx = b.CreateInsertValue(cplx, r, 0, "") + cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil } if typeFrom.Kind() == types.Complex64 && typeTo.Kind() == types.Complex128 { // Conversion from complex64 to complex128. - r := c.builder.CreateExtractValue(value, 0, "real.f32") - i := c.builder.CreateExtractValue(value, 1, "imag.f32") - r = c.builder.CreateFPExt(r, c.ctx.DoubleType(), "real.f64") - i = c.builder.CreateFPExt(i, c.ctx.DoubleType(), "imag.f64") - cplx := llvm.Undef(c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false)) - cplx = c.builder.CreateInsertValue(cplx, r, 0, "") - cplx = c.builder.CreateInsertValue(cplx, i, 1, "") + r := b.CreateExtractValue(value, 0, "real.f32") + i := b.CreateExtractValue(value, 1, "imag.f32") + r = b.CreateFPExt(r, b.ctx.DoubleType(), "real.f64") + i = b.CreateFPExt(i, b.ctx.DoubleType(), "imag.f64") + cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.DoubleType(), b.ctx.DoubleType()}, false)) + cplx = b.CreateInsertValue(cplx, r, 0, "") + cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil } - return llvm.Value{}, c.makeError(pos, "todo: convert: basic non-integer type: "+typeFrom.String()+" -> "+typeTo.String()) + return llvm.Value{}, b.makeError(pos, "todo: convert: basic non-integer type: "+typeFrom.String()+" -> "+typeTo.String()) case *types.Slice: if basic, ok := typeFrom.(*types.Basic); !ok || basic.Info()&types.IsString == 0 { @@ -2511,15 +2512,15 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p elemType := typeTo.Elem().Underlying().(*types.Basic) // must be byte or rune switch elemType.Kind() { case types.Byte: - return c.createRuntimeCall("stringToBytes", []llvm.Value{value}, ""), nil + return b.createRuntimeCall("stringToBytes", []llvm.Value{value}, ""), nil case types.Rune: - return c.createRuntimeCall("stringToRunes", []llvm.Value{value}, ""), nil + return b.createRuntimeCall("stringToRunes", []llvm.Value{value}, ""), nil default: panic("unexpected type in string to slice conversion") } default: - return llvm.Value{}, c.makeError(pos, "todo: convert "+typeTo.String()+" <- "+typeFrom.String()) + return llvm.Value{}, b.makeError(pos, "todo: convert "+typeTo.String()+" <- "+typeFrom.String()) } } diff --git a/compiler/map.go b/compiler/map.go index 6f93b88821..b4e163fe7f 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -36,7 +36,7 @@ func (c *Compiler) emitMakeMap(frame *Frame, expr *ssa.MakeMap) (llvm.Value, err if expr.Reserve != nil { sizeHint = frame.getValue(expr.Reserve) var err error - sizeHint, err = c.parseConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos()) + sizeHint, err = frame.createConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos()) if err != nil { return llvm.Value{}, err } From 11facda0738c791c5e6f4cfa5c46bc7ea181cc2a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 23 Nov 2019 12:59:55 +0100 Subject: [PATCH 09/21] compiler: refactor interface invoke wrapper creation Now that most of the utility compiler methods are ported over to the builder or compilerContext, it is possible to avoid having to do the wrapper creation in two steps. A new builder is created just to create the wrapper. This is a small reduction in line count (and a significant reduction in complexity!), even though more documentation was added. --- compiler/compiler.go | 20 ++++++------- compiler/interface.go | 68 ++++++++++++++++++------------------------- compiler/llvm.go | 7 +++++ 3 files changed, 44 insertions(+), 51 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 07ce42140f..cb63868fb9 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -58,9 +58,8 @@ type compilerContext struct { type Compiler struct { compilerContext - builder llvm.Builder - initFuncs []llvm.Value - interfaceInvokeWrappers []interfaceInvokeWrapper + builder llvm.Builder + initFuncs []llvm.Value } type Frame struct { @@ -301,12 +300,6 @@ func (c *Compiler) Compile(mainPath string) []error { c.parseFunc(frame) } - // Define the already declared functions that wrap methods for use in - // interfaces. - for _, state := range c.interfaceInvokeWrappers { - c.createInterfaceInvokeWrapper(state) - } - // After all packages are imported, add a synthetic initializer function // that calls the initializer of each package. initFn := c.ir.GetFunction(c.ir.Program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function)) @@ -801,12 +794,17 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) *Frame { return frame } -func (c *Compiler) attachDebugInfo(f *ir.Function) llvm.Metadata { +// attachDebugInfo adds debug info to a function declaration. It returns the +// DISubprogram metadata node. +func (c *compilerContext) attachDebugInfo(f *ir.Function) llvm.Metadata { pos := c.ir.Program.Fset.Position(f.Syntax().Pos()) return c.attachDebugInfoRaw(f, f.LLVMFn, "", pos.Filename, pos.Line) } -func (c *Compiler) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata { +// attachDebugInfo adds debug info to a function declaration. It returns the +// DISubprogram metadata node. This method allows some more control over how +// debug info is added to the function. +func (c *compilerContext) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata { // Debug info for this function. diparams := make([]llvm.Metadata, 0, len(f.Params)) for _, param := range f.Params { diff --git a/compiler/interface.go b/compiler/interface.go index 66a3b9a4a7..86f8d33388 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -422,20 +422,12 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu return fnCast, args } -// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and -// createInterfaceInvokeWrapper. The former is called during IR construction -// itself and the latter is called when finishing up the IR. -type interfaceInvokeWrapper struct { - fn *ir.Function - wrapper llvm.Value - receiverType llvm.Type -} - -// Wrap an interface method function pointer. The wrapper takes in a pointer to -// the underlying value, dereferences it, and calls the real method. This -// wrapper is only needed when the interface value actually doesn't fit in a -// pointer and a pointer to the value must be created. -func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { +// getInterfaceInvokeWrapper returns a wrapper for the given method so it can be +// invoked from an interface. The wrapper takes in a pointer to the underlying +// value, dereferences or unpacks it if necessary, and calls the real method. +// If the method to wrap has a pointer receiver, no wrapping is necessary and +// the function is returned directly. +func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { wrapperName := f.LinkName() + "$invoke" wrapper := c.mod.NamedFunction(wrapperName) if !wrapper.IsNil() { @@ -464,41 +456,37 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { if f.LLVMFn.LastParam().Name() == "parentHandle" { wrapper.LastParam().SetName("parentHandle") } - c.interfaceInvokeWrappers = append(c.interfaceInvokeWrappers, interfaceInvokeWrapper{ - fn: f, - wrapper: wrapper, - receiverType: receiverType, - }) - return wrapper -} -// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper, -// see that function for details. -func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) { - wrapper := state.wrapper - fn := state.fn - receiverType := state.receiverType wrapper.SetLinkage(llvm.InternalLinkage) wrapper.SetUnnamedAddr(true) + // Create a new builder just to create this wrapper. + b := builder{ + compilerContext: c, + Builder: c.ctx.NewBuilder(), + } + defer b.Builder.Dispose() + // add debug info if needed if c.Debug() { - pos := c.ir.Program.Fset.Position(fn.Pos()) - difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line) - c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) + pos := c.ir.Program.Fset.Position(f.Pos()) + difunc := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line) + b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } // set up IR builder - block := c.ctx.AddBasicBlock(wrapper, "entry") - c.builder.SetInsertPointAtEnd(block) - - 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, "") - c.builder.CreateRetVoid() + block := b.ctx.AddBasicBlock(wrapper, "entry") + b.SetInsertPointAtEnd(block) + + receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0] + params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...) + if f.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind { + b.CreateCall(f.LLVMFn, params, "") + b.CreateRetVoid() } else { - ret := c.builder.CreateCall(fn.LLVMFn, params, "ret") - c.builder.CreateRet(ret) + ret := b.CreateCall(f.LLVMFn, params, "ret") + b.CreateRet(ret) } + + return wrapper } diff --git a/compiler/llvm.go b/compiler/llvm.go index 2a9f6c9d91..ed5e004044 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -64,6 +64,13 @@ func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value { return llvmutil.EmitPointerPack(c.builder, c.mod, c.Config, values) } +// 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 (b *builder) emitPointerPack(values []llvm.Value) llvm.Value { + return llvmutil.EmitPointerPack(b.Builder, b.mod, b.Config, values) +} + // 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) From 7da1f02e15b621f8a96a5b51f1d1e5d5f6081088 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 23 Nov 2019 13:11:35 +0100 Subject: [PATCH 10/21] compiler: refactor interface creation and calling --- compiler/compiler.go | 4 ++-- compiler/defer.go | 2 +- compiler/interface.go | 52 +++++++++++++++++++++---------------------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index cb63868fb9..26115d6f91 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1333,7 +1333,7 @@ func (c *Compiler) parseFunctionCall(frame *Frame, args []ssa.Value, llvmFn, con func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) { if instr.IsInvoke() { - fnCast, args := c.getInvokeCall(frame, instr) + fnCast, args := frame.getInvokeCall(instr) return c.createCall(fnCast, args, ""), nil } @@ -1629,7 +1629,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { return c.parseMakeClosure(frame, expr) case *ssa.MakeInterface: val := frame.getValue(expr.X) - return c.parseMakeInterface(val, expr.X.Type(), expr.Pos()), nil + return frame.createMakeInterface(val, expr.X.Type(), expr.Pos()), nil case *ssa.MakeMap: return c.emitMakeMap(frame, expr) case *ssa.MakeSlice: diff --git a/compiler/defer.go b/compiler/defer.go index a2d878a482..03d576181b 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -272,7 +272,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) { // Parent coroutine handle. forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType)) - fnPtr, _ := c.getInvokeCall(frame, callback) + fnPtr, _ := frame.getInvokeCall(callback) c.createCall(fnPtr, forwardParams, "") case *ir.Function: diff --git a/compiler/interface.go b/compiler/interface.go index 86f8d33388..c6a8a61ac9 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -16,28 +16,28 @@ import ( "tinygo.org/x/go-llvm" ) -// parseMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction. +// createMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction. // It tries to put the type in the interface value, but if that's not possible, // it will do an allocation of the right size and put that in the interface // value field. // -// 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 { - itfValue := c.emitPointerPack([]llvm.Value{val}) - itfTypeCodeGlobal := c.getTypeCode(typ) - itfMethodSetGlobal := c.getTypeMethodSet(typ) - itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name()) +// An interface value is a {typecode, value} tuple named runtime._interface. +func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value { + itfValue := b.emitPointerPack([]llvm.Value{val}) + itfTypeCodeGlobal := b.getTypeCode(typ) + itfMethodSetGlobal := b.getTypeMethodSet(typ) + itfConcreteTypeGlobal := b.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name()) if itfConcreteTypeGlobal.IsNil() { - typeInInterface := c.getLLVMRuntimeType("typeInInterface") - itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name()) + typeInInterface := b.getLLVMRuntimeType("typeInInterface") + itfConcreteTypeGlobal = llvm.AddGlobal(b.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name()) itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal})) itfConcreteTypeGlobal.SetGlobalConstant(true) itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage) } - itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "") - itf := llvm.Undef(c.getLLVMRuntimeType("_interface")) - itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "") - itf = c.builder.CreateInsertValue(itf, itfValue, 1, "") + itfTypeCode := b.CreatePtrToInt(itfConcreteTypeGlobal, b.uintptrType, "") + itf := llvm.Undef(b.getLLVMRuntimeType("_interface")) + itf = b.CreateInsertValue(itf, itfTypeCode, 0, "") + itf = b.CreateInsertValue(itf, itfValue, 1, "") return itf } @@ -228,7 +228,7 @@ func getTypeCodeName(t types.Type) string { // getTypeMethodSet returns a reference (GEP) to a global method set. This // method set should be unreferenced after the interface lowering pass. -func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value { +func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { global := c.mod.NamedGlobal(typ.String() + "$methodset") zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) if !global.IsNil() { @@ -393,31 +393,31 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value { // getInvokeCall creates and returns the function pointer and parameters of an // interface call. It can be used in a call or defer instruction. -func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value) { +func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value) { // Call an interface method with dynamic dispatch. - itf := frame.getValue(instr.Value) // interface + itf := b.getValue(instr.Value) // interface - llvmFnType := c.getRawFuncType(instr.Method.Type().(*types.Signature)) + llvmFnType := b.getRawFuncType(instr.Method.Type().(*types.Signature)) - typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode") + typecode := b.CreateExtractValue(itf, 0, "invoke.typecode") values := []llvm.Value{ typecode, - c.getInterfaceMethodSet(instr.Value.Type().(*types.Named)), - c.getMethodSignature(instr.Method), + b.getInterfaceMethodSet(instr.Value.Type().(*types.Named)), + b.getMethodSignature(instr.Method), } - fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func") - fnCast := c.builder.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast") - receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver") + fn := b.createRuntimeCall("interfaceMethod", values, "invoke.func") + fnCast := b.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast") + receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver") args := []llvm.Value{receiverValue} for _, arg := range instr.Args { - args = append(args, frame.getValue(arg)) + args = append(args, b.getValue(arg)) } // Add the context parameter. An interface call never takes a context but we // have to supply the parameter anyway. - args = append(args, llvm.Undef(c.i8ptrType)) + args = append(args, llvm.Undef(b.i8ptrType)) // Add the parent goroutine handle. - args = append(args, llvm.Undef(c.i8ptrType)) + args = append(args, llvm.Undef(b.i8ptrType)) return fnCast, args } From 3dad71512da27a42528d856cf7add42346eee164 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 23 Nov 2019 13:34:21 +0100 Subject: [PATCH 11/21] compiler: refactor map operations to use the builder object --- compiler/compiler.go | 8 ++-- compiler/map.go | 106 +++++++++++++++++++++++-------------------- 2 files changed, 60 insertions(+), 54 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 26115d6f91..e128d5d6b8 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1107,7 +1107,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { key := frame.getValue(instr.Key) value := frame.getValue(instr.Value) mapType := instr.Map.Type().Underlying().(*types.Map) - c.emitMapUpdate(mapType.Key(), m, key, value, instr.Pos()) + frame.createMapUpdate(mapType.Key(), m, key, value, instr.Pos()) case *ssa.Panic: value := frame.getValue(instr.X) c.createRuntimeCall("_panic", []llvm.Value{value}, "") @@ -1219,7 +1219,7 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string, case "delete": m := frame.getValue(args[0]) key := frame.getValue(args[1]) - return llvm.Value{}, c.emitMapDelete(args[1].Type(), m, key, pos) + return llvm.Value{}, frame.createMapDelete(args[1].Type(), m, key, pos) case "imag": cplx := frame.getValue(args[0]) return c.builder.CreateExtractValue(cplx, 1, "imag"), nil @@ -1619,7 +1619,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { if expr.CommaOk { valueType = valueType.(*types.Tuple).At(0).Type() } - return c.emitMapLookup(xType.Key(), valueType, value, index, expr.CommaOk, expr.Pos()) + return frame.createMapLookup(xType.Key(), valueType, value, index, expr.CommaOk, expr.Pos()) default: panic("unknown lookup type: " + expr.String()) } @@ -1631,7 +1631,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { val := frame.getValue(expr.X) return frame.createMakeInterface(val, expr.X.Type(), expr.Pos()), nil case *ssa.MakeMap: - return c.emitMakeMap(frame, expr) + return frame.createMakeMap(expr) case *ssa.MakeSlice: sliceLen := frame.getValue(expr.Len) sliceCap := frame.getValue(expr.Cap) diff --git a/compiler/map.go b/compiler/map.go index b4e163fe7f..d469db5bb9 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -10,56 +10,58 @@ import ( "tinygo.org/x/go-llvm" ) -// emitMakeMap creates a new map object (runtime.hashmap) by allocating and +// createMakeMap creates a new map object (runtime.hashmap) by allocating and // initializing an appropriately sized object. -func (c *Compiler) emitMakeMap(frame *Frame, expr *ssa.MakeMap) (llvm.Value, error) { +func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) { mapType := expr.Type().Underlying().(*types.Map) keyType := mapType.Key().Underlying() - llvmValueType := c.getLLVMType(mapType.Elem().Underlying()) + llvmValueType := b.getLLVMType(mapType.Elem().Underlying()) var llvmKeyType llvm.Type if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // String keys. - llvmKeyType = c.getLLVMType(keyType) + llvmKeyType = b.getLLVMType(keyType) } else if hashmapIsBinaryKey(keyType) { // Trivially comparable keys. - llvmKeyType = c.getLLVMType(keyType) + llvmKeyType = b.getLLVMType(keyType) } else { // All other keys. Implemented as map[interface{}]valueType for ease of // implementation. - llvmKeyType = c.getLLVMRuntimeType("_interface") + llvmKeyType = b.getLLVMRuntimeType("_interface") } - keySize := c.targetData.TypeAllocSize(llvmKeyType) - valueSize := c.targetData.TypeAllocSize(llvmValueType) - llvmKeySize := llvm.ConstInt(c.ctx.Int8Type(), keySize, false) - llvmValueSize := llvm.ConstInt(c.ctx.Int8Type(), valueSize, false) - sizeHint := llvm.ConstInt(c.uintptrType, 8, false) + keySize := b.targetData.TypeAllocSize(llvmKeyType) + valueSize := b.targetData.TypeAllocSize(llvmValueType) + llvmKeySize := llvm.ConstInt(b.ctx.Int8Type(), keySize, false) + llvmValueSize := llvm.ConstInt(b.ctx.Int8Type(), valueSize, false) + sizeHint := llvm.ConstInt(b.uintptrType, 8, false) if expr.Reserve != nil { - sizeHint = frame.getValue(expr.Reserve) + sizeHint = b.getValue(expr.Reserve) var err error - sizeHint, err = frame.createConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos()) + sizeHint, err = b.createConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos()) if err != nil { return llvm.Value{}, err } } - hashmap := c.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint}, "") + hashmap := b.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint}, "") return hashmap, nil } -func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) { - llvmValueType := c.getLLVMType(valueType) +// createMapLookup returns the value in a map. It calls a runtime function +// depending on the map key type to load the map value and its comma-ok value. +func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) { + llvmValueType := b.getLLVMType(valueType) // Allocate the memory for the resulting type. Do not zero this memory: it // will be zeroed by the hashmap get implementation if the key is not // present in the map. - mapValueAlloca, mapValuePtr, mapValueAllocaSize := c.createTemporaryAlloca(llvmValueType, "hashmap.value") + mapValueAlloca, mapValuePtr, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value") // We need the map size (with type uintptr) to pass to the hashmap*Get // functions. This is necessary because those *Get functions are valid on // nil maps, and they'll need to zero the value pointer by that number of // bytes. mapValueSize := mapValueAllocaSize - if mapValueSize.Type().IntTypeWidth() > c.uintptrType.IntTypeWidth() { - mapValueSize = llvm.ConstTrunc(mapValueSize, c.uintptrType) + if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() { + mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType) } // Do the lookup. How it is done depends on the key type. @@ -68,84 +70,88 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string params := []llvm.Value{m, key, mapValuePtr, mapValueSize} - commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "") + commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "") } else if hashmapIsBinaryKey(keyType) { // key can be compared with runtime.memequal // Store the key in an alloca, in the entry block to avoid dynamic stack // growth. - mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key") - c.builder.CreateStore(key, mapKeyAlloca) + mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") + b.CreateStore(key, mapKeyAlloca) // Fetch the value from the hashmap. params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize} - commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "") - c.emitLifetimeEnd(mapKeyPtr, mapKeySize) + commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "") + b.emitLifetimeEnd(mapKeyPtr, mapKeySize) } else { // Not trivially comparable using memcmp. Make it an interface instead. itfKey := key if _, ok := keyType.(*types.Interface); !ok { // Not already an interface, so convert it to an interface now. - itfKey = c.parseMakeInterface(key, keyType, pos) + itfKey = b.createMakeInterface(key, keyType, pos) } params := []llvm.Value{m, itfKey, mapValuePtr, mapValueSize} - commaOkValue = c.createRuntimeCall("hashmapInterfaceGet", params, "") + commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "") } // Load the resulting value from the hashmap. The value is set to the zero // value if the key doesn't exist in the hashmap. - mapValue := c.builder.CreateLoad(mapValueAlloca, "") - c.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize) + mapValue := b.CreateLoad(mapValueAlloca, "") + b.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize) if commaOk { - tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false)) - tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "") - tuple = c.builder.CreateInsertValue(tuple, commaOkValue, 1, "") + tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false)) + tuple = b.CreateInsertValue(tuple, mapValue, 0, "") + tuple = b.CreateInsertValue(tuple, commaOkValue, 1, "") return tuple, nil } else { return mapValue, nil } } -func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { - valueAlloca, valuePtr, valueSize := c.createTemporaryAlloca(value.Type(), "hashmap.value") - c.builder.CreateStore(value, valueAlloca) +// createMapUpdate updates a map key to a given value, by creating an +// appropriate runtime call. +func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { + valueAlloca, valuePtr, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value") + b.CreateStore(value, valueAlloca) keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string params := []llvm.Value{m, key, valuePtr} - c.createRuntimeCall("hashmapStringSet", params, "") + b.createRuntimeCall("hashmapStringSet", params, "") } else if hashmapIsBinaryKey(keyType) { // key can be compared with runtime.memequal - keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key") - c.builder.CreateStore(key, keyAlloca) + keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") + b.CreateStore(key, keyAlloca) params := []llvm.Value{m, keyPtr, valuePtr} - c.createRuntimeCall("hashmapBinarySet", params, "") - c.emitLifetimeEnd(keyPtr, keySize) + b.createRuntimeCall("hashmapBinarySet", params, "") + b.emitLifetimeEnd(keyPtr, keySize) } else { // Key is not trivially comparable, so compare it as an interface instead. itfKey := key if _, ok := keyType.(*types.Interface); !ok { // Not already an interface, so convert it to an interface first. - itfKey = c.parseMakeInterface(key, keyType, pos) + itfKey = b.createMakeInterface(key, keyType, pos) } params := []llvm.Value{m, itfKey, valuePtr} - c.createRuntimeCall("hashmapInterfaceSet", params, "") + b.createRuntimeCall("hashmapInterfaceSet", params, "") } - c.emitLifetimeEnd(valuePtr, valueSize) + b.emitLifetimeEnd(valuePtr, valueSize) } -func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error { +// createMapDelete deletes a key from a map by calling the appropriate runtime +// function. It is the implementation of the Go delete() builtin. +func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error { keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string params := []llvm.Value{m, key} - c.createRuntimeCall("hashmapStringDelete", params, "") + b.createRuntimeCall("hashmapStringDelete", params, "") return nil } else if hashmapIsBinaryKey(keyType) { - keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key") - c.builder.CreateStore(key, keyAlloca) + keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") + b.CreateStore(key, keyAlloca) params := []llvm.Value{m, keyPtr} - c.createRuntimeCall("hashmapBinaryDelete", params, "") - c.emitLifetimeEnd(keyPtr, keySize) + b.createRuntimeCall("hashmapBinaryDelete", params, "") + b.emitLifetimeEnd(keyPtr, keySize) return nil } else { // Key is not trivially comparable, so compare it as an interface @@ -153,10 +159,10 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke itfKey := key if _, ok := keyType.(*types.Interface); !ok { // Not already an interface, so convert it to an interface first. - itfKey = c.parseMakeInterface(key, keyType, pos) + itfKey = b.createMakeInterface(key, keyType, pos) } params := []llvm.Value{m, itfKey} - c.createRuntimeCall("hashmapInterfaceDelete", params, "") + b.createRuntimeCall("hashmapInterfaceDelete", params, "") return nil } } From beae7af24d3af831d68fb9549c666ea90928e934 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 23 Nov 2019 13:37:50 +0100 Subject: [PATCH 12/21] compiler: refactor builtins --- compiler/compiler.go | 164 ++++++++++++++++++++++--------------------- 1 file changed, 83 insertions(+), 81 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index e128d5d6b8..7222041e78 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1144,122 +1144,124 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { } } -func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string, pos token.Pos) (llvm.Value, error) { +// createBuiltin lowers a builtin Go function (append, close, delete, etc.) to +// LLVM IR. It uses runtime calls for some builtins. +func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos) (llvm.Value, error) { switch callName { case "append": - src := frame.getValue(args[0]) - elems := frame.getValue(args[1]) - srcBuf := c.builder.CreateExtractValue(src, 0, "append.srcBuf") - srcPtr := c.builder.CreateBitCast(srcBuf, c.i8ptrType, "append.srcPtr") - srcLen := c.builder.CreateExtractValue(src, 1, "append.srcLen") - srcCap := c.builder.CreateExtractValue(src, 2, "append.srcCap") - elemsBuf := c.builder.CreateExtractValue(elems, 0, "append.elemsBuf") - elemsPtr := c.builder.CreateBitCast(elemsBuf, c.i8ptrType, "append.srcPtr") - elemsLen := c.builder.CreateExtractValue(elems, 1, "append.elemsLen") + src := b.getValue(args[0]) + elems := b.getValue(args[1]) + srcBuf := b.CreateExtractValue(src, 0, "append.srcBuf") + srcPtr := b.CreateBitCast(srcBuf, b.i8ptrType, "append.srcPtr") + srcLen := b.CreateExtractValue(src, 1, "append.srcLen") + srcCap := b.CreateExtractValue(src, 2, "append.srcCap") + elemsBuf := b.CreateExtractValue(elems, 0, "append.elemsBuf") + elemsPtr := b.CreateBitCast(elemsBuf, b.i8ptrType, "append.srcPtr") + elemsLen := b.CreateExtractValue(elems, 1, "append.elemsLen") elemType := srcBuf.Type().ElementType() - elemSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(elemType), false) - result := c.createRuntimeCall("sliceAppend", []llvm.Value{srcPtr, elemsPtr, srcLen, srcCap, elemsLen, elemSize}, "append.new") - newPtr := c.builder.CreateExtractValue(result, 0, "append.newPtr") - newBuf := c.builder.CreateBitCast(newPtr, srcBuf.Type(), "append.newBuf") - newLen := c.builder.CreateExtractValue(result, 1, "append.newLen") - newCap := c.builder.CreateExtractValue(result, 2, "append.newCap") + elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false) + result := b.createRuntimeCall("sliceAppend", []llvm.Value{srcPtr, elemsPtr, srcLen, srcCap, elemsLen, elemSize}, "append.new") + newPtr := b.CreateExtractValue(result, 0, "append.newPtr") + newBuf := b.CreateBitCast(newPtr, srcBuf.Type(), "append.newBuf") + newLen := b.CreateExtractValue(result, 1, "append.newLen") + newCap := b.CreateExtractValue(result, 2, "append.newCap") newSlice := llvm.Undef(src.Type()) - newSlice = c.builder.CreateInsertValue(newSlice, newBuf, 0, "") - newSlice = c.builder.CreateInsertValue(newSlice, newLen, 1, "") - newSlice = c.builder.CreateInsertValue(newSlice, newCap, 2, "") + newSlice = b.CreateInsertValue(newSlice, newBuf, 0, "") + newSlice = b.CreateInsertValue(newSlice, newLen, 1, "") + newSlice = b.CreateInsertValue(newSlice, newCap, 2, "") return newSlice, nil case "cap": - value := frame.getValue(args[0]) + value := b.getValue(args[0]) var llvmCap llvm.Value switch args[0].Type().(type) { case *types.Chan: // Channel. Buffered channels haven't been implemented yet so always // return 0. - llvmCap = llvm.ConstInt(c.intType, 0, false) + llvmCap = llvm.ConstInt(b.intType, 0, false) case *types.Slice: - llvmCap = c.builder.CreateExtractValue(value, 2, "cap") + llvmCap = b.CreateExtractValue(value, 2, "cap") default: - return llvm.Value{}, c.makeError(pos, "todo: cap: unknown type") + return llvm.Value{}, b.makeError(pos, "todo: cap: unknown type") } - if c.targetData.TypeAllocSize(llvmCap.Type()) < c.targetData.TypeAllocSize(c.intType) { - llvmCap = c.builder.CreateZExt(llvmCap, c.intType, "len.int") + if b.targetData.TypeAllocSize(llvmCap.Type()) < b.targetData.TypeAllocSize(b.intType) { + llvmCap = b.CreateZExt(llvmCap, b.intType, "len.int") } return llvmCap, nil case "close": - frame.createChanClose(args[0]) + b.createChanClose(args[0]) return llvm.Value{}, nil case "complex": - r := frame.getValue(args[0]) - i := frame.getValue(args[1]) + r := b.getValue(args[0]) + i := b.getValue(args[1]) t := args[0].Type().Underlying().(*types.Basic) var cplx llvm.Value switch t.Kind() { case types.Float32: - cplx = llvm.Undef(c.ctx.StructType([]llvm.Type{c.ctx.FloatType(), c.ctx.FloatType()}, false)) + cplx = llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.FloatType(), b.ctx.FloatType()}, false)) case types.Float64: - cplx = llvm.Undef(c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false)) + cplx = llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.DoubleType(), b.ctx.DoubleType()}, false)) default: - return llvm.Value{}, c.makeError(pos, "unsupported type in complex builtin: "+t.String()) + return llvm.Value{}, b.makeError(pos, "unsupported type in complex builtin: "+t.String()) } - cplx = c.builder.CreateInsertValue(cplx, r, 0, "") - cplx = c.builder.CreateInsertValue(cplx, i, 1, "") + cplx = b.CreateInsertValue(cplx, r, 0, "") + cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil case "copy": - dst := frame.getValue(args[0]) - src := frame.getValue(args[1]) - dstLen := c.builder.CreateExtractValue(dst, 1, "copy.dstLen") - srcLen := c.builder.CreateExtractValue(src, 1, "copy.srcLen") - dstBuf := c.builder.CreateExtractValue(dst, 0, "copy.dstArray") - srcBuf := c.builder.CreateExtractValue(src, 0, "copy.srcArray") + dst := b.getValue(args[0]) + src := b.getValue(args[1]) + dstLen := b.CreateExtractValue(dst, 1, "copy.dstLen") + srcLen := b.CreateExtractValue(src, 1, "copy.srcLen") + dstBuf := b.CreateExtractValue(dst, 0, "copy.dstArray") + srcBuf := b.CreateExtractValue(src, 0, "copy.srcArray") elemType := dstBuf.Type().ElementType() - dstBuf = c.builder.CreateBitCast(dstBuf, c.i8ptrType, "copy.dstPtr") - srcBuf = c.builder.CreateBitCast(srcBuf, c.i8ptrType, "copy.srcPtr") - elemSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(elemType), false) - return c.createRuntimeCall("sliceCopy", []llvm.Value{dstBuf, srcBuf, dstLen, srcLen, elemSize}, "copy.n"), nil + dstBuf = b.CreateBitCast(dstBuf, b.i8ptrType, "copy.dstPtr") + srcBuf = b.CreateBitCast(srcBuf, b.i8ptrType, "copy.srcPtr") + elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false) + return b.createRuntimeCall("sliceCopy", []llvm.Value{dstBuf, srcBuf, dstLen, srcLen, elemSize}, "copy.n"), nil case "delete": - m := frame.getValue(args[0]) - key := frame.getValue(args[1]) - return llvm.Value{}, frame.createMapDelete(args[1].Type(), m, key, pos) + m := b.getValue(args[0]) + key := b.getValue(args[1]) + return llvm.Value{}, b.createMapDelete(args[1].Type(), m, key, pos) case "imag": - cplx := frame.getValue(args[0]) - return c.builder.CreateExtractValue(cplx, 1, "imag"), nil + cplx := b.getValue(args[0]) + return b.CreateExtractValue(cplx, 1, "imag"), nil case "len": - value := frame.getValue(args[0]) + value := b.getValue(args[0]) var llvmLen llvm.Value switch args[0].Type().Underlying().(type) { case *types.Basic, *types.Slice: // string or slice - llvmLen = c.builder.CreateExtractValue(value, 1, "len") + llvmLen = b.CreateExtractValue(value, 1, "len") case *types.Chan: // Channel. Buffered channels haven't been implemented yet so always // return 0. - llvmLen = llvm.ConstInt(c.intType, 0, false) + llvmLen = llvm.ConstInt(b.intType, 0, false) case *types.Map: - llvmLen = c.createRuntimeCall("hashmapLen", []llvm.Value{value}, "len") + llvmLen = b.createRuntimeCall("hashmapLen", []llvm.Value{value}, "len") default: - return llvm.Value{}, c.makeError(pos, "todo: len: unknown type") + return llvm.Value{}, b.makeError(pos, "todo: len: unknown type") } - if c.targetData.TypeAllocSize(llvmLen.Type()) < c.targetData.TypeAllocSize(c.intType) { - llvmLen = c.builder.CreateZExt(llvmLen, c.intType, "len.int") + if b.targetData.TypeAllocSize(llvmLen.Type()) < b.targetData.TypeAllocSize(b.intType) { + llvmLen = b.CreateZExt(llvmLen, b.intType, "len.int") } return llvmLen, nil case "print", "println": for i, arg := range args { if i >= 1 && callName == "println" { - c.createRuntimeCall("printspace", nil, "") + b.createRuntimeCall("printspace", nil, "") } - value := frame.getValue(arg) + value := b.getValue(arg) typ := arg.Type().Underlying() switch typ := typ.(type) { case *types.Basic: switch typ.Kind() { case types.String, types.UntypedString: - c.createRuntimeCall("printstring", []llvm.Value{value}, "") + b.createRuntimeCall("printstring", []llvm.Value{value}, "") case types.Uintptr: - c.createRuntimeCall("printptr", []llvm.Value{value}, "") + b.createRuntimeCall("printptr", []llvm.Value{value}, "") case types.UnsafePointer: - ptrValue := c.builder.CreatePtrToInt(value, c.uintptrType, "") - c.createRuntimeCall("printptr", []llvm.Value{ptrValue}, "") + ptrValue := b.CreatePtrToInt(value, b.uintptrType, "") + b.createRuntimeCall("printptr", []llvm.Value{ptrValue}, "") default: // runtime.print{int,uint}{8,16,32,64} if typ.Info()&types.IsInteger != 0 { @@ -1269,47 +1271,47 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string, } else { name += "int" } - name += strconv.FormatUint(c.targetData.TypeAllocSize(value.Type())*8, 10) - c.createRuntimeCall(name, []llvm.Value{value}, "") + name += strconv.FormatUint(b.targetData.TypeAllocSize(value.Type())*8, 10) + b.createRuntimeCall(name, []llvm.Value{value}, "") } else if typ.Kind() == types.Bool { - c.createRuntimeCall("printbool", []llvm.Value{value}, "") + b.createRuntimeCall("printbool", []llvm.Value{value}, "") } else if typ.Kind() == types.Float32 { - c.createRuntimeCall("printfloat32", []llvm.Value{value}, "") + b.createRuntimeCall("printfloat32", []llvm.Value{value}, "") } else if typ.Kind() == types.Float64 { - c.createRuntimeCall("printfloat64", []llvm.Value{value}, "") + b.createRuntimeCall("printfloat64", []llvm.Value{value}, "") } else if typ.Kind() == types.Complex64 { - c.createRuntimeCall("printcomplex64", []llvm.Value{value}, "") + b.createRuntimeCall("printcomplex64", []llvm.Value{value}, "") } else if typ.Kind() == types.Complex128 { - c.createRuntimeCall("printcomplex128", []llvm.Value{value}, "") + b.createRuntimeCall("printcomplex128", []llvm.Value{value}, "") } else { - return llvm.Value{}, c.makeError(pos, "unknown basic arg type: "+typ.String()) + return llvm.Value{}, b.makeError(pos, "unknown basic arg type: "+typ.String()) } } case *types.Interface: - c.createRuntimeCall("printitf", []llvm.Value{value}, "") + b.createRuntimeCall("printitf", []llvm.Value{value}, "") case *types.Map: - c.createRuntimeCall("printmap", []llvm.Value{value}, "") + b.createRuntimeCall("printmap", []llvm.Value{value}, "") case *types.Pointer: - ptrValue := c.builder.CreatePtrToInt(value, c.uintptrType, "") - c.createRuntimeCall("printptr", []llvm.Value{ptrValue}, "") + ptrValue := b.CreatePtrToInt(value, b.uintptrType, "") + b.createRuntimeCall("printptr", []llvm.Value{ptrValue}, "") default: - return llvm.Value{}, c.makeError(pos, "unknown arg type: "+typ.String()) + return llvm.Value{}, b.makeError(pos, "unknown arg type: "+typ.String()) } } if callName == "println" { - c.createRuntimeCall("printnl", nil, "") + b.createRuntimeCall("printnl", nil, "") } return llvm.Value{}, nil // print() or println() returns void case "real": - cplx := frame.getValue(args[0]) - return c.builder.CreateExtractValue(cplx, 0, "real"), nil + cplx := b.getValue(args[0]) + return b.CreateExtractValue(cplx, 0, "real"), nil case "recover": - return c.createRuntimeCall("_recover", nil, ""), nil + return b.createRuntimeCall("_recover", nil, ""), nil case "ssa:wrapnilchk": // TODO: do an actual nil check? - return frame.getValue(args[0]), nil + return b.getValue(args[0]), nil default: - return llvm.Value{}, c.makeError(pos, "todo: builtin: "+callName) + return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName) } } @@ -1384,7 +1386,7 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e // Builtin or function pointer. switch call := instr.Value.(type) { case *ssa.Builtin: - return c.parseBuiltin(frame, instr.Args, call.Name(), instr.Pos()) + return frame.createBuiltin(instr.Args, call.Name(), instr.Pos()) default: // function pointer value := frame.getValue(instr.Value) // This is a func value, which cannot be called directly. We have to From 1d4042cda0cf286343c93f30b939ff7bcc96415e Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 23 Nov 2019 21:36:26 +0100 Subject: [PATCH 13/21] compiler: refactor function calling --- compiler/compiler.go | 108 +++++++++++++++++++++--------------------- compiler/func.go | 20 ++++---- compiler/inlineasm.go | 58 +++++++++++------------ compiler/interrupt.go | 50 +++++++++---------- compiler/syscall.go | 90 +++++++++++++++++------------------ compiler/volatile.go | 22 +++++---- 6 files changed, 177 insertions(+), 171 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 7222041e78..1807b8af5d 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1066,7 +1066,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { // A goroutine call on a func value, but the callee is trivial to find. For // example: immediately applied functions. funcValue := frame.getValue(value) - context = c.extractFuncContext(funcValue) + context = frame.extractFuncContext(funcValue) default: panic("StaticCallee returned an unexpected value") } @@ -1078,7 +1078,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { // goroutine: // * The function context, for closures. // * The function pointer (for tasks). - funcPtr, context := c.decodeFuncValue(frame.getValue(instr.Call.Value), instr.Call.Value.Type().(*types.Signature)) + funcPtr, context := frame.decodeFuncValue(frame.getValue(instr.Call.Value), instr.Call.Value.Type().(*types.Signature)) params = append(params, context) // context parameter switch c.Scheduler() { case "none", "coroutines": @@ -1315,86 +1315,90 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos } } -func (c *Compiler) parseFunctionCall(frame *Frame, args []ssa.Value, llvmFn, context llvm.Value, exported bool) llvm.Value { - var params []llvm.Value - for _, param := range args { - params = append(params, frame.getValue(param)) - } - - if !exported { - // This function takes a context parameter. - // Add it to the end of the parameter list. - params = append(params, context) - - // Parent coroutine handle. - params = append(params, llvm.Undef(c.i8ptrType)) - } - - return c.createCall(llvmFn, params, "") -} - -func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) { +// createFunctionCall lowers a Go SSA call instruction (to a simple function, +// closure, function pointer, builtin, method, etc.) to LLVM IR, usually a call +// instruction. +// +// This is also where compiler intrinsics are implemented. +func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) { if instr.IsInvoke() { - fnCast, args := frame.getInvokeCall(instr) - return c.createCall(fnCast, args, ""), nil + fnCast, args := b.getInvokeCall(instr) + return b.createCall(fnCast, args, ""), nil } // Try to call the function directly for trivially static calls. + var callee, context llvm.Value + exported := false if fn := instr.StaticCallee(); fn != nil { + // Direct function call, either to a named or anonymous (directly + // applied) function call. If it is anonymous, it may be a closure. name := fn.RelString(nil) switch { case name == "device/arm.ReadRegister" || name == "device/riscv.ReadRegister": - return c.emitReadRegister(name, instr.Args) + return b.createReadRegister(name, instr.Args) case name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm": - return c.emitAsm(instr.Args) + return b.createInlineAsm(instr.Args) case name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull": - return c.emitAsmFull(frame, instr) + return b.createInlineAsmFull(instr) case strings.HasPrefix(name, "device/arm.SVCall"): - return c.emitSVCall(frame, instr.Args) + return b.emitSVCall(instr.Args) case strings.HasPrefix(name, "(device/riscv.CSR)."): - return c.emitCSROperation(frame, instr) + return b.emitCSROperation(instr) case strings.HasPrefix(name, "syscall.Syscall"): - return c.emitSyscall(frame, instr) + return b.createSyscall(instr) case strings.HasPrefix(name, "runtime/volatile.Load"): - return c.emitVolatileLoad(frame, instr) + return b.createVolatileLoad(instr) case strings.HasPrefix(name, "runtime/volatile.Store"): - return c.emitVolatileStore(frame, instr) + return b.createVolatileStore(instr) case name == "runtime/interrupt.New": - return c.emitInterruptGlobal(frame, instr) + return b.createInterruptGlobal(instr) } - targetFunc := c.ir.GetFunction(fn) + targetFunc := b.ir.GetFunction(fn) if targetFunc.LLVMFn.IsNil() { - return llvm.Value{}, c.makeError(instr.Pos(), "undefined function: "+targetFunc.LinkName()) + return llvm.Value{}, b.makeError(instr.Pos(), "undefined function: "+targetFunc.LinkName()) } - var context llvm.Value switch value := instr.Value.(type) { case *ssa.Function: // Regular function call. No context is necessary. - context = llvm.Undef(c.i8ptrType) + context = llvm.Undef(b.i8ptrType) case *ssa.MakeClosure: // A call on a func value, but the callee is trivial to find. For // example: immediately applied functions. - funcValue := frame.getValue(value) - context = c.extractFuncContext(funcValue) + funcValue := b.getValue(value) + context = b.extractFuncContext(funcValue) default: panic("StaticCallee returned an unexpected value") } - return c.parseFunctionCall(frame, instr.Args, targetFunc.LLVMFn, context, targetFunc.IsExported()), nil - } - - // Builtin or function pointer. - switch call := instr.Value.(type) { - case *ssa.Builtin: - return frame.createBuiltin(instr.Args, call.Name(), instr.Pos()) - default: // function pointer - value := frame.getValue(instr.Value) + callee = targetFunc.LLVMFn + exported = targetFunc.IsExported() + } else if call, ok := instr.Value.(*ssa.Builtin); ok { + // Builtin function (append, close, delete, etc.).) + return b.createBuiltin(instr.Args, call.Name(), instr.Pos()) + } else { + // Function pointer. + value := b.getValue(instr.Value) // This is a func value, which cannot be called directly. We have to // extract the function pointer and context first from the func value. - funcPtr, context := c.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature)) - frame.createNilCheck(funcPtr, "fpcall") - return c.parseFunctionCall(frame, instr.Args, funcPtr, context, false), nil + callee, context = b.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature)) + b.createNilCheck(callee, "fpcall") + } + + var params []llvm.Value + for _, param := range instr.Args { + params = append(params, b.getValue(param)) + } + + if !exported { + // This function takes a context parameter. + // Add it to the end of the parameter list. + params = append(params, context) + + // Parent coroutine handle. + params = append(params, llvm.Undef(b.i8ptrType)) } + + return b.createCall(callee, params, ""), nil } // getValue returns the LLVM value of a constant, function value, global, or @@ -1462,9 +1466,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { y := frame.getValue(expr.Y) return frame.createBinOp(expr.Op, expr.X.Type(), x, y, expr.Pos()) case *ssa.Call: - // Passing the current task here to the subroutine. It is only used when - // the subroutine is blocking. - return c.parseCall(frame, expr.Common()) + return frame.createFunctionCall(expr.Common()) case *ssa.ChangeInterface: // Do not change between interface types: always use the underlying // (concrete) type in the type number of the interface. Every method diff --git a/compiler/func.go b/compiler/func.go index 69fb78bce5..9632e7125c 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -71,22 +71,22 @@ func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value { // extractFuncContext extracts the context pointer from this function value. It // is a cheap operation. -func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value { - return c.builder.CreateExtractValue(funcValue, 0, "") +func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value { + return b.CreateExtractValue(funcValue, 0, "") } // decodeFuncValue extracts the context and the function pointer from this func // value. This may be an expensive operation. -func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) { - context = c.builder.CreateExtractValue(funcValue, 0, "") - switch c.FuncImplementation() { +func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) { + context = b.CreateExtractValue(funcValue, 0, "") + switch b.FuncImplementation() { case compileopts.FuncValueDoubleword: - funcPtr = c.builder.CreateExtractValue(funcValue, 1, "") + funcPtr = b.CreateExtractValue(funcValue, 1, "") case compileopts.FuncValueSwitch: - llvmSig := c.getRawFuncType(sig) - sigGlobal := c.getTypeCode(sig) - funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "") - funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "") + llvmSig := b.getRawFuncType(sig) + sigGlobal := b.getTypeCode(sig) + funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "") + funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "") default: panic("unimplemented func value variant") } diff --git a/compiler/inlineasm.go b/compiler/inlineasm.go index 77e1af96a2..2727228a18 100644 --- a/compiler/inlineasm.go +++ b/compiler/inlineasm.go @@ -18,8 +18,8 @@ import ( // func ReadRegister(name string) uintptr // // The register name must be a constant, for example "sp". -func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value, error) { - fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false) +func (b *builder) createReadRegister(name string, args []ssa.Value) (llvm.Value, error) { + fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{}, false) regname := constant.StringVal(args[0].(*ssa.Const).Value) var asm string switch name { @@ -31,7 +31,7 @@ func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value, panic("unknown architecture") } target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0) - return c.builder.CreateCall(target, nil, ""), nil + return b.CreateCall(target, nil, ""), nil } // This is a compiler builtin, which emits a piece of inline assembly with no @@ -41,12 +41,12 @@ func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value, // func Asm(asm string) // // The provided assembly must be a constant. -func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) { +func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) { // Magic function: insert inline assembly instead of calling it. - fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{}, false) + fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{}, false) asm := constant.StringVal(args[0].(*ssa.Const).Value) target := llvm.InlineAsm(fnType, asm, "", true, false, 0) - return c.builder.CreateCall(target, nil, ""), nil + return b.CreateCall(target, nil, ""), nil } // This is a compiler builtin, which allows assembly to be called in a flexible @@ -63,7 +63,7 @@ func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) { // "value": 1 // "result": &dest, // }) -func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) { +func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) { asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value) registers := map[string]llvm.Value{} registerMap := instr.Args[1].(*ssa.MakeMap) @@ -73,17 +73,17 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, // ignore case *ssa.MapUpdate: if r.Block() != registerMap.Block() { - return llvm.Value{}, c.makeError(instr.Pos(), "register value map must be created in the same basic block") + return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block") } key := constant.StringVal(r.Key.(*ssa.Const).Value) //println("value:", r.Value.(*ssa.MakeInterface).X.String()) - registers[key] = frame.getValue(r.Value.(*ssa.MakeInterface).X) + registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X) case *ssa.Call: if r.Common() == instr { break } default: - return llvm.Value{}, c.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String()) + return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String()) } } // TODO: handle dollar signs in asm string @@ -98,7 +98,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, name := s[1 : len(s)-1] if _, ok := registers[name]; !ok { if err == nil { - err = c.makeError(instr.Pos(), "unknown register name: "+name) + err = b.makeError(instr.Pos(), "unknown register name: "+name) } return s } @@ -112,7 +112,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, case llvm.PointerTypeKind: constraints = append(constraints, "*m") default: - err = c.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name) + err = b.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name) return s } } @@ -121,9 +121,9 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, if err != nil { return llvm.Value{}, err } - fnType := llvm.FunctionType(c.ctx.VoidType(), argTypes, false) + fnType := llvm.FunctionType(b.ctx.VoidType(), argTypes, false) target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0) - return c.builder.CreateCall(target, args, ""), nil + return b.CreateCall(target, args, ""), nil } // This is a compiler builtin which emits an inline SVCall instruction. It can @@ -137,7 +137,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, // // The num parameter must be a constant. All other parameters may be any scalar // value supported by LLVM inline assembly. -func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error) { +func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) { num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value) llvmArgs := []llvm.Value{} argTypes := []llvm.Type{} @@ -150,7 +150,7 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error } else { constraints += ",{r" + strconv.Itoa(i) + "}" } - llvmValue := frame.getValue(arg) + llvmValue := b.getValue(arg) llvmArgs = append(llvmArgs, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } @@ -158,9 +158,9 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error // clobbered. r0 is used as an output register so doesn't have to be // marked as clobbered. constraints += ",~{r1},~{r2},~{r3}" - fnType := llvm.FunctionType(c.uintptrType, argTypes, false) + fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0) - return c.builder.CreateCall(target, llvmArgs, ""), nil + return b.CreateCall(target, llvmArgs, ""), nil } // This is a compiler builtin which emits CSR instructions. It can be one of: @@ -172,38 +172,38 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error // // The csr parameter (method receiver) must be a constant. Other parameter can // be any value. -func (c *Compiler) emitCSROperation(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) { +func (b *builder) emitCSROperation(call *ssa.CallCommon) (llvm.Value, error) { csrConst, ok := call.Args[0].(*ssa.Const) if !ok { - return llvm.Value{}, c.makeError(call.Pos(), "CSR must be constant") + return llvm.Value{}, b.makeError(call.Pos(), "CSR must be constant") } csr := csrConst.Uint64() switch name := call.StaticCallee().Name(); name { case "Get": // Note that this instruction may have side effects, and thus must be // marked as such. - fnType := llvm.FunctionType(c.uintptrType, nil, false) + fnType := llvm.FunctionType(b.uintptrType, nil, false) asm := fmt.Sprintf("csrr $0, %d", csr) target := llvm.InlineAsm(fnType, asm, "=r", true, false, 0) - return c.builder.CreateCall(target, nil, ""), nil + return b.CreateCall(target, nil, ""), nil case "Set": - fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.uintptrType}, false) + fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.uintptrType}, false) asm := fmt.Sprintf("csrw %d, $0", csr) target := llvm.InlineAsm(fnType, asm, "r", true, false, 0) - return c.builder.CreateCall(target, []llvm.Value{frame.getValue(call.Args[1])}, ""), nil + return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil case "SetBits": // Note: it may be possible to optimize this to csrrsi in many cases. - fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{c.uintptrType}, false) + fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false) asm := fmt.Sprintf("csrrs $0, %d, $1", csr) target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0) - return c.builder.CreateCall(target, []llvm.Value{frame.getValue(call.Args[1])}, ""), nil + return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil case "ClearBits": // Note: it may be possible to optimize this to csrrci in many cases. - fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{c.uintptrType}, false) + fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false) asm := fmt.Sprintf("csrrc $0, %d, $1", csr) target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0) - return c.builder.CreateCall(target, []llvm.Value{frame.getValue(call.Args[1])}, ""), nil + return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil default: - return llvm.Value{}, c.makeError(call.Pos(), "unknown CSR operation: "+name) + return llvm.Value{}, b.makeError(call.Pos(), "unknown CSR operation: "+name) } } diff --git a/compiler/interrupt.go b/compiler/interrupt.go index 7f4fd77ac4..e4e31c0807 100644 --- a/compiler/interrupt.go +++ b/compiler/interrupt.go @@ -8,62 +8,62 @@ import ( "tinygo.org/x/go-llvm" ) -// emitInterruptGlobal creates a new runtime/interrupt.Interrupt struct that +// createInterruptGlobal creates a new runtime/interrupt.Interrupt struct that // will be lowered to a real interrupt during interrupt lowering. // // This two-stage approach allows unused interrupts to be optimized away if // necessary. -func (c *Compiler) emitInterruptGlobal(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) { +func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, error) { // Get the interrupt number, which must be a compile-time constant. id, ok := instr.Args[0].(*ssa.Const) if !ok { - return llvm.Value{}, c.makeError(instr.Pos(), "interrupt ID is not a constant") + return llvm.Value{}, b.makeError(instr.Pos(), "interrupt ID is not a constant") } // Get the func value, which also must be a compile time constant. // Note that bound functions are allowed if the function has a pointer // receiver and is a global. This is rather strict but still allows for // idiomatic Go code. - funcValue := frame.getValue(instr.Args[1]) + funcValue := b.getValue(instr.Args[1]) if funcValue.IsAConstant().IsNil() { // Try to determine the cause of the non-constantness for a nice error // message. switch instr.Args[1].(type) { case *ssa.MakeClosure: // This may also be a bound method. - return llvm.Value{}, c.makeError(instr.Pos(), "closures are not supported in interrupt.New") + return llvm.Value{}, b.makeError(instr.Pos(), "closures are not supported in interrupt.New") } // Fall back to a generic error. - return llvm.Value{}, c.makeError(instr.Pos(), "interrupt function must be constant") + return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant") } // Create a new global of type runtime/interrupt.handle. Globals of this // type are lowered in the interrupt lowering pass. - globalType := c.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type() - globalLLVMType := c.getLLVMType(globalType) + globalType := b.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type() + globalLLVMType := b.getLLVMType(globalType) globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10) - if global := c.mod.NamedGlobal(globalName); !global.IsNil() { - return llvm.Value{}, c.makeError(instr.Pos(), "interrupt redeclared in this program") + if global := b.mod.NamedGlobal(globalName); !global.IsNil() { + return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program") } - global := llvm.AddGlobal(c.mod, globalLLVMType, globalName) + global := llvm.AddGlobal(b.mod, globalLLVMType, globalName) global.SetLinkage(llvm.PrivateLinkage) global.SetGlobalConstant(true) global.SetUnnamedAddr(true) initializer := llvm.ConstNull(globalLLVMType) initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0}) - initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(c.intType, uint64(id.Int64()), true), []uint32{1, 0}) + initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{1, 0}) global.SetInitializer(initializer) // Add debug info to the interrupt global. - if c.Debug() { - pos := c.ir.Program.Fset.Position(instr.Pos()) - diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{ + if b.Debug() { + pos := b.ir.Program.Fset.Position(instr.Pos()) + diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{ Name: "interrupt" + strconv.FormatInt(id.Int64(), 10), LinkageName: globalName, - File: c.getDIFile(pos.Filename), + File: b.getDIFile(pos.Filename), Line: pos.Line, - Type: c.getDIType(globalType), - Expr: c.dibuilder.CreateExpression(nil), + Type: b.getDIType(globalType), + Expr: b.dibuilder.CreateExpression(nil), LocalToUnit: false, }) global.AddMetadata(0, diglobal) @@ -71,21 +71,21 @@ func (c *Compiler) emitInterruptGlobal(frame *Frame, instr *ssa.CallCommon) (llv // Create the runtime/interrupt.Interrupt type. It is a struct with a single // member of type int. - num := llvm.ConstPtrToInt(global, c.intType) - interrupt := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num}) + num := llvm.ConstPtrToInt(global, b.intType) + interrupt := llvm.ConstNamedStruct(b.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num}) // Add dummy "use" call for AVR, because interrupts may be used even though // they are never referenced again. This is unlike Cortex-M or the RISC-V // PLIC where each interrupt must be enabled using the interrupt number, and // thus keeps the Interrupt object alive. // This call is removed during interrupt lowering. - if strings.HasPrefix(c.Triple(), "avr") { - useFn := c.mod.NamedFunction("runtime/interrupt.use") + if strings.HasPrefix(b.Triple(), "avr") { + useFn := b.mod.NamedFunction("runtime/interrupt.use") if useFn.IsNil() { - useFnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false) - useFn = llvm.AddFunction(c.mod, "runtime/interrupt.use", useFnType) + useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false) + useFn = llvm.AddFunction(b.mod, "runtime/interrupt.use", useFnType) } - c.builder.CreateCall(useFn, []llvm.Value{interrupt}, "") + b.CreateCall(useFn, []llvm.Value{interrupt}, "") } return interrupt, nil diff --git a/compiler/syscall.go b/compiler/syscall.go index 074a88caf2..8bb873facf 100644 --- a/compiler/syscall.go +++ b/compiler/syscall.go @@ -10,14 +10,14 @@ import ( "tinygo.org/x/go-llvm" ) -// emitSyscall emits an inline system call instruction, depending on the target -// OS/arch. -func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) { - num := frame.getValue(call.Args[0]) +// createSyscall emits an inline system call instruction, depending on the +// target OS/arch. +func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { + num := b.getValue(call.Args[0]) var syscallResult llvm.Value switch { - case c.GOARCH() == "amd64": - if c.GOOS() == "darwin" { + case b.GOARCH() == "amd64": + if b.GOOS() == "darwin" { // Darwin adds this magic number to system call numbers: // // > Syscall classes for 64-bit system call entry. @@ -28,13 +28,13 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, // > All system classes enter the kernel via the syscall instruction. // // Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h - num = c.builder.CreateOr(num, llvm.ConstInt(c.uintptrType, 0x2000000, false), "") + num = b.CreateOr(num, llvm.ConstInt(b.uintptrType, 0x2000000, false), "") } // Sources: // https://stackoverflow.com/a/2538212 // https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall args := []llvm.Value{num} - argTypes := []llvm.Type{c.uintptrType} + argTypes := []llvm.Type{b.uintptrType} // Constraints will look something like: // "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}" constraints := "={rax},0" @@ -50,21 +50,21 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, "{r12}", "{r13}", }[i] - llvmValue := frame.getValue(arg) + llvmValue := b.getValue(arg) args = append(args, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } constraints += ",~{rcx},~{r11}" - fnType := llvm.FunctionType(c.uintptrType, argTypes, false) + fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel) - syscallResult = c.builder.CreateCall(target, args, "") - case c.GOARCH() == "386" && c.GOOS() == "linux": + syscallResult = b.CreateCall(target, args, "") + case b.GOARCH() == "386" && b.GOOS() == "linux": // Sources: // syscall(2) man page // https://stackoverflow.com/a/2538212 // https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#int_0x80 args := []llvm.Value{num} - argTypes := []llvm.Type{c.uintptrType} + argTypes := []llvm.Type{b.uintptrType} // Constraints will look something like: // "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}" constraints := "={eax},0" @@ -77,14 +77,14 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, "{edi}", "{ebp}", }[i] - llvmValue := frame.getValue(arg) + llvmValue := b.getValue(arg) args = append(args, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } - fnType := llvm.FunctionType(c.uintptrType, argTypes, false) + fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel) - syscallResult = c.builder.CreateCall(target, args, "") - case c.GOARCH() == "arm" && c.GOOS() == "linux": + syscallResult = b.CreateCall(target, args, "") + case b.GOARCH() == "arm" && b.GOOS() == "linux": // Implement the EABI system call convention for Linux. // Source: syscall(2) man page. args := []llvm.Value{} @@ -102,21 +102,21 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, "{r5}", "{r6}", }[i] - llvmValue := frame.getValue(arg) + llvmValue := b.getValue(arg) args = append(args, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } args = append(args, num) - argTypes = append(argTypes, c.uintptrType) + argTypes = append(argTypes, b.uintptrType) constraints += ",{r7}" // syscall number for i := len(call.Args) - 1; i < 4; i++ { // r0-r3 get clobbered after the syscall returns constraints += ",~{r" + strconv.Itoa(i) + "}" } - fnType := llvm.FunctionType(c.uintptrType, argTypes, false) + fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0) - syscallResult = c.builder.CreateCall(target, args, "") - case c.GOARCH() == "arm64" && c.GOOS() == "linux": + syscallResult = b.CreateCall(target, args, "") + case b.GOARCH() == "arm64" && b.GOOS() == "linux": // Source: syscall(2) man page. args := []llvm.Value{} argTypes := []llvm.Type{} @@ -132,12 +132,12 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, "{x4}", "{x5}", }[i] - llvmValue := frame.getValue(arg) + llvmValue := b.getValue(arg) args = append(args, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } args = append(args, num) - argTypes = append(argTypes, c.uintptrType) + argTypes = append(argTypes, b.uintptrType) constraints += ",{x8}" // syscall number for i := len(call.Args) - 1; i < 8; i++ { // x0-x7 may get clobbered during the syscall following the aarch64 @@ -145,13 +145,13 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, constraints += ",~{x" + strconv.Itoa(i) + "}" } constraints += ",~{x16},~{x17}" // scratch registers - fnType := llvm.FunctionType(c.uintptrType, argTypes, false) + fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0) - syscallResult = c.builder.CreateCall(target, args, "") + syscallResult = b.CreateCall(target, args, "") default: - return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH()) + return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH()) } - switch c.GOOS() { + switch b.GOOS() { case "linux", "freebsd": // Return values: r0, r1 uintptr, err Errno // Pseudocode: @@ -160,15 +160,15 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, // err = -syscallResult // } // return syscallResult, 0, err - zero := llvm.ConstInt(c.uintptrType, 0, false) - inrange1 := c.builder.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "") - inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096 - hasError := c.builder.CreateAnd(inrange1, inrange2, "") - errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError") - retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false)) - retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "") - retval = c.builder.CreateInsertValue(retval, zero, 1, "") - retval = c.builder.CreateInsertValue(retval, errResult, 2, "") + zero := llvm.ConstInt(b.uintptrType, 0, false) + inrange1 := b.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(b.uintptrType, 0, false), "") + inrange2 := b.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(b.uintptrType, 0xfffffffffffff000, true), "") // -4096 + hasError := b.CreateAnd(inrange1, inrange2, "") + errResult := b.CreateSelect(hasError, b.CreateSub(zero, syscallResult, ""), zero, "syscallError") + retval := llvm.Undef(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false)) + retval = b.CreateInsertValue(retval, syscallResult, 0, "") + retval = b.CreateInsertValue(retval, zero, 1, "") + retval = b.CreateInsertValue(retval, errResult, 2, "") return retval, nil case "darwin": // Return values: r0, r1 uintptr, err Errno @@ -178,15 +178,15 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, // err = syscallResult // } // return syscallResult, 0, err - zero := llvm.ConstInt(c.uintptrType, 0, false) - hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "") - errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError") - retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false)) - retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "") - retval = c.builder.CreateInsertValue(retval, zero, 1, "") - retval = c.builder.CreateInsertValue(retval, errResult, 2, "") + zero := llvm.ConstInt(b.uintptrType, 0, false) + hasError := b.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(b.uintptrType, 0, false), "") + errResult := b.CreateSelect(hasError, syscallResult, zero, "syscallError") + retval := llvm.Undef(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false)) + retval = b.CreateInsertValue(retval, syscallResult, 0, "") + retval = b.CreateInsertValue(retval, zero, 1, "") + retval = b.CreateInsertValue(retval, errResult, 2, "") return retval, nil default: - return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH()) + return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH()) } } diff --git a/compiler/volatile.go b/compiler/volatile.go index 237d554619..d2e51c84da 100644 --- a/compiler/volatile.go +++ b/compiler/volatile.go @@ -8,19 +8,23 @@ import ( "tinygo.org/x/go-llvm" ) -func (c *Compiler) emitVolatileLoad(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) { - addr := frame.getValue(instr.Args[0]) - frame.createNilCheck(addr, "deref") - val := c.builder.CreateLoad(addr, "") +// createVolatileLoad is the implementation of the intrinsic function +// runtime/volatile.LoadT(). +func (b *builder) createVolatileLoad(instr *ssa.CallCommon) (llvm.Value, error) { + addr := b.getValue(instr.Args[0]) + b.createNilCheck(addr, "deref") + val := b.CreateLoad(addr, "") val.SetVolatile(true) return val, nil } -func (c *Compiler) emitVolatileStore(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) { - addr := frame.getValue(instr.Args[0]) - val := frame.getValue(instr.Args[1]) - frame.createNilCheck(addr, "deref") - store := c.builder.CreateStore(val, addr) +// createVolatileStore is the implementation of the intrinsic function +// runtime/volatile.StoreT(). +func (b *builder) createVolatileStore(instr *ssa.CallCommon) (llvm.Value, error) { + addr := b.getValue(instr.Args[0]) + val := b.getValue(instr.Args[1]) + b.createNilCheck(addr, "deref") + store := b.CreateStore(val, addr) store.SetVolatile(true) return llvm.Value{}, nil } From 1915b3789698c53b8c40faeaa3f3b9141de8e0e8 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 23 Nov 2019 21:45:53 +0100 Subject: [PATCH 14/21] compiler: refactor parseExpr parseExpr (now createExpr) and all callers (recursively) are switched over to the new builder object! --- compiler/compiler.go | 369 ++++++++++++++++++++++--------------------- compiler/func.go | 10 +- 2 files changed, 190 insertions(+), 189 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 1807b8af5d..4a1d1ee971 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1026,7 +1026,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { switch instr := instr.(type) { case ssa.Value: - if value, err := c.parseExpr(frame, instr); err != nil { + if value, err := frame.createExpr(instr); err != nil { // This expression could not be parsed. Add the error to the list // of diagnostics and continue with an undef value. // The resulting IR will be incorrect (but valid). However, @@ -1432,41 +1432,42 @@ func (b *builder) getValue(expr ssa.Value) llvm.Value { } } -// parseExpr translates a Go SSA expression to a LLVM instruction. -func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { - if _, ok := frame.locals[expr]; ok { +// createExpr translates a Go SSA expression to LLVM IR. This can be zero, one, +// or multiple LLVM IR instructions and/or runtime calls. +func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { + if _, ok := b.locals[expr]; ok { // sanity check - panic("local has already been parsed: " + expr.String()) + panic("instruction has already been created: " + expr.String()) } switch expr := expr.(type) { case *ssa.Alloc: - typ := c.getLLVMType(expr.Type().Underlying().(*types.Pointer).Elem()) + typ := b.getLLVMType(expr.Type().Underlying().(*types.Pointer).Elem()) if expr.Heap { - size := c.targetData.TypeAllocSize(typ) + size := b.targetData.TypeAllocSize(typ) // Calculate ^uintptr(0) - maxSize := llvm.ConstNot(llvm.ConstInt(c.uintptrType, 0, false)).ZExtValue() + maxSize := llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)).ZExtValue() if size > maxSize { // Size would be truncated if truncated to uintptr. - return llvm.Value{}, c.makeError(expr.Pos(), fmt.Sprintf("value is too big (%v bytes)", size)) + return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("value is too big (%v bytes)", size)) } - sizeValue := llvm.ConstInt(c.uintptrType, size, false) - buf := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, expr.Comment) - buf = c.builder.CreateBitCast(buf, llvm.PointerType(typ, 0), "") + sizeValue := llvm.ConstInt(b.uintptrType, size, false) + buf := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, expr.Comment) + buf = b.CreateBitCast(buf, llvm.PointerType(typ, 0), "") return buf, nil } else { - buf := llvmutil.CreateEntryBlockAlloca(c.builder, typ, expr.Comment) - if c.targetData.TypeAllocSize(typ) != 0 { - c.builder.CreateStore(llvm.ConstNull(typ), buf) // zero-initialize var + buf := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, expr.Comment) + if b.targetData.TypeAllocSize(typ) != 0 { + b.CreateStore(llvm.ConstNull(typ), buf) // zero-initialize var } return buf, nil } case *ssa.BinOp: - x := frame.getValue(expr.X) - y := frame.getValue(expr.Y) - return frame.createBinOp(expr.Op, expr.X.Type(), x, y, expr.Pos()) + x := b.getValue(expr.X) + y := b.getValue(expr.Y) + return b.createBinOp(expr.Op, expr.X.Type(), x, y, expr.Pos()) case *ssa.Call: - return frame.createFunctionCall(expr.Common()) + return b.createFunctionCall(expr.Common()) case *ssa.ChangeInterface: // Do not change between interface types: always use the underlying // (concrete) type in the type number of the interface. Every method @@ -1474,13 +1475,13 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { // This is different from how the official Go compiler works, because of // heap allocation and because it's easier to implement, see: // https://research.swtch.com/interfaces - return frame.getValue(expr.X), nil + return b.getValue(expr.X), nil case *ssa.ChangeType: // This instruction changes the type, but the underlying value remains // the same. This is often a no-op, but sometimes we have to change the // LLVM type as well. - x := frame.getValue(expr.X) - llvmType := c.getLLVMType(expr.Type()) + x := b.getValue(expr.X) + llvmType := b.getLLVMType(expr.Type()) if x.Type() == llvmType { // Different Go type but same LLVM type (for example, named int). // This is the common case. @@ -1494,70 +1495,70 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { // values from the previous struct in there. value := llvm.Undef(llvmType) for i := 0; i < llvmType.StructElementTypesCount(); i++ { - field := c.builder.CreateExtractValue(x, i, "changetype.field") - value = c.builder.CreateInsertValue(value, field, i, "changetype.struct") + field := b.CreateExtractValue(x, i, "changetype.field") + value = b.CreateInsertValue(value, field, i, "changetype.struct") } return value, nil case llvm.PointerTypeKind: // This can happen with pointers to structs. This case is easy: // simply bitcast the pointer to the destination type. - return c.builder.CreateBitCast(x, llvmType, "changetype.pointer"), nil + return b.CreateBitCast(x, llvmType, "changetype.pointer"), nil default: return llvm.Value{}, errors.New("todo: unknown ChangeType type: " + expr.X.Type().String()) } case *ssa.Const: panic("const is not an expression") case *ssa.Convert: - x := frame.getValue(expr.X) - return frame.createConvert(expr.X.Type(), expr.Type(), x, expr.Pos()) + x := b.getValue(expr.X) + return b.createConvert(expr.X.Type(), expr.Type(), x, expr.Pos()) case *ssa.Extract: if _, ok := expr.Tuple.(*ssa.Select); ok { - return frame.getChanSelectResult(expr), nil + return b.getChanSelectResult(expr), nil } - value := frame.getValue(expr.Tuple) - return c.builder.CreateExtractValue(value, expr.Index, ""), nil + value := b.getValue(expr.Tuple) + return b.CreateExtractValue(value, expr.Index, ""), nil case *ssa.Field: - value := frame.getValue(expr.X) - result := c.builder.CreateExtractValue(value, expr.Field, "") + value := b.getValue(expr.X) + result := b.CreateExtractValue(value, expr.Field, "") return result, nil case *ssa.FieldAddr: - val := frame.getValue(expr.X) + val := b.getValue(expr.X) // Check for nil pointer before calculating the address, from the spec: // > For an operand x of type T, the address operation &x generates a // > pointer of type *T to x. [...] If the evaluation of x would cause a // > run-time panic, then the evaluation of &x does too. - frame.createNilCheck(val, "gep") + b.createNilCheck(val, "gep") // Do a GEP on the pointer to get the field address. indices := []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), uint64(expr.Field), false), + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), uint64(expr.Field), false), } - return c.builder.CreateInBoundsGEP(val, indices, ""), nil + return b.CreateInBoundsGEP(val, indices, ""), nil case *ssa.Function: panic("function is not an expression") case *ssa.Global: panic("global is not an expression") case *ssa.Index: - array := frame.getValue(expr.X) - index := frame.getValue(expr.Index) + array := b.getValue(expr.X) + index := b.getValue(expr.Index) // Check bounds. arrayLen := expr.X.Type().(*types.Array).Len() - arrayLenLLVM := llvm.ConstInt(c.uintptrType, uint64(arrayLen), false) - frame.createLookupBoundsCheck(arrayLenLLVM, index, expr.Index.Type()) + arrayLenLLVM := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false) + b.createLookupBoundsCheck(arrayLenLLVM, index, expr.Index.Type()) // Can't load directly from array (as index is non-constant), so have to // do it using an alloca+gep+load. - alloca, allocaPtr, allocaSize := c.createTemporaryAlloca(array.Type(), "index.alloca") - c.builder.CreateStore(array, alloca) - zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) - ptr := c.builder.CreateInBoundsGEP(alloca, []llvm.Value{zero, index}, "index.gep") - result := c.builder.CreateLoad(ptr, "index.load") - c.emitLifetimeEnd(allocaPtr, allocaSize) + alloca, allocaPtr, allocaSize := b.createTemporaryAlloca(array.Type(), "index.alloca") + b.CreateStore(array, alloca) + zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) + ptr := b.CreateInBoundsGEP(alloca, []llvm.Value{zero, index}, "index.gep") + result := b.CreateLoad(ptr, "index.load") + b.emitLifetimeEnd(allocaPtr, allocaSize) return result, nil case *ssa.IndexAddr: - val := frame.getValue(expr.X) - index := frame.getValue(expr.Index) + val := b.getValue(expr.X) + index := b.getValue(expr.Index) // Get buffer pointer and length var bufptr, buflen llvm.Value @@ -1567,42 +1568,42 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { switch typ := typ.(type) { case *types.Array: bufptr = val - buflen = llvm.ConstInt(c.uintptrType, uint64(typ.Len()), false) + buflen = llvm.ConstInt(b.uintptrType, uint64(typ.Len()), false) // Check for nil pointer before calculating the address, from // the spec: // > For an operand x of type T, the address operation &x // > generates a pointer of type *T to x. [...] If the // > evaluation of x would cause a run-time panic, then the // > evaluation of &x does too. - frame.createNilCheck(bufptr, "gep") + b.createNilCheck(bufptr, "gep") default: - return llvm.Value{}, c.makeError(expr.Pos(), "todo: indexaddr: "+typ.String()) + return llvm.Value{}, b.makeError(expr.Pos(), "todo: indexaddr: "+typ.String()) } case *types.Slice: - bufptr = c.builder.CreateExtractValue(val, 0, "indexaddr.ptr") - buflen = c.builder.CreateExtractValue(val, 1, "indexaddr.len") + bufptr = b.CreateExtractValue(val, 0, "indexaddr.ptr") + buflen = b.CreateExtractValue(val, 1, "indexaddr.len") default: - return llvm.Value{}, c.makeError(expr.Pos(), "todo: indexaddr: "+ptrTyp.String()) + return llvm.Value{}, b.makeError(expr.Pos(), "todo: indexaddr: "+ptrTyp.String()) } // Bounds check. - frame.createLookupBoundsCheck(buflen, index, expr.Index.Type()) + b.createLookupBoundsCheck(buflen, index, expr.Index.Type()) switch expr.X.Type().Underlying().(type) { case *types.Pointer: indices := []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), 0, false), index, } - return c.builder.CreateInBoundsGEP(bufptr, indices, ""), nil + return b.CreateInBoundsGEP(bufptr, indices, ""), nil case *types.Slice: - return c.builder.CreateInBoundsGEP(bufptr, []llvm.Value{index}, ""), nil + return b.CreateInBoundsGEP(bufptr, []llvm.Value{index}, ""), nil default: panic("unreachable") } case *ssa.Lookup: - value := frame.getValue(expr.X) - index := frame.getValue(expr.Index) + value := b.getValue(expr.X) + index := b.getValue(expr.Index) switch xType := expr.X.Type().Underlying().(type) { case *types.Basic: // Value type must be a string, which is a basic type. @@ -1611,153 +1612,153 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { } // Bounds check. - length := c.builder.CreateExtractValue(value, 1, "len") - frame.createLookupBoundsCheck(length, index, expr.Index.Type()) + length := b.CreateExtractValue(value, 1, "len") + b.createLookupBoundsCheck(length, index, expr.Index.Type()) // Lookup byte - buf := c.builder.CreateExtractValue(value, 0, "") - bufPtr := c.builder.CreateInBoundsGEP(buf, []llvm.Value{index}, "") - return c.builder.CreateLoad(bufPtr, ""), nil + buf := b.CreateExtractValue(value, 0, "") + bufPtr := b.CreateInBoundsGEP(buf, []llvm.Value{index}, "") + return b.CreateLoad(bufPtr, ""), nil case *types.Map: valueType := expr.Type() if expr.CommaOk { valueType = valueType.(*types.Tuple).At(0).Type() } - return frame.createMapLookup(xType.Key(), valueType, value, index, expr.CommaOk, expr.Pos()) + return b.createMapLookup(xType.Key(), valueType, value, index, expr.CommaOk, expr.Pos()) default: panic("unknown lookup type: " + expr.String()) } case *ssa.MakeChan: - return frame.createMakeChan(expr), nil + return b.createMakeChan(expr), nil case *ssa.MakeClosure: - return c.parseMakeClosure(frame, expr) + return b.parseMakeClosure(expr) case *ssa.MakeInterface: - val := frame.getValue(expr.X) - return frame.createMakeInterface(val, expr.X.Type(), expr.Pos()), nil + val := b.getValue(expr.X) + return b.createMakeInterface(val, expr.X.Type(), expr.Pos()), nil case *ssa.MakeMap: - return frame.createMakeMap(expr) + return b.createMakeMap(expr) case *ssa.MakeSlice: - sliceLen := frame.getValue(expr.Len) - sliceCap := frame.getValue(expr.Cap) + sliceLen := b.getValue(expr.Len) + sliceCap := b.getValue(expr.Cap) sliceType := expr.Type().Underlying().(*types.Slice) - llvmElemType := c.getLLVMType(sliceType.Elem()) - elemSize := c.targetData.TypeAllocSize(llvmElemType) - elemSizeValue := llvm.ConstInt(c.uintptrType, elemSize, false) + llvmElemType := b.getLLVMType(sliceType.Elem()) + elemSize := b.targetData.TypeAllocSize(llvmElemType) + elemSizeValue := llvm.ConstInt(b.uintptrType, elemSize, false) // Calculate (^uintptr(0)) >> 1, which is the max value that fits in // uintptr if uintptr were signed. - maxSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(c.uintptrType, 0, false)), llvm.ConstInt(c.uintptrType, 1, false)) + maxSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false)) if elemSize > maxSize.ZExtValue() { // This seems to be checked by the typechecker already, but let's // check it again just to be sure. - return llvm.Value{}, c.makeError(expr.Pos(), fmt.Sprintf("slice element type is too big (%v bytes)", elemSize)) + return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("slice element type is too big (%v bytes)", elemSize)) } // Bounds checking. lenType := expr.Len.Type().(*types.Basic) capType := expr.Cap.Type().(*types.Basic) - frame.createSliceBoundsCheck(maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType) + b.createSliceBoundsCheck(maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType) // Allocate the backing array. - sliceCapCast, err := frame.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) + sliceCapCast, err := b.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) if err != nil { return llvm.Value{}, err } - sliceSize := c.builder.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap") - slicePtr := c.createRuntimeCall("alloc", []llvm.Value{sliceSize}, "makeslice.buf") - slicePtr = c.builder.CreateBitCast(slicePtr, llvm.PointerType(llvmElemType, 0), "makeslice.array") + sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap") + slicePtr := b.createRuntimeCall("alloc", []llvm.Value{sliceSize}, "makeslice.buf") + slicePtr = b.CreateBitCast(slicePtr, llvm.PointerType(llvmElemType, 0), "makeslice.array") // Extend or truncate if necessary. This is safe as we've already done // the bounds check. - sliceLen, err = frame.createConvert(expr.Len.Type(), types.Typ[types.Uintptr], sliceLen, expr.Pos()) + sliceLen, err = b.createConvert(expr.Len.Type(), types.Typ[types.Uintptr], sliceLen, expr.Pos()) if err != nil { return llvm.Value{}, err } - sliceCap, err = frame.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) + sliceCap, err = b.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) if err != nil { return llvm.Value{}, err } // Create the slice. - slice := c.ctx.ConstStruct([]llvm.Value{ + slice := b.ctx.ConstStruct([]llvm.Value{ llvm.Undef(slicePtr.Type()), - llvm.Undef(c.uintptrType), - llvm.Undef(c.uintptrType), + llvm.Undef(b.uintptrType), + llvm.Undef(b.uintptrType), }, false) - slice = c.builder.CreateInsertValue(slice, slicePtr, 0, "") - slice = c.builder.CreateInsertValue(slice, sliceLen, 1, "") - slice = c.builder.CreateInsertValue(slice, sliceCap, 2, "") + slice = b.CreateInsertValue(slice, slicePtr, 0, "") + slice = b.CreateInsertValue(slice, sliceLen, 1, "") + slice = b.CreateInsertValue(slice, sliceCap, 2, "") return slice, nil case *ssa.Next: rangeVal := expr.Iter.(*ssa.Range).X - llvmRangeVal := frame.getValue(rangeVal) - it := frame.getValue(expr.Iter) + llvmRangeVal := b.getValue(rangeVal) + it := b.getValue(expr.Iter) if expr.IsString { - return c.createRuntimeCall("stringNext", []llvm.Value{llvmRangeVal, it}, "range.next"), nil + return b.createRuntimeCall("stringNext", []llvm.Value{llvmRangeVal, it}, "range.next"), nil } else { // map - llvmKeyType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Key()) - llvmValueType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Elem()) - - mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(llvmKeyType, "range.key") - mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "range.value") - ok := c.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyPtr, mapValuePtr}, "range.next") - - tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{c.ctx.Int1Type(), llvmKeyType, llvmValueType}, false)) - tuple = c.builder.CreateInsertValue(tuple, ok, 0, "") - tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapKeyAlloca, ""), 1, "") - tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapValueAlloca, ""), 2, "") - c.emitLifetimeEnd(mapKeyPtr, mapKeySize) - c.emitLifetimeEnd(mapValuePtr, mapValueSize) + llvmKeyType := b.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Key()) + llvmValueType := b.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Elem()) + + mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(llvmKeyType, "range.key") + mapValueAlloca, mapValuePtr, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value") + ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyPtr, mapValuePtr}, "range.next") + + tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.Int1Type(), llvmKeyType, llvmValueType}, false)) + tuple = b.CreateInsertValue(tuple, ok, 0, "") + tuple = b.CreateInsertValue(tuple, b.CreateLoad(mapKeyAlloca, ""), 1, "") + tuple = b.CreateInsertValue(tuple, b.CreateLoad(mapValueAlloca, ""), 2, "") + b.emitLifetimeEnd(mapKeyPtr, mapKeySize) + b.emitLifetimeEnd(mapValuePtr, mapValueSize) return tuple, nil } case *ssa.Phi: - phi := c.builder.CreatePHI(c.getLLVMType(expr.Type()), "") - frame.phis = append(frame.phis, Phi{expr, phi}) + phi := b.CreatePHI(b.getLLVMType(expr.Type()), "") + b.phis = append(b.phis, Phi{expr, phi}) return phi, nil case *ssa.Range: var iteratorType llvm.Type switch typ := expr.X.Type().Underlying().(type) { case *types.Basic: // string - iteratorType = c.getLLVMRuntimeType("stringIterator") + iteratorType = b.getLLVMRuntimeType("stringIterator") case *types.Map: - iteratorType = c.getLLVMRuntimeType("hashmapIterator") + iteratorType = b.getLLVMRuntimeType("hashmapIterator") default: panic("unknown type in range: " + typ.String()) } - it, _, _ := c.createTemporaryAlloca(iteratorType, "range.it") - c.builder.CreateStore(llvm.ConstNull(iteratorType), it) + it, _, _ := b.createTemporaryAlloca(iteratorType, "range.it") + b.CreateStore(llvm.ConstNull(iteratorType), it) return it, nil case *ssa.Select: - return frame.createSelect(expr), nil + return b.createSelect(expr), nil case *ssa.Slice: - value := frame.getValue(expr.X) + value := b.getValue(expr.X) var lowType, highType, maxType *types.Basic var low, high, max llvm.Value if expr.Low != nil { lowType = expr.Low.Type().Underlying().(*types.Basic) - low = frame.getValue(expr.Low) - if low.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() { + low = b.getValue(expr.Low) + if low.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() { if lowType.Info()&types.IsUnsigned != 0 { - low = c.builder.CreateZExt(low, c.uintptrType, "") + low = b.CreateZExt(low, b.uintptrType, "") } else { - low = c.builder.CreateSExt(low, c.uintptrType, "") + low = b.CreateSExt(low, b.uintptrType, "") } } } else { lowType = types.Typ[types.Uintptr] - low = llvm.ConstInt(c.uintptrType, 0, false) + low = llvm.ConstInt(b.uintptrType, 0, false) } if expr.High != nil { highType = expr.High.Type().Underlying().(*types.Basic) - high = frame.getValue(expr.High) - if high.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() { + high = b.getValue(expr.High) + if high.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() { if highType.Info()&types.IsUnsigned != 0 { - high = c.builder.CreateZExt(high, c.uintptrType, "") + high = b.CreateZExt(high, b.uintptrType, "") } else { - high = c.builder.CreateSExt(high, c.uintptrType, "") + high = b.CreateSExt(high, b.uintptrType, "") } } } else { @@ -1766,12 +1767,12 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { if expr.Max != nil { maxType = expr.Max.Type().Underlying().(*types.Basic) - max = frame.getValue(expr.Max) - if max.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() { + max = b.getValue(expr.Max) + if max.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() { if maxType.Info()&types.IsUnsigned != 0 { - max = c.builder.CreateZExt(max, c.uintptrType, "") + max = b.CreateZExt(max, b.uintptrType, "") } else { - max = c.builder.CreateSExt(max, c.uintptrType, "") + max = b.CreateSExt(max, b.uintptrType, "") } } } else { @@ -1782,7 +1783,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { case *types.Pointer: // pointer to array // slice an array length := typ.Elem().Underlying().(*types.Array).Len() - llvmLen := llvm.ConstInt(c.uintptrType, uint64(length), false) + llvmLen := llvm.ConstInt(b.uintptrType, uint64(length), false) if high.IsNil() { high = llvmLen } @@ -1790,43 +1791,43 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { max = llvmLen } indices := []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), 0, false), low, } - frame.createSliceBoundsCheck(llvmLen, low, high, max, lowType, highType, maxType) + b.createSliceBoundsCheck(llvmLen, low, high, max, lowType, highType, maxType) // Truncate ints bigger than uintptr. This is after the bounds // check so it's safe. - if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { - low = c.builder.CreateTrunc(low, c.uintptrType, "") + if b.targetData.TypeAllocSize(low.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { + low = b.CreateTrunc(low, b.uintptrType, "") } - if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { - high = c.builder.CreateTrunc(high, c.uintptrType, "") + if b.targetData.TypeAllocSize(high.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { + high = b.CreateTrunc(high, b.uintptrType, "") } - if c.targetData.TypeAllocSize(max.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { - max = c.builder.CreateTrunc(max, c.uintptrType, "") + if b.targetData.TypeAllocSize(max.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { + max = b.CreateTrunc(max, b.uintptrType, "") } - sliceLen := c.builder.CreateSub(high, low, "slice.len") - slicePtr := c.builder.CreateInBoundsGEP(value, indices, "slice.ptr") - sliceCap := c.builder.CreateSub(max, low, "slice.cap") + sliceLen := b.CreateSub(high, low, "slice.len") + slicePtr := b.CreateInBoundsGEP(value, indices, "slice.ptr") + sliceCap := b.CreateSub(max, low, "slice.cap") - slice := c.ctx.ConstStruct([]llvm.Value{ + slice := b.ctx.ConstStruct([]llvm.Value{ llvm.Undef(slicePtr.Type()), - llvm.Undef(c.uintptrType), - llvm.Undef(c.uintptrType), + llvm.Undef(b.uintptrType), + llvm.Undef(b.uintptrType), }, false) - slice = c.builder.CreateInsertValue(slice, slicePtr, 0, "") - slice = c.builder.CreateInsertValue(slice, sliceLen, 1, "") - slice = c.builder.CreateInsertValue(slice, sliceCap, 2, "") + slice = b.CreateInsertValue(slice, slicePtr, 0, "") + slice = b.CreateInsertValue(slice, sliceLen, 1, "") + slice = b.CreateInsertValue(slice, sliceCap, 2, "") return slice, nil case *types.Slice: // slice a slice - oldPtr := c.builder.CreateExtractValue(value, 0, "") - oldLen := c.builder.CreateExtractValue(value, 1, "") - oldCap := c.builder.CreateExtractValue(value, 2, "") + oldPtr := b.CreateExtractValue(value, 0, "") + oldLen := b.CreateExtractValue(value, 1, "") + oldCap := b.CreateExtractValue(value, 2, "") if high.IsNil() { high = oldLen } @@ -1834,76 +1835,76 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { max = oldCap } - frame.createSliceBoundsCheck(oldCap, low, high, max, lowType, highType, maxType) + b.createSliceBoundsCheck(oldCap, low, high, max, lowType, highType, maxType) // Truncate ints bigger than uintptr. This is after the bounds // check so it's safe. - if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { - low = c.builder.CreateTrunc(low, c.uintptrType, "") + if b.targetData.TypeAllocSize(low.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { + low = b.CreateTrunc(low, b.uintptrType, "") } - if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { - high = c.builder.CreateTrunc(high, c.uintptrType, "") + if b.targetData.TypeAllocSize(high.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { + high = b.CreateTrunc(high, b.uintptrType, "") } - if c.targetData.TypeAllocSize(max.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { - max = c.builder.CreateTrunc(max, c.uintptrType, "") + if b.targetData.TypeAllocSize(max.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { + max = b.CreateTrunc(max, b.uintptrType, "") } - newPtr := c.builder.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "") - newLen := c.builder.CreateSub(high, low, "") - newCap := c.builder.CreateSub(max, low, "") - slice := c.ctx.ConstStruct([]llvm.Value{ + newPtr := b.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "") + newLen := b.CreateSub(high, low, "") + newCap := b.CreateSub(max, low, "") + slice := b.ctx.ConstStruct([]llvm.Value{ llvm.Undef(newPtr.Type()), - llvm.Undef(c.uintptrType), - llvm.Undef(c.uintptrType), + llvm.Undef(b.uintptrType), + llvm.Undef(b.uintptrType), }, false) - slice = c.builder.CreateInsertValue(slice, newPtr, 0, "") - slice = c.builder.CreateInsertValue(slice, newLen, 1, "") - slice = c.builder.CreateInsertValue(slice, newCap, 2, "") + slice = b.CreateInsertValue(slice, newPtr, 0, "") + slice = b.CreateInsertValue(slice, newLen, 1, "") + slice = b.CreateInsertValue(slice, newCap, 2, "") return slice, nil case *types.Basic: if typ.Info()&types.IsString == 0 { - return llvm.Value{}, c.makeError(expr.Pos(), "unknown slice type: "+typ.String()) + return llvm.Value{}, b.makeError(expr.Pos(), "unknown slice type: "+typ.String()) } // slice a string if expr.Max != nil { // This might as well be a panic, as the frontend should have // handled this already. - return llvm.Value{}, c.makeError(expr.Pos(), "slicing a string with a max parameter is not allowed by the spec") + return llvm.Value{}, b.makeError(expr.Pos(), "slicing a string with a max parameter is not allowed by the spec") } - oldPtr := c.builder.CreateExtractValue(value, 0, "") - oldLen := c.builder.CreateExtractValue(value, 1, "") + oldPtr := b.CreateExtractValue(value, 0, "") + oldLen := b.CreateExtractValue(value, 1, "") if high.IsNil() { high = oldLen } - frame.createSliceBoundsCheck(oldLen, low, high, high, lowType, highType, maxType) + b.createSliceBoundsCheck(oldLen, low, high, high, lowType, highType, maxType) // Truncate ints bigger than uintptr. This is after the bounds // check so it's safe. - if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { - low = c.builder.CreateTrunc(low, c.uintptrType, "") + if b.targetData.TypeAllocSize(low.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { + low = b.CreateTrunc(low, b.uintptrType, "") } - if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { - high = c.builder.CreateTrunc(high, c.uintptrType, "") + if b.targetData.TypeAllocSize(high.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { + high = b.CreateTrunc(high, b.uintptrType, "") } - newPtr := c.builder.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "") - newLen := c.builder.CreateSub(high, low, "") - str := llvm.Undef(c.getLLVMRuntimeType("_string")) - str = c.builder.CreateInsertValue(str, newPtr, 0, "") - str = c.builder.CreateInsertValue(str, newLen, 1, "") + newPtr := b.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "") + newLen := b.CreateSub(high, low, "") + str := llvm.Undef(b.getLLVMRuntimeType("_string")) + str = b.CreateInsertValue(str, newPtr, 0, "") + str = b.CreateInsertValue(str, newLen, 1, "") return str, nil default: - return llvm.Value{}, c.makeError(expr.Pos(), "unknown slice type: "+typ.String()) + return llvm.Value{}, b.makeError(expr.Pos(), "unknown slice type: "+typ.String()) } case *ssa.TypeAssert: - return frame.createTypeAssert(expr), nil + return b.createTypeAssert(expr), nil case *ssa.UnOp: - return frame.createUnOp(expr) + return b.createUnOp(expr) default: - return llvm.Value{}, c.makeError(expr.Pos(), "todo: unknown expression: "+expr.String()) + return llvm.Value{}, b.makeError(expr.Pos(), "todo: unknown expression: "+expr.String()) } } diff --git a/compiler/func.go b/compiler/func.go index 9632e7125c..c52f9e09ef 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -153,24 +153,24 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type { // parseMakeClosure makes a function value (with context) from the given // closure expression. -func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.Value, error) { +func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) { if len(expr.Bindings) == 0 { panic("unexpected: MakeClosure without bound variables") } - f := c.ir.GetFunction(expr.Fn.(*ssa.Function)) + f := b.ir.GetFunction(expr.Fn.(*ssa.Function)) // Collect all bound variables. boundVars := make([]llvm.Value, len(expr.Bindings)) for i, binding := range expr.Bindings { // The context stores the bound variables. - llvmBoundVar := frame.getValue(binding) + llvmBoundVar := b.getValue(binding) boundVars[i] = llvmBoundVar } // Store the bound variables in a single object, allocating it on the heap // if necessary. - context := c.emitPointerPack(boundVars) + context := b.emitPointerPack(boundVars) // Create the closure. - return c.createFuncValue(f.LLVMFn, context, f.Signature), nil + return b.createFuncValue(f.LLVMFn, context, f.Signature), nil } From e0c93d15690c9bf62eca746290908b10140d2ca7 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 24 Nov 2019 12:43:43 +0100 Subject: [PATCH 15/21] compiler: refactor defer operations --- compiler/compiler.go | 6 +- compiler/defer.go | 216 +++++++++++++++++++++---------------------- compiler/gc.go | 9 ++ 3 files changed, 120 insertions(+), 111 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 4a1d1ee971..dbf59ad811 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -958,7 +958,7 @@ func (c *Compiler) parseFunc(frame *Frame) { if frame.fn.Recover != nil { // This function has deferred function calls. Set some things up for // them. - c.deferInitFunc(frame) + frame.deferInitFunc() } // Fill blocks with instructions. @@ -1044,7 +1044,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { case *ssa.DebugRef: // ignore case *ssa.Defer: - c.emitDefer(frame, instr) + frame.createDefer(instr) case *ssa.Go: // Get all function parameters to pass to the goroutine. var params []llvm.Value @@ -1127,7 +1127,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { c.builder.CreateRet(retVal) } case *ssa.RunDefers: - c.emitRunDefers(frame) + frame.createRunDefers() case *ssa.Send: frame.createChanSend(instr) case *ssa.Store: diff --git a/compiler/defer.go b/compiler/defer.go index 03d576181b..fa7d7623b5 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -23,16 +23,16 @@ import ( // deferInitFunc sets up this function for future deferred calls. It must be // called from within the entry block when this function contains deferred // calls. -func (c *Compiler) deferInitFunc(frame *Frame) { +func (b *builder) deferInitFunc() { // Some setup. - frame.deferFuncs = make(map[*ir.Function]int) - frame.deferInvokeFuncs = make(map[string]int) - frame.deferClosureFuncs = make(map[*ir.Function]int) + b.deferFuncs = make(map[*ir.Function]int) + b.deferInvokeFuncs = make(map[string]int) + b.deferClosureFuncs = make(map[*ir.Function]int) // Create defer list pointer. - deferType := llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0) - frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr") - c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr) + deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0) + b.deferPtr = b.CreateAlloca(deferType, "deferPtr") + b.CreateStore(llvm.ConstPointerNull(deferType), b.deferPtr) } // isInLoop checks if there is a path from a basic block to itself. @@ -69,53 +69,53 @@ func isInLoop(start *ssa.BasicBlock) bool { return false } -// emitDefer emits a single defer instruction, to be run when this function +// createDefer emits a single defer instruction, to be run when this function // returns. -func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) { +func (b *builder) createDefer(instr *ssa.Defer) { // The pointer to the previous defer struct, which we will replace to // make a linked list. - next := c.builder.CreateLoad(frame.deferPtr, "defer.next") + next := b.CreateLoad(b.deferPtr, "defer.next") var values []llvm.Value - valueTypes := []llvm.Type{c.uintptrType, next.Type()} + valueTypes := []llvm.Type{b.uintptrType, next.Type()} if instr.Call.IsInvoke() { // Method call on an interface. // Get callback type number. methodName := instr.Call.Method.FullName() - if _, ok := frame.deferInvokeFuncs[methodName]; !ok { - frame.deferInvokeFuncs[methodName] = len(frame.allDeferFuncs) - frame.allDeferFuncs = append(frame.allDeferFuncs, &instr.Call) + if _, ok := b.deferInvokeFuncs[methodName]; !ok { + b.deferInvokeFuncs[methodName] = len(b.allDeferFuncs) + b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call) } - callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferInvokeFuncs[methodName]), false) + callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[methodName]), false) // Collect all values to be put in the struct (starting with // runtime._defer fields, followed by the call parameters). - itf := frame.getValue(instr.Call.Value) // interface - receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver") + itf := b.getValue(instr.Call.Value) // interface + receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver") values = []llvm.Value{callback, next, receiverValue} - valueTypes = append(valueTypes, c.i8ptrType) + valueTypes = append(valueTypes, b.i8ptrType) for _, arg := range instr.Call.Args { - val := frame.getValue(arg) + val := b.getValue(arg) values = append(values, val) valueTypes = append(valueTypes, val.Type()) } } else if callee, ok := instr.Call.Value.(*ssa.Function); ok { // Regular function call. - fn := c.ir.GetFunction(callee) + fn := b.ir.GetFunction(callee) - if _, ok := frame.deferFuncs[fn]; !ok { - frame.deferFuncs[fn] = len(frame.allDeferFuncs) - frame.allDeferFuncs = append(frame.allDeferFuncs, fn) + if _, ok := b.deferFuncs[fn]; !ok { + b.deferFuncs[fn] = len(b.allDeferFuncs) + b.allDeferFuncs = append(b.allDeferFuncs, fn) } - callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferFuncs[fn]), false) + callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[fn]), false) // Collect all values to be put in the struct (starting with // runtime._defer fields). values = []llvm.Value{callback, next} for _, param := range instr.Call.Args { - llvmParam := frame.getValue(param) + llvmParam := b.getValue(param) values = append(values, llvmParam) valueTypes = append(valueTypes, llvmParam.Type()) } @@ -127,23 +127,23 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) { // pointer. // TODO: ignore this closure entirely and put pointers to the free // variables directly in the defer struct, avoiding a memory allocation. - closure := frame.getValue(instr.Call.Value) - context := c.builder.CreateExtractValue(closure, 0, "") + closure := b.getValue(instr.Call.Value) + context := b.CreateExtractValue(closure, 0, "") // Get the callback number. - fn := c.ir.GetFunction(makeClosure.Fn.(*ssa.Function)) - if _, ok := frame.deferClosureFuncs[fn]; !ok { - frame.deferClosureFuncs[fn] = len(frame.allDeferFuncs) - frame.allDeferFuncs = append(frame.allDeferFuncs, makeClosure) + fn := b.ir.GetFunction(makeClosure.Fn.(*ssa.Function)) + if _, ok := b.deferClosureFuncs[fn]; !ok { + b.deferClosureFuncs[fn] = len(b.allDeferFuncs) + b.allDeferFuncs = append(b.allDeferFuncs, makeClosure) } - callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferClosureFuncs[fn]), false) + callback := llvm.ConstInt(b.uintptrType, uint64(b.deferClosureFuncs[fn]), false) // Collect all values to be put in the struct (starting with // runtime._defer fields, followed by all parameters including the // context pointer). values = []llvm.Value{callback, next} for _, param := range instr.Call.Args { - llvmParam := frame.getValue(param) + llvmParam := b.getValue(param) values = append(values, llvmParam) valueTypes = append(valueTypes, llvmParam.Type()) } @@ -151,41 +151,41 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) { valueTypes = append(valueTypes, context.Type()) } else { - c.addError(instr.Pos(), "todo: defer on uncommon function call type") + b.addError(instr.Pos(), "todo: defer on uncommon function call type") return } // Make a struct out of the collected values to put in the defer frame. - deferFrameType := c.ctx.StructType(valueTypes, false) + deferFrameType := b.ctx.StructType(valueTypes, false) deferFrame := llvm.ConstNull(deferFrameType) for i, value := range values { - deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "") + deferFrame = b.CreateInsertValue(deferFrame, value, i, "") } // Put this struct in an allocation. var alloca llvm.Value if !isInLoop(instr.Block()) { // This can safely use a stack allocation. - alloca = llvmutil.CreateEntryBlockAlloca(c.builder, deferFrameType, "defer.alloca") + alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferFrameType, "defer.alloca") } else { // This may be hit a variable number of times, so use a heap allocation. - size := c.targetData.TypeAllocSize(deferFrameType) - sizeValue := llvm.ConstInt(c.uintptrType, size, false) - allocCall := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call") - alloca = c.builder.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc") + size := b.targetData.TypeAllocSize(deferFrameType) + sizeValue := llvm.ConstInt(b.uintptrType, size, false) + allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call") + alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc") } - if c.NeedsStackObjects() { - c.trackPointer(alloca) + if b.NeedsStackObjects() { + b.trackPointer(alloca) } - c.builder.CreateStore(deferFrame, alloca) + b.CreateStore(deferFrame, alloca) // Push it on top of the linked list by replacing deferPtr. - allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast") - c.builder.CreateStore(allocaCast, frame.deferPtr) + allocaCast := b.CreateBitCast(alloca, next.Type(), "defer.alloca.cast") + b.CreateStore(allocaCast, b.deferPtr) } -// emitRunDefers emits code to run all deferred functions. -func (c *Compiler) emitRunDefers(frame *Frame) { +// createRunDefers emits code to run all deferred functions. +func (b *builder) createRunDefers() { // Add a loop like the following: // for stack != nil { // _stack := stack @@ -202,44 +202,44 @@ func (c *Compiler) emitRunDefers(frame *Frame) { // } // Create loop. - loophead := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead") - loop := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop") - unreachable := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default") - end := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end") - c.builder.CreateBr(loophead) + loophead := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loophead") + loop := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loop") + unreachable := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.default") + end := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.end") + b.CreateBr(loophead) // Create loop head: // for stack != nil { - c.builder.SetInsertPointAtEnd(loophead) - deferData := c.builder.CreateLoad(frame.deferPtr, "") - stackIsNil := c.builder.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil") - c.builder.CreateCondBr(stackIsNil, end, loop) + b.SetInsertPointAtEnd(loophead) + deferData := b.CreateLoad(b.deferPtr, "") + stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil") + b.CreateCondBr(stackIsNil, end, loop) // Create loop body: // _stack := stack // stack = stack.next // switch stack.callback { - c.builder.SetInsertPointAtEnd(loop) - nextStackGEP := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), 1, false), // .next field + b.SetInsertPointAtEnd(loop) + nextStackGEP := b.CreateInBoundsGEP(deferData, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field }, "stack.next.gep") - nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next") - c.builder.CreateStore(nextStack, frame.deferPtr) - gep := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), 0, false), // .callback field + nextStack := b.CreateLoad(nextStackGEP, "stack.next") + b.CreateStore(nextStack, b.deferPtr) + gep := b.CreateInBoundsGEP(deferData, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), 0, false), // .callback field }, "callback.gep") - callback := c.builder.CreateLoad(gep, "callback") - sw := c.builder.CreateSwitch(callback, unreachable, len(frame.allDeferFuncs)) + callback := b.CreateLoad(gep, "callback") + sw := b.CreateSwitch(callback, unreachable, len(b.allDeferFuncs)) - for i, callback := range frame.allDeferFuncs { + for i, callback := range b.allDeferFuncs { // Create switch case, for example: // case 0: // // run first deferred call - block := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback") - sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block) - c.builder.SetInsertPointAtEnd(block) + block := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.callback") + sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block) + b.SetInsertPointAtEnd(block) switch callback := callback.(type) { case *ssa.CallCommon: // Call on an interface value. @@ -248,50 +248,50 @@ func (c *Compiler) emitRunDefers(frame *Frame) { } // Get the real defer struct type and cast to it. - valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0), c.i8ptrType} + valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0), b.i8ptrType} for _, arg := range callback.Args { - valueTypes = append(valueTypes, c.getLLVMType(arg.Type())) + valueTypes = append(valueTypes, b.getLLVMType(arg.Type())) } - deferFrameType := c.ctx.StructType(valueTypes, false) - deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame") + deferFrameType := b.ctx.StructType(valueTypes, false) + deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame") // Extract the params from the struct (including receiver). forwardParams := []llvm.Value{} - zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) + zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) for i := 2; i < len(valueTypes); i++ { - gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep") - forwardParam := c.builder.CreateLoad(gep, "param") + gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep") + forwardParam := b.CreateLoad(gep, "param") forwardParams = append(forwardParams, forwardParam) } // Add the context parameter. An interface call cannot also be a // closure but we have to supply the parameter anyway for platforms // with a strict calling convention. - forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType)) + forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) // Parent coroutine handle. - forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType)) + forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) - fnPtr, _ := frame.getInvokeCall(callback) - c.createCall(fnPtr, forwardParams, "") + fnPtr, _ := b.getInvokeCall(callback) + b.createCall(fnPtr, forwardParams, "") case *ir.Function: // Direct call. // Get the real defer struct type and cast to it. - valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)} + valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)} for _, param := range callback.Params { - valueTypes = append(valueTypes, c.getLLVMType(param.Type())) + valueTypes = append(valueTypes, b.getLLVMType(param.Type())) } - deferFrameType := c.ctx.StructType(valueTypes, false) - deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame") + deferFrameType := b.ctx.StructType(valueTypes, false) + deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame") // Extract the params from the struct. forwardParams := []llvm.Value{} - zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) + zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) for i := range callback.Params { - gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep") - forwardParam := c.builder.CreateLoad(gep, "param") + gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") + forwardParam := b.CreateLoad(gep, "param") forwardParams = append(forwardParams, forwardParam) } @@ -300,57 +300,57 @@ func (c *Compiler) emitRunDefers(frame *Frame) { if !callback.IsExported() { // Add the context parameter. We know it is ignored by the receiving // function, but we have to pass one anyway. - forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType)) + forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) // Parent coroutine handle. - forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType)) + forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) } // Call real function. - c.createCall(callback.LLVMFn, forwardParams, "") + b.createCall(callback.LLVMFn, forwardParams, "") case *ssa.MakeClosure: // Get the real defer struct type and cast to it. - fn := c.ir.GetFunction(callback.Fn.(*ssa.Function)) - valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)} + fn := b.ir.GetFunction(callback.Fn.(*ssa.Function)) + valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)} params := fn.Signature.Params() for i := 0; i < params.Len(); i++ { - valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type())) + valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type())) } - valueTypes = append(valueTypes, c.i8ptrType) // closure - deferFrameType := c.ctx.StructType(valueTypes, false) - deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame") + valueTypes = append(valueTypes, b.i8ptrType) // closure + deferFrameType := b.ctx.StructType(valueTypes, false) + deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame") // Extract the params from the struct. forwardParams := []llvm.Value{} - zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) + zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) for i := 2; i < len(valueTypes); i++ { - gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "") - forwardParam := c.builder.CreateLoad(gep, "param") + gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "") + forwardParam := b.CreateLoad(gep, "param") forwardParams = append(forwardParams, forwardParam) } // Parent coroutine handle. - forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType)) + forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) // Call deferred function. - c.createCall(fn.LLVMFn, forwardParams, "") + b.createCall(fn.LLVMFn, forwardParams, "") default: panic("unknown deferred function type") } // Branch back to the start of the loop. - c.builder.CreateBr(loophead) + b.CreateBr(loophead) } // Create default unreachable block: // default: // unreachable // } - c.builder.SetInsertPointAtEnd(unreachable) - c.builder.CreateUnreachable() + b.SetInsertPointAtEnd(unreachable) + b.CreateUnreachable() // End of loop. - c.builder.SetInsertPointAtEnd(end) + b.SetInsertPointAtEnd(end) } diff --git a/compiler/gc.go b/compiler/gc.go index 0992c01d57..bd8b964fee 100644 --- a/compiler/gc.go +++ b/compiler/gc.go @@ -81,6 +81,15 @@ func (c *Compiler) trackPointer(value llvm.Value) { c.createRuntimeCall("trackPointer", []llvm.Value{value}, "") } +// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner +// first if needed. The input value must be of LLVM pointer type. +func (b *builder) trackPointer(value llvm.Value) { + if value.Type() != b.i8ptrType { + value = b.CreateBitCast(value, b.i8ptrType, "") + } + b.createRuntimeCall("trackPointer", []llvm.Value{value}, "") +} + // typeHasPointers returns whether this type is a pointer or contains pointers. // If the type is an aggregate type, it will check whether there is a pointer // inside. From 271921fcba56ae58a59876e8bb5fe44ca85e093f Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 9 Dec 2019 17:50:58 +0100 Subject: [PATCH 16/21] compiler: refactor starting new goroutines --- compiler/compiler.go | 4 ++-- compiler/goroutine.go | 49 ++++++++++++++++++++----------------------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index dbf59ad811..d3d4bab5c1 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1071,7 +1071,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { panic("StaticCallee returned an unexpected value") } params = append(params, context) // context parameter - c.emitStartGoroutine(calleeFn.LLVMFn, params) + frame.createGoInstruction(calleeFn.LLVMFn, params) } else if !instr.Call.IsInvoke() { // This is a function pointer. // At the moment, two extra params are passed to the newly started @@ -1089,7 +1089,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { default: panic("unknown scheduler type") } - c.emitStartGoroutine(funcPtr, params) + frame.createGoInstruction(funcPtr, params) } else { c.addError(instr.Pos(), "todo: go on interface call") } diff --git a/compiler/goroutine.go b/compiler/goroutine.go index 2abb8bb0fe..dd114b3219 100644 --- a/compiler/goroutine.go +++ b/compiler/goroutine.go @@ -3,27 +3,30 @@ package compiler // This file implements the 'go' keyword to start a new goroutine. See // goroutine-lowering.go for more details. -import "tinygo.org/x/go-llvm" +import ( + "github.com/tinygo-org/tinygo/compiler/llvmutil" + "tinygo.org/x/go-llvm" +) -// emitStartGoroutine starts a new goroutine with the provided function pointer +// createGoInstruction starts a new goroutine with the provided function pointer // and parameters. // In general, you should pass all regular parameters plus the context parameter. // There is one exception: the task-based scheduler needs to have the function // pointer passed in as a parameter too in addition to the context. // // Because a go statement doesn't return anything, return undef. -func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) llvm.Value { - paramBundle := c.emitPointerPack(params) +func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value) llvm.Value { + paramBundle := b.emitPointerPack(params) var callee llvm.Value - switch c.Scheduler() { + switch b.Scheduler() { case "none", "tasks": - callee = c.createGoroutineStartWrapper(funcPtr) + callee = b.createGoroutineStartWrapper(funcPtr) case "coroutines": - callee = c.builder.CreatePtrToInt(funcPtr, c.uintptrType, "") + callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "") default: panic("unreachable") } - c.createCall(c.mod.NamedFunction("internal/task.start"), []llvm.Value{callee, paramBundle, llvm.Undef(c.i8ptrType), llvm.ConstPointerNull(c.i8ptrType)}, "") + b.createCall(b.mod.NamedFunction("internal/task.start"), []llvm.Value{callee, paramBundle, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "") return llvm.Undef(funcPtr.Type().ElementType().ReturnType()) } @@ -45,36 +48,34 @@ func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) l // allows a single (pointer) argument to the newly started goroutine. Also, it // ignores the return value because newly started goroutines do not have a // return value. -func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value { +func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value) llvm.Value { var wrapper llvm.Value + builder := c.ctx.NewBuilder() + if !fn.IsAFunction().IsNil() { // See whether this wrapper has already been created. If so, return it. name := fn.Name() wrapper = c.mod.NamedFunction(name + "$gowrapper") if !wrapper.IsNil() { - return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "") + return llvm.ConstPtrToInt(wrapper, c.uintptrType) } - // Save the current position in the IR builder. - currentBlock := c.builder.GetInsertBlock() - defer c.builder.SetInsertPointAtEnd(currentBlock) - // Create the wrapper. wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false) wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType) wrapper.SetLinkage(llvm.PrivateLinkage) wrapper.SetUnnamedAddr(true) entry := c.ctx.AddBasicBlock(wrapper, "entry") - c.builder.SetInsertPointAtEnd(entry) + builder.SetInsertPointAtEnd(entry) // Create the list of params for the call. paramTypes := fn.Type().ElementType().ParamTypes() - params := c.emitPointerUnpack(wrapper.Param(0), paramTypes[:len(paramTypes)-1]) + params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes[:len(paramTypes)-1]) params = append(params, llvm.Undef(c.i8ptrType)) // Create the call. - c.builder.CreateCall(fn, params, "") + builder.CreateCall(fn, params, "") } else { // For a function pointer like this: @@ -94,22 +95,18 @@ func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value { // With a bit of luck, identical wrapper functions like these can be // merged into one. - // Save the current position in the IR builder. - currentBlock := c.builder.GetInsertBlock() - defer c.builder.SetInsertPointAtEnd(currentBlock) - // Create the wrapper. wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false) wrapper = llvm.AddFunction(c.mod, ".gowrapper", wrapperType) wrapper.SetLinkage(llvm.InternalLinkage) wrapper.SetUnnamedAddr(true) entry := c.ctx.AddBasicBlock(wrapper, "entry") - c.builder.SetInsertPointAtEnd(entry) + builder.SetInsertPointAtEnd(entry) // Get the list of parameters, with the extra parameters at the end. paramTypes := fn.Type().ElementType().ParamTypes() paramTypes[len(paramTypes)-1] = fn.Type() // the last element is the function pointer - params := c.emitPointerUnpack(wrapper.Param(0), paramTypes) + params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes) // Get the function pointer. fnPtr := params[len(params)-1] @@ -119,13 +116,13 @@ func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value { params[len(params)-1] = llvm.Undef(c.i8ptrType) // Create the call. - c.builder.CreateCall(fnPtr, params, "") + builder.CreateCall(fnPtr, params, "") } // Finish the function. Every basic block must end in a terminator, and // because goroutines never return a value we can simply return void. - c.builder.CreateRetVoid() + builder.CreateRetVoid() // Return a ptrtoint of the wrapper, not the function itself. - return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "") + return builder.CreatePtrToInt(wrapper, c.uintptrType, "") } From 3ccf613acc9d61f019b447f6fff79fd003f962ea Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 9 Dec 2019 18:16:45 +0100 Subject: [PATCH 17/21] compiler: refactor top-level createInstruction function --- compiler/compiler.go | 100 ++++++++++++++++++++++--------------------- compiler/gc.go | 26 +++++------ 2 files changed, 64 insertions(+), 62 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index d3d4bab5c1..65aa1ee5e4 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1000,7 +1000,7 @@ func (c *Compiler) parseFunc(frame *Frame) { fmt.Printf("\t%s\n", instr.String()) } } - c.parseInstr(frame, instr) + frame.createInstruction(instr) } if frame.fn.Name() == "init" && len(block.Instrs) == 0 { c.builder.CreateRetVoid() @@ -1018,69 +1018,71 @@ func (c *Compiler) parseFunc(frame *Frame) { } } -func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { - if c.Debug() { - pos := c.ir.Program.Fset.Position(instr.Pos()) - c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), frame.difunc, llvm.Metadata{}) +// createInstruction builds the LLVM IR equivalent instructions for the +// particular Go SSA instruction. +func (b *builder) createInstruction(instr ssa.Instruction) { + if b.Debug() { + pos := b.ir.Program.Fset.Position(instr.Pos()) + b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{}) } switch instr := instr.(type) { case ssa.Value: - if value, err := frame.createExpr(instr); err != nil { + if value, err := b.createExpr(instr); err != nil { // This expression could not be parsed. Add the error to the list // of diagnostics and continue with an undef value. // The resulting IR will be incorrect (but valid). However, // compilation can proceed which is useful because there may be // more compilation errors which can then all be shown together to // the user. - c.diagnostics = append(c.diagnostics, err) - frame.locals[instr] = llvm.Undef(c.getLLVMType(instr.Type())) + b.diagnostics = append(b.diagnostics, err) + b.locals[instr] = llvm.Undef(b.getLLVMType(instr.Type())) } else { - frame.locals[instr] = value - if len(*instr.Referrers()) != 0 && c.NeedsStackObjects() { - c.trackExpr(frame, instr, value) + b.locals[instr] = value + if len(*instr.Referrers()) != 0 && b.NeedsStackObjects() { + b.trackExpr(instr, value) } } case *ssa.DebugRef: // ignore case *ssa.Defer: - frame.createDefer(instr) + b.createDefer(instr) case *ssa.Go: // Get all function parameters to pass to the goroutine. var params []llvm.Value for _, param := range instr.Call.Args { - params = append(params, frame.getValue(param)) + params = append(params, b.getValue(param)) } // Start a new goroutine. if callee := instr.Call.StaticCallee(); callee != nil { // Static callee is known. This makes it easier to start a new // goroutine. - calleeFn := c.ir.GetFunction(callee) + calleeFn := b.ir.GetFunction(callee) var context llvm.Value switch value := instr.Call.Value.(type) { case *ssa.Function: // Goroutine call is regular function call. No context is necessary. - context = llvm.Undef(c.i8ptrType) + context = llvm.Undef(b.i8ptrType) case *ssa.MakeClosure: // A goroutine call on a func value, but the callee is trivial to find. For // example: immediately applied functions. - funcValue := frame.getValue(value) - context = frame.extractFuncContext(funcValue) + funcValue := b.getValue(value) + context = b.extractFuncContext(funcValue) default: panic("StaticCallee returned an unexpected value") } params = append(params, context) // context parameter - frame.createGoInstruction(calleeFn.LLVMFn, params) + b.createGoInstruction(calleeFn.LLVMFn, params) } else if !instr.Call.IsInvoke() { // This is a function pointer. // At the moment, two extra params are passed to the newly started // goroutine: // * The function context, for closures. // * The function pointer (for tasks). - funcPtr, context := frame.decodeFuncValue(frame.getValue(instr.Call.Value), instr.Call.Value.Type().(*types.Signature)) + funcPtr, context := b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().(*types.Signature)) params = append(params, context) // context parameter - switch c.Scheduler() { + switch b.Scheduler() { case "none", "coroutines": // There are no additional parameters needed for the goroutine start operation. case "tasks": @@ -1089,58 +1091,58 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { default: panic("unknown scheduler type") } - frame.createGoInstruction(funcPtr, params) + b.createGoInstruction(funcPtr, params) } else { - c.addError(instr.Pos(), "todo: go on interface call") + b.addError(instr.Pos(), "todo: go on interface call") } case *ssa.If: - cond := frame.getValue(instr.Cond) + cond := b.getValue(instr.Cond) block := instr.Block() - blockThen := frame.blockEntries[block.Succs[0]] - blockElse := frame.blockEntries[block.Succs[1]] - c.builder.CreateCondBr(cond, blockThen, blockElse) + blockThen := b.blockEntries[block.Succs[0]] + blockElse := b.blockEntries[block.Succs[1]] + b.CreateCondBr(cond, blockThen, blockElse) case *ssa.Jump: - blockJump := frame.blockEntries[instr.Block().Succs[0]] - c.builder.CreateBr(blockJump) + blockJump := b.blockEntries[instr.Block().Succs[0]] + b.CreateBr(blockJump) case *ssa.MapUpdate: - m := frame.getValue(instr.Map) - key := frame.getValue(instr.Key) - value := frame.getValue(instr.Value) + m := b.getValue(instr.Map) + key := b.getValue(instr.Key) + value := b.getValue(instr.Value) mapType := instr.Map.Type().Underlying().(*types.Map) - frame.createMapUpdate(mapType.Key(), m, key, value, instr.Pos()) + b.createMapUpdate(mapType.Key(), m, key, value, instr.Pos()) case *ssa.Panic: - value := frame.getValue(instr.X) - c.createRuntimeCall("_panic", []llvm.Value{value}, "") - c.builder.CreateUnreachable() + value := b.getValue(instr.X) + b.createRuntimeCall("_panic", []llvm.Value{value}, "") + b.CreateUnreachable() case *ssa.Return: if len(instr.Results) == 0 { - c.builder.CreateRetVoid() + b.CreateRetVoid() } else if len(instr.Results) == 1 { - c.builder.CreateRet(frame.getValue(instr.Results[0])) + b.CreateRet(b.getValue(instr.Results[0])) } else { // Multiple return values. Put them all in a struct. - retVal := llvm.ConstNull(frame.fn.LLVMFn.Type().ElementType().ReturnType()) + retVal := llvm.ConstNull(b.fn.LLVMFn.Type().ElementType().ReturnType()) for i, result := range instr.Results { - val := frame.getValue(result) - retVal = c.builder.CreateInsertValue(retVal, val, i, "") + val := b.getValue(result) + retVal = b.CreateInsertValue(retVal, val, i, "") } - c.builder.CreateRet(retVal) + b.CreateRet(retVal) } case *ssa.RunDefers: - frame.createRunDefers() + b.createRunDefers() case *ssa.Send: - frame.createChanSend(instr) + b.createChanSend(instr) case *ssa.Store: - llvmAddr := frame.getValue(instr.Addr) - llvmVal := frame.getValue(instr.Val) - frame.createNilCheck(llvmAddr, "store") - if c.targetData.TypeAllocSize(llvmVal.Type()) == 0 { + llvmAddr := b.getValue(instr.Addr) + llvmVal := b.getValue(instr.Val) + b.createNilCheck(llvmAddr, "store") + if b.targetData.TypeAllocSize(llvmVal.Type()) == 0 { // nothing to store return } - c.builder.CreateStore(llvmVal, llvmAddr) + b.CreateStore(llvmVal, llvmAddr) default: - c.addError(instr.Pos(), "unknown instruction: "+instr.String()) + b.addError(instr.Pos(), "unknown instruction: "+instr.String()) } } diff --git a/compiler/gc.go b/compiler/gc.go index bd8b964fee..7741f46458 100644 --- a/compiler/gc.go +++ b/compiler/gc.go @@ -12,53 +12,53 @@ import ( // 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) { +func (b *builder) trackExpr(expr ssa.Value, value llvm.Value) { // There are uses of this expression, Make sure the pointers // are tracked during GC. switch expr := expr.(type) { case *ssa.Alloc, *ssa.MakeChan, *ssa.MakeMap: // These values are always of pointer type in IR. - c.trackPointer(value) + b.trackPointer(value) case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next: if !value.IsNil() { - c.trackValue(value) + b.trackValue(value) } case *ssa.Select: - if alloca, ok := frame.selectRecvBuf[expr]; ok { + if alloca, ok := b.selectRecvBuf[expr]; ok { if alloca.IsAUndefValue().IsNil() { - c.trackPointer(alloca) + b.trackPointer(alloca) } } case *ssa.UnOp: switch expr.Op { case token.MUL: // Pointer dereference. - c.trackValue(value) + b.trackValue(value) case token.ARROW: // Channel receive operator. // It's not necessary to look at commaOk here, because in that // case it's just an aggregate and trackValue will extract the // pointer in there (if there is one). - c.trackValue(value) + b.trackValue(value) } } } // trackValue locates pointers in a value (possibly an aggregate) and tracks the // individual pointers -func (c *Compiler) trackValue(value llvm.Value) { +func (b *builder) trackValue(value llvm.Value) { typ := value.Type() switch typ.TypeKind() { case llvm.PointerTypeKind: - c.trackPointer(value) + b.trackPointer(value) case llvm.StructTypeKind: if !typeHasPointers(typ) { return } numElements := typ.StructElementTypesCount() for i := 0; i < numElements; i++ { - subValue := c.builder.CreateExtractValue(value, i, "") - c.trackValue(subValue) + subValue := b.CreateExtractValue(value, i, "") + b.trackValue(subValue) } case llvm.ArrayTypeKind: if !typeHasPointers(typ) { @@ -66,8 +66,8 @@ func (c *Compiler) trackValue(value llvm.Value) { } numElements := typ.ArrayLength() for i := 0; i < numElements; i++ { - subValue := c.builder.CreateExtractValue(value, i, "") - c.trackValue(subValue) + subValue := b.CreateExtractValue(value, i, "") + b.trackValue(subValue) } } } From ea0fcf58ed6720419d2ebb45ca7ddd5ba326ff43 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 9 Dec 2019 20:33:09 +0100 Subject: [PATCH 18/21] compiler: refactor creation of functions --- compiler/calls.go | 39 +++++----- compiler/compiler.go | 165 ++++++++++++++++++++++--------------------- 2 files changed, 107 insertions(+), 97 deletions(-) diff --git a/compiler/calls.go b/compiler/calls.go index b59fb30746..985485a062 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -82,13 +82,14 @@ func expandFormalParamType(t llvm.Type) []llvm.Type { } } -// Expand an argument type to a list of offsets from the start of the object. -// Used together with expandFormalParam to get the offset of each value from the -// start of the non-expanded value. -func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 { +// expandFormalParamOffsets returns a list of offsets from the start of an +// object of type t after it would have been split up by expandFormalParam. This +// is useful for debug information, where it is necessary to know the offset +// from the start of the combined object. +func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 { switch t.TypeKind() { case llvm.StructTypeKind: - fields := c.flattenAggregateTypeOffsets(t) + fields := b.flattenAggregateTypeOffsets(t) if len(fields) <= MaxFieldsPerParam { return fields } else { @@ -162,10 +163,13 @@ func flattenAggregateType(t llvm.Type) []llvm.Type { } } -// Return the offsets from the start of the object if this object type were -// flattened like in flattenAggregate. Used together with flattenAggregate to -// know the start indices of each value in the non-flattened object. -func (c *Compiler) flattenAggregateTypeOffsets(t llvm.Type) []uint64 { +// flattenAggregateTypeOffset returns the offsets from the start of an object of +// type t if this object were flattened like in flattenAggregate. Used together +// with flattenAggregate to know the start indices of each value in the +// non-flattened object. +// +// Note: this is an implementation detail, use expandFormalParamOffsets instead. +func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 { switch t.TypeKind() { case llvm.StructTypeKind: fields := make([]uint64, 0, t.StructElementTypesCount()) @@ -217,25 +221,28 @@ func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value { } } -// Collapse a list of fields into its original value. -func (c *Compiler) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value { - param, remaining := c.collapseFormalParamInternal(t, fields) +// collapseFormalParam combines an aggregate object back into the original +// value. This is used to join multiple LLVM parameters into a single Go value +// in the function entry block. +func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value { + param, remaining := b.collapseFormalParamInternal(t, fields) if len(remaining) != 0 { panic("failed to expand back all fields") } return param } -// Returns (value, remainingFields). Used by collapseFormalParam. -func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) { +// collapseFormalParamInternal is an implementation detail of +// collapseFormalParam: it works by recursing until there are no fields left. +func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) { switch t.TypeKind() { case llvm.StructTypeKind: if len(flattenAggregateType(t)) <= MaxFieldsPerParam { value := llvm.ConstNull(t) for i, subtyp := range t.StructElementTypes() { - structField, remaining := c.collapseFormalParamInternal(subtyp, fields) + structField, remaining := b.collapseFormalParamInternal(subtyp, fields) fields = remaining - value = c.builder.CreateInsertValue(value, structField, i, "") + value = b.CreateInsertValue(value, structField, i, "") } return value, fields } else { diff --git a/compiler/compiler.go b/compiler/compiler.go index 65aa1ee5e4..f5057836d9 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -297,7 +297,7 @@ func (c *Compiler) Compile(mainPath string) []error { if frame.fn.Blocks == nil { continue // external function } - c.parseFunc(frame) + frame.createFunctionDefinition() } // After all packages are imported, add a synthetic initializer function @@ -689,40 +689,40 @@ func (c *compilerContext) createDIType(typ types.Type) llvm.Metadata { // getLocalVariable returns a debug info entry for a local variable, which may // either be a parameter or a regular variable. It will create a new metadata // entry if there isn't one for the variable yet. -func (c *Compiler) getLocalVariable(frame *Frame, variable *types.Var) llvm.Metadata { - if dilocal, ok := frame.dilocals[variable]; ok { +func (b *builder) getLocalVariable(variable *types.Var) llvm.Metadata { + if dilocal, ok := b.dilocals[variable]; ok { // DILocalVariable was already created, return it directly. return dilocal } - pos := c.ir.Program.Fset.Position(variable.Pos()) + pos := b.ir.Program.Fset.Position(variable.Pos()) // Check whether this is a function parameter. - for i, param := range frame.fn.Params { + for i, param := range b.fn.Params { if param.Object().(*types.Var) == variable { // Yes it is, create it as a function parameter. - dilocal := c.dibuilder.CreateParameterVariable(frame.difunc, llvm.DIParameterVariable{ + dilocal := b.dibuilder.CreateParameterVariable(b.difunc, llvm.DIParameterVariable{ Name: param.Name(), - File: c.getDIFile(pos.Filename), + File: b.getDIFile(pos.Filename), Line: pos.Line, - Type: c.getDIType(variable.Type()), + Type: b.getDIType(variable.Type()), AlwaysPreserve: true, ArgNo: i + 1, }) - frame.dilocals[variable] = dilocal + b.dilocals[variable] = dilocal return dilocal } } // No, it's not a parameter. Create a regular (auto) variable. - dilocal := c.dibuilder.CreateAutoVariable(frame.difunc, llvm.DIAutoVariable{ + dilocal := b.dibuilder.CreateAutoVariable(b.difunc, llvm.DIAutoVariable{ Name: variable.Name(), - File: c.getDIFile(pos.Filename), + File: b.getDIFile(pos.Filename), Line: pos.Line, - Type: c.getDIType(variable.Type()), + Type: b.getDIType(variable.Type()), AlwaysPreserve: true, }) - frame.dilocals[variable] = dilocal + b.dilocals[variable] = dilocal return dilocal } @@ -845,86 +845,89 @@ func (c *compilerContext) getDIFile(filename string) llvm.Metadata { return c.difiles[filename] } -func (c *Compiler) parseFunc(frame *Frame) { - if c.DumpSSA() { - fmt.Printf("\nfunc %s:\n", frame.fn.Function) +// createFunctionDefinition builds the LLVM IR implementation for this function. +// The function must be declared but not yet defined, otherwise this function +// will create a diagnostic. +func (b *builder) createFunctionDefinition() { + if b.DumpSSA() { + fmt.Printf("\nfunc %s:\n", b.fn.Function) } - if !frame.fn.LLVMFn.IsDeclaration() { - errValue := frame.fn.LLVMFn.Name() + " redeclared in this program" - fnPos := getPosition(frame.fn.LLVMFn) + if !b.fn.LLVMFn.IsDeclaration() { + errValue := b.fn.Name() + " redeclared in this program" + fnPos := getPosition(b.fn.LLVMFn) if fnPos.IsValid() { errValue += "\n\tprevious declaration at " + fnPos.String() } - c.addError(frame.fn.Pos(), errValue) + b.addError(b.fn.Pos(), errValue) return } - if !frame.fn.IsExported() { - frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage) - frame.fn.LLVMFn.SetUnnamedAddr(true) + if !b.fn.IsExported() { + b.fn.LLVMFn.SetLinkage(llvm.InternalLinkage) + b.fn.LLVMFn.SetUnnamedAddr(true) } // Some functions have a pragma controlling the inlining level. - switch frame.fn.Inline() { + switch b.fn.Inline() { case ir.InlineHint: // Add LLVM inline hint to functions with //go:inline pragma. - inline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0) - frame.fn.LLVMFn.AddFunctionAttr(inline) + inline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0) + b.fn.LLVMFn.AddFunctionAttr(inline) case ir.InlineNone: // Add LLVM attribute to always avoid inlining this function. - noinline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0) - frame.fn.LLVMFn.AddFunctionAttr(noinline) + noinline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0) + b.fn.LLVMFn.AddFunctionAttr(noinline) } // Add debug info, if needed. - if c.Debug() { - if frame.fn.Synthetic == "package initializer" { + if b.Debug() { + if b.fn.Synthetic == "package initializer" { // Package initializers have no debug info. Create some fake debug // info to at least have *something*. - frame.difunc = c.attachDebugInfoRaw(frame.fn, frame.fn.LLVMFn, "", "", 0) - } else if frame.fn.Syntax() != nil { + b.difunc = b.attachDebugInfoRaw(b.fn, b.fn.LLVMFn, "", "", 0) + } else if b.fn.Syntax() != nil { // Create debug info file if needed. - frame.difunc = c.attachDebugInfo(frame.fn) + b.difunc = b.attachDebugInfo(b.fn) } - pos := c.ir.Program.Fset.Position(frame.fn.Pos()) - c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), frame.difunc, llvm.Metadata{}) + pos := b.ir.Program.Fset.Position(b.fn.Pos()) + b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{}) } // Pre-create all basic blocks in the function. - for _, block := range frame.fn.DomPreorder() { - llvmBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, block.Comment) - frame.blockEntries[block] = llvmBlock - frame.blockExits[block] = llvmBlock + for _, block := range b.fn.DomPreorder() { + llvmBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, block.Comment) + b.blockEntries[block] = llvmBlock + b.blockExits[block] = llvmBlock } - entryBlock := frame.blockEntries[frame.fn.Blocks[0]] - c.builder.SetInsertPointAtEnd(entryBlock) + entryBlock := b.blockEntries[b.fn.Blocks[0]] + b.SetInsertPointAtEnd(entryBlock) // Load function parameters llvmParamIndex := 0 - for _, param := range frame.fn.Params { - llvmType := c.getLLVMType(param.Type()) + for _, param := range b.fn.Params { + llvmType := b.getLLVMType(param.Type()) fields := make([]llvm.Value, 0, 1) for range expandFormalParamType(llvmType) { - fields = append(fields, frame.fn.LLVMFn.Param(llvmParamIndex)) + fields = append(fields, b.fn.LLVMFn.Param(llvmParamIndex)) llvmParamIndex++ } - frame.locals[param] = c.collapseFormalParam(llvmType, fields) + b.locals[param] = b.collapseFormalParam(llvmType, fields) // Add debug information to this parameter (if available) - if c.Debug() && frame.fn.Syntax() != nil { - dbgParam := c.getLocalVariable(frame, param.Object().(*types.Var)) - loc := c.builder.GetCurrentDebugLocation() + if b.Debug() && b.fn.Syntax() != nil { + dbgParam := b.getLocalVariable(param.Object().(*types.Var)) + loc := b.GetCurrentDebugLocation() if len(fields) == 1 { - expr := c.dibuilder.CreateExpression(nil) - c.dibuilder.InsertValueAtEnd(fields[0], dbgParam, expr, loc, entryBlock) + expr := b.dibuilder.CreateExpression(nil) + b.dibuilder.InsertValueAtEnd(fields[0], dbgParam, expr, loc, entryBlock) } else { - fieldOffsets := c.expandFormalParamOffsets(llvmType) + fieldOffsets := b.expandFormalParamOffsets(llvmType) for i, field := range fields { - expr := c.dibuilder.CreateExpression([]int64{ + expr := b.dibuilder.CreateExpression([]int64{ 0x1000, // DW_OP_LLVM_fragment int64(fieldOffsets[i]) * 8, // offset in bits - int64(c.targetData.TypeAllocSize(field.Type())) * 8, // size in bits + int64(b.targetData.TypeAllocSize(field.Type())) * 8, // size in bits }) - c.dibuilder.InsertValueAtEnd(field, dbgParam, expr, loc, entryBlock) + b.dibuilder.InsertValueAtEnd(field, dbgParam, expr, loc, entryBlock) } } } @@ -933,44 +936,44 @@ func (c *Compiler) parseFunc(frame *Frame) { // Load free variables from the context. This is a closure (or bound // method). var context llvm.Value - if !frame.fn.IsExported() { - parentHandle := frame.fn.LLVMFn.LastParam() + if !b.fn.IsExported() { + parentHandle := b.fn.LLVMFn.LastParam() parentHandle.SetName("parentHandle") context = llvm.PrevParam(parentHandle) context.SetName("context") } - if len(frame.fn.FreeVars) != 0 { + if len(b.fn.FreeVars) != 0 { // 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()) + freeVarTypes := make([]llvm.Type, len(b.fn.FreeVars)) + for i, freeVar := range b.fn.FreeVars { + freeVarTypes[i] = b.getLLVMType(freeVar.Type()) } // 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, val := range c.emitPointerUnpack(context, freeVarTypes) { - frame.locals[frame.fn.FreeVars[i]] = val + for i, val := range b.emitPointerUnpack(context, freeVarTypes) { + b.locals[b.fn.FreeVars[i]] = val } } - if frame.fn.Recover != nil { + if b.fn.Recover != nil { // This function has deferred function calls. Set some things up for // them. - frame.deferInitFunc() + b.deferInitFunc() } // Fill blocks with instructions. - for _, block := range frame.fn.DomPreorder() { - if c.DumpSSA() { + for _, block := range b.fn.DomPreorder() { + if b.DumpSSA() { fmt.Printf("%d: %s:\n", block.Index, block.Comment) } - c.builder.SetInsertPointAtEnd(frame.blockEntries[block]) - frame.currentBlock = block + b.SetInsertPointAtEnd(b.blockEntries[block]) + b.currentBlock = block for _, instr := range block.Instrs { if instr, ok := instr.(*ssa.DebugRef); ok { - if !c.Debug() { + if !b.Debug() { continue } object := instr.Object() @@ -984,35 +987,35 @@ func (c *Compiler) parseFunc(frame *Frame) { // for example. continue } - dbgVar := c.getLocalVariable(frame, variable) - pos := c.ir.Program.Fset.Position(instr.Pos()) - c.dibuilder.InsertValueAtEnd(frame.getValue(instr.X), dbgVar, c.dibuilder.CreateExpression(nil), llvm.DebugLoc{ + dbgVar := b.getLocalVariable(variable) + pos := b.ir.Program.Fset.Position(instr.Pos()) + b.dibuilder.InsertValueAtEnd(b.getValue(instr.X), dbgVar, b.dibuilder.CreateExpression(nil), llvm.DebugLoc{ Line: uint(pos.Line), Col: uint(pos.Column), - Scope: frame.difunc, - }, c.builder.GetInsertBlock()) + Scope: b.difunc, + }, b.GetInsertBlock()) continue } - if c.DumpSSA() { + if b.DumpSSA() { if val, ok := instr.(ssa.Value); ok && val.Name() != "" { fmt.Printf("\t%s = %s\n", val.Name(), val.String()) } else { fmt.Printf("\t%s\n", instr.String()) } } - frame.createInstruction(instr) + b.createInstruction(instr) } - if frame.fn.Name() == "init" && len(block.Instrs) == 0 { - c.builder.CreateRetVoid() + if b.fn.Name() == "init" && len(block.Instrs) == 0 { + b.CreateRetVoid() } } // Resolve phi nodes - for _, phi := range frame.phis { + for _, phi := range b.phis { block := phi.ssa.Block() for i, edge := range phi.ssa.Edges { - llvmVal := frame.getValue(edge) - llvmBlock := frame.blockExits[block.Preds[i]] + llvmVal := b.getValue(edge) + llvmBlock := b.blockExits[block.Preds[i]] phi.llvm.AddIncoming([]llvm.Value{llvmVal}, []llvm.BasicBlock{llvmBlock}) } } From c1181f8f40b095084a5f80f3059f0d9130bbd183 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 9 Dec 2019 21:09:42 +0100 Subject: [PATCH 19/21] compiler: remove *Frame type --- compiler/compiler.go | 59 +++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index f5057836d9..77fd3f36d7 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -62,10 +62,6 @@ type Compiler struct { initFuncs []llvm.Value } -type Frame struct { - builder -} - // builder contains all information relevant to build a single function. type builder struct { *compilerContext @@ -263,8 +259,6 @@ func (c *Compiler) Compile(mainPath string) []error { }) } - var frames []*Frame - c.loadASTComments(lprogram) // Declare runtime types. @@ -283,21 +277,32 @@ func (c *Compiler) Compile(mainPath string) []error { // Declare all functions. for _, f := range c.ir.Functions { - frames = append(frames, c.parseFuncDecl(f)) + c.createFunctionDeclaration(f) } // Add definitions to declarations. - for _, frame := range frames { - if frame.fn.Synthetic == "package initializer" { - c.initFuncs = append(c.initFuncs, frame.fn.LLVMFn) + for _, f := range c.ir.Functions { + if f.Synthetic == "package initializer" { + c.initFuncs = append(c.initFuncs, f.LLVMFn) } - if frame.fn.CName() != "" { + if f.CName() != "" { continue } - if frame.fn.Blocks == nil { + if f.Blocks == nil { continue // external function } - frame.createFunctionDefinition() + + // Create the function definition. + b := builder{ + compilerContext: &c.compilerContext, + Builder: c.builder, + fn: f, + locals: make(map[ssa.Value]llvm.Value), + dilocals: make(map[*types.Var]llvm.Metadata), + blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock), + blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock), + } + b.createFunctionDefinition() } // After all packages are imported, add a synthetic initializer function @@ -726,19 +731,9 @@ func (b *builder) getLocalVariable(variable *types.Var) llvm.Metadata { return dilocal } -func (c *Compiler) parseFuncDecl(f *ir.Function) *Frame { - frame := &Frame{ - builder: builder{ - compilerContext: &c.compilerContext, - Builder: c.builder, // TODO: use a separate builder per function - fn: f, - locals: make(map[ssa.Value]llvm.Value), - dilocals: make(map[*types.Var]llvm.Metadata), - blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock), - blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock), - }, - } - +// createFunctionDeclaration creates a LLVM function declaration without body. +// It can later be filled with frame.createFunctionDefinition(). +func (c *compilerContext) createFunctionDeclaration(f *ir.Function) { var retType llvm.Type if f.Signature.Results() == nil { retType = c.ctx.VoidType() @@ -769,9 +764,9 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) *Frame { fnType := llvm.FunctionType(retType, paramTypes, false) name := f.LinkName() - frame.fn.LLVMFn = c.mod.NamedFunction(name) - if frame.fn.LLVMFn.IsNil() { - frame.fn.LLVMFn = llvm.AddFunction(c.mod, name, fnType) + f.LLVMFn = c.mod.NamedFunction(name) + if f.LLVMFn.IsNil() { + f.LLVMFn = llvm.AddFunction(c.mod, name, fnType) } // External/exported functions may not retain pointer values. @@ -780,18 +775,16 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) *Frame { // Set the wasm-import-module attribute if the function's module is set. if f.Module() != "" { wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", f.Module()) - frame.fn.LLVMFn.AddFunctionAttr(wasmImportModuleAttr) + f.LLVMFn.AddFunctionAttr(wasmImportModuleAttr) } nocaptureKind := llvm.AttributeKindID("nocapture") nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0) for i, typ := range paramTypes { if typ.TypeKind() == llvm.PointerTypeKind { - frame.fn.LLVMFn.AddAttributeAtIndex(i+1, nocapture) + f.LLVMFn.AddAttributeAtIndex(i+1, nocapture) } } } - - return frame } // attachDebugInfo adds debug info to a function declaration. It returns the From ab4472580c421fd5e0e42b6d4b7ceec3c5518985 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 18 Mar 2020 19:35:02 +0100 Subject: [PATCH 20/21] compiler: remove leftover code after refactor A few functions were duplicated during the refactor. They can now be deleted. --- compiler/calls.go | 68 ----------------------------------------------- compiler/func.go | 12 --------- compiler/gc.go | 9 ------- compiler/llvm.go | 29 -------------------- 4 files changed, 118 deletions(-) diff --git a/compiler/calls.go b/compiler/calls.go index 985485a062..1de28b24ad 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -1,8 +1,6 @@ package compiler import ( - "fmt" - "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -13,24 +11,6 @@ import ( // a struct contains more fields, it is passed as a struct without expanding. const MaxFieldsPerParam = 3 -// Shortcut: create a call to runtime. with the given arguments. -func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value { - runtimePkg := c.ir.Program.ImportedPackage("runtime") - member := runtimePkg.Members[fnName] - if member == nil { - panic("trying to call runtime." + fnName) - } - fn := c.ir.GetFunction(member.(*ssa.Function)) - if fn.LLVMFn.IsNil() { - panic(fmt.Errorf("function %s does not appear in LLVM IR", fnName)) - } - if !fn.IsExported() { - args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter - args = append(args, llvm.ConstPointerNull(c.i8ptrType)) // coroutine handle - } - return c.createCall(fn.LLVMFn, args, name) -} - // createCall creates a new call to runtime. with the given arguments. func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value { fullName := "runtime." + fnName @@ -43,16 +23,6 @@ func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name strin return b.createCall(fn, args, name) } -// Create a call to the given function with the arguments possibly expanded. -func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value { - expanded := make([]llvm.Value, 0, len(args)) - for _, arg := range args { - fragments := c.expandFormalParam(arg) - expanded = append(expanded, fragments...) - } - return c.builder.CreateCall(fn, expanded, name) -} - // createCall creates a call to the given function with the arguments possibly // expanded. func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value { @@ -102,27 +72,6 @@ func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 { } } -// Equivalent of expandFormalParamType for parameter values. -func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value { - switch v.Type().TypeKind() { - case llvm.StructTypeKind: - fieldTypes := flattenAggregateType(v.Type()) - if len(fieldTypes) <= MaxFieldsPerParam { - fields := c.flattenAggregate(v) - if len(fields) != len(fieldTypes) { - panic("type and value param lowering don't match") - } - return fields - } else { - // failed to lower - return []llvm.Value{v} - } - default: - // TODO: split small arrays - return []llvm.Value{v} - } -} - // expandFormalParam splits a formal param value into pieces, so it can be // passed directly as part of a function call. For example, it splits up small // structs into individual fields. It is the equivalent of expandFormalParamType @@ -187,23 +136,6 @@ func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 { } } -// Break down a struct into its elementary types for argument passing. The value -// equivalent of flattenAggregateType -func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value { - switch v.Type().TypeKind() { - case llvm.StructTypeKind: - fields := make([]llvm.Value, 0, v.Type().StructElementTypesCount()) - for i := range v.Type().StructElementTypes() { - subfield := c.builder.CreateExtractValue(v, i, "") - subfields := c.flattenAggregate(subfield) - fields = append(fields, subfields...) - } - return fields - default: - return []llvm.Value{v} - } -} - // flattenAggregate breaks down a struct into its elementary values for argument // passing. It is the value equivalent of flattenAggregateType func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value { diff --git a/compiler/func.go b/compiler/func.go index c52f9e09ef..544f3e5fa3 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -11,12 +11,6 @@ import ( "tinygo.org/x/go-llvm" ) -// createFuncValue creates a function value from a raw function pointer with no -// context. -func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value { - return c.compilerContext.createFuncValue(c.builder, funcPtr, context, sig) -} - // createFuncValue creates a function value from a raw function pointer with no // context. func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value { @@ -57,12 +51,6 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context return funcValue } -// extractFuncScalar returns some scalar that can be used in comparisons. It is -// a cheap operation. -func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value { - return c.builder.CreateExtractValue(funcValue, 1, "") -} - // extractFuncScalar returns some scalar that can be used in comparisons. It is // a cheap operation. func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value { diff --git a/compiler/gc.go b/compiler/gc.go index 7741f46458..ceb071bf73 100644 --- a/compiler/gc.go +++ b/compiler/gc.go @@ -72,15 +72,6 @@ func (b *builder) trackValue(value llvm.Value) { } } -// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner -// first if needed. The input value must be of LLVM pointer type. -func (c *Compiler) trackPointer(value llvm.Value) { - if value.Type() != c.i8ptrType { - value = c.builder.CreateBitCast(value, c.i8ptrType, "") - } - c.createRuntimeCall("trackPointer", []llvm.Value{value}, "") -} - // trackPointer creates a call to runtime.trackPointer, bitcasting the poitner // first if needed. The input value must be of LLVM pointer type. func (b *builder) trackPointer(value llvm.Value) { diff --git a/compiler/llvm.go b/compiler/llvm.go index ed5e004044..e69c622276 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -23,16 +23,6 @@ func getUses(value llvm.Value) []llvm.Value { return uses } -// 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 (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) { - return llvmutil.CreateTemporaryAlloca(c.builder, c.mod, t, name) -} - // 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. @@ -43,13 +33,6 @@ func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitca return llvmutil.CreateTemporaryAlloca(b.Builder, b.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) { - llvmutil.EmitLifetimeEnd(c.builder, c.mod, ptr, size) -} - // emitLifetimeEnd signals the end of an (alloca) lifetime by calling the // llvm.lifetime.end intrinsic. It is commonly used together with // createTemporaryAlloca. @@ -57,13 +40,6 @@ func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) { llvmutil.EmitLifetimeEnd(b.Builder, b.mod, ptr, size) } -// 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) -} - // 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. @@ -71,11 +47,6 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value { return llvmutil.EmitPointerPack(b.Builder, b.mod, b.Config, values) } -// 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) -} - // 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) From b283224359d5e3a1cd4414cad91799f87f28f9af Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 18 Mar 2020 21:35:12 +0100 Subject: [PATCH 21/21] compiler: refactor public interface This commit merges NewCompiler and Compile into one simplifying the external interface. More importantly, it does away with the entire Compiler object so the public API becomes a lot smaller. The refactor is not complete: eventually, the compiler should just compile a single package without trying to load it first (that should be done by the builder package). --- builder/build.go | 72 +++++++++++--------- compiler/compiler.go | 152 ++++++++++++++++--------------------------- compiler/symbol.go | 2 +- 3 files changed, 96 insertions(+), 130 deletions(-) diff --git a/builder/build.go b/builder/build.go index 3ce7f644ee..2e42ea5ebf 100644 --- a/builder/build.go +++ b/builder/build.go @@ -17,6 +17,7 @@ import ( "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/interp" "github.com/tinygo-org/tinygo/transform" + "tinygo.org/x/go-llvm" ) // Build performs a single package to executable Go build. It takes in a package @@ -26,34 +27,34 @@ import ( // The error value may be of type *MultiError. Callers will likely want to check // for this case and print such errors individually. func Build(pkgName, outpath string, config *compileopts.Config, action func(string) error) error { - c, err := compiler.NewCompiler(pkgName, config) + // Compile Go code to IR. + machine, err := compiler.NewTargetMachine(config) if err != nil { return err } - - // Compile Go code to IR. - errs := c.Compile(pkgName) - if len(errs) != 0 { + mod, extraFiles, errs := compiler.Compile(pkgName, machine, config) + if errs != nil { return newMultiError(errs) } + if config.Options.PrintIR { fmt.Println("; Generated LLVM IR:") - fmt.Println(c.IR()) + fmt.Println(mod.String()) } - if err := c.Verify(); err != nil { + if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { return errors.New("verification error after IR construction") } - err = interp.Run(c.Module(), config.DumpSSA()) + err = interp.Run(mod, config.DumpSSA()) if err != nil { return err } - if err := c.Verify(); err != nil { + if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { return errors.New("verification error after interpreting runtime.initAll") } if config.GOOS() != "darwin" { - transform.ApplyFunctionSections(c.Module()) // -ffunction-sections + transform.ApplyFunctionSections(mod) // -ffunction-sections } // Browsers cannot handle external functions that have type i64 because it @@ -62,7 +63,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri // stack-allocated values. // Use -wasm-abi=generic to disable this behaviour. if config.Options.WasmAbi == "js" && strings.HasPrefix(config.Triple(), "wasm") { - err := transform.ExternalInt64AsPtr(c.Module()) + err := transform.ExternalInt64AsPtr(mod) if err != nil { return err } @@ -73,22 +74,22 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri errs = nil switch config.Options.Opt { case "none", "0": - errs = transform.Optimize(c.Module(), config, 0, 0, 0) // -O0 + errs = transform.Optimize(mod, config, 0, 0, 0) // -O0 case "1": - errs = transform.Optimize(c.Module(), config, 1, 0, 0) // -O1 + errs = transform.Optimize(mod, config, 1, 0, 0) // -O1 case "2": - errs = transform.Optimize(c.Module(), config, 2, 0, 225) // -O2 + errs = transform.Optimize(mod, config, 2, 0, 225) // -O2 case "s": - errs = transform.Optimize(c.Module(), config, 2, 1, 225) // -Os + errs = transform.Optimize(mod, config, 2, 1, 225) // -Os case "z": - errs = transform.Optimize(c.Module(), config, 2, 2, 5) // -Oz, default + errs = transform.Optimize(mod, config, 2, 2, 5) // -Oz, default default: errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)} } if len(errs) > 0 { return newMultiError(errs) } - if err := c.Verify(); err != nil { + if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { return errors.New("verification failure after LLVM optimization passes") } @@ -98,8 +99,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri // pointers are flash and which are in RAM so that pointers can have a // correct address space parameter (address space 1 is for flash). if strings.HasPrefix(config.Triple(), "avr") { - transform.NonConstGlobals(c.Module()) - if err := c.Verify(); err != nil { + transform.NonConstGlobals(mod) + if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { return errors.New("verification error after making all globals non-constant on AVR") } } @@ -108,11 +109,17 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri outext := filepath.Ext(outpath) switch outext { case ".o": - return c.EmitObject(outpath) + llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) + if err != nil { + return err + } + return ioutil.WriteFile(outpath, llvmBuf.Bytes(), 0666) case ".bc": - return c.EmitBitcode(outpath) + data := llvm.WriteBitcodeToMemoryBuffer(mod).Bytes() + return ioutil.WriteFile(outpath, data, 0666) case ".ll": - return c.EmitText(outpath) + data := []byte(mod.String()) + return ioutil.WriteFile(outpath, data, 0666) default: // Act as a compiler driver. @@ -125,7 +132,11 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri // Write the object file. objfile := filepath.Join(dir, "main.o") - err = c.EmitObject(objfile) + llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) + if err != nil { + return err + } + err = ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666) if err != nil { return err } @@ -161,16 +172,13 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri } // Compile C files in packages. - for i, pkg := range c.Packages() { - for _, file := range pkg.CFiles { - path := filepath.Join(pkg.Package.Dir, file) - outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o") - err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, path)...) - if err != nil { - return &commandError{"failed to build", path, err} - } - ldflags = append(ldflags, outpath) + for i, file := range extraFiles { + outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+filepath.Base(file)+".o") + err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...) + if err != nil { + return &commandError{"failed to build", file, err} } + ldflags = append(ldflags, outpath) } // Link the object files together. diff --git a/compiler/compiler.go b/compiler/compiler.go index 77fd3f36d7..966b0f62c8 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -56,12 +56,6 @@ type compilerContext struct { astComments map[string]*ast.CommentGroup } -type Compiler struct { - compilerContext - builder llvm.Builder - initFuncs []llvm.Value -} - // builder contains all information relevant to build a single function. type builder struct { *compilerContext @@ -88,28 +82,40 @@ type Phi struct { llvm llvm.Value } -func NewCompiler(pkgName string, config *compileopts.Config) (*Compiler, error) { - c := &Compiler{ - compilerContext: compilerContext{ - Config: config, - difiles: make(map[string]llvm.Metadata), - ditypes: make(map[types.Type]llvm.Metadata), - }, - } - +// NewTargetMachine returns a new llvm.TargetMachine based on the passed-in +// configuration. It is used by the compiler and is needed for machine code +// emission. +func NewTargetMachine(config *compileopts.Config) (llvm.TargetMachine, error) { target, err := llvm.GetTargetFromTriple(config.Triple()) if err != nil { - return nil, err + return llvm.TargetMachine{}, err } features := strings.Join(config.Features(), ",") - c.machine = target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault) - c.targetData = c.machine.CreateTargetData() + machine := target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault) + return machine, nil +} + +// Compile the given package path or .go file path. Return an error when this +// fails (in any stage). If successful it returns the LLVM module and a list of +// extra C files to be compiled. If not, one or more errors will be returned. +// +// The fact that it returns a list of filenames to compile is a layering +// violation. Eventually, this Compile function should only compile a single +// package and not the whole program, and loading of the program (including CGo +// processing) should be moved outside the compiler package. +func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) (llvm.Module, []string, []error) { + c := &compilerContext{ + Config: config, + difiles: make(map[string]llvm.Metadata), + ditypes: make(map[types.Type]llvm.Metadata), + machine: machine, + targetData: machine.CreateTargetData(), + } c.ctx = llvm.NewContext() c.mod = c.ctx.NewModule(pkgName) c.mod.SetTarget(config.Triple()) c.mod.SetDataLayout(c.targetData.String()) - c.builder = c.ctx.NewBuilder() if c.Debug() { c.dibuilder = llvm.NewDIBuilder(c.mod) } @@ -131,21 +137,6 @@ func NewCompiler(pkgName string, config *compileopts.Config) (*Compiler, error) c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace() dummyFunc.EraseFromParentAsFunction() - return c, nil -} - -func (c *Compiler) Packages() []*loader.Package { - return c.ir.LoaderProgram.Sorted() -} - -// Return the LLVM module. Only valid after a successful compile. -func (c *Compiler) Module() llvm.Module { - return c.mod -} - -// Compile the given package path or .go file path. Return an error when this -// fails (in any stage). -func (c *Compiler) Compile(mainPath string) []error { // Prefix the GOPATH with the system GOROOT, as GOROOT is already set to // the TinyGo root. overlayGopath := goenv.Get("GOPATH") @@ -157,7 +148,7 @@ func (c *Compiler) Compile(mainPath string) []error { wd, err := os.Getwd() if err != nil { - return []error{err} + return c.mod, nil, []error{err} } lprogram := &loader.Program{ Build: &build.Context{ @@ -217,17 +208,17 @@ func (c *Compiler) Compile(mainPath string) []error { ClangHeaders: c.ClangHeaders, } - if strings.HasSuffix(mainPath, ".go") { - _, err = lprogram.ImportFile(mainPath) + if strings.HasSuffix(pkgName, ".go") { + _, err = lprogram.ImportFile(pkgName) if err != nil { - return []error{err} + return c.mod, nil, []error{err} } } else { - _, err = lprogram.Import(mainPath, wd, token.Position{ + _, err = lprogram.Import(pkgName, wd, token.Position{ Filename: "build command-line-arguments", }) if err != nil { - return []error{err} + return c.mod, nil, []error{err} } } @@ -235,15 +226,15 @@ func (c *Compiler) Compile(mainPath string) []error { Filename: "build default import", }) if err != nil { - return []error{err} + return c.mod, nil, []error{err} } err = lprogram.Parse(c.TestConfig.CompileTestBinary) if err != nil { - return []error{err} + return c.mod, nil, []error{err} } - c.ir = ir.NewProgram(lprogram, mainPath) + c.ir = ir.NewProgram(lprogram, pkgName) // Run a simple dead code elimination pass. c.ir.SimpleDCE() @@ -252,7 +243,7 @@ func (c *Compiler) Compile(mainPath string) []error { if c.Debug() { c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{ Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?) - File: mainPath, + File: pkgName, Dir: "", Producer: "TinyGo", Optimized: true, @@ -281,9 +272,12 @@ func (c *Compiler) Compile(mainPath string) []error { } // Add definitions to declarations. + var initFuncs []llvm.Value + irbuilder := c.ctx.NewBuilder() + defer irbuilder.Dispose() for _, f := range c.ir.Functions { if f.Synthetic == "package initializer" { - c.initFuncs = append(c.initFuncs, f.LLVMFn) + initFuncs = append(initFuncs, f.LLVMFn) } if f.CName() != "" { continue @@ -294,8 +288,8 @@ func (c *Compiler) Compile(mainPath string) []error { // Create the function definition. b := builder{ - compilerContext: &c.compilerContext, - Builder: c.builder, + compilerContext: c, + Builder: irbuilder, fn: f, locals: make(map[ssa.Value]llvm.Value), dilocals: make(map[*types.Var]llvm.Metadata), @@ -313,14 +307,14 @@ func (c *Compiler) Compile(mainPath string) []error { if c.Debug() { difunc := c.attachDebugInfo(initFn) pos := c.ir.Program.Fset.Position(initFn.Pos()) - c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) + irbuilder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } block := c.ctx.AddBasicBlock(initFn.LLVMFn, "entry") - c.builder.SetInsertPointAtEnd(block) - for _, fn := range c.initFuncs { - c.builder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "") + irbuilder.SetInsertPointAtEnd(block) + for _, fn := range initFuncs { + irbuilder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "") } - c.builder.CreateRetVoid() + irbuilder.CreateRetVoid() // Conserve for goroutine lowering. Without marking these as external, they // would be optimized away. @@ -392,7 +386,15 @@ func (c *Compiler) Compile(mainPath string) []error { c.dibuilder.Finalize() } - return c.diagnostics + // Gather the list of (C) file paths that should be included in the build. + var extraFiles []string + for _, pkg := range c.ir.LoaderProgram.Sorted() { + for _, file := range pkg.CFiles { + extraFiles = append(extraFiles, filepath.Join(pkg.Package.Dir, file)) + } + } + + return c.mod, extraFiles, c.diagnostics } // getLLVMRuntimeType obtains a named type from the runtime package and returns @@ -2577,47 +2579,3 @@ func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) { return llvm.Value{}, b.makeError(unop.Pos(), "todo: unknown unop") } } - -// IR returns the whole IR as a human-readable string. -func (c *Compiler) IR() string { - return c.mod.String() -} - -func (c *Compiler) Verify() error { - return llvm.VerifyModule(c.mod, llvm.PrintMessageAction) -} - -// Emit object file (.o). -func (c *Compiler) EmitObject(path string) error { - llvmBuf, err := c.machine.EmitToMemoryBuffer(c.mod, llvm.ObjectFile) - if err != nil { - return err - } - return c.writeFile(llvmBuf.Bytes(), path) -} - -// Emit LLVM bitcode file (.bc). -func (c *Compiler) EmitBitcode(path string) error { - data := llvm.WriteBitcodeToMemoryBuffer(c.mod).Bytes() - return c.writeFile(data, path) -} - -// Emit LLVM IR source file (.ll). -func (c *Compiler) EmitText(path string) error { - data := []byte(c.mod.String()) - return c.writeFile(data, path) -} - -// Write the data to the file specified by path. -func (c *Compiler) writeFile(data []byte, path string) error { - // Write output to file - f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) - if err != nil { - return err - } - _, err = f.Write(data) - if err != nil { - return err - } - return f.Close() -} diff --git a/compiler/symbol.go b/compiler/symbol.go index 716fc86b65..ec7cd34092 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -26,7 +26,7 @@ type globalInfo struct { // loadASTComments loads comments on globals from the AST, for use later in the // program. In particular, they are required for //go:extern pragmas on globals. -func (c *Compiler) loadASTComments(lprogram *loader.Program) { +func (c *compilerContext) loadASTComments(lprogram *loader.Program) { c.astComments = map[string]*ast.CommentGroup{} for _, pkgInfo := range lprogram.Sorted() { for _, file := range pkgInfo.Files {