Commit 3a765430 authored by Austin Clements's avatar Austin Clements

cmd/compile: add go:nowritebarrierrec annotation

This introduces a recursive variant of the go:nowritebarrier
annotation that prohibits write barriers not only in the annotated
function, but in all functions it calls, recursively. The error
message gives the shortest call stack from the annotated function to
the function containing the prohibited write barrier, including the
names of the functions and the line numbers of the calls.

To demonstrate the annotation, we apply it to gcmarkwb_m, the write
barrier itself.

This is a new annotation rather than a modification of the existing
go:nowritebarrier annotation because, for better or worse, there are
many go:nowritebarrier functions that do call functions with write
barriers. In most of these cases this is benign because the annotation
was conservative, but it prohibits simply coopting the existing
annotation.

Change-Id: I225ca483c8f699e8436373ed96349e80ca2c2479
Reviewed-on: https://go-review.googlesource.com/16554Reviewed-by: 's avatarKeith Randall <khr@golang.org>
parent 2780abd6
......@@ -779,8 +779,13 @@ abop: // asymmetric binary
var sys_wbptr *Node
func cgen_wbptr(n, res *Node) {
if Curfn != nil && Curfn.Func.Nowritebarrier {
Yyerror("write barrier prohibited")
if Curfn != nil {
if Curfn.Func.Nowritebarrier {
Yyerror("write barrier prohibited")
}
if Curfn.Func.WBLineno == 0 {
Curfn.Func.WBLineno = lineno
}
}
if Debug_wb > 0 {
Warn("write barrier")
......@@ -822,8 +827,13 @@ func cgen_wbptr(n, res *Node) {
}
func cgen_wbfat(n, res *Node) {
if Curfn != nil && Curfn.Func.Nowritebarrier {
Yyerror("write barrier prohibited")
if Curfn != nil {
if Curfn.Func.Nowritebarrier {
Yyerror("write barrier prohibited")
}
if Curfn.Func.WBLineno == 0 {
Curfn.Func.WBLineno = lineno
}
}
if Debug_wb > 0 {
Warn("write barrier")
......
......@@ -1475,3 +1475,116 @@ func makefuncsym(s *Sym) {
s1.Def.Func.Shortname = newname(s)
funcsyms = append(funcsyms, s1.Def)
}
type nowritebarrierrecChecker struct {
curfn *Node
stable bool
// best maps from the ODCLFUNC of each visited function that
// recursively invokes a write barrier to the called function
// on the shortest path to a write barrier.
best map[*Node]nowritebarrierrecCall
}
type nowritebarrierrecCall struct {
target *Node
depth int
lineno int32
}
func checknowritebarrierrec() {
c := nowritebarrierrecChecker{
best: make(map[*Node]nowritebarrierrecCall),
}
visitBottomUp(xtop, func(list []*Node, recursive bool) {
// Functions with write barriers have depth 0.
for _, n := range list {
if n.Func.WBLineno != 0 {
c.best[n] = nowritebarrierrecCall{target: nil, depth: 0, lineno: n.Func.WBLineno}
}
}
// Propagate write barrier depth up from callees. In
// the recursive case, we have to update this at most
// len(list) times and can stop when we an iteration
// that doesn't change anything.
for _ = range list {
c.stable = false
for _, n := range list {
if n.Func.WBLineno == 0 {
c.curfn = n
c.visitcodelist(n.Nbody)
}
}
if c.stable {
break
}
}
// Check nowritebarrierrec functions.
for _, n := range list {
if !n.Func.Nowritebarrierrec {
continue
}
call, hasWB := c.best[n]
if !hasWB {
continue
}
// Build the error message in reverse.
err := ""
for call.target != nil {
err = fmt.Sprintf("\n\t%v: called by %v%s", Ctxt.Line(int(call.lineno)), n.Func.Nname, err)
n = call.target
call = c.best[n]
}
err = fmt.Sprintf("write barrier prohibited by caller; %v%s", n.Func.Nname, err)
yyerrorl(int(n.Func.WBLineno), err)
}
})
}
func (c *nowritebarrierrecChecker) visitcodelist(l *NodeList) {
for ; l != nil; l = l.Next {
c.visitcode(l.N)
}
}
func (c *nowritebarrierrecChecker) visitcode(n *Node) {
if n == nil {
return
}
if n.Op == OCALLFUNC || n.Op == OCALLMETH {
c.visitcall(n)
}
c.visitcodelist(n.Ninit)
c.visitcode(n.Left)
c.visitcode(n.Right)
c.visitcodelist(n.List)
c.visitcodelist(n.Nbody)
c.visitcodelist(n.Rlist)
}
func (c *nowritebarrierrecChecker) visitcall(n *Node) {
fn := n.Left
if n.Op == OCALLMETH {
fn = n.Left.Right.Sym.Def
}
if fn == nil || fn.Op != ONAME || fn.Class != PFUNC || fn.Name.Defn == nil {
return
}
defn := fn.Name.Defn
fnbest, ok := c.best[defn]
if !ok {
return
}
best, ok := c.best[c.curfn]
if ok && fnbest.depth+1 >= best.depth {
return
}
c.best[c.curfn] = nowritebarrierrecCall{target: defn, depth: fnbest.depth + 1, lineno: n.Lineno}
c.stable = false
}
......@@ -658,12 +658,13 @@ var instrumenting bool
// Pending annotations for next func declaration.
var (
noescape bool
noinline bool
norace bool
nosplit bool
nowritebarrier bool
systemstack bool
noescape bool
noinline bool
norace bool
nosplit bool
nowritebarrier bool
nowritebarrierrec bool
systemstack bool
)
var debuglive int
......
......@@ -479,6 +479,10 @@ func Main() {
fninit(xtop)
}
if compiling_runtime != 0 {
checknowritebarrierrec()
}
// Phase 9: Check external declarations.
for i, n := range externdcl {
if n.Op == ONAME {
......@@ -1682,6 +1686,15 @@ func getlinepragma() int {
nowritebarrier = true
return c
}
if verb == "go:nowritebarrierrec" {
if compiling_runtime == 0 {
Yyerror("//go:nowritebarrierrec only allowed in runtime")
}
nowritebarrierrec = true
nowritebarrier = true // Implies nowritebarrier
return c
}
return c
}
if c != 'l' {
......
......@@ -169,14 +169,17 @@ type Func struct {
Endlineno int32
Norace bool // func must not have race detector annotations
Nosplit bool // func should not execute on separate stack
Noinline bool // func should not be inlined
Nowritebarrier bool // emit compiler error instead of write barrier
Dupok bool // duplicate definitions ok
Wrapper bool // is method wrapper
Needctxt bool // function uses context register (has closure variables)
Systemstack bool // must run on system stack
Norace bool // func must not have race detector annotations
Nosplit bool // func should not execute on separate stack
Noinline bool // func should not be inlined
Nowritebarrier bool // emit compiler error instead of write barrier
Nowritebarrierrec bool // error on write barrier in this or recursive callees
Dupok bool // duplicate definitions ok
Wrapper bool // is method wrapper
Needctxt bool // function uses context register (has closure variables)
Systemstack bool // must run on system stack
WBLineno int32 // line number of first write barrier
}
type Op uint8
......
......@@ -2544,6 +2544,7 @@ yydefault:
yyVAL.node.Func.Nosplit = nosplit
yyVAL.node.Func.Noinline = noinline
yyVAL.node.Func.Nowritebarrier = nowritebarrier
yyVAL.node.Func.Nowritebarrierrec = nowritebarrierrec
yyVAL.node.Func.Systemstack = systemstack
funcbody(yyVAL.node)
}
......@@ -2745,6 +2746,7 @@ yydefault:
norace = false
nosplit = false
nowritebarrier = false
nowritebarrierrec = false
systemstack = false
}
case 221:
......
......@@ -84,7 +84,7 @@ import "unsafe"
// frames that have potentially been active since the concurrent scan,
// so it depends on write barriers to track changes to pointers in
// stack frames that have not been active.
//go:nowritebarrier
//go:nowritebarrierrec
func gcmarkwb_m(slot *uintptr, ptr uintptr) {
if writeBarrierEnabled {
if ptr != 0 && inheap(ptr) {
......
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