]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: add go:nowritebarrierrec annotation
authorAustin Clements <austin@google.com>
Mon, 2 Nov 2015 21:45:07 +0000 (16:45 -0500)
committerAustin Clements <austin@google.com>
Wed, 4 Nov 2015 14:42:04 +0000 (14:42 +0000)
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/16554
Reviewed-by: Keith Randall <khr@golang.org>
src/cmd/compile/internal/gc/cgen.go
src/cmd/compile/internal/gc/dcl.go
src/cmd/compile/internal/gc/go.go
src/cmd/compile/internal/gc/lex.go
src/cmd/compile/internal/gc/syntax.go
src/cmd/compile/internal/gc/y.go
src/runtime/mbarrier.go

index 8cbdd18c2939ed768dcc36085821b4b4584182b3..cbb84f9da8ac183e342baac21c3e18bd9dffaed2 100644 (file)
@@ -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")
index a3179c9d181f4803b5cc82e3bda78df04b7dc780..c0326c547b4853101d32a4f40b35206fabb518bf 100644 (file)
@@ -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
+}
index 4ccf3607b8f9370c0bb4becebaa715a7d2b5cd55..64c3e4772fdebabe9d812d27147874483611bd61 100644 (file)
@@ -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
index c7a16e89cdec9361b2a65915927ab4bfd334f606..9fe0f6cbc1024c2e7e3efc5b7bcebaee4f00f08c 100644 (file)
@@ -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' {
index e48f69229c71b4ff1e4cd882e34422d861a460e9..993e2ae04868c769577d747e77e65dcae7a7ef05 100644 (file)
@@ -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
index 2bc3e408a10c6e2508364034a2ba77a4142b7c2c..7c3ce88756980966a7574ab1f045284bb1461a2a 100644 (file)
@@ -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:
index 5aa1d20e7d30af030e323a46477d15b83ad29f97..f6e6c30648876afcb812eb960a0d812051948a12 100644 (file)
@@ -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) {