From b5e29bf0c194ca327dc8b43fd32beddfd6290e28 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 21 Nov 2019 14:20:12 +0100 Subject: [PATCH] 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 40d9443aa..e185d3c39 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 63b1dc920..030e67343 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 b2d8cc766..37205f01f 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 b79b17f82..b55d5240d 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 761a45c5c..f1599cd20 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 {