1
0
mirror of https://github.com/golang/go synced 2024-10-06 09:21:21 -06:00

[dev.ssa] cmd/internal/ssa: SSA backend compiler skeleton

First pass adding code for SSA backend.  It is standalone for now.
I've included just a few passes to make the review size manageable -
I have more passes coming.

cmd/internal/ssa is the library containing the ssa compiler proper.

cmd/internal/ssa/ssac is a driver that loads an sexpr-based IR,
converts it to SSA form, and calls the above library.  It is essentially
throwaway code - it will disappear once the Go compiler calls
cmd/internal/ssa itself.  The .goir files in ssac/ are dumps of fibonacci
programs I made from a hacked-up compiler.  They are just for testing.

Change-Id: I5ee89356ec12c87cd916681097cd3c2cd591040c
Reviewed-on: https://go-review.googlesource.com/6681
Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
Keith Randall 2015-03-03 13:38:14 -08:00
parent de7f6c77bc
commit f52b234579
26 changed files with 2438 additions and 0 deletions

View File

@ -0,0 +1,92 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
import (
"fmt"
"strings"
)
// Block represents a basic block in the control flow graph of a function.
type Block struct {
// A unique identifier for the block. The system will attempt to allocate
// these IDs densely, but no guarantees.
ID ID
// The kind of block this is.
Kind BlockKind
// Subsequent blocks, if any. The number and order depend on the block kind.
// All blocks must be distinct (to make phi values in successors unambiguous).
Succs []*Block
// Inverse of successors.
// The order is significant to Phi nodes in the block.
Preds []*Block
// TODO: predecessors is a pain to maintain. Can we somehow order phi
// arguments by block id and have this field computed explicitly when needed?
// A value that determines how the block is exited. Its value depends on the kind
// of the block. For instance, a BlockIf has a boolean control value and BlockExit
// has a memory control value.
Control *Value
// The unordered set of Values contained in this block.
// The list must include the control value, if any. (TODO: need this last condition?)
Values []*Value
// The containing function
Func *Func
}
// kind control successors
// ------------------------------------------
// Exit return mem []
// Plain nil [next]
// If a boolean Value [then, else]
// Call mem [nopanic, panic] (control opcode should be OpCall or OpStaticCall)
type BlockKind int8
const (
BlockExit BlockKind = iota // no successors. There should only be 1 of these.
BlockPlain // a single successor
BlockIf // 2 successors, if control goto Succs[0] else goto Succs[1]
BlockCall // 2 successors, normal return and panic
BlockUnknown
// 386/amd64 variants of BlockIf that take the flags register as an arg
BlockEQ
BlockNE
BlockLT
BlockLE
BlockGT
BlockGE
BlockULT
BlockULE
BlockUGT
BlockUGE
)
//go:generate stringer -type=BlockKind
// short form print
func (b *Block) String() string {
return fmt.Sprintf("b%d", b.ID)
}
// long form print
func (b *Block) LongString() string {
s := strings.TrimPrefix(b.Kind.String(), "Block")
if b.Control != nil {
s += fmt.Sprintf(" %s", b.Control)
}
if len(b.Succs) > 0 {
s += " ->"
for _, c := range b.Succs {
s += " " + c.String()
}
}
return s
}

View File

@ -0,0 +1,16 @@
// generated by stringer -type=BlockKind; DO NOT EDIT
package ssa
import "fmt"
const _BlockKind_name = "BlockExitBlockPlainBlockIfBlockCallBlockUnknownBlockEQBlockNEBlockLTBlockLEBlockGTBlockGEBlockULTBlockULEBlockUGTBlockUGE"
var _BlockKind_index = [...]uint8{0, 9, 19, 26, 35, 47, 54, 61, 68, 75, 82, 89, 97, 105, 113, 121}
func (i BlockKind) String() string {
if i < 0 || i+1 >= BlockKind(len(_BlockKind_index)) {
return fmt.Sprintf("BlockKind(%d)", i)
}
return _BlockKind_name[_BlockKind_index[i]:_BlockKind_index[i+1]]
}

View File

@ -0,0 +1,125 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
import "log"
// checkFunc checks invariants of f.
func checkFunc(f *Func) {
blockMark := make([]bool, f.NumBlocks())
valueMark := make([]bool, f.NumValues())
for _, b := range f.Blocks {
if blockMark[b.ID] {
log.Panicf("block %s appears twice in %s!", b, f.Name)
}
blockMark[b.ID] = true
if b.Func != f {
log.Panicf("%s.Func=%s, want %s", b, b.Func.Name, f.Name)
}
for i, c := range b.Succs {
for j, d := range b.Succs {
if i != j && c == d {
log.Panicf("%s.Succs has duplicate block %s", b, c)
}
}
}
// Note: duplicate successors are hard in the following case:
// if(...) goto x else goto x
// x: v = phi(a, b)
// If the conditional is true, does v get the value of a or b?
// We could solve this other ways, but the easiest is just to
// require (by possibly adding empty control-flow blocks) that
// all successors are distinct. They will need to be distinct
// anyway for register allocation (duplicate successors implies
// the existence of critical edges).
for _, p := range b.Preds {
var found bool
for _, c := range p.Succs {
if c == b {
found = true
break
}
}
if !found {
log.Panicf("block %s is not a succ of its pred block %s", b, p)
}
}
switch b.Kind {
case BlockExit:
if len(b.Succs) != 0 {
log.Panicf("exit block %s has successors", b)
}
if b.Control == nil {
log.Panicf("exit block %s has no control value", b)
}
if b.Control.Type != TypeMem {
log.Panicf("exit block %s has non-memory control value %s", b, b.Control.LongString())
}
case BlockPlain:
if len(b.Succs) != 1 {
log.Panicf("plain block %s len(Succs)==%d, want 1", b, len(b.Succs))
}
if b.Control != nil {
log.Panicf("plain block %s has non-nil control %s", b, b.Control.LongString())
}
case BlockIf:
if len(b.Succs) != 2 {
log.Panicf("if block %s len(Succs)==%d, want 2", b, len(b.Succs))
}
if b.Control == nil {
log.Panicf("if block %s has no control value", b)
}
if b.Control.Type != TypeBool {
log.Panicf("if block %s has non-bool control value %s", b, b.Control.LongString())
}
case BlockCall:
if len(b.Succs) != 2 {
log.Panicf("call block %s len(Succs)==%d, want 2", b, len(b.Succs))
}
if b.Control == nil {
log.Panicf("call block %s has no control value", b)
}
if b.Control.Type != TypeMem {
log.Panicf("call block %s has non-memory control value %s", b, b.Control.LongString())
}
if b.Succs[1].Kind != BlockExit {
log.Panicf("exception edge from call block %s does not go to exit but %s", b, b.Succs[1])
}
}
for _, v := range b.Values {
if valueMark[v.ID] {
log.Panicf("value %s appears twice!", v.LongString())
}
valueMark[v.ID] = true
if v.Block != b {
log.Panicf("%s.block != %s", v, b)
}
if v.Op == OpPhi && len(v.Args) != len(b.Preds) {
log.Panicf("phi length %s does not match pred length %d for block %s", v.LongString(), len(b.Preds), b)
}
// TODO: check idom
// TODO: check for cycles in values
// TODO: check type
}
}
for _, id := range f.bid.free {
if blockMark[id] {
log.Panicf("used block b%d in free list", id)
}
}
for _, id := range f.vid.free {
if valueMark[id] {
log.Panicf("used value v%d in free list", id)
}
}
}

View File

@ -0,0 +1,65 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
import "fmt"
// Compile is the main entry point for this package.
// Compile modifies f so that on return:
// · all Values in f map to 0 or 1 assembly instructions of the target architecture
// · the order of f.Blocks is the order to emit the Blocks
// · the order of b.Values is the order to emit the Values in each Block
// · f has a non-nil regAlloc field
func Compile(f *Func) {
// TODO: debugging - set flags to control verbosity of compiler,
// which phases to dump IR before/after, etc.
fmt.Printf("compiling %s\n", f.Name)
// hook to print function & phase if panic happens
phaseName := "init"
defer func() {
if phaseName != "" {
fmt.Printf("panic during %s while compiling %s\n", phaseName, f.Name)
}
}()
// Run all the passes
printFunc(f)
checkFunc(f)
for _, p := range passes {
phaseName = p.name
fmt.Printf(" pass %s begin\n", p.name)
p.fn(f)
fmt.Printf(" pass %s end\n", p.name)
printFunc(f)
checkFunc(f)
}
// Squash error printing defer
phaseName = ""
}
type pass struct {
name string
fn func(*Func)
}
// list of passes for the compiler
var passes = [...]pass{
{"phielim", phielim},
{"copyelim", copyelim},
//{"opt", opt},
// cse
{"deadcode", deadcode},
//{"fuse", fuse},
//{"lower", lower},
// cse
//{"critical", critical}, // remove critical edges
//{"layout", layout}, // schedule blocks
//{"schedule", schedule}, // schedule values
// regalloc
// stack slot alloc (+size stack frame)
//{"cgen", cgen},
}

View File

@ -0,0 +1,29 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
// copyelim removes all copies from f.
func copyelim(f *Func) {
for _, b := range f.Blocks {
for _, v := range b.Values {
for i, w := range v.Args {
x := w
for x.Op == OpCopy {
x = x.Args[0]
}
if x != w {
v.Args[i] = x
}
}
}
v := b.Control
if v != nil {
for v.Op == OpCopy {
v = v.Args[0]
}
b.Control = v
}
}
}

View File

@ -0,0 +1,153 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
import "log"
// deadcode removes dead code from f.
func deadcode(f *Func) {
// Find all reachable basic blocks.
reachable := make([]bool, f.NumBlocks())
reachable[f.Entry.ID] = true
p := []*Block{f.Entry} // stack-like worklist
for len(p) > 0 {
// pop a reachable block
b := p[len(p)-1]
p = p[:len(p)-1]
// constant-fold conditionals
// TODO: rewrite rules instead?
if b.Kind == BlockIf && b.Control.Op == OpConstBool {
cond := b.Control.Aux.(bool)
var c *Block
if cond {
// then branch is always taken
c = b.Succs[1]
} else {
// else branch is always taken
c = b.Succs[0]
b.Succs[0] = b.Succs[1]
}
b.Succs[1] = nil // aid GC
b.Succs = b.Succs[:1]
removePredecessor(b, c)
b.Kind = BlockPlain
b.Control = nil
}
for _, c := range b.Succs {
if !reachable[c.ID] {
reachable[c.ID] = true
p = append(p, c) // push
}
}
}
// Find all live values
live := make([]bool, f.NumValues()) // flag to set for each live value
var q []*Value // stack-like worklist of unscanned values
// Starting set: all control values of reachable blocks are live.
for _, b := range f.Blocks {
if !reachable[b.ID] {
continue
}
if v := b.Control; v != nil && !live[v.ID] {
live[v.ID] = true
q = append(q, v)
}
}
// Compute transitive closure of live values.
for len(q) > 0 {
// pop a reachable value
v := q[len(q)-1]
q = q[:len(q)-1]
for _, x := range v.Args {
if !live[x.ID] {
live[x.ID] = true
q = append(q, x) // push
}
}
}
// Remove dead values from blocks' value list. Return dead
// value ids to the allocator.
for _, b := range f.Blocks {
i := 0
for _, v := range b.Values {
if live[v.ID] {
b.Values[i] = v
i++
} else {
f.vid.put(v.ID)
}
}
for j := i; j < len(b.Values); j++ {
b.Values[j] = nil // aid GC
}
b.Values = b.Values[:i]
}
// Remove unreachable blocks. Return dead block ids to allocator.
i := 0
for _, b := range f.Blocks {
if reachable[b.ID] {
f.Blocks[i] = b
i++
} else {
if len(b.Values) > 0 {
panic("live value in unreachable block")
}
f.bid.put(b.ID)
}
}
// zero remainder to help gc
for j := i; j < len(f.Blocks); j++ {
f.Blocks[j] = nil
}
f.Blocks = f.Blocks[:i]
// TODO: renumber Blocks and Values densely?
}
// There was an edge b->c. It has been removed from b's successors.
// Fix up c to handle that fact.
func removePredecessor(b, c *Block) {
n := len(c.Preds) - 1
if n == 0 {
// c is now dead - don't bother working on it
if c.Preds[0] != b {
log.Panicf("%s.Preds[0]==%s, want %s", c, c.Preds[0], b)
}
return
}
// find index of b in c's predecessor list
var i int
for j, p := range c.Preds {
if p == b {
i = j
break
}
}
c.Preds[i] = c.Preds[n]
c.Preds[n] = nil // aid GC
c.Preds = c.Preds[:n]
// rewrite phi ops to match the new predecessor list
for _, v := range c.Values {
if v.Op != OpPhi {
continue
}
v.Args[i] = v.Args[n]
v.Args[n] = nil // aid GC
v.Args = v.Args[:n]
if n == 1 {
v.Op = OpCopy
}
}
}

View File

@ -0,0 +1,112 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// TODO: these tests are pretty verbose. Is there a way to simplify
// building a small Func for testing?
package ssa_test
import (
. "cmd/internal/ssa"
"testing"
)
func TestDeadLoop(t *testing.T) {
f := new(Func)
entry := f.NewBlock(BlockPlain)
exit := f.NewBlock(BlockExit)
f.Entry = entry
addEdge(entry, exit)
mem := entry.NewValue(OpArg, TypeMem, ".mem")
exit.Control = mem
// dead loop
deadblock := f.NewBlock(BlockIf)
addEdge(deadblock, deadblock)
addEdge(deadblock, exit)
// dead value in dead block
deadval := deadblock.NewValue(OpConstBool, TypeBool, true)
deadblock.Control = deadval
CheckFunc(f)
Deadcode(f)
CheckFunc(f)
for _, b := range f.Blocks {
if b == deadblock {
t.Errorf("dead block not removed")
}
for _, v := range b.Values {
if v == deadval {
t.Errorf("control value of dead block not removed")
}
}
}
}
func TestDeadValue(t *testing.T) {
f := new(Func)
entry := f.NewBlock(BlockPlain)
exit := f.NewBlock(BlockExit)
f.Entry = entry
addEdge(entry, exit)
mem := entry.NewValue(OpArg, TypeMem, ".mem")
exit.Control = mem
deadval := entry.NewValue(OpConstInt, TypeInt, 37)
CheckFunc(f)
Deadcode(f)
CheckFunc(f)
for _, b := range f.Blocks {
for _, v := range b.Values {
if v == deadval {
t.Errorf("dead value not removed")
}
}
}
}
func TestNeverTaken(t *testing.T) {
f := new(Func)
entry := f.NewBlock(BlockIf)
exit := f.NewBlock(BlockExit)
then := f.NewBlock(BlockPlain)
else_ := f.NewBlock(BlockPlain)
f.Entry = entry
addEdge(entry, then)
addEdge(entry, else_)
addEdge(then, exit)
addEdge(else_, exit)
mem := entry.NewValue(OpArg, TypeMem, ".mem")
exit.Control = mem
cond := entry.NewValue(OpConstBool, TypeBool, false)
entry.Control = cond
CheckFunc(f)
Deadcode(f)
CheckFunc(f)
if entry.Kind != BlockPlain {
t.Errorf("if(false) not simplified")
}
for _, b := range f.Blocks {
if b == then {
t.Errorf("then block still present")
}
for _, v := range b.Values {
if v == cond {
t.Errorf("constant condition still present")
}
}
}
}
func addEdge(b, c *Block) {
b.Succs = append(b.Succs, c)
c.Preds = append(c.Preds, b)
}

View File

@ -0,0 +1,9 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
var CheckFunc = checkFunc
var PrintFunc = printFunc
var Deadcode = deadcode

View File

@ -0,0 +1,61 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
// A Func represents a Go func declaration (or function literal) and
// its body. This package compiles each Func independently.
type Func struct {
Name string // e.g. bytes·Compare
Type Type // type signature of the function.
Blocks []*Block // unordered set of all basic blocks (note: not indexable by ID)
Entry *Block // the entry basic block
bid idAlloc // block ID allocator
vid idAlloc // value ID allocator
// when register allocation is done, maps value ids to locations
RegAlloc []Location
}
// NumBlocks returns an integer larger than the id of any Block in the Func.
func (f *Func) NumBlocks() int {
return f.bid.num()
}
// NumValues returns an integer larger than the id of any Value in the Func.
func (f *Func) NumValues() int {
return f.vid.num()
}
// NewBlock returns a new block of the given kind and appends it to f.Blocks.
func (f *Func) NewBlock(kind BlockKind) *Block {
b := &Block{
ID: f.bid.get(),
Kind: kind,
Func: f,
}
f.Blocks = append(f.Blocks, b)
return b
}
// NewValue returns a new value in the block with no arguments.
func (b *Block) NewValue(op Op, t Type, aux interface{}) *Value {
v := &Value{
ID: b.Func.vid.get(),
Op: op,
Type: t,
Aux: aux,
Block: b,
}
v.Args = v.argstorage[:0]
b.Values = append(b.Values, v)
return v
}
// ConstInt returns an int constant representing its argument.
func (f *Func) ConstInt(c int64) *Value {
// TODO: cache?
// TODO: different types?
return f.Entry.NewValue(OpConstInt, TypeInt, c)
}

View File

@ -0,0 +1,41 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
type ID int32
// idAlloc provides an allocator for unique integers.
type idAlloc struct {
last ID
free []ID
}
// get allocates an ID and returns it.
func (a *idAlloc) get() ID {
if n := len(a.free); n > 0 {
x := a.free[n-1]
a.free = a.free[:n-1]
return x
}
x := a.last
x++
if x == 1<<31-1 {
panic("too many ids for this function")
}
a.last = x
return x
}
// put deallocates an ID.
func (a *idAlloc) put(x ID) {
a.free = append(a.free, x)
// TODO: IR check should make sure that the IR contains
// no IDs that are in the free list.
}
// num returns the maximum ID ever returned + 1.
func (a *idAlloc) num() int {
return int(a.last + 1)
}

View File

@ -0,0 +1,42 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
import (
"fmt"
)
// A place that an ssa variable can reside.
type Location interface {
Name() string // name to use in assembly templates: %rax, 16(%rsp), ...
}
// A Register is a machine register, like %rax.
type Register struct {
name string
}
func (r *Register) Name() string {
return r.name
}
// A LocalSlot is a location in the stack frame.
type LocalSlot struct {
idx int64 // offset in locals area (distance down from FP == caller's SP)
}
func (s *LocalSlot) Name() string {
return fmt.Sprintf("loc%d", s.idx)
}
// An ArgSlot is a location in the parents' stack frame where it passed us an argument.
type ArgSlot struct {
idx int64 // offset in argument area
}
// A CalleeSlot is a location in the stack frame where we pass an argument to a callee.
type CalleeSlot struct {
idx int64 // offset in callee area
}

345
src/cmd/internal/ssa/op.go Normal file
View File

@ -0,0 +1,345 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
// An Op encodes the specific operation that a Value performs.
// Opcodes' semantics can be modified by the type and aux fields of the Value.
// For instance, OpAdd can be 32 or 64 bit, signed or unsigned, float or complex, depending on Value.Type.
// Semantics of each op are described below.
// Ops come in two flavors, architecture-independent and architecture-dependent.
type Op int32
// All the opcodes
const (
OpUnknown Op = iota
// machine-independent opcodes
OpNop // should never be used, appears only briefly during construction, Has type Void.
OpThunk // used during ssa construction. Like OpCopy, but the arg has not been specified yet.
// 2-input arithmetic
OpAdd
OpSub
OpMul
// 2-input comparisons
OpLess
// constants
OpConstNil
OpConstBool // aux is type bool
OpConstString // aux is type string
OpConstInt // aux is type int64
OpConstFloat // aux is type float64
OpConstComplex // aux is type complex128
OpArg // address of a function parameter/result
OpGlobal // address of a global variable
OpFunc // entry address of a function
OpCopy // output = input
OpPhi // select an input based on which predecessor we came from
OpSliceMake // args are ptr/len/cap
OpSlicePtr
OpSliceLen
OpSliceCap
OpStringMake // args are ptr/len
OpStringPtr
OpStringLen
OpSlice
OpIndex
OpIndexAddr
OpLoad // args are ptr, memory
OpStore // args are ptr, memory, returns memory
OpCheckNil // arg[0] != nil
OpCheckBound // 0 <= arg[0] < arg[1]
// function calls. Arguments to the call have already been written to the stack.
// Return values appear on the stack.
OpCall // args are function ptr, memory
OpStaticCall // aux is function, arg is memory
OpConvert
OpConvNop
// These ops return a pointer to a location on the stack. Aux contains an int64
// indicating an offset from the base pointer.
OpFPAddr // offset from FP (+ == args from caller, - == locals)
OpSPAddr // offset from SP
// load/store from constant offsets from SP/FP
// The distinction between FP/SP needs to be maintained until after
// register allocation because we don't know the size of the frame yet.
OpLoadFP
OpLoadSP
OpStoreFP
OpStoreSP
// spill&restore ops for the register allocator. These are
// semantically identical to OpCopy - they do not take/return
// stores like regular memory ops do. We can get away with that because
// we know there is no aliasing to spill slots on the stack.
OpStoreReg8
OpLoadReg8
// machine-dependent opcodes go here
// x86
OpADDQ
OpSUBQ
OpADDCQ // 1 input arg, add aux which is an int64 constant
OpSUBCQ // 1 input arg. output = input - aux.(int64)
OpNEGQ
OpCMPQ
OpCMPCQ // 1 input arg. Compares input with aux.(int64)
OpADDL
OpInvertFlags // inverts interpretation of the flags register (< to >=, etc.)
OpSETL // generate bool = "flags encode less than"
OpSETGE
OpLEAQ // x+y
OpLEAQ2 // x+2*y
OpLEAQ4 // x+4*y
OpLEAQ8 // x+8*y
OpLoadFP8
OpLoadSP8
OpStoreFP8
OpStoreSP8
OpMax // sentinel
)
//go:generate stringer -type=Op
type OpInfo struct {
flags int32
// assembly template
// %In: location of input n
// %On: location of output n
// %A: print aux with fmt.Print
asm string
// computes type for values with this opcode
typer func(v *Value)
// returns a reg constraint for the instruction. [0] gives a reg constraint
// for each input, [1] gives a reg constraint for each output. (Values have
// exactly one output for now)
reg [2][]regMask
}
type regMask uint64
var regs386 = [...]string{
"AX",
"BX",
"CX",
"DX",
"SI",
"DI",
"SP",
"BP",
"X0",
// pseudo registers
"FLAGS",
"OVERWRITE0", // the same register as the first input
}
// TODO: match up these with regs386 above
var gp regMask = 0xff
var cx regMask = 0x4
var flags regMask = 1 << 9
var overwrite0 regMask = 1 << 10
const (
// possible properties of opcodes
OpFlagCommutative int32 = 1 << iota
// architecture constants
Arch386
ArchAmd64
ArchArm
)
func firstArgTyper(v *Value) {
v.Type = v.Args[0].Type
}
func boolTyper(v *Value) {
v.Type = TypeBool
}
func stringTyper(v *Value) {
v.Type = TypeString
}
func flagsTyper(v *Value) {
v.Type = TypeFlags
}
func uint8Typer(v *Value) {
v.Type = TypeUint8
}
func uint64Typer(v *Value) {
v.Type = TypeUint64
}
func auxTyper(v *Value) {
v.Type = v.Aux.(Type)
}
// general purpose registers, 2 input, 1 output
var gp21 = [2][]regMask{{gp, gp}, {gp}}
var gp21_overwrite = [2][]regMask{{gp, gp}, {overwrite0}}
// general purpose registers, 1 input, 1 output
var gp11 = [2][]regMask{{gp}, {gp}}
var gp11_overwrite = [2][]regMask{{gp}, {overwrite0}}
// shift operations
var shift = [2][]regMask{{gp, cx}, {overwrite0}}
var gp2_flags = [2][]regMask{{gp, gp}, {flags}}
var gp1_flags = [2][]regMask{{gp}, {flags}}
var gpload = [2][]regMask{{gp, 0}, {gp}}
var gpstore = [2][]regMask{{gp, gp, 0}, {0}}
// Opcodes that represent the input Go program
var genericTable = [...]OpInfo{
// the unknown op is used only during building and should not appear in a
// fully formed ssa representation.
OpAdd: {flags: OpFlagCommutative, typer: firstArgTyper},
OpSub: {typer: firstArgTyper},
OpMul: {flags: OpFlagCommutative, typer: firstArgTyper},
OpLess: {typer: boolTyper},
OpConstBool: {typer: boolTyper}, // aux is a bool
OpConstString: {typer: stringTyper}, // aux is a string
OpConstInt: {}, // aux is an int64
OpConstFloat: {}, // aux is a float64
OpConstComplex: {},
OpArg: {}, // aux is the name of the input variable TODO:?
OpGlobal: {}, // address of a global variable
OpFunc: {},
OpCopy: {},
OpPhi: {},
OpConvNop: {}, // aux is the type to convert to
/*
// build and take apart slices
{name: "slicemake"}, // (ptr,len,cap) -> slice
{name: "sliceptr"}, // pointer part of slice
{name: "slicelen"}, // length part of slice
{name: "slicecap"}, // capacity part of slice
// build and take apart strings
{name: "stringmake"}, // (ptr,len) -> string
{name: "stringptr"}, // pointer part of string
{name: "stringlen"}, // length part of string
// operations on arrays/slices/strings
{name: "slice"}, // (s, i, j) -> s[i:j]
{name: "index"}, // (mem, ptr, idx) -> val
{name: "indexaddr"}, // (ptr, idx) -> ptr
// loads & stores
{name: "load"}, // (mem, check, ptr) -> val
{name: "store"}, // (mem, check, ptr, val) -> mem
// checks
{name: "checknil"}, // (mem, ptr) -> check
{name: "checkbound"}, // (mem, idx, len) -> check
// functions
{name: "call"},
// builtins
{name: "len"},
{name: "convert"},
// tuples
{name: "tuple"}, // build a tuple out of its arguments
{name: "extract"}, // aux is an int64. Extract that index out of a tuple
{name: "extractsuffix"}, // aux is an int64. Slice a tuple with [aux:]
*/
}
// Opcodes that appear in an output amd64 program
var amd64Table = [...]OpInfo{
OpADDQ: {flags: OpFlagCommutative, asm: "ADDQ\t%I0,%I1,%O0", reg: gp21, typer: firstArgTyper}, // TODO: overwrite
OpADDCQ: {asm: "ADDQ\t$%A,%I0,%O0", reg: gp11_overwrite, typer: firstArgTyper}, // aux = int64 constant to add
OpSUBQ: {asm: "SUBQ\t%I0,%I1,%O0", reg: gp21, typer: firstArgTyper},
OpSUBCQ: {asm: "SUBQ\t$%A,%I0,%O0", reg: gp11_overwrite, typer: firstArgTyper},
OpCMPQ: {asm: "CMPQ\t%I0,%I1", reg: gp2_flags, typer: flagsTyper}, // compute arg[0]-arg[1] and produce flags
OpCMPCQ: {asm: "CMPQ\t$%A,%I0", reg: gp1_flags},
OpLEAQ: {flags: OpFlagCommutative, asm: "LEAQ\t%A(%I0)(%I1*1),%O0", reg: gp21}, // aux = int64 constant to add
OpLEAQ2: {asm: "LEAQ\t%A(%I0)(%I1*2),%O0"},
OpLEAQ4: {asm: "LEAQ\t%A(%I0)(%I1*4),%O0"},
OpLEAQ8: {asm: "LEAQ\t%A(%I0)(%I1*8),%O0"},
//OpLoad8: {asm: "MOVQ\t%A(%I0),%O0", reg: gpload},
//OpStore8: {asm: "MOVQ\t%I1,%A(%I0)", reg: gpstore},
OpStaticCall: {asm: "CALL\t%A(SB)"},
OpCopy: {asm: "MOVQ\t%I0,%O0", reg: gp11},
// convert from flags back to boolean
OpSETL: {typer: boolTyper},
// ops for load/store to stack
OpLoadFP8: {asm: "MOVQ\t%A(FP),%O0"},
OpLoadSP8: {asm: "MOVQ\t%A(SP),%O0"},
OpStoreFP8: {asm: "MOVQ\t%I0,%A(FP)"},
OpStoreSP8: {asm: "MOVQ\t%I0,%A(SP)"},
// ops for spilling of registers
// unlike regular loads & stores, these take no memory argument.
// They are just like OpCopy but we use them during register allocation.
// TODO: different widths, float
OpLoadReg8: {asm: "MOVQ\t%I0,%O0", reg: gp11},
OpStoreReg8: {asm: "MOVQ\t%I0,%O0", reg: gp11},
}
// A Table is a list of opcodes with a common set of flags.
type Table struct {
t []OpInfo
flags int32
}
var tables = []Table{
{genericTable[:], 0},
{amd64Table[:], ArchAmd64}, // TODO: pick this dynamically
}
// table of opcodes, indexed by opcode ID
var opcodeTable [OpMax]OpInfo
// map from opcode names to opcode IDs
var nameToOp map[string]Op
func init() {
// build full opcode table
// Note that the arch-specific table overwrites the generic table
for _, t := range tables {
for op, entry := range t.t {
entry.flags |= t.flags
opcodeTable[op] = entry
}
}
// build name to opcode mapping
nameToOp = make(map[string]Op)
for op := range opcodeTable {
nameToOp[Op(op).String()] = Op(op)
}
}

View File

@ -0,0 +1,16 @@
// generated by stringer -type=Op; DO NOT EDIT
package ssa
import "fmt"
const _Op_name = "OpUnknownOpNopOpThunkOpAddOpSubOpMulOpLessOpConstNilOpConstBoolOpConstStringOpConstIntOpConstFloatOpConstComplexOpArgOpGlobalOpFuncOpCopyOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpSliceOpIndexOpIndexAddrOpLoadOpStoreOpCheckNilOpCheckBoundOpCallOpStaticCallOpConvertOpConvNopOpFPAddrOpSPAddrOpLoadFPOpLoadSPOpStoreFPOpStoreSPOpStoreReg8OpLoadReg8OpADDQOpSUBQOpADDCQOpSUBCQOpNEGQOpCMPQOpCMPCQOpADDLOpInvertFlagsOpSETLOpSETGEOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpLoadFP8OpLoadSP8OpStoreFP8OpStoreSP8OpMax"
var _Op_index = [...]uint16{0, 9, 14, 21, 26, 31, 36, 42, 52, 63, 76, 86, 98, 112, 117, 125, 131, 137, 142, 153, 163, 173, 183, 195, 206, 217, 224, 231, 242, 248, 255, 265, 277, 283, 295, 304, 313, 321, 329, 337, 345, 354, 363, 374, 384, 390, 396, 403, 410, 416, 422, 429, 435, 448, 454, 461, 467, 474, 481, 488, 497, 506, 516, 526, 531}
func (i Op) String() string {
if i < 0 || i+1 >= Op(len(_Op_index)) {
return fmt.Sprintf("Op(%d)", i)
}
return _Op_name[_Op_index[i]:_Op_index[i+1]]
}

View File

@ -0,0 +1,44 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
// phielim eliminates redundant phi values from f.
// A phi is redundant if its arguments are all equal. For
// purposes of counting, ignore the phi itself. Both of
// these phis are redundant:
// v = phi(x,x,x)
// v = phi(x,v,x,v)
func phielim(f *Func) {
args := newSparseSet(f.NumValues())
for _, b := range f.Blocks {
for _, v := range b.Values {
if v.Op != OpPhi {
continue
}
args.clear()
for _, x := range v.Args {
for x.Op == OpCopy {
x = x.Args[0]
}
args.add(x.ID)
}
switch {
case args.size() == 1:
v.Op = OpCopy
v.SetArgs1(v.Args[0])
case args.size() == 2 && args.contains(v.ID):
var w *Value
for _, x := range v.Args {
if x.ID != v.ID {
w = x
break
}
}
v.Op = OpCopy
v.SetArgs1(w)
}
}
}
}

View File

@ -0,0 +1,63 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
import "fmt"
func printFunc(f *Func) {
fmt.Print(f.Name)
fmt.Print(" ")
fmt.Println(f.Type)
printed := make([]bool, f.NumValues())
for _, b := range f.Blocks {
fmt.Printf(" b%d:\n", b.ID)
n := 0
// print phis first since all value cycles contain a phi
for _, v := range b.Values {
if v.Op != OpPhi {
continue
}
fmt.Print(" ")
fmt.Println(v.LongString())
printed[v.ID] = true
n++
}
// print rest of values in dependency order
for n < len(b.Values) {
m := n
outer:
for _, v := range b.Values {
if printed[v.ID] {
continue
}
for _, w := range v.Args {
if w.Block == b && !printed[w.ID] {
continue outer
}
}
fmt.Print(" ")
fmt.Println(v.LongString())
printed[v.ID] = true
n++
}
if m == n {
fmt.Println("dependency cycle!")
for _, v := range b.Values {
if printed[v.ID] {
continue
}
fmt.Print(" ")
fmt.Println(v.LongString())
printed[v.ID] = true
n++
}
}
}
fmt.Println(" " + b.LongString())
}
}

View File

@ -0,0 +1,60 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
// from http://research.swtch.com/sparse
// in turn, from Briggs and Torczon
type sparseSet struct {
dense []ID
sparse []int
}
// newSparseSet returns a sparseSet that can represent
// integers between 0 and n-1
func newSparseSet(n int) *sparseSet {
return &sparseSet{nil, make([]int, n)}
}
func (s *sparseSet) size() int {
return len(s.dense)
}
func (s *sparseSet) contains(x ID) bool {
i := s.sparse[x]
return i < len(s.dense) && s.dense[i] == x
}
func (s *sparseSet) add(x ID) {
i := len(s.dense)
s.dense = append(s.dense, x)
s.sparse[x] = i
}
func (s *sparseSet) remove(x ID) {
i := s.sparse[x]
if i < len(s.dense) && s.dense[i] == x {
y := s.dense[len(s.dense)-1]
s.dense[i] = y
s.sparse[y] = i
s.dense = s.dense[:len(s.dense)-1]
}
}
// pop removes an arbitrary element from the set.
// The set must be nonempty.
func (s *sparseSet) pop() ID {
x := s.dense[len(s.dense)-1]
s.dense = s.dense[:len(s.dense)-1]
return x
}
func (s *sparseSet) clear() {
s.dense = s.dense[:0]
}
func (s *sparseSet) contents() []ID {
return s.dense
}

1
src/cmd/internal/ssa/ssac/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
ssac

View File

@ -0,0 +1,46 @@
(TYPE T127bd68 int)
(TYPE T127bd68 int)
(TYPE T127bd68 int)
(TYPE T127bd68 int)
(TYPE T7faedc523360 (FUNC (int) (int)))
(TYPE T127bd68 int)
(TYPE T127bd68 int)
(TYPE T7faedc523360 (FUNC (int) (int)))
(TYPE T127bd68 int)
(TYPE T127bd68 int)
(TYPE T127bd68 int)
(TYPE T127bd68 int)
(TYPE T127bd68 int)
(TYPE T127bd68 int)
(DCL n T127bd68)
(DCL ~r1 T127bd68)
(DCL n T127bd68)
(DCL autotmp_0000 T127bd68)
(DCL fib T7faedc523360)
(DCL n T127bd68)
(DCL autotmp_0001 T127bd68)
(DCL fib T7faedc523360)
(DCL n T127bd68)
(DCL ~r1 T127bd68)
(DCL autotmp_0000 T127bd68)
(DCL autotmp_0001 T127bd68)
(DCL autotmp_0001 T127bd68)
(DCL autotmp_0000 T127bd68)
(IF (LT n (CINT 2)) .then0 .else0)
(LABEL .then0)
(AS ~r1 n)
(AS (SP T127bd68 8) ~r1)
(RETURN)
(GOTO .end0)
(LABEL .else0)
(GOTO .end0)
(LABEL .end0)
(AS (SP T127bd68 0) (SUB n (CINT 1)))
(CALL fib)
(AS autotmp_0000 (LOAD (SP T127bd68 8)))
(AS (SP T127bd68 0) (SUB n (CINT 2)))
(CALL fib)
(AS autotmp_0001 (LOAD (SP T127bd68 8)))
(AS ~r1 (ADD autotmp_0000 autotmp_0001))
(AS (SP T127bd68 8) ~r1)
(RETURN)

View File

@ -0,0 +1,62 @@
(NAME runtime·fibiter)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(TYPE Tf5dd68 int)
(DCL a Tf5dd68)
(DCL a Tf5dd68)
(DCL b Tf5dd68)
(DCL b Tf5dd68)
(DCL i Tf5dd68)
(DCL i Tf5dd68)
(DCL i Tf5dd68)
(DCL n Tf5dd68)
(DCL autotmp_0002 Tf5dd68)
(DCL i Tf5dd68)
(DCL i Tf5dd68)
(DCL autotmp_0002 Tf5dd68)
(DCL autotmp_0002 Tf5dd68)
(DCL autotmp_0003 Tf5dd68)
(DCL a Tf5dd68)
(DCL b Tf5dd68)
(DCL a Tf5dd68)
(DCL b Tf5dd68)
(DCL b Tf5dd68)
(DCL autotmp_0003 Tf5dd68)
(DCL ~r1 Tf5dd68)
(DCL a Tf5dd68)
(AS n (LOAD (SP Tf5dd68 0)))
(AS a (CINT 0))
(AS b (CINT 1))
(AS i (CINT 0))
(GOTO .top0)
(LABEL .top0)
(IF (LT i n) .body0 .end0)
(LABEL .body0)
(AS autotmp_0003 (ADD a b))
(AS a b)
(AS b autotmp_0003)
(AS autotmp_0002 i)
(AS i (ADD autotmp_0002 (CINT 1)))
(GOTO .top0)
(LABEL .end0)
(AS (SP Tf5dd68 8) a)
(RETURN)

View File

@ -0,0 +1,436 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
// Stub package for testing ssa compiler backend. Will eventually
// be deleted when ssa is called directly from the main compiler.
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"strconv"
"strings"
"cmd/internal/ssa/types"
"cmd/internal/ssa"
)
// testing harness which runs the compiler using an IR read from a file
func main() {
flag.Parse()
file := flag.Arg(0)
r, err := os.Open(file)
if err != nil {
panic(err)
}
f := buildFunc(readFunc(r))
ssa.Compile(f)
// TODO: output f
}
// readFunc reads the intermediate representation generated by the
// compiler frontend and returns it as a list of sexpressions.
func readFunc(r io.Reader) []sexpr {
var lines []sexpr
s := bufio.NewScanner(r)
for s.Scan() {
line := s.Text()
e := parseSexpr(strings.Trim(line, " "))
if !e.compound {
panic("bad stmt: " + line)
}
if e.parts[0].compound {
panic("bad op: " + line)
}
lines = append(lines, e)
}
return lines
}
// buildFunc converts from the 6g IR dump format to the internal
// form. Builds SSA and all that.
func buildFunc(lines []sexpr) *ssa.Func {
f := new(ssa.Func)
// We construct SSA using an algorithm similar to
// Brau, Buchwald, Hack, Leißa, Mallon, and Zwinkau
// http://pp.info.uni-karlsruhe.de/uploads/publikationen/braun13cc.pdf
// allocate starting block
f.Entry = f.NewBlock(ssa.BlockPlain)
// TODO: all args. Make a struct containing args/returnvals, declare
// an FP which contains a pointer to that struct.
var exit *ssa.Block // all returns (if any) branch to here TODO: defers & panics?
// add a block for each label
// Also a few other preprocessing steps, all in one pass.
labels := map[string]*ssa.Block{}
types := map[string]ssa.Type{}
callFallthrough := map[int]*ssa.Block{}
for i, e := range lines {
switch e.parts[0].name {
case "LABEL":
labels[e.parts[1].name] = f.NewBlock(ssa.BlockPlain)
case "NAME":
f.Name = e.parts[1].name
case "RETURN":
if exit == nil {
exit = f.NewBlock(ssa.BlockExit)
}
case "TYPE":
types[e.parts[1].name] = parseSexprType(e.parts[2])
case "CALL":
// allocate a new block for fallthrough
callFallthrough[i] = f.NewBlock(ssa.BlockPlain)
if exit == nil {
exit = f.NewBlock(ssa.BlockExit)
}
}
}
// map from block id to sexprs in that block
blocklines := make([][]sexpr, f.NumBlocks())
// Add sexprs to the correct block. Add edges between blocks.
b := f.Entry
var i int
for j, e := range lines {
if b == nil && e.parts[0].name != "LABEL" {
// dead code (e.g. return in "if" branch makes the "goto end" statement dead)
continue
}
switch e.parts[0].name {
case "IF":
if b.Kind != ssa.BlockPlain {
panic("bad b state")
}
b.Kind = ssa.BlockIf
edge(b, labels[e.parts[2].name])
edge(b, labels[e.parts[3].name])
blocklines[b.ID] = lines[i : j+1]
b = nil
case "GOTO":
edge(b, labels[e.parts[1].name])
blocklines[b.ID] = lines[i:j]
b = nil
case "LABEL":
b = labels[e.parts[1].name]
i = j + 1
case "RETURN":
if b.Kind != ssa.BlockPlain {
panic("bad b state")
}
edge(b, exit)
blocklines[b.ID] = lines[i:j]
b = nil
case "CALL":
if b.Kind != ssa.BlockPlain {
panic("bad b state")
}
b.Kind = ssa.BlockCall
c := callFallthrough[j]
edge(b, c)
edge(b, exit)
blocklines[b.ID] = lines[i : j+1]
b = c
i = j + 1
}
// note that we don't keep goto/label/return sexprs
}
if b != nil {
panic("control flow falls off end of function")
}
// Read types for each variable
// Number the variables densely
varids := map[string]int{} // map from variable name to id
var varnames []string // map from id to variable name
var vartypes []ssa.Type // map from variable id to type
for _, e := range lines {
if e.parts[0].name != "DCL" {
continue
}
name := e.parts[1].name
if _, ok := varids[name]; ok {
continue
}
id := len(varids)
if id == 1<<31-1 {
panic("too many variables")
}
fmt.Printf("var %d = %s\n", id, name)
varids[name] = id
varnames = append(varnames, name)
vartypes = append(vartypes, types[e.parts[2].name])
}
memID := len(varids)
fmt.Printf("var %d = .mem\n", memID)
varids[".mem"] = memID // TODO: need .mem here?
varnames = append(varnames, ".mem")
vartypes = append(vartypes, ssa.TypeMem)
// map from variable ID to current Value of that variable
curBlock := NewSparseMap(len(varids))
var state ssaFuncState
state.types = types
state.varids = varids
state.varnames = varnames
state.vartypes = vartypes
state.curBlock = curBlock
state.done = make([]bool, f.NumBlocks())
state.defs = map[blockvar]*ssa.Value{}
state.memID = memID
// Convert each block to ssa
// TODO: order blocks for maximum happiness - we want to process
// all the predecessors of a block before processing the block itself,
// if at all possible.
for _, b := range f.Blocks {
fmt.Printf("processing block %d\n", b.ID)
curBlock.Clear()
for _, e := range blocklines[b.ID] {
switch e.parts[0].name {
case "AS":
if e.parts[1].compound {
// store expression
lhs := genExpr(&state, b, e.parts[1])
rhs := genExpr(&state, b, e.parts[2])
mem := genVar(&state, b, memID)
v := b.NewValue(ssa.OpStore, ssa.TypeMem, nil)
v.AddArg(lhs)
v.AddArg(rhs)
v.AddArg(mem)
curBlock.Put(memID, v)
} else {
// variable assignment
v := genExpr(&state, b, e.parts[2])
curBlock.Put(varids[e.parts[1].name], v)
}
case "DCL":
// nothing to do
case "IF":
b.Control = genExpr(&state, b, e.parts[1])
case "CALL":
// only direct call for now - indirect call takes addr value as well
v := b.NewValue(ssa.OpStaticCall, ssa.TypeMem, e.parts[1].name)
v.AddArg(genVar(&state, b, memID))
curBlock.Put(memID, v)
b.Control = v
}
}
// link up thunks to their actual values
for _, v := range b.Values {
if v.Op != ssa.OpThunk {
continue
}
varid := v.Aux.(int)
w := genVar(&state, b, varid)
v.Op = ssa.OpCopy
v.Aux = nil
v.AddArg(w)
}
// record final values at the end of the block
for _, e := range curBlock.Contents() {
state.defs[blockvar{b.ID, e.Key}] = e.Val
// TODO: somehow avoid storing dead values to this map.
}
curBlock.Clear()
state.done[b.ID] = true
}
// the final store value is returned
if exit != nil {
exit.Control = genVar(&state, exit, memID)
}
return f
}
func edge(a, b *ssa.Block) {
a.Succs = append(a.Succs, b)
b.Preds = append(b.Preds, a)
}
func genVar(state *ssaFuncState, b *ssa.Block, id int) *ssa.Value {
// look up variable
v := state.curBlock.Get(id)
if v != nil {
// variable was defined previously in this block
// (or we memoized the result)
return v
}
// Variable comes in from outside of basic block.
v = lookupVarIncoming(state, b, id)
// memoize result so future callers will not look it up again
state.curBlock.Put(id, v)
return v
}
func genExpr(state *ssaFuncState, b *ssa.Block, e sexpr) *ssa.Value {
if !e.compound {
return genVar(state, b, state.varids[e.name])
}
switch e.parts[0].name {
case "ADD":
x := genExpr(state, b, e.parts[1])
y := genExpr(state, b, e.parts[2])
v := b.NewValue(ssa.OpAdd, x.Type, nil)
v.AddArg(x)
v.AddArg(y)
return v
case "SUB":
x := genExpr(state, b, e.parts[1])
y := genExpr(state, b, e.parts[2])
v := b.NewValue(ssa.OpSub, x.Type, nil)
v.AddArg(x)
v.AddArg(y)
return v
case "CINT":
c, err := strconv.ParseInt(e.parts[1].name, 10, 64)
if err != nil {
panic("bad cint value")
}
return b.Func.ConstInt(c)
case "LT":
x := genExpr(state, b, e.parts[1])
y := genExpr(state, b, e.parts[2])
v := b.NewValue(ssa.OpLess, ssa.TypeBool, nil)
v.AddArg(x)
v.AddArg(y)
return v
case "FP":
typ := state.types[e.parts[1].name]
offset, err := strconv.ParseInt(e.parts[2].name, 10, 64)
if err != nil {
panic(err)
}
v := b.NewValue(ssa.OpFPAddr, types.NewPointer(typ), offset)
return v
case "SP":
typ := state.types[e.parts[1].name]
offset, err := strconv.ParseInt(e.parts[2].name, 10, 64)
if err != nil {
panic(err)
}
v := b.NewValue(ssa.OpSPAddr, types.NewPointer(typ), offset)
return v
case "LOAD":
p := genExpr(state, b, e.parts[1])
v := b.NewValue(ssa.OpLoad, p.Type.(*types.Pointer).Elem(), nil)
v.AddArg(p)
v.AddArg(genVar(state, b, state.memID))
return v
default:
fmt.Println(e.parts[0].name)
panic("unknown op")
}
}
// map key combining block id and variable id
type blockvar struct {
bid ssa.ID
varid int
}
type ssaFuncState struct {
types map[string]ssa.Type
varnames []string
varids map[string]int
vartypes []ssa.Type
curBlock *SparseMap // value of each variable in block we're working on
defs map[blockvar]*ssa.Value // values for variables at the end of blocks
done []bool
memID int
}
// Find the value of the variable with the given id leaving block b.
func lookupVarOutgoing(state *ssaFuncState, b *ssa.Block, id int) *ssa.Value {
fmt.Printf("lookupOutgoing var=%d block=%d\n", id, b.ID)
v := state.defs[blockvar{b.ID, id}]
if v != nil {
return v
}
if state.done[b.ID] {
// The variable was not defined in this block, and we haven't
// memoized the answer yet. Look it up recursively. This might
// cause infinite recursion, so add a copy first.
v = b.NewValue(ssa.OpCopy, state.vartypes[id], nil)
state.defs[blockvar{b.ID, id}] = v
v.AddArg(lookupVarIncoming(state, b, id))
return v
}
// We don't know about defined variables in this block (yet).
// Make a thunk for this variable.
fmt.Printf("making thunk for var=%d in block=%d\n", id, b.ID)
v = b.NewValue(ssa.OpThunk, state.vartypes[id], id)
// memoize result
state.defs[blockvar{b.ID, id}] = v
return v
}
// Find the Value of the variable coming into block b.
func lookupVarIncoming(state *ssaFuncState, b *ssa.Block, id int) *ssa.Value {
fmt.Printf("lookupIncoming var=%d block=%d\n", id, b.ID)
var v *ssa.Value
switch len(b.Preds) {
case 0:
// TODO: handle function args some other way (assignments in starting block?)
// TODO: error if variable isn't a function arg (including mem input)
v = b.NewValue(ssa.OpArg, state.vartypes[id], state.varnames[id])
case 1:
v = lookupVarOutgoing(state, b.Preds[0], id)
default:
v = b.NewValue(ssa.OpCopy, state.vartypes[id], nil)
args := make([]*ssa.Value, len(b.Preds))
for i, p := range b.Preds {
args[i] = lookupVarOutgoing(state, p, id)
}
// if <=1 value that isn't this variable's thunk, don't make phi
v.Op = ssa.OpPhi
v.AddArgs(args...) // note: order corresponding to b.Pred
}
return v
}
func parseSexprType(e sexpr) ssa.Type {
if !e.compound {
switch e.name {
case "int":
return ssa.TypeInt
default:
fmt.Println(e.name)
panic("unknown type")
}
}
if e.parts[0].name == "FUNC" {
// TODO: receiver? Already folded into args? Variadic?
var args, rets []*types.Var
for _, s := range e.parts[1].parts {
t := parseSexprType(s)
args = append(args, types.NewParam(0, nil, "noname", t))
}
for _, s := range e.parts[2].parts {
t := parseSexprType(s)
rets = append(rets, types.NewParam(0, nil, "noname", t))
}
sig := types.NewSignature(nil, nil, types.NewTuple(args...), types.NewTuple(rets...), false)
return ssa.Type(sig)
}
// TODO: array/struct/...
panic("compound type")
}

View File

@ -0,0 +1,82 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import "strings"
// an sexpr is an s-expression. It is either a token or a
// parenthesized list of s-expressions.
//
// Used just for initial development. Should we keep it for testing, or
// ditch it once we've plugged into the main compiler output?
type sexpr struct {
compound bool
name string // !compound
parts []sexpr // compound
}
func (s *sexpr) String() string {
if !s.compound {
return s.name
}
x := "("
for i, p := range s.parts {
if i != 0 {
x += " "
}
x += p.String()
}
return x + ")"
}
func parseSexpr(s string) sexpr {
var e string
e, s = grabOne(s)
if len(e) > 0 && e[0] == '(' {
e = e[1 : len(e)-1]
var parts []sexpr
for e != "" {
var p string
p, e = grabOne(e)
parts = append(parts, parseSexpr(p))
}
return sexpr{true, "", parts}
}
return sexpr{false, e, nil}
}
// grabOne peels off first token or parenthesized string from s.
// returns first thing and the remainder of s.
func grabOne(s string) (string, string) {
for len(s) > 0 && s[0] == ' ' {
s = s[1:]
}
if len(s) == 0 || s[0] != '(' {
i := strings.Index(s, " ")
if i < 0 {
return s, ""
}
return s[:i], s[i:]
}
d := 0
i := 0
for {
if len(s) == i {
panic("unterminated s-expression: " + s)
}
if s[i] == '(' {
d++
}
if s[i] == ')' {
d--
if d == 0 {
i++
return s[:i], s[i:]
}
}
i++
}
}

View File

@ -0,0 +1,69 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
// Maintains a map[int]*ssa.Value, but cheaper.
// from http://research.swtch.com/sparse
// in turn, from Briggs and Torczon
import (
"cmd/internal/ssa"
)
type SparseMap struct {
dense []SparseMapEntry
sparse []int
}
type SparseMapEntry struct {
Key int
Val *ssa.Value
}
// NewSparseMap returns a SparseMap that can have
// integers between 0 and n-1 as keys.
func NewSparseMap(n int) *SparseMap {
return &SparseMap{nil, make([]int, n)}
}
func (s *SparseMap) Get(x int) *ssa.Value {
i := s.sparse[x]
if i < len(s.dense) && s.dense[i].Key == x {
return s.dense[i].Val
}
return nil
}
func (s *SparseMap) Put(x int, v *ssa.Value) {
i := s.sparse[x]
if i < len(s.dense) && s.dense[i].Key == x {
s.dense[i].Val = v
return
}
i = len(s.dense)
s.dense = append(s.dense, SparseMapEntry{x, v})
s.sparse[x] = i
}
func (s *SparseMap) Remove(x int) {
i := s.sparse[x]
if i < len(s.dense) && s.dense[i].Key == x {
y := s.dense[len(s.dense)-1]
s.dense[i] = y
s.sparse[y.Key] = i
s.dense = s.dense[:len(s.dense)-1]
}
}
func (s *SparseMap) Clear() {
s.dense = s.dense[:0]
}
// Contents returns a slice of key/value pairs.
// Caller must not modify any returned entries.
// The return value is invalid after the SparseMap is modified in any way.
func (s *SparseMap) Contents() []SparseMapEntry {
return s.dense
}

View File

@ -0,0 +1,84 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
import (
"cmd/internal/ssa/types" // TODO: use golang.org/x/tools/go/types instead
)
// We just inherit types from go/types
type Type types.Type
var (
// shortcuts for commonly used basic types
TypeInt = types.Typ[types.Int]
TypeUint = types.Typ[types.Uint]
TypeInt8 = types.Typ[types.Int8]
TypeInt16 = types.Typ[types.Int16]
TypeInt32 = types.Typ[types.Int32]
TypeInt64 = types.Typ[types.Int64]
TypeUint8 = types.Typ[types.Uint8]
TypeUint16 = types.Typ[types.Uint16]
TypeUint32 = types.Typ[types.Uint32]
TypeUint64 = types.Typ[types.Uint64]
TypeUintptr = types.Typ[types.Uintptr]
TypeBool = types.Typ[types.Bool]
TypeString = types.Typ[types.String]
TypeInvalid = types.Typ[types.Invalid]
// Additional compiler-only types go here.
TypeMem = &Memory{}
TypeFlags = &Flags{}
)
// typeIdentical returns whether it two arguments are the same type.
func typeIdentical(t, u Type) bool {
if t == TypeMem {
return u == TypeMem
}
if t == TypeFlags {
return u == TypeFlags
}
return types.Identical(t, u)
}
// A type representing all of memory
type Memory struct {
}
func (t *Memory) Underlying() types.Type { panic("Underlying of Memory") }
func (t *Memory) String() string { return "mem" }
// A type representing the unknown type
type Unknown struct {
}
func (t *Unknown) Underlying() types.Type { panic("Underlying of Unknown") }
func (t *Unknown) String() string { return "unk" }
// A type representing the void type. Used during building, should always
// be eliminated by the first deadcode pass.
type Void struct {
}
func (t *Void) Underlying() types.Type { panic("Underlying of Void") }
func (t *Void) String() string { return "void" }
// A type representing the results of a nil check or bounds check.
// TODO: or type check?
// TODO: just use bool?
type Check struct {
}
func (t *Check) Underlying() types.Type { panic("Underlying of Check") }
func (t *Check) String() string { return "check" }
// x86 flags type
type Flags struct {
}
func (t *Flags) Underlying() types.Type { panic("Underlying of Flags") }
func (t *Flags) String() string { return "flags" }

View File

@ -0,0 +1,39 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This package is a drop-in replacement for go/types
// for use until go/types is included in the main repo.
package types
// An Object describes a named language entity such as a package,
// constant, type, variable, function (incl. methods), or label.
// All objects implement the Object interface.
//
type Object interface {
Name() string // package local object name
Type() Type // object type
}
// An object implements the common parts of an Object.
type object struct {
name string
typ Type
}
func (obj *object) Name() string { return obj.name }
func (obj *object) Type() Type { return obj.typ }
// A Variable represents a declared variable (including function parameters and results, and struct fields).
type Var struct {
object
anonymous bool // if set, the variable is an anonymous struct field, and name is the type name
visited bool // for initialization cycle detection
isField bool // var is struct field
used bool // set if the variable was used
}
func NewParam(pos int, pkg *int, name string, typ Type) *Var {
return &Var{object: object{name, typ}, used: true} // parameters are always 'used'
}

View File

@ -0,0 +1,229 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This package is a drop-in replacement for go/types
// for use until go/types is included in the main repo.
package types
// A Type represents a type of Go.
// All types implement the Type interface.
type Type interface {
// Underlying returns the underlying type of a type.
Underlying() Type
// String returns a string representation of a type.
String() string
}
// BasicKind describes the kind of basic type.
type BasicKind int
const (
Invalid BasicKind = iota // type is invalid
// predeclared types
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
String
UnsafePointer
// types for untyped values
UntypedBool
UntypedInt
UntypedRune
UntypedFloat
UntypedComplex
UntypedString
UntypedNil
// aliases
Byte = Uint8
Rune = Int32
)
// BasicInfo is a set of flags describing properties of a basic type.
type BasicInfo int
// Properties of basic types.
const (
IsBoolean BasicInfo = 1 << iota
IsInteger
IsUnsigned
IsFloat
IsComplex
IsString
IsUntyped
IsOrdered = IsInteger | IsFloat | IsString
IsNumeric = IsInteger | IsFloat | IsComplex
IsConstType = IsBoolean | IsNumeric | IsString
)
// A Basic represents a basic type.
type Basic struct {
kind BasicKind
info BasicInfo
name string
}
// Kind returns the kind of basic type b.
func (b *Basic) Kind() BasicKind { return b.kind }
// Info returns information about properties of basic type b.
func (b *Basic) Info() BasicInfo { return b.info }
// Name returns the name of basic type b.
func (b *Basic) Name() string { return b.name }
// A Pointer represents a pointer type.
type Pointer struct {
base Type // element type
}
// NewPointer returns a new pointer type for the given element (base) type.
func NewPointer(elem Type) *Pointer { return &Pointer{base: elem} }
// Elem returns the element type for the given pointer p.
func (p *Pointer) Elem() Type { return p.base }
// A Slice represents a slice type.
type Slice struct {
elem Type
}
// NewSlice returns a new slice type for the given element type.
func NewSlice(elem Type) *Slice { return &Slice{elem} }
// Elem returns the element type of slice s.
func (s *Slice) Elem() Type { return s.elem }
// Implementations for Type methods.
func (t *Basic) Underlying() Type { return t }
func (t *Slice) Underlying() Type { return t }
func (t *Pointer) Underlying() Type { return t }
func (t *Signature) Underlying() Type { return t }
func (b *Basic) String() string { return b.name }
func (t *Slice) String() string { return "[]" + t.elem.String() }
func (t *Pointer) String() string { return "*" + t.base.String() }
func (t *Signature) String() string { return "sig" /* TODO */ }
var Typ = [...]*Basic{
Invalid: {Invalid, 0, "invalid type"},
Bool: {Bool, IsBoolean, "bool"},
Int: {Int, IsInteger, "int"},
Int8: {Int8, IsInteger, "int8"},
Int16: {Int16, IsInteger, "int16"},
Int32: {Int32, IsInteger, "int32"},
Int64: {Int64, IsInteger, "int64"},
Uint: {Uint, IsInteger | IsUnsigned, "uint"},
Uint8: {Uint8, IsInteger | IsUnsigned, "uint8"},
Uint16: {Uint16, IsInteger | IsUnsigned, "uint16"},
Uint32: {Uint32, IsInteger | IsUnsigned, "uint32"},
Uint64: {Uint64, IsInteger | IsUnsigned, "uint64"},
Uintptr: {Uintptr, IsInteger | IsUnsigned, "uintptr"},
Float32: {Float32, IsFloat, "float32"},
Float64: {Float64, IsFloat, "float64"},
Complex64: {Complex64, IsComplex, "complex64"},
Complex128: {Complex128, IsComplex, "complex128"},
String: {String, IsString, "string"},
UnsafePointer: {UnsafePointer, 0, "Pointer"},
UntypedBool: {UntypedBool, IsBoolean | IsUntyped, "untyped bool"},
UntypedInt: {UntypedInt, IsInteger | IsUntyped, "untyped int"},
UntypedRune: {UntypedRune, IsInteger | IsUntyped, "untyped rune"},
UntypedFloat: {UntypedFloat, IsFloat | IsUntyped, "untyped float"},
UntypedComplex: {UntypedComplex, IsComplex | IsUntyped, "untyped complex"},
UntypedString: {UntypedString, IsString | IsUntyped, "untyped string"},
UntypedNil: {UntypedNil, IsUntyped, "untyped nil"},
}
// Identical reports whether x and y are identical.
func Identical(x, y Type) bool {
if x == y {
return true
}
switch x := x.(type) {
case *Basic:
// Basic types are singletons except for the rune and byte
// aliases, thus we cannot solely rely on the x == y check
// above.
if y, ok := y.(*Basic); ok {
return x.kind == y.kind
}
default:
panic("can't handle yet")
}
return false
}
// A Tuple represents an ordered list of variables; a nil *Tuple is a valid (empty) tuple.
// Tuples are used as components of signatures and to represent the type of multiple
// assignments; they are not first class types of Go.
type Tuple struct {
vars []*Var
}
// NewTuple returns a new tuple for the given variables.
func NewTuple(x ...*Var) *Tuple {
if len(x) > 0 {
return &Tuple{x}
}
return nil
}
// Len returns the number variables of tuple t.
func (t *Tuple) Len() int {
if t != nil {
return len(t.vars)
}
return 0
}
// At returns the i'th variable of tuple t.
func (t *Tuple) At(i int) *Var { return t.vars[i] }
// A Signature represents a (non-builtin) function or method type.
type Signature struct {
recv *Var // nil if not a method
params *Tuple // (incoming) parameters from left to right; or nil
results *Tuple // (outgoing) results from left to right; or nil
variadic bool // true if the last parameter's type is of the form ...T (or string, for append built-in only)
}
// NewSignature returns a new function type for the given receiver, parameters,
// and results, either of which may be nil. If variadic is set, the function
// is variadic, it must have at least one parameter, and the last parameter
// must be of unnamed slice type.
func NewSignature(scope *int, recv *Var, params, results *Tuple, variadic bool) *Signature {
// TODO(gri) Should we rely on the correct (non-nil) incoming scope
// or should this function allocate and populate a scope?
if variadic {
n := params.Len()
if n == 0 {
panic("types.NewSignature: variadic function must have at least one parameter")
}
if _, ok := params.At(n - 1).typ.(*Slice); !ok {
panic("types.NewSignature: variadic parameter must be of unnamed slice type")
}
}
return &Signature{recv, params, results, variadic}
}

View File

@ -0,0 +1,117 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
import (
"fmt"
"strings"
)
// A Value represents a value in the SSA representation of the program.
// The ID and Type fields must not be modified. The remainder may be modified
// if they preserve the value of the Value (e.g. changing a (mul 2 x) to an (add x x)).
type Value struct {
// A unique identifier for the value. For performance we allocate these IDs
// densely starting at 0. There is no guarantee that there won't be occasional holes, though.
ID ID
// The operation that computes this value. See op.go.
Op Op
// The type of this value. Normally this will be a Go type, but there
// are a few other pseudo-types, see type.go.
Type Type
// Auxiliary info for this value. The type of this information depends on the opcode (& type).
Aux interface{}
// Arguments of this value
Args []*Value
// Containing basic block
Block *Block
// Storage for the first two args
argstorage [2]*Value
}
// Examples:
// Opcode aux args
// OpAdd nil 2
// OpConstStr string 0
// OpConstInt int64 0
// OpAddcq int64 1 amd64 op: v = arg[0] + constant
// short form print. Just v#.
func (v *Value) String() string {
return fmt.Sprintf("v%d", v.ID)
}
// long form print. v# = opcode <type> [aux] args [: reg]
func (v *Value) LongString() string {
s := fmt.Sprintf("v%d = %s", v.ID, strings.TrimPrefix(v.Op.String(), "Op"))
s += " <" + v.Type.String() + ">"
if v.Aux != nil {
s += fmt.Sprintf(" [%v]", v.Aux)
}
for _, a := range v.Args {
s += fmt.Sprintf(" %v", a)
}
r := v.Block.Func.RegAlloc
if r != nil && r[v.ID] != nil {
s += " : " + r[v.ID].Name()
}
return s
}
func (v *Value) AddArg(w *Value) {
v.Args = append(v.Args, w)
}
func (v *Value) AddArgs(a ...*Value) {
v.Args = append(v.Args, a...)
}
func (v *Value) SetArg(i int, w *Value) {
v.Args[i] = w
}
func (v *Value) RemoveArg(i int) {
copy(v.Args[i:], v.Args[i+1:])
v.Args = v.Args[:len(v.Args)-1]
}
func (v *Value) SetArgs1(a *Value) {
v.resetArgs()
v.AddArg(a)
}
func (v *Value) SetArgs2(a *Value, b *Value) {
v.resetArgs()
v.AddArg(a)
v.AddArg(b)
}
func (v *Value) resetArgs() {
v.argstorage[0] = nil
v.argstorage[1] = nil
v.Args = v.argstorage[:0]
}
// CopyFrom converts v to be the same value as w. v and w must
// have the same type.
func (v *Value) CopyFrom(w *Value) {
if !typeIdentical(v.Type, w.Type) {
panic("copyFrom with unequal types")
}
v.Op = w.Op
v.Aux = w.Aux
v.resetArgs()
v.AddArgs(w.Args...)
}
// SetType sets the type of v. v must not have had its type
// set yet (it must be TypeInvalid).
func (v *Value) SetType() {
if v.Type != TypeInvalid {
panic("setting type when it is already set")
}
opcodeTable[v.Op].typer(v)
}