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.
Этот коммит содержится в:
родитель
b5205cc3ca
коммит
dc1ff80e10
3 изменённых файлов: 132 добавлений и 245 удалений
|
@ -90,6 +90,7 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *C
|
|||
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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,10 +304,8 @@ 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 {
|
||||
func (c *compilerContext) loadASTComments(pkg *loader.Package) {
|
||||
for _, file := range pkg.Files {
|
||||
for _, decl := range file.Decls {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
|
@ -305,7 +318,7 @@ func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
|
|||
switch spec := spec.(type) {
|
||||
case *ast.ValueSpec: // decl.Tok == token.VAR
|
||||
for _, name := range spec.Names {
|
||||
id := pkgInfo.Pkg.Path() + "." + name.Name
|
||||
id := pkg.Pkg.Path() + "." + name.Name
|
||||
c.astComments[id] = decl.Doc
|
||||
}
|
||||
}
|
||||
|
@ -314,7 +327,6 @@ func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to
|
||||
|
@ -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),
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче