mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-10 22:13:39 +00:00
compiler: lower interfaces in a separate pass
This commit changes many things:
* Most interface-related operations are moved into an optimization
pass for more modularity. IR construction creates pseudo-calls which
are lowered in this pass.
* Type codes are assigned in this interface lowering pass, after DCE.
* Type codes are sorted by usage: types more often used in type
asserts are assigned lower numbers to ease jump table construction
during machine code generation.
* Interface assertions are optimized: they are replaced by constant
false, comparison against a constant, or a typeswitch with only
concrete types in the general case.
* Interface calls are replaced with unreachable, direct calls, or a
concrete type switch with direct calls depending on the number of
implementing types. This hopefully makes some interface patterns
zero-cost.
These changes lead to a ~0.5K reduction in code size on Cortex-M for
testdata/interface.go. It appears that a major cause for this is the
replacement of function pointers with direct calls, which are far more
susceptible to optimization. Also, not having a fixed global array of
function pointers greatly helps dead code elimination.
This change also makes future optimizations easier, like optimizations
on interface value comparisons.
This commit is contained in:
@@ -278,8 +278,6 @@ func (p *Program) interpret(instrs []ssa.Instruction, paramKeys []*ssa.Parameter
|
||||
} else {
|
||||
return i, errors.New("todo: init IndexAddr index: " + instr.Index.String())
|
||||
}
|
||||
case *ssa.MakeInterface:
|
||||
locals[instr] = &InterfaceValue{instr.X.Type(), locals[instr.X]}
|
||||
case *ssa.MakeMap:
|
||||
locals[instr] = &MapValue{instr.Type().Underlying().(*types.Map), nil, nil}
|
||||
case *ssa.MapUpdate:
|
||||
@@ -388,7 +386,6 @@ func canInterpret(callee *ssa.Function) bool {
|
||||
case *ssa.Extract:
|
||||
case *ssa.FieldAddr:
|
||||
case *ssa.IndexAddr:
|
||||
case *ssa.MakeInterface:
|
||||
case *ssa.MakeMap:
|
||||
case *ssa.MapUpdate:
|
||||
case *ssa.Return:
|
||||
@@ -447,8 +444,6 @@ func (p *Program) getZeroValue(t types.Type) (Value, error) {
|
||||
return &ZeroBasicValue{typ}, nil
|
||||
case *types.Signature:
|
||||
return &FunctionValue{typ, nil}, nil
|
||||
case *types.Interface:
|
||||
return &InterfaceValue{typ, nil}, nil
|
||||
case *types.Map:
|
||||
return &MapValue{typ, nil, nil}, nil
|
||||
case *types.Pointer:
|
||||
@@ -492,11 +487,6 @@ type FunctionValue struct {
|
||||
Elem *ssa.Function
|
||||
}
|
||||
|
||||
type InterfaceValue struct {
|
||||
Type types.Type
|
||||
Elem Value
|
||||
}
|
||||
|
||||
type PointerBitCastValue struct {
|
||||
Type types.Type
|
||||
Elem Value
|
||||
|
||||
@@ -19,21 +19,18 @@ import (
|
||||
// View on all functions, types, and globals in a program, with analysis
|
||||
// results.
|
||||
type Program struct {
|
||||
Program *ssa.Program
|
||||
mainPkg *ssa.Package
|
||||
Functions []*Function
|
||||
functionMap map[*ssa.Function]*Function
|
||||
Globals []*Global
|
||||
globalMap map[*ssa.Global]*Global
|
||||
comments map[string]*ast.CommentGroup
|
||||
NamedTypes []*NamedType
|
||||
needsScheduler bool
|
||||
goCalls []*ssa.Go
|
||||
typesWithMethods map[string]*TypeWithMethods // see AnalyseInterfaceConversions
|
||||
typesWithoutMethods map[string]int // see AnalyseInterfaceConversions
|
||||
methodSignatureNames map[string]int // see MethodNum
|
||||
interfaces map[string]*Interface // see AnalyseInterfaceConversions
|
||||
fpWithContext map[string]struct{} // see AnalyseFunctionPointers
|
||||
Program *ssa.Program
|
||||
mainPkg *ssa.Package
|
||||
Functions []*Function
|
||||
functionMap map[*ssa.Function]*Function
|
||||
Globals []*Global
|
||||
globalMap map[*ssa.Global]*Global
|
||||
comments map[string]*ast.CommentGroup
|
||||
NamedTypes []*NamedType
|
||||
needsScheduler bool
|
||||
goCalls []*ssa.Go
|
||||
typesInInterfaces map[string]struct{} // see AnalyseInterfaceConversions
|
||||
fpWithContext map[string]struct{} // see AnalyseFunctionPointers
|
||||
}
|
||||
|
||||
// Function or method.
|
||||
@@ -179,13 +176,11 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
||||
}
|
||||
|
||||
p := &Program{
|
||||
Program: program,
|
||||
mainPkg: mainPkg,
|
||||
functionMap: make(map[*ssa.Function]*Function),
|
||||
globalMap: make(map[*ssa.Global]*Global),
|
||||
methodSignatureNames: make(map[string]int),
|
||||
interfaces: make(map[string]*Interface),
|
||||
comments: comments,
|
||||
Program: program,
|
||||
mainPkg: mainPkg,
|
||||
functionMap: make(map[*ssa.Function]*Function),
|
||||
globalMap: make(map[*ssa.Global]*Global),
|
||||
comments: comments,
|
||||
}
|
||||
|
||||
for _, pkg := range packageList {
|
||||
@@ -270,18 +265,6 @@ func (p *Program) GetGlobal(ssaGlobal *ssa.Global) *Global {
|
||||
return p.globalMap[ssaGlobal]
|
||||
}
|
||||
|
||||
// SortMethods sorts the list of methods by method ID.
|
||||
func (p *Program) SortMethods(methods []*types.Selection) {
|
||||
m := &methodList{methods: methods, program: p}
|
||||
sort.Sort(m)
|
||||
}
|
||||
|
||||
// SortFuncs sorts the list of functions by method ID.
|
||||
func (p *Program) SortFuncs(funcs []*types.Func) {
|
||||
m := &funcList{funcs: funcs, program: p}
|
||||
sort.Sort(m)
|
||||
}
|
||||
|
||||
func (p *Program) MainPkg() *ssa.Package {
|
||||
return p.mainPkg
|
||||
}
|
||||
@@ -442,46 +425,6 @@ func (p *Program) IsVolatile(t types.Type) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper type to implement sort.Interface for []*types.Selection.
|
||||
type methodList struct {
|
||||
methods []*types.Selection
|
||||
program *Program
|
||||
}
|
||||
|
||||
func (m *methodList) Len() int {
|
||||
return len(m.methods)
|
||||
}
|
||||
|
||||
func (m *methodList) Less(i, j int) bool {
|
||||
iid := m.program.MethodNum(m.methods[i].Obj().(*types.Func))
|
||||
jid := m.program.MethodNum(m.methods[j].Obj().(*types.Func))
|
||||
return iid < jid
|
||||
}
|
||||
|
||||
func (m *methodList) Swap(i, j int) {
|
||||
m.methods[i], m.methods[j] = m.methods[j], m.methods[i]
|
||||
}
|
||||
|
||||
// Wrapper type to implement sort.Interface for []*types.Func.
|
||||
type funcList struct {
|
||||
funcs []*types.Func
|
||||
program *Program
|
||||
}
|
||||
|
||||
func (fl *funcList) Len() int {
|
||||
return len(fl.funcs)
|
||||
}
|
||||
|
||||
func (fl *funcList) Less(i, j int) bool {
|
||||
iid := fl.program.MethodNum(fl.funcs[i])
|
||||
jid := fl.program.MethodNum(fl.funcs[j])
|
||||
return iid < jid
|
||||
}
|
||||
|
||||
func (fl *funcList) Swap(i, j int) {
|
||||
fl.funcs[i], fl.funcs[j] = fl.funcs[j], fl.funcs[i]
|
||||
}
|
||||
|
||||
// Return true if this is a CGo-internal function that can be ignored.
|
||||
func isCGoInternal(name string) bool {
|
||||
if strings.HasPrefix(name, "_Cgo_") || strings.HasPrefix(name, "_cgo") {
|
||||
|
||||
+5
-97
@@ -2,8 +2,6 @@ package ir
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
@@ -59,18 +57,6 @@ func Signature(sig *types.Signature) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// Convert an interface type to a string of all method strings, separated by
|
||||
// "; ". For example: "Read([]byte) (int, error); Close() error"
|
||||
func InterfaceKey(itf *types.Interface) string {
|
||||
methodStrings := []string{}
|
||||
for i := 0; i < itf.NumMethods(); i++ {
|
||||
method := itf.Method(i)
|
||||
methodStrings = append(methodStrings, MethodSignature(method))
|
||||
}
|
||||
sort.Strings(methodStrings)
|
||||
return strings.Join(methodStrings, ";")
|
||||
}
|
||||
|
||||
// Fill in parents of all functions.
|
||||
//
|
||||
// All packages need to be added before this pass can run, or it will produce
|
||||
@@ -117,30 +103,17 @@ func (p *Program) AnalyseCallgraph() {
|
||||
|
||||
// Find all types that are put in an interface.
|
||||
func (p *Program) AnalyseInterfaceConversions() {
|
||||
// Clear, if AnalyseTypes has been called before.
|
||||
p.typesWithoutMethods = map[string]int{"nil": 0}
|
||||
p.typesWithMethods = map[string]*TypeWithMethods{}
|
||||
// Clear, if AnalyseInterfaceConversions has been called before.
|
||||
p.typesInInterfaces = map[string]struct{}{}
|
||||
|
||||
for _, f := range p.Functions {
|
||||
for _, block := range f.Blocks {
|
||||
for _, instr := range block.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case *ssa.MakeInterface:
|
||||
methods := getAllMethods(f.Prog, instr.X.Type())
|
||||
name := instr.X.Type().String()
|
||||
if _, ok := p.typesWithMethods[name]; !ok && len(methods) > 0 {
|
||||
t := &TypeWithMethods{
|
||||
t: instr.X.Type(),
|
||||
Num: len(p.typesWithMethods),
|
||||
Methods: make(map[string]*types.Selection),
|
||||
}
|
||||
for _, sel := range methods {
|
||||
name := MethodSignature(sel.Obj().(*types.Func))
|
||||
t.Methods[name] = sel
|
||||
}
|
||||
p.typesWithMethods[name] = t
|
||||
} else if _, ok := p.typesWithoutMethods[name]; !ok && len(methods) == 0 {
|
||||
p.typesWithoutMethods[name] = len(p.typesWithoutMethods)
|
||||
if _, ok := p.typesInInterfaces[name]; !ok {
|
||||
p.typesInInterfaces[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,75 +322,10 @@ func (p *Program) IsBlocking(f *Function) bool {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// InterfaceNum returns the numeric interface ID of this type, for use in type
|
||||
// asserts.
|
||||
func (p *Program) InterfaceNum(itfType *types.Interface) int {
|
||||
key := InterfaceKey(itfType)
|
||||
if itf, ok := p.interfaces[key]; !ok {
|
||||
num := len(p.interfaces)
|
||||
p.interfaces[key] = &Interface{Num: num, Type: itfType}
|
||||
return num
|
||||
} else {
|
||||
return itf.Num
|
||||
}
|
||||
}
|
||||
|
||||
// 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 := MethodSignature(method)
|
||||
if _, ok := p.methodSignatureNames[name]; !ok {
|
||||
p.methodSignatureNames[name] = len(p.methodSignatureNames)
|
||||
}
|
||||
return p.methodSignatureNames[MethodSignature(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() []*TypeWithMethods {
|
||||
l := make([]*TypeWithMethods, len(p.typesWithMethods))
|
||||
for _, m := range p.typesWithMethods {
|
||||
l[m.Num] = m
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// Return all interface types, sorted by interface ID.
|
||||
func (p *Program) AllInterfaces() []*Interface {
|
||||
l := make([]*Interface, len(p.interfaces))
|
||||
for _, itf := range p.interfaces {
|
||||
l[itf.Num] = itf
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (p *Program) FunctionNeedsContext(f *Function) bool {
|
||||
if !f.addressTaken {
|
||||
if f.Signature.Recv() != nil {
|
||||
_, hasInterfaceConversion := p.TypeNum(f.Signature.Recv().Type())
|
||||
_, hasInterfaceConversion := p.typesInInterfaces[f.Signature.Recv().Type().String()]
|
||||
if hasInterfaceConversion && p.SignatureNeedsContext(f.Signature) {
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user