github.com/aykevl/tinygo@v0.5.0/ir/ir.go (about)

     1  package ir
     2  
     3  import (
     4  	"go/ast"
     5  	"go/token"
     6  	"go/types"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/tinygo-org/tinygo/loader"
    11  	"golang.org/x/tools/go/ssa"
    12  	"tinygo.org/x/go-llvm"
    13  )
    14  
    15  // This file provides a wrapper around go/ssa values and adds extra
    16  // functionality to them.
    17  
    18  // View on all functions, types, and globals in a program, with analysis
    19  // results.
    20  type Program struct {
    21  	Program       *ssa.Program
    22  	LoaderProgram *loader.Program
    23  	mainPkg       *ssa.Package
    24  	Functions     []*Function
    25  	functionMap   map[*ssa.Function]*Function
    26  	Globals       []*Global
    27  	globalMap     map[*ssa.Global]*Global
    28  	comments      map[string]*ast.CommentGroup
    29  	NamedTypes    []*NamedType
    30  }
    31  
    32  // Function or method.
    33  type Function struct {
    34  	*ssa.Function
    35  	LLVMFn    llvm.Value
    36  	linkName  string // go:linkname, go:export, go:interrupt
    37  	exported  bool   // go:export
    38  	nobounds  bool   // go:nobounds
    39  	flag      bool   // used by dead code elimination
    40  	interrupt bool   // go:interrupt
    41  }
    42  
    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  
    52  // Type with a name and possibly methods.
    53  type NamedType struct {
    54  	*ssa.Type
    55  	LLVMType llvm.Type
    56  }
    57  
    58  // Type that is at some point put in an interface.
    59  type TypeWithMethods struct {
    60  	t       types.Type
    61  	Num     int
    62  	Methods map[string]*types.Selection
    63  }
    64  
    65  // Interface type that is at some point used in a type assert (to check whether
    66  // it implements another interface).
    67  type Interface struct {
    68  	Num  int
    69  	Type *types.Interface
    70  }
    71  
    72  // Create and intialize a new *Program from a *ssa.Program.
    73  func NewProgram(lprogram *loader.Program, mainPath string) *Program {
    74  	comments := map[string]*ast.CommentGroup{}
    75  	for _, pkgInfo := range lprogram.Sorted() {
    76  		for _, file := range pkgInfo.Files {
    77  			for _, decl := range file.Decls {
    78  				switch decl := decl.(type) {
    79  				case *ast.GenDecl:
    80  					switch decl.Tok {
    81  					case token.TYPE, token.VAR:
    82  						if len(decl.Specs) != 1 {
    83  							continue
    84  						}
    85  						for _, spec := range decl.Specs {
    86  							switch spec := spec.(type) {
    87  							case *ast.TypeSpec: // decl.Tok == token.TYPE
    88  								id := pkgInfo.Pkg.Path() + "." + spec.Name.Name
    89  								comments[id] = decl.Doc
    90  							case *ast.ValueSpec: // decl.Tok == token.VAR
    91  								for _, name := range spec.Names {
    92  									id := pkgInfo.Pkg.Path() + "." + name.Name
    93  									comments[id] = decl.Doc
    94  								}
    95  							}
    96  						}
    97  					}
    98  				}
    99  			}
   100  		}
   101  	}
   102  
   103  	program := lprogram.LoadSSA()
   104  	program.Build()
   105  
   106  	// Find the main package, which is a bit difficult when running a .go file
   107  	// directly.
   108  	mainPkg := program.ImportedPackage(mainPath)
   109  	if mainPkg == nil {
   110  		for _, pkgInfo := range program.AllPackages() {
   111  			if pkgInfo.Pkg.Name() == "main" {
   112  				if mainPkg != nil {
   113  					panic("more than one main package found")
   114  				}
   115  				mainPkg = pkgInfo
   116  			}
   117  		}
   118  	}
   119  	if mainPkg == nil {
   120  		panic("could not find main package")
   121  	}
   122  
   123  	// Make a list of packages in import order.
   124  	packageList := []*ssa.Package{}
   125  	packageSet := map[string]struct{}{}
   126  	worklist := []string{"runtime", mainPath}
   127  	for len(worklist) != 0 {
   128  		pkgPath := worklist[0]
   129  		var pkg *ssa.Package
   130  		if pkgPath == mainPath {
   131  			pkg = mainPkg // necessary for compiling individual .go files
   132  		} else {
   133  			pkg = program.ImportedPackage(pkgPath)
   134  		}
   135  		if pkg == nil {
   136  			// Non-SSA package (e.g. cgo).
   137  			packageSet[pkgPath] = struct{}{}
   138  			worklist = worklist[1:]
   139  			continue
   140  		}
   141  		if _, ok := packageSet[pkgPath]; ok {
   142  			// Package already in the final package list.
   143  			worklist = worklist[1:]
   144  			continue
   145  		}
   146  
   147  		unsatisfiedImports := make([]string, 0)
   148  		imports := pkg.Pkg.Imports()
   149  		for _, pkg := range imports {
   150  			if _, ok := packageSet[pkg.Path()]; ok {
   151  				continue
   152  			}
   153  			unsatisfiedImports = append(unsatisfiedImports, pkg.Path())
   154  		}
   155  		if len(unsatisfiedImports) == 0 {
   156  			// All dependencies of this package are satisfied, so add this
   157  			// package to the list.
   158  			packageList = append(packageList, pkg)
   159  			packageSet[pkgPath] = struct{}{}
   160  			worklist = worklist[1:]
   161  		} else {
   162  			// Prepend all dependencies to the worklist and reconsider this
   163  			// package (by not removing it from the worklist). At that point, it
   164  			// must be possible to add it to packageList.
   165  			worklist = append(unsatisfiedImports, worklist...)
   166  		}
   167  	}
   168  
   169  	p := &Program{
   170  		Program:       program,
   171  		LoaderProgram: lprogram,
   172  		mainPkg:       mainPkg,
   173  		functionMap:   make(map[*ssa.Function]*Function),
   174  		globalMap:     make(map[*ssa.Global]*Global),
   175  		comments:      comments,
   176  	}
   177  
   178  	for _, pkg := range packageList {
   179  		p.AddPackage(pkg)
   180  	}
   181  
   182  	return p
   183  }
   184  
   185  // Add a package to this Program. All packages need to be added first before any
   186  // analysis is done for correct results.
   187  func (p *Program) AddPackage(pkg *ssa.Package) {
   188  	memberNames := make([]string, 0)
   189  	for name := range pkg.Members {
   190  		memberNames = append(memberNames, name)
   191  	}
   192  	sort.Strings(memberNames)
   193  
   194  	for _, name := range memberNames {
   195  		member := pkg.Members[name]
   196  		switch member := member.(type) {
   197  		case *ssa.Function:
   198  			p.addFunction(member)
   199  		case *ssa.Type:
   200  			t := &NamedType{Type: member}
   201  			p.NamedTypes = append(p.NamedTypes, t)
   202  			methods := getAllMethods(pkg.Prog, member.Type())
   203  			if !types.IsInterface(member.Type()) {
   204  				// named type
   205  				for _, method := range methods {
   206  					p.addFunction(pkg.Prog.MethodValue(method))
   207  				}
   208  			}
   209  		case *ssa.Global:
   210  			g := &Global{program: p, Global: member}
   211  			doc := p.comments[g.RelString(nil)]
   212  			if doc != nil {
   213  				g.parsePragmas(doc)
   214  			}
   215  			p.Globals = append(p.Globals, g)
   216  			p.globalMap[member] = g
   217  		case *ssa.NamedConst:
   218  			// Ignore: these are already resolved.
   219  		default:
   220  			panic("unknown member type: " + member.String())
   221  		}
   222  	}
   223  }
   224  
   225  func (p *Program) addFunction(ssaFn *ssa.Function) {
   226  	f := &Function{Function: ssaFn}
   227  	f.parsePragmas()
   228  	p.Functions = append(p.Functions, f)
   229  	p.functionMap[ssaFn] = f
   230  
   231  	for _, anon := range ssaFn.AnonFuncs {
   232  		p.addFunction(anon)
   233  	}
   234  }
   235  
   236  // Return true if this package imports "unsafe", false otherwise.
   237  func hasUnsafeImport(pkg *types.Package) bool {
   238  	for _, imp := range pkg.Imports() {
   239  		if imp == types.Unsafe {
   240  			return true
   241  		}
   242  	}
   243  	return false
   244  }
   245  
   246  func (p *Program) GetFunction(ssaFn *ssa.Function) *Function {
   247  	return p.functionMap[ssaFn]
   248  }
   249  
   250  func (p *Program) GetGlobal(ssaGlobal *ssa.Global) *Global {
   251  	return p.globalMap[ssaGlobal]
   252  }
   253  
   254  func (p *Program) MainPkg() *ssa.Package {
   255  	return p.mainPkg
   256  }
   257  
   258  // Parse compiler directives in the preceding comments.
   259  func (f *Function) parsePragmas() {
   260  	if f.Syntax() == nil {
   261  		return
   262  	}
   263  	if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
   264  		for _, comment := range decl.Doc.List {
   265  			text := comment.Text
   266  			if strings.HasPrefix(text, "//export ") {
   267  				// Rewrite '//export' to '//go:export' for compatibility with
   268  				// gc.
   269  				text = "//go:" + text[2:]
   270  			}
   271  			if !strings.HasPrefix(text, "//go:") {
   272  				continue
   273  			}
   274  			parts := strings.Fields(text)
   275  			switch parts[0] {
   276  			case "//go:export":
   277  				if len(parts) != 2 {
   278  					continue
   279  				}
   280  				f.linkName = parts[1]
   281  				f.exported = true
   282  			case "//go:interrupt":
   283  				if len(parts) != 2 {
   284  					continue
   285  				}
   286  				name := parts[1]
   287  				if strings.HasSuffix(name, "_vect") {
   288  					// AVR vector naming
   289  					name = "__vector_" + name[:len(name)-5]
   290  				}
   291  				f.linkName = name
   292  				f.exported = true
   293  				f.interrupt = true
   294  			case "//go:linkname":
   295  				if len(parts) != 3 || parts[1] != f.Name() {
   296  					continue
   297  				}
   298  				// Only enable go:linkname when the package imports "unsafe".
   299  				// This is a slightly looser requirement than what gc uses: gc
   300  				// requires the file to import "unsafe", not the package as a
   301  				// whole.
   302  				if hasUnsafeImport(f.Pkg.Pkg) {
   303  					f.linkName = parts[2]
   304  				}
   305  			case "//go:nobounds":
   306  				// Skip bounds checking in this function. Useful for some
   307  				// runtime functions.
   308  				// This is somewhat dangerous and thus only imported in packages
   309  				// that import unsafe.
   310  				if hasUnsafeImport(f.Pkg.Pkg) {
   311  					f.nobounds = true
   312  				}
   313  			}
   314  		}
   315  	}
   316  }
   317  
   318  func (f *Function) IsNoBounds() bool {
   319  	return f.nobounds
   320  }
   321  
   322  // Return true iff this function is externally visible.
   323  func (f *Function) IsExported() bool {
   324  	return f.exported || f.CName() != ""
   325  }
   326  
   327  // Return true for functions annotated with //go:interrupt. The function name is
   328  // already customized in LinkName() to hook up in the interrupt vector.
   329  //
   330  // On some platforms (like AVR), interrupts need a special compiler flag.
   331  func (f *Function) IsInterrupt() bool {
   332  	return f.interrupt
   333  }
   334  
   335  // Return the link name for this function.
   336  func (f *Function) LinkName() string {
   337  	if f.linkName != "" {
   338  		return f.linkName
   339  	}
   340  	if f.Signature.Recv() != nil {
   341  		// Method on a defined type (which may be a pointer).
   342  		return f.RelString(nil)
   343  	} else {
   344  		// Bare function.
   345  		if name := f.CName(); name != "" {
   346  			// Name CGo functions directly.
   347  			return name
   348  		} else {
   349  			return f.RelString(nil)
   350  		}
   351  	}
   352  }
   353  
   354  // Return the name of the C function if this is a CGo wrapper. Otherwise, return
   355  // a zero-length string.
   356  func (f *Function) CName() string {
   357  	name := f.Name()
   358  	if strings.HasPrefix(name, "_Cfunc_") {
   359  		// emitted by `go tool cgo`
   360  		return name[len("_Cfunc_"):]
   361  	}
   362  	if strings.HasPrefix(name, "C.") {
   363  		// created by ../loader/cgo.go
   364  		return name[2:]
   365  	}
   366  	return ""
   367  }
   368  
   369  // Parse //go: pragma comments from the source.
   370  func (g *Global) parsePragmas(doc *ast.CommentGroup) {
   371  	for _, comment := range doc.List {
   372  		if !strings.HasPrefix(comment.Text, "//go:") {
   373  			continue
   374  		}
   375  		parts := strings.Fields(comment.Text)
   376  		switch parts[0] {
   377  		case "//go:extern":
   378  			g.extern = true
   379  			if len(parts) == 2 {
   380  				g.linkName = parts[1]
   381  			}
   382  		}
   383  	}
   384  }
   385  
   386  // Return the link name for this global.
   387  func (g *Global) LinkName() string {
   388  	if g.linkName != "" {
   389  		return g.linkName
   390  	}
   391  	if name := g.CName(); name != "" {
   392  		return name
   393  	}
   394  	return g.RelString(nil)
   395  }
   396  
   397  func (g *Global) IsExtern() bool {
   398  	return g.extern || g.CName() != ""
   399  }
   400  
   401  // Return the name of the C global if this is a CGo wrapper. Otherwise, return a
   402  // zero-length string.
   403  func (g *Global) CName() string {
   404  	name := g.Name()
   405  	if strings.HasPrefix(name, "C.") {
   406  		// created by ../loader/cgo.go
   407  		return name[2:]
   408  	}
   409  	return ""
   410  }
   411  
   412  // Return true if this named type is annotated with the //go:volatile pragma,
   413  // for volatile loads and stores.
   414  func (p *Program) IsVolatile(t types.Type) bool {
   415  	if t, ok := t.(*types.Named); !ok {
   416  		return false
   417  	} else {
   418  		if t.Obj().Pkg() == nil {
   419  			return false
   420  		}
   421  		id := t.Obj().Pkg().Path() + "." + t.Obj().Name()
   422  		doc := p.comments[id]
   423  		if doc == nil {
   424  			return false
   425  		}
   426  		for _, line := range doc.List {
   427  			if strings.TrimSpace(line.Text) == "//go:volatile" {
   428  				return true
   429  			}
   430  		}
   431  		return false
   432  	}
   433  }
   434  
   435  // Get all methods of a type.
   436  func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
   437  	ms := prog.MethodSets.MethodSet(typ)
   438  	methods := make([]*types.Selection, ms.Len())
   439  	for i := 0; i < ms.Len(); i++ {
   440  		methods[i] = ms.At(i)
   441  	}
   442  	return methods
   443  }