提交 685d8bf0 编写于 作者: chai2010's avatar chai2010

add internal/ssa

上级 480cc06c
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
// Simple block optimizations to simplify the control flow graph.
// TODO(adonovan): opt: instead of creating several "unreachable" blocks
// per function in the Builder, reuse a single one (e.g. at Blocks[1])
// to reduce garbage.
import (
"fmt"
"os"
)
// If true, perform sanity checking and show progress at each
// successive iteration of optimizeBlocks. Very verbose.
const debugBlockOpt = false
// markReachable sets Index=-1 for all blocks reachable from b.
func markReachable(b *BasicBlock) {
b.Index = -1
for _, succ := range b.Succs {
if succ.Index == 0 {
markReachable(succ)
}
}
}
// deleteUnreachableBlocks marks all reachable blocks of f and
// eliminates (nils) all others, including possibly cyclic subgraphs.
//
func deleteUnreachableBlocks(f *Function) {
const white, black = 0, -1
// We borrow b.Index temporarily as the mark bit.
for _, b := range f.Blocks {
b.Index = white
}
markReachable(f.Blocks[0])
if f.Recover != nil {
markReachable(f.Recover)
}
for i, b := range f.Blocks {
if b.Index == white {
for _, c := range b.Succs {
if c.Index == black {
c.removePred(b) // delete white->black edge
}
}
if debugBlockOpt {
fmt.Fprintln(os.Stderr, "unreachable", b)
}
f.Blocks[i] = nil // delete b
}
}
f.removeNilBlocks()
}
// jumpThreading attempts to apply simple jump-threading to block b,
// in which a->b->c become a->c if b is just a Jump.
// The result is true if the optimization was applied.
//
func jumpThreading(f *Function, b *BasicBlock) bool {
if b.Index == 0 {
return false // don't apply to entry block
}
if b.Instrs == nil {
return false
}
if _, ok := b.Instrs[0].(*Jump); !ok {
return false // not just a jump
}
c := b.Succs[0]
if c == b {
return false // don't apply to degenerate jump-to-self.
}
if c.hasPhi() {
return false // not sound without more effort
}
for j, a := range b.Preds {
a.replaceSucc(b, c)
// If a now has two edges to c, replace its degenerate If by Jump.
if len(a.Succs) == 2 && a.Succs[0] == c && a.Succs[1] == c {
jump := new(Jump)
jump.setBlock(a)
a.Instrs[len(a.Instrs)-1] = jump
a.Succs = a.Succs[:1]
c.removePred(b)
} else {
if j == 0 {
c.replacePred(b, a)
} else {
c.Preds = append(c.Preds, a)
}
}
if debugBlockOpt {
fmt.Fprintln(os.Stderr, "jumpThreading", a, b, c)
}
}
f.Blocks[b.Index] = nil // delete b
return true
}
// fuseBlocks attempts to apply the block fusion optimization to block
// a, in which a->b becomes ab if len(a.Succs)==len(b.Preds)==1.
// The result is true if the optimization was applied.
//
func fuseBlocks(f *Function, a *BasicBlock) bool {
if len(a.Succs) != 1 {
return false
}
b := a.Succs[0]
if len(b.Preds) != 1 {
return false
}
// Degenerate &&/|| ops may result in a straight-line CFG
// containing φ-nodes. (Ideally we'd replace such them with
// their sole operand but that requires Referrers, built later.)
if b.hasPhi() {
return false // not sound without further effort
}
// Eliminate jump at end of A, then copy all of B across.
a.Instrs = append(a.Instrs[:len(a.Instrs)-1], b.Instrs...)
for _, instr := range b.Instrs {
instr.setBlock(a)
}
// A inherits B's successors
a.Succs = append(a.succs2[:0], b.Succs...)
// Fix up Preds links of all successors of B.
for _, c := range b.Succs {
c.replacePred(b, a)
}
if debugBlockOpt {
fmt.Fprintln(os.Stderr, "fuseBlocks", a, b)
}
f.Blocks[b.Index] = nil // delete b
return true
}
// optimizeBlocks() performs some simple block optimizations on a
// completed function: dead block elimination, block fusion, jump
// threading.
//
func optimizeBlocks(f *Function) {
deleteUnreachableBlocks(f)
// Loop until no further progress.
changed := true
for changed {
changed = false
if debugBlockOpt {
f.WriteTo(os.Stderr)
mustSanityCheck(f, nil)
}
for _, b := range f.Blocks {
// f.Blocks will temporarily contain nils to indicate
// deleted blocks; we remove them at the end.
if b == nil {
continue
}
// Fuse blocks. b->c becomes bc.
if fuseBlocks(f, b) {
changed = true
}
// a->b->c becomes a->c if b contains only a Jump.
if jumpThreading(f, b) {
changed = true
continue // (b was disconnected)
}
}
}
f.removeNilBlocks()
}
此差异已折叠。
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
// This file defines the Const SSA value type.
import (
"fmt"
"strconv"
"github.com/wa-lang/wa/internal/constant"
"github.com/wa-lang/wa/internal/token"
"github.com/wa-lang/wa/internal/types"
)
// NewConst returns a new constant of the specified value and type.
// val must be valid according to the specification of Const.Value.
//
func NewConst(val constant.Value, typ types.Type) *Const {
return &Const{typ, val}
}
// intConst returns an 'int' constant that evaluates to i.
// (i is an int64 in case the host is narrower than the target.)
func intConst(i int64) *Const {
return NewConst(constant.MakeInt64(i), tInt)
}
// nilConst returns a nil constant of the specified type, which may
// be any reference type, including interfaces.
//
func nilConst(typ types.Type) *Const {
return NewConst(nil, typ)
}
// stringConst returns a 'string' constant that evaluates to s.
func stringConst(s string) *Const {
return NewConst(constant.MakeString(s), tString)
}
// zeroConst returns a new "zero" constant of the specified type,
// which must not be an array or struct type: the zero values of
// aggregates are well-defined but cannot be represented by Const.
//
func zeroConst(t types.Type) *Const {
switch t := t.(type) {
case *types.Basic:
switch {
case t.Info()&types.IsBoolean != 0:
return NewConst(constant.MakeBool(false), t)
case t.Info()&types.IsNumeric != 0:
return NewConst(constant.MakeInt64(0), t)
case t.Info()&types.IsString != 0:
return NewConst(constant.MakeString(""), t)
case t.Kind() == types.UnsafePointer:
fallthrough
case t.Kind() == types.UntypedNil:
return nilConst(t)
default:
panic(fmt.Sprint("zeroConst for unexpected type:", t))
}
case *types.Pointer, *types.Slice, *types.Interface, *types.Map, *types.Signature:
return nilConst(t)
case *types.Named:
return NewConst(zeroConst(t.Underlying()).Value, t)
case *types.Array, *types.Struct, *types.Tuple:
panic(fmt.Sprint("zeroConst applied to aggregate:", t))
}
panic(fmt.Sprint("zeroConst: unexpected ", t))
}
func (c *Const) RelString(from *types.Package) string {
var s string
if c.Value == nil {
s = "nil"
} else if c.Value.Kind() == constant.String {
s = constant.StringVal(c.Value)
const max = 20
// TODO(adonovan): don't cut a rune in half.
if len(s) > max {
s = s[:max-3] + "..." // abbreviate
}
s = strconv.Quote(s)
} else {
s = c.Value.String()
}
return s + ":" + relType(c.Type(), from)
}
func (c *Const) Name() string {
return c.RelString(nil)
}
func (c *Const) String() string {
return c.Name()
}
func (c *Const) Type() types.Type {
return c.typ
}
func (c *Const) Referrers() *[]Instruction {
return nil
}
func (c *Const) Parent() *Function { return nil }
func (c *Const) Pos() token.Pos {
return token.NoPos
}
// IsNil returns true if this constant represents a typed or untyped nil value.
func (c *Const) IsNil() bool {
return c.Value == nil
}
// TODO(adonovan): move everything below into golang.org/x/tools/go/ssa/interp.
// Int64 returns the numeric value of this constant truncated to fit
// a signed 64-bit integer.
//
func (c *Const) Int64() int64 {
switch x := constant.ToInt(c.Value); x.Kind() {
case constant.Int:
if i, ok := constant.Int64Val(x); ok {
return i
}
return 0
case constant.Float:
f, _ := constant.Float64Val(x)
return int64(f)
}
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
}
// Uint64 returns the numeric value of this constant truncated to fit
// an unsigned 64-bit integer.
//
func (c *Const) Uint64() uint64 {
switch x := constant.ToInt(c.Value); x.Kind() {
case constant.Int:
if u, ok := constant.Uint64Val(x); ok {
return u
}
return 0
case constant.Float:
f, _ := constant.Float64Val(x)
return uint64(f)
}
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
}
// Float64 returns the numeric value of this constant truncated to fit
// a float64.
//
func (c *Const) Float64() float64 {
f, _ := constant.Float64Val(c.Value)
return f
}
// Complex128 returns the complex value of this constant truncated to
// fit a complex128.
//
func (c *Const) Complex128() complex128 {
re, _ := constant.Float64Val(constant.Real(c.Value))
im, _ := constant.Float64Val(constant.Imag(c.Value))
return complex(re, im)
}
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
// This file implements the CREATE phase of SSA construction.
// See builder.go for explanation.
import (
"fmt"
"os"
"sync"
"github.com/wa-lang/wa/internal/ast"
"github.com/wa-lang/wa/internal/token"
"github.com/wa-lang/wa/internal/types"
"github.com/wa-lang/wa/internal/types/typeutil"
)
// NewProgram returns a new SSA Program.
//
// mode controls diagnostics and checking during SSA construction.
//
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
prog := &Program{
Fset: fset,
imported: make(map[string]*Package),
packages: make(map[*types.Package]*Package),
thunks: make(map[selectionKey]*Function),
bounds: make(map[*types.Func]*Function),
mode: mode,
}
h := typeutil.MakeHasher() // protected by methodsMu, in effect
prog.methodSets.SetHasher(h)
prog.canon.SetHasher(h)
return prog
}
// memberFromObject populates package pkg with a member for the
// typechecker object obj.
//
// For objects from Go source code, syntax is the associated syntax
// tree (for funcs and vars only); it will be used during the build
// phase.
//
func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
name := obj.Name()
switch obj := obj.(type) {
case *types.Builtin:
if pkg.Pkg != types.Unsafe {
panic("unexpected builtin object: " + obj.String())
}
case *types.TypeName:
pkg.Members[name] = &Type{
object: obj,
pkg: pkg,
}
case *types.Const:
c := &NamedConst{
object: obj,
Value: NewConst(obj.Val(), obj.Type()),
pkg: pkg,
}
pkg.values[obj] = c.Value
pkg.Members[name] = c
case *types.Var:
g := &Global{
Pkg: pkg,
name: name,
object: obj,
typ: types.NewPointer(obj.Type()), // address
pos: obj.Pos(),
}
pkg.values[obj] = g
pkg.Members[name] = g
case *types.Func:
sig := obj.Type().(*types.Signature)
if sig.Recv() == nil && name == "init" {
pkg.ninit++
name = fmt.Sprintf("init#%d", pkg.ninit)
}
fn := &Function{
name: name,
object: obj,
Signature: sig,
syntax: syntax,
pos: obj.Pos(),
Pkg: pkg,
Prog: pkg.Prog,
}
if syntax == nil {
fn.Synthetic = "loaded from gc object file"
}
pkg.values[obj] = fn
if sig.Recv() == nil {
pkg.Members[name] = fn // package-level function
}
default: // (incl. *types.Package)
panic("unexpected Object type: " + obj.String())
}
}
// membersFromDecl populates package pkg with members for each
// typechecker object (var, func, const or type) associated with the
// specified decl.
//
func membersFromDecl(pkg *Package, decl ast.Decl) {
switch decl := decl.(type) {
case *ast.GenDecl: // import, const, type or var
switch decl.Tok {
case token.CONST:
for _, spec := range decl.Specs {
for _, id := range spec.(*ast.ValueSpec).Names {
if !isBlankIdent(id) {
memberFromObject(pkg, pkg.info.Defs[id], nil)
}
}
}
case token.LET:
for _, spec := range decl.Specs {
for _, id := range spec.(*ast.ValueSpec).Names {
if !isBlankIdent(id) {
memberFromObject(pkg, pkg.info.Defs[id], spec)
}
}
}
case token.TYPE:
for _, spec := range decl.Specs {
id := spec.(*ast.TypeSpec).Name
if !isBlankIdent(id) {
memberFromObject(pkg, pkg.info.Defs[id], nil)
}
}
}
case *ast.FuncDecl:
id := decl.Name
if !isBlankIdent(id) {
memberFromObject(pkg, pkg.info.Defs[id], decl)
}
}
}
// CreatePackage constructs and returns an SSA Package from the
// specified type-checked, error-free file ASTs, and populates its
// Members mapping.
//
// importable determines whether this package should be returned by a
// subsequent call to ImportedPackage(pkg.Path()).
//
// The real work of building SSA form for each function is not done
// until a subsequent call to Package.Build().
//
func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *types.Info, importable bool) *Package {
p := &Package{
Prog: prog,
Members: make(map[string]Member),
values: make(map[types.Object]Value),
Pkg: pkg,
info: info, // transient (CREATE and BUILD phases)
files: files, // transient (CREATE and BUILD phases)
}
// Add init() function.
p.init = &Function{
name: "init",
Signature: new(types.Signature),
Synthetic: "package initializer",
Pkg: p,
Prog: prog,
}
p.Members[p.init.name] = p.init
// CREATE phase.
// Allocate all package members: vars, funcs, consts and types.
if len(files) > 0 {
// Go source package.
for _, file := range files {
for _, decl := range file.Decls {
membersFromDecl(p, decl)
}
}
} else {
// GC-compiled binary package (or "unsafe")
// No code.
// No position information.
scope := p.Pkg.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)
memberFromObject(p, obj, nil)
if obj, ok := obj.(*types.TypeName); ok {
if named, ok := obj.Type().(*types.Named); ok {
for i, n := 0, named.NumMethods(); i < n; i++ {
memberFromObject(p, named.Method(i), nil)
}
}
}
}
}
if prog.mode&BareInits == 0 {
// Add initializer guard variable.
initguard := &Global{
Pkg: p,
name: "init$guard",
typ: types.NewPointer(tBool),
}
p.Members[initguard.Name()] = initguard
}
if prog.mode&GlobalDebug != 0 {
p.SetDebugMode(true)
}
if prog.mode&PrintPackages != 0 {
printMu.Lock()
p.WriteTo(os.Stdout)
printMu.Unlock()
}
if importable {
prog.imported[p.Pkg.Path()] = p
}
prog.packages[p.Pkg] = p
return p
}
// printMu serializes printing of Packages/Functions to stdout.
var printMu sync.Mutex
// AllPackages returns a new slice containing all packages in the
// program prog in unspecified order.
//
func (prog *Program) AllPackages() []*Package {
pkgs := make([]*Package, 0, len(prog.packages))
for _, pkg := range prog.packages {
pkgs = append(pkgs, pkg)
}
return pkgs
}
// ImportedPackage returns the importable Package whose PkgPath
// is path, or nil if no such Package has been created.
//
// A parameter to CreatePackage determines whether a package should be
// considered importable. For example, no import declaration can resolve
// to the ad-hoc main package created by 'go build foo.go'.
//
// TODO(adonovan): rethink this function and the "importable" concept;
// most packages are importable. This function assumes that all
// types.Package.Path values are unique within the ssa.Program, which is
// false---yet this function remains very convenient.
// Clients should use (*Program).Package instead where possible.
// SSA doesn't really need a string-keyed map of packages.
//
func (prog *Program) ImportedPackage(path string) *Package {
return prog.imported[path]
}
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package ssa defines a representation of the elements of Go programs
// (packages, types, functions, variables and constants) using a
// static single-assignment (SSA) form intermediate representation
// (IR) for the bodies of functions.
//
// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
//
// For an introduction to SSA form, see
// http://en.wikipedia.org/wiki/Static_single_assignment_form.
// This page provides a broader reading list:
// http://www.dcs.gla.ac.uk/~jsinger/ssa.html.
//
// The level of abstraction of the SSA form is intentionally close to
// the source language to facilitate construction of source analysis
// tools. It is not intended for machine code generation.
//
// All looping, branching and switching constructs are replaced with
// unstructured control flow. Higher-level control flow constructs
// such as multi-way branch can be reconstructed as needed; see
// ssautil.Switches() for an example.
//
// The simplest way to create the SSA representation of a package is
// to load typed syntax trees using golang.org/x/tools/go/packages, then
// invoke the ssautil.Packages helper function. See ExampleLoadPackages
// and ExampleWholeProgram for examples.
// The resulting ssa.Program contains all the packages and their
// members, but SSA code is not created for function bodies until a
// subsequent call to (*Package).Build or (*Program).Build.
//
// The builder initially builds a naive SSA form in which all local
// variables are addresses of stack locations with explicit loads and
// stores. Registerisation of eligible locals and φ-node insertion
// using dominance and dataflow are then performed as a second pass
// called "lifting" to improve the accuracy and performance of
// subsequent analyses; this pass can be skipped by setting the
// NaiveForm builder flag.
//
// The primary interfaces of this package are:
//
// - Member: a named member of a Go package.
// - Value: an expression that yields a value.
// - Instruction: a statement that consumes values and performs computation.
// - Node: a Value or Instruction (emphasizing its membership in the SSA value graph)
//
// A computation that yields a result implements both the Value and
// Instruction interfaces. The following table shows for each
// concrete type which of these interfaces it implements.
//
// Value? Instruction? Member?
// *Alloc ✔ ✔
// *BinOp ✔ ✔
// *Builtin ✔
// *Call ✔ ✔
// *ChangeInterface ✔ ✔
// *ChangeType ✔ ✔
// *Const ✔
// *Convert ✔ ✔
// *DebugRef ✔
// *Defer ✔
// *Extract ✔ ✔
// *Field ✔ ✔
// *FieldAddr ✔ ✔
// *FreeVar ✔
// *Function ✔ ✔ (func)
// *Global ✔ ✔ (var)
// *If ✔
// *Index ✔ ✔
// *IndexAddr ✔ ✔
// *Jump ✔
// *Lookup ✔ ✔
// *MakeClosure ✔ ✔
// *MakeInterface ✔ ✔
// *MakeMap ✔ ✔
// *MakeSlice ✔ ✔
// *MapUpdate ✔
// *NamedConst ✔ (const)
// *Next ✔ ✔
// *Panic ✔
// *Parameter ✔
// *Phi ✔ ✔
// *Range ✔ ✔
// *Return ✔
// *RunDefers ✔
// *Slice ✔ ✔
// *Store ✔
// *Type ✔ (type)
// *TypeAssert ✔ ✔
// *UnOp ✔ ✔
//
// Other key types in this package include: Program, Package, Function
// and BasicBlock.
//
// The program representation constructed by this package is fully
// resolved internally, i.e. it does not rely on the names of Values,
// Packages, Functions, Types or BasicBlocks for the correct
// interpretation of the program. Only the identities of objects and
// the topology of the SSA and type graphs are semantically
// significant. (There is one exception: Ids, used to identify field
// and method names, contain strings.) Avoidance of name-based
// operations simplifies the implementation of subsequent passes and
// can make them very efficient. Many objects are nonetheless named
// to aid in debugging, but it is not essential that the names be
// either accurate or unambiguous. The public API exposes a number of
// name-based maps for client convenience.
//
// The ssa/ssautil package provides various utilities that depend only
// on the public API of this package.
//
// TODO(adonovan): Consider the exceptional control-flow implications
// of defer and recover().
//
// TODO(adonovan): write a how-to document for all the various cases
// of trying to determine corresponding elements across the four
// domains of source locations, ast.Nodes, types.Objects,
// ssa.Values/Instructions.
//
package ssa
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
// This file defines algorithms related to dominance.
// Dominator tree construction ----------------------------------------
//
// We use the algorithm described in Lengauer & Tarjan. 1979. A fast
// algorithm for finding dominators in a flowgraph.
// http://doi.acm.org/10.1145/357062.357071
//
// We also apply the optimizations to SLT described in Georgiadis et
// al, Finding Dominators in Practice, JGAA 2006,
// http://jgaa.info/accepted/2006/GeorgiadisTarjanWerneck2006.10.1.pdf
// to avoid the need for buckets of size > 1.
import (
"bytes"
"fmt"
"math/big"
"os"
"sort"
)
// Idom returns the block that immediately dominates b:
// its parent in the dominator tree, if any.
// Neither the entry node (b.Index==0) nor recover node
// (b==b.Parent().Recover()) have a parent.
//
func (b *BasicBlock) Idom() *BasicBlock { return b.dom.idom }
// Dominees returns the list of blocks that b immediately dominates:
// its children in the dominator tree.
//
func (b *BasicBlock) Dominees() []*BasicBlock { return b.dom.children }
// Dominates reports whether b dominates c.
func (b *BasicBlock) Dominates(c *BasicBlock) bool {
return b.dom.pre <= c.dom.pre && c.dom.post <= b.dom.post
}
type byDomPreorder []*BasicBlock
func (a byDomPreorder) Len() int { return len(a) }
func (a byDomPreorder) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byDomPreorder) Less(i, j int) bool { return a[i].dom.pre < a[j].dom.pre }
// DomPreorder returns a new slice containing the blocks of f in
// dominator tree preorder.
//
func (f *Function) DomPreorder() []*BasicBlock {
n := len(f.Blocks)
order := make(byDomPreorder, n)
copy(order, f.Blocks)
sort.Sort(order)
return order
}
// domInfo contains a BasicBlock's dominance information.
type domInfo struct {
idom *BasicBlock // immediate dominator (parent in domtree)
children []*BasicBlock // nodes immediately dominated by this one
pre, post int32 // pre- and post-order numbering within domtree
}
// ltState holds the working state for Lengauer-Tarjan algorithm
// (during which domInfo.pre is repurposed for CFG DFS preorder number).
type ltState struct {
// Each slice is indexed by b.Index.
sdom []*BasicBlock // b's semidominator
parent []*BasicBlock // b's parent in DFS traversal of CFG
ancestor []*BasicBlock // b's ancestor with least sdom
}
// dfs implements the depth-first search part of the LT algorithm.
func (lt *ltState) dfs(v *BasicBlock, i int32, preorder []*BasicBlock) int32 {
preorder[i] = v
v.dom.pre = i // For now: DFS preorder of spanning tree of CFG
i++
lt.sdom[v.Index] = v
lt.link(nil, v)
for _, w := range v.Succs {
if lt.sdom[w.Index] == nil {
lt.parent[w.Index] = v
i = lt.dfs(w, i, preorder)
}
}
return i
}
// eval implements the EVAL part of the LT algorithm.
func (lt *ltState) eval(v *BasicBlock) *BasicBlock {
// TODO(adonovan): opt: do path compression per simple LT.
u := v
for ; lt.ancestor[v.Index] != nil; v = lt.ancestor[v.Index] {
if lt.sdom[v.Index].dom.pre < lt.sdom[u.Index].dom.pre {
u = v
}
}
return u
}
// link implements the LINK part of the LT algorithm.
func (lt *ltState) link(v, w *BasicBlock) {
lt.ancestor[w.Index] = v
}
// buildDomTree computes the dominator tree of f using the LT algorithm.
// Precondition: all blocks are reachable (e.g. optimizeBlocks has been run).
//
func buildDomTree(f *Function) {
// The step numbers refer to the original LT paper; the
// reordering is due to Georgiadis.
// Clear any previous domInfo.
for _, b := range f.Blocks {
b.dom = domInfo{}
}
n := len(f.Blocks)
// Allocate space for 5 contiguous [n]*BasicBlock arrays:
// sdom, parent, ancestor, preorder, buckets.
space := make([]*BasicBlock, 5*n)
lt := ltState{
sdom: space[0:n],
parent: space[n : 2*n],
ancestor: space[2*n : 3*n],
}
// Step 1. Number vertices by depth-first preorder.
preorder := space[3*n : 4*n]
root := f.Blocks[0]
prenum := lt.dfs(root, 0, preorder)
recover := f.Recover
if recover != nil {
lt.dfs(recover, prenum, preorder)
}
buckets := space[4*n : 5*n]
copy(buckets, preorder)
// In reverse preorder...
for i := int32(n) - 1; i > 0; i-- {
w := preorder[i]
// Step 3. Implicitly define the immediate dominator of each node.
for v := buckets[i]; v != w; v = buckets[v.dom.pre] {
u := lt.eval(v)
if lt.sdom[u.Index].dom.pre < i {
v.dom.idom = u
} else {
v.dom.idom = w
}
}
// Step 2. Compute the semidominators of all nodes.
lt.sdom[w.Index] = lt.parent[w.Index]
for _, v := range w.Preds {
u := lt.eval(v)
if lt.sdom[u.Index].dom.pre < lt.sdom[w.Index].dom.pre {
lt.sdom[w.Index] = lt.sdom[u.Index]
}
}
lt.link(lt.parent[w.Index], w)
if lt.parent[w.Index] == lt.sdom[w.Index] {
w.dom.idom = lt.parent[w.Index]
} else {
buckets[i] = buckets[lt.sdom[w.Index].dom.pre]
buckets[lt.sdom[w.Index].dom.pre] = w
}
}
// The final 'Step 3' is now outside the loop.
for v := buckets[0]; v != root; v = buckets[v.dom.pre] {
v.dom.idom = root
}
// Step 4. Explicitly define the immediate dominator of each
// node, in preorder.
for _, w := range preorder[1:] {
if w == root || w == recover {
w.dom.idom = nil
} else {
if w.dom.idom != lt.sdom[w.Index] {
w.dom.idom = w.dom.idom.dom.idom
}
// Calculate Children relation as inverse of Idom.
w.dom.idom.dom.children = append(w.dom.idom.dom.children, w)
}
}
pre, post := numberDomTree(root, 0, 0)
if recover != nil {
numberDomTree(recover, pre, post)
}
// printDomTreeDot(os.Stderr, f) // debugging
// printDomTreeText(os.Stderr, root, 0) // debugging
if f.Prog.mode&SanityCheckFunctions != 0 {
sanityCheckDomTree(f)
}
}
// numberDomTree sets the pre- and post-order numbers of a depth-first
// traversal of the dominator tree rooted at v. These are used to
// answer dominance queries in constant time.
//
func numberDomTree(v *BasicBlock, pre, post int32) (int32, int32) {
v.dom.pre = pre
pre++
for _, child := range v.dom.children {
pre, post = numberDomTree(child, pre, post)
}
v.dom.post = post
post++
return pre, post
}
// Testing utilities ----------------------------------------
// sanityCheckDomTree checks the correctness of the dominator tree
// computed by the LT algorithm by comparing against the dominance
// relation computed by a naive Kildall-style forward dataflow
// analysis (Algorithm 10.16 from the "Dragon" book).
//
func sanityCheckDomTree(f *Function) {
n := len(f.Blocks)
// D[i] is the set of blocks that dominate f.Blocks[i],
// represented as a bit-set of block indices.
D := make([]big.Int, n)
one := big.NewInt(1)
// all is the set of all blocks; constant.
var all big.Int
all.Set(one).Lsh(&all, uint(n)).Sub(&all, one)
// Initialization.
for i, b := range f.Blocks {
if i == 0 || b == f.Recover {
// A root is dominated only by itself.
D[i].SetBit(&D[0], 0, 1)
} else {
// All other blocks are (initially) dominated
// by every block.
D[i].Set(&all)
}
}
// Iteration until fixed point.
for changed := true; changed; {
changed = false
for i, b := range f.Blocks {
if i == 0 || b == f.Recover {
continue
}
// Compute intersection across predecessors.
var x big.Int
x.Set(&all)
for _, pred := range b.Preds {
x.And(&x, &D[pred.Index])
}
x.SetBit(&x, i, 1) // a block always dominates itself.
if D[i].Cmp(&x) != 0 {
D[i].Set(&x)
changed = true
}
}
}
// Check the entire relation. O(n^2).
// The Recover block (if any) must be treated specially so we skip it.
ok := true
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
b, c := f.Blocks[i], f.Blocks[j]
if c == f.Recover {
continue
}
actual := b.Dominates(c)
expected := D[j].Bit(i) == 1
if actual != expected {
fmt.Fprintf(os.Stderr, "dominates(%s, %s)==%t, want %t\n", b, c, actual, expected)
ok = false
}
}
}
preorder := f.DomPreorder()
for _, b := range f.Blocks {
if got := preorder[b.dom.pre]; got != b {
fmt.Fprintf(os.Stderr, "preorder[%d]==%s, want %s\n", b.dom.pre, got, b)
ok = false
}
}
if !ok {
panic("sanityCheckDomTree failed for " + f.String())
}
}
// Printing functions ----------------------------------------
// printDomTree prints the dominator tree as text, using indentation.
func printDomTreeText(buf *bytes.Buffer, v *BasicBlock, indent int) {
fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v)
for _, child := range v.dom.children {
printDomTreeText(buf, child, indent+1)
}
}
// printDomTreeDot prints the dominator tree of f in AT&T GraphViz
// (.dot) format.
func printDomTreeDot(buf *bytes.Buffer, f *Function) {
fmt.Fprintln(buf, "//", f)
fmt.Fprintln(buf, "digraph domtree {")
for i, b := range f.Blocks {
v := b.dom
fmt.Fprintf(buf, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.pre, b, v.pre, v.post)
// TODO(adonovan): improve appearance of edges
// belonging to both dominator tree and CFG.
// Dominator tree edge.
if i != 0 {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.dom.pre, v.pre)
}
// CFG edges.
for _, pred := range b.Preds {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.dom.pre, v.pre)
}
}
fmt.Fprintln(buf, "}")
}
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
// Helpers for emitting SSA instructions.
import (
"fmt"
"github.com/wa-lang/wa/internal/ast"
"github.com/wa-lang/wa/internal/token"
"github.com/wa-lang/wa/internal/types"
)
// emitNew emits to f a new (heap Alloc) instruction allocating an
// object of type typ. pos is the optional source location.
//
func emitNew(f *Function, typ types.Type, pos token.Pos) *Alloc {
v := &Alloc{Heap: true}
v.setType(types.NewPointer(typ))
v.setPos(pos)
f.emit(v)
return v
}
// emitLoad emits to f an instruction to load the address addr into a
// new temporary, and returns the value so defined.
//
func emitLoad(f *Function, addr Value) *UnOp {
v := &UnOp{Op: token.MUL, X: addr}
v.setType(deref(addr.Type()))
f.emit(v)
return v
}
// emitDebugRef emits to f a DebugRef pseudo-instruction associating
// expression e with value v.
//
func emitDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) {
if !f.debugInfo() {
return // debugging not enabled
}
if v == nil || e == nil {
panic("nil")
}
var obj types.Object
e = unparen(e)
if id, ok := e.(*ast.Ident); ok {
if isBlankIdent(id) {
return
}
obj = f.Pkg.objectOf(id)
switch obj.(type) {
case *types.Nil, *types.Const, *types.Builtin:
return
}
}
f.emit(&DebugRef{
X: v,
Expr: e,
IsAddr: isAddr,
object: obj,
})
}
// emitArith emits to f code to compute the binary operation op(x, y)
// where op is an eager shift, logical or arithmetic operation.
// (Use emitCompare() for comparisons and Builder.logicalBinop() for
// non-eager operations.)
//
func emitArith(f *Function, op token.Token, x, y Value, t types.Type, pos token.Pos) Value {
switch op {
case token.SHL, token.SHR:
x = emitConv(f, x, t)
// y may be signed or an 'untyped' constant.
// TODO(adonovan): whence signed values?
if b, ok := y.Type().Underlying().(*types.Basic); ok && b.Info()&types.IsUnsigned == 0 {
y = emitConv(f, y, types.Typ[types.Uint64])
}
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT:
x = emitConv(f, x, t)
y = emitConv(f, y, t)
default:
panic("illegal op in emitArith: " + op.String())
}
v := &BinOp{
Op: op,
X: x,
Y: y,
}
v.setPos(pos)
v.setType(t)
return f.emit(v)
}
// emitCompare emits to f code compute the boolean result of
// comparison comparison 'x op y'.
//
func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value {
xt := x.Type().Underlying()
yt := y.Type().Underlying()
// Special case to optimise a tagless SwitchStmt so that
// these are equivalent
// switch { case e: ...}
// switch true { case e: ... }
// if e==true { ... }
// even in the case when e's type is an interface.
// TODO(adonovan): opt: generalise to x==true, false!=y, etc.
if x == vTrue && op == token.EQL {
if yt, ok := yt.(*types.Basic); ok && yt.Info()&types.IsBoolean != 0 {
return y
}
}
if types.Identical(xt, yt) {
// no conversion necessary
} else if _, ok := xt.(*types.Interface); ok {
y = emitConv(f, y, x.Type())
} else if _, ok := yt.(*types.Interface); ok {
x = emitConv(f, x, y.Type())
} else if _, ok := x.(*Const); ok {
x = emitConv(f, x, y.Type())
} else if _, ok := y.(*Const); ok {
y = emitConv(f, y, x.Type())
} else {
// other cases, e.g. channels. No-op.
}
v := &BinOp{
Op: op,
X: x,
Y: y,
}
v.setPos(pos)
v.setType(tBool)
return f.emit(v)
}
// isValuePreserving returns true if a conversion from ut_src to
// ut_dst is value-preserving, i.e. just a change of type.
// Precondition: neither argument is a named type.
//
func isValuePreserving(ut_src, ut_dst types.Type) bool {
// Identical underlying types?
if structTypesIdentical(ut_dst, ut_src) {
return true
}
switch ut_dst.(type) {
case *types.Pointer:
// Conversion between pointers with identical base types?
_, ok := ut_src.(*types.Pointer)
return ok
}
return false
}
// emitConv emits to f code to convert Value val to exactly type typ,
// and returns the converted value. Implicit conversions are required
// by language assignability rules in assignments, parameter passing,
// etc. Conversions cannot fail dynamically.
//
func emitConv(f *Function, val Value, typ types.Type) Value {
t_src := val.Type()
// Identical types? Conversion is a no-op.
if types.Identical(t_src, typ) {
return val
}
ut_dst := typ.Underlying()
ut_src := t_src.Underlying()
// Just a change of type, but not value or representation?
if isValuePreserving(ut_src, ut_dst) {
c := &ChangeType{X: val}
c.setType(typ)
return f.emit(c)
}
// Conversion to, or construction of a value of, an interface type?
if _, ok := ut_dst.(*types.Interface); ok {
// Assignment from one interface type to another?
if _, ok := ut_src.(*types.Interface); ok {
c := &ChangeInterface{X: val}
c.setType(typ)
return f.emit(c)
}
// Untyped nil constant? Return interface-typed nil constant.
if ut_src == tUntypedNil {
return nilConst(typ)
}
// Convert (non-nil) "untyped" literals to their default type.
if t, ok := ut_src.(*types.Basic); ok && t.Info()&types.IsUntyped != 0 {
val = emitConv(f, val, types.Default(ut_src))
}
f.Pkg.Prog.needMethodsOf(val.Type())
mi := &MakeInterface{X: val}
mi.setType(typ)
return f.emit(mi)
}
// Conversion of a compile-time constant value?
if c, ok := val.(*Const); ok {
if _, ok := ut_dst.(*types.Basic); ok || c.IsNil() {
// Conversion of a compile-time constant to
// another constant type results in a new
// constant of the destination type and
// (initially) the same abstract value.
// We don't truncate the value yet.
return NewConst(c.Value, typ)
}
// We're converting from constant to non-constant type,
// e.g. string -> []byte/[]rune.
}
// A representation-changing conversion?
// At least one of {ut_src,ut_dst} must be *Basic.
// (The other may be []byte or []rune.)
_, ok1 := ut_src.(*types.Basic)
_, ok2 := ut_dst.(*types.Basic)
if ok1 || ok2 {
c := &Convert{X: val}
c.setType(typ)
return f.emit(c)
}
panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), typ))
}
// emitStore emits to f an instruction to store value val at location
// addr, applying implicit conversions as required by assignability rules.
//
func emitStore(f *Function, addr, val Value, pos token.Pos) *Store {
s := &Store{
Addr: addr,
Val: emitConv(f, val, deref(addr.Type())),
pos: pos,
}
f.emit(s)
return s
}
// emitJump emits to f a jump to target, and updates the control-flow graph.
// Postcondition: f.currentBlock is nil.
//
func emitJump(f *Function, target *BasicBlock) {
b := f.currentBlock
b.emit(new(Jump))
addEdge(b, target)
f.currentBlock = nil
}
// emitIf emits to f a conditional jump to tblock or fblock based on
// cond, and updates the control-flow graph.
// Postcondition: f.currentBlock is nil.
//
func emitIf(f *Function, cond Value, tblock, fblock *BasicBlock) {
b := f.currentBlock
b.emit(&If{Cond: cond})
addEdge(b, tblock)
addEdge(b, fblock)
f.currentBlock = nil
}
// emitExtract emits to f an instruction to extract the index'th
// component of tuple. It returns the extracted value.
//
func emitExtract(f *Function, tuple Value, index int) Value {
e := &Extract{Tuple: tuple, Index: index}
e.setType(tuple.Type().(*types.Tuple).At(index).Type())
return f.emit(e)
}
// emitTypeAssert emits to f a type assertion value := x.(t) and
// returns the value. x.Type() must be an interface.
//
func emitTypeAssert(f *Function, x Value, t types.Type, pos token.Pos) Value {
a := &TypeAssert{X: x, AssertedType: t}
a.setPos(pos)
a.setType(t)
return f.emit(a)
}
// emitTypeTest emits to f a type test value,ok := x.(t) and returns
// a (value, ok) tuple. x.Type() must be an interface.
//
func emitTypeTest(f *Function, x Value, t types.Type, pos token.Pos) Value {
a := &TypeAssert{
X: x,
AssertedType: t,
CommaOk: true,
}
a.setPos(pos)
a.setType(types.NewTuple(
newVar("value", t),
varOk,
))
return f.emit(a)
}
// emitTailCall emits to f a function call in tail position. The
// caller is responsible for all fields of 'call' except its type.
// Intended for wrapper methods.
// Precondition: f does/will not use deferred procedure calls.
// Postcondition: f.currentBlock is nil.
//
func emitTailCall(f *Function, call *Call) {
tresults := f.Signature.Results()
nr := tresults.Len()
if nr == 1 {
call.typ = tresults.At(0).Type()
} else {
call.typ = tresults
}
tuple := f.emit(call)
var ret Return
switch nr {
case 0:
// no-op
case 1:
ret.Results = []Value{tuple}
default:
for i := 0; i < nr; i++ {
v := emitExtract(f, tuple, i)
// TODO(adonovan): in principle, this is required:
// v = emitConv(f, o.Type, f.Signature.Results[i].Type)
// but in practice emitTailCall is only used when
// the types exactly match.
ret.Results = append(ret.Results, v)
}
}
f.emit(&ret)
f.currentBlock = nil
}
// emitImplicitSelections emits to f code to apply the sequence of
// implicit field selections specified by indices to base value v, and
// returns the selected value.
//
// If v is the address of a struct, the result will be the address of
// a field; if it is the value of a struct, the result will be the
// value of a field.
//
func emitImplicitSelections(f *Function, v Value, indices []int) Value {
for _, index := range indices {
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
if isPointer(v.Type()) {
instr := &FieldAddr{
X: v,
Field: index,
}
instr.setType(types.NewPointer(fld.Type()))
v = f.emit(instr)
// Load the field's value iff indirectly embedded.
if isPointer(fld.Type()) {
v = emitLoad(f, v)
}
} else {
instr := &Field{
X: v,
Field: index,
}
instr.setType(fld.Type())
v = f.emit(instr)
}
}
return v
}
// emitFieldSelection emits to f code to select the index'th field of v.
//
// If wantAddr, the input must be a pointer-to-struct and the result
// will be the field's address; otherwise the result will be the
// field's value.
// Ident id is used for position and debug info.
//
func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.Ident) Value {
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
if isPointer(v.Type()) {
instr := &FieldAddr{
X: v,
Field: index,
}
instr.setPos(id.Pos())
instr.setType(types.NewPointer(fld.Type()))
v = f.emit(instr)
// Load the field's value iff we don't want its address.
if !wantAddr {
v = emitLoad(f, v)
}
} else {
instr := &Field{
X: v,
Field: index,
}
instr.setPos(id.Pos())
instr.setType(fld.Type())
v = f.emit(instr)
}
emitDebugRef(f, id, v, wantAddr)
return v
}
// zeroValue emits to f code to produce a zero value of type t,
// and returns it.
//
func zeroValue(f *Function, t types.Type) Value {
switch t.Underlying().(type) {
case *types.Struct, *types.Array:
return emitLoad(f, f.addLocal(t, token.NoPos))
default:
return zeroConst(t)
}
}
// createRecoverBlock emits to f a block of code to return after a
// recovered panic, and sets f.Recover to it.
//
// If f's result parameters are named, the code loads and returns
// their current values, otherwise it returns the zero values of their
// type.
//
// Idempotent.
//
func createRecoverBlock(f *Function) {
if f.Recover != nil {
return // already created
}
saved := f.currentBlock
f.Recover = f.newBasicBlock("recover")
f.currentBlock = f.Recover
var results []Value
if f.namedResults != nil {
// Reload NRPs to form value tuple.
for _, r := range f.namedResults {
results = append(results, emitLoad(f, r))
}
} else {
R := f.Signature.Results()
for i, n := 0, R.Len(); i < n; i++ {
T := R.At(i).Type()
// Return zero value of each result type.
results = append(results, zeroValue(f, T))
}
}
f.emit(&Return{Results: results})
f.currentBlock = saved
}
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
// This file implements the Function and BasicBlock types.
import (
"bytes"
"fmt"
"io"
"os"
"strings"
"github.com/wa-lang/wa/internal/ast"
"github.com/wa-lang/wa/internal/token"
"github.com/wa-lang/wa/internal/types"
)
// addEdge adds a control-flow graph edge from from to to.
func addEdge(from, to *BasicBlock) {
from.Succs = append(from.Succs, to)
to.Preds = append(to.Preds, from)
}
// Parent returns the function that contains block b.
func (b *BasicBlock) Parent() *Function { return b.parent }
// String returns a human-readable label of this block.
// It is not guaranteed unique within the function.
//
func (b *BasicBlock) String() string {
return fmt.Sprintf("%d", b.Index)
}
// emit appends an instruction to the current basic block.
// If the instruction defines a Value, it is returned.
//
func (b *BasicBlock) emit(i Instruction) Value {
i.setBlock(b)
b.Instrs = append(b.Instrs, i)
v, _ := i.(Value)
return v
}
// predIndex returns the i such that b.Preds[i] == c or panics if
// there is none.
func (b *BasicBlock) predIndex(c *BasicBlock) int {
for i, pred := range b.Preds {
if pred == c {
return i
}
}
panic(fmt.Sprintf("no edge %s -> %s", c, b))
}
// hasPhi returns true if b.Instrs contains φ-nodes.
func (b *BasicBlock) hasPhi() bool {
_, ok := b.Instrs[0].(*Phi)
return ok
}
// phis returns the prefix of b.Instrs containing all the block's φ-nodes.
func (b *BasicBlock) phis() []Instruction {
for i, instr := range b.Instrs {
if _, ok := instr.(*Phi); !ok {
return b.Instrs[:i]
}
}
return nil // unreachable in well-formed blocks
}
// replacePred replaces all occurrences of p in b's predecessor list with q.
// Ordinarily there should be at most one.
//
func (b *BasicBlock) replacePred(p, q *BasicBlock) {
for i, pred := range b.Preds {
if pred == p {
b.Preds[i] = q
}
}
}
// replaceSucc replaces all occurrences of p in b's successor list with q.
// Ordinarily there should be at most one.
//
func (b *BasicBlock) replaceSucc(p, q *BasicBlock) {
for i, succ := range b.Succs {
if succ == p {
b.Succs[i] = q
}
}
}
// removePred removes all occurrences of p in b's
// predecessor list and φ-nodes.
// Ordinarily there should be at most one.
//
func (b *BasicBlock) removePred(p *BasicBlock) {
phis := b.phis()
// We must preserve edge order for φ-nodes.
j := 0
for i, pred := range b.Preds {
if pred != p {
b.Preds[j] = b.Preds[i]
// Strike out φ-edge too.
for _, instr := range phis {
phi := instr.(*Phi)
phi.Edges[j] = phi.Edges[i]
}
j++
}
}
// Nil out b.Preds[j:] and φ-edges[j:] to aid GC.
for i := j; i < len(b.Preds); i++ {
b.Preds[i] = nil
for _, instr := range phis {
instr.(*Phi).Edges[i] = nil
}
}
b.Preds = b.Preds[:j]
for _, instr := range phis {
phi := instr.(*Phi)
phi.Edges = phi.Edges[:j]
}
}
// Destinations associated with unlabelled for/switch/select stmts.
// We push/pop one of these as we enter/leave each construct and for
// each BranchStmt we scan for the innermost target of the right type.
//
type targets struct {
tail *targets // rest of stack
_break *BasicBlock
_continue *BasicBlock
_fallthrough *BasicBlock
}
// Destinations associated with a labelled block.
// We populate these as labels are encountered in forward gotos or
// labelled statements.
//
type lblock struct {
_goto *BasicBlock
_break *BasicBlock
_continue *BasicBlock
}
// labelledBlock returns the branch target associated with the
// specified label, creating it if needed.
//
func (f *Function) labelledBlock(label *ast.Ident) *lblock {
lb := f.lblocks[label.Obj]
if lb == nil {
lb = &lblock{_goto: f.newBasicBlock(label.Name)}
if f.lblocks == nil {
f.lblocks = make(map[*ast.Object]*lblock)
}
f.lblocks[label.Obj] = lb
}
return lb
}
// addParam adds a (non-escaping) parameter to f.Params of the
// specified name, type and source position.
//
func (f *Function) addParam(name string, typ types.Type, pos token.Pos) *Parameter {
v := &Parameter{
name: name,
typ: typ,
pos: pos,
parent: f,
}
f.Params = append(f.Params, v)
return v
}
func (f *Function) addParamObj(obj types.Object) *Parameter {
name := obj.Name()
if name == "" {
name = fmt.Sprintf("arg%d", len(f.Params))
}
param := f.addParam(name, obj.Type(), obj.Pos())
param.object = obj
return param
}
// addSpilledParam declares a parameter that is pre-spilled to the
// stack; the function body will load/store the spilled location.
// Subsequent lifting will eliminate spills where possible.
//
func (f *Function) addSpilledParam(obj types.Object) {
param := f.addParamObj(obj)
spill := &Alloc{Comment: obj.Name()}
spill.setType(types.NewPointer(obj.Type()))
spill.setPos(obj.Pos())
f.objects[obj] = spill
f.Locals = append(f.Locals, spill)
f.emit(spill)
f.emit(&Store{Addr: spill, Val: param})
}
// startBody initializes the function prior to generating SSA code for its body.
// Precondition: f.Type() already set.
//
func (f *Function) startBody() {
f.currentBlock = f.newBasicBlock("entry")
f.objects = make(map[types.Object]Value) // needed for some synthetics, e.g. init
}
// createSyntacticParams populates f.Params and generates code (spills
// and named result locals) for all the parameters declared in the
// syntax. In addition it populates the f.objects mapping.
//
// Preconditions:
// f.startBody() was called.
// Postcondition:
// len(f.Params) == len(f.Signature.Params) + (f.Signature.Recv() ? 1 : 0)
//
func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.FuncType) {
// Receiver (at most one inner iteration).
if recv != nil {
for _, field := range recv.List {
for _, n := range field.Names {
f.addSpilledParam(f.Pkg.info.Defs[n])
}
// Anonymous receiver? No need to spill.
if field.Names == nil {
f.addParamObj(f.Signature.Recv())
}
}
}
// Parameters.
if functype.Params != nil {
n := len(f.Params) // 1 if has recv, 0 otherwise
for _, field := range functype.Params.List {
for _, n := range field.Names {
f.addSpilledParam(f.Pkg.info.Defs[n])
}
// Anonymous parameter? No need to spill.
if field.Names == nil {
f.addParamObj(f.Signature.Params().At(len(f.Params) - n))
}
}
}
// Named results.
if functype.Results != nil {
for _, field := range functype.Results.List {
// Implicit "var" decl of locals for named results.
for _, n := range field.Names {
f.namedResults = append(f.namedResults, f.addLocalForIdent(n))
}
}
}
}
type setNumable interface {
setNum(int)
}
// numberRegisters assigns numbers to all SSA registers
// (value-defining Instructions) in f, to aid debugging.
// (Non-Instruction Values are named at construction.)
//
func numberRegisters(f *Function) {
v := 0
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
switch instr.(type) {
case Value:
instr.(setNumable).setNum(v)
v++
}
}
}
}
// buildReferrers populates the def/use information in all non-nil
// Value.Referrers slice.
// Precondition: all such slices are initially empty.
func buildReferrers(f *Function) {
var rands []*Value
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
rands = instr.Operands(rands[:0]) // recycle storage
for _, rand := range rands {
if r := *rand; r != nil {
if ref := r.Referrers(); ref != nil {
*ref = append(*ref, instr)
}
}
}
}
}
}
// finishBody() finalizes the function after SSA code generation of its body.
func (f *Function) finishBody() {
f.objects = nil
f.currentBlock = nil
f.lblocks = nil
// Don't pin the AST in memory (except in debug mode).
if n := f.syntax; n != nil && !f.debugInfo() {
f.syntax = extentNode{n.Pos(), n.End()}
}
// Remove from f.Locals any Allocs that escape to the heap.
j := 0
for _, l := range f.Locals {
if !l.Heap {
f.Locals[j] = l
j++
}
}
// Nil out f.Locals[j:] to aid GC.
for i := j; i < len(f.Locals); i++ {
f.Locals[i] = nil
}
f.Locals = f.Locals[:j]
optimizeBlocks(f)
buildReferrers(f)
buildDomTree(f)
if f.Prog.mode&NaiveForm == 0 {
// For debugging pre-state of lifting pass:
// numberRegisters(f)
// f.WriteTo(os.Stderr)
lift(f)
}
f.namedResults = nil // (used by lifting)
numberRegisters(f)
if f.Prog.mode&PrintFunctions != 0 {
printMu.Lock()
f.WriteTo(os.Stdout)
printMu.Unlock()
}
if f.Prog.mode&SanityCheckFunctions != 0 {
mustSanityCheck(f, nil)
}
}
// removeNilBlocks eliminates nils from f.Blocks and updates each
// BasicBlock.Index. Use this after any pass that may delete blocks.
//
func (f *Function) removeNilBlocks() {
j := 0
for _, b := range f.Blocks {
if b != nil {
b.Index = j
f.Blocks[j] = b
j++
}
}
// Nil out f.Blocks[j:] to aid GC.
for i := j; i < len(f.Blocks); i++ {
f.Blocks[i] = nil
}
f.Blocks = f.Blocks[:j]
}
// SetDebugMode sets the debug mode for package pkg. If true, all its
// functions will include full debug info. This greatly increases the
// size of the instruction stream, and causes Functions to depend upon
// the ASTs, potentially keeping them live in memory for longer.
//
func (pkg *Package) SetDebugMode(debug bool) {
// TODO(adonovan): do we want ast.File granularity?
pkg.debug = debug
}
// debugInfo reports whether debug info is wanted for this function.
func (f *Function) debugInfo() bool {
return f.Pkg != nil && f.Pkg.debug
}
// addNamedLocal creates a local variable, adds it to function f and
// returns it. Its name and type are taken from obj. Subsequent
// calls to f.lookup(obj) will return the same local.
//
func (f *Function) addNamedLocal(obj types.Object) *Alloc {
l := f.addLocal(obj.Type(), obj.Pos())
l.Comment = obj.Name()
f.objects[obj] = l
return l
}
func (f *Function) addLocalForIdent(id *ast.Ident) *Alloc {
return f.addNamedLocal(f.Pkg.info.Defs[id])
}
// addLocal creates an anonymous local variable of type typ, adds it
// to function f and returns it. pos is the optional source location.
//
func (f *Function) addLocal(typ types.Type, pos token.Pos) *Alloc {
v := &Alloc{}
v.setType(types.NewPointer(typ))
v.setPos(pos)
f.Locals = append(f.Locals, v)
f.emit(v)
return v
}
// lookup returns the address of the named variable identified by obj
// that is local to function f or one of its enclosing functions.
// If escaping, the reference comes from a potentially escaping pointer
// expression and the referent must be heap-allocated.
//
func (f *Function) lookup(obj types.Object, escaping bool) Value {
if v, ok := f.objects[obj]; ok {
if alloc, ok := v.(*Alloc); ok && escaping {
alloc.Heap = true
}
return v // function-local var (address)
}
// Definition must be in an enclosing function;
// plumb it through intervening closures.
if f.parent == nil {
panic("no ssa.Value for " + obj.String())
}
outer := f.parent.lookup(obj, true) // escaping
v := &FreeVar{
name: obj.Name(),
typ: outer.Type(),
pos: outer.Pos(),
outer: outer,
parent: f,
}
f.objects[obj] = v
f.FreeVars = append(f.FreeVars, v)
return v
}
// emit emits the specified instruction to function f.
func (f *Function) emit(instr Instruction) Value {
return f.currentBlock.emit(instr)
}
// RelString returns the full name of this function, qualified by
// package name, receiver type, etc.
//
// The specific formatting rules are not guaranteed and may change.
//
// Examples:
// "math.IsNaN" // a package-level function
// "(*bytes.Buffer).Bytes" // a declared method or a wrapper
// "(*bytes.Buffer).Bytes$thunk" // thunk (func wrapping method; receiver is param 0)
// "(*bytes.Buffer).Bytes$bound" // bound (func wrapping method; receiver supplied by closure)
// "main.main$1" // an anonymous function in main
// "main.init#1" // a declared init function
// "main.init" // the synthesized package initializer
//
// When these functions are referred to from within the same package
// (i.e. from == f.Pkg.Object), they are rendered without the package path.
// For example: "IsNaN", "(*Buffer).Bytes", etc.
//
// All non-synthetic functions have distinct package-qualified names.
// (But two methods may have the same name "(T).f" if one is a synthetic
// wrapper promoting a non-exported method "f" from another package; in
// that case, the strings are equal but the identifiers "f" are distinct.)
//
func (f *Function) RelString(from *types.Package) string {
// Anonymous?
if f.parent != nil {
// An anonymous function's Name() looks like "parentName$1",
// but its String() should include the type/package/etc.
parent := f.parent.RelString(from)
for i, anon := range f.parent.AnonFuncs {
if anon == f {
return fmt.Sprintf("%s$%d", parent, 1+i)
}
}
return f.name // should never happen
}
// Method (declared or wrapper)?
if recv := f.Signature.Recv(); recv != nil {
return f.relMethod(from, recv.Type())
}
// Thunk?
if f.method != nil {
return f.relMethod(from, f.method.Recv())
}
// Bound?
if len(f.FreeVars) == 1 && strings.HasSuffix(f.name, "$bound") {
return f.relMethod(from, f.FreeVars[0].Type())
}
// Package-level function?
// Prefix with package name for cross-package references only.
if p := f.pkg(); p != nil && p != from {
return fmt.Sprintf("%s.%s", p.Path(), f.name)
}
// Unknown.
return f.name
}
func (f *Function) relMethod(from *types.Package, recv types.Type) string {
return fmt.Sprintf("(%s).%s", relType(recv, from), f.name)
}
// writeSignature writes to buf the signature sig in declaration syntax.
func writeSignature(buf *bytes.Buffer, from *types.Package, name string, sig *types.Signature, params []*Parameter) {
buf.WriteString("func ")
if recv := sig.Recv(); recv != nil {
buf.WriteString("(")
if n := params[0].Name(); n != "" {
buf.WriteString(n)
buf.WriteString(" ")
}
types.WriteType(buf, params[0].Type(), types.RelativeTo(from))
buf.WriteString(") ")
}
buf.WriteString(name)
types.WriteSignature(buf, sig, types.RelativeTo(from))
}
func (f *Function) pkg() *types.Package {
if f.Pkg != nil {
return f.Pkg.Pkg
}
return nil
}
var _ io.WriterTo = (*Function)(nil) // *Function implements io.Writer
func (f *Function) WriteTo(w io.Writer) (int64, error) {
var buf bytes.Buffer
WriteFunction(&buf, f)
n, err := w.Write(buf.Bytes())
return int64(n), err
}
// WriteFunction writes to buf a human-readable "disassembly" of f.
func WriteFunction(buf *bytes.Buffer, f *Function) {
fmt.Fprintf(buf, "# Name: %s\n", f.String())
if f.Pkg != nil {
fmt.Fprintf(buf, "# Package: %s\n", f.Pkg.Pkg.Path())
}
if syn := f.Synthetic; syn != "" {
fmt.Fprintln(buf, "# Synthetic:", syn)
}
if pos := f.Pos(); pos.IsValid() {
fmt.Fprintf(buf, "# Location: %s\n", f.Prog.Fset.Position(pos))
}
if f.parent != nil {
fmt.Fprintf(buf, "# Parent: %s\n", f.parent.Name())
}
if f.Recover != nil {
fmt.Fprintf(buf, "# Recover: %s\n", f.Recover)
}
from := f.pkg()
if f.FreeVars != nil {
buf.WriteString("# Free variables:\n")
for i, fv := range f.FreeVars {
fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, fv.Name(), relType(fv.Type(), from))
}
}
if len(f.Locals) > 0 {
buf.WriteString("# Locals:\n")
for i, l := range f.Locals {
fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, l.Name(), relType(deref(l.Type()), from))
}
}
writeSignature(buf, from, f.Name(), f.Signature, f.Params)
buf.WriteString(":\n")
if f.Blocks == nil {
buf.WriteString("\t(external)\n")
}
// NB. column calculations are confused by non-ASCII
// characters and assume 8-space tabs.
const punchcard = 80 // for old time's sake.
const tabwidth = 8
for _, b := range f.Blocks {
if b == nil {
// Corrupt CFG.
fmt.Fprintf(buf, ".nil:\n")
continue
}
n, _ := fmt.Fprintf(buf, "%d:", b.Index)
bmsg := fmt.Sprintf("%s P:%d S:%d", b.Comment, len(b.Preds), len(b.Succs))
fmt.Fprintf(buf, "%*s%s\n", punchcard-1-n-len(bmsg), "", bmsg)
if false { // CFG debugging
fmt.Fprintf(buf, "\t# CFG: %s --> %s --> %s\n", b.Preds, b, b.Succs)
}
for _, instr := range b.Instrs {
buf.WriteString("\t")
switch v := instr.(type) {
case Value:
l := punchcard - tabwidth
// Left-align the instruction.
if name := v.Name(); name != "" {
n, _ := fmt.Fprintf(buf, "%s = ", name)
l -= n
}
n, _ := buf.WriteString(instr.String())
l -= n
// Right-align the type if there's space.
if t := v.Type(); t != nil {
buf.WriteByte(' ')
ts := relType(t, from)
l -= len(ts) + len(" ") // (spaces before and after type)
if l > 0 {
fmt.Fprintf(buf, "%*s", l, "")
}
buf.WriteString(ts)
}
case nil:
// Be robust against bad transforms.
buf.WriteString("<deleted>")
default:
buf.WriteString(instr.String())
}
buf.WriteString("\n")
}
}
fmt.Fprintf(buf, "\n")
}
// newBasicBlock adds to f a new basic block and returns it. It does
// not automatically become the current block for subsequent calls to emit.
// comment is an optional string for more readable debugging output.
//
func (f *Function) newBasicBlock(comment string) *BasicBlock {
b := &BasicBlock{
Index: len(f.Blocks),
Comment: comment,
parent: f,
}
b.Succs = b.succs2[:0]
f.Blocks = append(f.Blocks, b)
return b
}
// NewFunction returns a new synthetic Function instance belonging to
// prog, with its name and signature fields set as specified.
//
// The caller is responsible for initializing the remaining fields of
// the function object, e.g. Pkg, Params, Blocks.
//
// It is practically impossible for clients to construct well-formed
// SSA functions/packages/programs directly, so we assume this is the
// job of the Builder alone. NewFunction exists to provide clients a
// little flexibility. For example, analysis tools may wish to
// construct fake Functions for the root of the callgraph, a fake
// "reflect" package, etc.
//
// TODO(adonovan): think harder about the API here.
//
func (prog *Program) NewFunction(name string, sig *types.Signature, provenance string) *Function {
return &Function{Prog: prog, name: name, Signature: sig, Synthetic: provenance}
}
type extentNode [2]token.Pos
func (n extentNode) Pos() token.Pos { return n[0] }
func (n extentNode) End() token.Pos { return n[1] }
// Syntax returns an ast.Node whose Pos/End methods provide the
// lexical extent of the function if it was defined by Go source code
// (f.Synthetic==""), or nil otherwise.
//
// If f was built with debug information (see Package.SetDebugRef),
// the result is the *ast.FuncDecl or *ast.FuncLit that declared the
// function. Otherwise, it is an opaque Node providing only position
// information; this avoids pinning the AST in memory.
//
func (f *Function) Syntax() ast.Node { return f.syntax }
//go:build ignore
package main
import (
"os"
"github.com/wa-lang/wa/internal/ast"
"github.com/wa-lang/wa/internal/parser"
"github.com/wa-lang/wa/internal/ssa"
"github.com/wa-lang/wa/internal/token"
"github.com/wa-lang/wa/internal/types"
)
func main() {
prog := NewProgram(map[string]string{
"main": `
package main
func main() {
for i := 0; i < 3; i++ {
println(i, "hello chai2010")
}
}
`,
})
pkg, f, info, _ := prog.LoadPackage("main")
var ssaProg = ssa.NewProgram(prog.fset, ssa.SanityCheckFunctions)
var ssaPkg = ssaProg.CreatePackage(pkg, []*ast.File{f}, info, true)
ssaPkg.Build()
ssaPkg.WriteTo(os.Stdout)
ssaPkg.Func("main").WriteTo(os.Stdout)
}
type Program struct {
fs map[string]string
ast map[string]*ast.File
pkgs map[string]*types.Package
infos map[string]*types.Info
fset *token.FileSet
}
func NewProgram(fs map[string]string) *Program {
return &Program{
fs: fs,
ast: make(map[string]*ast.File),
pkgs: make(map[string]*types.Package),
infos: make(map[string]*types.Info),
fset: token.NewFileSet(),
}
}
func (p *Program) LoadPackage(path string) (pkg *types.Package, f *ast.File, info *types.Info, err error) {
if pkg, ok := p.pkgs[path]; ok {
return pkg, p.ast[path], p.infos[path], nil
}
f, err = parser.ParseFile(nil, p.fset, path, p.fs[path], parser.AllErrors)
if err != nil {
return nil, nil, nil, err
}
info = &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
Scopes: make(map[ast.Node]*types.Scope),
}
conf := types.Config{Importer: p}
pkg, err = conf.Check(path, p.fset, []*ast.File{f}, info)
if err != nil {
return nil, nil, nil, err
}
p.ast[path] = f
p.pkgs[path] = pkg
p.infos[path] = info
return pkg, f, info, nil
}
func (p *Program) Import(path string) (*types.Package, error) {
if pkg, ok := p.pkgs[path]; ok {
return pkg, nil
}
pkg, _, _, err := p.LoadPackage(path)
return pkg, err
}
//go:build go1.8
// +build go1.8
package ssa
import "github.com/wa-lang/wa/internal/types"
var structTypesIdentical = types.IdenticalIgnoreTags
//go:build !go1.8
// +build !go1.8
package ssa
import "github.com/wa-lang/wa/internal/types"
var structTypesIdentical = types.Identical
此差异已折叠。
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
// lvalues are the union of addressable expressions and map-index
// expressions.
import (
"github.com/wa-lang/wa/internal/ast"
"github.com/wa-lang/wa/internal/token"
"github.com/wa-lang/wa/internal/types"
)
// An lvalue represents an assignable location that may appear on the
// left-hand side of an assignment. This is a generalization of a
// pointer to permit updates to elements of maps.
//
type lvalue interface {
store(fn *Function, v Value) // stores v into the location
load(fn *Function) Value // loads the contents of the location
address(fn *Function) Value // address of the location
typ() types.Type // returns the type of the location
}
// An address is an lvalue represented by a true pointer.
type address struct {
addr Value
pos token.Pos // source position
expr ast.Expr // source syntax of the value (not address) [debug mode]
}
func (a *address) load(fn *Function) Value {
load := emitLoad(fn, a.addr)
load.pos = a.pos
return load
}
func (a *address) store(fn *Function, v Value) {
store := emitStore(fn, a.addr, v, a.pos)
if a.expr != nil {
// store.Val is v, converted for assignability.
emitDebugRef(fn, a.expr, store.Val, false)
}
}
func (a *address) address(fn *Function) Value {
if a.expr != nil {
emitDebugRef(fn, a.expr, a.addr, true)
}
return a.addr
}
func (a *address) typ() types.Type {
return deref(a.addr.Type())
}
// An element is an lvalue represented by m[k], the location of an
// element of a map or string. These locations are not addressable
// since pointers cannot be formed from them, but they do support
// load(), and in the case of maps, store().
//
type element struct {
m, k Value // map or string
t types.Type // map element type or string byte type
pos token.Pos // source position of colon ({k:v}) or lbrack (m[k]=v)
}
func (e *element) load(fn *Function) Value {
l := &Lookup{
X: e.m,
Index: e.k,
}
l.setPos(e.pos)
l.setType(e.t)
return fn.emit(l)
}
func (e *element) store(fn *Function, v Value) {
up := &MapUpdate{
Map: e.m,
Key: e.k,
Value: emitConv(fn, v, e.t),
}
up.pos = e.pos
fn.emit(up)
}
func (e *element) address(fn *Function) Value {
panic("map/string elements are not addressable")
}
func (e *element) typ() types.Type {
return e.t
}
// A blank is a dummy variable whose name is "_".
// It is not reified: loads are illegal and stores are ignored.
//
type blank struct{}
func (bl blank) load(fn *Function) Value {
panic("blank.load is illegal")
}
func (bl blank) store(fn *Function, v Value) {
// no-op
}
func (bl blank) address(fn *Function) Value {
panic("blank var is not addressable")
}
func (bl blank) typ() types.Type {
// This should be the type of the blank Ident; the typechecker
// doesn't provide this yet, but fortunately, we don't need it
// yet either.
panic("blank.typ is unimplemented")
}
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
// This file defines utilities for population of method sets.
import (
"fmt"
"github.com/wa-lang/wa/internal/types"
)
// MethodValue returns the Function implementing method sel, building
// wrapper methods on demand. It returns nil if sel denotes an
// abstract (interface) method.
//
// Precondition: sel.Kind() == MethodVal.
//
// Thread-safe.
//
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
//
func (prog *Program) MethodValue(sel *types.Selection) *Function {
if sel.Kind() != types.MethodVal {
panic(fmt.Sprintf("MethodValue(%s) kind != MethodVal", sel))
}
T := sel.Recv()
if isInterface(T) {
return nil // abstract method
}
if prog.mode&LogSource != 0 {
defer logStack("MethodValue %s %v", T, sel)()
}
prog.methodsMu.Lock()
defer prog.methodsMu.Unlock()
return prog.addMethod(prog.createMethodSet(T), sel)
}
// LookupMethod returns the implementation of the method of type T
// identified by (pkg, name). It returns nil if the method exists but
// is abstract, and panics if T has no such method.
//
func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function {
sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name)
if sel == nil {
panic(fmt.Sprintf("%s has no method %s", T, types.Id(pkg, name)))
}
return prog.MethodValue(sel)
}
// methodSet contains the (concrete) methods of a non-interface type.
type methodSet struct {
mapping map[string]*Function // populated lazily
complete bool // mapping contains all methods
}
// Precondition: !isInterface(T).
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
func (prog *Program) createMethodSet(T types.Type) *methodSet {
mset, ok := prog.methodSets.At(T).(*methodSet)
if !ok {
mset = &methodSet{mapping: make(map[string]*Function)}
prog.methodSets.Set(T, mset)
}
return mset
}
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
func (prog *Program) addMethod(mset *methodSet, sel *types.Selection) *Function {
if sel.Kind() == types.MethodExpr {
panic(sel)
}
id := sel.Obj().Id()
fn := mset.mapping[id]
if fn == nil {
obj := sel.Obj().(*types.Func)
needsPromotion := len(sel.Index()) > 1
needsIndirection := !isPointer(recvType(obj)) && isPointer(sel.Recv())
if needsPromotion || needsIndirection {
fn = makeWrapper(prog, sel)
} else {
fn = prog.declaredFunc(obj)
}
if fn.Signature.Recv() == nil {
panic(fn) // missing receiver
}
mset.mapping[id] = fn
}
return fn
}
// RuntimeTypes returns a new unordered slice containing all
// concrete types in the program for which a complete (non-empty)
// method set is required at run-time.
//
// Thread-safe.
//
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
//
func (prog *Program) RuntimeTypes() []types.Type {
prog.methodsMu.Lock()
defer prog.methodsMu.Unlock()
var res []types.Type
prog.methodSets.Iterate(func(T types.Type, v interface{}) {
if v.(*methodSet).complete {
res = append(res, T)
}
})
return res
}
// declaredFunc returns the concrete function/method denoted by obj.
// Panic ensues if there is none.
//
func (prog *Program) declaredFunc(obj *types.Func) *Function {
if v := prog.packageLevelValue(obj); v != nil {
return v.(*Function)
}
panic("no concrete method: " + obj.String())
}
// needMethodsOf ensures that runtime type information (including the
// complete method set) is available for the specified type T and all
// its subcomponents.
//
// needMethodsOf must be called for at least every type that is an
// operand of some MakeInterface instruction, and for the type of
// every exported package member.
//
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
//
// Thread-safe. (Called via emitConv from multiple builder goroutines.)
//
// TODO(adonovan): make this faster. It accounts for 20% of SSA build time.
//
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
//
func (prog *Program) needMethodsOf(T types.Type) {
prog.methodsMu.Lock()
prog.needMethods(T, false)
prog.methodsMu.Unlock()
}
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
// Recursive case: skip => don't create methods for T.
//
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
//
func (prog *Program) needMethods(T types.Type, skip bool) {
// Each package maintains its own set of types it has visited.
if prevSkip, ok := prog.runtimeTypes.At(T).(bool); ok {
// needMethods(T) was previously called
if !prevSkip || skip {
return // already seen, with same or false 'skip' value
}
}
prog.runtimeTypes.Set(T, skip)
tmset := prog.MethodSets.MethodSet(T)
if !skip && !isInterface(T) && tmset.Len() > 0 {
// Create methods of T.
mset := prog.createMethodSet(T)
if !mset.complete {
mset.complete = true
n := tmset.Len()
for i := 0; i < n; i++ {
prog.addMethod(mset, tmset.At(i))
}
}
}
// Recursion over signatures of each method.
for i := 0; i < tmset.Len(); i++ {
sig := tmset.At(i).Type().(*types.Signature)
prog.needMethods(sig.Params(), false)
prog.needMethods(sig.Results(), false)
}
switch t := T.(type) {
case *types.Basic:
// nop
case *types.Interface:
// nop---handled by recursion over method set.
case *types.Pointer:
prog.needMethods(t.Elem(), false)
case *types.Slice:
prog.needMethods(t.Elem(), false)
case *types.Map:
prog.needMethods(t.Key(), false)
prog.needMethods(t.Elem(), false)
case *types.Signature:
if t.Recv() != nil {
panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv()))
}
prog.needMethods(t.Params(), false)
prog.needMethods(t.Results(), false)
case *types.Named:
// A pointer-to-named type can be derived from a named
// type via reflection. It may have methods too.
prog.needMethods(types.NewPointer(T), false)
// Consider 'type T struct{S}' where S has methods.
// Reflection provides no way to get from T to struct{S},
// only to S, so the method set of struct{S} is unwanted,
// so set 'skip' flag during recursion.
prog.needMethods(t.Underlying(), true)
case *types.Array:
prog.needMethods(t.Elem(), false)
case *types.Struct:
for i, n := 0, t.NumFields(); i < n; i++ {
prog.needMethods(t.Field(i).Type(), false)
}
case *types.Tuple:
for i, n := 0, t.Len(); i < n; i++ {
prog.needMethods(t.At(i).Type(), false)
}
default:
panic(T)
}
}
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
// This file defines the BuilderMode type and its command-line flag.
import (
"bytes"
"fmt"
)
// BuilderMode is a bitmask of options for diagnostics and checking.
//
// *BuilderMode satisfies the flag.Value interface. Example:
//
// var mode = ssa.BuilderMode(0)
// func init() { flag.Var(&mode, "build", ssa.BuilderModeDoc) }
//
type BuilderMode uint
const (
PrintPackages BuilderMode = 1 << iota // Print package inventory to stdout
PrintFunctions // Print function SSA code to stdout
LogSource // Log source locations as SSA builder progresses
SanityCheckFunctions // Perform sanity checking of function bodies
NaiveForm // Build naïve SSA form: don't replace local loads/stores with registers
BuildSerially // Build packages serially, not in parallel.
GlobalDebug // Enable debug info for all packages
BareInits // Build init functions without guards or calls to dependent inits
)
const BuilderModeDoc = `Options controlling the SSA builder.
The value is a sequence of zero or more of these letters:
C perform sanity [C]hecking of the SSA form.
D include [D]ebug info for every function.
P print [P]ackage inventory.
F print [F]unction SSA code.
S log [S]ource locations as SSA builder progresses.
L build distinct packages seria[L]ly instead of in parallel.
N build [N]aive SSA form: don't replace local loads/stores with registers.
I build bare [I]nit functions: no init guards or calls to dependent inits.
`
func (m BuilderMode) String() string {
var buf bytes.Buffer
if m&GlobalDebug != 0 {
buf.WriteByte('D')
}
if m&PrintPackages != 0 {
buf.WriteByte('P')
}
if m&PrintFunctions != 0 {
buf.WriteByte('F')
}
if m&LogSource != 0 {
buf.WriteByte('S')
}
if m&SanityCheckFunctions != 0 {
buf.WriteByte('C')
}
if m&NaiveForm != 0 {
buf.WriteByte('N')
}
if m&BuildSerially != 0 {
buf.WriteByte('L')
}
return buf.String()
}
// Set parses the flag characters in s and updates *m.
func (m *BuilderMode) Set(s string) error {
var mode BuilderMode
for _, c := range s {
switch c {
case 'D':
mode |= GlobalDebug
case 'P':
mode |= PrintPackages
case 'F':
mode |= PrintFunctions
case 'S':
mode |= LogSource | BuildSerially
case 'C':
mode |= SanityCheckFunctions
case 'N':
mode |= NaiveForm
case 'L':
mode |= BuildSerially
default:
return fmt.Errorf("unknown BuilderMode option: %q", c)
}
}
*m = mode
return nil
}
// Get returns m.
func (m BuilderMode) Get() interface{} { return m }
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册