1
0
mirror of https://github.com/golang/go synced 2024-11-18 15:54:42 -07:00

exp/ssa: (#3 of 5): Function, BasicBlock and optimisations

R=gri, iant, iant
CC=golang-dev
https://golang.org/cl/7202051
This commit is contained in:
Alan Donovan 2013-01-28 18:14:09 -05:00
parent 66bf59712e
commit 8f90915692
2 changed files with 594 additions and 0 deletions

190
src/pkg/exp/ssa/blockopt.go Normal file
View File

@ -0,0 +1,190 @@
package ssa
// Simple block optimisations to simplify the control flow graph.
// TODO(adonovan): instead of creating several "unreachable" blocks
// per function in the Builder, reuse a single one (e.g. at Blocks[1])
// to reduce garbage.
//
// TODO(adonovan): in the absence of multiway branch instructions,
// each BasicBlock has 0, 1, or 2 successors. We should preallocate
// the backing array for the Succs slice inline in BasicBlock.
import (
"fmt"
"os"
)
// If true, perform sanity checking and show progress at each
// successive iteration of optimizeBlocks. Very verbose.
const debugBlockOpt = false
func hasPhi(b *BasicBlock) bool {
_, ok := b.Instrs[0].(*Phi)
return ok
}
// prune attempts to prune block b if it is unreachable (i.e. has no
// predecessors other than itself), disconnecting it from the CFG.
// The result is true if the optimisation was applied. i is the block
// index within the function.
//
func prune(f *Function, i int, b *BasicBlock) bool {
if i == 0 {
return false // don't prune entry block
}
if len(b.Preds) == 0 || len(b.Preds) == 1 && b.Preds[0] == b {
// Disconnect it from its successors.
for _, c := range b.Succs {
c.removePred(b)
}
if debugBlockOpt {
fmt.Fprintln(os.Stderr, "prune", b.Name)
}
// Delete b.
f.Blocks[i] = nil
return true
}
return false
}
// 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 optimisation was applied.
// i is the block index within the function.
//
func jumpThreading(f *Function, i int, b *BasicBlock) bool {
if i == 0 {
return false // don't apply to entry block
}
if b.Instrs == nil {
fmt.Println("empty block ", b.Name)
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 hasPhi(c) {
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.Name, b.Name, c.Name)
}
}
f.Blocks[i] = nil
return true
}
// fuseBlocks attempts to apply the block fusion optimisation to block
// a, in which a->b becomes ab if len(a.Succs)==len(b.Preds)==1.
// The result is true if the optimisation 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 = 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.Name, b.Name)
}
// Make b unreachable. Subsequent pruning will reclaim it.
b.Preds = nil
return true
}
// optimizeBlocks() performs some simple block optimizations on a
// completed function: dead block elimination, block fusion, jump
// threading.
//
func optimizeBlocks(f *Function) {
// Loop until no further progress.
changed := true
for changed {
changed = false
if debugBlockOpt {
f.DumpTo(os.Stderr)
MustSanityCheck(f, nil)
}
for i, b := range f.Blocks {
// f.Blocks will temporarily contain nils to indicate
// deleted blocks; we remove them at the end.
if b == nil {
continue
}
// Prune unreachable blocks (including all empty blocks).
if prune(f, i, b) {
changed = true
continue // (b was pruned)
}
// 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, i, b) {
changed = true
continue // (b was disconnected)
}
}
}
// Eliminate nils from Blocks.
j := 0
for _, b := range f.Blocks {
if b != nil {
f.Blocks[j] = b
j++
}
}
// Nil out b.Blocks[j:] to aid GC.
for i := j; i < len(f.Blocks); i++ {
f.Blocks[i] = nil
}
f.Blocks = f.Blocks[:j]
}

404
src/pkg/exp/ssa/func.go Normal file
View File

@ -0,0 +1,404 @@
package ssa
// This file implements the Function and BasicBlock types.
import (
"fmt"
"go/ast"
"go/types"
"io"
"os"
)
// 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)
}
// 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
}
// 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." + label.Name)}
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_: pointer(typ), // address of param
}
f.Params = append(f.Params, v)
return v
}
func (f *Function) addObjParam(obj types.Object) *Parameter {
p := f.addParam(obj.GetName(), obj.GetType())
f.objects[obj] = p
return p
}
// start initializes the function prior to generating SSA code for its body.
// Precondition: f.Type() already set.
//
// If f.syntax != nil, f is a Go source function and idents must be a
// mapping from syntactic identifiers to their canonical type objects;
// Otherwise, idents is ignored and the usual set-up for Go source
// functions is skipped.
//
func (f *Function) start(mode BuilderMode, idents map[*ast.Ident]types.Object) {
if mode&LogSource != 0 {
fmt.Fprintf(os.Stderr, "build function %s @ %s\n", f.FullName(), f.Prog.Files.Position(f.Pos))
}
f.currentBlock = f.newBasicBlock("entry")
f.objects = make(map[types.Object]Value) // needed for some synthetics, e.g. init
if f.syntax == nil {
return // synthetic function; no syntax tree
}
f.lblocks = make(map[*ast.Object]*lblock)
// Receiver (at most one inner iteration).
if f.syntax.recvField != nil {
for _, field := range f.syntax.recvField.List {
for _, n := range field.Names {
f.addObjParam(idents[n])
}
if field.Names == nil {
f.addParam(f.Signature.Recv.Name, f.Signature.Recv.Type)
}
}
}
// Parameters.
if f.syntax.paramFields != nil {
for _, field := range f.syntax.paramFields.List {
for _, n := range field.Names {
f.addObjParam(idents[n])
}
}
}
// 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.results = append(f.results, f.addNamedLocal(idents[n]))
}
}
}
}
// finish() finalizes the function after SSA code generation of its body.
func (f *Function) finish(mode BuilderMode) {
f.objects = nil
f.results = 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]
// Ensure all value-defining Instructions have register names.
// (Non-Instruction Values are named at construction.)
tmp := 0
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
switch instr := instr.(type) {
case *Alloc:
// Local Allocs may already be named.
if instr.Name_ == "" {
instr.Name_ = fmt.Sprintf("t%d", tmp)
tmp++
}
case Value:
instr.(interface {
setNum(int)
}).setNum(tmp)
tmp++
}
}
}
optimizeBlocks(f)
if mode&LogFunctions != 0 {
f.DumpTo(os.Stderr)
}
if mode&SanityCheckFunctions != 0 {
MustSanityCheck(f, nil)
}
if mode&LogSource != 0 {
fmt.Fprintf(os.Stderr, "build function %s done\n", f.FullName())
}
}
// 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())
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.
//
func (f *Function) addLocal(typ types.Type) *Alloc {
v := &Alloc{Type_: pointer(typ)}
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 {
switch v := v.(type) {
case *Capture:
// TODO(adonovan): fix: we must support this case.
// Requires copying to a 'new' Alloc.
fmt.Fprintln(os.Stderr, "Error: escaping reference to Capture")
case *Parameter:
v.Heap = true
case *Alloc:
v.Heap = true
default:
panic(fmt.Sprintf("Unexpected Function.objects kind: %T", v))
}
}
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{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)
}
// 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))
fmt.Fprintf(w, "# Type: %s\n", f.Type())
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())
}
}
params := f.Params
if f.Signature.Recv != nil {
fmt.Fprintf(w, "func (%s) %s(", params[0].Name(), f.Name())
params = params[1:]
} else {
fmt.Fprintf(w, "func %s(", f.Name())
}
for i, v := range params {
if i > 0 {
io.WriteString(w, ", ")
}
io.WriteString(w, v.Name())
}
io.WriteString(w, "):\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.Name, len(b.Preds), len(b.Succs))
if false { // CFG debugging
fmt.Fprintf(w, "\t# CFG: %s --> %s --> %s\n", blockNames(b.Preds), b.Name, blockNames(b.Succs))
}
for _, instr := range b.Instrs {
io.WriteString(w, "\t")
if v, ok := instr.(Value); ok {
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-9, t)
}
} else {
io.WriteString(w, instr.String())
}
io.WriteString(w, "\n")
}
}
fmt.Fprintf(w, "\n")
}
// newBasicBlock adds to f a new basic block with a unique name and
// returns it. It does not automatically become the current block for
// subsequent calls to emit.
//
func (f *Function) newBasicBlock(name string) *BasicBlock {
b := &BasicBlock{
Name: fmt.Sprintf("%d.%s", len(f.Blocks), name),
Func: f,
}
f.Blocks = append(f.Blocks, b)
return b
}