From 7460e10894cf0244208372bdf4cf375aaf77eaf3 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 19 Aug 2018 20:44:55 +0200 Subject: [PATCH] Add dead code elimination (DCE) pass This should make it much easier (in the future) to use the standard library when unused functions contain unimplemented features. But more importantly, it makes later passes better and makes compiling faster by not having to scan dead code. --- compiler.go | 1 + ir.go | 2 ++ passes.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/compiler.go b/compiler.go index 137541ff6..297fb4549 100644 --- a/compiler.go +++ b/compiler.go @@ -205,6 +205,7 @@ func (c *Compiler) Parse(mainPath string, buildTags []string) error { for _, pkg := range packageList { c.ir.AddPackage(pkg) } + c.ir.SimpleDCE() // remove most dead code c.ir.AnalyseCallgraph() // set up callgraph c.ir.AnalyseInterfaceConversions() // determine which types are converted to an interface c.ir.AnalyseBlockingRecursive() // make all parents of blocking calls blocking (transitively) diff --git a/ir.go b/ir.go index 80f5bcd6a..4d628e937 100644 --- a/ir.go +++ b/ir.go @@ -31,6 +31,7 @@ type Function struct { fn *ssa.Function llvmFn llvm.Value blocking bool + flag bool // used by dead code elimination parents []*Function // calculated by AnalyseCallgraph children []*Function } @@ -39,6 +40,7 @@ type Function struct { type Global struct { g *ssa.Global llvmGlobal llvm.Value + flag bool // used by dead code elimination } // Type with a name and possibly methods. diff --git a/passes.go b/passes.go index 838d7ccb8..1de495378 100644 --- a/passes.go +++ b/passes.go @@ -180,6 +180,92 @@ func (p *Program) AnalyseGoCalls() { } } +// Simple pass that removes dead code. This pass makes later analysis passes +// more useful. +func (p *Program) SimpleDCE() { + // Unmark all functions and globals. + for _, f := range p.Functions { + f.flag = false + } + for _, f := range p.Globals { + f.flag = false + } + + // Initial set of live functions. Include main.main, *.init and runtime.* + // functions. + main := p.mainPkg.Members["main"].(*ssa.Function) + runtimePkg := p.program.ImportedPackage("runtime") + p.GetFunction(main).flag = true + worklist := []*ssa.Function{main} + for _, f := range p.Functions { + if f.fn.Synthetic == "package initializer" || f.fn.Pkg == runtimePkg { + if f.flag || isCGoInternal(f.fn.Name()) { + continue + } + f.flag = true + worklist = append(worklist, f.fn) + } + } + + // 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(p.program, instr.X.Type()) { + callee := p.GetFunction(p.program.MethodValue(sel)) + if !callee.flag { + callee.flag = true + worklist = append(worklist, callee.fn) + } + } + } + for _, operand := range instr.Operands(nil) { + if operand == nil || *operand == nil || isCGoInternal((*operand).Name()) { + continue + } + switch operand := (*operand).(type) { + case *ssa.Function: + f := p.GetFunction(operand) + if !f.flag { + f.flag = true + worklist = append(worklist, operand) + } + case *ssa.Global: + // TODO: globals that reference other globals + global := p.GetGlobal(operand) + global.flag = true + } + } + } + } + } + + // Remove unmarked functions. + livefunctions := []*Function{p.GetFunction(main)} + for _, f := range p.Functions { + if f.flag { + livefunctions = append(livefunctions, f) + } else { + delete(p.functionMap, f.fn) + } + } + p.Functions = livefunctions + + // Remove unmarked globals. + liveglobals := []*Global{} + for _, g := range p.Globals { + if g.flag { + liveglobals = append(liveglobals, g) + } else { + delete(p.globalMap, g.g) + } + } + p.Globals = liveglobals +} + // Whether this function needs a scheduler. // // Depends on AnalyseGoCalls.