1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
11 "cmd/compile/internal/base"
12 "cmd/compile/internal/ir"
13 "cmd/compile/internal/typecheck"
14 "cmd/compile/internal/types"
19 func EnableNoWriteBarrierRecCheck() {
20 nowritebarrierrecCheck = newNowritebarrierrecChecker()
23 func NoWriteBarrierRecCheck() {
24 // Write barriers are now known. Check the
26 nowritebarrierrecCheck.check()
27 nowritebarrierrecCheck = nil
30 var nowritebarrierrecCheck *nowritebarrierrecChecker
32 type nowritebarrierrecChecker struct {
33 // extraCalls contains extra function calls that may not be
34 // visible during later analysis. It maps from the ODCLFUNC of
35 // the caller to a list of callees.
36 extraCalls map[*ir.Func][]nowritebarrierrecCall
38 // curfn is the current function during AST walks.
42 type nowritebarrierrecCall struct {
43 target *ir.Func // caller or callee
44 lineno src.XPos // line of call
47 // newNowritebarrierrecChecker creates a nowritebarrierrecChecker. It
48 // must be called before walk.
49 func newNowritebarrierrecChecker() *nowritebarrierrecChecker {
50 c := &nowritebarrierrecChecker{
51 extraCalls: make(map[*ir.Func][]nowritebarrierrecCall),
54 // Find all systemstack calls and record their targets. In
55 // general, flow analysis can't see into systemstack, but it's
56 // important to handle it for this check, so we model it
57 // directly. This has to happen before transforming closures in walk since
58 // it's a lot harder to work out the argument after.
59 for _, n := range typecheck.Target.Funcs {
61 if c.curfn.ABIWrapper() {
62 // We only want "real" calls to these
63 // functions, not the generated ones within
64 // their own ABI wrappers.
67 ir.Visit(n, c.findExtraCalls)
73 func (c *nowritebarrierrecChecker) findExtraCalls(nn ir.Node) {
74 if nn.Op() != ir.OCALLFUNC {
77 n := nn.(*ir.CallExpr)
78 if n.X == nil || n.X.Op() != ir.ONAME {
82 if fn.Class != ir.PFUNC || fn.Defn == nil {
85 if types.RuntimeSymName(fn.Sym()) != "systemstack" {
94 callee = arg.Defn.(*ir.Func)
96 arg := arg.(*ir.ClosureExpr)
99 base.Fatalf("expected ONAME or OCLOSURE node, got %+v", arg)
101 c.extraCalls[c.curfn] = append(c.extraCalls[c.curfn], nowritebarrierrecCall{callee, n.Pos()})
104 // recordCall records a call from ODCLFUNC node "from", to function
105 // symbol "to" at position pos.
107 // This should be done as late as possible during compilation to
108 // capture precise call graphs. The target of the call is an LSym
109 // because that's all we know after we start SSA.
111 // This can be called concurrently for different from Nodes.
112 func (c *nowritebarrierrecChecker) recordCall(fn *ir.Func, to *obj.LSym, pos src.XPos) {
113 // We record this information on the *Func so this is concurrent-safe.
114 if fn.NWBRCalls == nil {
115 fn.NWBRCalls = new([]ir.SymAndPos)
117 *fn.NWBRCalls = append(*fn.NWBRCalls, ir.SymAndPos{Sym: to, Pos: pos})
120 func (c *nowritebarrierrecChecker) check() {
121 // We walk the call graph as late as possible so we can
122 // capture all calls created by lowering, but this means we
123 // only get to see the obj.LSyms of calls. symToFunc lets us
124 // get back to the ODCLFUNCs.
125 symToFunc := make(map[*obj.LSym]*ir.Func)
126 // funcs records the back-edges of the BFS call graph walk. It
127 // maps from the ODCLFUNC of each function that must not have
128 // write barriers to the call that inhibits them. Functions
129 // that are directly marked go:nowritebarrierrec are in this
130 // map with a zero-valued nowritebarrierrecCall. This also
131 // acts as the set of marks for the BFS of the call graph.
132 funcs := make(map[*ir.Func]nowritebarrierrecCall)
133 // q is the queue of ODCLFUNC Nodes to visit in BFS order.
136 for _, fn := range typecheck.Target.Funcs {
137 symToFunc[fn.LSym] = fn
139 // Make nowritebarrierrec functions BFS roots.
140 if fn.Pragma&ir.Nowritebarrierrec != 0 {
141 funcs[fn] = nowritebarrierrecCall{}
142 q.PushRight(fn.Nname)
144 // Check go:nowritebarrier functions.
145 if fn.Pragma&ir.Nowritebarrier != 0 && fn.WBPos.IsKnown() {
146 base.ErrorfAt(fn.WBPos, 0, "write barrier prohibited")
150 // Perform a BFS of the call graph from all
151 // go:nowritebarrierrec functions.
152 enqueue := func(src, target *ir.Func, pos src.XPos) {
153 if target.Pragma&ir.Yeswritebarrierrec != 0 {
154 // Don't flow into this function.
157 if _, ok := funcs[target]; ok {
158 // Already found a path to target.
163 funcs[target] = nowritebarrierrecCall{target: src, lineno: pos}
164 q.PushRight(target.Nname)
167 fn := q.PopLeft().Func
170 if fn.WBPos.IsKnown() {
171 var err strings.Builder
173 for call.target != nil {
174 fmt.Fprintf(&err, "\n\t%v: called by %v", base.FmtPos(call.lineno), call.target.Nname)
175 call = funcs[call.target]
177 base.ErrorfAt(fn.WBPos, 0, "write barrier prohibited by caller; %v%s", fn.Nname, err.String())
181 // Enqueue fn's calls.
182 for _, callee := range c.extraCalls[fn] {
183 enqueue(fn, callee.target, callee.lineno)
185 if fn.NWBRCalls == nil {
188 for _, callee := range *fn.NWBRCalls {
189 target := symToFunc[callee.Sym]
191 enqueue(fn, target, callee.Pos)