From f52b234579abb8045da366c8fd5f24e0cf2c7ed2 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 3 Mar 2015 13:38:14 -0800 Subject: [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 --- src/cmd/internal/ssa/block.go | 92 +++++++ src/cmd/internal/ssa/blockkind_string.go | 16 ++ src/cmd/internal/ssa/check.go | 125 +++++++++ src/cmd/internal/ssa/compile.go | 65 +++++ src/cmd/internal/ssa/copyelim.go | 29 ++ src/cmd/internal/ssa/deadcode.go | 153 +++++++++++ src/cmd/internal/ssa/deadcode_test.go | 112 ++++++++ src/cmd/internal/ssa/export_test.go | 9 + src/cmd/internal/ssa/func.go | 61 +++++ src/cmd/internal/ssa/id.go | 41 +++ src/cmd/internal/ssa/location.go | 42 +++ src/cmd/internal/ssa/op.go | 345 ++++++++++++++++++++++++ src/cmd/internal/ssa/op_string.go | 16 ++ src/cmd/internal/ssa/phielim.go | 44 ++++ src/cmd/internal/ssa/print.go | 63 +++++ src/cmd/internal/ssa/sparseset.go | 60 +++++ src/cmd/internal/ssa/ssac/.gitignore | 1 + src/cmd/internal/ssa/ssac/fib.goir | 46 ++++ src/cmd/internal/ssa/ssac/fibiter.goir | 62 +++++ src/cmd/internal/ssa/ssac/main.go | 436 +++++++++++++++++++++++++++++++ src/cmd/internal/ssa/ssac/sexpr.go | 82 ++++++ src/cmd/internal/ssa/ssac/sparsemap.go | 69 +++++ src/cmd/internal/ssa/type.go | 84 ++++++ src/cmd/internal/ssa/types/object.go | 39 +++ src/cmd/internal/ssa/types/type.go | 229 ++++++++++++++++ src/cmd/internal/ssa/value.go | 117 +++++++++ 26 files changed, 2438 insertions(+) create mode 100644 src/cmd/internal/ssa/block.go create mode 100644 src/cmd/internal/ssa/blockkind_string.go create mode 100644 src/cmd/internal/ssa/check.go create mode 100644 src/cmd/internal/ssa/compile.go create mode 100644 src/cmd/internal/ssa/copyelim.go create mode 100644 src/cmd/internal/ssa/deadcode.go create mode 100644 src/cmd/internal/ssa/deadcode_test.go create mode 100644 src/cmd/internal/ssa/export_test.go create mode 100644 src/cmd/internal/ssa/func.go create mode 100644 src/cmd/internal/ssa/id.go create mode 100644 src/cmd/internal/ssa/location.go create mode 100644 src/cmd/internal/ssa/op.go create mode 100644 src/cmd/internal/ssa/op_string.go create mode 100644 src/cmd/internal/ssa/phielim.go create mode 100644 src/cmd/internal/ssa/print.go create mode 100644 src/cmd/internal/ssa/sparseset.go create mode 100644 src/cmd/internal/ssa/ssac/.gitignore create mode 100644 src/cmd/internal/ssa/ssac/fib.goir create mode 100644 src/cmd/internal/ssa/ssac/fibiter.goir create mode 100644 src/cmd/internal/ssa/ssac/main.go create mode 100644 src/cmd/internal/ssa/ssac/sexpr.go create mode 100644 src/cmd/internal/ssa/ssac/sparsemap.go create mode 100644 src/cmd/internal/ssa/type.go create mode 100644 src/cmd/internal/ssa/types/object.go create mode 100644 src/cmd/internal/ssa/types/type.go create mode 100644 src/cmd/internal/ssa/value.go (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/ssa/block.go b/src/cmd/internal/ssa/block.go new file mode 100644 index 0000000000..ff1cb1b30a --- /dev/null +++ b/src/cmd/internal/ssa/block.go @@ -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 +} diff --git a/src/cmd/internal/ssa/blockkind_string.go b/src/cmd/internal/ssa/blockkind_string.go new file mode 100644 index 0000000000..6204f1948f --- /dev/null +++ b/src/cmd/internal/ssa/blockkind_string.go @@ -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]] +} diff --git a/src/cmd/internal/ssa/check.go b/src/cmd/internal/ssa/check.go new file mode 100644 index 0000000000..b501cdb54c --- /dev/null +++ b/src/cmd/internal/ssa/check.go @@ -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) + } + } +} diff --git a/src/cmd/internal/ssa/compile.go b/src/cmd/internal/ssa/compile.go new file mode 100644 index 0000000000..5e21bdf6e1 --- /dev/null +++ b/src/cmd/internal/ssa/compile.go @@ -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}, +} diff --git a/src/cmd/internal/ssa/copyelim.go b/src/cmd/internal/ssa/copyelim.go new file mode 100644 index 0000000000..10c2dcc440 --- /dev/null +++ b/src/cmd/internal/ssa/copyelim.go @@ -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 + } + } +} diff --git a/src/cmd/internal/ssa/deadcode.go b/src/cmd/internal/ssa/deadcode.go new file mode 100644 index 0000000000..1647ea955d --- /dev/null +++ b/src/cmd/internal/ssa/deadcode.go @@ -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 + } + } +} diff --git a/src/cmd/internal/ssa/deadcode_test.go b/src/cmd/internal/ssa/deadcode_test.go new file mode 100644 index 0000000000..94fc359af7 --- /dev/null +++ b/src/cmd/internal/ssa/deadcode_test.go @@ -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) +} diff --git a/src/cmd/internal/ssa/export_test.go b/src/cmd/internal/ssa/export_test.go new file mode 100644 index 0000000000..ab4ab82345 --- /dev/null +++ b/src/cmd/internal/ssa/export_test.go @@ -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 diff --git a/src/cmd/internal/ssa/func.go b/src/cmd/internal/ssa/func.go new file mode 100644 index 0000000000..6868e3d1ed --- /dev/null +++ b/src/cmd/internal/ssa/func.go @@ -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) +} diff --git a/src/cmd/internal/ssa/id.go b/src/cmd/internal/ssa/id.go new file mode 100644 index 0000000000..43f23c838c --- /dev/null +++ b/src/cmd/internal/ssa/id.go @@ -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) +} diff --git a/src/cmd/internal/ssa/location.go b/src/cmd/internal/ssa/location.go new file mode 100644 index 0000000000..94c1b426a2 --- /dev/null +++ b/src/cmd/internal/ssa/location.go @@ -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 +} diff --git a/src/cmd/internal/ssa/op.go b/src/cmd/internal/ssa/op.go new file mode 100644 index 0000000000..a4364b1c5c --- /dev/null +++ b/src/cmd/internal/ssa/op.go @@ -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) + } +} diff --git a/src/cmd/internal/ssa/op_string.go b/src/cmd/internal/ssa/op_string.go new file mode 100644 index 0000000000..40051eb321 --- /dev/null +++ b/src/cmd/internal/ssa/op_string.go @@ -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]] +} diff --git a/src/cmd/internal/ssa/phielim.go b/src/cmd/internal/ssa/phielim.go new file mode 100644 index 0000000000..19c0d077e5 --- /dev/null +++ b/src/cmd/internal/ssa/phielim.go @@ -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) + } + } + } +} diff --git a/src/cmd/internal/ssa/print.go b/src/cmd/internal/ssa/print.go new file mode 100644 index 0000000000..eeea30d970 --- /dev/null +++ b/src/cmd/internal/ssa/print.go @@ -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()) + } +} diff --git a/src/cmd/internal/ssa/sparseset.go b/src/cmd/internal/ssa/sparseset.go new file mode 100644 index 0000000000..e1f9a9a81d --- /dev/null +++ b/src/cmd/internal/ssa/sparseset.go @@ -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 +} diff --git a/src/cmd/internal/ssa/ssac/.gitignore b/src/cmd/internal/ssa/ssac/.gitignore new file mode 100644 index 0000000000..ab17b9d28e --- /dev/null +++ b/src/cmd/internal/ssa/ssac/.gitignore @@ -0,0 +1 @@ +ssac diff --git a/src/cmd/internal/ssa/ssac/fib.goir b/src/cmd/internal/ssa/ssac/fib.goir new file mode 100644 index 0000000000..b572cdaa3a --- /dev/null +++ b/src/cmd/internal/ssa/ssac/fib.goir @@ -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) diff --git a/src/cmd/internal/ssa/ssac/fibiter.goir b/src/cmd/internal/ssa/ssac/fibiter.goir new file mode 100644 index 0000000000..43c7a3de91 --- /dev/null +++ b/src/cmd/internal/ssa/ssac/fibiter.goir @@ -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) diff --git a/src/cmd/internal/ssa/ssac/main.go b/src/cmd/internal/ssa/ssac/main.go new file mode 100644 index 0000000000..4975b50db4 --- /dev/null +++ b/src/cmd/internal/ssa/ssac/main.go @@ -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") +} diff --git a/src/cmd/internal/ssa/ssac/sexpr.go b/src/cmd/internal/ssa/ssac/sexpr.go new file mode 100644 index 0000000000..77e8923dd0 --- /dev/null +++ b/src/cmd/internal/ssa/ssac/sexpr.go @@ -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++ + } +} diff --git a/src/cmd/internal/ssa/ssac/sparsemap.go b/src/cmd/internal/ssa/ssac/sparsemap.go new file mode 100644 index 0000000000..b7a0fb0fde --- /dev/null +++ b/src/cmd/internal/ssa/ssac/sparsemap.go @@ -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 +} diff --git a/src/cmd/internal/ssa/type.go b/src/cmd/internal/ssa/type.go new file mode 100644 index 0000000000..3389622c74 --- /dev/null +++ b/src/cmd/internal/ssa/type.go @@ -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" } diff --git a/src/cmd/internal/ssa/types/object.go b/src/cmd/internal/ssa/types/object.go new file mode 100644 index 0000000000..cd0be163b7 --- /dev/null +++ b/src/cmd/internal/ssa/types/object.go @@ -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' +} diff --git a/src/cmd/internal/ssa/types/type.go b/src/cmd/internal/ssa/types/type.go new file mode 100644 index 0000000000..e01de5c1e4 --- /dev/null +++ b/src/cmd/internal/ssa/types/type.go @@ -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} +} diff --git a/src/cmd/internal/ssa/value.go b/src/cmd/internal/ssa/value.go new file mode 100644 index 0000000000..740525a5f5 --- /dev/null +++ b/src/cmd/internal/ssa/value.go @@ -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 [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) +} -- cgit v1.3 From 7c2c0b4e533d3d75df8993eb87f6948c49c04cc8 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Mon, 16 Mar 2015 16:31:13 -0700 Subject: [dev.ssa] cmd/internal/ssa: minor cleanup These were review comments for CL 6681 that didn't get sent in time. Change-Id: If161af3655770487f3ba34535d3fb55dbfde7917 Reviewed-on: https://go-review.googlesource.com/7644 Reviewed-by: Keith Randall --- src/cmd/internal/ssa/deadcode.go | 13 ++++++++----- src/cmd/internal/ssa/op.go | 2 +- src/cmd/internal/ssa/type.go | 2 +- src/cmd/internal/ssa/value.go | 9 ++++++++- 4 files changed, 18 insertions(+), 8 deletions(-) (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/ssa/deadcode.go b/src/cmd/internal/ssa/deadcode.go index 1647ea955d..e8c8bfcc03 100644 --- a/src/cmd/internal/ssa/deadcode.go +++ b/src/cmd/internal/ssa/deadcode.go @@ -86,8 +86,10 @@ func deadcode(f *Func) { f.vid.put(v.ID) } } - for j := i; j < len(b.Values); j++ { - b.Values[j] = nil // aid GC + // aid GC + tail := b.Values[i:] + for j := range tail { + tail[j] = nil } b.Values = b.Values[:i] } @@ -105,9 +107,10 @@ func deadcode(f *Func) { f.bid.put(b.ID) } } - // zero remainder to help gc - for j := i; j < len(f.Blocks); j++ { - f.Blocks[j] = nil + // zero remainder to help GC + tail := f.Blocks[i:] + for j := range tail { + tail[j] = nil } f.Blocks = f.Blocks[:i] diff --git a/src/cmd/internal/ssa/op.go b/src/cmd/internal/ssa/op.go index a4364b1c5c..905d62b69c 100644 --- a/src/cmd/internal/ssa/op.go +++ b/src/cmd/internal/ssa/op.go @@ -82,7 +82,7 @@ const ( OpStoreFP OpStoreSP - // spill&restore ops for the register allocator. These are + // spill and 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. diff --git a/src/cmd/internal/ssa/type.go b/src/cmd/internal/ssa/type.go index 3389622c74..e9c017d38a 100644 --- a/src/cmd/internal/ssa/type.go +++ b/src/cmd/internal/ssa/type.go @@ -34,7 +34,7 @@ var ( TypeFlags = &Flags{} ) -// typeIdentical returns whether it two arguments are the same type. +// typeIdentical reports whether its two arguments are the same type. func typeIdentical(t, u Type) bool { if t == TypeMem { return u == TypeMem diff --git a/src/cmd/internal/ssa/value.go b/src/cmd/internal/ssa/value.go index 740525a5f5..f6f099cd32 100644 --- a/src/cmd/internal/ssa/value.go +++ b/src/cmd/internal/ssa/value.go @@ -24,7 +24,7 @@ type Value struct { // 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). + // Auxiliary info for this value. The type of this information depends on the opcode and type. Aux interface{} // Arguments of this value @@ -67,9 +67,15 @@ func (v *Value) LongString() string { } func (v *Value) AddArg(w *Value) { + if v.Args == nil { + v.resetArgs() // use argstorage + } v.Args = append(v.Args, w) } func (v *Value) AddArgs(a ...*Value) { + if v.Args == nil { + v.resetArgs() // use argstorage + } v.Args = append(v.Args, a...) } func (v *Value) SetArg(i int, w *Value) { @@ -77,6 +83,7 @@ func (v *Value) SetArg(i int, w *Value) { } func (v *Value) RemoveArg(i int) { copy(v.Args[i:], v.Args[i+1:]) + v.Args[len(v.Args)-1] = nil // aid GC v.Args = v.Args[:len(v.Args)-1] } func (v *Value) SetArgs1(a *Value) { -- cgit v1.3 From 7b9628429553b2bce59cd292c0894a2276c54245 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Mon, 23 Mar 2015 17:02:11 -0700 Subject: [dev.ssa] cmd/internal/ssa: implement more compiler passes opt: machine-independent optimization fuse: join basic blocks lower: convert to machine-dependent opcodes critical: remove critical edges for register alloc layout: order basic blocks schedule: order values in basic blocks cgen: generate assembly output opt and lower use machine-generated matching rules using the rule generator in rulegen/ cgen will probably change in the real compiler, as we want to generate binary directly instead of ascii assembly. Change-Id: Iedd7ca70f6f55a4cde30e27cfad6a7fa05691b83 Reviewed-on: https://go-review.googlesource.com/7981 Reviewed-by: Alan Donovan Reviewed-by: Keith Randall --- src/cmd/internal/ssa/cgen.go | 117 +++++++++ src/cmd/internal/ssa/compile.go | 58 ++++- src/cmd/internal/ssa/critical.go | 51 ++++ src/cmd/internal/ssa/fuse.go | 40 +++ src/cmd/internal/ssa/generic.go | 111 +++++++++ src/cmd/internal/ssa/layout.go | 88 +++++++ src/cmd/internal/ssa/lower.go | 43 ++++ src/cmd/internal/ssa/lowerAmd64.go | 307 +++++++++++++++++++++++ src/cmd/internal/ssa/op.go | 25 +- src/cmd/internal/ssa/op_string.go | 4 +- src/cmd/internal/ssa/opt.go | 13 + src/cmd/internal/ssa/rewrite.go | 70 ++++++ src/cmd/internal/ssa/rulegen/generic.rules | 16 ++ src/cmd/internal/ssa/rulegen/lower_amd64.rules | 46 ++++ src/cmd/internal/ssa/rulegen/rulegen.go | 328 +++++++++++++++++++++++++ src/cmd/internal/ssa/schedule.go | 69 ++++++ src/cmd/internal/ssa/ssac/fib.goir | 5 +- src/cmd/internal/ssa/ssac/fibiter.goir | 4 +- 18 files changed, 1372 insertions(+), 23 deletions(-) create mode 100644 src/cmd/internal/ssa/cgen.go create mode 100644 src/cmd/internal/ssa/critical.go create mode 100644 src/cmd/internal/ssa/fuse.go create mode 100644 src/cmd/internal/ssa/generic.go create mode 100644 src/cmd/internal/ssa/layout.go create mode 100644 src/cmd/internal/ssa/lower.go create mode 100644 src/cmd/internal/ssa/lowerAmd64.go create mode 100644 src/cmd/internal/ssa/opt.go create mode 100644 src/cmd/internal/ssa/rewrite.go create mode 100644 src/cmd/internal/ssa/rulegen/generic.rules create mode 100644 src/cmd/internal/ssa/rulegen/lower_amd64.rules create mode 100644 src/cmd/internal/ssa/rulegen/rulegen.go create mode 100644 src/cmd/internal/ssa/schedule.go (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/ssa/cgen.go b/src/cmd/internal/ssa/cgen.go new file mode 100644 index 0000000000..4b1a90b89d --- /dev/null +++ b/src/cmd/internal/ssa/cgen.go @@ -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" + +// cgen selects machine instructions for the function. +// This pass generates assembly output for now, but should +// TODO(khr): generate binary output (via liblink?) instead of text. +func cgen(f *Func) { + fmt.Printf("TEXT %s(SB),0,$0\n", f.Name) // TODO: frame size / arg size + + // TODO: prolog, allocate stack frame + + // hack for now, until regalloc is done + f.RegAlloc = make([]Location, f.NumValues()) + + for idx, b := range f.Blocks { + fmt.Printf("%d:\n", b.ID) + for _, v := range b.Values { + asm := opcodeTable[v.Op].asm + fmt.Print("\t") + if asm == "" { + fmt.Print("\t") + } + for i := 0; i < len(asm); i++ { + switch asm[i] { + default: + fmt.Printf("%c", asm[i]) + case '%': + i++ + switch asm[i] { + case '%': + fmt.Print("%") + case 'I': + i++ + n := asm[i] - '0' + if f.RegAlloc[v.Args[n].ID] != nil { + fmt.Print(f.RegAlloc[v.Args[n].ID].Name()) + } else { + fmt.Printf("v%d", v.Args[n].ID) + } + case 'O': + i++ + n := asm[i] - '0' + if n != 0 { + panic("can only handle 1 output for now") + } + if f.RegAlloc[v.ID] != nil { + // TODO: output tuple + fmt.Print(f.RegAlloc[v.ID].Name()) + } else { + fmt.Printf("v%d", v.ID) + } + case 'A': + fmt.Print(v.Aux) + } + } + } + fmt.Println("\t; " + v.LongString()) + } + // find next block in layout sequence + var next *Block + if idx < len(f.Blocks)-1 { + next = f.Blocks[idx+1] + } + // emit end of block code + // TODO: this is machine specific + switch b.Kind { + case BlockPlain: + if b.Succs[0] != next { + fmt.Printf("\tJMP\t%d\n", b.Succs[0].ID) + } + case BlockExit: + // TODO: run defers (if any) + // TODO: deallocate frame + fmt.Println("\tRET") + case BlockCall: + // nothing to emit - call instruction already happened + case BlockEQ: + if b.Succs[0] == next { + fmt.Printf("\tJNE\t%d\n", b.Succs[1].ID) + } else if b.Succs[1] == next { + fmt.Printf("\tJEQ\t%d\n", b.Succs[0].ID) + } else { + fmt.Printf("\tJEQ\t%d\n", b.Succs[0].ID) + fmt.Printf("\tJMP\t%d\n", b.Succs[1].ID) + } + case BlockNE: + if b.Succs[0] == next { + fmt.Printf("\tJEQ\t%d\n", b.Succs[1].ID) + } else if b.Succs[1] == next { + fmt.Printf("\tJNE\t%d\n", b.Succs[0].ID) + } else { + fmt.Printf("\tJNE\t%d\n", b.Succs[0].ID) + fmt.Printf("\tJMP\t%d\n", b.Succs[1].ID) + } + case BlockLT: + if b.Succs[0] == next { + fmt.Printf("\tJGE\t%d\n", b.Succs[1].ID) + } else if b.Succs[1] == next { + fmt.Printf("\tJLT\t%d\n", b.Succs[0].ID) + } else { + fmt.Printf("\tJLT\t%d\n", b.Succs[0].ID) + fmt.Printf("\tJMP\t%d\n", b.Succs[1].ID) + } + default: + fmt.Printf("\t%s ->", b.Kind.String()) + for _, s := range b.Succs { + fmt.Printf(" %d", s.ID) + } + fmt.Printf("\n") + } + } +} diff --git a/src/cmd/internal/ssa/compile.go b/src/cmd/internal/ssa/compile.go index 5e21bdf6e1..b8f34c52fc 100644 --- a/src/cmd/internal/ssa/compile.go +++ b/src/cmd/internal/ssa/compile.go @@ -4,7 +4,10 @@ package ssa -import "fmt" +import ( + "fmt" + "log" +) // Compile is the main entry point for this package. // Compile modifies f so that on return: @@ -50,16 +53,55 @@ type pass struct { var passes = [...]pass{ {"phielim", phielim}, {"copyelim", copyelim}, - //{"opt", opt}, + {"opt", opt}, // cse {"deadcode", deadcode}, - //{"fuse", fuse}, - //{"lower", lower}, + {"fuse", fuse}, + {"lower", lower}, // cse - //{"critical", critical}, // remove critical edges - //{"layout", layout}, // schedule blocks - //{"schedule", schedule}, // schedule values + {"critical", critical}, // remove critical edges + {"layout", layout}, // schedule blocks + {"schedule", schedule}, // schedule values // regalloc // stack slot alloc (+size stack frame) - //{"cgen", cgen}, + {"cgen", cgen}, +} + +// Double-check phase ordering constraints. +// This code is intended to document the ordering requirements +// between different phases. It does not override the passes +// list above. +var passOrder = map[string]string{ + // don't layout blocks until critical edges have been removed + "critical": "layout", + // regalloc requires the removal of all critical edges + //"critical": "regalloc", + // regalloc requires all the values in a block to be scheduled + //"schedule": "regalloc", + // code generation requires register allocation + //"cgen":"regalloc", +} + +func init() { + for a, b := range passOrder { + i := -1 + j := -1 + for k, p := range passes { + if p.name == a { + i = k + } + if p.name == b { + j = k + } + } + if i < 0 { + log.Panicf("pass %s not found", a) + } + if j < 0 { + log.Panicf("pass %s not found", b) + } + if i >= j { + log.Panicf("passes %s and %s out of order", a, b) + } + } } diff --git a/src/cmd/internal/ssa/critical.go b/src/cmd/internal/ssa/critical.go new file mode 100644 index 0000000000..5bbad8f2f5 --- /dev/null +++ b/src/cmd/internal/ssa/critical.go @@ -0,0 +1,51 @@ +// 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 + +// critical splits critical edges (those that go from a block with +// more than one outedge to a block with more than one inedge). +// Regalloc wants a critical-edge-free CFG so it can implement phi values. +func critical(f *Func) { + for _, b := range f.Blocks { + if len(b.Preds) <= 1 { + continue + } + + // decide if we need to split edges coming into b. + hasphi := false + for _, v := range b.Values { + if v.Op == OpPhi && v.Type != TypeMem { + hasphi = true + break + } + } + if !hasphi { + // no splitting needed + continue + } + + // split input edges coming from multi-output blocks. + for i, c := range b.Preds { + if c.Kind == BlockPlain { + continue + } + + // allocate a new block to place on the edge + d := f.NewBlock(BlockPlain) + + // splice it in + d.Preds = append(d.Preds, c) + d.Succs = append(d.Succs, b) + b.Preds[i] = d + // replace b with d in c's successor list. + for j, b2 := range c.Succs { + if b2 == b { + c.Succs[j] = d + break + } + } + } + } +} diff --git a/src/cmd/internal/ssa/fuse.go b/src/cmd/internal/ssa/fuse.go new file mode 100644 index 0000000000..bfce9ef970 --- /dev/null +++ b/src/cmd/internal/ssa/fuse.go @@ -0,0 +1,40 @@ +// 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 + +// fuse simplifies control flow by joining basic blocks. +func fuse(f *Func) { + for _, b := range f.Blocks { + if b.Kind != BlockPlain { + continue + } + c := b.Succs[0] + if len(c.Preds) != 1 { + continue + } + + // move all of b's values to c. + for _, v := range b.Values { + v.Block = c + c.Values = append(c.Values, v) + } + + // replace b->c edge with preds(b) -> c + c.Preds = b.Preds + for _, p := range c.Preds { + for i, q := range p.Succs { + if q == b { + p.Succs[i] = c + } + } + } + + // trash b, just in case + b.Kind = BlockUnknown + b.Values = nil + b.Preds = nil + b.Succs = nil + } +} diff --git a/src/cmd/internal/ssa/generic.go b/src/cmd/internal/ssa/generic.go new file mode 100644 index 0000000000..f28633b19a --- /dev/null +++ b/src/cmd/internal/ssa/generic.go @@ -0,0 +1,111 @@ +// autogenerated from rulegen/generic.rules: do not edit! +// generated with: go run rulegen/rulegen.go rulegen/generic.rules genericRules generic.go +package ssa + +func genericRules(v *Value) bool { + switch v.Op { + case OpAdd: + // match: (Add (ConstInt [c]) (ConstInt [d])) + // cond: is64BitInt(t) + // result: (ConstInt [{c.(int64)+d.(int64)}]) + { + t := v.Type + if v.Args[0].Op != OpConstInt { + goto end0 + } + c := v.Args[0].Aux + if v.Args[1].Op != OpConstInt { + goto end0 + } + d := v.Args[1].Aux + if !(is64BitInt(t)) { + goto end0 + } + v.Op = OpConstInt + v.Aux = nil + v.Args = v.argstorage[:0] + v.Aux = c.(int64) + d.(int64) + return true + } + end0: + ; + case OpLoad: + // match: (Load (FPAddr [offset]) mem) + // cond: + // result: (LoadFP [offset] mem) + { + if v.Args[0].Op != OpFPAddr { + goto end1 + } + offset := v.Args[0].Aux + mem := v.Args[1] + v.Op = OpLoadFP + v.Aux = nil + v.Args = v.argstorage[:0] + v.Aux = offset + v.AddArg(mem) + return true + } + end1: + ; + // match: (Load (SPAddr [offset]) mem) + // cond: + // result: (LoadSP [offset] mem) + { + if v.Args[0].Op != OpSPAddr { + goto end2 + } + offset := v.Args[0].Aux + mem := v.Args[1] + v.Op = OpLoadSP + v.Aux = nil + v.Args = v.argstorage[:0] + v.Aux = offset + v.AddArg(mem) + return true + } + end2: + ; + case OpStore: + // match: (Store (FPAddr [offset]) val mem) + // cond: + // result: (StoreFP [offset] val mem) + { + if v.Args[0].Op != OpFPAddr { + goto end3 + } + offset := v.Args[0].Aux + val := v.Args[1] + mem := v.Args[2] + v.Op = OpStoreFP + v.Aux = nil + v.Args = v.argstorage[:0] + v.Aux = offset + v.AddArg(val) + v.AddArg(mem) + return true + } + end3: + ; + // match: (Store (SPAddr [offset]) val mem) + // cond: + // result: (StoreSP [offset] val mem) + { + if v.Args[0].Op != OpSPAddr { + goto end4 + } + offset := v.Args[0].Aux + val := v.Args[1] + mem := v.Args[2] + v.Op = OpStoreSP + v.Aux = nil + v.Args = v.argstorage[:0] + v.Aux = offset + v.AddArg(val) + v.AddArg(mem) + return true + } + end4: + } + return false +} diff --git a/src/cmd/internal/ssa/layout.go b/src/cmd/internal/ssa/layout.go new file mode 100644 index 0000000000..7123397c4c --- /dev/null +++ b/src/cmd/internal/ssa/layout.go @@ -0,0 +1,88 @@ +// 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" + +// layout orders basic blocks in f with the goal of minimizing control flow instructions. +// After this phase returns, the order of f.Blocks matters and is the order +// in which those blocks will appear in the assembly output. +func layout(f *Func) { + order := make([]*Block, 0, f.NumBlocks()) + scheduled := make([]bool, f.NumBlocks()) + idToBlock := make([]*Block, f.NumBlocks()) + indegree := make([]int, f.NumBlocks()) + posdegree := newSparseSet(f.NumBlocks()) // blocks with positive remaining degree + zerodegree := newSparseSet(f.NumBlocks()) // blocks with zero remaining degree + + // Initialize indegree of each block + for _, b := range f.Blocks { + idToBlock[b.ID] = b + indegree[b.ID] = len(b.Preds) + if len(b.Preds) == 0 { + zerodegree.add(b.ID) + } else { + posdegree.add(b.ID) + } + } + + bid := f.Entry.ID +blockloop: + for { + // add block to schedule + b := idToBlock[bid] + order = append(order, b) + scheduled[bid] = true + if len(order) == len(f.Blocks) { + break + } + + for _, c := range b.Succs { + indegree[c.ID]-- + if indegree[c.ID] == 0 { + posdegree.remove(c.ID) + zerodegree.add(c.ID) + } + } + + // Pick the next block to schedule + // Pick among the successor blocks that have not been scheduled yet. + // Just use degree for now. TODO(khr): use likely direction hints. + bid = 0 + mindegree := f.NumBlocks() + for _, c := range order[len(order)-1].Succs { + if scheduled[c.ID] { + continue + } + if indegree[c.ID] < mindegree { + mindegree = indegree[c.ID] + bid = c.ID + } + } + if bid != 0 { + continue + } + // TODO: improve this part + // No successor of the previously scheduled block works. + // Pick a zero-degree block if we can. + for zerodegree.size() > 0 { + cid := zerodegree.pop() + if !scheduled[cid] { + bid = cid + continue blockloop + } + } + // Still nothing, pick any block. + for { + cid := posdegree.pop() + if !scheduled[cid] { + bid = cid + continue blockloop + } + } + log.Panicf("no block available for layout") + } + f.Blocks = order +} diff --git a/src/cmd/internal/ssa/lower.go b/src/cmd/internal/ssa/lower.go new file mode 100644 index 0000000000..7d97b0b466 --- /dev/null +++ b/src/cmd/internal/ssa/lower.go @@ -0,0 +1,43 @@ +// 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 ( + // TODO(khr): put arch configuration constants together somewhere + intSize = 8 + ptrSize = 8 +) + +//go:generate go run rulegen/rulegen.go rulegen/lower_amd64.rules lowerAmd64 lowerAmd64.go + +// convert to machine-dependent ops +func lower(f *Func) { + // repeat rewrites until we find no more rewrites + // TODO: pick the target arch from config + applyRewrite(f, lowerAmd64) + + // TODO: check for unlowered opcodes, fail if we find one + + // additional pass for 386/amd64, link condition codes directly to blocks + // TODO: do generically somehow? Special "block" rewrite rules? + for _, b := range f.Blocks { + switch b.Kind { + case BlockIf: + switch b.Control.Op { + case OpSETL: + b.Kind = BlockLT + b.Control = b.Control.Args[0] + // TODO: others + } + case BlockLT: + if b.Control.Op == OpInvertFlags { + b.Kind = BlockGE + b.Control = b.Control.Args[0] + } + // TODO: others + } + } + deadcode(f) // TODO: separate pass? +} diff --git a/src/cmd/internal/ssa/lowerAmd64.go b/src/cmd/internal/ssa/lowerAmd64.go new file mode 100644 index 0000000000..ab79ed09b1 --- /dev/null +++ b/src/cmd/internal/ssa/lowerAmd64.go @@ -0,0 +1,307 @@ +// autogenerated from rulegen/lower_amd64.rules: do not edit! +// generated with: go run rulegen/rulegen.go rulegen/lower_amd64.rules lowerAmd64 lowerAmd64.go +package ssa + +func lowerAmd64(v *Value) bool { + switch v.Op { + case OpADDQ: + // match: (ADDQ x (ConstInt [c])) + // cond: + // result: (ADDCQ [c] x) + { + x := v.Args[0] + if v.Args[1].Op != OpConstInt { + goto end0 + } + c := v.Args[1].Aux + v.Op = OpADDCQ + v.Aux = nil + v.Args = v.argstorage[:0] + v.Aux = c + v.AddArg(x) + return true + } + end0: + ; + // match: (ADDQ (ConstInt [c]) x) + // cond: + // result: (ADDCQ [c] x) + { + if v.Args[0].Op != OpConstInt { + goto end1 + } + c := v.Args[0].Aux + x := v.Args[1] + v.Op = OpADDCQ + v.Aux = nil + v.Args = v.argstorage[:0] + v.Aux = c + v.AddArg(x) + return true + } + end1: + ; + case OpAdd: + // match: (Add x y) + // cond: is64BitInt(t) + // result: (ADDQ x y) + { + t := v.Type + x := v.Args[0] + y := v.Args[1] + if !(is64BitInt(t)) { + goto end2 + } + v.Op = OpADDQ + v.Aux = nil + v.Args = v.argstorage[:0] + v.AddArg(x) + v.AddArg(y) + return true + } + end2: + ; + // match: (Add x y) + // cond: is32BitInt(t) + // result: (ADDL x y) + { + t := v.Type + x := v.Args[0] + y := v.Args[1] + if !(is32BitInt(t)) { + goto end3 + } + v.Op = OpADDL + v.Aux = nil + v.Args = v.argstorage[:0] + v.AddArg(x) + v.AddArg(y) + return true + } + end3: + ; + case OpCMPQ: + // match: (CMPQ x (ConstInt [c])) + // cond: + // result: (CMPCQ x [c]) + { + x := v.Args[0] + if v.Args[1].Op != OpConstInt { + goto end4 + } + c := v.Args[1].Aux + v.Op = OpCMPCQ + v.Aux = nil + v.Args = v.argstorage[:0] + v.AddArg(x) + v.Aux = c + return true + } + end4: + ; + // match: (CMPQ (ConstInt [c]) x) + // cond: + // result: (InvertFlags (CMPCQ x [c])) + { + if v.Args[0].Op != OpConstInt { + goto end5 + } + c := v.Args[0].Aux + x := v.Args[1] + v.Op = OpInvertFlags + v.Aux = nil + v.Args = v.argstorage[:0] + v0 := v.Block.NewValue(OpCMPCQ, TypeInvalid, nil) + v0.AddArg(x) + v0.Aux = c + v0.SetType() + v.AddArg(v0) + return true + } + end5: + ; + case OpLess: + // match: (Less x y) + // cond: is64BitInt(v.Args[0].Type) && isSigned(v.Args[0].Type) + // result: (SETL (CMPQ x y)) + { + x := v.Args[0] + y := v.Args[1] + if !(is64BitInt(v.Args[0].Type) && isSigned(v.Args[0].Type)) { + goto end6 + } + v.Op = OpSETL + v.Aux = nil + v.Args = v.argstorage[:0] + v0 := v.Block.NewValue(OpCMPQ, TypeInvalid, nil) + v0.AddArg(x) + v0.AddArg(y) + v0.SetType() + v.AddArg(v0) + return true + } + end6: + ; + case OpLoadFP: + // match: (LoadFP [offset] mem) + // cond: typeSize(t) == 8 + // result: (LoadFP8 [offset] mem) + { + t := v.Type + offset := v.Aux + mem := v.Args[0] + if !(typeSize(t) == 8) { + goto end7 + } + v.Op = OpLoadFP8 + v.Aux = nil + v.Args = v.argstorage[:0] + v.Type = t + v.Aux = offset + v.AddArg(mem) + return true + } + end7: + ; + case OpLoadSP: + // match: (LoadSP [offset] mem) + // cond: typeSize(t) == 8 + // result: (LoadSP8 [offset] mem) + { + t := v.Type + offset := v.Aux + mem := v.Args[0] + if !(typeSize(t) == 8) { + goto end8 + } + v.Op = OpLoadSP8 + v.Aux = nil + v.Args = v.argstorage[:0] + v.Type = t + v.Aux = offset + v.AddArg(mem) + return true + } + end8: + ; + case OpSETL: + // match: (SETL (InvertFlags x)) + // cond: + // result: (SETGE x) + { + if v.Args[0].Op != OpInvertFlags { + goto end9 + } + x := v.Args[0].Args[0] + v.Op = OpSETGE + v.Aux = nil + v.Args = v.argstorage[:0] + v.AddArg(x) + return true + } + end9: + ; + case OpSUBQ: + // match: (SUBQ x (ConstInt [c])) + // cond: + // result: (SUBCQ x [c]) + { + x := v.Args[0] + if v.Args[1].Op != OpConstInt { + goto end10 + } + c := v.Args[1].Aux + v.Op = OpSUBCQ + v.Aux = nil + v.Args = v.argstorage[:0] + v.AddArg(x) + v.Aux = c + return true + } + end10: + ; + // match: (SUBQ (ConstInt [c]) x) + // cond: + // result: (NEGQ (SUBCQ x [c])) + { + if v.Args[0].Op != OpConstInt { + goto end11 + } + c := v.Args[0].Aux + x := v.Args[1] + v.Op = OpNEGQ + v.Aux = nil + v.Args = v.argstorage[:0] + v0 := v.Block.NewValue(OpSUBCQ, TypeInvalid, nil) + v0.AddArg(x) + v0.Aux = c + v0.SetType() + v.AddArg(v0) + return true + } + end11: + ; + case OpStoreFP: + // match: (StoreFP [offset] val mem) + // cond: typeSize(val.Type) == 8 + // result: (StoreFP8 [offset] val mem) + { + offset := v.Aux + val := v.Args[0] + mem := v.Args[1] + if !(typeSize(val.Type) == 8) { + goto end12 + } + v.Op = OpStoreFP8 + v.Aux = nil + v.Args = v.argstorage[:0] + v.Aux = offset + v.AddArg(val) + v.AddArg(mem) + return true + } + end12: + ; + case OpStoreSP: + // match: (StoreSP [offset] val mem) + // cond: typeSize(val.Type) == 8 + // result: (StoreSP8 [offset] val mem) + { + offset := v.Aux + val := v.Args[0] + mem := v.Args[1] + if !(typeSize(val.Type) == 8) { + goto end13 + } + v.Op = OpStoreSP8 + v.Aux = nil + v.Args = v.argstorage[:0] + v.Aux = offset + v.AddArg(val) + v.AddArg(mem) + return true + } + end13: + ; + case OpSub: + // match: (Sub x y) + // cond: is64BitInt(t) + // result: (SUBQ x y) + { + t := v.Type + x := v.Args[0] + y := v.Args[1] + if !(is64BitInt(t)) { + goto end14 + } + v.Op = OpSUBQ + v.Aux = nil + v.Args = v.argstorage[:0] + v.AddArg(x) + v.AddArg(y) + return true + } + end14: + } + return false +} diff --git a/src/cmd/internal/ssa/op.go b/src/cmd/internal/ssa/op.go index 905d62b69c..da69657411 100644 --- a/src/cmd/internal/ssa/op.go +++ b/src/cmd/internal/ssa/op.go @@ -62,7 +62,9 @@ const ( 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. + // Return values appear on the stack. The method receiver, if any, is treated + // as a phantom first argument. + // TODO: closure pointer must be in a register. OpCall // args are function ptr, memory OpStaticCall // aux is function, arg is memory @@ -82,33 +84,38 @@ const ( OpStoreFP OpStoreSP - // spill and 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. + // 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 without memory + // args because we know there is no aliasing of spill slots on the stack. OpStoreReg8 OpLoadReg8 // machine-dependent opcodes go here - // x86 + // amd64 OpADDQ OpSUBQ - OpADDCQ // 1 input arg, add aux which is an int64 constant + OpADDCQ // 1 input arg. output = input + aux.(int64) 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" + OpSETL // generate bool = "flags encode less than" OpSETGE + // InvertFlags reverses direction of flags register interpretation: + // (InvertFlags (OpCMPQ a b)) == (OpCMPQ b a) + // This is a pseudo-op which can't appear in assembly output. + OpInvertFlags + OpLEAQ // x+y OpLEAQ2 // x+2*y OpLEAQ4 // x+4*y OpLEAQ8 // x+8*y + // load/store 8-byte integer register from stack slot. OpLoadFP8 OpLoadSP8 OpStoreFP8 diff --git a/src/cmd/internal/ssa/op_string.go b/src/cmd/internal/ssa/op_string.go index 40051eb321..9aee7de43e 100644 --- a/src/cmd/internal/ssa/op_string.go +++ b/src/cmd/internal/ssa/op_string.go @@ -4,9 +4,9 @@ package ssa import "fmt" -const _Op_name = "OpUnknownOpNopOpThunkOpAddOpSubOpMulOpLessOpConstNilOpConstBoolOpConstStringOpConstIntOpConstFloatOpConstComplexOpArgOpGlobalOpFuncOpCopyOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpSliceOpIndexOpIndexAddrOpLoadOpStoreOpCheckNilOpCheckBoundOpCallOpStaticCallOpConvertOpConvNopOpFPAddrOpSPAddrOpLoadFPOpLoadSPOpStoreFPOpStoreSPOpStoreReg8OpLoadReg8OpADDQOpSUBQOpADDCQOpSUBCQOpNEGQOpCMPQOpCMPCQOpADDLOpInvertFlagsOpSETLOpSETGEOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpLoadFP8OpLoadSP8OpStoreFP8OpStoreSP8OpMax" +const _Op_name = "OpUnknownOpNopOpThunkOpAddOpSubOpMulOpLessOpConstNilOpConstBoolOpConstStringOpConstIntOpConstFloatOpConstComplexOpArgOpGlobalOpFuncOpCopyOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpSliceOpIndexOpIndexAddrOpLoadOpStoreOpCheckNilOpCheckBoundOpCallOpStaticCallOpConvertOpConvNopOpFPAddrOpSPAddrOpLoadFPOpLoadSPOpStoreFPOpStoreSPOpStoreReg8OpLoadReg8OpADDQOpSUBQOpADDCQOpSUBCQOpNEGQOpCMPQOpCMPCQOpADDLOpSETLOpSETGEOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpLoadFP8OpLoadSP8OpStoreFP8OpStoreSP8OpMax" -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} +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, 441, 448, 461, 467, 474, 481, 488, 497, 506, 516, 526, 531} func (i Op) String() string { if i < 0 || i+1 >= Op(len(_Op_index)) { diff --git a/src/cmd/internal/ssa/opt.go b/src/cmd/internal/ssa/opt.go new file mode 100644 index 0000000000..ea2bcf0e98 --- /dev/null +++ b/src/cmd/internal/ssa/opt.go @@ -0,0 +1,13 @@ +// 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 + +// machine-independent optimization + +//go:generate go run rulegen/rulegen.go rulegen/generic.rules genericRules generic.go + +func opt(f *Func) { + applyRewrite(f, genericRules) +} diff --git a/src/cmd/internal/ssa/rewrite.go b/src/cmd/internal/ssa/rewrite.go new file mode 100644 index 0000000000..0d7c0c1c64 --- /dev/null +++ b/src/cmd/internal/ssa/rewrite.go @@ -0,0 +1,70 @@ +// 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 +) + +func applyRewrite(f *Func, r func(*Value) bool) { + // repeat rewrites until we find no more rewrites + for { + change := false + for _, b := range f.Blocks { + for _, v := range b.Values { + if r(v) { + change = true + } + } + } + if !change { + return + } + } +} + +// Common functions called from rewriting rules + +func is64BitInt(t Type) bool { + return typeIdentical(t, TypeInt64) || + typeIdentical(t, TypeUint64) || + (typeIdentical(t, TypeInt) && intSize == 8) || + (typeIdentical(t, TypeUint) && intSize == 8) || + (typeIdentical(t, TypeUintptr) && ptrSize == 8) +} + +func is32BitInt(t Type) bool { + return typeIdentical(t, TypeInt32) || + typeIdentical(t, TypeUint32) || + (typeIdentical(t, TypeInt) && intSize == 4) || + (typeIdentical(t, TypeUint) && intSize == 4) || + (typeIdentical(t, TypeUintptr) && ptrSize == 4) +} + +func isSigned(t Type) bool { + return typeIdentical(t, TypeInt) || + typeIdentical(t, TypeInt8) || + typeIdentical(t, TypeInt16) || + typeIdentical(t, TypeInt32) || + typeIdentical(t, TypeInt64) +} + +func typeSize(t Type) int { + switch t { + case TypeInt32, TypeUint32: + return 4 + case TypeInt64, TypeUint64: + return 8 + case TypeUintptr: + return ptrSize + case TypeInt, TypeUint: + return intSize + default: + if _, ok := t.(*types.Pointer); ok { + return ptrSize + } + panic("TODO: width of " + t.String()) + } +} diff --git a/src/cmd/internal/ssa/rulegen/generic.rules b/src/cmd/internal/ssa/rulegen/generic.rules new file mode 100644 index 0000000000..73e6e4a329 --- /dev/null +++ b/src/cmd/internal/ssa/rulegen/generic.rules @@ -0,0 +1,16 @@ +// 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. + +// constant folding +(Add (ConstInt [c]) (ConstInt [d])) && is64BitInt(t) -> (ConstInt [{c.(int64)+d.(int64)}]) + +// load/store to stack +(Load (FPAddr [offset]) mem) -> (LoadFP [offset] mem) +(Store (FPAddr [offset]) val mem) -> (StoreFP [offset] val mem) + +(Load (SPAddr [offset]) mem) -> (LoadSP [offset] mem) +(Store (SPAddr [offset]) val mem) -> (StoreSP [offset] val mem) + +// expand array indexing +// others? Depends on what is already done by frontend diff --git a/src/cmd/internal/ssa/rulegen/lower_amd64.rules b/src/cmd/internal/ssa/rulegen/lower_amd64.rules new file mode 100644 index 0000000000..525035b8c2 --- /dev/null +++ b/src/cmd/internal/ssa/rulegen/lower_amd64.rules @@ -0,0 +1,46 @@ +// 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. + +// values are specified using the following format: +// (op [aux] arg0 arg1 ...) +// the type and aux fields are optional +// on the matching side +// - the types and aux fields must match if they are specified. +// on the generated side +// - types will be computed by opcode typers if not specified explicitly. +// - aux will be nil if not specified. + +// x86 register conventions: +// - Integer types live in the low portion of registers. Upper portions are junk. +// - Boolean types use the low-order byte of a register. Upper bytes are junk. +// - We do not use AH,BH,CH,DH registers. +// - Floating-point types will live in the low natural slot of an sse2 register. +// Unused portions are junk. + +// These are the lowerings themselves +(Add x y) && is64BitInt(t) -> (ADDQ x y) +(Add x y) && is32BitInt(t) -> (ADDL x y) + +(Sub x y) && is64BitInt(t) -> (SUBQ x y) + +(Less x y) && is64BitInt(v.Args[0].Type) && isSigned(v.Args[0].Type) -> (SETL (CMPQ x y)) + +// stack loads/stores +(LoadFP [offset] mem) && typeSize(t) == 8 -> (LoadFP8 [offset] mem) +(StoreFP [offset] val mem) && typeSize(val.Type) == 8 -> (StoreFP8 [offset] val mem) +(LoadSP [offset] mem) && typeSize(t) == 8 -> (LoadSP8 [offset] mem) +(StoreSP [offset] val mem) && typeSize(val.Type) == 8 -> (StoreSP8 [offset] val mem) + +// Rules below here apply some simple optimizations after lowering. +// TODO: Should this be a separate pass? + +(ADDQ x (ConstInt [c])) -> (ADDCQ [c] x) // TODO: restrict c to int32 range? +(ADDQ (ConstInt [c]) x) -> (ADDCQ [c] x) +(SUBQ x (ConstInt [c])) -> (SUBCQ x [c]) +(SUBQ (ConstInt [c]) x) -> (NEGQ (SUBCQ x [c])) +(CMPQ x (ConstInt [c])) -> (CMPCQ x [c]) +(CMPQ (ConstInt [c]) x) -> (InvertFlags (CMPCQ x [c])) + +// reverse ordering of compare instruction +(SETL (InvertFlags x)) -> (SETGE x) diff --git a/src/cmd/internal/ssa/rulegen/rulegen.go b/src/cmd/internal/ssa/rulegen/rulegen.go new file mode 100644 index 0000000000..f125828f64 --- /dev/null +++ b/src/cmd/internal/ssa/rulegen/rulegen.go @@ -0,0 +1,328 @@ +// 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 program generates Go code that applies rewrite rules to a Value. +// The generated code implements a function of type func (v *Value) bool +// which returns true iff if did something. +// Ideas stolen from Swift: http://www.hpl.hp.com/techreports/Compaq-DEC/WRL-2000-2.html + +// Run with something like "go run rulegen.go lower_amd64.rules lowerAmd64 lowerAmd64.go" + +package main + +import ( + "bufio" + "bytes" + "fmt" + "go/format" + "io" + "log" + "os" + "sort" + "strings" +) + +// rule syntax: +// sexpr [&& extra conditions] -> sexpr +// +// sexpr are s-expressions (lisp-like parenthesized groupings) +// sexpr ::= (opcode sexpr*) +// | variable +// | [aux] +// | +// | {code} +// +// aux ::= variable | {code} +// type ::= variable | {code} +// variable ::= some token +// opcode ::= one of the opcodes from ../op.go (without the Op prefix) + +// extra conditions is just a chunk of Go that evaluates to a boolean. It may use +// variables declared in the matching sexpr. The variable "v" is predefined to be +// the value matched by the entire rule. + +// If multiple rules match, the first one in file order is selected. + +func main() { + if len(os.Args) < 3 || len(os.Args) > 4 { + fmt.Printf("usage: go run rulegen.go []") + os.Exit(1) + } + rulefile := os.Args[1] + rulefn := os.Args[2] + + // Open input file. + text, err := os.Open(rulefile) + if err != nil { + log.Fatalf("can't read rule file: %v", err) + } + + // oprules contains a list of rules for each opcode + oprules := map[string][]string{} + + // read rule file + scanner := bufio.NewScanner(text) + for scanner.Scan() { + line := scanner.Text() + if i := strings.Index(line, "//"); i >= 0 { + // Remove comments. Note that this isn't string safe, so + // it will truncate lines with // inside strings. Oh well. + line = line[:i] + } + line = strings.TrimSpace(line) + if line == "" { + continue + } + op := strings.Split(line, " ")[0][1:] + oprules[op] = append(oprules[op], line) + } + if err := scanner.Err(); err != nil { + log.Fatalf("scanner failed: %v\n", err) + } + + // Start output buffer, write header. + w := new(bytes.Buffer) + fmt.Fprintf(w, "// autogenerated from %s: do not edit!\n", rulefile) + fmt.Fprintf(w, "// generated with: go run rulegen/rulegen.go %s\n", strings.Join(os.Args[1:], " ")) + fmt.Fprintln(w, "package ssa") + fmt.Fprintf(w, "func %s(v *Value) bool {\n", rulefn) + + // generate code for each rule + fmt.Fprintf(w, "switch v.Op {\n") + var ops []string + for op := range oprules { + ops = append(ops, op) + } + sort.Strings(ops) + rulenum := 0 + for _, op := range ops { + fmt.Fprintf(w, "case Op%s:\n", op) + for _, rule := range oprules[op] { + // split at -> + s := strings.Split(rule, "->") + if len(s) != 2 { + log.Fatalf("no arrow in rule %s", rule) + } + lhs := strings.Trim(s[0], " \t") + result := strings.Trim(s[1], " \t\n") + + // split match into matching part and additional condition + match := lhs + cond := "" + if i := strings.Index(match, "&&"); i >= 0 { + cond = strings.Trim(match[i+2:], " \t") + match = strings.Trim(match[:i], " \t") + } + + fmt.Fprintf(w, "// match: %s\n", match) + fmt.Fprintf(w, "// cond: %s\n", cond) + fmt.Fprintf(w, "// result: %s\n", result) + + fail := fmt.Sprintf("{\ngoto end%d\n}\n", rulenum) + + fmt.Fprintf(w, "{\n") + genMatch(w, match, fail) + + if cond != "" { + fmt.Fprintf(w, "if !(%s) %s", cond, fail) + } + + genResult(w, result) + fmt.Fprintf(w, "return true\n") + + fmt.Fprintf(w, "}\n") + fmt.Fprintf(w, "end%d:;\n", rulenum) + rulenum++ + } + } + fmt.Fprintf(w, "}\n") + fmt.Fprintf(w, "return false\n") + fmt.Fprintf(w, "}\n") + + // gofmt result + b := w.Bytes() + b, err = format.Source(b) + if err != nil { + panic(err) + } + + // Write to a file if given, otherwise stdout. + var out io.WriteCloser + if len(os.Args) >= 4 { + outfile := os.Args[3] + out, err = os.Create(outfile) + if err != nil { + log.Fatalf("can't open output file %s: %v\n", outfile, err) + } + } else { + out = os.Stdout + } + if _, err = out.Write(b); err != nil { + log.Fatalf("can't write output: %v\n", err) + } + if err = out.Close(); err != nil { + log.Fatalf("can't close output: %v\n", err) + } +} + +func genMatch(w io.Writer, match, fail string) { + genMatch0(w, match, "v", fail, map[string]string{}, true) +} + +func genMatch0(w io.Writer, match, v, fail string, m map[string]string, top bool) { + if match[0] != '(' { + if x, ok := m[match]; ok { + // variable already has a definition. Check whether + // the old definition and the new definition match. + // For example, (add x x). Equality is just pointer equality + // on Values (so cse is important to do before lowering). + fmt.Fprintf(w, "if %s != %s %s", v, x, fail) + return + } + // remember that this variable references the given value + m[match] = v + fmt.Fprintf(w, "%s := %s\n", match, v) + return + } + + // split body up into regions. Split by spaces/tabs, except those + // contained in () or {}. + s := split(match[1 : len(match)-1]) + + // check op + if !top { + fmt.Fprintf(w, "if %s.Op != Op%s %s", v, s[0], fail) + } + + // check type/aux/args + argnum := 0 + for _, a := range s[1:] { + if a[0] == '<' { + // type restriction + t := a[1 : len(a)-1] + if t[0] == '{' { + // code. We must match the results of this code. + fmt.Fprintf(w, "if %s.Type != %s %s", v, t[1:len(t)-1], fail) + } else { + // variable + if u, ok := m[t]; ok { + // must match previous variable + fmt.Fprintf(w, "if %s.Type != %s %s", v, u, fail) + } else { + m[t] = v + ".Type" + fmt.Fprintf(w, "%s := %s.Type\n", t, v) + } + } + } else if a[0] == '[' { + // aux restriction + x := a[1 : len(a)-1] + if x[0] == '{' { + // code + fmt.Fprintf(w, "if %s.Aux != %s %s", v, x[1:len(x)-1], fail) + } else { + // variable + if y, ok := m[x]; ok { + fmt.Fprintf(w, "if %s.Aux != %s %s", v, y, fail) + } else { + m[x] = v + ".Aux" + fmt.Fprintf(w, "%s := %s.Aux\n", x, v) + } + } + } else if a[0] == '{' { + fmt.Fprintf(w, "if %s.Args[%d] != %s %s", v, argnum, a[1:len(a)-1], fail) + argnum++ + } else { + // variable or sexpr + genMatch0(w, a, fmt.Sprintf("%s.Args[%d]", v, argnum), fail, m, false) + argnum++ + } + } +} + +func genResult(w io.Writer, result string) { + genResult0(w, result, new(int), true) +} +func genResult0(w io.Writer, result string, alloc *int, top bool) string { + if result[0] != '(' { + // variable + return result + } + + s := split(result[1 : len(result)-1]) + var v string + var needsType bool + if top { + v = "v" + fmt.Fprintf(w, "v.Op = Op%s\n", s[0]) + fmt.Fprintf(w, "v.Aux = nil\n") + fmt.Fprintf(w, "v.Args = v.argstorage[:0]\n") + } else { + v = fmt.Sprintf("v%d", *alloc) + *alloc++ + fmt.Fprintf(w, "%s := v.Block.NewValue(Op%s, TypeInvalid, nil)\n", v, s[0]) + needsType = true + } + for _, a := range s[1:] { + if a[0] == '<' { + // type restriction + t := a[1 : len(a)-1] + if t[0] == '{' { + t = t[1 : len(t)-1] + } + fmt.Fprintf(w, "%s.Type = %s\n", v, t) + needsType = false + } else if a[0] == '[' { + // aux restriction + x := a[1 : len(a)-1] + if x[0] == '{' { + x = x[1 : len(x)-1] + } + fmt.Fprintf(w, "%s.Aux = %s\n", v, x) + } else if a[0] == '{' { + fmt.Fprintf(w, "%s.AddArg(%s)\n", v, a[1:len(a)-1]) + } else { + // regular argument (sexpr or variable) + x := genResult0(w, a, alloc, false) + fmt.Fprintf(w, "%s.AddArg(%s)\n", v, x) + } + } + if needsType { + fmt.Fprintf(w, "%s.SetType()\n", v) + } + return v +} + +func split(s string) []string { + var r []string + +outer: + for s != "" { + d := 0 // depth of ({[< + nonsp := false // found a non-space char so far + for i := 0; i < len(s); i++ { + switch s[i] { + case '(', '{', '[', '<': + d++ + case ')', '}', ']', '>': + d-- + case ' ', '\t': + if d == 0 && nonsp { + r = append(r, strings.TrimSpace(s[:i])) + s = s[i:] + continue outer + } + default: + nonsp = true + } + } + if d != 0 { + panic("imbalanced expression: " + s) + } + if nonsp { + r = append(r, strings.TrimSpace(s)) + } + break + } + return r +} diff --git a/src/cmd/internal/ssa/schedule.go b/src/cmd/internal/ssa/schedule.go new file mode 100644 index 0000000000..0a89ac3773 --- /dev/null +++ b/src/cmd/internal/ssa/schedule.go @@ -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 ssa + +// Schedule the Values in each Block. After this phase returns, the +// order of b.Values matters and is the order in which those values +// will appear in the assembly output. For now it generates an +// arbitrary valid schedule using a topological sort. TODO(khr): +// schedule smarter. +func schedule(f *Func) { + const ( + unmarked = 0 + found = 1 + expanded = 2 + done = 3 + ) + state := make([]byte, f.NumValues()) + var queue []*Value //stack-like worklist. Contains found and expanded nodes. + var order []*Value + + for _, b := range f.Blocks { + // Topologically sort the values in b. + order = order[:0] + for _, v := range b.Values { + if v.Op == OpPhi { + // Phis all go first. We handle phis specially + // because they may have self edges "a = phi(a, b, c)" + order = append(order, v) + continue + } + if state[v.ID] != unmarked { + if state[v.ID] != done { + panic("bad state") + } + continue + } + state[v.ID] = found + queue = append(queue, v) + for len(queue) > 0 { + v = queue[len(queue)-1] + switch state[v.ID] { + case found: + state[v.ID] = expanded + // Note that v is not popped. We leave it in place + // until all its children have been explored. + for _, w := range v.Args { + if w.Block == b && w.Op != OpPhi && state[w.ID] == unmarked { + state[w.ID] = found + queue = append(queue, w) + } + } + case expanded: + queue = queue[:len(queue)-1] + state[v.ID] = done + order = append(order, v) + default: + panic("bad state") + } + } + } + copy(b.Values, order) + } + // TODO: only allow one live mem type and one live flags type (x86) + // This restriction will force any loads (and any flag uses) to appear + // before the next store (flag update). This "anti-dependence" is not + // recorded explicitly in ssa form. +} diff --git a/src/cmd/internal/ssa/ssac/fib.goir b/src/cmd/internal/ssa/ssac/fib.goir index b572cdaa3a..0875d63ca3 100644 --- a/src/cmd/internal/ssa/ssac/fib.goir +++ b/src/cmd/internal/ssa/ssac/fib.goir @@ -13,6 +13,7 @@ (TYPE T127bd68 int) (TYPE T127bd68 int) (DCL n T127bd68) + (AS n (LOAD (FP T127bd68 0))) (DCL ~r1 T127bd68) (DCL n T127bd68) (DCL autotmp_0000 T127bd68) @@ -29,7 +30,7 @@ (IF (LT n (CINT 2)) .then0 .else0) (LABEL .then0) (AS ~r1 n) - (AS (SP T127bd68 8) ~r1) + (AS (FP T127bd68 8) ~r1) (RETURN) (GOTO .end0) (LABEL .else0) @@ -42,5 +43,5 @@ (CALL fib) (AS autotmp_0001 (LOAD (SP T127bd68 8))) (AS ~r1 (ADD autotmp_0000 autotmp_0001)) - (AS (SP T127bd68 8) ~r1) + (AS (FP T127bd68 8) ~r1) (RETURN) diff --git a/src/cmd/internal/ssa/ssac/fibiter.goir b/src/cmd/internal/ssa/ssac/fibiter.goir index 43c7a3de91..98b2b2b576 100644 --- a/src/cmd/internal/ssa/ssac/fibiter.goir +++ b/src/cmd/internal/ssa/ssac/fibiter.goir @@ -43,7 +43,7 @@ (DCL autotmp_0003 Tf5dd68) (DCL ~r1 Tf5dd68) (DCL a Tf5dd68) - (AS n (LOAD (SP Tf5dd68 0))) + (AS n (LOAD (FP Tf5dd68 0))) (AS a (CINT 0)) (AS b (CINT 1)) (AS i (CINT 0)) @@ -58,5 +58,5 @@ (AS i (ADD autotmp_0002 (CINT 1))) (GOTO .top0) (LABEL .end0) - (AS (SP Tf5dd68 8) a) + (AS (FP Tf5dd68 8) a) (RETURN) -- cgit v1.3 From 2c9b491e01dbc5e06d7cf98deaf1b4f1779f5da9 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Thu, 26 Mar 2015 10:49:03 -0700 Subject: [dev.ssa] cmd/internal/ssa: SSA cleanups Mostly suggested by Alan. Convert Const* ops to just one Const op. Use more of go/types. Get rid of typers, all types must be specified explicitly. Change-Id: Id4758f2b887d8a6888e88a7e047d97af55e34b62 Reviewed-on: https://go-review.googlesource.com/8110 Reviewed-by: Alan Donovan --- src/cmd/internal/ssa/block.go | 1 + src/cmd/internal/ssa/compile.go | 2 +- src/cmd/internal/ssa/deadcode.go | 2 +- src/cmd/internal/ssa/func.go | 2 +- src/cmd/internal/ssa/generic.go | 54 ++++++++---- src/cmd/internal/ssa/lowerAmd64.go | 37 ++++---- src/cmd/internal/ssa/op.go | 76 +++++----------- src/cmd/internal/ssa/op_string.go | 4 +- src/cmd/internal/ssa/rewrite.go | 55 +++++------- src/cmd/internal/ssa/rulegen/generic.rules | 3 +- src/cmd/internal/ssa/rulegen/lower_amd64.rules | 17 ++-- src/cmd/internal/ssa/rulegen/rulegen.go | 25 ++---- src/cmd/internal/ssa/ssac/main.go | 3 +- src/cmd/internal/ssa/type.go | 34 ++++--- src/cmd/internal/ssa/types/sizes.go | 117 +++++++++++++++++++++++++ src/cmd/internal/ssa/value.go | 13 +-- 16 files changed, 271 insertions(+), 174 deletions(-) create mode 100644 src/cmd/internal/ssa/types/sizes.go (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/ssa/block.go b/src/cmd/internal/ssa/block.go index ff1cb1b30a..81b5594f38 100644 --- a/src/cmd/internal/ssa/block.go +++ b/src/cmd/internal/ssa/block.go @@ -54,6 +54,7 @@ const ( BlockPlain // a single successor BlockIf // 2 successors, if control goto Succs[0] else goto Succs[1] BlockCall // 2 successors, normal return and panic + // TODO(khr): BlockPanic for the built-in panic call, has 1 edge to the exit block BlockUnknown // 386/amd64 variants of BlockIf that take the flags register as an arg diff --git a/src/cmd/internal/ssa/compile.go b/src/cmd/internal/ssa/compile.go index b8f34c52fc..6103cc9557 100644 --- a/src/cmd/internal/ssa/compile.go +++ b/src/cmd/internal/ssa/compile.go @@ -79,7 +79,7 @@ var passOrder = map[string]string{ // regalloc requires all the values in a block to be scheduled //"schedule": "regalloc", // code generation requires register allocation - //"cgen":"regalloc", + //"regalloc": "cgen", } func init() { diff --git a/src/cmd/internal/ssa/deadcode.go b/src/cmd/internal/ssa/deadcode.go index e8c8bfcc03..f9e4b18d5f 100644 --- a/src/cmd/internal/ssa/deadcode.go +++ b/src/cmd/internal/ssa/deadcode.go @@ -20,7 +20,7 @@ func deadcode(f *Func) { // constant-fold conditionals // TODO: rewrite rules instead? - if b.Kind == BlockIf && b.Control.Op == OpConstBool { + if b.Kind == BlockIf && b.Control.Op == OpConst { cond := b.Control.Aux.(bool) var c *Block if cond { diff --git a/src/cmd/internal/ssa/func.go b/src/cmd/internal/ssa/func.go index 6868e3d1ed..b4677c97b3 100644 --- a/src/cmd/internal/ssa/func.go +++ b/src/cmd/internal/ssa/func.go @@ -57,5 +57,5 @@ func (b *Block) NewValue(op Op, t Type, aux interface{}) *Value { func (f *Func) ConstInt(c int64) *Value { // TODO: cache? // TODO: different types? - return f.Entry.NewValue(OpConstInt, TypeInt, c) + return f.Entry.NewValue(OpConst, TypeInt64, c) } diff --git a/src/cmd/internal/ssa/generic.go b/src/cmd/internal/ssa/generic.go index f28633b19a..3118b3af9d 100644 --- a/src/cmd/internal/ssa/generic.go +++ b/src/cmd/internal/ssa/generic.go @@ -5,23 +5,23 @@ package ssa func genericRules(v *Value) bool { switch v.Op { case OpAdd: - // match: (Add (ConstInt [c]) (ConstInt [d])) - // cond: is64BitInt(t) - // result: (ConstInt [{c.(int64)+d.(int64)}]) + // match: (Add (Const [c]) (Const [d])) + // cond: is64BitInt(t) && isSigned(t) + // result: (Const [{c.(int64)+d.(int64)}]) { t := v.Type - if v.Args[0].Op != OpConstInt { + if v.Args[0].Op != OpConst { goto end0 } c := v.Args[0].Aux - if v.Args[1].Op != OpConstInt { + if v.Args[1].Op != OpConst { goto end0 } d := v.Args[1].Aux - if !(is64BitInt(t)) { + if !(is64BitInt(t) && isSigned(t)) { goto end0 } - v.Op = OpConstInt + v.Op = OpConst v.Aux = nil v.Args = v.argstorage[:0] v.Aux = c.(int64) + d.(int64) @@ -29,13 +29,37 @@ func genericRules(v *Value) bool { } end0: ; + // match: (Add (Const [c]) (Const [d])) + // cond: is64BitInt(t) && !isSigned(t) + // result: (Const [{c.(uint64)+d.(uint64)}]) + { + t := v.Type + if v.Args[0].Op != OpConst { + goto end1 + } + c := v.Args[0].Aux + if v.Args[1].Op != OpConst { + goto end1 + } + d := v.Args[1].Aux + if !(is64BitInt(t) && !isSigned(t)) { + goto end1 + } + v.Op = OpConst + v.Aux = nil + v.Args = v.argstorage[:0] + v.Aux = c.(uint64) + d.(uint64) + return true + } + end1: + ; case OpLoad: // match: (Load (FPAddr [offset]) mem) // cond: // result: (LoadFP [offset] mem) { if v.Args[0].Op != OpFPAddr { - goto end1 + goto end2 } offset := v.Args[0].Aux mem := v.Args[1] @@ -46,14 +70,14 @@ func genericRules(v *Value) bool { v.AddArg(mem) return true } - end1: + end2: ; // match: (Load (SPAddr [offset]) mem) // cond: // result: (LoadSP [offset] mem) { if v.Args[0].Op != OpSPAddr { - goto end2 + goto end3 } offset := v.Args[0].Aux mem := v.Args[1] @@ -64,7 +88,7 @@ func genericRules(v *Value) bool { v.AddArg(mem) return true } - end2: + end3: ; case OpStore: // match: (Store (FPAddr [offset]) val mem) @@ -72,7 +96,7 @@ func genericRules(v *Value) bool { // result: (StoreFP [offset] val mem) { if v.Args[0].Op != OpFPAddr { - goto end3 + goto end4 } offset := v.Args[0].Aux val := v.Args[1] @@ -85,14 +109,14 @@ func genericRules(v *Value) bool { v.AddArg(mem) return true } - end3: + end4: ; // match: (Store (SPAddr [offset]) val mem) // cond: // result: (StoreSP [offset] val mem) { if v.Args[0].Op != OpSPAddr { - goto end4 + goto end5 } offset := v.Args[0].Aux val := v.Args[1] @@ -105,7 +129,7 @@ func genericRules(v *Value) bool { v.AddArg(mem) return true } - end4: + end5: } return false } diff --git a/src/cmd/internal/ssa/lowerAmd64.go b/src/cmd/internal/ssa/lowerAmd64.go index ab79ed09b1..88f0e43bd8 100644 --- a/src/cmd/internal/ssa/lowerAmd64.go +++ b/src/cmd/internal/ssa/lowerAmd64.go @@ -5,12 +5,12 @@ package ssa func lowerAmd64(v *Value) bool { switch v.Op { case OpADDQ: - // match: (ADDQ x (ConstInt [c])) + // match: (ADDQ x (Const [c])) // cond: // result: (ADDCQ [c] x) { x := v.Args[0] - if v.Args[1].Op != OpConstInt { + if v.Args[1].Op != OpConst { goto end0 } c := v.Args[1].Aux @@ -23,11 +23,11 @@ func lowerAmd64(v *Value) bool { } end0: ; - // match: (ADDQ (ConstInt [c]) x) + // match: (ADDQ (Const [c]) x) // cond: // result: (ADDCQ [c] x) { - if v.Args[0].Op != OpConstInt { + if v.Args[0].Op != OpConst { goto end1 } c := v.Args[0].Aux @@ -81,12 +81,12 @@ func lowerAmd64(v *Value) bool { end3: ; case OpCMPQ: - // match: (CMPQ x (ConstInt [c])) + // match: (CMPQ x (Const [c])) // cond: // result: (CMPCQ x [c]) { x := v.Args[0] - if v.Args[1].Op != OpConstInt { + if v.Args[1].Op != OpConst { goto end4 } c := v.Args[1].Aux @@ -99,11 +99,11 @@ func lowerAmd64(v *Value) bool { } end4: ; - // match: (CMPQ (ConstInt [c]) x) + // match: (CMPQ (Const [c]) x) // cond: - // result: (InvertFlags (CMPCQ x [c])) + // result: (InvertFlags (CMPCQ x [c])) { - if v.Args[0].Op != OpConstInt { + if v.Args[0].Op != OpConst { goto end5 } c := v.Args[0].Aux @@ -112,9 +112,9 @@ func lowerAmd64(v *Value) bool { v.Aux = nil v.Args = v.argstorage[:0] v0 := v.Block.NewValue(OpCMPCQ, TypeInvalid, nil) + v0.Type = TypeFlags v0.AddArg(x) v0.Aux = c - v0.SetType() v.AddArg(v0) return true } @@ -123,7 +123,7 @@ func lowerAmd64(v *Value) bool { case OpLess: // match: (Less x y) // cond: is64BitInt(v.Args[0].Type) && isSigned(v.Args[0].Type) - // result: (SETL (CMPQ x y)) + // result: (SETL (CMPQ x y)) { x := v.Args[0] y := v.Args[1] @@ -134,9 +134,9 @@ func lowerAmd64(v *Value) bool { v.Aux = nil v.Args = v.argstorage[:0] v0 := v.Block.NewValue(OpCMPQ, TypeInvalid, nil) + v0.Type = TypeFlags v0.AddArg(x) v0.AddArg(y) - v0.SetType() v.AddArg(v0) return true } @@ -202,12 +202,12 @@ func lowerAmd64(v *Value) bool { end9: ; case OpSUBQ: - // match: (SUBQ x (ConstInt [c])) + // match: (SUBQ x (Const [c])) // cond: // result: (SUBCQ x [c]) { x := v.Args[0] - if v.Args[1].Op != OpConstInt { + if v.Args[1].Op != OpConst { goto end10 } c := v.Args[1].Aux @@ -220,11 +220,12 @@ func lowerAmd64(v *Value) bool { } end10: ; - // match: (SUBQ (ConstInt [c]) x) + // match: (SUBQ (Const [c]) x) // cond: - // result: (NEGQ (SUBCQ x [c])) + // result: (NEGQ (SUBCQ x [c])) { - if v.Args[0].Op != OpConstInt { + t := v.Type + if v.Args[0].Op != OpConst { goto end11 } c := v.Args[0].Aux @@ -233,9 +234,9 @@ func lowerAmd64(v *Value) bool { v.Aux = nil v.Args = v.argstorage[:0] v0 := v.Block.NewValue(OpSUBCQ, TypeInvalid, nil) + v0.Type = t v0.AddArg(x) v0.Aux = c - v0.SetType() v.AddArg(v0) return true } diff --git a/src/cmd/internal/ssa/op.go b/src/cmd/internal/ssa/op.go index da69657411..19d973921c 100644 --- a/src/cmd/internal/ssa/op.go +++ b/src/cmd/internal/ssa/op.go @@ -29,14 +29,9 @@ const ( 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 + OpConst + + OpArg // address of a function parameter/result. Memory input is an arg called ".mem". OpGlobal // address of a global variable OpFunc // entry address of a function OpCopy // output = input @@ -56,7 +51,7 @@ const ( OpIndexAddr OpLoad // args are ptr, memory - OpStore // args are ptr, memory, returns memory + OpStore // args are ptr, value, memory, returns memory OpCheckNil // arg[0] != nil OpCheckBound // 0 <= arg[0] < arg[1] @@ -135,9 +130,6 @@ type OpInfo struct { // %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) @@ -178,28 +170,6 @@ const ( 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}} @@ -221,21 +191,17 @@ 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: {}, + OpAdd: {flags: OpFlagCommutative}, + OpSub: {}, + OpMul: {flags: OpFlagCommutative}, + OpLess: {}, + + OpConst: {}, // aux matches the type (e.g. bool, int64 float64) + 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 @@ -281,12 +247,12 @@ var genericTable = [...]OpInfo{ // 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}, + OpADDQ: {flags: OpFlagCommutative, asm: "ADDQ\t%I0,%I1,%O0", reg: gp21}, // TODO: overwrite + OpADDCQ: {asm: "ADDQ\t$%A,%I0,%O0", reg: gp11_overwrite}, // aux = int64 constant to add + OpSUBQ: {asm: "SUBQ\t%I0,%I1,%O0", reg: gp21}, + OpSUBCQ: {asm: "SUBQ\t$%A,%I0,%O0", reg: gp11_overwrite}, - OpCMPQ: {asm: "CMPQ\t%I0,%I1", reg: gp2_flags, typer: flagsTyper}, // compute arg[0]-arg[1] and produce flags + OpCMPQ: {asm: "CMPQ\t%I0,%I1", reg: gp2_flags}, // 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 @@ -302,7 +268,7 @@ var amd64Table = [...]OpInfo{ OpCopy: {asm: "MOVQ\t%I0,%O0", reg: gp11}, // convert from flags back to boolean - OpSETL: {typer: boolTyper}, + OpSETL: {}, // ops for load/store to stack OpLoadFP8: {asm: "MOVQ\t%A(FP),%O0"}, diff --git a/src/cmd/internal/ssa/op_string.go b/src/cmd/internal/ssa/op_string.go index 9aee7de43e..dba1725262 100644 --- a/src/cmd/internal/ssa/op_string.go +++ b/src/cmd/internal/ssa/op_string.go @@ -4,9 +4,9 @@ package ssa import "fmt" -const _Op_name = "OpUnknownOpNopOpThunkOpAddOpSubOpMulOpLessOpConstNilOpConstBoolOpConstStringOpConstIntOpConstFloatOpConstComplexOpArgOpGlobalOpFuncOpCopyOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpSliceOpIndexOpIndexAddrOpLoadOpStoreOpCheckNilOpCheckBoundOpCallOpStaticCallOpConvertOpConvNopOpFPAddrOpSPAddrOpLoadFPOpLoadSPOpStoreFPOpStoreSPOpStoreReg8OpLoadReg8OpADDQOpSUBQOpADDCQOpSUBCQOpNEGQOpCMPQOpCMPCQOpADDLOpSETLOpSETGEOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpLoadFP8OpLoadSP8OpStoreFP8OpStoreSP8OpMax" +const _Op_name = "OpUnknownOpNopOpThunkOpAddOpSubOpMulOpLessOpConstOpArgOpGlobalOpFuncOpCopyOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpSliceOpIndexOpIndexAddrOpLoadOpStoreOpCheckNilOpCheckBoundOpCallOpStaticCallOpConvertOpConvNopOpFPAddrOpSPAddrOpLoadFPOpLoadSPOpStoreFPOpStoreSPOpStoreReg8OpLoadReg8OpADDQOpSUBQOpADDCQOpSUBCQOpNEGQOpCMPQOpCMPCQOpADDLOpSETLOpSETGEOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpLoadFP8OpLoadSP8OpStoreFP8OpStoreSP8OpMax" -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, 441, 448, 461, 467, 474, 481, 488, 497, 506, 516, 526, 531} +var _Op_index = [...]uint16{0, 9, 14, 21, 26, 31, 36, 42, 49, 54, 62, 68, 74, 79, 90, 100, 110, 120, 132, 143, 154, 161, 168, 179, 185, 192, 202, 214, 220, 232, 241, 250, 258, 266, 274, 282, 291, 300, 311, 321, 327, 333, 340, 347, 353, 359, 366, 372, 378, 385, 398, 404, 411, 418, 425, 434, 443, 453, 463, 468} func (i Op) String() string { if i < 0 || i+1 >= Op(len(_Op_index)) { diff --git a/src/cmd/internal/ssa/rewrite.go b/src/cmd/internal/ssa/rewrite.go index 0d7c0c1c64..d22926e8f9 100644 --- a/src/cmd/internal/ssa/rewrite.go +++ b/src/cmd/internal/ssa/rewrite.go @@ -28,43 +28,36 @@ func applyRewrite(f *Func, r func(*Value) bool) { // Common functions called from rewriting rules func is64BitInt(t Type) bool { - return typeIdentical(t, TypeInt64) || - typeIdentical(t, TypeUint64) || - (typeIdentical(t, TypeInt) && intSize == 8) || - (typeIdentical(t, TypeUint) && intSize == 8) || - (typeIdentical(t, TypeUintptr) && ptrSize == 8) + if b, ok := t.Underlying().(*types.Basic); ok { + switch b.Kind() { + case types.Int64, types.Uint64: + return true + } + } + return false } func is32BitInt(t Type) bool { - return typeIdentical(t, TypeInt32) || - typeIdentical(t, TypeUint32) || - (typeIdentical(t, TypeInt) && intSize == 4) || - (typeIdentical(t, TypeUint) && intSize == 4) || - (typeIdentical(t, TypeUintptr) && ptrSize == 4) + if b, ok := t.Underlying().(*types.Basic); ok { + switch b.Kind() { + case types.Int32, types.Uint32: + return true + } + } + return false } func isSigned(t Type) bool { - return typeIdentical(t, TypeInt) || - typeIdentical(t, TypeInt8) || - typeIdentical(t, TypeInt16) || - typeIdentical(t, TypeInt32) || - typeIdentical(t, TypeInt64) -} - -func typeSize(t Type) int { - switch t { - case TypeInt32, TypeUint32: - return 4 - case TypeInt64, TypeUint64: - return 8 - case TypeUintptr: - return ptrSize - case TypeInt, TypeUint: - return intSize - default: - if _, ok := t.(*types.Pointer); ok { - return ptrSize + if b, ok := t.Underlying().(*types.Basic); ok { + switch b.Kind() { + case types.Int8, types.Int16, types.Int32, types.Int64: + return true } - panic("TODO: width of " + t.String()) } + return false +} + +var sizer types.Sizes = &types.StdSizes{int64(ptrSize), int64(ptrSize)} // TODO(khr): from config +func typeSize(t Type) int64 { + return sizer.Sizeof(t) } diff --git a/src/cmd/internal/ssa/rulegen/generic.rules b/src/cmd/internal/ssa/rulegen/generic.rules index 73e6e4a329..1fc1620c5c 100644 --- a/src/cmd/internal/ssa/rulegen/generic.rules +++ b/src/cmd/internal/ssa/rulegen/generic.rules @@ -3,7 +3,8 @@ // license that can be found in the LICENSE file. // constant folding -(Add (ConstInt [c]) (ConstInt [d])) && is64BitInt(t) -> (ConstInt [{c.(int64)+d.(int64)}]) +(Add (Const [c]) (Const [d])) && is64BitInt(t) && isSigned(t) -> (Const [{c.(int64)+d.(int64)}]) +(Add (Const [c]) (Const [d])) && is64BitInt(t) && !isSigned(t) -> (Const [{c.(uint64)+d.(uint64)}]) // load/store to stack (Load (FPAddr [offset]) mem) -> (LoadFP [offset] mem) diff --git a/src/cmd/internal/ssa/rulegen/lower_amd64.rules b/src/cmd/internal/ssa/rulegen/lower_amd64.rules index 525035b8c2..f60ac361ad 100644 --- a/src/cmd/internal/ssa/rulegen/lower_amd64.rules +++ b/src/cmd/internal/ssa/rulegen/lower_amd64.rules @@ -8,7 +8,8 @@ // on the matching side // - the types and aux fields must match if they are specified. // on the generated side -// - types will be computed by opcode typers if not specified explicitly. +// - the type of the top-level expression is the same as the one on the left-hand side. +// - the type of any subexpressions must be specified explicitly. // - aux will be nil if not specified. // x86 register conventions: @@ -24,7 +25,7 @@ (Sub x y) && is64BitInt(t) -> (SUBQ x y) -(Less x y) && is64BitInt(v.Args[0].Type) && isSigned(v.Args[0].Type) -> (SETL (CMPQ x y)) +(Less x y) && is64BitInt(v.Args[0].Type) && isSigned(v.Args[0].Type) -> (SETL (CMPQ x y)) // stack loads/stores (LoadFP [offset] mem) && typeSize(t) == 8 -> (LoadFP8 [offset] mem) @@ -35,12 +36,12 @@ // Rules below here apply some simple optimizations after lowering. // TODO: Should this be a separate pass? -(ADDQ x (ConstInt [c])) -> (ADDCQ [c] x) // TODO: restrict c to int32 range? -(ADDQ (ConstInt [c]) x) -> (ADDCQ [c] x) -(SUBQ x (ConstInt [c])) -> (SUBCQ x [c]) -(SUBQ (ConstInt [c]) x) -> (NEGQ (SUBCQ x [c])) -(CMPQ x (ConstInt [c])) -> (CMPCQ x [c]) -(CMPQ (ConstInt [c]) x) -> (InvertFlags (CMPCQ x [c])) +(ADDQ x (Const [c])) -> (ADDCQ [c] x) // TODO: restrict c to int32 range? +(ADDQ (Const [c]) x) -> (ADDCQ [c] x) +(SUBQ x (Const [c])) -> (SUBCQ x [c]) +(SUBQ (Const [c]) x) -> (NEGQ (SUBCQ x [c])) +(CMPQ x (Const [c])) -> (CMPCQ x [c]) +(CMPQ (Const [c]) x) -> (InvertFlags (CMPCQ x [c])) // reverse ordering of compare instruction (SETL (InvertFlags x)) -> (SETGE x) diff --git a/src/cmd/internal/ssa/rulegen/rulegen.go b/src/cmd/internal/ssa/rulegen/rulegen.go index f125828f64..4038662ca8 100644 --- a/src/cmd/internal/ssa/rulegen/rulegen.go +++ b/src/cmd/internal/ssa/rulegen/rulegen.go @@ -17,6 +17,7 @@ import ( "fmt" "go/format" "io" + "io/ioutil" "log" "os" "sort" @@ -148,22 +149,14 @@ func main() { } // Write to a file if given, otherwise stdout. - var out io.WriteCloser if len(os.Args) >= 4 { - outfile := os.Args[3] - out, err = os.Create(outfile) - if err != nil { - log.Fatalf("can't open output file %s: %v\n", outfile, err) - } + err = ioutil.WriteFile(os.Args[3], b, 0666) } else { - out = os.Stdout + _, err = os.Stdout.Write(b) } - if _, err = out.Write(b); err != nil { + if err != nil { log.Fatalf("can't write output: %v\n", err) } - if err = out.Close(); err != nil { - log.Fatalf("can't close output: %v\n", err) - } } func genMatch(w io.Writer, match, fail string) { @@ -251,17 +244,17 @@ func genResult0(w io.Writer, result string, alloc *int, top bool) string { s := split(result[1 : len(result)-1]) var v string - var needsType bool + var hasType bool if top { v = "v" fmt.Fprintf(w, "v.Op = Op%s\n", s[0]) fmt.Fprintf(w, "v.Aux = nil\n") fmt.Fprintf(w, "v.Args = v.argstorage[:0]\n") + hasType = true } else { v = fmt.Sprintf("v%d", *alloc) *alloc++ fmt.Fprintf(w, "%s := v.Block.NewValue(Op%s, TypeInvalid, nil)\n", v, s[0]) - needsType = true } for _, a := range s[1:] { if a[0] == '<' { @@ -271,7 +264,7 @@ func genResult0(w io.Writer, result string, alloc *int, top bool) string { t = t[1 : len(t)-1] } fmt.Fprintf(w, "%s.Type = %s\n", v, t) - needsType = false + hasType = true } else if a[0] == '[' { // aux restriction x := a[1 : len(a)-1] @@ -287,8 +280,8 @@ func genResult0(w io.Writer, result string, alloc *int, top bool) string { fmt.Fprintf(w, "%s.AddArg(%s)\n", v, x) } } - if needsType { - fmt.Fprintf(w, "%s.SetType()\n", v) + if !hasType { + log.Fatalf("sub-expression %s must have a type", result) } return v } diff --git a/src/cmd/internal/ssa/ssac/main.go b/src/cmd/internal/ssa/ssac/main.go index 4975b50db4..361bc87bff 100644 --- a/src/cmd/internal/ssa/ssac/main.go +++ b/src/cmd/internal/ssa/ssac/main.go @@ -411,7 +411,8 @@ func parseSexprType(e sexpr) ssa.Type { if !e.compound { switch e.name { case "int": - return ssa.TypeInt + // TODO: pick correct width + return ssa.TypeInt64 default: fmt.Println(e.name) panic("unknown type") diff --git a/src/cmd/internal/ssa/type.go b/src/cmd/internal/ssa/type.go index e9c017d38a..98efe54133 100644 --- a/src/cmd/internal/ssa/type.go +++ b/src/cmd/internal/ssa/type.go @@ -13,25 +13,33 @@ 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] + //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{} + + // TODO(khr): we probably shouldn't use int/uint/uintptr as Value types in the compiler. + // In OpConst's case, their width is the compiler's width, not the to-be-compiled + // program's width. For now, we can translate int/uint/uintptr to their specific + // widths variants before SSA. + // However, we may need at some point to maintain all possible user types in the + // compiler to handle things like interface conversion. At that point, we may + // need to revisit this decision. ) // typeIdentical reports whether its two arguments are the same type. diff --git a/src/cmd/internal/ssa/types/sizes.go b/src/cmd/internal/ssa/types/sizes.go new file mode 100644 index 0000000000..b52f636fc5 --- /dev/null +++ b/src/cmd/internal/ssa/types/sizes.go @@ -0,0 +1,117 @@ +// Copyright 2013 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 file implements Sizes. + +package types + +import "log" + +// Sizes defines the sizing functions for package unsafe. +type Sizes interface { + // Alignof returns the alignment of a variable of type T. + // Alignof must implement the alignment guarantees required by the spec. + Alignof(T Type) int64 + + // Offsetsof returns the offsets of the given struct fields, in bytes. + // Offsetsof must implement the offset guarantees required by the spec. + Offsetsof(fields []*Var) []int64 + + // Sizeof returns the size of a variable of type T. + // Sizeof must implement the size guarantees required by the spec. + Sizeof(T Type) int64 +} + +// StdSizes is a convenience type for creating commonly used Sizes. +// It makes the following simplifying assumptions: +// +// - The size of explicitly sized basic types (int16, etc.) is the +// specified size. +// - The size of strings and interfaces is 2*WordSize. +// - The size of slices is 3*WordSize. +// - The size of an array of n elements corresponds to the size of +// a struct of n consecutive fields of the array's element type. +// - The size of a struct is the offset of the last field plus that +// field's size. As with all element types, if the struct is used +// in an array its size must first be aligned to a multiple of the +// struct's alignment. +// - All other types have size WordSize. +// - Arrays and structs are aligned per spec definition; all other +// types are naturally aligned with a maximum alignment MaxAlign. +// +// *StdSizes implements Sizes. +// +type StdSizes struct { + WordSize int64 // word size in bytes - must be >= 4 (32bits) + MaxAlign int64 // maximum alignment in bytes - must be >= 1 +} + +func (s *StdSizes) Alignof(T Type) int64 { + a := s.Sizeof(T) // may be 0 + // spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1." + if a < 1 { + return 1 + } + if a > s.MaxAlign { + return s.MaxAlign + } + return a +} + +func (s *StdSizes) Offsetsof(fields []*Var) []int64 { + offsets := make([]int64, len(fields)) + var o int64 + for i, f := range fields { + a := s.Alignof(f.typ) + o = align(o, a) + offsets[i] = o + o += s.Sizeof(f.typ) + } + return offsets +} + +var basicSizes = [...]byte{ + Bool: 1, + Int8: 1, + Int16: 2, + Int32: 4, + Int64: 8, + Uint8: 1, + Uint16: 2, + Uint32: 4, + Uint64: 8, + Float32: 4, + Float64: 8, + Complex64: 8, + Complex128: 16, +} + +func (s *StdSizes) Sizeof(T Type) int64 { + switch t := T.Underlying().(type) { + case *Basic: + k := t.kind + if int(k) < len(basicSizes) { + if s := basicSizes[k]; s > 0 { + return int64(s) + } + } + if k == String { + return s.WordSize * 2 + } + case *Slice: + return s.WordSize * 3 + default: + log.Fatalf("not implemented") + } + return s.WordSize // catch-all +} + +// stdSizes is used if Config.Sizes == nil. +var stdSizes = StdSizes{8, 8} + +// align returns the smallest y >= x such that y % a == 0. +func align(x, a int64) int64 { + y := x + a - 1 + return y - y%a +} diff --git a/src/cmd/internal/ssa/value.go b/src/cmd/internal/ssa/value.go index f6f099cd32..389ba1ff77 100644 --- a/src/cmd/internal/ssa/value.go +++ b/src/cmd/internal/ssa/value.go @@ -40,8 +40,8 @@ type Value struct { // Examples: // Opcode aux args // OpAdd nil 2 -// OpConstStr string 0 -// OpConstInt int64 0 +// OpConst string 0 string constant +// OpConst int64 0 int64 constant // OpAddcq int64 1 amd64 op: v = arg[0] + constant // short form print. Just v#. @@ -113,12 +113,3 @@ func (v *Value) CopyFrom(w *Value) { 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) -} -- cgit v1.3 From 149671dfc30889b72254a7a43ba515783b4c5bf7 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Fri, 27 Mar 2015 13:41:30 -0700 Subject: [dev.ssa] cmd/internal/ssa: add CSE pass Implement a simple common-subexpression elimination. It uses value numbering & a dominator tree to detect redundant computation. Change-Id: Id0ff775e439c22f4d41bdd5976176017dd2a2086 Reviewed-on: https://go-review.googlesource.com/8172 Reviewed-by: Alan Donovan --- src/cmd/internal/ssa/compile.go | 7 +- src/cmd/internal/ssa/cse.go | 163 ++++++++++++++++++++++++++++++++++++++++ src/cmd/internal/ssa/dom.go | 121 +++++++++++++++++++++++++++++ src/cmd/internal/ssa/lower.go | 1 - 4 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 src/cmd/internal/ssa/cse.go create mode 100644 src/cmd/internal/ssa/dom.go (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/ssa/compile.go b/src/cmd/internal/ssa/compile.go index 6103cc9557..08477d470c 100644 --- a/src/cmd/internal/ssa/compile.go +++ b/src/cmd/internal/ssa/compile.go @@ -54,11 +54,12 @@ var passes = [...]pass{ {"phielim", phielim}, {"copyelim", copyelim}, {"opt", opt}, - // cse - {"deadcode", deadcode}, + {"generic cse", cse}, + {"generic deadcode", deadcode}, {"fuse", fuse}, {"lower", lower}, - // cse + {"lowered cse", cse}, + {"lowered deadcode", deadcode}, {"critical", critical}, // remove critical edges {"layout", layout}, // schedule blocks {"schedule", schedule}, // schedule values diff --git a/src/cmd/internal/ssa/cse.go b/src/cmd/internal/ssa/cse.go new file mode 100644 index 0000000000..71f23013cf --- /dev/null +++ b/src/cmd/internal/ssa/cse.go @@ -0,0 +1,163 @@ +// 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 ( + "sort" +) + +// cse does common-subexpression elimination on the Function. +// Values are just relinked, nothing is deleted. A subsequent deadcode +// pass is required to actually remove duplicate expressions. +func cse(f *Func) { + // Two values are equivalent if they satisfy the following definition: + // equivalent(v, w): + // v.op == w.op + // v.type == w.type + // v.aux == w.aux + // len(v.args) == len(w.args) + // equivalent(v.args[i], w.args[i]) for i in 0..len(v.args)-1 + + // The algorithm searches for a partition of f's values into + // equivalence classes using the above definition. + // It starts with a coarse partition and iteratively refines it + // until it reaches a fixed point. + + // Make initial partition based on opcode/type/aux/nargs + // TODO(khr): types are not canonical, so we may split unnecessarily. Fix that. + type key struct { + op Op + typ Type + aux interface{} + nargs int + } + m := map[key]eqclass{} + for _, b := range f.Blocks { + for _, v := range b.Values { + k := key{v.Op, v.Type, v.Aux, len(v.Args)} + m[k] = append(m[k], v) + } + } + + // A partition is a set of disjoint eqclasses. + var partition []eqclass + for _, v := range m { + partition = append(partition, v) + } + + // map from value id back to eqclass id + valueEqClass := make([]int, f.NumValues()) + for i, e := range partition { + for _, v := range e { + valueEqClass[v.ID] = i + } + } + + // Find an equivalence class where some members of the class have + // non-equvalent arguments. Split the equivalence class appropriately. + // Repeat until we can't find any more splits. + for { + changed := false + + for i, e := range partition { + v := e[0] + // all values in this equiv class that are not equivalent to v get moved + // into another equiv class q. + var q eqclass + eqloop: + for j := 1; j < len(e); { + w := e[j] + for i := 0; i < len(v.Args); i++ { + if valueEqClass[v.Args[i].ID] != valueEqClass[w.Args[i].ID] { + // w is not equivalent to v. + // remove w from e + e, e[j] = e[:len(e)-1], e[len(e)-1] + // add w to q + q = append(q, w) + valueEqClass[w.ID] = len(partition) + changed = true + continue eqloop + } + } + // v and w are equivalent. Keep w in e. + j++ + } + partition[i] = e + if q != nil { + partition = append(partition, q) + } + } + + if !changed { + break + } + } + + // Compute dominator tree + idom := dominators(f) + + // Compute substitutions we would like to do. We substitute v for w + // if v and w are in the same equivalence class and v dominates w. + rewrite := make([]*Value, f.NumValues()) + for _, e := range partition { + sort.Sort(e) // ensure deterministic ordering + for len(e) > 1 { + // Find a maximal dominant element in e + v := e[0] + for _, w := range e[1:] { + if dom(w.Block, v.Block, idom) { + v = w + } + } + + // Replace all elements of e which v dominates + for i := 0; i < len(e); { + w := e[i] + if w != v && dom(v.Block, w.Block, idom) { + rewrite[w.ID] = v + e, e[i] = e[:len(e)-1], e[len(e)-1] + } else { + i++ + } + } + // TODO(khr): if value is a control value, do we need to keep it block-local? + } + } + + // Apply substitutions + for _, b := range f.Blocks { + for _, v := range b.Values { + for i, w := range v.Args { + if x := rewrite[w.ID]; x != nil { + v.SetArg(i, x) + } + } + } + } +} + +// returns true if b dominates c. +// TODO(khr): faster +func dom(b, c *Block, idom []*Block) bool { + // Walk up from c in the dominator tree looking for b. + for c != nil { + if c == b { + return true + } + c = idom[c.ID] + } + // Reached the entry block, never saw b. + return false +} + +// An eqclass approximates an equivalence class. During the +// algorithm it may represent the union of several of the +// final equivalence classes. +type eqclass []*Value + +// Sort an equivalence class by value ID. +func (e eqclass) Len() int { return len(e) } +func (e eqclass) Swap(i, j int) { e[i], e[j] = e[j], e[i] } +func (e eqclass) Less(i, j int) bool { return e[i].ID < e[j].ID } diff --git a/src/cmd/internal/ssa/dom.go b/src/cmd/internal/ssa/dom.go new file mode 100644 index 0000000000..aaf3ab3da1 --- /dev/null +++ b/src/cmd/internal/ssa/dom.go @@ -0,0 +1,121 @@ +// 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 + +// This file contains code to compute the dominator tree +// of a control-flow graph. + +import "log" + +// postorder computes a postorder traversal ordering for the +// basic blocks in f. Unreachable blocks will not appear. +func postorder(f *Func) []*Block { + mark := make([]byte, f.NumBlocks()) + // mark values + const ( + notFound = 0 // block has not been discovered yet + notExplored = 1 // discovered and in queue, outedges not processed yet + explored = 2 // discovered and in queue, outedges processed + done = 3 // all done, in output ordering + ) + + // result ordering + var order []*Block + + // stack of blocks + var s []*Block + s = append(s, f.Entry) + mark[f.Entry.ID] = notExplored + for len(s) > 0 { + b := s[len(s)-1] + switch mark[b.ID] { + case explored: + // Children have all been visited. Pop & output block. + s = s[:len(s)-1] + mark[b.ID] = done + order = append(order, b) + case notExplored: + // Children have not been visited yet. Mark as explored + // and queue any children we haven't seen yet. + mark[b.ID] = explored + for _, c := range b.Succs { + if mark[c.ID] == notFound { + mark[c.ID] = notExplored + s = append(s, c) + } + } + default: + log.Fatalf("bad stack state %v %d", b, mark[b.ID]) + } + } + return order +} + +// dominators computes the dominator tree for f. It returns a slice +// which maps block ID to the immediate dominator of that block. +// Unreachable blocks map to nil. The entry block maps to nil. +func dominators(f *Func) []*Block { + // A simple algorithm for now + // Cooper, Harvey, Kennedy + idom := make([]*Block, f.NumBlocks()) + + // Compute postorder walk + post := postorder(f) + + // Make map from block id to order index (for intersect call) + postnum := make([]int, f.NumBlocks()) + for i, b := range post { + postnum[b.ID] = i + } + + // Make the entry block a self-loop + idom[f.Entry.ID] = f.Entry + if postnum[f.Entry.ID] != len(post)-1 { + log.Fatalf("entry block %v not last in postorder", f.Entry) + } + + // Compute relaxation of idom entries + for { + changed := false + + for i := len(post) - 2; i >= 0; i-- { + b := post[i] + var d *Block + for _, p := range b.Preds { + if idom[p.ID] == nil { + continue + } + if d == nil { + d = p + continue + } + d = intersect(d, p, postnum, idom) + } + if d != idom[b.ID] { + idom[b.ID] = d + changed = true + } + } + if !changed { + break + } + } + // Set idom of entry block to nil instead of itself. + idom[f.Entry.ID] = nil + return idom +} + +// intersect finds the closest dominator of both b and c. +// It requires a postorder numbering of all the blocks. +func intersect(b, c *Block, postnum []int, idom []*Block) *Block { + for b != c { + if postnum[b.ID] < postnum[c.ID] { + b = idom[b.ID] + } else { + c = idom[c.ID] + } + } + return b +} diff --git a/src/cmd/internal/ssa/lower.go b/src/cmd/internal/ssa/lower.go index 7d97b0b466..18fe9861a6 100644 --- a/src/cmd/internal/ssa/lower.go +++ b/src/cmd/internal/ssa/lower.go @@ -39,5 +39,4 @@ func lower(f *Func) { // TODO: others } } - deadcode(f) // TODO: separate pass? } -- cgit v1.3 From 412944484c775d86d677e1dce38b923e27b437b0 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 31 Mar 2015 15:37:25 -0700 Subject: [dev.ssa] cmd/internal/ssa: fix typo Change-Id: I2209da94f1fd76267847d8d599e17f9d9a296ed3 Reviewed-on: https://go-review.googlesource.com/8320 Reviewed-by: Alan Donovan --- src/cmd/internal/ssa/cse.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/ssa/cse.go b/src/cmd/internal/ssa/cse.go index 71f23013cf..c44b08f61c 100644 --- a/src/cmd/internal/ssa/cse.go +++ b/src/cmd/internal/ssa/cse.go @@ -56,7 +56,7 @@ func cse(f *Func) { } // Find an equivalence class where some members of the class have - // non-equvalent arguments. Split the equivalence class appropriately. + // non-equivalent arguments. Split the equivalence class appropriately. // Repeat until we can't find any more splits. for { changed := false -- cgit v1.3 From d2fd43aa770094e579bc394946e2ce9c75a44417 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Wed, 15 Apr 2015 15:51:25 -0700 Subject: [dev.ssa] cmd/internal/gc: convert standard IR into SSA. Hook into the current compiler to convert the existing IR (after walk) into SSA. Any function ending in "_ssa" will take this path. The resulting assembly is printed and then discarded. Use gc.Type directly in ssa instead of a wrapper for go types. It makes the IR->SSA rewrite a lot simpler. Only a few opcodes are implemented in this change. It is enough to compile simple examples like func f(p *int) int { return *p } func g(a []int, i int) int { return a[i] } Change-Id: I5e18841b752a83ca0519aa1b2d36ef02ce1de6f9 Reviewed-on: https://go-review.googlesource.com/8971 Reviewed-by: Alan Donovan --- src/cmd/dist/buildtool.go | 1 + src/cmd/internal/gc/pgen.go | 9 + src/cmd/internal/gc/ssa.go | 450 ++++++++++++++++++++ src/cmd/internal/gc/type.go | 62 +++ src/cmd/internal/ssa/cgen.go | 49 ++- src/cmd/internal/ssa/check.go | 1 - src/cmd/internal/ssa/config.go | 48 +++ src/cmd/internal/ssa/cse.go | 8 +- src/cmd/internal/ssa/deadcode.go | 1 + src/cmd/internal/ssa/deadcode_test.go | 6 +- src/cmd/internal/ssa/func.go | 51 ++- src/cmd/internal/ssa/fuse.go | 3 + src/cmd/internal/ssa/generic.go | 144 ++++--- src/cmd/internal/ssa/id.go | 2 - src/cmd/internal/ssa/lower.go | 30 +- src/cmd/internal/ssa/lowerAmd64.go | 548 ++++++++++++++++++++----- src/cmd/internal/ssa/op.go | 79 ++-- src/cmd/internal/ssa/op_string.go | 4 +- src/cmd/internal/ssa/rewrite.go | 43 +- src/cmd/internal/ssa/rulegen/generic.rules | 14 +- src/cmd/internal/ssa/rulegen/lower_amd64.rules | 51 ++- src/cmd/internal/ssa/rulegen/rulegen.go | 16 +- src/cmd/internal/ssa/ssac/main.go | 90 ++-- src/cmd/internal/ssa/type.go | 128 +++--- src/cmd/internal/ssa/types/object.go | 39 -- src/cmd/internal/ssa/types/sizes.go | 117 ------ src/cmd/internal/ssa/types/type.go | 229 ----------- src/cmd/internal/ssa/value.go | 12 - 28 files changed, 1458 insertions(+), 777 deletions(-) create mode 100644 src/cmd/internal/gc/ssa.go create mode 100644 src/cmd/internal/gc/type.go create mode 100644 src/cmd/internal/ssa/config.go delete mode 100644 src/cmd/internal/ssa/types/object.go delete mode 100644 src/cmd/internal/ssa/types/sizes.go delete mode 100644 src/cmd/internal/ssa/types/type.go (limited to 'src/cmd/internal') diff --git a/src/cmd/dist/buildtool.go b/src/cmd/dist/buildtool.go index 69e077c002..daaf66c596 100644 --- a/src/cmd/dist/buildtool.go +++ b/src/cmd/dist/buildtool.go @@ -46,6 +46,7 @@ var bootstrapDirs = []string{ "internal/obj/arm64", "internal/obj/ppc64", "internal/obj/x86", + "internal/ssa", "old5a", "old6a", "old8a", diff --git a/src/cmd/internal/gc/pgen.go b/src/cmd/internal/gc/pgen.go index b6c9f30b98..78b41eef4d 100644 --- a/src/cmd/internal/gc/pgen.go +++ b/src/cmd/internal/gc/pgen.go @@ -418,6 +418,15 @@ func compile(fn *Node) { goto ret } + // Build an SSA backend function + { + name := Curfn.Nname.Sym.Name + if len(name) > 4 && name[len(name)-4:] == "_ssa" { + buildssa(Curfn) + // TODO(khr): use result of buildssa + } + } + continpc = nil breakpc = nil diff --git a/src/cmd/internal/gc/ssa.go b/src/cmd/internal/gc/ssa.go new file mode 100644 index 0000000000..415e9dc639 --- /dev/null +++ b/src/cmd/internal/gc/ssa.go @@ -0,0 +1,450 @@ +// 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 gc + +import ( + "log" + + "cmd/internal/ssa" +) + +func buildssa(fn *Node) { + dumplist("buildssa", Curfn.Nbody) + + var s ssaState + + // TODO(khr): build config just once at the start of the compiler binary + s.config = ssa.NewConfig(Thearch.Thestring) + s.f = s.config.NewFunc() + s.f.Name = fn.Nname.Sym.Name + + // 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 + // TODO: check this comment + + // Allocate starting block + s.f.Entry = s.f.NewBlock(ssa.BlockPlain) + + // Allocate exit block + s.exit = s.f.NewBlock(ssa.BlockExit) + + // TODO(khr): all args. Make a struct containing args/returnvals, declare + // an FP which contains a pointer to that struct. + + s.vars = map[string]*ssa.Value{} + s.labels = map[string]*ssa.Block{} + s.argOffsets = map[string]int64{} + + // Convert the AST-based IR to the SSA-based IR + s.startBlock(s.f.Entry) + s.stmtList(fn.Nbody) + + // Finish up exit block + s.startBlock(s.exit) + s.exit.Control = s.mem() + s.endBlock() + + // Link up variable uses to variable definitions + s.linkForwardReferences() + + ssa.Compile(s.f) + + // TODO(khr): Use the resulting s.f to generate code +} + +type ssaState struct { + // configuration (arch) information + config *ssa.Config + + // function we're building + f *ssa.Func + + // exit block that "return" jumps to (and panics jump to) + exit *ssa.Block + + // the target block for each label in f + labels map[string]*ssa.Block + + // current location where we're interpreting the AST + curBlock *ssa.Block + + // variable assignments in the current block (map from variable name to ssa value) + vars map[string]*ssa.Value + + // all defined variables at the end of each block. Indexed by block ID. + defvars []map[string]*ssa.Value + + // offsets of argument slots + // unnamed and unused args are not listed. + argOffsets map[string]int64 +} + +// startBlock sets the current block we're generating code in to b. +func (s *ssaState) startBlock(b *ssa.Block) { + s.curBlock = b + s.vars = map[string]*ssa.Value{} +} + +// endBlock marks the end of generating code for the current block. +// Returns the (former) current block. Returns nil if there is no current +// block, i.e. if no code flows to the current execution point. +func (s *ssaState) endBlock() *ssa.Block { + b := s.curBlock + if b == nil { + return nil + } + for len(s.defvars) <= int(b.ID) { + s.defvars = append(s.defvars, nil) + } + s.defvars[b.ID] = s.vars + s.curBlock = nil + s.vars = nil + return b +} + +// ssaStmtList converts the statement n to SSA and adds it to s. +func (s *ssaState) stmtList(l *NodeList) { + for ; l != nil; l = l.Next { + s.stmt(l.N) + } +} + +// ssaStmt converts the statement n to SSA and adds it to s. +func (s *ssaState) stmt(n *Node) { + s.stmtList(n.Ninit) + switch n.Op { + + case OBLOCK: + s.stmtList(n.List) + + case ODCL: + // TODO: ??? Assign 0? + + case OLABEL, OGOTO: + // get block at label, or make one + t := s.labels[n.Left.Sym.Name] + if t == nil { + t = s.f.NewBlock(ssa.BlockPlain) + s.labels[n.Left.Sym.Name] = t + } + // go to that label (we pretend "label:" is preceded by "goto label") + b := s.endBlock() + addEdge(b, t) + + if n.Op == OLABEL { + // next we work on the label's target block + s.startBlock(t) + } + + case OAS: + // TODO(khr): colas? + val := s.expr(n.Right) + if n.Left.Op == OINDREG { + // indirect off a register (TODO: always SP?) + // used for storing arguments to callees + addr := s.f.Entry.NewValue(ssa.OpSPAddr, Ptrto(n.Right.Type), n.Left.Xoffset) + s.vars[".mem"] = s.curBlock.NewValue3(ssa.OpStore, ssa.TypeMem, nil, addr, val, s.mem()) + } else if n.Left.Op != ONAME { + // some more complicated expression. Rewrite to a store. TODO + addr := s.expr(n.Left) // TODO: wrap in & + + // TODO(khr): nil check + s.vars[".mem"] = s.curBlock.NewValue3(ssa.OpStore, n.Right.Type, nil, addr, val, s.mem()) + } else if n.Left.Addable == 0 { + // TODO + log.Fatalf("assignment to non-addable value") + } else if n.Left.Class&PHEAP != 0 { + // TODO + log.Fatalf("assignment to heap value") + } else if n.Left.Class == PPARAMOUT { + // store to parameter slot + addr := s.f.Entry.NewValue(ssa.OpFPAddr, Ptrto(n.Right.Type), n.Left.Xoffset) + s.vars[".mem"] = s.curBlock.NewValue3(ssa.OpStore, ssa.TypeMem, nil, addr, val, s.mem()) + } else { + // normal variable + s.vars[n.Left.Sym.Name] = val + } + case OIF: + cond := s.expr(n.Ntest) + b := s.endBlock() + b.Kind = ssa.BlockIf + b.Control = cond + // TODO(khr): likely direction + + bThen := s.f.NewBlock(ssa.BlockPlain) + bEnd := s.f.NewBlock(ssa.BlockPlain) + var bElse *ssa.Block + + if n.Nelse == nil { + addEdge(b, bThen) + addEdge(b, bEnd) + } else { + bElse = s.f.NewBlock(ssa.BlockPlain) + addEdge(b, bThen) + addEdge(b, bElse) + } + + s.startBlock(bThen) + s.stmtList(n.Nbody) + b = s.endBlock() + if b != nil { + addEdge(b, bEnd) + } + + if n.Nelse != nil { + s.startBlock(bElse) + s.stmtList(n.Nelse) + b = s.endBlock() + if b != nil { + addEdge(b, bEnd) + } + } + s.startBlock(bEnd) + + case ORETURN: + s.stmtList(n.List) + b := s.endBlock() + addEdge(b, s.exit) + + case OFOR: + bCond := s.f.NewBlock(ssa.BlockPlain) + bBody := s.f.NewBlock(ssa.BlockPlain) + bEnd := s.f.NewBlock(ssa.BlockPlain) + + // first, jump to condition test + b := s.endBlock() + addEdge(b, bCond) + + // generate code to test condition + // TODO(khr): Ntest == nil exception + s.startBlock(bCond) + cond := s.expr(n.Ntest) + b = s.endBlock() + b.Kind = ssa.BlockIf + b.Control = cond + // TODO(khr): likely direction + addEdge(b, bBody) + addEdge(b, bEnd) + + // generate body + s.startBlock(bBody) + s.stmtList(n.Nbody) + s.stmt(n.Nincr) + b = s.endBlock() + addEdge(b, bCond) + + s.startBlock(bEnd) + + case OVARKILL: + // TODO(khr): ??? anything to do here? Only for addrtaken variables? + // Maybe just link it in the store chain? + default: + log.Fatalf("unhandled stmt %s", opnames[n.Op]) + } +} + +// expr converts the expression n to ssa, adds it to s and returns the ssa result. +func (s *ssaState) expr(n *Node) *ssa.Value { + if n == nil { + // TODO(khr): is this nil??? + return s.f.Entry.NewValue(ssa.OpConst, n.Type, nil) + } + switch n.Op { + case ONAME: + // remember offsets for PPARAM names + s.argOffsets[n.Sym.Name] = n.Xoffset + return s.variable(n.Sym.Name, n.Type) + // binary ops + case OLITERAL: + switch n.Val.Ctype { + case CTINT: + return s.f.ConstInt(n.Type, Mpgetfix(n.Val.U.Xval)) + default: + log.Fatalf("unhandled OLITERAL %v", n.Val.Ctype) + return nil + } + case OLT: + a := s.expr(n.Left) + b := s.expr(n.Right) + return s.curBlock.NewValue2(ssa.OpLess, ssa.TypeBool, nil, a, b) + case OADD: + a := s.expr(n.Left) + b := s.expr(n.Right) + return s.curBlock.NewValue2(ssa.OpAdd, a.Type, nil, a, b) + + case OSUB: + // TODO:(khr) fold code for all binary ops together somehow + a := s.expr(n.Left) + b := s.expr(n.Right) + return s.curBlock.NewValue2(ssa.OpSub, a.Type, nil, a, b) + + case OIND: + p := s.expr(n.Left) + c := s.curBlock.NewValue1(ssa.OpCheckNil, ssa.TypeBool, nil, p) + b := s.endBlock() + b.Kind = ssa.BlockIf + b.Control = c + bNext := s.f.NewBlock(ssa.BlockPlain) + addEdge(b, bNext) + addEdge(b, s.exit) + s.startBlock(bNext) + // TODO(khr): if ptr check fails, don't go directly to exit. + // Instead, go to a call to panicnil or something. + // TODO: implicit nil checks somehow? + + return s.curBlock.NewValue2(ssa.OpLoad, n.Type, nil, p, s.mem()) + case ODOTPTR: + p := s.expr(n.Left) + // TODO: nilcheck + p = s.curBlock.NewValue2(ssa.OpAdd, p.Type, nil, p, s.f.ConstInt(s.config.UIntPtr, n.Xoffset)) + return s.curBlock.NewValue2(ssa.OpLoad, n.Type, nil, p, s.mem()) + + case OINDEX: + // TODO: slice vs array? Map index is already reduced to a function call + a := s.expr(n.Left) + i := s.expr(n.Right) + // convert index to full width + // TODO: if index is 64-bit and we're compiling to 32-bit, check that high + // 32 bits are zero (and use a low32 op instead of convnop here). + i = s.curBlock.NewValue1(ssa.OpConvNop, s.config.UIntPtr, nil, i) + + // bounds check + len := s.curBlock.NewValue1(ssa.OpSliceLen, s.config.UIntPtr, nil, a) + cmp := s.curBlock.NewValue2(ssa.OpCheckBound, ssa.TypeBool, nil, i, len) + b := s.endBlock() + b.Kind = ssa.BlockIf + b.Control = cmp + bNext := s.f.NewBlock(ssa.BlockPlain) + addEdge(b, bNext) + addEdge(b, s.exit) + s.startBlock(bNext) + // TODO: don't go directly to s.exit. Go to a stub that calls panicindex first. + + return s.curBlock.NewValue3(ssa.OpSliceIndex, n.Left.Type.Type, nil, a, i, s.mem()) + + case OCALLFUNC: + // run all argument assignments + // TODO(khr): do we need to evaluate function first? + // Or is it already side-effect-free and does not require a call? + s.stmtList(n.List) + + if n.Left.Op != ONAME { + // TODO(khr): closure calls? + log.Fatalf("can't handle CALLFUNC with non-ONAME fn %s", opnames[n.Left.Op]) + } + bNext := s.f.NewBlock(ssa.BlockPlain) + call := s.curBlock.NewValue1(ssa.OpStaticCall, ssa.TypeMem, n.Left.Sym.Name, s.mem()) + b := s.endBlock() + b.Kind = ssa.BlockCall + b.Control = call + addEdge(b, bNext) + addEdge(b, s.exit) + + // read result from stack at the start of the fallthrough block + s.startBlock(bNext) + var titer Iter + fp := Structfirst(&titer, Getoutarg(n.Left.Type)) + a := s.f.Entry.NewValue(ssa.OpSPAddr, Ptrto(fp.Type), fp.Width) + return s.curBlock.NewValue2(ssa.OpLoad, fp.Type, nil, a, call) + default: + log.Fatalf("unhandled expr %s", opnames[n.Op]) + return nil + } +} + +// variable returns the value of a variable at the current location. +func (s *ssaState) variable(name string, t ssa.Type) *ssa.Value { + if s.curBlock == nil { + log.Fatalf("nil curblock!") + } + v := s.vars[name] + if v == nil { + // TODO: get type? Take Sym as arg? + v = s.curBlock.NewValue(ssa.OpFwdRef, t, name) + s.vars[name] = v + } + return v +} + +func (s *ssaState) mem() *ssa.Value { + return s.variable(".mem", ssa.TypeMem) +} + +func (s *ssaState) linkForwardReferences() { + // Build ssa graph. Each variable on its first use in a basic block + // leaves a FwdRef in that block representing the incoming value + // of that variable. This function links that ref up with possible definitions, + // inserting Phi values as needed. This is essentially the algorithm + // described by Brau, Buchwald, Hack, Leißa, Mallon, and Zwinkau: + // http://pp.info.uni-karlsruhe.de/uploads/publikationen/braun13cc.pdf + for _, b := range s.f.Blocks { + for _, v := range b.Values { + if v.Op != ssa.OpFwdRef { + continue + } + name := v.Aux.(string) + v.Op = ssa.OpCopy + v.Aux = nil + v.SetArgs1(s.lookupVarIncoming(b, v.Type, name)) + } + } +} + +// lookupVarIncoming finds the variable's value at the start of block b. +func (s *ssaState) lookupVarIncoming(b *ssa.Block, t ssa.Type, name string) *ssa.Value { + // TODO(khr): have lookupVarIncoming overwrite the fwdRef or copy it + // will be used in, instead of having the result used in a copy value. + if b == s.f.Entry { + if name == ".mem" { + return b.NewValue(ssa.OpArg, t, name) + } + // variable is live at the entry block. Load it. + a := s.f.Entry.NewValue(ssa.OpFPAddr, Ptrto(t.(*Type)), s.argOffsets[name]) + m := b.NewValue(ssa.OpArg, ssa.TypeMem, ".mem") // TODO: reuse mem starting value + return b.NewValue2(ssa.OpLoad, t, nil, a, m) + } + var vals []*ssa.Value + for _, p := range b.Preds { + vals = append(vals, s.lookupVarOutgoing(p, t, name)) + } + v0 := vals[0] + for i := 1; i < len(vals); i++ { + if vals[i] != v0 { + // need a phi value + v := b.NewValue(ssa.OpPhi, t, nil) + v.AddArgs(vals...) + return v + } + } + return v0 +} + +// lookupVarOutgoing finds the variable's value at the end of block b. +func (s *ssaState) lookupVarOutgoing(b *ssa.Block, t ssa.Type, name string) *ssa.Value { + m := s.defvars[b.ID] + if v, ok := m[name]; ok { + return v + } + // The variable is not defined by b and we haven't + // looked it up yet. Generate v, a copy value which + // will be the outgoing value of the variable. Then + // look up w, the incoming value of the variable. + // Make v = copy(w). We need the extra copy to + // prevent infinite recursion when looking up the + // incoming value of the variable. + v := b.NewValue(ssa.OpCopy, t, nil) + m[name] = v + v.AddArg(s.lookupVarIncoming(b, t, name)) + return v +} + +// TODO: the above mutually recursive functions can lead to very deep stacks. Fix that. + +// addEdge adds an edge from b to c. +func addEdge(b, c *ssa.Block) { + b.Succs = append(b.Succs, c) + c.Preds = append(c.Preds, b) +} diff --git a/src/cmd/internal/gc/type.go b/src/cmd/internal/gc/type.go new file mode 100644 index 0000000000..e88ca7c898 --- /dev/null +++ b/src/cmd/internal/gc/type.go @@ -0,0 +1,62 @@ +// 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 file provides methods that let us export a Type as an ../ssa:Type. +// We don't export this package's Type directly because it would lead +// to an import cycle with this package and ../ssa. +// TODO: move Type to its own package, then we don't need to dance around import cycles. + +package gc + +import ( + "cmd/internal/ssa" +) + +func (t *Type) Size() int64 { + dowidth(t) + return t.Width +} + +func (t *Type) IsBoolean() bool { + return t.Etype == TBOOL +} + +func (t *Type) IsInteger() bool { + switch t.Etype { + case TINT8, TUINT8, TINT16, TUINT16, TINT32, TUINT32, TINT64, TUINT64, TINT, TUINT, TUINTPTR: + return true + } + return false +} + +func (t *Type) IsSigned() bool { + switch t.Etype { + case TINT8, TINT16, TINT32, TINT64, TINT: + return true + } + return false +} + +func (t *Type) IsFloat() bool { + return t.Etype == TFLOAT32 || t.Etype == TFLOAT64 +} + +func (t *Type) IsPtr() bool { + return t.Etype == TPTR32 || t.Etype == TPTR64 || + t.Etype == TMAP || t.Etype == TCHAN || t.Etype == TFUNC +} + +func (t *Type) Elem() ssa.Type { + return t.Type +} +func (t *Type) PtrTo() ssa.Type { + return Ptrto(t) +} + +func (t *Type) IsMemory() bool { return false } +func (t *Type) IsFlags() bool { return false } + +func (t *Type) String() string { + return typefmt(t, 0) +} diff --git a/src/cmd/internal/ssa/cgen.go b/src/cmd/internal/ssa/cgen.go index 4b1a90b89d..c13e715653 100644 --- a/src/cmd/internal/ssa/cgen.go +++ b/src/cmd/internal/ssa/cgen.go @@ -4,7 +4,11 @@ package ssa -import "fmt" +import ( + "bytes" + "fmt" + "os" +) // cgen selects machine instructions for the function. // This pass generates assembly output for now, but should @@ -20,27 +24,30 @@ func cgen(f *Func) { for idx, b := range f.Blocks { fmt.Printf("%d:\n", b.ID) for _, v := range b.Values { + var buf bytes.Buffer asm := opcodeTable[v.Op].asm - fmt.Print("\t") - if asm == "" { - fmt.Print("\t") - } + buf.WriteString(" ") for i := 0; i < len(asm); i++ { switch asm[i] { default: - fmt.Printf("%c", asm[i]) + buf.WriteByte(asm[i]) + case '\t': + buf.WriteByte(' ') + for buf.Len()%8 != 0 { + buf.WriteByte(' ') + } case '%': i++ switch asm[i] { case '%': - fmt.Print("%") + buf.WriteByte('%') case 'I': i++ n := asm[i] - '0' if f.RegAlloc[v.Args[n].ID] != nil { - fmt.Print(f.RegAlloc[v.Args[n].ID].Name()) + buf.WriteString(f.RegAlloc[v.Args[n].ID].Name()) } else { - fmt.Printf("v%d", v.Args[n].ID) + fmt.Fprintf(&buf, "v%d", v.Args[n].ID) } case 'O': i++ @@ -49,17 +56,22 @@ func cgen(f *Func) { panic("can only handle 1 output for now") } if f.RegAlloc[v.ID] != nil { - // TODO: output tuple - fmt.Print(f.RegAlloc[v.ID].Name()) + buf.WriteString(f.RegAlloc[v.ID].Name()) } else { - fmt.Printf("v%d", v.ID) + fmt.Fprintf(&buf, "v%d", v.ID) } case 'A': - fmt.Print(v.Aux) + fmt.Fprint(&buf, v.Aux) } } } - fmt.Println("\t; " + v.LongString()) + for buf.Len() < 40 { + buf.WriteByte(' ') + } + buf.WriteString("; ") + buf.WriteString(v.LongString()) + buf.WriteByte('\n') + os.Stdout.Write(buf.Bytes()) } // find next block in layout sequence var next *Block @@ -106,6 +118,15 @@ func cgen(f *Func) { fmt.Printf("\tJLT\t%d\n", b.Succs[0].ID) fmt.Printf("\tJMP\t%d\n", b.Succs[1].ID) } + case BlockULT: + if b.Succs[0] == next { + fmt.Printf("\tJAE\t%d\n", b.Succs[1].ID) + } else if b.Succs[1] == next { + fmt.Printf("\tJB\t%d\n", b.Succs[0].ID) + } else { + fmt.Printf("\tJB\t%d\n", b.Succs[0].ID) + fmt.Printf("\tJMP\t%d\n", b.Succs[1].ID) + } default: fmt.Printf("\t%s ->", b.Kind.String()) for _, s := range b.Succs { diff --git a/src/cmd/internal/ssa/check.go b/src/cmd/internal/ssa/check.go index b501cdb54c..453388a899 100644 --- a/src/cmd/internal/ssa/check.go +++ b/src/cmd/internal/ssa/check.go @@ -106,7 +106,6 @@ func checkFunc(f *Func) { 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 } diff --git a/src/cmd/internal/ssa/config.go b/src/cmd/internal/ssa/config.go new file mode 100644 index 0000000000..80acda4b23 --- /dev/null +++ b/src/cmd/internal/ssa/config.go @@ -0,0 +1,48 @@ +// 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" + +type Config struct { + arch string // "amd64", etc. + ptrSize int64 // 4 or 8 + UIntPtr Type // pointer arithmetic type + lower func(*Value) bool // lowering function + + // TODO: more stuff. Compiler flags of interest, ... +} + +// NewConfig returns a new configuration object for the given architecture. +func NewConfig(arch string) *Config { + c := &Config{arch: arch} + switch arch { + case "amd64": + c.ptrSize = 8 + c.lower = lowerAmd64 + case "386": + c.ptrSize = 4 + c.lower = lowerAmd64 // TODO(khr): full 32-bit support + default: + log.Fatalf("arch %s not implemented", arch) + } + + // cache the intptr type in the config + c.UIntPtr = TypeUInt32 + if c.ptrSize == 8 { + c.UIntPtr = TypeUInt64 + } + + return c +} + +// NewFunc returns a new, empty function object +func (c *Config) NewFunc() *Func { + // TODO(khr): should this function take name, type, etc. as arguments? + return &Func{Config: c} +} + +// TODO(khr): do we really need a separate Config, or can we just +// store all its fields inside a Func? diff --git a/src/cmd/internal/ssa/cse.go b/src/cmd/internal/ssa/cse.go index c44b08f61c..aba24aeabc 100644 --- a/src/cmd/internal/ssa/cse.go +++ b/src/cmd/internal/ssa/cse.go @@ -4,9 +4,7 @@ package ssa -import ( - "sort" -) +import "sort" // cse does common-subexpression elimination on the Function. // Values are just relinked, nothing is deleted. A subsequent deadcode @@ -115,7 +113,9 @@ func cse(f *Func) { // Replace all elements of e which v dominates for i := 0; i < len(e); { w := e[i] - if w != v && dom(v.Block, w.Block, idom) { + if w == v { + e, e[i] = e[:len(e)-1], e[len(e)-1] + } else if dom(v.Block, w.Block, idom) { rewrite[w.ID] = v e, e[i] = e[:len(e)-1], e[len(e)-1] } else { diff --git a/src/cmd/internal/ssa/deadcode.go b/src/cmd/internal/ssa/deadcode.go index f9e4b18d5f..a805861489 100644 --- a/src/cmd/internal/ssa/deadcode.go +++ b/src/cmd/internal/ssa/deadcode.go @@ -115,6 +115,7 @@ func deadcode(f *Func) { f.Blocks = f.Blocks[:i] // TODO: renumber Blocks and Values densely? + // TODO: save dead Values and Blocks for reuse? Or should we just let GC handle it? } // There was an edge b->c. It has been removed from b's successors. diff --git a/src/cmd/internal/ssa/deadcode_test.go b/src/cmd/internal/ssa/deadcode_test.go index 94fc359af7..1b7c81c568 100644 --- a/src/cmd/internal/ssa/deadcode_test.go +++ b/src/cmd/internal/ssa/deadcode_test.go @@ -27,7 +27,7 @@ func TestDeadLoop(t *testing.T) { addEdge(deadblock, exit) // dead value in dead block - deadval := deadblock.NewValue(OpConstBool, TypeBool, true) + deadval := deadblock.NewValue(OpConst, TypeBool, true) deadblock.Control = deadval CheckFunc(f) @@ -55,7 +55,7 @@ func TestDeadValue(t *testing.T) { mem := entry.NewValue(OpArg, TypeMem, ".mem") exit.Control = mem - deadval := entry.NewValue(OpConstInt, TypeInt, 37) + deadval := entry.NewValue(OpConst, TypeInt64, int64(37)) CheckFunc(f) Deadcode(f) @@ -84,7 +84,7 @@ func TestNeverTaken(t *testing.T) { mem := entry.NewValue(OpArg, TypeMem, ".mem") exit.Control = mem - cond := entry.NewValue(OpConstBool, TypeBool, false) + cond := entry.NewValue(OpConst, TypeBool, false) entry.Control = cond CheckFunc(f) diff --git a/src/cmd/internal/ssa/func.go b/src/cmd/internal/ssa/func.go index b4677c97b3..bdc8815e1a 100644 --- a/src/cmd/internal/ssa/func.go +++ b/src/cmd/internal/ssa/func.go @@ -7,6 +7,7 @@ package ssa // A Func represents a Go func declaration (or function literal) and // its body. This package compiles each Func independently. type Func struct { + Config *Config // architecture information 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) @@ -53,9 +54,53 @@ func (b *Block) NewValue(op Op, t Type, aux interface{}) *Value { return v } +// NewValue1 returns a new value in the block with one argument. +func (b *Block) NewValue1(op Op, t Type, aux interface{}, arg *Value) *Value { + v := &Value{ + ID: b.Func.vid.get(), + Op: op, + Type: t, + Aux: aux, + Block: b, + } + v.Args = v.argstorage[:1] + v.Args[0] = arg + b.Values = append(b.Values, v) + return v +} + +// NewValue2 returns a new value in the block with two arguments. +func (b *Block) NewValue2(op Op, t Type, aux interface{}, arg0, arg1 *Value) *Value { + v := &Value{ + ID: b.Func.vid.get(), + Op: op, + Type: t, + Aux: aux, + Block: b, + } + v.Args = v.argstorage[:2] + v.Args[0] = arg0 + v.Args[1] = arg1 + b.Values = append(b.Values, v) + return v +} + +// NewValue3 returns a new value in the block with three arguments. +func (b *Block) NewValue3(op Op, t Type, aux interface{}, arg0, arg1, arg2 *Value) *Value { + v := &Value{ + ID: b.Func.vid.get(), + Op: op, + Type: t, + Aux: aux, + Block: b, + } + v.Args = []*Value{arg0, arg1, arg2} + b.Values = append(b.Values, v) + return v +} + // ConstInt returns an int constant representing its argument. -func (f *Func) ConstInt(c int64) *Value { +func (f *Func) ConstInt(t Type, c int64) *Value { // TODO: cache? - // TODO: different types? - return f.Entry.NewValue(OpConst, TypeInt64, c) + return f.Entry.NewValue(OpConst, t, c) } diff --git a/src/cmd/internal/ssa/fuse.go b/src/cmd/internal/ssa/fuse.go index bfce9ef970..af3e8a8e14 100644 --- a/src/cmd/internal/ssa/fuse.go +++ b/src/cmd/internal/ssa/fuse.go @@ -30,6 +30,9 @@ func fuse(f *Func) { } } } + if f.Entry == b { + f.Entry = c + } // trash b, just in case b.Kind = BlockUnknown diff --git a/src/cmd/internal/ssa/generic.go b/src/cmd/internal/ssa/generic.go index 3118b3af9d..2a96793c61 100644 --- a/src/cmd/internal/ssa/generic.go +++ b/src/cmd/internal/ssa/generic.go @@ -11,23 +11,24 @@ func genericRules(v *Value) bool { { t := v.Type if v.Args[0].Op != OpConst { - goto end0 + goto endc86f5c160a87f6f5ec90b6551ec099d9 } c := v.Args[0].Aux if v.Args[1].Op != OpConst { - goto end0 + goto endc86f5c160a87f6f5ec90b6551ec099d9 } d := v.Args[1].Aux if !(is64BitInt(t) && isSigned(t)) { - goto end0 + goto endc86f5c160a87f6f5ec90b6551ec099d9 } v.Op = OpConst v.Aux = nil - v.Args = v.argstorage[:0] + v.resetArgs() v.Aux = c.(int64) + d.(int64) return true } - end0: + goto endc86f5c160a87f6f5ec90b6551ec099d9 + endc86f5c160a87f6f5ec90b6551ec099d9: ; // match: (Add (Const [c]) (Const [d])) // cond: is64BitInt(t) && !isSigned(t) @@ -35,101 +36,130 @@ func genericRules(v *Value) bool { { t := v.Type if v.Args[0].Op != OpConst { - goto end1 + goto end8941c2a515c1bd38530b7fd96862bac4 } c := v.Args[0].Aux if v.Args[1].Op != OpConst { - goto end1 + goto end8941c2a515c1bd38530b7fd96862bac4 } d := v.Args[1].Aux if !(is64BitInt(t) && !isSigned(t)) { - goto end1 + goto end8941c2a515c1bd38530b7fd96862bac4 } v.Op = OpConst v.Aux = nil - v.Args = v.argstorage[:0] + v.resetArgs() v.Aux = c.(uint64) + d.(uint64) return true } - end1: + goto end8941c2a515c1bd38530b7fd96862bac4 + end8941c2a515c1bd38530b7fd96862bac4: ; - case OpLoad: - // match: (Load (FPAddr [offset]) mem) + case OpSliceCap: + // match: (SliceCap (Load ptr mem)) // cond: - // result: (LoadFP [offset] mem) + // result: (Load (Add ptr (Const [int64(v.Block.Func.Config.ptrSize*2)])) mem) { - if v.Args[0].Op != OpFPAddr { - goto end2 + if v.Args[0].Op != OpLoad { + goto ende03f9b79848867df439b56889bb4e55d } - offset := v.Args[0].Aux - mem := v.Args[1] - v.Op = OpLoadFP + ptr := v.Args[0].Args[0] + mem := v.Args[0].Args[1] + v.Op = OpLoad v.Aux = nil - v.Args = v.argstorage[:0] - v.Aux = offset + v.resetArgs() + v0 := v.Block.NewValue(OpAdd, TypeInvalid, nil) + v0.Type = ptr.Type + v0.AddArg(ptr) + v1 := v.Block.NewValue(OpConst, TypeInvalid, nil) + v1.Type = v.Block.Func.Config.UIntPtr + v1.Aux = int64(v.Block.Func.Config.ptrSize * 2) + v0.AddArg(v1) + v.AddArg(v0) v.AddArg(mem) return true } - end2: + goto ende03f9b79848867df439b56889bb4e55d + ende03f9b79848867df439b56889bb4e55d: ; - // match: (Load (SPAddr [offset]) mem) + case OpSliceIndex: + // match: (SliceIndex s i mem) // cond: - // result: (LoadSP [offset] mem) + // result: (Load (Add (SlicePtr s) (Mul i (Const [s.Type.Elem().Size()]))) mem) { - if v.Args[0].Op != OpSPAddr { - goto end3 - } - offset := v.Args[0].Aux - mem := v.Args[1] - v.Op = OpLoadSP + s := v.Args[0] + i := v.Args[1] + mem := v.Args[2] + v.Op = OpLoad v.Aux = nil - v.Args = v.argstorage[:0] - v.Aux = offset + v.resetArgs() + v0 := v.Block.NewValue(OpAdd, TypeInvalid, nil) + v0.Type = s.Type.Elem().PtrTo() + v1 := v.Block.NewValue(OpSlicePtr, TypeInvalid, nil) + v1.Type = s.Type.Elem().PtrTo() + v1.AddArg(s) + v0.AddArg(v1) + v2 := v.Block.NewValue(OpMul, TypeInvalid, nil) + v2.Type = v.Block.Func.Config.UIntPtr + v2.AddArg(i) + v3 := v.Block.NewValue(OpConst, TypeInvalid, nil) + v3.Type = v.Block.Func.Config.UIntPtr + v3.Aux = s.Type.Elem().Size() + v2.AddArg(v3) + v0.AddArg(v2) + v.AddArg(v0) v.AddArg(mem) return true } - end3: + goto end733704831a61760840348f790b3ab045 + end733704831a61760840348f790b3ab045: ; - case OpStore: - // match: (Store (FPAddr [offset]) val mem) + case OpSliceLen: + // match: (SliceLen (Load ptr mem)) // cond: - // result: (StoreFP [offset] val mem) + // result: (Load (Add ptr (Const [int64(v.Block.Func.Config.ptrSize)])) mem) { - if v.Args[0].Op != OpFPAddr { - goto end4 + if v.Args[0].Op != OpLoad { + goto ende94950a57eca1871c93afdeaadb90223 } - offset := v.Args[0].Aux - val := v.Args[1] - mem := v.Args[2] - v.Op = OpStoreFP + ptr := v.Args[0].Args[0] + mem := v.Args[0].Args[1] + v.Op = OpLoad v.Aux = nil - v.Args = v.argstorage[:0] - v.Aux = offset - v.AddArg(val) + v.resetArgs() + v0 := v.Block.NewValue(OpAdd, TypeInvalid, nil) + v0.Type = ptr.Type + v0.AddArg(ptr) + v1 := v.Block.NewValue(OpConst, TypeInvalid, nil) + v1.Type = v.Block.Func.Config.UIntPtr + v1.Aux = int64(v.Block.Func.Config.ptrSize) + v0.AddArg(v1) + v.AddArg(v0) v.AddArg(mem) return true } - end4: + goto ende94950a57eca1871c93afdeaadb90223 + ende94950a57eca1871c93afdeaadb90223: ; - // match: (Store (SPAddr [offset]) val mem) + case OpSlicePtr: + // match: (SlicePtr (Load ptr mem)) // cond: - // result: (StoreSP [offset] val mem) + // result: (Load ptr mem) { - if v.Args[0].Op != OpSPAddr { - goto end5 + if v.Args[0].Op != OpLoad { + goto end459613b83f95b65729d45c2ed663a153 } - offset := v.Args[0].Aux - val := v.Args[1] - mem := v.Args[2] - v.Op = OpStoreSP + ptr := v.Args[0].Args[0] + mem := v.Args[0].Args[1] + v.Op = OpLoad v.Aux = nil - v.Args = v.argstorage[:0] - v.Aux = offset - v.AddArg(val) + v.resetArgs() + v.AddArg(ptr) v.AddArg(mem) return true } - end5: + goto end459613b83f95b65729d45c2ed663a153 + end459613b83f95b65729d45c2ed663a153: } return false } diff --git a/src/cmd/internal/ssa/id.go b/src/cmd/internal/ssa/id.go index 43f23c838c..3f53e1a434 100644 --- a/src/cmd/internal/ssa/id.go +++ b/src/cmd/internal/ssa/id.go @@ -31,8 +31,6 @@ func (a *idAlloc) get() ID { // 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. diff --git a/src/cmd/internal/ssa/lower.go b/src/cmd/internal/ssa/lower.go index 18fe9861a6..82e5d23241 100644 --- a/src/cmd/internal/ssa/lower.go +++ b/src/cmd/internal/ssa/lower.go @@ -4,19 +4,12 @@ package ssa -var ( - // TODO(khr): put arch configuration constants together somewhere - intSize = 8 - ptrSize = 8 -) - //go:generate go run rulegen/rulegen.go rulegen/lower_amd64.rules lowerAmd64 lowerAmd64.go // convert to machine-dependent ops func lower(f *Func) { // repeat rewrites until we find no more rewrites - // TODO: pick the target arch from config - applyRewrite(f, lowerAmd64) + applyRewrite(f, f.Config.lower) // TODO: check for unlowered opcodes, fail if we find one @@ -29,6 +22,12 @@ func lower(f *Func) { case OpSETL: b.Kind = BlockLT b.Control = b.Control.Args[0] + case OpSETNE: + b.Kind = BlockNE + b.Control = b.Control.Args[0] + case OpSETB: + b.Kind = BlockULT + b.Control = b.Control.Args[0] // TODO: others } case BlockLT: @@ -36,6 +35,21 @@ func lower(f *Func) { b.Kind = BlockGE b.Control = b.Control.Args[0] } + case BlockULT: + if b.Control.Op == OpInvertFlags { + b.Kind = BlockUGE + b.Control = b.Control.Args[0] + } + case BlockEQ: + if b.Control.Op == OpInvertFlags { + b.Kind = BlockNE + b.Control = b.Control.Args[0] + } + case BlockNE: + if b.Control.Op == OpInvertFlags { + b.Kind = BlockEQ + b.Control = b.Control.Args[0] + } // TODO: others } } diff --git a/src/cmd/internal/ssa/lowerAmd64.go b/src/cmd/internal/ssa/lowerAmd64.go index 88f0e43bd8..6c0a42d976 100644 --- a/src/cmd/internal/ssa/lowerAmd64.go +++ b/src/cmd/internal/ssa/lowerAmd64.go @@ -4,6 +4,65 @@ package ssa func lowerAmd64(v *Value) bool { switch v.Op { + case OpADDCQ: + // match: (ADDCQ [c] (LEAQ8 [d] x y)) + // cond: + // result: (LEAQ8 [c.(int64)+d.(int64)] x y) + { + c := v.Aux + if v.Args[0].Op != OpLEAQ8 { + goto end16348939e556e99e8447227ecb986f01 + } + d := v.Args[0].Aux + x := v.Args[0].Args[0] + y := v.Args[0].Args[1] + v.Op = OpLEAQ8 + v.Aux = nil + v.resetArgs() + v.Aux = c.(int64) + d.(int64) + v.AddArg(x) + v.AddArg(y) + return true + } + goto end16348939e556e99e8447227ecb986f01 + end16348939e556e99e8447227ecb986f01: + ; + // match: (ADDCQ [off1] (FPAddr [off2])) + // cond: + // result: (FPAddr [off1.(int64)+off2.(int64)]) + { + off1 := v.Aux + if v.Args[0].Op != OpFPAddr { + goto end28e093ab0618066e6b2609db7aaf309b + } + off2 := v.Args[0].Aux + v.Op = OpFPAddr + v.Aux = nil + v.resetArgs() + v.Aux = off1.(int64) + off2.(int64) + return true + } + goto end28e093ab0618066e6b2609db7aaf309b + end28e093ab0618066e6b2609db7aaf309b: + ; + // match: (ADDCQ [off1] (SPAddr [off2])) + // cond: + // result: (SPAddr [off1.(int64)+off2.(int64)]) + { + off1 := v.Aux + if v.Args[0].Op != OpSPAddr { + goto endd0c27c62d150b88168075c5ba113d1fa + } + off2 := v.Args[0].Aux + v.Op = OpSPAddr + v.Aux = nil + v.resetArgs() + v.Aux = off1.(int64) + off2.(int64) + return true + } + goto endd0c27c62d150b88168075c5ba113d1fa + endd0c27c62d150b88168075c5ba113d1fa: + ; case OpADDQ: // match: (ADDQ x (Const [c])) // cond: @@ -11,55 +70,82 @@ func lowerAmd64(v *Value) bool { { x := v.Args[0] if v.Args[1].Op != OpConst { - goto end0 + goto endef6908cfdf56e102cc327a3ddc14393d } c := v.Args[1].Aux v.Op = OpADDCQ v.Aux = nil - v.Args = v.argstorage[:0] + v.resetArgs() v.Aux = c v.AddArg(x) return true } - end0: + goto endef6908cfdf56e102cc327a3ddc14393d + endef6908cfdf56e102cc327a3ddc14393d: ; // match: (ADDQ (Const [c]) x) // cond: // result: (ADDCQ [c] x) { if v.Args[0].Op != OpConst { - goto end1 + goto endb54a32cf3147f424f08b46db62c69b23 } c := v.Args[0].Aux x := v.Args[1] v.Op = OpADDCQ v.Aux = nil - v.Args = v.argstorage[:0] + v.resetArgs() v.Aux = c v.AddArg(x) return true } - end1: + goto endb54a32cf3147f424f08b46db62c69b23 + endb54a32cf3147f424f08b46db62c69b23: + ; + // match: (ADDQ x (SHLCQ [shift] y)) + // cond: shift.(int64) == 3 + // result: (LEAQ8 [int64(0)] x y) + { + x := v.Args[0] + if v.Args[1].Op != OpSHLCQ { + goto end7fa0d837edd248748cef516853fd9475 + } + shift := v.Args[1].Aux + y := v.Args[1].Args[0] + if !(shift.(int64) == 3) { + goto end7fa0d837edd248748cef516853fd9475 + } + v.Op = OpLEAQ8 + v.Aux = nil + v.resetArgs() + v.Aux = int64(0) + v.AddArg(x) + v.AddArg(y) + return true + } + goto end7fa0d837edd248748cef516853fd9475 + end7fa0d837edd248748cef516853fd9475: ; case OpAdd: // match: (Add x y) - // cond: is64BitInt(t) + // cond: (is64BitInt(t) || isPtr(t)) // result: (ADDQ x y) { t := v.Type x := v.Args[0] y := v.Args[1] - if !(is64BitInt(t)) { - goto end2 + if !(is64BitInt(t) || isPtr(t)) { + goto endf031c523d7dd08e4b8e7010a94cd94c9 } v.Op = OpADDQ v.Aux = nil - v.Args = v.argstorage[:0] + v.resetArgs() v.AddArg(x) v.AddArg(y) return true } - end2: + goto endf031c523d7dd08e4b8e7010a94cd94c9 + endf031c523d7dd08e4b8e7010a94cd94c9: ; // match: (Add x y) // cond: is32BitInt(t) @@ -69,16 +155,17 @@ func lowerAmd64(v *Value) bool { x := v.Args[0] y := v.Args[1] if !(is32BitInt(t)) { - goto end3 + goto end35a02a1587264e40cf1055856ff8445a } v.Op = OpADDL v.Aux = nil - v.Args = v.argstorage[:0] + v.resetArgs() v.AddArg(x) v.AddArg(y) return true } - end3: + goto end35a02a1587264e40cf1055856ff8445a + end35a02a1587264e40cf1055856ff8445a: ; case OpCMPQ: // match: (CMPQ x (Const [c])) @@ -87,30 +174,31 @@ func lowerAmd64(v *Value) bool { { x := v.Args[0] if v.Args[1].Op != OpConst { - goto end4 + goto end1770a40e4253d9f669559a360514613e } c := v.Args[1].Aux v.Op = OpCMPCQ v.Aux = nil - v.Args = v.argstorage[:0] + v.resetArgs() v.AddArg(x) v.Aux = c return true } - end4: + goto end1770a40e4253d9f669559a360514613e + end1770a40e4253d9f669559a360514613e: ; // match: (CMPQ (Const [c]) x) // cond: // result: (InvertFlags (CMPCQ x [c])) { if v.Args[0].Op != OpConst { - goto end5 + goto enda4e64c7eaeda16c1c0db9dac409cd126 } c := v.Args[0].Aux x := v.Args[1] v.Op = OpInvertFlags v.Aux = nil - v.Args = v.argstorage[:0] + v.resetArgs() v0 := v.Block.NewValue(OpCMPCQ, TypeInvalid, nil) v0.Type = TypeFlags v0.AddArg(x) @@ -118,7 +206,47 @@ func lowerAmd64(v *Value) bool { v.AddArg(v0) return true } - end5: + goto enda4e64c7eaeda16c1c0db9dac409cd126 + enda4e64c7eaeda16c1c0db9dac409cd126: + ; + case OpCheckBound: + // match: (CheckBound idx len) + // cond: + // result: (SETB (CMPQ idx len)) + { + idx := v.Args[0] + len := v.Args[1] + v.Op = OpSETB + v.Aux = nil + v.resetArgs() + v0 := v.Block.NewValue(OpCMPQ, TypeInvalid, nil) + v0.Type = TypeFlags + v0.AddArg(idx) + v0.AddArg(len) + v.AddArg(v0) + return true + } + goto end249426f6f996d45a62f89a591311a954 + end249426f6f996d45a62f89a591311a954: + ; + case OpCheckNil: + // match: (CheckNil p) + // cond: + // result: (SETNE (TESTQ p p)) + { + p := v.Args[0] + v.Op = OpSETNE + v.Aux = nil + v.resetArgs() + v0 := v.Block.NewValue(OpTESTQ, TypeInvalid, nil) + v0.Type = TypeFlags + v0.AddArg(p) + v0.AddArg(p) + v.AddArg(v0) + return true + } + goto end90d3057824f74ef953074e473aa0b282 + end90d3057824f74ef953074e473aa0b282: ; case OpLess: // match: (Less x y) @@ -128,11 +256,11 @@ func lowerAmd64(v *Value) bool { x := v.Args[0] y := v.Args[1] if !(is64BitInt(v.Args[0].Type) && isSigned(v.Args[0].Type)) { - goto end6 + goto endcecf13a952d4c6c2383561c7d68a3cf9 } v.Op = OpSETL v.Aux = nil - v.Args = v.argstorage[:0] + v.resetArgs() v0 := v.Block.NewValue(OpCMPQ, TypeInvalid, nil) v0.Type = TypeFlags v0.AddArg(x) @@ -140,49 +268,292 @@ func lowerAmd64(v *Value) bool { v.AddArg(v0) return true } - end6: + goto endcecf13a952d4c6c2383561c7d68a3cf9 + endcecf13a952d4c6c2383561c7d68a3cf9: ; - case OpLoadFP: - // match: (LoadFP [offset] mem) - // cond: typeSize(t) == 8 - // result: (LoadFP8 [offset] mem) + case OpLoad: + // match: (Load ptr mem) + // cond: (is64BitInt(t) || isPtr(t)) + // result: (MOVQload [int64(0)] ptr mem) { t := v.Type - offset := v.Aux - mem := v.Args[0] - if !(typeSize(t) == 8) { - goto end7 + ptr := v.Args[0] + mem := v.Args[1] + if !(is64BitInt(t) || isPtr(t)) { + goto end581ce5a20901df1b8143448ba031685b + } + v.Op = OpMOVQload + v.Aux = nil + v.resetArgs() + v.Aux = int64(0) + v.AddArg(ptr) + v.AddArg(mem) + return true + } + goto end581ce5a20901df1b8143448ba031685b + end581ce5a20901df1b8143448ba031685b: + ; + case OpMOVQload: + // match: (MOVQload [off1] (FPAddr [off2]) mem) + // cond: + // result: (MOVQloadFP [off1.(int64)+off2.(int64)] mem) + { + off1 := v.Aux + if v.Args[0].Op != OpFPAddr { + goto endce972b1aa84b56447978c43def87fa57 } - v.Op = OpLoadFP8 + off2 := v.Args[0].Aux + mem := v.Args[1] + v.Op = OpMOVQloadFP v.Aux = nil - v.Args = v.argstorage[:0] - v.Type = t - v.Aux = offset + v.resetArgs() + v.Aux = off1.(int64) + off2.(int64) v.AddArg(mem) return true } - end7: + goto endce972b1aa84b56447978c43def87fa57 + endce972b1aa84b56447978c43def87fa57: ; - case OpLoadSP: - // match: (LoadSP [offset] mem) - // cond: typeSize(t) == 8 - // result: (LoadSP8 [offset] mem) + // match: (MOVQload [off1] (SPAddr [off2]) mem) + // cond: + // result: (MOVQloadSP [off1.(int64)+off2.(int64)] mem) { - t := v.Type - offset := v.Aux - mem := v.Args[0] - if !(typeSize(t) == 8) { - goto end8 + off1 := v.Aux + if v.Args[0].Op != OpSPAddr { + goto end3d8628a6536350a123be81240b8a1376 + } + off2 := v.Args[0].Aux + mem := v.Args[1] + v.Op = OpMOVQloadSP + v.Aux = nil + v.resetArgs() + v.Aux = off1.(int64) + off2.(int64) + v.AddArg(mem) + return true + } + goto end3d8628a6536350a123be81240b8a1376 + end3d8628a6536350a123be81240b8a1376: + ; + // match: (MOVQload [off1] (ADDCQ [off2] ptr) mem) + // cond: + // result: (MOVQload [off1.(int64)+off2.(int64)] ptr mem) + { + off1 := v.Aux + if v.Args[0].Op != OpADDCQ { + goto enda68a39292ba2a05b3436191cb0bb0516 + } + off2 := v.Args[0].Aux + ptr := v.Args[0].Args[0] + mem := v.Args[1] + v.Op = OpMOVQload + v.Aux = nil + v.resetArgs() + v.Aux = off1.(int64) + off2.(int64) + v.AddArg(ptr) + v.AddArg(mem) + return true + } + goto enda68a39292ba2a05b3436191cb0bb0516 + enda68a39292ba2a05b3436191cb0bb0516: + ; + // match: (MOVQload [off1] (LEAQ8 [off2] ptr idx) mem) + // cond: + // result: (MOVQload8 [off1.(int64)+off2.(int64)] ptr idx mem) + { + off1 := v.Aux + if v.Args[0].Op != OpLEAQ8 { + goto end35060118a284c93323ab3fb827156638 } - v.Op = OpLoadSP8 + off2 := v.Args[0].Aux + ptr := v.Args[0].Args[0] + idx := v.Args[0].Args[1] + mem := v.Args[1] + v.Op = OpMOVQload8 v.Aux = nil - v.Args = v.argstorage[:0] - v.Type = t - v.Aux = offset + v.resetArgs() + v.Aux = off1.(int64) + off2.(int64) + v.AddArg(ptr) + v.AddArg(idx) v.AddArg(mem) return true } - end8: + goto end35060118a284c93323ab3fb827156638 + end35060118a284c93323ab3fb827156638: + ; + case OpMOVQstore: + // match: (MOVQstore [off1] (FPAddr [off2]) val mem) + // cond: + // result: (MOVQstoreFP [off1.(int64)+off2.(int64)] val mem) + { + off1 := v.Aux + if v.Args[0].Op != OpFPAddr { + goto end0a2a81a20558dfc93790aecb1e9cc81a + } + off2 := v.Args[0].Aux + val := v.Args[1] + mem := v.Args[2] + v.Op = OpMOVQstoreFP + v.Aux = nil + v.resetArgs() + v.Aux = off1.(int64) + off2.(int64) + v.AddArg(val) + v.AddArg(mem) + return true + } + goto end0a2a81a20558dfc93790aecb1e9cc81a + end0a2a81a20558dfc93790aecb1e9cc81a: + ; + // match: (MOVQstore [off1] (SPAddr [off2]) val mem) + // cond: + // result: (MOVQstoreSP [off1.(int64)+off2.(int64)] val mem) + { + off1 := v.Aux + if v.Args[0].Op != OpSPAddr { + goto end1cb5b7e766f018270fa434c6f46f607f + } + off2 := v.Args[0].Aux + val := v.Args[1] + mem := v.Args[2] + v.Op = OpMOVQstoreSP + v.Aux = nil + v.resetArgs() + v.Aux = off1.(int64) + off2.(int64) + v.AddArg(val) + v.AddArg(mem) + return true + } + goto end1cb5b7e766f018270fa434c6f46f607f + end1cb5b7e766f018270fa434c6f46f607f: + ; + // match: (MOVQstore [off1] (ADDCQ [off2] ptr) val mem) + // cond: + // result: (MOVQstore [off1.(int64)+off2.(int64)] ptr val mem) + { + off1 := v.Aux + if v.Args[0].Op != OpADDCQ { + goto end271e3052de832e22b1f07576af2854de + } + off2 := v.Args[0].Aux + ptr := v.Args[0].Args[0] + val := v.Args[1] + mem := v.Args[2] + v.Op = OpMOVQstore + v.Aux = nil + v.resetArgs() + v.Aux = off1.(int64) + off2.(int64) + v.AddArg(ptr) + v.AddArg(val) + v.AddArg(mem) + return true + } + goto end271e3052de832e22b1f07576af2854de + end271e3052de832e22b1f07576af2854de: + ; + // match: (MOVQstore [off1] (LEAQ8 [off2] ptr idx) val mem) + // cond: + // result: (MOVQstore8 [off1.(int64)+off2.(int64)] ptr idx val mem) + { + off1 := v.Aux + if v.Args[0].Op != OpLEAQ8 { + goto endb5cba0ee3ba21d2bd8e5aa163d2b984e + } + off2 := v.Args[0].Aux + ptr := v.Args[0].Args[0] + idx := v.Args[0].Args[1] + val := v.Args[1] + mem := v.Args[2] + v.Op = OpMOVQstore8 + v.Aux = nil + v.resetArgs() + v.Aux = off1.(int64) + off2.(int64) + v.AddArg(ptr) + v.AddArg(idx) + v.AddArg(val) + v.AddArg(mem) + return true + } + goto endb5cba0ee3ba21d2bd8e5aa163d2b984e + endb5cba0ee3ba21d2bd8e5aa163d2b984e: + ; + case OpMULCQ: + // match: (MULCQ [c] x) + // cond: c.(int64) == 8 + // result: (SHLCQ [int64(3)] x) + { + c := v.Aux + x := v.Args[0] + if !(c.(int64) == 8) { + goto end90a1c055d9658aecacce5e101c1848b4 + } + v.Op = OpSHLCQ + v.Aux = nil + v.resetArgs() + v.Aux = int64(3) + v.AddArg(x) + return true + } + goto end90a1c055d9658aecacce5e101c1848b4 + end90a1c055d9658aecacce5e101c1848b4: + ; + case OpMULQ: + // match: (MULQ x (Const [c])) + // cond: + // result: (MULCQ [c] x) + { + x := v.Args[0] + if v.Args[1].Op != OpConst { + goto endc427f4838d2e83c00cc097b20bd20a37 + } + c := v.Args[1].Aux + v.Op = OpMULCQ + v.Aux = nil + v.resetArgs() + v.Aux = c + v.AddArg(x) + return true + } + goto endc427f4838d2e83c00cc097b20bd20a37 + endc427f4838d2e83c00cc097b20bd20a37: + ; + // match: (MULQ (Const [c]) x) + // cond: + // result: (MULCQ [c] x) + { + if v.Args[0].Op != OpConst { + goto endd70de938e71150d1c9e8173c2a5b2d95 + } + c := v.Args[0].Aux + x := v.Args[1] + v.Op = OpMULCQ + v.Aux = nil + v.resetArgs() + v.Aux = c + v.AddArg(x) + return true + } + goto endd70de938e71150d1c9e8173c2a5b2d95 + endd70de938e71150d1c9e8173c2a5b2d95: + ; + case OpMul: + // match: (Mul x y) + // cond: is64BitInt(t) + // result: (MULQ x y) + { + t := v.Type + x := v.Args[0] + y := v.Args[1] + if !(is64BitInt(t)) { + goto endfab0d598f376ecba45a22587d50f7aff + } + v.Op = OpMULQ + v.Aux = nil + v.resetArgs() + v.AddArg(x) + v.AddArg(y) + return true + } + goto endfab0d598f376ecba45a22587d50f7aff + endfab0d598f376ecba45a22587d50f7aff: ; case OpSETL: // match: (SETL (InvertFlags x)) @@ -190,16 +561,17 @@ func lowerAmd64(v *Value) bool { // result: (SETGE x) { if v.Args[0].Op != OpInvertFlags { - goto end9 + goto end456c7681d48305698c1ef462d244bdc6 } x := v.Args[0].Args[0] v.Op = OpSETGE v.Aux = nil - v.Args = v.argstorage[:0] + v.resetArgs() v.AddArg(x) return true } - end9: + goto end456c7681d48305698c1ef462d244bdc6 + end456c7681d48305698c1ef462d244bdc6: ; case OpSUBQ: // match: (SUBQ x (Const [c])) @@ -208,17 +580,18 @@ func lowerAmd64(v *Value) bool { { x := v.Args[0] if v.Args[1].Op != OpConst { - goto end10 + goto endb31e242f283867de4722665a5796008c } c := v.Args[1].Aux v.Op = OpSUBCQ v.Aux = nil - v.Args = v.argstorage[:0] + v.resetArgs() v.AddArg(x) v.Aux = c return true } - end10: + goto endb31e242f283867de4722665a5796008c + endb31e242f283867de4722665a5796008c: ; // match: (SUBQ (Const [c]) x) // cond: @@ -226,13 +599,13 @@ func lowerAmd64(v *Value) bool { { t := v.Type if v.Args[0].Op != OpConst { - goto end11 + goto end569cc755877d1f89a701378bec05c08d } c := v.Args[0].Aux x := v.Args[1] v.Op = OpNEGQ v.Aux = nil - v.Args = v.argstorage[:0] + v.resetArgs() v0 := v.Block.NewValue(OpSUBCQ, TypeInvalid, nil) v0.Type = t v0.AddArg(x) @@ -240,49 +613,31 @@ func lowerAmd64(v *Value) bool { v.AddArg(v0) return true } - end11: + goto end569cc755877d1f89a701378bec05c08d + end569cc755877d1f89a701378bec05c08d: ; - case OpStoreFP: - // match: (StoreFP [offset] val mem) - // cond: typeSize(val.Type) == 8 - // result: (StoreFP8 [offset] val mem) + case OpStore: + // match: (Store ptr val mem) + // cond: (is64BitInt(val.Type) || isPtr(val.Type)) + // result: (MOVQstore [int64(0)] ptr val mem) { - offset := v.Aux - val := v.Args[0] - mem := v.Args[1] - if !(typeSize(val.Type) == 8) { - goto end12 - } - v.Op = OpStoreFP8 - v.Aux = nil - v.Args = v.argstorage[:0] - v.Aux = offset - v.AddArg(val) - v.AddArg(mem) - return true - } - end12: - ; - case OpStoreSP: - // match: (StoreSP [offset] val mem) - // cond: typeSize(val.Type) == 8 - // result: (StoreSP8 [offset] val mem) - { - offset := v.Aux - val := v.Args[0] - mem := v.Args[1] - if !(typeSize(val.Type) == 8) { - goto end13 + ptr := v.Args[0] + val := v.Args[1] + mem := v.Args[2] + if !(is64BitInt(val.Type) || isPtr(val.Type)) { + goto end9680b43f504bc06f9fab000823ce471a } - v.Op = OpStoreSP8 + v.Op = OpMOVQstore v.Aux = nil - v.Args = v.argstorage[:0] - v.Aux = offset + v.resetArgs() + v.Aux = int64(0) + v.AddArg(ptr) v.AddArg(val) v.AddArg(mem) return true } - end13: + goto end9680b43f504bc06f9fab000823ce471a + end9680b43f504bc06f9fab000823ce471a: ; case OpSub: // match: (Sub x y) @@ -293,16 +648,17 @@ func lowerAmd64(v *Value) bool { x := v.Args[0] y := v.Args[1] if !(is64BitInt(t)) { - goto end14 + goto ende6ef29f885a8ecf3058212bb95917323 } v.Op = OpSUBQ v.Aux = nil - v.Args = v.argstorage[:0] + v.resetArgs() v.AddArg(x) v.AddArg(y) return true } - end14: + goto ende6ef29f885a8ecf3058212bb95917323 + ende6ef29f885a8ecf3058212bb95917323: } return false } diff --git a/src/cmd/internal/ssa/op.go b/src/cmd/internal/ssa/op.go index 19d973921c..600dc9faa6 100644 --- a/src/cmd/internal/ssa/op.go +++ b/src/cmd/internal/ssa/op.go @@ -17,8 +17,8 @@ const ( // 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. + OpNop // should never be used, appears only briefly during construction, Has type Void. + OpFwdRef // used during ssa construction. Like OpCopy, but the arg has not been specified yet. // 2-input arithmetic OpAdd @@ -28,7 +28,12 @@ const ( // 2-input comparisons OpLess - // constants + // constants. Constant values are stored in the aux field. + // booleans have a bool aux field, strings have a string aux + // field, and so on. All integer types store their value + // in the aux field as an int64 (including int, uint64, etc.). + // We could store int8 as an int8, but that won't work for int, + // as it may be different widths on the host and target. OpConst OpArg // address of a function parameter/result. Memory input is an arg called ".mem". @@ -46,12 +51,11 @@ const ( OpStringPtr OpStringLen - OpSlice - OpIndex - OpIndexAddr + OpSliceIndex + OpSliceIndexAddr - OpLoad // args are ptr, memory - OpStore // args are ptr, value, memory, returns memory + OpLoad // args are ptr, memory. Loads from ptr+aux.(int64) + OpStore // args are ptr, value, memory, returns memory. Stores to ptr+aux.(int64) OpCheckNil // arg[0] != nil OpCheckBound // 0 <= arg[0] < arg[1] @@ -71,14 +75,6 @@ const ( 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 without memory @@ -93,12 +89,22 @@ const ( OpSUBQ OpADDCQ // 1 input arg. output = input + aux.(int64) OpSUBCQ // 1 input arg. output = input - aux.(int64) + OpMULQ + OpMULCQ // output = input * aux.(int64) + OpSHLQ // output = input0 << input1 + OpSHLCQ // output = input << aux.(int64) OpNEGQ OpCMPQ OpCMPCQ // 1 input arg. Compares input with aux.(int64) OpADDL - OpSETL // generate bool = "flags encode less than" - OpSETGE + OpTESTQ // compute flags of arg[0] & arg[1] + OpSETEQ + OpSETNE + + // generate boolean based on the flags setting + OpSETL // less than + OpSETGE // >= + OpSETB // "below" = unsigned less than // InvertFlags reverses direction of flags register interpretation: // (InvertFlags (OpCMPQ a b)) == (OpCMPQ b a) @@ -110,11 +116,16 @@ const ( OpLEAQ4 // x+4*y OpLEAQ8 // x+8*y + OpMOVQload // (ptr, mem): loads from ptr+aux.(int64) + OpMOVQstore // (ptr, val, mem): stores val to ptr+aux.(int64), returns mem + OpMOVQload8 // (ptr,idx,mem): loads from ptr+idx*8+aux.(int64) + OpMOVQstore8 // (ptr,idx,val,mem): stores to ptr+idx*8+aux.(int64), returns mem + // load/store 8-byte integer register from stack slot. - OpLoadFP8 - OpLoadSP8 - OpStoreFP8 - OpStoreSP8 + OpMOVQloadFP + OpMOVQloadSP + OpMOVQstoreFP + OpMOVQstoreSP OpMax // sentinel ) @@ -184,7 +195,9 @@ 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 gploadX = [2][]regMask{{gp, gp, 0}, {gp}} // indexed loads var gpstore = [2][]regMask{{gp, gp, 0}, {0}} +var gpstoreX = [2][]regMask{{gp, gp, gp, 0}, {0}} // indexed stores // Opcodes that represent the input Go program var genericTable = [...]OpInfo{ @@ -197,7 +210,7 @@ var genericTable = [...]OpInfo{ OpLess: {}, OpConst: {}, // aux matches the type (e.g. bool, int64 float64) - OpArg: {}, // aux is the name of the input variable TODO:? + OpArg: {}, // aux is the name of the input variable. Currently only ".mem" is used OpGlobal: {}, // address of a global variable OpFunc: {}, OpCopy: {}, @@ -251,17 +264,25 @@ var amd64Table = [...]OpInfo{ OpADDCQ: {asm: "ADDQ\t$%A,%I0,%O0", reg: gp11_overwrite}, // aux = int64 constant to add OpSUBQ: {asm: "SUBQ\t%I0,%I1,%O0", reg: gp21}, OpSUBCQ: {asm: "SUBQ\t$%A,%I0,%O0", reg: gp11_overwrite}, + OpMULQ: {asm: "MULQ\t%I0,%I1,%O0", reg: gp21}, + OpMULCQ: {asm: "MULQ\t$%A,%I0,%O0", reg: gp11_overwrite}, + OpSHLQ: {asm: "SHLQ\t%I0,%I1,%O0", reg: gp21}, + OpSHLCQ: {asm: "SHLQ\t$%A,%I0,%O0", reg: gp11_overwrite}, OpCMPQ: {asm: "CMPQ\t%I0,%I1", reg: gp2_flags}, // compute arg[0]-arg[1] and produce flags OpCMPCQ: {asm: "CMPQ\t$%A,%I0", reg: gp1_flags}, + OpTESTQ: {asm: "TESTQ\t%I0,%I1", reg: gp2_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}, + // loads and stores + OpMOVQload: {asm: "MOVQ\t%A(%I0),%O0", reg: gpload}, + OpMOVQstore: {asm: "MOVQ\t%I1,%A(%I0)", reg: gpstore}, + OpMOVQload8: {asm: "MOVQ\t%A(%I0)(%I1*8),%O0", reg: gploadX}, + OpMOVQstore8: {asm: "MOVQ\t%I2,%A(%I0)(%I1*8)", reg: gpstoreX}, OpStaticCall: {asm: "CALL\t%A(SB)"}, @@ -271,10 +292,10 @@ var amd64Table = [...]OpInfo{ OpSETL: {}, // 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)"}, + OpMOVQloadFP: {asm: "MOVQ\t%A(FP),%O0"}, + OpMOVQloadSP: {asm: "MOVQ\t%A(SP),%O0"}, + OpMOVQstoreFP: {asm: "MOVQ\t%I0,%A(FP)"}, + OpMOVQstoreSP: {asm: "MOVQ\t%I0,%A(SP)"}, // ops for spilling of registers // unlike regular loads & stores, these take no memory argument. diff --git a/src/cmd/internal/ssa/op_string.go b/src/cmd/internal/ssa/op_string.go index dba1725262..5c42d22439 100644 --- a/src/cmd/internal/ssa/op_string.go +++ b/src/cmd/internal/ssa/op_string.go @@ -4,9 +4,9 @@ package ssa import "fmt" -const _Op_name = "OpUnknownOpNopOpThunkOpAddOpSubOpMulOpLessOpConstOpArgOpGlobalOpFuncOpCopyOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpSliceOpIndexOpIndexAddrOpLoadOpStoreOpCheckNilOpCheckBoundOpCallOpStaticCallOpConvertOpConvNopOpFPAddrOpSPAddrOpLoadFPOpLoadSPOpStoreFPOpStoreSPOpStoreReg8OpLoadReg8OpADDQOpSUBQOpADDCQOpSUBCQOpNEGQOpCMPQOpCMPCQOpADDLOpSETLOpSETGEOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpLoadFP8OpLoadSP8OpStoreFP8OpStoreSP8OpMax" +const _Op_name = "OpUnknownOpNopOpFwdRefOpAddOpSubOpMulOpLessOpConstOpArgOpGlobalOpFuncOpCopyOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpSliceIndexOpSliceIndexAddrOpLoadOpStoreOpCheckNilOpCheckBoundOpCallOpStaticCallOpConvertOpConvNopOpFPAddrOpSPAddrOpStoreReg8OpLoadReg8OpADDQOpSUBQOpADDCQOpSUBCQOpMULQOpMULCQOpSHLQOpSHLCQOpNEGQOpCMPQOpCMPCQOpADDLOpTESTQOpSETEQOpSETNEOpSETLOpSETGEOpSETBOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpMOVQloadOpMOVQstoreOpMOVQload8OpMOVQstore8OpMOVQloadFPOpMOVQloadSPOpMOVQstoreFPOpMOVQstoreSPOpMax" -var _Op_index = [...]uint16{0, 9, 14, 21, 26, 31, 36, 42, 49, 54, 62, 68, 74, 79, 90, 100, 110, 120, 132, 143, 154, 161, 168, 179, 185, 192, 202, 214, 220, 232, 241, 250, 258, 266, 274, 282, 291, 300, 311, 321, 327, 333, 340, 347, 353, 359, 366, 372, 378, 385, 398, 404, 411, 418, 425, 434, 443, 453, 463, 468} +var _Op_index = [...]uint16{0, 9, 14, 22, 27, 32, 37, 43, 50, 55, 63, 69, 75, 80, 91, 101, 111, 121, 133, 144, 155, 167, 183, 189, 196, 206, 218, 224, 236, 245, 254, 262, 270, 281, 291, 297, 303, 310, 317, 323, 330, 336, 343, 349, 355, 362, 368, 375, 382, 389, 395, 402, 408, 421, 427, 434, 441, 448, 458, 469, 480, 492, 504, 516, 529, 542, 547} func (i Op) String() string { if i < 0 || i+1 >= Op(len(_Op_index)) { diff --git a/src/cmd/internal/ssa/rewrite.go b/src/cmd/internal/ssa/rewrite.go index d22926e8f9..855719a877 100644 --- a/src/cmd/internal/ssa/rewrite.go +++ b/src/cmd/internal/ssa/rewrite.go @@ -4,16 +4,22 @@ package ssa -import ( - "cmd/internal/ssa/types" // TODO: use golang.org/x/tools/go/types instead -) +import "fmt" func applyRewrite(f *Func, r func(*Value) bool) { // repeat rewrites until we find no more rewrites + var curv *Value + defer func() { + if curv != nil { + fmt.Printf("panic during rewrite of %s\n", curv.LongString()) + // TODO(khr): print source location also + } + }() for { change := false for _, b := range f.Blocks { for _, v := range b.Values { + curv = v if r(v) { change = true } @@ -28,36 +34,21 @@ func applyRewrite(f *Func, r func(*Value) bool) { // Common functions called from rewriting rules func is64BitInt(t Type) bool { - if b, ok := t.Underlying().(*types.Basic); ok { - switch b.Kind() { - case types.Int64, types.Uint64: - return true - } - } - return false + return t.Size() == 8 && t.IsInteger() } func is32BitInt(t Type) bool { - if b, ok := t.Underlying().(*types.Basic); ok { - switch b.Kind() { - case types.Int32, types.Uint32: - return true - } - } - return false + return t.Size() == 4 && t.IsInteger() +} + +func isPtr(t Type) bool { + return t.IsPtr() } func isSigned(t Type) bool { - if b, ok := t.Underlying().(*types.Basic); ok { - switch b.Kind() { - case types.Int8, types.Int16, types.Int32, types.Int64: - return true - } - } - return false + return t.IsSigned() } -var sizer types.Sizes = &types.StdSizes{int64(ptrSize), int64(ptrSize)} // TODO(khr): from config func typeSize(t Type) int64 { - return sizer.Sizeof(t) + return t.Size() } diff --git a/src/cmd/internal/ssa/rulegen/generic.rules b/src/cmd/internal/ssa/rulegen/generic.rules index 1fc1620c5c..d17449930f 100644 --- a/src/cmd/internal/ssa/rulegen/generic.rules +++ b/src/cmd/internal/ssa/rulegen/generic.rules @@ -6,12 +6,14 @@ (Add (Const [c]) (Const [d])) && is64BitInt(t) && isSigned(t) -> (Const [{c.(int64)+d.(int64)}]) (Add (Const [c]) (Const [d])) && is64BitInt(t) && !isSigned(t) -> (Const [{c.(uint64)+d.(uint64)}]) -// load/store to stack -(Load (FPAddr [offset]) mem) -> (LoadFP [offset] mem) -(Store (FPAddr [offset]) val mem) -> (StoreFP [offset] val mem) - -(Load (SPAddr [offset]) mem) -> (LoadSP [offset] mem) -(Store (SPAddr [offset]) val mem) -> (StoreSP [offset] val mem) +// tear apart slices +// TODO: anything that generates a slice needs to go in here. +(SlicePtr (Load ptr mem)) -> (Load ptr mem) +(SliceLen (Load ptr mem)) -> (Load (Add ptr (Const [int64(v.Block.Func.Config.ptrSize)])) mem) +(SliceCap (Load ptr mem)) -> (Load (Add ptr (Const [int64(v.Block.Func.Config.ptrSize*2)])) mem) // expand array indexing // others? Depends on what is already done by frontend + +// Note: bounds check has already been done +(SliceIndex s i mem) -> (Load (Add (SlicePtr s) (Mul i (Const [s.Type.Elem().Size()]))) mem) diff --git a/src/cmd/internal/ssa/rulegen/lower_amd64.rules b/src/cmd/internal/ssa/rulegen/lower_amd64.rules index f60ac361ad..10c8dcc50f 100644 --- a/src/cmd/internal/ssa/rulegen/lower_amd64.rules +++ b/src/cmd/internal/ssa/rulegen/lower_amd64.rules @@ -13,35 +13,72 @@ // - aux will be nil if not specified. // x86 register conventions: -// - Integer types live in the low portion of registers. Upper portions are junk. +// - Integer types live in the low portion of registers. +// Upper portions are correctly extended. // - Boolean types use the low-order byte of a register. Upper bytes are junk. // - We do not use AH,BH,CH,DH registers. // - Floating-point types will live in the low natural slot of an sse2 register. // Unused portions are junk. // These are the lowerings themselves -(Add x y) && is64BitInt(t) -> (ADDQ x y) +(Add x y) && (is64BitInt(t) || isPtr(t)) -> (ADDQ x y) (Add x y) && is32BitInt(t) -> (ADDL x y) (Sub x y) && is64BitInt(t) -> (SUBQ x y) +(Mul x y) && is64BitInt(t) -> (MULQ x y) + (Less x y) && is64BitInt(v.Args[0].Type) && isSigned(v.Args[0].Type) -> (SETL (CMPQ x y)) -// stack loads/stores -(LoadFP [offset] mem) && typeSize(t) == 8 -> (LoadFP8 [offset] mem) -(StoreFP [offset] val mem) && typeSize(val.Type) == 8 -> (StoreFP8 [offset] val mem) -(LoadSP [offset] mem) && typeSize(t) == 8 -> (LoadSP8 [offset] mem) -(StoreSP [offset] val mem) && typeSize(val.Type) == 8 -> (StoreSP8 [offset] val mem) +(Load ptr mem) && (is64BitInt(t) || isPtr(t)) -> (MOVQload [int64(0)] ptr mem) +(Store ptr val mem) && (is64BitInt(val.Type) || isPtr(val.Type)) -> (MOVQstore [int64(0)] ptr val mem) + +// checks +(CheckNil p) -> (SETNE (TESTQ p p)) +(CheckBound idx len) -> (SETB (CMPQ idx len)) // Rules below here apply some simple optimizations after lowering. // TODO: Should this be a separate pass? +// stack loads/stores +(MOVQload [off1] (FPAddr [off2]) mem) -> (MOVQloadFP [off1.(int64)+off2.(int64)] mem) +(MOVQload [off1] (SPAddr [off2]) mem) -> (MOVQloadSP [off1.(int64)+off2.(int64)] mem) +(MOVQstore [off1] (FPAddr [off2]) val mem) -> (MOVQstoreFP [off1.(int64)+off2.(int64)] val mem) +(MOVQstore [off1] (SPAddr [off2]) val mem) -> (MOVQstoreSP [off1.(int64)+off2.(int64)] val mem) + +// fold constants into instructions (ADDQ x (Const [c])) -> (ADDCQ [c] x) // TODO: restrict c to int32 range? (ADDQ (Const [c]) x) -> (ADDCQ [c] x) (SUBQ x (Const [c])) -> (SUBCQ x [c]) (SUBQ (Const [c]) x) -> (NEGQ (SUBCQ x [c])) +(MULQ x (Const [c])) -> (MULCQ [c] x) +(MULQ (Const [c]) x) -> (MULCQ [c] x) (CMPQ x (Const [c])) -> (CMPCQ x [c]) (CMPQ (Const [c]) x) -> (InvertFlags (CMPCQ x [c])) +// strength reduction +// TODO: do this a lot more generically +(MULCQ [c] x) && c.(int64) == 8 -> (SHLCQ [int64(3)] x) + +// fold add/shift into leaq +(ADDQ x (SHLCQ [shift] y)) && shift.(int64) == 3 -> (LEAQ8 [int64(0)] x y) +(ADDCQ [c] (LEAQ8 [d] x y)) -> (LEAQ8 [c.(int64)+d.(int64)] x y) + // reverse ordering of compare instruction (SETL (InvertFlags x)) -> (SETGE x) + +// fold constants into memory operations +// Note that this is not always a good idea because if not all the uses of +// the ADDCQ get eliminated, we still have to compute the ADDCQ and we now +// have potentially two live values (ptr and (ADDCQ [off] ptr)) instead of one. +// Nevertheless, let's do it! +(MOVQload [off1] (ADDCQ [off2] ptr) mem) -> (MOVQload [off1.(int64)+off2.(int64)] ptr mem) +(MOVQstore [off1] (ADDCQ [off2] ptr) val mem) -> (MOVQstore [off1.(int64)+off2.(int64)] ptr val mem) + +// indexed loads and stores +(MOVQload [off1] (LEAQ8 [off2] ptr idx) mem) -> (MOVQload8 [off1.(int64)+off2.(int64)] ptr idx mem) +(MOVQstore [off1] (LEAQ8 [off2] ptr idx) val mem) -> (MOVQstore8 [off1.(int64)+off2.(int64)] ptr idx val mem) + +// Combine the offset of a stack object with the offset within a stack object +(ADDCQ [off1] (FPAddr [off2])) -> (FPAddr [off1.(int64)+off2.(int64)]) +(ADDCQ [off1] (SPAddr [off2])) -> (SPAddr [off1.(int64)+off2.(int64)]) diff --git a/src/cmd/internal/ssa/rulegen/rulegen.go b/src/cmd/internal/ssa/rulegen/rulegen.go index 4038662ca8..31f46f7cce 100644 --- a/src/cmd/internal/ssa/rulegen/rulegen.go +++ b/src/cmd/internal/ssa/rulegen/rulegen.go @@ -14,6 +14,7 @@ package main import ( "bufio" "bytes" + "crypto/md5" "fmt" "go/format" "io" @@ -96,10 +97,15 @@ func main() { ops = append(ops, op) } sort.Strings(ops) - rulenum := 0 for _, op := range ops { fmt.Fprintf(w, "case Op%s:\n", op) for _, rule := range oprules[op] { + // Note: we use a hash to identify the rule so that its + // identity is invariant to adding/removing rules elsewhere + // in the rules file. This is useful to squash spurious + // diffs that would occur if we used rule index. + rulehash := fmt.Sprintf("%02x", md5.Sum([]byte(rule))) + // split at -> s := strings.Split(rule, "->") if len(s) != 2 { @@ -120,7 +126,7 @@ func main() { fmt.Fprintf(w, "// cond: %s\n", cond) fmt.Fprintf(w, "// result: %s\n", result) - fail := fmt.Sprintf("{\ngoto end%d\n}\n", rulenum) + fail := fmt.Sprintf("{\ngoto end%s\n}\n", rulehash) fmt.Fprintf(w, "{\n") genMatch(w, match, fail) @@ -133,8 +139,8 @@ func main() { fmt.Fprintf(w, "return true\n") fmt.Fprintf(w, "}\n") - fmt.Fprintf(w, "end%d:;\n", rulenum) - rulenum++ + fmt.Fprintf(w, "goto end%s\n", rulehash) // use label + fmt.Fprintf(w, "end%s:;\n", rulehash) } } fmt.Fprintf(w, "}\n") @@ -249,7 +255,7 @@ func genResult0(w io.Writer, result string, alloc *int, top bool) string { v = "v" fmt.Fprintf(w, "v.Op = Op%s\n", s[0]) fmt.Fprintf(w, "v.Aux = nil\n") - fmt.Fprintf(w, "v.Args = v.argstorage[:0]\n") + fmt.Fprintf(w, "v.resetArgs()\n") hasType = true } else { v = fmt.Sprintf("v%d", *alloc) diff --git a/src/cmd/internal/ssa/ssac/main.go b/src/cmd/internal/ssa/ssac/main.go index 361bc87bff..2afa7c6aa9 100644 --- a/src/cmd/internal/ssa/ssac/main.go +++ b/src/cmd/internal/ssa/ssac/main.go @@ -16,8 +16,6 @@ import ( "strconv" "strings" - "cmd/internal/ssa/types" - "cmd/internal/ssa" ) @@ -227,9 +225,9 @@ func buildFunc(lines []sexpr) *ssa.Func { b.Control = v } } - // link up thunks to their actual values + // link up forward references to their actual values for _, v := range b.Values { - if v.Op != ssa.OpThunk { + if v.Op != ssa.OpFwdRef { continue } varid := v.Aux.(int) @@ -302,7 +300,7 @@ func genExpr(state *ssaFuncState, b *ssa.Block, e sexpr) *ssa.Value { if err != nil { panic("bad cint value") } - return b.Func.ConstInt(c) + return b.Func.ConstInt(ssa.TypeInt64, c) case "LT": x := genExpr(state, b, e.parts[1]) y := genExpr(state, b, e.parts[2]) @@ -310,28 +308,30 @@ func genExpr(state *ssaFuncState, b *ssa.Block, e sexpr) *ssa.Value { 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 + /* + 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") @@ -372,9 +372,9 @@ func lookupVarOutgoing(state *ssaFuncState, b *ssa.Block, id int) *ssa.Value { 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) + // Make a forward reference for this variable. + fmt.Printf("making fwdRef for var=%d in block=%d\n", id, b.ID) + v = b.NewValue(ssa.OpFwdRef, state.vartypes[id], id) // memoize result state.defs[blockvar{b.ID, id}] = v @@ -400,7 +400,7 @@ func lookupVarIncoming(state *ssaFuncState, b *ssa.Block, id int) *ssa.Value { args[i] = lookupVarOutgoing(state, p, id) } - // if <=1 value that isn't this variable's thunk, don't make phi + // if <=1 value that isn't this variable's fwdRef, don't make phi v.Op = ssa.OpPhi v.AddArgs(args...) // note: order corresponding to b.Pred } @@ -418,20 +418,22 @@ func parseSexprType(e sexpr) ssa.Type { 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)) + /* + 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) } - sig := types.NewSignature(nil, nil, types.NewTuple(args...), types.NewTuple(rets...), false) - return ssa.Type(sig) - } + */ // TODO: array/struct/... panic("compound type") } diff --git a/src/cmd/internal/ssa/type.go b/src/cmd/internal/ssa/type.go index 98efe54133..611c85834a 100644 --- a/src/cmd/internal/ssa/type.go +++ b/src/cmd/internal/ssa/type.go @@ -4,89 +4,71 @@ 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] +// TODO: use go/types instead? - TypeInvalid = types.Typ[types.Invalid] - - // Additional compiler-only types go here. - TypeMem = &Memory{} - TypeFlags = &Flags{} - - // TODO(khr): we probably shouldn't use int/uint/uintptr as Value types in the compiler. - // In OpConst's case, their width is the compiler's width, not the to-be-compiled - // program's width. For now, we can translate int/uint/uintptr to their specific - // widths variants before SSA. - // However, we may need at some point to maintain all possible user types in the - // compiler to handle things like interface conversion. At that point, we may - // need to revisit this decision. -) +// A type interface used to import cmd/internal/gc:Type +// Type instances are not guaranteed to be canonical. +type Type interface { + Size() int64 // return the size in bytes -// typeIdentical reports whether its 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) -} + IsBoolean() bool // is a named or unnamed boolean type + IsInteger() bool // ... ditto for the others + IsSigned() bool + IsFloat() bool + IsPtr() bool -// A type representing all of memory -type Memory struct { -} + IsMemory() bool // special ssa-package-only types + IsFlags() bool -func (t *Memory) Underlying() types.Type { panic("Underlying of Memory") } -func (t *Memory) String() string { return "mem" } + Elem() Type // given []T or *T, return T + PtrTo() Type // given T, return *T -// A type representing the unknown type -type Unknown struct { + String() string } -func (t *Unknown) Underlying() types.Type { panic("Underlying of Unknown") } -func (t *Unknown) String() string { return "unk" } +// Stub implementation for now, until we are completely using ../gc:Type +type TypeImpl struct { + Size_ int64 + Boolean bool + Integer bool + Signed bool + Float bool + Ptr bool -// A type representing the void type. Used during building, should always -// be eliminated by the first deadcode pass. -type Void struct { -} + Memory bool + Flags bool -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 { + Name string } -func (t *Check) Underlying() types.Type { panic("Underlying of Check") } -func (t *Check) String() string { return "check" } +func (t *TypeImpl) Size() int64 { return t.Size_ } +func (t *TypeImpl) IsBoolean() bool { return t.Boolean } +func (t *TypeImpl) IsInteger() bool { return t.Integer } +func (t *TypeImpl) IsSigned() bool { return t.Signed } +func (t *TypeImpl) IsFloat() bool { return t.Float } +func (t *TypeImpl) IsPtr() bool { return t.Ptr } +func (t *TypeImpl) IsMemory() bool { return t.Memory } +func (t *TypeImpl) IsFlags() bool { return t.Flags } +func (t *TypeImpl) String() string { return t.Name } +func (t *TypeImpl) Elem() Type { panic("not implemented"); return nil } +func (t *TypeImpl) PtrTo() Type { panic("not implemented"); return nil } -// x86 flags type -type Flags struct { -} +var ( + // shortcuts for commonly used basic types + TypeInt8 = &TypeImpl{Size_: 1, Integer: true, Signed: true, Name: "int8"} + TypeInt16 = &TypeImpl{Size_: 2, Integer: true, Signed: true, Name: "int16"} + TypeInt32 = &TypeImpl{Size_: 4, Integer: true, Signed: true, Name: "int32"} + TypeInt64 = &TypeImpl{Size_: 8, Integer: true, Signed: true, Name: "int64"} + TypeUInt8 = &TypeImpl{Size_: 1, Integer: true, Name: "uint8"} + TypeUInt16 = &TypeImpl{Size_: 2, Integer: true, Name: "uint16"} + TypeUInt32 = &TypeImpl{Size_: 4, Integer: true, Name: "uint32"} + TypeUInt64 = &TypeImpl{Size_: 8, Integer: true, Name: "uint64"} + TypeBool = &TypeImpl{Size_: 1, Boolean: true, Name: "bool"} + //TypeString = types.Typ[types.String] + + TypeInvalid = &TypeImpl{Name: "invalid"} -func (t *Flags) Underlying() types.Type { panic("Underlying of Flags") } -func (t *Flags) String() string { return "flags" } + // Additional compiler-only types go here. + TypeMem = &TypeImpl{Memory: true, Name: "mem"} + TypeFlags = &TypeImpl{Flags: true, Name: "flags"} +) diff --git a/src/cmd/internal/ssa/types/object.go b/src/cmd/internal/ssa/types/object.go deleted file mode 100644 index cd0be163b7..0000000000 --- a/src/cmd/internal/ssa/types/object.go +++ /dev/null @@ -1,39 +0,0 @@ -// 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' -} diff --git a/src/cmd/internal/ssa/types/sizes.go b/src/cmd/internal/ssa/types/sizes.go deleted file mode 100644 index b52f636fc5..0000000000 --- a/src/cmd/internal/ssa/types/sizes.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2013 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 file implements Sizes. - -package types - -import "log" - -// Sizes defines the sizing functions for package unsafe. -type Sizes interface { - // Alignof returns the alignment of a variable of type T. - // Alignof must implement the alignment guarantees required by the spec. - Alignof(T Type) int64 - - // Offsetsof returns the offsets of the given struct fields, in bytes. - // Offsetsof must implement the offset guarantees required by the spec. - Offsetsof(fields []*Var) []int64 - - // Sizeof returns the size of a variable of type T. - // Sizeof must implement the size guarantees required by the spec. - Sizeof(T Type) int64 -} - -// StdSizes is a convenience type for creating commonly used Sizes. -// It makes the following simplifying assumptions: -// -// - The size of explicitly sized basic types (int16, etc.) is the -// specified size. -// - The size of strings and interfaces is 2*WordSize. -// - The size of slices is 3*WordSize. -// - The size of an array of n elements corresponds to the size of -// a struct of n consecutive fields of the array's element type. -// - The size of a struct is the offset of the last field plus that -// field's size. As with all element types, if the struct is used -// in an array its size must first be aligned to a multiple of the -// struct's alignment. -// - All other types have size WordSize. -// - Arrays and structs are aligned per spec definition; all other -// types are naturally aligned with a maximum alignment MaxAlign. -// -// *StdSizes implements Sizes. -// -type StdSizes struct { - WordSize int64 // word size in bytes - must be >= 4 (32bits) - MaxAlign int64 // maximum alignment in bytes - must be >= 1 -} - -func (s *StdSizes) Alignof(T Type) int64 { - a := s.Sizeof(T) // may be 0 - // spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1." - if a < 1 { - return 1 - } - if a > s.MaxAlign { - return s.MaxAlign - } - return a -} - -func (s *StdSizes) Offsetsof(fields []*Var) []int64 { - offsets := make([]int64, len(fields)) - var o int64 - for i, f := range fields { - a := s.Alignof(f.typ) - o = align(o, a) - offsets[i] = o - o += s.Sizeof(f.typ) - } - return offsets -} - -var basicSizes = [...]byte{ - Bool: 1, - Int8: 1, - Int16: 2, - Int32: 4, - Int64: 8, - Uint8: 1, - Uint16: 2, - Uint32: 4, - Uint64: 8, - Float32: 4, - Float64: 8, - Complex64: 8, - Complex128: 16, -} - -func (s *StdSizes) Sizeof(T Type) int64 { - switch t := T.Underlying().(type) { - case *Basic: - k := t.kind - if int(k) < len(basicSizes) { - if s := basicSizes[k]; s > 0 { - return int64(s) - } - } - if k == String { - return s.WordSize * 2 - } - case *Slice: - return s.WordSize * 3 - default: - log.Fatalf("not implemented") - } - return s.WordSize // catch-all -} - -// stdSizes is used if Config.Sizes == nil. -var stdSizes = StdSizes{8, 8} - -// align returns the smallest y >= x such that y % a == 0. -func align(x, a int64) int64 { - y := x + a - 1 - return y - y%a -} diff --git a/src/cmd/internal/ssa/types/type.go b/src/cmd/internal/ssa/types/type.go deleted file mode 100644 index e01de5c1e4..0000000000 --- a/src/cmd/internal/ssa/types/type.go +++ /dev/null @@ -1,229 +0,0 @@ -// 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} -} diff --git a/src/cmd/internal/ssa/value.go b/src/cmd/internal/ssa/value.go index 389ba1ff77..dab6239dee 100644 --- a/src/cmd/internal/ssa/value.go +++ b/src/cmd/internal/ssa/value.go @@ -101,15 +101,3 @@ func (v *Value) resetArgs() { 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...) -} -- cgit v1.3 From a9a37dab4ac3b16cfeb865bd1ced940bc1d2004b Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 5 May 2015 16:19:12 -0700 Subject: [dev.ssa] cmd/internal/ssa: Add register allocation Add a simple register allocator. It does only intra-basicblock allocation. It uses a greedy one-pass allocation treating the register file as a cache. Change-Id: Ib6b52f48270e08dfda98f2dd842b05afc3ab01ce Reviewed-on: https://go-review.googlesource.com/9761 Reviewed-by: Alan Donovan --- src/cmd/internal/ssa/block.go | 5 +- src/cmd/internal/ssa/cgen.go | 3 - src/cmd/internal/ssa/compile.go | 25 ++- src/cmd/internal/ssa/critical.go | 2 +- src/cmd/internal/ssa/location.go | 2 +- src/cmd/internal/ssa/op.go | 50 +++-- src/cmd/internal/ssa/op_string.go | 4 +- src/cmd/internal/ssa/regalloc.go | 421 +++++++++++++++++++++++++++++++++++++ src/cmd/internal/ssa/sparseset.go | 19 +- src/cmd/internal/ssa/stackalloc.go | 51 +++++ 10 files changed, 542 insertions(+), 40 deletions(-) create mode 100644 src/cmd/internal/ssa/regalloc.go create mode 100644 src/cmd/internal/ssa/stackalloc.go (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/ssa/block.go b/src/cmd/internal/ssa/block.go index 81b5594f38..dcf3676bc2 100644 --- a/src/cmd/internal/ssa/block.go +++ b/src/cmd/internal/ssa/block.go @@ -19,7 +19,7 @@ type Block struct { 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). + // All successors must be distinct (to make phi values in successors unambiguous). Succs []*Block // Inverse of successors. @@ -33,8 +33,9 @@ type Block struct { // has a memory control value. Control *Value - // The unordered set of Values contained in this block. + // The unordered set of Values that define the operation of this block. // The list must include the control value, if any. (TODO: need this last condition?) + // After the scheduling pass, this list is ordered. Values []*Value // The containing function diff --git a/src/cmd/internal/ssa/cgen.go b/src/cmd/internal/ssa/cgen.go index c13e715653..51c72aacd9 100644 --- a/src/cmd/internal/ssa/cgen.go +++ b/src/cmd/internal/ssa/cgen.go @@ -18,9 +18,6 @@ func cgen(f *Func) { // TODO: prolog, allocate stack frame - // hack for now, until regalloc is done - f.RegAlloc = make([]Location, f.NumValues()) - for idx, b := range f.Blocks { fmt.Printf("%d:\n", b.ID) for _, v := range b.Values { diff --git a/src/cmd/internal/ssa/compile.go b/src/cmd/internal/ssa/compile.go index 08477d470c..c1f7956791 100644 --- a/src/cmd/internal/ssa/compile.go +++ b/src/cmd/internal/ssa/compile.go @@ -63,8 +63,8 @@ var passes = [...]pass{ {"critical", critical}, // remove critical edges {"layout", layout}, // schedule blocks {"schedule", schedule}, // schedule values - // regalloc - // stack slot alloc (+size stack frame) + {"regalloc", regalloc}, + {"stackalloc", stackalloc}, {"cgen", cgen}, } @@ -72,19 +72,26 @@ var passes = [...]pass{ // This code is intended to document the ordering requirements // between different phases. It does not override the passes // list above. -var passOrder = map[string]string{ +type constraint struct { + a, b string // a must come before b +} + +var passOrder = [...]constraint{ // don't layout blocks until critical edges have been removed - "critical": "layout", + {"critical", "layout"}, // regalloc requires the removal of all critical edges - //"critical": "regalloc", + {"critical", "regalloc"}, // regalloc requires all the values in a block to be scheduled - //"schedule": "regalloc", - // code generation requires register allocation - //"regalloc": "cgen", + {"schedule", "regalloc"}, + // stack allocation requires register allocation + {"regalloc", "stackalloc"}, + // code generation requires stack allocation + {"stackalloc", "cgen"}, } func init() { - for a, b := range passOrder { + for _, c := range passOrder { + a, b := c.a, c.b i := -1 j := -1 for k, p := range passes { diff --git a/src/cmd/internal/ssa/critical.go b/src/cmd/internal/ssa/critical.go index 5bbad8f2f5..503681ffd3 100644 --- a/src/cmd/internal/ssa/critical.go +++ b/src/cmd/internal/ssa/critical.go @@ -29,7 +29,7 @@ func critical(f *Func) { // split input edges coming from multi-output blocks. for i, c := range b.Preds { if c.Kind == BlockPlain { - continue + continue // only single output block } // allocate a new block to place on the edge diff --git a/src/cmd/internal/ssa/location.go b/src/cmd/internal/ssa/location.go index 94c1b426a2..5fc2c5c934 100644 --- a/src/cmd/internal/ssa/location.go +++ b/src/cmd/internal/ssa/location.go @@ -28,7 +28,7 @@ type LocalSlot struct { } func (s *LocalSlot) Name() string { - return fmt.Sprintf("loc%d", s.idx) + return fmt.Sprintf("-%d(FP)", s.idx) } // An ArgSlot is a location in the parents' stack frame where it passed us an argument. diff --git a/src/cmd/internal/ssa/op.go b/src/cmd/internal/ssa/op.go index 600dc9faa6..2d60b92939 100644 --- a/src/cmd/internal/ssa/op.go +++ b/src/cmd/internal/ssa/op.go @@ -127,6 +127,9 @@ const ( OpMOVQstoreFP OpMOVQstoreSP + // materialize a constant into a register + OpMOVQconst + OpMax // sentinel ) @@ -151,14 +154,13 @@ type regMask uint64 var regs386 = [...]string{ "AX", - "BX", "CX", "DX", - "SI", - "DI", + "BX", "SP", "BP", - "X0", + "SI", + "DI", // pseudo registers "FLAGS", @@ -166,10 +168,10 @@ var regs386 = [...]string{ } // 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 +var gp regMask = 0xef +var cx regMask = 0x2 +var flags regMask = 1 << 8 +var overwrite0 regMask = 1 << 9 const ( // possible properties of opcodes @@ -177,20 +179,23 @@ const ( // architecture constants Arch386 - ArchAmd64 - ArchArm + ArchAMD64 + ArchARM ) // general purpose registers, 2 input, 1 output var gp21 = [2][]regMask{{gp, gp}, {gp}} -var gp21_overwrite = [2][]regMask{{gp, gp}, {overwrite0}} +var gp21_overwrite = [2][]regMask{{gp, gp}, {gp}} // general purpose registers, 1 input, 1 output var gp11 = [2][]regMask{{gp}, {gp}} -var gp11_overwrite = [2][]regMask{{gp}, {overwrite0}} +var gp11_overwrite = [2][]regMask{{gp}, {gp}} + +// general purpose registers, 0 input, 1 output +var gp01 = [2][]regMask{{}, {gp}} // shift operations -var shift = [2][]regMask{{gp, cx}, {overwrite0}} +var shift = [2][]regMask{{gp, cx}, {gp}} var gp2_flags = [2][]regMask{{gp, gp}, {flags}} var gp1_flags = [2][]regMask{{gp}, {flags}} @@ -199,6 +204,9 @@ var gploadX = [2][]regMask{{gp, gp, 0}, {gp}} // indexed loads var gpstore = [2][]regMask{{gp, gp, 0}, {0}} var gpstoreX = [2][]regMask{{gp, gp, gp, 0}, {0}} // indexed stores +var gpload_stack = [2][]regMask{{0}, {gp}} +var gpstore_stack = [2][]regMask{{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 @@ -284,6 +292,8 @@ var amd64Table = [...]OpInfo{ OpMOVQload8: {asm: "MOVQ\t%A(%I0)(%I1*8),%O0", reg: gploadX}, OpMOVQstore8: {asm: "MOVQ\t%I2,%A(%I0)(%I1*8)", reg: gpstoreX}, + OpMOVQconst: {asm: "MOVQ\t$%A,%O0", reg: gp01}, + OpStaticCall: {asm: "CALL\t%A(SB)"}, OpCopy: {asm: "MOVQ\t%I0,%O0", reg: gp11}, @@ -292,17 +302,17 @@ var amd64Table = [...]OpInfo{ OpSETL: {}, // ops for load/store to stack - OpMOVQloadFP: {asm: "MOVQ\t%A(FP),%O0"}, - OpMOVQloadSP: {asm: "MOVQ\t%A(SP),%O0"}, - OpMOVQstoreFP: {asm: "MOVQ\t%I0,%A(FP)"}, - OpMOVQstoreSP: {asm: "MOVQ\t%I0,%A(SP)"}, + OpMOVQloadFP: {asm: "MOVQ\t%A(FP),%O0", reg: gpload_stack}, // mem -> value + OpMOVQloadSP: {asm: "MOVQ\t%A(SP),%O0", reg: gpload_stack}, // mem -> value + OpMOVQstoreFP: {asm: "MOVQ\t%I0,%A(FP)", reg: gpstore_stack}, // mem, value -> mem + OpMOVQstoreSP: {asm: "MOVQ\t%I0,%A(SP)", reg: gpstore_stack}, // mem, value -> mem // 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}, + OpLoadReg8: {asm: "MOVQ\t%I0,%O0"}, + OpStoreReg8: {asm: "MOVQ\t%I0,%O0"}, } // A Table is a list of opcodes with a common set of flags. @@ -313,7 +323,7 @@ type Table struct { var tables = []Table{ {genericTable[:], 0}, - {amd64Table[:], ArchAmd64}, // TODO: pick this dynamically + {amd64Table[:], ArchAMD64}, // TODO: pick this dynamically } // table of opcodes, indexed by opcode ID diff --git a/src/cmd/internal/ssa/op_string.go b/src/cmd/internal/ssa/op_string.go index 5c42d22439..c095fba52b 100644 --- a/src/cmd/internal/ssa/op_string.go +++ b/src/cmd/internal/ssa/op_string.go @@ -4,9 +4,9 @@ package ssa import "fmt" -const _Op_name = "OpUnknownOpNopOpFwdRefOpAddOpSubOpMulOpLessOpConstOpArgOpGlobalOpFuncOpCopyOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpSliceIndexOpSliceIndexAddrOpLoadOpStoreOpCheckNilOpCheckBoundOpCallOpStaticCallOpConvertOpConvNopOpFPAddrOpSPAddrOpStoreReg8OpLoadReg8OpADDQOpSUBQOpADDCQOpSUBCQOpMULQOpMULCQOpSHLQOpSHLCQOpNEGQOpCMPQOpCMPCQOpADDLOpTESTQOpSETEQOpSETNEOpSETLOpSETGEOpSETBOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpMOVQloadOpMOVQstoreOpMOVQload8OpMOVQstore8OpMOVQloadFPOpMOVQloadSPOpMOVQstoreFPOpMOVQstoreSPOpMax" +const _Op_name = "OpUnknownOpNopOpFwdRefOpAddOpSubOpMulOpLessOpConstOpArgOpGlobalOpFuncOpCopyOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpSliceIndexOpSliceIndexAddrOpLoadOpStoreOpCheckNilOpCheckBoundOpCallOpStaticCallOpConvertOpConvNopOpFPAddrOpSPAddrOpStoreReg8OpLoadReg8OpADDQOpSUBQOpADDCQOpSUBCQOpMULQOpMULCQOpSHLQOpSHLCQOpNEGQOpCMPQOpCMPCQOpADDLOpTESTQOpSETEQOpSETNEOpSETLOpSETGEOpSETBOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpMOVQloadOpMOVQstoreOpMOVQload8OpMOVQstore8OpMOVQloadFPOpMOVQloadSPOpMOVQstoreFPOpMOVQstoreSPOpMOVQconstOpMax" -var _Op_index = [...]uint16{0, 9, 14, 22, 27, 32, 37, 43, 50, 55, 63, 69, 75, 80, 91, 101, 111, 121, 133, 144, 155, 167, 183, 189, 196, 206, 218, 224, 236, 245, 254, 262, 270, 281, 291, 297, 303, 310, 317, 323, 330, 336, 343, 349, 355, 362, 368, 375, 382, 389, 395, 402, 408, 421, 427, 434, 441, 448, 458, 469, 480, 492, 504, 516, 529, 542, 547} +var _Op_index = [...]uint16{0, 9, 14, 22, 27, 32, 37, 43, 50, 55, 63, 69, 75, 80, 91, 101, 111, 121, 133, 144, 155, 167, 183, 189, 196, 206, 218, 224, 236, 245, 254, 262, 270, 281, 291, 297, 303, 310, 317, 323, 330, 336, 343, 349, 355, 362, 368, 375, 382, 389, 395, 402, 408, 421, 427, 434, 441, 448, 458, 469, 480, 492, 504, 516, 529, 542, 553, 558} func (i Op) String() string { if i < 0 || i+1 >= Op(len(_Op_index)) { diff --git a/src/cmd/internal/ssa/regalloc.go b/src/cmd/internal/ssa/regalloc.go new file mode 100644 index 0000000000..724a0557d5 --- /dev/null +++ b/src/cmd/internal/ssa/regalloc.go @@ -0,0 +1,421 @@ +package ssa + +import ( + "fmt" + "log" + "sort" +) + +func setloc(home []Location, v *Value, loc Location) []Location { + for v.ID >= ID(len(home)) { + home = append(home, nil) + } + home[v.ID] = loc + return home +} + +type register uint + +// TODO: make arch-dependent +var numRegs register = 32 + +var registers = [...]Register{ + Register{"AX"}, + Register{"CX"}, + Register{"DX"}, + Register{"BX"}, + Register{"SP"}, + Register{"BP"}, + Register{"SI"}, + Register{"DI"}, + + // TODO R8, X0, ... + // TODO: make arch-dependent + Register{"FLAGS"}, + Register{"OVERWRITE"}, +} + +// countRegs returns the number of set bits in the register mask. +func countRegs(r regMask) int { + n := 0 + for r != 0 { + n += int(r & 1) + r >>= 1 + } + return n +} + +// pickReg picks an arbitrary register from the register mask. +func pickReg(r regMask) register { + // pick the lowest one + if r == 0 { + panic("can't pick a register from an empty set") + } + for i := register(0); ; i++ { + if r&1 != 0 { + return i + } + r >>= 1 + } +} + +// regalloc performs register allocation on f. It sets f.RegAlloc +// to the resulting allocation. +func regalloc(f *Func) { + // For now, a very simple allocator. Everything has a home + // location on the stack (TBD as a subsequent stackalloc pass). + // Values live in the home locations at basic block boundaries. + // We use a simple greedy allocator within a basic block. + home := make([]Location, f.NumValues()) + + addPhiCopies(f) // add copies of phi inputs in preceeding blocks + + // Compute live values at the end of each block. + live := live(f) + lastUse := make([]int, f.NumValues()) + + var oldSched []*Value + + // Register allocate each block separately. All live values will live + // in home locations (stack slots) between blocks. + for _, b := range f.Blocks { + + // Compute the index of the last use of each Value in the Block. + // Scheduling has already happened, so Values are totally ordered. + // lastUse[x] = max(i) where b.Value[i] uses Value x. + for i, v := range b.Values { + lastUse[v.ID] = -1 + for _, w := range v.Args { + // could condition this store on w.Block == b, but no need + lastUse[w.ID] = i + } + } + // Values which are live at block exit have a lastUse of len(b.Values). + if b.Control != nil { + lastUse[b.Control.ID] = len(b.Values) + } + // Values live after block exit have a lastUse of len(b.Values)+1. + for _, vid := range live[b.ID] { + lastUse[vid] = len(b.Values) + 1 + } + + // For each register, store which value it contains + type regInfo struct { + v *Value // stack-homed original value (or nil if empty) + c *Value // the register copy of v + dirty bool // if the stack-homed copy is out of date + } + regs := make([]regInfo, numRegs) + + var used regMask // has a 1 for each non-nil entry in regs + var dirty regMask // has a 1 for each dirty entry in regs + + oldSched = append(oldSched[:0], b.Values...) + b.Values = b.Values[:0] + + for idx, v := range oldSched { + // For each instruction, do: + // set up inputs to v in registers + // pick output register + // run insn + // mark output register as dirty + // Note that v represents the Value at "home" (on the stack), and c + // is its register equivalent. There are two ways to establish c: + // - use of v. c will be a load from v's home. + // - definition of v. c will be identical to v but will live in + // a register. v will be modified into a spill of c. + regspec := opcodeTable[v.Op].reg + if v.Op == OpConvNop { + regspec = opcodeTable[v.Args[0].Op].reg + } + inputs := regspec[0] + outputs := regspec[1] + if len(inputs) == 0 && len(outputs) == 0 { + // No register allocation required (or none specified yet) + b.Values = append(b.Values, v) + continue + } + + // Compute a good input ordering. Start with the most constrained input. + order := make([]intPair, len(inputs)) + for i, input := range inputs { + order[i] = intPair{countRegs(input), i} + } + sort.Sort(byKey(order)) + + // nospill contains registers that we can't spill because + // we already set them up for use by the current instruction. + var nospill regMask + + // Move inputs into registers + for _, o := range order { + w := v.Args[o.val] + mask := inputs[o.val] + if mask == 0 { + // Input doesn't need a register + continue + } + // TODO: 2-address overwrite instructions + + // Find registers that w is already in + var wreg regMask + for r := register(0); r < numRegs; r++ { + if regs[r].v == w { + wreg |= regMask(1) << r + } + } + + var r register + if mask&wreg != 0 { + // w is already in an allowed register. We're done. + r = pickReg(mask & wreg) + } else { + // Pick a register for w + // Priorities (in order) + // - an unused register + // - a clean register + // - a dirty register + // TODO: for used registers, pick the one whose next use is the + // farthest in the future. + mask &^= nospill + if mask & ^dirty != 0 { + mask &^= dirty + } + if mask & ^used != 0 { + mask &^= used + } + r = pickReg(mask) + + // Kick out whomever is using this register. + if regs[r].v != nil { + x := regs[r].v + c := regs[r].c + if regs[r].dirty && lastUse[x.ID] > idx { + // Write x back to home. Its value is currently held in c. + x.Op = OpStoreReg8 + x.Aux = nil + x.resetArgs() + x.AddArg(c) + b.Values = append(b.Values, x) + regs[r].dirty = false + dirty &^= regMask(1) << r + } + regs[r].v = nil + regs[r].c = nil + used &^= regMask(1) << r + } + + // Load w into this register + var c *Value + if w.Op == OpConst { + // Materialize w + // TODO: arch-specific MOV op + c = b.NewValue(OpMOVQconst, w.Type, w.Aux) + } else if wreg != 0 { + // Copy from another register. + // Typically just an optimization, but this is + // required if w is dirty. + s := pickReg(wreg) + // inv: s != r + c = b.NewValue(OpCopy, w.Type, nil) + c.AddArg(regs[s].c) + } else { + // Load from home location + c = b.NewValue(OpLoadReg8, w.Type, nil) + c.AddArg(w) + } + home = setloc(home, c, ®isters[r]) + // Remember what we did + regs[r].v = w + regs[r].c = c + regs[r].dirty = false + used |= regMask(1) << r + } + + // Replace w with its in-register copy. + v.SetArg(o.val, regs[r].c) + + // Remember not to undo this register assignment until after + // the instruction is issued. + nospill |= regMask(1) << r + } + + // pick a register for v itself. + if len(outputs) > 1 { + panic("can't do multi-output yet") + } + if len(outputs) == 0 || outputs[0] == 0 { + // output doesn't need a register + b.Values = append(b.Values, v) + } else { + mask := outputs[0] + if mask & ^dirty != 0 { + mask &^= dirty + } + if mask & ^used != 0 { + mask &^= used + } + r := pickReg(mask) + + // Kick out whomever is using this register. + if regs[r].v != nil { + x := regs[r].v + c := regs[r].c + if regs[r].dirty && lastUse[x.ID] > idx { + // Write x back to home. Its value is currently held in c. + x.Op = OpStoreReg8 + x.Aux = nil + x.resetArgs() + x.AddArg(c) + b.Values = append(b.Values, x) + regs[r].dirty = false + dirty &^= regMask(1) << r + } + regs[r].v = nil + regs[r].c = nil + used &^= regMask(1) << r + } + + // Reissue v with new op, with r as its home. + c := b.NewValue(v.Op, v.Type, v.Aux) + c.AddArgs(v.Args...) + home = setloc(home, c, ®isters[r]) + + // Remember what we did + regs[r].v = v + regs[r].c = c + regs[r].dirty = true + used |= regMask(1) << r + dirty |= regMask(1) << r + } + } + + // If the block ends in a call, we must put the call after the spill code. + var call *Value + if b.Kind == BlockCall { + call = b.Control + if call != b.Values[len(b.Values)-1] { + log.Fatalf("call not at end of block %b %v", b, call) + } + b.Values = b.Values[:len(b.Values)-1] + // TODO: do this for all control types? + } + + // at the end of the block, spill any remaining dirty, live values + for r := register(0); r < numRegs; r++ { + if !regs[r].dirty { + continue + } + v := regs[r].v + c := regs[r].c + if lastUse[v.ID] <= len(oldSched) { + continue // not live after block + } + + // change v to be a copy of c + v.Op = OpStoreReg8 + v.Aux = nil + v.resetArgs() + v.AddArg(c) + b.Values = append(b.Values, v) + } + + // add call back after spills + if b.Kind == BlockCall { + b.Values = append(b.Values, call) + } + } + f.RegAlloc = home +} + +// addPhiCopies adds copies of phi inputs in the blocks +// immediately preceding the phi's block. +func addPhiCopies(f *Func) { + for _, b := range f.Blocks { + for _, v := range b.Values { + if v.Op != OpPhi { + break // all phis should appear first + } + if v.Type.IsMemory() { // TODO: only "regallocable" types + continue + } + for i, w := range v.Args { + c := b.Preds[i] + cpy := c.NewValue1(OpCopy, v.Type, nil, w) + v.Args[i] = cpy + } + } + } +} + +// live returns a map from block ID to a list of value IDs live at the end of that block +// TODO: this could be quadratic if lots of variables are live across lots of +// basic blocks. Figure out a way to make this function (or, more precisely, the user +// of this function) require only linear size & time. +func live(f *Func) [][]ID { + live := make([][]ID, f.NumBlocks()) + var phis []*Value + + s := newSparseSet(f.NumValues()) + t := newSparseSet(f.NumValues()) + for { + for _, b := range f.Blocks { + fmt.Printf("live %s %v\n", b, live[b.ID]) + } + changed := false + + for _, b := range f.Blocks { + // Start with known live values at the end of the block + s.clear() + s.addAll(live[b.ID]) + + // Propagate backwards to the start of the block + // Assumes Values have been scheduled. + phis := phis[:0] + for i := len(b.Values) - 1; i >= 0; i-- { + v := b.Values[i] + s.remove(v.ID) + if v.Op == OpPhi { + // save phi ops for later + phis = append(phis, v) + continue + } + s.addAllValues(v.Args) + } + + // for each predecessor of b, expand its list of live-at-end values + // inv: s contains the values live at the start of b (excluding phi inputs) + for i, p := range b.Preds { + t.clear() + t.addAll(live[p.ID]) + t.addAll(s.contents()) + for _, v := range phis { + t.add(v.Args[i].ID) + } + if t.size() == len(live[p.ID]) { + continue + } + // grow p's live set + c := make([]ID, t.size()) + copy(c, t.contents()) + live[p.ID] = c + changed = true + } + } + + if !changed { + break + } + } + return live +} + +// for sorting a pair of integers by key +type intPair struct { + key, val int +} +type byKey []intPair + +func (a byKey) Len() int { return len(a) } +func (a byKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byKey) Less(i, j int) bool { return a[i].key < a[j].key } diff --git a/src/cmd/internal/ssa/sparseset.go b/src/cmd/internal/ssa/sparseset.go index e1f9a9a81d..b79aee8497 100644 --- a/src/cmd/internal/ssa/sparseset.go +++ b/src/cmd/internal/ssa/sparseset.go @@ -28,9 +28,24 @@ func (s *sparseSet) contains(x ID) bool { } func (s *sparseSet) add(x ID) { - i := len(s.dense) + i := s.sparse[x] + if i < len(s.dense) && s.dense[i] == x { + return + } s.dense = append(s.dense, x) - s.sparse[x] = i + s.sparse[x] = len(s.dense) - 1 +} + +func (s *sparseSet) addAll(a []ID) { + for _, x := range a { + s.add(x) + } +} + +func (s *sparseSet) addAllValues(a []*Value) { + for _, v := range a { + s.add(v.ID) + } } func (s *sparseSet) remove(x ID) { diff --git a/src/cmd/internal/ssa/stackalloc.go b/src/cmd/internal/ssa/stackalloc.go new file mode 100644 index 0000000000..aa6d829fa2 --- /dev/null +++ b/src/cmd/internal/ssa/stackalloc.go @@ -0,0 +1,51 @@ +package ssa + +// stackalloc allocates storage in the stack frame for +// all Values that did not get a register. +func stackalloc(f *Func) { + home := f.RegAlloc + + var n int64 = 8 // 8 = space for return address. TODO: arch-dependent + + // Assign stack locations to phis first, because we + // must also assign the same locations to the phi copies + // introduced during regalloc. + for _, b := range f.Blocks { + for _, v := range b.Values { + if v.Op != OpPhi { + continue + } + n += v.Type.Size() + // a := v.Type.Align() + // n = (n + a - 1) / a * a TODO + loc := &LocalSlot{n} + home = setloc(home, v, loc) + for _, w := range v.Args { + home = setloc(home, w, loc) + } + } + } + + // Now do all other unassigned values. + for _, b := range f.Blocks { + for _, v := range b.Values { + if v.ID < ID(len(home)) && home[v.ID] != nil { + continue + } + if v.Type.IsMemory() { // TODO: only "regallocable" types + continue + } + // a := v.Type.Align() + // n = (n + a - 1) / a * a TODO + n += v.Type.Size() + loc := &LocalSlot{n} + home = setloc(home, v, loc) + } + } + f.RegAlloc = home + + // TODO: share stack slots among noninterfering (& gc type compatible) values + // TODO: align final n + // TODO: compute total frame size: n + max paramout space + // TODO: save total size somewhere +} -- cgit v1.3 From 23df95b9b509c80a2ebef2fe91a90c32d242005a Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 12 May 2015 15:16:52 -0700 Subject: [dev.ssa] cmd/internal/ssa: implement global variables Fix a few compilation errors due to previous merge from tip. Change-Id: I826ad5a9d602a8f8be2762ad00b030dea6f41bcc Reviewed-on: https://go-review.googlesource.com/9967 Reviewed-by: Alan Donovan --- src/cmd/internal/gc/ssa.go | 13 ++++++-- src/cmd/internal/gc/type.go | 4 --- src/cmd/internal/ssa/lowerAmd64.go | 42 ++++++++++++++++++++++++++ src/cmd/internal/ssa/op.go | 17 ++++++++++- src/cmd/internal/ssa/op_string.go | 4 +-- src/cmd/internal/ssa/rulegen/lower_amd64.rules | 4 +++ 6 files changed, 75 insertions(+), 9 deletions(-) (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/gc/ssa.go b/src/cmd/internal/gc/ssa.go index 415e9dc639..ec747e970b 100644 --- a/src/cmd/internal/gc/ssa.go +++ b/src/cmd/internal/gc/ssa.go @@ -153,12 +153,16 @@ func (s *ssaState) stmt(n *Node) { // TODO(khr): nil check s.vars[".mem"] = s.curBlock.NewValue3(ssa.OpStore, n.Right.Type, nil, addr, val, s.mem()) - } else if n.Left.Addable == 0 { + } else if !n.Left.Addable { // TODO log.Fatalf("assignment to non-addable value") } else if n.Left.Class&PHEAP != 0 { // TODO log.Fatalf("assignment to heap value") + } else if n.Left.Class == PEXTERN { + // assign to global variable + addr := s.f.Entry.NewValue(ssa.OpGlobal, Ptrto(n.Left.Type), n.Left.Sym) + s.vars[".mem"] = s.curBlock.NewValue3(ssa.OpStore, ssa.TypeMem, nil, addr, val, s.mem()) } else if n.Left.Class == PPARAMOUT { // store to parameter slot addr := s.f.Entry.NewValue(ssa.OpFPAddr, Ptrto(n.Right.Type), n.Left.Xoffset) @@ -254,7 +258,12 @@ func (s *ssaState) expr(n *Node) *ssa.Value { } switch n.Op { case ONAME: - // remember offsets for PPARAM names + // TODO: remember offsets for PPARAM names + if n.Class == PEXTERN { + // global variable + addr := s.f.Entry.NewValue(ssa.OpGlobal, Ptrto(n.Type), n.Sym) + return s.curBlock.NewValue2(ssa.OpLoad, n.Type, nil, addr, s.mem()) + } s.argOffsets[n.Sym.Name] = n.Xoffset return s.variable(n.Sym.Name, n.Type) // binary ops diff --git a/src/cmd/internal/gc/type.go b/src/cmd/internal/gc/type.go index e88ca7c898..6f7830d70a 100644 --- a/src/cmd/internal/gc/type.go +++ b/src/cmd/internal/gc/type.go @@ -56,7 +56,3 @@ func (t *Type) PtrTo() ssa.Type { func (t *Type) IsMemory() bool { return false } func (t *Type) IsFlags() bool { return false } - -func (t *Type) String() string { - return typefmt(t, 0) -} diff --git a/src/cmd/internal/ssa/lowerAmd64.go b/src/cmd/internal/ssa/lowerAmd64.go index 6c0a42d976..842822bda4 100644 --- a/src/cmd/internal/ssa/lowerAmd64.go +++ b/src/cmd/internal/ssa/lowerAmd64.go @@ -333,6 +333,26 @@ func lowerAmd64(v *Value) bool { } goto end3d8628a6536350a123be81240b8a1376 end3d8628a6536350a123be81240b8a1376: + ; + // match: (MOVQload [off] (Global [sym]) mem) + // cond: + // result: (MOVQloadglobal [GlobalOffset{sym,off.(int64)}] mem) + { + off := v.Aux + if v.Args[0].Op != OpGlobal { + goto end20693899317f3f8d1b47fefa64087654 + } + sym := v.Args[0].Aux + mem := v.Args[1] + v.Op = OpMOVQloadglobal + v.Aux = nil + v.resetArgs() + v.Aux = GlobalOffset{sym, off.(int64)} + v.AddArg(mem) + return true + } + goto end20693899317f3f8d1b47fefa64087654 + end20693899317f3f8d1b47fefa64087654: ; // match: (MOVQload [off1] (ADDCQ [off2] ptr) mem) // cond: @@ -424,6 +444,28 @@ func lowerAmd64(v *Value) bool { } goto end1cb5b7e766f018270fa434c6f46f607f end1cb5b7e766f018270fa434c6f46f607f: + ; + // match: (MOVQstore [off] (Global [sym]) val mem) + // cond: + // result: (MOVQstoreglobal [GlobalOffset{sym,off.(int64)}] val mem) + { + off := v.Aux + if v.Args[0].Op != OpGlobal { + goto end657d07e37c720a8fbb108a31bb48090d + } + sym := v.Args[0].Aux + val := v.Args[1] + mem := v.Args[2] + v.Op = OpMOVQstoreglobal + v.Aux = nil + v.resetArgs() + v.Aux = GlobalOffset{sym, off.(int64)} + v.AddArg(val) + v.AddArg(mem) + return true + } + goto end657d07e37c720a8fbb108a31bb48090d + end657d07e37c720a8fbb108a31bb48090d: ; // match: (MOVQstore [off1] (ADDCQ [off2] ptr) val mem) // cond: diff --git a/src/cmd/internal/ssa/op.go b/src/cmd/internal/ssa/op.go index 2d60b92939..1d374db61d 100644 --- a/src/cmd/internal/ssa/op.go +++ b/src/cmd/internal/ssa/op.go @@ -37,7 +37,7 @@ const ( OpConst OpArg // address of a function parameter/result. Memory input is an arg called ".mem". - OpGlobal // address of a global variable + OpGlobal // address of a global variable (aux is a *gc.Sym) OpFunc // entry address of a function OpCopy // output = input OpPhi // select an input based on which predecessor we came from @@ -121,6 +121,10 @@ const ( OpMOVQload8 // (ptr,idx,mem): loads from ptr+idx*8+aux.(int64) OpMOVQstore8 // (ptr,idx,val,mem): stores to ptr+idx*8+aux.(int64), returns mem + // load/store from global. aux = GlobalOffset + OpMOVQloadglobal // (mem) -> value + OpMOVQstoreglobal // (val, mem) -> mem + // load/store 8-byte integer register from stack slot. OpMOVQloadFP OpMOVQloadSP @@ -133,6 +137,12 @@ const ( OpMax // sentinel ) +// GlobalOffset represents a fixed offset within a global variable +type GlobalOffset struct { + Global interface{} // holds a *cmd/internal/gc.Sym + Offset int64 +} + //go:generate stringer -type=Op type OpInfo struct { @@ -203,6 +213,8 @@ var gpload = [2][]regMask{{gp, 0}, {gp}} var gploadX = [2][]regMask{{gp, gp, 0}, {gp}} // indexed loads var gpstore = [2][]regMask{{gp, gp, 0}, {0}} var gpstoreX = [2][]regMask{{gp, gp, gp, 0}, {0}} // indexed stores +var gploadglobal = [2][]regMask{{0}, {gp}} +var gpstoreglobal = [2][]regMask{{gp, 0}, {0}} var gpload_stack = [2][]regMask{{0}, {gp}} var gpstore_stack = [2][]regMask{{gp, 0}, {0}} @@ -292,6 +304,9 @@ var amd64Table = [...]OpInfo{ OpMOVQload8: {asm: "MOVQ\t%A(%I0)(%I1*8),%O0", reg: gploadX}, OpMOVQstore8: {asm: "MOVQ\t%I2,%A(%I0)(%I1*8)", reg: gpstoreX}, + OpMOVQloadglobal: {reg: gploadglobal}, + OpMOVQstoreglobal: {reg: gpstoreglobal}, + OpMOVQconst: {asm: "MOVQ\t$%A,%O0", reg: gp01}, OpStaticCall: {asm: "CALL\t%A(SB)"}, diff --git a/src/cmd/internal/ssa/op_string.go b/src/cmd/internal/ssa/op_string.go index c095fba52b..adce17a1f2 100644 --- a/src/cmd/internal/ssa/op_string.go +++ b/src/cmd/internal/ssa/op_string.go @@ -4,9 +4,9 @@ package ssa import "fmt" -const _Op_name = "OpUnknownOpNopOpFwdRefOpAddOpSubOpMulOpLessOpConstOpArgOpGlobalOpFuncOpCopyOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpSliceIndexOpSliceIndexAddrOpLoadOpStoreOpCheckNilOpCheckBoundOpCallOpStaticCallOpConvertOpConvNopOpFPAddrOpSPAddrOpStoreReg8OpLoadReg8OpADDQOpSUBQOpADDCQOpSUBCQOpMULQOpMULCQOpSHLQOpSHLCQOpNEGQOpCMPQOpCMPCQOpADDLOpTESTQOpSETEQOpSETNEOpSETLOpSETGEOpSETBOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpMOVQloadOpMOVQstoreOpMOVQload8OpMOVQstore8OpMOVQloadFPOpMOVQloadSPOpMOVQstoreFPOpMOVQstoreSPOpMOVQconstOpMax" +const _Op_name = "OpUnknownOpNopOpFwdRefOpAddOpSubOpMulOpLessOpConstOpArgOpGlobalOpFuncOpCopyOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpSliceIndexOpSliceIndexAddrOpLoadOpStoreOpCheckNilOpCheckBoundOpCallOpStaticCallOpConvertOpConvNopOpFPAddrOpSPAddrOpStoreReg8OpLoadReg8OpADDQOpSUBQOpADDCQOpSUBCQOpMULQOpMULCQOpSHLQOpSHLCQOpNEGQOpCMPQOpCMPCQOpADDLOpTESTQOpSETEQOpSETNEOpSETLOpSETGEOpSETBOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpMOVQloadOpMOVQstoreOpMOVQload8OpMOVQstore8OpMOVQloadglobalOpMOVQstoreglobalOpMOVQloadFPOpMOVQloadSPOpMOVQstoreFPOpMOVQstoreSPOpMOVQconstOpMax" -var _Op_index = [...]uint16{0, 9, 14, 22, 27, 32, 37, 43, 50, 55, 63, 69, 75, 80, 91, 101, 111, 121, 133, 144, 155, 167, 183, 189, 196, 206, 218, 224, 236, 245, 254, 262, 270, 281, 291, 297, 303, 310, 317, 323, 330, 336, 343, 349, 355, 362, 368, 375, 382, 389, 395, 402, 408, 421, 427, 434, 441, 448, 458, 469, 480, 492, 504, 516, 529, 542, 553, 558} +var _Op_index = [...]uint16{0, 9, 14, 22, 27, 32, 37, 43, 50, 55, 63, 69, 75, 80, 91, 101, 111, 121, 133, 144, 155, 167, 183, 189, 196, 206, 218, 224, 236, 245, 254, 262, 270, 281, 291, 297, 303, 310, 317, 323, 330, 336, 343, 349, 355, 362, 368, 375, 382, 389, 395, 402, 408, 421, 427, 434, 441, 448, 458, 469, 480, 492, 508, 525, 537, 549, 562, 575, 586, 591} func (i Op) String() string { if i < 0 || i+1 >= Op(len(_Op_index)) { diff --git a/src/cmd/internal/ssa/rulegen/lower_amd64.rules b/src/cmd/internal/ssa/rulegen/lower_amd64.rules index 10c8dcc50f..8882e3c253 100644 --- a/src/cmd/internal/ssa/rulegen/lower_amd64.rules +++ b/src/cmd/internal/ssa/rulegen/lower_amd64.rules @@ -46,6 +46,10 @@ (MOVQstore [off1] (FPAddr [off2]) val mem) -> (MOVQstoreFP [off1.(int64)+off2.(int64)] val mem) (MOVQstore [off1] (SPAddr [off2]) val mem) -> (MOVQstoreSP [off1.(int64)+off2.(int64)] val mem) +// global loads/stores +(MOVQload [off] (Global [sym]) mem) -> (MOVQloadglobal [GlobalOffset{sym,off.(int64)}] mem) +(MOVQstore [off] (Global [sym]) val mem) -> (MOVQstoreglobal [GlobalOffset{sym,off.(int64)}] val mem) + // fold constants into instructions (ADDQ x (Const [c])) -> (ADDCQ [c] x) // TODO: restrict c to int32 range? (ADDQ (Const [c]) x) -> (ADDCQ [c] x) -- cgit v1.3 From 12f980bc85c1ded76ba8b861b5ec4a50146f868f Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Wed, 13 May 2015 14:18:12 -0700 Subject: [dev.ssa] cmd/internal/ssa: delete ssac We don't need this standalone tool any more. We can now feed the ssa compiler directly from the Go frontend. Change-Id: I922f1e061c2d3db6bf77acc137d4d1fc7dc86c0d Reviewed-on: https://go-review.googlesource.com/10034 Reviewed-by: Alan Donovan --- src/cmd/internal/ssa/ssac/.gitignore | 1 - src/cmd/internal/ssa/ssac/fib.goir | 47 ---- src/cmd/internal/ssa/ssac/fibiter.goir | 62 ----- src/cmd/internal/ssa/ssac/main.go | 439 --------------------------------- src/cmd/internal/ssa/ssac/sexpr.go | 82 ------ src/cmd/internal/ssa/ssac/sparsemap.go | 69 ------ 6 files changed, 700 deletions(-) delete mode 100644 src/cmd/internal/ssa/ssac/.gitignore delete mode 100644 src/cmd/internal/ssa/ssac/fib.goir delete mode 100644 src/cmd/internal/ssa/ssac/fibiter.goir delete mode 100644 src/cmd/internal/ssa/ssac/main.go delete mode 100644 src/cmd/internal/ssa/ssac/sexpr.go delete mode 100644 src/cmd/internal/ssa/ssac/sparsemap.go (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/ssa/ssac/.gitignore b/src/cmd/internal/ssa/ssac/.gitignore deleted file mode 100644 index ab17b9d28e..0000000000 --- a/src/cmd/internal/ssa/ssac/.gitignore +++ /dev/null @@ -1 +0,0 @@ -ssac diff --git a/src/cmd/internal/ssa/ssac/fib.goir b/src/cmd/internal/ssa/ssac/fib.goir deleted file mode 100644 index 0875d63ca3..0000000000 --- a/src/cmd/internal/ssa/ssac/fib.goir +++ /dev/null @@ -1,47 +0,0 @@ - (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) - (AS n (LOAD (FP T127bd68 0))) - (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 (FP 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 (FP T127bd68 8) ~r1) - (RETURN) diff --git a/src/cmd/internal/ssa/ssac/fibiter.goir b/src/cmd/internal/ssa/ssac/fibiter.goir deleted file mode 100644 index 98b2b2b576..0000000000 --- a/src/cmd/internal/ssa/ssac/fibiter.goir +++ /dev/null @@ -1,62 +0,0 @@ - (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 (FP 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 (FP Tf5dd68 8) a) - (RETURN) diff --git a/src/cmd/internal/ssa/ssac/main.go b/src/cmd/internal/ssa/ssac/main.go deleted file mode 100644 index 2afa7c6aa9..0000000000 --- a/src/cmd/internal/ssa/ssac/main.go +++ /dev/null @@ -1,439 +0,0 @@ -// 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" -) - -// 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 forward references to their actual values - for _, v := range b.Values { - if v.Op != ssa.OpFwdRef { - 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(ssa.TypeInt64, 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 forward reference for this variable. - fmt.Printf("making fwdRef for var=%d in block=%d\n", id, b.ID) - v = b.NewValue(ssa.OpFwdRef, 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 fwdRef, 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": - // TODO: pick correct width - return ssa.TypeInt64 - 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") -} diff --git a/src/cmd/internal/ssa/ssac/sexpr.go b/src/cmd/internal/ssa/ssac/sexpr.go deleted file mode 100644 index 77e8923dd0..0000000000 --- a/src/cmd/internal/ssa/ssac/sexpr.go +++ /dev/null @@ -1,82 +0,0 @@ -// 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++ - } -} diff --git a/src/cmd/internal/ssa/ssac/sparsemap.go b/src/cmd/internal/ssa/ssac/sparsemap.go deleted file mode 100644 index b7a0fb0fde..0000000000 --- a/src/cmd/internal/ssa/ssac/sparsemap.go +++ /dev/null @@ -1,69 +0,0 @@ -// 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 -} -- cgit v1.3 From b3137966db7c000d2edd8d2f9d6fd10ebbb4da9c Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Wed, 13 May 2015 14:11:39 -0700 Subject: [dev.ssa] cmd/internal/ssa: reorganize opcode tables Separate out opcode tables into separate ranges for each architecture. Put architecture-specific opcodes into separate files. Comment each opcode in a consistent format. Change-Id: Iddf03c062bc8a88ad2bcebbf6528088c01a75779 Reviewed-on: https://go-review.googlesource.com/10033 Reviewed-by: Alan Donovan --- src/cmd/internal/gc/ssa.go | 6 +- src/cmd/internal/ssa/lowerAmd64.go | 36 ++-- src/cmd/internal/ssa/op.go | 273 +++++-------------------- src/cmd/internal/ssa/op_string.go | 32 ++- src/cmd/internal/ssa/opamd64.go | 171 ++++++++++++++++ src/cmd/internal/ssa/regalloc.go | 12 +- src/cmd/internal/ssa/rulegen/lower_amd64.rules | 8 +- 7 files changed, 282 insertions(+), 256 deletions(-) create mode 100644 src/cmd/internal/ssa/opamd64.go (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/gc/ssa.go b/src/cmd/internal/gc/ssa.go index ec747e970b..1d3abb3f37 100644 --- a/src/cmd/internal/gc/ssa.go +++ b/src/cmd/internal/gc/ssa.go @@ -292,7 +292,7 @@ func (s *ssaState) expr(n *Node) *ssa.Value { case OIND: p := s.expr(n.Left) - c := s.curBlock.NewValue1(ssa.OpCheckNil, ssa.TypeBool, nil, p) + c := s.curBlock.NewValue1(ssa.OpIsNonNil, ssa.TypeBool, nil, p) b := s.endBlock() b.Kind = ssa.BlockIf b.Control = c @@ -322,7 +322,7 @@ func (s *ssaState) expr(n *Node) *ssa.Value { // bounds check len := s.curBlock.NewValue1(ssa.OpSliceLen, s.config.UIntPtr, nil, a) - cmp := s.curBlock.NewValue2(ssa.OpCheckBound, ssa.TypeBool, nil, i, len) + cmp := s.curBlock.NewValue2(ssa.OpIsInBounds, ssa.TypeBool, nil, i, len) b := s.endBlock() b.Kind = ssa.BlockIf b.Control = cmp @@ -345,7 +345,7 @@ func (s *ssaState) expr(n *Node) *ssa.Value { log.Fatalf("can't handle CALLFUNC with non-ONAME fn %s", opnames[n.Left.Op]) } bNext := s.f.NewBlock(ssa.BlockPlain) - call := s.curBlock.NewValue1(ssa.OpStaticCall, ssa.TypeMem, n.Left.Sym.Name, s.mem()) + call := s.curBlock.NewValue1(ssa.OpStaticCall, ssa.TypeMem, n.Left.Sym, s.mem()) b := s.endBlock() b.Kind = ssa.BlockCall b.Control = call diff --git a/src/cmd/internal/ssa/lowerAmd64.go b/src/cmd/internal/ssa/lowerAmd64.go index 842822bda4..ef891c37d9 100644 --- a/src/cmd/internal/ssa/lowerAmd64.go +++ b/src/cmd/internal/ssa/lowerAmd64.go @@ -209,8 +209,8 @@ func lowerAmd64(v *Value) bool { goto enda4e64c7eaeda16c1c0db9dac409cd126 enda4e64c7eaeda16c1c0db9dac409cd126: ; - case OpCheckBound: - // match: (CheckBound idx len) + case OpIsInBounds: + // match: (IsInBounds idx len) // cond: // result: (SETB (CMPQ idx len)) { @@ -226,11 +226,11 @@ func lowerAmd64(v *Value) bool { v.AddArg(v0) return true } - goto end249426f6f996d45a62f89a591311a954 - end249426f6f996d45a62f89a591311a954: + goto endb51d371171154c0f1613b687757e0576 + endb51d371171154c0f1613b687757e0576: ; - case OpCheckNil: - // match: (CheckNil p) + case OpIsNonNil: + // match: (IsNonNil p) // cond: // result: (SETNE (TESTQ p p)) { @@ -245,8 +245,8 @@ func lowerAmd64(v *Value) bool { v.AddArg(v0) return true } - goto end90d3057824f74ef953074e473aa0b282 - end90d3057824f74ef953074e473aa0b282: + goto endff508c3726edfb573abc6128c177e76c + endff508c3726edfb573abc6128c177e76c: ; case OpLess: // match: (Less x y) @@ -378,17 +378,17 @@ func lowerAmd64(v *Value) bool { ; // match: (MOVQload [off1] (LEAQ8 [off2] ptr idx) mem) // cond: - // result: (MOVQload8 [off1.(int64)+off2.(int64)] ptr idx mem) + // result: (MOVQloadidx8 [off1.(int64)+off2.(int64)] ptr idx mem) { off1 := v.Aux if v.Args[0].Op != OpLEAQ8 { - goto end35060118a284c93323ab3fb827156638 + goto endba0e5cee85021614041016b1a2709ab8 } off2 := v.Args[0].Aux ptr := v.Args[0].Args[0] idx := v.Args[0].Args[1] mem := v.Args[1] - v.Op = OpMOVQload8 + v.Op = OpMOVQloadidx8 v.Aux = nil v.resetArgs() v.Aux = off1.(int64) + off2.(int64) @@ -397,8 +397,8 @@ func lowerAmd64(v *Value) bool { v.AddArg(mem) return true } - goto end35060118a284c93323ab3fb827156638 - end35060118a284c93323ab3fb827156638: + goto endba0e5cee85021614041016b1a2709ab8 + endba0e5cee85021614041016b1a2709ab8: ; case OpMOVQstore: // match: (MOVQstore [off1] (FPAddr [off2]) val mem) @@ -493,18 +493,18 @@ func lowerAmd64(v *Value) bool { ; // match: (MOVQstore [off1] (LEAQ8 [off2] ptr idx) val mem) // cond: - // result: (MOVQstore8 [off1.(int64)+off2.(int64)] ptr idx val mem) + // result: (MOVQstoreidx8 [off1.(int64)+off2.(int64)] ptr idx val mem) { off1 := v.Aux if v.Args[0].Op != OpLEAQ8 { - goto endb5cba0ee3ba21d2bd8e5aa163d2b984e + goto end4ad469f534c7369f6ac36bdace3462ad } off2 := v.Args[0].Aux ptr := v.Args[0].Args[0] idx := v.Args[0].Args[1] val := v.Args[1] mem := v.Args[2] - v.Op = OpMOVQstore8 + v.Op = OpMOVQstoreidx8 v.Aux = nil v.resetArgs() v.Aux = off1.(int64) + off2.(int64) @@ -514,8 +514,8 @@ func lowerAmd64(v *Value) bool { v.AddArg(mem) return true } - goto endb5cba0ee3ba21d2bd8e5aa163d2b984e - endb5cba0ee3ba21d2bd8e5aa163d2b984e: + goto end4ad469f534c7369f6ac36bdace3462ad + end4ad469f534c7369f6ac36bdace3462ad: ; case OpMULCQ: // match: (MULCQ [c] x) diff --git a/src/cmd/internal/ssa/op.go b/src/cmd/internal/ssa/op.go index 1d374db61d..ebe4a8e747 100644 --- a/src/cmd/internal/ssa/op.go +++ b/src/cmd/internal/ssa/op.go @@ -8,25 +8,33 @@ package ssa // 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. +// Architecture-independent opcodes appear in this file. +// Architecture-dependent opcodes appear in op{arch}.go files. type Op int32 -// All the opcodes +// Opcode ranges, a generic one and one for each architecture. const ( - OpUnknown Op = iota + opInvalid Op = 0 + opGenericBase Op = 1 + 1000*iota + opAMD64Base + op386Base - // machine-independent opcodes + opMax // sentinel +) - OpNop // should never be used, appears only briefly during construction, Has type Void. - OpFwdRef // used during ssa construction. Like OpCopy, but the arg has not been specified yet. +// Generic opcodes +const ( + opGenericStart Op = opGenericBase + iota // 2-input arithmetic - OpAdd - OpSub - OpMul + OpAdd // arg0 + arg1 + OpSub // arg0 - arg1 + OpMul // arg0 * arg1 // 2-input comparisons - OpLess + OpLess // arg0 < arg1 // constants. Constant values are stored in the aux field. // booleans have a bool aux field, strings have a string aux @@ -36,44 +44,40 @@ const ( // as it may be different widths on the host and target. OpConst - OpArg // address of a function parameter/result. Memory input is an arg called ".mem". - OpGlobal // address of a global variable (aux is a *gc.Sym) + OpArg // address of a function parameter/result. Memory input is an arg called ".mem". aux is a string (TODO: make it something other than a string?) + OpGlobal // the address of a global variable aux.(*gc.Sym) 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 + OpCopy // output = arg0 + OpPhi // select an argument based on which predecessor block we came from - OpSliceIndex - OpSliceIndexAddr + OpSliceMake // arg0=ptr, arg1=len, arg2=cap + OpSlicePtr // ptr(arg0) + OpSliceLen // len(arg0) + OpSliceCap // cap(arg0) - OpLoad // args are ptr, memory. Loads from ptr+aux.(int64) - OpStore // args are ptr, value, memory, returns memory. Stores to ptr+aux.(int64) + OpStringMake // arg0=ptr, arg1=len + OpStringPtr // ptr(arg0) + OpStringLen // len(arg0) - OpCheckNil // arg[0] != nil - OpCheckBound // 0 <= arg[0] < arg[1] + OpLoad // Load from arg0+aux.(int64). arg1=memory + OpStore // Store arg1 to arg0+aux.(int64). arg2=memory. Returns memory. + OpSliceIndex // arg0=slice, arg1=index, arg2=memory + OpIsNonNil // arg0 != nil + OpIsInBounds // 0 <= arg0 < arg1 // function calls. Arguments to the call have already been written to the stack. // Return values appear on the stack. The method receiver, if any, is treated // as a phantom first argument. - // TODO: closure pointer must be in a register. - OpCall // args are function ptr, memory - OpStaticCall // aux is function, arg is memory + OpCall // arg0=code pointer, arg1=context ptr, arg2=memory. Returns memory. + OpStaticCall // call function aux.(*gc.Sym), arg0=memory. Returns memory. - OpConvert - OpConvNop + OpConvert // convert arg0 to another type + OpConvNop // interpret arg0 as another type - // 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 + // These ops return a pointer to a location on the stack. + OpFPAddr // FP + aux.(int64) (+ == args from caller, - == locals) + OpSPAddr // SP + aux.(int64) // spill&restore ops for the register allocator. These are // semantically identical to OpCopy; they do not take/return @@ -82,70 +86,19 @@ const ( OpStoreReg8 OpLoadReg8 - // machine-dependent opcodes go here - - // amd64 - OpADDQ - OpSUBQ - OpADDCQ // 1 input arg. output = input + aux.(int64) - OpSUBCQ // 1 input arg. output = input - aux.(int64) - OpMULQ - OpMULCQ // output = input * aux.(int64) - OpSHLQ // output = input0 << input1 - OpSHLCQ // output = input << aux.(int64) - OpNEGQ - OpCMPQ - OpCMPCQ // 1 input arg. Compares input with aux.(int64) - OpADDL - OpTESTQ // compute flags of arg[0] & arg[1] - OpSETEQ - OpSETNE - - // generate boolean based on the flags setting - OpSETL // less than - OpSETGE // >= - OpSETB // "below" = unsigned less than - - // InvertFlags reverses direction of flags register interpretation: - // (InvertFlags (OpCMPQ a b)) == (OpCMPQ b a) - // This is a pseudo-op which can't appear in assembly output. - OpInvertFlags - - OpLEAQ // x+y - OpLEAQ2 // x+2*y - OpLEAQ4 // x+4*y - OpLEAQ8 // x+8*y - - OpMOVQload // (ptr, mem): loads from ptr+aux.(int64) - OpMOVQstore // (ptr, val, mem): stores val to ptr+aux.(int64), returns mem - OpMOVQload8 // (ptr,idx,mem): loads from ptr+idx*8+aux.(int64) - OpMOVQstore8 // (ptr,idx,val,mem): stores to ptr+idx*8+aux.(int64), returns mem - - // load/store from global. aux = GlobalOffset - OpMOVQloadglobal // (mem) -> value - OpMOVQstoreglobal // (val, mem) -> mem - - // load/store 8-byte integer register from stack slot. - OpMOVQloadFP - OpMOVQloadSP - OpMOVQstoreFP - OpMOVQstoreSP - - // materialize a constant into a register - OpMOVQconst - - OpMax // sentinel + // used during ssa construction. Like OpCopy, but the arg has not been specified yet. + OpFwdRef ) // GlobalOffset represents a fixed offset within a global variable type GlobalOffset struct { - Global interface{} // holds a *cmd/internal/gc.Sym + Global interface{} // holds a *gc.Sym Offset int64 } //go:generate stringer -type=Op -type OpInfo struct { +type opInfo struct { flags int32 // assembly template @@ -160,67 +113,13 @@ type OpInfo struct { reg [2][]regMask } -type regMask uint64 - -var regs386 = [...]string{ - "AX", - "CX", - "DX", - "BX", - "SP", - "BP", - "SI", - "DI", - - // pseudo registers - "FLAGS", - "OVERWRITE0", // the same register as the first input -} - -// TODO: match up these with regs386 above -var gp regMask = 0xef -var cx regMask = 0x2 -var flags regMask = 1 << 8 -var overwrite0 regMask = 1 << 9 - const ( // possible properties of opcodes OpFlagCommutative int32 = 1 << iota - - // architecture constants - Arch386 - ArchAMD64 - ArchARM ) -// general purpose registers, 2 input, 1 output -var gp21 = [2][]regMask{{gp, gp}, {gp}} -var gp21_overwrite = [2][]regMask{{gp, gp}, {gp}} - -// general purpose registers, 1 input, 1 output -var gp11 = [2][]regMask{{gp}, {gp}} -var gp11_overwrite = [2][]regMask{{gp}, {gp}} - -// general purpose registers, 0 input, 1 output -var gp01 = [2][]regMask{{}, {gp}} - -// shift operations -var shift = [2][]regMask{{gp, cx}, {gp}} - -var gp2_flags = [2][]regMask{{gp, gp}, {flags}} -var gp1_flags = [2][]regMask{{gp}, {flags}} -var gpload = [2][]regMask{{gp, 0}, {gp}} -var gploadX = [2][]regMask{{gp, gp, 0}, {gp}} // indexed loads -var gpstore = [2][]regMask{{gp, gp, 0}, {0}} -var gpstoreX = [2][]regMask{{gp, gp, gp, 0}, {0}} // indexed stores -var gploadglobal = [2][]regMask{{0}, {gp}} -var gpstoreglobal = [2][]regMask{{gp, 0}, {0}} - -var gpload_stack = [2][]regMask{{0}, {gp}} -var gpstore_stack = [2][]regMask{{gp, 0}, {0}} - // Opcodes that represent the input Go program -var genericTable = [...]OpInfo{ +var genericTable = map[Op]opInfo{ // the unknown op is used only during building and should not appear in a // fully formed ssa representation. @@ -278,87 +177,11 @@ var genericTable = [...]OpInfo{ */ } -// Opcodes that appear in an output amd64 program -var amd64Table = [...]OpInfo{ - OpADDQ: {flags: OpFlagCommutative, asm: "ADDQ\t%I0,%I1,%O0", reg: gp21}, // TODO: overwrite - OpADDCQ: {asm: "ADDQ\t$%A,%I0,%O0", reg: gp11_overwrite}, // aux = int64 constant to add - OpSUBQ: {asm: "SUBQ\t%I0,%I1,%O0", reg: gp21}, - OpSUBCQ: {asm: "SUBQ\t$%A,%I0,%O0", reg: gp11_overwrite}, - OpMULQ: {asm: "MULQ\t%I0,%I1,%O0", reg: gp21}, - OpMULCQ: {asm: "MULQ\t$%A,%I0,%O0", reg: gp11_overwrite}, - OpSHLQ: {asm: "SHLQ\t%I0,%I1,%O0", reg: gp21}, - OpSHLCQ: {asm: "SHLQ\t$%A,%I0,%O0", reg: gp11_overwrite}, - - OpCMPQ: {asm: "CMPQ\t%I0,%I1", reg: gp2_flags}, // compute arg[0]-arg[1] and produce flags - OpCMPCQ: {asm: "CMPQ\t$%A,%I0", reg: gp1_flags}, - OpTESTQ: {asm: "TESTQ\t%I0,%I1", reg: gp2_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"}, - - // loads and stores - OpMOVQload: {asm: "MOVQ\t%A(%I0),%O0", reg: gpload}, - OpMOVQstore: {asm: "MOVQ\t%I1,%A(%I0)", reg: gpstore}, - OpMOVQload8: {asm: "MOVQ\t%A(%I0)(%I1*8),%O0", reg: gploadX}, - OpMOVQstore8: {asm: "MOVQ\t%I2,%A(%I0)(%I1*8)", reg: gpstoreX}, - - OpMOVQloadglobal: {reg: gploadglobal}, - OpMOVQstoreglobal: {reg: gpstoreglobal}, - - OpMOVQconst: {asm: "MOVQ\t$%A,%O0", reg: gp01}, - - OpStaticCall: {asm: "CALL\t%A(SB)"}, - - OpCopy: {asm: "MOVQ\t%I0,%O0", reg: gp11}, - - // convert from flags back to boolean - OpSETL: {}, - - // ops for load/store to stack - OpMOVQloadFP: {asm: "MOVQ\t%A(FP),%O0", reg: gpload_stack}, // mem -> value - OpMOVQloadSP: {asm: "MOVQ\t%A(SP),%O0", reg: gpload_stack}, // mem -> value - OpMOVQstoreFP: {asm: "MOVQ\t%I0,%A(FP)", reg: gpstore_stack}, // mem, value -> mem - OpMOVQstoreSP: {asm: "MOVQ\t%I0,%A(SP)", reg: gpstore_stack}, // mem, value -> mem - - // 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"}, - OpStoreReg8: {asm: "MOVQ\t%I0,%O0"}, -} - -// 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 +var opcodeTable [opMax]opInfo 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) + for op, info := range genericTable { + opcodeTable[op] = info } } diff --git a/src/cmd/internal/ssa/op_string.go b/src/cmd/internal/ssa/op_string.go index adce17a1f2..0851cfe0fb 100644 --- a/src/cmd/internal/ssa/op_string.go +++ b/src/cmd/internal/ssa/op_string.go @@ -4,13 +4,37 @@ package ssa import "fmt" -const _Op_name = "OpUnknownOpNopOpFwdRefOpAddOpSubOpMulOpLessOpConstOpArgOpGlobalOpFuncOpCopyOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpSliceIndexOpSliceIndexAddrOpLoadOpStoreOpCheckNilOpCheckBoundOpCallOpStaticCallOpConvertOpConvNopOpFPAddrOpSPAddrOpStoreReg8OpLoadReg8OpADDQOpSUBQOpADDCQOpSUBCQOpMULQOpMULCQOpSHLQOpSHLCQOpNEGQOpCMPQOpCMPCQOpADDLOpTESTQOpSETEQOpSETNEOpSETLOpSETGEOpSETBOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpMOVQloadOpMOVQstoreOpMOVQload8OpMOVQstore8OpMOVQloadglobalOpMOVQstoreglobalOpMOVQloadFPOpMOVQloadSPOpMOVQstoreFPOpMOVQstoreSPOpMOVQconstOpMax" +const ( + _Op_name_0 = "opInvalid" + _Op_name_1 = "opGenericBaseOpAddOpSubOpMulOpLessOpConstOpArgOpGlobalOpFuncOpCopyOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpLoadOpStoreOpSliceIndexOpIsNonNilOpIsInBoundsOpCallOpStaticCallOpConvertOpConvNopOpFPAddrOpSPAddrOpStoreReg8OpLoadReg8OpFwdRef" + _Op_name_2 = "opAMD64BaseOpADDQOpSUBQOpADDCQOpSUBCQOpMULQOpMULCQOpSHLQOpSHLCQOpNEGQOpADDLOpCMPQOpCMPCQOpTESTQOpSETEQOpSETNEOpSETLOpSETGEOpSETBOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpMOVQloadOpMOVQstoreOpMOVQloadidx8OpMOVQstoreidx8OpMOVQloadglobalOpMOVQstoreglobalOpMOVQloadFPOpMOVQloadSPOpMOVQstoreFPOpMOVQstoreSPOpMOVQconst" + _Op_name_3 = "op386Base" + _Op_name_4 = "opMax" +) -var _Op_index = [...]uint16{0, 9, 14, 22, 27, 32, 37, 43, 50, 55, 63, 69, 75, 80, 91, 101, 111, 121, 133, 144, 155, 167, 183, 189, 196, 206, 218, 224, 236, 245, 254, 262, 270, 281, 291, 297, 303, 310, 317, 323, 330, 336, 343, 349, 355, 362, 368, 375, 382, 389, 395, 402, 408, 421, 427, 434, 441, 448, 458, 469, 480, 492, 508, 525, 537, 549, 562, 575, 586, 591} +var ( + _Op_index_0 = [...]uint8{0, 9} + _Op_index_1 = [...]uint16{0, 13, 18, 23, 28, 34, 41, 46, 54, 60, 66, 71, 82, 92, 102, 112, 124, 135, 146, 152, 159, 171, 181, 193, 199, 211, 220, 229, 237, 245, 256, 266, 274} + _Op_index_2 = [...]uint16{0, 11, 17, 23, 30, 37, 43, 50, 56, 63, 69, 75, 81, 88, 95, 102, 109, 115, 122, 128, 141, 147, 154, 161, 168, 178, 189, 203, 218, 234, 251, 263, 275, 288, 301, 312} + _Op_index_3 = [...]uint8{0, 9} + _Op_index_4 = [...]uint8{0, 5} +) func (i Op) String() string { - if i < 0 || i+1 >= Op(len(_Op_index)) { + switch { + case i == 0: + return _Op_name_0 + case 1001 <= i && i <= 1032: + i -= 1001 + return _Op_name_1[_Op_index_1[i]:_Op_index_1[i+1]] + case 2001 <= i && i <= 2035: + i -= 2001 + return _Op_name_2[_Op_index_2[i]:_Op_index_2[i+1]] + case i == 3001: + return _Op_name_3 + case i == 4001: + return _Op_name_4 + default: return fmt.Sprintf("Op(%d)", i) } - return _Op_name[_Op_index[i]:_Op_index[i+1]] } diff --git a/src/cmd/internal/ssa/opamd64.go b/src/cmd/internal/ssa/opamd64.go new file mode 100644 index 0000000000..8bdd19f713 --- /dev/null +++ b/src/cmd/internal/ssa/opamd64.go @@ -0,0 +1,171 @@ +// 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 + +// amd64-specific opcodes + +const ( + opAMD64start Op = opAMD64Base + iota + + // Suffixes encode the bit width of various instructions. + // Q = 64 bit, L = 32 bit, W = 16 bit, B = 8 bit + + // arithmetic + OpADDQ // arg0 + arg1 + OpSUBQ // arg0 - arg1 + OpADDCQ // arg + aux.(int64) + OpSUBCQ // arg - aux.(int64) + OpMULQ // arg0 * arg1 + OpMULCQ // arg * aux.(int64) + OpSHLQ // arg0 << arg1 + OpSHLCQ // arg << aux.(int64) + OpNEGQ // -arg + OpADDL // arg0 + arg1 + + // Flags value generation. + // We pretend the flags type is an opaque thing that comparisons generate + // and from which we can extract boolean conditions like <, ==, etc. + OpCMPQ // arg0 compare to arg1 + OpCMPCQ // arg0 compare to aux.(int64) + OpTESTQ // (arg0 & arg1) compare to 0 + + // These opcodes extract a particular boolean condition from a flags value. + OpSETEQ // extract == condition from arg0 + OpSETNE // extract != condition from arg0 + OpSETL // extract signed < condition from arg0 + OpSETGE // extract signed >= condition from arg0 + OpSETB // extract unsigned < condition from arg0 + + // InvertFlags reverses the direction of a flags type interpretation: + // (InvertFlags (OpCMPQ a b)) == (OpCMPQ b a) + // This is a pseudo-op which can't appear in assembly output. + OpInvertFlags // reverse direction of arg0 + + OpLEAQ // arg0 + arg1 + aux.(int64) + OpLEAQ2 // arg0 + 2*arg1 + aux.(int64) + OpLEAQ4 // arg0 + 4*arg1 + aux.(int64) + OpLEAQ8 // arg0 + 8*arg1 + aux.(int64) + + // Load/store from general address + OpMOVQload // Load from arg0+aux.(int64). arg1=memory + OpMOVQstore // Store arg1 to arg0+aux.(int64). arg2=memory, returns memory. + OpMOVQloadidx8 // Load from arg0+arg1*8+aux.(int64). arg2=memory + OpMOVQstoreidx8 // Store arg2 to arg0+arg1*8+aux.(int64). arg3=memory, returns memory. + + // Load/store from global. aux.(GlobalOffset) encodes the global location. + OpMOVQloadglobal // arg0 = memory + OpMOVQstoreglobal // store arg0. arg1=memory, returns memory. + + // Load/store from stack slot. + OpMOVQloadFP // load from FP+aux.(int64). arg0=memory + OpMOVQloadSP // load from SP+aux.(int64). arg0=memory + OpMOVQstoreFP // store arg0 to FP+aux.(int64). arg1=memory, returns memory. + OpMOVQstoreSP // store arg0 to SP+aux.(int64). arg1=memory, returns memory. + + // materialize a constant into a register + OpMOVQconst // (takes no arguments) +) + +type regMask uint64 + +var regsAMD64 = [...]string{ + "AX", + "CX", + "DX", + "BX", + "SP", + "BP", + "SI", + "DI", + "R8", + "R9", + "R10", + "R11", + "R12", + "R13", + "R14", + "R15", + + // pseudo registers + "FLAGS", + "OVERWRITE0", // the same register as the first input +} + +var gp regMask = 0xef // all integer registers except SP +var cx regMask = 0x2 +var flags regMask = 1 << 16 + +var ( + // gp = general purpose (integer) registers + gp21 = [2][]regMask{{gp, gp}, {gp}} // 2 input, 1 output + gp11 = [2][]regMask{{gp}, {gp}} // 1 input, 1 output + gp01 = [2][]regMask{{}, {gp}} // 0 input, 1 output + shift = [2][]regMask{{gp, cx}, {gp}} // shift operations + gp2_flags = [2][]regMask{{gp, gp}, {flags}} // generate flags from 2 gp regs + gp1_flags = [2][]regMask{{gp}, {flags}} // generate flags from 1 gp reg + + gpload = [2][]regMask{{gp, 0}, {gp}} + gploadidx = [2][]regMask{{gp, gp, 0}, {gp}} + gpstore = [2][]regMask{{gp, gp, 0}, {0}} + gpstoreidx = [2][]regMask{{gp, gp, gp, 0}, {0}} + + gpload_stack = [2][]regMask{{0}, {gp}} + gpstore_stack = [2][]regMask{{gp, 0}, {0}} +) + +// Opcodes that appear in an output amd64 program +var amd64Table = map[Op]opInfo{ + OpADDQ: {flags: OpFlagCommutative, asm: "ADDQ\t%I0,%I1,%O0", reg: gp21}, // TODO: overwrite + OpADDCQ: {asm: "ADDQ\t$%A,%I0,%O0", reg: gp11}, // aux = int64 constant to add + OpSUBQ: {asm: "SUBQ\t%I0,%I1,%O0", reg: gp21}, + OpSUBCQ: {asm: "SUBQ\t$%A,%I0,%O0", reg: gp11}, + OpMULQ: {asm: "MULQ\t%I0,%I1,%O0", reg: gp21}, + OpMULCQ: {asm: "MULQ\t$%A,%I0,%O0", reg: gp11}, + OpSHLQ: {asm: "SHLQ\t%I0,%I1,%O0", reg: gp21}, + OpSHLCQ: {asm: "SHLQ\t$%A,%I0,%O0", reg: gp11}, + + OpCMPQ: {asm: "CMPQ\t%I0,%I1", reg: gp2_flags}, // compute arg[0]-arg[1] and produce flags + OpCMPCQ: {asm: "CMPQ\t$%A,%I0", reg: gp1_flags}, + OpTESTQ: {asm: "TESTQ\t%I0,%I1", reg: gp2_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"}, + + // loads and stores + OpMOVQload: {asm: "MOVQ\t%A(%I0),%O0", reg: gpload}, + OpMOVQstore: {asm: "MOVQ\t%I1,%A(%I0)", reg: gpstore}, + OpMOVQloadidx8: {asm: "MOVQ\t%A(%I0)(%I1*8),%O0", reg: gploadidx}, + OpMOVQstoreidx8: {asm: "MOVQ\t%I2,%A(%I0)(%I1*8)", reg: gpstoreidx}, + + OpMOVQconst: {asm: "MOVQ\t$%A,%O0", reg: gp01}, + + OpStaticCall: {asm: "CALL\t%A(SB)"}, + + OpCopy: {asm: "MOVQ\t%I0,%O0", reg: gp11}, + + // convert from flags back to boolean + OpSETL: {}, + + // ops for load/store to stack + OpMOVQloadFP: {asm: "MOVQ\t%A(FP),%O0", reg: gpload_stack}, // mem -> value + OpMOVQloadSP: {asm: "MOVQ\t%A(SP),%O0", reg: gpload_stack}, // mem -> value + OpMOVQstoreFP: {asm: "MOVQ\t%I0,%A(FP)", reg: gpstore_stack}, // mem, value -> mem + OpMOVQstoreSP: {asm: "MOVQ\t%I0,%A(SP)", reg: gpstore_stack}, // mem, value -> mem + + // 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"}, + OpStoreReg8: {asm: "MOVQ\t%I0,%O0"}, +} + +func init() { + for op, info := range amd64Table { + opcodeTable[op] = info + } +} diff --git a/src/cmd/internal/ssa/regalloc.go b/src/cmd/internal/ssa/regalloc.go index 724a0557d5..bc397f323f 100644 --- a/src/cmd/internal/ssa/regalloc.go +++ b/src/cmd/internal/ssa/regalloc.go @@ -28,8 +28,16 @@ var registers = [...]Register{ Register{"BP"}, Register{"SI"}, Register{"DI"}, - - // TODO R8, X0, ... + Register{"R8"}, + Register{"R9"}, + Register{"R10"}, + Register{"R11"}, + Register{"R12"}, + Register{"R13"}, + Register{"R14"}, + Register{"R15"}, + + // TODO X0, ... // TODO: make arch-dependent Register{"FLAGS"}, Register{"OVERWRITE"}, diff --git a/src/cmd/internal/ssa/rulegen/lower_amd64.rules b/src/cmd/internal/ssa/rulegen/lower_amd64.rules index 8882e3c253..55267d6842 100644 --- a/src/cmd/internal/ssa/rulegen/lower_amd64.rules +++ b/src/cmd/internal/ssa/rulegen/lower_amd64.rules @@ -34,8 +34,8 @@ (Store ptr val mem) && (is64BitInt(val.Type) || isPtr(val.Type)) -> (MOVQstore [int64(0)] ptr val mem) // checks -(CheckNil p) -> (SETNE (TESTQ p p)) -(CheckBound idx len) -> (SETB (CMPQ idx len)) +(IsNonNil p) -> (SETNE (TESTQ p p)) +(IsInBounds idx len) -> (SETB (CMPQ idx len)) // Rules below here apply some simple optimizations after lowering. // TODO: Should this be a separate pass? @@ -80,8 +80,8 @@ (MOVQstore [off1] (ADDCQ [off2] ptr) val mem) -> (MOVQstore [off1.(int64)+off2.(int64)] ptr val mem) // indexed loads and stores -(MOVQload [off1] (LEAQ8 [off2] ptr idx) mem) -> (MOVQload8 [off1.(int64)+off2.(int64)] ptr idx mem) -(MOVQstore [off1] (LEAQ8 [off2] ptr idx) val mem) -> (MOVQstore8 [off1.(int64)+off2.(int64)] ptr idx val mem) +(MOVQload [off1] (LEAQ8 [off2] ptr idx) mem) -> (MOVQloadidx8 [off1.(int64)+off2.(int64)] ptr idx mem) +(MOVQstore [off1] (LEAQ8 [off2] ptr idx) val mem) -> (MOVQstoreidx8 [off1.(int64)+off2.(int64)] ptr idx val mem) // Combine the offset of a stack object with the offset within a stack object (ADDCQ [off1] (FPAddr [off2])) -> (FPAddr [off1.(int64)+off2.(int64)]) -- cgit v1.3 From 310d09bf73c2063626cd824b8df809595d6f0392 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Tue, 12 May 2015 12:06:45 -0400 Subject: cmd/internal/ssa: utility functions to make Funcs Adds a more convenient way to define Funcs for testing. For instance, b1: v1 = Arg [.mem] Plain -> b2 b2: Exit v1 b3: v2 = Const [true] If v2 -> b3 b2 can be defined as fun :=Fun("entry", Bloc("entry", Valu("mem", OpArg, TypeMem, ".mem"), Goto("exit")), Bloc("exit", Exit("mem")), Bloc("deadblock", Valu("deadval", OpConst, TypeBool, true), If("deadval", "deadblock", "exit"))) Also add an Equiv function to test two Funcs for equivalence. Change-Id: If1633865aeefb8e765e772b6dad19250d93a413a Reviewed-on: https://go-review.googlesource.com/9992 Reviewed-by: Keith Randall --- src/cmd/internal/ssa/deadcode_test.go | 127 +++++------ src/cmd/internal/ssa/func_test.go | 401 ++++++++++++++++++++++++++++++++++ 2 files changed, 455 insertions(+), 73 deletions(-) create mode 100644 src/cmd/internal/ssa/func_test.go (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/ssa/deadcode_test.go b/src/cmd/internal/ssa/deadcode_test.go index 1b7c81c568..ced46e524b 100644 --- a/src/cmd/internal/ssa/deadcode_test.go +++ b/src/cmd/internal/ssa/deadcode_test.go @@ -2,44 +2,35 @@ // 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 +package ssa 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(OpConst, TypeBool, true) - deadblock.Control = deadval - - CheckFunc(f) - Deadcode(f) - CheckFunc(f) - - for _, b := range f.Blocks { - if b == deadblock { + fun := Fun("entry", + Bloc("entry", + Valu("mem", OpArg, TypeMem, ".mem"), + Goto("exit")), + Bloc("exit", + Exit("mem")), + // dead loop + Bloc("deadblock", + // dead value in dead block + Valu("deadval", OpConst, TypeBool, true), + If("deadval", "deadblock", "exit"))) + + CheckFunc(fun.f) + Deadcode(fun.f) + CheckFunc(fun.f) + + for _, b := range fun.f.Blocks { + if b == fun.blocks["deadblock"] { t.Errorf("dead block not removed") } for _, v := range b.Values { - if v == deadval { + if v == fun.values["deadval"] { t.Errorf("control value of dead block not removed") } } @@ -47,23 +38,21 @@ func TestDeadLoop(t *testing.T) { } 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(OpConst, TypeInt64, int64(37)) - - CheckFunc(f) - Deadcode(f) - CheckFunc(f) - - for _, b := range f.Blocks { + fun := Fun("entry", + Bloc("entry", + Valu("mem", OpArg, TypeMem, ".mem"), + Valu("deadval", OpConst, TypeInt64, int64(37)), + Goto("exit")), + Bloc("exit", + Exit("mem"))) + + CheckFunc(fun.f) + Deadcode(fun.f) + CheckFunc(fun.f) + + for _, b := range fun.f.Blocks { for _, v := range b.Values { - if v == deadval { + if v == fun.values["deadval"] { t.Errorf("dead value not removed") } } @@ -71,42 +60,34 @@ func TestDeadValue(t *testing.T) { } 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(OpConst, TypeBool, false) - entry.Control = cond - - CheckFunc(f) - Deadcode(f) - CheckFunc(f) - - if entry.Kind != BlockPlain { + fun := Fun("entry", + Bloc("entry", + Valu("cond", OpConst, TypeBool, false), + Valu("mem", OpArg, TypeMem, ".mem"), + If("cond", "then", "else")), + Bloc("then", + Goto("exit")), + Bloc("else", + Goto("exit")), + Bloc("exit", + Exit("mem"))) + + CheckFunc(fun.f) + Deadcode(fun.f) + CheckFunc(fun.f) + + if fun.blocks["entry"].Kind != BlockPlain { t.Errorf("if(false) not simplified") } - for _, b := range f.Blocks { - if b == then { + for _, b := range fun.f.Blocks { + if b == fun.blocks["then"] { t.Errorf("then block still present") } for _, v := range b.Values { - if v == cond { + if v == fun.values["cond"] { t.Errorf("constant condition still present") } } } -} -func addEdge(b, c *Block) { - b.Succs = append(b.Succs, c) - c.Preds = append(c.Preds, b) } diff --git a/src/cmd/internal/ssa/func_test.go b/src/cmd/internal/ssa/func_test.go new file mode 100644 index 0000000000..e7619ca4f8 --- /dev/null +++ b/src/cmd/internal/ssa/func_test.go @@ -0,0 +1,401 @@ +// This file contains some utility functions to help define Funcs for testing. +// As an example, the following func +// +// b1: +// v1 = Arg [.mem] +// Plain -> b2 +// b2: +// Exit v1 +// b3: +// v2 = Const [true] +// If v2 -> b3 b2 +// +// can be defined as +// +// fun := Fun("entry", +// Bloc("entry", +// Valu("mem", OpArg, TypeMem, ".mem"), +// Goto("exit")), +// Bloc("exit", +// Exit("mem")), +// Bloc("deadblock", +// Valu("deadval", OpConst, TypeBool, true), +// If("deadval", "deadblock", "exit"))) +// +// and the Blocks or Values used in the Func can be accessed +// like this: +// fun.blocks["entry"] or fun.values["deadval"] + +package ssa + +// TODO(matloob): Choose better names for Fun, Bloc, Goto, etc. +// TODO(matloob): Write a parser for the Func disassembly. Maybe +// the parser can be used instead of Fun. + +import ( + "log" + "reflect" + "testing" +) + +// Compare two Funcs for equivalence. Their CFGs must be isomorphic, +// and their values must correspond. +// Requires that values and predecessors are in the same order, even +// though Funcs could be equivalent when they are not. +// TODO(matloob): Allow values and predecessors to be in different +// orders if the CFG are otherwise equivalent. +func Equiv(f, g *Func) bool { + valcor := make(map[*Value]*Value) + var checkVal func(fv, gv *Value) bool + checkVal = func(fv, gv *Value) bool { + if fv == nil && gv == nil { + return true + } + if valcor[fv] == nil && valcor[gv] == nil { + valcor[fv] = gv + valcor[gv] = fv + // Ignore ids. Ops and Types are compared for equality. + // TODO(matloob): Make sure types are canonical and can + // be compared for equality. + if fv.Op != gv.Op || fv.Type != gv.Type { + return false + } + if !reflect.DeepEqual(fv.Aux, gv.Aux) { + // This makes the assumption that aux values can be compared + // using DeepEqual. + // TODO(matloob): Aux values may be *gc.Sym pointers in the near + // future. Make sure they are canonical. + return false + } + if len(fv.Args) != len(gv.Args) { + return false + } + for i := range fv.Args { + if !checkVal(fv.Args[i], gv.Args[i]) { + return false + } + } + } + return valcor[fv] == gv && valcor[gv] == fv + } + blkcor := make(map[*Block]*Block) + var checkBlk func(fb, gb *Block) bool + checkBlk = func(fb, gb *Block) bool { + if blkcor[fb] == nil && blkcor[gb] == nil { + blkcor[fb] = gb + blkcor[gb] = fb + // ignore ids + if fb.Kind != gb.Kind { + return false + } + if len(fb.Values) != len(gb.Values) { + return false + } + for i := range fb.Values { + if !checkVal(fb.Values[i], gb.Values[i]) { + return false + } + } + if len(fb.Succs) != len(gb.Succs) { + return false + } + for i := range fb.Succs { + if !checkBlk(fb.Succs[i], gb.Succs[i]) { + return false + } + } + if len(fb.Preds) != len(gb.Preds) { + return false + } + for i := range fb.Preds { + if !checkBlk(fb.Preds[i], gb.Preds[i]) { + return false + } + } + return true + + } + return blkcor[fb] == gb && blkcor[gb] == fb + } + + return checkBlk(f.Entry, g.Entry) +} + +// fun is the return type of Fun. It contains the created func +// itself as well as indexes from block and value names into the +// corresponding Blocks and Values. +type fun struct { + f *Func + blocks map[string]*Block + values map[string]*Value +} + +// Fun takes the name of an entry bloc and a series of Bloc calls, and +// returns a fun containing the composed Func. entry must be a name +// supplied to one of the Bloc functions. Each of the bloc names and +// valu names should be unique across the Fun. +func Fun(entry string, blocs ...bloc) fun { + f := new(Func) + blocks := make(map[string]*Block) + values := make(map[string]*Value) + // Create all the blocks and values. + for _, bloc := range blocs { + b := f.NewBlock(bloc.control.kind) + blocks[bloc.name] = b + for _, valu := range bloc.valus { + // args are filled in the second pass. + values[valu.name] = b.NewValue(valu.op, valu.t, valu.aux) + } + } + // Connect the blocks together and specify control values. + f.Entry = blocks[entry] + for _, bloc := range blocs { + b := blocks[bloc.name] + c := bloc.control + // Specify control values. + if c.control != "" { + cval, ok := values[c.control] + if !ok { + log.Panicf("control value for block %s missing", bloc.name) + } + b.Control = cval + } + // Fill in args. + for _, valu := range bloc.valus { + v := values[valu.name] + for _, arg := range valu.args { + a, ok := values[arg] + if !ok { + log.Panicf("arg %s missing for value %s in block %s", + arg, valu.name, bloc.name) + } + v.AddArg(a) + } + } + // Connect to successors. + for _, succ := range c.succs { + addEdge(b, blocks[succ]) + } + } + return fun{f, blocks, values} +} + +// Bloc defines a block for Fun. The bloc name should be unique +// across the containing Fun. entries should consist of calls to valu, +// as well as one call to Goto, If, or Exit to specify the block kind. +func Bloc(name string, entries ...interface{}) bloc { + b := bloc{} + b.name = name + seenCtrl := false + for _, e := range entries { + switch v := e.(type) { + case ctrl: + // there should be exactly one Ctrl entry. + if seenCtrl { + log.Panicf("already seen control for block %s", name) + } + b.control = v + seenCtrl = true + case valu: + b.valus = append(b.valus, v) + } + } + if !seenCtrl { + log.Panicf("block %s doesn't have control", b.name) + } + return b +} + +// Valu defines a value in a block. +func Valu(name string, op Op, t Type, aux interface{}, args ...string) valu { + return valu{name, op, t, aux, args} +} + +// Goto specifies that this is a BlockPlain and names the single successor. +// TODO(matloob): choose a better name. +func Goto(succ string) ctrl { + return ctrl{BlockPlain, "", []string{succ}} +} + +// If specifies a BlockIf. +func If(cond, sub, alt string) ctrl { + return ctrl{BlockIf, cond, []string{sub, alt}} +} + +// Exit specifies a BlockExit. +func Exit(arg string) ctrl { + return ctrl{BlockExit, arg, []string{}} +} + +// bloc, ctrl, and valu are internal structures used by Bloc, Valu, Goto, +// If, and Exit to help define blocks. + +type bloc struct { + name string + control ctrl + valus []valu +} + +type ctrl struct { + kind BlockKind + control string + succs []string +} + +type valu struct { + name string + op Op + t Type + aux interface{} + args []string +} + +func addEdge(b, c *Block) { + b.Succs = append(b.Succs, c) + c.Preds = append(c.Preds, b) +} + +func TestArgs(t *testing.T) { + fun := Fun("entry", + Bloc("entry", + Valu("a", OpConst, TypeInt64, 14), + Valu("b", OpConst, TypeInt64, 26), + Valu("sum", OpAdd, TypeInt64, nil, "a", "b"), + Valu("mem", OpArg, TypeMem, ".mem"), + Goto("exit")), + Bloc("exit", + Exit("mem"))) + sum := fun.values["sum"] + for i, name := range []string{"a", "b"} { + if sum.Args[i] != fun.values[name] { + t.Errorf("arg %d for sum is incorrect: want %s, got %s", + i, sum.Args[i], fun.values[name]) + } + } +} + +func TestEquiv(t *testing.T) { + equivalentCases := []struct{ f, g fun }{ + // simple case + { + Fun("entry", + Bloc("entry", + Valu("a", OpConst, TypeInt64, 14), + Valu("b", OpConst, TypeInt64, 26), + Valu("sum", OpAdd, TypeInt64, nil, "a", "b"), + Valu("mem", OpArg, TypeMem, ".mem"), + Goto("exit")), + Bloc("exit", + Exit("mem"))), + Fun("entry", + Bloc("entry", + Valu("a", OpConst, TypeInt64, 14), + Valu("b", OpConst, TypeInt64, 26), + Valu("sum", OpAdd, TypeInt64, nil, "a", "b"), + Valu("mem", OpArg, TypeMem, ".mem"), + Goto("exit")), + Bloc("exit", + Exit("mem"))), + }, + // block order changed + { + Fun("entry", + Bloc("entry", + Valu("a", OpConst, TypeInt64, 14), + Valu("b", OpConst, TypeInt64, 26), + Valu("sum", OpAdd, TypeInt64, nil, "a", "b"), + Valu("mem", OpArg, TypeMem, ".mem"), + Goto("exit")), + Bloc("exit", + Exit("mem"))), + Fun("entry", + Bloc("exit", + Exit("mem")), + Bloc("entry", + Valu("a", OpConst, TypeInt64, 14), + Valu("b", OpConst, TypeInt64, 26), + Valu("sum", OpAdd, TypeInt64, nil, "a", "b"), + Valu("mem", OpArg, TypeMem, ".mem"), + Goto("exit"))), + }, + } + for _, c := range equivalentCases { + if !Equiv(c.f.f, c.g.f) { + t.Errorf("expected equivalence. Func definitions:") + // TODO(matloob): Rewrite PrintFunc to output to a string or writer, + // so the functions can be written to the error log. + PrintFunc(c.f.f) + PrintFunc(c.g.f) + } + } + + differentCases := []struct{ f, g fun }{ + // different shape + { + Fun("entry", + Bloc("entry", + Valu("mem", OpArg, TypeMem, ".mem"), + Goto("exit")), + Bloc("exit", + Exit("mem"))), + Fun("entry", + Bloc("entry", + Valu("mem", OpArg, TypeMem, ".mem"), + Exit("mem"))), + }, + // value order changed + { + Fun("entry", + Bloc("entry", + Valu("mem", OpArg, TypeMem, ".mem"), + Valu("b", OpConst, TypeInt64, 26), + Valu("a", OpConst, TypeInt64, 14), + Exit("mem"))), + Fun("entry", + Bloc("entry", + Valu("mem", OpArg, TypeMem, ".mem"), + Valu("a", OpConst, TypeInt64, 14), + Valu("b", OpConst, TypeInt64, 26), + Exit("mem"))), + }, + // value aux different + { + Fun("entry", + Bloc("entry", + Valu("mem", OpArg, TypeMem, ".mem"), + Valu("a", OpConst, TypeInt64, 14), + Exit("mem"))), + Fun("entry", + Bloc("entry", + Valu("mem", OpArg, TypeMem, ".mem"), + Valu("a", OpConst, TypeInt64, 26), + Exit("mem"))), + }, + // value args different + { + Fun("entry", + Bloc("entry", + Valu("mem", OpArg, TypeMem, ".mem"), + Valu("a", OpConst, TypeInt64, 14), + Valu("b", OpConst, TypeInt64, 26), + Valu("sum", OpAdd, TypeInt64, nil, "a", "b"), + Exit("mem"))), + Fun("entry", + Bloc("entry", + Valu("mem", OpArg, TypeMem, ".mem"), + Valu("a", OpConst, TypeInt64, 0), + Valu("b", OpConst, TypeInt64, 14), + Valu("sum", OpAdd, TypeInt64, nil, "b", "a"), + Exit("mem"))), + }, + } + for _, c := range differentCases { + if Equiv(c.f.f, c.g.f) { + t.Errorf("expected difference. Func definitions:") + // TODO(matloob): Rewrite PrintFunc to output to a string or writer, + // so the functions can be written to the error log. + PrintFunc(c.f.f) + PrintFunc(c.g.f) + } + } +} -- cgit v1.3 From 083a646f63055427c203d5600ef65f05f55783bf Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 12 May 2015 11:06:44 -0700 Subject: [dev.ssa] cmd/internal/gc: Generate code from ssa form After the ssa compiler finishes, extract a cmd/internal/obj program from the result. Can compile and run iterative Fibonacci. The code is awful, but it runs. Change-Id: I19fa27ffe69863950a8cb594f33a5e9a671a7663 Reviewed-on: https://go-review.googlesource.com/9971 Reviewed-by: Russ Cox --- src/cmd/internal/gc/pgen.go | 9 +- src/cmd/internal/gc/ssa.go | 258 ++++++++++++++++++++++++++++++++++++- src/cmd/internal/ssa/location.go | 6 +- src/cmd/internal/ssa/regalloc.go | 36 +++--- src/cmd/internal/ssa/stackalloc.go | 6 + 5 files changed, 291 insertions(+), 24 deletions(-) (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/gc/pgen.go b/src/cmd/internal/gc/pgen.go index ae7fcce1ba..2c225c8778 100644 --- a/src/cmd/internal/gc/pgen.go +++ b/src/cmd/internal/gc/pgen.go @@ -6,6 +6,7 @@ package gc import ( "cmd/internal/obj" + "cmd/internal/ssa" "crypto/md5" "fmt" "strings" @@ -367,6 +368,7 @@ func compile(fn *Node) { var nam *Node var gcargs *Sym var gclocals *Sym + var ssafn *ssa.Func if fn.Nbody == nil { if pure_go != 0 || strings.HasPrefix(fn.Nname.Sym.Name, "init.") { Yyerror("missing function body for %q", fn.Nname.Sym.Name) @@ -422,8 +424,7 @@ func compile(fn *Node) { { name := Curfn.Nname.Sym.Name if len(name) > 4 && name[len(name)-4:] == "_ssa" { - buildssa(Curfn) - // TODO(khr): use result of buildssa + ssafn = buildssa(Curfn) } } @@ -488,6 +489,10 @@ func compile(fn *Node) { } Genlist(Curfn.Func.Enter) + if ssafn != nil { + genssa(ssafn, ptxt, gcargs, gclocals) + return + } Genlist(Curfn.Nbody) gclean() checklabels() diff --git a/src/cmd/internal/gc/ssa.go b/src/cmd/internal/gc/ssa.go index 1d3abb3f37..ec6ad8abcb 100644 --- a/src/cmd/internal/gc/ssa.go +++ b/src/cmd/internal/gc/ssa.go @@ -7,10 +7,12 @@ package gc import ( "log" + "cmd/internal/obj" + "cmd/internal/obj/x86" // TODO: remove "cmd/internal/ssa" ) -func buildssa(fn *Node) { +func buildssa(fn *Node) *ssa.Func { dumplist("buildssa", Curfn.Nbody) var s ssaState @@ -50,9 +52,10 @@ func buildssa(fn *Node) { // Link up variable uses to variable definitions s.linkForwardReferences() + // Main call to ssa package to compile function ssa.Compile(s.f) - // TODO(khr): Use the resulting s.f to generate code + return s.f } type ssaState struct { @@ -457,3 +460,254 @@ func addEdge(b, c *ssa.Block) { b.Succs = append(b.Succs, c) c.Preds = append(c.Preds, b) } + +// an unresolved branch +type branch struct { + p *obj.Prog // branch instruction + b *ssa.Block // target +} + +// genssa appends entries to ptxt for each instruction in f. +// gcargs and gclocals are filled in with pointer maps for the frame. +func genssa(f *ssa.Func, ptxt *obj.Prog, gcargs, gclocals *Sym) { + // TODO: line numbers + // TODO: layout frame + stkSize := int64(64) + + if Hasdefer != 0 { + // deferreturn pretends to have one uintptr argument. + // Reserve space for it so stack scanner is happy. + if Maxarg < int64(Widthptr) { + Maxarg = int64(Widthptr) + } + } + if stkSize+Maxarg > 1<<31 { + Yyerror("stack frame too large (>2GB)") + return + } + frameSize := stkSize + Maxarg + + ptxt.To.Type = obj.TYPE_TEXTSIZE + ptxt.To.Val = int32(Rnd(Curfn.Type.Argwid, int64(Widthptr))) // arg size + ptxt.To.Offset = frameSize - 8 // TODO: arch-dependent + + // Remember where each block starts. + bstart := make([]*obj.Prog, f.NumBlocks()) + + // Remember all the branch instructions we've seen + // and where they would like to go + var branches []branch + + // Emit basic blocks + for i, b := range f.Blocks { + bstart[b.ID] = Pc + // Emit values in block + for _, v := range b.Values { + genValue(v, frameSize) + } + // Emit control flow instructions for block + var next *ssa.Block + if i < len(f.Blocks)-1 { + next = f.Blocks[i+1] + } + branches = genBlock(b, next, branches) + } + + // Resolve branches + for _, br := range branches { + br.p.To.Val = bstart[br.b.ID] + } + + Pc.As = obj.ARET // overwrite AEND + + // TODO: liveness + // TODO: gcargs + // TODO: gclocals + + // TODO: dump frame if -f + + // Emit garbage collection symbols. TODO: put something in them + liveness(Curfn, ptxt, gcargs, gclocals) +} + +func genValue(v *ssa.Value, frameSize int64) { + switch v.Op { + case ssa.OpADDQ: + // TODO: use addq instead of leaq if target is in the right register. + p := Prog(x86.ALEAQ) + p.From.Type = obj.TYPE_MEM + p.From.Reg = regnum(v.Args[0]) + p.From.Scale = 1 + p.From.Index = regnum(v.Args[1]) + p.To.Type = obj.TYPE_REG + p.To.Reg = regnum(v) + case ssa.OpADDCQ: + // TODO: use addq instead of leaq if target is in the right register. + p := Prog(x86.ALEAQ) + p.From.Type = obj.TYPE_MEM + p.From.Reg = regnum(v.Args[0]) + p.From.Offset = v.Aux.(int64) + p.To.Type = obj.TYPE_REG + p.To.Reg = regnum(v) + case ssa.OpSUBCQ: + // This code compensates for the fact that the register allocator + // doesn't understand 2-address instructions yet. TODO: fix that. + x := regnum(v.Args[0]) + r := regnum(v) + if x != r { + p := Prog(x86.AMOVQ) + p.From.Type = obj.TYPE_REG + p.From.Reg = x + p.To.Type = obj.TYPE_REG + p.To.Reg = r + x = r + } + p := Prog(x86.ASUBQ) + p.From.Type = obj.TYPE_CONST + p.From.Offset = v.Aux.(int64) + p.To.Type = obj.TYPE_REG + p.To.Reg = r + case ssa.OpCMPQ: + x := regnum(v.Args[0]) + y := regnum(v.Args[1]) + p := Prog(x86.ACMPQ) + p.From.Type = obj.TYPE_REG + p.From.Reg = x + p.To.Type = obj.TYPE_REG + p.To.Reg = y + case ssa.OpMOVQconst: + x := regnum(v) + p := Prog(x86.AMOVQ) + p.From.Type = obj.TYPE_CONST + p.From.Offset = v.Aux.(int64) + p.To.Type = obj.TYPE_REG + p.To.Reg = x + case ssa.OpMOVQloadFP: + x := regnum(v) + p := Prog(x86.AMOVQ) + p.From.Type = obj.TYPE_MEM + p.From.Reg = x86.REG_SP + p.From.Offset = v.Aux.(int64) + frameSize + p.To.Type = obj.TYPE_REG + p.To.Reg = x + case ssa.OpMOVQstoreFP: + x := regnum(v.Args[0]) + p := Prog(x86.AMOVQ) + p.From.Type = obj.TYPE_REG + p.From.Reg = x + p.To.Type = obj.TYPE_MEM + p.To.Reg = x86.REG_SP + p.To.Offset = v.Aux.(int64) + frameSize + case ssa.OpCopy: + x := regnum(v.Args[0]) + y := regnum(v) + if x != y { + p := Prog(x86.AMOVQ) + p.From.Type = obj.TYPE_REG + p.From.Reg = x + p.To.Type = obj.TYPE_REG + p.To.Reg = y + } + case ssa.OpLoadReg8: + p := Prog(x86.AMOVQ) + p.From.Type = obj.TYPE_MEM + p.From.Reg = x86.REG_SP + p.From.Offset = frameSize - localOffset(v.Args[0]) + p.To.Type = obj.TYPE_REG + p.To.Reg = regnum(v) + case ssa.OpStoreReg8: + p := Prog(x86.AMOVQ) + p.From.Type = obj.TYPE_REG + p.From.Reg = regnum(v.Args[0]) + p.To.Type = obj.TYPE_MEM + p.To.Reg = x86.REG_SP + p.To.Offset = frameSize - localOffset(v) + case ssa.OpPhi: + // just check to make sure regalloc did it right + f := v.Block.Func + loc := f.RegAlloc[v.ID] + for _, a := range v.Args { + if f.RegAlloc[a.ID] != loc { // TODO: .Equal() instead? + log.Fatalf("phi arg at different location than phi %v %v %v %v", v, loc, a, f.RegAlloc[a.ID]) + } + } + case ssa.OpConst: + if v.Block.Func.RegAlloc[v.ID] != nil { + log.Fatalf("const value %v shouldn't have a location", v) + } + case ssa.OpArg: + // memory arg needs no code + // TODO: only mem arg goes here. + default: + log.Fatalf("value %v not implemented yet", v) + } +} + +func genBlock(b, next *ssa.Block, branches []branch) []branch { + switch b.Kind { + case ssa.BlockPlain: + if b.Succs[0] != next { + p := Prog(obj.AJMP) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[0]}) + } + case ssa.BlockExit: + Prog(obj.ARET) + case ssa.BlockLT: + if b.Succs[0] == next { + p := Prog(x86.AJGE) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[1]}) + } else if b.Succs[1] == next { + p := Prog(x86.AJLT) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[0]}) + } else { + p := Prog(x86.AJLT) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[0]}) + q := Prog(obj.AJMP) + q.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{q, b.Succs[1]}) + } + default: + log.Fatalf("branch at %v not implemented yet", b) + } + return branches +} + +// ssaRegToReg maps ssa register numbers to obj register numbers. +var ssaRegToReg = [...]int16{ + x86.REG_AX, + x86.REG_CX, + x86.REG_DX, + x86.REG_BX, + x86.REG_SP, + x86.REG_BP, + x86.REG_SI, + x86.REG_DI, + x86.REG_R8, + x86.REG_R9, + x86.REG_R10, + x86.REG_R11, + x86.REG_R12, + x86.REG_R13, + x86.REG_R14, + x86.REG_R15, + // TODO: more + // TODO: arch-dependent +} + +// regnum returns the register (in cmd/internal/obj numbering) to +// which v has been allocated. Panics if v is not assigned to a +// register. +func regnum(v *ssa.Value) int16 { + return ssaRegToReg[v.Block.Func.RegAlloc[v.ID].(*ssa.Register).Num] +} + +// localOffset returns the offset below the frame pointer where +// a stack-allocated local has been allocated. Panics if v +// is not assigned to a local slot. +func localOffset(v *ssa.Value) int64 { + return v.Block.Func.RegAlloc[v.ID].(*ssa.LocalSlot).Idx +} diff --git a/src/cmd/internal/ssa/location.go b/src/cmd/internal/ssa/location.go index 5fc2c5c934..528956e681 100644 --- a/src/cmd/internal/ssa/location.go +++ b/src/cmd/internal/ssa/location.go @@ -14,7 +14,9 @@ type Location interface { } // A Register is a machine register, like %rax. +// They are numbered densely from 0 (for each architecture). type Register struct { + Num int32 name string } @@ -24,11 +26,11 @@ func (r *Register) Name() string { // 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) + Idx int64 // offset in locals area (distance down from FP == caller's SP) } func (s *LocalSlot) Name() string { - return fmt.Sprintf("-%d(FP)", s.idx) + return fmt.Sprintf("-%d(FP)", s.Idx) } // An ArgSlot is a location in the parents' stack frame where it passed us an argument. diff --git a/src/cmd/internal/ssa/regalloc.go b/src/cmd/internal/ssa/regalloc.go index bc397f323f..e2de10896e 100644 --- a/src/cmd/internal/ssa/regalloc.go +++ b/src/cmd/internal/ssa/regalloc.go @@ -20,27 +20,27 @@ type register uint var numRegs register = 32 var registers = [...]Register{ - Register{"AX"}, - Register{"CX"}, - Register{"DX"}, - Register{"BX"}, - Register{"SP"}, - Register{"BP"}, - Register{"SI"}, - Register{"DI"}, - Register{"R8"}, - Register{"R9"}, - Register{"R10"}, - Register{"R11"}, - Register{"R12"}, - Register{"R13"}, - Register{"R14"}, - Register{"R15"}, + Register{0, "AX"}, + Register{1, "CX"}, + Register{2, "DX"}, + Register{3, "BX"}, + Register{4, "SP"}, + Register{5, "BP"}, + Register{6, "SI"}, + Register{7, "DI"}, + Register{8, "R8"}, + Register{9, "R9"}, + Register{10, "R10"}, + Register{11, "R11"}, + Register{12, "R12"}, + Register{13, "R13"}, + Register{14, "R14"}, + Register{15, "R15"}, // TODO X0, ... // TODO: make arch-dependent - Register{"FLAGS"}, - Register{"OVERWRITE"}, + Register{16, "FLAGS"}, + Register{17, "OVERWRITE"}, } // countRegs returns the number of set bits in the register mask. diff --git a/src/cmd/internal/ssa/stackalloc.go b/src/cmd/internal/ssa/stackalloc.go index aa6d829fa2..4d0359ed81 100644 --- a/src/cmd/internal/ssa/stackalloc.go +++ b/src/cmd/internal/ssa/stackalloc.go @@ -35,6 +35,12 @@ func stackalloc(f *Func) { if v.Type.IsMemory() { // TODO: only "regallocable" types continue } + if v.Op == OpConst { + // don't allocate space for OpConsts. They should + // have been rematerialized everywhere. + // TODO: is this the right thing to do? + continue + } // a := v.Type.Align() // n = (n + a - 1) / a * a TODO n += v.Type.Size() -- cgit v1.3 From cfc2aa56b0bf6b7dfb8f38cd2cfbe8799fc5a31a Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Mon, 18 May 2015 16:44:20 -0700 Subject: [dev.ssa] cmd/internal/ssa: Handle more instructions + some cleanup Add & as an input op. Add several output ops (loads & stores, TESTB, LEAQglobal, branches, memcopy) Some other small things: - Add exprAddr to builder to generate addresses of expressions. Use it in various places that had ad-hoc code. - Separate out nil & bounds check generation to separate functions. - Add explicit FP and SP ops so we dont need specialized *FP and *SP opcodes. - Fix fallthrough at end of functions with no return values. - rematerialization of more opcodes. Change-Id: I781decfcef9770fb15f0cd6b061547f7824a2d5e Reviewed-on: https://go-review.googlesource.com/10213 Reviewed-by: Alan Donovan --- src/cmd/internal/gc/ssa.go | 371 ++++++++++++++++------ src/cmd/internal/obj/x86/6.out.go | 32 +- src/cmd/internal/ssa/check.go | 6 +- src/cmd/internal/ssa/config.go | 6 +- src/cmd/internal/ssa/generic.go | 189 ++++++++---- src/cmd/internal/ssa/lower.go | 115 ++++--- src/cmd/internal/ssa/lowerAmd64.go | 408 +++++++++++++------------ src/cmd/internal/ssa/op.go | 19 +- src/cmd/internal/ssa/op_string.go | 12 +- src/cmd/internal/ssa/opamd64.go | 57 ++-- src/cmd/internal/ssa/regalloc.go | 43 ++- src/cmd/internal/ssa/rewrite.go | 33 +- src/cmd/internal/ssa/rulegen/generic.rules | 21 +- src/cmd/internal/ssa/rulegen/lower_amd64.rules | 49 +-- src/cmd/internal/ssa/rulegen/rulegen.go | 35 ++- src/cmd/internal/ssa/stackalloc.go | 12 +- 16 files changed, 905 insertions(+), 503 deletions(-) (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/gc/ssa.go b/src/cmd/internal/gc/ssa.go index ec6ad8abcb..8e81163ad4 100644 --- a/src/cmd/internal/gc/ssa.go +++ b/src/cmd/internal/gc/ssa.go @@ -15,7 +15,7 @@ import ( func buildssa(fn *Node) *ssa.Func { dumplist("buildssa", Curfn.Nbody) - var s ssaState + var s state // TODO(khr): build config just once at the start of the compiler binary s.config = ssa.NewConfig(Thearch.Thestring) @@ -33,8 +33,10 @@ func buildssa(fn *Node) *ssa.Func { // Allocate exit block s.exit = s.f.NewBlock(ssa.BlockExit) - // TODO(khr): all args. Make a struct containing args/returnvals, declare - // an FP which contains a pointer to that struct. + // Allocate starting values + s.startmem = s.f.Entry.NewValue(ssa.OpArg, ssa.TypeMem, ".mem") + s.fp = s.f.Entry.NewValue(ssa.OpFP, s.config.Uintptr, nil) // TODO: use generic pointer type (unsafe.Pointer?) instead + s.sp = s.f.Entry.NewValue(ssa.OpSP, s.config.Uintptr, nil) s.vars = map[string]*ssa.Value{} s.labels = map[string]*ssa.Block{} @@ -44,6 +46,11 @@ func buildssa(fn *Node) *ssa.Func { s.startBlock(s.f.Entry) s.stmtList(fn.Nbody) + // fallthrough to exit + if b := s.endBlock(); b != nil { + addEdge(b, s.exit) + } + // Finish up exit block s.startBlock(s.exit) s.exit.Control = s.mem() @@ -58,7 +65,7 @@ func buildssa(fn *Node) *ssa.Func { return s.f } -type ssaState struct { +type state struct { // configuration (arch) information config *ssa.Config @@ -83,10 +90,18 @@ type ssaState struct { // offsets of argument slots // unnamed and unused args are not listed. argOffsets map[string]int64 + + // starting values. Memory, frame pointer, and stack pointer + startmem *ssa.Value + fp *ssa.Value + sp *ssa.Value } // startBlock sets the current block we're generating code in to b. -func (s *ssaState) startBlock(b *ssa.Block) { +func (s *state) startBlock(b *ssa.Block) { + if s.curBlock != nil { + log.Fatalf("starting block %v when block %v has not ended", b, s.curBlock) + } s.curBlock = b s.vars = map[string]*ssa.Value{} } @@ -94,7 +109,7 @@ func (s *ssaState) startBlock(b *ssa.Block) { // endBlock marks the end of generating code for the current block. // Returns the (former) current block. Returns nil if there is no current // block, i.e. if no code flows to the current execution point. -func (s *ssaState) endBlock() *ssa.Block { +func (s *state) endBlock() *ssa.Block { b := s.curBlock if b == nil { return nil @@ -109,14 +124,14 @@ func (s *ssaState) endBlock() *ssa.Block { } // ssaStmtList converts the statement n to SSA and adds it to s. -func (s *ssaState) stmtList(l *NodeList) { +func (s *state) stmtList(l *NodeList) { for ; l != nil; l = l.Next { s.stmt(l.N) } } // ssaStmt converts the statement n to SSA and adds it to s. -func (s *ssaState) stmt(n *Node) { +func (s *state) stmt(n *Node) { s.stmtList(n.Ninit) switch n.Op { @@ -145,35 +160,15 @@ func (s *ssaState) stmt(n *Node) { case OAS: // TODO(khr): colas? val := s.expr(n.Right) - if n.Left.Op == OINDREG { - // indirect off a register (TODO: always SP?) - // used for storing arguments to callees - addr := s.f.Entry.NewValue(ssa.OpSPAddr, Ptrto(n.Right.Type), n.Left.Xoffset) - s.vars[".mem"] = s.curBlock.NewValue3(ssa.OpStore, ssa.TypeMem, nil, addr, val, s.mem()) - } else if n.Left.Op != ONAME { - // some more complicated expression. Rewrite to a store. TODO - addr := s.expr(n.Left) // TODO: wrap in & - - // TODO(khr): nil check - s.vars[".mem"] = s.curBlock.NewValue3(ssa.OpStore, n.Right.Type, nil, addr, val, s.mem()) - } else if !n.Left.Addable { - // TODO - log.Fatalf("assignment to non-addable value") - } else if n.Left.Class&PHEAP != 0 { - // TODO - log.Fatalf("assignment to heap value") - } else if n.Left.Class == PEXTERN { - // assign to global variable - addr := s.f.Entry.NewValue(ssa.OpGlobal, Ptrto(n.Left.Type), n.Left.Sym) - s.vars[".mem"] = s.curBlock.NewValue3(ssa.OpStore, ssa.TypeMem, nil, addr, val, s.mem()) - } else if n.Left.Class == PPARAMOUT { - // store to parameter slot - addr := s.f.Entry.NewValue(ssa.OpFPAddr, Ptrto(n.Right.Type), n.Left.Xoffset) - s.vars[".mem"] = s.curBlock.NewValue3(ssa.OpStore, ssa.TypeMem, nil, addr, val, s.mem()) - } else { - // normal variable + if n.Left.Op == ONAME && !n.Left.Addrtaken && n.Left.Class&PHEAP == 0 && n.Left.Class != PEXTERN && n.Left.Class != PPARAMOUT { + // ssa-able variable. s.vars[n.Left.Sym.Name] = val + return } + // not ssa-able. Treat as a store. + addr := s.addr(n.Left) + s.vars[".mem"] = s.curBlock.NewValue3(ssa.OpStore, ssa.TypeMem, nil, addr, val, s.mem()) + // TODO: try to make more variables registerizeable. case OIF: cond := s.expr(n.Ntest) b := s.endBlock() @@ -254,7 +249,7 @@ func (s *ssaState) stmt(n *Node) { } // expr converts the expression n to ssa, adds it to s and returns the ssa result. -func (s *ssaState) expr(n *Node) *ssa.Value { +func (s *state) expr(n *Node) *ssa.Value { if n == nil { // TODO(khr): is this nil??? return s.f.Entry.NewValue(ssa.OpConst, n.Type, nil) @@ -269,7 +264,6 @@ func (s *ssaState) expr(n *Node) *ssa.Value { } s.argOffsets[n.Sym.Name] = n.Xoffset return s.variable(n.Sym.Name, n.Type) - // binary ops case OLITERAL: switch n.Val.Ctype { case CTINT: @@ -278,6 +272,8 @@ func (s *ssaState) expr(n *Node) *ssa.Value { log.Fatalf("unhandled OLITERAL %v", n.Val.Ctype) return nil } + + // binary ops case OLT: a := s.expr(n.Left) b := s.expr(n.Right) @@ -286,56 +282,36 @@ func (s *ssaState) expr(n *Node) *ssa.Value { a := s.expr(n.Left) b := s.expr(n.Right) return s.curBlock.NewValue2(ssa.OpAdd, a.Type, nil, a, b) - case OSUB: // TODO:(khr) fold code for all binary ops together somehow a := s.expr(n.Left) b := s.expr(n.Right) return s.curBlock.NewValue2(ssa.OpSub, a.Type, nil, a, b) + case OADDR: + return s.addr(n.Left) + case OIND: p := s.expr(n.Left) - c := s.curBlock.NewValue1(ssa.OpIsNonNil, ssa.TypeBool, nil, p) - b := s.endBlock() - b.Kind = ssa.BlockIf - b.Control = c - bNext := s.f.NewBlock(ssa.BlockPlain) - addEdge(b, bNext) - addEdge(b, s.exit) - s.startBlock(bNext) - // TODO(khr): if ptr check fails, don't go directly to exit. - // Instead, go to a call to panicnil or something. - // TODO: implicit nil checks somehow? - + s.nilCheck(p) return s.curBlock.NewValue2(ssa.OpLoad, n.Type, nil, p, s.mem()) + case ODOTPTR: p := s.expr(n.Left) - // TODO: nilcheck - p = s.curBlock.NewValue2(ssa.OpAdd, p.Type, nil, p, s.f.ConstInt(s.config.UIntPtr, n.Xoffset)) + s.nilCheck(p) + p = s.curBlock.NewValue2(ssa.OpAdd, p.Type, nil, p, s.f.ConstInt(s.config.Uintptr, n.Xoffset)) return s.curBlock.NewValue2(ssa.OpLoad, n.Type, nil, p, s.mem()) case OINDEX: - // TODO: slice vs array? Map index is already reduced to a function call - a := s.expr(n.Left) - i := s.expr(n.Right) - // convert index to full width - // TODO: if index is 64-bit and we're compiling to 32-bit, check that high - // 32 bits are zero (and use a low32 op instead of convnop here). - i = s.curBlock.NewValue1(ssa.OpConvNop, s.config.UIntPtr, nil, i) - - // bounds check - len := s.curBlock.NewValue1(ssa.OpSliceLen, s.config.UIntPtr, nil, a) - cmp := s.curBlock.NewValue2(ssa.OpIsInBounds, ssa.TypeBool, nil, i, len) - b := s.endBlock() - b.Kind = ssa.BlockIf - b.Control = cmp - bNext := s.f.NewBlock(ssa.BlockPlain) - addEdge(b, bNext) - addEdge(b, s.exit) - s.startBlock(bNext) - // TODO: don't go directly to s.exit. Go to a stub that calls panicindex first. - - return s.curBlock.NewValue3(ssa.OpSliceIndex, n.Left.Type.Type, nil, a, i, s.mem()) + if n.Left.Type.Bound >= 0 { // array + a := s.expr(n.Left) + i := s.expr(n.Right) + s.boundsCheck(i, s.f.ConstInt(s.config.Uintptr, n.Left.Type.Bound)) + return s.curBlock.NewValue2(ssa.OpArrayIndex, n.Left.Type.Type, nil, a, i) + } else { // slice + p := s.addr(n) + return s.curBlock.NewValue2(ssa.OpLoad, n.Left.Type.Type, nil, p, s.mem()) + } case OCALLFUNC: // run all argument assignments @@ -359,7 +335,7 @@ func (s *ssaState) expr(n *Node) *ssa.Value { s.startBlock(bNext) var titer Iter fp := Structfirst(&titer, Getoutarg(n.Left.Type)) - a := s.f.Entry.NewValue(ssa.OpSPAddr, Ptrto(fp.Type), fp.Width) + a := s.f.Entry.NewValue1(ssa.OpOffPtr, Ptrto(fp.Type), fp.Width, s.sp) return s.curBlock.NewValue2(ssa.OpLoad, fp.Type, nil, a, call) default: log.Fatalf("unhandled expr %s", opnames[n.Op]) @@ -367,8 +343,81 @@ func (s *ssaState) expr(n *Node) *ssa.Value { } } +// expr converts the address of the expression n to SSA, adds it to s and returns the SSA result. +func (s *state) addr(n *Node) *ssa.Value { + switch n.Op { + case ONAME: + if n.Class == PEXTERN { + // global variable + return s.f.Entry.NewValue(ssa.OpGlobal, Ptrto(n.Type), n.Sym) + } + if n.Class == PPARAMOUT { + // store to parameter slot + return s.f.Entry.NewValue1(ssa.OpOffPtr, Ptrto(n.Type), n.Xoffset, s.fp) + } + // TODO: address of locals + log.Fatalf("variable address of %v not implemented", n) + return nil + case OINDREG: + // indirect off a register (TODO: always SP?) + // used for storing/loading arguments/returns to/from callees + return s.f.Entry.NewValue1(ssa.OpOffPtr, Ptrto(n.Type), n.Xoffset, s.sp) + case OINDEX: + if n.Left.Type.Bound >= 0 { // array + a := s.addr(n.Left) + i := s.expr(n.Right) + len := s.f.ConstInt(s.config.Uintptr, n.Left.Type.Bound) + s.boundsCheck(i, len) + return s.curBlock.NewValue2(ssa.OpPtrIndex, Ptrto(n.Left.Type.Type), nil, a, i) + } else { // slice + a := s.expr(n.Left) + i := s.expr(n.Right) + len := s.curBlock.NewValue1(ssa.OpSliceLen, s.config.Uintptr, nil, a) + s.boundsCheck(i, len) + p := s.curBlock.NewValue1(ssa.OpSlicePtr, Ptrto(n.Left.Type.Type), nil, a) + return s.curBlock.NewValue2(ssa.OpPtrIndex, Ptrto(n.Left.Type.Type), nil, p, i) + } + default: + log.Fatalf("addr: bad op %v", n.Op) + return nil + } +} + +// nilCheck generates nil pointer checking code. +// Starts a new block on return. +func (s *state) nilCheck(ptr *ssa.Value) { + c := s.curBlock.NewValue1(ssa.OpIsNonNil, ssa.TypeBool, nil, ptr) + b := s.endBlock() + b.Kind = ssa.BlockIf + b.Control = c + bNext := s.f.NewBlock(ssa.BlockPlain) + addEdge(b, bNext) + addEdge(b, s.exit) + s.startBlock(bNext) + // TODO(khr): Don't go directly to exit. Go to a stub that calls panicmem first. + // TODO: implicit nil checks somehow? +} + +// boundsCheck generates bounds checking code. Checks if 0 <= idx < len, branches to exit if not. +// Starts a new block on return. +func (s *state) boundsCheck(idx, len *ssa.Value) { + // TODO: convert index to full width? + // TODO: if index is 64-bit and we're compiling to 32-bit, check that high 32 bits are zero. + + // bounds check + cmp := s.curBlock.NewValue2(ssa.OpIsInBounds, ssa.TypeBool, nil, idx, len) + b := s.endBlock() + b.Kind = ssa.BlockIf + b.Control = cmp + bNext := s.f.NewBlock(ssa.BlockPlain) + addEdge(b, bNext) + addEdge(b, s.exit) + // TODO: don't go directly to s.exit. Go to a stub that calls panicindex first. + s.startBlock(bNext) +} + // variable returns the value of a variable at the current location. -func (s *ssaState) variable(name string, t ssa.Type) *ssa.Value { +func (s *state) variable(name string, t ssa.Type) *ssa.Value { if s.curBlock == nil { log.Fatalf("nil curblock!") } @@ -381,11 +430,11 @@ func (s *ssaState) variable(name string, t ssa.Type) *ssa.Value { return v } -func (s *ssaState) mem() *ssa.Value { +func (s *state) mem() *ssa.Value { return s.variable(".mem", ssa.TypeMem) } -func (s *ssaState) linkForwardReferences() { +func (s *state) linkForwardReferences() { // Build ssa graph. Each variable on its first use in a basic block // leaves a FwdRef in that block representing the incoming value // of that variable. This function links that ref up with possible definitions, @@ -406,17 +455,16 @@ func (s *ssaState) linkForwardReferences() { } // lookupVarIncoming finds the variable's value at the start of block b. -func (s *ssaState) lookupVarIncoming(b *ssa.Block, t ssa.Type, name string) *ssa.Value { +func (s *state) lookupVarIncoming(b *ssa.Block, t ssa.Type, name string) *ssa.Value { // TODO(khr): have lookupVarIncoming overwrite the fwdRef or copy it // will be used in, instead of having the result used in a copy value. if b == s.f.Entry { if name == ".mem" { - return b.NewValue(ssa.OpArg, t, name) + return s.startmem } // variable is live at the entry block. Load it. - a := s.f.Entry.NewValue(ssa.OpFPAddr, Ptrto(t.(*Type)), s.argOffsets[name]) - m := b.NewValue(ssa.OpArg, ssa.TypeMem, ".mem") // TODO: reuse mem starting value - return b.NewValue2(ssa.OpLoad, t, nil, a, m) + addr := s.f.Entry.NewValue1(ssa.OpOffPtr, Ptrto(t.(*Type)), s.argOffsets[name], s.fp) + return b.NewValue2(ssa.OpLoad, t, nil, addr, s.startmem) } var vals []*ssa.Value for _, p := range b.Preds { @@ -435,7 +483,7 @@ func (s *ssaState) lookupVarIncoming(b *ssa.Block, t ssa.Type, name string) *ssa } // lookupVarOutgoing finds the variable's value at the end of block b. -func (s *ssaState) lookupVarOutgoing(b *ssa.Block, t ssa.Type, name string) *ssa.Value { +func (s *state) lookupVarOutgoing(b *ssa.Block, t ssa.Type, name string) *ssa.Value { m := s.defvars[b.ID] if v, ok := m[name]; ok { return v @@ -568,13 +616,23 @@ func genValue(v *ssa.Value, frameSize int64) { p.To.Type = obj.TYPE_REG p.To.Reg = r case ssa.OpCMPQ: - x := regnum(v.Args[0]) - y := regnum(v.Args[1]) p := Prog(x86.ACMPQ) p.From.Type = obj.TYPE_REG - p.From.Reg = x + p.From.Reg = regnum(v.Args[0]) + p.To.Type = obj.TYPE_REG + p.To.Reg = regnum(v.Args[1]) + case ssa.OpCMPCQ: + p := Prog(x86.ACMPQ) + p.From.Type = obj.TYPE_REG + p.From.Reg = regnum(v.Args[0]) + p.To.Type = obj.TYPE_CONST + p.To.Offset = v.Aux.(int64) + case ssa.OpTESTB: + p := Prog(x86.ATESTB) + p.From.Type = obj.TYPE_REG + p.From.Reg = regnum(v.Args[0]) p.To.Type = obj.TYPE_REG - p.To.Reg = y + p.To.Reg = regnum(v.Args[1]) case ssa.OpMOVQconst: x := regnum(v) p := Prog(x86.AMOVQ) @@ -582,22 +640,57 @@ func genValue(v *ssa.Value, frameSize int64) { p.From.Offset = v.Aux.(int64) p.To.Type = obj.TYPE_REG p.To.Reg = x - case ssa.OpMOVQloadFP: - x := regnum(v) + case ssa.OpMOVQload: p := Prog(x86.AMOVQ) p.From.Type = obj.TYPE_MEM - p.From.Reg = x86.REG_SP - p.From.Offset = v.Aux.(int64) + frameSize + if v.Block.Func.RegAlloc[v.Args[0].ID].Name() == "FP" { + // TODO: do the fp/sp adjustment somewhere else? + p.From.Reg = x86.REG_SP + p.From.Offset = v.Aux.(int64) + frameSize + } else { + p.From.Reg = regnum(v.Args[0]) + p.From.Offset = v.Aux.(int64) + } p.To.Type = obj.TYPE_REG - p.To.Reg = x - case ssa.OpMOVQstoreFP: - x := regnum(v.Args[0]) + p.To.Reg = regnum(v) + case ssa.OpMOVBload: + p := Prog(x86.AMOVB) + p.From.Type = obj.TYPE_MEM + if v.Block.Func.RegAlloc[v.Args[0].ID].Name() == "FP" { + p.From.Reg = x86.REG_SP + p.From.Offset = v.Aux.(int64) + frameSize + } else { + p.From.Reg = regnum(v.Args[0]) + p.From.Offset = v.Aux.(int64) + } + p.To.Type = obj.TYPE_REG + p.To.Reg = regnum(v) + case ssa.OpMOVQloadidx8: + p := Prog(x86.AMOVQ) + p.From.Type = obj.TYPE_MEM + if v.Block.Func.RegAlloc[v.Args[0].ID].Name() == "FP" { + p.From.Reg = x86.REG_SP + p.From.Offset = v.Aux.(int64) + frameSize + } else { + p.From.Reg = regnum(v.Args[0]) + p.From.Offset = v.Aux.(int64) + } + p.From.Scale = 8 + p.From.Index = regnum(v.Args[1]) + p.To.Type = obj.TYPE_REG + p.To.Reg = regnum(v) + case ssa.OpMOVQstore: p := Prog(x86.AMOVQ) p.From.Type = obj.TYPE_REG - p.From.Reg = x + p.From.Reg = regnum(v.Args[1]) p.To.Type = obj.TYPE_MEM - p.To.Reg = x86.REG_SP - p.To.Offset = v.Aux.(int64) + frameSize + if v.Block.Func.RegAlloc[v.Args[0].ID].Name() == "FP" { + p.To.Reg = x86.REG_SP + p.To.Offset = v.Aux.(int64) + frameSize + } else { + p.To.Reg = regnum(v.Args[0]) + p.To.Offset = v.Aux.(int64) + } case ssa.OpCopy: x := regnum(v.Args[0]) y := regnum(v) @@ -638,8 +731,19 @@ func genValue(v *ssa.Value, frameSize int64) { case ssa.OpArg: // memory arg needs no code // TODO: only mem arg goes here. + case ssa.OpLEAQglobal: + g := v.Aux.(ssa.GlobalOffset) + p := Prog(x86.ALEAQ) + p.From.Type = obj.TYPE_MEM + p.From.Name = obj.NAME_EXTERN + p.From.Sym = Linksym(g.Global.(*Sym)) + p.From.Offset = g.Offset + p.To.Type = obj.TYPE_REG + p.To.Reg = regnum(v) + case ssa.OpFP, ssa.OpSP: + // nothing to do default: - log.Fatalf("value %v not implemented yet", v) + log.Fatalf("value %s not implemented yet", v.LongString()) } } @@ -653,6 +757,40 @@ func genBlock(b, next *ssa.Block, branches []branch) []branch { } case ssa.BlockExit: Prog(obj.ARET) + case ssa.BlockEQ: + if b.Succs[0] == next { + p := Prog(x86.AJNE) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[1]}) + } else if b.Succs[1] == next { + p := Prog(x86.AJEQ) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[0]}) + } else { + p := Prog(x86.AJEQ) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[0]}) + q := Prog(obj.AJMP) + q.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{q, b.Succs[1]}) + } + case ssa.BlockNE: + if b.Succs[0] == next { + p := Prog(x86.AJEQ) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[1]}) + } else if b.Succs[1] == next { + p := Prog(x86.AJNE) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[0]}) + } else { + p := Prog(x86.AJNE) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[0]}) + q := Prog(obj.AJMP) + q.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{q, b.Succs[1]}) + } case ssa.BlockLT: if b.Succs[0] == next { p := Prog(x86.AJGE) @@ -670,8 +808,43 @@ func genBlock(b, next *ssa.Block, branches []branch) []branch { q.To.Type = obj.TYPE_BRANCH branches = append(branches, branch{q, b.Succs[1]}) } + case ssa.BlockULT: + if b.Succs[0] == next { + p := Prog(x86.AJCC) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[1]}) + } else if b.Succs[1] == next { + p := Prog(x86.AJCS) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[0]}) + } else { + p := Prog(x86.AJCS) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[0]}) + q := Prog(obj.AJMP) + q.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{q, b.Succs[1]}) + } + case ssa.BlockUGT: + if b.Succs[0] == next { + p := Prog(x86.AJLS) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[1]}) + } else if b.Succs[1] == next { + p := Prog(x86.AJHI) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[0]}) + } else { + p := Prog(x86.AJHI) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[0]}) + q := Prog(obj.AJMP) + q.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{q, b.Succs[1]}) + } + default: - log.Fatalf("branch at %v not implemented yet", b) + log.Fatalf("branch %s not implemented yet", b.LongString()) } return branches } diff --git a/src/cmd/internal/obj/x86/6.out.go b/src/cmd/internal/obj/x86/6.out.go index c7f46e1801..e36cb9e7a3 100644 --- a/src/cmd/internal/obj/x86/6.out.go +++ b/src/cmd/internal/obj/x86/6.out.go @@ -110,23 +110,23 @@ const ( AINTO AIRETL AIRETW - AJCC - AJCS + AJCC // >= unsigned + AJCS // < unsigned AJCXZL - AJEQ - AJGE - AJGT - AJHI - AJLE - AJLS - AJLT - AJMI - AJNE - AJOC - AJOS - AJPC - AJPL - AJPS + AJEQ // == (zero) + AJGE // >= signed + AJGT // > signed + AJHI // > unsigned + AJLE // <= signed + AJLS // <= unsigned + AJLT // < signed + AJMI // sign bit set (negative) + AJNE // != (nonzero) + AJOC // overflow clear + AJOS // overflow set + AJPC // parity clear + AJPL // sign bit clear (positive) + AJPS // parity set ALAHF ALARL ALARW diff --git a/src/cmd/internal/ssa/check.go b/src/cmd/internal/ssa/check.go index 453388a899..667313ad9f 100644 --- a/src/cmd/internal/ssa/check.go +++ b/src/cmd/internal/ssa/check.go @@ -58,7 +58,7 @@ func checkFunc(f *Func) { if b.Control == nil { log.Panicf("exit block %s has no control value", b) } - if b.Control.Type != TypeMem { + if !b.Control.Type.IsMemory() { log.Panicf("exit block %s has non-memory control value %s", b, b.Control.LongString()) } case BlockPlain: @@ -75,7 +75,7 @@ func checkFunc(f *Func) { if b.Control == nil { log.Panicf("if block %s has no control value", b) } - if b.Control.Type != TypeBool { + if !b.Control.Type.IsBoolean() { log.Panicf("if block %s has non-bool control value %s", b, b.Control.LongString()) } case BlockCall: @@ -85,7 +85,7 @@ func checkFunc(f *Func) { if b.Control == nil { log.Panicf("call block %s has no control value", b) } - if b.Control.Type != TypeMem { + if !b.Control.Type.IsMemory() { log.Panicf("call block %s has non-memory control value %s", b, b.Control.LongString()) } if b.Succs[1].Kind != BlockExit { diff --git a/src/cmd/internal/ssa/config.go b/src/cmd/internal/ssa/config.go index 80acda4b23..9f1d2a8593 100644 --- a/src/cmd/internal/ssa/config.go +++ b/src/cmd/internal/ssa/config.go @@ -9,7 +9,7 @@ import "log" type Config struct { arch string // "amd64", etc. ptrSize int64 // 4 or 8 - UIntPtr Type // pointer arithmetic type + Uintptr Type // pointer arithmetic type lower func(*Value) bool // lowering function // TODO: more stuff. Compiler flags of interest, ... @@ -30,9 +30,9 @@ func NewConfig(arch string) *Config { } // cache the intptr type in the config - c.UIntPtr = TypeUInt32 + c.Uintptr = TypeUInt32 if c.ptrSize == 8 { - c.UIntPtr = TypeUInt64 + c.Uintptr = TypeUInt64 } return c diff --git a/src/cmd/internal/ssa/generic.go b/src/cmd/internal/ssa/generic.go index 2a96793c61..91f9c17d11 100644 --- a/src/cmd/internal/ssa/generic.go +++ b/src/cmd/internal/ssa/generic.go @@ -6,20 +6,20 @@ func genericRules(v *Value) bool { switch v.Op { case OpAdd: // match: (Add (Const [c]) (Const [d])) - // cond: is64BitInt(t) && isSigned(t) + // cond: is64BitInt(t) // result: (Const [{c.(int64)+d.(int64)}]) { t := v.Type if v.Args[0].Op != OpConst { - goto endc86f5c160a87f6f5ec90b6551ec099d9 + goto end8d047ed0ae9537b840adc79ea82c6e05 } c := v.Args[0].Aux if v.Args[1].Op != OpConst { - goto endc86f5c160a87f6f5ec90b6551ec099d9 + goto end8d047ed0ae9537b840adc79ea82c6e05 } d := v.Args[1].Aux - if !(is64BitInt(t) && isSigned(t)) { - goto endc86f5c160a87f6f5ec90b6551ec099d9 + if !(is64BitInt(t)) { + goto end8d047ed0ae9537b840adc79ea82c6e05 } v.Op = OpConst v.Aux = nil @@ -27,100 +27,141 @@ func genericRules(v *Value) bool { v.Aux = c.(int64) + d.(int64) return true } - goto endc86f5c160a87f6f5ec90b6551ec099d9 - endc86f5c160a87f6f5ec90b6551ec099d9: + goto end8d047ed0ae9537b840adc79ea82c6e05 + end8d047ed0ae9537b840adc79ea82c6e05: ; - // match: (Add (Const [c]) (Const [d])) - // cond: is64BitInt(t) && !isSigned(t) - // result: (Const [{c.(uint64)+d.(uint64)}]) + case OpArrayIndex: + // match: (ArrayIndex (Load ptr mem) idx) + // cond: + // result: (Load (PtrIndex ptr idx) mem) + { + if v.Args[0].Op != OpLoad { + goto end3809f4c52270a76313e4ea26e6f0b753 + } + ptr := v.Args[0].Args[0] + mem := v.Args[0].Args[1] + idx := v.Args[1] + v.Op = OpLoad + v.Aux = nil + v.resetArgs() + v0 := v.Block.NewValue(OpPtrIndex, TypeInvalid, nil) + v0.Type = ptr.Type.Elem().Elem().PtrTo() + v0.AddArg(ptr) + v0.AddArg(idx) + v.AddArg(v0) + v.AddArg(mem) + return true + } + goto end3809f4c52270a76313e4ea26e6f0b753 + end3809f4c52270a76313e4ea26e6f0b753: + ; + case OpIsInBounds: + // match: (IsInBounds (Const [c]) (Const [d])) + // cond: + // result: (Const [inBounds(c.(int64),d.(int64))]) + { + if v.Args[0].Op != OpConst { + goto enddbd1a394d9b71ee64335361b8384865c + } + c := v.Args[0].Aux + if v.Args[1].Op != OpConst { + goto enddbd1a394d9b71ee64335361b8384865c + } + d := v.Args[1].Aux + v.Op = OpConst + v.Aux = nil + v.resetArgs() + v.Aux = inBounds(c.(int64), d.(int64)) + return true + } + goto enddbd1a394d9b71ee64335361b8384865c + enddbd1a394d9b71ee64335361b8384865c: + ; + case OpMul: + // match: (Mul (Const [c]) (Const [d])) + // cond: is64BitInt(t) + // result: (Const [{c.(int64)*d.(int64)}]) { t := v.Type if v.Args[0].Op != OpConst { - goto end8941c2a515c1bd38530b7fd96862bac4 + goto end776610f88cf04f438242d76ed2b14f1c } c := v.Args[0].Aux if v.Args[1].Op != OpConst { - goto end8941c2a515c1bd38530b7fd96862bac4 + goto end776610f88cf04f438242d76ed2b14f1c } d := v.Args[1].Aux - if !(is64BitInt(t) && !isSigned(t)) { - goto end8941c2a515c1bd38530b7fd96862bac4 + if !(is64BitInt(t)) { + goto end776610f88cf04f438242d76ed2b14f1c } v.Op = OpConst v.Aux = nil v.resetArgs() - v.Aux = c.(uint64) + d.(uint64) + v.Aux = c.(int64) * d.(int64) return true } - goto end8941c2a515c1bd38530b7fd96862bac4 - end8941c2a515c1bd38530b7fd96862bac4: + goto end776610f88cf04f438242d76ed2b14f1c + end776610f88cf04f438242d76ed2b14f1c: ; - case OpSliceCap: - // match: (SliceCap (Load ptr mem)) + case OpPtrIndex: + // match: (PtrIndex ptr idx) // cond: - // result: (Load (Add ptr (Const [int64(v.Block.Func.Config.ptrSize*2)])) mem) + // result: (Add ptr (Mul idx (Const [t.Elem().Size()]))) { - if v.Args[0].Op != OpLoad { - goto ende03f9b79848867df439b56889bb4e55d - } - ptr := v.Args[0].Args[0] - mem := v.Args[0].Args[1] - v.Op = OpLoad + t := v.Type + ptr := v.Args[0] + idx := v.Args[1] + v.Op = OpAdd v.Aux = nil v.resetArgs() - v0 := v.Block.NewValue(OpAdd, TypeInvalid, nil) - v0.Type = ptr.Type - v0.AddArg(ptr) + v.AddArg(ptr) + v0 := v.Block.NewValue(OpMul, TypeInvalid, nil) + v0.Type = v.Block.Func.Config.Uintptr + v0.AddArg(idx) v1 := v.Block.NewValue(OpConst, TypeInvalid, nil) - v1.Type = v.Block.Func.Config.UIntPtr - v1.Aux = int64(v.Block.Func.Config.ptrSize * 2) + v1.Type = v.Block.Func.Config.Uintptr + v1.Aux = t.Elem().Size() v0.AddArg(v1) v.AddArg(v0) - v.AddArg(mem) return true } - goto ende03f9b79848867df439b56889bb4e55d - ende03f9b79848867df439b56889bb4e55d: + goto end383c68c41e72d22ef00c4b7b0fddcbb8 + end383c68c41e72d22ef00c4b7b0fddcbb8: ; - case OpSliceIndex: - // match: (SliceIndex s i mem) + case OpSliceCap: + // match: (SliceCap (Load ptr mem)) // cond: - // result: (Load (Add (SlicePtr s) (Mul i (Const [s.Type.Elem().Size()]))) mem) + // result: (Load (Add ptr (Const [int64(v.Block.Func.Config.ptrSize*2)])) mem) { - s := v.Args[0] - i := v.Args[1] - mem := v.Args[2] + if v.Args[0].Op != OpLoad { + goto endbf1d4db93c4664ed43be3f73afb4dfa3 + } + ptr := v.Args[0].Args[0] + mem := v.Args[0].Args[1] v.Op = OpLoad v.Aux = nil v.resetArgs() v0 := v.Block.NewValue(OpAdd, TypeInvalid, nil) - v0.Type = s.Type.Elem().PtrTo() - v1 := v.Block.NewValue(OpSlicePtr, TypeInvalid, nil) - v1.Type = s.Type.Elem().PtrTo() - v1.AddArg(s) + v0.Type = ptr.Type + v0.AddArg(ptr) + v1 := v.Block.NewValue(OpConst, TypeInvalid, nil) + v1.Type = v.Block.Func.Config.Uintptr + v1.Aux = int64(v.Block.Func.Config.ptrSize * 2) v0.AddArg(v1) - v2 := v.Block.NewValue(OpMul, TypeInvalid, nil) - v2.Type = v.Block.Func.Config.UIntPtr - v2.AddArg(i) - v3 := v.Block.NewValue(OpConst, TypeInvalid, nil) - v3.Type = v.Block.Func.Config.UIntPtr - v3.Aux = s.Type.Elem().Size() - v2.AddArg(v3) - v0.AddArg(v2) v.AddArg(v0) v.AddArg(mem) return true } - goto end733704831a61760840348f790b3ab045 - end733704831a61760840348f790b3ab045: + goto endbf1d4db93c4664ed43be3f73afb4dfa3 + endbf1d4db93c4664ed43be3f73afb4dfa3: ; case OpSliceLen: // match: (SliceLen (Load ptr mem)) // cond: - // result: (Load (Add ptr (Const [int64(v.Block.Func.Config.ptrSize)])) mem) + // result: (Load (Add ptr (Const [int64(v.Block.Func.Config.ptrSize)])) mem) { if v.Args[0].Op != OpLoad { - goto ende94950a57eca1871c93afdeaadb90223 + goto end9190b1ecbda4c5dd6d3e05d2495fb297 } ptr := v.Args[0].Args[0] mem := v.Args[0].Args[1] @@ -131,15 +172,15 @@ func genericRules(v *Value) bool { v0.Type = ptr.Type v0.AddArg(ptr) v1 := v.Block.NewValue(OpConst, TypeInvalid, nil) - v1.Type = v.Block.Func.Config.UIntPtr + v1.Type = v.Block.Func.Config.Uintptr v1.Aux = int64(v.Block.Func.Config.ptrSize) v0.AddArg(v1) v.AddArg(v0) v.AddArg(mem) return true } - goto ende94950a57eca1871c93afdeaadb90223 - ende94950a57eca1871c93afdeaadb90223: + goto end9190b1ecbda4c5dd6d3e05d2495fb297 + end9190b1ecbda4c5dd6d3e05d2495fb297: ; case OpSlicePtr: // match: (SlicePtr (Load ptr mem)) @@ -160,6 +201,36 @@ func genericRules(v *Value) bool { } goto end459613b83f95b65729d45c2ed663a153 end459613b83f95b65729d45c2ed663a153: + ; + case OpStore: + // match: (Store dst (Load src mem) mem) + // cond: t.Size() > 8 + // result: (Move [t.Size()] dst src mem) + { + dst := v.Args[0] + if v.Args[1].Op != OpLoad { + goto end324ffb6d2771808da4267f62c854e9c8 + } + t := v.Args[1].Type + src := v.Args[1].Args[0] + mem := v.Args[1].Args[1] + if v.Args[2] != v.Args[1].Args[1] { + goto end324ffb6d2771808da4267f62c854e9c8 + } + if !(t.Size() > 8) { + goto end324ffb6d2771808da4267f62c854e9c8 + } + v.Op = OpMove + v.Aux = nil + v.resetArgs() + v.Aux = t.Size() + v.AddArg(dst) + v.AddArg(src) + v.AddArg(mem) + return true + } + goto end324ffb6d2771808da4267f62c854e9c8 + end324ffb6d2771808da4267f62c854e9c8: } return false } diff --git a/src/cmd/internal/ssa/lower.go b/src/cmd/internal/ssa/lower.go index 82e5d23241..84379c00de 100644 --- a/src/cmd/internal/ssa/lower.go +++ b/src/cmd/internal/ssa/lower.go @@ -16,41 +16,88 @@ func lower(f *Func) { // additional pass for 386/amd64, link condition codes directly to blocks // TODO: do generically somehow? Special "block" rewrite rules? for _, b := range f.Blocks { - switch b.Kind { - case BlockIf: - switch b.Control.Op { - case OpSETL: - b.Kind = BlockLT - b.Control = b.Control.Args[0] - case OpSETNE: - b.Kind = BlockNE - b.Control = b.Control.Args[0] - case OpSETB: - b.Kind = BlockULT - b.Control = b.Control.Args[0] - // TODO: others + for { + switch b.Kind { + case BlockIf: + switch b.Control.Op { + case OpSETL: + b.Kind = BlockLT + b.Control = b.Control.Args[0] + continue + case OpSETNE: + b.Kind = BlockNE + b.Control = b.Control.Args[0] + continue + case OpSETB: + b.Kind = BlockULT + b.Control = b.Control.Args[0] + continue + case OpMOVBload: + b.Kind = BlockNE + b.Control = b.NewValue2(OpTESTB, TypeFlags, nil, b.Control, b.Control) + continue + // TODO: others + } + case BlockLT: + if b.Control.Op == OpInvertFlags { + b.Kind = BlockGT + b.Control = b.Control.Args[0] + continue + } + case BlockGT: + if b.Control.Op == OpInvertFlags { + b.Kind = BlockLT + b.Control = b.Control.Args[0] + continue + } + case BlockLE: + if b.Control.Op == OpInvertFlags { + b.Kind = BlockGE + b.Control = b.Control.Args[0] + continue + } + case BlockGE: + if b.Control.Op == OpInvertFlags { + b.Kind = BlockLE + b.Control = b.Control.Args[0] + continue + } + case BlockULT: + if b.Control.Op == OpInvertFlags { + b.Kind = BlockUGT + b.Control = b.Control.Args[0] + continue + } + case BlockUGT: + if b.Control.Op == OpInvertFlags { + b.Kind = BlockULT + b.Control = b.Control.Args[0] + continue + } + case BlockULE: + if b.Control.Op == OpInvertFlags { + b.Kind = BlockUGE + b.Control = b.Control.Args[0] + continue + } + case BlockUGE: + if b.Control.Op == OpInvertFlags { + b.Kind = BlockULE + b.Control = b.Control.Args[0] + continue + } + case BlockEQ: + if b.Control.Op == OpInvertFlags { + b.Control = b.Control.Args[0] + continue + } + case BlockNE: + if b.Control.Op == OpInvertFlags { + b.Control = b.Control.Args[0] + continue + } } - case BlockLT: - if b.Control.Op == OpInvertFlags { - b.Kind = BlockGE - b.Control = b.Control.Args[0] - } - case BlockULT: - if b.Control.Op == OpInvertFlags { - b.Kind = BlockUGE - b.Control = b.Control.Args[0] - } - case BlockEQ: - if b.Control.Op == OpInvertFlags { - b.Kind = BlockNE - b.Control = b.Control.Args[0] - } - case BlockNE: - if b.Control.Op == OpInvertFlags { - b.Kind = BlockEQ - b.Control = b.Control.Args[0] - } - // TODO: others + break } } } diff --git a/src/cmd/internal/ssa/lowerAmd64.go b/src/cmd/internal/ssa/lowerAmd64.go index ef891c37d9..356f646dcc 100644 --- a/src/cmd/internal/ssa/lowerAmd64.go +++ b/src/cmd/internal/ssa/lowerAmd64.go @@ -7,11 +7,11 @@ func lowerAmd64(v *Value) bool { case OpADDCQ: // match: (ADDCQ [c] (LEAQ8 [d] x y)) // cond: - // result: (LEAQ8 [c.(int64)+d.(int64)] x y) + // result: (LEAQ8 [addOff(c, d)] x y) { c := v.Aux if v.Args[0].Op != OpLEAQ8 { - goto end16348939e556e99e8447227ecb986f01 + goto end3bc1457811adc0cb81ad6b88a7461c60 } d := v.Args[0].Aux x := v.Args[0].Args[0] @@ -19,58 +19,40 @@ func lowerAmd64(v *Value) bool { v.Op = OpLEAQ8 v.Aux = nil v.resetArgs() - v.Aux = c.(int64) + d.(int64) + v.Aux = addOff(c, d) v.AddArg(x) v.AddArg(y) return true } - goto end16348939e556e99e8447227ecb986f01 - end16348939e556e99e8447227ecb986f01: + goto end3bc1457811adc0cb81ad6b88a7461c60 + end3bc1457811adc0cb81ad6b88a7461c60: ; - // match: (ADDCQ [off1] (FPAddr [off2])) - // cond: - // result: (FPAddr [off1.(int64)+off2.(int64)]) - { - off1 := v.Aux - if v.Args[0].Op != OpFPAddr { - goto end28e093ab0618066e6b2609db7aaf309b - } - off2 := v.Args[0].Aux - v.Op = OpFPAddr - v.Aux = nil - v.resetArgs() - v.Aux = off1.(int64) + off2.(int64) - return true - } - goto end28e093ab0618066e6b2609db7aaf309b - end28e093ab0618066e6b2609db7aaf309b: - ; - // match: (ADDCQ [off1] (SPAddr [off2])) - // cond: - // result: (SPAddr [off1.(int64)+off2.(int64)]) + // match: (ADDCQ [off] x) + // cond: off.(int64) == 0 + // result: (Copy x) { - off1 := v.Aux - if v.Args[0].Op != OpSPAddr { - goto endd0c27c62d150b88168075c5ba113d1fa + off := v.Aux + x := v.Args[0] + if !(off.(int64) == 0) { + goto end6710a6679c47b70577ecea7ad00dae87 } - off2 := v.Args[0].Aux - v.Op = OpSPAddr + v.Op = OpCopy v.Aux = nil v.resetArgs() - v.Aux = off1.(int64) + off2.(int64) + v.AddArg(x) return true } - goto endd0c27c62d150b88168075c5ba113d1fa - endd0c27c62d150b88168075c5ba113d1fa: + goto end6710a6679c47b70577ecea7ad00dae87 + end6710a6679c47b70577ecea7ad00dae87: ; case OpADDQ: - // match: (ADDQ x (Const [c])) + // match: (ADDQ x (MOVQconst [c])) // cond: // result: (ADDCQ [c] x) { x := v.Args[0] - if v.Args[1].Op != OpConst { - goto endef6908cfdf56e102cc327a3ddc14393d + if v.Args[1].Op != OpMOVQconst { + goto end39b79e84f20a6d44b5c4136aae220ac2 } c := v.Args[1].Aux v.Op = OpADDCQ @@ -80,15 +62,15 @@ func lowerAmd64(v *Value) bool { v.AddArg(x) return true } - goto endef6908cfdf56e102cc327a3ddc14393d - endef6908cfdf56e102cc327a3ddc14393d: + goto end39b79e84f20a6d44b5c4136aae220ac2 + end39b79e84f20a6d44b5c4136aae220ac2: ; - // match: (ADDQ (Const [c]) x) + // match: (ADDQ (MOVQconst [c]) x) // cond: // result: (ADDCQ [c] x) { - if v.Args[0].Op != OpConst { - goto endb54a32cf3147f424f08b46db62c69b23 + if v.Args[0].Op != OpMOVQconst { + goto endc05ff5a2a132241b69d00c852001d820 } c := v.Args[0].Aux x := v.Args[1] @@ -99,8 +81,8 @@ func lowerAmd64(v *Value) bool { v.AddArg(x) return true } - goto endb54a32cf3147f424f08b46db62c69b23 - endb54a32cf3147f424f08b46db62c69b23: + goto endc05ff5a2a132241b69d00c852001d820 + endc05ff5a2a132241b69d00c852001d820: ; // match: (ADDQ x (SHLCQ [shift] y)) // cond: shift.(int64) == 3 @@ -168,13 +150,13 @@ func lowerAmd64(v *Value) bool { end35a02a1587264e40cf1055856ff8445a: ; case OpCMPQ: - // match: (CMPQ x (Const [c])) + // match: (CMPQ x (MOVQconst [c])) // cond: // result: (CMPCQ x [c]) { x := v.Args[0] - if v.Args[1].Op != OpConst { - goto end1770a40e4253d9f669559a360514613e + if v.Args[1].Op != OpMOVQconst { + goto endf180bae15b3d24c0213520d7f7aa98b4 } c := v.Args[1].Aux v.Op = OpCMPCQ @@ -184,15 +166,15 @@ func lowerAmd64(v *Value) bool { v.Aux = c return true } - goto end1770a40e4253d9f669559a360514613e - end1770a40e4253d9f669559a360514613e: + goto endf180bae15b3d24c0213520d7f7aa98b4 + endf180bae15b3d24c0213520d7f7aa98b4: ; - // match: (CMPQ (Const [c]) x) + // match: (CMPQ (MOVQconst [c]) x) // cond: // result: (InvertFlags (CMPCQ x [c])) { - if v.Args[0].Op != OpConst { - goto enda4e64c7eaeda16c1c0db9dac409cd126 + if v.Args[0].Op != OpMOVQconst { + goto end8fc58bffa73b3df80b3de72c91844884 } c := v.Args[0].Aux x := v.Args[1] @@ -206,8 +188,42 @@ func lowerAmd64(v *Value) bool { v.AddArg(v0) return true } - goto enda4e64c7eaeda16c1c0db9dac409cd126 - enda4e64c7eaeda16c1c0db9dac409cd126: + goto end8fc58bffa73b3df80b3de72c91844884 + end8fc58bffa73b3df80b3de72c91844884: + ; + case OpConst: + // match: (Const [val]) + // cond: is64BitInt(t) + // result: (MOVQconst [val]) + { + t := v.Type + val := v.Aux + if !(is64BitInt(t)) { + goto end7f5c5b34093fbc6860524cb803ee51bf + } + v.Op = OpMOVQconst + v.Aux = nil + v.resetArgs() + v.Aux = val + return true + } + goto end7f5c5b34093fbc6860524cb803ee51bf + end7f5c5b34093fbc6860524cb803ee51bf: + ; + case OpGlobal: + // match: (Global [sym]) + // cond: + // result: (LEAQglobal [GlobalOffset{sym,0}]) + { + sym := v.Aux + v.Op = OpLEAQglobal + v.Aux = nil + v.resetArgs() + v.Aux = GlobalOffset{sym, 0} + return true + } + goto end3a3c76fac0e2e53c0e1c60b9524e6f1c + end3a3c76fac0e2e53c0e1c60b9524e6f1c: ; case OpIsInBounds: // match: (IsInBounds idx len) @@ -273,16 +289,16 @@ func lowerAmd64(v *Value) bool { ; case OpLoad: // match: (Load ptr mem) - // cond: (is64BitInt(t) || isPtr(t)) - // result: (MOVQload [int64(0)] ptr mem) + // cond: t.IsBoolean() + // result: (MOVBload [int64(0)] ptr mem) { t := v.Type ptr := v.Args[0] mem := v.Args[1] - if !(is64BitInt(t) || isPtr(t)) { - goto end581ce5a20901df1b8143448ba031685b + if !(t.IsBoolean()) { + goto end73f21632e56c3614902d3c29c82dc4ea } - v.Op = OpMOVQload + v.Op = OpMOVBload v.Aux = nil v.resetArgs() v.Aux = int64(0) @@ -290,77 +306,38 @@ func lowerAmd64(v *Value) bool { v.AddArg(mem) return true } - goto end581ce5a20901df1b8143448ba031685b - end581ce5a20901df1b8143448ba031685b: - ; - case OpMOVQload: - // match: (MOVQload [off1] (FPAddr [off2]) mem) - // cond: - // result: (MOVQloadFP [off1.(int64)+off2.(int64)] mem) - { - off1 := v.Aux - if v.Args[0].Op != OpFPAddr { - goto endce972b1aa84b56447978c43def87fa57 - } - off2 := v.Args[0].Aux - mem := v.Args[1] - v.Op = OpMOVQloadFP - v.Aux = nil - v.resetArgs() - v.Aux = off1.(int64) + off2.(int64) - v.AddArg(mem) - return true - } - goto endce972b1aa84b56447978c43def87fa57 - endce972b1aa84b56447978c43def87fa57: + goto end73f21632e56c3614902d3c29c82dc4ea + end73f21632e56c3614902d3c29c82dc4ea: ; - // match: (MOVQload [off1] (SPAddr [off2]) mem) - // cond: - // result: (MOVQloadSP [off1.(int64)+off2.(int64)] mem) + // match: (Load ptr mem) + // cond: (is64BitInt(t) || isPtr(t)) + // result: (MOVQload [int64(0)] ptr mem) { - off1 := v.Aux - if v.Args[0].Op != OpSPAddr { - goto end3d8628a6536350a123be81240b8a1376 - } - off2 := v.Args[0].Aux + t := v.Type + ptr := v.Args[0] mem := v.Args[1] - v.Op = OpMOVQloadSP - v.Aux = nil - v.resetArgs() - v.Aux = off1.(int64) + off2.(int64) - v.AddArg(mem) - return true - } - goto end3d8628a6536350a123be81240b8a1376 - end3d8628a6536350a123be81240b8a1376: - ; - // match: (MOVQload [off] (Global [sym]) mem) - // cond: - // result: (MOVQloadglobal [GlobalOffset{sym,off.(int64)}] mem) - { - off := v.Aux - if v.Args[0].Op != OpGlobal { - goto end20693899317f3f8d1b47fefa64087654 + if !(is64BitInt(t) || isPtr(t)) { + goto end581ce5a20901df1b8143448ba031685b } - sym := v.Args[0].Aux - mem := v.Args[1] - v.Op = OpMOVQloadglobal + v.Op = OpMOVQload v.Aux = nil v.resetArgs() - v.Aux = GlobalOffset{sym, off.(int64)} + v.Aux = int64(0) + v.AddArg(ptr) v.AddArg(mem) return true } - goto end20693899317f3f8d1b47fefa64087654 - end20693899317f3f8d1b47fefa64087654: + goto end581ce5a20901df1b8143448ba031685b + end581ce5a20901df1b8143448ba031685b: ; + case OpMOVQload: // match: (MOVQload [off1] (ADDCQ [off2] ptr) mem) // cond: - // result: (MOVQload [off1.(int64)+off2.(int64)] ptr mem) + // result: (MOVQload [addOff(off1, off2)] ptr mem) { off1 := v.Aux if v.Args[0].Op != OpADDCQ { - goto enda68a39292ba2a05b3436191cb0bb0516 + goto end218ceec16b8299d573d3c9ccaa69b086 } off2 := v.Args[0].Aux ptr := v.Args[0].Args[0] @@ -368,21 +345,21 @@ func lowerAmd64(v *Value) bool { v.Op = OpMOVQload v.Aux = nil v.resetArgs() - v.Aux = off1.(int64) + off2.(int64) + v.Aux = addOff(off1, off2) v.AddArg(ptr) v.AddArg(mem) return true } - goto enda68a39292ba2a05b3436191cb0bb0516 - enda68a39292ba2a05b3436191cb0bb0516: + goto end218ceec16b8299d573d3c9ccaa69b086 + end218ceec16b8299d573d3c9ccaa69b086: ; // match: (MOVQload [off1] (LEAQ8 [off2] ptr idx) mem) // cond: - // result: (MOVQloadidx8 [off1.(int64)+off2.(int64)] ptr idx mem) + // result: (MOVQloadidx8 [addOff(off1, off2)] ptr idx mem) { off1 := v.Aux if v.Args[0].Op != OpLEAQ8 { - goto endba0e5cee85021614041016b1a2709ab8 + goto end02f5ad148292c46463e7c20d3b821735 } off2 := v.Args[0].Aux ptr := v.Args[0].Args[0] @@ -391,131 +368,117 @@ func lowerAmd64(v *Value) bool { v.Op = OpMOVQloadidx8 v.Aux = nil v.resetArgs() - v.Aux = off1.(int64) + off2.(int64) + v.Aux = addOff(off1, off2) v.AddArg(ptr) v.AddArg(idx) v.AddArg(mem) return true } - goto endba0e5cee85021614041016b1a2709ab8 - endba0e5cee85021614041016b1a2709ab8: + goto end02f5ad148292c46463e7c20d3b821735 + end02f5ad148292c46463e7c20d3b821735: ; - case OpMOVQstore: - // match: (MOVQstore [off1] (FPAddr [off2]) val mem) + case OpMOVQloadidx8: + // match: (MOVQloadidx8 [off1] (ADDCQ [off2] ptr) idx mem) // cond: - // result: (MOVQstoreFP [off1.(int64)+off2.(int64)] val mem) + // result: (MOVQloadidx8 [addOff(off1, off2)] ptr idx mem) { off1 := v.Aux - if v.Args[0].Op != OpFPAddr { - goto end0a2a81a20558dfc93790aecb1e9cc81a + if v.Args[0].Op != OpADDCQ { + goto ende47e8d742e2615f39fb6509a5749e414 } off2 := v.Args[0].Aux - val := v.Args[1] + ptr := v.Args[0].Args[0] + idx := v.Args[1] mem := v.Args[2] - v.Op = OpMOVQstoreFP + v.Op = OpMOVQloadidx8 v.Aux = nil v.resetArgs() - v.Aux = off1.(int64) + off2.(int64) - v.AddArg(val) + v.Aux = addOff(off1, off2) + v.AddArg(ptr) + v.AddArg(idx) v.AddArg(mem) return true } - goto end0a2a81a20558dfc93790aecb1e9cc81a - end0a2a81a20558dfc93790aecb1e9cc81a: + goto ende47e8d742e2615f39fb6509a5749e414 + ende47e8d742e2615f39fb6509a5749e414: ; - // match: (MOVQstore [off1] (SPAddr [off2]) val mem) + case OpMOVQstore: + // match: (MOVQstore [off1] (ADDCQ [off2] ptr) val mem) // cond: - // result: (MOVQstoreSP [off1.(int64)+off2.(int64)] val mem) + // result: (MOVQstore [addOff(off1, off2)] ptr val mem) { off1 := v.Aux - if v.Args[0].Op != OpSPAddr { - goto end1cb5b7e766f018270fa434c6f46f607f + if v.Args[0].Op != OpADDCQ { + goto enddfd4c7a20fd3b84eb9dcf84b98c661fc } off2 := v.Args[0].Aux + ptr := v.Args[0].Args[0] val := v.Args[1] mem := v.Args[2] - v.Op = OpMOVQstoreSP - v.Aux = nil - v.resetArgs() - v.Aux = off1.(int64) + off2.(int64) - v.AddArg(val) - v.AddArg(mem) - return true - } - goto end1cb5b7e766f018270fa434c6f46f607f - end1cb5b7e766f018270fa434c6f46f607f: - ; - // match: (MOVQstore [off] (Global [sym]) val mem) - // cond: - // result: (MOVQstoreglobal [GlobalOffset{sym,off.(int64)}] val mem) - { - off := v.Aux - if v.Args[0].Op != OpGlobal { - goto end657d07e37c720a8fbb108a31bb48090d - } - sym := v.Args[0].Aux - val := v.Args[1] - mem := v.Args[2] - v.Op = OpMOVQstoreglobal + v.Op = OpMOVQstore v.Aux = nil v.resetArgs() - v.Aux = GlobalOffset{sym, off.(int64)} + v.Aux = addOff(off1, off2) + v.AddArg(ptr) v.AddArg(val) v.AddArg(mem) return true } - goto end657d07e37c720a8fbb108a31bb48090d - end657d07e37c720a8fbb108a31bb48090d: + goto enddfd4c7a20fd3b84eb9dcf84b98c661fc + enddfd4c7a20fd3b84eb9dcf84b98c661fc: ; - // match: (MOVQstore [off1] (ADDCQ [off2] ptr) val mem) + // match: (MOVQstore [off1] (LEAQ8 [off2] ptr idx) val mem) // cond: - // result: (MOVQstore [off1.(int64)+off2.(int64)] ptr val mem) + // result: (MOVQstoreidx8 [addOff(off1, off2)] ptr idx val mem) { off1 := v.Aux - if v.Args[0].Op != OpADDCQ { - goto end271e3052de832e22b1f07576af2854de + if v.Args[0].Op != OpLEAQ8 { + goto endce1db8c8d37c8397c500a2068a65c215 } off2 := v.Args[0].Aux ptr := v.Args[0].Args[0] + idx := v.Args[0].Args[1] val := v.Args[1] mem := v.Args[2] - v.Op = OpMOVQstore + v.Op = OpMOVQstoreidx8 v.Aux = nil v.resetArgs() - v.Aux = off1.(int64) + off2.(int64) + v.Aux = addOff(off1, off2) v.AddArg(ptr) + v.AddArg(idx) v.AddArg(val) v.AddArg(mem) return true } - goto end271e3052de832e22b1f07576af2854de - end271e3052de832e22b1f07576af2854de: + goto endce1db8c8d37c8397c500a2068a65c215 + endce1db8c8d37c8397c500a2068a65c215: ; - // match: (MOVQstore [off1] (LEAQ8 [off2] ptr idx) val mem) + case OpMOVQstoreidx8: + // match: (MOVQstoreidx8 [off1] (ADDCQ [off2] ptr) idx val mem) // cond: - // result: (MOVQstoreidx8 [off1.(int64)+off2.(int64)] ptr idx val mem) + // result: (MOVQstoreidx8 [addOff(off1, off2)] ptr idx val mem) { off1 := v.Aux - if v.Args[0].Op != OpLEAQ8 { - goto end4ad469f534c7369f6ac36bdace3462ad + if v.Args[0].Op != OpADDCQ { + goto endcdb222707a568ad468f7fff2fc42fc39 } off2 := v.Args[0].Aux ptr := v.Args[0].Args[0] - idx := v.Args[0].Args[1] - val := v.Args[1] - mem := v.Args[2] + idx := v.Args[1] + val := v.Args[2] + mem := v.Args[3] v.Op = OpMOVQstoreidx8 v.Aux = nil v.resetArgs() - v.Aux = off1.(int64) + off2.(int64) + v.Aux = addOff(off1, off2) v.AddArg(ptr) v.AddArg(idx) v.AddArg(val) v.AddArg(mem) return true } - goto end4ad469f534c7369f6ac36bdace3462ad - end4ad469f534c7369f6ac36bdace3462ad: + goto endcdb222707a568ad468f7fff2fc42fc39 + endcdb222707a568ad468f7fff2fc42fc39: ; case OpMULCQ: // match: (MULCQ [c] x) @@ -538,13 +501,13 @@ func lowerAmd64(v *Value) bool { end90a1c055d9658aecacce5e101c1848b4: ; case OpMULQ: - // match: (MULQ x (Const [c])) + // match: (MULQ x (MOVQconst [c])) // cond: // result: (MULCQ [c] x) { x := v.Args[0] - if v.Args[1].Op != OpConst { - goto endc427f4838d2e83c00cc097b20bd20a37 + if v.Args[1].Op != OpMOVQconst { + goto endce35d001482ea209e62e9394bd07c7cb } c := v.Args[1].Aux v.Op = OpMULCQ @@ -554,15 +517,15 @@ func lowerAmd64(v *Value) bool { v.AddArg(x) return true } - goto endc427f4838d2e83c00cc097b20bd20a37 - endc427f4838d2e83c00cc097b20bd20a37: + goto endce35d001482ea209e62e9394bd07c7cb + endce35d001482ea209e62e9394bd07c7cb: ; - // match: (MULQ (Const [c]) x) + // match: (MULQ (MOVQconst [c]) x) // cond: // result: (MULCQ [c] x) { - if v.Args[0].Op != OpConst { - goto endd70de938e71150d1c9e8173c2a5b2d95 + if v.Args[0].Op != OpMOVQconst { + goto end804f58b1f6a7cce19d48379999ec03f1 } c := v.Args[0].Aux x := v.Args[1] @@ -573,8 +536,32 @@ func lowerAmd64(v *Value) bool { v.AddArg(x) return true } - goto endd70de938e71150d1c9e8173c2a5b2d95 - endd70de938e71150d1c9e8173c2a5b2d95: + goto end804f58b1f6a7cce19d48379999ec03f1 + end804f58b1f6a7cce19d48379999ec03f1: + ; + case OpMove: + // match: (Move [size] dst src mem) + // cond: + // result: (REPMOVSB dst src (Const [size.(int64)]) mem) + { + size := v.Aux + dst := v.Args[0] + src := v.Args[1] + mem := v.Args[2] + v.Op = OpREPMOVSB + v.Aux = nil + v.resetArgs() + v.AddArg(dst) + v.AddArg(src) + v0 := v.Block.NewValue(OpConst, TypeInvalid, nil) + v0.Type = TypeUInt64 + v0.Aux = size.(int64) + v.AddArg(v0) + v.AddArg(mem) + return true + } + goto end48909259b265a6bb2a076bc2c2dc7d1f + end48909259b265a6bb2a076bc2c2dc7d1f: ; case OpMul: // match: (Mul x y) @@ -597,6 +584,23 @@ func lowerAmd64(v *Value) bool { goto endfab0d598f376ecba45a22587d50f7aff endfab0d598f376ecba45a22587d50f7aff: ; + case OpOffPtr: + // match: (OffPtr [off] ptr) + // cond: + // result: (ADDCQ [off] ptr) + { + off := v.Aux + ptr := v.Args[0] + v.Op = OpADDCQ + v.Aux = nil + v.resetArgs() + v.Aux = off + v.AddArg(ptr) + return true + } + goto endfe8f713b1d237a23311fb721ee46bedb + endfe8f713b1d237a23311fb721ee46bedb: + ; case OpSETL: // match: (SETL (InvertFlags x)) // cond: @@ -616,13 +620,13 @@ func lowerAmd64(v *Value) bool { end456c7681d48305698c1ef462d244bdc6: ; case OpSUBQ: - // match: (SUBQ x (Const [c])) + // match: (SUBQ x (MOVQconst [c])) // cond: // result: (SUBCQ x [c]) { x := v.Args[0] - if v.Args[1].Op != OpConst { - goto endb31e242f283867de4722665a5796008c + if v.Args[1].Op != OpMOVQconst { + goto endc96cd1cb2dd98427c34fb9543feca4fe } c := v.Args[1].Aux v.Op = OpSUBCQ @@ -632,16 +636,16 @@ func lowerAmd64(v *Value) bool { v.Aux = c return true } - goto endb31e242f283867de4722665a5796008c - endb31e242f283867de4722665a5796008c: + goto endc96cd1cb2dd98427c34fb9543feca4fe + endc96cd1cb2dd98427c34fb9543feca4fe: ; - // match: (SUBQ (Const [c]) x) + // match: (SUBQ (MOVQconst [c]) x) // cond: // result: (NEGQ (SUBCQ x [c])) { t := v.Type - if v.Args[0].Op != OpConst { - goto end569cc755877d1f89a701378bec05c08d + if v.Args[0].Op != OpMOVQconst { + goto end900aaaf28cefac6bb62e76b5151611cf } c := v.Args[0].Aux x := v.Args[1] @@ -655,8 +659,8 @@ func lowerAmd64(v *Value) bool { v.AddArg(v0) return true } - goto end569cc755877d1f89a701378bec05c08d - end569cc755877d1f89a701378bec05c08d: + goto end900aaaf28cefac6bb62e76b5151611cf + end900aaaf28cefac6bb62e76b5151611cf: ; case OpStore: // match: (Store ptr val mem) diff --git a/src/cmd/internal/ssa/op.go b/src/cmd/internal/ssa/op.go index ebe4a8e747..e0dc531fc9 100644 --- a/src/cmd/internal/ssa/op.go +++ b/src/cmd/internal/ssa/op.go @@ -4,6 +4,8 @@ package ssa +import "fmt" + // 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. @@ -47,8 +49,11 @@ const ( OpArg // address of a function parameter/result. Memory input is an arg called ".mem". aux is a string (TODO: make it something other than a string?) OpGlobal // the address of a global variable aux.(*gc.Sym) OpFunc // entry address of a function + OpFP // frame pointer + OpSP // stack pointer OpCopy // output = arg0 + OpMove // arg0=destptr, arg1=srcptr, arg2=mem, aux.(int64)=size. Returns memory. OpPhi // select an argument based on which predecessor block we came from OpSliceMake // arg0=ptr, arg1=len, arg2=cap @@ -62,7 +67,8 @@ const ( OpLoad // Load from arg0+aux.(int64). arg1=memory OpStore // Store arg1 to arg0+aux.(int64). arg2=memory. Returns memory. - OpSliceIndex // arg0=slice, arg1=index, arg2=memory + OpArrayIndex // arg0=array, arg1=index. Returns a[i] + OpPtrIndex // arg0=ptr, arg1=index. Computes ptr+sizeof(*v.type)*index, where index is extended to ptrwidth type OpIsNonNil // arg0 != nil OpIsInBounds // 0 <= arg0 < arg1 @@ -75,6 +81,8 @@ const ( OpConvert // convert arg0 to another type OpConvNop // interpret arg0 as another type + OpOffPtr // arg0 + aux.(int64) (arg0 and result are pointers) + // These ops return a pointer to a location on the stack. OpFPAddr // FP + aux.(int64) (+ == args from caller, - == locals) OpSPAddr // SP + aux.(int64) @@ -96,6 +104,15 @@ type GlobalOffset struct { Offset int64 } +// offset adds x to the location specified by g and returns it. +func (g GlobalOffset) offset(x int64) GlobalOffset { + return GlobalOffset{g.Global, g.Offset + x} +} + +func (g GlobalOffset) String() string { + return fmt.Sprintf("%v+%d", g.Global, g.Offset) +} + //go:generate stringer -type=Op type opInfo struct { diff --git a/src/cmd/internal/ssa/op_string.go b/src/cmd/internal/ssa/op_string.go index 0851cfe0fb..9b22f664ef 100644 --- a/src/cmd/internal/ssa/op_string.go +++ b/src/cmd/internal/ssa/op_string.go @@ -6,16 +6,16 @@ import "fmt" const ( _Op_name_0 = "opInvalid" - _Op_name_1 = "opGenericBaseOpAddOpSubOpMulOpLessOpConstOpArgOpGlobalOpFuncOpCopyOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpLoadOpStoreOpSliceIndexOpIsNonNilOpIsInBoundsOpCallOpStaticCallOpConvertOpConvNopOpFPAddrOpSPAddrOpStoreReg8OpLoadReg8OpFwdRef" - _Op_name_2 = "opAMD64BaseOpADDQOpSUBQOpADDCQOpSUBCQOpMULQOpMULCQOpSHLQOpSHLCQOpNEGQOpADDLOpCMPQOpCMPCQOpTESTQOpSETEQOpSETNEOpSETLOpSETGEOpSETBOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpMOVQloadOpMOVQstoreOpMOVQloadidx8OpMOVQstoreidx8OpMOVQloadglobalOpMOVQstoreglobalOpMOVQloadFPOpMOVQloadSPOpMOVQstoreFPOpMOVQstoreSPOpMOVQconst" + _Op_name_1 = "opGenericBaseOpAddOpSubOpMulOpLessOpConstOpArgOpGlobalOpFuncOpFPOpSPOpCopyOpMoveOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpLoadOpStoreOpArrayIndexOpPtrIndexOpIsNonNilOpIsInBoundsOpCallOpStaticCallOpConvertOpConvNopOpOffPtrOpFPAddrOpSPAddrOpStoreReg8OpLoadReg8OpFwdRef" + _Op_name_2 = "opAMD64BaseOpADDQOpSUBQOpADDCQOpSUBCQOpMULQOpMULCQOpSHLQOpSHLCQOpNEGQOpADDLOpCMPQOpCMPCQOpTESTQOpTESTBOpSETEQOpSETNEOpSETLOpSETGEOpSETBOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpLEAQglobalOpMOVBloadOpMOVBQZXloadOpMOVBQSXloadOpMOVQloadOpMOVQstoreOpMOVQloadidx8OpMOVQstoreidx8OpMOVQloadglobalOpMOVQstoreglobalOpMOVQconstOpREPMOVSB" _Op_name_3 = "op386Base" _Op_name_4 = "opMax" ) var ( _Op_index_0 = [...]uint8{0, 9} - _Op_index_1 = [...]uint16{0, 13, 18, 23, 28, 34, 41, 46, 54, 60, 66, 71, 82, 92, 102, 112, 124, 135, 146, 152, 159, 171, 181, 193, 199, 211, 220, 229, 237, 245, 256, 266, 274} - _Op_index_2 = [...]uint16{0, 11, 17, 23, 30, 37, 43, 50, 56, 63, 69, 75, 81, 88, 95, 102, 109, 115, 122, 128, 141, 147, 154, 161, 168, 178, 189, 203, 218, 234, 251, 263, 275, 288, 301, 312} + _Op_index_1 = [...]uint16{0, 13, 18, 23, 28, 34, 41, 46, 54, 60, 64, 68, 74, 80, 85, 96, 106, 116, 126, 138, 149, 160, 166, 173, 185, 195, 205, 217, 223, 235, 244, 253, 261, 269, 277, 288, 298, 306} + _Op_index_2 = [...]uint16{0, 11, 17, 23, 30, 37, 43, 50, 56, 63, 69, 75, 81, 88, 95, 102, 109, 116, 122, 129, 135, 148, 154, 161, 168, 175, 187, 197, 210, 223, 233, 244, 258, 273, 289, 306, 317, 327} _Op_index_3 = [...]uint8{0, 9} _Op_index_4 = [...]uint8{0, 5} ) @@ -24,10 +24,10 @@ func (i Op) String() string { switch { case i == 0: return _Op_name_0 - case 1001 <= i && i <= 1032: + case 1001 <= i && i <= 1037: i -= 1001 return _Op_name_1[_Op_index_1[i]:_Op_index_1[i+1]] - case 2001 <= i && i <= 2035: + case 2001 <= i && i <= 2037: i -= 2001 return _Op_name_2[_Op_index_2[i]:_Op_index_2[i+1]] case i == 3001: diff --git a/src/cmd/internal/ssa/opamd64.go b/src/cmd/internal/ssa/opamd64.go index 8bdd19f713..46f0a69dfb 100644 --- a/src/cmd/internal/ssa/opamd64.go +++ b/src/cmd/internal/ssa/opamd64.go @@ -30,6 +30,7 @@ const ( OpCMPQ // arg0 compare to arg1 OpCMPCQ // arg0 compare to aux.(int64) OpTESTQ // (arg0 & arg1) compare to 0 + OpTESTB // (arg0 & arg1) compare to 0 // These opcodes extract a particular boolean condition from a flags value. OpSETEQ // extract == condition from arg0 @@ -43,29 +44,30 @@ const ( // This is a pseudo-op which can't appear in assembly output. OpInvertFlags // reverse direction of arg0 - OpLEAQ // arg0 + arg1 + aux.(int64) - OpLEAQ2 // arg0 + 2*arg1 + aux.(int64) - OpLEAQ4 // arg0 + 4*arg1 + aux.(int64) - OpLEAQ8 // arg0 + 8*arg1 + aux.(int64) + OpLEAQ // arg0 + arg1 + aux.(int64) + OpLEAQ2 // arg0 + 2*arg1 + aux.(int64) + OpLEAQ4 // arg0 + 4*arg1 + aux.(int64) + OpLEAQ8 // arg0 + 8*arg1 + aux.(int64) + OpLEAQglobal // no args. address of aux.(GlobalOffset) // Load/store from general address - OpMOVQload // Load from arg0+aux.(int64). arg1=memory + OpMOVBload // Load from arg0+aux.(int64). arg1=memory + OpMOVBQZXload + OpMOVBQSXload + OpMOVQload OpMOVQstore // Store arg1 to arg0+aux.(int64). arg2=memory, returns memory. OpMOVQloadidx8 // Load from arg0+arg1*8+aux.(int64). arg2=memory OpMOVQstoreidx8 // Store arg2 to arg0+arg1*8+aux.(int64). arg3=memory, returns memory. - // Load/store from global. aux.(GlobalOffset) encodes the global location. + // Load/store from global. Same as the above loads, but arg0 is missing and aux is a GlobalOffset instead of an int64. OpMOVQloadglobal // arg0 = memory OpMOVQstoreglobal // store arg0. arg1=memory, returns memory. - // Load/store from stack slot. - OpMOVQloadFP // load from FP+aux.(int64). arg0=memory - OpMOVQloadSP // load from SP+aux.(int64). arg0=memory - OpMOVQstoreFP // store arg0 to FP+aux.(int64). arg1=memory, returns memory. - OpMOVQstoreSP // store arg0 to SP+aux.(int64). arg1=memory, returns memory. - // materialize a constant into a register OpMOVQconst // (takes no arguments) + + // move memory + OpREPMOVSB // arg0=destptr, arg1=srcptr, arg2=len, arg3=mem ) type regMask uint64 @@ -89,13 +91,16 @@ var regsAMD64 = [...]string{ "R15", // pseudo registers + "FP", "FLAGS", "OVERWRITE0", // the same register as the first input } -var gp regMask = 0xef // all integer registers except SP -var cx regMask = 0x2 -var flags regMask = 1 << 16 +var gp regMask = 0x1ffff // all integer registers (including SP&FP) +var cx regMask = 1 << 1 +var si regMask = 1 << 6 +var di regMask = 1 << 7 +var flags regMask = 1 << 17 var ( // gp = general purpose (integer) registers @@ -129,13 +134,16 @@ var amd64Table = map[Op]opInfo{ OpCMPQ: {asm: "CMPQ\t%I0,%I1", reg: gp2_flags}, // compute arg[0]-arg[1] and produce flags OpCMPCQ: {asm: "CMPQ\t$%A,%I0", reg: gp1_flags}, OpTESTQ: {asm: "TESTQ\t%I0,%I1", reg: gp2_flags}, + OpTESTB: {asm: "TESTB\t%I0,%I1", reg: gp2_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"}, + 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"}, + OpLEAQglobal: {asm: "LEAQ\t%A(SB),%O0", reg: gp01}, // loads and stores + OpMOVBload: {asm: "MOVB\t%A(%I0),%O0", reg: gpload}, OpMOVQload: {asm: "MOVQ\t%A(%I0),%O0", reg: gpload}, OpMOVQstore: {asm: "MOVQ\t%I1,%A(%I0)", reg: gpstore}, OpMOVQloadidx8: {asm: "MOVQ\t%A(%I0)(%I1*8),%O0", reg: gploadidx}, @@ -145,23 +153,20 @@ var amd64Table = map[Op]opInfo{ OpStaticCall: {asm: "CALL\t%A(SB)"}, - OpCopy: {asm: "MOVQ\t%I0,%O0", reg: gp11}, + OpCopy: {asm: "MOVQ\t%I0,%O0", reg: gp11}, // TODO: make arch-specific + OpConvNop: {asm: "MOVQ\t%I0,%O0", reg: gp11}, // TODO: make arch-specific. Or get rid of this altogether. // convert from flags back to boolean OpSETL: {}, - // ops for load/store to stack - OpMOVQloadFP: {asm: "MOVQ\t%A(FP),%O0", reg: gpload_stack}, // mem -> value - OpMOVQloadSP: {asm: "MOVQ\t%A(SP),%O0", reg: gpload_stack}, // mem -> value - OpMOVQstoreFP: {asm: "MOVQ\t%I0,%A(FP)", reg: gpstore_stack}, // mem, value -> mem - OpMOVQstoreSP: {asm: "MOVQ\t%I0,%A(SP)", reg: gpstore_stack}, // mem, value -> mem - // 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"}, OpStoreReg8: {asm: "MOVQ\t%I0,%O0"}, + + OpREPMOVSB: {asm: "REP MOVSB", reg: [2][]regMask{{di, si, cx, 0}, {0}}}, // TODO: record that si/di/cx are clobbered } func init() { diff --git a/src/cmd/internal/ssa/regalloc.go b/src/cmd/internal/ssa/regalloc.go index e2de10896e..c798d2e936 100644 --- a/src/cmd/internal/ssa/regalloc.go +++ b/src/cmd/internal/ssa/regalloc.go @@ -39,8 +39,9 @@ var registers = [...]Register{ // TODO X0, ... // TODO: make arch-dependent - Register{16, "FLAGS"}, - Register{17, "OVERWRITE"}, + Register{16, "FP"}, // pseudo-register, actually a constant offset from SP + Register{17, "FLAGS"}, + Register{18, "OVERWRITE"}, } // countRegs returns the number of set bits in the register mask. @@ -84,6 +85,19 @@ func regalloc(f *Func) { var oldSched []*Value + // Hack to find fp, sp Values and assign them a register. (TODO: make not so hacky) + var fp, sp *Value + for _, v := range f.Entry.Values { + switch v.Op { + case OpSP: + sp = v + home = setloc(home, v, ®isters[4]) // TODO: arch-dependent + case OpFP: + fp = v + home = setloc(home, v, ®isters[16]) // TODO: arch-dependent + } + } + // Register allocate each block separately. All live values will live // in home locations (stack slots) between blocks. for _, b := range f.Blocks { @@ -115,6 +129,10 @@ func regalloc(f *Func) { } regs := make([]regInfo, numRegs) + // TODO: hack: initialize fixed registers + regs[4] = regInfo{sp, sp, false} + regs[16] = regInfo{fp, fp, false} + var used regMask // has a 1 for each non-nil entry in regs var dirty regMask // has a 1 for each dirty entry in regs @@ -133,9 +151,6 @@ func regalloc(f *Func) { // - definition of v. c will be identical to v but will live in // a register. v will be modified into a spill of c. regspec := opcodeTable[v.Op].reg - if v.Op == OpConvNop { - regspec = opcodeTable[v.Args[0].Op].reg - } inputs := regspec[0] outputs := regspec[1] if len(inputs) == 0 && len(outputs) == 0 { @@ -154,6 +169,7 @@ func regalloc(f *Func) { // nospill contains registers that we can't spill because // we already set them up for use by the current instruction. var nospill regMask + nospill |= 0x10010 // SP and FP can't be spilled (TODO: arch-specific) // Move inputs into registers for _, o := range order { @@ -215,10 +231,16 @@ func regalloc(f *Func) { // Load w into this register var c *Value - if w.Op == OpConst { + if len(w.Args) == 0 { // Materialize w - // TODO: arch-specific MOV op - c = b.NewValue(OpMOVQconst, w.Type, w.Aux) + if w.Op == OpFP || w.Op == OpSP || w.Op == OpGlobal { + c = b.NewValue1(OpCopy, w.Type, nil, w) + } else { + c = b.NewValue(w.Op, w.Type, w.Aux) + } + } else if len(w.Args) == 1 && (w.Args[0].Op == OpFP || w.Args[0].Op == OpSP || w.Args[0].Op == OpGlobal) { + // Materialize offsets from SP/FP/Global + c = b.NewValue1(w.Op, w.Type, w.Aux, w.Args[0]) } else if wreg != 0 { // Copy from another register. // Typically just an optimization, but this is @@ -317,6 +339,10 @@ func regalloc(f *Func) { v := regs[r].v c := regs[r].c if lastUse[v.ID] <= len(oldSched) { + if v == v.Block.Control { + // link control value to register version + v.Block.Control = c + } continue // not live after block } @@ -334,6 +360,7 @@ func regalloc(f *Func) { } } f.RegAlloc = home + deadcode(f) // remove values that had all of their uses rematerialized. TODO: separate pass? } // addPhiCopies adds copies of phi inputs in the blocks diff --git a/src/cmd/internal/ssa/rewrite.go b/src/cmd/internal/ssa/rewrite.go index 855719a877..75e910d690 100644 --- a/src/cmd/internal/ssa/rewrite.go +++ b/src/cmd/internal/ssa/rewrite.go @@ -4,14 +4,14 @@ package ssa -import "fmt" +import "log" func applyRewrite(f *Func, r func(*Value) bool) { // repeat rewrites until we find no more rewrites var curv *Value defer func() { if curv != nil { - fmt.Printf("panic during rewrite of %s\n", curv.LongString()) + log.Printf("panic during rewrite of %s\n", curv.LongString()) // TODO(khr): print source location also } }() @@ -19,6 +19,18 @@ func applyRewrite(f *Func, r func(*Value) bool) { change := false for _, b := range f.Blocks { for _, v := range b.Values { + // elide any copies generated during rewriting + for i, a := range v.Args { + if a.Op != OpCopy { + continue + } + for a.Op == OpCopy { + a = a.Args[0] + } + v.Args[i] = a + } + + // apply rewrite function curv = v if r(v) { change = true @@ -26,6 +38,7 @@ func applyRewrite(f *Func, r func(*Value) bool) { } } if !change { + curv = nil return } } @@ -52,3 +65,19 @@ func isSigned(t Type) bool { func typeSize(t Type) int64 { return t.Size() } + +// addOff adds two offset aux values. Each should be an int64. Fails if wraparound happens. +func addOff(a, b interface{}) interface{} { + x := a.(int64) + y := b.(int64) + z := x + y + // x and y have same sign and z has a different sign => overflow + if x^y >= 0 && x^z < 0 { + log.Panicf("offset overflow %d %d\n", x, y) + } + return z +} + +func inBounds(idx, len int64) bool { + return idx >= 0 && idx < len +} diff --git a/src/cmd/internal/ssa/rulegen/generic.rules b/src/cmd/internal/ssa/rulegen/generic.rules index d17449930f..c49d9d9f2e 100644 --- a/src/cmd/internal/ssa/rulegen/generic.rules +++ b/src/cmd/internal/ssa/rulegen/generic.rules @@ -3,17 +3,22 @@ // license that can be found in the LICENSE file. // constant folding -(Add (Const [c]) (Const [d])) && is64BitInt(t) && isSigned(t) -> (Const [{c.(int64)+d.(int64)}]) -(Add (Const [c]) (Const [d])) && is64BitInt(t) && !isSigned(t) -> (Const [{c.(uint64)+d.(uint64)}]) +(Add (Const [c]) (Const [d])) && is64BitInt(t) -> (Const [{c.(int64)+d.(int64)}]) +(Mul (Const [c]) (Const [d])) && is64BitInt(t) -> (Const [{c.(int64)*d.(int64)}]) +(IsInBounds (Const [c]) (Const [d])) -> (Const [inBounds(c.(int64),d.(int64))]) // tear apart slices // TODO: anything that generates a slice needs to go in here. (SlicePtr (Load ptr mem)) -> (Load ptr mem) -(SliceLen (Load ptr mem)) -> (Load (Add ptr (Const [int64(v.Block.Func.Config.ptrSize)])) mem) -(SliceCap (Load ptr mem)) -> (Load (Add ptr (Const [int64(v.Block.Func.Config.ptrSize*2)])) mem) - -// expand array indexing -// others? Depends on what is already done by frontend +(SliceLen (Load ptr mem)) -> (Load (Add ptr (Const [int64(v.Block.Func.Config.ptrSize)])) mem) +(SliceCap (Load ptr mem)) -> (Load (Add ptr (Const [int64(v.Block.Func.Config.ptrSize*2)])) mem) +// indexing operations // Note: bounds check has already been done -(SliceIndex s i mem) -> (Load (Add (SlicePtr s) (Mul i (Const [s.Type.Elem().Size()]))) mem) +(ArrayIndex (Load ptr mem) idx) -> (Load (PtrIndex ptr idx) mem) +(PtrIndex ptr idx) -> (Add ptr (Mul idx (Const [t.Elem().Size()]))) +// TODO: hopefully this will get rid of all full-width array copies. + +// big-object moves +// TODO: fix size +(Store dst (Load src mem) mem) && t.Size() > 8 -> (Move [t.Size()] dst src mem) diff --git a/src/cmd/internal/ssa/rulegen/lower_amd64.rules b/src/cmd/internal/ssa/rulegen/lower_amd64.rules index 55267d6842..0fed21e740 100644 --- a/src/cmd/internal/ssa/rulegen/lower_amd64.rules +++ b/src/cmd/internal/ssa/rulegen/lower_amd64.rules @@ -30,6 +30,7 @@ (Less x y) && is64BitInt(v.Args[0].Type) && isSigned(v.Args[0].Type) -> (SETL (CMPQ x y)) +(Load ptr mem) && t.IsBoolean() -> (MOVBload [int64(0)] ptr mem) (Load ptr mem) && (is64BitInt(t) || isPtr(t)) -> (MOVQload [int64(0)] ptr mem) (Store ptr val mem) && (is64BitInt(val.Type) || isPtr(val.Type)) -> (MOVQstore [int64(0)] ptr val mem) @@ -37,28 +38,27 @@ (IsNonNil p) -> (SETNE (TESTQ p p)) (IsInBounds idx len) -> (SETB (CMPQ idx len)) +(Move [size] dst src mem) -> (REPMOVSB dst src (Const [size.(int64)]) mem) + +(OffPtr [off] ptr) -> (ADDCQ [off] ptr) + +(Const [val]) && is64BitInt(t) -> (MOVQconst [val]) + // Rules below here apply some simple optimizations after lowering. // TODO: Should this be a separate pass? -// stack loads/stores -(MOVQload [off1] (FPAddr [off2]) mem) -> (MOVQloadFP [off1.(int64)+off2.(int64)] mem) -(MOVQload [off1] (SPAddr [off2]) mem) -> (MOVQloadSP [off1.(int64)+off2.(int64)] mem) -(MOVQstore [off1] (FPAddr [off2]) val mem) -> (MOVQstoreFP [off1.(int64)+off2.(int64)] val mem) -(MOVQstore [off1] (SPAddr [off2]) val mem) -> (MOVQstoreSP [off1.(int64)+off2.(int64)] val mem) - // global loads/stores -(MOVQload [off] (Global [sym]) mem) -> (MOVQloadglobal [GlobalOffset{sym,off.(int64)}] mem) -(MOVQstore [off] (Global [sym]) val mem) -> (MOVQstoreglobal [GlobalOffset{sym,off.(int64)}] val mem) +(Global [sym]) -> (LEAQglobal [GlobalOffset{sym,0}]) // fold constants into instructions -(ADDQ x (Const [c])) -> (ADDCQ [c] x) // TODO: restrict c to int32 range? -(ADDQ (Const [c]) x) -> (ADDCQ [c] x) -(SUBQ x (Const [c])) -> (SUBCQ x [c]) -(SUBQ (Const [c]) x) -> (NEGQ (SUBCQ x [c])) -(MULQ x (Const [c])) -> (MULCQ [c] x) -(MULQ (Const [c]) x) -> (MULCQ [c] x) -(CMPQ x (Const [c])) -> (CMPCQ x [c]) -(CMPQ (Const [c]) x) -> (InvertFlags (CMPCQ x [c])) +(ADDQ x (MOVQconst [c])) -> (ADDCQ [c] x) // TODO: restrict c to int32 range? +(ADDQ (MOVQconst [c]) x) -> (ADDCQ [c] x) +(SUBQ x (MOVQconst [c])) -> (SUBCQ x [c]) +(SUBQ (MOVQconst [c]) x) -> (NEGQ (SUBCQ x [c])) +(MULQ x (MOVQconst [c])) -> (MULCQ [c] x) +(MULQ (MOVQconst [c]) x) -> (MULCQ [c] x) +(CMPQ x (MOVQconst [c])) -> (CMPCQ x [c]) +(CMPQ (MOVQconst [c]) x) -> (InvertFlags (CMPCQ x [c])) // strength reduction // TODO: do this a lot more generically @@ -66,7 +66,7 @@ // fold add/shift into leaq (ADDQ x (SHLCQ [shift] y)) && shift.(int64) == 3 -> (LEAQ8 [int64(0)] x y) -(ADDCQ [c] (LEAQ8 [d] x y)) -> (LEAQ8 [c.(int64)+d.(int64)] x y) +(ADDCQ [c] (LEAQ8 [d] x y)) -> (LEAQ8 [addOff(c, d)] x y) // reverse ordering of compare instruction (SETL (InvertFlags x)) -> (SETGE x) @@ -76,13 +76,14 @@ // the ADDCQ get eliminated, we still have to compute the ADDCQ and we now // have potentially two live values (ptr and (ADDCQ [off] ptr)) instead of one. // Nevertheless, let's do it! -(MOVQload [off1] (ADDCQ [off2] ptr) mem) -> (MOVQload [off1.(int64)+off2.(int64)] ptr mem) -(MOVQstore [off1] (ADDCQ [off2] ptr) val mem) -> (MOVQstore [off1.(int64)+off2.(int64)] ptr val mem) +(MOVQload [off1] (ADDCQ [off2] ptr) mem) -> (MOVQload [addOff(off1, off2)] ptr mem) +(MOVQstore [off1] (ADDCQ [off2] ptr) val mem) -> (MOVQstore [addOff(off1, off2)] ptr val mem) // indexed loads and stores -(MOVQload [off1] (LEAQ8 [off2] ptr idx) mem) -> (MOVQloadidx8 [off1.(int64)+off2.(int64)] ptr idx mem) -(MOVQstore [off1] (LEAQ8 [off2] ptr idx) val mem) -> (MOVQstoreidx8 [off1.(int64)+off2.(int64)] ptr idx val mem) +(MOVQload [off1] (LEAQ8 [off2] ptr idx) mem) -> (MOVQloadidx8 [addOff(off1, off2)] ptr idx mem) +(MOVQstore [off1] (LEAQ8 [off2] ptr idx) val mem) -> (MOVQstoreidx8 [addOff(off1, off2)] ptr idx val mem) + +(MOVQloadidx8 [off1] (ADDCQ [off2] ptr) idx mem) -> (MOVQloadidx8 [addOff(off1, off2)] ptr idx mem) +(MOVQstoreidx8 [off1] (ADDCQ [off2] ptr) idx val mem) -> (MOVQstoreidx8 [addOff(off1, off2)] ptr idx val mem) -// Combine the offset of a stack object with the offset within a stack object -(ADDCQ [off1] (FPAddr [off2])) -> (FPAddr [off1.(int64)+off2.(int64)]) -(ADDCQ [off1] (SPAddr [off2])) -> (SPAddr [off1.(int64)+off2.(int64)]) +(ADDCQ [off] x) && off.(int64) == 0 -> (Copy x) diff --git a/src/cmd/internal/ssa/rulegen/rulegen.go b/src/cmd/internal/ssa/rulegen/rulegen.go index 31f46f7cce..4ac930298b 100644 --- a/src/cmd/internal/ssa/rulegen/rulegen.go +++ b/src/cmd/internal/ssa/rulegen/rulegen.go @@ -245,6 +245,12 @@ func genResult(w io.Writer, result string) { func genResult0(w io.Writer, result string, alloc *int, top bool) string { if result[0] != '(' { // variable + if top { + fmt.Fprintf(w, "v.Op = %s.Op\n", result) + fmt.Fprintf(w, "v.Aux = %s.Aux\n", result) + fmt.Fprintf(w, "v.resetArgs()\n") + fmt.Fprintf(w, "v.AddArgs(%s.Args...)\n", result) + } return result } @@ -297,20 +303,33 @@ func split(s string) []string { outer: for s != "" { - d := 0 // depth of ({[< - nonsp := false // found a non-space char so far + d := 0 // depth of ({[< + var open, close byte // opening and closing markers ({[< or )}]> + nonsp := false // found a non-space char so far for i := 0; i < len(s); i++ { - switch s[i] { - case '(', '{', '[', '<': + switch { + case d == 0 && s[i] == '(': + open, close = '(', ')' d++ - case ')', '}', ']', '>': - d-- - case ' ', '\t': - if d == 0 && nonsp { + case d == 0 && s[i] == '<': + open, close = '<', '>' + d++ + case d == 0 && s[i] == '[': + open, close = '[', ']' + d++ + case d == 0 && s[i] == '{': + open, close = '{', '}' + d++ + case d == 0 && (s[i] == ' ' || s[i] == '\t'): + if nonsp { r = append(r, strings.TrimSpace(s[:i])) s = s[i:] continue outer } + case d > 0 && s[i] == open: + d++ + case d > 0 && s[i] == close: + d-- default: nonsp = true } diff --git a/src/cmd/internal/ssa/stackalloc.go b/src/cmd/internal/ssa/stackalloc.go index 4d0359ed81..8a315e1045 100644 --- a/src/cmd/internal/ssa/stackalloc.go +++ b/src/cmd/internal/ssa/stackalloc.go @@ -15,6 +15,9 @@ func stackalloc(f *Func) { if v.Op != OpPhi { continue } + if v.Type.IsMemory() { // TODO: only "regallocable" types + continue + } n += v.Type.Size() // a := v.Type.Align() // n = (n + a - 1) / a * a TODO @@ -35,10 +38,11 @@ func stackalloc(f *Func) { if v.Type.IsMemory() { // TODO: only "regallocable" types continue } - if v.Op == OpConst { - // don't allocate space for OpConsts. They should - // have been rematerialized everywhere. - // TODO: is this the right thing to do? + if len(v.Args) == 0 { + // v will have been materialized wherever it is needed. + continue + } + if len(v.Args) == 1 && (v.Args[0].Op == OpFP || v.Args[0].Op == OpSP || v.Args[0].Op == OpGlobal) { continue } // a := v.Type.Align() -- cgit v1.3 From 247786c1745abc0c7185f7c15ca256edf68ed6d6 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Thu, 28 May 2015 10:47:24 -0700 Subject: [dev.ssa] B[dev.ssa] cmd/internal/ssa: Cleanup & reorg Rename ops like ADDCQ to ADDQconst, so it is clear what the base opcode is and what the modifiers are. Convert FP references to SP references once we know the frame size. Related, compute the frame size in the ssa package. Do a bunch of small fixes. Add a TODO list for people to peruse. Change-Id: Ia6a3fe2bf57e5a2e5e883032e2a2a3fdd566c038 Reviewed-on: https://go-review.googlesource.com/10465 Reviewed-by: Alan Donovan --- src/cmd/internal/gc/ssa.go | 125 ++++++---- src/cmd/internal/ssa/TODO | 47 ++++ src/cmd/internal/ssa/func.go | 2 + src/cmd/internal/ssa/location.go | 14 +- src/cmd/internal/ssa/lower.go | 11 +- src/cmd/internal/ssa/lowerAmd64.go | 327 +++++++++++++++---------- src/cmd/internal/ssa/op.go | 8 +- src/cmd/internal/ssa/op_string.go | 10 +- src/cmd/internal/ssa/opamd64.go | 71 +++--- src/cmd/internal/ssa/rewrite.go | 5 +- src/cmd/internal/ssa/rulegen/lower_amd64.rules | 42 ++-- src/cmd/internal/ssa/stackalloc.go | 58 ++++- 12 files changed, 454 insertions(+), 266 deletions(-) create mode 100644 src/cmd/internal/ssa/TODO (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/gc/ssa.go b/src/cmd/internal/gc/ssa.go index 8e81163ad4..bb4d278383 100644 --- a/src/cmd/internal/gc/ssa.go +++ b/src/cmd/internal/gc/ssa.go @@ -287,6 +287,14 @@ func (s *state) expr(n *Node) *ssa.Value { a := s.expr(n.Left) b := s.expr(n.Right) return s.curBlock.NewValue2(ssa.OpSub, a.Type, nil, a, b) + case OLSH: + a := s.expr(n.Left) + b := s.expr(n.Right) + return s.curBlock.NewValue2(ssa.OpLsh, a.Type, nil, a, b) + case ORSH: + a := s.expr(n.Left) + b := s.expr(n.Right) + return s.curBlock.NewValue2(ssa.OpRsh, a.Type, nil, a, b) case OADDR: return s.addr(n.Left) @@ -519,25 +527,15 @@ type branch struct { // gcargs and gclocals are filled in with pointer maps for the frame. func genssa(f *ssa.Func, ptxt *obj.Prog, gcargs, gclocals *Sym) { // TODO: line numbers - // TODO: layout frame - stkSize := int64(64) - - if Hasdefer != 0 { - // deferreturn pretends to have one uintptr argument. - // Reserve space for it so stack scanner is happy. - if Maxarg < int64(Widthptr) { - Maxarg = int64(Widthptr) - } - } - if stkSize+Maxarg > 1<<31 { + + if f.FrameSize > 1<<31 { Yyerror("stack frame too large (>2GB)") return } - frameSize := stkSize + Maxarg ptxt.To.Type = obj.TYPE_TEXTSIZE ptxt.To.Val = int32(Rnd(Curfn.Type.Argwid, int64(Widthptr))) // arg size - ptxt.To.Offset = frameSize - 8 // TODO: arch-dependent + ptxt.To.Offset = f.FrameSize - 8 // TODO: arch-dependent // Remember where each block starts. bstart := make([]*obj.Prog, f.NumBlocks()) @@ -551,7 +549,7 @@ func genssa(f *ssa.Func, ptxt *obj.Prog, gcargs, gclocals *Sym) { bstart[b.ID] = Pc // Emit values in block for _, v := range b.Values { - genValue(v, frameSize) + genValue(v) } // Emit control flow instructions for block var next *ssa.Block @@ -578,7 +576,7 @@ func genssa(f *ssa.Func, ptxt *obj.Prog, gcargs, gclocals *Sym) { liveness(Curfn, ptxt, gcargs, gclocals) } -func genValue(v *ssa.Value, frameSize int64) { +func genValue(v *ssa.Value) { switch v.Op { case ssa.OpADDQ: // TODO: use addq instead of leaq if target is in the right register. @@ -589,7 +587,7 @@ func genValue(v *ssa.Value, frameSize int64) { p.From.Index = regnum(v.Args[1]) p.To.Type = obj.TYPE_REG p.To.Reg = regnum(v) - case ssa.OpADDCQ: + case ssa.OpADDQconst: // TODO: use addq instead of leaq if target is in the right register. p := Prog(x86.ALEAQ) p.From.Type = obj.TYPE_MEM @@ -597,7 +595,17 @@ func genValue(v *ssa.Value, frameSize int64) { p.From.Offset = v.Aux.(int64) p.To.Type = obj.TYPE_REG p.To.Reg = regnum(v) - case ssa.OpSUBCQ: + case ssa.OpMULQconst: + // TODO: this isn't right. doasm fails on it. I don't think obj + // has ever been taught to compile imul $c, r1, r2. + p := Prog(x86.AIMULQ) + p.From.Type = obj.TYPE_CONST + p.From.Offset = v.Aux.(int64) + p.From3.Type = obj.TYPE_REG + p.From3.Reg = regnum(v.Args[0]) + p.To.Type = obj.TYPE_REG + p.To.Reg = regnum(v) + case ssa.OpSUBQconst: // This code compensates for the fact that the register allocator // doesn't understand 2-address instructions yet. TODO: fix that. x := regnum(v.Args[0]) @@ -615,13 +623,38 @@ func genValue(v *ssa.Value, frameSize int64) { p.From.Offset = v.Aux.(int64) p.To.Type = obj.TYPE_REG p.To.Reg = r + case ssa.OpSHLQconst: + x := regnum(v.Args[0]) + r := regnum(v) + if x != r { + p := Prog(x86.AMOVQ) + p.From.Type = obj.TYPE_REG + p.From.Reg = x + p.To.Type = obj.TYPE_REG + p.To.Reg = r + x = r + } + p := Prog(x86.ASHLQ) + p.From.Type = obj.TYPE_CONST + p.From.Offset = v.Aux.(int64) + p.To.Type = obj.TYPE_REG + p.To.Reg = r + case ssa.OpLEAQ: + p := Prog(x86.ALEAQ) + p.From.Type = obj.TYPE_MEM + p.From.Reg = regnum(v.Args[0]) + p.From.Scale = 1 + p.From.Index = regnum(v.Args[1]) + p.From.Offset = v.Aux.(int64) + p.To.Type = obj.TYPE_REG + p.To.Reg = regnum(v) case ssa.OpCMPQ: p := Prog(x86.ACMPQ) p.From.Type = obj.TYPE_REG p.From.Reg = regnum(v.Args[0]) p.To.Type = obj.TYPE_REG p.To.Reg = regnum(v.Args[1]) - case ssa.OpCMPCQ: + case ssa.OpCMPQconst: p := Prog(x86.ACMPQ) p.From.Type = obj.TYPE_REG p.From.Reg = regnum(v.Args[0]) @@ -643,38 +676,22 @@ func genValue(v *ssa.Value, frameSize int64) { case ssa.OpMOVQload: p := Prog(x86.AMOVQ) p.From.Type = obj.TYPE_MEM - if v.Block.Func.RegAlloc[v.Args[0].ID].Name() == "FP" { - // TODO: do the fp/sp adjustment somewhere else? - p.From.Reg = x86.REG_SP - p.From.Offset = v.Aux.(int64) + frameSize - } else { - p.From.Reg = regnum(v.Args[0]) - p.From.Offset = v.Aux.(int64) - } + p.From.Reg = regnum(v.Args[0]) + p.From.Offset = v.Aux.(int64) p.To.Type = obj.TYPE_REG p.To.Reg = regnum(v) case ssa.OpMOVBload: p := Prog(x86.AMOVB) p.From.Type = obj.TYPE_MEM - if v.Block.Func.RegAlloc[v.Args[0].ID].Name() == "FP" { - p.From.Reg = x86.REG_SP - p.From.Offset = v.Aux.(int64) + frameSize - } else { - p.From.Reg = regnum(v.Args[0]) - p.From.Offset = v.Aux.(int64) - } + p.From.Reg = regnum(v.Args[0]) + p.From.Offset = v.Aux.(int64) p.To.Type = obj.TYPE_REG p.To.Reg = regnum(v) case ssa.OpMOVQloadidx8: p := Prog(x86.AMOVQ) p.From.Type = obj.TYPE_MEM - if v.Block.Func.RegAlloc[v.Args[0].ID].Name() == "FP" { - p.From.Reg = x86.REG_SP - p.From.Offset = v.Aux.(int64) + frameSize - } else { - p.From.Reg = regnum(v.Args[0]) - p.From.Offset = v.Aux.(int64) - } + p.From.Reg = regnum(v.Args[0]) + p.From.Offset = v.Aux.(int64) p.From.Scale = 8 p.From.Index = regnum(v.Args[1]) p.To.Type = obj.TYPE_REG @@ -684,13 +701,8 @@ func genValue(v *ssa.Value, frameSize int64) { p.From.Type = obj.TYPE_REG p.From.Reg = regnum(v.Args[1]) p.To.Type = obj.TYPE_MEM - if v.Block.Func.RegAlloc[v.Args[0].ID].Name() == "FP" { - p.To.Reg = x86.REG_SP - p.To.Offset = v.Aux.(int64) + frameSize - } else { - p.To.Reg = regnum(v.Args[0]) - p.To.Offset = v.Aux.(int64) - } + p.To.Reg = regnum(v.Args[0]) + p.To.Offset = v.Aux.(int64) case ssa.OpCopy: x := regnum(v.Args[0]) y := regnum(v) @@ -705,7 +717,7 @@ func genValue(v *ssa.Value, frameSize int64) { p := Prog(x86.AMOVQ) p.From.Type = obj.TYPE_MEM p.From.Reg = x86.REG_SP - p.From.Offset = frameSize - localOffset(v.Args[0]) + p.From.Offset = localOffset(v.Args[0]) p.To.Type = obj.TYPE_REG p.To.Reg = regnum(v) case ssa.OpStoreReg8: @@ -714,7 +726,7 @@ func genValue(v *ssa.Value, frameSize int64) { p.From.Reg = regnum(v.Args[0]) p.To.Type = obj.TYPE_MEM p.To.Reg = x86.REG_SP - p.To.Offset = frameSize - localOffset(v) + p.To.Offset = localOffset(v) case ssa.OpPhi: // just check to make sure regalloc did it right f := v.Block.Func @@ -740,10 +752,15 @@ func genValue(v *ssa.Value, frameSize int64) { p.From.Offset = g.Offset p.To.Type = obj.TYPE_REG p.To.Reg = regnum(v) + case ssa.OpStaticCall: + p := Prog(obj.ACALL) + p.To.Type = obj.TYPE_MEM + p.To.Name = obj.NAME_EXTERN + p.To.Sym = Linksym(v.Aux.(*Sym)) case ssa.OpFP, ssa.OpSP: // nothing to do default: - log.Fatalf("value %s not implemented yet", v.LongString()) + log.Fatalf("value %s not implemented", v.LongString()) } } @@ -757,6 +774,12 @@ func genBlock(b, next *ssa.Block, branches []branch) []branch { } case ssa.BlockExit: Prog(obj.ARET) + case ssa.BlockCall: + if b.Succs[0] != next { + p := Prog(obj.AJMP) + p.To.Type = obj.TYPE_BRANCH + branches = append(branches, branch{p, b.Succs[0]}) + } case ssa.BlockEQ: if b.Succs[0] == next { p := Prog(x86.AJNE) @@ -844,7 +867,7 @@ func genBlock(b, next *ssa.Block, branches []branch) []branch { } default: - log.Fatalf("branch %s not implemented yet", b.LongString()) + log.Fatalf("branch %s not implemented", b.LongString()) } return branches } diff --git a/src/cmd/internal/ssa/TODO b/src/cmd/internal/ssa/TODO new file mode 100644 index 0000000000..afb723ae4c --- /dev/null +++ b/src/cmd/internal/ssa/TODO @@ -0,0 +1,47 @@ +This is a list of things that need to be worked on. It is by no means complete. + +Allocation +- Allocation of decls in stackalloc. Decls survive if they are + addrtaken or are too large for registerization. + +Scheduling + - Make sure loads are scheduled correctly with respect to stores. + Same for flag type values. We can't have more than one value of + mem or flag types live at once. + - Reduce register pressure. Schedule instructions which kill + variables first. + +Values + - Add a line number field. Figure out how to populate it and + maintain it during rewrites. + - Store *Type instead of Type? Keep an array of used Types in Func + and reference by id? Unify with the type ../gc so we just use a + pointer instead of an interface? + - Recycle dead values instead of using GC to do that. + - A lot of Aux fields are just int64. Add a separate AuxInt field? + If not that, then cache the interfaces that wrap int64s. + - OpStore uses 3 args. Increase the size of argstorage to 3? + +Opcodes + - Rename ops to prevent cross-arch conflicts. MOVQ -> MOVQamd64 (or + MOVQ6?). Other option: build opcode table in Config instead of globally. + - Remove asm string from opinfo, no longer needed. + - It's annoying to list the opcode both in the opcode list and an + opInfo map entry. Specify it one place and use go:generate to + produce both? + +Regalloc + - Make less arch-dependent + - Don't spill everything at every basic block boundary. + - Allow args and return values to be ssa-able. + - Handle 2-address instructions. + +Rewrites + - Strength reduction (both arch-indep and arch-dependent?) + - Code sequence for shifts >= wordsize + - Start another architecture (arm?) + +Common-Subexpression Elimination + - Make better decision about which value in an equivalence class we should + choose to replace other values in that class. + - Can we move control values out of their basic block? diff --git a/src/cmd/internal/ssa/func.go b/src/cmd/internal/ssa/func.go index bdc8815e1a..3e41ef3bc1 100644 --- a/src/cmd/internal/ssa/func.go +++ b/src/cmd/internal/ssa/func.go @@ -17,6 +17,8 @@ type Func struct { // when register allocation is done, maps value ids to locations RegAlloc []Location + // when stackalloc is done, the size of the stack frame + FrameSize int64 } // NumBlocks returns an integer larger than the id of any Block in the Func. diff --git a/src/cmd/internal/ssa/location.go b/src/cmd/internal/ssa/location.go index 528956e681..1b6f6d66c1 100644 --- a/src/cmd/internal/ssa/location.go +++ b/src/cmd/internal/ssa/location.go @@ -26,19 +26,9 @@ func (r *Register) Name() string { // 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) + Idx int64 // offset in locals area (distance up from SP) } func (s *LocalSlot) Name() string { - return fmt.Sprintf("-%d(FP)", 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 + return fmt.Sprintf("%d(SP)", s.Idx) } diff --git a/src/cmd/internal/ssa/lower.go b/src/cmd/internal/ssa/lower.go index 84379c00de..44f0b83fa8 100644 --- a/src/cmd/internal/ssa/lower.go +++ b/src/cmd/internal/ssa/lower.go @@ -4,6 +4,8 @@ package ssa +import "log" + //go:generate go run rulegen/rulegen.go rulegen/lower_amd64.rules lowerAmd64 lowerAmd64.go // convert to machine-dependent ops @@ -11,7 +13,14 @@ func lower(f *Func) { // repeat rewrites until we find no more rewrites applyRewrite(f, f.Config.lower) - // TODO: check for unlowered opcodes, fail if we find one + // Check for unlowered opcodes, fail if we find one. + for _, b := range f.Blocks { + for _, v := range b.Values { + if v.Op < OpGenericEnd && v.Op != OpFP && v.Op != OpSP && v.Op != OpArg && v.Op != OpCopy && v.Op != OpPhi { + log.Panicf("%s not lowered", v.LongString()) + } + } + } // additional pass for 386/amd64, link condition codes directly to blocks // TODO: do generically somehow? Special "block" rewrite rules? diff --git a/src/cmd/internal/ssa/lowerAmd64.go b/src/cmd/internal/ssa/lowerAmd64.go index 356f646dcc..51cef97b30 100644 --- a/src/cmd/internal/ssa/lowerAmd64.go +++ b/src/cmd/internal/ssa/lowerAmd64.go @@ -4,98 +4,57 @@ package ssa func lowerAmd64(v *Value) bool { switch v.Op { - case OpADDCQ: - // match: (ADDCQ [c] (LEAQ8 [d] x y)) - // cond: - // result: (LEAQ8 [addOff(c, d)] x y) - { - c := v.Aux - if v.Args[0].Op != OpLEAQ8 { - goto end3bc1457811adc0cb81ad6b88a7461c60 - } - d := v.Args[0].Aux - x := v.Args[0].Args[0] - y := v.Args[0].Args[1] - v.Op = OpLEAQ8 - v.Aux = nil - v.resetArgs() - v.Aux = addOff(c, d) - v.AddArg(x) - v.AddArg(y) - return true - } - goto end3bc1457811adc0cb81ad6b88a7461c60 - end3bc1457811adc0cb81ad6b88a7461c60: - ; - // match: (ADDCQ [off] x) - // cond: off.(int64) == 0 - // result: (Copy x) - { - off := v.Aux - x := v.Args[0] - if !(off.(int64) == 0) { - goto end6710a6679c47b70577ecea7ad00dae87 - } - v.Op = OpCopy - v.Aux = nil - v.resetArgs() - v.AddArg(x) - return true - } - goto end6710a6679c47b70577ecea7ad00dae87 - end6710a6679c47b70577ecea7ad00dae87: - ; case OpADDQ: // match: (ADDQ x (MOVQconst [c])) // cond: - // result: (ADDCQ [c] x) + // result: (ADDQconst [c] x) { x := v.Args[0] if v.Args[1].Op != OpMOVQconst { - goto end39b79e84f20a6d44b5c4136aae220ac2 + goto endacffd55e74ee0ff59ad58a18ddfc9973 } c := v.Args[1].Aux - v.Op = OpADDCQ + v.Op = OpADDQconst v.Aux = nil v.resetArgs() v.Aux = c v.AddArg(x) return true } - goto end39b79e84f20a6d44b5c4136aae220ac2 - end39b79e84f20a6d44b5c4136aae220ac2: + goto endacffd55e74ee0ff59ad58a18ddfc9973 + endacffd55e74ee0ff59ad58a18ddfc9973: ; // match: (ADDQ (MOVQconst [c]) x) // cond: - // result: (ADDCQ [c] x) + // result: (ADDQconst [c] x) { if v.Args[0].Op != OpMOVQconst { - goto endc05ff5a2a132241b69d00c852001d820 + goto end7166f476d744ab7a51125959d3d3c7e2 } c := v.Args[0].Aux x := v.Args[1] - v.Op = OpADDCQ + v.Op = OpADDQconst v.Aux = nil v.resetArgs() v.Aux = c v.AddArg(x) return true } - goto endc05ff5a2a132241b69d00c852001d820 - endc05ff5a2a132241b69d00c852001d820: + goto end7166f476d744ab7a51125959d3d3c7e2 + end7166f476d744ab7a51125959d3d3c7e2: ; - // match: (ADDQ x (SHLCQ [shift] y)) + // match: (ADDQ x (SHLQconst [shift] y)) // cond: shift.(int64) == 3 // result: (LEAQ8 [int64(0)] x y) { x := v.Args[0] - if v.Args[1].Op != OpSHLCQ { - goto end7fa0d837edd248748cef516853fd9475 + if v.Args[1].Op != OpSHLQconst { + goto endaf4f724e1e17f2b116d336c07da0165d } shift := v.Args[1].Aux y := v.Args[1].Args[0] if !(shift.(int64) == 3) { - goto end7fa0d837edd248748cef516853fd9475 + goto endaf4f724e1e17f2b116d336c07da0165d } v.Op = OpLEAQ8 v.Aux = nil @@ -105,8 +64,49 @@ func lowerAmd64(v *Value) bool { v.AddArg(y) return true } - goto end7fa0d837edd248748cef516853fd9475 - end7fa0d837edd248748cef516853fd9475: + goto endaf4f724e1e17f2b116d336c07da0165d + endaf4f724e1e17f2b116d336c07da0165d: + ; + case OpADDQconst: + // match: (ADDQconst [c] (LEAQ8 [d] x y)) + // cond: + // result: (LEAQ8 [addOff(c, d)] x y) + { + c := v.Aux + if v.Args[0].Op != OpLEAQ8 { + goto ende2cc681c9abf9913288803fb1b39e639 + } + d := v.Args[0].Aux + x := v.Args[0].Args[0] + y := v.Args[0].Args[1] + v.Op = OpLEAQ8 + v.Aux = nil + v.resetArgs() + v.Aux = addOff(c, d) + v.AddArg(x) + v.AddArg(y) + return true + } + goto ende2cc681c9abf9913288803fb1b39e639 + ende2cc681c9abf9913288803fb1b39e639: + ; + // match: (ADDQconst [off] x) + // cond: off.(int64) == 0 + // result: (Copy x) + { + off := v.Aux + x := v.Args[0] + if !(off.(int64) == 0) { + goto endfa1c7cc5ac4716697e891376787f86ce + } + v.Op = OpCopy + v.Aux = nil + v.resetArgs() + v.AddArg(x) + return true + } + goto endfa1c7cc5ac4716697e891376787f86ce + endfa1c7cc5ac4716697e891376787f86ce: ; case OpAdd: // match: (Add x y) @@ -152,44 +152,44 @@ func lowerAmd64(v *Value) bool { case OpCMPQ: // match: (CMPQ x (MOVQconst [c])) // cond: - // result: (CMPCQ x [c]) + // result: (CMPQconst x [c]) { x := v.Args[0] if v.Args[1].Op != OpMOVQconst { - goto endf180bae15b3d24c0213520d7f7aa98b4 + goto end32ef1328af280ac18fa8045a3502dae9 } c := v.Args[1].Aux - v.Op = OpCMPCQ + v.Op = OpCMPQconst v.Aux = nil v.resetArgs() v.AddArg(x) v.Aux = c return true } - goto endf180bae15b3d24c0213520d7f7aa98b4 - endf180bae15b3d24c0213520d7f7aa98b4: + goto end32ef1328af280ac18fa8045a3502dae9 + end32ef1328af280ac18fa8045a3502dae9: ; // match: (CMPQ (MOVQconst [c]) x) // cond: - // result: (InvertFlags (CMPCQ x [c])) + // result: (InvertFlags (CMPQconst x [c])) { if v.Args[0].Op != OpMOVQconst { - goto end8fc58bffa73b3df80b3de72c91844884 + goto endf8ca12fe79290bc82b11cfa463bc9413 } c := v.Args[0].Aux x := v.Args[1] v.Op = OpInvertFlags v.Aux = nil v.resetArgs() - v0 := v.Block.NewValue(OpCMPCQ, TypeInvalid, nil) + v0 := v.Block.NewValue(OpCMPQconst, TypeInvalid, nil) v0.Type = TypeFlags v0.AddArg(x) v0.Aux = c v.AddArg(v0) return true } - goto end8fc58bffa73b3df80b3de72c91844884 - end8fc58bffa73b3df80b3de72c91844884: + goto endf8ca12fe79290bc82b11cfa463bc9413 + endf8ca12fe79290bc82b11cfa463bc9413: ; case OpConst: // match: (Const [val]) @@ -330,14 +330,35 @@ func lowerAmd64(v *Value) bool { goto end581ce5a20901df1b8143448ba031685b end581ce5a20901df1b8143448ba031685b: ; + case OpLsh: + // match: (Lsh x y) + // cond: is64BitInt(t) + // result: (SHLQ x y) + { + t := v.Type + x := v.Args[0] + y := v.Args[1] + if !(is64BitInt(t)) { + goto end9f05c9539e51db6ad557989e0c822e9b + } + v.Op = OpSHLQ + v.Aux = nil + v.resetArgs() + v.AddArg(x) + v.AddArg(y) + return true + } + goto end9f05c9539e51db6ad557989e0c822e9b + end9f05c9539e51db6ad557989e0c822e9b: + ; case OpMOVQload: - // match: (MOVQload [off1] (ADDCQ [off2] ptr) mem) + // match: (MOVQload [off1] (ADDQconst [off2] ptr) mem) // cond: // result: (MOVQload [addOff(off1, off2)] ptr mem) { off1 := v.Aux - if v.Args[0].Op != OpADDCQ { - goto end218ceec16b8299d573d3c9ccaa69b086 + if v.Args[0].Op != OpADDQconst { + goto end843d29b538c4483b432b632e5666d6e3 } off2 := v.Args[0].Aux ptr := v.Args[0].Args[0] @@ -350,8 +371,8 @@ func lowerAmd64(v *Value) bool { v.AddArg(mem) return true } - goto end218ceec16b8299d573d3c9ccaa69b086 - end218ceec16b8299d573d3c9ccaa69b086: + goto end843d29b538c4483b432b632e5666d6e3 + end843d29b538c4483b432b632e5666d6e3: ; // match: (MOVQload [off1] (LEAQ8 [off2] ptr idx) mem) // cond: @@ -378,13 +399,13 @@ func lowerAmd64(v *Value) bool { end02f5ad148292c46463e7c20d3b821735: ; case OpMOVQloadidx8: - // match: (MOVQloadidx8 [off1] (ADDCQ [off2] ptr) idx mem) + // match: (MOVQloadidx8 [off1] (ADDQconst [off2] ptr) idx mem) // cond: // result: (MOVQloadidx8 [addOff(off1, off2)] ptr idx mem) { off1 := v.Aux - if v.Args[0].Op != OpADDCQ { - goto ende47e8d742e2615f39fb6509a5749e414 + if v.Args[0].Op != OpADDQconst { + goto ende81e44bcfb11f90916ccb440c590121f } off2 := v.Args[0].Aux ptr := v.Args[0].Args[0] @@ -399,17 +420,17 @@ func lowerAmd64(v *Value) bool { v.AddArg(mem) return true } - goto ende47e8d742e2615f39fb6509a5749e414 - ende47e8d742e2615f39fb6509a5749e414: + goto ende81e44bcfb11f90916ccb440c590121f + ende81e44bcfb11f90916ccb440c590121f: ; case OpMOVQstore: - // match: (MOVQstore [off1] (ADDCQ [off2] ptr) val mem) + // match: (MOVQstore [off1] (ADDQconst [off2] ptr) val mem) // cond: // result: (MOVQstore [addOff(off1, off2)] ptr val mem) { off1 := v.Aux - if v.Args[0].Op != OpADDCQ { - goto enddfd4c7a20fd3b84eb9dcf84b98c661fc + if v.Args[0].Op != OpADDQconst { + goto end2108c693a43c79aed10b9246c39c80aa } off2 := v.Args[0].Aux ptr := v.Args[0].Args[0] @@ -424,8 +445,8 @@ func lowerAmd64(v *Value) bool { v.AddArg(mem) return true } - goto enddfd4c7a20fd3b84eb9dcf84b98c661fc - enddfd4c7a20fd3b84eb9dcf84b98c661fc: + goto end2108c693a43c79aed10b9246c39c80aa + end2108c693a43c79aed10b9246c39c80aa: ; // match: (MOVQstore [off1] (LEAQ8 [off2] ptr idx) val mem) // cond: @@ -454,13 +475,13 @@ func lowerAmd64(v *Value) bool { endce1db8c8d37c8397c500a2068a65c215: ; case OpMOVQstoreidx8: - // match: (MOVQstoreidx8 [off1] (ADDCQ [off2] ptr) idx val mem) + // match: (MOVQstoreidx8 [off1] (ADDQconst [off2] ptr) idx val mem) // cond: // result: (MOVQstoreidx8 [addOff(off1, off2)] ptr idx val mem) { off1 := v.Aux - if v.Args[0].Op != OpADDCQ { - goto endcdb222707a568ad468f7fff2fc42fc39 + if v.Args[0].Op != OpADDQconst { + goto end01c970657b0fdefeab82458c15022163 } off2 := v.Args[0].Aux ptr := v.Args[0].Args[0] @@ -477,67 +498,89 @@ func lowerAmd64(v *Value) bool { v.AddArg(mem) return true } - goto endcdb222707a568ad468f7fff2fc42fc39 - endcdb222707a568ad468f7fff2fc42fc39: - ; - case OpMULCQ: - // match: (MULCQ [c] x) - // cond: c.(int64) == 8 - // result: (SHLCQ [int64(3)] x) - { - c := v.Aux - x := v.Args[0] - if !(c.(int64) == 8) { - goto end90a1c055d9658aecacce5e101c1848b4 - } - v.Op = OpSHLCQ - v.Aux = nil - v.resetArgs() - v.Aux = int64(3) - v.AddArg(x) - return true - } - goto end90a1c055d9658aecacce5e101c1848b4 - end90a1c055d9658aecacce5e101c1848b4: + goto end01c970657b0fdefeab82458c15022163 + end01c970657b0fdefeab82458c15022163: ; case OpMULQ: // match: (MULQ x (MOVQconst [c])) - // cond: - // result: (MULCQ [c] x) + // cond: c.(int64) == int64(int32(c.(int64))) + // result: (MULQconst [c] x) { x := v.Args[0] if v.Args[1].Op != OpMOVQconst { - goto endce35d001482ea209e62e9394bd07c7cb + goto ende8c09b194fcde7d9cdc69f2deff86304 } c := v.Args[1].Aux - v.Op = OpMULCQ + if !(c.(int64) == int64(int32(c.(int64)))) { + goto ende8c09b194fcde7d9cdc69f2deff86304 + } + v.Op = OpMULQconst v.Aux = nil v.resetArgs() v.Aux = c v.AddArg(x) return true } - goto endce35d001482ea209e62e9394bd07c7cb - endce35d001482ea209e62e9394bd07c7cb: + goto ende8c09b194fcde7d9cdc69f2deff86304 + ende8c09b194fcde7d9cdc69f2deff86304: ; // match: (MULQ (MOVQconst [c]) x) // cond: - // result: (MULCQ [c] x) + // result: (MULQconst [c] x) { if v.Args[0].Op != OpMOVQconst { - goto end804f58b1f6a7cce19d48379999ec03f1 + goto endc6e18d6968175d6e58eafa6dcf40c1b8 } c := v.Args[0].Aux x := v.Args[1] - v.Op = OpMULCQ + v.Op = OpMULQconst v.Aux = nil v.resetArgs() v.Aux = c v.AddArg(x) return true } - goto end804f58b1f6a7cce19d48379999ec03f1 - end804f58b1f6a7cce19d48379999ec03f1: + goto endc6e18d6968175d6e58eafa6dcf40c1b8 + endc6e18d6968175d6e58eafa6dcf40c1b8: + ; + case OpMULQconst: + // match: (MULQconst [c] x) + // cond: c.(int64) == 8 + // result: (SHLQconst [int64(3)] x) + { + c := v.Aux + x := v.Args[0] + if !(c.(int64) == 8) { + goto end7e16978c56138324ff2abf91fd6d94d4 + } + v.Op = OpSHLQconst + v.Aux = nil + v.resetArgs() + v.Aux = int64(3) + v.AddArg(x) + return true + } + goto end7e16978c56138324ff2abf91fd6d94d4 + end7e16978c56138324ff2abf91fd6d94d4: + ; + // match: (MULQconst [c] x) + // cond: c.(int64) == 64 + // result: (SHLQconst [int64(5)] x) + { + c := v.Aux + x := v.Args[0] + if !(c.(int64) == 64) { + goto end2c7a02f230e4b311ac3a4e22f70a4f08 + } + v.Op = OpSHLQconst + v.Aux = nil + v.resetArgs() + v.Aux = int64(5) + v.AddArg(x) + return true + } + goto end2c7a02f230e4b311ac3a4e22f70a4f08 + end2c7a02f230e4b311ac3a4e22f70a4f08: ; case OpMove: // match: (Move [size] dst src mem) @@ -587,19 +630,19 @@ func lowerAmd64(v *Value) bool { case OpOffPtr: // match: (OffPtr [off] ptr) // cond: - // result: (ADDCQ [off] ptr) + // result: (ADDQconst [off] ptr) { off := v.Aux ptr := v.Args[0] - v.Op = OpADDCQ + v.Op = OpADDQconst v.Aux = nil v.resetArgs() v.Aux = off v.AddArg(ptr) return true } - goto endfe8f713b1d237a23311fb721ee46bedb - endfe8f713b1d237a23311fb721ee46bedb: + goto end0429f947ee7ac49ff45a243e461a5290 + end0429f947ee7ac49ff45a243e461a5290: ; case OpSETL: // match: (SETL (InvertFlags x)) @@ -619,48 +662,68 @@ func lowerAmd64(v *Value) bool { goto end456c7681d48305698c1ef462d244bdc6 end456c7681d48305698c1ef462d244bdc6: ; + case OpSHLQ: + // match: (SHLQ x (MOVQconst [c])) + // cond: + // result: (SHLQconst [c] x) + { + x := v.Args[0] + if v.Args[1].Op != OpMOVQconst { + goto endcca412bead06dc3d56ef034a82d184d6 + } + c := v.Args[1].Aux + v.Op = OpSHLQconst + v.Aux = nil + v.resetArgs() + v.Aux = c + v.AddArg(x) + return true + } + goto endcca412bead06dc3d56ef034a82d184d6 + endcca412bead06dc3d56ef034a82d184d6: + ; case OpSUBQ: // match: (SUBQ x (MOVQconst [c])) // cond: - // result: (SUBCQ x [c]) + // result: (SUBQconst x [c]) { x := v.Args[0] if v.Args[1].Op != OpMOVQconst { - goto endc96cd1cb2dd98427c34fb9543feca4fe + goto end5a74a63bd9ad15437717c6df3b25eebb } c := v.Args[1].Aux - v.Op = OpSUBCQ + v.Op = OpSUBQconst v.Aux = nil v.resetArgs() v.AddArg(x) v.Aux = c return true } - goto endc96cd1cb2dd98427c34fb9543feca4fe - endc96cd1cb2dd98427c34fb9543feca4fe: + goto end5a74a63bd9ad15437717c6df3b25eebb + end5a74a63bd9ad15437717c6df3b25eebb: ; // match: (SUBQ (MOVQconst [c]) x) // cond: - // result: (NEGQ (SUBCQ x [c])) + // result: (NEGQ (SUBQconst x [c])) { t := v.Type if v.Args[0].Op != OpMOVQconst { - goto end900aaaf28cefac6bb62e76b5151611cf + goto end78e66b6fc298684ff4ac8aec5ce873c9 } c := v.Args[0].Aux x := v.Args[1] v.Op = OpNEGQ v.Aux = nil v.resetArgs() - v0 := v.Block.NewValue(OpSUBCQ, TypeInvalid, nil) + v0 := v.Block.NewValue(OpSUBQconst, TypeInvalid, nil) v0.Type = t v0.AddArg(x) v0.Aux = c v.AddArg(v0) return true } - goto end900aaaf28cefac6bb62e76b5151611cf - end900aaaf28cefac6bb62e76b5151611cf: + goto end78e66b6fc298684ff4ac8aec5ce873c9 + end78e66b6fc298684ff4ac8aec5ce873c9: ; case OpStore: // match: (Store ptr val mem) diff --git a/src/cmd/internal/ssa/op.go b/src/cmd/internal/ssa/op.go index e0dc531fc9..f02c1ae0c0 100644 --- a/src/cmd/internal/ssa/op.go +++ b/src/cmd/internal/ssa/op.go @@ -34,6 +34,8 @@ const ( OpAdd // arg0 + arg1 OpSub // arg0 - arg1 OpMul // arg0 * arg1 + OpLsh // arg0 << arg1 + OpRsh // arg0 >> arg1 (signed/unsigned depending on signedness of type) // 2-input comparisons OpLess // arg0 < arg1 @@ -83,10 +85,6 @@ const ( OpOffPtr // arg0 + aux.(int64) (arg0 and result are pointers) - // These ops return a pointer to a location on the stack. - OpFPAddr // FP + aux.(int64) (+ == args from caller, - == locals) - OpSPAddr // SP + aux.(int64) - // 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 without memory @@ -96,6 +94,8 @@ const ( // used during ssa construction. Like OpCopy, but the arg has not been specified yet. OpFwdRef + + OpGenericEnd ) // GlobalOffset represents a fixed offset within a global variable diff --git a/src/cmd/internal/ssa/op_string.go b/src/cmd/internal/ssa/op_string.go index 9b22f664ef..c8f27bb2e4 100644 --- a/src/cmd/internal/ssa/op_string.go +++ b/src/cmd/internal/ssa/op_string.go @@ -6,16 +6,16 @@ import "fmt" const ( _Op_name_0 = "opInvalid" - _Op_name_1 = "opGenericBaseOpAddOpSubOpMulOpLessOpConstOpArgOpGlobalOpFuncOpFPOpSPOpCopyOpMoveOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpLoadOpStoreOpArrayIndexOpPtrIndexOpIsNonNilOpIsInBoundsOpCallOpStaticCallOpConvertOpConvNopOpOffPtrOpFPAddrOpSPAddrOpStoreReg8OpLoadReg8OpFwdRef" - _Op_name_2 = "opAMD64BaseOpADDQOpSUBQOpADDCQOpSUBCQOpMULQOpMULCQOpSHLQOpSHLCQOpNEGQOpADDLOpCMPQOpCMPCQOpTESTQOpTESTBOpSETEQOpSETNEOpSETLOpSETGEOpSETBOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpLEAQglobalOpMOVBloadOpMOVBQZXloadOpMOVBQSXloadOpMOVQloadOpMOVQstoreOpMOVQloadidx8OpMOVQstoreidx8OpMOVQloadglobalOpMOVQstoreglobalOpMOVQconstOpREPMOVSB" + _Op_name_1 = "opGenericBaseOpAddOpSubOpMulOpLshOpRshOpLessOpConstOpArgOpGlobalOpFuncOpFPOpSPOpCopyOpMoveOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpLoadOpStoreOpArrayIndexOpPtrIndexOpIsNonNilOpIsInBoundsOpCallOpStaticCallOpConvertOpConvNopOpOffPtrOpStoreReg8OpLoadReg8OpFwdRefOpGenericEnd" + _Op_name_2 = "opAMD64BaseOpADDQOpADDQconstOpSUBQOpSUBQconstOpMULQOpMULQconstOpSHLQOpSHLQconstOpNEGQOpADDLOpCMPQOpCMPQconstOpTESTQOpTESTBOpSETEQOpSETNEOpSETLOpSETGEOpSETBOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpLEAQglobalOpMOVBloadOpMOVBQZXloadOpMOVBQSXloadOpMOVQloadOpMOVQstoreOpMOVQloadidx8OpMOVQstoreidx8OpMOVQloadglobalOpMOVQstoreglobalOpMOVQconstOpREPMOVSB" _Op_name_3 = "op386Base" _Op_name_4 = "opMax" ) var ( _Op_index_0 = [...]uint8{0, 9} - _Op_index_1 = [...]uint16{0, 13, 18, 23, 28, 34, 41, 46, 54, 60, 64, 68, 74, 80, 85, 96, 106, 116, 126, 138, 149, 160, 166, 173, 185, 195, 205, 217, 223, 235, 244, 253, 261, 269, 277, 288, 298, 306} - _Op_index_2 = [...]uint16{0, 11, 17, 23, 30, 37, 43, 50, 56, 63, 69, 75, 81, 88, 95, 102, 109, 116, 122, 129, 135, 148, 154, 161, 168, 175, 187, 197, 210, 223, 233, 244, 258, 273, 289, 306, 317, 327} + _Op_index_1 = [...]uint16{0, 13, 18, 23, 28, 33, 38, 44, 51, 56, 64, 70, 74, 78, 84, 90, 95, 106, 116, 126, 136, 148, 159, 170, 176, 183, 195, 205, 215, 227, 233, 245, 254, 263, 271, 282, 292, 300, 312} + _Op_index_2 = [...]uint16{0, 11, 17, 28, 34, 45, 51, 62, 68, 79, 85, 91, 97, 108, 115, 122, 129, 136, 142, 149, 155, 168, 174, 181, 188, 195, 207, 217, 230, 243, 253, 264, 278, 293, 309, 326, 337, 347} _Op_index_3 = [...]uint8{0, 9} _Op_index_4 = [...]uint8{0, 5} ) @@ -24,7 +24,7 @@ func (i Op) String() string { switch { case i == 0: return _Op_name_0 - case 1001 <= i && i <= 1037: + case 1001 <= i && i <= 1038: i -= 1001 return _Op_name_1[_Op_index_1[i]:_Op_index_1[i+1]] case 2001 <= i && i <= 2037: diff --git a/src/cmd/internal/ssa/opamd64.go b/src/cmd/internal/ssa/opamd64.go index 46f0a69dfb..46a0069a18 100644 --- a/src/cmd/internal/ssa/opamd64.go +++ b/src/cmd/internal/ssa/opamd64.go @@ -13,24 +13,24 @@ const ( // Q = 64 bit, L = 32 bit, W = 16 bit, B = 8 bit // arithmetic - OpADDQ // arg0 + arg1 - OpSUBQ // arg0 - arg1 - OpADDCQ // arg + aux.(int64) - OpSUBCQ // arg - aux.(int64) - OpMULQ // arg0 * arg1 - OpMULCQ // arg * aux.(int64) - OpSHLQ // arg0 << arg1 - OpSHLCQ // arg << aux.(int64) - OpNEGQ // -arg - OpADDL // arg0 + arg1 + OpADDQ // arg0 + arg1 + OpADDQconst // arg + aux.(int64) + OpSUBQ // arg0 - arg1 + OpSUBQconst // arg - aux.(int64) + OpMULQ // arg0 * arg1 + OpMULQconst // arg * aux.(int64) + OpSHLQ // arg0 << arg1 + OpSHLQconst // arg << aux.(int64) + OpNEGQ // -arg + OpADDL // arg0 + arg1 // Flags value generation. // We pretend the flags type is an opaque thing that comparisons generate // and from which we can extract boolean conditions like <, ==, etc. - OpCMPQ // arg0 compare to arg1 - OpCMPCQ // arg0 compare to aux.(int64) - OpTESTQ // (arg0 & arg1) compare to 0 - OpTESTB // (arg0 & arg1) compare to 0 + OpCMPQ // arg0 compare to arg1 + OpCMPQconst // arg0 compare to aux.(int64) + OpTESTQ // (arg0 & arg1) compare to 0 + OpTESTB // (arg0 & arg1) compare to 0 // These opcodes extract a particular boolean condition from a flags value. OpSETEQ // extract == condition from arg0 @@ -96,7 +96,8 @@ var regsAMD64 = [...]string{ "OVERWRITE0", // the same register as the first input } -var gp regMask = 0x1ffff // all integer registers (including SP&FP) +var gp regMask = 0x1ffff // all integer registers including SP&FP +var gpout regMask = 0xffef // integer registers not including SP&FP var cx regMask = 1 << 1 var si regMask = 1 << 6 var di regMask = 1 << 7 @@ -104,37 +105,37 @@ var flags regMask = 1 << 17 var ( // gp = general purpose (integer) registers - gp21 = [2][]regMask{{gp, gp}, {gp}} // 2 input, 1 output - gp11 = [2][]regMask{{gp}, {gp}} // 1 input, 1 output - gp01 = [2][]regMask{{}, {gp}} // 0 input, 1 output - shift = [2][]regMask{{gp, cx}, {gp}} // shift operations + gp21 = [2][]regMask{{gp, gp}, {gpout}} // 2 input, 1 output + gp11 = [2][]regMask{{gp}, {gpout}} // 1 input, 1 output + gp01 = [2][]regMask{{}, {gpout}} // 0 input, 1 output + shift = [2][]regMask{{gp, cx}, {gpout}} // shift operations gp2_flags = [2][]regMask{{gp, gp}, {flags}} // generate flags from 2 gp regs gp1_flags = [2][]regMask{{gp}, {flags}} // generate flags from 1 gp reg - gpload = [2][]regMask{{gp, 0}, {gp}} - gploadidx = [2][]regMask{{gp, gp, 0}, {gp}} + gpload = [2][]regMask{{gp, 0}, {gpout}} + gploadidx = [2][]regMask{{gp, gp, 0}, {gpout}} gpstore = [2][]regMask{{gp, gp, 0}, {0}} gpstoreidx = [2][]regMask{{gp, gp, gp, 0}, {0}} - gpload_stack = [2][]regMask{{0}, {gp}} + gpload_stack = [2][]regMask{{0}, {gpout}} gpstore_stack = [2][]regMask{{gp, 0}, {0}} ) // Opcodes that appear in an output amd64 program var amd64Table = map[Op]opInfo{ - OpADDQ: {flags: OpFlagCommutative, asm: "ADDQ\t%I0,%I1,%O0", reg: gp21}, // TODO: overwrite - OpADDCQ: {asm: "ADDQ\t$%A,%I0,%O0", reg: gp11}, // aux = int64 constant to add - OpSUBQ: {asm: "SUBQ\t%I0,%I1,%O0", reg: gp21}, - OpSUBCQ: {asm: "SUBQ\t$%A,%I0,%O0", reg: gp11}, - OpMULQ: {asm: "MULQ\t%I0,%I1,%O0", reg: gp21}, - OpMULCQ: {asm: "MULQ\t$%A,%I0,%O0", reg: gp11}, - OpSHLQ: {asm: "SHLQ\t%I0,%I1,%O0", reg: gp21}, - OpSHLCQ: {asm: "SHLQ\t$%A,%I0,%O0", reg: gp11}, - - OpCMPQ: {asm: "CMPQ\t%I0,%I1", reg: gp2_flags}, // compute arg[0]-arg[1] and produce flags - OpCMPCQ: {asm: "CMPQ\t$%A,%I0", reg: gp1_flags}, - OpTESTQ: {asm: "TESTQ\t%I0,%I1", reg: gp2_flags}, - OpTESTB: {asm: "TESTB\t%I0,%I1", reg: gp2_flags}, + OpADDQ: {flags: OpFlagCommutative, asm: "ADDQ\t%I0,%I1,%O0", reg: gp21}, // TODO: overwrite + OpADDQconst: {asm: "ADDQ\t$%A,%I0,%O0", reg: gp11}, // aux = int64 constant to add + OpSUBQ: {asm: "SUBQ\t%I0,%I1,%O0", reg: gp21}, + OpSUBQconst: {asm: "SUBQ\t$%A,%I0,%O0", reg: gp11}, + OpMULQ: {asm: "MULQ\t%I0,%I1,%O0", reg: gp21}, + OpMULQconst: {asm: "IMULQ\t$%A,%I0,%O0", reg: gp11}, + OpSHLQ: {asm: "SHLQ\t%I0,%I1,%O0", reg: gp21}, + OpSHLQconst: {asm: "SHLQ\t$%A,%I0,%O0", reg: gp11}, + + OpCMPQ: {asm: "CMPQ\t%I0,%I1", reg: gp2_flags}, // compute arg[0]-arg[1] and produce flags + OpCMPQconst: {asm: "CMPQ\t$%A,%I0", reg: gp1_flags}, + OpTESTQ: {asm: "TESTQ\t%I0,%I1", reg: gp2_flags}, + OpTESTB: {asm: "TESTB\t%I0,%I1", reg: gp2_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"}, diff --git a/src/cmd/internal/ssa/rewrite.go b/src/cmd/internal/ssa/rewrite.go index 75e910d690..671270d7f2 100644 --- a/src/cmd/internal/ssa/rewrite.go +++ b/src/cmd/internal/ssa/rewrite.go @@ -68,8 +68,9 @@ func typeSize(t Type) int64 { // addOff adds two offset aux values. Each should be an int64. Fails if wraparound happens. func addOff(a, b interface{}) interface{} { - x := a.(int64) - y := b.(int64) + return addOffset(a.(int64), b.(int64)) +} +func addOffset(x, y int64) int64 { z := x + y // x and y have same sign and z has a different sign => overflow if x^y >= 0 && x^z < 0 { diff --git a/src/cmd/internal/ssa/rulegen/lower_amd64.rules b/src/cmd/internal/ssa/rulegen/lower_amd64.rules index 0fed21e740..dc910b70b1 100644 --- a/src/cmd/internal/ssa/rulegen/lower_amd64.rules +++ b/src/cmd/internal/ssa/rulegen/lower_amd64.rules @@ -27,7 +27,7 @@ (Sub x y) && is64BitInt(t) -> (SUBQ x y) (Mul x y) && is64BitInt(t) -> (MULQ x y) - +(Lsh x y) && is64BitInt(t) -> (SHLQ x y) // TODO: check y>63 (Less x y) && is64BitInt(v.Args[0].Type) && isSigned(v.Args[0].Type) -> (SETL (CMPQ x y)) (Load ptr mem) && t.IsBoolean() -> (MOVBload [int64(0)] ptr mem) @@ -40,7 +40,7 @@ (Move [size] dst src mem) -> (REPMOVSB dst src (Const [size.(int64)]) mem) -(OffPtr [off] ptr) -> (ADDCQ [off] ptr) +(OffPtr [off] ptr) -> (ADDQconst [off] ptr) (Const [val]) && is64BitInt(t) -> (MOVQconst [val]) @@ -51,39 +51,41 @@ (Global [sym]) -> (LEAQglobal [GlobalOffset{sym,0}]) // fold constants into instructions -(ADDQ x (MOVQconst [c])) -> (ADDCQ [c] x) // TODO: restrict c to int32 range? -(ADDQ (MOVQconst [c]) x) -> (ADDCQ [c] x) -(SUBQ x (MOVQconst [c])) -> (SUBCQ x [c]) -(SUBQ (MOVQconst [c]) x) -> (NEGQ (SUBCQ x [c])) -(MULQ x (MOVQconst [c])) -> (MULCQ [c] x) -(MULQ (MOVQconst [c]) x) -> (MULCQ [c] x) -(CMPQ x (MOVQconst [c])) -> (CMPCQ x [c]) -(CMPQ (MOVQconst [c]) x) -> (InvertFlags (CMPCQ x [c])) +(ADDQ x (MOVQconst [c])) -> (ADDQconst [c] x) // TODO: restrict c to int32 range? +(ADDQ (MOVQconst [c]) x) -> (ADDQconst [c] x) +(SUBQ x (MOVQconst [c])) -> (SUBQconst x [c]) +(SUBQ (MOVQconst [c]) x) -> (NEGQ (SUBQconst x [c])) +(MULQ x (MOVQconst [c])) && c.(int64) == int64(int32(c.(int64))) -> (MULQconst [c] x) +(MULQ (MOVQconst [c]) x) -> (MULQconst [c] x) +(SHLQ x (MOVQconst [c])) -> (SHLQconst [c] x) +(CMPQ x (MOVQconst [c])) -> (CMPQconst x [c]) +(CMPQ (MOVQconst [c]) x) -> (InvertFlags (CMPQconst x [c])) // strength reduction // TODO: do this a lot more generically -(MULCQ [c] x) && c.(int64) == 8 -> (SHLCQ [int64(3)] x) +(MULQconst [c] x) && c.(int64) == 8 -> (SHLQconst [int64(3)] x) +(MULQconst [c] x) && c.(int64) == 64 -> (SHLQconst [int64(5)] x) // fold add/shift into leaq -(ADDQ x (SHLCQ [shift] y)) && shift.(int64) == 3 -> (LEAQ8 [int64(0)] x y) -(ADDCQ [c] (LEAQ8 [d] x y)) -> (LEAQ8 [addOff(c, d)] x y) +(ADDQ x (SHLQconst [shift] y)) && shift.(int64) == 3 -> (LEAQ8 [int64(0)] x y) +(ADDQconst [c] (LEAQ8 [d] x y)) -> (LEAQ8 [addOff(c, d)] x y) // reverse ordering of compare instruction (SETL (InvertFlags x)) -> (SETGE x) // fold constants into memory operations // Note that this is not always a good idea because if not all the uses of -// the ADDCQ get eliminated, we still have to compute the ADDCQ and we now -// have potentially two live values (ptr and (ADDCQ [off] ptr)) instead of one. +// the ADDQconst get eliminated, we still have to compute the ADDQconst and we now +// have potentially two live values (ptr and (ADDQconst [off] ptr)) instead of one. // Nevertheless, let's do it! -(MOVQload [off1] (ADDCQ [off2] ptr) mem) -> (MOVQload [addOff(off1, off2)] ptr mem) -(MOVQstore [off1] (ADDCQ [off2] ptr) val mem) -> (MOVQstore [addOff(off1, off2)] ptr val mem) +(MOVQload [off1] (ADDQconst [off2] ptr) mem) -> (MOVQload [addOff(off1, off2)] ptr mem) +(MOVQstore [off1] (ADDQconst [off2] ptr) val mem) -> (MOVQstore [addOff(off1, off2)] ptr val mem) // indexed loads and stores (MOVQload [off1] (LEAQ8 [off2] ptr idx) mem) -> (MOVQloadidx8 [addOff(off1, off2)] ptr idx mem) (MOVQstore [off1] (LEAQ8 [off2] ptr idx) val mem) -> (MOVQstoreidx8 [addOff(off1, off2)] ptr idx val mem) -(MOVQloadidx8 [off1] (ADDCQ [off2] ptr) idx mem) -> (MOVQloadidx8 [addOff(off1, off2)] ptr idx mem) -(MOVQstoreidx8 [off1] (ADDCQ [off2] ptr) idx val mem) -> (MOVQstoreidx8 [addOff(off1, off2)] ptr idx val mem) +(MOVQloadidx8 [off1] (ADDQconst [off2] ptr) idx mem) -> (MOVQloadidx8 [addOff(off1, off2)] ptr idx mem) +(MOVQstoreidx8 [off1] (ADDQconst [off2] ptr) idx val mem) -> (MOVQstoreidx8 [addOff(off1, off2)] ptr idx val mem) -(ADDCQ [off] x) && off.(int64) == 0 -> (Copy x) +(ADDQconst [off] x) && off.(int64) == 0 -> (Copy x) diff --git a/src/cmd/internal/ssa/stackalloc.go b/src/cmd/internal/ssa/stackalloc.go index 8a315e1045..ab686470be 100644 --- a/src/cmd/internal/ssa/stackalloc.go +++ b/src/cmd/internal/ssa/stackalloc.go @@ -1,11 +1,22 @@ package ssa +import "log" + // stackalloc allocates storage in the stack frame for // all Values that did not get a register. func stackalloc(f *Func) { home := f.RegAlloc - var n int64 = 8 // 8 = space for return address. TODO: arch-dependent + // First compute the size of the outargs section. + n := int64(16) //TODO: compute max of all callsites + + // Include one slot for deferreturn. + if false && n < f.Config.ptrSize { //TODO: check for deferreturn + n = f.Config.ptrSize + } + + // TODO: group variables by ptr/nonptr, size, etc. Emit ptr vars last + // so stackmap is smaller. // Assign stack locations to phis first, because we // must also assign the same locations to the phi copies @@ -52,10 +63,49 @@ func stackalloc(f *Func) { home = setloc(home, v, loc) } } + + // TODO: align n + n += f.Config.ptrSize // space for return address. TODO: arch-dependent f.RegAlloc = home + f.FrameSize = n // TODO: share stack slots among noninterfering (& gc type compatible) values - // TODO: align final n - // TODO: compute total frame size: n + max paramout space - // TODO: save total size somewhere + + // adjust all uses of FP to SP now that we have the frame size. + var fp *Value + for _, b := range f.Blocks { + for _, v := range b.Values { + if v.Op == OpFP { + if fp != nil { + log.Panicf("multiple FP ops: %s %s", fp, v) + } + fp = v + } + for i, a := range v.Args { + if a.Op != OpFP { + continue + } + // TODO: do this with arch-specific rewrite rules somehow? + switch v.Op { + case OpADDQ: + // (ADDQ (FP) x) -> (LEAQ [n] (SP) x) + v.Op = OpLEAQ + v.Aux = n + case OpLEAQ, OpMOVQload, OpMOVQstore, OpMOVBload, OpMOVQloadidx8: + if v.Op == OpMOVQloadidx8 && i == 1 { + // Note: we could do it, but it is probably an error + log.Panicf("can't do FP->SP adjust on index slot of load %s", v.Op) + } + // eg: (MOVQload [c] (FP) mem) -> (MOVQload [c+n] (SP) mem) + v.Aux = addOffset(v.Aux.(int64), n) + default: + log.Panicf("can't do FP->SP adjust on %s", v.Op) + } + } + } + } + if fp != nil { + fp.Op = OpSP + home[fp.ID] = ®isters[4] // TODO: arch-dependent + } } -- cgit v1.3 From 3d23afb9133c151404635f2476bf895028b972bc Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 12 Aug 2015 11:22:16 -0700 Subject: [dev.ssa] cmd/compile: implement OGETG Change-Id: I7ecf62cf399c710b4a617803c43e83fce09b8a7d Reviewed-on: https://go-review.googlesource.com/13585 Reviewed-by: Keith Randall --- src/cmd/compile/internal/gc/ssa.go | 31 ++++++++++++++++++++++++++ src/cmd/compile/internal/ssa/gen/AMD64.rules | 1 + src/cmd/compile/internal/ssa/gen/AMD64Ops.go | 3 ++- src/cmd/compile/internal/ssa/gen/generic.rules | 2 ++ src/cmd/compile/internal/ssa/gen/genericOps.go | 2 ++ src/cmd/compile/internal/ssa/opGen.go | 10 +++++++++ src/cmd/compile/internal/ssa/rewriteAMD64.go | 14 ++++++++++++ src/cmd/compile/internal/ssa/rewritegeneric.go | 24 ++++++++++++++++++++ src/cmd/internal/obj/x86/obj6.go | 4 ++-- 9 files changed, 88 insertions(+), 3 deletions(-) (limited to 'src/cmd/internal') diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 0086feceab..c8ec01f5b6 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -1340,6 +1340,10 @@ func (s *state) expr(n *Node) *ssa.Value { } a := s.entryNewValue1I(ssa.OpOffPtr, Ptrto(fp.Type), fp.Width, s.sp) return s.newValue2(ssa.OpLoad, fp.Type, a, call) + + case OGETG: + return s.newValue0(ssa.OpGetG, n.Type) + default: s.Unimplementedf("unhandled expr %s", opnames[n.Op]) return nil @@ -2185,6 +2189,33 @@ func genValue(v *ssa.Value) { q.From.Reg = x86.REG_AX q.To.Type = obj.TYPE_MEM q.To.Reg = x86.REG_AX + case ssa.OpAMD64LoweredGetG: + r := regnum(v) + // See the comments in cmd/internal/obj/x86/obj6.go + // near CanUse1InsnTLS for a detailed explanation of these instructions. + if x86.CanUse1InsnTLS(Ctxt) { + // MOVQ (TLS), r + p := Prog(x86.AMOVQ) + p.From.Type = obj.TYPE_MEM + p.From.Reg = x86.REG_TLS + p.To.Type = obj.TYPE_REG + p.To.Reg = r + } else { + // MOVQ TLS, r + // MOVQ (r)(TLS*1), r + p := Prog(x86.AMOVQ) + p.From.Type = obj.TYPE_REG + p.From.Reg = x86.REG_TLS + p.To.Type = obj.TYPE_REG + p.To.Reg = r + q := Prog(x86.AMOVQ) + q.From.Type = obj.TYPE_MEM + q.From.Reg = r + q.From.Index = x86.REG_TLS + q.From.Scale = 1 + q.To.Type = obj.TYPE_REG + q.To.Reg = r + } case ssa.OpAMD64CALLstatic: p := Prog(obj.ACALL) p.To.Type = obj.TYPE_MEM diff --git a/src/cmd/compile/internal/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules index 29f60d9a6b..ab8e44a444 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules @@ -217,6 +217,7 @@ (IsInBounds idx len) -> (SETB (CMPQ idx len)) (PanicNilCheck ptr mem) -> (LoweredPanicNilCheck ptr mem) +(GetG) -> (LoweredGetG) (Move [size] dst src mem) -> (REPMOVSB dst src (MOVQconst [size]) mem) diff --git a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go index 9808745e35..903eea3057 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go +++ b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go @@ -288,8 +288,9 @@ func init() { // InvertFlags is a pseudo-op which can't appear in assembly output. {name: "InvertFlags"}, // reverse direction of arg0 - // LoweredPanicNilCheck is a pseudo-op. + // Pseudo-ops {name: "LoweredPanicNilCheck"}, + {name: "LoweredGetG"}, } var AMD64blocks = []blockData{ diff --git a/src/cmd/compile/internal/ssa/gen/generic.rules b/src/cmd/compile/internal/ssa/gen/generic.rules index 8656b7cc4f..f4f49acb86 100644 --- a/src/cmd/compile/internal/ssa/gen/generic.rules +++ b/src/cmd/compile/internal/ssa/gen/generic.rules @@ -71,6 +71,8 @@ (StringLen (StringMake _ len)) -> len (Store dst str mem) && str.Type.IsString() -> (Store (OffPtr [config.PtrSize] dst) (StringLen str) (Store dst (StringPtr str) mem)) +(If (IsNonNil (GetG)) yes no) -> (Plain nil yes) + (If (Not cond) yes no) -> (If cond no yes) (If (ConstBool {c}) yes no) && c.(bool) -> (Plain nil yes) (If (ConstBool {c}) yes no) && !c.(bool) -> (Plain nil no) diff --git a/src/cmd/compile/internal/ssa/gen/genericOps.go b/src/cmd/compile/internal/ssa/gen/genericOps.go index 6ff5d1ea1a..ec4f038f43 100644 --- a/src/cmd/compile/internal/ssa/gen/genericOps.go +++ b/src/cmd/compile/internal/ssa/gen/genericOps.go @@ -252,7 +252,9 @@ var genericOps = []opData{ {name: "IsNonNil"}, // arg0 != nil {name: "IsInBounds"}, // 0 <= arg0 < arg1 + // Pseudo-ops {name: "PanicNilCheck"}, // trigger a dereference fault; arg0=nil ptr, arg1=mem + {name: "GetG"}, // runtime.getg() (read g pointer) // Indexing operations {name: "ArrayIndex"}, // arg0=array, arg1=index. Returns a[i] diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index d56a8ba81b..425c7e468c 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -195,6 +195,7 @@ const ( OpAMD64REPMOVSB OpAMD64InvertFlags OpAMD64LoweredPanicNilCheck + OpAMD64LoweredGetG OpAdd8 OpAdd16 @@ -369,6 +370,7 @@ const ( OpIsNonNil OpIsInBounds OpPanicNilCheck + OpGetG OpArrayIndex OpPtrIndex OpOffPtr @@ -2119,6 +2121,10 @@ var opcodeTable = [...]opInfo{ name: "LoweredPanicNilCheck", reg: regInfo{}, }, + { + name: "LoweredGetG", + reg: regInfo{}, + }, { name: "Add8", @@ -2812,6 +2818,10 @@ var opcodeTable = [...]opInfo{ name: "PanicNilCheck", generic: true, }, + { + name: "GetG", + generic: true, + }, { name: "ArrayIndex", generic: true, diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go index 2668d570d1..a18097f91e 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -1893,6 +1893,20 @@ func rewriteValueAMD64(v *Value, config *Config) bool { goto endd30ee67afc0284c419cef70261f61452 endd30ee67afc0284c419cef70261f61452: ; + case OpGetG: + // match: (GetG) + // cond: + // result: (LoweredGetG) + { + v.Op = OpAMD64LoweredGetG + v.AuxInt = 0 + v.Aux = nil + v.resetArgs() + return true + } + goto endb17140e71dd641aa4d89e14479160260 + endb17140e71dd641aa4d89e14479160260: + ; case OpGreater16: // match: (Greater16 x y) // cond: diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go index 6371ac2b38..e39305461d 100644 --- a/src/cmd/compile/internal/ssa/rewritegeneric.go +++ b/src/cmd/compile/internal/ssa/rewritegeneric.go @@ -782,6 +782,30 @@ func rewriteValuegeneric(v *Value, config *Config) bool { func rewriteBlockgeneric(b *Block) bool { switch b.Kind { case BlockIf: + // match: (If (IsNonNil (GetG)) yes no) + // cond: + // result: (Plain nil yes) + { + v := b.Control + if v.Op != OpIsNonNil { + goto end0f2bb0111a86be0436b44210dbd83a90 + } + if v.Args[0].Op != OpGetG { + goto end0f2bb0111a86be0436b44210dbd83a90 + } + yes := b.Succs[0] + no := b.Succs[1] + b.Func.removePredecessor(b, no) + b.Kind = BlockPlain + b.Control = nil + b.Succs = b.Succs[:1] + b.Succs[0] = yes + b.Likely = BranchUnknown + return true + } + goto end0f2bb0111a86be0436b44210dbd83a90 + end0f2bb0111a86be0436b44210dbd83a90: + ; // match: (If (Not cond) yes no) // cond: // result: (If cond no yes) diff --git a/src/cmd/internal/obj/x86/obj6.go b/src/cmd/internal/obj/x86/obj6.go index fa9c474adb..5249ca9581 100644 --- a/src/cmd/internal/obj/x86/obj6.go +++ b/src/cmd/internal/obj/x86/obj6.go @@ -38,7 +38,7 @@ import ( "math" ) -func canuse1insntls(ctxt *obj.Link) bool { +func CanUse1InsnTLS(ctxt *obj.Link) bool { if ctxt.Arch.Regsize == 4 { switch ctxt.Headtype { case obj.Hlinux, @@ -120,7 +120,7 @@ func progedit(ctxt *obj.Link, p *obj.Prog) { // rewriting the instructions more comprehensively, and it only does because // we only support a single TLS variable (g). - if canuse1insntls(ctxt) { + if CanUse1InsnTLS(ctxt) { // Reduce 2-instruction sequence to 1-instruction sequence. // Sequences like // MOVQ TLS, BX -- cgit v1.3 From 35fb514596b970a73be972fa917fa23ca74f7be6 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Mon, 10 Aug 2015 12:15:52 -0700 Subject: [dev.ssa] cmd/compile: add HTML SSA printer This is an initial implementation. There are many rough edges and TODOs, which will hopefully be polished out with use. Fixes #12071. Change-Id: I1d6fd5a343063b5200623bceef2c2cfcc885794e Reviewed-on: https://go-review.googlesource.com/13472 Reviewed-by: Keith Randall --- src/cmd/compile/internal/gc/ssa.go | 40 +++ src/cmd/compile/internal/ssa/compile.go | 3 + src/cmd/compile/internal/ssa/config.go | 16 +- src/cmd/compile/internal/ssa/deadcode.go | 17 +- src/cmd/compile/internal/ssa/html.go | 461 +++++++++++++++++++++++++++++++ src/cmd/compile/internal/ssa/print.go | 90 ++++-- src/cmd/internal/obj/obj.go | 25 +- 7 files changed, 607 insertions(+), 45 deletions(-) create mode 100644 src/cmd/compile/internal/ssa/html.go (limited to 'src/cmd/internal') diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index c8ec01f5b6..882efc0dae 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -5,7 +5,9 @@ package gc import ( + "bytes" "fmt" + "html" "os" "strings" @@ -40,6 +42,18 @@ func buildssa(fn *Node) (ssafn *ssa.Func, usessa bool) { s.f = s.config.NewFunc() s.f.Name = name + if name == os.Getenv("GOSSAFUNC") { + // TODO: tempfile? it is handy to have the location + // of this file be stable, so you can just reload in the browser. + s.config.HTML = ssa.NewHTMLWriter("ssa.html", &s, name) + // TODO: generate and print a mapping from nodes to values and blocks + } + defer func() { + if !usessa { + s.config.HTML.Close() + } + }() + // If SSA support for the function is incomplete, // assume that any panics are due to violated // invariants. Swallow them silently. @@ -1811,6 +1825,30 @@ func genssa(f *ssa.Func, ptxt *obj.Prog, gcargs, gclocals *Sym) { } f.Logf("%s\t%s\n", s, p) } + if f.Config.HTML != nil { + saved := ptxt.Ctxt.LineHist.PrintFilenameOnly + ptxt.Ctxt.LineHist.PrintFilenameOnly = true + var buf bytes.Buffer + buf.WriteString("") + buf.WriteString("
") + for p := ptxt; p != nil; p = p.Link { + buf.WriteString("
") + if v, ok := valueProgs[p]; ok { + buf.WriteString(v.HTML()) + } else if b, ok := blockProgs[p]; ok { + buf.WriteString(b.HTML()) + } + buf.WriteString("
") + buf.WriteString("
") + buf.WriteString(html.EscapeString(p.String())) + buf.WriteString("
") + buf.WriteString("") + } + buf.WriteString("
") + buf.WriteString("
") + f.Config.HTML.WriteColumn("genssa", buf.String()) + ptxt.Ctxt.LineHist.PrintFilenameOnly = saved + } } // Emit static data @@ -1834,6 +1872,8 @@ func genssa(f *ssa.Func, ptxt *obj.Prog, gcargs, gclocals *Sym) { ggloblsym(gcargs, 4, obj.RODATA|obj.DUPOK) duint32(gclocals, 0, 0) ggloblsym(gclocals, 4, obj.RODATA|obj.DUPOK) + + f.Config.HTML.Close() } func genValue(v *ssa.Value) { diff --git a/src/cmd/compile/internal/ssa/compile.go b/src/cmd/compile/internal/ssa/compile.go index 7ab8ddf3dc..e85fb10e00 100644 --- a/src/cmd/compile/internal/ssa/compile.go +++ b/src/cmd/compile/internal/ssa/compile.go @@ -34,13 +34,16 @@ func Compile(f *Func) { // Run all the passes printFunc(f) + f.Config.HTML.WriteFunc("start", f) checkFunc(f) for _, p := range passes { phaseName = p.name f.Logf(" pass %s begin\n", p.name) + // TODO: capture logging during this pass, add it to the HTML p.fn(f) f.Logf(" pass %s end\n", p.name) printFunc(f) + f.Config.HTML.WriteFunc("after "+phaseName, f) checkFunc(f) } diff --git a/src/cmd/compile/internal/ssa/config.go b/src/cmd/compile/internal/ssa/config.go index 8aea59d13c..ad6441117c 100644 --- a/src/cmd/compile/internal/ssa/config.go +++ b/src/cmd/compile/internal/ssa/config.go @@ -11,6 +11,7 @@ type Config struct { lowerBlock func(*Block) bool // lowering function lowerValue func(*Value, *Config) bool // lowering function fe Frontend // callbacks into compiler frontend + HTML *HTMLWriter // html writer, for debugging // TODO: more stuff. Compiler flags of interest, ... } @@ -31,12 +32,7 @@ type TypeSource interface { TypeBytePtr() Type // TODO: use unsafe.Pointer instead? } -type Frontend interface { - TypeSource - - // StringData returns a symbol pointing to the given string's contents. - StringData(string) interface{} // returns *gc.Sym - +type Logger interface { // Log logs a message from the compiler. Logf(string, ...interface{}) @@ -48,6 +44,14 @@ type Frontend interface { Unimplementedf(msg string, args ...interface{}) } +type Frontend interface { + TypeSource + Logger + + // StringData returns a symbol pointing to the given string's contents. + StringData(string) interface{} // returns *gc.Sym +} + // NewConfig returns a new configuration object for the given architecture. func NewConfig(arch string, fe Frontend) *Config { c := &Config{arch: arch, fe: fe} diff --git a/src/cmd/compile/internal/ssa/deadcode.go b/src/cmd/compile/internal/ssa/deadcode.go index 426e6865c0..109b3dd09f 100644 --- a/src/cmd/compile/internal/ssa/deadcode.go +++ b/src/cmd/compile/internal/ssa/deadcode.go @@ -4,10 +4,10 @@ package ssa -// deadcode removes dead code from f. -func deadcode(f *Func) { +// findlive returns the reachable blocks and live values in f. +func findlive(f *Func) (reachable []bool, live []bool) { // Find all reachable basic blocks. - reachable := make([]bool, f.NumBlocks()) + reachable = make([]bool, f.NumBlocks()) reachable[f.Entry.ID] = true p := []*Block{f.Entry} // stack-like worklist for len(p) > 0 { @@ -24,8 +24,8 @@ func deadcode(f *Func) { } // 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 + 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 { @@ -54,6 +54,13 @@ func deadcode(f *Func) { } } + return reachable, live +} + +// deadcode removes dead code from f. +func deadcode(f *Func) { + reachable, live := findlive(f) + // Remove dead values from blocks' value list. Return dead // value ids to the allocator. for _, b := range f.Blocks { diff --git a/src/cmd/compile/internal/ssa/html.go b/src/cmd/compile/internal/ssa/html.go new file mode 100644 index 0000000000..581331a215 --- /dev/null +++ b/src/cmd/compile/internal/ssa/html.go @@ -0,0 +1,461 @@ +// 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 ( + "bytes" + "fmt" + "html" + "io" + "os" +) + +type HTMLWriter struct { + Logger + *os.File +} + +func NewHTMLWriter(path string, logger Logger, funcname string) *HTMLWriter { + out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + logger.Fatalf("%v", err) + } + html := HTMLWriter{File: out, Logger: logger} + html.start(funcname) + return &html +} + +func (w *HTMLWriter) start(name string) { + if w == nil { + return + } + w.WriteString("") + w.WriteString(` + + + + +`) + // TODO: Add javascript click handlers for blocks + // to outline that block across all phases + w.WriteString("") + w.WriteString("

") + w.WriteString(html.EscapeString(name)) + w.WriteString("

") + w.WriteString(` +help +
+ +

+Click on a value or block to toggle highlighting of that value/block and its uses. +Values and blocks are highlighted by ID, which may vary across passes. +(TODO: Fix this.) +

+ +

+Faded out values and blocks are dead code that has not been eliminated. +

+ +

+Values printed in italics have a dependency cycle. +

+ +
+`) + w.WriteString("") + w.WriteString("") +} + +func (w *HTMLWriter) Close() { + if w == nil { + return + } + w.WriteString("") + w.WriteString("
") + w.WriteString("") + w.WriteString("") + w.File.Close() +} + +// WriteFunc writes f in a column headed by title. +func (w *HTMLWriter) WriteFunc(title string, f *Func) { + if w == nil { + return // avoid generating HTML just to discard it + } + w.WriteColumn(title, f.HTML()) + // TODO: Add visual representation of f's CFG. +} + +// WriteColumn writes raw HTML in a column headed by title. +// It is intended for pre- and post-compilation log output. +func (w *HTMLWriter) WriteColumn(title string, html string) { + if w == nil { + return + } + w.WriteString("") + w.WriteString("

" + title + "

") + w.WriteString(html) + w.WriteString("") +} + +func (w *HTMLWriter) Printf(msg string, v ...interface{}) { + if _, err := fmt.Fprintf(w.File, msg, v...); err != nil { + w.Fatalf("%v", err) + } +} + +func (w *HTMLWriter) WriteString(s string) { + if _, err := w.File.WriteString(s); err != nil { + w.Fatalf("%v", err) + } +} + +func (v *Value) HTML() string { + // TODO: Using the value ID as the class ignores the fact + // that value IDs get recycled and that some values + // are transmuted into other values. + return fmt.Sprintf("%[1]s", v.String()) +} + +func (v *Value) LongHTML() string { + // TODO: Any intra-value formatting? + // I'm wary of adding too much visual noise, + // but a little bit might be valuable. + // We already have visual noise in the form of punctuation + // maybe we could replace some of that with formatting. + s := fmt.Sprintf("", v.String()) + s += fmt.Sprintf("%s = %s", v.HTML(), v.Op.String()) + s += " <" + html.EscapeString(v.Type.String()) + ">" + if v.AuxInt != 0 { + s += fmt.Sprintf(" [%d]", v.AuxInt) + } + if v.Aux != nil { + if _, ok := v.Aux.(string); ok { + s += html.EscapeString(fmt.Sprintf(" {%q}", v.Aux)) + } else { + s += html.EscapeString(fmt.Sprintf(" {%v}", v.Aux)) + } + } + for _, a := range v.Args { + s += fmt.Sprintf(" %s", a.HTML()) + } + r := v.Block.Func.RegAlloc + if r != nil && r[v.ID] != nil { + s += " : " + r[v.ID].Name() + } + + s += "" + return s +} + +func (b *Block) HTML() string { + // TODO: Using the value ID as the class ignores the fact + // that value IDs get recycled and that some values + // are transmuted into other values. + return fmt.Sprintf("%[1]s", html.EscapeString(b.String())) +} + +func (b *Block) LongHTML() string { + // TODO: improve this for HTML? + s := b.Kind.String() + if b.Control != nil { + s += fmt.Sprintf(" %s", b.Control.HTML()) + } + if len(b.Succs) > 0 { + s += " →" // right arrow + for _, c := range b.Succs { + s += " " + c.HTML() + } + } + return s +} + +func (f *Func) HTML() string { + var buf bytes.Buffer + fmt.Fprint(&buf, "") + p := htmlFuncPrinter{w: &buf} + fprintFunc(p, f) + + // fprintFunc(&buf, f) // TODO: HTML, not text,
for line breaks, etc. + fmt.Fprint(&buf, "
") + return buf.String() +} + +type htmlFuncPrinter struct { + w io.Writer +} + +func (p htmlFuncPrinter) header(f *Func) {} + +func (p htmlFuncPrinter) startBlock(b *Block, reachable bool) { + // TODO: Make blocks collapsable? + var dead string + if !reachable { + dead = "dead-block" + } + fmt.Fprintf(p.w, "
    ", b, dead) + fmt.Fprintf(p.w, "
  • %s:", b.HTML()) + if len(b.Preds) > 0 { + io.WriteString(p.w, " ←") // left arrow + for _, pred := range b.Preds { + fmt.Fprintf(p.w, " %s", pred.HTML()) + } + } + io.WriteString(p.w, "
  • ") + if len(b.Values) > 0 { // start list of values + io.WriteString(p.w, "
  • ") + io.WriteString(p.w, "
      ") + } +} + +func (p htmlFuncPrinter) endBlock(b *Block) { + if len(b.Values) > 0 { // end list of values + io.WriteString(p.w, "
    ") + io.WriteString(p.w, "
  • ") + } + io.WriteString(p.w, "
  • ") + fmt.Fprint(p.w, b.LongHTML()) + io.WriteString(p.w, "
  • ") + io.WriteString(p.w, "
") + // io.WriteString(p.w, "") +} + +func (p htmlFuncPrinter) value(v *Value, live bool) { + var dead string + if !live { + dead = "dead-value" + } + fmt.Fprintf(p.w, "
  • ", dead) + fmt.Fprint(p.w, v.LongHTML()) + io.WriteString(p.w, "
  • ") +} + +func (p htmlFuncPrinter) startDepCycle() { + fmt.Fprintln(p.w, "") +} + +func (p htmlFuncPrinter) endDepCycle() { + fmt.Fprintln(p.w, "") +} diff --git a/src/cmd/compile/internal/ssa/print.go b/src/cmd/compile/internal/ssa/print.go index 2f9db4438f..192dc83b39 100644 --- a/src/cmd/compile/internal/ssa/print.go +++ b/src/cmd/compile/internal/ssa/print.go @@ -16,33 +16,77 @@ func printFunc(f *Func) { func (f *Func) String() string { var buf bytes.Buffer - fprintFunc(&buf, f) + p := stringFuncPrinter{w: &buf} + fprintFunc(p, f) return buf.String() } -func fprintFunc(w io.Writer, f *Func) { - fmt.Fprint(w, f.Name) - fmt.Fprint(w, " ") - fmt.Fprintln(w, f.Type) +type funcPrinter interface { + header(f *Func) + startBlock(b *Block, reachable bool) + endBlock(b *Block) + value(v *Value, live bool) + startDepCycle() + endDepCycle() +} + +type stringFuncPrinter struct { + w io.Writer +} + +func (p stringFuncPrinter) header(f *Func) { + fmt.Fprint(p.w, f.Name) + fmt.Fprint(p.w, " ") + fmt.Fprintln(p.w, f.Type) +} + +func (p stringFuncPrinter) startBlock(b *Block, reachable bool) { + fmt.Fprintf(p.w, " b%d:", b.ID) + if len(b.Preds) > 0 { + io.WriteString(p.w, " <-") + for _, pred := range b.Preds { + fmt.Fprintf(p.w, " b%d", pred.ID) + } + } + if !reachable { + fmt.Fprint(p.w, " DEAD") + } + io.WriteString(p.w, "\n") +} + +func (p stringFuncPrinter) endBlock(b *Block) { + fmt.Fprintln(p.w, " "+b.LongString()) +} + +func (p stringFuncPrinter) value(v *Value, live bool) { + fmt.Fprint(p.w, " ") + fmt.Fprint(p.w, v.LongString()) + if !live { + fmt.Fprint(p.w, " DEAD") + } + fmt.Fprintln(p.w) +} + +func (p stringFuncPrinter) startDepCycle() { + fmt.Fprintln(p.w, "dependency cycle!") +} + +func (p stringFuncPrinter) endDepCycle() {} + +func fprintFunc(p funcPrinter, f *Func) { + reachable, live := findlive(f) + p.header(f) printed := make([]bool, f.NumValues()) for _, b := range f.Blocks { - fmt.Fprintf(w, " b%d:", b.ID) - if len(b.Preds) > 0 { - io.WriteString(w, " <-") - for _, pred := range b.Preds { - fmt.Fprintf(w, " b%d", pred.ID) - } - } - io.WriteString(w, "\n") + p.startBlock(b, reachable[b.ID]) if f.scheduled { // Order of Values has been decided - print in that order. for _, v := range b.Values { - fmt.Fprint(w, " ") - fmt.Fprintln(w, v.LongString()) + p.value(v, live[v.ID]) printed[v.ID] = true } - fmt.Fprintln(w, " "+b.LongString()) + p.endBlock(b) continue } @@ -52,8 +96,7 @@ func fprintFunc(w io.Writer, f *Func) { if v.Op != OpPhi { continue } - fmt.Fprint(w, " ") - fmt.Fprintln(w, v.LongString()) + p.value(v, live[v.ID]) printed[v.ID] = true n++ } @@ -73,25 +116,24 @@ func fprintFunc(w io.Writer, f *Func) { continue outer } } - fmt.Fprint(w, " ") - fmt.Fprintln(w, v.LongString()) + p.value(v, live[v.ID]) printed[v.ID] = true n++ } if m == n { - fmt.Fprintln(w, "dependency cycle!") + p.startDepCycle() for _, v := range b.Values { if printed[v.ID] { continue } - fmt.Fprint(w, " ") - fmt.Fprintln(w, v.LongString()) + p.value(v, live[v.ID]) printed[v.ID] = true n++ } + p.endDepCycle() } } - fmt.Fprintln(w, " "+b.LongString()) + p.endBlock(b) } } diff --git a/src/cmd/internal/obj/obj.go b/src/cmd/internal/obj/obj.go index af3290d3a5..6229bbb288 100644 --- a/src/cmd/internal/obj/obj.go +++ b/src/cmd/internal/obj/obj.go @@ -25,12 +25,13 @@ import ( // together, so that given (only) calls Push(10, "x.go", 1) and Pop(15), // virtual line 12 corresponds to x.go line 3. type LineHist struct { - Top *LineStack // current top of stack - Ranges []LineRange // ranges for lookup - Dir string // directory to qualify relative paths - TrimPathPrefix string // remove leading TrimPath from recorded file names - GOROOT string // current GOROOT - GOROOT_FINAL string // target GOROOT + Top *LineStack // current top of stack + Ranges []LineRange // ranges for lookup + Dir string // directory to qualify relative paths + TrimPathPrefix string // remove leading TrimPath from recorded file names + PrintFilenameOnly bool // ignore path when pretty-printing a line; internal use only + GOROOT string // current GOROOT + GOROOT_FINAL string // target GOROOT } // A LineStack is an entry in the recorded line history. @@ -221,20 +222,24 @@ func (h *LineHist) LineString(lineno int) string { return "" } - text := fmt.Sprintf("%s:%d", stk.File, stk.fileLineAt(lineno)) + filename := stk.File + if h.PrintFilenameOnly { + filename = filepath.Base(filename) + } + text := fmt.Sprintf("%s:%d", filename, stk.fileLineAt(lineno)) if stk.Directive && stk.Parent != nil { stk = stk.Parent - text += fmt.Sprintf("[%s:%d]", stk.File, stk.fileLineAt(lineno)) + text += fmt.Sprintf("[%s:%d]", filename, stk.fileLineAt(lineno)) } const showFullStack = false // was used by old C compilers if showFullStack { for stk.Parent != nil { lineno = stk.Lineno - 1 stk = stk.Parent - text += fmt.Sprintf(" %s:%d", stk.File, stk.fileLineAt(lineno)) + text += fmt.Sprintf(" %s:%d", filename, stk.fileLineAt(lineno)) if stk.Directive && stk.Parent != nil { stk = stk.Parent - text += fmt.Sprintf("[%s:%d]", stk.File, stk.fileLineAt(lineno)) + text += fmt.Sprintf("[%s:%d]", filename, stk.fileLineAt(lineno)) } } } -- cgit v1.3 From 270e2152c4645a72ac3263fcc92ce878e8035d71 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Mon, 17 Aug 2015 00:18:31 -0700 Subject: [dev.ssa] cmd/compile: fix dev.ssa build Broken by CL 13472. Change-Id: Ib65331b291c8fab4238ca91e085779bb954d70e8 Reviewed-on: https://go-review.googlesource.com/13638 Reviewed-by: David Chase --- src/cmd/internal/obj/obj.go | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/obj/obj.go b/src/cmd/internal/obj/obj.go index 6229bbb288..937e3b9812 100644 --- a/src/cmd/internal/obj/obj.go +++ b/src/cmd/internal/obj/obj.go @@ -229,6 +229,10 @@ func (h *LineHist) LineString(lineno int) string { text := fmt.Sprintf("%s:%d", filename, stk.fileLineAt(lineno)) if stk.Directive && stk.Parent != nil { stk = stk.Parent + filename = stk.File + if h.PrintFilenameOnly { + filename = filepath.Base(filename) + } text += fmt.Sprintf("[%s:%d]", filename, stk.fileLineAt(lineno)) } const showFullStack = false // was used by old C compilers -- cgit v1.3 From 5cb352edeba36e862995dd82fe7312368e6e8571 Mon Sep 17 00:00:00 2001 From: Todd Neal Date: Tue, 1 Sep 2015 21:25:24 -0500 Subject: [dev.ssa] cmd/compile: fix liblink rewrite of -0.0 liblink was rewriting xor by a negative zero (used by SSA for negation) as XORPS reg,reg. Fixes strconv. Change-Id: I627a0a7366618e6b07ba8f0ad0db0e102340c5e3 Reviewed-on: https://go-review.googlesource.com/14200 Reviewed-by: Josh Bleecher Snyder Reviewed-by: Keith Randall --- src/cmd/compile/internal/gc/testdata/fp_ssa.go | 14 ++++++++++++++ src/cmd/internal/obj/x86/obj6.go | 6 ++++-- 2 files changed, 18 insertions(+), 2 deletions(-) (limited to 'src/cmd/internal') diff --git a/src/cmd/compile/internal/gc/testdata/fp_ssa.go b/src/cmd/compile/internal/gc/testdata/fp_ssa.go index 6193983e4c..ee3163abb3 100644 --- a/src/cmd/compile/internal/gc/testdata/fp_ssa.go +++ b/src/cmd/compile/internal/gc/testdata/fp_ssa.go @@ -105,6 +105,12 @@ func div64_ssa(a, b float64) float64 { return a / b } +func neg64_ssa(a, b float64) float64 { + switch { + } + return -a + -1*b +} + func add32_ssa(a, b float32) float32 { switch { } @@ -128,6 +134,12 @@ func div32_ssa(a, b float32) float32 { return a / b } +func neg32_ssa(a, b float32) float32 { + switch { + } + return -a + -1*b +} + func conv2Float64_ssa(a int8, b uint8, c int16, d uint16, e int32, f uint32, g int64, h uint64, i float32) (aa, bb, cc, dd, ee, ff, gg, hh, ii float64) { switch { @@ -1548,11 +1560,13 @@ func main() { fails += fail64("*", mul64_ssa, a, b, 12.0) fails += fail64("-", sub64_ssa, a, b, -1.0) fails += fail64("/", div64_ssa, a, b, 0.75) + fails += fail64("neg", neg64_ssa, a, b, -7) fails += fail32("+", add32_ssa, c, d, 7.0) fails += fail32("*", mul32_ssa, c, d, 12.0) fails += fail32("-", sub32_ssa, c, d, -1.0) fails += fail32("/", div32_ssa, c, d, 0.75) + fails += fail32("neg", neg32_ssa, c, d, -7) // denorm-squared should underflow to zero. fails += fail32("*", mul32_ssa, tiny, tiny, 0) diff --git a/src/cmd/internal/obj/x86/obj6.go b/src/cmd/internal/obj/x86/obj6.go index d0efffbc0a..d55dcc7cac 100644 --- a/src/cmd/internal/obj/x86/obj6.go +++ b/src/cmd/internal/obj/x86/obj6.go @@ -221,7 +221,8 @@ func progedit(ctxt *obj.Link, p *obj.Prog) { // Convert AMOVSS $(0), Xx to AXORPS Xx, Xx case AMOVSS: if p.From.Type == obj.TYPE_FCONST { - if p.From.Val.(float64) == 0 { + // f == 0 can't be used here due to -0, so use Float64bits + if f := p.From.Val.(float64); math.Float64bits(f) == 0 { if p.To.Type == obj.TYPE_REG && REG_X0 <= p.To.Reg && p.To.Reg <= REG_X15 { p.As = AXORPS p.From = p.To @@ -261,7 +262,8 @@ func progedit(ctxt *obj.Link, p *obj.Prog) { case AMOVSD: // Convert AMOVSD $(0), Xx to AXORPS Xx, Xx if p.From.Type == obj.TYPE_FCONST { - if p.From.Val.(float64) == 0 { + // f == 0 can't be used here due to -0, so use Float64bits + if f := p.From.Val.(float64); math.Float64bits(f) == 0 { if p.To.Type == obj.TYPE_REG && REG_X0 <= p.To.Reg && p.To.Reg <= REG_X15 { p.As = AXORPS p.From = p.To -- cgit v1.3 From e3869a6b65bb0f95dac7eca3d86055160b12589f Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Mon, 7 Sep 2015 23:18:02 -0700 Subject: [dev.ssa] cmd/compile/internal/ssa: implement write barriers For now, we only use typedmemmove. This can be optimized in future CLs. Also add a feature to help with binary searching bad compilations. Together with GOSSAPKG, GOSSAHASH specifies the last few binary digits of the hash of function names that should be compiled. So GOSSAHASH=0110 means compile only those functions whose last 4 bits of hash are 0110. By adding digits to the front we can binary search for the function whose SSA-generated code is causing a test to fail. Change-Id: I5a8b6b70c6f034f59e5753965234cd42ea36d524 Reviewed-on: https://go-review.googlesource.com/14530 Reviewed-by: Keith Randall --- src/cmd/compile/internal/gc/builtin.go | 1 + src/cmd/compile/internal/gc/builtin/runtime.go | 1 + src/cmd/compile/internal/gc/ssa.go | 62 ++++++++++++++++++++++++-- src/cmd/compile/internal/gc/ssa_test.go | 1 - src/cmd/dist/test.go | 7 +-- src/cmd/internal/obj/stack.go | 2 +- src/runtime/mbarrier.go | 8 ++++ src/runtime/stack2.go | 4 +- test/nosplit.go | 4 +- 9 files changed, 76 insertions(+), 14 deletions(-) (limited to 'src/cmd/internal') diff --git a/src/cmd/compile/internal/gc/builtin.go b/src/cmd/compile/internal/gc/builtin.go index f09dd5690f..0e5fe2ab60 100644 --- a/src/cmd/compile/internal/gc/builtin.go +++ b/src/cmd/compile/internal/gc/builtin.go @@ -118,6 +118,7 @@ const runtimeimport = "" + "func @\"\".writebarrierfat1110 (@\"\".dst·1 *any, _ uintptr, @\"\".src·3 any)\n" + "func @\"\".writebarrierfat1111 (@\"\".dst·1 *any, _ uintptr, @\"\".src·3 any)\n" + "func @\"\".typedmemmove (@\"\".typ·1 *byte, @\"\".dst·2 *any, @\"\".src·3 *any)\n" + + "func @\"\".typedmemmove_nostore (@\"\".typ·1 *byte, @\"\".dst·2 *any)\n" + "func @\"\".typedslicecopy (@\"\".typ·2 *byte, @\"\".dst·3 any, @\"\".src·4 any) (? int)\n" + "func @\"\".selectnbsend (@\"\".chanType·2 *byte, @\"\".hchan·3 chan<- any, @\"\".elem·4 *any) (? bool)\n" + "func @\"\".selectnbrecv (@\"\".chanType·2 *byte, @\"\".elem·3 *any, @\"\".hchan·4 <-chan any) (? bool)\n" + diff --git a/src/cmd/compile/internal/gc/builtin/runtime.go b/src/cmd/compile/internal/gc/builtin/runtime.go index 6210f10cdf..f8487de45b 100644 --- a/src/cmd/compile/internal/gc/builtin/runtime.go +++ b/src/cmd/compile/internal/gc/builtin/runtime.go @@ -147,6 +147,7 @@ func writebarrierfat1111(dst *any, _ uintptr, src any) // *byte is really *runtime.Type func typedmemmove(typ *byte, dst *any, src *any) +func typedmemmove_nostore(typ *byte, dst *any) func typedslicecopy(typ *byte, dst any, src any) int func selectnbsend(chanType *byte, hchan chan<- any, elem *any) bool diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 738685b044..e6a5627abf 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -6,6 +6,7 @@ package gc import ( "bytes" + "crypto/sha1" "fmt" "html" "math" @@ -162,7 +163,28 @@ func buildssa(fn *Node) (ssafn *ssa.Func, usessa bool) { // TODO: enable codegen more broadly once the codegen stabilizes // and runtime support is in (gc maps, write barriers, etc.) - return s.f, usessa || localpkg.Name == os.Getenv("GOSSAPKG") + if usessa { + return s.f, true + } + if localpkg.Name != os.Getenv("GOSSAPKG") { + return s.f, false + } + if os.Getenv("GOSSAHASH") == "" { + // Use everything in the package + return s.f, true + } + // Check the hash of the name against a partial input hash. + // We use this feature to do a binary search within a package to + // find a function that is incorrectly compiled. + hstr := "" + for _, b := range sha1.Sum([]byte(name)) { + hstr += fmt.Sprintf("%08b", b) + } + if strings.HasSuffix(hstr, os.Getenv("GOSSAHASH")) { + fmt.Println("GOSSAHASH triggered %s\n", name) + return s.f, true + } + return s.f, false } type state struct { @@ -744,6 +766,7 @@ func (s *state) stmt(n *Node) { fn := call.Left if call.Op != OCALLFUNC { s.Unimplementedf("defer/go of %s", opnames[call.Op]) + return } // Run all argument assignments. The arg slots have already @@ -1852,8 +1875,6 @@ func (s *state) assign(left *Node, right *ssa.Value, wb bool) { if left.Op == ONAME && isblank(left) { return } - // TODO: do write barrier - // if wb t := left.Type dowidth(t) if right == nil { @@ -1880,6 +1901,41 @@ func (s *state) assign(left *Node, right *ssa.Value, wb bool) { s.vars[&memvar] = s.newValue1A(ssa.OpVarDef, ssa.TypeMem, left, s.mem()) } s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, t.Size(), addr, right, s.mem()) + if wb { + // if writeBarrierEnabled { + // typedmemmove_nostore(t, &l) + // } + bThen := s.f.NewBlock(ssa.BlockPlain) + bNext := s.f.NewBlock(ssa.BlockPlain) + + aux := &ssa.ExternSymbol{Types[TBOOL], syslook("writeBarrierEnabled", 0).Sym} + flagaddr := s.newValue1A(ssa.OpAddr, Ptrto(Types[TBOOL]), aux, s.sb) + flag := s.newValue2(ssa.OpLoad, Types[TBOOL], flagaddr, s.mem()) + b := s.endBlock() + b.Kind = ssa.BlockIf + b.Likely = ssa.BranchUnlikely + b.Control = flag + b.AddEdgeTo(bThen) + b.AddEdgeTo(bNext) + + s.startBlock(bThen) + // NOTE: there must be no GC suspension points between the write above + // (the OpStore) and this call to typedmemmove_nostore. + // TODO: writebarrierptr_nostore if just one pointer word (or a few?) + taddr := s.newValue1A(ssa.OpAddr, Types[TUINTPTR], &ssa.ExternSymbol{Types[TUINTPTR], typenamesym(left.Type)}, s.sb) + s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, int64(Widthptr), s.sp, taddr, s.mem()) + spplus8 := s.newValue1I(ssa.OpOffPtr, Types[TUINTPTR], int64(Widthptr), s.sp) + s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, int64(Widthptr), spplus8, addr, s.mem()) + call := s.newValue1A(ssa.OpStaticCall, ssa.TypeMem, syslook("typedmemmove_nostore", 0).Sym, s.mem()) + call.AuxInt = int64(2 * Widthptr) + s.vars[&memvar] = call + c := s.endBlock() + c.Kind = ssa.BlockCall + c.Control = call + c.AddEdgeTo(bNext) + + s.startBlock(bNext) + } } // zeroVal returns the zero value for type t. diff --git a/src/cmd/compile/internal/gc/ssa_test.go b/src/cmd/compile/internal/gc/ssa_test.go index 74415fd560..b3ab09d914 100644 --- a/src/cmd/compile/internal/gc/ssa_test.go +++ b/src/cmd/compile/internal/gc/ssa_test.go @@ -31,7 +31,6 @@ func doTest(t *testing.T, filename string, kind string) { cmd := exec.Command("go", kind, filepath.Join("testdata", filename)) cmd.Stdout = &stdout cmd.Stderr = &stderr - // TODO: set GOGC=off until we have stackmaps if err := cmd.Run(); err != nil { t.Fatalf("Failed: %v:\nOut: %s\nStderr: %s\n", err, &stdout, &stderr) } diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index d80547ed1c..5f8afd0cb3 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -277,11 +277,6 @@ func (t *tester) registerStdTest(pkg string) { // TODO: Remove when SSA codegen is used by default. func (t *tester) registerSSATest(pkg string) { - switch pkg { - // known failures due to GOGC=off - case "runtime", "runtime/pprof", "runtime/trace", "sync": - return - } t.tests = append(t.tests, distTest{ name: "go_test_ssa:" + pkg, heading: "Testing packages with SSA codegen.", @@ -297,7 +292,7 @@ func (t *tester) registerSSATest(pkg string) { } args = append(args, pkg) cmd := exec.Command("go", args...) - cmd.Env = mergeEnvLists([]string{"GOSSAPKG=" + path.Base(pkg), "GOGC=off"}, os.Environ()) + cmd.Env = mergeEnvLists([]string{"GOSSAPKG=" + path.Base(pkg)}, os.Environ()) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() diff --git a/src/cmd/internal/obj/stack.go b/src/cmd/internal/obj/stack.go index 87698b3eeb..b1630b55fc 100644 --- a/src/cmd/internal/obj/stack.go +++ b/src/cmd/internal/obj/stack.go @@ -41,7 +41,7 @@ const ( STACKSYSTEM = 0 StackSystem = STACKSYSTEM StackBig = 4096 - StackGuard = 640*stackGuardMultiplier + StackSystem + StackGuard = 960*stackGuardMultiplier + StackSystem StackSmall = 128 StackLimit = StackGuard - StackSystem - StackSmall ) diff --git a/src/runtime/mbarrier.go b/src/runtime/mbarrier.go index 0dbe1ffc9d..c94e44f142 100644 --- a/src/runtime/mbarrier.go +++ b/src/runtime/mbarrier.go @@ -185,6 +185,14 @@ func typedmemmove(typ *_type, dst, src unsafe.Pointer) { heapBitsBulkBarrier(uintptr(dst), typ.size) } +//go:nosplit +func typedmemmove_nostore(typ *_type, dst unsafe.Pointer) { + if typ.kind&kindNoPointers != 0 { + return + } + heapBitsBulkBarrier(uintptr(dst), typ.size) +} + //go:linkname reflect_typedmemmove reflect.typedmemmove func reflect_typedmemmove(typ *_type, dst, src unsafe.Pointer) { typedmemmove(typ, dst, src) diff --git a/src/runtime/stack2.go b/src/runtime/stack2.go index 5ec8d8d060..02b82ebe13 100644 --- a/src/runtime/stack2.go +++ b/src/runtime/stack2.go @@ -54,6 +54,8 @@ The linkers explore all possible call traces involving non-splitting functions to make sure that this limit cannot be violated. */ +// Constants here match those in cmd/internal/obj/stack.go. + const ( // StackSystem is a number of additional bytes to add // to each stack below the usual guard area for OS-specific @@ -84,7 +86,7 @@ const ( // The stack guard is a pointer this many bytes above the // bottom of the stack. - _StackGuard = 640*stackGuardMultiplier + _StackSystem + _StackGuard = 960*stackGuardMultiplier + _StackSystem // After a stack split check the SP is allowed to be this // many bytes below the stack guard. This saves an instruction diff --git a/test/nosplit.go b/test/nosplit.go index e5c2a9f30e..e7c00f5783 100644 --- a/test/nosplit.go +++ b/test/nosplit.go @@ -285,12 +285,12 @@ TestCases: // Instead of rewriting the test cases above, adjust // the first stack frame to use up the extra bytes. if i == 0 { - size += 512 - 128 + size += 832 - 128 // Noopt builds have a larger stackguard. // See ../cmd/dist/buildruntime.go:stackGuardMultiplier for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") { if s == "-N" { - size += 640 + size += 960 } } } -- cgit v1.3 From d29e92be523efd8270c0e7ca0eaa6afa86bbedca Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Sat, 19 Sep 2015 12:01:39 -0700 Subject: [dev.ssa] cmd/compile: Use varkill only for non-SSAable vars For variables which get SSA'd, SSA keeps track of all the def/kill. It is only for on-stack variables that we need them. This reduces stack frame sizes significantly because often the only use of a variable was a varkill, and without that last use the variable doesn't get allocated in the frame at all. Fixes #12602 Change-Id: I3f00a768aa5ddd8d7772f375b25f846086a3e689 Reviewed-on: https://go-review.googlesource.com/14758 Reviewed-by: Brad Fitzpatrick --- src/cmd/compile/internal/gc/ssa.go | 4 +++- src/cmd/internal/obj/stack.go | 2 +- src/runtime/stack2.go | 2 +- test/nosplit.go | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) (limited to 'src/cmd/internal') diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 8e0f0dcc9b..6cb5c571c2 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -794,7 +794,9 @@ func (s *state) stmt(n *Node) { // We only care about liveness info at call sites, so putting the // varkill in the store chain is enough to keep it correctly ordered // with respect to call ops. - s.vars[&memVar] = s.newValue1A(ssa.OpVarKill, ssa.TypeMem, n.Left, s.mem()) + if !canSSA(n.Left) { + s.vars[&memVar] = s.newValue1A(ssa.OpVarKill, ssa.TypeMem, n.Left, s.mem()) + } case OCHECKNIL: p := s.expr(n.Left) diff --git a/src/cmd/internal/obj/stack.go b/src/cmd/internal/obj/stack.go index b1630b55fc..87698b3eeb 100644 --- a/src/cmd/internal/obj/stack.go +++ b/src/cmd/internal/obj/stack.go @@ -41,7 +41,7 @@ const ( STACKSYSTEM = 0 StackSystem = STACKSYSTEM StackBig = 4096 - StackGuard = 960*stackGuardMultiplier + StackSystem + StackGuard = 640*stackGuardMultiplier + StackSystem StackSmall = 128 StackLimit = StackGuard - StackSystem - StackSmall ) diff --git a/src/runtime/stack2.go b/src/runtime/stack2.go index 02b82ebe13..59d4ef694d 100644 --- a/src/runtime/stack2.go +++ b/src/runtime/stack2.go @@ -86,7 +86,7 @@ const ( // The stack guard is a pointer this many bytes above the // bottom of the stack. - _StackGuard = 960*stackGuardMultiplier + _StackSystem + _StackGuard = 640*stackGuardMultiplier + _StackSystem // After a stack split check the SP is allowed to be this // many bytes below the stack guard. This saves an instruction diff --git a/test/nosplit.go b/test/nosplit.go index e7c00f5783..e5c2a9f30e 100644 --- a/test/nosplit.go +++ b/test/nosplit.go @@ -285,12 +285,12 @@ TestCases: // Instead of rewriting the test cases above, adjust // the first stack frame to use up the extra bytes. if i == 0 { - size += 832 - 128 + size += 512 - 128 // Noopt builds have a larger stackguard. // See ../cmd/dist/buildruntime.go:stackGuardMultiplier for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") { if s == "-N" { - size += 960 + size += 640 } } } -- cgit v1.3 From e99dd520665000dfeb848fb4ecd381314b8fe61b Mon Sep 17 00:00:00 2001 From: David Chase Date: Mon, 19 Oct 2015 11:36:07 -0400 Subject: [dev.ssa] cmd/compile: enhance SSA filtering, add OpConvert Modified GOSSA{HASH.PKG} environment variable filters to make it easier to make/run with all SSA for testing. Disable attempts at SSA for architectures that are not amd64 (avoid spurious errors/unimplementeds.) Removed easy out for unimplemented features. Add convert op for proper liveness in presence of uintptr to/from unsafe.Pointer conversions. Tweaked stack sizes to get a pass on windows; 1024 instead 768, was observed to pass at least once. Change-Id: Ida3800afcda67d529e3b1cf48ca4a3f0fa48b2c5 Reviewed-on: https://go-review.googlesource.com/16201 Reviewed-by: Keith Randall Run-TryBot: David Chase --- src/cmd/compile/internal/gc/pgen.go | 4 +- src/cmd/compile/internal/gc/ssa.go | 85 +++++++++++++++++--------- src/cmd/compile/internal/ssa/gen/AMD64.rules | 3 + src/cmd/compile/internal/ssa/gen/genericOps.go | 5 +- src/cmd/compile/internal/ssa/opGen.go | 5 ++ src/cmd/compile/internal/ssa/rewriteAMD64.go | 18 ++++++ src/cmd/compile/internal/ssa/tighten.go | 8 ++- src/cmd/dist/test.go | 5 -- src/cmd/internal/obj/stack.go | 2 +- src/cmd/internal/obj/util.go | 3 + src/runtime/stack.go | 2 +- test/nosplit.go | 8 ++- 12 files changed, 105 insertions(+), 43 deletions(-) (limited to 'src/cmd/internal') diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go index a5010a31b4..b3ba2fbb46 100644 --- a/src/cmd/compile/internal/gc/pgen.go +++ b/src/cmd/compile/internal/gc/pgen.go @@ -414,7 +414,9 @@ func compile(fn *Node) { // Build an SSA backend function. // TODO: get rid of usessa. - ssafn, usessa = buildssa(Curfn) + if Thearch.Thestring == "amd64" { + ssafn, usessa = buildssa(Curfn) + } continpc = nil breakpc = nil diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 64391b0fca..8939f14136 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -24,8 +24,32 @@ import ( // it will never return nil, and the bool can be removed. func buildssa(fn *Node) (ssafn *ssa.Func, usessa bool) { name := fn.Func.Nname.Sym.Name + gossahash := os.Getenv("GOSSAHASH") usessa = strings.HasSuffix(name, "_ssa") || strings.Contains(name, "_ssa.") || name == os.Getenv("GOSSAFUNC") + // Environment variable control of SSA CG + // 1. IF GOSSAFUNC == current function name THEN + // compile this function with SSA and log output to ssa.html + + // 2. IF GOSSAHASH == "y" or "Y" THEN + // compile this function (and everything else) with SSA + + // 3. IF GOSSAHASH == "" THEN + // IF GOSSAPKG == current package name THEN + // compile this function (and everything in this package) with SSA + // ELSE + // use the old back end for this function. + // This is for compatibility with existing test harness and should go away. + + // 4. IF GOSSAHASH is a suffix of the binary-rendered SHA1 hash of the function name THEN + // compile this function with SSA + // ELSE + // compile this function with the old back end. + + // Plan is for 3 to be remove, and the 2) dependence on GOSSAHASH changes + // from "y"/"Y" to empty -- then SSA is default, and is disabled by setting + // GOSSAHASH to a value that is neither 0 nor 1 (e.g., "N" or "X") + if usessa { fmt.Println("generating SSA for", name) dumplist("buildssa-enter", fn.Func.Enter) @@ -58,17 +82,6 @@ func buildssa(fn *Node) (ssafn *ssa.Func, usessa bool) { } }() - // If SSA support for the function is incomplete, - // assume that any panics are due to violated - // invariants. Swallow them silently. - defer func() { - if err := recover(); err != nil { - if !e.unimplemented { - panic(err) - } - } - }() - // 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 @@ -167,27 +180,17 @@ func buildssa(fn *Node) (ssafn *ssa.Func, usessa bool) { // Main call to ssa package to compile function ssa.Compile(s.f) - // Calculate stats about what percentage of functions SSA handles. - if false { - fmt.Printf("SSA implemented: %t\n", !e.unimplemented) - } - - if e.unimplemented { - return nil, false - } - - // TODO: enable codegen more broadly once the codegen stabilizes - // and runtime support is in (gc maps, write barriers, etc.) - if usessa { + if usessa || gossahash == "y" || gossahash == "Y" { return s.f, true } - if localpkg.Name != os.Getenv("GOSSAPKG") { - return s.f, false - } - if os.Getenv("GOSSAHASH") == "" { + if gossahash == "" { + if localpkg.Name != os.Getenv("GOSSAPKG") { + return s.f, false + } // Use everything in the package return s.f, true } + // Check the hash of the name against a partial input hash. // We use this feature to do a binary search within a package to // find a function that is incorrectly compiled. @@ -195,10 +198,26 @@ func buildssa(fn *Node) (ssafn *ssa.Func, usessa bool) { for _, b := range sha1.Sum([]byte(name)) { hstr += fmt.Sprintf("%08b", b) } - if strings.HasSuffix(hstr, os.Getenv("GOSSAHASH")) { + + if strings.HasSuffix(hstr, gossahash) { fmt.Printf("GOSSAHASH triggered %s\n", name) return s.f, true } + + // Iteratively try additional hashes to allow tests for multi-point + // failure. + for i := 0; true; i++ { + ev := fmt.Sprintf("GOSSAHASH%d", i) + evv := os.Getenv(ev) + if evv == "" { + break + } + if strings.HasSuffix(hstr, evv) { + fmt.Printf("%s triggered %s\n", ev, name) + return s.f, true + } + } + return s.f, false } @@ -1353,6 +1372,15 @@ func (s *state) expr(n *Node) *ssa.Value { // Assume everything will work out, so set up our return value. // Anything interesting that happens from here is a fatal. x := s.expr(n.Left) + + // Special case for not confusing GC and liveness. + // We don't want pointers accidentally classified + // as not-pointers or vice-versa because of copy + // elision. + if to.IsPtr() != from.IsPtr() { + return s.newValue1(ssa.OpConvert, to, x) + } + v := s.newValue1(ssa.OpCopy, to, x) // ensure that v has the right type // CONVNOP closure @@ -1364,6 +1392,7 @@ func (s *state) expr(n *Node) *ssa.Value { if from.Etype == to.Etype { return v } + // unsafe.Pointer <--> *T if to.Etype == TUNSAFEPTR && from.IsPtr() || from.Etype == TUNSAFEPTR && to.IsPtr() { return v diff --git a/src/cmd/compile/internal/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules index dd50dd2d27..abe103571d 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules @@ -281,6 +281,9 @@ (Store [2] ptr val mem) -> (MOVWstore ptr val mem) (Store [1] ptr val mem) -> (MOVBstore ptr val mem) +// We want this to stick out so the to/from ptr conversion is obvious +(Convert x) -> (LEAQ x) + // checks (IsNonNil p) -> (SETNE (TESTQ p p)) (IsInBounds idx len) -> (SETB (CMPQ idx len)) diff --git a/src/cmd/compile/internal/ssa/gen/genericOps.go b/src/cmd/compile/internal/ssa/gen/genericOps.go index 5881596441..8a8837c0e9 100644 --- a/src/cmd/compile/internal/ssa/gen/genericOps.go +++ b/src/cmd/compile/internal/ssa/gen/genericOps.go @@ -237,8 +237,9 @@ var genericOps = []opData{ {name: "Sqrt"}, // sqrt(arg0), float64 only // Data movement - {name: "Phi"}, // select an argument based on which predecessor block we came from - {name: "Copy"}, // output = arg0 + {name: "Phi"}, // select an argument based on which predecessor block we came from + {name: "Copy"}, // output = arg0 + {name: "Convert"}, // output = arg0 -- a copy that converts to/from a pointer // constants. Constant values are stored in the aux field. // booleans have a bool aux field, strings have a string aux diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index d86dce354b..4c191807ba 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -455,6 +455,7 @@ const ( OpSqrt OpPhi OpCopy + OpConvert OpConstBool OpConstString OpConstNil @@ -3866,6 +3867,10 @@ var opcodeTable = [...]opInfo{ name: "Copy", generic: true, }, + { + name: "Convert", + generic: true, + }, { name: "ConstBool", generic: true, diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go index 2fd9a08d5b..3fe272c204 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -1670,6 +1670,24 @@ func rewriteValueAMD64(v *Value, config *Config) bool { goto endc395c0a53eeccf597e225a07b53047d1 endc395c0a53eeccf597e225a07b53047d1: ; + case OpConvert: + // match: (Convert x) + // cond: + // result: (LEAQ x) + { + t := v.Type + x := v.Args[0] + v.Op = OpAMD64LEAQ + v.AuxInt = 0 + v.Aux = nil + v.resetArgs() + v.Type = t + v.AddArg(x) + return true + } + goto end1cac40a6074914d6ae3d4aa039a625ed + end1cac40a6074914d6ae3d4aa039a625ed: + ; case OpCvt32Fto32: // match: (Cvt32Fto32 x) // cond: diff --git a/src/cmd/compile/internal/ssa/tighten.go b/src/cmd/compile/internal/ssa/tighten.go index 1da5071a2a..4fa26d2d18 100644 --- a/src/cmd/compile/internal/ssa/tighten.go +++ b/src/cmd/compile/internal/ssa/tighten.go @@ -54,8 +54,12 @@ func tighten(f *Func) { for _, b := range f.Blocks { for i := 0; i < len(b.Values); i++ { v := b.Values[i] - if v.Op == OpPhi || v.Op == OpGetClosurePtr { - // GetClosurePtr must stay in entry block + if v.Op == OpPhi || v.Op == OpGetClosurePtr || v.Op == OpConvert { + // GetClosurePtr must stay in entry block. + // OpConvert must not float over call sites. + // TODO do we instead need a dependence edge of some sort for OpConvert? + // Would memory do the trick, or do we need something else that relates + // to safe point operations? continue } if len(v.Args) > 0 && v.Args[len(v.Args)-1].Type.IsMemory() { diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index c92109afa5..be6cdb5c0b 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -278,11 +278,6 @@ func (t *tester) registerStdTest(pkg string) { // TODO: Remove when SSA codegen is used by default. func (t *tester) registerSSATest(pkg string) { - switch pkg { - // known failures - case "runtime": - return - } t.tests = append(t.tests, distTest{ name: "go_test_ssa:" + pkg, heading: "Testing packages with SSA codegen.", diff --git a/src/cmd/internal/obj/stack.go b/src/cmd/internal/obj/stack.go index 87698b3eeb..1ca673285a 100644 --- a/src/cmd/internal/obj/stack.go +++ b/src/cmd/internal/obj/stack.go @@ -41,7 +41,7 @@ const ( STACKSYSTEM = 0 StackSystem = STACKSYSTEM StackBig = 4096 - StackGuard = 640*stackGuardMultiplier + StackSystem + StackGuard = 1024*stackGuardMultiplier + StackSystem StackSmall = 128 StackLimit = StackGuard - StackSystem - StackSmall ) diff --git a/src/cmd/internal/obj/util.go b/src/cmd/internal/obj/util.go index 73d33666e2..a71d69edfc 100644 --- a/src/cmd/internal/obj/util.go +++ b/src/cmd/internal/obj/util.go @@ -385,6 +385,9 @@ func Dconv(p *Prog, a *Addr) string { if a.Index != REG_NONE { str += fmt.Sprintf("(%v*%d)", Rconv(int(a.Index)), int(a.Scale)) } + if p.As == ATYPE && a.Gotype != nil { + str += fmt.Sprintf("%s", a.Gotype.Name) + } case TYPE_CONST: if a.Reg != 0 { diff --git a/src/runtime/stack.go b/src/runtime/stack.go index 1809a4d9ac..128278ebdc 100644 --- a/src/runtime/stack.go +++ b/src/runtime/stack.go @@ -86,7 +86,7 @@ const ( // The stack guard is a pointer this many bytes above the // bottom of the stack. - _StackGuard = 640*stackGuardMultiplier + _StackSystem + _StackGuard = 1024*stackGuardMultiplier + _StackSystem // After a stack split check the SP is allowed to be this // many bytes below the stack guard. This saves an instruction diff --git a/test/nosplit.go b/test/nosplit.go index e5c2a9f30e..70e8fced86 100644 --- a/test/nosplit.go +++ b/test/nosplit.go @@ -9,6 +9,7 @@ package main import ( "bytes" + "cmd/internal/obj" "fmt" "io/ioutil" "log" @@ -285,12 +286,13 @@ TestCases: // Instead of rewriting the test cases above, adjust // the first stack frame to use up the extra bytes. if i == 0 { - size += 512 - 128 + size += (obj.StackGuard - 128) - 128 // Noopt builds have a larger stackguard. - // See ../cmd/dist/buildruntime.go:stackGuardMultiplier + // See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier + // This increase is included in obj.StackGuard for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") { if s == "-N" { - size += 640 + size += obj.StackGuard } } } -- cgit v1.3 From 7b773946c09e075ed50c49e76e08f61c16616ee4 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Fri, 22 Jan 2016 13:44:58 -0800 Subject: [dev.ssa] cmd/compile: disable xor clearing when flags must be preserved The x86 backend automatically rewrites MOV $0, AX to XOR AX, AX. That rewrite isn't ok when the flags register is live across the MOV. Keep track of which moves care about preserving flags, then disable this rewrite for them. On x86, Prog.Mark was being used to hold the length of the instruction. We already store that in Prog.Isize, so no need to store it in Prog.Mark also. This frees up Prog.Mark to hold a bitmask on x86 just like all the other architectures. Update #12405 Change-Id: Ibad8a8f41fc6222bec1e4904221887d3cc3ca029 Reviewed-on: https://go-review.googlesource.com/18861 Reviewed-by: David Chase Reviewed-by: Russ Cox --- src/cmd/compile/internal/gc/ssa.go | 29 ++++++++++++++++++++++++++++ src/cmd/compile/internal/ssa/block.go | 3 +++ src/cmd/compile/internal/ssa/flagalloc.go | 5 +++++ src/cmd/compile/internal/ssa/gen/AMD64Ops.go | 11 ++++------- src/cmd/compile/internal/ssa/opGen.go | 4 ---- src/cmd/compile/internal/ssa/regalloc.go | 9 --------- src/cmd/internal/obj/link.go | 6 +++--- src/cmd/internal/obj/pass.go | 1 - src/cmd/internal/obj/x86/a.out.go | 6 ++++++ src/cmd/internal/obj/x86/asm6.go | 9 ++++++--- src/cmd/internal/obj/x86/obj6.go | 20 +++++++++---------- 11 files changed, 66 insertions(+), 37 deletions(-) (limited to 'src/cmd/internal') diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 5b8d2423d7..de00fe9651 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -3405,6 +3405,7 @@ func genssa(f *ssa.Func, ptxt *obj.Prog, gcargs, gclocals *Sym) { for i, b := range f.Blocks { s.bstart[b.ID] = Pc // Emit values in block + s.markMoves(b) for _, v := range b.Values { x := Pc s.genValue(v) @@ -3864,6 +3865,11 @@ func (s *genState) genValue(v *ssa.Value) { p.From.Offset = i p.To.Type = obj.TYPE_REG p.To.Reg = x + // If flags are live at this instruction, suppress the + // MOV $0,AX -> XOR AX,AX optimization. + if v.Aux != nil { + p.Mark |= x86.PRESERVEFLAGS + } case ssa.OpAMD64MOVSSconst, ssa.OpAMD64MOVSDconst: x := regnum(v) p := Prog(v.Op.Asm()) @@ -4237,6 +4243,29 @@ func (s *genState) genValue(v *ssa.Value) { } } +// markMoves marks any MOVXconst ops that need to avoid clobbering flags. +func (s *genState) markMoves(b *ssa.Block) { + flive := b.FlagsLiveAtEnd + if b.Control != nil && b.Control.Type.IsFlags() { + flive = true + } + for i := len(b.Values) - 1; i >= 0; i-- { + v := b.Values[i] + if flive && (v.Op == ssa.OpAMD64MOVWconst || v.Op == ssa.OpAMD64MOVLconst || v.Op == ssa.OpAMD64MOVQconst) { + // The "mark" is any non-nil Aux value. + v.Aux = v + } + if v.Type.IsFlags() { + flive = false + } + for _, a := range v.Args { + if a.Type.IsFlags() { + flive = true + } + } + } +} + // movZero generates a register indirect move with a 0 immediate and keeps track of bytes left and next offset func movZero(as int, width int64, nbytes int64, offset int64, regnum int16) (nleft int64, noff int64) { p := Prog(as) diff --git a/src/cmd/compile/internal/ssa/block.go b/src/cmd/compile/internal/ssa/block.go index 5fb93cd5a7..02673f0650 100644 --- a/src/cmd/compile/internal/ssa/block.go +++ b/src/cmd/compile/internal/ssa/block.go @@ -50,6 +50,9 @@ type Block struct { // Ignored if len(Succs) < 2. // Fatal if not BranchUnknown and len(Succs) > 2. Likely BranchPrediction + + // After flagalloc, records whether flags are live at the end of the block. + FlagsLiveAtEnd bool } // kind control successors diff --git a/src/cmd/compile/internal/ssa/flagalloc.go b/src/cmd/compile/internal/ssa/flagalloc.go index c088158057..f4e289e782 100644 --- a/src/cmd/compile/internal/ssa/flagalloc.go +++ b/src/cmd/compile/internal/ssa/flagalloc.go @@ -120,4 +120,9 @@ func flagalloc(f *Func) { // standard regs, and it runs next.) } } + + // Save live flag state for later. + for _, b := range f.Blocks { + b.FlagsLiveAtEnd = end[b.ID] != nil + } } diff --git a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go index daee7336b0..dcffb49f63 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go +++ b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go @@ -93,7 +93,6 @@ func init() { // Common regInfo var ( gp01 = regInfo{inputs: []regMask{}, outputs: gponly} - gp01flags = regInfo{inputs: []regMask{}, outputs: gponly, clobbers: flags} gp11 = regInfo{inputs: []regMask{gpsp}, outputs: gponly, clobbers: flags} gp11nf = regInfo{inputs: []regMask{gpsp}, outputs: gponly} // nf: no flags clobbered gp11sb = regInfo{inputs: []regMask{gpspsb}, outputs: gponly} @@ -340,12 +339,10 @@ func init() { {name: "MOVLQSX", reg: gp11nf, asm: "MOVLQSX"}, // sign extend arg0 from int32 to int64 {name: "MOVLQZX", reg: gp11nf, asm: "MOVLQZX"}, // zero extend arg0 from int32 to int64 - // clobbers flags as liblink will rewrite these to XOR reg, reg if the constant is zero - // TODO: revisit when issue 12405 is fixed - {name: "MOVBconst", reg: gp01flags, asm: "MOVB", typ: "UInt8"}, // 8 low bits of auxint - {name: "MOVWconst", reg: gp01flags, asm: "MOVW", typ: "UInt16"}, // 16 low bits of auxint - {name: "MOVLconst", reg: gp01flags, asm: "MOVL", typ: "UInt32"}, // 32 low bits of auxint - {name: "MOVQconst", reg: gp01flags, asm: "MOVQ", typ: "UInt64"}, // auxint + {name: "MOVBconst", reg: gp01, asm: "MOVB", typ: "UInt8"}, // 8 low bits of auxint + {name: "MOVWconst", reg: gp01, asm: "MOVW", typ: "UInt16"}, // 16 low bits of auxint + {name: "MOVLconst", reg: gp01, asm: "MOVL", typ: "UInt32"}, // 32 low bits of auxint + {name: "MOVQconst", reg: gp01, asm: "MOVQ", typ: "UInt64"}, // auxint {name: "CVTTSD2SL", reg: fpgp, asm: "CVTTSD2SL"}, // convert float64 to int32 {name: "CVTTSD2SQ", reg: fpgp, asm: "CVTTSD2SQ"}, // convert float64 to int64 diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index 497b690192..d391b2435e 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -2694,7 +2694,6 @@ var opcodeTable = [...]opInfo{ name: "MOVBconst", asm: x86.AMOVB, reg: regInfo{ - clobbers: 8589934592, // .FLAGS outputs: []regMask{ 65519, // .AX .CX .DX .BX .BP .SI .DI .R8 .R9 .R10 .R11 .R12 .R13 .R14 .R15 }, @@ -2704,7 +2703,6 @@ var opcodeTable = [...]opInfo{ name: "MOVWconst", asm: x86.AMOVW, reg: regInfo{ - clobbers: 8589934592, // .FLAGS outputs: []regMask{ 65519, // .AX .CX .DX .BX .BP .SI .DI .R8 .R9 .R10 .R11 .R12 .R13 .R14 .R15 }, @@ -2714,7 +2712,6 @@ var opcodeTable = [...]opInfo{ name: "MOVLconst", asm: x86.AMOVL, reg: regInfo{ - clobbers: 8589934592, // .FLAGS outputs: []regMask{ 65519, // .AX .CX .DX .BX .BP .SI .DI .R8 .R9 .R10 .R11 .R12 .R13 .R14 .R15 }, @@ -2724,7 +2721,6 @@ var opcodeTable = [...]opInfo{ name: "MOVQconst", asm: x86.AMOVQ, reg: regInfo{ - clobbers: 8589934592, // .FLAGS outputs: []regMask{ 65519, // .AX .CX .DX .BX .BP .SI .DI .R8 .R9 .R10 .R11 .R12 .R13 .R14 .R15 }, diff --git a/src/cmd/compile/internal/ssa/regalloc.go b/src/cmd/compile/internal/ssa/regalloc.go index 27deeba718..7cbd30311f 100644 --- a/src/cmd/compile/internal/ssa/regalloc.go +++ b/src/cmd/compile/internal/ssa/regalloc.go @@ -1415,15 +1415,6 @@ func (v *Value) rematerializeable() bool { // We can't rematerialize instructions which // clobber the flags register. if regspec.clobbers&flagRegMask != 0 { - if v.Op == OpAMD64MOVQconst && v.AuxInt != 0 || - v.Op == OpAMD64MOVLconst && int32(v.AuxInt) != 0 || - v.Op == OpAMD64MOVWconst && int16(v.AuxInt) != 0 || - v.Op == OpAMD64MOVBconst && int8(v.AuxInt) != 0 { - // These are marked as clobbering flags, but only - // the 0 versions actually do. TODO: fix MOV->XOR rewrites - // to understand when they are allowed to clobber flags? - return true - } return false } diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index bc898235c1..f3d1a9557a 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -214,14 +214,14 @@ type Prog struct { Spadj int32 As int16 Reg int16 - RegTo2 int16 // 2nd register output operand - Mark uint16 + RegTo2 int16 // 2nd register output operand + Mark uint16 // bitmask of arch-specific items Optab uint16 Scond uint8 Back uint8 Ft uint8 Tt uint8 - Isize uint8 + Isize uint8 // size of the instruction in bytes (x86 only) Mode int8 Info ProgInfo diff --git a/src/cmd/internal/obj/pass.go b/src/cmd/internal/obj/pass.go index b92dfe23fb..14c9b6aaba 100644 --- a/src/cmd/internal/obj/pass.go +++ b/src/cmd/internal/obj/pass.go @@ -203,7 +203,6 @@ func linkpatch(ctxt *Link, sym *LSym) { } for p := sym.Text; p != nil; p = p.Link { - p.Mark = 0 /* initialization for follow */ if p.Pcond != nil { p.Pcond = brloop(ctxt, p.Pcond) if p.Pcond != nil { diff --git a/src/cmd/internal/obj/x86/a.out.go b/src/cmd/internal/obj/x86/a.out.go index 4ee8cfbc6c..f163505fd0 100644 --- a/src/cmd/internal/obj/x86/a.out.go +++ b/src/cmd/internal/obj/x86/a.out.go @@ -34,6 +34,12 @@ import "cmd/internal/obj" //go:generate go run ../stringer.go -i $GOFILE -o anames.go -p x86 +const ( + /* mark flags */ + DONE = 1 << iota + PRESERVEFLAGS // not allowed to clobber flags +) + /* * amd64 */ diff --git a/src/cmd/internal/obj/x86/asm6.go b/src/cmd/internal/obj/x86/asm6.go index 164dbd6064..8d0f86681f 100644 --- a/src/cmd/internal/obj/x86/asm6.go +++ b/src/cmd/internal/obj/x86/asm6.go @@ -1748,7 +1748,7 @@ func span6(ctxt *obj.Link, s *obj.LSym) { // process forward jumps to p for q = p.Rel; q != nil; q = q.Forwd { - v = int32(p.Pc - (q.Pc + int64(q.Mark))) + v = int32(p.Pc - (q.Pc + int64(q.Isize))) if q.Back&2 != 0 { // short if v > 127 { loop++ @@ -1761,7 +1761,7 @@ func span6(ctxt *obj.Link, s *obj.LSym) { s.P[q.Pc+1] = byte(v) } } else { - bp = s.P[q.Pc+int64(q.Mark)-4:] + bp = s.P[q.Pc+int64(q.Isize)-4:] bp[0] = byte(v) bp = bp[1:] bp[0] = byte(v >> 8) @@ -1784,7 +1784,6 @@ func span6(ctxt *obj.Link, s *obj.LSym) { obj.Symgrow(ctxt, s, p.Pc+int64(m)) copy(s.P[p.Pc:][:m], ctxt.And[:m]) - p.Mark = uint16(m) c += int32(m) } @@ -2157,6 +2156,10 @@ func oclass(ctxt *obj.Link, p *obj.Prog, a *obj.Addr) int { v = int64(int32(v)) } if v == 0 { + if p.Mark&PRESERVEFLAGS != 0 { + // If PRESERVEFLAGS is set, avoid MOV $0, AX turning into XOR AX, AX. + return Yu7 + } return Yi0 } if v == 1 { diff --git a/src/cmd/internal/obj/x86/obj6.go b/src/cmd/internal/obj/x86/obj6.go index eff6c004c6..e545374828 100644 --- a/src/cmd/internal/obj/x86/obj6.go +++ b/src/cmd/internal/obj/x86/obj6.go @@ -1214,16 +1214,16 @@ loop: q = p.Pcond if q != nil && q.As != obj.ATEXT { /* mark instruction as done and continue layout at target of jump */ - p.Mark = 1 + p.Mark |= DONE p = q - if p.Mark == 0 { + if p.Mark&DONE == 0 { goto loop } } } - if p.Mark != 0 { + if p.Mark&DONE != 0 { /* * p goes here, but already used it elsewhere. * copy up to 4 instructions or else branch to other copy. @@ -1246,7 +1246,7 @@ loop: if nofollow(a) || pushpop(a) { break // NOTE(rsc): arm does goto copy } - if q.Pcond == nil || q.Pcond.Mark != 0 { + if q.Pcond == nil || q.Pcond.Mark&DONE != 0 { continue } if a == obj.ACALL || a == ALOOP { @@ -1260,10 +1260,10 @@ loop: q = obj.Copyp(ctxt, p) p = p.Link - q.Mark = 1 + q.Mark |= DONE (*last).Link = q *last = q - if int(q.As) != a || q.Pcond == nil || q.Pcond.Mark != 0 { + if int(q.As) != a || q.Pcond == nil || q.Pcond.Mark&DONE != 0 { continue } @@ -1273,7 +1273,7 @@ loop: q.Link = p xfol(ctxt, q.Link, last) p = q.Link - if p.Mark != 0 { + if p.Mark&DONE != 0 { return } goto loop @@ -1290,7 +1290,7 @@ loop: } /* emit p */ - p.Mark = 1 + p.Mark |= DONE (*last).Link = p *last = p @@ -1328,7 +1328,7 @@ loop: } } else { q = p.Link - if q.Mark != 0 { + if q.Mark&DONE != 0 { if a != ALOOP { p.As = relinv(int16(a)) p.Link = p.Pcond @@ -1338,7 +1338,7 @@ loop: } xfol(ctxt, p.Link, last) - if p.Pcond.Mark != 0 { + if p.Pcond.Mark&DONE != 0 { return } p = p.Pcond -- cgit v1.3 From 7de8cfdf9ce3942a255172e03c8f2ad380a30aca Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Thu, 4 Feb 2016 11:21:31 -0800 Subject: [dev.ssa] cmd/internal/obj/x86: don't clobber flags with dynlink rewrite LEAQ symbol+100(SB), AX Under dynamic link, rewrites to MOVQ symbol@GOT(SB), AX ADDQ $100, AX but ADDQ clobbers flags, whereas the original LEAQ (when not dynamic linking) doesn't. Use LEAQ instead of ADDQ to add that constant in so we preserve flags. Change-Id: Ibb055403d94a4c5163e1c7d2f45da633ffd0b6a3 Reviewed-on: https://go-review.googlesource.com/19230 Reviewed-by: David Chase Run-TryBot: David Chase Reviewed-by: Ian Lance Taylor --- src/cmd/internal/obj/x86/obj6.go | 7 ++++--- src/cmd/internal/obj/x86/obj6_test.go | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/obj/x86/obj6.go b/src/cmd/internal/obj/x86/obj6.go index e545374828..1955aa560d 100644 --- a/src/cmd/internal/obj/x86/obj6.go +++ b/src/cmd/internal/obj/x86/obj6.go @@ -377,7 +377,7 @@ func rewriteToUseGot(ctxt *obj.Link, p *obj.Prog) { } if p.From.Type == obj.TYPE_ADDR && p.From.Name == obj.NAME_EXTERN && !p.From.Sym.Local { // $MOV $sym, Rx becomes $MOV sym@GOT, Rx - // $MOV $sym+, Rx becomes $MOV sym@GOT, Rx; $ADD , Rx + // $MOV $sym+, Rx becomes $MOV sym@GOT, Rx; $LEA (Rx), Rx // On 386 only, more complicated things like PUSHL $sym become $MOV sym@GOT, CX; PUSHL CX cmplxdest := false pAs := p.As @@ -399,8 +399,9 @@ func rewriteToUseGot(ctxt *obj.Link, p *obj.Prog) { q := p if p.From.Offset != 0 { q = obj.Appendp(ctxt, p) - q.As = add - q.From.Type = obj.TYPE_CONST + q.As = lea + q.From.Type = obj.TYPE_MEM + q.From.Reg = p.To.Reg q.From.Offset = p.From.Offset q.To = p.To p.From.Offset = 0 diff --git a/src/cmd/internal/obj/x86/obj6_test.go b/src/cmd/internal/obj/x86/obj6_test.go index 4387db696d..d83ab24ab8 100644 --- a/src/cmd/internal/obj/x86/obj6_test.go +++ b/src/cmd/internal/obj/x86/obj6_test.go @@ -20,9 +20,9 @@ const testdata = ` MOVQ AX, AX -> MOVQ AX, AX LEAQ name(SB), AX -> MOVQ name@GOT(SB), AX -LEAQ name+10(SB), AX -> MOVQ name@GOT(SB), AX; ADDQ $10, AX +LEAQ name+10(SB), AX -> MOVQ name@GOT(SB), AX; LEAQ 10(AX), AX MOVQ $name(SB), AX -> MOVQ name@GOT(SB), AX -MOVQ $name+10(SB), AX -> MOVQ name@GOT(SB), AX; ADDQ $10, AX +MOVQ $name+10(SB), AX -> MOVQ name@GOT(SB), AX; LEAQ 10(AX), AX MOVQ name(SB), AX -> NOP; MOVQ name@GOT(SB), R15; MOVQ (R15), AX MOVQ name+10(SB), AX -> NOP; MOVQ name@GOT(SB), R15; MOVQ 10(R15), AX -- cgit v1.3 From d3f15ff6bc353d94b7249f33bb030ee1f7ee887e Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Thu, 25 Feb 2016 11:40:51 -0800 Subject: [dev.ssa] cmd/compile: shrink stack guard Our stack frame sizes look pretty good now. Lower the stack guard from 1024 to 720. Tip is currently using 720. We could go lower (to 640 at least) except PPC doesn't like that. Change-Id: Ie5f96c0e822435638223f1e8a2bd1a1eed68e6aa Reviewed-on: https://go-review.googlesource.com/19922 Run-TryBot: Keith Randall TryBot-Result: Gobot Gobot Reviewed-by: David Chase --- src/cmd/internal/obj/stack.go | 2 +- src/runtime/stack.go | 2 +- test/nosplit.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/cmd/internal') diff --git a/src/cmd/internal/obj/stack.go b/src/cmd/internal/obj/stack.go index 1a2ee12291..80f6c6c164 100644 --- a/src/cmd/internal/obj/stack.go +++ b/src/cmd/internal/obj/stack.go @@ -11,7 +11,7 @@ const ( STACKSYSTEM = 0 StackSystem = STACKSYSTEM StackBig = 4096 - StackGuard = 1024*stackGuardMultiplier + StackSystem + StackGuard = 720*stackGuardMultiplier + StackSystem StackSmall = 128 StackLimit = StackGuard - StackSystem - StackSmall ) diff --git a/src/runtime/stack.go b/src/runtime/stack.go index ba1a1bb143..81059965d9 100644 --- a/src/runtime/stack.go +++ b/src/runtime/stack.go @@ -90,7 +90,7 @@ const ( // The stack guard is a pointer this many bytes above the // bottom of the stack. - _StackGuard = 1024*sys.StackGuardMultiplier + _StackSystem + _StackGuard = 720*sys.StackGuardMultiplier + _StackSystem // After a stack split check the SP is allowed to be this // many bytes below the stack guard. This saves an instruction diff --git a/test/nosplit.go b/test/nosplit.go index 2bf7077808..082fc3b0e6 100644 --- a/test/nosplit.go +++ b/test/nosplit.go @@ -302,13 +302,13 @@ TestCases: // Instead of rewriting the test cases above, adjust // the first stack frame to use up the extra bytes. if i == 0 { - size += (1024 - 128) - 128 + size += (720 - 128) - 128 // Noopt builds have a larger stackguard. // See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier // This increase is included in obj.StackGuard for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") { if s == "-N" { - size += 1024 + size += 720 } } } -- cgit v1.3