Commit d71f36b5 authored by David Chase's avatar David Chase

cmd/compile: check loop rescheduling with stack bound, not counter

After benchmarking with a compiler modified to have better
spill location, it became clear that this method of checking
was actually faster on (at least) two different architectures
(ppc64 and amd64) and it also provides more timely interruption
of loops.

This change adds a modified FOR loop node "FORUNTIL" that
checks after executing the loop body instead of before (i.e.,
always at least once).  This ensures that a pointer past the
end of a slice or array is not made visible to the garbage
collector.

Without the rescheduling checks inserted, the restructured
loop from this  change apparently provides a 1% geomean
improvement on PPC64 running the go1 benchmarks; the
improvement on AMD64 is only 0.12%.

Inserting the rescheduling check exposed some peculiar bug
with the ssa test code for s390x; this was updated based on
initial code actually generated for GOARCH=s390x to use
appropriate OpArg, OpAddr, and OpVarDef.

NaCl is disabled in testing.

Change-Id: Ieafaa9a61d2a583ad00968110ef3e7a441abca50
Reviewed-on: https://go-review.googlesource.com/36206
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarCherry Zhang <cherryyz@google.com>
parent 6fbedc1a
...@@ -654,7 +654,7 @@ func (e *EscState) esc(n *Node, parent *Node) { ...@@ -654,7 +654,7 @@ func (e *EscState) esc(n *Node, parent *Node) {
// ninit logically runs at a different loopdepth than the rest of the for loop. // ninit logically runs at a different loopdepth than the rest of the for loop.
e.esclist(n.Ninit, n) e.esclist(n.Ninit, n)
if n.Op == OFOR || n.Op == ORANGE { if n.Op == OFOR || n.Op == OFORUNTIL || n.Op == ORANGE {
e.loopdepth++ e.loopdepth++
} }
...@@ -700,7 +700,7 @@ func (e *EscState) esc(n *Node, parent *Node) { ...@@ -700,7 +700,7 @@ func (e *EscState) esc(n *Node, parent *Node) {
e.esclist(n.Rlist, n) e.esclist(n.Rlist, n)
} }
if n.Op == OFOR || n.Op == ORANGE { if n.Op == OFOR || n.Op == OFORUNTIL || n.Op == ORANGE {
e.loopdepth-- e.loopdepth--
} }
......
...@@ -169,6 +169,7 @@ var goopnames = []string{ ...@@ -169,6 +169,7 @@ var goopnames = []string{
OEQ: "==", OEQ: "==",
OFALL: "fallthrough", OFALL: "fallthrough",
OFOR: "for", OFOR: "for",
OFORUNTIL: "foruntil", // not actual syntax; used to avoid off-end pointer live on backedge.892
OGE: ">=", OGE: ">=",
OGOTO: "goto", OGOTO: "goto",
OGT: ">", OGT: ">",
...@@ -787,7 +788,7 @@ func (t *Type) typefmt(flag FmtFlag) string { ...@@ -787,7 +788,7 @@ func (t *Type) typefmt(flag FmtFlag) string {
// Statements which may be rendered with a simplestmt as init. // Statements which may be rendered with a simplestmt as init.
func stmtwithinit(op Op) bool { func stmtwithinit(op Op) bool {
switch op { switch op {
case OIF, OFOR, OSWITCH: case OIF, OFOR, OFORUNTIL, OSWITCH:
return true return true
} }
...@@ -882,13 +883,17 @@ func (n *Node) stmtfmt(s fmt.State) { ...@@ -882,13 +883,17 @@ func (n *Node) stmtfmt(s fmt.State) {
fmt.Fprintf(s, " else { %v }", n.Rlist) fmt.Fprintf(s, " else { %v }", n.Rlist)
} }
case OFOR: case OFOR, OFORUNTIL:
opname := "for"
if n.Op == OFORUNTIL {
opname = "foruntil"
}
if fmtmode == FErr { // TODO maybe only if FmtShort, same below if fmtmode == FErr { // TODO maybe only if FmtShort, same below
fmt.Fprint(s, "for loop") fmt.Fprintf(s, "%s loop", opname)
break break
} }
fmt.Fprint(s, "for") fmt.Fprint(s, opname)
if simpleinit { if simpleinit {
fmt.Fprintf(s, " %v;", n.Ninit.First()) fmt.Fprintf(s, " %v;", n.Ninit.First())
} else if n.Right != nil { } else if n.Right != nil {
...@@ -1089,6 +1094,7 @@ var opprec = []int{ ...@@ -1089,6 +1094,7 @@ var opprec = []int{
OEMPTY: -1, OEMPTY: -1,
OFALL: -1, OFALL: -1,
OFOR: -1, OFOR: -1,
OFORUNTIL: -1,
OGOTO: -1, OGOTO: -1,
OIF: -1, OIF: -1,
OLABEL: -1, OLABEL: -1,
......
...@@ -249,6 +249,7 @@ func ishairy(n *Node, budget *int32, reason *string) bool { ...@@ -249,6 +249,7 @@ func ishairy(n *Node, budget *int32, reason *string) bool {
OCALLPART, OCALLPART,
ORANGE, ORANGE,
OFOR, OFOR,
OFORUNTIL,
OSELECT, OSELECT,
OTYPESW, OTYPESW,
OPROC, OPROC,
...@@ -429,7 +430,7 @@ func inlnode(n *Node) *Node { ...@@ -429,7 +430,7 @@ func inlnode(n *Node) *Node {
n.Right = inlnode(n.Right) n.Right = inlnode(n.Right)
if n.Right != nil && n.Right.Op == OINLCALL { if n.Right != nil && n.Right.Op == OINLCALL {
if n.Op == OFOR { if n.Op == OFOR || n.Op == OFORUNTIL {
inlconv2stmt(n.Right) inlconv2stmt(n.Right)
} else { } else {
n.Right = inlconv2expr(n.Right) n.Right = inlconv2expr(n.Right)
......
...@@ -124,6 +124,7 @@ var opnames = []string{ ...@@ -124,6 +124,7 @@ var opnames = []string{
OFALL: "FALL", OFALL: "FALL",
OXFALL: "XFALL", OXFALL: "XFALL",
OFOR: "FOR", OFOR: "FOR",
OFORUNTIL: "FORUNTIL",
OGOTO: "GOTO", OGOTO: "GOTO",
OIF: "IF", OIF: "IF",
OLABEL: "LABEL", OLABEL: "LABEL",
......
...@@ -372,7 +372,7 @@ func instrumentnode(np **Node, init *Nodes, wr int, skip int) { ...@@ -372,7 +372,7 @@ func instrumentnode(np **Node, init *Nodes, wr int, skip int) {
yyerror("instrument: OGETG can happen only in runtime which we don't instrument") yyerror("instrument: OGETG can happen only in runtime which we don't instrument")
goto ret goto ret
case OFOR: case OFOR, OFORUNTIL:
if n.Left != nil { if n.Left != nil {
instrumentnode(&n.Left, &n.Left.Ninit, 0, 0) instrumentnode(&n.Left, &n.Left.Ninit, 0, 0)
} }
......
...@@ -133,7 +133,11 @@ out: ...@@ -133,7 +133,11 @@ out:
decldepth-- decldepth--
} }
func walkrange(n *Node) { // walkrange transforms various forms of ORANGE into
// simpler forms. The result must be assigned back to n.
// Node n may also be modified in place, and may also be
// the returned node.
func walkrange(n *Node) *Node {
// variable name conventions: // variable name conventions:
// ohv1, hv1, hv2: hidden (old) val 1, 2 // ohv1, hv1, hv2: hidden (old) val 1, 2
// ha, hit: hidden aggregate, iterator // ha, hit: hidden aggregate, iterator
...@@ -160,6 +164,10 @@ func walkrange(n *Node) { ...@@ -160,6 +164,10 @@ func walkrange(n *Node) {
Fatalf("walkrange: v2 != nil while v1 == nil") Fatalf("walkrange: v2 != nil while v1 == nil")
} }
var ifGuard *Node
translatedLoopOp := OFOR
// n.List has no meaning anymore, clear it // n.List has no meaning anymore, clear it
// to avoid erroneous processing by racewalk. // to avoid erroneous processing by racewalk.
n.List.Set(nil) n.List.Set(nil)
...@@ -173,7 +181,7 @@ func walkrange(n *Node) { ...@@ -173,7 +181,7 @@ func walkrange(n *Node) {
case TARRAY, TSLICE: case TARRAY, TSLICE:
if memclrrange(n, v1, v2, a) { if memclrrange(n, v1, v2, a) {
lineno = lno lineno = lno
return return n
} }
// orderstmt arranged for a copy of the array/slice variable if needed. // orderstmt arranged for a copy of the array/slice variable if needed.
...@@ -185,6 +193,7 @@ func walkrange(n *Node) { ...@@ -185,6 +193,7 @@ func walkrange(n *Node) {
init = append(init, nod(OAS, hv1, nil)) init = append(init, nod(OAS, hv1, nil))
init = append(init, nod(OAS, hn, nod(OLEN, ha, nil))) init = append(init, nod(OAS, hn, nod(OLEN, ha, nil)))
if v2 != nil { if v2 != nil {
hp = temp(ptrto(n.Type.Elem())) hp = temp(ptrto(n.Type.Elem()))
tmp := nod(OINDEX, ha, nodintconst(0)) tmp := nod(OINDEX, ha, nodintconst(0))
...@@ -198,7 +207,11 @@ func walkrange(n *Node) { ...@@ -198,7 +207,11 @@ func walkrange(n *Node) {
body = nil body = nil
} else if v2 == nil { } else if v2 == nil {
body = []*Node{nod(OAS, v1, hv1)} body = []*Node{nod(OAS, v1, hv1)}
} else { } else { // for i,a := range thing { body }
ifGuard = nod(OIF, nil, nil)
ifGuard.Left = nod(OLT, hv1, hn)
translatedLoopOp = OFORUNTIL
a := nod(OAS2, nil, nil) a := nod(OAS2, nil, nil)
a.List.Set2(v1, v2) a.List.Set2(v1, v2)
a.Rlist.Set2(hv1, nod(OIND, hp, nil)) a.Rlist.Set2(hv1, nod(OIND, hp, nil))
...@@ -360,17 +373,33 @@ func walkrange(n *Node) { ...@@ -360,17 +373,33 @@ func walkrange(n *Node) {
} }
} }
n.Op = OFOR n.Op = translatedLoopOp
typecheckslice(init, Etop) typecheckslice(init, Etop)
n.Ninit.Append(init...)
if ifGuard != nil {
ifGuard.Ninit.Append(init...)
typecheckslice(ifGuard.Left.Ninit.Slice(), Etop)
ifGuard.Left = typecheck(ifGuard.Left, Erv)
} else {
n.Ninit.Append(init...)
}
typecheckslice(n.Left.Ninit.Slice(), Etop) typecheckslice(n.Left.Ninit.Slice(), Etop)
n.Left = typecheck(n.Left, Erv) n.Left = typecheck(n.Left, Erv)
n.Right = typecheck(n.Right, Etop) n.Right = typecheck(n.Right, Etop)
typecheckslice(body, Etop) typecheckslice(body, Etop)
n.Nbody.Prepend(body...) n.Nbody.Prepend(body...)
if ifGuard != nil {
ifGuard.Nbody.Set1(n)
n = ifGuard
}
n = walkstmt(n) n = walkstmt(n)
lineno = lno lineno = lno
return n
} }
// Lower n into runtime·memclr if possible, for // Lower n into runtime·memclr if possible, for
......
...@@ -182,7 +182,7 @@ type state struct { ...@@ -182,7 +182,7 @@ type state struct {
// function we're building // function we're building
f *ssa.Func f *ssa.Func
// labels and labeled control flow nodes (OFOR, OSWITCH, OSELECT) in f // labels and labeled control flow nodes (OFOR, OFORUNTIL, OSWITCH, OSELECT) in f
labels map[string]*ssaLabel labels map[string]*ssaLabel
labeledNodes map[*Node]*ssaLabel labeledNodes map[*Node]*ssaLabel
...@@ -594,7 +594,7 @@ func (s *state) stmt(n *Node) { ...@@ -594,7 +594,7 @@ func (s *state) stmt(n *Node) {
// Associate label with its control flow node, if any // Associate label with its control flow node, if any
if ctl := n.Name.Defn; ctl != nil { if ctl := n.Name.Defn; ctl != nil {
switch ctl.Op { switch ctl.Op {
case OFOR, OSWITCH, OSELECT: case OFOR, OFORUNTIL, OSWITCH, OSELECT:
s.labeledNodes[ctl] = lab s.labeledNodes[ctl] = lab
} }
} }
...@@ -840,24 +840,30 @@ func (s *state) stmt(n *Node) { ...@@ -840,24 +840,30 @@ func (s *state) stmt(n *Node) {
b.AddEdgeTo(to) b.AddEdgeTo(to)
} }
case OFOR: case OFOR, OFORUNTIL:
// OFOR: for Ninit; Left; Right { Nbody } // OFOR: for Ninit; Left; Right { Nbody }
// For = cond; body; incr
// Foruntil = body; incr; cond
bCond := s.f.NewBlock(ssa.BlockPlain) bCond := s.f.NewBlock(ssa.BlockPlain)
bBody := s.f.NewBlock(ssa.BlockPlain) bBody := s.f.NewBlock(ssa.BlockPlain)
bIncr := s.f.NewBlock(ssa.BlockPlain) bIncr := s.f.NewBlock(ssa.BlockPlain)
bEnd := s.f.NewBlock(ssa.BlockPlain) bEnd := s.f.NewBlock(ssa.BlockPlain)
// first, jump to condition test // first, jump to condition test (OFOR) or body (OFORUNTIL)
b := s.endBlock() b := s.endBlock()
b.AddEdgeTo(bCond) if n.Op == OFOR {
b.AddEdgeTo(bCond)
// generate code to test condition
s.startBlock(bCond)
if n.Left != nil {
s.condBranch(n.Left, bBody, bEnd, 1)
} else {
b := s.endBlock()
b.Kind = ssa.BlockPlain
b.AddEdgeTo(bBody)
}
// generate code to test condition
s.startBlock(bCond)
if n.Left != nil {
s.condBranch(n.Left, bBody, bEnd, 1)
} else { } else {
b := s.endBlock()
b.Kind = ssa.BlockPlain
b.AddEdgeTo(bBody) b.AddEdgeTo(bBody)
} }
...@@ -898,6 +904,19 @@ func (s *state) stmt(n *Node) { ...@@ -898,6 +904,19 @@ func (s *state) stmt(n *Node) {
if b := s.endBlock(); b != nil { if b := s.endBlock(); b != nil {
b.AddEdgeTo(bCond) b.AddEdgeTo(bCond)
} }
if n.Op == OFORUNTIL {
// generate code to test condition
s.startBlock(bCond)
if n.Left != nil {
s.condBranch(n.Left, bBody, bEnd, 1)
} else {
b := s.endBlock()
b.Kind = ssa.BlockPlain
b.AddEdgeTo(bBody)
}
}
s.startBlock(bEnd) s.startBlock(bEnd)
case OSWITCH, OSELECT: case OSWITCH, OSELECT:
......
...@@ -492,6 +492,7 @@ const ( ...@@ -492,6 +492,7 @@ const (
OFALL // fallthrough (after processing) OFALL // fallthrough (after processing)
OXFALL // fallthrough (before processing) OXFALL // fallthrough (before processing)
OFOR // for Ninit; Left; Right { Nbody } OFOR // for Ninit; Left; Right { Nbody }
OFORUNTIL // for Ninit; Left; Right { Nbody } ; test applied after executing body, not before
OGOTO // goto Left OGOTO // goto Left
OIF // if Ninit; Left { Nbody } else { Rlist } OIF // if Ninit; Left { Nbody } else { Rlist }
OLABEL // Left: OLABEL // Left:
......
...@@ -2022,7 +2022,7 @@ OpSwitch: ...@@ -2022,7 +2022,7 @@ OpSwitch:
checkdefergo(n) checkdefergo(n)
break OpSwitch break OpSwitch
case OFOR: case OFOR, OFORUNTIL:
ok |= Etop ok |= Etop
typecheckslice(n.Ninit.Slice(), Etop) typecheckslice(n.Ninit.Slice(), Etop)
decldepth++ decldepth++
...@@ -3890,6 +3890,7 @@ func markbreak(n *Node, implicit *Node) { ...@@ -3890,6 +3890,7 @@ func markbreak(n *Node, implicit *Node) {
} }
case OFOR, case OFOR,
OFORUNTIL,
OSWITCH, OSWITCH,
OTYPESW, OTYPESW,
OSELECT, OSELECT,
...@@ -3915,7 +3916,7 @@ func markbreaklist(l Nodes, implicit *Node) { ...@@ -3915,7 +3916,7 @@ func markbreaklist(l Nodes, implicit *Node) {
} }
if n.Op == OLABEL && i+1 < len(s) && n.Name.Defn == s[i+1] { if n.Op == OLABEL && i+1 < len(s) && n.Name.Defn == s[i+1] {
switch n.Name.Defn.Op { switch n.Name.Defn.Op {
case OFOR, OSWITCH, OTYPESW, OSELECT, ORANGE: case OFOR, OFORUNTIL, OSWITCH, OTYPESW, OSELECT, ORANGE:
n.Left.Sym.Label = n.Name.Defn n.Left.Sym.Label = n.Name.Defn
markbreak(n.Name.Defn, n.Name.Defn) markbreak(n.Name.Defn, n.Name.Defn)
n.Left.Sym.Label = nil n.Left.Sym.Label = nil
...@@ -3958,7 +3959,7 @@ func (n *Node) isterminating() bool { ...@@ -3958,7 +3959,7 @@ func (n *Node) isterminating() bool {
OXFALL: OXFALL:
return true return true
case OFOR: case OFOR, OFORUNTIL:
if n.Left != nil { if n.Left != nil {
return false return false
} }
......
...@@ -261,7 +261,7 @@ func walkstmt(n *Node) *Node { ...@@ -261,7 +261,7 @@ func walkstmt(n *Node) *Node {
// make room for size & fn arguments. // make room for size & fn arguments.
adjustargs(n, 2*Widthptr) adjustargs(n, 2*Widthptr)
case OFOR: case OFOR, OFORUNTIL:
if n.Left != nil { if n.Left != nil {
walkstmtlist(n.Left.Ninit.Slice()) walkstmtlist(n.Left.Ninit.Slice())
init := n.Left.Ninit init := n.Left.Ninit
...@@ -351,7 +351,7 @@ func walkstmt(n *Node) *Node { ...@@ -351,7 +351,7 @@ func walkstmt(n *Node) *Node {
walkswitch(n) walkswitch(n)
case ORANGE: case ORANGE:
walkrange(n) n = walkrange(n)
case OXFALL: case OXFALL:
yyerror("fallthrough statement out of place") yyerror("fallthrough statement out of place")
......
...@@ -32,11 +32,24 @@ type DummyFrontend struct { ...@@ -32,11 +32,24 @@ type DummyFrontend struct {
t testing.TB t testing.TB
} }
type DummyAuto struct {
t Type
s string
}
func (d *DummyAuto) Typ() Type {
return d.t
}
func (d *DummyAuto) String() string {
return d.s
}
func (DummyFrontend) StringData(s string) interface{} { func (DummyFrontend) StringData(s string) interface{} {
return nil return nil
} }
func (DummyFrontend) Auto(t Type) GCNode { func (DummyFrontend) Auto(t Type) GCNode {
return nil return &DummyAuto{t: t, s: "aDummyAuto"}
} }
func (d DummyFrontend) SplitString(s LocalSlot) (LocalSlot, LocalSlot) { func (d DummyFrontend) SplitString(s LocalSlot) (LocalSlot, LocalSlot) {
return LocalSlot{s.N, d.TypeBytePtr(), s.Off}, LocalSlot{s.N, d.TypeInt(), s.Off + 8} return LocalSlot{s.N, d.TypeBytePtr(), s.Off}, LocalSlot{s.N, d.TypeInt(), s.Off + 8}
......
...@@ -48,9 +48,8 @@ func TestLoopConditionS390X(t *testing.T) { ...@@ -48,9 +48,8 @@ func TestLoopConditionS390X(t *testing.T) {
Bloc("entry", Bloc("entry",
Valu("mem", OpInitMem, TypeMem, 0, nil), Valu("mem", OpInitMem, TypeMem, 0, nil),
Valu("SP", OpSP, TypeUInt64, 0, nil), Valu("SP", OpSP, TypeUInt64, 0, nil),
Valu("Nptr", OpOffPtr, TypeInt64Ptr, 8, nil, "SP"), Valu("ret", OpAddr, TypeInt64Ptr, 0, nil, "SP"),
Valu("ret", OpOffPtr, TypeInt64Ptr, 16, nil, "SP"), Valu("N", OpArg, TypeInt64, 0, c.fe.Auto(TypeInt64)),
Valu("N", OpLoad, TypeInt64, 0, nil, "Nptr", "mem"),
Valu("starti", OpConst64, TypeInt64, 0, nil), Valu("starti", OpConst64, TypeInt64, 0, nil),
Valu("startsum", OpConst64, TypeInt64, 0, nil), Valu("startsum", OpConst64, TypeInt64, 0, nil),
Goto("b1")), Goto("b1")),
...@@ -66,7 +65,8 @@ func TestLoopConditionS390X(t *testing.T) { ...@@ -66,7 +65,8 @@ func TestLoopConditionS390X(t *testing.T) {
Valu("sum", OpAdd64, TypeInt64, 0, nil, "phisum", "c3"), Valu("sum", OpAdd64, TypeInt64, 0, nil, "phisum", "c3"),
Goto("b1")), Goto("b1")),
Bloc("b3", Bloc("b3",
Valu("store", OpStore, TypeMem, 8, nil, "ret", "phisum", "mem"), Valu("retdef", OpVarDef, TypeMem, 0, nil, "mem"),
Valu("store", OpStore, TypeMem, 8, nil, "ret", "phisum", "retdef"),
Exit("store"))) Exit("store")))
CheckFunc(fun.f) CheckFunc(fun.f)
Compile(fun.f) Compile(fun.f)
......
...@@ -6,13 +6,12 @@ package ssa ...@@ -6,13 +6,12 @@ package ssa
import "fmt" import "fmt"
// an edgeMemCtr records a backedge, together with the memory and // an edgeMem records a backedge, together with the memory
// counter phi functions at the target of the backedge that must // phi functions at the target of the backedge that must
// be updated when a rescheduling check replaces the backedge. // be updated when a rescheduling check replaces the backedge.
type edgeMemCtr struct { type edgeMem struct {
e Edge e Edge
m *Value // phi for memory at dest of e m *Value // phi for memory at dest of e
c *Value // phi for counter at dest of e
} }
// a rewriteTarget is a a value-argindex pair indicating // a rewriteTarget is a a value-argindex pair indicating
...@@ -38,32 +37,26 @@ func (r *rewrite) String() string { ...@@ -38,32 +37,26 @@ func (r *rewrite) String() string {
return s return s
} }
const initialRescheduleCounterValue = 1021 // Largest 10-bit prime. 97 nSec loop bodies will check every 100 uSec.
// insertLoopReschedChecks inserts rescheduling checks on loop backedges. // insertLoopReschedChecks inserts rescheduling checks on loop backedges.
func insertLoopReschedChecks(f *Func) { func insertLoopReschedChecks(f *Func) {
// TODO: when split information is recorded in export data, insert checks only on backedges that can be reached on a split-call-free path. // TODO: when split information is recorded in export data, insert checks only on backedges that can be reached on a split-call-free path.
// Loop reschedule checks decrement a per-function counter // Loop reschedule checks compare the stack pointer with
// shared by all loops, and when the counter becomes non-positive // the per-g stack bound. If the pointer appears invalid,
// a call is made to a rescheduling check in the runtime. // that means a reschedule check is needed.
// //
// Steps: // Steps:
// 1. locate backedges. // 1. locate backedges.
// 2. Record memory definitions at block end so that // 2. Record memory definitions at block end so that
// the SSA graph for mem can be prperly modified. // the SSA graph for mem can be properly modified.
// 3. Define a counter and record its future uses (at backedges) // 3. Ensure that phi functions that will-be-needed for mem
// (Same process as 2, applied to a single definition of the counter.
// difference for mem is that there are zero-to-many existing mem
// definitions, versus exactly one for the new counter.)
// 4. Ensure that phi functions that will-be-needed for mem and counter
// are present in the graph, initially with trivial inputs. // are present in the graph, initially with trivial inputs.
// 5. Record all to-be-modified uses of mem and counter; // 4. Record all to-be-modified uses of mem;
// apply modifications (split into two steps to simplify and // apply modifications (split into two steps to simplify and
// avoided nagging order-dependences). // avoided nagging order-dependences).
// 6. Rewrite backedges to include counter check, reschedule check, // 5. Rewrite backedges to include reschedule check,
// and modify destination phi function appropriately with new // and modify destination phi function appropriately with new
// definitions for mem and counter. // definitions for mem.
if f.NoSplit { // nosplit functions don't reschedule. if f.NoSplit { // nosplit functions don't reschedule.
return return
...@@ -83,10 +76,10 @@ func insertLoopReschedChecks(f *Func) { ...@@ -83,10 +76,10 @@ func insertLoopReschedChecks(f *Func) {
fmt.Printf("before %s = %s\n", f.Name, sdom.treestructure(f.Entry)) fmt.Printf("before %s = %s\n", f.Name, sdom.treestructure(f.Entry))
} }
tofixBackedges := []edgeMemCtr{} tofixBackedges := []edgeMem{}
for _, e := range backedges { // TODO: could filter here by calls in loops, if declared and inferred nosplit are recorded in export data. for _, e := range backedges { // TODO: could filter here by calls in loops, if declared and inferred nosplit are recorded in export data.
tofixBackedges = append(tofixBackedges, edgeMemCtr{e, nil, nil}) tofixBackedges = append(tofixBackedges, edgeMem{e, nil})
} }
// It's possible that there is no memory state (no global/pointer loads/stores or calls) // It's possible that there is no memory state (no global/pointer loads/stores or calls)
...@@ -108,40 +101,8 @@ func insertLoopReschedChecks(f *Func) { ...@@ -108,40 +101,8 @@ func insertLoopReschedChecks(f *Func) {
memDefsAtBlockEnds[b.ID] = mem memDefsAtBlockEnds[b.ID] = mem
} }
// Set up counter. There are no phis etc pre-existing for it.
counter0 := f.Entry.NewValue0I(f.Entry.Pos, OpConst32, f.Config.fe.TypeInt32(), initialRescheduleCounterValue)
ctrDefsAtBlockEnds := make([]*Value, f.NumBlocks()) // For each block, def visible at its end, if that def will be used.
// There's a minor difference between memDefsAtBlockEnds and ctrDefsAtBlockEnds;
// because the counter only matter for loops and code that reaches them, it is nil for blocks where the ctr is no
// longer live. This will avoid creation of dead phi functions. This optimization is ignored for the mem variable
// because it is harder and also less likely to be helpful, though dead code elimination ought to clean this out anyhow.
for _, emc := range tofixBackedges {
e := emc.e
// set initial uses of counter zero (note available-at-bottom and use are the same thing initially.)
// each back-edge will be rewritten to include a reschedule check, and that will use the counter.
src := e.b.Preds[e.i].b
ctrDefsAtBlockEnds[src.ID] = counter0
}
// Push uses towards root
for _, b := range f.postorder() {
bd := ctrDefsAtBlockEnds[b.ID]
if bd == nil {
continue
}
for _, e := range b.Preds {
p := e.b
if ctrDefsAtBlockEnds[p.ID] == nil {
ctrDefsAtBlockEnds[p.ID] = bd
}
}
}
// Maps from block to newly-inserted phi function in block. // Maps from block to newly-inserted phi function in block.
newmemphis := make(map[*Block]rewrite) newmemphis := make(map[*Block]rewrite)
newctrphis := make(map[*Block]rewrite)
// Insert phi functions as necessary for future changes to flow graph. // Insert phi functions as necessary for future changes to flow graph.
for i, emc := range tofixBackedges { for i, emc := range tofixBackedges {
...@@ -167,29 +128,14 @@ func insertLoopReschedChecks(f *Func) { ...@@ -167,29 +128,14 @@ func insertLoopReschedChecks(f *Func) {
} }
tofixBackedges[i].m = headerMemPhi tofixBackedges[i].m = headerMemPhi
var headerCtrPhi *Value
rw, ok := newctrphis[h]
if !ok {
headerCtrPhi = newPhiFor(h, counter0)
newctrphis[h] = rewrite{before: counter0, after: headerCtrPhi}
addDFphis(counter0, h, h, f, ctrDefsAtBlockEnds, newctrphis)
} else {
headerCtrPhi = rw.after
}
tofixBackedges[i].c = headerCtrPhi
} }
rewriteNewPhis(f.Entry, f.Entry, f, memDefsAtBlockEnds, newmemphis) rewriteNewPhis(f.Entry, f.Entry, f, memDefsAtBlockEnds, newmemphis)
rewriteNewPhis(f.Entry, f.Entry, f, ctrDefsAtBlockEnds, newctrphis)
if f.pass.debug > 0 { if f.pass.debug > 0 {
for b, r := range newmemphis { for b, r := range newmemphis {
fmt.Printf("b=%s, rewrite=%s\n", b, r.String()) fmt.Printf("b=%s, rewrite=%s\n", b, r.String())
} }
for b, r := range newctrphis {
fmt.Printf("b=%s, rewrite=%s\n", b, r.String())
}
} }
// Apply collected rewrites. // Apply collected rewrites.
...@@ -199,26 +145,15 @@ func insertLoopReschedChecks(f *Func) { ...@@ -199,26 +145,15 @@ func insertLoopReschedChecks(f *Func) {
} }
} }
for _, r := range newctrphis {
for _, rw := range r.rewrites {
rw.v.SetArg(rw.i, r.after)
}
}
zero := f.Entry.NewValue0I(f.Entry.Pos, OpConst32, f.Config.fe.TypeInt32(), 0)
one := f.Entry.NewValue0I(f.Entry.Pos, OpConst32, f.Config.fe.TypeInt32(), 1)
// Rewrite backedges to include reschedule checks. // Rewrite backedges to include reschedule checks.
for _, emc := range tofixBackedges { for _, emc := range tofixBackedges {
e := emc.e e := emc.e
headerMemPhi := emc.m headerMemPhi := emc.m
headerCtrPhi := emc.c
h := e.b h := e.b
i := e.i i := e.i
p := h.Preds[i] p := h.Preds[i]
bb := p.b bb := p.b
mem0 := headerMemPhi.Args[i] mem0 := headerMemPhi.Args[i]
ctr0 := headerCtrPhi.Args[i]
// bb e->p h, // bb e->p h,
// Because we're going to insert a rare-call, make sure the // Because we're going to insert a rare-call, make sure the
// looping edge still looks likely. // looping edge still looks likely.
...@@ -236,22 +171,20 @@ func insertLoopReschedChecks(f *Func) { ...@@ -236,22 +171,20 @@ func insertLoopReschedChecks(f *Func) {
// //
// new block(s): // new block(s):
// test: // test:
// ctr1 := ctr0 - 1 // if sp < g.limit { goto sched }
// if ctr1 <= 0 { goto sched }
// goto join // goto join
// sched: // sched:
// mem1 := call resched (mem0) // mem1 := call resched (mem0)
// goto join // goto join
// join: // join:
// ctr2 := phi(ctr1, counter0) // counter0 is the constant
// mem2 := phi(mem0, mem1) // mem2 := phi(mem0, mem1)
// goto h // goto h
// //
// and correct arg i of headerMemPhi and headerCtrPhi // and correct arg i of headerMemPhi and headerCtrPhi
// //
// EXCEPT: block containing only phi functions is bad // EXCEPT: join block containing only phi functions is bad
// for the register allocator. Therefore, there is no // for the register allocator. Therefore, there is no
// join, and instead branches targeting join instead target // join, and branches targeting join must instead target
// the header, and the other phi functions within header are // the header, and the other phi functions within header are
// adjusted for the additional input. // adjusted for the additional input.
...@@ -261,20 +194,30 @@ func insertLoopReschedChecks(f *Func) { ...@@ -261,20 +194,30 @@ func insertLoopReschedChecks(f *Func) {
test.Pos = bb.Pos test.Pos = bb.Pos
sched.Pos = bb.Pos sched.Pos = bb.Pos
// ctr1 := ctr0 - 1 // if sp < g.limit { goto sched }
// if ctr1 <= 0 { goto sched } // goto header
// goto header
ctr1 := test.NewValue2(bb.Pos, OpSub32, f.Config.fe.TypeInt32(), ctr0, one) pt := f.Config.Frontend().TypeUintptr()
cmp := test.NewValue2(bb.Pos, OpLeq32, f.Config.fe.TypeBool(), ctr1, zero) g := test.NewValue1(bb.Pos, OpGetG, pt, mem0)
sp := test.NewValue0(bb.Pos, OpSP, pt)
cmpOp := OpLess64U
if pt.Size() == 4 {
cmpOp = OpLess32U
}
limaddr := test.NewValue1I(bb.Pos, OpOffPtr, pt, 2*pt.Size(), g)
lim := test.NewValue2(bb.Pos, OpLoad, pt, limaddr, mem0)
cmp := test.NewValue2(bb.Pos, cmpOp, f.Config.fe.TypeBool(), sp, lim)
test.SetControl(cmp) test.SetControl(cmp)
test.AddEdgeTo(sched) // if true
// if false -- rewrite edge to header. // if true, goto sched
test.AddEdgeTo(sched)
// if false, rewrite edge to header.
// do NOT remove+add, because that will perturb all the other phi functions // do NOT remove+add, because that will perturb all the other phi functions
// as well as messing up other edges to the header. // as well as messing up other edges to the header.
test.Succs = append(test.Succs, Edge{h, i}) test.Succs = append(test.Succs, Edge{h, i})
h.Preds[i] = Edge{test, 1} h.Preds[i] = Edge{test, 1}
headerMemPhi.SetArg(i, mem0) headerMemPhi.SetArg(i, mem0)
headerCtrPhi.SetArg(i, ctr1)
test.Likely = BranchUnlikely test.Likely = BranchUnlikely
...@@ -285,16 +228,15 @@ func insertLoopReschedChecks(f *Func) { ...@@ -285,16 +228,15 @@ func insertLoopReschedChecks(f *Func) {
mem1 := sched.NewValue1A(bb.Pos, OpStaticCall, TypeMem, resched, mem0) mem1 := sched.NewValue1A(bb.Pos, OpStaticCall, TypeMem, resched, mem0)
sched.AddEdgeTo(h) sched.AddEdgeTo(h)
headerMemPhi.AddArg(mem1) headerMemPhi.AddArg(mem1)
headerCtrPhi.AddArg(counter0)
bb.Succs[p.i] = Edge{test, 0} bb.Succs[p.i] = Edge{test, 0}
test.Preds = append(test.Preds, Edge{bb, p.i}) test.Preds = append(test.Preds, Edge{bb, p.i})
// Must correct all the other phi functions in the header for new incoming edge. // Must correct all the other phi functions in the header for new incoming edge.
// Except for mem and counter phis, it will be the same value seen on the original // Except for mem phis, it will be the same value seen on the original
// backedge at index i. // backedge at index i.
for _, v := range h.Values { for _, v := range h.Values {
if v.Op == OpPhi && v != headerMemPhi && v != headerCtrPhi { if v.Op == OpPhi && v != headerMemPhi {
v.AddArg(v.Args[i]) v.AddArg(v.Args[i])
} }
} }
...@@ -354,7 +296,7 @@ func rewriteNewPhis(h, b *Block, f *Func, defsForUses []*Value, newphis map[*Blo ...@@ -354,7 +296,7 @@ func rewriteNewPhis(h, b *Block, f *Func, defsForUses []*Value, newphis map[*Blo
// in dominance frontier, self, and dominated. // in dominance frontier, self, and dominated.
// If the variable def reaching uses in b is itself defined in b, then the new phi function // If the variable def reaching uses in b is itself defined in b, then the new phi function
// does not reach the successors of b. (This assumes a bit about the structure of the // does not reach the successors of b. (This assumes a bit about the structure of the
// phi use-def graph, but it's true for memory and the inserted counter.) // phi use-def graph, but it's true for memory.)
if dfu := defsForUses[b.ID]; dfu != nil && dfu.Block != b { if dfu := defsForUses[b.ID]; dfu != nil && dfu.Block != b {
for _, e := range b.Succs { for _, e := range b.Succs {
s := e.b s := e.b
......
...@@ -232,22 +232,18 @@ func forcegchelper() { ...@@ -232,22 +232,18 @@ func forcegchelper() {
} }
} }
//go:nosplit
// Gosched yields the processor, allowing other goroutines to run. It does not // Gosched yields the processor, allowing other goroutines to run. It does not
// suspend the current goroutine, so execution resumes automatically. // suspend the current goroutine, so execution resumes automatically.
//go:nosplit
func Gosched() { func Gosched() {
mcall(gosched_m) mcall(gosched_m)
} }
var alwaysFalse bool // goschedguarded yields the processor like gosched, but also checks
// for forbidden states and opts out of the yield in those cases.
// goschedguarded does nothing, but is written in a way that guarantees a preemption check in its prologue. //go:nosplit
// Calls to this function are inserted by the compiler in otherwise uninterruptible loops (see insertLoopReschedChecks).
func goschedguarded() { func goschedguarded() {
if alwaysFalse { mcall(goschedguarded_m)
goschedguarded()
}
} }
// Puts the current goroutine into a waiting state and calls unlockf. // Puts the current goroutine into a waiting state and calls unlockf.
...@@ -2294,6 +2290,19 @@ func gosched_m(gp *g) { ...@@ -2294,6 +2290,19 @@ func gosched_m(gp *g) {
goschedImpl(gp) goschedImpl(gp)
} }
// goschedguarded is a forbidden-states-avoided version of gosched_m
func goschedguarded_m(gp *g) {
if gp.m.locks != 0 || gp.m.mallocing != 0 || gp.m.preemptoff != "" || gp.m.p.ptr().status != _Prunning {
gogo(&gp.sched) // never return
}
if trace.enabled {
traceGoSched()
}
goschedImpl(gp)
}
func gopreempt_m(gp *g) { func gopreempt_m(gp *g) {
if trace.enabled { if trace.enabled {
traceGoPreempt() traceGoPreempt()
......
// +build !nacl,disabled // +build !nacl
// buildrun -t 10 -gcflags=-d=ssa/insert_resched_checks/on,ssa/check/on // buildrun -t 10 -gcflags=-d=ssa/insert_resched_checks/on,ssa/check/on
// Copyright 2016 The Go Authors. All rights reserved. // Copyright 2016 The Go Authors. All rights reserved.
......
...@@ -31,7 +31,7 @@ func f0c(a []int) int { ...@@ -31,7 +31,7 @@ func f0c(a []int) int {
func f1(a []int) int { func f1(a []int) int {
x := 0 x := 0
for _, i := range a { // ERROR "Induction variable with minimum 0 and increment 1$" for _, i := range a { // Change to "for i,e := range array/slice" hides IV report.
x += i x += i
} }
return x return x
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment