Commit 7f1ff65c authored by David Chase's avatar David Chase

cmd/compile: insert scheduling checks on loop backedges

Loop breaking with a counter.  Benchmarked (see comments),
eyeball checked for sanity on popular loops.  This code
ought to handle loops in general, and properly inserts phi
functions in cases where the earlier version might not have.

Includes test, plus modifications to test/run.go to deal with
timeout and killing looping test.  Tests broken by the addition
of extra code (branch frequency and live vars) for added
checks turn the check insertion off.

If GOEXPERIMENT=preemptibleloops, the compiler inserts reschedule
checks on every backedge of every reducible loop.  Alternately,
specifying GO_GCFLAGS=-d=ssa/insert_resched_checks/on will
enable it for a single compilation, but because the core Go
libraries contain some loops that may run long, this is less
likely to have the desired effect.

This is intended as a tool to help in the study and diagnosis
of GC and other latency problems, now that goal STW GC latency
is on the order of 100 microseconds or less.

Updates #17831.
Updates #10958.

Change-Id: I6206c163a5b0248e3f21eb4fc65f73a179e1f639
Reviewed-on: https://go-review.googlesource.com/33910
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarKeith Randall <khr@golang.org>
parent f412bd31
...@@ -15,6 +15,7 @@ var runtimeDecls = [...]struct { ...@@ -15,6 +15,7 @@ var runtimeDecls = [...]struct {
{"panicwrap", funcTag, 7}, {"panicwrap", funcTag, 7},
{"gopanic", funcTag, 9}, {"gopanic", funcTag, 9},
{"gorecover", funcTag, 12}, {"gorecover", funcTag, 12},
{"goschedguarded", funcTag, 5},
{"printbool", funcTag, 14}, {"printbool", funcTag, 14},
{"printfloat", funcTag, 16}, {"printfloat", funcTag, 16},
{"printint", funcTag, 18}, {"printint", funcTag, 18},
......
...@@ -21,6 +21,7 @@ func panicwrap(string, string, string) ...@@ -21,6 +21,7 @@ func panicwrap(string, string, string)
func gopanic(interface{}) func gopanic(interface{})
func gorecover(*int32) interface{} func gorecover(*int32) interface{}
func goschedguarded()
func printbool(bool) func printbool(bool)
func printfloat(float64) func printfloat(float64)
......
...@@ -64,6 +64,9 @@ func buildssa(fn *Node) *ssa.Func { ...@@ -64,6 +64,9 @@ func buildssa(fn *Node) *ssa.Func {
s.config = initssa() s.config = initssa()
s.f = s.config.NewFunc() s.f = s.config.NewFunc()
s.f.Name = name s.f.Name = name
if fn.Func.Pragma&Nosplit != 0 {
s.f.NoSplit = true
}
s.exitCode = fn.Func.Exit s.exitCode = fn.Func.Exit
s.panics = map[funcLine]*ssa.Block{} s.panics = map[funcLine]*ssa.Block{}
s.config.DebugTest = s.config.DebugHashMatch("GOSSAHASH", name) s.config.DebugTest = s.config.DebugHashMatch("GOSSAHASH", name)
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
package ssa package ssa
import ( import (
"cmd/internal/obj"
"fmt" "fmt"
"log" "log"
"os" "os"
...@@ -349,6 +350,8 @@ var passes = [...]pass{ ...@@ -349,6 +350,8 @@ var passes = [...]pass{
{name: "writebarrier", fn: writebarrier, required: true}, // expand write barrier ops {name: "writebarrier", fn: writebarrier, required: true}, // expand write barrier ops
{name: "fuse", fn: fuse}, {name: "fuse", fn: fuse},
{name: "dse", fn: dse}, {name: "dse", fn: dse},
{name: "insert resched checks", fn: insertLoopReschedChecks,
disabled: obj.Preemptibleloops_enabled == 0}, // insert resched checks in loops.
{name: "tighten", fn: tighten}, // move values closer to their uses {name: "tighten", fn: tighten}, // move values closer to their uses
{name: "lower", fn: lower, required: true}, {name: "lower", fn: lower, required: true},
{name: "lowered cse", fn: cse}, {name: "lowered cse", fn: cse},
...@@ -378,7 +381,13 @@ type constraint struct { ...@@ -378,7 +381,13 @@ type constraint struct {
} }
var passOrder = [...]constraint{ var passOrder = [...]constraint{
// prove reliese on common-subexpression elimination for maximum benefits. // "insert resched checks" uses mem, better to clean out stores first.
{"dse", "insert resched checks"},
// insert resched checks adds new blocks containing generic instructions
{"insert resched checks", "lower"},
{"insert resched checks", "tighten"},
// prove relies on common-subexpression elimination for maximum benefits.
{"generic cse", "prove"}, {"generic cse", "prove"},
// deadcode after prove to eliminate all new dead blocks. // deadcode after prove to eliminate all new dead blocks.
{"prove", "generic deadcode"}, {"prove", "generic deadcode"},
......
...@@ -24,6 +24,7 @@ type Func struct { ...@@ -24,6 +24,7 @@ type Func struct {
vid idAlloc // value ID allocator vid idAlloc // value ID allocator
scheduled bool // Values in Blocks are in final order scheduled bool // Values in Blocks are in final order
NoSplit bool // true if function is marked as nosplit. Used by schedule check pass.
// when register allocation is done, maps value ids to locations // when register allocation is done, maps value ids to locations
RegAlloc []Location RegAlloc []Location
......
This diff is collapsed.
...@@ -4,7 +4,10 @@ ...@@ -4,7 +4,10 @@
package ssa package ssa
import "fmt" import (
"fmt"
"strings"
)
type SparseTreeNode struct { type SparseTreeNode struct {
child *Block child *Block
...@@ -67,6 +70,34 @@ func newSparseTree(f *Func, parentOf []*Block) SparseTree { ...@@ -67,6 +70,34 @@ func newSparseTree(f *Func, parentOf []*Block) SparseTree {
return t return t
} }
// treestructure provides a string description of the dominator
// tree and flow structure of block b and all blocks that it
// dominates.
func (t SparseTree) treestructure(b *Block) string {
return t.treestructure1(b, 0)
}
func (t SparseTree) treestructure1(b *Block, i int) string {
s := "\n" + strings.Repeat("\t", i) + b.String() + "->["
for i, e := range b.Succs {
if i > 0 {
s = s + ","
}
s = s + e.b.String()
}
s += "]"
if c0 := t[b.ID].child; c0 != nil {
s += "("
for c := c0; c != nil; c = t[c.ID].sibling {
if c != c0 {
s += " "
}
s += t.treestructure1(c, i+1)
}
s += ")"
}
return s
}
// numberBlock assigns entry and exit numbers for b and b's // numberBlock assigns entry and exit numbers for b and b's
// children in an in-order walk from a gappy sequence, where n // children in an in-order walk from a gappy sequence, where n
// is the first number not yet assigned or reserved. N should // is the first number not yet assigned or reserved. N should
......
...@@ -15,6 +15,7 @@ import ( ...@@ -15,6 +15,7 @@ import (
var ( var (
framepointer_enabled int framepointer_enabled int
Fieldtrack_enabled int Fieldtrack_enabled int
Preemptibleloops_enabled int
) )
// Toolchain experiments. // Toolchain experiments.
...@@ -27,6 +28,7 @@ var exper = []struct { ...@@ -27,6 +28,7 @@ var exper = []struct {
}{ }{
{"fieldtrack", &Fieldtrack_enabled}, {"fieldtrack", &Fieldtrack_enabled},
{"framepointer", &framepointer_enabled}, {"framepointer", &framepointer_enabled},
{"preemptibleloops", &Preemptibleloops_enabled},
} }
func addexp(s string) { func addexp(s string) {
......
...@@ -240,6 +240,16 @@ func Gosched() { ...@@ -240,6 +240,16 @@ func Gosched() {
mcall(gosched_m) mcall(gosched_m)
} }
var alwaysFalse bool
// goschedguarded does nothing, but is written in a way that guarantees a preemption check in its prologue.
// Calls to this function are inserted by the compiler in otherwise uninterruptible loops (see insertLoopReschedChecks).
func goschedguarded() {
if alwaysFalse {
goschedguarded()
}
}
// Puts the current goroutine into a waiting state and calls unlockf. // Puts the current goroutine into a waiting state and calls unlockf.
// If unlockf returns false, the goroutine is resumed. // If unlockf returns false, the goroutine is resumed.
// unlockf must not access this G's stack, as it may be moved between // unlockf must not access this G's stack, as it may be moved between
......
// +build !nacl
// buildrun -t 2 -gcflags=-d=ssa/insert_resched_checks/on,ssa/check/on
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This checks to see that call-free infinite loops do not
// block garbage collection.
package main
import (
"runtime"
)
var someglobal1 int
var someglobal2 int
var someglobal3 int
//go:noinline
func f() {}
func standinacorner1() {
for someglobal1&1 == 0 {
someglobal1++
someglobal1++
}
}
func standinacorner2(i int) {
// contains an irreducible loop containing changes to memory
if i != 0 {
goto midloop
}
loop:
if someglobal2&1 != 0 {
goto done
}
someglobal2++
midloop:
someglobal2++
goto loop
done:
return
}
func standinacorner3() {
for someglobal3&1 == 0 {
if someglobal3&2 != 0 {
for someglobal3&3 == 2 {
someglobal3++
someglobal3++
someglobal3++
someglobal3++
}
}
someglobal3++
someglobal3++
someglobal3++
someglobal3++
}
}
func main() {
go standinacorner1()
go standinacorner2(0)
go standinacorner3()
// println("About to stand in a corner1")
for someglobal1 == 0 {
runtime.Gosched()
}
// println("About to stand in a corner2")
for someglobal2 == 0 {
runtime.Gosched()
}
// println("About to stand in a corner3")
for someglobal3 == 0 {
runtime.Gosched()
}
// println("About to GC")
runtime.GC()
// println("Success")
}
// errorcheckwithauto -0 -l -live -wb=0 // errorcheckwithauto -0 -l -live -wb=0 -d=ssa/insert_resched_checks/off
// +build !ppc64,!ppc64le // +build !ppc64,!ppc64le
// ppc64 needs a better tighten pass to make f18 pass // ppc64 needs a better tighten pass to make f18 pass
// rescheduling checks need to be turned off because there are some live variables across the inserted check call
// Copyright 2014 The Go Authors. All rights reserved. // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
......
// +build amd64 // +build amd64
// errorcheck -0 -d=ssa/likelyadjust/debug=1 // errorcheck -0 -d=ssa/likelyadjust/debug=1,ssa/insert_resched_checks/off
// rescheduling check insertion is turend off because the inserted conditional branches perturb the errorcheck
// Copyright 2016 The Go Authors. All rights reserved. // Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
......
...@@ -463,6 +463,7 @@ func (t *test) run() { ...@@ -463,6 +463,7 @@ func (t *test) run() {
} }
var args, flags []string var args, flags []string
var tim int
wantError := false wantError := false
wantAuto := false wantAuto := false
singlefilepkgs := false singlefilepkgs := false
...@@ -478,7 +479,7 @@ func (t *test) run() { ...@@ -478,7 +479,7 @@ func (t *test) run() {
action = "rundir" action = "rundir"
case "cmpout": case "cmpout":
action = "run" // the run case already looks for <dir>/<test>.out files action = "run" // the run case already looks for <dir>/<test>.out files
case "compile", "compiledir", "build", "run", "runoutput", "rundir": case "compile", "compiledir", "build", "run", "buildrun", "runoutput", "rundir":
// nothing to do // nothing to do
case "errorcheckandrundir": case "errorcheckandrundir":
wantError = false // should be no error if also will run wantError = false // should be no error if also will run
...@@ -505,6 +506,14 @@ func (t *test) run() { ...@@ -505,6 +506,14 @@ func (t *test) run() {
wantError = false wantError = false
case "-s": case "-s":
singlefilepkgs = true singlefilepkgs = true
case "-t": // timeout in seconds
args = args[1:]
var err error
tim, err = strconv.Atoi(args[0])
if err != nil {
t.err = fmt.Errorf("need number of seconds for -t timeout, got %s instead", args[0])
}
default: default:
flags = append(flags, args[0]) flags = append(flags, args[0])
} }
...@@ -539,7 +548,31 @@ func (t *test) run() { ...@@ -539,7 +548,31 @@ func (t *test) run() {
} else { } else {
cmd.Env = os.Environ() cmd.Env = os.Environ()
} }
err := cmd.Run()
var err error
if tim != 0 {
err = cmd.Start()
// This command-timeout code adapted from cmd/go/test.go
if err == nil {
tick := time.NewTimer(time.Duration(tim) * time.Second)
done := make(chan error)
go func() {
done <- cmd.Wait()
}()
select {
case err = <-done:
// ok
case <-tick.C:
cmd.Process.Kill()
err = <-done
// err = errors.New("Test timeout")
}
tick.Stop()
}
} else {
err = cmd.Run()
}
if err != nil { if err != nil {
err = fmt.Errorf("%s\n%s", err, buf.Bytes()) err = fmt.Errorf("%s\n%s", err, buf.Bytes())
} }
...@@ -671,6 +704,32 @@ func (t *test) run() { ...@@ -671,6 +704,32 @@ func (t *test) run() {
t.err = err t.err = err
} }
case "buildrun": // build binary, then run binary, instead of go run. Useful for timeout tests where failure mode is infinite loop.
// TODO: not supported on NaCl
useTmp = true
cmd := []string{"go", "build", "-o", "a.exe"}
if *linkshared {
cmd = append(cmd, "-linkshared")
}
longdirgofile := filepath.Join(filepath.Join(cwd, t.dir), t.gofile)
cmd = append(cmd, flags...)
cmd = append(cmd, longdirgofile)
out, err := runcmd(cmd...)
if err != nil {
t.err = err
return
}
cmd = []string{"./a.exe"}
out, err = runcmd(append(cmd, args...)...)
if err != nil {
t.err = err
return
}
if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() {
t.err = fmt.Errorf("incorrect output\n%s", out)
}
case "run": case "run":
useTmp = false useTmp = false
cmd := []string{"go", "run"} cmd := []string{"go", "run"}
......
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