golang nowb 代码
文件路径:/src/cmd/compile/internal/ssagen/nowb.go
/ Copyright 2009 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.
package ssagen
import (
"bytes"
"fmt"
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/src"
)
func EnableNoWriteBarrierRecCheck() {
nowritebarrierrecCheck = newNowritebarrierrecChecker()
}
func NoWriteBarrierRecCheck() {
/ Write barriers are now known. Check the
/ call graph.
nowritebarrierrecCheck.check()
nowritebarrierrecCheck = nil
}
var nowritebarrierrecCheck *nowritebarrierrecChecker
type nowritebarrierrecChecker struct {
/ extraCalls contains extra function calls that may not be
/ visible during later analysis. It maps from the ODCLFUNC of
/ the caller to a list of callees.
extraCalls map[*ir.Func][]nowritebarrierrecCall
/ curfn is the current function during AST walks.
curfn *ir.Func
}
type nowritebarrierrecCall struct {
target *ir.Func / caller or callee
lineno src.XPos / line of call
}
/ newNowritebarrierrecChecker creates a nowritebarrierrecChecker. It
/ must be called before walk
func newNowritebarrierrecChecker() *nowritebarrierrecChecker {
c := &nowritebarrierrecChecker{
extraCalls: make(map[*ir.Func][]nowritebarrierrecCall),
}
/ Find all systemstack calls and record their targets. In
/ general, flow analysis can't see into systemstack, but it's
/ important to handle it for this check, so we model it
/ directly. This has to happen before transforming closures in walk since
/ it's a lot harder to work out the argument after.
for _, n := range typecheck.Target.Decls {
if n.Op() != ir.ODCLFUNC {
continue
}
c.curfn = n.(*ir.Func)
if c.curfn.ABIWrapper() {
/ We only want "real" calls to these
/ functions, not the generated ones within
/ their own ABI wrappers.
continue
}
ir.Visit(n, c.findExtraCalls)
}
c.curfn = nil
return c
}
func (c *nowritebarrierrecChecker) findExtraCalls(nn ir.Node) {
if nn.Op() != ir.OCALLFUNC {
return
}
n := nn.(*ir.CallExpr)
if n.X == nil || n.X.Op() != ir.ONAME {
return
}
fn := n.X.(*ir.Name)
if fn.Class != ir.PFUNC || fn.Defn == nil {
return
}
if !types.IsRuntimePkg(fn.Sym().Pkg) || fn.Sym().Name != "systemstack" {
return
}
var callee *ir.Func
arg := n.Args[0]
switch arg.Op() {
case ir.ONAME:
arg := arg.(*ir.Name)
callee = arg.Defn.(*ir.Func)
case ir.OCLOSURE:
arg := arg.(*ir.ClosureExpr)
callee = arg.Func
default:
base.Fatalf("expected ONAME or OCLOSURE node, got %+v", arg)
}
if callee.Op() != ir.ODCLFUNC {
base.Fatalf("expected ODCLFUNC node, got %+v", callee)
}
c.extraCalls[c.curfn] = append(c.extraCalls[c.curfn], nowritebarrierrecCall{callee, n.Pos()})
}
/ recordCall records a call from ODCLFUNC node "from", to function
/ symbol "to" at position pos.
/
/ This should be done as late as possible during compilation to
/ capture precise call graphs. The target of the call is an LSym
/ because that's all we know after we start SSA.
/
/ This can be called concurrently for different from Nodes.
func (c *nowritebarrierrecChecker) recordCall(fn *ir.Func, to *obj.LSym, pos src.XPos) {
/ We record this information on the *Func so this is concurrent-safe.
if fn.NWBRCalls == nil {
fn.NWBRCalls = new([]ir.SymAndPos)
}
*fn.NWBRCalls = append(*fn.NWBRCalls, ir.SymAndPos{Sym: to, Pos: pos})
}
func (c *nowritebarrierrecChecker) check() {
/ We walk the call graph as late as possible so we can
/ capture all calls created by lowering, but this means we
/ only get to see the obj.LSyms of calls. symToFunc lets us
/ get back to the ODCLFUNCs.
symToFunc := make(map[*obj.LSym]*ir.Func)
/ funcs records the back-edges of the BFS call graph walk. It
/ maps from the ODCLFUNC of each function that must not have
/ write barriers to the call that inhibits them. Functions
/ that are directly marked go:nowritebarrierrec are in this
/ map with a zero-valued nowritebarrierrecCall. This also
/ acts as the set of marks for the BFS of the call graph.
funcs := make(map[*ir.Func]nowritebarrierrecCall)
/ q is the queue of ODCLFUNC Nodes to visit in BFS order.
var q ir.NameQueue
for _, n := range typecheck.Target.Decls {
if n.Op() != ir.ODCLFUNC {
continue
}
fn := n.(*ir.Func)
symToFunc[fn.LSym] = fn
/ Make nowritebarrierrec functions BFS roots.
if fn.Pragma&ir.Nowritebarrierrec != 0 {
funcs[fn] = nowritebarrierrecCall{}
q.PushRight(fn.Nname)
}
/ Check go:nowritebarrier functions.
if fn.Pragma&ir.Nowritebarrier != 0 && fn.WBPos.IsKnown() {
base.ErrorfAt(fn.WBPos, "write barrier prohibited")
}
}
/ Perform a BFS of the call graph from all
/ go:nowritebarrierrec functions.
enqueue := func(src, target *ir.Func, pos src.XPos) {
if target.Pragma&ir.Yeswritebarrierrec != 0 {
/ Don't flow into this function.
return
}
if _, ok := funcs[target]; ok {
/ Already found a path to target.
return
}
/ Record the path.
funcs[target] = nowritebarrierrecCall{target: src, lineno: pos}
q.PushRight(target.Nname)
}
for !q.Empty() {
fn := q.PopLeft().Func
/ Check fn.
if fn.WBPos.IsKnown() {
var err bytes.Buffer
call := funcs[fn]
for call.target != nil {
fmt.Fprintf(&err, "\n\t%v: called by %v", base.FmtPos(call.lineno), call.target.Nname)
call = funcs[call.target]
}
base.ErrorfAt(fn.WBPos, "write barrier prohibited by caller; %v%s", fn.Nname, err.String())
continue
}
/ Enqueue fn's calls.
for _, callee := range c.extraCalls[fn] {
enqueue(fn, callee.target, callee.lineno)
}
if fn.NWBRCalls == nil {
continue
}
for _, callee := range *fn.NWBRCalls {
target := symToFunc[callee.Sym]
if target != nil {
enqueue(fn, target, callee.Pos)
}
}
}
}