Файловый менеджер - Редактировать - /var/www/html/inlheur.zip
Ðазад
PK ! p-� � serialize.gonu �[��� // 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 "strings" func (funcProps *FuncProps) SerializeToString() string { if funcProps == nil { return "" } var sb strings.Builder writeUleb128(&sb, uint64(funcProps.Flags)) writeUleb128(&sb, uint64(len(funcProps.ParamFlags))) for _, pf := range funcProps.ParamFlags { writeUleb128(&sb, uint64(pf)) } writeUleb128(&sb, uint64(len(funcProps.ResultFlags))) for _, rf := range funcProps.ResultFlags { writeUleb128(&sb, uint64(rf)) } return sb.String() } func DeserializeFromString(s string) *FuncProps { if len(s) == 0 { return nil } var funcProps FuncProps var v uint64 sl := []byte(s) v, sl = readULEB128(sl) funcProps.Flags = FuncPropBits(v) v, sl = readULEB128(sl) funcProps.ParamFlags = make([]ParamPropBits, v) for i := range funcProps.ParamFlags { v, sl = readULEB128(sl) funcProps.ParamFlags[i] = ParamPropBits(v) } v, sl = readULEB128(sl) funcProps.ResultFlags = make([]ResultPropBits, v) for i := range funcProps.ResultFlags { v, sl = readULEB128(sl) funcProps.ResultFlags[i] = ResultPropBits(v) } return &funcProps } func readULEB128(sl []byte) (value uint64, rsl []byte) { var shift uint for { b := sl[0] sl = sl[1:] value |= (uint64(b&0x7F) << shift) if b&0x80 == 0 { break } shift += 7 } return value, sl } func writeUleb128(sb *strings.Builder, v uint64) { if v < 128 { sb.WriteByte(uint8(v)) return } more := true for more { c := uint8(v & 0x7f) v >>= 7 more = v != 0 if more { c |= 0x80 } sb.WriteByte(c) } } PK ! ӧ�( �( analyze_func_flags.gonu �[��� // 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/base" "cmd/compile/internal/ir" "cmd/compile/internal/types" "fmt" "os" ) // funcFlagsAnalyzer computes the "Flags" value for the FuncProps // object we're computing. The main item of interest here is "nstate", // which stores the disposition of a given ir Node with respect to the // flags/properties we're trying to compute. type funcFlagsAnalyzer struct { fn *ir.Func nstate map[ir.Node]pstate noInfo bool // set if we see something inscrutable/un-analyzable } // pstate keeps track of the disposition of a given node and its // children with respect to panic/exit calls. type pstate int const ( psNoInfo pstate = iota // nothing interesting about this node psCallsPanic // node causes call to panic or os.Exit psMayReturn // executing node may trigger a "return" stmt psTop // dataflow lattice "top" element ) func makeFuncFlagsAnalyzer(fn *ir.Func) *funcFlagsAnalyzer { return &funcFlagsAnalyzer{ fn: fn, nstate: make(map[ir.Node]pstate), } } // setResults transfers func flag results to 'funcProps'. func (ffa *funcFlagsAnalyzer) setResults(funcProps *FuncProps) { var rv FuncPropBits if !ffa.noInfo && ffa.stateForList(ffa.fn.Body) == psCallsPanic { rv = FuncPropNeverReturns } // This is slightly hacky and not at all required, but include a // special case for main.main, which often ends in a call to // os.Exit. People who write code like this (very common I // imagine) // // func main() { // rc = perform() // ... // foo() // os.Exit(rc) // } // // will be constantly surprised when foo() is inlined in many // other spots in the program but not in main(). if isMainMain(ffa.fn) { rv &^= FuncPropNeverReturns } funcProps.Flags = rv } func (ffa *funcFlagsAnalyzer) getState(n ir.Node) pstate { return ffa.nstate[n] } func (ffa *funcFlagsAnalyzer) setState(n ir.Node, st pstate) { if st != psNoInfo { ffa.nstate[n] = st } } func (ffa *funcFlagsAnalyzer) updateState(n ir.Node, st pstate) { if st == psNoInfo { delete(ffa.nstate, n) } else { ffa.nstate[n] = st } } func (ffa *funcFlagsAnalyzer) panicPathTable() map[ir.Node]pstate { return ffa.nstate } // blockCombine merges together states as part of a linear sequence of // statements, where 'pred' and 'succ' are analysis results for a pair // of consecutive statements. Examples: // // case 1: case 2: // panic("foo") if q { return x } <-pred // return x panic("boo") <-succ // // In case 1, since the pred state is "always panic" it doesn't matter // what the succ state is, hence the state for the combination of the // two blocks is "always panics". In case 2, because there is a path // to return that avoids the panic in succ, the state for the // combination of the two statements is "may return". func blockCombine(pred, succ pstate) pstate { switch succ { case psTop: return pred case psMayReturn: if pred == psCallsPanic { return psCallsPanic } return psMayReturn case psNoInfo: return pred case psCallsPanic: if pred == psMayReturn { return psMayReturn } return psCallsPanic } panic("should never execute") } // branchCombine combines two states at a control flow branch point where // either p1 or p2 executes (as in an "if" statement). func branchCombine(p1, p2 pstate) pstate { if p1 == psCallsPanic && p2 == psCallsPanic { return psCallsPanic } if p1 == psMayReturn || p2 == psMayReturn { return psMayReturn } return psNoInfo } // stateForList walks through a list of statements and computes the // state/diposition for the entire list as a whole, as well // as updating disposition of intermediate nodes. func (ffa *funcFlagsAnalyzer) stateForList(list ir.Nodes) pstate { st := psTop // Walk the list backwards so that we can update the state for // earlier list elements based on what we find out about their // successors. Example: // // if ... { // L10: foo() // L11: <stmt> // L12: panic(...) // } // // After combining the dispositions for line 11 and 12, we want to // update the state for the call at line 10 based on that combined // disposition (if L11 has no path to "return", then the call at // line 10 will be on a panic path). for i := len(list) - 1; i >= 0; i-- { n := list[i] psi := ffa.getState(n) if debugTrace&debugTraceFuncFlags != 0 { fmt.Fprintf(os.Stderr, "=-= %v: stateForList n=%s ps=%s\n", ir.Line(n), n.Op().String(), psi.String()) } st = blockCombine(psi, st) ffa.updateState(n, st) } if st == psTop { st = psNoInfo } return st } func isMainMain(fn *ir.Func) bool { s := fn.Sym() return (s.Pkg.Name == "main" && s.Name == "main") } func isWellKnownFunc(s *types.Sym, pkg, name string) bool { return s.Pkg.Path == pkg && s.Name == name } // isExitCall reports TRUE if the node itself is an unconditional // call to os.Exit(), a panic, or a function that does likewise. func isExitCall(n ir.Node) bool { if n.Op() != ir.OCALLFUNC { return false } cx := n.(*ir.CallExpr) name := ir.StaticCalleeName(cx.Fun) if name == nil { return false } s := name.Sym() if isWellKnownFunc(s, "os", "Exit") || isWellKnownFunc(s, "runtime", "throw") { return true } if funcProps := propsForFunc(name.Func); funcProps != nil { if funcProps.Flags&FuncPropNeverReturns != 0 { return true } } return name.Func.NeverReturns() } // pessimize is called to record the fact that we saw something in the // function that renders it entirely impossible to analyze. func (ffa *funcFlagsAnalyzer) pessimize() { ffa.noInfo = true } // shouldVisit reports TRUE if this is an interesting node from the // perspective of computing function flags. NB: due to the fact that // ir.CallExpr implements the Stmt interface, we wind up visiting // a lot of nodes that we don't really need to, but these can // simply be screened out as part of the visit. func shouldVisit(n ir.Node) bool { _, isStmt := n.(ir.Stmt) return n.Op() != ir.ODCL && (isStmt || n.Op() == ir.OCALLFUNC || n.Op() == ir.OPANIC) } // nodeVisitPost helps implement the propAnalyzer interface; when // called on a given node, it decides the disposition of that node // based on the state(s) of the node's children. func (ffa *funcFlagsAnalyzer) nodeVisitPost(n ir.Node) { if debugTrace&debugTraceFuncFlags != 0 { fmt.Fprintf(os.Stderr, "=+= nodevis %v %s should=%v\n", ir.Line(n), n.Op().String(), shouldVisit(n)) } if !shouldVisit(n) { return } var st pstate switch n.Op() { case ir.OCALLFUNC: if isExitCall(n) { st = psCallsPanic } case ir.OPANIC: st = psCallsPanic case ir.ORETURN: st = psMayReturn case ir.OBREAK, ir.OCONTINUE: // FIXME: this handling of break/continue is sub-optimal; we // have them as "mayReturn" in order to help with this case: // // for { // if q() { break } // panic(...) // } // // where the effect of the 'break' is to cause the subsequent // panic to be skipped. One possible improvement would be to // track whether the currently enclosing loop is a "for {" or // a for/range with condition, then use mayReturn only for the // former. Note also that "break X" or "continue X" is treated // the same as "goto", since we don't have a good way to track // the target of the branch. st = psMayReturn n := n.(*ir.BranchStmt) if n.Label != nil { ffa.pessimize() } case ir.OBLOCK: n := n.(*ir.BlockStmt) st = ffa.stateForList(n.List) case ir.OCASE: if ccst, ok := n.(*ir.CaseClause); ok { st = ffa.stateForList(ccst.Body) } else if ccst, ok := n.(*ir.CommClause); ok { st = ffa.stateForList(ccst.Body) } else { panic("unexpected") } case ir.OIF: n := n.(*ir.IfStmt) st = branchCombine(ffa.stateForList(n.Body), ffa.stateForList(n.Else)) case ir.OFOR: // Treat for { XXX } like a block. // Treat for <cond> { XXX } like an if statement with no else. n := n.(*ir.ForStmt) bst := ffa.stateForList(n.Body) if n.Cond == nil { st = bst } else { if bst == psMayReturn { st = psMayReturn } } case ir.ORANGE: // Treat for range { XXX } like an if statement with no else. n := n.(*ir.RangeStmt) if ffa.stateForList(n.Body) == psMayReturn { st = psMayReturn } case ir.OGOTO: // punt if we see even one goto. if we built a control // flow graph we could do more, but this is just a tree walk. ffa.pessimize() case ir.OSELECT: // process selects for "may return" but not "always panics", // the latter case seems very improbable. n := n.(*ir.SelectStmt) if len(n.Cases) != 0 { st = psTop for _, c := range n.Cases { st = branchCombine(ffa.stateForList(c.Body), st) } } case ir.OSWITCH: n := n.(*ir.SwitchStmt) if len(n.Cases) != 0 { st = psTop for _, c := range n.Cases { st = branchCombine(ffa.stateForList(c.Body), st) } } st, fall := psTop, psNoInfo for i := len(n.Cases) - 1; i >= 0; i-- { cas := n.Cases[i] cst := ffa.stateForList(cas.Body) endsInFallthrough := false if len(cas.Body) != 0 { endsInFallthrough = cas.Body[0].Op() == ir.OFALL } if endsInFallthrough { cst = blockCombine(cst, fall) } st = branchCombine(st, cst) fall = cst } case ir.OFALL: // Not important. case ir.ODCLFUNC, ir.ORECOVER, ir.OAS, ir.OAS2, ir.OAS2FUNC, ir.OASOP, ir.OPRINTLN, ir.OPRINT, ir.OLABEL, ir.OCALLINTER, ir.ODEFER, ir.OSEND, ir.ORECV, ir.OSELRECV2, ir.OGO, ir.OAPPEND, ir.OAS2DOTTYPE, ir.OAS2MAPR, ir.OGETG, ir.ODELETE, ir.OINLMARK, ir.OAS2RECV, ir.OMIN, ir.OMAX, ir.OMAKE, ir.ORECOVERFP, ir.OGETCALLERSP: // these should all be benign/uninteresting case ir.OTAILCALL, ir.OJUMPTABLE, ir.OTYPESW: // don't expect to see these at all. base.Fatalf("unexpected op %s in func %s", n.Op().String(), ir.FuncName(ffa.fn)) default: base.Fatalf("%v: unhandled op %s in func %v", ir.Line(n), n.Op().String(), ir.FuncName(ffa.fn)) } if debugTrace&debugTraceFuncFlags != 0 { fmt.Fprintf(os.Stderr, "=-= %v: visit n=%s returns %s\n", ir.Line(n), n.Op().String(), st.String()) } ffa.setState(n, st) } func (ffa *funcFlagsAnalyzer) nodeVisitPre(n ir.Node) { } PK ! ���w* * resultpropbits_string.gonu �[��� // 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. // Code generated by "stringer -bitset -type ResultPropBits"; DO NOT EDIT. package inlheur import ( "bytes" "strconv" ) func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[ResultNoInfo-0] _ = x[ResultIsAllocatedMem-2] _ = x[ResultIsConcreteTypeConvertedToInterface-4] _ = x[ResultAlwaysSameConstant-8] _ = x[ResultAlwaysSameFunc-16] _ = x[ResultAlwaysSameInlinableFunc-32] } var _ResultPropBits_value = [...]uint64{ 0x0, /* ResultNoInfo */ 0x2, /* ResultIsAllocatedMem */ 0x4, /* ResultIsConcreteTypeConvertedToInterface */ 0x8, /* ResultAlwaysSameConstant */ 0x10, /* ResultAlwaysSameFunc */ 0x20, /* ResultAlwaysSameInlinableFunc */ } const _ResultPropBits_name = "ResultNoInfoResultIsAllocatedMemResultIsConcreteTypeConvertedToInterfaceResultAlwaysSameConstantResultAlwaysSameFuncResultAlwaysSameInlinableFunc" var _ResultPropBits_index = [...]uint8{0, 12, 32, 72, 96, 116, 145} func (i ResultPropBits) String() string { var b bytes.Buffer remain := uint64(i) seen := false for k, v := range _ResultPropBits_value { x := _ResultPropBits_name[_ResultPropBits_index[k]:_ResultPropBits_index[k+1]] if v == 0 { if i == 0 { b.WriteString(x) return b.String() } continue } if (v & remain) == v { remain &^= v x := _ResultPropBits_name[_ResultPropBits_index[k]:_ResultPropBits_index[k+1]] if seen { b.WriteString("|") } seen = true b.WriteString(x) } } if remain == 0 { return b.String() } return "ResultPropBits(0x" + strconv.FormatInt(int64(i), 16) + ")" } PK ! ��Ǵ@ @ trace_off.gonu �[��� // 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 !debugtrace package inlheur const debugTrace = 0 func enableDebugTrace(x int) { } func enableDebugTraceIfEnv() { } func disableDebugTrace() { } PK ! ']\�� � eclassify.gonu �[��� // 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 } PK ! �ޛ funcpropbits_string.gonu �[��� // 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. // Code generated by "stringer -bitset -type FuncPropBits"; DO NOT EDIT. package inlheur import ( "bytes" "strconv" ) func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[FuncPropNeverReturns-1] } var _FuncPropBits_value = [...]uint64{ 0x1, /* FuncPropNeverReturns */ } const _FuncPropBits_name = "FuncPropNeverReturns" var _FuncPropBits_index = [...]uint8{0, 20} func (i FuncPropBits) String() string { var b bytes.Buffer remain := uint64(i) seen := false for k, v := range _FuncPropBits_value { x := _FuncPropBits_name[_FuncPropBits_index[k]:_FuncPropBits_index[k+1]] if v == 0 { if i == 0 { b.WriteString(x) return b.String() } continue } if (v & remain) == v { remain &^= v x := _FuncPropBits_name[_FuncPropBits_index[k]:_FuncPropBits_index[k+1]] if seen { b.WriteString("|") } seen = true b.WriteString(x) } } if remain == 0 { return b.String() } return "FuncPropBits(0x" + strconv.FormatInt(int64(i), 16) + ")" } PK ! n���A A pstate_string.gonu �[��� // 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. // Code generated by "stringer -type pstate"; DO NOT EDIT. package inlheur import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[psNoInfo-0] _ = x[psCallsPanic-1] _ = x[psMayReturn-2] _ = x[psTop-3] } const _pstate_name = "psNoInfopsCallsPanicpsMayReturnpsTop" var _pstate_index = [...]uint8{0, 8, 20, 31, 36} func (i pstate) String() string { if i < 0 || i >= pstate(len(_pstate_index)-1) { return "pstate(" + strconv.FormatInt(int64(i), 10) + ")" } return _pstate_name[_pstate_index[i]:_pstate_index[i+1]] } PK ! Y���H H trace_on.gonu �[��� // 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 debugtrace package inlheur import ( "os" "strconv" ) var debugTrace = 0 func enableDebugTrace(x int) { debugTrace = x } func enableDebugTraceIfEnv() { v := os.Getenv("DEBUG_TRACE_INLHEUR") if v == "" { return } if v[0] == '*' { if !UnitTesting() { return } v = v[1:] } i, err := strconv.Atoi(v) if err != nil { return } debugTrace = i } func disableDebugTrace() { debugTrace = 0 } PK ! 8�l�� � cspropbits_string.gonu �[��� // Code generated by "stringer -bitset -type CSPropBits"; DO NOT EDIT. package inlheur import "strconv" import "bytes" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[CallSiteInLoop-1] _ = x[CallSiteOnPanicPath-2] _ = x[CallSiteInInitFunc-4] } var _CSPropBits_value = [...]uint64{ 0x1, /* CallSiteInLoop */ 0x2, /* CallSiteOnPanicPath */ 0x4, /* CallSiteInInitFunc */ } const _CSPropBits_name = "CallSiteInLoopCallSiteOnPanicPathCallSiteInInitFunc" var _CSPropBits_index = [...]uint8{0, 14, 33, 51} func (i CSPropBits) String() string { var b bytes.Buffer remain := uint64(i) seen := false for k, v := range _CSPropBits_value { x := _CSPropBits_name[_CSPropBits_index[k]:_CSPropBits_index[k+1]] if v == 0 { if i == 0 { b.WriteString(x) return b.String() } continue } if (v & remain) == v { remain &^= v x := _CSPropBits_name[_CSPropBits_index[k]:_CSPropBits_index[k+1]] if seen { b.WriteString("|") } seen = true b.WriteString(x) } } if remain == 0 { return b.String() } return "CSPropBits(0x" + strconv.FormatInt(int64(i), 16) + ")" } PK ! [q�� � actualexprpropbits_string.gonu �[��� // Code generated by "stringer -bitset -type ActualExprPropBits"; DO NOT EDIT. package inlheur import "strconv" import "bytes" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[ActualExprConstant-1] _ = x[ActualExprIsConcreteConvIface-2] _ = x[ActualExprIsFunc-4] _ = x[ActualExprIsInlinableFunc-8] } var _ActualExprPropBits_value = [...]uint64{ 0x1, /* ActualExprConstant */ 0x2, /* ActualExprIsConcreteConvIface */ 0x4, /* ActualExprIsFunc */ 0x8, /* ActualExprIsInlinableFunc */ } const _ActualExprPropBits_name = "ActualExprConstantActualExprIsConcreteConvIfaceActualExprIsFuncActualExprIsInlinableFunc" var _ActualExprPropBits_index = [...]uint8{0, 18, 47, 63, 88} func (i ActualExprPropBits) String() string { var b bytes.Buffer remain := uint64(i) seen := false for k, v := range _ActualExprPropBits_value { x := _ActualExprPropBits_name[_ActualExprPropBits_index[k]:_ActualExprPropBits_index[k+1]] if v == 0 { if i == 0 { b.WriteString(x) return b.String() } continue } if (v & remain) == v { remain &^= v x := _ActualExprPropBits_name[_ActualExprPropBits_index[k]:_ActualExprPropBits_index[k+1]] if seen { b.WriteString("|") } seen = true b.WriteString(x) } } if remain == 0 { return b.String() } return "ActualExprPropBits(0x" + strconv.FormatInt(int64(i), 16) + ")" } PK ! &|'vf f parampropbits_string.gonu �[��� // 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. // Code generated by "stringer -bitset -type ParamPropBits"; DO NOT EDIT. package inlheur import ( "bytes" "strconv" ) func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[ParamNoInfo-0] _ = x[ParamFeedsInterfaceMethodCall-2] _ = x[ParamMayFeedInterfaceMethodCall-4] _ = x[ParamFeedsIndirectCall-8] _ = x[ParamMayFeedIndirectCall-16] _ = x[ParamFeedsIfOrSwitch-32] _ = x[ParamMayFeedIfOrSwitch-64] } var _ParamPropBits_value = [...]uint64{ 0x0, /* ParamNoInfo */ 0x2, /* ParamFeedsInterfaceMethodCall */ 0x4, /* ParamMayFeedInterfaceMethodCall */ 0x8, /* ParamFeedsIndirectCall */ 0x10, /* ParamMayFeedIndirectCall */ 0x20, /* ParamFeedsIfOrSwitch */ 0x40, /* ParamMayFeedIfOrSwitch */ } const _ParamPropBits_name = "ParamNoInfoParamFeedsInterfaceMethodCallParamMayFeedInterfaceMethodCallParamFeedsIndirectCallParamMayFeedIndirectCallParamFeedsIfOrSwitchParamMayFeedIfOrSwitch" var _ParamPropBits_index = [...]uint8{0, 11, 40, 71, 93, 117, 137, 159} func (i ParamPropBits) String() string { var b bytes.Buffer remain := uint64(i) seen := false for k, v := range _ParamPropBits_value { x := _ParamPropBits_name[_ParamPropBits_index[k]:_ParamPropBits_index[k+1]] if v == 0 { if i == 0 { b.WriteString(x) return b.String() } continue } if (v & remain) == v { remain &^= v x := _ParamPropBits_name[_ParamPropBits_index[k]:_ParamPropBits_index[k+1]] if seen { b.WriteString("|") } seen = true b.WriteString(x) } } if remain == 0 { return b.String() } return "ParamPropBits(0x" + strconv.FormatInt(int64(i), 16) + ")" } PK ! Y7ƳH H texpr_classify_test.gonu �[��� // 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" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" "cmd/internal/src" "go/constant" "testing" ) var pos src.XPos var local *types.Pkg var f *ir.Func func init() { types.PtrSize = 8 types.RegSize = 8 types.MaxWidth = 1 << 50 typecheck.InitUniverse() local = types.NewPkg("", "") fsym := &types.Sym{ Pkg: types.NewPkg("my/import/path", "path"), Name: "function", } f = ir.NewFunc(src.NoXPos, src.NoXPos, fsym, nil) } type state struct { ntab map[string]*ir.Name } func mkstate() *state { return &state{ ntab: make(map[string]*ir.Name), } } func bin(x ir.Node, op ir.Op, y ir.Node) ir.Node { return ir.NewBinaryExpr(pos, op, x, y) } func conv(x ir.Node, t *types.Type) ir.Node { return ir.NewConvExpr(pos, ir.OCONV, t, x) } func logical(x ir.Node, op ir.Op, y ir.Node) ir.Node { return ir.NewLogicalExpr(pos, op, x, y) } func un(op ir.Op, x ir.Node) ir.Node { return ir.NewUnaryExpr(pos, op, x) } func liti(i int64) ir.Node { return ir.NewBasicLit(pos, types.Types[types.TINT64], constant.MakeInt64(i)) } func lits(s string) ir.Node { return ir.NewBasicLit(pos, types.Types[types.TSTRING], constant.MakeString(s)) } func (s *state) nm(name string, t *types.Type) *ir.Name { if n, ok := s.ntab[name]; ok { if n.Type() != t { panic("bad") } return n } sym := local.Lookup(name) nn := ir.NewNameAt(pos, sym, t) s.ntab[name] = nn return nn } func (s *state) nmi64(name string) *ir.Name { return s.nm(name, types.Types[types.TINT64]) } func (s *state) nms(name string) *ir.Name { return s.nm(name, types.Types[types.TSTRING]) } func TestClassifyIntegerCompare(t *testing.T) { // (n < 10 || n > 100) && (n >= 12 || n <= 99 || n != 101) s := mkstate() nn := s.nmi64("n") nlt10 := bin(nn, ir.OLT, liti(10)) // n < 10 ngt100 := bin(nn, ir.OGT, liti(100)) // n > 100 nge12 := bin(nn, ir.OGE, liti(12)) // n >= 12 nle99 := bin(nn, ir.OLE, liti(99)) // n < 10 nne101 := bin(nn, ir.ONE, liti(101)) // n != 101 noror1 := logical(nlt10, ir.OOROR, ngt100) // n < 10 || n > 100 noror2 := logical(nge12, ir.OOROR, nle99) // n >= 12 || n <= 99 noror3 := logical(noror2, ir.OOROR, nne101) nandand := typecheck.Expr(logical(noror1, ir.OANDAND, noror3)) wantv := true v := ShouldFoldIfNameConstant(nandand, []*ir.Name{nn}) if v != wantv { t.Errorf("wanted shouldfold(%v) %v, got %v", nandand, wantv, v) } } func TestClassifyStringCompare(t *testing.T) { // s != "foo" && s < "ooblek" && s > "plarkish" s := mkstate() nn := s.nms("s") snefoo := bin(nn, ir.ONE, lits("foo")) // s != "foo" sltoob := bin(nn, ir.OLT, lits("ooblek")) // s < "ooblek" sgtpk := bin(nn, ir.OGT, lits("plarkish")) // s > "plarkish" nandand := logical(snefoo, ir.OANDAND, sltoob) top := typecheck.Expr(logical(nandand, ir.OANDAND, sgtpk)) wantv := true v := ShouldFoldIfNameConstant(top, []*ir.Name{nn}) if v != wantv { t.Errorf("wanted shouldfold(%v) %v, got %v", top, wantv, v) } } func TestClassifyIntegerArith(t *testing.T) { // n+1 ^ n-3 * n/2 + n<<9 + n>>2 - n&^7 s := mkstate() nn := s.nmi64("n") np1 := bin(nn, ir.OADD, liti(1)) // n+1 nm3 := bin(nn, ir.OSUB, liti(3)) // n-3 nd2 := bin(nn, ir.ODIV, liti(2)) // n/2 nls9 := bin(nn, ir.OLSH, liti(9)) // n<<9 nrs2 := bin(nn, ir.ORSH, liti(2)) // n>>2 nan7 := bin(nn, ir.OANDNOT, liti(7)) // n&^7 c1xor := bin(np1, ir.OXOR, nm3) c2mul := bin(c1xor, ir.OMUL, nd2) c3add := bin(c2mul, ir.OADD, nls9) c4add := bin(c3add, ir.OADD, nrs2) c5sub := bin(c4add, ir.OSUB, nan7) top := typecheck.Expr(c5sub) wantv := true v := ShouldFoldIfNameConstant(top, []*ir.Name{nn}) if v != wantv { t.Errorf("wanted shouldfold(%v) %v, got %v", top, wantv, v) } } func TestClassifyAssortedShifts(t *testing.T) { s := mkstate() nn := s.nmi64("n") badcases := []ir.Node{ bin(liti(3), ir.OLSH, nn), // 3<<n bin(liti(7), ir.ORSH, nn), // 7>>n } for _, bc := range badcases { wantv := false v := ShouldFoldIfNameConstant(typecheck.Expr(bc), []*ir.Name{nn}) if v != wantv { t.Errorf("wanted shouldfold(%v) %v, got %v", bc, wantv, v) } } } func TestClassifyFloat(t *testing.T) { // float32(n) + float32(10) s := mkstate() nn := s.nm("n", types.Types[types.TUINT32]) f1 := conv(nn, types.Types[types.TFLOAT32]) f2 := conv(liti(10), types.Types[types.TFLOAT32]) add := bin(f1, ir.OADD, f2) wantv := false v := ShouldFoldIfNameConstant(typecheck.Expr(add), []*ir.Name{nn}) if v != wantv { t.Errorf("wanted shouldfold(%v) %v, got %v", add, wantv, v) } } func TestMultipleNamesAllUsed(t *testing.T) { // n != 101 && m < 2 s := mkstate() nn := s.nmi64("n") nm := s.nmi64("m") nne101 := bin(nn, ir.ONE, liti(101)) // n != 101 mlt2 := bin(nm, ir.OLT, liti(2)) // m < 2 nandand := typecheck.Expr(logical(nne101, ir.OANDAND, mlt2)) // all names used wantv := true v := ShouldFoldIfNameConstant(nandand, []*ir.Name{nn, nm}) if v != wantv { t.Errorf("wanted shouldfold(%v) %v, got %v", nandand, wantv, v) } // not all names used wantv = false v = ShouldFoldIfNameConstant(nne101, []*ir.Name{nn, nm}) if v != wantv { t.Errorf("wanted shouldfold(%v) %v, got %v", nne101, wantv, v) } // other names used. np := s.nmi64("p") pne0 := bin(np, ir.ONE, liti(101)) // p != 0 noror := logical(nandand, ir.OOROR, pne0) wantv = false v = ShouldFoldIfNameConstant(noror, []*ir.Name{nn, nm}) if v != wantv { t.Errorf("wanted shouldfold(%v) %v, got %v", noror, wantv, v) } } PK ! �x� � function_properties.gonu �[��� // 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 // This file defines a set of Go function "properties" intended to // guide inlining heuristics; these properties may apply to the // function as a whole, or to one or more function return values or // parameters. // // IMPORTANT: function properties are produced on a "best effort" // basis, meaning that the code that computes them doesn't verify that // the properties are guaranteed to be true in 100% of cases. For this // reason, properties should only be used to drive always-safe // optimization decisions (e.g. "should I inline this call", or // "should I unroll this loop") as opposed to potentially unsafe IR // alterations that could change program semantics (e.g. "can I delete // this variable" or "can I move this statement to a new location"). // //---------------------------------------------------------------- // FuncProps describes a set of function or method properties that may // be useful for inlining heuristics. Here 'Flags' are properties that // we think apply to the entire function; 'RecvrParamFlags' are // properties of specific function params (or the receiver), and // 'ResultFlags' are things properties we think will apply to values // of specific results. Note that 'ParamFlags' includes and entry for // the receiver if applicable, and does include etries for blank // params; for a function such as "func foo(_ int, b byte, _ float32)" // the length of ParamFlags will be 3. type FuncProps struct { Flags FuncPropBits ParamFlags []ParamPropBits // slot 0 receiver if applicable ResultFlags []ResultPropBits } type FuncPropBits uint32 const ( // Function always panics or invokes os.Exit() or a func that does // likewise. FuncPropNeverReturns FuncPropBits = 1 << iota ) type ParamPropBits uint32 const ( // No info about this param ParamNoInfo ParamPropBits = 0 // Parameter value feeds unmodified into a top-level interface // call (this assumes the parameter is of interface type). ParamFeedsInterfaceMethodCall ParamPropBits = 1 << iota // Parameter value feeds unmodified into an interface call that // may be conditional/nested and not always executed (this assumes // the parameter is of interface type). ParamMayFeedInterfaceMethodCall ParamPropBits = 1 << iota // Parameter value feeds unmodified into a top level indirect // function call (assumes parameter is of function type). ParamFeedsIndirectCall // Parameter value feeds unmodified into an indirect function call // that is conditional/nested (not guaranteed to execute). Assumes // parameter is of function type. ParamMayFeedIndirectCall // Parameter value feeds unmodified into a top level "switch" // statement or "if" statement simple expressions (see more on // "simple" expression classification below). ParamFeedsIfOrSwitch // Parameter value feeds unmodified into a "switch" or "if" // statement simple expressions (see more on "simple" expression // classification below), where the if/switch is // conditional/nested. ParamMayFeedIfOrSwitch ) type ResultPropBits uint32 const ( // No info about this result ResultNoInfo ResultPropBits = 0 // This result always contains allocated memory. ResultIsAllocatedMem ResultPropBits = 1 << iota // This result is always a single concrete type that is // implicitly converted to interface. ResultIsConcreteTypeConvertedToInterface // Result is always the same non-composite compile time constant. ResultAlwaysSameConstant // Result is always the same function or closure. ResultAlwaysSameFunc // Result is always the same (potentially) inlinable function or closure. ResultAlwaysSameInlinableFunc ) PK ! �{�% �% analyze_func_params.gonu �[��� // 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" ) // paramsAnalyzer holds state information for the phase that computes // flags for a Go functions parameters, for use in inline heuristics. // Note that the params slice below includes entries for blanks. type paramsAnalyzer struct { fname string values []ParamPropBits params []*ir.Name top []bool *condLevelTracker *nameFinder } // getParams returns an *ir.Name slice containing all params for the // function (plus rcvr as well if applicable). func getParams(fn *ir.Func) []*ir.Name { sig := fn.Type() numParams := sig.NumRecvs() + sig.NumParams() return fn.Dcl[:numParams] } // addParamsAnalyzer creates a new paramsAnalyzer helper object for // the function fn, appends it to the analyzers list, and returns the // new list. If the function in question doesn't have any interesting // parameters then the analyzer list is returned unchanged, and the // params flags in "fp" are updated accordingly. func addParamsAnalyzer(fn *ir.Func, analyzers []propAnalyzer, fp *FuncProps, nf *nameFinder) []propAnalyzer { pa, props := makeParamsAnalyzer(fn, nf) if pa != nil { analyzers = append(analyzers, pa) } else { fp.ParamFlags = props } return analyzers } // makeParamAnalyzer creates a new helper object to analyze parameters // of function fn. If the function doesn't have any interesting // params, a nil helper is returned along with a set of default param // flags for the func. func makeParamsAnalyzer(fn *ir.Func, nf *nameFinder) (*paramsAnalyzer, []ParamPropBits) { params := getParams(fn) // includes receiver if applicable if len(params) == 0 { return nil, nil } vals := make([]ParamPropBits, len(params)) if fn.Inl == nil { return nil, vals } top := make([]bool, len(params)) interestingToAnalyze := false for i, pn := range params { if pn == nil { continue } pt := pn.Type() if !pt.IsScalar() && !pt.HasNil() { // existing properties not applicable here (for things // like structs, arrays, slices, etc). continue } // If param is reassigned, skip it. if ir.Reassigned(pn) { continue } top[i] = true interestingToAnalyze = true } if !interestingToAnalyze { return nil, vals } if debugTrace&debugTraceParams != 0 { fmt.Fprintf(os.Stderr, "=-= param analysis of func %v:\n", fn.Sym().Name) for i := range vals { n := "_" if params[i] != nil { n = params[i].Sym().String() } fmt.Fprintf(os.Stderr, "=-= %d: %q %s top=%v\n", i, n, vals[i].String(), top[i]) } } pa := ¶msAnalyzer{ fname: fn.Sym().Name, values: vals, params: params, top: top, condLevelTracker: new(condLevelTracker), nameFinder: nf, } return pa, nil } func (pa *paramsAnalyzer) setResults(funcProps *FuncProps) { funcProps.ParamFlags = pa.values } func (pa *paramsAnalyzer) findParamIdx(n *ir.Name) int { if n == nil { panic("bad") } for i := range pa.params { if pa.params[i] == n { return i } } return -1 } type testfType func(x ir.Node, param *ir.Name, idx int) (bool, bool) // paramsAnalyzer invokes function 'testf' on the specified expression // 'x' for each parameter, and if the result is TRUE, or's 'flag' into // the flags for that param. func (pa *paramsAnalyzer) checkParams(x ir.Node, flag ParamPropBits, mayflag ParamPropBits, testf testfType) { for idx, p := range pa.params { if !pa.top[idx] && pa.values[idx] == ParamNoInfo { continue } result, may := testf(x, p, idx) if debugTrace&debugTraceParams != 0 { fmt.Fprintf(os.Stderr, "=-= test expr %v param %s result=%v flag=%s\n", x, p.Sym().Name, result, flag.String()) } if result { v := flag if pa.condLevel != 0 || may { v = mayflag } pa.values[idx] |= v pa.top[idx] = false } } } // foldCheckParams checks expression 'x' (an 'if' condition or // 'switch' stmt expr) to see if the expr would fold away if a // specific parameter had a constant value. func (pa *paramsAnalyzer) foldCheckParams(x ir.Node) { pa.checkParams(x, ParamFeedsIfOrSwitch, ParamMayFeedIfOrSwitch, func(x ir.Node, p *ir.Name, idx int) (bool, bool) { return ShouldFoldIfNameConstant(x, []*ir.Name{p}), false }) } // callCheckParams examines the target of call expression 'ce' to see // if it is making a call to the value passed in for some parameter. func (pa *paramsAnalyzer) callCheckParams(ce *ir.CallExpr) { switch ce.Op() { case ir.OCALLINTER: if ce.Op() != ir.OCALLINTER { return } sel := ce.Fun.(*ir.SelectorExpr) r := pa.staticValue(sel.X) if r.Op() != ir.ONAME { return } name := r.(*ir.Name) if name.Class != ir.PPARAM { return } pa.checkParams(r, ParamFeedsInterfaceMethodCall, ParamMayFeedInterfaceMethodCall, func(x ir.Node, p *ir.Name, idx int) (bool, bool) { name := x.(*ir.Name) return name == p, false }) case ir.OCALLFUNC: if ce.Fun.Op() != ir.ONAME { return } called := ir.StaticValue(ce.Fun) if called.Op() != ir.ONAME { return } name := called.(*ir.Name) if name.Class == ir.PPARAM { pa.checkParams(called, ParamFeedsIndirectCall, ParamMayFeedIndirectCall, func(x ir.Node, p *ir.Name, idx int) (bool, bool) { name := x.(*ir.Name) return name == p, false }) } else { cname := pa.funcName(called) if cname != nil { pa.deriveFlagsFromCallee(ce, cname.Func) } } } } // deriveFlagsFromCallee tries to derive flags for the current // function based on a call this function makes to some other // function. Example: // // /* Simple */ /* Derived from callee */ // func foo(f func(int)) { func foo(f func(int)) { // f(2) bar(32, f) // } } // func bar(x int, f func()) { // f(x) // } // // Here we can set the "param feeds indirect call" flag for // foo's param 'f' since we know that bar has that flag set for // its second param, and we're passing that param a function. func (pa *paramsAnalyzer) deriveFlagsFromCallee(ce *ir.CallExpr, callee *ir.Func) { calleeProps := propsForFunc(callee) if calleeProps == nil { return } if debugTrace&debugTraceParams != 0 { fmt.Fprintf(os.Stderr, "=-= callee props for %v:\n%s", callee.Sym().Name, calleeProps.String()) } must := []ParamPropBits{ParamFeedsInterfaceMethodCall, ParamFeedsIndirectCall, ParamFeedsIfOrSwitch} may := []ParamPropBits{ParamMayFeedInterfaceMethodCall, ParamMayFeedIndirectCall, ParamMayFeedIfOrSwitch} for pidx, arg := range ce.Args { // Does the callee param have any interesting properties? // If not we can skip this one. pflag := calleeProps.ParamFlags[pidx] if pflag == 0 { continue } // See if one of the caller's parameters is flowing unmodified // into this actual expression. r := pa.staticValue(arg) if r.Op() != ir.ONAME { return } name := r.(*ir.Name) if name.Class != ir.PPARAM { return } callerParamIdx := pa.findParamIdx(name) // note that callerParamIdx may return -1 in the case where // the param belongs not to the current closure func we're // analyzing but to an outer enclosing func. if callerParamIdx == -1 { return } if pa.params[callerParamIdx] == nil { panic("something went wrong") } if !pa.top[callerParamIdx] && pa.values[callerParamIdx] == ParamNoInfo { continue } if debugTrace&debugTraceParams != 0 { fmt.Fprintf(os.Stderr, "=-= pflag for arg %d is %s\n", pidx, pflag.String()) } for i := range must { mayv := may[i] mustv := must[i] if pflag&mustv != 0 && pa.condLevel == 0 { pa.values[callerParamIdx] |= mustv } else if pflag&(mustv|mayv) != 0 { pa.values[callerParamIdx] |= mayv } } pa.top[callerParamIdx] = false } } func (pa *paramsAnalyzer) nodeVisitPost(n ir.Node) { if len(pa.values) == 0 { return } pa.condLevelTracker.post(n) switch n.Op() { case ir.OCALLFUNC: ce := n.(*ir.CallExpr) pa.callCheckParams(ce) case ir.OCALLINTER: ce := n.(*ir.CallExpr) pa.callCheckParams(ce) case ir.OIF: ifst := n.(*ir.IfStmt) pa.foldCheckParams(ifst.Cond) case ir.OSWITCH: swst := n.(*ir.SwitchStmt) if swst.Tag != nil { pa.foldCheckParams(swst.Tag) } } } func (pa *paramsAnalyzer) nodeVisitPre(n ir.Node) { if len(pa.values) == 0 { return } pa.condLevelTracker.pre(n) } // condLevelTracker helps keeps track very roughly of "level of conditional // nesting", e.g. how many "if" statements you have to go through to // get to the point where a given stmt executes. Example: // // cond nesting level // func foo() { // G = 1 0 // if x < 10 { 0 // if y < 10 { 1 // G = 0 2 // } // } // } // // The intent here is to provide some sort of very abstract relative // hotness metric, e.g. "G = 1" above is expected to be executed more // often than "G = 0" (in the aggregate, across large numbers of // functions). type condLevelTracker struct { condLevel int } func (c *condLevelTracker) pre(n ir.Node) { // Increment level of "conditional testing" if we see // an "if" or switch statement, and decrement if in // a loop. switch n.Op() { case ir.OIF, ir.OSWITCH: c.condLevel++ case ir.OFOR, ir.ORANGE: c.condLevel-- } } func (c *condLevelTracker) post(n ir.Node) { switch n.Op() { case ir.OFOR, ir.ORANGE: c.condLevel++ case ir.OIF: c.condLevel-- case ir.OSWITCH: c.condLevel-- } } PK ! y�̡/ �/ score_callresult_uses.gonu �[��� // 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" ) // This file contains code to re-score callsites based on how the // results of the call were used. Example: // // func foo() { // x, fptr := bar() // switch x { // case 10: fptr = baz() // default: blix() // } // fptr(100) // } // // The initial scoring pass will assign a score to "bar()" based on // various criteria, however once the first pass of scoring is done, // we look at the flags on the result from bar, and check to see // how those results are used. If bar() always returns the same constant // for its first result, and if the variable receiving that result // isn't redefined, and if that variable feeds into an if/switch // condition, then we will try to adjust the score for "bar" (on the // theory that if we inlined, we can constant fold / deadcode). type resultPropAndCS struct { defcs *CallSite props ResultPropBits } type resultUseAnalyzer struct { resultNameTab map[*ir.Name]resultPropAndCS fn *ir.Func cstab CallSiteTab *condLevelTracker } // rescoreBasedOnCallResultUses examines how call results are used, // and tries to update the scores of calls based on how their results // are used in the function. func (csa *callSiteAnalyzer) rescoreBasedOnCallResultUses(fn *ir.Func, resultNameTab map[*ir.Name]resultPropAndCS, cstab CallSiteTab) { enableDebugTraceIfEnv() rua := &resultUseAnalyzer{ resultNameTab: resultNameTab, fn: fn, cstab: cstab, condLevelTracker: new(condLevelTracker), } var doNode func(ir.Node) bool doNode = func(n ir.Node) bool { rua.nodeVisitPre(n) ir.DoChildren(n, doNode) rua.nodeVisitPost(n) return false } doNode(fn) disableDebugTrace() } func (csa *callSiteAnalyzer) examineCallResults(cs *CallSite, resultNameTab map[*ir.Name]resultPropAndCS) map[*ir.Name]resultPropAndCS { if debugTrace&debugTraceScoring != 0 { fmt.Fprintf(os.Stderr, "=-= examining call results for %q\n", EncodeCallSiteKey(cs)) } // Invoke a helper to pick out the specific ir.Name's the results // from this call are assigned into, e.g. "x, y := fooBar()". If // the call is not part of an assignment statement, or if the // variables in question are not newly defined, then we'll receive // an empty list here. // names, autoTemps, props := namesDefined(cs) if len(names) == 0 { return resultNameTab } if debugTrace&debugTraceScoring != 0 { fmt.Fprintf(os.Stderr, "=-= %d names defined\n", len(names)) } // For each returned value, if the value has interesting // properties (ex: always returns the same constant), and the name // in question is never redefined, then make an entry in the // result table for it. const interesting = (ResultIsConcreteTypeConvertedToInterface | ResultAlwaysSameConstant | ResultAlwaysSameInlinableFunc | ResultAlwaysSameFunc) for idx, n := range names { rprop := props.ResultFlags[idx] if debugTrace&debugTraceScoring != 0 { fmt.Fprintf(os.Stderr, "=-= props for ret %d %q: %s\n", idx, n.Sym().Name, rprop.String()) } if rprop&interesting == 0 { continue } if csa.nameFinder.reassigned(n) { continue } if resultNameTab == nil { resultNameTab = make(map[*ir.Name]resultPropAndCS) } else if _, ok := resultNameTab[n]; ok { panic("should never happen") } entry := resultPropAndCS{ defcs: cs, props: rprop, } resultNameTab[n] = entry if autoTemps[idx] != nil { resultNameTab[autoTemps[idx]] = entry } if debugTrace&debugTraceScoring != 0 { fmt.Fprintf(os.Stderr, "=-= add resultNameTab table entry n=%v autotemp=%v props=%s\n", n, autoTemps[idx], rprop.String()) } } return resultNameTab } // namesDefined returns a list of ir.Name's corresponding to locals // that receive the results from the call at site 'cs', plus the // properties object for the called function. If a given result // isn't cleanly assigned to a newly defined local, the // slot for that result in the returned list will be nil. Example: // // call returned name list // // x := foo() [ x ] // z, y := bar() [ nil, nil ] // _, q := baz() [ nil, q ] // // In the case of a multi-return call, such as "x, y := foo()", // the pattern we see from the front end will be a call op // assigning to auto-temps, and then an assignment of the auto-temps // to the user-level variables. In such cases we return // first the user-level variable (in the first func result) // and then the auto-temp name in the second result. func namesDefined(cs *CallSite) ([]*ir.Name, []*ir.Name, *FuncProps) { // If this call doesn't feed into an assignment (and of course not // all calls do), then we don't have anything to work with here. if cs.Assign == nil { return nil, nil, nil } funcInlHeur, ok := fpmap[cs.Callee] if !ok { // TODO: add an assert/panic here. return nil, nil, nil } if len(funcInlHeur.props.ResultFlags) == 0 { return nil, nil, nil } // Single return case. if len(funcInlHeur.props.ResultFlags) == 1 { asgn, ok := cs.Assign.(*ir.AssignStmt) if !ok { return nil, nil, nil } // locate name being assigned aname, ok := asgn.X.(*ir.Name) if !ok { return nil, nil, nil } return []*ir.Name{aname}, []*ir.Name{nil}, funcInlHeur.props } // Multi-return case asgn, ok := cs.Assign.(*ir.AssignListStmt) if !ok || !asgn.Def { return nil, nil, nil } userVars := make([]*ir.Name, len(funcInlHeur.props.ResultFlags)) autoTemps := make([]*ir.Name, len(funcInlHeur.props.ResultFlags)) for idx, x := range asgn.Lhs { if n, ok := x.(*ir.Name); ok { userVars[idx] = n r := asgn.Rhs[idx] if r.Op() == ir.OCONVNOP { r = r.(*ir.ConvExpr).X } if ir.IsAutoTmp(r) { autoTemps[idx] = r.(*ir.Name) } if debugTrace&debugTraceScoring != 0 { fmt.Fprintf(os.Stderr, "=-= multi-ret namedef uv=%v at=%v\n", x, autoTemps[idx]) } } else { return nil, nil, nil } } return userVars, autoTemps, funcInlHeur.props } func (rua *resultUseAnalyzer) nodeVisitPost(n ir.Node) { rua.condLevelTracker.post(n) } func (rua *resultUseAnalyzer) nodeVisitPre(n ir.Node) { rua.condLevelTracker.pre(n) switch n.Op() { case ir.OCALLINTER: if debugTrace&debugTraceScoring != 0 { fmt.Fprintf(os.Stderr, "=-= rescore examine iface call %v:\n", n) } rua.callTargetCheckResults(n) case ir.OCALLFUNC: if debugTrace&debugTraceScoring != 0 { fmt.Fprintf(os.Stderr, "=-= rescore examine call %v:\n", n) } rua.callTargetCheckResults(n) case ir.OIF: ifst := n.(*ir.IfStmt) rua.foldCheckResults(ifst.Cond) case ir.OSWITCH: swst := n.(*ir.SwitchStmt) if swst.Tag != nil { rua.foldCheckResults(swst.Tag) } } } // callTargetCheckResults examines a given call to see whether the // callee expression is potentially an inlinable function returned // from a potentially inlinable call. Examples: // // Scenario 1: named intermediate // // fn1 := foo() conc := bar() // fn1("blah") conc.MyMethod() // // Scenario 2: returned func or concrete object feeds directly to call // // foo()("blah") bar().MyMethod() // // In the second case although at the source level the result of the // direct call feeds right into the method call or indirect call, // we're relying on the front end having inserted an auto-temp to // capture the value. func (rua *resultUseAnalyzer) callTargetCheckResults(call ir.Node) { ce := call.(*ir.CallExpr) rname := rua.getCallResultName(ce) if rname == nil { return } if debugTrace&debugTraceScoring != 0 { fmt.Fprintf(os.Stderr, "=-= staticvalue returns %v:\n", rname) } if rname.Class != ir.PAUTO { return } switch call.Op() { case ir.OCALLINTER: if debugTrace&debugTraceScoring != 0 { fmt.Fprintf(os.Stderr, "=-= in %s checking %v for cci prop:\n", rua.fn.Sym().Name, rname) } if cs := rua.returnHasProp(rname, ResultIsConcreteTypeConvertedToInterface); cs != nil { adj := returnFeedsConcreteToInterfaceCallAdj cs.Score, cs.ScoreMask = adjustScore(adj, cs.Score, cs.ScoreMask) } case ir.OCALLFUNC: if debugTrace&debugTraceScoring != 0 { fmt.Fprintf(os.Stderr, "=-= in %s checking %v for samefunc props:\n", rua.fn.Sym().Name, rname) v, ok := rua.resultNameTab[rname] if !ok { fmt.Fprintf(os.Stderr, "=-= no entry for %v in rt\n", rname) } else { fmt.Fprintf(os.Stderr, "=-= props for %v: %q\n", rname, v.props.String()) } } if cs := rua.returnHasProp(rname, ResultAlwaysSameInlinableFunc); cs != nil { adj := returnFeedsInlinableFuncToIndCallAdj cs.Score, cs.ScoreMask = adjustScore(adj, cs.Score, cs.ScoreMask) } else if cs := rua.returnHasProp(rname, ResultAlwaysSameFunc); cs != nil { adj := returnFeedsFuncToIndCallAdj cs.Score, cs.ScoreMask = adjustScore(adj, cs.Score, cs.ScoreMask) } } } // foldCheckResults examines the specified if/switch condition 'cond' // to see if it refers to locals defined by a (potentially inlinable) // function call at call site C, and if so, whether 'cond' contains // only combinations of simple references to all of the names in // 'names' with selected constants + operators. If these criteria are // met, then we adjust the score for call site C to reflect the // fact that inlining will enable deadcode and/or constant propagation. // Note: for this heuristic to kick in, the names in question have to // be all from the same callsite. Examples: // // q, r := baz() x, y := foo() // switch q+r { a, b, c := bar() // ... if x && y && a && b && c { // } ... // } // // For the call to "baz" above we apply a score adjustment, but not // for the calls to "foo" or "bar". func (rua *resultUseAnalyzer) foldCheckResults(cond ir.Node) { namesUsed := collectNamesUsed(cond) if len(namesUsed) == 0 { return } var cs *CallSite for _, n := range namesUsed { rpcs, found := rua.resultNameTab[n] if !found { return } if cs != nil && rpcs.defcs != cs { return } cs = rpcs.defcs if rpcs.props&ResultAlwaysSameConstant == 0 { return } } if debugTrace&debugTraceScoring != 0 { nls := func(nl []*ir.Name) string { r := "" for _, n := range nl { r += " " + n.Sym().Name } return r } fmt.Fprintf(os.Stderr, "=-= calling ShouldFoldIfNameConstant on names={%s} cond=%v\n", nls(namesUsed), cond) } if !ShouldFoldIfNameConstant(cond, namesUsed) { return } adj := returnFeedsConstToIfAdj cs.Score, cs.ScoreMask = adjustScore(adj, cs.Score, cs.ScoreMask) } func collectNamesUsed(expr ir.Node) []*ir.Name { res := []*ir.Name{} ir.Visit(expr, func(n ir.Node) { if n.Op() != ir.ONAME { return } nn := n.(*ir.Name) if nn.Class != ir.PAUTO { return } res = append(res, nn) }) return res } func (rua *resultUseAnalyzer) returnHasProp(name *ir.Name, prop ResultPropBits) *CallSite { v, ok := rua.resultNameTab[name] if !ok { return nil } if v.props&prop == 0 { return nil } return v.defcs } func (rua *resultUseAnalyzer) getCallResultName(ce *ir.CallExpr) *ir.Name { var callTarg ir.Node if sel, ok := ce.Fun.(*ir.SelectorExpr); ok { // method call callTarg = sel.X } else if ctarg, ok := ce.Fun.(*ir.Name); ok { // regular call callTarg = ctarg } else { return nil } r := ir.StaticValue(callTarg) if debugTrace&debugTraceScoring != 0 { fmt.Fprintf(os.Stderr, "=-= staticname on %v returns %v:\n", callTarg, r) } if r.Op() == ir.OCALLFUNC { // This corresponds to the "x := foo()" case; here // ir.StaticValue has brought us all the way back to // the call expression itself. We need to back off to // the name defined by the call; do this by looking up // the callsite. ce := r.(*ir.CallExpr) cs, ok := rua.cstab[ce] if !ok { return nil } names, _, _ := namesDefined(cs) if len(names) == 0 { return nil } return names[0] } else if r.Op() == ir.ONAME { return r.(*ir.Name) } return nil } PK ! ���� � tserial_test.gonu �[��� // 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 "testing" func fpeq(fp1, fp2 FuncProps) bool { if fp1.Flags != fp2.Flags { return false } if len(fp1.ParamFlags) != len(fp2.ParamFlags) { return false } for i := range fp1.ParamFlags { if fp1.ParamFlags[i] != fp2.ParamFlags[i] { return false } } if len(fp1.ResultFlags) != len(fp2.ResultFlags) { return false } for i := range fp1.ResultFlags { if fp1.ResultFlags[i] != fp2.ResultFlags[i] { return false } } return true } func TestSerDeser(t *testing.T) { testcases := []FuncProps{ FuncProps{}, FuncProps{ Flags: 0xfffff, }, FuncProps{ Flags: 1, ResultFlags: []ResultPropBits{ResultAlwaysSameConstant}, }, FuncProps{ Flags: 1, ParamFlags: []ParamPropBits{0x99, 0xaa, 0xfffff}, ResultFlags: []ResultPropBits{0xfeedface}, }, } for k, tc := range testcases { s := tc.SerializeToString() fp := DeserializeFromString(s) got := fp.String() want := tc.String() if !fpeq(*fp, tc) { t.Errorf("eq check failed for test %d: got:\n%s\nwant:\n%s\n", k, got, want) } } var nilt *FuncProps ns := nilt.SerializeToString() nfp := DeserializeFromString(ns) if len(ns) != 0 || nfp != nil { t.Errorf("nil serialize/deserialize failed") } } PK ! �C�(� � funcprop_string.gonu �[��� // 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 ( "fmt" "strings" ) func (fp *FuncProps) String() string { return fp.ToString("") } func (fp *FuncProps) ToString(prefix string) string { var sb strings.Builder if fp.Flags != 0 { fmt.Fprintf(&sb, "%sFlags %s\n", prefix, fp.Flags) } flagSliceToSB[ParamPropBits](&sb, fp.ParamFlags, prefix, "ParamFlags") flagSliceToSB[ResultPropBits](&sb, fp.ResultFlags, prefix, "ResultFlags") return sb.String() } func flagSliceToSB[T interface { ~uint32 String() string }](sb *strings.Builder, sl []T, prefix string, tag string) { var sb2 strings.Builder foundnz := false fmt.Fprintf(&sb2, "%s%s\n", prefix, tag) for i, e := range sl { if e != 0 { foundnz = true } fmt.Fprintf(&sb2, "%s %d %s\n", prefix, i, e.String()) } if foundnz { sb.WriteString(sb2.String()) } } PK ! �U,<