--- /dev/null
+// 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"
+ "encoding/json"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+)
+
+const (
+ debugTraceFuncs = 1 << iota
+)
+
+// fnInlHeur contains inline heuristics state information about
+// a specific Go function being analyzed/considered by the inliner.
+type fnInlHeur struct {
+ fname string
+ file string
+ line uint
+ props *FuncProps
+}
+
+// computeFuncProps examines the Go function 'fn' and computes for it
+// a function "properties" object, to be used to drive inlining
+// heuristics. See comments on the FuncProps type for more info.
+func computeFuncProps(fn *ir.Func) *FuncProps {
+ if debugTrace&debugTraceFuncs != 0 {
+ fmt.Fprintf(os.Stderr, "=-= starting analysis of func %v:\n%+v\n",
+ fn.Sym().Name, fn)
+ }
+ // implementation stubbed out for now
+ return &FuncProps{}
+}
+
+func fnFileLine(fn *ir.Func) (string, uint) {
+ p := base.Ctxt.InnermostPos(fn.Pos())
+ return filepath.Base(p.Filename()), p.Line()
+}
+
+// DumpFuncProps computes and caches function properties for the func
+// 'fn', or if fn is nil, writes out the cached set of properties to
+// the file given in 'dumpfile'. Used for the "-d=dumpinlfuncprops=..."
+// command line flag, intended for use primarily in unit testing.
+func DumpFuncProps(fn *ir.Func, dumpfile string) {
+ if fn != nil {
+ captureFuncDumpEntry(fn)
+ } else {
+ emitDumpToFile(dumpfile)
+ }
+}
+
+// emitDumpToFile writes out the buffer function property dump entries
+// to a file, for unit testing. Dump entries need to be sorted by
+// definition line, and due to generics we need to account for the
+// possibility that several ir.Func's will have the same def line.
+func emitDumpToFile(dumpfile string) {
+ outf, err := os.OpenFile(dumpfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
+ if err != nil {
+ base.Fatalf("opening function props dump file %q: %v\n", dumpfile, err)
+ }
+ defer outf.Close()
+ dumpFilePreamble(outf)
+
+ atline := map[uint]uint{}
+ sl := make([]fnInlHeur, 0, len(dumpBuffer))
+ for _, e := range dumpBuffer {
+ sl = append(sl, e)
+ atline[e.line] = atline[e.line] + 1
+ }
+ sl = sortFnInlHeurSlice(sl)
+
+ prevline := uint(0)
+ for _, entry := range sl {
+ idx := uint(0)
+ if prevline == entry.line {
+ idx++
+ }
+ prevline = entry.line
+ atl := atline[entry.line]
+ if err := dumpFnPreamble(outf, &entry, idx, atl); err != nil {
+ base.Fatalf("function props dump: %v\n", err)
+ }
+ }
+ dumpBuffer = nil
+}
+
+// captureFuncDumpEntry analyzes function 'fn' and adds a entry
+// for it to 'dumpBuffer'. Used for unit testing.
+func captureFuncDumpEntry(fn *ir.Func) {
+ // avoid capturing compiler-generated equality funcs.
+ if strings.HasPrefix(fn.Sym().Name, ".eq.") {
+ return
+ }
+ if dumpBuffer == nil {
+ dumpBuffer = make(map[*ir.Func]fnInlHeur)
+ }
+ if _, ok := dumpBuffer[fn]; ok {
+ // we can wind up seeing closures multiple times here,
+ // so don't add them more than once.
+ return
+ }
+ fp := computeFuncProps(fn)
+ file, line := fnFileLine(fn)
+ entry := fnInlHeur{
+ fname: fn.Sym().Name,
+ file: file,
+ line: line,
+ props: fp,
+ }
+ dumpBuffer[fn] = entry
+}
+
+// dumpFilePreamble writes out a file-level preamble for a given
+// Go function as part of a function properties dump.
+func dumpFilePreamble(w io.Writer) {
+ fmt.Fprintf(w, "// DO NOT EDIT (use 'go test -v -update-expected' instead.)\n")
+ fmt.Fprintf(w, "// See cmd/compile/internal/inline/inlheur/testdata/props/README.txt\n")
+ fmt.Fprintf(w, "// for more information on the format of this file.\n")
+ fmt.Fprintf(w, "// %s\n", preambleDelimiter)
+}
+
+// dumpFilePreamble writes out a function-level preamble for a given
+// Go function as part of a function properties dump. See the
+// README.txt file in testdata/props for more on the format of
+// this preamble.
+func dumpFnPreamble(w io.Writer, fih *fnInlHeur, idx, atl uint) error {
+ fmt.Fprintf(w, "// %s %s %d %d %d\n",
+ fih.file, fih.fname, fih.line, idx, atl)
+ // emit props as comments, followed by delimiter
+ fmt.Fprintf(w, "%s// %s\n", fih.props.ToString("// "), comDelimiter)
+ data, err := json.Marshal(fih.props)
+ if err != nil {
+ return fmt.Errorf("marshall error %v\n", err)
+ }
+ fmt.Fprintf(w, "// %s\n// %s\n", string(data), fnDelimiter)
+ return nil
+}
+
+// sortFnInlHeurSlice sorts a slice of fnInlHeur based on
+// the starting line of the function definition, then by name.
+func sortFnInlHeurSlice(sl []fnInlHeur) []fnInlHeur {
+ sort.SliceStable(sl, func(i, j int) bool {
+ if sl[i].line != sl[j].line {
+ return sl[i].line < sl[j].line
+ }
+ return sl[i].fname < sl[j].fname
+ })
+ return sl
+}
+
+// delimiters written to various preambles to make parsing of
+// dumps easier.
+const preambleDelimiter = "<endfilepreamble>"
+const fnDelimiter = "<endfuncpreamble>"
+const comDelimiter = "<endpropsdump>"
+
+// dumpBuffer stores up function properties dumps when
+// "-d=dumpinlfuncprops=..." is in effect.
+var dumpBuffer map[*ir.Func]fnInlHeur
--- /dev/null
+// 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 (
+ "bufio"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "internal/testenv"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "testing"
+ "time"
+)
+
+var remasterflag = flag.Bool("update-expected", false, "if true, generate updated golden results in testcases for all props tests")
+
+func TestFuncProperties(t *testing.T) {
+ td := t.TempDir()
+ //td = "/tmp/qqq"
+ //os.RemoveAll(td)
+ //os.Mkdir(td, 0777)
+ testenv.MustHaveGoBuild(t)
+
+ // NOTE: this testpoint has the unfortunate characteristic that it
+ // relies on the installed compiler, meaning that if you make
+ // changes to the inline heuristics code in your working copy and
+ // then run the test, it will test the installed compiler and not
+ // your local modifications. TODO: decide whether to convert this
+ // to building a fresh compiler on the fly, or using some other
+ // scheme.
+
+ testcases := []string{"stub"}
+
+ for _, tc := range testcases {
+ dumpfile, err := gatherPropsDumpForFile(t, tc, td)
+ if err != nil {
+ t.Fatalf("dumping func props for %q: error %v", tc, err)
+ }
+ // Read in the newly generated dump.
+ dentries, derr := readDump(t, dumpfile)
+ if derr != nil {
+ t.Fatalf("reading func prop dump: %v", derr)
+ }
+ if *remasterflag {
+ updateExpected(t, tc, dentries)
+ continue
+ }
+ // Generate expected dump.
+ epath, gerr := genExpected(td, tc)
+ if gerr != nil {
+ t.Fatalf("generating expected func prop dump: %v", gerr)
+ }
+ // Read in the expected result entries.
+ eentries, eerr := readDump(t, epath)
+ if eerr != nil {
+ t.Fatalf("reading expected func prop dump: %v", eerr)
+ }
+ // Compare new vs expected.
+ n := len(dentries)
+ eidx := 0
+ for i := 0; i < n; i++ {
+ dentry := dentries[i]
+ if !interestingToCompare(dentry.fname) {
+ continue
+ }
+ if eidx >= len(eentries) {
+ t.Errorf("missing expected entry for %s, skipping",
+ dentry.fname)
+ continue
+ }
+ eentry := eentries[eidx]
+ eidx++
+ if dentry.fname != eentry.fname {
+ t.Errorf("got fn %q wanted %q, skipping checks",
+ dentry.fname, eentry.fname)
+ continue
+ }
+ compareEntries(t, tc, &dentry, &eentry)
+ }
+ }
+}
+
+func propBitsToString[T interface{ String() string }](sl []T) string {
+ var sb strings.Builder
+ for i, f := range sl {
+ fmt.Fprintf(&sb, "%d: %s\n", i, f.String())
+ }
+ return sb.String()
+}
+
+func compareEntries(t *testing.T, tc string, dentry *fnInlHeur, eentry *fnInlHeur) {
+ dfp := dentry.props
+ efp := eentry.props
+ dfn := dentry.fname
+
+ // Compare function flags.
+ if dfp.Flags != efp.Flags {
+ t.Errorf("testcase %s: Flags mismatch for %q: got %s, wanted %s",
+ tc, dfn, dfp.Flags.String(), efp.Flags.String())
+ }
+ // Compare returns
+ rgot := propBitsToString[ResultPropBits](dfp.ResultFlags)
+ rwant := propBitsToString[ResultPropBits](efp.ResultFlags)
+ if rgot != rwant {
+ t.Errorf("Results mismatch for %q: got:\n%swant:\n%s",
+ dfn, rgot, rwant)
+ }
+ // Compare receiver + params.
+ pgot := propBitsToString[ParamPropBits](dfp.ParamFlags)
+ pwant := propBitsToString[ParamPropBits](efp.ParamFlags)
+ if pgot != pwant {
+ t.Errorf("Params mismatch for %q: got:\n%swant:\n%s",
+ dfn, pgot, pwant)
+ }
+}
+
+type dumpReader struct {
+ s *bufio.Scanner
+ t *testing.T
+ p string
+ ln int
+}
+
+// readDump reads in the contents of a dump file produced
+// by the "-d=dumpinlfuncprops=..." command line flag by the Go
+// compiler. It breaks the dump down into separate sections
+// by function, then deserializes each func section into a
+// fnInlHeur object and returns a slice of those objects.
+func readDump(t *testing.T, path string) ([]fnInlHeur, error) {
+ content, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ dr := &dumpReader{
+ s: bufio.NewScanner(strings.NewReader(string(content))),
+ t: t,
+ p: path,
+ ln: 1,
+ }
+ // consume header comment until preamble delimiter.
+ found := false
+ for dr.scan() {
+ if dr.curLine() == preambleDelimiter {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return nil, fmt.Errorf("malformed testcase file %s, missing preamble delimiter", path)
+ }
+ res := []fnInlHeur{}
+ for {
+ dentry, err := dr.readEntry()
+ if err != nil {
+ t.Fatalf("reading func prop dump: %v", err)
+ }
+ if dentry.fname == "" {
+ break
+ }
+ res = append(res, dentry)
+ }
+ return res, nil
+}
+
+func (dr *dumpReader) scan() bool {
+ v := dr.s.Scan()
+ if v {
+ dr.ln++
+ }
+ return v
+}
+
+func (dr *dumpReader) curLine() string {
+ res := strings.TrimSpace(dr.s.Text())
+ if !strings.HasPrefix(res, "// ") {
+ dr.t.Fatalf("malformed line %s:%d, no comment: %s", dr.p, dr.ln, res)
+ }
+ return res[3:]
+}
+
+// readObjBlob reads in a series of commented lines until
+// it hits a delimiter, then returns the contents of the comments.
+func (dr *dumpReader) readObjBlob(delim string) (string, error) {
+ var sb strings.Builder
+ foundDelim := false
+ for dr.scan() {
+ line := dr.curLine()
+ if delim == line {
+ foundDelim = true
+ break
+ }
+ sb.WriteString(line + "\n")
+ }
+ if err := dr.s.Err(); err != nil {
+ return "", err
+ }
+ if !foundDelim {
+ return "", fmt.Errorf("malformed input %s, missing delimiter %q",
+ dr.p, delim)
+ }
+ return sb.String(), nil
+}
+
+// readEntry reads a single function's worth of material from
+// a file produced by the "-d=dumpinlfuncprops=..." command line
+// flag. It deserializes the json for the func properties and
+// returns the resulting properties and function name. EOF is
+// signaled by a nil FuncProps return (with no error
+func (dr *dumpReader) readEntry() (fnInlHeur, error) {
+ var fih fnInlHeur
+ if !dr.scan() {
+ return fih, nil
+ }
+ // first line contains info about function: file/name/line
+ info := dr.curLine()
+ chunks := strings.Fields(info)
+ fih.file = chunks[0]
+ fih.fname = chunks[1]
+ if _, err := fmt.Sscanf(chunks[2], "%d", &fih.line); err != nil {
+ return fih, err
+ }
+ // consume comments until and including delimiter
+ for {
+ if !dr.scan() {
+ break
+ }
+ if dr.curLine() == comDelimiter {
+ break
+ }
+ }
+
+ // Consume JSON for encoded props.
+ dr.scan()
+ line := dr.curLine()
+ fp := &FuncProps{}
+ if err := json.Unmarshal([]byte(line), fp); err != nil {
+ return fih, err
+ }
+ fih.props = fp
+
+ // Consume delimiter.
+ dr.scan()
+ line = dr.curLine()
+ if line != fnDelimiter {
+ return fih, fmt.Errorf("malformed testcase file %q, missing delimiter %q", dr.p, fnDelimiter)
+ }
+
+ return fih, nil
+}
+
+// gatherPropsDumpForFile builds the specified testcase 'testcase' from
+// testdata/props passing the "-d=dumpinlfuncprops=..." compiler option,
+// to produce a properties dump, then returns the path of the newly
+// created file. NB: we can't use "go tool compile" here, since
+// some of the test cases import stdlib packages (such as "os").
+// This means using "go build", which is problematic since the
+// Go command can potentially cache the results of the compile step,
+// causing the test to fail when being run interactively. E.g.
+//
+// $ rm -f dump.txt
+// $ go build -o foo.a -gcflags=-d=dumpinlfuncprops=dump.txt foo.go
+// $ rm -f dump.txt foo.a
+// $ go build -o foo.a -gcflags=-d=dumpinlfuncprops=dump.txt foo.go
+// $ ls foo.a dump.txt > /dev/null
+// ls : cannot access 'dump.txt': No such file or directory
+// $
+//
+// For this reason, pick a unique filename for the dump, so as to
+// defeat the caching.
+func gatherPropsDumpForFile(t *testing.T, testcase string, td string) (string, error) {
+ t.Helper()
+ gopath := "testdata/props/" + testcase + ".go"
+ outpath := filepath.Join(td, testcase+".a")
+ salt := fmt.Sprintf(".p%dt%d", os.Getpid(), time.Now().UnixNano())
+ dumpfile := filepath.Join(td, testcase+salt+".dump.txt")
+ run := []string{testenv.GoToolPath(t), "build",
+ "-gcflags=-d=dumpinlfuncprops=" + dumpfile, "-o", outpath, gopath}
+ out, err := testenv.Command(t, run[0], run[1:]...).CombinedOutput()
+ if strings.TrimSpace(string(out)) != "" {
+ t.Logf("%s", out)
+ }
+ return dumpfile, err
+}
+
+// genExpected reads in a given Go testcase file, strips out all the
+// unindented (column 0) commands, writes them out to a new file, and
+// returns the path of that new file. By picking out just the comments
+// from the Go file we wind up with something that resembles the
+// output from a "-d=dumpinlfuncprops=..." compilation.
+func genExpected(td string, testcase string) (string, error) {
+ epath := filepath.Join(td, testcase+".expected")
+ outf, err := os.OpenFile(epath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
+ if err != nil {
+ return "", err
+ }
+ gopath := "testdata/props/" + testcase + ".go"
+ content, err := os.ReadFile(gopath)
+ if err != nil {
+ return "", err
+ }
+ lines := strings.Split(string(content), "\n")
+ for _, line := range lines[3:] {
+ if !strings.HasPrefix(line, "// ") {
+ continue
+ }
+ fmt.Fprintf(outf, "%s\n", line)
+ }
+ if err := outf.Close(); err != nil {
+ return "", err
+ }
+ return epath, nil
+}
+
+type upexState struct {
+ dentries []fnInlHeur
+ newgolines []string
+ atline map[uint]uint
+}
+
+func mkUpexState(dentries []fnInlHeur) *upexState {
+ atline := make(map[uint]uint)
+ for _, e := range dentries {
+ atline[e.line] = atline[e.line] + 1
+ }
+ return &upexState{
+ dentries: dentries,
+ atline: atline,
+ }
+}
+
+// updateExpected takes a given Go testcase file X.go and writes out a
+// new/updated version of the file to X.go.new, where the column-0
+// "expected" comments have been updated using fresh data from
+// "dentries".
+//
+// Writing of expected results is complicated by closures and by
+// generics, where you can have multiple functions that all share the
+// same starting line. Currently we combine up all the dups and
+// closures into the single pre-func comment.
+func updateExpected(t *testing.T, testcase string, dentries []fnInlHeur) {
+ nd := len(dentries)
+
+ ues := mkUpexState(dentries)
+
+ gopath := "testdata/props/" + testcase + ".go"
+ newgopath := "testdata/props/" + testcase + ".go.new"
+
+ // Read the existing Go file.
+ content, err := os.ReadFile(gopath)
+ if err != nil {
+ t.Fatalf("opening %s: %v", gopath, err)
+ }
+ golines := strings.Split(string(content), "\n")
+
+ // Preserve copyright.
+ ues.newgolines = append(ues.newgolines, golines[:4]...)
+ if !strings.HasPrefix(golines[0], "// Copyright") {
+ t.Fatalf("missing copyright from existing testcase")
+ }
+ golines = golines[4:]
+
+ clore := regexp.MustCompile(`.+\.func\d+[\.\d]*$`)
+
+ emitFunc := func(e *fnInlHeur, instance, atl uint) {
+ var sb strings.Builder
+ dumpFnPreamble(&sb, e, instance, atl)
+ ues.newgolines = append(ues.newgolines,
+ strings.Split(strings.TrimSpace(sb.String()), "\n")...)
+ }
+
+ // Write file preamble with "DO NOT EDIT" message and such.
+ var sb strings.Builder
+ dumpFilePreamble(&sb)
+ ues.newgolines = append(ues.newgolines,
+ strings.Split(strings.TrimSpace(sb.String()), "\n")...)
+
+ // Helper to add a clump of functions to the output file.
+ processClump := func(idx int, emit bool) int {
+ // Process func itself, plus anything else defined
+ // on the same line
+ atl := ues.atline[dentries[idx].line]
+ for k := uint(0); k < atl; k++ {
+ if emit {
+ emitFunc(&dentries[idx], k, atl)
+ }
+ idx++
+ }
+ // now process any closures it contains
+ ncl := 0
+ for idx < nd {
+ nfn := dentries[idx].fname
+ if !clore.MatchString(nfn) {
+ break
+ }
+ ncl++
+ if emit {
+ emitFunc(&dentries[idx], 0, 1)
+ }
+ idx++
+ }
+ return idx
+ }
+
+ didx := 0
+ for _, line := range golines {
+ if strings.HasPrefix(line, "func ") {
+
+ // We have a function definition.
+ // Pick out the corresponding entry or entries in the dump
+ // and emit if interesting (or skip if not).
+ dentry := dentries[didx]
+ emit := interestingToCompare(dentry.fname)
+ didx = processClump(didx, emit)
+ }
+
+ // Consume all existing comments.
+ if strings.HasPrefix(line, "//") {
+ continue
+ }
+ ues.newgolines = append(ues.newgolines, line)
+ }
+
+ if didx != nd {
+ t.Logf("didx=%d wanted %d", didx, nd)
+ }
+
+ // Open new Go file and write contents.
+ of, err := os.OpenFile(newgopath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
+ if err != nil {
+ t.Fatalf("opening %s: %v", newgopath, err)
+ }
+ fmt.Fprintf(of, "%s", strings.Join(ues.newgolines, "\n"))
+ if err := of.Close(); err != nil {
+ t.Fatalf("closing %s: %v", newgopath, err)
+ }
+
+ t.Logf("update-expected: emitted updated file %s", newgopath)
+ t.Logf("please compare the two files, then overwrite %s with %s\n",
+ gopath, newgopath)
+}
+
+// interestingToCompare returns TRUE if we want to compare results
+// for function 'fname'.
+func interestingToCompare(fname string) bool {
+ if strings.HasPrefix(fname, "init.") {
+ return true
+ }
+ if strings.HasPrefix(fname, "T_") {
+ return true
+ }
+ f := strings.Split(fname, ".")
+ if len(f) == 2 && strings.HasPrefix(f[1], "T_") {
+ return true
+ }
+ return false
+}
--- /dev/null
+// 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.
+
+Notes on the format of the testcase files in
+cmd/compile/internal/inline/inlheur/testdata/props:
+
+- each (compilable) file contains input Go code and expected results
+ in the form of column-0 comments.
+
+- functions or methods that begin with "T_" are targeted for testing,
+ as well as "init" functions; all other functions are ignored.
+
+- function header comments begin with a line containing
+ the file name, function name, definition line, then index
+ and a count of the number of funcs that share that same
+ definition line (needed to support generics). Example:
+
+ // foo.go T_mumble 35 1 4
+
+ Here "T_mumble" is defined at line 35, and it is func 0
+ out of the 4 funcs that share that same line.
+
+- function property expected results appear as comments in immediately
+ prior to the function. For example, here we have first the function
+ name ("T_feeds_if_simple"), then human-readable dump of the function
+ properties, as well as the JSON for the properties object, each
+ section separated by a "<>" delimiter.
+
+ // funcflags.go T_feeds_if_simple 35 0 1
+ // RecvrParamFlags:
+ // 0: ParamFeedsIfOrSwitch
+ // <endpropsdump>
+ // {"Flags":0,"RecvrParamFlags":[8],"ReturnFlags":[]}
+ // <endfuncpreamble>
+ func T_feeds_if_simple(x int) {
+ if x < 100 {
+ os.Exit(1)
+ }
+ println(x)
+ }
+
+- when the test runs, it will compile the Go source file with an
+ option to dump out function properties, then compare the new dump
+ for each function with the JSON appearing in the header comment for
+ the function (in the example above, the JSON appears between
+ "<endpropsdump>" and "<endfuncpreamble>". The material prior to the
+ dump is simply there for human consumption, so that a developer can
+ easily see that "RecvrParamFlags":[8] means that the first parameter
+ has flag ParamFeedsIfOrSwitch.
+
+- when making changes to the compiler (which can alter the expected
+ results) or edits/additions to the go code in the testcase files,
+ you can remaster the results by running
+
+ go test -v -count=1 .
+
+ In the trace output of this run, you'll see messages of the form
+
+ === RUN TestFuncProperties
+ funcprops_test.go:NNN: update-expected: emitted updated file
+ testdata/props/XYZ.go.new
+ funcprops_test.go:MMM: please compare the two files, then overwrite
+ testdata/props/XYZ.go with testdata/props/XYZ.go.new
+
+ at which point you can compare the old and new files by hand, then
+ overwrite the *.go file with the *.go.new file if you are happy with
+ the diffs.
+
+- note that the remastering process will strip out any existing
+ column-0 (unindented) comments; if you write comments that you
+ want to see preserved, use "/* */" or indent them.
+
+
+
--- /dev/null
+// 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.
+
+// DO NOT EDIT (use 'go test -v -update-expected' instead.)
+// See cmd/compile/internal/inline/inlheur/testdata/props/README.txt
+// for more information on the format of this file.
+// <endfilepreamble>
+
+package stub
+
+// stub.go T_stub 16 0 1
+// <endpropsdump>
+// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
+// <endfuncpreamble>
+func T_stub() {
+}
+
+func ThisFunctionShouldBeIgnored(x int) {
+ println(x)
+}
+
+// stub.go init.0 27 0 1
+// <endpropsdump>
+// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
+// <endfuncpreamble>
+func init() {
+ ThisFunctionShouldBeIgnored(1)
+}
+
+// stub.go T_contains_closures 43 0 1
+// <endpropsdump>
+// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
+// <endfuncpreamble>
+// stub.go T_contains_closures.func1 44 0 1
+// <endpropsdump>
+// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
+// <endfuncpreamble>
+// stub.go T_contains_closures.func2 46 0 1
+// <endpropsdump>
+// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
+// <endfuncpreamble>
+func T_contains_closures(q int) func() {
+ f := func() { M["a"] = 9 }
+ f()
+ f2 := func() { M["a"] = 4 }
+ if M["b"] != 9 {
+ return f
+ }
+ return f2
+}
+
+// stub.go T_Unique[go.shape.int] 69 0 4
+// <endpropsdump>
+// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
+// <endfuncpreamble>
+// stub.go T_Unique[go.shape.string] 69 1 4
+// <endpropsdump>
+// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
+// <endfuncpreamble>
+// stub.go T_Unique[int] 69 2 4
+// <endpropsdump>
+// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
+// <endfuncpreamble>
+// stub.go T_Unique[string] 69 3 4
+// <endpropsdump>
+// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
+// <endfuncpreamble>
+func T_Unique[T comparable](set []T) []T {
+ nset := make([]T, 0, 8)
+loop:
+ for _, s := range set {
+ for _, e := range nset {
+ if s == e {
+ continue loop
+ }
+ }
+ nset = append(nset, s)
+ }
+
+ return nset
+}
+
+// stub.go T_uniq_int_count 88 0 1
+// <endpropsdump>
+// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
+// <endfuncpreamble>
+func T_uniq_int_count(s []int) int {
+ return len(T_Unique[int](s))
+}
+
+// stub.go T_uniq_string_count 96 0 1
+// <endpropsdump>
+// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
+// <endfuncpreamble>
+func T_uniq_string_count(s []string) int {
+ return len(T_Unique[string](s))
+}
+
+// stub.go T_epilog 104 0 1
+// <endpropsdump>
+// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
+// <endfuncpreamble>
+func T_epilog() {
+}
+
+var M = map[string]int{}