From dc1ff80e10b8d4ec414d7a7491477f1768917ddc Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 31 Jan 2021 17:56:26 +0100 Subject: [PATCH] compiler: remove SimpleDCE pass The SimpleDCE pass was previously used to only compile the parts of the program that were in use. However, lately the only real purpose has been to speed up the compiler a bit by only compiling the necessary functions. This pass however is a problem for compiling (and caching) packages in parallel. Therefore, this commit removes it as a preparatory step towards that goal. --- compiler/compiler.go | 157 ++++++++++++++++++++++++++--------------- compiler/passes.go | 164 ------------------------------------------- compiler/symbol.go | 56 ++++++++------- 3 files changed, 132 insertions(+), 245 deletions(-) delete mode 100644 compiler/passes.go diff --git a/compiler/compiler.go b/compiler/compiler.go index faa5038a..e25cc673 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -84,12 +84,13 @@ type compilerContext struct { // importantly with a newly created LLVM context and module. func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *Config, dumpSSA bool) *compilerContext { c := &compilerContext{ - Config: config, - DumpSSA: dumpSSA, - difiles: make(map[string]llvm.Metadata), - ditypes: make(map[types.Type]llvm.Metadata), - machine: machine, - targetData: machine.CreateTargetData(), + Config: config, + DumpSSA: dumpSSA, + difiles: make(map[string]llvm.Metadata), + ditypes: make(map[types.Type]llvm.Metadata), + machine: machine, + targetData: machine.CreateTargetData(), + astComments: map[string]*ast.CommentGroup{}, } c.ctx = llvm.NewContext() @@ -251,12 +252,6 @@ func CompileProgram(lprogram *loader.Program, machine llvm.TargetMachine, config c.program.Build() c.runtimePkg = c.program.ImportedPackage("runtime").Pkg - // Run a simple dead code elimination pass. - functions, err := c.simpleDCE(lprogram) - if err != nil { - return llvm.Module{}, []error{err} - } - // Initialize debug information. if c.Debug { c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{ @@ -268,27 +263,22 @@ func CompileProgram(lprogram *loader.Program, machine llvm.TargetMachine, config }) } - c.loadASTComments(lprogram) + for _, pkg := range lprogram.Sorted() { + c.loadASTComments(pkg) + } // Predeclare the runtime.alloc function, which is used by the wordpack // functionality. c.getFunction(c.program.ImportedPackage("runtime").Members["alloc"].(*ssa.Function)) - // Add definitions to declarations. + // Define all functions. var initFuncs []llvm.Value irbuilder := c.ctx.NewBuilder() defer irbuilder.Dispose() - for _, f := range functions { - if f.Synthetic == "package initializer" { - initFuncs = append(initFuncs, c.getFunction(f)) - } - if f.Blocks == nil { - continue // external function - } - - // Create the function definition. - b := newBuilder(c, irbuilder, f) - b.createFunction() + for _, pkg := range lprogram.Sorted() { + ssaPkg := c.program.Package(pkg.Pkg) + c.createPackage(irbuilder, ssaPkg) + initFuncs = append(initFuncs, c.getFunction(ssaPkg.Members["init"].(*ssa.Function))) } // After all packages are imported, add a synthetic initializer function @@ -341,38 +331,10 @@ func CompilePackage(moduleName string, pkg *loader.Package, machine llvm.TargetM ssaPkg := pkg.LoadSSA() ssaPkg.Build() - // Sort by position, so that the order of the functions in the IR matches - // the order of functions in the source file. This is useful for testing, - // for example. - var members []string - for name := range ssaPkg.Members { - members = append(members, name) - } - sort.Slice(members, func(i, j int) bool { - iPos := ssaPkg.Members[members[i]].Pos() - jPos := ssaPkg.Members[members[j]].Pos() - if i == j { - // Cannot sort by pos, so do it by name. - return members[i] < members[j] - } - return iPos < jPos - }) - - // Define all functions. + // Compile all functions, methods, and global variables in this package. irbuilder := c.ctx.NewBuilder() defer irbuilder.Dispose() - for _, name := range members { - member := ssaPkg.Members[name] - switch member := member.(type) { - case *ssa.Function: - if member.Blocks == nil { - continue // external function - } - // Create the function definition. - b := newBuilder(c, irbuilder, member) - b.createFunction() - } - } + c.createPackage(irbuilder, ssaPkg) return c.mod, nil } @@ -759,6 +721,83 @@ func (c *compilerContext) getDIFile(filename string) llvm.Metadata { return c.difiles[filename] } +// createPackage builds the LLVM IR for all types, methods, and global variables +// in the given package. +func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package) { + // Sort by position, so that the order of the functions in the IR matches + // the order of functions in the source file. This is useful for testing, + // for example. + var members []string + for name := range pkg.Members { + members = append(members, name) + } + sort.Slice(members, func(i, j int) bool { + iPos := pkg.Members[members[i]].Pos() + jPos := pkg.Members[members[j]].Pos() + if i == j { + // Cannot sort by pos, so do it by name. + return members[i] < members[j] + } + return iPos < jPos + }) + + // Define all functions. + for _, name := range members { + member := pkg.Members[name] + switch member := member.(type) { + case *ssa.Function: + if member.Blocks == nil { + continue // external function + } + // Create the function definition. + b := newBuilder(c, irbuilder, member) + b.createFunction() + case *ssa.Type: + if types.IsInterface(member.Type()) { + // Interfaces don't have concrete methods. + continue + } + + // Named type. We should make sure all methods are created. + // This includes both functions with pointer receivers and those + // without. + methods := getAllMethods(pkg.Prog, member.Type()) + methods = append(methods, getAllMethods(pkg.Prog, types.NewPointer(member.Type()))...) + for _, method := range methods { + // Parse this method. + fn := pkg.Prog.MethodValue(method) + if fn.Blocks == nil { + continue // external function + } + if member.Type().String() != member.String() { + // This is a member on a type alias. Do not build such a + // function. + continue + } + if fn.Synthetic != "" && fn.Synthetic != "package initializer" { + // This function is a kind of wrapper function (created by + // the ssa package, not appearing in the source code) that + // is created by the getFunction method as needed. + // Therefore, don't build it here to avoid "function + // redeclared" errors. + continue + } + // Create the function definition. + b := newBuilder(c, irbuilder, fn) + b.createFunction() + } + case *ssa.Global: + // Global variable. + info := c.getGlobalInfo(member) + if !info.extern { + global := c.getGlobal(member) + global.SetInitializer(llvm.ConstNull(global.Type().ElementType())) + global.SetLinkage(llvm.InternalLinkage) + } + } + } +} + // createFunction builds the LLVM IR implementation for this function. The // function must not yet be defined, otherwise this function will create a // diagnostic. @@ -767,7 +806,7 @@ func (b *builder) createFunction() { fmt.Printf("\nfunc %s:\n", b.fn) } if !b.llvmFn.IsDeclaration() { - errValue := b.fn.Name() + " redeclared in this program" + errValue := b.llvmFn.Name() + " redeclared in this program" fnPos := getPosition(b.llvmFn) if fnPos.IsValid() { errValue += "\n\tprevious declaration at " + fnPos.String() @@ -948,6 +987,12 @@ func (b *builder) createFunction() { b.trackValue(phi.llvm) } } + + // Create anonymous functions (closures etc.). + for _, sub := range b.fn.AnonFuncs { + b := newBuilder(b.compilerContext, b.Builder, sub) + b.createFunction() + } } // createInstruction builds the LLVM IR equivalent instructions for the diff --git a/compiler/passes.go b/compiler/passes.go deleted file mode 100644 index 092f5f0b..00000000 --- a/compiler/passes.go +++ /dev/null @@ -1,164 +0,0 @@ -package compiler - -// This file implements a simple reachability analysis, to reduce compile time. -// This DCE pass used to be necessary for improving other passes but now it -// isn't necessary anymore. - -import ( - "errors" - "go/types" - "sort" - - "github.com/tinygo-org/tinygo/loader" - "golang.org/x/tools/go/ssa" -) - -type dceState struct { - *compilerContext - functions []*dceFunction - functionMap map[*ssa.Function]*dceFunction -} - -type dceFunction struct { - *ssa.Function - functionInfo - flag bool // used by dead code elimination -} - -func (p *dceState) addFunction(ssaFn *ssa.Function) { - if _, ok := p.functionMap[ssaFn]; ok { - return - } - f := &dceFunction{Function: ssaFn} - f.functionInfo = p.getFunctionInfo(ssaFn) - p.functions = append(p.functions, f) - p.functionMap[ssaFn] = f - - for _, anon := range ssaFn.AnonFuncs { - p.addFunction(anon) - } -} - -// simpleDCE returns a list of alive functions in the program. Compiling only -// these functions makes the compiler faster. -// -// This functionality will likely be replaced in the future with build caching. -func (c *compilerContext) simpleDCE(lprogram *loader.Program) ([]*ssa.Function, error) { - mainPkg := c.program.Package(lprogram.MainPkg().Pkg) - if mainPkg == nil { - panic("could not find main package") - } - p := &dceState{ - compilerContext: c, - functionMap: make(map[*ssa.Function]*dceFunction), - } - - for _, pkg := range lprogram.Sorted() { - pkg := c.program.Package(pkg.Pkg) - memberNames := make([]string, 0) - for name := range pkg.Members { - memberNames = append(memberNames, name) - } - sort.Strings(memberNames) - - for _, name := range memberNames { - member := pkg.Members[name] - switch member := member.(type) { - case *ssa.Function: - p.addFunction(member) - case *ssa.Type: - methods := getAllMethods(pkg.Prog, member.Type()) - if !types.IsInterface(member.Type()) { - // named type - for _, method := range methods { - p.addFunction(pkg.Prog.MethodValue(method)) - } - } - case *ssa.Global: - // Ignore. Globals are not handled here. - case *ssa.NamedConst: - // Ignore: these are already resolved. - default: - panic("unknown member type: " + member.String()) - } - } - } - - // Initial set of live functions. Include main.main, *.init and runtime.* - // functions. - main, ok := mainPkg.Members["main"].(*ssa.Function) - if !ok { - if mainPkg.Members["main"] == nil { - return nil, errors.New("function main is undeclared in the main package") - } else { - return nil, errors.New("cannot declare main - must be func") - } - } - runtimePkg := c.program.ImportedPackage("runtime") - mathPkg := c.program.ImportedPackage("math") - taskPkg := c.program.ImportedPackage("internal/task") - p.functionMap[main].flag = true - worklist := []*ssa.Function{main} - for _, f := range p.functions { - if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) { - if f.flag { - continue - } - f.flag = true - worklist = append(worklist, f.Function) - } - } - - // Mark all called functions recursively. - for len(worklist) != 0 { - f := worklist[len(worklist)-1] - worklist = worklist[:len(worklist)-1] - for _, block := range f.Blocks { - for _, instr := range block.Instrs { - if instr, ok := instr.(*ssa.MakeInterface); ok { - for _, sel := range getAllMethods(c.program, instr.X.Type()) { - fn := c.program.MethodValue(sel) - callee := p.functionMap[fn] - if callee == nil { - // TODO: why is this necessary? - p.addFunction(fn) - callee = p.functionMap[fn] - } - if !callee.flag { - callee.flag = true - worklist = append(worklist, callee.Function) - } - } - } - for _, operand := range instr.Operands(nil) { - if operand == nil || *operand == nil { - continue - } - switch operand := (*operand).(type) { - case *ssa.Function: - f := p.functionMap[operand] - if f == nil { - // FIXME HACK: this function should have been - // discovered already. It is not for bound methods. - p.addFunction(operand) - f = p.functionMap[operand] - } - if !f.flag { - f.flag = true - worklist = append(worklist, operand) - } - } - } - } - } - } - - // Return all live functions. - liveFunctions := []*ssa.Function{} - for _, f := range p.functions { - if f.flag { - liveFunctions = append(liveFunctions, f.Function) - } - } - return liveFunctions, nil -} diff --git a/compiler/symbol.go b/compiler/symbol.go index b819b5ab..ea0b01d1 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -172,6 +172,21 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value { } } + // Synthetic functions are functions that do not appear in the source code, + // they are artificially constructed. Usually they are wrapper functions + // that are not referenced anywhere except in a SSA call instruction so + // should be created right away. + // The exception is the package initializer, which does appear in the + // *ssa.Package members and so shouldn't be created here. + if fn.Synthetic != "" && fn.Synthetic != "package initializer" { + irbuilder := c.ctx.NewBuilder() + b := newBuilder(c, irbuilder, fn) + b.createFunction() + irbuilder.Dispose() + llvmFn.SetLinkage(llvm.InternalLinkage) + llvmFn.SetUnnamedAddr(true) + } + return llvmFn } @@ -289,25 +304,22 @@ 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 *compilerContext) loadASTComments(lprogram *loader.Program) { - c.astComments = map[string]*ast.CommentGroup{} - for _, pkgInfo := range lprogram.Sorted() { - for _, file := range pkgInfo.Files { - for _, decl := range file.Decls { - switch decl := decl.(type) { - case *ast.GenDecl: - switch decl.Tok { - case token.VAR: - if len(decl.Specs) != 1 { - continue - } - for _, spec := range decl.Specs { - switch spec := spec.(type) { - case *ast.ValueSpec: // decl.Tok == token.VAR - for _, name := range spec.Names { - id := pkgInfo.Pkg.Path() + "." + name.Name - c.astComments[id] = decl.Doc - } +func (c *compilerContext) loadASTComments(pkg *loader.Package) { + for _, file := range pkg.Files { + for _, decl := range file.Decls { + switch decl := decl.(type) { + case *ast.GenDecl: + switch decl.Tok { + case token.VAR: + if len(decl.Specs) != 1 { + continue + } + for _, spec := range decl.Specs { + switch spec := spec.(type) { + case *ast.ValueSpec: // decl.Tok == token.VAR + for _, name := range spec.Names { + id := pkg.Pkg.Path() + "." + name.Name + c.astComments[id] = decl.Doc } } } @@ -326,10 +338,6 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value { typ := g.Type().(*types.Pointer).Elem() llvmType := c.getLLVMType(typ) llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName) - if !info.extern { - llvmGlobal.SetInitializer(llvm.ConstNull(llvmType)) - llvmGlobal.SetLinkage(llvm.InternalLinkage) - } // Set alignment from the //go:align comment. var alignInBits uint32 @@ -347,8 +355,6 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value { if c.Debug && !info.extern { // Add debug info. - // TODO: this should be done for every global in the program, not just - // the ones that are referenced from some code. pos := c.program.Fset.Position(g.Pos()) diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{ Name: g.RelString(nil),