Skip to content

Commit b0e767c

Browse files
aykevldeadprogram
authored andcommitted
compiler: move global handling from ir to compiler package
This is part of a larger rafactor that tries to shrink the ir package and in general tries to shrink the amount of state that is kept around in the compiler. The end goal is being able to compile packages independent of each other, linking them together in a later stage. Along the way, it cleans up lots of old cruft that has accumulated over the months. This refactor also results in globals being loaded lazily. This may be a problem for some specific programs but will probably change back in a commit in the near future.
1 parent 6b5b4a6 commit b0e767c

File tree

3 files changed

+115
-113
lines changed

3 files changed

+115
-113
lines changed

compiler/compiler.go

+7-18
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package compiler
33
import (
44
"errors"
55
"fmt"
6+
"go/ast"
67
"go/build"
78
"go/constant"
89
"go/token"
@@ -66,6 +67,7 @@ type Compiler struct {
6667
interfaceInvokeWrappers []interfaceInvokeWrapper
6768
ir *ir.Program
6869
diagnostics []error
70+
astComments map[string]*ast.CommentGroup
6971
}
7072

7173
type Frame struct {
@@ -275,20 +277,7 @@ func (c *Compiler) Compile(mainPath string) []error {
275277

276278
var frames []*Frame
277279

278-
// Declare all globals.
279-
for _, g := range c.ir.Globals {
280-
typ := g.Type().(*types.Pointer).Elem()
281-
llvmType := c.getLLVMType(typ)
282-
global := c.mod.NamedGlobal(g.LinkName())
283-
if global.IsNil() {
284-
global = llvm.AddGlobal(c.mod, llvmType, g.LinkName())
285-
}
286-
g.LLVMGlobal = global
287-
if !g.IsExtern() {
288-
global.SetLinkage(llvm.InternalLinkage)
289-
global.SetInitializer(c.getZeroValue(llvmType))
290-
}
291-
}
280+
c.loadASTComments(lprogram)
292281

293282
// Declare all functions.
294283
for _, f := range c.ir.Functions {
@@ -1357,9 +1346,9 @@ func (c *Compiler) getValue(frame *Frame, expr ssa.Value) llvm.Value {
13571346
}
13581347
return c.createFuncValue(fn.LLVMFn, llvm.Undef(c.i8ptrType), fn.Signature)
13591348
case *ssa.Global:
1360-
value := c.ir.GetGlobal(expr).LLVMGlobal
1349+
value := c.getGlobal(expr)
13611350
if value.IsNil() {
1362-
c.addError(expr.Pos(), "global not found: "+c.ir.GetGlobal(expr).LinkName())
1351+
c.addError(expr.Pos(), "global not found: "+expr.RelString(nil))
13631352
return llvm.Undef(c.getLLVMType(expr.Type()))
13641353
}
13651354
return value
@@ -2511,8 +2500,8 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
25112500
// var C.add unsafe.Pointer
25122501
// Instead of a load from the global, create a bitcast of the
25132502
// function pointer itself.
2514-
global := c.ir.GetGlobal(unop.X.(*ssa.Global))
2515-
name := global.LinkName()[:len(global.LinkName())-len("$funcaddr")]
2503+
globalName := c.getGlobalInfo(unop.X.(*ssa.Global)).linkName
2504+
name := globalName[:len(globalName)-len("$funcaddr")]
25162505
fn := c.mod.NamedFunction(name)
25172506
if fn.IsNil() {
25182507
return llvm.Value{}, c.makeError(unop.Pos(), "cgo function not found: "+name)

compiler/symbol.go

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package compiler
2+
3+
// This file manages symbols, that is, functions and globals. It reads their
4+
// pragmas, determines the link name, etc.
5+
6+
import (
7+
"go/ast"
8+
"go/token"
9+
"go/types"
10+
"strings"
11+
12+
"github.com/tinygo-org/tinygo/loader"
13+
"golang.org/x/tools/go/ssa"
14+
"tinygo.org/x/go-llvm"
15+
)
16+
17+
// globalInfo contains some information about a specific global. By default,
18+
// linkName is equal to .RelString(nil) on a global and extern is false, but for
19+
// some symbols this is different (due to //go:extern for example).
20+
type globalInfo struct {
21+
linkName string // go:extern
22+
extern bool // go:extern
23+
}
24+
25+
// loadASTComments loads comments on globals from the AST, for use later in the
26+
// program. In particular, they are required for //go:extern pragmas on globals.
27+
func (c *Compiler) loadASTComments(lprogram *loader.Program) {
28+
c.astComments = map[string]*ast.CommentGroup{}
29+
for _, pkgInfo := range lprogram.Sorted() {
30+
for _, file := range pkgInfo.Files {
31+
for _, decl := range file.Decls {
32+
switch decl := decl.(type) {
33+
case *ast.GenDecl:
34+
switch decl.Tok {
35+
case token.VAR:
36+
if len(decl.Specs) != 1 {
37+
continue
38+
}
39+
for _, spec := range decl.Specs {
40+
switch spec := spec.(type) {
41+
case *ast.ValueSpec: // decl.Tok == token.VAR
42+
for _, name := range spec.Names {
43+
id := pkgInfo.Pkg.Path() + "." + name.Name
44+
c.astComments[id] = decl.Doc
45+
}
46+
}
47+
}
48+
}
49+
}
50+
}
51+
}
52+
}
53+
}
54+
55+
// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to
56+
// the LLVM IR if it has not been added already.
57+
func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
58+
info := c.getGlobalInfo(g)
59+
llvmGlobal := c.mod.NamedGlobal(info.linkName)
60+
if llvmGlobal.IsNil() {
61+
llvmType := c.getLLVMType(g.Type().(*types.Pointer).Elem())
62+
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
63+
if !info.extern {
64+
llvmGlobal.SetInitializer(c.getZeroValue(llvmType))
65+
llvmGlobal.SetLinkage(llvm.InternalLinkage)
66+
}
67+
}
68+
return llvmGlobal
69+
}
70+
71+
// getGlobalInfo returns some information about a specific global.
72+
func (c *Compiler) getGlobalInfo(g *ssa.Global) globalInfo {
73+
info := globalInfo{}
74+
if strings.HasPrefix(g.Name(), "C.") {
75+
// Created by CGo: such a name cannot be created by regular C code.
76+
info.linkName = g.Name()[2:]
77+
info.extern = true
78+
} else {
79+
// Pick the default linkName.
80+
info.linkName = g.RelString(nil)
81+
// Check for //go: pragmas, which may change the link name (among
82+
// others).
83+
doc := c.astComments[info.linkName]
84+
if doc != nil {
85+
info.parsePragmas(doc)
86+
}
87+
}
88+
return info
89+
}
90+
91+
// Parse //go: pragma comments from the source. In particular, it parses the
92+
// //go:extern pragma on globals.
93+
func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
94+
for _, comment := range doc.List {
95+
if !strings.HasPrefix(comment.Text, "//go:") {
96+
continue
97+
}
98+
parts := strings.Fields(comment.Text)
99+
switch parts[0] {
100+
case "//go:extern":
101+
info.extern = true
102+
if len(parts) == 2 {
103+
info.linkName = parts[1]
104+
}
105+
}
106+
}
107+
}

ir/ir.go

+1-95
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package ir
22

33
import (
44
"go/ast"
5-
"go/token"
65
"go/types"
76
"sort"
87
"strings"
@@ -23,9 +22,6 @@ type Program struct {
2322
mainPkg *ssa.Package
2423
Functions []*Function
2524
functionMap map[*ssa.Function]*Function
26-
Globals []*Global
27-
globalMap map[*ssa.Global]*Global
28-
comments map[string]*ast.CommentGroup
2925
}
3026

3127
// Function or method.
@@ -40,15 +36,6 @@ type Function struct {
4036
inline InlineType // go:inline
4137
}
4238

43-
// Global variable, possibly constant.
44-
type Global struct {
45-
*ssa.Global
46-
program *Program
47-
LLVMGlobal llvm.Value
48-
linkName string // go:extern
49-
extern bool // go:extern
50-
}
51-
5239
// Interface type that is at some point used in a type assert (to check whether
5340
// it implements another interface).
5441
type Interface struct {
@@ -73,32 +60,6 @@ const (
7360

7461
// Create and initialize a new *Program from a *ssa.Program.
7562
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
76-
comments := map[string]*ast.CommentGroup{}
77-
for _, pkgInfo := range lprogram.Sorted() {
78-
for _, file := range pkgInfo.Files {
79-
for _, decl := range file.Decls {
80-
switch decl := decl.(type) {
81-
case *ast.GenDecl:
82-
switch decl.Tok {
83-
case token.TYPE, token.VAR:
84-
if len(decl.Specs) != 1 {
85-
continue
86-
}
87-
for _, spec := range decl.Specs {
88-
switch spec := spec.(type) {
89-
case *ast.ValueSpec: // decl.Tok == token.VAR
90-
for _, name := range spec.Names {
91-
id := pkgInfo.Pkg.Path() + "." + name.Name
92-
comments[id] = decl.Doc
93-
}
94-
}
95-
}
96-
}
97-
}
98-
}
99-
}
100-
}
101-
10263
program := lprogram.LoadSSA()
10364
program.Build()
10465

@@ -170,8 +131,6 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
170131
LoaderProgram: lprogram,
171132
mainPkg: mainPkg,
172133
functionMap: make(map[*ssa.Function]*Function),
173-
globalMap: make(map[*ssa.Global]*Global),
174-
comments: comments,
175134
}
176135

177136
for _, pkg := range packageList {
@@ -204,13 +163,7 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
204163
}
205164
}
206165
case *ssa.Global:
207-
g := &Global{program: p, Global: member}
208-
doc := p.comments[g.RelString(nil)]
209-
if doc != nil {
210-
g.parsePragmas(doc)
211-
}
212-
p.Globals = append(p.Globals, g)
213-
p.globalMap[member] = g
166+
// Ignore. Globals are not handled here.
214167
case *ssa.NamedConst:
215168
// Ignore: these are already resolved.
216169
default:
@@ -244,10 +197,6 @@ func (p *Program) GetFunction(ssaFn *ssa.Function) *Function {
244197
return p.functionMap[ssaFn]
245198
}
246199

247-
func (p *Program) GetGlobal(ssaGlobal *ssa.Global) *Global {
248-
return p.globalMap[ssaGlobal]
249-
}
250-
251200
func (p *Program) MainPkg() *ssa.Package {
252201
return p.mainPkg
253202
}
@@ -370,49 +319,6 @@ func (f *Function) CName() string {
370319
return ""
371320
}
372321

373-
// Parse //go: pragma comments from the source.
374-
func (g *Global) parsePragmas(doc *ast.CommentGroup) {
375-
for _, comment := range doc.List {
376-
if !strings.HasPrefix(comment.Text, "//go:") {
377-
continue
378-
}
379-
parts := strings.Fields(comment.Text)
380-
switch parts[0] {
381-
case "//go:extern":
382-
g.extern = true
383-
if len(parts) == 2 {
384-
g.linkName = parts[1]
385-
}
386-
}
387-
}
388-
}
389-
390-
// Return the link name for this global.
391-
func (g *Global) LinkName() string {
392-
if g.linkName != "" {
393-
return g.linkName
394-
}
395-
if name := g.CName(); name != "" {
396-
return name
397-
}
398-
return g.RelString(nil)
399-
}
400-
401-
func (g *Global) IsExtern() bool {
402-
return g.extern || g.CName() != ""
403-
}
404-
405-
// Return the name of the C global if this is a CGo wrapper. Otherwise, return a
406-
// zero-length string.
407-
func (g *Global) CName() string {
408-
name := g.Name()
409-
if strings.HasPrefix(name, "C.") {
410-
// created by ../loader/cgo.go
411-
return name[2:]
412-
}
413-
return ""
414-
}
415-
416322
// Get all methods of a type.
417323
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
418324
ms := prog.MethodSets.MethodSet(typ)

0 commit comments

Comments
 (0)