|
|
|
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"
|
|
|
|
"strconv"
|
|
|
|
"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
|
|
|
|
align int // go:align
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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() {
|
|
|
|
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
|
|
|
|
if info.align < 0 || info.align&(info.align-1) != 0 {
|
|
|
|
// Check for power-of-two (or 0).
|
|
|
|
// See: https://stackoverflow.com/a/108360
|
|
|
|
c.addError(g.Pos(), "global variable alignment must be a positive power of two")
|
|
|
|
} else {
|
|
|
|
// Set the alignment only when it is a power of two.
|
|
|
|
alignInBits = uint32(info.align) ^ uint32(info.align-1)
|
|
|
|
if info.align > c.targetData.ABITypeAlignment(llvmType) {
|
|
|
|
llvmGlobal.SetAlignment(info.align)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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.ir.Program.Fset.Position(g.Pos())
|
|
|
|
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
|
|
|
|
Name: g.RelString(nil),
|
|
|
|
LinkageName: info.linkName,
|
|
|
|
File: c.getDIFile(pos.Filename),
|
|
|
|
Line: pos.Line,
|
|
|
|
Type: c.getDIType(typ),
|
|
|
|
LocalToUnit: false,
|
|
|
|
Expr: c.dibuilder.CreateExpression(nil),
|
|
|
|
AlignInBits: alignInBits,
|
|
|
|
})
|
|
|
|
llvmGlobal.AddMetadata(0, diglobal)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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]
|
|
|
|
}
|
|
|
|
case "//go:align":
|
|
|
|
align, err := strconv.Atoi(parts[1])
|
|
|
|
if err == nil {
|
|
|
|
info.align = align
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|