Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 95 additions & 63 deletions src/cmd/compile/internal/escape/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,50 +31,52 @@ func (e *escape) call(ks []hole, call ir.Node) {
call := call.(*ir.CallExpr)
typecheck.AssertFixedCall(call)

// Pick out the function callee, if statically known.
// Pick out the function callee(s), if statically known.
// fns collects all known callees; for a single static callee
// it has one element. For unknown callees fns is nil.
//
// TODO(mdempsky): Change fn from *ir.Name to *ir.Func, but some
// functions (e.g., runtime builtins, method wrappers, generated
// eq/hash functions) don't have it set. Investigate whether
// that's a concern.
var fn *ir.Name
// TODO(mdempsky): Change fns from []*ir.Name to []*ir.Func,
// but some functions (e.g., runtime builtins, method wrappers,
// generated eq/hash functions) don't have it set. Investigate
// whether that's a concern.
var fns []*ir.Name
switch call.Op() {
case ir.OCALLFUNC:
// TODO(thepudds): use an ir.ReassignOracle here.
v := ir.StaticValue(call.Fun)
fn = ir.StaticCalleeName(v)
}

// argumentParam handles escape analysis of assigning a call
// argument to its corresponding parameter.
argumentParam := func(param *types.Field, arg ir.Node) {
e.rewriteArgument(arg, call, fn)
argument(e.tagHole(ks, fn, param), arg)
}

if call.IsCompilerVarLive {
// Don't escape compiler-inserted KeepAlive.
argumentParam = func(param *types.Field, arg ir.Node) {
argument(e.discardHole(), arg)
if fn := ir.StaticCalleeName(v); fn != nil {
fns = []*ir.Name{fn}
} else if name, ok := v.(*ir.Name); ok {
orig := name.Canonical()
if as := ir.FuncSingleAssignment(orig); as != nil {
if callee := ir.StaticCalleeName(as.Y); callee != nil {
fns = []*ir.Name{callee}
}
}
}
}

fntype := call.Fun.Type()
if fn != nil {
fntype = fn.Type()
if len(fns) == 1 {
fntype = fns[0].Type()
}

if ks != nil && fn != nil && e.inMutualBatch(fn) {
for i, result := range fn.Type().Results() {
e.expr(ks[i], result.Nname.(*ir.Name))
// Wire result flows for in-batch callees.
if ks != nil {
for _, f := range fns {
if e.inMutualBatch(f) {
for i, result := range f.Type().Results() {
e.expr(ks[i], result.Nname.(*ir.Name))
}
}
}
}

var recvArg ir.Node
if call.Op() == ir.OCALLFUNC {
// Evaluate callee function expression.
calleeK := e.discardHole()
if fn == nil { // unknown callee
if len(fns) == 0 { // unknown callee
for _, k := range ks {
if k.dst != &e.blankLoc {
// The results flow somewhere, but we don't statically
Expand All @@ -91,46 +93,48 @@ func (e *escape) call(ks []hole, call ir.Node) {
recvArg = call.Fun.(*ir.SelectorExpr).X
}

// internal/abi.EscapeNonString forces its argument to be on
// the heap, if it contains a non-string pointer.
// This is used in hash/maphash.Comparable, where we cannot
// hash pointers to local variables, as the address of the
// local variable might change on stack growth.
// Strings are okay as the hash depends on only the content,
// not the pointer.
// This is also used in unique.clone, to model the data flow
// edge on the value with strings excluded, because strings
// are cloned (by content).
// The actual call we match is
// internal/abi.EscapeNonString[go.shape.T](dict, go.shape.T)
if fn != nil && fn.Sym().Pkg.Path == "internal/abi" && strings.HasPrefix(fn.Sym().Name, "EscapeNonString[") {
ps := fntype.Params()
if len(ps) == 2 && ps[1].Type.IsShape() {
if !hasNonStringPointers(ps[1].Type) {
argumentParam = func(param *types.Field, arg ir.Node) {
argument(e.discardHole(), arg)
}
} else {
argumentParam = func(param *types.Field, arg ir.Node) {
argument(e.heapHole(), arg)
}
}
}
}

args := call.Args
if recvParam := fntype.Recv(); recvParam != nil {
if fntype.Recv() != nil {
if recvArg == nil {
// Function call using method expression. Receiver argument is
// at the front of the regular arguments list.
recvArg, args = args[0], args[1:]
}

argumentParam(recvParam, recvArg)
}

for i, param := range fntype.Params() {
argumentParam(param, args[i])
if call.IsCompilerVarLive {
// Don't escape compiler-inserted KeepAlive.
if recvArg != nil {
argument(e.discardHole(), recvArg)
}
for _, arg := range args {
argument(e.discardHole(), arg)
}
} else if isEscapeNonString(fns, fntype) {
// internal/abi.EscapeNonString forces its argument to
// the heap if it contains a non-string pointer. This is
// used in hash/maphash.Comparable (where we cannot hash
// pointers to locals whose address may change on stack
// growth) and unique.clone (to model the data flow edge
// with strings excluded, because strings are cloned by
// content). The actual call we match is:
// internal/abi.EscapeNonString[go.shape.T](dict, go.shape.T)
k := e.heapHole()
if !hasNonStringPointers(fntype.Params()[1].Type) {
k = e.discardHole()
}
for _, arg := range args {
argument(k, arg)
}
} else {
if recvArg != nil {
e.rewriteArgument(recvArg, call, fns)
argument(e.mergedTagHole(ks, fns, -1, len(fntype.Params())), recvArg)
}
for i := range fntype.Params() {
e.rewriteArgument(args[i], call, fns)
argument(e.mergedTagHole(ks, fns, i, len(fntype.Params())), args[i])
}
}

case ir.OINLCALL:
Expand Down Expand Up @@ -269,12 +273,14 @@ func (e *escape) goDeferStmt(n *ir.GoDeferStmt) {
}

// rewriteArgument rewrites the argument arg of the given call expression.
// fn is the static callee function, if known.
func (e *escape) rewriteArgument(arg ir.Node, call *ir.CallExpr, fn *ir.Name) {
if fn == nil || fn.Func == nil {
return
// fns is the list of statically known callees, if any.
func (e *escape) rewriteArgument(arg ir.Node, call *ir.CallExpr, fns []*ir.Name) {
var pragma ir.PragmaFlag
for _, fn := range fns {
if fn.Func != nil {
pragma |= fn.Func.Pragma
}
}
pragma := fn.Func.Pragma
if pragma&(ir.UintptrKeepAlive|ir.UintptrEscapes) == 0 {
return
}
Expand Down Expand Up @@ -361,6 +367,25 @@ func (e *escape) copyExpr(pos src.XPos, expr ir.Node, init *ir.Nodes) *ir.Name {
return tmp
}

func (e *escape) mergedTagHole(ks []hole, fns []*ir.Name, paramIdx int, nParams int) hole {
if len(fns) == 0 {
return e.heapHole()
}
holes := make([]hole, 0, len(fns))
for _, f := range fns {
offset := nParams - len(f.Type().Params())
j := paramIdx - offset
var p *types.Field
if j >= 0 {
p = f.Type().Params()[j]
} else {
p = f.Type().Recv()
}
holes = append(holes, e.tagHole(ks, f, p))
}
return e.teeHole(holes...)
}

// tagHole returns a hole for evaluating an argument passed to param.
// ks should contain the holes representing where the function
// callee's results flows. fn is the statically-known callee function,
Expand Down Expand Up @@ -404,6 +429,13 @@ func (e *escape) tagHole(ks []hole, fn *ir.Name, param *types.Field) hole {
return e.teeHole(tagKs...)
}

func isEscapeNonString(fns []*ir.Name, fntype *types.Type) bool {
return len(fns) == 1 &&
fns[0].Sym().Pkg.Path == "internal/abi" &&
strings.HasPrefix(fns[0].Sym().Name, "EscapeNonString[") &&
len(fntype.Params()) == 2 && fntype.Params()[1].Type.IsShape()
}

func hasNonStringPointers(t *types.Type) bool {
if !t.HasPointers() {
return false
Expand Down
99 changes: 99 additions & 0 deletions src/cmd/compile/internal/ir/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,105 @@ func Reassigned(name *Name) bool {
return Any(name.Curfn, do)
}

// FuncSingleAssignment returns the sole OAS *AssignStmt that assigns a
// non-zero value to name, if name is a func-typed local variable (PAUTO)
// with exactly one such assignment. Zero-value assignments (nil, bare
// declarations) are ignored since nil panics on call. Returns nil if the
// variable is not PAUTO, not func-typed, address-taken, has multiple
// non-zero assignments, or has any complex assignments (OAS2, ORANGE).
// Assignments inside nested closures are accepted because this is only
// used for escape analysis callee resolution: the only alternative value
// is nil, which panics on call.
//
// TODO: fold this into [ReassignOracle] so it can share the single
// walk with StaticValue and Reassigned.
func FuncSingleAssignment(name *Name) *AssignStmt {
if name.Class != PAUTO {
return nil
}
base.AssertfAt(name.Curfn != nil, name.Pos(), "PAUTO %v has nil Curfn", name)
if name.Addrtaken() {
return nil
}
if name.Type().Kind() != types.TFUNC {
return nil
}
// Reject variables with non-zero defining assignments we can't
// analyze (e.g., type switch case variables whose Defn is a
// TypeSwitchGuard, not an AssignStmt).
if name.Defn != nil {
as, ok := name.Defn.(*AssignStmt)
if !ok || !isNilAssign(as) {
return nil
}
}

isName := func(x Node) bool {
if x == nil {
return false
}
n, ok := OuterValue(x).(*Name)
return ok && n.Canonical() == name
}

var found *AssignStmt

var do func(n Node) bool
do = func(n Node) bool {
switch n.Op() {
case OAS:
as := n.(*AssignStmt)
if isName(as.X) {
if isNilAssign(as) {
break
}
if found != nil {
found = nil
return true
}
found = as
}
case OAS2, OAS2FUNC, OAS2MAPR, OAS2DOTTYPE, OAS2RECV, OSELRECV2:
as := n.(*AssignListStmt)
for _, p := range as.Lhs {
if isName(p) {
found = nil
return true
}
}
case ORANGE:
rs := n.(*RangeStmt)
if isName(rs.Key) || isName(rs.Value) {
found = nil
return true
}
case OCLOSURE:
n := n.(*ClosureExpr)
if Any(n.Func, do) {
return true
}
}
return false
}

if Any(name.Curfn, do) {
return nil
}
return found
}

// isNilAssign reports whether as has a nil or absent RHS.
func isNilAssign(as *AssignStmt) bool {
if as.Y == nil {
return true
}
y := as.Y
for y.Op() == OCONVNOP {
y = y.(*ConvExpr).X
}
return IsNil(y)
}

// StaticCalleeName returns the ONAME/PFUNC for n, if known.
func StaticCalleeName(n Node) *Name {
switch n.Op() {
Expand Down
36 changes: 30 additions & 6 deletions src/cmd/compile/internal/riscv64/ssa.go
Original file line number Diff line number Diff line change
Expand Up @@ -610,13 +610,23 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
p.To.Sym = ir.Syms.PanicBounds

case ssa.OpRISCV64LoweredAtomicLoad8:
s.Prog(riscv.AFENCE)
p1 := s.Prog(riscv.AFENCE)
p1.From.Type = obj.TYPE_SPECIAL
p1.From.Offset = int64(riscv.SPOP_FENCE_RW)
p1.To.Type = obj.TYPE_SPECIAL
p1.To.Offset = int64(riscv.SPOP_FENCE_RW)

p := s.Prog(riscv.AMOVBU)
p.From.Type = obj.TYPE_MEM
p.From.Reg = v.Args[0].Reg()
p.To.Type = obj.TYPE_REG
p.To.Reg = v.Reg0()
s.Prog(riscv.AFENCE)

p2 := s.Prog(riscv.AFENCE)
p2.From.Type = obj.TYPE_SPECIAL
p2.From.Offset = int64(riscv.SPOP_FENCE_R)
p2.To.Type = obj.TYPE_SPECIAL
p2.To.Offset = int64(riscv.SPOP_FENCE_RW)

case ssa.OpRISCV64LoweredAtomicLoad32, ssa.OpRISCV64LoweredAtomicLoad64:
as := riscv.ALRW
Expand All @@ -630,13 +640,23 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
p.To.Reg = v.Reg0()

case ssa.OpRISCV64LoweredAtomicStore8:
s.Prog(riscv.AFENCE)
p1 := s.Prog(riscv.AFENCE)
p1.From.Type = obj.TYPE_SPECIAL
p1.From.Offset = int64(riscv.SPOP_FENCE_RW)
p1.To.Type = obj.TYPE_SPECIAL
p1.To.Offset = int64(riscv.SPOP_FENCE_W)

p := s.Prog(riscv.AMOVB)
p.From.Type = obj.TYPE_REG
p.From.Reg = v.Args[1].Reg()
p.To.Type = obj.TYPE_MEM
p.To.Reg = v.Args[0].Reg()
s.Prog(riscv.AFENCE)

p2 := s.Prog(riscv.AFENCE)
p2.From.Type = obj.TYPE_SPECIAL
p2.From.Offset = int64(riscv.SPOP_FENCE_RW)
p2.To.Type = obj.TYPE_SPECIAL
p2.To.Offset = int64(riscv.SPOP_FENCE_RW)

case ssa.OpRISCV64LoweredAtomicStore32, ssa.OpRISCV64LoweredAtomicStore64:
as := riscv.AAMOSWAPW
Expand Down Expand Up @@ -961,8 +981,12 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
p.To.Reg = v.Reg()

case ssa.OpRISCV64LoweredPubBarrier:
// FENCE
s.Prog(v.Op.Asm())
// FENCE W, W
p := s.Prog(v.Op.Asm())
p.From.Type = obj.TYPE_SPECIAL
p.From.Offset = int64(riscv.SPOP_FENCE_W)
p.To.Type = obj.TYPE_SPECIAL
p.To.Offset = int64(riscv.SPOP_FENCE_W)

case ssa.OpRISCV64LoweredRound32F, ssa.OpRISCV64LoweredRound64F:
// input is already rounded
Expand Down
Loading
Loading