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/obj/x86/6.out.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'src/cmd/internal/obj') 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 -- 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/obj') 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/obj') 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/obj') 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/obj') 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/obj') 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/obj') 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/obj') 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/obj') 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/obj') 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/obj') 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