mirror of
https://github.com/golang/go
synced 2024-11-17 19:15:21 -07:00
cmd/compile/internal/ir: add batch-mode reassignment detection oracle
Add a new helper type 'ReassignOracle', useful for doing "batch mode" reassignment analysis, e.g. deciding whether a given ir.Name or (chain of connected names) has a single definition and is never reassigned. The intended usage model is for clients to create/initialize a ReassignOracle for a given function, then make a series of queries using it (with the understanding that changing/mutating the func body IR can invalidate the info cached in the oracle). This oracle is intended to provide the same sort of analysis that ir.StaticValue and ir.Reassigned carry out, but at a much reduced cost in compile time. Notes: - the new helper isn't actually used for anything useful in this patch; it will be hooked into the inline heuristics code as part of a subsequent CL. - this is probably not an ideal long-term solution; it would be better to switch to a scheme based a flag on ir.Name, as opposed to a side table. Change-Id: I283e748e440a9f595df495f6aa48ee9c498702d9 Reviewed-on: https://go-review.googlesource.com/c/go/+/539319 Reviewed-by: Matthew Dempsky <mdempsky@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
515f3c0da6
commit
8be8bfeaa2
9
src/cmd/compile/internal/ir/check_reassign_no.go
Normal file
9
src/cmd/compile/internal/ir/check_reassign_no.go
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
//go:build !checknewoldreassignment
|
||||
|
||||
package ir
|
||||
|
||||
const consistencyCheckEnabled = false
|
9
src/cmd/compile/internal/ir/check_reassign_yes.go
Normal file
9
src/cmd/compile/internal/ir/check_reassign_yes.go
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
//go:build checknewoldreassignment
|
||||
|
||||
package ir
|
||||
|
||||
const consistencyCheckEnabled = true
|
@ -923,6 +923,8 @@ FindRHS:
|
||||
// NB: global variables are always considered to be re-assigned.
|
||||
// TODO: handle initial declaration not including an assignment and
|
||||
// followed by a single assignment?
|
||||
// NOTE: any changes made here should also be made in the corresponding
|
||||
// code in the ReassignOracle.Init method.
|
||||
func Reassigned(name *Name) bool {
|
||||
if name.Op() != ONAME {
|
||||
base.Fatalf("reassigned %v", name)
|
||||
|
46
src/cmd/compile/internal/ir/reassign_consistency_check.go
Normal file
46
src/cmd/compile/internal/ir/reassign_consistency_check.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2023 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 ir
|
||||
|
||||
import (
|
||||
"cmd/compile/internal/base"
|
||||
"cmd/internal/src"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// checkStaticValueResult compares the result from ReassignOracle.StaticValue
|
||||
// with the corresponding result from ir.StaticValue to make sure they agree.
|
||||
// This method is called only when turned on via build tag.
|
||||
func checkStaticValueResult(n Node, newres Node) {
|
||||
oldres := StaticValue(n)
|
||||
if oldres != newres {
|
||||
base.Fatalf("%s: new/old static value disagreement on %v:\nnew=%v\nold=%v", fmtFullPos(n.Pos()), n, newres, oldres)
|
||||
}
|
||||
}
|
||||
|
||||
// checkStaticValueResult compares the result from ReassignOracle.Reassigned
|
||||
// with the corresponding result from ir.Reassigned to make sure they agree.
|
||||
// This method is called only when turned on via build tag.
|
||||
func checkReassignedResult(n *Name, newres bool) {
|
||||
origres := Reassigned(n)
|
||||
if newres != origres {
|
||||
base.Fatalf("%s: new/old reassigned disagreement on %v (class %s) newres=%v oldres=%v", fmtFullPos(n.Pos()), n, n.Class.String(), newres, origres)
|
||||
}
|
||||
}
|
||||
|
||||
// fmtFullPos returns a verbose dump for pos p, including inlines.
|
||||
func fmtFullPos(p src.XPos) string {
|
||||
var sb strings.Builder
|
||||
sep := ""
|
||||
base.Ctxt.AllPos(p, func(pos src.Pos) {
|
||||
fmt.Fprintf(&sb, sep)
|
||||
sep = "|"
|
||||
file := filepath.Base(pos.Filename())
|
||||
fmt.Fprintf(&sb, "%s:%d:%d", file, pos.Line(), pos.Col())
|
||||
})
|
||||
return sb.String()
|
||||
}
|
205
src/cmd/compile/internal/ir/reassignment.go
Normal file
205
src/cmd/compile/internal/ir/reassignment.go
Normal file
@ -0,0 +1,205 @@
|
||||
// Copyright 2023 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 ir
|
||||
|
||||
import (
|
||||
"cmd/compile/internal/base"
|
||||
)
|
||||
|
||||
// A ReassignOracle efficiently answers queries about whether local
|
||||
// variables are reassigned. This helper works by looking for function
|
||||
// params and short variable declarations (e.g.
|
||||
// https://go.dev/ref/spec#Short_variable_declarations) that are
|
||||
// neither address taken nor subsequently re-assigned. It is intended
|
||||
// to operate much like "ir.StaticValue" and "ir.Reassigned", but in a
|
||||
// way that does just a single walk of the containing function (as
|
||||
// opposed to a new walk on every call).
|
||||
type ReassignOracle struct {
|
||||
fn *Func
|
||||
// maps candidate name to its defining assignment (or for
|
||||
// for params, defining func).
|
||||
singleDef map[*Name]Node
|
||||
}
|
||||
|
||||
// Init initializes the oracle based on the IR in function fn, laying
|
||||
// the groundwork for future calls to the StaticValue and Reassigned
|
||||
// methods. If the fn's IR is subsequently modified, Init must be
|
||||
// called again.
|
||||
func (ro *ReassignOracle) Init(fn *Func) {
|
||||
ro.fn = fn
|
||||
|
||||
// Collect candidate map. Start by adding function parameters
|
||||
// explicitly.
|
||||
ro.singleDef = make(map[*Name]Node)
|
||||
sig := fn.Type()
|
||||
numParams := sig.NumRecvs() + sig.NumParams()
|
||||
for _, param := range fn.Dcl[:numParams] {
|
||||
if IsBlank(param) {
|
||||
continue
|
||||
}
|
||||
// For params, use func itself as defining node.
|
||||
ro.singleDef[param] = fn
|
||||
}
|
||||
|
||||
// Walk the function body to discover any locals assigned
|
||||
// via ":=" syntax (e.g. "a := <expr>").
|
||||
var findLocals func(n Node) bool
|
||||
findLocals = func(n Node) bool {
|
||||
if nn, ok := n.(*Name); ok {
|
||||
if nn.Defn != nil && !nn.Addrtaken() && nn.Class == PAUTO {
|
||||
ro.singleDef[nn] = nn.Defn
|
||||
}
|
||||
} else if nn, ok := n.(*ClosureExpr); ok {
|
||||
Any(nn.Func, findLocals)
|
||||
}
|
||||
return false
|
||||
}
|
||||
Any(fn, findLocals)
|
||||
|
||||
outerName := func(x Node) *Name {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
n, ok := OuterValue(x).(*Name)
|
||||
if ok {
|
||||
return n.Canonical()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pruneIfNeeded examines node nn appearing on the left hand side
|
||||
// of assignment statement asn to see if it contains a reassignment
|
||||
// to any nodes in our candidate map ro.singleDef; if a reassignment
|
||||
// is found, the corresponding name is deleted from singleDef.
|
||||
pruneIfNeeded := func(nn Node, asn Node) {
|
||||
oname := outerName(nn)
|
||||
if oname == nil {
|
||||
return
|
||||
}
|
||||
defn, ok := ro.singleDef[oname]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// any assignment to a param invalidates the entry.
|
||||
paramAssigned := oname.Class == PPARAM
|
||||
// assignment to local ok iff assignment is its orig def.
|
||||
localAssigned := (oname.Class == PAUTO && asn != defn)
|
||||
if paramAssigned || localAssigned {
|
||||
// We found an assignment to name N that doesn't
|
||||
// correspond to its original definition; remove
|
||||
// from candidates.
|
||||
delete(ro.singleDef, oname)
|
||||
}
|
||||
}
|
||||
|
||||
// Prune away anything that looks assigned. This code modeled after
|
||||
// similar code in ir.Reassigned; any changes there should be made
|
||||
// here as well.
|
||||
var do func(n Node) bool
|
||||
do = func(n Node) bool {
|
||||
switch n.Op() {
|
||||
case OAS:
|
||||
asn := n.(*AssignStmt)
|
||||
pruneIfNeeded(asn.X, n)
|
||||
case OAS2, OAS2FUNC, OAS2MAPR, OAS2DOTTYPE, OAS2RECV, OSELRECV2:
|
||||
asn := n.(*AssignListStmt)
|
||||
for _, p := range asn.Lhs {
|
||||
pruneIfNeeded(p, n)
|
||||
}
|
||||
case OASOP:
|
||||
asn := n.(*AssignOpStmt)
|
||||
pruneIfNeeded(asn.X, n)
|
||||
case ORANGE:
|
||||
rs := n.(*RangeStmt)
|
||||
pruneIfNeeded(rs.Key, n)
|
||||
pruneIfNeeded(rs.Value, n)
|
||||
case OCLOSURE:
|
||||
n := n.(*ClosureExpr)
|
||||
Any(n.Func, do)
|
||||
}
|
||||
return false
|
||||
}
|
||||
Any(fn, do)
|
||||
}
|
||||
|
||||
// StaticValue method has the same semantics as the ir package function
|
||||
// of the same name; see comments on [StaticValue].
|
||||
func (ro *ReassignOracle) StaticValue(n Node) Node {
|
||||
arg := n
|
||||
for {
|
||||
if n.Op() == OCONVNOP {
|
||||
n = n.(*ConvExpr).X
|
||||
continue
|
||||
}
|
||||
|
||||
if n.Op() == OINLCALL {
|
||||
n = n.(*InlinedCallExpr).SingleResult()
|
||||
continue
|
||||
}
|
||||
|
||||
n1 := ro.staticValue1(n)
|
||||
if n1 == nil {
|
||||
if consistencyCheckEnabled {
|
||||
checkStaticValueResult(arg, n)
|
||||
}
|
||||
return n
|
||||
}
|
||||
n = n1
|
||||
}
|
||||
}
|
||||
|
||||
func (ro *ReassignOracle) staticValue1(nn Node) Node {
|
||||
if nn.Op() != ONAME {
|
||||
return nil
|
||||
}
|
||||
n := nn.(*Name).Canonical()
|
||||
if n.Class != PAUTO {
|
||||
return nil
|
||||
}
|
||||
|
||||
defn := n.Defn
|
||||
if defn == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var rhs Node
|
||||
FindRHS:
|
||||
switch defn.Op() {
|
||||
case OAS:
|
||||
defn := defn.(*AssignStmt)
|
||||
rhs = defn.Y
|
||||
case OAS2:
|
||||
defn := defn.(*AssignListStmt)
|
||||
for i, lhs := range defn.Lhs {
|
||||
if lhs == n {
|
||||
rhs = defn.Rhs[i]
|
||||
break FindRHS
|
||||
}
|
||||
}
|
||||
base.Fatalf("%v missing from LHS of %v", n, defn)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if rhs == nil {
|
||||
base.Fatalf("RHS is nil: %v", defn)
|
||||
}
|
||||
|
||||
if _, ok := ro.singleDef[n]; !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return rhs
|
||||
}
|
||||
|
||||
// Reassigned method has the same semantics as the ir package function
|
||||
// of the same name; see comments on [Reassigned] for more info.
|
||||
func (ro *ReassignOracle) Reassigned(n *Name) bool {
|
||||
_, ok := ro.singleDef[n]
|
||||
result := !ok
|
||||
if consistencyCheckEnabled {
|
||||
checkReassignedResult(n, result)
|
||||
}
|
||||
return result
|
||||
}
|
Loading…
Reference in New Issue
Block a user