
There were a few problems with the go/packages package. While it is more or less designed for our purpose, it didn't work quite well as it didn't provide access to indirectly imported packages (most importantly the runtime package). This led to a workaround that sometimes broke `tinygo test`. This PR contains a number of related changes: * It uses `go list` directly to retrieve the list of packages/files to compile, instead of relying on the go/packages package. * It replaces our custom TestMain replace code with the standard code for running tests (generated by `go list`). * It adds a dummy runtime/pprof package and modifies the testing package, to get tests to run again with the code generated by `go list`.
271 строка
6,6 КиБ
Go
271 строка
6,6 КиБ
Go
package ir
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/types"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/tinygo-org/tinygo/loader"
|
|
"golang.org/x/tools/go/ssa"
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
// 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
|
|
Functions []*Function
|
|
functionMap map[*ssa.Function]*Function
|
|
}
|
|
|
|
// Function or method.
|
|
type Function struct {
|
|
*ssa.Function
|
|
LLVMFn llvm.Value
|
|
module string // go:wasm-module
|
|
linkName string // go:linkname, go:export
|
|
exported bool // go:export
|
|
nobounds bool // go:nobounds
|
|
flag bool // used by dead code elimination
|
|
inline InlineType // go:inline
|
|
}
|
|
|
|
// Interface type that is at some point used in a type assert (to check whether
|
|
// it implements another interface).
|
|
type Interface struct {
|
|
Num int
|
|
Type *types.Interface
|
|
}
|
|
|
|
type InlineType int
|
|
|
|
// How much to inline.
|
|
const (
|
|
// Default behavior. The compiler decides for itself whether any given
|
|
// function will be inlined. Whether any function is inlined depends on the
|
|
// optimization level.
|
|
InlineDefault InlineType = iota
|
|
|
|
// Inline hint, just like the C inline keyword (signalled using
|
|
// //go:inline). The compiler will be more likely to inline this function,
|
|
// but it is not a guarantee.
|
|
InlineHint
|
|
|
|
// Don't inline, just like the GCC noinline attribute. Signalled using
|
|
// //go:noinline.
|
|
InlineNone
|
|
)
|
|
|
|
// Create and initialize a new *Program from a *ssa.Program.
|
|
func NewProgram(lprogram *loader.Program) *Program {
|
|
program := lprogram.LoadSSA()
|
|
program.Build()
|
|
|
|
mainPkg := program.ImportedPackage(lprogram.MainPkg().ImportPath)
|
|
if mainPkg == nil {
|
|
panic("could not find main package")
|
|
}
|
|
p := &Program{
|
|
Program: program,
|
|
LoaderProgram: lprogram,
|
|
mainPkg: mainPkg,
|
|
functionMap: make(map[*ssa.Function]*Function),
|
|
}
|
|
|
|
for _, pkg := range lprogram.Sorted() {
|
|
p.AddPackage(program.ImportedPackage(pkg.ImportPath))
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
// Add a package to this Program. All packages need to be added first before any
|
|
// analysis is done for correct results.
|
|
func (p *Program) AddPackage(pkg *ssa.Package) {
|
|
memberNames := make([]string, 0)
|
|
for name := range pkg.Members {
|
|
memberNames = append(memberNames, name)
|
|
}
|
|
sort.Strings(memberNames)
|
|
|
|
for _, name := range memberNames {
|
|
member := pkg.Members[name]
|
|
switch member := member.(type) {
|
|
case *ssa.Function:
|
|
p.addFunction(member)
|
|
case *ssa.Type:
|
|
methods := getAllMethods(pkg.Prog, member.Type())
|
|
if !types.IsInterface(member.Type()) {
|
|
// named type
|
|
for _, method := range methods {
|
|
p.addFunction(pkg.Prog.MethodValue(method))
|
|
}
|
|
}
|
|
case *ssa.Global:
|
|
// Ignore. Globals are not handled here.
|
|
case *ssa.NamedConst:
|
|
// Ignore: these are already resolved.
|
|
default:
|
|
panic("unknown member type: " + member.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Program) addFunction(ssaFn *ssa.Function) {
|
|
if _, ok := p.functionMap[ssaFn]; ok {
|
|
return
|
|
}
|
|
f := &Function{Function: ssaFn}
|
|
f.parsePragmas()
|
|
p.Functions = append(p.Functions, f)
|
|
p.functionMap[ssaFn] = f
|
|
|
|
for _, anon := range ssaFn.AnonFuncs {
|
|
p.addFunction(anon)
|
|
}
|
|
}
|
|
|
|
// Return true if this package imports "unsafe", false otherwise.
|
|
func hasUnsafeImport(pkg *types.Package) bool {
|
|
for _, imp := range pkg.Imports() {
|
|
if imp == types.Unsafe {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *Program) GetFunction(ssaFn *ssa.Function) *Function {
|
|
return p.functionMap[ssaFn]
|
|
}
|
|
|
|
func (p *Program) MainPkg() *ssa.Package {
|
|
return p.mainPkg
|
|
}
|
|
|
|
// Parse compiler directives in the preceding comments.
|
|
func (f *Function) parsePragmas() {
|
|
if f.Syntax() == nil {
|
|
return
|
|
}
|
|
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
|
|
for _, comment := range decl.Doc.List {
|
|
text := comment.Text
|
|
if strings.HasPrefix(text, "//export ") {
|
|
// Rewrite '//export' to '//go:export' for compatibility with
|
|
// gc.
|
|
text = "//go:" + text[2:]
|
|
}
|
|
if !strings.HasPrefix(text, "//go:") {
|
|
continue
|
|
}
|
|
parts := strings.Fields(text)
|
|
switch parts[0] {
|
|
case "//go:export":
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
f.linkName = parts[1]
|
|
f.exported = true
|
|
case "//go:wasm-module":
|
|
// Alternative comment for setting the import module.
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
f.module = parts[1]
|
|
case "//go:inline":
|
|
f.inline = InlineHint
|
|
case "//go:noinline":
|
|
f.inline = InlineNone
|
|
case "//go:linkname":
|
|
if len(parts) != 3 || parts[1] != f.Name() {
|
|
continue
|
|
}
|
|
// Only enable go:linkname when the package imports "unsafe".
|
|
// This is a slightly looser requirement than what gc uses: gc
|
|
// requires the file to import "unsafe", not the package as a
|
|
// whole.
|
|
if hasUnsafeImport(f.Pkg.Pkg) {
|
|
f.linkName = parts[2]
|
|
}
|
|
case "//go:nobounds":
|
|
// Skip bounds checking in this function. Useful for some
|
|
// runtime functions.
|
|
// This is somewhat dangerous and thus only imported in packages
|
|
// that import unsafe.
|
|
if hasUnsafeImport(f.Pkg.Pkg) {
|
|
f.nobounds = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (f *Function) IsNoBounds() bool {
|
|
return f.nobounds
|
|
}
|
|
|
|
// Return true iff this function is externally visible.
|
|
func (f *Function) IsExported() bool {
|
|
return f.exported || f.CName() != ""
|
|
}
|
|
|
|
// Return the inline directive of this function.
|
|
func (f *Function) Inline() InlineType {
|
|
return f.inline
|
|
}
|
|
|
|
// Return the module name if not the default.
|
|
func (f *Function) Module() string {
|
|
return f.module
|
|
}
|
|
|
|
// Return the link name for this function.
|
|
func (f *Function) LinkName() string {
|
|
if f.linkName != "" {
|
|
return f.linkName
|
|
}
|
|
if f.Signature.Recv() != nil {
|
|
// Method on a defined type (which may be a pointer).
|
|
return f.RelString(nil)
|
|
} else {
|
|
// Bare function.
|
|
if name := f.CName(); name != "" {
|
|
// Name CGo functions directly.
|
|
return name
|
|
} else {
|
|
return f.RelString(nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the name of the C function if this is a CGo wrapper. Otherwise, return
|
|
// a zero-length string.
|
|
func (f *Function) CName() string {
|
|
name := f.Name()
|
|
if strings.HasPrefix(name, "_Cfunc_") {
|
|
// emitted by `go tool cgo`
|
|
return name[len("_Cfunc_"):]
|
|
}
|
|
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)
|
|
methods := make([]*types.Selection, ms.Len())
|
|
for i := 0; i < ms.Len(); i++ {
|
|
methods[i] = ms.At(i)
|
|
}
|
|
return methods
|
|
}
|