diff --git a/compiler/compiler.go b/compiler/compiler.go index 1c22208c..f27b8ade 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -3,6 +3,7 @@ package compiler import ( "errors" "fmt" + "go/ast" "go/build" "go/constant" "go/token" @@ -66,6 +67,7 @@ type Compiler struct { interfaceInvokeWrappers []interfaceInvokeWrapper ir *ir.Program diagnostics []error + astComments map[string]*ast.CommentGroup } type Frame struct { @@ -275,20 +277,7 @@ func (c *Compiler) Compile(mainPath string) []error { var frames []*Frame - // Declare all globals. - for _, g := range c.ir.Globals { - typ := g.Type().(*types.Pointer).Elem() - llvmType := c.getLLVMType(typ) - global := c.mod.NamedGlobal(g.LinkName()) - if global.IsNil() { - global = llvm.AddGlobal(c.mod, llvmType, g.LinkName()) - } - g.LLVMGlobal = global - if !g.IsExtern() { - global.SetLinkage(llvm.InternalLinkage) - global.SetInitializer(c.getZeroValue(llvmType)) - } - } + c.loadASTComments(lprogram) // Declare all functions. for _, f := range c.ir.Functions { @@ -1357,9 +1346,9 @@ func (c *Compiler) getValue(frame *Frame, expr ssa.Value) llvm.Value { } return c.createFuncValue(fn.LLVMFn, llvm.Undef(c.i8ptrType), fn.Signature) case *ssa.Global: - value := c.ir.GetGlobal(expr).LLVMGlobal + value := c.getGlobal(expr) if value.IsNil() { - c.addError(expr.Pos(), "global not found: "+c.ir.GetGlobal(expr).LinkName()) + c.addError(expr.Pos(), "global not found: "+expr.RelString(nil)) return llvm.Undef(c.getLLVMType(expr.Type())) } return value @@ -2511,8 +2500,8 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) { // var C.add unsafe.Pointer // Instead of a load from the global, create a bitcast of the // function pointer itself. - global := c.ir.GetGlobal(unop.X.(*ssa.Global)) - name := global.LinkName()[:len(global.LinkName())-len("$funcaddr")] + globalName := c.getGlobalInfo(unop.X.(*ssa.Global)).linkName + name := globalName[:len(globalName)-len("$funcaddr")] fn := c.mod.NamedFunction(name) if fn.IsNil() { return llvm.Value{}, c.makeError(unop.Pos(), "cgo function not found: "+name) diff --git a/compiler/symbol.go b/compiler/symbol.go new file mode 100644 index 00000000..0df2ab6e --- /dev/null +++ b/compiler/symbol.go @@ -0,0 +1,107 @@ +package compiler + +// This file manages symbols, that is, functions and globals. It reads their +// pragmas, determines the link name, etc. + +import ( + "go/ast" + "go/token" + "go/types" + "strings" + + "github.com/tinygo-org/tinygo/loader" + "golang.org/x/tools/go/ssa" + "tinygo.org/x/go-llvm" +) + +// globalInfo contains some information about a specific global. By default, +// linkName is equal to .RelString(nil) on a global and extern is false, but for +// some symbols this is different (due to //go:extern for example). +type globalInfo struct { + linkName string // go:extern + extern bool // go:extern +} + +// 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 *Compiler) 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 + } + } + } + } + } + } + } + } +} + +// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to +// the LLVM IR if it has not been added already. +func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value { + info := c.getGlobalInfo(g) + llvmGlobal := c.mod.NamedGlobal(info.linkName) + if llvmGlobal.IsNil() { + llvmType := c.getLLVMType(g.Type().(*types.Pointer).Elem()) + llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName) + if !info.extern { + llvmGlobal.SetInitializer(c.getZeroValue(llvmType)) + llvmGlobal.SetLinkage(llvm.InternalLinkage) + } + } + return llvmGlobal +} + +// getGlobalInfo returns some information about a specific global. +func (c *Compiler) getGlobalInfo(g *ssa.Global) globalInfo { + info := globalInfo{} + if strings.HasPrefix(g.Name(), "C.") { + // Created by CGo: such a name cannot be created by regular C code. + info.linkName = g.Name()[2:] + info.extern = true + } else { + // Pick the default linkName. + info.linkName = g.RelString(nil) + // Check for //go: pragmas, which may change the link name (among + // others). + doc := c.astComments[info.linkName] + if doc != nil { + info.parsePragmas(doc) + } + } + return info +} + +// Parse //go: pragma comments from the source. In particular, it parses the +// //go:extern pragma on globals. +func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) { + for _, comment := range doc.List { + if !strings.HasPrefix(comment.Text, "//go:") { + continue + } + parts := strings.Fields(comment.Text) + switch parts[0] { + case "//go:extern": + info.extern = true + if len(parts) == 2 { + info.linkName = parts[1] + } + } + } +} diff --git a/ir/ir.go b/ir/ir.go index 5f73b90e..d9d5764e 100644 --- a/ir/ir.go +++ b/ir/ir.go @@ -2,7 +2,6 @@ package ir import ( "go/ast" - "go/token" "go/types" "sort" "strings" @@ -23,9 +22,6 @@ type Program struct { mainPkg *ssa.Package Functions []*Function functionMap map[*ssa.Function]*Function - Globals []*Global - globalMap map[*ssa.Global]*Global - comments map[string]*ast.CommentGroup } // Function or method. @@ -40,15 +36,6 @@ type Function struct { inline InlineType // go:inline } -// Global variable, possibly constant. -type Global struct { - *ssa.Global - program *Program - LLVMGlobal llvm.Value - linkName string // go:extern - extern bool // go:extern -} - // Interface type that is at some point used in a type assert (to check whether // it implements another interface). type Interface struct { @@ -73,32 +60,6 @@ const ( // Create and initialize a new *Program from a *ssa.Program. func NewProgram(lprogram *loader.Program, mainPath string) *Program { - comments := 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.TYPE, 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 - comments[id] = decl.Doc - } - } - } - } - } - } - } - } - program := lprogram.LoadSSA() program.Build() @@ -170,8 +131,6 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program { LoaderProgram: lprogram, mainPkg: mainPkg, functionMap: make(map[*ssa.Function]*Function), - globalMap: make(map[*ssa.Global]*Global), - comments: comments, } for _, pkg := range packageList { @@ -204,13 +163,7 @@ func (p *Program) AddPackage(pkg *ssa.Package) { } } case *ssa.Global: - g := &Global{program: p, Global: member} - doc := p.comments[g.RelString(nil)] - if doc != nil { - g.parsePragmas(doc) - } - p.Globals = append(p.Globals, g) - p.globalMap[member] = g + // Ignore. Globals are not handled here. case *ssa.NamedConst: // Ignore: these are already resolved. default: @@ -244,10 +197,6 @@ func (p *Program) GetFunction(ssaFn *ssa.Function) *Function { return p.functionMap[ssaFn] } -func (p *Program) GetGlobal(ssaGlobal *ssa.Global) *Global { - return p.globalMap[ssaGlobal] -} - func (p *Program) MainPkg() *ssa.Package { return p.mainPkg } @@ -370,49 +319,6 @@ func (f *Function) CName() string { return "" } -// Parse //go: pragma comments from the source. -func (g *Global) parsePragmas(doc *ast.CommentGroup) { - for _, comment := range doc.List { - if !strings.HasPrefix(comment.Text, "//go:") { - continue - } - parts := strings.Fields(comment.Text) - switch parts[0] { - case "//go:extern": - g.extern = true - if len(parts) == 2 { - g.linkName = parts[1] - } - } - } -} - -// Return the link name for this global. -func (g *Global) LinkName() string { - if g.linkName != "" { - return g.linkName - } - if name := g.CName(); name != "" { - return name - } - return g.RelString(nil) -} - -func (g *Global) IsExtern() bool { - return g.extern || g.CName() != "" -} - -// Return the name of the C global if this is a CGo wrapper. Otherwise, return a -// zero-length string. -func (g *Global) CName() string { - name := g.Name() - if strings.HasPrefix(name, "C.") { - // created by ../loader/cgo.go - return name[2:] - } - return "" -} - // Get all methods of a type. func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection { ms := prog.MethodSets.MethodSet(typ)