mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
go.tools: add missing files ssa/*.go
R=golang-dev, adonovan CC=golang-dev https://golang.org/cl/9500043
This commit is contained in:
parent
01f8cd246d
commit
83f21b9226
173
ssa/blockopt.go
Normal file
173
ssa/blockopt.go
Normal 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
2703
ssa/builder.go
Normal file
File diff suppressed because it is too large
Load Diff
115
ssa/doc.go
Normal file
115
ssa/doc.go
Normal 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
296
ssa/dom.go
Normal 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
301
ssa/emit.go
Normal 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
608
ssa/func.go
Normal 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
155
ssa/importer.go
Normal 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
513
ssa/lift.go
Normal 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
139
ssa/literal.go
Normal 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
86
ssa/lvalue.go
Normal 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
408
ssa/print.go
Normal 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
447
ssa/promote.go
Normal 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
307
ssa/sanity.go
Normal 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
1400
ssa/ssa.go
Normal file
File diff suppressed because it is too large
Load Diff
117
ssa/ssadump.go
Normal file
117
ssa/ssadump.go
Normal 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
201
ssa/typeinfo.go
Normal 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
251
ssa/util.go
Normal 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")
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user