tinygo/analysis.go
Ayke van Laethem a97ca91c1f
compiler: Implement interface calls
This is a big combined change. Other changes in this commit:

  * Analyze makeinterface and make sure type switches don't include
    unnecessary cases.
  * Do not include CGo wrapper functions in the analyzer callgraph.
    This also avoids some unnecessary type IDs.
  * Give all Go named structs a name in LLVM.
  * Use such a named struct for compiler-generated task data.
  * Use the type and function names defined by the ssa and types
    package instead of generating our own.
  * Some improvements to function pointers.
  * A few other minor improvements.

The one thing lacking here is interface-to-interface assertions.
2018-06-17 15:50:19 +02:00

290 строки
8 КиБ
Go

package main
import (
"go/types"
"golang.org/x/tools/go/ssa"
)
// Analysis results over a whole program.
type Analysis struct {
functions map[*ssa.Function]*FuncMeta
needsScheduler bool
goCalls []*ssa.Go
typesWithMethods map[string]*TypeMeta
typesWithoutMethods map[string]int
methodSignatureNames map[string]int
}
// Some analysis results of a single function.
type FuncMeta struct {
f *ssa.Function
blocking bool
parents []*ssa.Function // calculated by AnalyseCallgraph
children []*ssa.Function
}
type TypeMeta struct {
t types.Type
Num int
Methods map[string]*types.Selection
}
// Return a new Analysis object.
func NewAnalysis() *Analysis {
return &Analysis{
functions: make(map[*ssa.Function]*FuncMeta),
typesWithMethods: make(map[string]*TypeMeta),
typesWithoutMethods: make(map[string]int),
methodSignatureNames: make(map[string]int),
}
}
// Add a given package to the analyzer, to be analyzed later.
func (a *Analysis) AddPackage(pkg *ssa.Package) {
for _, member := range pkg.Members {
switch member := member.(type) {
case *ssa.Function:
if isCGoInternal(member.Name()) || getCName(member.Name()) != "" {
continue
}
a.addFunction(member)
case *ssa.Type:
methods := getAllMethods(pkg.Prog, member.Type())
if types.IsInterface(member.Type()) {
for _, method := range methods {
a.MethodName(method.Obj().(*types.Func))
}
} else { // named type
for _, method := range methods {
a.addFunction(pkg.Prog.MethodValue(method))
}
}
}
}
}
// Analyze the given function quickly without any recursion, and add it to the
// list of functions in the analyzer.
func (a *Analysis) addFunction(f *ssa.Function) {
fm := &FuncMeta{}
for _, block := range f.Blocks {
for _, instr := range block.Instrs {
switch instr := instr.(type) {
case *ssa.Call:
if instr.Common().IsInvoke() {
name := a.MethodName(instr.Common().Method)
a.methodSignatureNames[name] = len(a.methodSignatureNames)
} else {
switch call := instr.Call.Value.(type) {
case *ssa.Builtin:
// ignore
case *ssa.Function:
if isCGoInternal(call.Name()) || getCName(call.Name()) != "" {
continue
}
name := getFunctionName(call, false)
if name == "runtime.Sleep" {
fm.blocking = true
}
fm.children = append(fm.children, call)
}
}
case *ssa.MakeInterface:
methods := getAllMethods(f.Prog, instr.X.Type())
if _, ok := a.typesWithMethods[instr.X.Type().String()]; !ok && len(methods) > 0 {
meta := &TypeMeta{
t: instr.X.Type(),
Num: len(a.typesWithMethods),
Methods: make(map[string]*types.Selection),
}
for _, sel := range methods {
name := a.MethodName(sel.Obj().(*types.Func))
meta.Methods[name] = sel
}
a.typesWithMethods[instr.X.Type().String()] = meta
} else if _, ok := a.typesWithoutMethods[instr.X.Type().String()]; !ok && len(methods) == 0 {
a.typesWithoutMethods[instr.X.Type().String()] = len(a.typesWithoutMethods)
}
case *ssa.Go:
a.goCalls = append(a.goCalls, instr)
}
}
}
a.functions[f] = fm
for _, child := range f.AnonFuncs {
a.addFunction(child)
}
}
// Make a readable version of the method signature (including the function name,
// excluding the receiver name). This string is used internally to match
// interfaces and to call the correct method on an interface. Examples:
//
// String() string
// Read([]byte) (int, error)
func (a *Analysis) MethodName(method *types.Func) string {
sig := method.Type().(*types.Signature)
name := method.Name()
if sig.Params().Len() == 0 {
name += "()"
} else {
name += "("
for i := 0; i < sig.Params().Len(); i++ {
if i > 0 {
name += ", "
}
name += sig.Params().At(i).Type().String()
}
name += ")"
}
if sig.Results().Len() == 0 {
// keep as-is
} else if sig.Results().Len() == 1 {
name += " " + sig.Results().At(0).Type().String()
} else {
name += " ("
for i := 0; i < sig.Results().Len(); i++ {
if i > 0 {
name += ", "
}
name += sig.Results().At(i).Type().String()
}
name += ")"
}
return name
}
// Fill in parents of all functions.
//
// All packages need to be added before this pass can run, or it will produce
// incorrect results.
func (a *Analysis) AnalyseCallgraph() {
for f, fm := range a.functions {
for _, child := range fm.children {
childRes, ok := a.functions[child]
if !ok {
println("child not found: " + child.Pkg.Pkg.Path() + "." + child.Name() + ", function: " + f.Name())
continue
}
childRes.parents = append(childRes.parents, f)
}
}
}
// Analyse which functions are recursively blocking.
//
// Depends on AnalyseCallgraph.
func (a *Analysis) AnalyseBlockingRecursive() {
worklist := make([]*FuncMeta, 0)
// Fill worklist with directly blocking functions.
for _, fm := range a.functions {
if fm.blocking {
worklist = append(worklist, fm)
}
}
// Keep reducing this worklist by marking a function as recursively blocking
// from the worklist and pushing all its parents that are non-blocking.
// This is somewhat similar to a worklist in a mark-sweep garbage collector.
// The work items are then grey objects.
for len(worklist) != 0 {
// Pick the topmost.
fm := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
for _, parent := range fm.parents {
parentfm := a.functions[parent]
if !parentfm.blocking {
parentfm.blocking = true
worklist = append(worklist, parentfm)
}
}
}
}
// Check whether we need a scheduler. A scheduler is only necessary when there
// are go calls that start blocking functions (if they're not blocking, the go
// function can be turned into a regular function call).
//
// Depends on AnalyseBlockingRecursive.
func (a *Analysis) AnalyseGoCalls() {
for _, instr := range a.goCalls {
if a.isBlocking(instr.Call.Value) {
a.needsScheduler = true
}
}
}
// Whether this function needs a scheduler.
//
// Depends on AnalyseGoCalls.
func (a *Analysis) NeedsScheduler() bool {
return a.needsScheduler
}
// Whether this function blocks. Builtins are also accepted for convenience.
// They will always be non-blocking.
//
// Depends on AnalyseBlockingRecursive.
func (a *Analysis) IsBlocking(f ssa.Value) bool {
if !a.needsScheduler {
return false
}
return a.isBlocking(f)
}
func (a *Analysis) isBlocking(f ssa.Value) bool {
switch f := f.(type) {
case *ssa.Builtin:
return false
case *ssa.Function:
return a.functions[f].blocking
default:
panic("Analysis.IsBlocking on unknown type")
}
}
// Return the type number and whether this type is actually used. Used in
// interface conversions (type is always used) and type asserts (type may not be
// used, meaning assert is always false in this program).
//
// May only be used after all packages have been added to the analyser.
func (a *Analysis) TypeNum(typ types.Type) (int, bool) {
if n, ok := a.typesWithoutMethods[typ.String()]; ok {
return n, true
} else if meta, ok := a.typesWithMethods[typ.String()]; ok {
return len(a.typesWithoutMethods) + meta.Num, true
} else {
return -1, false // type is never put in an interface
}
}
// MethodNum returns the numeric ID of this method, to be used in method lookups
// on interfaces for example.
func (a *Analysis) MethodNum(method *types.Func) int {
if n, ok := a.methodSignatureNames[a.MethodName(method)]; ok {
return n
}
return -1 // signal error
}
// The start index of the first dynamic type that has methods.
// Types without methods always have a lower ID and types with methods have this
// or a higher ID.
//
// May only be used after all packages have been added to the analyser.
func (a *Analysis) FirstDynamicType() int {
return len(a.typesWithoutMethods)
}
// Return all types with methods, sorted by type ID.
func (a *Analysis) AllDynamicTypes() []*TypeMeta {
l := make([]*TypeMeta, len(a.typesWithMethods))
for _, m := range a.typesWithMethods {
l[m.Num] = m
}
return l
}