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 -}