// 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 }