From dd04f34059479dc3cc06445045fa26357dc07335 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 11 Apr 2020 17:20:46 +0200 Subject: [PATCH] compiler: integrate ir package into compiler package The ir package has long lost its original purpose (which was doing some analysis and optimization on the Go SSA directly). There is very little of it left, which is best integrated directly in the compiler package to avoid unnecessary abstraction. --- Makefile | 2 +- compiler/calls.go | 2 +- compiler/compiler.go | 107 ++++++++++++++++++++++++------ compiler/errors.go | 2 +- compiler/goroutine.go | 6 +- compiler/interface.go | 56 ++++++++++++++-- compiler/interrupt.go | 4 +- compiler/symbol.go | 2 +- ir/ir.go | 151 ------------------------------------------ 9 files changed, 148 insertions(+), 184 deletions(-) delete mode 100644 ir/ir.go diff --git a/Makefile b/Makefile index 351270c8f..638488e45 100644 --- a/Makefile +++ b/Makefile @@ -111,7 +111,7 @@ endif clean: @rm -rf build -FMT_PATHS = ./*.go builder cgo compiler compiler/testdata interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform +FMT_PATHS = ./*.go builder cgo compiler compiler/testdata interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform fmt: @gofmt -l -w $(FMT_PATHS) fmt-check: diff --git a/compiler/calls.go b/compiler/calls.go index da92e8682..87eab6048 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -35,7 +35,7 @@ const ( // createCall creates a new call to runtime. with the given arguments. func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value { - fn := b.ir.Program.ImportedPackage("runtime").Members[fnName].(*ssa.Function) + fn := b.program.ImportedPackage("runtime").Members[fnName].(*ssa.Function) llvmFn := b.getFunction(fn) if llvmFn.IsNil() { panic("trying to call non-existent function: " + fn.RelString(nil)) diff --git a/compiler/compiler.go b/compiler/compiler.go index 18c5d5c40..45bba1146 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -18,7 +18,6 @@ import ( "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/goenv" - "github.com/tinygo-org/tinygo/ir" "github.com/tinygo-org/tinygo/loader" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" @@ -52,7 +51,7 @@ type compilerContext struct { i8ptrType llvm.Type // for convenience funcPtrAddrSpace int uintptrType llvm.Type - ir *ir.Program + program *ssa.Program diagnostics []error astComments map[string]*ast.CommentGroup } @@ -245,7 +244,8 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con return c.mod, nil, []error{err} } - c.ir = ir.NewProgram(lprogram, pkgName) + c.program = lprogram.LoadSSA() + c.program.Build() // Initialize debug information. if c.Debug() { @@ -264,7 +264,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con // 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 { + for _, member := range c.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 { @@ -276,11 +276,13 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con // Predeclare the runtime.alloc function, which is used by the wordpack // functionality. - c.getFunction(c.ir.Program.ImportedPackage("runtime").Members["alloc"].(*ssa.Function)) + c.getFunction(c.program.ImportedPackage("runtime").Members["alloc"].(*ssa.Function)) + + sortedPackages := sortPackages(c.program, pkgName) // Find package initializers. var initFuncs []llvm.Value - for _, pkg := range c.ir.Packages() { + for _, pkg := range sortedPackages { for _, member := range pkg.Members { switch member := member.(type) { case *ssa.Function: @@ -294,19 +296,19 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con // Add definitions to declarations. irbuilder := c.ctx.NewBuilder() defer irbuilder.Dispose() - for _, pkg := range c.ir.Packages() { + for _, pkg := range sortedPackages { c.createPackage(pkg, irbuilder) } // After all packages are imported, add a synthetic initializer function // that calls the initializer of each package. - initFn := c.ir.Program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function) + initFn := c.program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function) llvmInitFn := c.getFunction(initFn) llvmInitFn.SetLinkage(llvm.InternalLinkage) llvmInitFn.SetUnnamedAddr(true) if c.Debug() { difunc := c.attachDebugInfo(initFn) - pos := c.ir.Program.Fset.Position(initFn.Pos()) + pos := c.program.Fset.Position(initFn.Pos()) irbuilder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } block := c.ctx.AddBasicBlock(llvmInitFn, "entry") @@ -318,7 +320,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con // Conserve for goroutine lowering. Without marking these as external, they // would be optimized away. - realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main") + realMain := c.mod.NamedFunction(pkgName + ".main") realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering // Replace callMain placeholder with actual main function. @@ -374,7 +376,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con // 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 _, pkg := range lprogram.Sorted() { for _, file := range pkg.CFiles { extraFiles = append(extraFiles, filepath.Join(pkg.Package.Dir, file)) } @@ -383,6 +385,73 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con return c.mod, extraFiles, c.diagnostics } +// sortPackages returns a list of all packages, sorted by import order. +func sortPackages(program *ssa.Program, mainPath string) []*ssa.Package { + // Find the main package, which is a bit difficult when running a .go file + // directly. + mainPkg := program.ImportedPackage(mainPath) + if mainPkg == nil { + for _, pkgInfo := range program.AllPackages() { + if pkgInfo.Pkg.Name() == "main" { + if mainPkg != nil { + panic("more than one main package found") + } + mainPkg = pkgInfo + } + } + } + if mainPkg == nil { + panic("could not find main package") + } + + packageList := []*ssa.Package{} + packageSet := map[string]struct{}{} + worklist := []string{"runtime", mainPath} + for len(worklist) != 0 { + pkgPath := worklist[0] + var pkg *ssa.Package + if pkgPath == mainPath { + pkg = mainPkg // necessary for compiling individual .go files + } else { + pkg = program.ImportedPackage(pkgPath) + } + if pkg == nil { + // Non-SSA package (e.g. cgo). + packageSet[pkgPath] = struct{}{} + worklist = worklist[1:] + continue + } + if _, ok := packageSet[pkgPath]; ok { + // Package already in the final package list. + worklist = worklist[1:] + continue + } + + unsatisfiedImports := make([]string, 0) + imports := pkg.Pkg.Imports() + for _, pkg := range imports { + if _, ok := packageSet[pkg.Path()]; ok { + continue + } + unsatisfiedImports = append(unsatisfiedImports, pkg.Path()) + } + if len(unsatisfiedImports) == 0 { + // All dependencies of this package are satisfied, so add this + // package to the list. + packageList = append(packageList, pkg) + packageSet[pkgPath] = struct{}{} + worklist = worklist[1:] + } else { + // Prepend all dependencies to the worklist and reconsider this + // package (by not removing it from the worklist). At that point, it + // must be possible to add it to packageList. + worklist = append(unsatisfiedImports, worklist...) + } + } + + return packageList +} + // 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)). @@ -576,11 +645,11 @@ func (c *compilerContext) createDIType(typ types.Type) llvm.Metadata { Encoding: encoding, }) case *types.Chan: - return c.getDIType(types.NewPointer(c.ir.Program.ImportedPackage("runtime").Members["channel"].(*ssa.Type).Type())) + return c.getDIType(types.NewPointer(c.program.ImportedPackage("runtime").Members["channel"].(*ssa.Type).Type())) case *types.Interface: - return c.getDIType(c.ir.Program.ImportedPackage("runtime").Members["_interface"].(*ssa.Type).Type()) + return c.getDIType(c.program.ImportedPackage("runtime").Members["_interface"].(*ssa.Type).Type()) case *types.Map: - return c.getDIType(types.NewPointer(c.ir.Program.ImportedPackage("runtime").Members["hashmap"].(*ssa.Type).Type())) + return c.getDIType(types.NewPointer(c.program.ImportedPackage("runtime").Members["hashmap"].(*ssa.Type).Type())) case *types.Named: return c.dibuilder.CreateTypedef(llvm.DITypedef{ Type: c.getDIType(typ.Underlying()), @@ -688,7 +757,7 @@ func (b *builder) getLocalVariable(variable *types.Var) llvm.Metadata { return dilocal } - pos := b.ir.Program.Fset.Position(variable.Pos()) + pos := b.program.Fset.Position(variable.Pos()) // Check whether this is a function parameter. for i, param := range b.fn.Params { @@ -722,7 +791,7 @@ func (b *builder) getLocalVariable(variable *types.Var) llvm.Metadata { // attachDebugInfo adds debug info to a function declaration. It returns the // DISubprogram metadata node. func (c *compilerContext) attachDebugInfo(f *ssa.Function) llvm.Metadata { - pos := c.ir.Program.Fset.Position(f.Syntax().Pos()) + pos := c.program.Fset.Position(f.Syntax().Pos()) return c.attachDebugInfoRaw(f, c.getFunction(f), "", pos.Filename, pos.Line) } @@ -876,7 +945,7 @@ func (c *compilerContext) createFunction(irbuilder llvm.Builder, fn *ssa.Functio // Create debug info file if needed. b.difunc = b.attachDebugInfo(b.fn) } - pos := b.ir.Program.Fset.Position(b.fn.Pos()) + pos := b.program.Fset.Position(b.fn.Pos()) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{}) } @@ -978,7 +1047,7 @@ func (c *compilerContext) createFunction(irbuilder llvm.Builder, fn *ssa.Functio continue } dbgVar := b.getLocalVariable(variable) - pos := b.ir.Program.Fset.Position(instr.Pos()) + pos := b.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), @@ -1032,7 +1101,7 @@ func (c *compilerContext) createFunction(irbuilder llvm.Builder, fn *ssa.Functio // particular Go SSA instruction. func (b *builder) createInstruction(instr ssa.Instruction) { if b.Debug() { - pos := b.ir.Program.Fset.Position(instr.Pos()) + pos := b.program.Fset.Position(instr.Pos()) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{}) } diff --git a/compiler/errors.go b/compiler/errors.go index 85de4d161..118132298 100644 --- a/compiler/errors.go +++ b/compiler/errors.go @@ -14,7 +14,7 @@ import ( // 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, + Fset: c.program.Fset, Pos: pos, Msg: msg, } diff --git a/compiler/goroutine.go b/compiler/goroutine.go index ca9afbb04..1863fcb2b 100644 --- a/compiler/goroutine.go +++ b/compiler/goroutine.go @@ -29,7 +29,7 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p default: panic("unreachable") } - start := b.getFunction(b.ir.Program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) + start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) b.createCall(start, []llvm.Value{callee, paramBundle, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "") return llvm.Undef(funcPtr.Type().ElementType().ReturnType()) } @@ -75,7 +75,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri builder.SetInsertPointAtEnd(entry) if c.Debug() { - pos := c.ir.Program.Fset.Position(pos) + pos := c.program.Fset.Position(pos) diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ File: c.getDIFile(pos.Filename), Parameters: nil, // do not show parameters in debugger @@ -131,7 +131,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri builder.SetInsertPointAtEnd(entry) if c.Debug() { - pos := c.ir.Program.Fset.Position(pos) + pos := c.program.Fset.Position(pos) diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ File: c.getDIFile(pos.Filename), Parameters: nil, // do not show parameters in debugger diff --git a/compiler/interface.go b/compiler/interface.go index 8b764fa36..72df707d7 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -11,7 +11,6 @@ import ( "strconv" "strings" - "github.com/tinygo-org/tinygo/ir" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -236,7 +235,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { return llvm.ConstGEP(global, []llvm.Value{zero, zero}) } - ms := c.ir.Program.MethodSets.MethodSet(typ) + ms := c.program.MethodSets.MethodSet(typ) if ms.Len() == 0 { // no methods, so can leave that one out return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0)) @@ -247,7 +246,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { for i := 0; i < ms.Len(); i++ { method := ms.At(i) signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func)) - fn := c.ir.Program.MethodValue(method) + fn := c.program.MethodValue(method) llvmFn := c.getFunction(fn) if llvmFn.IsNil() { // compiler error, so panic @@ -311,7 +310,7 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value { // external *i8 indicating the indicating the signature of this method. It is // used during the interface lowering pass. func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value { - signature := ir.MethodSignature(method) + signature := methodSignature(method) signatureGlobal := c.mod.NamedGlobal("func " + signature) if signatureGlobal.IsNil() { signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature) @@ -489,7 +488,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv // add debug info if needed if c.Debug() { - pos := c.ir.Program.Fset.Position(fn.Pos()) + pos := c.program.Fset.Position(fn.Pos()) difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } @@ -522,3 +521,50 @@ func isAnonymous(typ types.Type) bool { } return false } + +// methodSignature creates a readable version of a method signature (including +// the function name, excluding the receiver name). This string is used +// internally to match interfaces and to call the correct method on an +// interface. Examples: +// +// String() string +// Read([]byte) (int, error) +func methodSignature(method *types.Func) string { + return method.Name() + signature(method.Type().(*types.Signature)) +} + +// Make a readable version of a function (pointer) signature. +// Examples: +// +// () string +// (string, int) (int, error) +func signature(sig *types.Signature) string { + s := "" + if sig.Params().Len() == 0 { + s += "()" + } else { + s += "(" + for i := 0; i < sig.Params().Len(); i++ { + if i > 0 { + s += ", " + } + s += sig.Params().At(i).Type().String() + } + s += ")" + } + if sig.Results().Len() == 0 { + // keep as-is + } else if sig.Results().Len() == 1 { + s += " " + sig.Results().At(0).Type().String() + } else { + s += " (" + for i := 0; i < sig.Results().Len(); i++ { + if i > 0 { + s += ", " + } + s += sig.Results().At(i).Type().String() + } + s += ")" + } + return s +} diff --git a/compiler/interrupt.go b/compiler/interrupt.go index e4e31c080..82585d56b 100644 --- a/compiler/interrupt.go +++ b/compiler/interrupt.go @@ -39,7 +39,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro // Create a new global of type runtime/interrupt.handle. Globals of this // type are lowered in the interrupt lowering pass. - globalType := b.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type() + globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type() globalLLVMType := b.getLLVMType(globalType) globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10) if global := b.mod.NamedGlobal(globalName); !global.IsNil() { @@ -56,7 +56,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro // Add debug info to the interrupt global. if b.Debug() { - pos := b.ir.Program.Fset.Position(instr.Pos()) + pos := b.program.Fset.Position(instr.Pos()) diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{ Name: "interrupt" + strconv.FormatInt(id.Int64(), 10), LinkageName: globalName, diff --git a/compiler/symbol.go b/compiler/symbol.go index 2502d3038..25b27a947 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -277,7 +277,7 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value { // 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.ir.Program.Fset.Position(g.Pos()) + pos := c.program.Fset.Position(g.Pos()) diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{ Name: g.RelString(nil), LinkageName: info.linkName, diff --git a/ir/ir.go b/ir/ir.go deleted file mode 100644 index 5f7209342..000000000 --- a/ir/ir.go +++ /dev/null @@ -1,151 +0,0 @@ -package ir - -import ( - "go/types" - - "github.com/tinygo-org/tinygo/loader" - "golang.org/x/tools/go/ssa" -) - -// This file provides a wrapper around go/ssa values and adds extra -// functionality to them. - -// View on all functions, types, and globals in a program, with analysis -// results. -type Program struct { - Program *ssa.Program - LoaderProgram *loader.Program - mainPkg *ssa.Package - mainPath string -} - -// Create and initialize a new *Program from a *ssa.Program. -func NewProgram(lprogram *loader.Program, mainPath string) *Program { - program := lprogram.LoadSSA() - program.Build() - - // Find the main package, which is a bit difficult when running a .go file - // directly. - mainPkg := program.ImportedPackage(mainPath) - if mainPkg == nil { - for _, pkgInfo := range program.AllPackages() { - if pkgInfo.Pkg.Name() == "main" { - if mainPkg != nil { - panic("more than one main package found") - } - mainPkg = pkgInfo - } - } - } - if mainPkg == nil { - panic("could not find main package") - } - - return &Program{ - Program: program, - LoaderProgram: lprogram, - mainPkg: mainPkg, - mainPath: mainPath, - } -} - -// Packages returns a list of all packages, sorted by import order. -func (p *Program) Packages() []*ssa.Package { - packageList := []*ssa.Package{} - packageSet := map[string]struct{}{} - worklist := []string{"runtime", p.mainPath} - for len(worklist) != 0 { - pkgPath := worklist[0] - var pkg *ssa.Package - if pkgPath == p.mainPath { - pkg = p.mainPkg // necessary for compiling individual .go files - } else { - pkg = p.Program.ImportedPackage(pkgPath) - } - if pkg == nil { - // Non-SSA package (e.g. cgo). - packageSet[pkgPath] = struct{}{} - worklist = worklist[1:] - continue - } - if _, ok := packageSet[pkgPath]; ok { - // Package already in the final package list. - worklist = worklist[1:] - continue - } - - unsatisfiedImports := make([]string, 0) - imports := pkg.Pkg.Imports() - for _, pkg := range imports { - if _, ok := packageSet[pkg.Path()]; ok { - continue - } - unsatisfiedImports = append(unsatisfiedImports, pkg.Path()) - } - if len(unsatisfiedImports) == 0 { - // All dependencies of this package are satisfied, so add this - // package to the list. - packageList = append(packageList, pkg) - packageSet[pkgPath] = struct{}{} - worklist = worklist[1:] - } else { - // Prepend all dependencies to the worklist and reconsider this - // package (by not removing it from the worklist). At that point, it - // must be possible to add it to packageList. - worklist = append(unsatisfiedImports, worklist...) - } - } - - return packageList -} - -func (p *Program) MainPkg() *ssa.Package { - return p.mainPkg -} - -// MethodSignature creates a readable version of a method signature (including -// the function name, excluding the receiver name). This string is used -// internally to match interfaces and to call the correct method on an -// interface. Examples: -// -// String() string -// Read([]byte) (int, error) -func MethodSignature(method *types.Func) string { - return method.Name() + signature(method.Type().(*types.Signature)) -} - -// Make a readable version of a function (pointer) signature. -// Examples: -// -// () string -// (string, int) (int, error) -func signature(sig *types.Signature) string { - s := "" - if sig.Params().Len() == 0 { - s += "()" - } else { - s += "(" - for i := 0; i < sig.Params().Len(); i++ { - if i > 0 { - s += ", " - } - s += sig.Params().At(i).Type().String() - } - s += ")" - } - if sig.Results().Len() == 0 { - // keep as-is - } else if sig.Results().Len() == 1 { - s += " " + sig.Results().At(0).Type().String() - } else { - s += " (" - for i := 0; i < sig.Results().Len(); i++ { - if i > 0 { - s += ", " - } - s += sig.Results().At(i).Type().String() - } - s += ")" - } - return s -}