// 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 inlheur import ( "cmd/compile/internal/ir" "fmt" "os" ) // ShouldFoldIfNameConstant analyzes expression tree 'e' to see // whether it contains only combinations of simple references to all // of the names in 'names' with selected constants + operators. The // intent is to identify expression that could be folded away to a // constant if the value of 'n' were available. Return value is TRUE // if 'e' does look foldable given the value of 'n', and given that // 'e' actually makes reference to 'n'. Some examples where the type // of "n" is int64, type of "s" is string, and type of "p" is *byte: // // Simple? Expr // yes n<10 // yes n*n-100 // yes (n < 10 || n > 100) && (n >= 12 || n <= 99 || n != 101) // yes s == "foo" // yes p == nil // no nm // no float32(n)<1.0 // no *p == 1 // no 1 + 100 // no 1 / n // no 1 + unsafe.Sizeof(n) // // To avoid complexities (e.g. nan, inf) we stay way from folding and // floating point or complex operations (integers, bools, and strings // only). We also try to be conservative about avoiding any operation // that might result in a panic at runtime, e.g. for "n" with type // int64: // // 1<<(n-9) < 100/(n<<9999) // // we would return FALSE due to the negative shift count and/or // potential divide by zero. func ShouldFoldIfNameConstant(n ir.Node, names []*ir.Name) bool { cl := makeExprClassifier(names) var doNode func(ir.Node) bool doNode = func(n ir.Node) bool { ir.DoChildren(n, doNode) cl.Visit(n) return false } doNode(n) if cl.getdisp(n) != exprSimple { return false } for _, v := range cl.names { if !v { return false } } return true } // exprClassifier holds intermediate state about nodes within an // expression tree being analyzed by ShouldFoldIfNameConstant. Here // "name" is the name node passed in, and "disposition" stores the // result of classifying a given IR node. type exprClassifier struct { names map[*ir.Name]bool disposition map[ir.Node]disp } type disp int const ( // no info on this expr exprNoInfo disp = iota // expr contains only literals exprLiterals // expr is legal combination of literals and specified names exprSimple ) func (d disp) String() string { switch d { case exprNoInfo: return "noinfo" case exprSimple: return "simple" case exprLiterals: return "literals" default: return fmt.Sprintf("unknown<%d>", d) } } func makeExprClassifier(names []*ir.Name) *exprClassifier { m := make(map[*ir.Name]bool, len(names)) for _, n := range names { m[n] = false } return &exprClassifier{ names: m, disposition: make(map[ir.Node]disp), } } // Visit sets the classification for 'n' based on the previously // calculated classifications for n's children, as part of a bottom-up // walk over an expression tree. func (ec *exprClassifier) Visit(n ir.Node) { ndisp := exprNoInfo binparts := func(n ir.Node) (ir.Node, ir.Node) { if lex, ok := n.(*ir.LogicalExpr); ok { return lex.X, lex.Y } else if bex, ok := n.(*ir.BinaryExpr); ok { return bex.X, bex.Y } else { panic("bad") } } t := n.Type() if t == nil { if debugTrace&debugTraceExprClassify != 0 { fmt.Fprintf(os.Stderr, "=-= *** untyped op=%s\n", n.Op().String()) } } else if t.IsInteger() || t.IsString() || t.IsBoolean() || t.HasNil() { switch n.Op() { // FIXME: maybe add support for OADDSTR? case ir.ONIL: ndisp = exprLiterals case ir.OLITERAL: if _, ok := n.(*ir.BasicLit); ok { } else { panic("unexpected") } ndisp = exprLiterals case ir.ONAME: nn := n.(*ir.Name) if _, ok := ec.names[nn]; ok { ndisp = exprSimple ec.names[nn] = true } else { sv := ir.StaticValue(n) if sv.Op() == ir.ONAME { nn = sv.(*ir.Name) } if _, ok := ec.names[nn]; ok { ndisp = exprSimple ec.names[nn] = true } } case ir.ONOT, ir.OPLUS, ir.ONEG: uex := n.(*ir.UnaryExpr) ndisp = ec.getdisp(uex.X) case ir.OEQ, ir.ONE, ir.OLT, ir.OGT, ir.OGE, ir.OLE: // compare ops x, y := binparts(n) ndisp = ec.dispmeet(x, y) if debugTrace&debugTraceExprClassify != 0 { fmt.Fprintf(os.Stderr, "=-= meet(%s,%s) = %s for op=%s\n", ec.getdisp(x), ec.getdisp(y), ec.dispmeet(x, y), n.Op().String()) } case ir.OLSH, ir.ORSH, ir.ODIV, ir.OMOD: x, y := binparts(n) if ec.getdisp(y) == exprLiterals { ndisp = ec.dispmeet(x, y) } case ir.OADD, ir.OSUB, ir.OOR, ir.OXOR, ir.OMUL, ir.OAND, ir.OANDNOT, ir.OANDAND, ir.OOROR: x, y := binparts(n) if debugTrace&debugTraceExprClassify != 0 { fmt.Fprintf(os.Stderr, "=-= meet(%s,%s) = %s for op=%s\n", ec.getdisp(x), ec.getdisp(y), ec.dispmeet(x, y), n.Op().String()) } ndisp = ec.dispmeet(x, y) } } if debugTrace&debugTraceExprClassify != 0 { fmt.Fprintf(os.Stderr, "=-= op=%s disp=%v\n", n.Op().String(), ndisp.String()) } ec.disposition[n] = ndisp } func (ec *exprClassifier) getdisp(x ir.Node) disp { if d, ok := ec.disposition[x]; ok { return d } else { panic("missing node from disp table") } } // dispmeet performs a "meet" operation on the data flow states of // node x and y (where the term "meet" is being drawn from traditional // lattice-theoretical data flow analysis terminology). func (ec *exprClassifier) dispmeet(x, y ir.Node) disp { xd := ec.getdisp(x) if xd == exprNoInfo { return exprNoInfo } yd := ec.getdisp(y) if yd == exprNoInfo { return exprNoInfo } if xd == exprSimple || yd == exprSimple { return exprSimple } if xd != exprLiterals || yd != exprLiterals { panic("unexpected") } return exprLiterals }