go.tools: add missing files ssa/*.go

R=golang-dev, adonovan
CC=golang-dev
https://golang.org/cl/9500043
This commit is contained in:
Rob Pike 2013-05-17 13:25:48 -07:00
parent 01f8cd246d
commit 83f21b9226
17 changed files with 8220 additions and 0 deletions

173
ssa/blockopt.go Normal file
View File

@ -0,0 +1,173 @@
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])
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 {
fmt.Println("empty block ", b)
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
}
// 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.DumpTo(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()
}

2703
ssa/builder.go Normal file

File diff suppressed because it is too large Load Diff

115
ssa/doc.go Normal file
View File

@ -0,0 +1,115 @@
// 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 primarily intended for machine code generation.
//
// All looping, branching and switching constructs are replaced with
// unstructured control flow. We may add higher-level control flow
// primitives in the future to facilitate constant-time dispatch of
// switch statements, for example.
//
// Builder encapsulates the tasks of type-checking (using go/types)
// abstract syntax trees (as defined by go/ast) for the source files
// comprising a Go program, and the conversion of each function from
// Go ASTs to the SSA representation.
//
// By supplying an instance of the SourceLocator function prototype,
// clients may control how the builder locates, loads and parses Go
// sources files for imported packages. This package provides
// GorootLoader, which uses go/build to locate packages in the Go
// source distribution, and go/parser to parse them.
//
// 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 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.
//
// Given a Go source package such as this:
//
// package main
//
// import "fmt"
//
// const message = "Hello, World!"
//
// func hello() {
// fmt.Println(message)
// }
//
// The SSA Builder creates a *Program containing a main *Package such
// as this:
//
// Package(Name: "main")
// Members:
// "message": *Literal (Type: untyped string, Value: "Hello, World!")
// "init·guard": *Global (Type: *bool)
// "hello": *Function (Type: func())
// Init: *Function (Type: func())
//
// The printed representation of the function main.hello is shown
// below. Within the function listing, the name of each BasicBlock
// such as ".0.entry" is printed left-aligned, followed by the block's
// instructions, i.e. implementations of Instruction.
// For each instruction that defines an SSA virtual register
// (i.e. implements Value), the type of that value is shown in the
// right column.
//
// # Name: main.hello
// # Declared at hello.go:7:6
// # Type: func()
// func hello():
// .0.entry:
// t0 = new [1]interface{} *[1]interface{}
// t1 = &t0[0:untyped integer] *interface{}
// t2 = make interface interface{} <- string ("Hello, World!":string) interface{}
// *t1 = t2
// t3 = slice t0[:] []interface{}
// t4 = fmt.Println(t3) (n int, err error)
// ret
//
//
// The ssadump utility is an example of an application that loads and
// dumps the SSA form of a Go program, whether a single package or a
// whole program.
//
// TODO(adonovan): demonstrate more features in the example:
// parameters and control flow at the least.
//
// TODO(adonovan): Consider how token.Pos source location information
// should be made available generally. Currently it is only present in
// Package, Function and CallCommon.
//
// TODO(adonovan): Consider the exceptional control-flow implications
// of defer and recover().
//
// TODO(adonovan): build tables/functions that relate source variables
// to SSA variables to assist user interfaces that make queries about
// specific source entities.
package ssa

296
ssa/dom.go Normal file
View File

@ -0,0 +1,296 @@
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 (
"fmt"
"io"
"math/big"
"os"
)
// domNode represents a node in the dominator tree.
//
// TODO(adonovan): export this, when ready.
type domNode struct {
Block *BasicBlock // the basic block; n.Block.dom == n
Idom *domNode // immediate dominator (parent in dominator tree)
Children []*domNode // nodes dominated by this one
Level int // level number of node within tree; zero for root
Pre, Post int // pre- and post-order numbering within dominator tree
// Working state for Lengauer-Tarjan algorithm
// (during which Pre is repurposed for CFG DFS preorder number).
// TODO(adonovan): opt: measure allocating these as temps.
semi *domNode // semidominator
parent *domNode // parent in DFS traversal of CFG
ancestor *domNode // ancestor with least sdom
}
// ltDfs implements the depth-first search part of the LT algorithm.
func ltDfs(v *domNode, i int, preorder []*domNode) int {
preorder[i] = v
v.Pre = i // For now: DFS preorder of spanning tree of CFG
i++
v.semi = v
v.ancestor = nil
for _, succ := range v.Block.Succs {
if w := succ.dom; w.semi == nil {
w.parent = v
i = ltDfs(w, i, preorder)
}
}
return i
}
// ltEval implements the EVAL part of the LT algorithm.
func ltEval(v *domNode) *domNode {
// TODO(adonovan): opt: do path compression per simple LT.
u := v
for ; v.ancestor != nil; v = v.ancestor {
if v.semi.Pre < u.semi.Pre {
u = v
}
}
return u
}
// ltLink implements the LINK part of the LT algorithm.
func ltLink(v, w *domNode) {
w.ancestor = 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
// reodering is due to Georgiadis.
// Initialize domNode nodes.
for _, b := range f.Blocks {
dom := b.dom
if dom == nil {
dom = &domNode{Block: b}
b.dom = dom
} else {
dom.Block = b // reuse
}
}
// Step 1. Number vertices by depth-first preorder.
n := len(f.Blocks)
preorder := make([]*domNode, n)
root := f.Blocks[0].dom
ltDfs(root, 0, preorder)
buckets := make([]*domNode, n)
copy(buckets, preorder)
// In reverse preorder...
for i := 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.Pre] {
u := ltEval(v)
if u.semi.Pre < i {
v.Idom = u
} else {
v.Idom = w
}
}
// Step 2. Compute the semidominators of all nodes.
w.semi = w.parent
for _, pred := range w.Block.Preds {
v := pred.dom
u := ltEval(v)
if u.semi.Pre < w.semi.Pre {
w.semi = u.semi
}
}
ltLink(w.parent, w)
if w.parent == w.semi {
w.Idom = w.parent
} else {
buckets[i] = buckets[w.semi.Pre]
buckets[w.semi.Pre] = w
}
}
// The final 'Step 3' is now outside the loop.
for v := buckets[0]; v != root; v = buckets[v.Pre] {
v.Idom = root
}
// Step 4. Explicitly define the immediate dominator of each
// node, in preorder.
for _, w := range preorder[1:] {
if w == root {
w.Idom = nil
} else {
if w.Idom != w.semi {
w.Idom = w.Idom.Idom
}
// Calculate Children relation as inverse of Idom.
w.Idom.Children = append(w.Idom.Children, w)
}
// Clear working state.
w.semi = nil
w.parent = nil
w.ancestor = nil
}
numberDomTree(root, 0, 0, 0)
// 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. Also, it sets the level
// numbers (zero for the root) used for frontier computation.
//
func numberDomTree(v *domNode, pre, post, level int) (int, int) {
v.Level = level
level++
v.Pre = pre
pre++
for _, child := range v.Children {
pre, post = numberDomTree(child, pre, post, level)
}
v.Post = post
post++
return pre, post
}
// dominates returns true if b dominates c.
// Requires that dominance information is up-to-date.
//
func dominates(b, c *BasicBlock) bool {
return b.dom.Pre <= c.dom.Pre && c.dom.Post <= b.dom.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 := range f.Blocks {
if i == 0 {
// The 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 {
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).
ok := true
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
b, c := f.Blocks[i], f.Blocks[j]
actual := dominates(b, 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
}
}
}
if !ok {
panic("sanityCheckDomTree failed for " + f.FullName())
}
}
// Printing functions ----------------------------------------
// printDomTree prints the dominator tree as text, using indentation.
func printDomTreeText(w io.Writer, v *domNode, indent int) {
fmt.Fprintf(w, "%*s%s\n", 4*indent, "", v.Block)
for _, child := range v.Children {
printDomTreeText(w, child, indent+1)
}
}
// printDomTreeDot prints the dominator tree of f in AT&T GraphViz
// (.dot) format.
func printDomTreeDot(w io.Writer, f *Function) {
fmt.Fprintln(w, "//", f.FullName())
fmt.Fprintln(w, "digraph domtree {")
for i, b := range f.Blocks {
v := b.dom
fmt.Fprintf(w, "\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(w, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.Idom.Pre, v.Pre)
}
// CFG edges.
for _, pred := range b.Preds {
fmt.Fprintf(w, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.dom.Pre, v.Pre)
}
}
fmt.Fprintln(w, "}")
}

301
ssa/emit.go Normal file
View File

@ -0,0 +1,301 @@
package ssa
// Helpers for emitting SSA instructions.
import (
"go/token"
"code.google.com/p/go.tools/go/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) Value {
return f.emit(&Alloc{
Type_: pointer(typ),
Heap: true,
Pos: pos,
})
}
// 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) Value {
v := &UnOp{Op: token.MUL, X: addr}
v.setType(indirectType(addr.Type()))
return f.emit(v)
}
// 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) Value {
switch op {
case token.SHL, token.SHR:
x = emitConv(f, x, t)
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.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) Value {
xt := underlyingType(x.Type())
yt := underlyingType(y.Type())
// 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.IsIdentical(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.(*Literal); ok {
x = emitConv(f, x, y.Type())
} else if _, ok := y.(*Literal); ok {
y = emitConv(f, y, x.Type())
} else {
// other cases, e.g. channels. No-op.
}
v := &BinOp{
Op: op,
X: x,
Y: y,
}
v.setType(tBool)
return f.emit(v)
}
// emitConv emits to f code to convert Value val to exactly type typ,
// and returns the converted value. Implicit conversions are implied
// by language assignability rules in the following operations:
//
// - from rvalue type to lvalue type in assignments.
// - from actual- to formal-parameter types in function calls.
// - from return value type to result type in return statements.
// - population of struct fields, array and slice elements, and map
// keys and values within compoisite literals
// - from index value to index type in indexing expressions.
// - for both arguments of comparisons.
// - from value type to channel type in send expressions.
//
func emitConv(f *Function, val Value, typ types.Type) Value {
// fmt.Printf("emitConv %s -> %s, %T", val.Type(), typ, val) // debugging
// Identical types? Conversion is a no-op.
if types.IsIdentical(val.Type(), typ) {
return val
}
ut_dst := underlyingType(typ)
ut_src := underlyingType(val.Type())
// Identical underlying types? Conversion is a name change.
if types.IsIdentical(ut_dst, ut_src) {
// TODO(adonovan): make this use a distinct
// instruction, ChangeType. This instruction must
// also cover the cases of channel type restrictions and
// conversions between pointers to identical base
// types.
c := &Conv{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 {
return emitTypeAssert(f, val, typ)
}
// Untyped nil literal? Return interface-typed nil literal.
if ut_src == tUntypedNil {
return nilLiteral(typ)
}
// Convert (non-nil) "untyped" literals to their default type.
// TODO(gri): expose types.isUntyped().
if t, ok := ut_src.(*types.Basic); ok && t.Info&types.IsUntyped != 0 {
val = emitConv(f, val, DefaultType(ut_src))
}
mi := &MakeInterface{
X: val,
Methods: f.Prog.MethodSet(val.Type()),
}
mi.setType(typ)
return f.emit(mi)
}
// Conversion of a literal to a non-interface type results in
// a new literal of the destination type and (initially) the
// same abstract value. We don't compute the representation
// change yet; this defers the point at which the number of
// possible representations explodes.
if l, ok := val.(*Literal); ok {
return newLiteral(l.Value, typ)
}
// A representation-changing conversion.
c := &Conv{X: val}
c.setType(typ)
return f.emit(c)
}
// emitStore emits to f an instruction to store value val at location
// addr, applying implicit conversions as required by assignabilty rules.
//
func emitStore(f *Function, addr, val Value) {
f.emit(&Store{
Addr: addr,
Val: emitConv(f, val, indirectType(addr.Type())),
})
}
// 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, ascribing it type typ. It returns the
// extracted value.
//
func emitExtract(f *Function, tuple Value, index int, typ types.Type) Value {
e := &Extract{Tuple: tuple, Index: index}
// In all cases but one (tSelect's recv), typ is redundant w.r.t.
// tuple.Type().(*types.Result).Values[index].Type.
e.setType(typ)
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) Value {
// Simplify infallible assertions.
txi := underlyingType(x.Type()).(*types.Interface)
if ti, ok := underlyingType(t).(*types.Interface); ok {
if types.IsIdentical(ti, txi) {
return x
}
if isSuperinterface(ti, txi) {
c := &ChangeInterface{X: x}
c.setType(t)
return f.emit(c)
}
}
a := &TypeAssert{X: x, AssertedType: t}
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) Value {
// TODO(adonovan): opt: simplify infallible tests as per
// emitTypeAssert, and return (x, vTrue).
// (Requires that exprN returns a slice of extracted values,
// not a single Value of type *types.Results.)
a := &TypeAssert{
X: x,
AssertedType: t,
CommaOk: true,
}
a.setType(&types.Result{Values: []*types.Var{
{Name: "value", Type: t},
varOk,
}})
return f.emit(a)
}
// emitTailCall emits to f a function call in tail position,
// passing on all but the first formal parameter to f as actual
// values in the call. Intended for delegating bridge methods.
// Precondition: f does/will not use deferred procedure calls.
// Postcondition: f.currentBlock is nil.
//
func emitTailCall(f *Function, call *Call) {
for _, arg := range f.Params[1:] {
call.Call.Args = append(call.Call.Args, arg)
}
nr := len(f.Signature.Results)
if nr == 1 {
call.Type_ = f.Signature.Results[0].Type
} else {
call.Type_ = &types.Result{Values: f.Signature.Results}
}
tuple := f.emit(call)
var ret Ret
switch nr {
case 0:
// no-op
case 1:
ret.Results = []Value{tuple}
default:
for i, o := range call.Type().(*types.Result).Values {
v := emitExtract(f, tuple, i, o.Type)
// 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
}

608
ssa/func.go Normal file
View File

@ -0,0 +1,608 @@
package ssa
// This file implements the Function and BasicBlock types.
import (
"fmt"
"go/ast"
"go/token"
"io"
"os"
"code.google.com/p/go.tools/go/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)
}
// 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.%s", b.Index, b.Comment)
}
// 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
}
// funcSyntax holds the syntax tree for the function declaration and body.
type funcSyntax struct {
recvField *ast.FieldList
paramFields *ast.FieldList
resultFields *ast.FieldList
body *ast.BlockStmt
}
// 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 and type.
//
func (f *Function) addParam(name string, typ types.Type) *Parameter {
v := &Parameter{
Name_: name,
Type_: typ,
}
f.Params = append(f.Params, v)
return v
}
// 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) {
name := obj.GetName()
param := f.addParam(name, obj.GetType())
spill := &Alloc{
Name_: name + "~", // "~" means "spilled"
Type_: pointer(obj.GetType()),
}
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.
//
// idents must be a mapping from syntactic identifiers to their
// canonical type objects.
//
// Preconditions:
// f.syntax != nil, i.e. this is a Go source function.
// f.startBody() was called.
// Postcondition:
// len(f.Params) == len(f.Signature.Params) + (f.Signature.Recv ? 1 : 0)
//
func (f *Function) createSyntacticParams(idents map[*ast.Ident]types.Object) {
// Receiver (at most one inner iteration).
if f.syntax.recvField != nil {
for _, field := range f.syntax.recvField.List {
for _, n := range field.Names {
f.addSpilledParam(idents[n])
}
// Anonymous receiver? No need to spill.
if field.Names == nil {
recvVar := f.Signature.Recv
f.addParam(recvVar.Name, recvVar.Type)
}
}
}
// Parameters.
if f.syntax.paramFields != nil {
n := len(f.Params) // 1 if has recv, 0 otherwise
for _, field := range f.syntax.paramFields.List {
for _, n := range field.Names {
f.addSpilledParam(idents[n])
}
// Anonymous parameter? No need to spill.
if field.Names == nil {
paramVar := f.Signature.Params[len(f.Params)-n]
f.addParam(paramVar.Name, paramVar.Type)
}
}
}
// Named results.
if f.syntax.resultFields != nil {
for _, field := range f.syntax.resultFields.List {
// Implicit "var" decl of locals for named results.
for _, n := range field.Names {
f.namedResults = append(f.namedResults, f.addNamedLocal(idents[n]))
}
}
}
}
// numberRegisters assigns numbers to all SSA registers
// (value-defining Instructions) in f, to aid debugging.
// (Non-Instruction Values are named at construction.)
// NB: named Allocs retain their existing name.
// TODO(adonovan): when we have source position info,
// preserve names only for source locals.
//
func numberRegisters(f *Function) {
a, v := 0, 0
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
switch instr := instr.(type) {
case *Alloc:
// Allocs may be named at birth.
if instr.Name_ == "" {
instr.Name_ = fmt.Sprintf("a%d", a)
a++
}
case Value:
instr.(interface {
setNum(int)
}).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.namedResults = nil
f.currentBlock = nil
f.lblocks = nil
f.syntax = nil
// Remove any f.Locals that are now heap-allocated.
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)
if f.Prog.mode&NaiveForm == 0 {
// For debugging pre-state of lifting pass:
// numberRegisters(f)
// f.DumpTo(os.Stderr)
lift(f)
}
numberRegisters(f)
if f.Prog.mode&LogFunctions != 0 {
f.DumpTo(os.Stderr)
}
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]
}
// 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.
//
// Precondition: f.syntax != nil (i.e. a Go source function).
//
func (f *Function) addNamedLocal(obj types.Object) *Alloc {
l := f.addLocal(obj.GetType(), obj.GetPos())
l.Name_ = obj.GetName()
f.objects[obj] = l
return l
}
// 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{Type_: pointer(typ), Pos: 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 escaping {
// Walk up the chain of Captures.
x := v
for {
if c, ok := x.(*Capture); ok {
x = c.Outer
} else {
break
}
}
// By construction, all captures are ultimately Allocs in the
// naive SSA form. Parameters are pre-spilled to the stack.
x.(*Alloc).Heap = true
}
return v // function-local var (address)
}
// Definition must be in an enclosing function;
// plumb it through intervening closures.
if f.Enclosing == nil {
panic("no Value for type.Object " + obj.GetName())
}
v := &Capture{Outer: f.Enclosing.lookup(obj, true)} // escaping
f.objects[obj] = v
f.FreeVars = append(f.FreeVars, v)
return v
}
// emit emits the specified instruction to function f, updating the
// control-flow graph if required.
//
func (f *Function) emit(instr Instruction) Value {
return f.currentBlock.emit(instr)
}
// FullName 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
// "IsNaN" // intra-package reference to same
// "(*sync.WaitGroup).Add" // a declared method
// "(*exp/ssa.Ret).Block" // a bridge method
// "(ssa.Instruction).Block" // an interface method thunk
// "func@5.32" // an anonymous function
//
func (f *Function) FullName() string {
return f.fullName(nil)
}
// Like FullName, but if from==f.Pkg, suppress package qualification.
func (f *Function) fullName(from *Package) string {
// Anonymous?
if f.Enclosing != nil {
return f.Name_
}
recv := f.Signature.Recv
// Synthetic?
if f.Pkg == nil {
var recvType types.Type
if recv != nil {
recvType = recv.Type // bridge method
} else {
recvType = f.Params[0].Type() // interface method thunk
}
return fmt.Sprintf("(%s).%s", recvType, f.Name_)
}
// Declared method?
if recv != nil {
return fmt.Sprintf("(%s).%s", recv.Type, f.Name_)
}
// Package-level function.
// Prefix with package name for cross-package references only.
if from != f.Pkg {
return fmt.Sprintf("%s.%s", f.Pkg.Types.Path, f.Name_)
}
return f.Name_
}
// writeSignature writes to w the signature sig in declaration syntax.
// Derived from types.Signature.String().
//
func writeSignature(w io.Writer, name string, sig *types.Signature, params []*Parameter) {
io.WriteString(w, "func ")
if sig.Recv != nil {
io.WriteString(w, "(")
if n := params[0].Name(); n != "" {
io.WriteString(w, n)
io.WriteString(w, " ")
}
io.WriteString(w, params[0].Type().String())
io.WriteString(w, ") ")
params = params[1:]
}
io.WriteString(w, name)
io.WriteString(w, "(")
for i, v := range params {
if i > 0 {
io.WriteString(w, ", ")
}
io.WriteString(w, v.Name())
io.WriteString(w, " ")
if sig.IsVariadic && i == len(params)-1 {
io.WriteString(w, "...")
io.WriteString(w, underlyingType(v.Type()).(*types.Slice).Elt.String())
} else {
io.WriteString(w, v.Type().String())
}
}
io.WriteString(w, ")")
if res := sig.Results; res != nil {
io.WriteString(w, " ")
var t types.Type
if len(res) == 1 && res[0].Name == "" {
t = res[0].Type
} else {
t = &types.Result{Values: res}
}
io.WriteString(w, t.String())
}
}
// DumpTo prints to w a human readable "disassembly" of the SSA code of
// all basic blocks of function f.
//
func (f *Function) DumpTo(w io.Writer) {
fmt.Fprintf(w, "# Name: %s\n", f.FullName())
fmt.Fprintf(w, "# Declared at %s\n", f.Prog.Files.Position(f.Pos))
if f.Enclosing != nil {
fmt.Fprintf(w, "# Parent: %s\n", f.Enclosing.Name())
}
if f.FreeVars != nil {
io.WriteString(w, "# Free variables:\n")
for i, fv := range f.FreeVars {
fmt.Fprintf(w, "# % 3d:\t%s %s\n", i, fv.Name(), fv.Type())
}
}
if len(f.Locals) > 0 {
io.WriteString(w, "# Locals:\n")
for i, l := range f.Locals {
fmt.Fprintf(w, "# % 3d:\t%s %s\n", i, l.Name(), indirectType(l.Type()))
}
}
writeSignature(w, f.Name(), f.Signature, f.Params)
io.WriteString(w, ":\n")
if f.Blocks == nil {
io.WriteString(w, "\t(external)\n")
}
for _, b := range f.Blocks {
if b == nil {
// Corrupt CFG.
fmt.Fprintf(w, ".nil:\n")
continue
}
fmt.Fprintf(w, ".%s:\t\t\t\t\t\t\t P:%d S:%d\n", b, len(b.Preds), len(b.Succs))
if false { // CFG debugging
fmt.Fprintf(w, "\t# CFG: %s --> %s --> %s\n", b.Preds, b, b.Succs)
}
for _, instr := range b.Instrs {
io.WriteString(w, "\t")
switch v := instr.(type) {
case Value:
l := 80 // for old time's sake.
// Left-align the instruction.
if name := v.Name(); name != "" {
n, _ := fmt.Fprintf(w, "%s = ", name)
l -= n
}
n, _ := io.WriteString(w, instr.String())
l -= n
// Right-align the type.
if t := v.Type(); t != nil {
fmt.Fprintf(w, " %*s", l-10, t)
}
case nil:
// Be robust against bad transforms.
io.WriteString(w, "<deleted>")
default:
io.WriteString(w, instr.String())
}
io.WriteString(w, "\n")
}
}
fmt.Fprintf(w, "\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,
Func: f,
}
b.Succs = b.succs2[:0]
f.Blocks = append(f.Blocks, b)
return b
}

155
ssa/importer.go Normal file
View File

@ -0,0 +1,155 @@
package ssa
// This file defines an implementation of the types.Importer interface
// (func) that loads the transitive closure of dependencies of a
// "main" package.
import (
"errors"
"go/ast"
"go/build"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
"code.google.com/p/go.tools/go/types"
)
// Prototype of a function that locates, reads and parses a set of
// source files given an import path.
//
// fset is the fileset to which the ASTs should be added.
// path is the imported path, e.g. "sync/atomic".
//
// On success, the function returns files, the set of ASTs produced,
// or the first error encountered.
//
type SourceLoader func(fset *token.FileSet, path string) (files []*ast.File, err error)
// doImport loads the typechecker package identified by path
// Implements the types.Importer prototype.
//
func (b *Builder) doImport(imports map[string]*types.Package, path string) (typkg *types.Package, err error) {
// Package unsafe is handled specially, and has no ssa.Package.
if path == "unsafe" {
return types.Unsafe, nil
}
if pkg := b.Prog.Packages[path]; pkg != nil {
typkg = pkg.Types
imports[path] = typkg
return // positive cache hit
}
if err = b.importErrs[path]; err != nil {
return // negative cache hit
}
var files []*ast.File
var info *TypeInfo
if b.Context.Mode&UseGCImporter != 0 {
typkg, err = types.GcImport(imports, path)
} else {
files, err = b.Context.Loader(b.Prog.Files, path)
if err == nil {
typkg, info, err = b.typecheck(files)
}
}
if err != nil {
// Cache failure
b.importErrs[path] = err
return nil, err
}
// Cache success
imports[path] = typkg // cache for just this package.
b.Prog.Packages[path] = b.createPackageImpl(typkg, path, files, info) // cache across all packages
return typkg, nil
}
// GorootLoader is an implementation of the SourceLoader function
// prototype that loads and parses Go source files from the package
// directory beneath $GOROOT/src/pkg.
//
// TODO(adonovan): get rsc and adg (go/build owners) to review this.
// TODO(adonovan): permit clients to specify a non-default go/build.Context.
//
func GorootLoader(fset *token.FileSet, path string) (files []*ast.File, err error) {
// TODO(adonovan): fix: Do we need cwd? Shouldn't ImportDir(path) / $GOROOT suffice?
srcDir, err := os.Getwd()
if err != nil {
return // serious misconfiguration
}
bp, err := build.Import(path, srcDir, 0)
if err != nil {
return // import failed
}
files, err = ParseFiles(fset, bp.Dir, bp.GoFiles...)
if err != nil {
return nil, err
}
return
}
// ParseFiles parses the Go source files files within directory dir
// and returns their ASTs, or the first parse error if any.
//
// This utility function is provided to facilitate implementing a
// SourceLoader.
//
func ParseFiles(fset *token.FileSet, dir string, files ...string) (parsed []*ast.File, err error) {
for _, file := range files {
var f *ast.File
if !filepath.IsAbs(file) {
file = filepath.Join(dir, file)
}
f, err = parser.ParseFile(fset, file, nil, parser.DeclarationErrors)
if err != nil {
return // parsing failed
}
parsed = append(parsed, f)
}
return
}
// CreatePackageFromArgs builds an initial Package from a list of
// command-line arguments.
// If args is a list of *.go files, they are parsed and type-checked.
// If args is a Go package import path, that package is imported.
// rest is the suffix of args that were not consumed.
//
// This utility is provided to facilitate construction of command-line
// tools with a consistent user interface.
//
func CreatePackageFromArgs(builder *Builder, args []string) (pkg *Package, rest []string, err error) {
var pkgname string
var files []*ast.File
switch {
case len(args) == 0:
err = errors.New("No *.go source files nor package name was specified.")
case strings.HasSuffix(args[0], ".go"):
// % tool a.go b.go ...
// Leading consecutive *.go arguments constitute main package.
pkgname = "main"
i := 1
for ; i < len(args) && strings.HasSuffix(args[i], ".go"); i++ {
}
files, err = ParseFiles(builder.Prog.Files, ".", args[:i]...)
rest = args[i:]
default:
// % tool my/package ...
// First argument is import path of main package.
pkgname = args[0]
rest = args[1:]
files, err = builder.Context.Loader(builder.Prog.Files, pkgname)
}
if err == nil {
pkg, err = builder.CreatePackage(pkgname, files)
}
return
}

513
ssa/lift.go Normal file
View File

@ -0,0 +1,513 @@
package ssa
// This file defines the lifting pass which tries to "lift" Alloc
// cells (new/local variables) into SSA registers, replacing loads
// with the dominating stored value, eliminating loads and stores, and
// inserting φ-nodes as needed.
// Cited papers and resources:
//
// Ron Cytron et al. 1991. Efficiently computing SSA form...
// http://doi.acm.org/10.1145/115372.115320
//
// Cooper, Harvey, Kennedy. 2001. A Simple, Fast Dominance Algorithm.
// Software Practice and Experience 2001, 4:1-10.
// http://www.hipersoft.rice.edu/grads/publications/dom14.pdf
//
// Daniel Berlin, llvmdev mailing list, 2012.
// http://lists.cs.uiuc.edu/pipermail/llvmdev/2012-January/046638.html
// (Be sure to expand the whole thread.)
// TODO(adonovan): opt: there are many optimizations worth evaluating, and
// the conventional wisdom for SSA construction is that a simple
// algorithm well engineered often beats those of better asymptotic
// complexity on all but the most egregious inputs.
//
// Danny Berlin suggests that the Cooper et al. algorithm for
// computing the dominance frontier is superior to Cytron et al.
// Furthermore he recommends that rather than computing the DF for the
// whole function then renaming all alloc cells, it may be cheaper to
// compute the DF for each alloc cell separately and throw it away.
//
// Consider exploiting liveness information to avoid creating dead
// φ-nodes which we then immediately remove.
//
// Integrate lifting with scalar replacement of aggregates (SRA) since
// the two are synergistic.
//
// Also see many other "TODO: opt" suggestions in the code.
import (
"fmt"
"go/token"
"math/big"
"os"
"code.google.com/p/go.tools/go/types"
)
// If true, perform sanity checking and show diagnostic information at
// each step of lifting. Very verbose.
const debugLifting = false
// domFrontier maps each block to the set of blocks in its dominance
// frontier. The outer slice is conceptually a map keyed by
// Block.Index. The inner slice is conceptually a set, possibly
// containing duplicates.
//
// TODO(adonovan): opt: measure impact of dups; consider a packed bit
// representation, e.g. big.Int, and bitwise parallel operations for
// the union step in the Children loop.
//
// domFrontier's methods mutate the slice's elements but not its
// length, so their receivers needn't be pointers.
//
type domFrontier [][]*BasicBlock
func (df domFrontier) add(u, v *domNode) {
p := &df[u.Block.Index]
*p = append(*p, v.Block)
}
// build builds the dominance frontier df for the dominator (sub)tree
// rooted at u, using the Cytron et al. algorithm.
//
// TODO(adonovan): opt: consider Berlin approach, computing pruned SSA
// by pruning the entire IDF computation, rather than merely pruning
// the DF -> IDF step.
func (df domFrontier) build(u *domNode) {
// Encounter each node u in postorder of dom tree.
for _, child := range u.Children {
df.build(child)
}
for _, vb := range u.Block.Succs {
if v := vb.dom; v.Idom != u {
df.add(u, v)
}
}
for _, w := range u.Children {
for _, vb := range df[w.Block.Index] {
// TODO(adonovan): opt: use word-parallel bitwise union.
if v := vb.dom; v.Idom != u {
df.add(u, v)
}
}
}
}
func buildDomFrontier(fn *Function) domFrontier {
df := make(domFrontier, len(fn.Blocks))
df.build(fn.Blocks[0].dom)
return df
}
// lift attempts to replace local and new Allocs accessed only with
// load/store by SSA registers, inserting φ-nodes where necessary.
// The result is a program in classical pruned SSA form.
//
// Preconditions:
// - fn has no dead blocks (blockopt has run).
// - Def/use info (Operands and Referrers) is up-to-date.
//
func lift(fn *Function) {
// TODO(adonovan): opt: lots of little optimizations may be
// worthwhile here, especially if they cause us to avoid
// buildDomTree. For example:
//
// - Alloc never loaded? Eliminate.
// - Alloc never stored? Replace all loads with a zero literal.
// - Alloc stored once? Replace loads with dominating store;
// don't forget that an Alloc is itself an effective store
// of zero.
// - Alloc used only within a single block?
// Use degenerate algorithm avoiding φ-nodes.
// - Consider synergy with scalar replacement of aggregates (SRA).
// e.g. *(&x.f) where x is an Alloc.
// Perhaps we'd get better results if we generated this as x.f
// i.e. Field(x, .f) instead of Load(FieldIndex(x, .f)).
// Unclear.
//
// But we will start with the simplest correct code to make
// life easier for reviewers.
buildDomTree(fn)
df := buildDomFrontier(fn)
if debugLifting {
title := false
for i, blocks := range df {
if blocks != nil {
if !title {
fmt.Fprintln(os.Stderr, "Dominance frontier:")
title = true
}
fmt.Fprintf(os.Stderr, "\t%s: %s\n", fn.Blocks[i], blocks)
}
}
}
newPhis := make(newPhiMap)
// During this pass we will replace some BasicBlock.Instrs
// (allocs, loads and stores) with nil, keeping a count in
// BasicBlock.gaps. At the end we will reset Instrs to the
// concatenation of all non-dead newPhis and non-nil Instrs
// for the block, reusing the original array if space permits.
// While we're here, we also eliminate 'rundefers'
// instructions in functions that contain no 'defer'
// instructions.
usesDefer := false
// Determine which allocs we can lift and number them densely.
// The renaming phase uses this numbering for compact maps.
numAllocs := 0
for _, b := range fn.Blocks {
b.gaps = 0
b.rundefers = 0
for i, instr := range b.Instrs {
switch instr := instr.(type) {
case *Alloc:
if liftAlloc(df, instr, newPhis) {
instr.index = numAllocs
numAllocs++
// Delete the alloc.
b.Instrs[i] = nil
b.gaps++
} else {
instr.index = -1
}
case *Defer:
usesDefer = true
case *RunDefers:
b.rundefers++
}
}
}
// renaming maps an alloc (keyed by index) to its replacement
// value. Initially the renaming contains nil, signifying the
// zero literal of the appropriate type; we construct the
// Literal lazily at most once on each path through the domtree.
// TODO(adonovan): opt: cache per-function not per subtree.
renaming := make([]Value, numAllocs)
// Renaming.
rename(fn.Blocks[0], renaming, newPhis)
// Eliminate dead new phis, then prepend the live ones to each block.
for _, b := range fn.Blocks {
// Compress the newPhis slice to eliminate unused phis.
// TODO(adonovan): opt: compute liveness to avoid
// placing phis in blocks for which the alloc cell is
// not live.
nps := newPhis[b]
j := 0
for _, np := range nps {
if len(*np.phi.Referrers()) == 0 {
continue // unreferenced phi
}
nps[j] = np
j++
}
nps = nps[:j]
rundefersToKill := b.rundefers
if usesDefer {
rundefersToKill = 0
}
if j+b.gaps+rundefersToKill == 0 {
continue // fast path: no new phis or gaps
}
// Compact nps + non-nil Instrs into a new slice.
// TODO(adonovan): opt: compact in situ if there is
// sufficient space or slack in the slice.
dst := make([]Instruction, len(b.Instrs)+j-b.gaps-rundefersToKill)
for i, np := range nps {
dst[i] = np.phi
}
for _, instr := range b.Instrs {
if instr == nil {
continue
}
if !usesDefer {
if _, ok := instr.(*RunDefers); ok {
continue
}
}
dst[j] = instr
j++
}
for i, np := range nps {
dst[i] = np.phi
}
b.Instrs = dst
}
// Remove any fn.Locals that were lifted.
j := 0
for _, l := range fn.Locals {
if l.index == -1 {
fn.Locals[j] = l
j++
}
}
// Nil out fn.Locals[j:] to aid GC.
for i := j; i < len(fn.Locals); i++ {
fn.Locals[i] = nil
}
fn.Locals = fn.Locals[:j]
}
type blockSet struct{ big.Int } // (inherit methods from Int)
// add adds b to the set and returns true if the set changed.
func (s *blockSet) add(b *BasicBlock) bool {
i := b.Index
if s.Bit(i) != 0 {
return false
}
s.SetBit(&s.Int, i, 1)
return true
}
// take removes an arbitrary element from a set s and
// returns its index, or returns -1 if empty.
func (s *blockSet) take() int {
l := s.BitLen()
for i := 0; i < l; i++ {
if s.Bit(i) == 1 {
s.SetBit(&s.Int, i, 0)
return i
}
}
return -1
}
// newPhi is a pair of a newly introduced φ-node and the lifted Alloc
// it replaces.
type newPhi struct {
phi *Phi
alloc *Alloc
}
// newPhiMap records for each basic block, the set of newPhis that
// must be prepended to the block.
type newPhiMap map[*BasicBlock][]newPhi
// liftAlloc determines whether alloc can be lifted into registers,
// and if so, it populates newPhis with all the φ-nodes it may require
// and returns true.
//
func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap) bool {
// Don't lift aggregates into registers.
// We'll need a separate SRA pass for that.
switch underlyingType(indirectType(alloc.Type())).(type) {
case *types.Array, *types.Struct:
return false
}
// Compute defblocks, the set of blocks containing a
// definition of the alloc cell.
var defblocks blockSet
for _, instr := range *alloc.Referrers() {
// Bail out if we discover the alloc is not liftable;
// the only operations permitted to use the alloc are
// loads/stores into the cell.
switch instr := instr.(type) {
case *Store:
if instr.Val == alloc {
return false // address used as value
}
if instr.Addr != alloc {
panic("Alloc.Referrers is inconsistent")
}
defblocks.add(instr.Block())
case *UnOp:
if instr.Op != token.MUL {
return false // not a load
}
if instr.X != alloc {
panic("Alloc.Referrers is inconsistent")
}
default:
return false // some other instruction
}
}
// The Alloc itself counts as a (zero) definition of the cell.
defblocks.add(alloc.Block())
if debugLifting {
fmt.Fprintln(os.Stderr, "liftAlloc: lifting ", alloc, alloc.Name())
}
fn := alloc.Block().Func
// Φ-insertion.
//
// What follows is the body of the main loop of the insert-φ
// function described by Cytron et al, but instead of using
// counter tricks, we just reset the 'hasAlready' and 'work'
// sets each iteration. These are bitmaps so it's pretty cheap.
//
// TODO(adonovan): opt: recycle slice storage for W,
// hasAlready, defBlocks across liftAlloc calls.
var hasAlready blockSet
// Initialize W and work to defblocks.
var work blockSet = defblocks // blocks seen
var W blockSet // blocks to do
W.Set(&defblocks.Int)
// Traverse iterated dominance frontier, inserting φ-nodes.
for i := W.take(); i != -1; i = W.take() {
u := fn.Blocks[i]
for _, v := range df[u.Index] {
if hasAlready.add(v) {
// Create φ-node.
// It will be prepended to v.Instrs later, if needed.
phi := &Phi{
Edges: make([]Value, len(v.Preds)),
Comment: alloc.Name(),
}
phi.setType(indirectType(alloc.Type()))
phi.Block_ = v
if debugLifting {
fmt.Fprintf(os.Stderr, "place %s = %s at block %s\n", phi.Name(), phi, v)
}
newPhis[v] = append(newPhis[v], newPhi{phi, alloc})
if work.add(v) {
W.add(v)
}
}
}
}
return true
}
// replaceAll replaces all intraprocedural uses of x with y,
// updating x.Referrers and y.Referrers.
// Precondition: x.Referrers() != nil, i.e. x must be local to some function.
//
func replaceAll(x, y Value) {
var rands []*Value
pxrefs := x.Referrers()
pyrefs := y.Referrers()
for _, instr := range *pxrefs {
rands = instr.Operands(rands[:0]) // recycle storage
for _, rand := range rands {
if *rand != nil {
if *rand == x {
*rand = y
}
}
}
if pyrefs != nil {
*pyrefs = append(*pyrefs, instr) // dups ok
}
}
*pxrefs = nil // x is now unreferenced
}
// renamed returns the value to which alloc is being renamed,
// constructing it lazily if it's the implicit zero initialization.
//
func renamed(renaming []Value, alloc *Alloc) Value {
v := renaming[alloc.index]
if v == nil {
v = zeroLiteral(indirectType(alloc.Type()))
renaming[alloc.index] = v
}
return v
}
// rename implements the (Cytron et al) SSA renaming algorithm, a
// preorder traversal of the dominator tree replacing all loads of
// Alloc cells with the value stored to that cell by the dominating
// store instruction. For lifting, we need only consider loads,
// stores and φ-nodes.
//
// renaming is a map from *Alloc (keyed by index number) to its
// dominating stored value; newPhis[x] is the set of new φ-nodes to be
// prepended to block x.
//
func rename(u *BasicBlock, renaming []Value, newPhis newPhiMap) {
// Each φ-node becomes the new name for its associated Alloc.
for _, np := range newPhis[u] {
phi := np.phi
alloc := np.alloc
renaming[alloc.index] = phi
}
// Rename loads and stores of allocs.
for i, instr := range u.Instrs {
_ = i
switch instr := instr.(type) {
case *Store:
if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index != -1 { // store to Alloc cell
// Delete the Store.
u.Instrs[i] = nil
u.gaps++
// Replace dominated loads by the
// stored value.
renaming[alloc.index] = instr.Val
if debugLifting {
fmt.Fprintln(os.Stderr, "Kill store ", instr, "; current value is now ", instr.Val.Name())
}
}
case *UnOp:
if instr.Op == token.MUL {
if alloc, ok := instr.X.(*Alloc); ok && alloc.index != -1 { // load of Alloc cell
newval := renamed(renaming, alloc)
if debugLifting {
fmt.Fprintln(os.Stderr, "Replace refs to load", instr.Name(), "=", instr, "with", newval.Name())
}
// Replace all references to
// the loaded value by the
// dominating stored value.
replaceAll(instr, newval)
// Delete the Load.
u.Instrs[i] = nil
u.gaps++
}
}
}
}
// For each φ-node in a CFG successor, rename the edge.
for _, v := range u.Succs {
phis := newPhis[v]
if len(phis) == 0 {
continue
}
i := v.predIndex(u)
for _, np := range phis {
phi := np.phi
alloc := np.alloc
newval := renamed(renaming, alloc)
if debugLifting {
fmt.Fprintf(os.Stderr, "setphi %s edge %s -> %s (#%d) (alloc=%s) := %s\n \n",
phi.Name(), u, v, i, alloc.Name(), newval.Name())
}
phi.Edges[i] = newval
if prefs := newval.Referrers(); prefs != nil {
*prefs = append(*prefs, phi)
}
}
}
// Continue depth-first recursion over domtree, pushing a
// fresh copy of the renaming map for each subtree.
for _, v := range u.dom.Children {
// TODO(adonovan): opt: avoid copy on final iteration; use destructive update.
r := make([]Value, len(renaming))
copy(r, renaming)
rename(v.Block, r, newPhis)
}
}

139
ssa/literal.go Normal file
View File

@ -0,0 +1,139 @@
package ssa
// This file defines the Literal SSA value type.
import (
"fmt"
"strconv"
"code.google.com/p/go.tools/go/exact"
"code.google.com/p/go.tools/go/types"
)
// newLiteral returns a new literal of the specified value and type.
// val must be valid according to the specification of Literal.Value.
//
func newLiteral(val exact.Value, typ types.Type) *Literal {
// This constructor exists to provide a single place to
// insert logging/assertions during debugging.
return &Literal{typ, val}
}
// intLiteral returns an untyped integer literal that evaluates to i.
func intLiteral(i int64) *Literal {
return newLiteral(exact.MakeInt64(i), types.Typ[types.UntypedInt])
}
// nilLiteral returns a nil literal of the specified type, which may
// be any reference type, including interfaces.
//
func nilLiteral(typ types.Type) *Literal {
return newLiteral(exact.MakeNil(), typ)
}
// zeroLiteral returns a new "zero" literal 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 Literal.
//
func zeroLiteral(t types.Type) *Literal {
switch t := t.(type) {
case *types.Basic:
switch {
case t.Info&types.IsBoolean != 0:
return newLiteral(exact.MakeBool(false), t)
case t.Info&types.IsNumeric != 0:
return newLiteral(exact.MakeInt64(0), t)
case t.Info&types.IsString != 0:
return newLiteral(exact.MakeString(""), t)
case t.Kind == types.UnsafePointer:
fallthrough
case t.Kind == types.UntypedNil:
return nilLiteral(t)
default:
panic(fmt.Sprint("zeroLiteral for unexpected type:", t))
}
case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature:
return nilLiteral(t)
case *types.NamedType:
return newLiteral(zeroLiteral(t.Underlying).Value, t)
case *types.Array, *types.Struct:
panic(fmt.Sprint("zeroLiteral applied to aggregate:", t))
}
panic(fmt.Sprint("zeroLiteral: unexpected ", t))
}
func (l *Literal) Name() string {
s := l.Value.String()
if l.Value.Kind() == exact.String {
const n = 20
if len(s) > n {
s = s[:n-3] + "..." // abbreviate
}
s = strconv.Quote(s)
}
return s + ":" + l.Type_.String()
}
func (l *Literal) Type() types.Type {
return l.Type_
}
func (l *Literal) Referrers() *[]Instruction {
return nil
}
// IsNil returns true if this literal represents a typed or untyped nil value.
func (l *Literal) IsNil() bool {
return l.Value.Kind() == exact.Nil
}
// Int64 returns the numeric value of this literal truncated to fit
// a signed 64-bit integer.
//
func (l *Literal) Int64() int64 {
switch x := l.Value; x.Kind() {
case exact.Int:
if i, ok := exact.Int64Val(x); ok {
return i
}
return 0
case exact.Float:
f, _ := exact.Float64Val(x)
return int64(f)
}
panic(fmt.Sprintf("unexpected literal value: %T", l.Value))
}
// Uint64 returns the numeric value of this literal truncated to fit
// an unsigned 64-bit integer.
//
func (l *Literal) Uint64() uint64 {
switch x := l.Value; x.Kind() {
case exact.Int:
if u, ok := exact.Uint64Val(x); ok {
return u
}
return 0
case exact.Float:
f, _ := exact.Float64Val(x)
return uint64(f)
}
panic(fmt.Sprintf("unexpected literal value: %T", l.Value))
}
// Float64 returns the numeric value of this literal truncated to fit
// a float64.
//
func (l *Literal) Float64() float64 {
f, _ := exact.Float64Val(l.Value)
return f
}
// Complex128 returns the complex value of this literal truncated to
// fit a complex128.
//
func (l *Literal) Complex128() complex128 {
re, _ := exact.Float64Val(exact.Real(l.Value))
im, _ := exact.Float64Val(exact.Imag(l.Value))
return complex(re, im)
}

86
ssa/lvalue.go Normal file
View File

@ -0,0 +1,86 @@
package ssa
// lvalues are the union of addressable expressions and map-index
// expressions.
import (
"code.google.com/p/go.tools/go/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
typ() types.Type // returns the type of the location
}
// An address is an lvalue represented by a true pointer.
type address struct {
addr Value
}
func (a address) load(fn *Function) Value {
return emitLoad(fn, a.addr)
}
func (a address) store(fn *Function, v Value) {
emitStore(fn, a.addr, v)
}
func (a address) typ() types.Type {
return indirectType(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
}
func (e *element) load(fn *Function) Value {
l := &Lookup{
X: e.m,
Index: e.k,
}
l.setType(e.t)
return fn.emit(l)
}
func (e *element) store(fn *Function, v Value) {
fn.emit(&MapUpdate{
Map: e.m,
Key: e.k,
Value: emitConv(fn, v, e.t),
})
}
func (e *element) typ() types.Type {
return e.t
}
// A blanks 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) 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")
}

408
ssa/print.go Normal file
View File

@ -0,0 +1,408 @@
package ssa
// This file implements the String() methods for all Value and
// Instruction types.
import (
"bytes"
"fmt"
"go/ast"
"io"
"sort"
"code.google.com/p/go.tools/go/types"
)
func (id Id) String() string {
if id.Pkg == nil {
return id.Name
}
return fmt.Sprintf("%s/%s", id.Pkg.Path, id.Name)
}
// relName returns the name of v relative to i.
// In most cases, this is identical to v.Name(), but for references to
// Functions (including methods) and Globals, the FullName is used
// instead, explicitly package-qualified for cross-package references.
//
func relName(v Value, i Instruction) string {
switch v := v.(type) {
case *Global:
if i != nil && v.Pkg == i.Block().Func.Pkg {
return v.Name()
}
return v.FullName()
case *Function:
var pkg *Package
if i != nil {
pkg = i.Block().Func.Pkg
}
return v.fullName(pkg)
}
return v.Name()
}
// Value.String()
//
// This method is provided only for debugging.
// It never appears in disassembly, which uses Value.Name().
func (v *Literal) String() string {
return fmt.Sprintf("literal %s rep=%T", v.Name(), v.Value)
}
func (v *Parameter) String() string {
return fmt.Sprintf("parameter %s : %s", v.Name(), v.Type())
}
func (v *Capture) String() string {
return fmt.Sprintf("capture %s : %s", v.Name(), v.Type())
}
func (v *Global) String() string {
return fmt.Sprintf("global %s : %s", v.Name(), v.Type())
}
func (v *Builtin) String() string {
return fmt.Sprintf("builtin %s : %s", v.Name(), v.Type())
}
func (v *Function) String() string {
return fmt.Sprintf("function %s : %s", v.Name(), v.Type())
}
// FullName returns g's package-qualified name.
func (g *Global) FullName() string {
return fmt.Sprintf("%s.%s", g.Pkg.Types.Path, g.Name_)
}
// Instruction.String()
func (v *Alloc) String() string {
op := "local"
if v.Heap {
op = "new"
}
return fmt.Sprintf("%s %s", op, indirectType(v.Type()))
}
func (v *Phi) String() string {
var b bytes.Buffer
b.WriteString("phi [")
for i, edge := range v.Edges {
if i > 0 {
b.WriteString(", ")
}
// Be robust against malformed CFG.
blockname := "?"
if v.Block_ != nil && i < len(v.Block_.Preds) {
blockname = v.Block_.Preds[i].String()
}
b.WriteString(blockname)
b.WriteString(": ")
edgeVal := "<nil>" // be robust
if edge != nil {
edgeVal = relName(edge, v)
}
b.WriteString(edgeVal)
}
b.WriteString("]")
if v.Comment != "" {
b.WriteString(" #")
b.WriteString(v.Comment)
}
return b.String()
}
func printCall(v *CallCommon, prefix string, instr Instruction) string {
var b bytes.Buffer
b.WriteString(prefix)
if !v.IsInvoke() {
b.WriteString(relName(v.Func, instr))
} else {
name := underlyingType(v.Recv.Type()).(*types.Interface).Methods[v.Method].Name
fmt.Fprintf(&b, "invoke %s.%s [#%d]", relName(v.Recv, instr), name, v.Method)
}
b.WriteString("(")
for i, arg := range v.Args {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(relName(arg, instr))
}
if v.HasEllipsis {
b.WriteString("...")
}
b.WriteString(")")
return b.String()
}
func (c *CallCommon) String() string {
return printCall(c, "", nil)
}
func (v *Call) String() string {
return printCall(&v.Call, "", v)
}
func (v *BinOp) String() string {
return fmt.Sprintf("%s %s %s", relName(v.X, v), v.Op.String(), relName(v.Y, v))
}
func (v *UnOp) String() string {
return fmt.Sprintf("%s%s%s", v.Op, relName(v.X, v), commaOk(v.CommaOk))
}
func (v *Conv) String() string {
return fmt.Sprintf("convert %s <- %s (%s)", v.Type(), v.X.Type(), relName(v.X, v))
}
func (v *ChangeInterface) String() string {
return fmt.Sprintf("change interface %s <- %s (%s)", v.Type(), v.X.Type(), relName(v.X, v))
}
func (v *MakeInterface) String() string {
return fmt.Sprintf("make interface %s <- %s (%s)", v.Type(), v.X.Type(), relName(v.X, v))
}
func (v *MakeClosure) String() string {
var b bytes.Buffer
fmt.Fprintf(&b, "make closure %s", relName(v.Fn, v))
if v.Bindings != nil {
b.WriteString(" [")
for i, c := range v.Bindings {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(relName(c, v))
}
b.WriteString("]")
}
return b.String()
}
func (v *MakeSlice) String() string {
var b bytes.Buffer
b.WriteString("make slice ")
b.WriteString(v.Type().String())
b.WriteString(" ")
b.WriteString(relName(v.Len, v))
b.WriteString(" ")
b.WriteString(relName(v.Cap, v))
return b.String()
}
func (v *Slice) String() string {
var b bytes.Buffer
b.WriteString("slice ")
b.WriteString(relName(v.X, v))
b.WriteString("[")
if v.Low != nil {
b.WriteString(relName(v.Low, v))
}
b.WriteString(":")
if v.High != nil {
b.WriteString(relName(v.High, v))
}
b.WriteString("]")
return b.String()
}
func (v *MakeMap) String() string {
res := ""
if v.Reserve != nil {
res = relName(v.Reserve, v)
}
return fmt.Sprintf("make %s %s", v.Type(), res)
}
func (v *MakeChan) String() string {
return fmt.Sprintf("make %s %s", v.Type(), relName(v.Size, v))
}
func (v *FieldAddr) String() string {
fields := underlyingType(indirectType(v.X.Type())).(*types.Struct).Fields
// Be robust against a bad index.
name := "?"
if v.Field >= 0 && v.Field < len(fields) {
name = fields[v.Field].Name
}
return fmt.Sprintf("&%s.%s [#%d]", relName(v.X, v), name, v.Field)
}
func (v *Field) String() string {
fields := underlyingType(v.X.Type()).(*types.Struct).Fields
// Be robust against a bad index.
name := "?"
if v.Field >= 0 && v.Field < len(fields) {
name = fields[v.Field].Name
}
return fmt.Sprintf("%s.%s [#%d]", relName(v.X, v), name, v.Field)
}
func (v *IndexAddr) String() string {
return fmt.Sprintf("&%s[%s]", relName(v.X, v), relName(v.Index, v))
}
func (v *Index) String() string {
return fmt.Sprintf("%s[%s]", relName(v.X, v), relName(v.Index, v))
}
func (v *Lookup) String() string {
return fmt.Sprintf("%s[%s]%s", relName(v.X, v), relName(v.Index, v), commaOk(v.CommaOk))
}
func (v *Range) String() string {
return "range " + relName(v.X, v)
}
func (v *Next) String() string {
return "next " + relName(v.Iter, v)
}
func (v *TypeAssert) String() string {
return fmt.Sprintf("typeassert%s %s.(%s)", commaOk(v.CommaOk), relName(v.X, v), v.AssertedType)
}
func (v *Extract) String() string {
return fmt.Sprintf("extract %s #%d", relName(v.Tuple, v), v.Index)
}
func (s *Jump) String() string {
// Be robust against malformed CFG.
blockname := "?"
if s.Block_ != nil && len(s.Block_.Succs) == 1 {
blockname = s.Block_.Succs[0].String()
}
return fmt.Sprintf("jump %s", blockname)
}
func (s *If) String() string {
// Be robust against malformed CFG.
tblockname, fblockname := "?", "?"
if s.Block_ != nil && len(s.Block_.Succs) == 2 {
tblockname = s.Block_.Succs[0].String()
fblockname = s.Block_.Succs[1].String()
}
return fmt.Sprintf("if %s goto %s else %s", relName(s.Cond, s), tblockname, fblockname)
}
func (s *Go) String() string {
return printCall(&s.Call, "go ", s)
}
func (s *Panic) String() string {
return "panic " + relName(s.X, s)
}
func (s *Ret) String() string {
var b bytes.Buffer
b.WriteString("ret")
for i, r := range s.Results {
if i == 0 {
b.WriteString(" ")
} else {
b.WriteString(", ")
}
b.WriteString(relName(r, s))
}
return b.String()
}
func (*RunDefers) String() string {
return "rundefers"
}
func (s *Send) String() string {
return fmt.Sprintf("send %s <- %s", relName(s.Chan, s), relName(s.X, s))
}
func (s *Defer) String() string {
return printCall(&s.Call, "defer ", s)
}
func (s *Select) String() string {
var b bytes.Buffer
for i, st := range s.States {
if i > 0 {
b.WriteString(", ")
}
if st.Dir == ast.RECV {
b.WriteString("<-")
b.WriteString(relName(st.Chan, s))
} else {
b.WriteString(relName(st.Chan, s))
b.WriteString("<-")
b.WriteString(relName(st.Send, s))
}
}
non := ""
if !s.Blocking {
non = "non"
}
return fmt.Sprintf("select %sblocking [%s]", non, b.String())
}
func (s *Store) String() string {
return fmt.Sprintf("*%s = %s", relName(s.Addr, s), relName(s.Val, s))
}
func (s *MapUpdate) String() string {
return fmt.Sprintf("%s[%s] = %s", relName(s.Map, s), relName(s.Key, s), relName(s.Value, s))
}
func (p *Package) String() string {
return "Package " + p.Types.Path
}
func (p *Package) DumpTo(w io.Writer) {
fmt.Fprintf(w, "Package %s:\n", p.Types.Path)
var names []string
maxname := 0
for name := range p.Members {
if l := len(name); l > maxname {
maxname = l
}
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
switch mem := p.Members[name].(type) {
case *Constant:
fmt.Fprintf(w, " const %-*s %s = %s\n", maxname, name, mem.Name(), mem.Value.Name())
case *Function:
fmt.Fprintf(w, " func %-*s %s\n", maxname, name, mem.Type())
case *Type:
fmt.Fprintf(w, " type %-*s %s\n", maxname, name, mem.NamedType.Underlying)
// We display only PtrMethods since its keys
// are a superset of Methods' keys, though the
// methods themselves may differ,
// e.g. different bridge methods.
// TODO(adonovan): show pointerness of receivers.
var keys ids
for id := range mem.PtrMethods {
keys = append(keys, id)
}
sort.Sort(keys)
for _, id := range keys {
method := mem.PtrMethods[id]
fmt.Fprintf(w, " method %s %s\n", id, method.Signature)
}
case *Global:
fmt.Fprintf(w, " var %-*s %s\n", maxname, name, mem.Type())
}
}
}
func commaOk(x bool) string {
if x {
return ",ok"
}
return ""
}

447
ssa/promote.go Normal file
View File

@ -0,0 +1,447 @@
package ssa
// This file defines algorithms related to "promotion" of field and
// method selector expressions e.x, such as desugaring implicit field
// and method selections, method-set computation, and construction of
// synthetic "bridge" methods.
import (
"fmt"
"code.google.com/p/go.tools/go/types"
)
// anonFieldPath is a linked list of anonymous fields entered by
// breadth-first traversal has entered, rightmost (outermost) first.
// e.g. "e.f" denoting "e.A.B.C.f" would have a path [C, B, A].
// Common tails may be shared.
//
// It is used by various "promotion"-related algorithms.
//
type anonFieldPath struct {
tail *anonFieldPath
index int // index of field within enclosing types.Struct.Fields
field *types.Field
}
func (p *anonFieldPath) contains(f *types.Field) bool {
for ; p != nil; p = p.tail {
if p.field == f {
return true
}
}
return false
}
// reverse returns the linked list reversed, as a slice.
func (p *anonFieldPath) reverse() []*anonFieldPath {
n := 0
for q := p; q != nil; q = q.tail {
n++
}
s := make([]*anonFieldPath, n)
n = 0
for ; p != nil; p = p.tail {
s[len(s)-1-n] = p
n++
}
return s
}
// isIndirect returns true if the path indirects a pointer.
func (p *anonFieldPath) isIndirect() bool {
for ; p != nil; p = p.tail {
if isPointer(p.field.Type) {
return true
}
}
return false
}
// Method Set construction ----------------------------------------
// A candidate is a method eligible for promotion: a method of an
// abstract (interface) or concrete (anonymous struct or named) type,
// along with the anonymous field path via which it is implicitly
// reached. If there is exactly one candidate for a given id, it will
// be promoted to membership of the original type's method-set.
//
// Candidates with path=nil are trivially members of the original
// type's method-set.
//
type candidate struct {
method *types.Method // method object of abstract or concrete type
concrete *Function // actual method (iff concrete)
path *anonFieldPath // desugared selector path
}
// For debugging.
func (c candidate) String() string {
s := ""
// Inefficient!
for p := c.path; p != nil; p = p.tail {
s = "." + p.field.Name + s
}
return "@" + s + "." + c.method.Name
}
// ptrRecv returns true if this candidate has a pointer receiver.
func (c candidate) ptrRecv() bool {
return c.concrete != nil && isPointer(c.concrete.Signature.Recv.Type)
}
// MethodSet returns the method set for type typ,
// building bridge methods as needed for promoted methods.
// A nil result indicates an empty set.
//
// Thread-safe.
func (p *Program) MethodSet(typ types.Type) MethodSet {
if !canHaveConcreteMethods(typ, true) {
return nil
}
p.methodSetsMu.Lock()
defer p.methodSetsMu.Unlock()
// TODO(adonovan): Using Types as map keys doesn't properly
// de-dup. e.g. *NamedType are canonical but *Struct and
// others are not. Need to de-dup based on using a two-level
// hash-table with hash function types.Type.String and
// equivalence relation types.IsIdentical.
mset := p.methodSets[typ]
if mset == nil {
mset = buildMethodSet(p, typ)
p.methodSets[typ] = mset
}
return mset
}
// buildMethodSet computes the concrete method set for type typ.
// It is the implementation of Program.MethodSet.
//
func buildMethodSet(prog *Program, typ types.Type) MethodSet {
if prog.mode&LogSource != 0 {
defer logStack("buildMethodSet %s %T", typ, typ)()
}
// cands maps ids (field and method names) encountered at any
// level of of the breadth-first traversal to a unique
// promotion candidate. A nil value indicates a "blocked" id
// (i.e. a field or ambiguous method).
//
// nextcands is the same but carries just the level in progress.
cands, nextcands := make(map[Id]*candidate), make(map[Id]*candidate)
var next, list []*anonFieldPath
list = append(list, nil) // hack: nil means "use typ"
// For each level of the type graph...
for len(list) > 0 {
// Invariant: next=[], nextcands={}.
// Collect selectors from one level into 'nextcands'.
// Record the next levels into 'next'.
for _, node := range list {
t := typ // first time only
if node != nil {
t = node.field.Type
}
t = deref(t)
if nt, ok := t.(*types.NamedType); ok {
for _, meth := range nt.Methods {
addCandidate(nextcands, IdFromQualifiedName(meth.QualifiedName), meth, prog.concreteMethods[meth], node)
}
t = nt.Underlying
}
switch t := t.(type) {
case *types.Interface:
for _, meth := range t.Methods {
addCandidate(nextcands, IdFromQualifiedName(meth.QualifiedName), meth, nil, node)
}
case *types.Struct:
for i, f := range t.Fields {
nextcands[IdFromQualifiedName(f.QualifiedName)] = nil // a field: block id
// Queue up anonymous fields for next iteration.
// Break cycles to ensure termination.
if f.IsAnonymous && !node.contains(f) {
next = append(next, &anonFieldPath{node, i, f})
}
}
}
}
// Examine collected selectors.
// Promote unique, non-blocked ones to cands.
for id, cand := range nextcands {
delete(nextcands, id)
if cand == nil {
// Update cands so we ignore it at all deeper levels.
// Don't clobber existing (shallower) binding!
if _, ok := cands[id]; !ok {
cands[id] = nil // block id
}
continue
}
if _, ok := cands[id]; ok {
// Ignore candidate: a shallower binding exists.
} else {
cands[id] = cand
}
}
list, next = next, list[:0] // reuse array
}
// Build method sets and bridge methods.
mset := make(MethodSet)
for id, cand := range cands {
if cand == nil {
continue // blocked; ignore
}
if cand.ptrRecv() && !(isPointer(typ) || cand.path.isIndirect()) {
// A candidate concrete method f with receiver
// *C is promoted into the method set of
// (non-pointer) E iff the implicit path selection
// is indirect, e.g. e.A->B.C.f
continue
}
var method *Function
if cand.path == nil {
// Trivial member of method-set; no bridge needed.
method = cand.concrete
} else {
method = makeBridgeMethod(prog, typ, cand)
}
if method == nil {
panic("unexpected nil method in method set")
}
mset[id] = method
}
return mset
}
// addCandidate adds the promotion candidate (method, node) to m[id].
// If m[id] already exists (whether nil or not), m[id] is set to nil.
// If method denotes a concrete method, concrete is its implementation.
//
func addCandidate(m map[Id]*candidate, id Id, method *types.Method, concrete *Function, node *anonFieldPath) {
prev, found := m[id]
switch {
case prev != nil:
// Two candidates for same selector: ambiguous; block it.
m[id] = nil
case found:
// Already blocked.
default:
// A viable candidate.
m[id] = &candidate{method, concrete, node}
}
}
// makeBridgeMethod creates a synthetic Function that delegates to a
// "promoted" method. For example, given these decls:
//
// type A struct {B}
// type B struct {*C}
// type C ...
// func (*C) f()
//
// then makeBridgeMethod(typ=A, cand={method:(*C).f, path:[B,*C]}) will
// synthesize this bridge method:
//
// func (a A) f() { return a.B.C->f() }
//
// prog is the program to which the synthesized method will belong.
// typ is the receiver type of the bridge method. cand is the
// candidate method to be promoted; it may be concrete or an interface
// method.
//
func makeBridgeMethod(prog *Program, typ types.Type, cand *candidate) *Function {
sig := *cand.method.Type // make a copy, sharing underlying Values
sig.Recv = &types.Var{Name: "recv", Type: typ}
if prog.mode&LogSource != 0 {
defer logStack("makeBridgeMethod %s, %s, type %s", typ, cand, &sig)()
}
fn := &Function{
Name_: cand.method.Name,
Signature: &sig,
Prog: prog,
}
fn.startBody()
fn.addSpilledParam(sig.Recv)
createParams(fn)
// Each bridge method performs a sequence of selections,
// then tailcalls the promoted method.
// We use pointer arithmetic (FieldAddr possibly followed by
// Load) in preference to value extraction (Field possibly
// preceded by Load).
var v Value = fn.Locals[0] // spilled receiver
if isPointer(typ) {
v = emitLoad(fn, v)
}
// Iterate over selections e.A.B.C.f in the natural order [A,B,C].
for _, p := range cand.path.reverse() {
// Loop invariant: v holds a pointer to a struct.
if _, ok := underlyingType(indirectType(v.Type())).(*types.Struct); !ok {
panic(fmt.Sprint("not a *struct: ", v.Type(), p.field.Type))
}
sel := &FieldAddr{
X: v,
Field: p.index,
}
sel.setType(pointer(p.field.Type))
v = fn.emit(sel)
if isPointer(p.field.Type) {
v = emitLoad(fn, v)
}
}
if !cand.ptrRecv() {
v = emitLoad(fn, v)
}
var c Call
if cand.concrete != nil {
c.Call.Func = cand.concrete
fn.Pos = c.Call.Func.(*Function).Pos // TODO(adonovan): fix: wrong.
c.Call.Pos = fn.Pos // TODO(adonovan): fix: wrong.
c.Call.Args = append(c.Call.Args, v)
} else {
c.Call.Recv = v
c.Call.Method = 0
}
emitTailCall(fn, &c)
fn.finishBody()
return fn
}
// createParams creates parameters for bridge method fn based on its Signature.
func createParams(fn *Function) {
var last *Parameter
for i, p := range fn.Signature.Params {
name := p.Name
if name == "" {
name = fmt.Sprintf("arg%d", i)
}
last = fn.addParam(name, p.Type)
}
if fn.Signature.IsVariadic {
last.Type_ = &types.Slice{Elt: last.Type_}
}
}
// Thunks for standalone interface methods ----------------------------------------
// makeImethodThunk returns a synthetic thunk function permitting an
// method id of interface typ to be called like a standalone function,
// e.g.:
//
// type I interface { f(x int) R }
// m := I.f // thunk
// var i I
// m(i, 0)
//
// The thunk is defined as if by:
//
// func I.f(i I, x int, ...) R {
// return i.f(x, ...)
// }
//
// The generated thunks do not belong to any package. (Arguably they
// belong in the package that defines the interface, but we have no
// way to determine that on demand; we'd have to create all possible
// thunks a priori.)
//
// TODO(adonovan): opt: currently the stub is created even when used
// in call position: I.f(i, 0). Clearly this is suboptimal.
//
// TODO(adonovan): memoize creation of these functions in the Program.
//
func makeImethodThunk(prog *Program, typ types.Type, id Id) *Function {
if prog.mode&LogSource != 0 {
defer logStack("makeImethodThunk %s.%s", typ, id)()
}
itf := underlyingType(typ).(*types.Interface)
index, meth := methodIndex(itf, itf.Methods, id)
sig := *meth.Type // copy; shared Values
fn := &Function{
Name_: meth.Name,
Signature: &sig,
Prog: prog,
}
// TODO(adonovan): set fn.Pos to location of interface method ast.Field.
fn.startBody()
fn.addParam("recv", typ)
createParams(fn)
var c Call
c.Call.Method = index
c.Call.Recv = fn.Params[0]
emitTailCall(fn, &c)
fn.finishBody()
return fn
}
// Implicit field promotion ----------------------------------------
// For a given struct type and (promoted) field Id, findEmbeddedField
// returns the path of implicit anonymous field selections, and the
// field index of the explicit (=outermost) selection.
//
// TODO(gri): if go/types/operand.go's lookupFieldBreadthFirst were to
// record (e.g. call a client-provided callback) the implicit field
// selection path discovered for a particular ast.SelectorExpr, we could
// eliminate this function.
//
func findPromotedField(st *types.Struct, id Id) (*anonFieldPath, int) {
// visited records the types that have been searched already.
// Invariant: keys are all *types.NamedType.
// (types.Type is not a sound map key in general.)
visited := make(map[types.Type]bool)
var list, next []*anonFieldPath
for i, f := range st.Fields {
if f.IsAnonymous {
list = append(list, &anonFieldPath{nil, i, f})
}
}
// Search the current level if there is any work to do and collect
// embedded types of the next lower level in the next list.
for {
// look for name in all types at this level
for _, node := range list {
typ := deref(node.field.Type).(*types.NamedType)
if visited[typ] {
continue
}
visited[typ] = true
switch typ := typ.Underlying.(type) {
case *types.Struct:
for i, f := range typ.Fields {
if IdFromQualifiedName(f.QualifiedName) == id {
return node, i
}
}
for i, f := range typ.Fields {
if f.IsAnonymous {
next = append(next, &anonFieldPath{node, i, f})
}
}
}
}
if len(next) == 0 {
panic("field not found: " + id.String())
}
// No match so far.
list, next = next, list[:0] // reuse arrays
}
panic("unreachable")
}

307
ssa/sanity.go Normal file
View File

@ -0,0 +1,307 @@
package ssa
// An optional pass for sanity-checking invariants of the SSA representation.
// Currently it checks CFG invariants but little at the instruction level.
import (
"fmt"
"io"
"os"
)
type sanity struct {
reporter io.Writer
fn *Function
block *BasicBlock
insane bool
}
// SanityCheck performs integrity checking of the SSA representation
// of the function fn and returns true if it was valid. Diagnostics
// are written to reporter if non-nil, os.Stderr otherwise. Some
// diagnostics are only warnings and do not imply a negative result.
//
// Sanity checking is intended to facilitate the debugging of code
// transformation passes.
//
func SanityCheck(fn *Function, reporter io.Writer) bool {
if reporter == nil {
reporter = os.Stderr
}
return (&sanity{reporter: reporter}).checkFunction(fn)
}
// MustSanityCheck is like SanityCheck but panics instead of returning
// a negative result.
//
func MustSanityCheck(fn *Function, reporter io.Writer) {
if !SanityCheck(fn, reporter) {
panic("SanityCheck failed")
}
}
func (s *sanity) diagnostic(prefix, format string, args ...interface{}) {
fmt.Fprintf(s.reporter, "%s: function %s", prefix, s.fn.FullName())
if s.block != nil {
fmt.Fprintf(s.reporter, ", block %s", s.block)
}
io.WriteString(s.reporter, ": ")
fmt.Fprintf(s.reporter, format, args...)
io.WriteString(s.reporter, "\n")
}
func (s *sanity) errorf(format string, args ...interface{}) {
s.insane = true
s.diagnostic("Error", format, args...)
}
func (s *sanity) warnf(format string, args ...interface{}) {
s.diagnostic("Warning", format, args...)
}
// findDuplicate returns an arbitrary basic block that appeared more
// than once in blocks, or nil if all were unique.
func findDuplicate(blocks []*BasicBlock) *BasicBlock {
if len(blocks) < 2 {
return nil
}
if blocks[0] == blocks[1] {
return blocks[0]
}
// Slow path:
m := make(map[*BasicBlock]bool)
for _, b := range blocks {
if m[b] {
return b
}
m[b] = true
}
return nil
}
func (s *sanity) checkInstr(idx int, instr Instruction) {
switch instr := instr.(type) {
case *If, *Jump, *Ret, *Panic:
s.errorf("control flow instruction not at end of block")
case *Phi:
if idx == 0 {
// It suffices to apply this check to just the first phi node.
if dup := findDuplicate(s.block.Preds); dup != nil {
s.errorf("phi node in block with duplicate predecessor %s", dup)
}
} else {
prev := s.block.Instrs[idx-1]
if _, ok := prev.(*Phi); !ok {
s.errorf("Phi instruction follows a non-Phi: %T", prev)
}
}
if ne, np := len(instr.Edges), len(s.block.Preds); ne != np {
s.errorf("phi node has %d edges but %d predecessors", ne, np)
} else {
for i, e := range instr.Edges {
if e == nil {
s.errorf("phi node '%s' has no value for edge #%d from %s", instr.Comment, i, s.block.Preds[i])
}
}
}
case *Alloc:
if !instr.Heap {
found := false
for _, l := range s.fn.Locals {
if l == instr {
found = true
break
}
}
if !found {
s.errorf("local alloc %s = %s does not appear in Function.Locals", instr.Name(), instr)
}
}
case *BinOp:
case *Call:
case *ChangeInterface:
case *Conv:
case *Defer:
case *Extract:
case *Field:
case *FieldAddr:
case *Go:
case *Index:
case *IndexAddr:
case *Lookup:
case *MakeChan:
case *MakeClosure:
// TODO(adonovan): check FreeVars count matches.
case *MakeInterface:
case *MakeMap:
case *MakeSlice:
case *MapUpdate:
case *Next:
case *Range:
case *RunDefers:
case *Select:
case *Send:
case *Slice:
case *Store:
case *TypeAssert:
case *UnOp:
// TODO(adonovan): implement checks.
default:
panic(fmt.Sprintf("Unknown instruction type: %T", instr))
}
}
func (s *sanity) checkFinalInstr(idx int, instr Instruction) {
switch instr.(type) {
case *If:
if nsuccs := len(s.block.Succs); nsuccs != 2 {
s.errorf("If-terminated block has %d successors; expected 2", nsuccs)
return
}
if s.block.Succs[0] == s.block.Succs[1] {
s.errorf("If-instruction has same True, False target blocks: %s", s.block.Succs[0])
return
}
case *Jump:
if nsuccs := len(s.block.Succs); nsuccs != 1 {
s.errorf("Jump-terminated block has %d successors; expected 1", nsuccs)
return
}
case *Ret:
if nsuccs := len(s.block.Succs); nsuccs != 0 {
s.errorf("Ret-terminated block has %d successors; expected none", nsuccs)
return
}
// TODO(adonovan): check number and types of results
case *Panic:
if nsuccs := len(s.block.Succs); nsuccs != 0 {
s.errorf("Panic-terminated block has %d successors; expected none", nsuccs)
return
}
default:
s.errorf("non-control flow instruction at end of block")
}
}
func (s *sanity) checkBlock(b *BasicBlock, index int) {
s.block = b
if b.Index != index {
s.errorf("block has incorrect Index %d", b.Index)
}
if b.Func != s.fn {
s.errorf("block has incorrect Func %s", b.Func.FullName())
}
// Check all blocks are reachable.
// (The entry block is always implicitly reachable.)
if index > 0 && len(b.Preds) == 0 {
s.warnf("unreachable block")
if b.Instrs == nil {
// Since this block is about to be pruned,
// tolerating transient problems in it
// simplifies other optimizations.
return
}
}
// Check predecessor and successor relations are dual,
// and that all blocks in CFG belong to same function.
for _, a := range b.Preds {
found := false
for _, bb := range a.Succs {
if bb == b {
found = true
break
}
}
if !found {
s.errorf("expected successor edge in predecessor %s; found only: %s", a, a.Succs)
}
if a.Func != s.fn {
s.errorf("predecessor %s belongs to different function %s", a, a.Func.FullName())
}
}
for _, c := range b.Succs {
found := false
for _, bb := range c.Preds {
if bb == b {
found = true
break
}
}
if !found {
s.errorf("expected predecessor edge in successor %s; found only: %s", c, c.Preds)
}
if c.Func != s.fn {
s.errorf("successor %s belongs to different function %s", c, c.Func.FullName())
}
}
// Check each instruction is sane.
// TODO(adonovan): check Instruction invariants:
// - check Operands is dual to Value.Referrers.
// - check all Operands that are also Instructions belong to s.fn too
// (and for bonus marks, that their block dominates block b).
n := len(b.Instrs)
if n == 0 {
s.errorf("basic block contains no instructions")
}
for j, instr := range b.Instrs {
if instr == nil {
s.errorf("nil instruction at index %d", j)
continue
}
if b2 := instr.Block(); b2 == nil {
s.errorf("nil Block() for instruction at index %d", j)
continue
} else if b2 != b {
s.errorf("wrong Block() (%s) for instruction at index %d ", b2, j)
continue
}
if j < n-1 {
s.checkInstr(j, instr)
} else {
s.checkFinalInstr(j, instr)
}
}
}
func (s *sanity) checkFunction(fn *Function) bool {
// TODO(adonovan): check Function invariants:
// - check owning Package (if any) contains this (possibly anon) function
// - check params match signature
// - check transient fields are nil
// - warn if any fn.Locals do not appear among block instructions.
s.fn = fn
if fn.Prog == nil {
s.errorf("nil Prog")
}
for i, l := range fn.Locals {
if l.Heap {
s.errorf("Local %s at index %d has Heap flag set", l.Name(), i)
}
}
if fn.Blocks != nil && len(fn.Blocks) == 0 {
// Function _had_ blocks (so it's not external) but
// they were "optimized" away, even the entry block.
s.errorf("Blocks slice is non-nil but empty")
}
for i, b := range fn.Blocks {
if b == nil {
s.warnf("nil *BasicBlock at f.Blocks[%d]", i)
continue
}
s.checkBlock(b, i)
}
s.block = nil
s.fn = nil
return !s.insane
}

1400
ssa/ssa.go Normal file

File diff suppressed because it is too large Load Diff

117
ssa/ssadump.go Normal file
View File

@ -0,0 +1,117 @@
// +build ignore
package main
// ssadump: a tool for displaying and interpreting the SSA form of Go programs.
import (
"flag"
"fmt"
"log"
"os"
"runtime/pprof"
"code.google.com/p/go.tools/ssa"
"code.google.com/p/go.tools/ssa/interp"
)
var buildFlag = flag.String("build", "", `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.
P log [P]ackage inventory.
F log [F]unction SSA code.
S log [S]ource locations as SSA builder progresses.
G use binary object files from gc to provide imports (no code).
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.
`)
var runFlag = flag.Bool("run", false, "Invokes the SSA interpreter on the program.")
var interpFlag = flag.String("interp", "", `Options controlling the SSA test interpreter.
The value is a sequence of zero or more more of these letters:
R disable [R]ecover() from panic; show interpreter crash instead.
T [T]race execution of the program. Best for single-threaded programs!
`)
const usage = `SSA builder and interpreter.
Usage: ssadump [<flag> ...] [<file.go> ...] [<arg> ...]
ssadump [<flag> ...] <import/path> [<arg> ...]
Use -help flag to display options.
Examples:
% ssadump -run -interp=T hello.go # interpret a program, with tracing
% ssadump -build=FPG hello.go # quickly dump SSA form of a single package
`
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
func main() {
flag.Parse()
args := flag.Args()
var mode ssa.BuilderMode
for _, c := range *buildFlag {
switch c {
case 'P':
mode |= ssa.LogPackages | ssa.BuildSerially
case 'F':
mode |= ssa.LogFunctions | ssa.BuildSerially
case 'S':
mode |= ssa.LogSource | ssa.BuildSerially
case 'C':
mode |= ssa.SanityCheckFunctions
case 'N':
mode |= ssa.NaiveForm
case 'G':
mode |= ssa.UseGCImporter
case 'L':
mode |= ssa.BuildSerially
default:
log.Fatalf("Unknown -build option: '%c'.", c)
}
}
var interpMode interp.Mode
for _, c := range *interpFlag {
switch c {
case 'T':
interpMode |= interp.EnableTracing
case 'R':
interpMode |= interp.DisableRecover
default:
log.Fatalf("Unknown -interp option: '%c'.", c)
}
}
if len(args) == 0 {
fmt.Fprint(os.Stderr, usage)
os.Exit(1)
}
// Profiling support.
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
context := &ssa.Context{
Mode: mode,
Loader: ssa.GorootLoader,
}
b := ssa.NewBuilder(context)
mainpkg, args, err := ssa.CreatePackageFromArgs(b, args)
if err != nil {
log.Fatal(err.Error())
}
b.BuildAllPackages()
b = nil // discard Builder
if *runFlag {
interp.Interpret(mainpkg, interpMode, mainpkg.Name(), args)
}
}

201
ssa/typeinfo.go Normal file
View File

@ -0,0 +1,201 @@
package ssa
// This file defines utilities for querying the results of typechecker:
// types of expressions, values of constant expressions, referents of identifiers.
import (
"code.google.com/p/go.tools/go/types"
"fmt"
"go/ast"
)
// TypeInfo contains information provided by the type checker about
// the abstract syntax for a single package.
type TypeInfo struct {
types map[ast.Expr]types.Type // inferred types of expressions
constants map[ast.Expr]*Literal // values of constant expressions
idents map[*ast.Ident]types.Object // canonical type objects for named entities
}
// TypeOf returns the type of expression e.
// Precondition: e belongs to the package's ASTs.
func (info *TypeInfo) TypeOf(e ast.Expr) types.Type {
// For Ident, b.types may be more specific than
// b.obj(id.(*ast.Ident)).GetType(),
// e.g. in the case of typeswitch.
if t, ok := info.types[e]; ok {
return t
}
// The typechecker doesn't notify us of all Idents,
// e.g. s.Key and s.Value in a RangeStmt.
// So we have this fallback.
// TODO(gri): This is a typechecker bug. When fixed,
// eliminate this case and panic.
if id, ok := e.(*ast.Ident); ok {
return info.ObjectOf(id).GetType()
}
panic("no type for expression")
}
// ValueOf returns the value of expression e if it is a constant,
// nil otherwise.
//
func (info *TypeInfo) ValueOf(e ast.Expr) *Literal {
return info.constants[e]
}
// ObjectOf returns the typechecker object denoted by the specified id.
// Precondition: id belongs to the package's ASTs.
//
func (info *TypeInfo) ObjectOf(id *ast.Ident) types.Object {
if obj, ok := info.idents[id]; ok {
return obj
}
panic(fmt.Sprintf("no types.Object for ast.Ident %s @ %p", id.Name, id))
}
// IsType returns true iff expression e denotes a type.
// Precondition: e belongs to the package's ASTs.
//
func (info *TypeInfo) IsType(e ast.Expr) bool {
switch e := e.(type) {
case *ast.SelectorExpr: // pkg.Type
if obj := info.isPackageRef(e); obj != nil {
return objKind(obj) == ast.Typ
}
case *ast.StarExpr: // *T
return info.IsType(e.X)
case *ast.Ident:
return objKind(info.ObjectOf(e)) == ast.Typ
case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
return true
case *ast.ParenExpr:
return info.IsType(e.X)
}
return false
}
// isPackageRef returns the identity of the object if sel is a
// package-qualified reference to a named const, var, func or type.
// Otherwise it returns nil.
// Precondition: sel belongs to the package's ASTs.
//
func (info *TypeInfo) isPackageRef(sel *ast.SelectorExpr) types.Object {
if id, ok := sel.X.(*ast.Ident); ok {
if obj := info.ObjectOf(id); objKind(obj) == ast.Pkg {
return obj.(*types.Package).Scope.Lookup(sel.Sel.Name)
}
}
return nil
}
// builtinCallSignature returns a new Signature describing the
// effective type of a builtin operator for the particular call e.
//
// This requires ad-hoc typing rules for all variadic (append, print,
// println) and polymorphic (append, copy, delete, close) built-ins.
// This logic could be part of the typechecker, and should arguably
// be moved there and made accessible via an additional types.Context
// callback.
//
// The returned Signature is degenerate and only intended for use by
// emitCallArgs.
//
func builtinCallSignature(info *TypeInfo, e *ast.CallExpr) *types.Signature {
var params []*types.Var
var isVariadic bool
switch builtin := noparens(e.Fun).(*ast.Ident).Name; builtin {
case "append":
var t0, t1 types.Type
t0 = info.TypeOf(e) // infer arg[0] type from result type
if e.Ellipsis != 0 {
// append([]T, []T) []T
// append([]byte, string) []byte
t1 = info.TypeOf(e.Args[1]) // no conversion
} else {
// append([]T, ...T) []T
t1 = underlyingType(t0).(*types.Slice).Elt
isVariadic = true
}
params = append(params,
&types.Var{Type: t0},
&types.Var{Type: t1})
case "print", "println": // print{,ln}(any, ...interface{})
isVariadic = true
// Note, arg0 may have any type, not necessarily tEface.
params = append(params,
&types.Var{Type: info.TypeOf(e.Args[0])},
&types.Var{Type: tEface})
case "close":
params = append(params, &types.Var{Type: info.TypeOf(e.Args[0])})
case "copy":
// copy([]T, []T) int
// Infer arg types from each other. Sleazy.
var st *types.Slice
if t, ok := underlyingType(info.TypeOf(e.Args[0])).(*types.Slice); ok {
st = t
} else if t, ok := underlyingType(info.TypeOf(e.Args[1])).(*types.Slice); ok {
st = t
} else {
panic("cannot infer types in call to copy()")
}
stvar := &types.Var{Type: st}
params = append(params, stvar, stvar)
case "delete":
// delete(map[K]V, K)
tmap := info.TypeOf(e.Args[0])
tkey := underlyingType(tmap).(*types.Map).Key
params = append(params,
&types.Var{Type: tmap},
&types.Var{Type: tkey})
case "len", "cap":
params = append(params, &types.Var{Type: info.TypeOf(e.Args[0])})
case "real", "imag":
// Reverse conversion to "complex" case below.
var argType types.Type
switch info.TypeOf(e).(*types.Basic).Kind {
case types.UntypedFloat:
argType = types.Typ[types.UntypedComplex]
case types.Float64:
argType = tComplex128
case types.Float32:
argType = tComplex64
default:
unreachable()
}
params = append(params, &types.Var{Type: argType})
case "complex":
var argType types.Type
switch info.TypeOf(e).(*types.Basic).Kind {
case types.UntypedComplex:
argType = types.Typ[types.UntypedFloat]
case types.Complex128:
argType = tFloat64
case types.Complex64:
argType = tFloat32
default:
unreachable()
}
v := &types.Var{Type: argType}
params = append(params, v, v)
case "panic":
params = append(params, &types.Var{Type: tEface})
case "recover":
// no params
default:
panic("unknown builtin: " + builtin)
}
return &types.Signature{Params: params, IsVariadic: isVariadic}
}

251
ssa/util.go Normal file
View File

@ -0,0 +1,251 @@
package ssa
// This file defines a number of miscellaneous utility functions.
import (
"fmt"
"go/ast"
"io"
"os"
"reflect"
"code.google.com/p/go.tools/go/types"
)
func unreachable() {
panic("unreachable")
}
//// AST utilities
// noparens returns e with any enclosing parentheses stripped.
func noparens(e ast.Expr) ast.Expr {
for {
p, ok := e.(*ast.ParenExpr)
if !ok {
break
}
e = p.X
}
return e
}
// isBlankIdent returns true iff e is an Ident with name "_".
// They have no associated types.Object, and thus no type.
//
// TODO(gri): consider making typechecker not treat them differently.
// It's one less thing for clients like us to worry about.
//
func isBlankIdent(e ast.Expr) bool {
id, ok := e.(*ast.Ident)
return ok && id.Name == "_"
}
//// Type utilities. Some of these belong in go/types.
// underlyingType returns the underlying type of typ.
// TODO(gri): this is a copy of go/types.underlying; export that function.
//
func underlyingType(typ types.Type) types.Type {
if typ, ok := typ.(*types.NamedType); ok {
return typ.Underlying // underlying types are never NamedTypes
}
if typ == nil {
panic("underlyingType(nil)")
}
return typ
}
// isPointer returns true for types whose underlying type is a pointer.
func isPointer(typ types.Type) bool {
if nt, ok := typ.(*types.NamedType); ok {
typ = nt.Underlying
}
_, ok := typ.(*types.Pointer)
return ok
}
// pointer(typ) returns the type that is a pointer to typ.
func pointer(typ types.Type) *types.Pointer {
return &types.Pointer{Base: typ}
}
// indirect(typ) assumes that typ is a pointer type,
// or named alias thereof, and returns its base type.
// Panic ensures if it is not a pointer.
//
func indirectType(ptr types.Type) types.Type {
if v, ok := underlyingType(ptr).(*types.Pointer); ok {
return v.Base
}
// When debugging it is convenient to comment out this line
// and let it continue to print the (illegal) SSA form.
panic("indirect() of non-pointer type: " + ptr.String())
return nil
}
// deref returns a pointer's base type; otherwise it returns typ.
func deref(typ types.Type) types.Type {
if typ, ok := underlyingType(typ).(*types.Pointer); ok {
return typ.Base
}
return typ
}
// methodIndex returns the method (and its index) named id within the
// method table methods of named or interface type typ. If not found,
// panic ensues.
//
func methodIndex(typ types.Type, methods []*types.Method, id Id) (i int, m *types.Method) {
for i, m = range methods {
if IdFromQualifiedName(m.QualifiedName) == id {
return
}
}
panic(fmt.Sprint("method not found: ", id, " in interface ", typ))
}
// isSuperinterface returns true if x is a superinterface of y,
// i.e. x's methods are a subset of y's.
//
func isSuperinterface(x, y *types.Interface) bool {
if len(y.Methods) < len(x.Methods) {
return false
}
// TODO(adonovan): opt: this is quadratic.
outer:
for _, xm := range x.Methods {
for _, ym := range y.Methods {
if IdFromQualifiedName(xm.QualifiedName) == IdFromQualifiedName(ym.QualifiedName) {
if !types.IsIdentical(xm.Type, ym.Type) {
return false // common name but conflicting types
}
continue outer
}
}
return false // y doesn't have this method
}
return true
}
// objKind returns the syntactic category of the named entity denoted by obj.
func objKind(obj types.Object) ast.ObjKind {
switch obj.(type) {
case *types.Package:
return ast.Pkg
case *types.TypeName:
return ast.Typ
case *types.Const:
return ast.Con
case *types.Var:
return ast.Var
case *types.Func:
return ast.Fun
}
panic(fmt.Sprintf("unexpected Object type: %T", obj))
}
// canHaveConcreteMethods returns true iff typ may have concrete
// methods associated with it. Callers must supply allowPtr=true.
//
// TODO(gri): consider putting this in go/types. It's surprisingly subtle.
func canHaveConcreteMethods(typ types.Type, allowPtr bool) bool {
switch typ := typ.(type) {
case *types.Pointer:
return allowPtr && canHaveConcreteMethods(typ.Base, false)
case *types.NamedType:
switch typ.Underlying.(type) {
case *types.Pointer, *types.Interface:
return false
}
return true
case *types.Struct:
return true
}
return false
}
// DefaultType returns the default "typed" type for an "untyped" type;
// it returns the incoming type for all other types. If there is no
// corresponding untyped type, the result is types.Typ[types.Invalid].
//
// Exported to exp/ssa/interp.
//
// TODO(gri): this is a copy of go/types.defaultType; export that function.
//
func DefaultType(typ types.Type) types.Type {
if t, ok := typ.(*types.Basic); ok {
k := types.Invalid
switch t.Kind {
// case UntypedNil:
// There is no default type for nil. For a good error message,
// catch this case before calling this function.
case types.UntypedBool:
k = types.Bool
case types.UntypedInt:
k = types.Int
case types.UntypedRune:
k = types.Rune
case types.UntypedFloat:
k = types.Float64
case types.UntypedComplex:
k = types.Complex128
case types.UntypedString:
k = types.String
}
typ = types.Typ[k]
}
return typ
}
// makeId returns the Id (name, pkg) if the name is exported or
// (name, nil) otherwise.
//
func makeId(name string, pkg *types.Package) (id Id) {
id.Name = name
if !ast.IsExported(name) {
id.Pkg = pkg
// TODO(gri): fix
// if pkg.Path == "" {
// panic("Package " + pkg.Name + "has empty Path")
// }
}
return
}
// IdFromQualifiedName returns the Id (qn.Name, qn.Pkg) if qn is an
// exported name or (qn.Name, nil) otherwise.
//
// Exported to exp/ssa/interp.
//
func IdFromQualifiedName(qn types.QualifiedName) Id {
return makeId(qn.Name, qn.Pkg)
}
type ids []Id // a sortable slice of Id
func (p ids) Len() int { return len(p) }
func (p ids) Less(i, j int) bool {
x, y := p[i], p[j]
// *Package pointers are canonical so order by them.
// Don't use x.Pkg.ImportPath because sometimes it's empty.
// (TODO(gri): fix that.)
return reflect.ValueOf(x.Pkg).Pointer() < reflect.ValueOf(y.Pkg).Pointer() ||
x.Pkg == y.Pkg && x.Name < y.Name
}
func (p ids) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// logStack prints the formatted "start" message to stderr and
// returns a closure that prints the corresponding "end" message.
// Call using 'defer logStack(...)()' to show builder stack on panic.
// Don't forget trailing parens!
//
func logStack(format string, args ...interface{}) func() {
msg := fmt.Sprintf(format, args...)
io.WriteString(os.Stderr, msg)
io.WriteString(os.Stderr, "\n")
return func() {
io.WriteString(os.Stderr, msg)
io.WriteString(os.Stderr, " end\n")
}
}