mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
[dev.ssa] cmd/compile/ssa: separate logging, work in progress, and fatal errors
The SSA implementation logs for three purposes: * debug logging * fatal errors * unimplemented features Separating these three uses lets us attempt an SSA implementation for all functions, not just _ssa functions. This turns the entire standard library into a compilation test, and makes it easy to figure out things like "how much coverage does SSA have now" and "what should we do next to get more coverage?". Functions called _ssa are still special. They log profusely by default and the output of the SSA implementation is used. For all other functions, logging is off, and the implementation is built and discarded, due to lack of support for the runtime. While we're here, fix a few minor bugs and add some extra Unimplementeds to allow all.bash to pass. As of now, SSA handles 20.79% of the functions in the standard library (689 of 3314). The top missing features are: 10.03% 2597 SSA unimplemented: zero for type error not implemented 7.79% 2016 SSA unimplemented: addr: bad op DOTPTR 7.33% 1898 SSA unimplemented: unhandled expr EQ 6.10% 1579 SSA unimplemented: unhandled expr OROR 4.91% 1271 SSA unimplemented: unhandled expr NE 4.49% 1163 SSA unimplemented: unhandled expr LROT 4.00% 1036 SSA unimplemented: unhandled expr LEN 3.56% 923 SSA unimplemented: unhandled stmt CALLFUNC 2.37% 615 SSA unimplemented: zero for type []byte not implemented 1.90% 492 SSA unimplemented: unhandled stmt CALLMETH 1.74% 450 SSA unimplemented: unhandled expr CALLINTER 1.74% 450 SSA unimplemented: unhandled expr DOT 1.71% 444 SSA unimplemented: unhandled expr ANDAND 1.65% 426 SSA unimplemented: unhandled expr CLOSUREVAR 1.54% 400 SSA unimplemented: unhandled expr CALLMETH 1.51% 390 SSA unimplemented: unhandled stmt SWITCH 1.47% 380 SSA unimplemented: unhandled expr CONV 1.33% 345 SSA unimplemented: addr: bad op * 1.30% 336 SSA unimplemented: unhandled OLITERAL 6 Change-Id: I4ca07951e276714dc13c31de28640aead17a1be7 Reviewed-on: https://go-review.googlesource.com/11160 Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
parent
2aabacdb5a
commit
8c6abfeacb
26 changed files with 211 additions and 132 deletions
|
|
@ -355,6 +355,7 @@ func compile(fn *Node) {
|
||||||
var gcargs *Sym
|
var gcargs *Sym
|
||||||
var gclocals *Sym
|
var gclocals *Sym
|
||||||
var ssafn *ssa.Func
|
var ssafn *ssa.Func
|
||||||
|
var usessa bool
|
||||||
if fn.Nbody == nil {
|
if fn.Nbody == nil {
|
||||||
if pure_go != 0 || strings.HasPrefix(fn.Func.Nname.Sym.Name, "init.") {
|
if pure_go != 0 || strings.HasPrefix(fn.Func.Nname.Sym.Name, "init.") {
|
||||||
Yyerror("missing function body for %q", fn.Func.Nname.Sym.Name)
|
Yyerror("missing function body for %q", fn.Func.Nname.Sym.Name)
|
||||||
|
|
@ -406,13 +407,9 @@ func compile(fn *Node) {
|
||||||
goto ret
|
goto ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build an SSA backend function
|
// Build an SSA backend function.
|
||||||
{
|
// TODO: get rid of usessa.
|
||||||
name := Curfn.Func.Nname.Sym.Name
|
ssafn, usessa = buildssa(Curfn)
|
||||||
if len(name) > 4 && name[len(name)-4:] == "_ssa" {
|
|
||||||
ssafn = buildssa(Curfn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
continpc = nil
|
continpc = nil
|
||||||
breakpc = nil
|
breakpc = nil
|
||||||
|
|
@ -475,7 +472,7 @@ func compile(fn *Node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ssafn != nil {
|
if ssafn != nil && usessa {
|
||||||
genssa(ssafn, ptxt, gcargs, gclocals)
|
genssa(ssafn, ptxt, gcargs, gclocals)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,26 +5,48 @@
|
||||||
package gc
|
package gc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"fmt"
|
||||||
|
|
||||||
"cmd/compile/internal/ssa"
|
"cmd/compile/internal/ssa"
|
||||||
"cmd/internal/obj"
|
"cmd/internal/obj"
|
||||||
"cmd/internal/obj/x86" // TODO: remove
|
"cmd/internal/obj/x86" // TODO: remove
|
||||||
)
|
)
|
||||||
|
|
||||||
func buildssa(fn *Node) *ssa.Func {
|
// buildssa builds an SSA function
|
||||||
dumplist("buildssa-enter", fn.Func.Enter)
|
// and reports whether it should be used.
|
||||||
dumplist("buildssa-body", fn.Nbody)
|
// Once the SSA implementation is complete,
|
||||||
|
// 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
|
||||||
|
usessa = len(name) > 4 && name[len(name)-4:] == "_ssa"
|
||||||
|
|
||||||
|
if usessa {
|
||||||
|
dumplist("buildssa-enter", fn.Func.Enter)
|
||||||
|
dumplist("buildssa-body", fn.Nbody)
|
||||||
|
}
|
||||||
|
|
||||||
var s state
|
var s state
|
||||||
|
|
||||||
s.pushLine(fn.Lineno)
|
s.pushLine(fn.Lineno)
|
||||||
defer s.popLine()
|
defer s.popLine()
|
||||||
|
|
||||||
// TODO(khr): build config just once at the start of the compiler binary
|
// TODO(khr): build config just once at the start of the compiler binary
|
||||||
s.config = ssa.NewConfig(Thearch.Thestring, ssaExport{})
|
|
||||||
|
var e ssaExport
|
||||||
|
e.log = usessa
|
||||||
|
s.config = ssa.NewConfig(Thearch.Thestring, &e)
|
||||||
s.f = s.config.NewFunc()
|
s.f = s.config.NewFunc()
|
||||||
s.f.Name = fn.Func.Nname.Sym.Name
|
s.f.Name = name
|
||||||
|
|
||||||
|
// 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
|
// We construct SSA using an algorithm similar to
|
||||||
// Brau, Buchwald, Hack, Leißa, Mallon, and Zwinkau
|
// Brau, Buchwald, Hack, Leißa, Mallon, and Zwinkau
|
||||||
|
|
@ -67,7 +89,15 @@ func buildssa(fn *Node) *ssa.Func {
|
||||||
// Main call to ssa package to compile function
|
// Main call to ssa package to compile function
|
||||||
ssa.Compile(s.f)
|
ssa.Compile(s.f)
|
||||||
|
|
||||||
return 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
|
||||||
|
}
|
||||||
|
return s.f, usessa // TODO: return s.f, true once runtime support is in (gc maps, write barriers, etc.)
|
||||||
}
|
}
|
||||||
|
|
||||||
type state struct {
|
type state struct {
|
||||||
|
|
@ -105,10 +135,13 @@ type state struct {
|
||||||
line []int32
|
line []int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *state) Fatal(msg string, args ...interface{}) { s.config.Fatal(msg, args...) }
|
||||||
|
func (s *state) Unimplemented(msg string, args ...interface{}) { s.config.Unimplemented(msg, args...) }
|
||||||
|
|
||||||
// startBlock sets the current block we're generating code in to b.
|
// startBlock sets the current block we're generating code in to b.
|
||||||
func (s *state) startBlock(b *ssa.Block) {
|
func (s *state) startBlock(b *ssa.Block) {
|
||||||
if s.curBlock != nil {
|
if s.curBlock != nil {
|
||||||
log.Fatalf("starting block %v when block %v has not ended", b, s.curBlock)
|
s.Fatal("starting block %v when block %v has not ended", b, s.curBlock)
|
||||||
}
|
}
|
||||||
s.curBlock = b
|
s.curBlock = b
|
||||||
s.vars = map[string]*ssa.Value{}
|
s.vars = map[string]*ssa.Value{}
|
||||||
|
|
@ -230,7 +263,7 @@ func (s *state) stmt(n *Node) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if compiling_runtime != 0 {
|
if compiling_runtime != 0 {
|
||||||
log.Fatalf("%v escapes to heap, not allowed in runtime.", n)
|
Fatal("%v escapes to heap, not allowed in runtime.", n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: the old pass hides the details of PHEAP
|
// TODO: the old pass hides the details of PHEAP
|
||||||
|
|
@ -260,6 +293,9 @@ func (s *state) stmt(n *Node) {
|
||||||
// next we work on the label's target block
|
// next we work on the label's target block
|
||||||
s.startBlock(t)
|
s.startBlock(t)
|
||||||
}
|
}
|
||||||
|
if n.Op == OGOTO && s.curBlock == nil {
|
||||||
|
s.Unimplemented("goto at start of function; see test/goto.go")
|
||||||
|
}
|
||||||
|
|
||||||
case OAS, OASWB:
|
case OAS, OASWB:
|
||||||
s.assign(n.Op, n.Left, n.Right)
|
s.assign(n.Op, n.Left, n.Right)
|
||||||
|
|
@ -317,6 +353,9 @@ func (s *state) stmt(n *Node) {
|
||||||
|
|
||||||
// generate code to test condition
|
// generate code to test condition
|
||||||
// TODO(khr): Left == nil exception
|
// TODO(khr): Left == nil exception
|
||||||
|
if n.Left == nil {
|
||||||
|
s.Unimplemented("cond n.Left == nil: %v", n)
|
||||||
|
}
|
||||||
s.startBlock(bCond)
|
s.startBlock(bCond)
|
||||||
cond := s.expr(n.Left)
|
cond := s.expr(n.Left)
|
||||||
b = s.endBlock()
|
b = s.endBlock()
|
||||||
|
|
@ -342,7 +381,7 @@ func (s *state) stmt(n *Node) {
|
||||||
// TODO(khr): ??? anything to do here? Only for addrtaken variables?
|
// TODO(khr): ??? anything to do here? Only for addrtaken variables?
|
||||||
// Maybe just link it in the store chain?
|
// Maybe just link it in the store chain?
|
||||||
default:
|
default:
|
||||||
log.Fatalf("unhandled stmt %s", opnames[n.Op])
|
s.Unimplemented("unhandled stmt %s", opnames[n.Op])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -370,7 +409,7 @@ func (s *state) expr(n *Node) *ssa.Value {
|
||||||
case CTSTR:
|
case CTSTR:
|
||||||
return s.entryNewValue0A(ssa.OpConst, n.Type, n.Val().U)
|
return s.entryNewValue0A(ssa.OpConst, n.Type, n.Val().U)
|
||||||
default:
|
default:
|
||||||
log.Fatalf("unhandled OLITERAL %v", n.Val().Ctype())
|
s.Unimplemented("unhandled OLITERAL %v", n.Val().Ctype())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case OCONVNOP:
|
case OCONVNOP:
|
||||||
|
|
@ -474,7 +513,7 @@ func (s *state) expr(n *Node) *ssa.Value {
|
||||||
a := s.entryNewValue1I(ssa.OpOffPtr, Ptrto(fp.Type), fp.Width, s.sp)
|
a := s.entryNewValue1I(ssa.OpOffPtr, Ptrto(fp.Type), fp.Width, s.sp)
|
||||||
return s.newValue2(ssa.OpLoad, fp.Type, a, call)
|
return s.newValue2(ssa.OpLoad, fp.Type, a, call)
|
||||||
default:
|
default:
|
||||||
log.Fatalf("unhandled expr %s", opnames[n.Op])
|
s.Unimplemented("unhandled expr %s", opnames[n.Op])
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -494,7 +533,7 @@ func (s *state) assign(op uint8, left *Node, right *Node) {
|
||||||
case t.IsBoolean():
|
case t.IsBoolean():
|
||||||
val = s.entryNewValue0A(ssa.OpConst, left.Type, false) // TODO: store bools as 0/1 in AuxInt?
|
val = s.entryNewValue0A(ssa.OpConst, left.Type, false) // TODO: store bools as 0/1 in AuxInt?
|
||||||
default:
|
default:
|
||||||
log.Fatalf("zero for type %v not implemented", t)
|
s.Unimplemented("zero for type %v not implemented", t)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val = s.expr(right)
|
val = s.expr(right)
|
||||||
|
|
@ -524,7 +563,7 @@ func (s *state) addr(n *Node) *ssa.Value {
|
||||||
return s.expr(n.Name.Heapaddr)
|
return s.expr(n.Name.Heapaddr)
|
||||||
default:
|
default:
|
||||||
// TODO: address of locals
|
// TODO: address of locals
|
||||||
log.Fatalf("variable address of %v not implemented", n)
|
s.Unimplemented("variable address of %v not implemented", n)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case OINDREG:
|
case OINDREG:
|
||||||
|
|
@ -547,7 +586,7 @@ func (s *state) addr(n *Node) *ssa.Value {
|
||||||
return s.newValue2(ssa.OpPtrIndex, Ptrto(n.Left.Type.Type), p, i)
|
return s.newValue2(ssa.OpPtrIndex, Ptrto(n.Left.Type.Type), p, i)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Fatalf("addr: bad op %v", Oconv(int(n.Op), 0))
|
s.Unimplemented("addr: bad op %v", Oconv(int(n.Op), 0))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -556,7 +595,7 @@ func (s *state) addr(n *Node) *ssa.Value {
|
||||||
// n must be an ONAME.
|
// n must be an ONAME.
|
||||||
func canSSA(n *Node) bool {
|
func canSSA(n *Node) bool {
|
||||||
if n.Op != ONAME {
|
if n.Op != ONAME {
|
||||||
log.Fatalf("canSSA passed a non-ONAME %s %v", Oconv(int(n.Op), 0), n)
|
Fatal("canSSA passed a non-ONAME %s %v", Oconv(int(n.Op), 0), n)
|
||||||
}
|
}
|
||||||
if n.Addrtaken {
|
if n.Addrtaken {
|
||||||
return false
|
return false
|
||||||
|
|
@ -610,7 +649,7 @@ func (s *state) boundsCheck(idx, len *ssa.Value) {
|
||||||
// variable returns the value of a variable at the current location.
|
// variable returns the value of a variable at the current location.
|
||||||
func (s *state) variable(name string, t ssa.Type) *ssa.Value {
|
func (s *state) variable(name string, t ssa.Type) *ssa.Value {
|
||||||
if s.curBlock == nil {
|
if s.curBlock == nil {
|
||||||
log.Fatalf("nil curblock!")
|
s.Fatal("nil curblock!")
|
||||||
}
|
}
|
||||||
v := s.vars[name]
|
v := s.vars[name]
|
||||||
if v == nil {
|
if v == nil {
|
||||||
|
|
@ -662,6 +701,10 @@ func (s *state) lookupVarIncoming(b *ssa.Block, t ssa.Type, name string) *ssa.Va
|
||||||
for _, p := range b.Preds {
|
for _, p := range b.Preds {
|
||||||
vals = append(vals, s.lookupVarOutgoing(p, t, name))
|
vals = append(vals, s.lookupVarOutgoing(p, t, name))
|
||||||
}
|
}
|
||||||
|
if len(vals) == 0 {
|
||||||
|
s.Unimplemented("TODO: Handle fixedbugs/bug076.go")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
v0 := vals[0]
|
v0 := vals[0]
|
||||||
for i := 1; i < len(vals); i++ {
|
for i := 1; i < len(vals); i++ {
|
||||||
if vals[i] != v0 {
|
if vals[i] != v0 {
|
||||||
|
|
@ -822,11 +865,14 @@ func genValue(v *ssa.Value) {
|
||||||
p.To.Type = obj.TYPE_REG
|
p.To.Type = obj.TYPE_REG
|
||||||
p.To.Reg = regnum(v)
|
p.To.Reg = regnum(v)
|
||||||
case ssa.OpAMD64MULQconst:
|
case ssa.OpAMD64MULQconst:
|
||||||
|
v.Unimplemented("IMULQ doasm")
|
||||||
|
return
|
||||||
// TODO: this isn't right. doasm fails on it. I don't think obj
|
// TODO: this isn't right. doasm fails on it. I don't think obj
|
||||||
// has ever been taught to compile imul $c, r1, r2.
|
// has ever been taught to compile imul $c, r1, r2.
|
||||||
p := Prog(x86.AIMULQ)
|
p := Prog(x86.AIMULQ)
|
||||||
p.From.Type = obj.TYPE_CONST
|
p.From.Type = obj.TYPE_CONST
|
||||||
p.From.Offset = v.AuxInt
|
p.From.Offset = v.AuxInt
|
||||||
|
p.From3 = new(obj.Addr)
|
||||||
p.From3.Type = obj.TYPE_REG
|
p.From3.Type = obj.TYPE_REG
|
||||||
p.From3.Reg = regnum(v.Args[0])
|
p.From3.Reg = regnum(v.Args[0])
|
||||||
p.To.Type = obj.TYPE_REG
|
p.To.Type = obj.TYPE_REG
|
||||||
|
|
@ -854,7 +900,7 @@ func genValue(v *ssa.Value) {
|
||||||
r := regnum(v)
|
r := regnum(v)
|
||||||
if x != r {
|
if x != r {
|
||||||
if r == x86.REG_CX {
|
if r == x86.REG_CX {
|
||||||
log.Fatalf("can't implement %s, target and shift both in CX", v.LongString())
|
v.Fatal("can't implement %s, target and shift both in CX", v.LongString())
|
||||||
}
|
}
|
||||||
p := Prog(x86.AMOVQ)
|
p := Prog(x86.AMOVQ)
|
||||||
p.From.Type = obj.TYPE_REG
|
p.From.Type = obj.TYPE_REG
|
||||||
|
|
@ -1003,12 +1049,12 @@ func genValue(v *ssa.Value) {
|
||||||
loc := f.RegAlloc[v.ID]
|
loc := f.RegAlloc[v.ID]
|
||||||
for _, a := range v.Args {
|
for _, a := range v.Args {
|
||||||
if f.RegAlloc[a.ID] != loc { // TODO: .Equal() instead?
|
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])
|
v.Fatal("phi arg at different location than phi %v %v %v %v", v, loc, a, f.RegAlloc[a.ID])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ssa.OpConst:
|
case ssa.OpConst:
|
||||||
if v.Block.Func.RegAlloc[v.ID] != nil {
|
if v.Block.Func.RegAlloc[v.ID] != nil {
|
||||||
log.Fatalf("const value %v shouldn't have a location", v)
|
v.Fatal("const value %v shouldn't have a location", v)
|
||||||
}
|
}
|
||||||
case ssa.OpArg:
|
case ssa.OpArg:
|
||||||
// memory arg needs no code
|
// memory arg needs no code
|
||||||
|
|
@ -1033,7 +1079,7 @@ func genValue(v *ssa.Value) {
|
||||||
case ssa.OpFP, ssa.OpSP:
|
case ssa.OpFP, ssa.OpSP:
|
||||||
// nothing to do
|
// nothing to do
|
||||||
default:
|
default:
|
||||||
log.Fatalf("value %s not implemented", v.LongString())
|
v.Unimplemented("value %s not implemented", v.LongString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1141,7 +1187,7 @@ func genBlock(b, next *ssa.Block, branches []branch) []branch {
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Fatalf("branch %s not implemented", b.LongString())
|
b.Unimplemented("branch %s not implemented", b.LongString())
|
||||||
}
|
}
|
||||||
return branches
|
return branches
|
||||||
}
|
}
|
||||||
|
|
@ -1183,10 +1229,40 @@ func localOffset(v *ssa.Value) int64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ssaExport exports a bunch of compiler services for the ssa backend.
|
// ssaExport exports a bunch of compiler services for the ssa backend.
|
||||||
type ssaExport struct{}
|
type ssaExport struct {
|
||||||
|
log bool
|
||||||
|
unimplemented bool
|
||||||
|
}
|
||||||
|
|
||||||
// StringSym returns a symbol (a *Sym wrapped in an interface) which
|
// StringSym returns a symbol (a *Sym wrapped in an interface) which
|
||||||
// is a global string constant containing s.
|
// is a global string constant containing s.
|
||||||
func (serv ssaExport) StringSym(s string) interface{} {
|
func (*ssaExport) StringSym(s string) interface{} {
|
||||||
return stringsym(s)
|
return stringsym(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log logs a message from the compiler.
|
||||||
|
func (e *ssaExport) Log(msg string, args ...interface{}) {
|
||||||
|
// If e was marked as unimplemented, anything could happen. Ignore.
|
||||||
|
if e.log && !e.unimplemented {
|
||||||
|
fmt.Printf(msg, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal reports a compiler error and exits.
|
||||||
|
func (e *ssaExport) Fatal(msg string, args ...interface{}) {
|
||||||
|
// If e was marked as unimplemented, anything could happen. Ignore.
|
||||||
|
if !e.unimplemented {
|
||||||
|
Fatal(msg, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unimplemented reports that the function cannot be compiled.
|
||||||
|
// It will be removed once SSA work is complete.
|
||||||
|
func (e *ssaExport) Unimplemented(msg string, args ...interface{}) {
|
||||||
|
const alwaysLog = false // enable to calculate top unimplemented features
|
||||||
|
if !e.unimplemented && (e.log || alwaysLog) {
|
||||||
|
// first implementation failure, print explanation
|
||||||
|
fmt.Printf("SSA unimplemented: "+msg+"\n", args...)
|
||||||
|
}
|
||||||
|
e.unimplemented = true
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@ Common-Subexpression Elimination
|
||||||
- Can we move control values out of their basic block?
|
- Can we move control values out of their basic block?
|
||||||
|
|
||||||
Other
|
Other
|
||||||
- Use gc.Fatal for errors. Add a callback to Frontend?
|
|
||||||
- Write barriers
|
- Write barriers
|
||||||
- For testing, do something more sophisticated than
|
- For testing, do something more sophisticated than
|
||||||
checkOpcodeCounts. Michael Matloob suggests using a similar
|
checkOpcodeCounts. Michael Matloob suggests using a similar
|
||||||
|
|
|
||||||
|
|
@ -69,3 +69,7 @@ func (b *Block) LongString() string {
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Block) Log(msg string, args ...interface{}) { b.Func.Log(msg, args...) }
|
||||||
|
func (b *Block) Fatal(msg string, args ...interface{}) { b.Func.Fatal(msg, args...) }
|
||||||
|
func (b *Block) Unimplemented(msg string, args ...interface{}) { b.Func.Unimplemented(msg, args...) }
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
import "log"
|
|
||||||
|
|
||||||
// checkFunc checks invariants of f.
|
// checkFunc checks invariants of f.
|
||||||
func checkFunc(f *Func) {
|
func checkFunc(f *Func) {
|
||||||
blockMark := make([]bool, f.NumBlocks())
|
blockMark := make([]bool, f.NumBlocks())
|
||||||
|
|
@ -13,17 +11,17 @@ func checkFunc(f *Func) {
|
||||||
|
|
||||||
for _, b := range f.Blocks {
|
for _, b := range f.Blocks {
|
||||||
if blockMark[b.ID] {
|
if blockMark[b.ID] {
|
||||||
log.Panicf("block %s appears twice in %s!", b, f.Name)
|
f.Fatal("block %s appears twice in %s!", b, f.Name)
|
||||||
}
|
}
|
||||||
blockMark[b.ID] = true
|
blockMark[b.ID] = true
|
||||||
if b.Func != f {
|
if b.Func != f {
|
||||||
log.Panicf("%s.Func=%s, want %s", b, b.Func.Name, f.Name)
|
f.Fatal("%s.Func=%s, want %s", b, b.Func.Name, f.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, c := range b.Succs {
|
for i, c := range b.Succs {
|
||||||
for j, d := range b.Succs {
|
for j, d := range b.Succs {
|
||||||
if i != j && c == d {
|
if i != j && c == d {
|
||||||
log.Panicf("%s.Succs has duplicate block %s", b, c)
|
f.Fatal("%s.Succs has duplicate block %s", b, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -46,64 +44,64 @@ func checkFunc(f *Func) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
log.Panicf("block %s is not a succ of its pred block %s", b, p)
|
f.Fatal("block %s is not a succ of its pred block %s", b, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch b.Kind {
|
switch b.Kind {
|
||||||
case BlockExit:
|
case BlockExit:
|
||||||
if len(b.Succs) != 0 {
|
if len(b.Succs) != 0 {
|
||||||
log.Panicf("exit block %s has successors", b)
|
f.Fatal("exit block %s has successors", b)
|
||||||
}
|
}
|
||||||
if b.Control == nil {
|
if b.Control == nil {
|
||||||
log.Panicf("exit block %s has no control value", b)
|
f.Fatal("exit block %s has no control value", b)
|
||||||
}
|
}
|
||||||
if !b.Control.Type.IsMemory() {
|
if !b.Control.Type.IsMemory() {
|
||||||
log.Panicf("exit block %s has non-memory control value %s", b, b.Control.LongString())
|
f.Fatal("exit block %s has non-memory control value %s", b, b.Control.LongString())
|
||||||
}
|
}
|
||||||
case BlockPlain:
|
case BlockPlain:
|
||||||
if len(b.Succs) != 1 {
|
if len(b.Succs) != 1 {
|
||||||
log.Panicf("plain block %s len(Succs)==%d, want 1", b, len(b.Succs))
|
f.Fatal("plain block %s len(Succs)==%d, want 1", b, len(b.Succs))
|
||||||
}
|
}
|
||||||
if b.Control != nil {
|
if b.Control != nil {
|
||||||
log.Panicf("plain block %s has non-nil control %s", b, b.Control.LongString())
|
f.Fatal("plain block %s has non-nil control %s", b, b.Control.LongString())
|
||||||
}
|
}
|
||||||
case BlockIf:
|
case BlockIf:
|
||||||
if len(b.Succs) != 2 {
|
if len(b.Succs) != 2 {
|
||||||
log.Panicf("if block %s len(Succs)==%d, want 2", b, len(b.Succs))
|
f.Fatal("if block %s len(Succs)==%d, want 2", b, len(b.Succs))
|
||||||
}
|
}
|
||||||
if b.Control == nil {
|
if b.Control == nil {
|
||||||
log.Panicf("if block %s has no control value", b)
|
f.Fatal("if block %s has no control value", b)
|
||||||
}
|
}
|
||||||
if !b.Control.Type.IsBoolean() {
|
if !b.Control.Type.IsBoolean() {
|
||||||
log.Panicf("if block %s has non-bool control value %s", b, b.Control.LongString())
|
f.Fatal("if block %s has non-bool control value %s", b, b.Control.LongString())
|
||||||
}
|
}
|
||||||
case BlockCall:
|
case BlockCall:
|
||||||
if len(b.Succs) != 2 {
|
if len(b.Succs) != 2 {
|
||||||
log.Panicf("call block %s len(Succs)==%d, want 2", b, len(b.Succs))
|
f.Fatal("call block %s len(Succs)==%d, want 2", b, len(b.Succs))
|
||||||
}
|
}
|
||||||
if b.Control == nil {
|
if b.Control == nil {
|
||||||
log.Panicf("call block %s has no control value", b)
|
f.Fatal("call block %s has no control value", b)
|
||||||
}
|
}
|
||||||
if !b.Control.Type.IsMemory() {
|
if !b.Control.Type.IsMemory() {
|
||||||
log.Panicf("call block %s has non-memory control value %s", b, b.Control.LongString())
|
f.Fatal("call block %s has non-memory control value %s", b, b.Control.LongString())
|
||||||
}
|
}
|
||||||
if b.Succs[1].Kind != BlockExit {
|
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])
|
f.Fatal("exception edge from call block %s does not go to exit but %s", b, b.Succs[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range b.Values {
|
for _, v := range b.Values {
|
||||||
if valueMark[v.ID] {
|
if valueMark[v.ID] {
|
||||||
log.Panicf("value %s appears twice!", v.LongString())
|
f.Fatal("value %s appears twice!", v.LongString())
|
||||||
}
|
}
|
||||||
valueMark[v.ID] = true
|
valueMark[v.ID] = true
|
||||||
|
|
||||||
if v.Block != b {
|
if v.Block != b {
|
||||||
log.Panicf("%s.block != %s", v, b)
|
f.Fatal("%s.block != %s", v, b)
|
||||||
}
|
}
|
||||||
if v.Op == OpPhi && len(v.Args) != len(b.Preds) {
|
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)
|
f.Fatal("phi length %s does not match pred length %d for block %s", v.LongString(), len(b.Preds), b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check for cycles in values
|
// TODO: check for cycles in values
|
||||||
|
|
@ -113,12 +111,12 @@ func checkFunc(f *Func) {
|
||||||
|
|
||||||
for _, id := range f.bid.free {
|
for _, id := range f.bid.free {
|
||||||
if blockMark[id] {
|
if blockMark[id] {
|
||||||
log.Panicf("used block b%d in free list", id)
|
f.Fatal("used block b%d in free list", id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, id := range f.vid.free {
|
for _, id := range f.vid.free {
|
||||||
if valueMark[id] {
|
if valueMark[id] {
|
||||||
log.Panicf("used value v%d in free list", id)
|
f.Fatal("used value v%d in free list", id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,7 @@
|
||||||
|
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
import (
|
import "log"
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Compile is the main entry point for this package.
|
// Compile is the main entry point for this package.
|
||||||
// Compile modifies f so that on return:
|
// Compile modifies f so that on return:
|
||||||
|
|
@ -18,13 +15,13 @@ import (
|
||||||
func Compile(f *Func) {
|
func Compile(f *Func) {
|
||||||
// TODO: debugging - set flags to control verbosity of compiler,
|
// TODO: debugging - set flags to control verbosity of compiler,
|
||||||
// which phases to dump IR before/after, etc.
|
// which phases to dump IR before/after, etc.
|
||||||
fmt.Printf("compiling %s\n", f.Name)
|
f.Log("compiling %s\n", f.Name)
|
||||||
|
|
||||||
// hook to print function & phase if panic happens
|
// hook to print function & phase if panic happens
|
||||||
phaseName := "init"
|
phaseName := "init"
|
||||||
defer func() {
|
defer func() {
|
||||||
if phaseName != "" {
|
if phaseName != "" {
|
||||||
fmt.Printf("panic during %s while compiling %s\n", phaseName, f.Name)
|
f.Fatal("panic during %s while compiling %s\n", phaseName, f.Name)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -33,9 +30,9 @@ func Compile(f *Func) {
|
||||||
checkFunc(f)
|
checkFunc(f)
|
||||||
for _, p := range passes {
|
for _, p := range passes {
|
||||||
phaseName = p.name
|
phaseName = p.name
|
||||||
fmt.Printf(" pass %s begin\n", p.name)
|
f.Log(" pass %s begin\n", p.name)
|
||||||
p.fn(f)
|
p.fn(f)
|
||||||
fmt.Printf(" pass %s end\n", p.name)
|
f.Log(" pass %s end\n", p.name)
|
||||||
printFunc(f)
|
printFunc(f)
|
||||||
checkFunc(f)
|
checkFunc(f)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
import "log"
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
arch string // "amd64", etc.
|
arch string // "amd64", etc.
|
||||||
ptrSize int64 // 4 or 8
|
ptrSize int64 // 4 or 8
|
||||||
|
|
@ -22,6 +20,16 @@ type Frontend interface {
|
||||||
// Strings are laid out in read-only memory with one word of pointer,
|
// Strings are laid out in read-only memory with one word of pointer,
|
||||||
// one word of length, then the contents of the string.
|
// one word of length, then the contents of the string.
|
||||||
StringSym(string) interface{} // returns *gc.Sym
|
StringSym(string) interface{} // returns *gc.Sym
|
||||||
|
|
||||||
|
// Log logs a message from the compiler.
|
||||||
|
Log(string, ...interface{})
|
||||||
|
|
||||||
|
// Fatal reports a compiler error and exits.
|
||||||
|
Fatal(string, ...interface{})
|
||||||
|
|
||||||
|
// Unimplemented reports that the function cannot be compiled.
|
||||||
|
// It will be removed once SSA work is complete.
|
||||||
|
Unimplemented(msg string, args ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig returns a new configuration object for the given architecture.
|
// NewConfig returns a new configuration object for the given architecture.
|
||||||
|
|
@ -37,7 +45,7 @@ func NewConfig(arch string, fe Frontend) *Config {
|
||||||
c.lowerBlock = rewriteBlockAMD64
|
c.lowerBlock = rewriteBlockAMD64
|
||||||
c.lowerValue = rewriteValueAMD64 // TODO(khr): full 32-bit support
|
c.lowerValue = rewriteValueAMD64 // TODO(khr): full 32-bit support
|
||||||
default:
|
default:
|
||||||
log.Fatalf("arch %s not implemented", arch)
|
fe.Unimplemented("arch %s not implemented", arch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// cache the intptr type in the config
|
// cache the intptr type in the config
|
||||||
|
|
@ -55,5 +63,9 @@ func (c *Config) NewFunc() *Func {
|
||||||
return &Func{Config: c}
|
return &Func{Config: c}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Config) Log(msg string, args ...interface{}) { c.fe.Log(msg, args...) }
|
||||||
|
func (c *Config) Fatal(msg string, args ...interface{}) { c.fe.Fatal(msg, args...) }
|
||||||
|
func (c *Config) Unimplemented(msg string, args ...interface{}) { c.fe.Unimplemented(msg, args...) }
|
||||||
|
|
||||||
// TODO(khr): do we really need a separate Config, or can we just
|
// TODO(khr): do we really need a separate Config, or can we just
|
||||||
// store all its fields inside a Func?
|
// store all its fields inside a Func?
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
import "log"
|
|
||||||
|
|
||||||
// deadcode removes dead code from f.
|
// deadcode removes dead code from f.
|
||||||
func deadcode(f *Func) {
|
func deadcode(f *Func) {
|
||||||
|
|
||||||
|
|
@ -82,7 +80,7 @@ func deadcode(f *Func) {
|
||||||
i++
|
i++
|
||||||
} else {
|
} else {
|
||||||
if len(b.Values) > 0 {
|
if len(b.Values) > 0 {
|
||||||
log.Panicf("live values in unreachable block %v: %v", b, b.Values)
|
b.Fatal("live values in unreachable block %v: %v", b, b.Values)
|
||||||
}
|
}
|
||||||
f.bid.put(b.ID)
|
f.bid.put(b.ID)
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +103,7 @@ func removePredecessor(b, c *Block) {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
// c is now dead - don't bother working on it
|
// c is now dead - don't bother working on it
|
||||||
if c.Preds[0] != b {
|
if c.Preds[0] != b {
|
||||||
log.Panicf("%s.Preds[0]==%s, want %s", c, c.Preds[0], b)
|
b.Fatal("%s.Preds[0]==%s, want %s", c, c.Preds[0], b)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ package ssa
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestDeadLoop(t *testing.T) {
|
func TestDeadLoop(t *testing.T) {
|
||||||
c := NewConfig("amd64", DummyFrontend{})
|
c := NewConfig("amd64", DummyFrontend{t})
|
||||||
fun := Fun(c, "entry",
|
fun := Fun(c, "entry",
|
||||||
Bloc("entry",
|
Bloc("entry",
|
||||||
Valu("mem", OpArg, TypeMem, 0, ".mem"),
|
Valu("mem", OpArg, TypeMem, 0, ".mem"),
|
||||||
|
|
@ -37,7 +37,7 @@ func TestDeadLoop(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeadValue(t *testing.T) {
|
func TestDeadValue(t *testing.T) {
|
||||||
c := NewConfig("amd64", DummyFrontend{})
|
c := NewConfig("amd64", DummyFrontend{t})
|
||||||
fun := Fun(c, "entry",
|
fun := Fun(c, "entry",
|
||||||
Bloc("entry",
|
Bloc("entry",
|
||||||
Valu("mem", OpArg, TypeMem, 0, ".mem"),
|
Valu("mem", OpArg, TypeMem, 0, ".mem"),
|
||||||
|
|
@ -60,7 +60,7 @@ func TestDeadValue(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNeverTaken(t *testing.T) {
|
func TestNeverTaken(t *testing.T) {
|
||||||
c := NewConfig("amd64", DummyFrontend{})
|
c := NewConfig("amd64", DummyFrontend{t})
|
||||||
fun := Fun(c, "entry",
|
fun := Fun(c, "entry",
|
||||||
Bloc("entry",
|
Bloc("entry",
|
||||||
Valu("cond", OpConst, TypeBool, 0, false),
|
Valu("cond", OpConst, TypeBool, 0, false),
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
import "log"
|
|
||||||
|
|
||||||
// dse does dead-store elimination on the Function.
|
// dse does dead-store elimination on the Function.
|
||||||
// Dead stores are those which are unconditionally followed by
|
// Dead stores are those which are unconditionally followed by
|
||||||
// another store to the same location, with no intervening load.
|
// another store to the same location, with no intervening load.
|
||||||
|
|
@ -58,12 +56,12 @@ func dse(f *Func) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if last != nil {
|
if last != nil {
|
||||||
log.Fatalf("two final stores - simultaneous live stores", last, v)
|
b.Fatal("two final stores - simultaneous live stores", last, v)
|
||||||
}
|
}
|
||||||
last = v
|
last = v
|
||||||
}
|
}
|
||||||
if last == nil {
|
if last == nil {
|
||||||
log.Fatalf("no last store found - cycle?")
|
b.Fatal("no last store found - cycle?")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk backwards looking for dead stores. Keep track of shadowed addresses.
|
// Walk backwards looking for dead stores. Keep track of shadowed addresses.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDeadStore(t *testing.T) {
|
func TestDeadStore(t *testing.T) {
|
||||||
c := NewConfig("amd64", DummyFrontend{})
|
c := NewConfig("amd64", DummyFrontend{t})
|
||||||
ptrType := &TypeImpl{Size_: 8, Ptr: true, Name: "testptr"} // dummy for testing
|
ptrType := &TypeImpl{Size_: 8, Ptr: true, Name: "testptr"} // dummy for testing
|
||||||
fun := Fun(c, "entry",
|
fun := Fun(c, "entry",
|
||||||
Bloc("entry",
|
Bloc("entry",
|
||||||
|
|
@ -35,7 +35,7 @@ func TestDeadStore(t *testing.T) {
|
||||||
}
|
}
|
||||||
func TestDeadStorePhi(t *testing.T) {
|
func TestDeadStorePhi(t *testing.T) {
|
||||||
// make sure we don't get into an infinite loop with phi values.
|
// make sure we don't get into an infinite loop with phi values.
|
||||||
c := NewConfig("amd64", DummyFrontend{})
|
c := NewConfig("amd64", DummyFrontend{t})
|
||||||
ptrType := &TypeImpl{Size_: 8, Ptr: true, Name: "testptr"} // dummy for testing
|
ptrType := &TypeImpl{Size_: 8, Ptr: true, Name: "testptr"} // dummy for testing
|
||||||
fun := Fun(c, "entry",
|
fun := Fun(c, "entry",
|
||||||
Bloc("entry",
|
Bloc("entry",
|
||||||
|
|
@ -60,7 +60,7 @@ func TestDeadStoreTypes(t *testing.T) {
|
||||||
// stronger restriction, that one store can't shadow another unless the
|
// stronger restriction, that one store can't shadow another unless the
|
||||||
// types of the address fields are identical (where identicalness is
|
// types of the address fields are identical (where identicalness is
|
||||||
// decided by the CSE pass).
|
// decided by the CSE pass).
|
||||||
c := NewConfig("amd64", DummyFrontend{})
|
c := NewConfig("amd64", DummyFrontend{t})
|
||||||
t1 := &TypeImpl{Size_: 8, Ptr: true, Name: "t1"}
|
t1 := &TypeImpl{Size_: 8, Ptr: true, Name: "t1"}
|
||||||
t2 := &TypeImpl{Size_: 4, Ptr: true, Name: "t2"}
|
t2 := &TypeImpl{Size_: 4, Ptr: true, Name: "t2"}
|
||||||
fun := Fun(c, "entry",
|
fun := Fun(c, "entry",
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ package ssa
|
||||||
// This file contains code to compute the dominator tree
|
// This file contains code to compute the dominator tree
|
||||||
// of a control-flow graph.
|
// of a control-flow graph.
|
||||||
|
|
||||||
import "log"
|
|
||||||
|
|
||||||
// postorder computes a postorder traversal ordering for the
|
// postorder computes a postorder traversal ordering for the
|
||||||
// basic blocks in f. Unreachable blocks will not appear.
|
// basic blocks in f. Unreachable blocks will not appear.
|
||||||
func postorder(f *Func) []*Block {
|
func postorder(f *Func) []*Block {
|
||||||
|
|
@ -47,7 +45,7 @@ func postorder(f *Func) []*Block {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Fatalf("bad stack state %v %d", b, mark[b.ID])
|
b.Fatal("bad stack state %v %d", b, mark[b.ID])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return order
|
return order
|
||||||
|
|
@ -73,7 +71,7 @@ func dominators(f *Func) []*Block {
|
||||||
// Make the entry block a self-loop
|
// Make the entry block a self-loop
|
||||||
idom[f.Entry.ID] = f.Entry
|
idom[f.Entry.ID] = f.Entry
|
||||||
if postnum[f.Entry.ID] != len(post)-1 {
|
if postnum[f.Entry.ID] != len(post)-1 {
|
||||||
log.Fatalf("entry block %v not last in postorder", f.Entry)
|
f.Fatal("entry block %v not last in postorder", f.Entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute relaxation of idom entries
|
// Compute relaxation of idom entries
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,21 @@
|
||||||
|
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
var CheckFunc = checkFunc
|
var CheckFunc = checkFunc
|
||||||
var PrintFunc = printFunc
|
var PrintFunc = printFunc
|
||||||
var Opt = opt
|
var Opt = opt
|
||||||
var Deadcode = deadcode
|
var Deadcode = deadcode
|
||||||
|
|
||||||
type DummyFrontend struct{}
|
type DummyFrontend struct {
|
||||||
|
t *testing.T
|
||||||
|
}
|
||||||
|
|
||||||
func (d DummyFrontend) StringSym(s string) interface{} {
|
func (DummyFrontend) StringSym(s string) interface{} {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d DummyFrontend) Log(msg string, args ...interface{}) { d.t.Logf(msg, args...) }
|
||||||
|
func (d DummyFrontend) Fatal(msg string, args ...interface{}) { d.t.Fatalf(msg, args...) }
|
||||||
|
func (d DummyFrontend) Unimplemented(msg string, args ...interface{}) { d.t.Fatalf(msg, args...) }
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
import "log"
|
|
||||||
|
|
||||||
// A Func represents a Go func declaration (or function literal) and
|
// A Func represents a Go func declaration (or function literal) and
|
||||||
// its body. This package compiles each Func independently.
|
// its body. This package compiles each Func independently.
|
||||||
type Func struct {
|
type Func struct {
|
||||||
|
|
@ -79,7 +77,7 @@ func (b *Block) NewValue0A(line int32, op Op, t Type, aux interface{}) *Value {
|
||||||
// Disallow int64 aux values. They should be in the auxint field instead.
|
// Disallow int64 aux values. They should be in the auxint field instead.
|
||||||
// Maybe we want to allow this at some point, but for now we disallow it
|
// Maybe we want to allow this at some point, but for now we disallow it
|
||||||
// to prevent errors like using NewValue1A instead of NewValue1I.
|
// to prevent errors like using NewValue1A instead of NewValue1I.
|
||||||
log.Fatalf("aux field has int64 type op=%s type=%s aux=%v", op, t, aux)
|
b.Fatal("aux field has int64 type op=%s type=%s aux=%v", op, t, aux)
|
||||||
}
|
}
|
||||||
v := &Value{
|
v := &Value{
|
||||||
ID: b.Func.vid.get(),
|
ID: b.Func.vid.get(),
|
||||||
|
|
@ -209,3 +207,7 @@ func (f *Func) ConstInt(line int32, t Type, c int64) *Value {
|
||||||
// TODO: cache?
|
// TODO: cache?
|
||||||
return f.Entry.NewValue0I(line, OpConst, t, c)
|
return f.Entry.NewValue0I(line, OpConst, t, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Func) Log(msg string, args ...interface{}) { f.Config.Log(msg, args...) }
|
||||||
|
func (f *Func) Fatal(msg string, args ...interface{}) { f.Config.Fatal(msg, args...) }
|
||||||
|
func (f *Func) Unimplemented(msg string, args ...interface{}) { f.Config.Unimplemented(msg, args...) }
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ package ssa
|
||||||
// the parser can be used instead of Fun.
|
// the parser can be used instead of Fun.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
@ -161,7 +161,7 @@ func Fun(c *Config, entry string, blocs ...bloc) fun {
|
||||||
if c.control != "" {
|
if c.control != "" {
|
||||||
cval, ok := values[c.control]
|
cval, ok := values[c.control]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Panicf("control value for block %s missing", bloc.name)
|
f.Fatal("control value for block %s missing", bloc.name)
|
||||||
}
|
}
|
||||||
b.Control = cval
|
b.Control = cval
|
||||||
}
|
}
|
||||||
|
|
@ -171,7 +171,7 @@ func Fun(c *Config, entry string, blocs ...bloc) fun {
|
||||||
for _, arg := range valu.args {
|
for _, arg := range valu.args {
|
||||||
a, ok := values[arg]
|
a, ok := values[arg]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Panicf("arg %s missing for value %s in block %s",
|
b.Fatal("arg %s missing for value %s in block %s",
|
||||||
arg, valu.name, bloc.name)
|
arg, valu.name, bloc.name)
|
||||||
}
|
}
|
||||||
v.AddArg(a)
|
v.AddArg(a)
|
||||||
|
|
@ -197,7 +197,7 @@ func Bloc(name string, entries ...interface{}) bloc {
|
||||||
case ctrl:
|
case ctrl:
|
||||||
// there should be exactly one Ctrl entry.
|
// there should be exactly one Ctrl entry.
|
||||||
if seenCtrl {
|
if seenCtrl {
|
||||||
log.Panicf("already seen control for block %s", name)
|
panic(fmt.Sprintf("already seen control for block %s", name))
|
||||||
}
|
}
|
||||||
b.control = v
|
b.control = v
|
||||||
seenCtrl = true
|
seenCtrl = true
|
||||||
|
|
@ -206,7 +206,7 @@ func Bloc(name string, entries ...interface{}) bloc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !seenCtrl {
|
if !seenCtrl {
|
||||||
log.Panicf("block %s doesn't have control", b.name)
|
panic(fmt.Sprintf("block %s doesn't have control", b.name))
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
@ -262,7 +262,7 @@ func addEdge(b, c *Block) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArgs(t *testing.T) {
|
func TestArgs(t *testing.T) {
|
||||||
c := NewConfig("amd64", DummyFrontend{})
|
c := NewConfig("amd64", DummyFrontend{t})
|
||||||
fun := Fun(c, "entry",
|
fun := Fun(c, "entry",
|
||||||
Bloc("entry",
|
Bloc("entry",
|
||||||
Valu("a", OpConst, TypeInt64, 14, nil),
|
Valu("a", OpConst, TypeInt64, 14, nil),
|
||||||
|
|
@ -282,7 +282,7 @@ func TestArgs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEquiv(t *testing.T) {
|
func TestEquiv(t *testing.T) {
|
||||||
c := NewConfig("amd64", DummyFrontend{})
|
c := NewConfig("amd64", DummyFrontend{t})
|
||||||
equivalentCases := []struct{ f, g fun }{
|
equivalentCases := []struct{ f, g fun }{
|
||||||
// simple case
|
// simple case
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
// indexing operations
|
// indexing operations
|
||||||
// Note: bounds check has already been done
|
// Note: bounds check has already been done
|
||||||
(ArrayIndex (Load ptr mem) idx) -> (Load (PtrIndex <ptr.Type.Elem().Elem().PtrTo()> ptr idx) mem)
|
(ArrayIndex (Load ptr mem) idx) -> (Load (PtrIndex <v.Type.PtrTo()> ptr idx) mem)
|
||||||
(PtrIndex <t> ptr idx) -> (Add ptr (Mul <config.Uintptr> idx (Const <config.Uintptr> [t.Elem().Size()])))
|
(PtrIndex <t> ptr idx) -> (Add ptr (Mul <config.Uintptr> idx (Const <config.Uintptr> [t.Elem().Size()])))
|
||||||
|
|
||||||
// big-object moves
|
// big-object moves
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
import "log"
|
|
||||||
|
|
||||||
// layout orders basic blocks in f with the goal of minimizing control flow instructions.
|
// 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
|
// After this phase returns, the order of f.Blocks matters and is the order
|
||||||
// in which those blocks will appear in the assembly output.
|
// in which those blocks will appear in the assembly output.
|
||||||
|
|
@ -82,7 +80,7 @@ blockloop:
|
||||||
continue blockloop
|
continue blockloop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Panicf("no block available for layout")
|
b.Fatal("no block available for layout")
|
||||||
}
|
}
|
||||||
f.Blocks = order
|
f.Blocks = order
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
import "log"
|
|
||||||
|
|
||||||
// convert to machine-dependent ops
|
// convert to machine-dependent ops
|
||||||
func lower(f *Func) {
|
func lower(f *Func) {
|
||||||
// repeat rewrites until we find no more rewrites
|
// repeat rewrites until we find no more rewrites
|
||||||
|
|
@ -15,7 +13,7 @@ func lower(f *Func) {
|
||||||
for _, b := range f.Blocks {
|
for _, b := range f.Blocks {
|
||||||
for _, v := range b.Values {
|
for _, v := range b.Values {
|
||||||
if opcodeTable[v.Op].generic && v.Op != OpFP && v.Op != OpSP && v.Op != OpArg && v.Op != OpCopy && v.Op != OpPhi {
|
if opcodeTable[v.Op].generic && v.Op != OpFP && v.Op != OpSP && v.Op != OpArg && v.Op != OpCopy && v.Op != OpPhi {
|
||||||
log.Panicf("%s not lowered", v.LongString())
|
f.Unimplemented("%s not lowered", v.LongString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,10 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func printFunc(f *Func) {
|
func printFunc(f *Func) {
|
||||||
fprintFunc(os.Stdout, f)
|
f.Log("%s", f.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Func) String() string {
|
func (f *Func) String() string {
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,7 @@
|
||||||
|
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
import (
|
import "sort"
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setloc(home []Location, v *Value, loc Location) []Location {
|
func setloc(home []Location, v *Value, loc Location) []Location {
|
||||||
for v.ID >= ID(len(home)) {
|
for v.ID >= ID(len(home)) {
|
||||||
|
|
@ -353,7 +349,7 @@ func regalloc(f *Func) {
|
||||||
if b.Kind == BlockCall {
|
if b.Kind == BlockCall {
|
||||||
call = b.Control
|
call = b.Control
|
||||||
if call != b.Values[len(b.Values)-1] {
|
if call != b.Values[len(b.Values)-1] {
|
||||||
log.Fatalf("call not at end of block %b %v", b, call)
|
b.Fatal("call not at end of block %b %v", b, call)
|
||||||
}
|
}
|
||||||
b.Values = b.Values[:len(b.Values)-1]
|
b.Values = b.Values[:len(b.Values)-1]
|
||||||
// TODO: do this for all control types?
|
// TODO: do this for all control types?
|
||||||
|
|
@ -423,7 +419,7 @@ func live(f *Func) [][]ID {
|
||||||
t := newSparseSet(f.NumValues())
|
t := newSparseSet(f.NumValues())
|
||||||
for {
|
for {
|
||||||
for _, b := range f.Blocks {
|
for _, b := range f.Blocks {
|
||||||
fmt.Printf("live %s %v\n", b, live[b.ID])
|
f.Log("live %s %v\n", b, live[b.ID])
|
||||||
}
|
}
|
||||||
changed := false
|
changed := false
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
import "log"
|
import "fmt"
|
||||||
|
|
||||||
func applyRewrite(f *Func, rb func(*Block) bool, rv func(*Value, *Config) bool) {
|
func applyRewrite(f *Func, rb func(*Block) bool, rv func(*Value, *Config) bool) {
|
||||||
// repeat rewrites until we find no more rewrites
|
// repeat rewrites until we find no more rewrites
|
||||||
|
|
@ -12,11 +12,10 @@ func applyRewrite(f *Func, rb func(*Block) bool, rv func(*Value, *Config) bool)
|
||||||
var curv *Value
|
var curv *Value
|
||||||
defer func() {
|
defer func() {
|
||||||
if curb != nil {
|
if curb != nil {
|
||||||
log.Printf("panic during rewrite of block %s\n", curb.LongString())
|
curb.Fatal("panic during rewrite of block %s\n", curb.LongString())
|
||||||
}
|
}
|
||||||
if curv != nil {
|
if curv != nil {
|
||||||
log.Printf("panic during rewrite of value %s\n", curv.LongString())
|
curv.Fatal("panic during rewrite of value %s\n", curv.LongString())
|
||||||
panic("rewrite failed")
|
|
||||||
// TODO(khr): print source location also
|
// TODO(khr): print source location also
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
@ -90,12 +89,12 @@ func typeSize(t Type) int64 {
|
||||||
return t.Size()
|
return t.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
// addOff adds two int64 offsets. Fails if wraparound happens.
|
// addOff adds two int64 offsets. Fails if wraparound happens.
|
||||||
func addOff(x, y int64) int64 {
|
func addOff(x, y int64) int64 {
|
||||||
z := x + y
|
z := x + y
|
||||||
// x and y have same sign and z has a different sign => overflow
|
// x and y have same sign and z has a different sign => overflow
|
||||||
if x^y >= 0 && x^z < 0 {
|
if x^y >= 0 && x^z < 0 {
|
||||||
log.Panicf("offset overflow %d %d\n", x, y)
|
panic(fmt.Sprintf("offset overflow %d %d", x, y))
|
||||||
}
|
}
|
||||||
return z
|
return z
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,10 @@ func rewriteValuegeneric(v *Value, config *Config) bool {
|
||||||
case OpArrayIndex:
|
case OpArrayIndex:
|
||||||
// match: (ArrayIndex (Load ptr mem) idx)
|
// match: (ArrayIndex (Load ptr mem) idx)
|
||||||
// cond:
|
// cond:
|
||||||
// result: (Load (PtrIndex <ptr.Type.Elem().Elem().PtrTo()> ptr idx) mem)
|
// result: (Load (PtrIndex <v.Type.PtrTo()> ptr idx) mem)
|
||||||
{
|
{
|
||||||
if v.Args[0].Op != OpLoad {
|
if v.Args[0].Op != OpLoad {
|
||||||
goto end3809f4c52270a76313e4ea26e6f0b753
|
goto end4894dd7b58383fee5f8a92be08437c33
|
||||||
}
|
}
|
||||||
ptr := v.Args[0].Args[0]
|
ptr := v.Args[0].Args[0]
|
||||||
mem := v.Args[0].Args[1]
|
mem := v.Args[0].Args[1]
|
||||||
|
|
@ -47,15 +47,15 @@ func rewriteValuegeneric(v *Value, config *Config) bool {
|
||||||
v.Aux = nil
|
v.Aux = nil
|
||||||
v.resetArgs()
|
v.resetArgs()
|
||||||
v0 := v.Block.NewValue0(v.Line, OpPtrIndex, TypeInvalid)
|
v0 := v.Block.NewValue0(v.Line, OpPtrIndex, TypeInvalid)
|
||||||
v0.Type = ptr.Type.Elem().Elem().PtrTo()
|
v0.Type = v.Type.PtrTo()
|
||||||
v0.AddArg(ptr)
|
v0.AddArg(ptr)
|
||||||
v0.AddArg(idx)
|
v0.AddArg(idx)
|
||||||
v.AddArg(v0)
|
v.AddArg(v0)
|
||||||
v.AddArg(mem)
|
v.AddArg(mem)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
goto end3809f4c52270a76313e4ea26e6f0b753
|
goto end4894dd7b58383fee5f8a92be08437c33
|
||||||
end3809f4c52270a76313e4ea26e6f0b753:
|
end4894dd7b58383fee5f8a92be08437c33:
|
||||||
;
|
;
|
||||||
case OpConst:
|
case OpConst:
|
||||||
// match: (Const <t> {s})
|
// match: (Const <t> {s})
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ package ssa
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestSchedule(t *testing.T) {
|
func TestSchedule(t *testing.T) {
|
||||||
c := NewConfig("amd64", DummyFrontend{})
|
c := NewConfig("amd64", DummyFrontend{t})
|
||||||
cases := []fun{
|
cases := []fun{
|
||||||
Fun(c, "entry",
|
Fun(c, "entry",
|
||||||
Bloc("entry",
|
Bloc("entry",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestShiftConstAMD64(t *testing.T) {
|
func TestShiftConstAMD64(t *testing.T) {
|
||||||
c := NewConfig("amd64", DummyFrontend{})
|
c := NewConfig("amd64", DummyFrontend{t})
|
||||||
fun := makeConstShiftFunc(c, 18, OpLsh, TypeUInt64)
|
fun := makeConstShiftFunc(c, 18, OpLsh, TypeUInt64)
|
||||||
checkOpcodeCounts(t, fun.f, map[Op]int{OpAMD64SHLQconst: 1, OpAMD64CMPQconst: 0, OpAMD64ANDQconst: 0})
|
checkOpcodeCounts(t, fun.f, map[Op]int{OpAMD64SHLQconst: 1, OpAMD64CMPQconst: 0, OpAMD64ANDQconst: 0})
|
||||||
fun = makeConstShiftFunc(c, 66, OpLsh, TypeUInt64)
|
fun = makeConstShiftFunc(c, 66, OpLsh, TypeUInt64)
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
import "log"
|
|
||||||
|
|
||||||
// stackalloc allocates storage in the stack frame for
|
// stackalloc allocates storage in the stack frame for
|
||||||
// all Values that did not get a register.
|
// all Values that did not get a register.
|
||||||
func stackalloc(f *Func) {
|
func stackalloc(f *Func) {
|
||||||
|
|
@ -79,7 +77,7 @@ func stackalloc(f *Func) {
|
||||||
for _, v := range b.Values {
|
for _, v := range b.Values {
|
||||||
if v.Op == OpFP {
|
if v.Op == OpFP {
|
||||||
if fp != nil {
|
if fp != nil {
|
||||||
log.Panicf("multiple FP ops: %s %s", fp, v)
|
b.Fatal("multiple FP ops: %s %s", fp, v)
|
||||||
}
|
}
|
||||||
fp = v
|
fp = v
|
||||||
}
|
}
|
||||||
|
|
@ -99,12 +97,12 @@ func stackalloc(f *Func) {
|
||||||
case OpAMD64LEAQ, OpAMD64MOVQload, OpAMD64MOVQstore, OpAMD64MOVLload, OpAMD64MOVLstore, OpAMD64MOVWload, OpAMD64MOVWstore, OpAMD64MOVBload, OpAMD64MOVBstore, OpAMD64MOVQloadidx8:
|
case OpAMD64LEAQ, OpAMD64MOVQload, OpAMD64MOVQstore, OpAMD64MOVLload, OpAMD64MOVLstore, OpAMD64MOVWload, OpAMD64MOVWstore, OpAMD64MOVBload, OpAMD64MOVBstore, OpAMD64MOVQloadidx8:
|
||||||
if v.Op == OpAMD64MOVQloadidx8 && i == 1 {
|
if v.Op == OpAMD64MOVQloadidx8 && i == 1 {
|
||||||
// Note: we could do it, but it is probably an error
|
// 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)
|
f.Fatal("can't do FP->SP adjust on index slot of load %s", v.Op)
|
||||||
}
|
}
|
||||||
// eg: (MOVQload [c] (FP) mem) -> (MOVQload [c+n] (SP) mem)
|
// eg: (MOVQload [c] (FP) mem) -> (MOVQload [c+n] (SP) mem)
|
||||||
v.AuxInt = addOff(v.AuxInt, n)
|
v.AuxInt = addOff(v.AuxInt, n)
|
||||||
default:
|
default:
|
||||||
log.Panicf("can't do FP->SP adjust on %s", v.Op)
|
f.Unimplemented("can't do FP->SP adjust on %s", v.Op)
|
||||||
// TODO: OpCopy -> ADDQ
|
// TODO: OpCopy -> ADDQ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -106,3 +106,7 @@ func (v *Value) resetArgs() {
|
||||||
v.argstorage[1] = nil
|
v.argstorage[1] = nil
|
||||||
v.Args = v.argstorage[:0]
|
v.Args = v.argstorage[:0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Value) Log(msg string, args ...interface{}) { v.Block.Log(msg, args...) }
|
||||||
|
func (v *Value) Fatal(msg string, args ...interface{}) { v.Block.Fatal(msg, args...) }
|
||||||
|
func (v *Value) Unimplemented(msg string, args ...interface{}) { v.Block.Unimplemented(msg, args...) }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue