blob: 11246e5b969b75cb8f73189e48cbbb97f4bb3d34 [file] [log] [blame]
// 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 inline
// This file defines the analysis of callee effects.
import (
"go/ast"
"go/token"
"go/types"
)
const (
rinf = -1 // R∞: arbitrary read from memory
winf = -2 // W∞: arbitrary write to memory (or unknown control)
)
// calleefx returns a list of parameter indices indicating the order
// in which parameters are first referenced during evaluation of the
// callee, relative both to each other and to other effects of the
// callee (if any), such as arbitrary reads (rinf) and arbitrary
// effects (winf), including unknown control flow. Each parameter
// that is referenced appears once in the list.
//
// For example, the effects list of this function:
//
// func f(x, y, z int) int {
// return y + x + g() + z
// }
//
// is [1 0 -2 2], indicating reads of y and x, followed by the unknown
// effects of the g() call. and finally the read of parameter z. This
// information is used during inlining to ascertain when it is safe
// for parameter references to be replaced by their corresponding
// argument expressions. Such substitutions are permitted only when
// they do not cause "write" operations (those with effects) to
// commute with "read" operations (those that have no effect but are
// not pure). Impure operations may be reordered with other impure
// operations, and pure operations may be reordered arbitrarily.
//
// The analysis ignores the effects of runtime panics, on the
// assumption that well-behaved programs shouldn't encounter them.
func calleefx(info *types.Info, body *ast.BlockStmt, paramInfos map[*types.Var]*paramInfo) []int {
// This traversal analyzes the callee's statements (in syntax
// form, though one could do better with SSA) to compute the
// sequence of events of the following kinds:
//
// 1 read of a parameter variable.
// 2. reads from other memory.
// 3. writes to memory
var effects []int // indices of parameters, or rinf/winf (-ve)
seen := make(map[int]bool)
effect := func(i int) {
if !seen[i] {
seen[i] = true
effects = append(effects, i)
}
}
// unknown is called for statements of unknown effects (or control).
unknown := func() {
effect(winf)
// Ensure that all remaining parameters are "seen"
// after we go into the unknown (unless they are
// unreferenced by the function body). This lets us
// not bother implementing the complete traversal into
// control structures.
//
// TODO(adonovan): add them in a deterministic order.
// (This is not a bug but determinism is good.)
for _, pinfo := range paramInfos {
if !pinfo.IsResult && len(pinfo.Refs) > 0 {
effect(pinfo.Index)
}
}
}
var visitExpr func(n ast.Expr)
var visitStmt func(n ast.Stmt) bool
visitExpr = func(n ast.Expr) {
switch n := n.(type) {
case *ast.Ident:
if v, ok := info.Uses[n].(*types.Var); ok && !v.IsField() {
// Use of global?
if v.Parent() == v.Pkg().Scope() {
effect(rinf) // read global var
}
// Use of parameter?
if pinfo, ok := paramInfos[v]; ok && !pinfo.IsResult {
effect(pinfo.Index) // read parameter var
}
// Use of local variables is ok.
}
case *ast.BasicLit:
// no effect
case *ast.FuncLit:
// A func literal has no read or write effect
// until called, and (most) function calls are
// considered to have arbitrary effects.
// So, no effect.
case *ast.CompositeLit:
for _, elt := range n.Elts {
visitExpr(elt) // note: visits KeyValueExpr
}
case *ast.ParenExpr:
visitExpr(n.X)
case *ast.SelectorExpr:
if seln, ok := info.Selections[n]; ok {
visitExpr(n.X)
// See types.SelectionKind for background.
switch seln.Kind() {
case types.MethodExpr:
// A method expression T.f acts like a
// reference to a func decl,
// so it doesn't read x until called.
case types.MethodVal, types.FieldVal:
// A field or method value selection x.f
// reads x if the selection indirects a pointer.
if indirectSelection(seln) {
effect(rinf)
}
}
} else {
// qualified identifier: treat like unqualified
visitExpr(n.Sel)
}
case *ast.IndexExpr:
if tv := info.Types[n.Index]; tv.IsType() {
// no effect (G[T] instantiation)
} else {
visitExpr(n.X)
visitExpr(n.Index)
switch tv.Type.Underlying().(type) {
case *types.Slice, *types.Pointer: // []T, *[n]T (not string, [n]T)
effect(rinf) // indirect read of slice/array element
}
}
case *ast.IndexListExpr:
// no effect (M[K,V] instantiation)
case *ast.SliceExpr:
visitExpr(n.X)
visitExpr(n.Low)
visitExpr(n.High)
visitExpr(n.Max)
case *ast.TypeAssertExpr:
visitExpr(n.X)
case *ast.CallExpr:
if info.Types[n.Fun].IsType() {
// conversion T(x)
visitExpr(n.Args[0])
} else {
// call f(args)
visitExpr(n.Fun)
for i, arg := range n.Args {
if i == 0 && info.Types[arg].IsType() {
continue // new(T), make(T, n)
}
visitExpr(arg)
}
// The pure built-ins have no effects beyond
// those of their operands (not even memory reads).
// All other calls have unknown effects.
if !callsPureBuiltin(info, n) {
unknown() // arbitrary effects
}
}
case *ast.StarExpr:
visitExpr(n.X)
effect(rinf) // *ptr load or store depends on state of heap
case *ast.UnaryExpr: // + - ! ^ & ~ <-
visitExpr(n.X)
if n.Op == token.ARROW {
unknown() // effect: channel receive
}
case *ast.BinaryExpr:
visitExpr(n.X)
visitExpr(n.Y)
case *ast.KeyValueExpr:
visitExpr(n.Key) // may be a struct field
visitExpr(n.Value)
case *ast.BadExpr:
// no effect
case nil:
// optional subtree
default:
// type syntax: unreachable given traversal
panic(n)
}
}
// visitStmt's result indicates the continuation:
// false for return, true for the next statement.
//
// We could treat return as an unknown, but this way
// yields definite effects for simple sequences like
// {S1; S2; return}, so unreferenced parameters are
// not spuriously added to the effects list, and thus
// not spuriously disqualified from elimination.
visitStmt = func(n ast.Stmt) bool {
switch n := n.(type) {
case *ast.DeclStmt:
decl := n.Decl.(*ast.GenDecl)
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.ValueSpec:
for _, v := range spec.Values {
visitExpr(v)
}
case *ast.TypeSpec:
// no effect
}
}
case *ast.LabeledStmt:
return visitStmt(n.Stmt)
case *ast.ExprStmt:
visitExpr(n.X)
case *ast.SendStmt:
visitExpr(n.Chan)
visitExpr(n.Value)
unknown() // effect: channel send
case *ast.IncDecStmt:
visitExpr(n.X)
unknown() // effect: variable increment
case *ast.AssignStmt:
for _, lhs := range n.Lhs {
visitExpr(lhs)
}
for _, rhs := range n.Rhs {
visitExpr(rhs)
}
for _, lhs := range n.Lhs {
id, _ := lhs.(*ast.Ident)
if id != nil && id.Name == "_" {
continue // blank assign has no effect
}
if n.Tok == token.DEFINE && id != nil && info.Defs[id] != nil {
continue // new var declared by := has no effect
}
unknown() // assignment to existing var
break
}
case *ast.GoStmt:
visitExpr(n.Call.Fun)
for _, arg := range n.Call.Args {
visitExpr(arg)
}
unknown() // effect: create goroutine
case *ast.DeferStmt:
visitExpr(n.Call.Fun)
for _, arg := range n.Call.Args {
visitExpr(arg)
}
unknown() // effect: push defer
case *ast.ReturnStmt:
for _, res := range n.Results {
visitExpr(res)
}
return false
case *ast.BlockStmt:
for _, stmt := range n.List {
if !visitStmt(stmt) {
return false
}
}
case *ast.BranchStmt:
unknown() // control flow
case *ast.IfStmt:
visitStmt(n.Init)
visitExpr(n.Cond)
unknown() // control flow
case *ast.SwitchStmt:
visitStmt(n.Init)
visitExpr(n.Tag)
unknown() // control flow
case *ast.TypeSwitchStmt:
visitStmt(n.Init)
visitStmt(n.Assign)
unknown() // control flow
case *ast.SelectStmt:
unknown() // control flow
case *ast.ForStmt:
visitStmt(n.Init)
visitExpr(n.Cond)
unknown() // control flow
case *ast.RangeStmt:
visitExpr(n.X)
unknown() // control flow
case *ast.EmptyStmt, *ast.BadStmt:
// no effect
case nil:
// optional subtree
default:
panic(n)
}
return true
}
visitStmt(body)
return effects
}