mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-12 15:03:41 +00:00
Rename analysis.go -> passes.go
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// 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 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 (p *Program) AnalyseCallgraph() {
|
||||
for _, f := range p.Functions {
|
||||
// Clear, if AnalyseCallgraph has been called before.
|
||||
f.children = nil
|
||||
f.parents = nil
|
||||
|
||||
for _, block := range f.fn.Blocks {
|
||||
for _, instr := range block.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case *ssa.Call:
|
||||
if instr.Common().IsInvoke() {
|
||||
continue
|
||||
}
|
||||
switch call := instr.Call.Value.(type) {
|
||||
case *ssa.Builtin:
|
||||
// ignore
|
||||
case *ssa.Function:
|
||||
if isCGoInternal(call.Name()) {
|
||||
continue
|
||||
}
|
||||
child := p.GetFunction(call)
|
||||
if child.CName() != "" {
|
||||
continue // assume non-blocking
|
||||
}
|
||||
if child.Name(false) == "runtime.Sleep" {
|
||||
f.blocking = true
|
||||
}
|
||||
f.children = append(f.children, child)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, f := range p.Functions {
|
||||
for _, child := range f.children {
|
||||
child.parents = append(child.parents, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find all types that are put in an interface.
|
||||
func (p *Program) AnalyseInterfaceConversions() {
|
||||
// Clear, if AnalyseTypes has been called before.
|
||||
p.typesWithMethods = make(map[string]*InterfaceType)
|
||||
p.typesWithoutMethods = make(map[string]int)
|
||||
|
||||
for _, f := range p.Functions {
|
||||
for _, block := range f.fn.Blocks {
|
||||
for _, instr := range block.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case *ssa.MakeInterface:
|
||||
methods := getAllMethods(f.fn.Prog, instr.X.Type())
|
||||
name := instr.X.Type().String()
|
||||
if _, ok := p.typesWithMethods[name]; !ok && len(methods) > 0 {
|
||||
t := &InterfaceType{
|
||||
t: instr.X.Type(),
|
||||
Num: len(p.typesWithMethods),
|
||||
Methods: make(map[string]*types.Selection),
|
||||
}
|
||||
for _, sel := range methods {
|
||||
name := MethodName(sel.Obj().(*types.Func))
|
||||
t.Methods[name] = sel
|
||||
}
|
||||
p.typesWithMethods[instr.X.Type().String()] = t
|
||||
} else if _, ok := p.typesWithoutMethods[name]; !ok && len(methods) == 0 {
|
||||
p.typesWithoutMethods[name] = len(p.typesWithoutMethods)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Analyse which functions are recursively blocking.
|
||||
//
|
||||
// Depends on AnalyseCallgraph.
|
||||
func (p *Program) AnalyseBlockingRecursive() {
|
||||
worklist := make([]*Function, 0)
|
||||
|
||||
// Fill worklist with directly blocking functions.
|
||||
for _, f := range p.Functions {
|
||||
if f.blocking {
|
||||
worklist = append(worklist, f)
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
f := worklist[len(worklist)-1]
|
||||
worklist = worklist[:len(worklist)-1]
|
||||
for _, parent := range f.parents {
|
||||
if !parent.blocking {
|
||||
parent.blocking = true
|
||||
worklist = append(worklist, parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (p *Program) AnalyseGoCalls() {
|
||||
p.goCalls = nil
|
||||
for _, f := range p.Functions {
|
||||
for _, block := range f.fn.Blocks {
|
||||
for _, instr := range block.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case *ssa.Go:
|
||||
p.goCalls = append(p.goCalls, instr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, instr := range p.goCalls {
|
||||
switch instr := instr.Call.Value.(type) {
|
||||
case *ssa.Builtin:
|
||||
case *ssa.Function:
|
||||
if p.functionMap[instr].blocking {
|
||||
p.needsScheduler = true
|
||||
}
|
||||
default:
|
||||
panic("unknown go call function type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Whether this function needs a scheduler.
|
||||
//
|
||||
// Depends on AnalyseGoCalls.
|
||||
func (p *Program) NeedsScheduler() bool {
|
||||
return p.needsScheduler
|
||||
}
|
||||
|
||||
// Whether this function blocks. Builtins are also accepted for convenience.
|
||||
// They will always be non-blocking.
|
||||
//
|
||||
// Depends on AnalyseBlockingRecursive.
|
||||
func (p *Program) IsBlocking(f *Function) bool {
|
||||
if !p.needsScheduler {
|
||||
return false
|
||||
}
|
||||
return f.blocking
|
||||
}
|
||||
|
||||
// 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 (p *Program) TypeNum(typ types.Type) (int, bool) {
|
||||
if n, ok := p.typesWithoutMethods[typ.String()]; ok {
|
||||
return n, true
|
||||
} else if meta, ok := p.typesWithMethods[typ.String()]; ok {
|
||||
return len(p.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 (p *Program) MethodNum(method *types.Func) int {
|
||||
name := MethodName(method)
|
||||
if _, ok := p.methodSignatureNames[name]; !ok {
|
||||
p.methodSignatureNames[name] = len(p.methodSignatureNames)
|
||||
}
|
||||
return p.methodSignatureNames[MethodName(method)]
|
||||
}
|
||||
|
||||
// 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 (p *Program) FirstDynamicType() int {
|
||||
return len(p.typesWithoutMethods)
|
||||
}
|
||||
|
||||
// Return all types with methods, sorted by type ID.
|
||||
func (p *Program) AllDynamicTypes() []*InterfaceType {
|
||||
l := make([]*InterfaceType, len(p.typesWithMethods))
|
||||
for _, m := range p.typesWithMethods {
|
||||
l[m.Num] = m
|
||||
}
|
||||
return l
|
||||
}
|
||||
Reference in New Issue
Block a user