mirror of
https://github.com/golang/go
synced 2024-11-26 07:38:00 -07:00
cmd/compile, runtime: add go:yeswritebarrierrec pragma
This pragma cancels the effect of go:nowritebarrierrec. This is useful in the scheduler because there are places where we enter a function without a valid P (and hence cannot have write barriers), but then obtain a P. This allows us to annotate the function with go:nowritebarrierrec and split out the part after we've obtained a P into a go:yeswritebarrierrec function. Change-Id: Ic8ce4b6d3c074a1ecd8280ad90eaf39f0ffbcc2a Reviewed-on: https://go-review.googlesource.com/30938 Reviewed-by: Matthew Dempsky <mdempsky@google.com> Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
parent
6347367be3
commit
a9e6cebde2
@ -1317,7 +1317,7 @@ func checknowritebarrierrec() {
|
|||||||
visitBottomUp(xtop, func(list []*Node, recursive bool) {
|
visitBottomUp(xtop, func(list []*Node, recursive bool) {
|
||||||
// Functions with write barriers have depth 0.
|
// Functions with write barriers have depth 0.
|
||||||
for _, n := range list {
|
for _, n := range list {
|
||||||
if n.Func.WBLineno != 0 {
|
if n.Func.WBLineno != 0 && n.Func.Pragma&Yeswritebarrierrec == 0 {
|
||||||
c.best[n] = nowritebarrierrecCall{target: nil, depth: 0, lineno: n.Func.WBLineno}
|
c.best[n] = nowritebarrierrecCall{target: nil, depth: 0, lineno: n.Func.WBLineno}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1329,6 +1329,12 @@ func checknowritebarrierrec() {
|
|||||||
for _ = range list {
|
for _ = range list {
|
||||||
c.stable = false
|
c.stable = false
|
||||||
for _, n := range list {
|
for _, n := range list {
|
||||||
|
if n.Func.Pragma&Yeswritebarrierrec != 0 {
|
||||||
|
// Don't propagate write
|
||||||
|
// barrier up to a
|
||||||
|
// yeswritebarrierrec function.
|
||||||
|
continue
|
||||||
|
}
|
||||||
if n.Func.WBLineno == 0 {
|
if n.Func.WBLineno == 0 {
|
||||||
c.curfn = n
|
c.curfn = n
|
||||||
c.visitcodelist(n.Nbody)
|
c.visitcodelist(n.Nbody)
|
||||||
@ -1393,9 +1399,6 @@ func (c *nowritebarrierrecChecker) visitcall(n *Node) {
|
|||||||
if fn == nil || fn.Op != ONAME || fn.Class != PFUNC || fn.Name.Defn == nil {
|
if fn == nil || fn.Op != ONAME || fn.Class != PFUNC || fn.Name.Defn == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (compiling_runtime || fn.Sym.Pkg == Runtimepkg) && fn.Sym.Name == "allocm" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defn := fn.Name.Defn
|
defn := fn.Name.Defn
|
||||||
|
|
||||||
fnbest, ok := c.best[defn]
|
fnbest, ok := c.best[defn]
|
||||||
|
@ -69,11 +69,16 @@ const (
|
|||||||
Norace // func must not have race detector annotations
|
Norace // func must not have race detector annotations
|
||||||
Nosplit // func should not execute on separate stack
|
Nosplit // func should not execute on separate stack
|
||||||
Noinline // func should not be inlined
|
Noinline // func should not be inlined
|
||||||
|
CgoUnsafeArgs // treat a pointer to one arg as a pointer to them all
|
||||||
|
UintptrEscapes // pointers converted to uintptr escape
|
||||||
|
|
||||||
|
// Runtime-only pragmas.
|
||||||
|
// See ../../../../runtime/README.md for detailed descriptions.
|
||||||
|
|
||||||
Systemstack // func must run on system stack
|
Systemstack // func must run on system stack
|
||||||
Nowritebarrier // emit compiler error instead of write barrier
|
Nowritebarrier // emit compiler error instead of write barrier
|
||||||
Nowritebarrierrec // error on write barrier in this or recursive callees
|
Nowritebarrierrec // error on write barrier in this or recursive callees
|
||||||
CgoUnsafeArgs // treat a pointer to one arg as a pointer to them all
|
Yeswritebarrierrec // cancels Nowritebarrierrec in this function and callees
|
||||||
UintptrEscapes // pointers converted to uintptr escape
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func pragmaValue(verb string) Pragma {
|
func pragmaValue(verb string) Pragma {
|
||||||
@ -105,6 +110,11 @@ func pragmaValue(verb string) Pragma {
|
|||||||
yyerror("//go:nowritebarrierrec only allowed in runtime")
|
yyerror("//go:nowritebarrierrec only allowed in runtime")
|
||||||
}
|
}
|
||||||
return Nowritebarrierrec | Nowritebarrier // implies Nowritebarrier
|
return Nowritebarrierrec | Nowritebarrier // implies Nowritebarrier
|
||||||
|
case "go:yeswritebarrierrec":
|
||||||
|
if !compiling_runtime {
|
||||||
|
yyerror("//go:yeswritebarrierrec only allowed in runtime")
|
||||||
|
}
|
||||||
|
return Yeswritebarrierrec
|
||||||
case "go:cgo_unsafe_args":
|
case "go:cgo_unsafe_args":
|
||||||
return CgoUnsafeArgs
|
return CgoUnsafeArgs
|
||||||
case "go:uintptrescapes":
|
case "go:uintptrescapes":
|
||||||
|
@ -3312,7 +3312,7 @@ func (s *state) insertWBmove(t *Type, left, right *ssa.Value, line int32, rightI
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
if s.noWB {
|
if s.noWB {
|
||||||
s.Fatalf("write barrier prohibited")
|
s.Error("write barrier prohibited")
|
||||||
}
|
}
|
||||||
if s.WBLineno == 0 {
|
if s.WBLineno == 0 {
|
||||||
s.WBLineno = left.Line
|
s.WBLineno = left.Line
|
||||||
@ -3377,7 +3377,7 @@ func (s *state) insertWBstore(t *Type, left, right *ssa.Value, line int32, skip
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
if s.noWB {
|
if s.noWB {
|
||||||
s.Fatalf("write barrier prohibited")
|
s.Error("write barrier prohibited")
|
||||||
}
|
}
|
||||||
if s.WBLineno == 0 {
|
if s.WBLineno == 0 {
|
||||||
s.WBLineno = left.Line
|
s.WBLineno = left.Line
|
||||||
|
49
src/runtime/HACKING.md
Normal file
49
src/runtime/HACKING.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
This is a very incomplete and probably out-of-date guide to
|
||||||
|
programming in the Go runtime and how it differs from writing normal
|
||||||
|
Go.
|
||||||
|
|
||||||
|
Runtime-only compiler directives
|
||||||
|
================================
|
||||||
|
|
||||||
|
In addition to the "//go:" directives documented in "go doc compile",
|
||||||
|
the compiler supports additional directives only in the runtime.
|
||||||
|
|
||||||
|
go:systemstack
|
||||||
|
--------------
|
||||||
|
|
||||||
|
`go:systemstack` indicates that a function must run on the system
|
||||||
|
stack. This is checked dynamically by a special function prologue.
|
||||||
|
|
||||||
|
go:nowritebarrier
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
`go:nowritebarrier` directs the compiler to emit an error if the
|
||||||
|
following function contains any write barriers. (It *does not*
|
||||||
|
suppress the generation of write barriers; it is simply an assertion.)
|
||||||
|
|
||||||
|
Usually you want `go:nowritebarrierrec`. `go:nowritebarrier` is
|
||||||
|
primarily useful in situations where it's "nice" not to have write
|
||||||
|
barriers, but not required for correctness.
|
||||||
|
|
||||||
|
go:nowritebarrierrec and go:yeswritebarrierrec
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
`go:nowritebarrierrec` directs the compiler to emit an error if the
|
||||||
|
following function or any function it calls recursively, up to a
|
||||||
|
`go:yeswritebarrierrec`, contains a write barrier.
|
||||||
|
|
||||||
|
Logically, the compiler floods the call graph starting from each
|
||||||
|
`go:nowritebarrierrec` function and produces an error if it encounters
|
||||||
|
a function containing a write barrier. This flood stops at
|
||||||
|
`go:yeswritebarrierrec` functions.
|
||||||
|
|
||||||
|
`go:nowritebarrierrec` is used in the implementation of the write
|
||||||
|
barrier to prevent infinite loops.
|
||||||
|
|
||||||
|
Both directives are used in the scheduler. The write barrier requires
|
||||||
|
an active P (`getg().m.p != nil`) and scheduler code often runs
|
||||||
|
without an active P. In this case, `go:nowritebarrierrec` is used on
|
||||||
|
functions that release the P or may run without a P and
|
||||||
|
`go:yeswritebarrierrec` is used when code re-acquires an active P.
|
||||||
|
Since these are function-level annotations, code that releases or
|
||||||
|
acquires a P may need to be split across two functions.
|
@ -1275,8 +1275,10 @@ type cgothreadstart struct {
|
|||||||
// Can use p for allocation context if needed.
|
// Can use p for allocation context if needed.
|
||||||
// fn is recorded as the new m's m.mstartfn.
|
// fn is recorded as the new m's m.mstartfn.
|
||||||
//
|
//
|
||||||
// This function it known to the compiler to inhibit the
|
// This function is allowed to have write barriers even if the caller
|
||||||
// go:nowritebarrierrec annotation because it uses P for allocation.
|
// isn't because it borrows _p_.
|
||||||
|
//
|
||||||
|
//go:yeswritebarrierrec
|
||||||
func allocm(_p_ *p, fn func()) *m {
|
func allocm(_p_ *p, fn func()) *m {
|
||||||
_g_ := getg()
|
_g_ := getg()
|
||||||
_g_.m.locks++ // disable GC because it can be called from sysmon
|
_g_.m.locks++ // disable GC because it can be called from sysmon
|
||||||
@ -2459,7 +2461,11 @@ func entersyscallblock_handoff() {
|
|||||||
// Arrange for it to run on a cpu again.
|
// Arrange for it to run on a cpu again.
|
||||||
// This is called only from the go syscall library, not
|
// This is called only from the go syscall library, not
|
||||||
// from the low-level system calls used by the runtime.
|
// from the low-level system calls used by the runtime.
|
||||||
|
//
|
||||||
|
// Write barriers are not allowed because our P may have been stolen.
|
||||||
|
//
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
|
//go:nowritebarrierrec
|
||||||
func exitsyscall(dummy int32) {
|
func exitsyscall(dummy int32) {
|
||||||
_g_ := getg()
|
_g_ := getg()
|
||||||
|
|
||||||
@ -2552,22 +2558,7 @@ func exitsyscallfast() bool {
|
|||||||
// Try to re-acquire the last P.
|
// Try to re-acquire the last P.
|
||||||
if _g_.m.p != 0 && _g_.m.p.ptr().status == _Psyscall && atomic.Cas(&_g_.m.p.ptr().status, _Psyscall, _Prunning) {
|
if _g_.m.p != 0 && _g_.m.p.ptr().status == _Psyscall && atomic.Cas(&_g_.m.p.ptr().status, _Psyscall, _Prunning) {
|
||||||
// There's a cpu for us, so we can run.
|
// There's a cpu for us, so we can run.
|
||||||
_g_.m.mcache = _g_.m.p.ptr().mcache
|
exitsyscallfast_reacquired()
|
||||||
_g_.m.p.ptr().m.set(_g_.m)
|
|
||||||
if _g_.m.syscalltick != _g_.m.p.ptr().syscalltick {
|
|
||||||
if trace.enabled {
|
|
||||||
// The p was retaken and then enter into syscall again (since _g_.m.syscalltick has changed).
|
|
||||||
// traceGoSysBlock for this syscall was already emitted,
|
|
||||||
// but here we effectively retake the p from the new syscall running on the same p.
|
|
||||||
systemstack(func() {
|
|
||||||
// Denote blocking of the new syscall.
|
|
||||||
traceGoSysBlock(_g_.m.p.ptr())
|
|
||||||
// Denote completion of the current syscall.
|
|
||||||
traceGoSysExit(0)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_g_.m.p.ptr().syscalltick++
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2597,6 +2588,35 @@ func exitsyscallfast() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// exitsyscallfast_reacquired is the exitsyscall path on which this G
|
||||||
|
// has successfully reacquired the P it was running on before the
|
||||||
|
// syscall.
|
||||||
|
//
|
||||||
|
// This function is allowed to have write barriers because exitsyscall
|
||||||
|
// has acquired a P at this point.
|
||||||
|
//
|
||||||
|
//go:yeswritebarrierrec
|
||||||
|
//go:nosplit
|
||||||
|
func exitsyscallfast_reacquired() {
|
||||||
|
_g_ := getg()
|
||||||
|
_g_.m.mcache = _g_.m.p.ptr().mcache
|
||||||
|
_g_.m.p.ptr().m.set(_g_.m)
|
||||||
|
if _g_.m.syscalltick != _g_.m.p.ptr().syscalltick {
|
||||||
|
if trace.enabled {
|
||||||
|
// The p was retaken and then enter into syscall again (since _g_.m.syscalltick has changed).
|
||||||
|
// traceGoSysBlock for this syscall was already emitted,
|
||||||
|
// but here we effectively retake the p from the new syscall running on the same p.
|
||||||
|
systemstack(func() {
|
||||||
|
// Denote blocking of the new syscall.
|
||||||
|
traceGoSysBlock(_g_.m.p.ptr())
|
||||||
|
// Denote completion of the current syscall.
|
||||||
|
traceGoSysExit(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_g_.m.p.ptr().syscalltick++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func exitsyscallfast_pidle() bool {
|
func exitsyscallfast_pidle() bool {
|
||||||
lock(&sched.lock)
|
lock(&sched.lock)
|
||||||
_p_ := pidleget()
|
_p_ := pidleget()
|
||||||
|
78
test/nowritebarrier.go
Normal file
78
test/nowritebarrier.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// errorcheck -+
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Test go:nowritebarrier and related directives.
|
||||||
|
|
||||||
|
package p
|
||||||
|
|
||||||
|
type t struct {
|
||||||
|
f *t
|
||||||
|
}
|
||||||
|
|
||||||
|
var x t
|
||||||
|
var y *t
|
||||||
|
|
||||||
|
//go:nowritebarrier
|
||||||
|
func a1() {
|
||||||
|
x.f = y // ERROR "write barrier prohibited"
|
||||||
|
a2() // no error
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:noinline
|
||||||
|
func a2() {
|
||||||
|
x.f = y
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:nowritebarrierrec
|
||||||
|
func b1() {
|
||||||
|
b2()
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:noinline
|
||||||
|
func b2() {
|
||||||
|
x.f = y // ERROR "write barrier prohibited by caller"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test recursive cycles through nowritebarrierrec and yeswritebarrierrec.
|
||||||
|
|
||||||
|
//go:nowritebarrierrec
|
||||||
|
func c1() {
|
||||||
|
c2()
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:yeswritebarrierrec
|
||||||
|
func c2() {
|
||||||
|
c3()
|
||||||
|
}
|
||||||
|
|
||||||
|
func c3() {
|
||||||
|
x.f = y
|
||||||
|
c4()
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:nowritebarrierrec
|
||||||
|
func c4() {
|
||||||
|
c2()
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:nowritebarrierrec
|
||||||
|
func d1() {
|
||||||
|
d2()
|
||||||
|
}
|
||||||
|
|
||||||
|
func d2() {
|
||||||
|
d3()
|
||||||
|
}
|
||||||
|
|
||||||
|
func d3() {
|
||||||
|
x.f = y // ERROR "write barrier prohibited by caller"
|
||||||
|
d4()
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:yeswritebarrierrec
|
||||||
|
func d4() {
|
||||||
|
d2()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user