mirror of
https://github.com/golang/go
synced 2024-10-06 07:21:22 -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:
parent
de7f6c77bc
commit
f52b234579
92
src/cmd/internal/ssa/block.go
Normal file
92
src/cmd/internal/ssa/block.go
Normal 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
|
||||
}
|
16
src/cmd/internal/ssa/blockkind_string.go
Normal file
16
src/cmd/internal/ssa/blockkind_string.go
Normal 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]]
|
||||
}
|
125
src/cmd/internal/ssa/check.go
Normal file
125
src/cmd/internal/ssa/check.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
65
src/cmd/internal/ssa/compile.go
Normal file
65
src/cmd/internal/ssa/compile.go
Normal 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},
|
||||
}
|
29
src/cmd/internal/ssa/copyelim.go
Normal file
29
src/cmd/internal/ssa/copyelim.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
153
src/cmd/internal/ssa/deadcode.go
Normal file
153
src/cmd/internal/ssa/deadcode.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
112
src/cmd/internal/ssa/deadcode_test.go
Normal file
112
src/cmd/internal/ssa/deadcode_test.go
Normal 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)
|
||||
}
|
9
src/cmd/internal/ssa/export_test.go
Normal file
9
src/cmd/internal/ssa/export_test.go
Normal 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
|
61
src/cmd/internal/ssa/func.go
Normal file
61
src/cmd/internal/ssa/func.go
Normal 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)
|
||||
}
|
41
src/cmd/internal/ssa/id.go
Normal file
41
src/cmd/internal/ssa/id.go
Normal 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)
|
||||
}
|
42
src/cmd/internal/ssa/location.go
Normal file
42
src/cmd/internal/ssa/location.go
Normal 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
345
src/cmd/internal/ssa/op.go
Normal 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)
|
||||
}
|
||||
}
|
16
src/cmd/internal/ssa/op_string.go
Normal file
16
src/cmd/internal/ssa/op_string.go
Normal 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]]
|
||||
}
|
44
src/cmd/internal/ssa/phielim.go
Normal file
44
src/cmd/internal/ssa/phielim.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
63
src/cmd/internal/ssa/print.go
Normal file
63
src/cmd/internal/ssa/print.go
Normal 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())
|
||||
}
|
||||
}
|
60
src/cmd/internal/ssa/sparseset.go
Normal file
60
src/cmd/internal/ssa/sparseset.go
Normal 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
1
src/cmd/internal/ssa/ssac/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
ssac
|
46
src/cmd/internal/ssa/ssac/fib.goir
Normal file
46
src/cmd/internal/ssa/ssac/fib.goir
Normal 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)
|
62
src/cmd/internal/ssa/ssac/fibiter.goir
Normal file
62
src/cmd/internal/ssa/ssac/fibiter.goir
Normal 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)
|
436
src/cmd/internal/ssa/ssac/main.go
Normal file
436
src/cmd/internal/ssa/ssac/main.go
Normal 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")
|
||||
}
|
82
src/cmd/internal/ssa/ssac/sexpr.go
Normal file
82
src/cmd/internal/ssa/ssac/sexpr.go
Normal 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++
|
||||
}
|
||||
}
|
69
src/cmd/internal/ssa/ssac/sparsemap.go
Normal file
69
src/cmd/internal/ssa/ssac/sparsemap.go
Normal 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
|
||||
}
|
84
src/cmd/internal/ssa/type.go
Normal file
84
src/cmd/internal/ssa/type.go
Normal 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" }
|
39
src/cmd/internal/ssa/types/object.go
Normal file
39
src/cmd/internal/ssa/types/object.go
Normal 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'
|
||||
}
|
229
src/cmd/internal/ssa/types/type.go
Normal file
229
src/cmd/internal/ssa/types/type.go
Normal 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}
|
||||
}
|
117
src/cmd/internal/ssa/value.go
Normal file
117
src/cmd/internal/ssa/value.go
Normal 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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user