| // 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 n<foo() | |
| // no n<1 || n>m | |
| // 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 | |
| } |