mirror of
https://github.com/golang/go.git
synced 2024-09-22 10:58:58 +00:00
[dev.typeparams] all: merge dev.regabi (d9acf6f
) into dev.typeparams
Conflicts: - src/cmd/compile/fmtmap_test.go Merge List: + 2021-01-12d9acf6f3a3
[dev.regabi] cmd/compile: remove Func.ClosureType + 2021-01-1241352fd401
[dev.regabi] cmd/compile: transform closures during walk + 2021-01-12d6ad88b4db
[dev.regabi] cmd/compile: compile functions before closures + 2021-01-12432f9ffb11
[dev.regabi] cmd/compile: unindent compileFunctions + 2021-01-12cc90e7a51e
[dev.regabi] cmd/compile: always use the compile queue + 2021-01-12cd5b74d2df
[dev.regabi] cmd/compile: call NeedFuncSym in InitLSym + 2021-01-1295acd8121b
[dev.regabi] cmd/compile: remove Name.Typegen + 2021-01-1212ee55ba7b
[dev.regabi] cmd/compile: stop using Vargen for import/export + 2021-01-12b4d2a0445b
[dev.regabi] cmd/compile: refactor closure var setup/teardown + 2021-01-12f57f484053
[dev.regabi] cmd/compile: decouple escape analysis from Name.Vargen + 2021-01-107fd84c6e46
[dev.regabi] cmd/compile: remove OCLOSUREREAD + 2021-01-10c9c26d7ffb
[dev.regabi] cmd/compile: use ClosureVars for method value wrappers + 2021-01-10950cf4d46c
[dev.regabi] cmd/compile: bind closure vars during SSA constructions + 2021-01-108b2efa990b
[dev.regabi] cmd/compile: deref PAUTOHEAPs during SSA construction + 2021-01-086ee9b118a2
[dev.regabi] cmd/compile: remove fmt_test code; it has outlived its usefulness + 2021-01-08b241938e04
[dev.regabi] cmd/compile: fix some methods error text Change-Id: I9a530f9a78b16e2bb14ea0a4ecbd9a75f9350342
This commit is contained in:
commit
f065ff221b
@ -1,615 +0,0 @@
|
||||
// Copyright 2016 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.
|
||||
|
||||
// This file implements TestFormats; a test that verifies
|
||||
// format strings in the compiler (this directory and all
|
||||
// subdirectories, recursively).
|
||||
//
|
||||
// TestFormats finds potential (Printf, etc.) format strings.
|
||||
// If they are used in a call, the format verbs are verified
|
||||
// based on the matching argument type against a precomputed
|
||||
// map of valid formats (knownFormats). This map can be used to
|
||||
// automatically rewrite format strings across all compiler
|
||||
// files with the -r flag.
|
||||
//
|
||||
// The format map needs to be updated whenever a new (type,
|
||||
// format) combination is found and the format verb is not
|
||||
// 'v' or 'T' (as in "%v" or "%T"). To update the map auto-
|
||||
// matically from the compiler source's use of format strings,
|
||||
// use the -u flag. (Whether formats are valid for the values
|
||||
// to be formatted must be verified manually, of course.)
|
||||
//
|
||||
// The -v flag prints out the names of all functions called
|
||||
// with a format string, the names of files that were not
|
||||
// processed, and any format rewrites made (with -r).
|
||||
//
|
||||
// Run as: go test -run Formats [-r][-u][-v]
|
||||
//
|
||||
// Known shortcomings:
|
||||
// - indexed format strings ("%[2]s", etc.) are not supported
|
||||
// (the test will fail)
|
||||
// - format strings that are not simple string literals cannot
|
||||
// be updated automatically
|
||||
// (the test will fail with respective warnings)
|
||||
// - format strings in _test packages outside the current
|
||||
// package are not processed
|
||||
// (the test will report those files)
|
||||
//
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/constant"
|
||||
"go/format"
|
||||
"go/importer"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"internal/testenv"
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var (
|
||||
rewrite = flag.Bool("r", false, "rewrite format strings")
|
||||
update = flag.Bool("u", false, "update known formats")
|
||||
)
|
||||
|
||||
// The following variables collect information across all processed files.
|
||||
var (
|
||||
fset = token.NewFileSet()
|
||||
formatStrings = make(map[*ast.BasicLit]bool) // set of all potential format strings found
|
||||
foundFormats = make(map[string]bool) // set of all formats found
|
||||
callSites = make(map[*ast.CallExpr]*callSite) // map of all calls
|
||||
)
|
||||
|
||||
// A File is a corresponding (filename, ast) pair.
|
||||
type File struct {
|
||||
name string
|
||||
ast *ast.File
|
||||
}
|
||||
|
||||
func TestFormats(t *testing.T) {
|
||||
if testing.Short() && testenv.Builder() == "" {
|
||||
t.Skip("Skipping in short mode")
|
||||
}
|
||||
testenv.MustHaveGoBuild(t) // more restrictive than necessary, but that's ok
|
||||
|
||||
// process all directories
|
||||
filepath.WalkDir(".", func(path string, info fs.DirEntry, err error) error {
|
||||
if info.IsDir() {
|
||||
if info.Name() == "testdata" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
importPath := filepath.Join("cmd/compile", path)
|
||||
if ignoredPackages[filepath.ToSlash(importPath)] {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
pkg, err := build.Import(importPath, path, 0)
|
||||
if err != nil {
|
||||
if _, ok := err.(*build.NoGoError); ok {
|
||||
return nil // nothing to do here
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
collectPkgFormats(t, pkg)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// test and rewrite formats
|
||||
updatedFiles := make(map[string]File) // files that were rewritten
|
||||
for _, p := range callSites {
|
||||
// test current format literal and determine updated one
|
||||
out := formatReplace(p.str, func(index int, in string) string {
|
||||
if in == "*" {
|
||||
return in // cannot rewrite '*' (as in "%*d")
|
||||
}
|
||||
// in != '*'
|
||||
typ := p.types[index]
|
||||
format := typ + " " + in // e.g., "*Node %n"
|
||||
|
||||
// Do not bother reporting basic types, nor %v, %T, %p.
|
||||
// Vet handles basic types, and those three formats apply to all types.
|
||||
if !strings.Contains(typ, ".") || (in == "%v" || in == "%T" || in == "%p") {
|
||||
return in
|
||||
}
|
||||
|
||||
// check if format is known
|
||||
out, known := knownFormats[format]
|
||||
|
||||
// record format if not yet found
|
||||
_, found := foundFormats[format]
|
||||
if !found {
|
||||
foundFormats[format] = true
|
||||
}
|
||||
|
||||
// report an error if the format is unknown and this is the first
|
||||
// time we see it; ignore "%v" and "%T" which are always valid
|
||||
if !known && !found && in != "%v" && in != "%T" {
|
||||
t.Errorf("%s: unknown format %q for %s argument", posString(p.arg), in, typ)
|
||||
}
|
||||
|
||||
if out == "" {
|
||||
out = in
|
||||
}
|
||||
return out
|
||||
})
|
||||
|
||||
// replace existing format literal if it changed
|
||||
if out != p.str {
|
||||
// we cannot replace the argument if it's not a string literal for now
|
||||
// (e.g., it may be "foo" + "bar")
|
||||
lit, ok := p.arg.(*ast.BasicLit)
|
||||
if !ok {
|
||||
delete(callSites, p.call) // treat as if we hadn't found this site
|
||||
continue
|
||||
}
|
||||
|
||||
if testing.Verbose() {
|
||||
fmt.Printf("%s:\n\t- %q\n\t+ %q\n", posString(p.arg), p.str, out)
|
||||
}
|
||||
|
||||
// find argument index of format argument
|
||||
index := -1
|
||||
for i, arg := range p.call.Args {
|
||||
if p.arg == arg {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index < 0 {
|
||||
// we may have processed the same call site twice,
|
||||
// but that shouldn't happen
|
||||
panic("internal error: matching argument not found")
|
||||
}
|
||||
|
||||
// replace literal
|
||||
new := *lit // make a copy
|
||||
new.Value = strconv.Quote(out) // this may introduce "-quotes where there were `-quotes
|
||||
p.call.Args[index] = &new
|
||||
updatedFiles[p.file.name] = p.file
|
||||
}
|
||||
}
|
||||
|
||||
// write dirty files back
|
||||
var filesUpdated bool
|
||||
if len(updatedFiles) > 0 && *rewrite {
|
||||
for _, file := range updatedFiles {
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, fset, file.ast); err != nil {
|
||||
t.Errorf("WARNING: gofmt %s failed: %v", file.name, err)
|
||||
continue
|
||||
}
|
||||
if err := ioutil.WriteFile(file.name, buf.Bytes(), 0x666); err != nil {
|
||||
t.Errorf("WARNING: writing %s failed: %v", file.name, err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("updated %s\n", file.name)
|
||||
filesUpdated = true
|
||||
}
|
||||
}
|
||||
|
||||
// report the names of all functions called with a format string
|
||||
if len(callSites) > 0 && testing.Verbose() {
|
||||
set := make(map[string]bool)
|
||||
for _, p := range callSites {
|
||||
set[nodeString(p.call.Fun)] = true
|
||||
}
|
||||
var list []string
|
||||
for s := range set {
|
||||
list = append(list, s)
|
||||
}
|
||||
fmt.Println("\nFunctions called with a format string")
|
||||
writeList(os.Stdout, list)
|
||||
}
|
||||
|
||||
// update formats
|
||||
if len(foundFormats) > 0 && *update {
|
||||
var list []string
|
||||
for s := range foundFormats {
|
||||
list = append(list, fmt.Sprintf("%q: \"\",", s))
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(knownFormatsHeader)
|
||||
writeList(&buf, list)
|
||||
buf.WriteString("}\n")
|
||||
out, err := format.Source(buf.Bytes())
|
||||
const outfile = "fmtmap_test.go"
|
||||
if err != nil {
|
||||
t.Errorf("WARNING: gofmt %s failed: %v", outfile, err)
|
||||
out = buf.Bytes() // continue with unformatted source
|
||||
}
|
||||
if err = ioutil.WriteFile(outfile, out, 0644); err != nil {
|
||||
t.Errorf("WARNING: updating format map failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// check that knownFormats is up to date
|
||||
if !*rewrite && !*update {
|
||||
var mismatch bool
|
||||
for s := range foundFormats {
|
||||
if _, ok := knownFormats[s]; !ok {
|
||||
mismatch = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !mismatch {
|
||||
for s := range knownFormats {
|
||||
if _, ok := foundFormats[s]; !ok {
|
||||
mismatch = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if mismatch {
|
||||
t.Errorf("format map is out of date; run 'go test -u' to update and manually verify correctness of change'")
|
||||
}
|
||||
}
|
||||
|
||||
// all format strings of calls must be in the formatStrings set (self-verification)
|
||||
for _, p := range callSites {
|
||||
if lit, ok := p.arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
|
||||
if formatStrings[lit] {
|
||||
// ok
|
||||
delete(formatStrings, lit)
|
||||
} else {
|
||||
// this should never happen
|
||||
panic(fmt.Sprintf("internal error: format string not found (%s)", posString(lit)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we have any strings left, we may need to update them manually
|
||||
if len(formatStrings) > 0 && filesUpdated {
|
||||
var list []string
|
||||
for lit := range formatStrings {
|
||||
list = append(list, fmt.Sprintf("%s: %s", posString(lit), nodeString(lit)))
|
||||
}
|
||||
fmt.Println("\nWARNING: Potentially missed format strings")
|
||||
writeList(os.Stdout, list)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// A callSite describes a function call that appears to contain
|
||||
// a format string.
|
||||
type callSite struct {
|
||||
file File
|
||||
call *ast.CallExpr // call containing the format string
|
||||
arg ast.Expr // format argument (string literal or constant)
|
||||
str string // unquoted format string
|
||||
types []string // argument types
|
||||
}
|
||||
|
||||
func collectPkgFormats(t *testing.T, pkg *build.Package) {
|
||||
// collect all files
|
||||
var filenames []string
|
||||
filenames = append(filenames, pkg.GoFiles...)
|
||||
filenames = append(filenames, pkg.CgoFiles...)
|
||||
filenames = append(filenames, pkg.TestGoFiles...)
|
||||
|
||||
// TODO(gri) verify _test files outside package
|
||||
for _, name := range pkg.XTestGoFiles {
|
||||
// don't process this test itself
|
||||
if name != "fmt_test.go" && testing.Verbose() {
|
||||
fmt.Printf("WARNING: %s not processed\n", filepath.Join(pkg.Dir, name))
|
||||
}
|
||||
}
|
||||
|
||||
// make filenames relative to .
|
||||
for i, name := range filenames {
|
||||
filenames[i] = filepath.Join(pkg.Dir, name)
|
||||
}
|
||||
|
||||
// parse all files
|
||||
files := make([]*ast.File, len(filenames))
|
||||
for i, filename := range filenames {
|
||||
f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
files[i] = f
|
||||
}
|
||||
|
||||
// typecheck package
|
||||
conf := types.Config{Importer: importer.Default()}
|
||||
etypes := make(map[ast.Expr]types.TypeAndValue)
|
||||
if _, err := conf.Check(pkg.ImportPath, fset, files, &types.Info{Types: etypes}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// collect all potential format strings (for extra verification later)
|
||||
for _, file := range files {
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
if s, ok := stringLit(n); ok && isFormat(s) {
|
||||
formatStrings[n.(*ast.BasicLit)] = true
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// collect all formats/arguments of calls with format strings
|
||||
for index, file := range files {
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
if call, ok := n.(*ast.CallExpr); ok {
|
||||
if ignoredFunctions[nodeString(call.Fun)] {
|
||||
return true
|
||||
}
|
||||
// look for an arguments that might be a format string
|
||||
for i, arg := range call.Args {
|
||||
if s, ok := stringVal(etypes[arg]); ok && isFormat(s) {
|
||||
// make sure we have enough arguments
|
||||
n := numFormatArgs(s)
|
||||
if i+1+n > len(call.Args) {
|
||||
t.Errorf("%s: not enough format args (ignore %s?)", posString(call), nodeString(call.Fun))
|
||||
break // ignore this call
|
||||
}
|
||||
// assume last n arguments are to be formatted;
|
||||
// determine their types
|
||||
argTypes := make([]string, n)
|
||||
for i, arg := range call.Args[len(call.Args)-n:] {
|
||||
if tv, ok := etypes[arg]; ok {
|
||||
argTypes[i] = typeString(tv.Type)
|
||||
}
|
||||
}
|
||||
// collect call site
|
||||
if callSites[call] != nil {
|
||||
panic("internal error: file processed twice?")
|
||||
}
|
||||
callSites[call] = &callSite{
|
||||
file: File{filenames[index], file},
|
||||
call: call,
|
||||
arg: arg,
|
||||
str: s,
|
||||
types: argTypes,
|
||||
}
|
||||
break // at most one format per argument list
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// writeList writes list in sorted order to w.
|
||||
func writeList(w io.Writer, list []string) {
|
||||
sort.Strings(list)
|
||||
for _, s := range list {
|
||||
fmt.Fprintln(w, "\t", s)
|
||||
}
|
||||
}
|
||||
|
||||
// posString returns a string representation of n's position
|
||||
// in the form filename:line:col: .
|
||||
func posString(n ast.Node) string {
|
||||
if n == nil {
|
||||
return ""
|
||||
}
|
||||
return fset.Position(n.Pos()).String()
|
||||
}
|
||||
|
||||
// nodeString returns a string representation of n.
|
||||
func nodeString(n ast.Node) string {
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, fset, n); err != nil {
|
||||
log.Fatal(err) // should always succeed
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// typeString returns a string representation of n.
|
||||
func typeString(typ types.Type) string {
|
||||
s := filepath.ToSlash(typ.String())
|
||||
|
||||
// Report all the concrete IR types as Node, to shorten fmtmap.
|
||||
const ir = "cmd/compile/internal/ir."
|
||||
if s == "*"+ir+"Name" || s == "*"+ir+"Func" || s == "*"+ir+"Decl" ||
|
||||
s == ir+"Ntype" || s == ir+"Expr" || s == ir+"Stmt" ||
|
||||
strings.HasPrefix(s, "*"+ir) && (strings.HasSuffix(s, "Expr") || strings.HasSuffix(s, "Stmt")) {
|
||||
return "cmd/compile/internal/ir.Node"
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// stringLit returns the unquoted string value and true if
|
||||
// n represents a string literal; otherwise it returns ""
|
||||
// and false.
|
||||
func stringLit(n ast.Node) (string, bool) {
|
||||
if lit, ok := n.(*ast.BasicLit); ok && lit.Kind == token.STRING {
|
||||
s, err := strconv.Unquote(lit.Value)
|
||||
if err != nil {
|
||||
log.Fatal(err) // should not happen with correct ASTs
|
||||
}
|
||||
return s, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// stringVal returns the (unquoted) string value and true if
|
||||
// tv is a string constant; otherwise it returns "" and false.
|
||||
func stringVal(tv types.TypeAndValue) (string, bool) {
|
||||
if tv.IsValue() && tv.Value != nil && tv.Value.Kind() == constant.String {
|
||||
return constant.StringVal(tv.Value), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// formatIter iterates through the string s in increasing
|
||||
// index order and calls f for each format specifier '%..v'.
|
||||
// The arguments for f describe the specifier's index range.
|
||||
// If a format specifier contains a "*", f is called with
|
||||
// the index range for "*" alone, before being called for
|
||||
// the entire specifier. The result of f is the index of
|
||||
// the rune at which iteration continues.
|
||||
func formatIter(s string, f func(i, j int) int) {
|
||||
i := 0 // index after current rune
|
||||
var r rune // current rune
|
||||
|
||||
next := func() {
|
||||
r1, w := utf8.DecodeRuneInString(s[i:])
|
||||
if w == 0 {
|
||||
r1 = -1 // signal end-of-string
|
||||
}
|
||||
r = r1
|
||||
i += w
|
||||
}
|
||||
|
||||
flags := func() {
|
||||
for r == ' ' || r == '#' || r == '+' || r == '-' || r == '0' {
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
index := func() {
|
||||
if r == '[' {
|
||||
log.Fatalf("cannot handle indexed arguments: %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
digits := func() {
|
||||
index()
|
||||
if r == '*' {
|
||||
i = f(i-1, i)
|
||||
next()
|
||||
return
|
||||
}
|
||||
for '0' <= r && r <= '9' {
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
for next(); r >= 0; next() {
|
||||
if r == '%' {
|
||||
i0 := i
|
||||
next()
|
||||
flags()
|
||||
digits()
|
||||
if r == '.' {
|
||||
next()
|
||||
digits()
|
||||
}
|
||||
index()
|
||||
// accept any letter (a-z, A-Z) as format verb;
|
||||
// ignore anything else
|
||||
if 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' {
|
||||
i = f(i0-1, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isFormat reports whether s contains format specifiers.
|
||||
func isFormat(s string) (yes bool) {
|
||||
formatIter(s, func(i, j int) int {
|
||||
yes = true
|
||||
return len(s) // stop iteration
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// oneFormat reports whether s is exactly one format specifier.
|
||||
func oneFormat(s string) (yes bool) {
|
||||
formatIter(s, func(i, j int) int {
|
||||
yes = i == 0 && j == len(s)
|
||||
return j
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// numFormatArgs returns the number of format specifiers in s.
|
||||
func numFormatArgs(s string) int {
|
||||
count := 0
|
||||
formatIter(s, func(i, j int) int {
|
||||
count++
|
||||
return j
|
||||
})
|
||||
return count
|
||||
}
|
||||
|
||||
// formatReplace replaces the i'th format specifier s in the incoming
|
||||
// string in with the result of f(i, s) and returns the new string.
|
||||
func formatReplace(in string, f func(i int, s string) string) string {
|
||||
var buf []byte
|
||||
i0 := 0
|
||||
index := 0
|
||||
formatIter(in, func(i, j int) int {
|
||||
if sub := in[i:j]; sub != "*" { // ignore calls for "*" width/length specifiers
|
||||
buf = append(buf, in[i0:i]...)
|
||||
buf = append(buf, f(index, sub)...)
|
||||
i0 = j
|
||||
}
|
||||
index++
|
||||
return j
|
||||
})
|
||||
return string(append(buf, in[i0:]...))
|
||||
}
|
||||
|
||||
// ignoredPackages is the set of packages which can
|
||||
// be ignored.
|
||||
var ignoredPackages = map[string]bool{}
|
||||
|
||||
// ignoredFunctions is the set of functions which may have
|
||||
// format-like arguments but which don't do any formatting and
|
||||
// thus may be ignored.
|
||||
var ignoredFunctions = map[string]bool{}
|
||||
|
||||
func init() {
|
||||
// verify that knownFormats entries are correctly formatted
|
||||
for key, val := range knownFormats {
|
||||
// key must be "typename format", and format starts with a '%'
|
||||
// (formats containing '*' alone are not collected in this map)
|
||||
i := strings.Index(key, "%")
|
||||
if i < 0 || !oneFormat(key[i:]) {
|
||||
log.Fatalf("incorrect knownFormats key: %q", key)
|
||||
}
|
||||
// val must be "format" or ""
|
||||
if val != "" && !oneFormat(val) {
|
||||
log.Fatalf("incorrect knownFormats value: %q (key = %q)", val, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const knownFormatsHeader = `// Copyright 2018 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.
|
||||
|
||||
// This file implements the knownFormats map which records the valid
|
||||
// formats for a given type. The valid formats must correspond to
|
||||
// supported compiler formats implemented in fmt.go, or whatever
|
||||
// other format verbs are implemented for the given type. The map may
|
||||
// also be used to change the use of a format verb across all compiler
|
||||
// sources automatically (for instance, if the implementation of fmt.go
|
||||
// changes), by using the -r option together with the new formats in the
|
||||
// map. To generate this file automatically from the existing source,
|
||||
// run: go test -run Formats -u.
|
||||
//
|
||||
// See the package comment in fmt_test.go for additional information.
|
||||
|
||||
package main_test
|
||||
|
||||
// knownFormats entries are of the form "typename format" -> "newformat".
|
||||
// An absent entry means that the format is not recognized as valid.
|
||||
// An empty new format means that the format should remain unchanged.
|
||||
var knownFormats = map[string]string{
|
||||
`
|
@ -1,102 +0,0 @@
|
||||
// Copyright 2018 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.
|
||||
|
||||
// This file implements the knownFormats map which records the valid
|
||||
// formats for a given type. The valid formats must correspond to
|
||||
// supported compiler formats implemented in fmt.go, or whatever
|
||||
// other format verbs are implemented for the given type. The map may
|
||||
// also be used to change the use of a format verb across all compiler
|
||||
// sources automatically (for instance, if the implementation of fmt.go
|
||||
// changes), by using the -r option together with the new formats in the
|
||||
// map. To generate this file automatically from the existing source,
|
||||
// run: go test -run Formats -u.
|
||||
//
|
||||
// See the package comment in fmt_test.go for additional information.
|
||||
|
||||
package main_test
|
||||
|
||||
// knownFormats entries are of the form "typename format" -> "newformat".
|
||||
// An absent entry means that the format is not recognized as valid.
|
||||
// An empty new format means that the format should remain unchanged.
|
||||
var knownFormats = map[string]string{
|
||||
"*bytes.Buffer %s": "",
|
||||
"*cmd/compile/internal/ssa.Block %s": "",
|
||||
"*cmd/compile/internal/ssa.Func %s": "",
|
||||
"*cmd/compile/internal/ssa.Register %s": "",
|
||||
"*cmd/compile/internal/ssa.Value %s": "",
|
||||
"*cmd/compile/internal/syntax.CallExpr %s": "",
|
||||
"*cmd/compile/internal/syntax.FuncLit %s": "",
|
||||
"*cmd/compile/internal/syntax.IndexExpr %s": "",
|
||||
"*cmd/compile/internal/types.Sym %+v": "",
|
||||
"*cmd/compile/internal/types.Sym %S": "",
|
||||
"*cmd/compile/internal/types.Type %+v": "",
|
||||
"*cmd/compile/internal/types.Type %-S": "",
|
||||
"*cmd/compile/internal/types.Type %L": "",
|
||||
"*cmd/compile/internal/types.Type %S": "",
|
||||
"*cmd/compile/internal/types.Type %s": "",
|
||||
"*cmd/compile/internal/types2.Basic %s": "",
|
||||
"*cmd/compile/internal/types2.Chan %s": "",
|
||||
"*cmd/compile/internal/types2.Func %s": "",
|
||||
"*cmd/compile/internal/types2.Initializer %s": "",
|
||||
"*cmd/compile/internal/types2.Interface %s": "",
|
||||
"*cmd/compile/internal/types2.MethodSet %s": "",
|
||||
"*cmd/compile/internal/types2.Named %s": "",
|
||||
"*cmd/compile/internal/types2.Package %s": "",
|
||||
"*cmd/compile/internal/types2.Selection %s": "",
|
||||
"*cmd/compile/internal/types2.Signature %s": "",
|
||||
"*cmd/compile/internal/types2.TypeName %s": "",
|
||||
"*cmd/compile/internal/types2.TypeParam %s": "",
|
||||
"*cmd/compile/internal/types2.Var %s": "",
|
||||
"*cmd/compile/internal/types2.operand %s": "",
|
||||
"*cmd/compile/internal/types2.substMap %s": "",
|
||||
"*math/big.Float %f": "",
|
||||
"*math/big.Int %s": "",
|
||||
"[]*cmd/compile/internal/types2.TypeName %s": "",
|
||||
"[]cmd/compile/internal/syntax.token %s": "",
|
||||
"[]cmd/compile/internal/types2.Type %s": "",
|
||||
"cmd/compile/internal/arm.shift %d": "",
|
||||
"cmd/compile/internal/gc.RegIndex %d": "",
|
||||
"cmd/compile/internal/ir.Class %d": "",
|
||||
"cmd/compile/internal/ir.Node %+v": "",
|
||||
"cmd/compile/internal/ir.Node %L": "",
|
||||
"cmd/compile/internal/ir.Nodes %+v": "",
|
||||
"cmd/compile/internal/ir.Nodes %.v": "",
|
||||
"cmd/compile/internal/ir.Op %+v": "",
|
||||
"cmd/compile/internal/ssa.Aux %#v": "",
|
||||
"cmd/compile/internal/ssa.Aux %q": "",
|
||||
"cmd/compile/internal/ssa.Aux %s": "",
|
||||
"cmd/compile/internal/ssa.BranchPrediction %d": "",
|
||||
"cmd/compile/internal/ssa.ID %d": "",
|
||||
"cmd/compile/internal/ssa.LocalSlot %s": "",
|
||||
"cmd/compile/internal/ssa.Location %s": "",
|
||||
"cmd/compile/internal/ssa.Op %s": "",
|
||||
"cmd/compile/internal/ssa.ValAndOff %s": "",
|
||||
"cmd/compile/internal/ssa.flagConstant %s": "",
|
||||
"cmd/compile/internal/ssa.rbrank %d": "",
|
||||
"cmd/compile/internal/ssa.regMask %d": "",
|
||||
"cmd/compile/internal/ssa.register %d": "",
|
||||
"cmd/compile/internal/ssa.relation %s": "",
|
||||
"cmd/compile/internal/syntax.ChanDir %d": "",
|
||||
"cmd/compile/internal/syntax.Error %q": "",
|
||||
"cmd/compile/internal/syntax.Expr %#v": "",
|
||||
"cmd/compile/internal/syntax.Expr %s": "",
|
||||
"cmd/compile/internal/syntax.LitKind %d": "",
|
||||
"cmd/compile/internal/syntax.Operator %s": "",
|
||||
"cmd/compile/internal/syntax.Pos %s": "",
|
||||
"cmd/compile/internal/syntax.position %s": "",
|
||||
"cmd/compile/internal/syntax.token %q": "",
|
||||
"cmd/compile/internal/syntax.token %s": "",
|
||||
"cmd/compile/internal/types.Kind %d": "",
|
||||
"cmd/compile/internal/types.Kind %s": "",
|
||||
"cmd/compile/internal/types2.Object %s": "",
|
||||
"cmd/compile/internal/types2.Type %s": "",
|
||||
"cmd/compile/internal/types2.color %s": "",
|
||||
"cmd/compile/internal/walk.initKind %d": "",
|
||||
"go/constant.Value %#v": "",
|
||||
"go/constant.Value %s": "",
|
||||
"map[*cmd/compile/internal/types2.TypeParam]cmd/compile/internal/types2.Type %s": "",
|
||||
"math/big.Accuracy %s": "",
|
||||
"reflect.Type %s": "",
|
||||
"time.Duration %d": "",
|
||||
}
|
@ -32,7 +32,6 @@ type DebugFlags struct {
|
||||
Append int `help:"print information about append compilation"`
|
||||
Checkptr int `help:"instrument unsafe pointer conversions"`
|
||||
Closure int `help:"print information about closure compilation"`
|
||||
CompileLater int `help:"compile functions as late as possible"`
|
||||
DclStack int `help:"run internal dclstack check"`
|
||||
Defer int `help:"print information about defer compilation"`
|
||||
DisableNil int `help:"disable nil checks"`
|
||||
|
@ -126,6 +126,11 @@ type location struct {
|
||||
edges []edge // incoming edges
|
||||
loopDepth int // loopDepth at declaration
|
||||
|
||||
// resultIndex records the tuple index (starting at 1) for
|
||||
// PPARAMOUT variables within their function's result type.
|
||||
// For non-PPARAMOUT variables it's 0.
|
||||
resultIndex int
|
||||
|
||||
// derefs and walkgen are used during walkOne to track the
|
||||
// minimal dereferences from the walk root.
|
||||
derefs int // >= -1
|
||||
@ -259,11 +264,16 @@ func (b *batch) initFunc(fn *ir.Func) {
|
||||
}
|
||||
|
||||
// Allocate locations for local variables.
|
||||
for _, dcl := range fn.Dcl {
|
||||
if dcl.Op() == ir.ONAME {
|
||||
e.newLoc(dcl, false)
|
||||
for _, n := range fn.Dcl {
|
||||
if n.Op() == ir.ONAME {
|
||||
e.newLoc(n, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize resultIndex for result parameters.
|
||||
for i, f := range fn.Type().Results().FieldSlice() {
|
||||
e.oldLoc(f.Nname.(*ir.Name)).resultIndex = 1 + i
|
||||
}
|
||||
}
|
||||
|
||||
func (b *batch) walkFunc(fn *ir.Func) {
|
||||
@ -575,7 +585,7 @@ func (e *escape) exprSkipInit(k hole, n ir.Node) {
|
||||
default:
|
||||
base.Fatalf("unexpected expr: %v", n)
|
||||
|
||||
case ir.OLITERAL, ir.ONIL, ir.OGETG, ir.OCLOSUREREAD, ir.OTYPE, ir.OMETHEXPR:
|
||||
case ir.OLITERAL, ir.ONIL, ir.OGETG, ir.OTYPE, ir.OMETHEXPR:
|
||||
// nop
|
||||
|
||||
case ir.ONAME:
|
||||
@ -583,6 +593,9 @@ func (e *escape) exprSkipInit(k hole, n ir.Node) {
|
||||
if n.Class == ir.PFUNC || n.Class == ir.PEXTERN {
|
||||
return
|
||||
}
|
||||
if n.IsClosureVar() && n.Defn == nil {
|
||||
return // ".this" from method value wrapper
|
||||
}
|
||||
e.flow(k, e.oldLoc(n))
|
||||
|
||||
case ir.ONAMEOFFSET:
|
||||
@ -1606,8 +1619,7 @@ func (l *location) leakTo(sink *location, derefs int) {
|
||||
// If sink is a result parameter and we can fit return bits
|
||||
// into the escape analysis tag, then record a return leak.
|
||||
if sink.isName(ir.PPARAMOUT) && sink.curfn == l.curfn {
|
||||
// TODO(mdempsky): Eliminate dependency on Vargen here.
|
||||
ri := int(sink.n.Name().Vargen) - 1
|
||||
ri := sink.resultIndex - 1
|
||||
if ri < numEscResults {
|
||||
// Leak to result parameter.
|
||||
l.paramEsc.AddResult(ri, derefs)
|
||||
@ -1923,7 +1935,7 @@ func mayAffectMemory(n ir.Node) bool {
|
||||
// an ir.Any looking for any op that's not the ones in the case statement.
|
||||
// But that produces changes in the compiled output detected by buildall.
|
||||
switch n.Op() {
|
||||
case ir.ONAME, ir.OCLOSUREREAD, ir.OLITERAL, ir.ONIL:
|
||||
case ir.ONAME, ir.OLITERAL, ir.ONIL:
|
||||
return false
|
||||
|
||||
case ir.OADD, ir.OSUB, ir.OOR, ir.OXOR, ir.OMUL, ir.OLSH, ir.ORSH, ir.OAND, ir.OANDNOT, ir.ODIV, ir.OMOD:
|
||||
|
@ -26,20 +26,20 @@ var (
|
||||
compilequeue []*ir.Func // functions waiting to be compiled
|
||||
)
|
||||
|
||||
func funccompile(fn *ir.Func) {
|
||||
func enqueueFunc(fn *ir.Func) {
|
||||
if ir.CurFunc != nil {
|
||||
base.Fatalf("funccompile %v inside %v", fn.Sym(), ir.CurFunc.Sym())
|
||||
base.FatalfAt(fn.Pos(), "enqueueFunc %v inside %v", fn, ir.CurFunc)
|
||||
}
|
||||
|
||||
if fn.Type() == nil {
|
||||
if base.Errors() == 0 {
|
||||
base.Fatalf("funccompile missing type")
|
||||
}
|
||||
if ir.FuncName(fn) == "_" {
|
||||
// Skip compiling blank functions.
|
||||
// Frontend already reported any spec-mandated errors (#29870).
|
||||
return
|
||||
}
|
||||
|
||||
// assign parameter offsets
|
||||
types.CalcSize(fn.Type())
|
||||
if clo := fn.OClosure; clo != nil && !ir.IsTrivialClosure(clo) {
|
||||
return // we'll get this as part of its enclosing function
|
||||
}
|
||||
|
||||
if len(fn.Body) == 0 {
|
||||
// Initialize ABI wrappers if necessary.
|
||||
@ -48,35 +48,42 @@ func funccompile(fn *ir.Func) {
|
||||
return
|
||||
}
|
||||
|
||||
typecheck.DeclContext = ir.PAUTO
|
||||
ir.CurFunc = fn
|
||||
compile(fn)
|
||||
ir.CurFunc = nil
|
||||
typecheck.DeclContext = ir.PEXTERN
|
||||
errorsBefore := base.Errors()
|
||||
|
||||
todo := []*ir.Func{fn}
|
||||
for len(todo) > 0 {
|
||||
next := todo[len(todo)-1]
|
||||
todo = todo[:len(todo)-1]
|
||||
|
||||
prepareFunc(next)
|
||||
todo = append(todo, next.Closures...)
|
||||
}
|
||||
|
||||
if base.Errors() > errorsBefore {
|
||||
return
|
||||
}
|
||||
|
||||
// Enqueue just fn itself. compileFunctions will handle
|
||||
// scheduling compilation of its closures after it's done.
|
||||
compilequeue = append(compilequeue, fn)
|
||||
}
|
||||
|
||||
func compile(fn *ir.Func) {
|
||||
// prepareFunc handles any remaining frontend compilation tasks that
|
||||
// aren't yet safe to perform concurrently.
|
||||
func prepareFunc(fn *ir.Func) {
|
||||
// Set up the function's LSym early to avoid data races with the assemblers.
|
||||
// Do this before walk, as walk needs the LSym to set attributes/relocations
|
||||
// (e.g. in markTypeUsedInInterface).
|
||||
ssagen.InitLSym(fn, true)
|
||||
|
||||
errorsBefore := base.Errors()
|
||||
// Calculate parameter offsets.
|
||||
types.CalcSize(fn.Type())
|
||||
|
||||
typecheck.DeclContext = ir.PAUTO
|
||||
ir.CurFunc = fn
|
||||
walk.Walk(fn)
|
||||
if base.Errors() > errorsBefore {
|
||||
return
|
||||
}
|
||||
|
||||
// From this point, there should be no uses of Curfn. Enforce that.
|
||||
ir.CurFunc = nil
|
||||
|
||||
if ir.FuncName(fn) == "_" {
|
||||
// We don't need to generate code for this function, just report errors in its body.
|
||||
// At this point we've generated any errors needed.
|
||||
// (Beyond here we generate only non-spec errors, like "stack frame too large".)
|
||||
// See issue 29870.
|
||||
return
|
||||
}
|
||||
ir.CurFunc = nil // enforce no further uses of CurFunc
|
||||
typecheck.DeclContext = ir.PEXTERN
|
||||
|
||||
// Make sure type syms are declared for all types that might
|
||||
// be types of stack objects. We need to do this here
|
||||
@ -95,84 +102,65 @@ func compile(fn *ir.Func) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if compilenow(fn) {
|
||||
ssagen.Compile(fn, 0)
|
||||
} else {
|
||||
compilequeue = append(compilequeue, fn)
|
||||
}
|
||||
}
|
||||
|
||||
// compilenow reports whether to compile immediately.
|
||||
// If functions are not compiled immediately,
|
||||
// they are enqueued in compilequeue,
|
||||
// which is drained by compileFunctions.
|
||||
func compilenow(fn *ir.Func) bool {
|
||||
// Issue 38068: if this function is a method AND an inline
|
||||
// candidate AND was not inlined (yet), put it onto the compile
|
||||
// queue instead of compiling it immediately. This is in case we
|
||||
// wind up inlining it into a method wrapper that is generated by
|
||||
// compiling a function later on in the Target.Decls list.
|
||||
if ir.IsMethod(fn) && isInlinableButNotInlined(fn) {
|
||||
return false
|
||||
}
|
||||
return base.Flag.LowerC == 1 && base.Debug.CompileLater == 0
|
||||
}
|
||||
|
||||
// compileFunctions compiles all functions in compilequeue.
|
||||
// It fans out nBackendWorkers to do the work
|
||||
// and waits for them to complete.
|
||||
func compileFunctions() {
|
||||
if len(compilequeue) != 0 {
|
||||
types.CalcSizeDisabled = true // not safe to calculate sizes concurrently
|
||||
if race.Enabled {
|
||||
// Randomize compilation order to try to shake out races.
|
||||
tmp := make([]*ir.Func, len(compilequeue))
|
||||
perm := rand.Perm(len(compilequeue))
|
||||
for i, v := range perm {
|
||||
tmp[v] = compilequeue[i]
|
||||
}
|
||||
copy(compilequeue, tmp)
|
||||
} else {
|
||||
// Compile the longest functions first,
|
||||
// since they're most likely to be the slowest.
|
||||
// This helps avoid stragglers.
|
||||
sort.Slice(compilequeue, func(i, j int) bool {
|
||||
return len(compilequeue[i].Body) > len(compilequeue[j].Body)
|
||||
})
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
base.Ctxt.InParallel = true
|
||||
c := make(chan *ir.Func, base.Flag.LowerC)
|
||||
for i := 0; i < base.Flag.LowerC; i++ {
|
||||
wg.Add(1)
|
||||
go func(worker int) {
|
||||
for fn := range c {
|
||||
ssagen.Compile(fn, worker)
|
||||
}
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
for _, fn := range compilequeue {
|
||||
c <- fn
|
||||
}
|
||||
close(c)
|
||||
compilequeue = nil
|
||||
wg.Wait()
|
||||
base.Ctxt.InParallel = false
|
||||
types.CalcSizeDisabled = false
|
||||
if len(compilequeue) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// isInlinableButNotInlined returns true if 'fn' was marked as an
|
||||
// inline candidate but then never inlined (presumably because we
|
||||
// found no call sites).
|
||||
func isInlinableButNotInlined(fn *ir.Func) bool {
|
||||
if fn.Inl == nil {
|
||||
return false
|
||||
if race.Enabled {
|
||||
// Randomize compilation order to try to shake out races.
|
||||
tmp := make([]*ir.Func, len(compilequeue))
|
||||
perm := rand.Perm(len(compilequeue))
|
||||
for i, v := range perm {
|
||||
tmp[v] = compilequeue[i]
|
||||
}
|
||||
copy(compilequeue, tmp)
|
||||
} else {
|
||||
// Compile the longest functions first,
|
||||
// since they're most likely to be the slowest.
|
||||
// This helps avoid stragglers.
|
||||
sort.Slice(compilequeue, func(i, j int) bool {
|
||||
return len(compilequeue[i].Body) > len(compilequeue[j].Body)
|
||||
})
|
||||
}
|
||||
if fn.Sym() == nil {
|
||||
return true
|
||||
|
||||
// We queue up a goroutine per function that needs to be
|
||||
// compiled, but require them to grab an available worker ID
|
||||
// before doing any substantial work to limit parallelism.
|
||||
workerIDs := make(chan int, base.Flag.LowerC)
|
||||
for i := 0; i < base.Flag.LowerC; i++ {
|
||||
workerIDs <- i
|
||||
}
|
||||
return !fn.Linksym().WasInlined()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var asyncCompile func(*ir.Func)
|
||||
asyncCompile = func(fn *ir.Func) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
worker := <-workerIDs
|
||||
ssagen.Compile(fn, worker)
|
||||
workerIDs <- worker
|
||||
|
||||
// Done compiling fn. Schedule it's closures for compilation.
|
||||
for _, closure := range fn.Closures {
|
||||
asyncCompile(closure)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
types.CalcSizeDisabled = true // not safe to calculate sizes concurrently
|
||||
base.Ctxt.InParallel = true
|
||||
for _, fn := range compilequeue {
|
||||
asyncCompile(fn)
|
||||
}
|
||||
compilequeue = nil
|
||||
wg.Wait()
|
||||
base.Ctxt.InParallel = false
|
||||
types.CalcSizeDisabled = false
|
||||
}
|
||||
|
@ -20,10 +20,8 @@ import (
|
||||
"cmd/compile/internal/reflectdata"
|
||||
"cmd/compile/internal/ssa"
|
||||
"cmd/compile/internal/ssagen"
|
||||
"cmd/compile/internal/staticdata"
|
||||
"cmd/compile/internal/typecheck"
|
||||
"cmd/compile/internal/types"
|
||||
"cmd/compile/internal/walk"
|
||||
"cmd/internal/dwarf"
|
||||
"cmd/internal/obj"
|
||||
"cmd/internal/objabi"
|
||||
@ -194,7 +192,6 @@ func Main(archInit func(*ssagen.ArchInfo)) {
|
||||
|
||||
typecheck.Target = new(ir.Package)
|
||||
|
||||
typecheck.NeedFuncSym = staticdata.NeedFuncSym
|
||||
typecheck.NeedITab = func(t, iface *types.Type) { reflectdata.ITabAddr(t, iface) }
|
||||
typecheck.NeedRuntimeType = reflectdata.NeedRuntimeType // TODO(rsc): typenamesym for lock?
|
||||
|
||||
@ -271,20 +268,6 @@ func Main(archInit func(*ssagen.ArchInfo)) {
|
||||
ssagen.EnableNoWriteBarrierRecCheck()
|
||||
}
|
||||
|
||||
// Transform closure bodies to properly reference captured variables.
|
||||
// This needs to happen before walk, because closures must be transformed
|
||||
// before walk reaches a call of a closure.
|
||||
base.Timer.Start("fe", "xclosures")
|
||||
for _, n := range typecheck.Target.Decls {
|
||||
if n.Op() == ir.ODCLFUNC {
|
||||
n := n.(*ir.Func)
|
||||
if n.OClosure != nil {
|
||||
ir.CurFunc = n
|
||||
walk.Closure(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare for SSA compilation.
|
||||
// This must be before peekitabs, because peekitabs
|
||||
// can trigger function compilation.
|
||||
@ -302,9 +285,8 @@ func Main(archInit func(*ssagen.ArchInfo)) {
|
||||
base.Timer.Start("be", "compilefuncs")
|
||||
fcount := int64(0)
|
||||
for i := 0; i < len(typecheck.Target.Decls); i++ {
|
||||
n := typecheck.Target.Decls[i]
|
||||
if n.Op() == ir.ODCLFUNC {
|
||||
funccompile(n.(*ir.Func))
|
||||
if fn, ok := typecheck.Target.Decls[i].(*ir.Func); ok {
|
||||
enqueueFunc(fn)
|
||||
fcount++
|
||||
}
|
||||
}
|
||||
|
@ -131,9 +131,8 @@ func dumpdata() {
|
||||
// It was not until issue 24761 that we found any code that required a loop at all.
|
||||
for {
|
||||
for i := numDecls; i < len(typecheck.Target.Decls); i++ {
|
||||
n := typecheck.Target.Decls[i]
|
||||
if n.Op() == ir.ODCLFUNC {
|
||||
funccompile(n.(*ir.Func))
|
||||
if n, ok := typecheck.Target.Decls[i].(*ir.Func); ok {
|
||||
enqueueFunc(n)
|
||||
}
|
||||
}
|
||||
numDecls = len(typecheck.Target.Decls)
|
||||
|
@ -203,19 +203,6 @@ func NewClosureExpr(pos src.XPos, fn *Func) *ClosureExpr {
|
||||
return n
|
||||
}
|
||||
|
||||
// A ClosureRead denotes reading a variable stored within a closure struct.
|
||||
type ClosureReadExpr struct {
|
||||
miniExpr
|
||||
Offset int64
|
||||
}
|
||||
|
||||
func NewClosureRead(typ *types.Type, offset int64) *ClosureReadExpr {
|
||||
n := &ClosureReadExpr{Offset: offset}
|
||||
n.typ = typ
|
||||
n.op = OCLOSUREREAD
|
||||
return n
|
||||
}
|
||||
|
||||
// A CompLitExpr is a composite literal Type{Vals}.
|
||||
// Before type-checking, the type is Ntype.
|
||||
type CompLitExpr struct {
|
||||
@ -727,7 +714,7 @@ func IsAddressable(n Node) bool {
|
||||
return false
|
||||
}
|
||||
fallthrough
|
||||
case ODEREF, ODOTPTR, OCLOSUREREAD:
|
||||
case ODEREF, ODOTPTR:
|
||||
return true
|
||||
|
||||
case ODOT:
|
||||
@ -889,7 +876,7 @@ func SameSafeExpr(l Node, r Node) bool {
|
||||
}
|
||||
|
||||
switch l.Op() {
|
||||
case ONAME, OCLOSUREREAD:
|
||||
case ONAME:
|
||||
return l == r
|
||||
|
||||
case ODOT, ODOTPTR:
|
||||
|
@ -61,12 +61,16 @@ type Func struct {
|
||||
// memory for escaping parameters.
|
||||
Enter Nodes
|
||||
Exit Nodes
|
||||
|
||||
// ONAME nodes for all params/locals for this func/closure, does NOT
|
||||
// include closurevars until transformclosure runs.
|
||||
// Names must be listed PPARAMs, PPARAMOUTs, then PAUTOs,
|
||||
// with PPARAMs and PPARAMOUTs in order corresponding to the function signature.
|
||||
// However, as anonymous or blank PPARAMs are not actually declared,
|
||||
// they are omitted from Dcl.
|
||||
// Anonymous and blank PPARAMOUTs are declared as ~rNN and ~bNN Names, respectively.
|
||||
Dcl []*Name
|
||||
|
||||
ClosureType Ntype // closure representation type
|
||||
|
||||
// ClosureVars lists the free variables that are used within a
|
||||
// function literal, but formally declared in an enclosing
|
||||
// function. The variables in this slice are the closure function's
|
||||
@ -75,6 +79,10 @@ type Func struct {
|
||||
// Byval set if they're captured by value.
|
||||
ClosureVars []*Name
|
||||
|
||||
// Enclosed functions that need to be compiled.
|
||||
// Populated during walk.
|
||||
Closures []*Func
|
||||
|
||||
// Parents records the parent scope of each scope within a
|
||||
// function. The root scope (0) has no parent, so the i'th
|
||||
// scope's parent is stored at Parents[i-1].
|
||||
|
@ -55,12 +55,6 @@ type Name struct {
|
||||
// The function, method, or closure in which local variable or param is declared.
|
||||
Curfn *Func
|
||||
|
||||
// Unique number for ONAME nodes within a function. Function outputs
|
||||
// (results) are numbered starting at one, followed by function inputs
|
||||
// (parameters), and then local variables. Vargen is used to distinguish
|
||||
// local variables/params with the same name.
|
||||
Vargen int32
|
||||
|
||||
Ntype Ntype
|
||||
Heapaddr *Name // temp holding heap address of param
|
||||
|
||||
@ -264,7 +258,7 @@ const (
|
||||
nameNeedzero // if it contains pointers, needs to be zeroed on function entry
|
||||
nameAutoTemp // is the variable a temporary (implies no dwarf info. reset if escapes to heap)
|
||||
nameUsed // for variable declared and not used error
|
||||
nameIsClosureVar // PAUTOHEAP closure pseudo-variable; original at n.Name.Defn
|
||||
nameIsClosureVar // PAUTOHEAP closure pseudo-variable; original (if any) at n.Defn
|
||||
nameIsOutputParamHeapAddr // pointer to a result parameter's heap copy
|
||||
nameAddrtaken // address taken, even if not moved to heap
|
||||
nameInlFormal // PAUTO created by inliner, derived from callee formal
|
||||
@ -332,7 +326,7 @@ func (n *Name) SetVal(v constant.Value) {
|
||||
// it appears in the function that immediately contains the
|
||||
// declaration. Otherwise, Canonical simply returns n itself.
|
||||
func (n *Name) Canonical() *Name {
|
||||
if n.IsClosureVar() {
|
||||
if n.IsClosureVar() && n.Defn != nil {
|
||||
n = n.Defn.(*Name)
|
||||
}
|
||||
return n
|
||||
@ -351,6 +345,82 @@ func (n *Name) Byval() bool {
|
||||
return n.Canonical().flags&nameByval != 0
|
||||
}
|
||||
|
||||
// CaptureName returns a Name suitable for referring to n from within function
|
||||
// fn or from the package block if fn is nil. If n is a free variable declared
|
||||
// within a function that encloses fn, then CaptureName returns a closure
|
||||
// variable that refers to n and adds it to fn.ClosureVars. Otherwise, it simply
|
||||
// returns n.
|
||||
func CaptureName(pos src.XPos, fn *Func, n *Name) *Name {
|
||||
if n.IsClosureVar() {
|
||||
base.FatalfAt(pos, "misuse of CaptureName on closure variable: %v", n)
|
||||
}
|
||||
if n.Op() != ONAME || n.Curfn == nil || n.Curfn == fn {
|
||||
return n // okay to use directly
|
||||
}
|
||||
if fn == nil {
|
||||
base.FatalfAt(pos, "package-block reference to %v, declared in %v", n, n.Curfn)
|
||||
}
|
||||
|
||||
c := n.Innermost
|
||||
if c != nil && c.Curfn == fn {
|
||||
return c
|
||||
}
|
||||
|
||||
// Do not have a closure var for the active closure yet; make one.
|
||||
c = NewNameAt(pos, n.Sym())
|
||||
c.Curfn = fn
|
||||
c.Class = PAUTOHEAP
|
||||
c.SetIsClosureVar(true)
|
||||
c.Defn = n
|
||||
|
||||
// Link into list of active closure variables.
|
||||
// Popped from list in FinishCaptureNames.
|
||||
c.Outer = n.Innermost
|
||||
n.Innermost = c
|
||||
fn.ClosureVars = append(fn.ClosureVars, c)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// FinishCaptureNames handles any work leftover from calling CaptureName
|
||||
// earlier. outerfn should be the function that immediately encloses fn.
|
||||
func FinishCaptureNames(pos src.XPos, outerfn, fn *Func) {
|
||||
// closure-specific variables are hanging off the
|
||||
// ordinary ones; see CaptureName above.
|
||||
// unhook them.
|
||||
// make the list of pointers for the closure call.
|
||||
for _, cv := range fn.ClosureVars {
|
||||
// Unlink from n; see comment in syntax.go type Param for these fields.
|
||||
n := cv.Defn.(*Name)
|
||||
n.Innermost = cv.Outer
|
||||
|
||||
// If the closure usage of n is not dense, we need to make it
|
||||
// dense by recapturing n within the enclosing function.
|
||||
//
|
||||
// That is, suppose we just finished parsing the innermost
|
||||
// closure f4 in this code:
|
||||
//
|
||||
// func f() {
|
||||
// n := 1
|
||||
// func() { // f2
|
||||
// use(n)
|
||||
// func() { // f3
|
||||
// func() { // f4
|
||||
// use(n)
|
||||
// }()
|
||||
// }()
|
||||
// }()
|
||||
// }
|
||||
//
|
||||
// At this point cv.Outer is f2's n; there is no n for f3. To
|
||||
// construct the closure f4 from within f3, we need to use f3's
|
||||
// n and in this case we need to create f3's n with CaptureName.
|
||||
//
|
||||
// We'll decide later in walk whether to use v directly or &v.
|
||||
cv.Outer = CaptureName(pos, outerfn, n)
|
||||
}
|
||||
}
|
||||
|
||||
// SameSource reports whether two nodes refer to the same source
|
||||
// element.
|
||||
//
|
||||
|
@ -294,20 +294,19 @@ const (
|
||||
OTSLICE // []int
|
||||
|
||||
// misc
|
||||
OINLCALL // intermediary representation of an inlined call.
|
||||
OEFACE // itable and data words of an empty-interface value.
|
||||
OITAB // itable word of an interface value.
|
||||
OIDATA // data word of an interface value in Left
|
||||
OSPTR // base pointer of a slice or string.
|
||||
OCLOSUREREAD // read from inside closure struct at beginning of closure function
|
||||
OCFUNC // reference to c function pointer (not go func value)
|
||||
OCHECKNIL // emit code to ensure pointer/interface not nil
|
||||
OVARDEF // variable is about to be fully initialized
|
||||
OVARKILL // variable is dead
|
||||
OVARLIVE // variable is alive
|
||||
ORESULT // result of a function call; Xoffset is stack offset
|
||||
OINLMARK // start of an inlined body, with file/line of caller. Xoffset is an index into the inline tree.
|
||||
ONAMEOFFSET // offset within a name
|
||||
OINLCALL // intermediary representation of an inlined call.
|
||||
OEFACE // itable and data words of an empty-interface value.
|
||||
OITAB // itable word of an interface value.
|
||||
OIDATA // data word of an interface value in Left
|
||||
OSPTR // base pointer of a slice or string.
|
||||
OCFUNC // reference to c function pointer (not go func value)
|
||||
OCHECKNIL // emit code to ensure pointer/interface not nil
|
||||
OVARDEF // variable is about to be fully initialized
|
||||
OVARKILL // variable is dead
|
||||
OVARLIVE // variable is alive
|
||||
ORESULT // result of a function call; Xoffset is stack offset
|
||||
OINLMARK // start of an inlined body, with file/line of caller. Xoffset is an index into the inline tree.
|
||||
ONAMEOFFSET // offset within a name
|
||||
|
||||
// arch-specific opcodes
|
||||
ORETJMP // return to other function
|
||||
|
@ -353,22 +353,6 @@ func (n *ClosureExpr) editChildren(edit func(Node) Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func (n *ClosureReadExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) }
|
||||
func (n *ClosureReadExpr) copy() Node {
|
||||
c := *n
|
||||
c.init = copyNodes(c.init)
|
||||
return &c
|
||||
}
|
||||
func (n *ClosureReadExpr) doChildren(do func(Node) bool) bool {
|
||||
if doNodes(n.init, do) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (n *ClosureReadExpr) editChildren(edit func(Node) Node) {
|
||||
editNodes(n.init, edit)
|
||||
}
|
||||
|
||||
func (n *CommClause) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) }
|
||||
func (n *CommClause) copy() Node {
|
||||
c := *n
|
||||
|
@ -150,23 +150,22 @@ func _() {
|
||||
_ = x[OITAB-139]
|
||||
_ = x[OIDATA-140]
|
||||
_ = x[OSPTR-141]
|
||||
_ = x[OCLOSUREREAD-142]
|
||||
_ = x[OCFUNC-143]
|
||||
_ = x[OCHECKNIL-144]
|
||||
_ = x[OVARDEF-145]
|
||||
_ = x[OVARKILL-146]
|
||||
_ = x[OVARLIVE-147]
|
||||
_ = x[ORESULT-148]
|
||||
_ = x[OINLMARK-149]
|
||||
_ = x[ONAMEOFFSET-150]
|
||||
_ = x[ORETJMP-151]
|
||||
_ = x[OGETG-152]
|
||||
_ = x[OEND-153]
|
||||
_ = x[OCFUNC-142]
|
||||
_ = x[OCHECKNIL-143]
|
||||
_ = x[OVARDEF-144]
|
||||
_ = x[OVARKILL-145]
|
||||
_ = x[OVARLIVE-146]
|
||||
_ = x[ORESULT-147]
|
||||
_ = x[OINLMARK-148]
|
||||
_ = x[ONAMEOFFSET-149]
|
||||
_ = x[ORETJMP-150]
|
||||
_ = x[OGETG-151]
|
||||
_ = x[OEND-152]
|
||||
}
|
||||
|
||||
const _Op_name = "XXXNAMENONAMETYPEPACKLITERALNILADDSUBORXORADDSTRADDRANDANDAPPENDBYTES2STRBYTES2STRTMPRUNES2STRSTR2BYTESSTR2BYTESTMPSTR2RUNESASAS2AS2DOTTYPEAS2FUNCAS2MAPRAS2RECVASOPCALLCALLFUNCCALLMETHCALLINTERCALLPARTCAPCLOSECLOSURECOMPLITMAPLITSTRUCTLITARRAYLITSLICELITPTRLITCONVCONVIFACECONVNOPCOPYDCLDCLFUNCDCLCONSTDCLTYPEDELETEDOTDOTPTRDOTMETHDOTINTERXDOTDOTTYPEDOTTYPE2EQNELTLEGEGTDEREFINDEXINDEXMAPKEYSTRUCTKEYLENMAKEMAKECHANMAKEMAPMAKESLICEMAKESLICECOPYMULDIVMODLSHRSHANDANDNOTNEWNEWOBJNOTBITNOTPLUSNEGORORPANICPRINTPRINTNPARENSENDSLICESLICEARRSLICESTRSLICE3SLICE3ARRSLICEHEADERRECOVERRECVRUNESTRSELRECV2IOTAREALIMAGCOMPLEXALIGNOFOFFSETOFSIZEOFMETHEXPRSTMTEXPRBLOCKBREAKCASECONTINUEDEFERFALLFORFORUNTILGOTOIFLABELGORANGERETURNSELECTSWITCHTYPESWTCHANTMAPTSTRUCTTINTERTFUNCTARRAYTSLICEINLCALLEFACEITABIDATASPTRCLOSUREREADCFUNCCHECKNILVARDEFVARKILLVARLIVERESULTINLMARKNAMEOFFSETRETJMPGETGEND"
|
||||
const _Op_name = "XXXNAMENONAMETYPEPACKLITERALNILADDSUBORXORADDSTRADDRANDANDAPPENDBYTES2STRBYTES2STRTMPRUNES2STRSTR2BYTESSTR2BYTESTMPSTR2RUNESASAS2AS2DOTTYPEAS2FUNCAS2MAPRAS2RECVASOPCALLCALLFUNCCALLMETHCALLINTERCALLPARTCAPCLOSECLOSURECOMPLITMAPLITSTRUCTLITARRAYLITSLICELITPTRLITCONVCONVIFACECONVNOPCOPYDCLDCLFUNCDCLCONSTDCLTYPEDELETEDOTDOTPTRDOTMETHDOTINTERXDOTDOTTYPEDOTTYPE2EQNELTLEGEGTDEREFINDEXINDEXMAPKEYSTRUCTKEYLENMAKEMAKECHANMAKEMAPMAKESLICEMAKESLICECOPYMULDIVMODLSHRSHANDANDNOTNEWNEWOBJNOTBITNOTPLUSNEGORORPANICPRINTPRINTNPARENSENDSLICESLICEARRSLICESTRSLICE3SLICE3ARRSLICEHEADERRECOVERRECVRUNESTRSELRECV2IOTAREALIMAGCOMPLEXALIGNOFOFFSETOFSIZEOFMETHEXPRSTMTEXPRBLOCKBREAKCASECONTINUEDEFERFALLFORFORUNTILGOTOIFLABELGORANGERETURNSELECTSWITCHTYPESWTCHANTMAPTSTRUCTTINTERTFUNCTARRAYTSLICEINLCALLEFACEITABIDATASPTRCFUNCCHECKNILVARDEFVARKILLVARLIVERESULTINLMARKNAMEOFFSETRETJMPGETGEND"
|
||||
|
||||
var _Op_index = [...]uint16{0, 3, 7, 13, 17, 21, 28, 31, 34, 37, 39, 42, 48, 52, 58, 64, 73, 85, 94, 103, 115, 124, 126, 129, 139, 146, 153, 160, 164, 168, 176, 184, 193, 201, 204, 209, 216, 223, 229, 238, 246, 254, 260, 264, 273, 280, 284, 287, 294, 302, 309, 315, 318, 324, 331, 339, 343, 350, 358, 360, 362, 364, 366, 368, 370, 375, 380, 388, 391, 400, 403, 407, 415, 422, 431, 444, 447, 450, 453, 456, 459, 462, 468, 471, 477, 480, 486, 490, 493, 497, 502, 507, 513, 518, 522, 527, 535, 543, 549, 558, 569, 576, 580, 587, 595, 599, 603, 607, 614, 621, 629, 635, 643, 651, 656, 661, 665, 673, 678, 682, 685, 693, 697, 699, 704, 706, 711, 717, 723, 729, 735, 740, 744, 751, 757, 762, 768, 774, 781, 786, 790, 795, 799, 810, 815, 823, 829, 836, 843, 849, 856, 866, 872, 876, 879}
|
||||
var _Op_index = [...]uint16{0, 3, 7, 13, 17, 21, 28, 31, 34, 37, 39, 42, 48, 52, 58, 64, 73, 85, 94, 103, 115, 124, 126, 129, 139, 146, 153, 160, 164, 168, 176, 184, 193, 201, 204, 209, 216, 223, 229, 238, 246, 254, 260, 264, 273, 280, 284, 287, 294, 302, 309, 315, 318, 324, 331, 339, 343, 350, 358, 360, 362, 364, 366, 368, 370, 375, 380, 388, 391, 400, 403, 407, 415, 422, 431, 444, 447, 450, 453, 456, 459, 462, 468, 471, 477, 480, 486, 490, 493, 497, 502, 507, 513, 518, 522, 527, 535, 543, 549, 558, 569, 576, 580, 587, 595, 599, 603, 607, 614, 621, 629, 635, 643, 651, 656, 661, 665, 673, 678, 682, 685, 693, 697, 699, 704, 706, 711, 717, 723, 729, 735, 740, 744, 751, 757, 762, 768, 774, 781, 786, 790, 795, 799, 804, 812, 818, 825, 832, 838, 845, 855, 861, 865, 868}
|
||||
|
||||
func (i Op) String() string {
|
||||
if i >= Op(len(_Op_index)-1) {
|
||||
|
@ -20,8 +20,8 @@ func TestSizeof(t *testing.T) {
|
||||
_32bit uintptr // size on 32bit platforms
|
||||
_64bit uintptr // size on 64bit platforms
|
||||
}{
|
||||
{Func{}, 184, 320},
|
||||
{Name{}, 120, 216},
|
||||
{Func{}, 188, 328},
|
||||
{Name{}, 116, 208},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -1223,6 +1223,7 @@ func WriteFuncMap(fn *ir.Func) {
|
||||
if ir.FuncName(fn) == "_" || fn.Sym().Linkname != "" {
|
||||
return
|
||||
}
|
||||
types.CalcSize(fn.Type())
|
||||
lsym := base.Ctxt.Lookup(fn.LSym.Name + ".args_stackmap")
|
||||
nptr := int(fn.Type().ArgWidth() / int64(types.PtrSize))
|
||||
bv := bitvec.New(int32(nptr) * 2)
|
||||
|
@ -1888,7 +1888,6 @@ func fakeRecv() *ir.Field {
|
||||
|
||||
func (p *noder) funcLit(expr *syntax.FuncLit) ir.Node {
|
||||
xtype := p.typeExpr(expr.Type)
|
||||
ntype := p.typeExpr(expr.Type)
|
||||
|
||||
fn := ir.NewFunc(p.pos(expr))
|
||||
fn.SetIsHiddenClosure(ir.CurFunc != nil)
|
||||
@ -1899,50 +1898,11 @@ func (p *noder) funcLit(expr *syntax.FuncLit) ir.Node {
|
||||
fn.Nname.Defn = fn
|
||||
|
||||
clo := ir.NewClosureExpr(p.pos(expr), fn)
|
||||
fn.ClosureType = ntype
|
||||
fn.OClosure = clo
|
||||
|
||||
p.funcBody(fn, expr.Body)
|
||||
|
||||
// closure-specific variables are hanging off the
|
||||
// ordinary ones in the symbol table; see oldname.
|
||||
// unhook them.
|
||||
// make the list of pointers for the closure call.
|
||||
for _, v := range fn.ClosureVars {
|
||||
// Unlink from v1; see comment in syntax.go type Param for these fields.
|
||||
v1 := v.Defn
|
||||
v1.Name().Innermost = v.Outer
|
||||
|
||||
// If the closure usage of v is not dense,
|
||||
// we need to make it dense; now that we're out
|
||||
// of the function in which v appeared,
|
||||
// look up v.Sym in the enclosing function
|
||||
// and keep it around for use in the compiled code.
|
||||
//
|
||||
// That is, suppose we just finished parsing the innermost
|
||||
// closure f4 in this code:
|
||||
//
|
||||
// func f() {
|
||||
// v := 1
|
||||
// func() { // f2
|
||||
// use(v)
|
||||
// func() { // f3
|
||||
// func() { // f4
|
||||
// use(v)
|
||||
// }()
|
||||
// }()
|
||||
// }()
|
||||
// }
|
||||
//
|
||||
// At this point v.Outer is f2's v; there is no f3's v.
|
||||
// To construct the closure f4 from within f3,
|
||||
// we need to use f3's v and in this case we need to create f3's v.
|
||||
// We are now in the context of f3, so calling oldname(v.Sym)
|
||||
// obtains f3's v, creating it if necessary (as it is in the example).
|
||||
//
|
||||
// capturevars will decide whether to use v directly or &v.
|
||||
v.Outer = oldname(v.Sym()).(*ir.Name)
|
||||
}
|
||||
ir.FinishCaptureNames(base.Pos, ir.CurFunc, fn)
|
||||
|
||||
return clo
|
||||
}
|
||||
@ -1976,32 +1936,12 @@ func oldname(s *types.Sym) ir.Node {
|
||||
return ir.NewIdent(base.Pos, s)
|
||||
}
|
||||
|
||||
if ir.CurFunc != nil && n.Op() == ir.ONAME && n.Name().Curfn != nil && n.Name().Curfn != ir.CurFunc {
|
||||
// Inner func is referring to var in outer func.
|
||||
//
|
||||
if n, ok := n.(*ir.Name); ok {
|
||||
// TODO(rsc): If there is an outer variable x and we
|
||||
// are parsing x := 5 inside the closure, until we get to
|
||||
// the := it looks like a reference to the outer x so we'll
|
||||
// make x a closure variable unnecessarily.
|
||||
n := n.(*ir.Name)
|
||||
c := n.Innermost
|
||||
if c == nil || c.Curfn != ir.CurFunc {
|
||||
// Do not have a closure var for the active closure yet; make one.
|
||||
c = typecheck.NewName(s)
|
||||
c.Class = ir.PAUTOHEAP
|
||||
c.SetIsClosureVar(true)
|
||||
c.Defn = n
|
||||
|
||||
// Link into list of active closure variables.
|
||||
// Popped from list in func funcLit.
|
||||
c.Outer = n.Innermost
|
||||
n.Innermost = c
|
||||
|
||||
ir.CurFunc.ClosureVars = append(ir.CurFunc.ClosureVars, c)
|
||||
}
|
||||
|
||||
// return ref to closure var, not original
|
||||
return c
|
||||
return ir.CaptureName(base.Pos, ir.CurFunc, n)
|
||||
}
|
||||
|
||||
return n
|
||||
|
@ -42,8 +42,8 @@ func eqCanPanic(t *types.Type) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// AlgType is like algtype1, except it returns the fixed-width AMEMxx variants
|
||||
// instead of the general AMEM kind when possible.
|
||||
// AlgType returns the fixed-width AMEMxx variants instead of the general
|
||||
// AMEM kind when possible.
|
||||
func AlgType(t *types.Type) types.AlgKind {
|
||||
a, _ := types.AlgType(t)
|
||||
if a == types.AMEM {
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"cmd/compile/internal/base"
|
||||
"cmd/compile/internal/escape"
|
||||
"cmd/compile/internal/ir"
|
||||
"cmd/compile/internal/staticdata"
|
||||
"cmd/compile/internal/typecheck"
|
||||
"cmd/compile/internal/types"
|
||||
"cmd/internal/obj"
|
||||
@ -137,6 +138,8 @@ func ReadSymABIs(file, myimportpath string) {
|
||||
// For body-less functions, we only create the LSym; for functions
|
||||
// with bodies call a helper to setup up / populate the LSym.
|
||||
func InitLSym(f *ir.Func, hasBody bool) {
|
||||
staticdata.NeedFuncSym(f.Sym())
|
||||
|
||||
// FIXME: for new-style ABI wrappers, we set up the lsym at the
|
||||
// point the wrapper is created.
|
||||
if f.LSym != nil && base.Flag.ABIWrap {
|
||||
@ -152,7 +155,7 @@ func InitLSym(f *ir.Func, hasBody bool) {
|
||||
// makes calls to helpers to create ABI wrappers if needed.
|
||||
func selectLSym(f *ir.Func, hasBody bool) {
|
||||
if f.LSym != nil {
|
||||
base.Fatalf("Func.initLSym called twice")
|
||||
base.FatalfAt(f.Pos(), "Func.initLSym called twice on %v", f)
|
||||
}
|
||||
|
||||
if nam := f.Nname; !ir.IsBlank(nam) {
|
||||
|
@ -470,6 +470,47 @@ func buildssa(fn *ir.Func, worker int) *ssa.Func {
|
||||
}
|
||||
}
|
||||
|
||||
// Populate closure variables.
|
||||
if !fn.ClosureCalled() {
|
||||
clo := s.entryNewValue0(ssa.OpGetClosurePtr, s.f.Config.Types.BytePtr)
|
||||
offset := int64(types.PtrSize) // PtrSize to skip past function entry PC field
|
||||
for _, n := range fn.ClosureVars {
|
||||
typ := n.Type()
|
||||
if !n.Byval() {
|
||||
typ = types.NewPtr(typ)
|
||||
}
|
||||
|
||||
offset = types.Rnd(offset, typ.Alignment())
|
||||
r := s.newValue1I(ssa.OpOffPtr, types.NewPtr(typ), offset, clo)
|
||||
offset += typ.Size()
|
||||
|
||||
if n.Byval() && TypeOK(n.Type()) {
|
||||
// If it is a small variable captured by value, downgrade it to PAUTO.
|
||||
r = s.load(n.Type(), r)
|
||||
|
||||
n.Class = ir.PAUTO
|
||||
} else {
|
||||
if !n.Byval() {
|
||||
r = s.load(typ, r)
|
||||
}
|
||||
|
||||
// Declare variable holding address taken from closure.
|
||||
addr := ir.NewNameAt(fn.Pos(), &types.Sym{Name: "&" + n.Sym().Name, Pkg: types.LocalPkg})
|
||||
addr.SetType(types.NewPtr(n.Type()))
|
||||
addr.Class = ir.PAUTO
|
||||
addr.SetUsed(true)
|
||||
addr.Curfn = fn
|
||||
types.CalcSize(addr.Type())
|
||||
|
||||
n.Heapaddr = addr
|
||||
n = addr
|
||||
}
|
||||
|
||||
fn.Dcl = append(fn.Dcl, n)
|
||||
s.assign(n, r, false, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the AST-based IR to the SSA-based IR
|
||||
s.stmtList(fn.Enter)
|
||||
s.stmtList(fn.Body)
|
||||
@ -2127,9 +2168,6 @@ func (s *state) expr(n ir.Node) *ssa.Value {
|
||||
}
|
||||
addr := s.addr(n)
|
||||
return s.load(n.Type(), addr)
|
||||
case ir.OCLOSUREREAD:
|
||||
addr := s.addr(n)
|
||||
return s.load(n.Type(), addr)
|
||||
case ir.ONIL:
|
||||
n := n.(*ir.NilExpr)
|
||||
t := n.Type()
|
||||
@ -3222,8 +3260,8 @@ func (s *state) assign(left ir.Node, right *ssa.Value, deref bool, skip skipMask
|
||||
|
||||
// If this assignment clobbers an entire local variable, then emit
|
||||
// OpVarDef so liveness analysis knows the variable is redefined.
|
||||
if base := clobberBase(left); base.Op() == ir.ONAME && base.(*ir.Name).Class != ir.PEXTERN && skip == 0 {
|
||||
s.vars[memVar] = s.newValue1Apos(ssa.OpVarDef, types.TypeMem, base.(*ir.Name), s.mem(), !ir.IsAutoTmp(base))
|
||||
if base, ok := clobberBase(left).(*ir.Name); ok && base.Op() == ir.ONAME && base.Class != ir.PEXTERN && base.Class != ir.PAUTOHEAP && skip == 0 {
|
||||
s.vars[memVar] = s.newValue1Apos(ssa.OpVarDef, types.TypeMem, base, s.mem(), !ir.IsAutoTmp(base))
|
||||
}
|
||||
|
||||
// Left is not ssa-able. Compute its address.
|
||||
@ -4986,6 +5024,8 @@ func (s *state) addr(n ir.Node) *ssa.Value {
|
||||
// ensure that we reuse symbols for out parameters so
|
||||
// that cse works on their addresses
|
||||
return s.newValue2Apos(ssa.OpLocalAddr, t, n, s.sp, s.mem(), true)
|
||||
case ir.PAUTOHEAP:
|
||||
return s.expr(n.Heapaddr)
|
||||
default:
|
||||
s.Fatalf("variable address class %v not implemented", n.Class)
|
||||
return nil
|
||||
@ -5031,10 +5071,6 @@ func (s *state) addr(n ir.Node) *ssa.Value {
|
||||
n := n.(*ir.SelectorExpr)
|
||||
p := s.exprPtr(n.X, n.Bounded(), n.Pos())
|
||||
return s.newValue1I(ssa.OpOffPtr, t, n.Offset(), p)
|
||||
case ir.OCLOSUREREAD:
|
||||
n := n.(*ir.ClosureReadExpr)
|
||||
return s.newValue1I(ssa.OpOffPtr, t, n.Offset,
|
||||
s.entryNewValue0(ssa.OpGetClosurePtr, s.f.Config.Types.BytePtr))
|
||||
case ir.OCONVNOP:
|
||||
n := n.(*ir.ConvExpr)
|
||||
if n.Type() == n.X.Type() {
|
||||
@ -5096,11 +5132,8 @@ func (s *state) canSSAName(name *ir.Name) bool {
|
||||
if ir.IsParamHeapCopy(name) {
|
||||
return false
|
||||
}
|
||||
if name.Class == ir.PAUTOHEAP {
|
||||
s.Fatalf("canSSA of PAUTOHEAP %v", name)
|
||||
}
|
||||
switch name.Class {
|
||||
case ir.PEXTERN:
|
||||
case ir.PEXTERN, ir.PAUTOHEAP:
|
||||
return false
|
||||
case ir.PPARAMOUT:
|
||||
if s.hasdefer {
|
||||
|
@ -265,7 +265,7 @@ func FuncLinksym(n *ir.Name) *obj.LSym {
|
||||
return FuncSym(n.Sym()).Linksym()
|
||||
}
|
||||
|
||||
// NeedFuncSym ensures that s·f is exported.
|
||||
// NeedFuncSym ensures that s·f is exported, if needed.
|
||||
// It is only used with -dynlink.
|
||||
// When not compiling for dynamic linking,
|
||||
// the funcsyms are created as needed by
|
||||
@ -275,8 +275,13 @@ func FuncLinksym(n *ir.Name) *obj.LSym {
|
||||
// So instead, when dynamic linking, we only create
|
||||
// the s·f stubs in s's package.
|
||||
func NeedFuncSym(s *types.Sym) {
|
||||
if base.Ctxt.InParallel {
|
||||
// The append below probably just needs to lock
|
||||
// funcsymsmu, like in FuncSym.
|
||||
base.Fatalf("NeedFuncSym must be called in serial")
|
||||
}
|
||||
if !base.Ctxt.Flag_dynlink {
|
||||
base.Fatalf("makefuncsym dynlink")
|
||||
return
|
||||
}
|
||||
if s.IsBlank() {
|
||||
return
|
||||
@ -287,9 +292,7 @@ func NeedFuncSym(s *types.Sym) {
|
||||
// get funcsyms.
|
||||
return
|
||||
}
|
||||
if _, existed := s.Pkg.LookupOK(ir.FuncSymName(s)); !existed {
|
||||
funcsyms = append(funcsyms, s)
|
||||
}
|
||||
funcsyms = append(funcsyms, s)
|
||||
}
|
||||
|
||||
func WriteFuncSyms() {
|
||||
|
@ -7,7 +7,6 @@ package typecheck
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"cmd/compile/internal/base"
|
||||
"cmd/compile/internal/ir"
|
||||
@ -47,7 +46,6 @@ func Declare(n *ir.Name, ctxt ir.Class) {
|
||||
base.ErrorfAt(n.Pos(), "cannot declare name %v", s)
|
||||
}
|
||||
|
||||
gen := 0
|
||||
if ctxt == ir.PEXTERN {
|
||||
if s.Name == "init" {
|
||||
base.ErrorfAt(n.Pos(), "cannot declare init - must be func")
|
||||
@ -64,13 +62,6 @@ func Declare(n *ir.Name, ctxt ir.Class) {
|
||||
if ir.CurFunc != nil && ctxt != ir.PFUNC && n.Op() == ir.ONAME {
|
||||
ir.CurFunc.Dcl = append(ir.CurFunc.Dcl, n)
|
||||
}
|
||||
if n.Op() == ir.OTYPE {
|
||||
declare_typegen++
|
||||
gen = declare_typegen
|
||||
} else if n.Op() == ir.ONAME && ctxt == ir.PAUTO && !strings.Contains(s.Name, "·") {
|
||||
vargen++
|
||||
gen = vargen
|
||||
}
|
||||
types.Pushdcl(s)
|
||||
n.Curfn = ir.CurFunc
|
||||
}
|
||||
@ -90,7 +81,6 @@ func Declare(n *ir.Name, ctxt ir.Class) {
|
||||
s.Block = types.Block
|
||||
s.Lastlineno = base.Pos
|
||||
s.Def = n
|
||||
n.Vargen = int32(gen)
|
||||
n.Class = ctxt
|
||||
if ctxt == ir.PFUNC {
|
||||
n.Sym().SetFunc(true)
|
||||
@ -314,10 +304,6 @@ func checkembeddedtype(t *types.Type) {
|
||||
}
|
||||
}
|
||||
|
||||
// declare individual names - var, typ, const
|
||||
|
||||
var declare_typegen int
|
||||
|
||||
func fakeRecvField() *types.Field {
|
||||
return types.NewField(src.NoXPos, nil, types.FakeRecvType())
|
||||
}
|
||||
@ -338,9 +324,6 @@ func funcarg(n *ir.Field, ctxt ir.Class) {
|
||||
n.Decl = name
|
||||
name.Ntype = n.Ntype
|
||||
Declare(name, ctxt)
|
||||
|
||||
vargen++
|
||||
n.Decl.Vargen = int32(vargen)
|
||||
}
|
||||
|
||||
func funcarg2(f *types.Field, ctxt ir.Class) {
|
||||
@ -358,15 +341,6 @@ func funcargs(nt *ir.FuncType) {
|
||||
base.Fatalf("funcargs %v", nt.Op())
|
||||
}
|
||||
|
||||
// re-start the variable generation number
|
||||
// we want to use small numbers for the return variables,
|
||||
// so let them have the chunk starting at 1.
|
||||
//
|
||||
// TODO(mdempsky): This is ugly, and only necessary because
|
||||
// esc.go uses Vargen to figure out result parameters' index
|
||||
// within the result tuple.
|
||||
vargen = len(nt.Results)
|
||||
|
||||
// declare the receiver and in arguments.
|
||||
if nt.Recv != nil {
|
||||
funcarg(nt.Recv, ir.PPARAM)
|
||||
@ -375,9 +349,6 @@ func funcargs(nt *ir.FuncType) {
|
||||
funcarg(n, ir.PPARAM)
|
||||
}
|
||||
|
||||
oldvargen := vargen
|
||||
vargen = 0
|
||||
|
||||
// declare the out arguments.
|
||||
gen := len(nt.Params)
|
||||
for _, n := range nt.Results {
|
||||
@ -399,8 +370,6 @@ func funcargs(nt *ir.FuncType) {
|
||||
|
||||
funcarg(n, ir.PPARAMOUT)
|
||||
}
|
||||
|
||||
vargen = oldvargen
|
||||
}
|
||||
|
||||
// Same as funcargs, except run over an already constructed TFUNC.
|
||||
@ -422,8 +391,6 @@ func funcargs2(t *types.Type) {
|
||||
}
|
||||
}
|
||||
|
||||
var vargen int
|
||||
|
||||
func Temp(t *types.Type) *ir.Name {
|
||||
return TempAt(base.Pos, ir.CurFunc, t)
|
||||
}
|
||||
|
@ -246,29 +246,26 @@ func MethodValueWrapper(dot *ir.SelectorExpr) *ir.Func {
|
||||
fn.SetWrapper(true)
|
||||
|
||||
// Declare and initialize variable holding receiver.
|
||||
cr := ir.NewClosureRead(rcvrtype, types.Rnd(int64(types.PtrSize), int64(rcvrtype.Align)))
|
||||
var ptr *ir.Name
|
||||
var body []ir.Node
|
||||
if rcvrtype.IsPtr() || rcvrtype.IsInterface() {
|
||||
ptr = Temp(rcvrtype)
|
||||
body = append(body, ir.NewAssignStmt(base.Pos, ptr, cr))
|
||||
} else {
|
||||
ptr = Temp(types.NewPtr(rcvrtype))
|
||||
body = append(body, ir.NewAssignStmt(base.Pos, ptr, NodAddr(cr)))
|
||||
}
|
||||
ptr := ir.NewNameAt(base.Pos, Lookup(".this"))
|
||||
ptr.Class = ir.PAUTOHEAP
|
||||
ptr.SetType(rcvrtype)
|
||||
ptr.Curfn = fn
|
||||
ptr.SetIsClosureVar(true)
|
||||
ptr.SetByval(true)
|
||||
fn.ClosureVars = append(fn.ClosureVars, ptr)
|
||||
|
||||
call := ir.NewCallExpr(base.Pos, ir.OCALL, ir.NewSelectorExpr(base.Pos, ir.OXDOT, ptr, meth), nil)
|
||||
call.Args = ir.ParamNames(tfn.Type())
|
||||
call.IsDDD = tfn.Type().IsVariadic()
|
||||
|
||||
var body ir.Node = call
|
||||
if t0.NumResults() != 0 {
|
||||
ret := ir.NewReturnStmt(base.Pos, nil)
|
||||
ret.Results = []ir.Node{call}
|
||||
body = append(body, ret)
|
||||
} else {
|
||||
body = append(body, call)
|
||||
body = ret
|
||||
}
|
||||
|
||||
fn.Body = body
|
||||
fn.Body = []ir.Node{body}
|
||||
FinishFuncBody()
|
||||
|
||||
Func(fn)
|
||||
@ -296,20 +293,20 @@ func tcClosure(clo *ir.ClosureExpr, top int) {
|
||||
fn.Iota = x
|
||||
}
|
||||
|
||||
fn.ClosureType = typecheckNtype(fn.ClosureType)
|
||||
clo.SetType(fn.ClosureType.Type())
|
||||
fn.SetClosureCalled(top&ctxCallee != 0)
|
||||
|
||||
// Do not typecheck fn twice, otherwise, we will end up pushing
|
||||
// fn to Target.Decls multiple times, causing initLSym called twice.
|
||||
// See #30709
|
||||
if fn.Typecheck() == 1 {
|
||||
clo.SetType(fn.Type())
|
||||
return
|
||||
}
|
||||
|
||||
fn.Nname.SetSym(closurename(ir.CurFunc))
|
||||
ir.MarkFunc(fn.Nname)
|
||||
Func(fn)
|
||||
clo.SetType(fn.Type())
|
||||
|
||||
// Type check the body now, but only if we're inside a function.
|
||||
// At top level (in a variable initialization: curfn==nil) we're not
|
||||
@ -367,10 +364,6 @@ func tcFunc(n *ir.Func) {
|
||||
n.Nname.SetSym(ir.MethodSym(rcvr.Type, n.Shortname))
|
||||
Declare(n.Nname, ir.PFUNC)
|
||||
}
|
||||
|
||||
if base.Ctxt.Flag_dynlink && !inimport && n.Nname != nil {
|
||||
NeedFuncSym(n.Sym())
|
||||
}
|
||||
}
|
||||
|
||||
// tcCall typechecks an OCALL node.
|
||||
|
@ -422,6 +422,10 @@ type exportWriter struct {
|
||||
prevFile string
|
||||
prevLine int64
|
||||
prevColumn int64
|
||||
|
||||
// dclIndex maps function-scoped declarations to their index
|
||||
// within their respective Func's Dcl list.
|
||||
dclIndex map[*ir.Name]int
|
||||
}
|
||||
|
||||
func (p *iexporter) doDecl(n *ir.Name) {
|
||||
@ -529,7 +533,8 @@ func (p *iexporter) doInline(f *ir.Name) {
|
||||
w := p.newWriter()
|
||||
w.setPkg(fnpkg(f), false)
|
||||
|
||||
w.stmtList(ir.Nodes(f.Func.Inl.Body))
|
||||
w.dclIndex = make(map[*ir.Name]int, len(f.Func.Inl.Dcl))
|
||||
w.funcBody(f.Func)
|
||||
|
||||
w.finish("inl", p.inlineIndex, f.Sym())
|
||||
}
|
||||
@ -756,7 +761,7 @@ func (w *exportWriter) paramList(fs []*types.Field) {
|
||||
|
||||
func (w *exportWriter) param(f *types.Field) {
|
||||
w.pos(f.Pos)
|
||||
w.localIdent(types.OrigSym(f.Sym), 0)
|
||||
w.localIdent(types.OrigSym(f.Sym))
|
||||
w.typ(f.Type)
|
||||
}
|
||||
|
||||
@ -1030,7 +1035,19 @@ func (w *exportWriter) typeExt(t *types.Type) {
|
||||
|
||||
// Inline bodies.
|
||||
|
||||
func (w *exportWriter) stmtList(list ir.Nodes) {
|
||||
func (w *exportWriter) funcBody(fn *ir.Func) {
|
||||
w.int64(int64(len(fn.Inl.Dcl)))
|
||||
for i, n := range fn.Inl.Dcl {
|
||||
w.pos(n.Pos())
|
||||
w.localIdent(n.Sym())
|
||||
w.typ(n.Type())
|
||||
w.dclIndex[n] = i
|
||||
}
|
||||
|
||||
w.stmtList(fn.Inl.Body)
|
||||
}
|
||||
|
||||
func (w *exportWriter) stmtList(list []ir.Node) {
|
||||
for _, n := range list {
|
||||
w.node(n)
|
||||
}
|
||||
@ -1070,10 +1087,11 @@ func (w *exportWriter) stmt(n ir.Node) {
|
||||
|
||||
case ir.ODCL:
|
||||
n := n.(*ir.Decl)
|
||||
if ir.IsBlank(n.X) {
|
||||
return // blank declarations not useful to importers
|
||||
}
|
||||
w.op(ir.ODCL)
|
||||
w.pos(n.X.Pos())
|
||||
w.localName(n.X)
|
||||
w.typ(n.X.Type())
|
||||
|
||||
case ir.OAS:
|
||||
// Don't export "v = <N>" initializing statements, hope they're always
|
||||
@ -1288,7 +1306,7 @@ func (w *exportWriter) expr(n ir.Node) {
|
||||
}
|
||||
s = n.Tag.Sym()
|
||||
}
|
||||
w.localIdent(s, 0) // declared pseudo-variable, if any
|
||||
w.localIdent(s) // declared pseudo-variable, if any
|
||||
w.expr(n.X)
|
||||
|
||||
// case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC:
|
||||
@ -1518,22 +1536,19 @@ func (w *exportWriter) fieldList(list ir.Nodes) {
|
||||
}
|
||||
|
||||
func (w *exportWriter) localName(n *ir.Name) {
|
||||
// Escape analysis happens after inline bodies are saved, but
|
||||
// we're using the same ONAME nodes, so we might still see
|
||||
// PAUTOHEAP here.
|
||||
//
|
||||
// Check for Stackcopy to identify PAUTOHEAP that came from
|
||||
// PPARAM/PPARAMOUT, because we only want to include vargen in
|
||||
// non-param names.
|
||||
var v int32
|
||||
if n.Class == ir.PAUTO || (n.Class == ir.PAUTOHEAP && n.Stackcopy == nil) {
|
||||
v = n.Vargen
|
||||
if ir.IsBlank(n) {
|
||||
w.int64(-1)
|
||||
return
|
||||
}
|
||||
|
||||
w.localIdent(n.Sym(), v)
|
||||
i, ok := w.dclIndex[n]
|
||||
if !ok {
|
||||
base.FatalfAt(n.Pos(), "missing from dclIndex: %+v", n)
|
||||
}
|
||||
w.int64(int64(i))
|
||||
}
|
||||
|
||||
func (w *exportWriter) localIdent(s *types.Sym, v int32) {
|
||||
func (w *exportWriter) localIdent(s *types.Sym) {
|
||||
if w.currPkg == nil {
|
||||
base.Fatalf("missing currPkg")
|
||||
}
|
||||
@ -1555,13 +1570,6 @@ func (w *exportWriter) localIdent(s *types.Sym, v int32) {
|
||||
base.Fatalf("unexpected dot in identifier: %v", name)
|
||||
}
|
||||
|
||||
if v > 0 {
|
||||
if strings.Contains(name, "·") {
|
||||
base.Fatalf("exporter: unexpected · in symbol name")
|
||||
}
|
||||
name = fmt.Sprintf("%s·%d", name, v)
|
||||
}
|
||||
|
||||
if s.Pkg != w.currPkg {
|
||||
base.Fatalf("weird package in name: %v => %v from %q, not %q", s, name, s.Pkg.Path, w.currPkg.Path)
|
||||
}
|
||||
|
@ -262,6 +262,9 @@ type importReader struct {
|
||||
prevBase *src.PosBase
|
||||
prevLine int64
|
||||
prevColumn int64
|
||||
|
||||
// curfn is the current function we're importing into.
|
||||
curfn *ir.Func
|
||||
}
|
||||
|
||||
func (p *iimporter) newReader(off uint64, pkg *types.Pkg) *importReader {
|
||||
@ -715,19 +718,7 @@ func (r *importReader) doInline(fn *ir.Func) {
|
||||
base.Fatalf("%v already has inline body", fn)
|
||||
}
|
||||
|
||||
StartFuncBody(fn)
|
||||
body := r.stmtList()
|
||||
FinishFuncBody()
|
||||
if body == nil {
|
||||
//
|
||||
// Make sure empty body is not interpreted as
|
||||
// no inlineable body (see also parser.fnbody)
|
||||
// (not doing so can cause significant performance
|
||||
// degradation due to unnecessary calls to empty
|
||||
// functions).
|
||||
body = []ir.Node{}
|
||||
}
|
||||
fn.Inl.Body = body
|
||||
r.funcBody(fn)
|
||||
|
||||
importlist = append(importlist, fn)
|
||||
|
||||
@ -755,6 +746,68 @@ func (r *importReader) doInline(fn *ir.Func) {
|
||||
// unrefined nodes (since this is what the importer uses). The respective case
|
||||
// entries are unreachable in the importer.
|
||||
|
||||
func (r *importReader) funcBody(fn *ir.Func) {
|
||||
outerfn := r.curfn
|
||||
r.curfn = fn
|
||||
|
||||
// Import local declarations.
|
||||
dcls := make([]*ir.Name, r.int64())
|
||||
for i := range dcls {
|
||||
n := ir.NewDeclNameAt(r.pos(), ir.ONAME, r.localIdent())
|
||||
n.Class = ir.PAUTO // overwritten below for parameters/results
|
||||
n.Curfn = fn
|
||||
n.SetType(r.typ())
|
||||
dcls[i] = n
|
||||
}
|
||||
fn.Inl.Dcl = dcls
|
||||
|
||||
// Fixup parameter classes and associate with their
|
||||
// signature's type fields.
|
||||
i := 0
|
||||
fix := func(f *types.Field, class ir.Class) {
|
||||
if class == ir.PPARAM && (f.Sym == nil || f.Sym.Name == "_") {
|
||||
return
|
||||
}
|
||||
n := dcls[i]
|
||||
n.Class = class
|
||||
f.Nname = n
|
||||
i++
|
||||
}
|
||||
|
||||
typ := fn.Type()
|
||||
if recv := typ.Recv(); recv != nil {
|
||||
fix(recv, ir.PPARAM)
|
||||
}
|
||||
for _, f := range typ.Params().FieldSlice() {
|
||||
fix(f, ir.PPARAM)
|
||||
}
|
||||
for _, f := range typ.Results().FieldSlice() {
|
||||
fix(f, ir.PPARAMOUT)
|
||||
}
|
||||
|
||||
// Import function body.
|
||||
body := r.stmtList()
|
||||
if body == nil {
|
||||
// Make sure empty body is not interpreted as
|
||||
// no inlineable body (see also parser.fnbody)
|
||||
// (not doing so can cause significant performance
|
||||
// degradation due to unnecessary calls to empty
|
||||
// functions).
|
||||
body = []ir.Node{}
|
||||
}
|
||||
fn.Inl.Body = body
|
||||
|
||||
r.curfn = outerfn
|
||||
}
|
||||
|
||||
func (r *importReader) localName() *ir.Name {
|
||||
i := r.int64()
|
||||
if i < 0 {
|
||||
return ir.BlankNode.(*ir.Name)
|
||||
}
|
||||
return r.curfn.Inl.Dcl[i]
|
||||
}
|
||||
|
||||
func (r *importReader) stmtList() []ir.Node {
|
||||
var list []ir.Node
|
||||
for {
|
||||
@ -784,13 +837,8 @@ func (r *importReader) caseList(switchExpr ir.Node) []*ir.CaseClause {
|
||||
cas := ir.NewCaseStmt(r.pos(), nil, nil)
|
||||
cas.List = r.stmtList()
|
||||
if namedTypeSwitch {
|
||||
// Note: per-case variables will have distinct, dotted
|
||||
// names after import. That's okay: swt.go only needs
|
||||
// Sym for diagnostics anyway.
|
||||
caseVar := ir.NewNameAt(cas.Pos(), r.localIdent())
|
||||
Declare(caseVar, DeclContext)
|
||||
cas.Var = caseVar
|
||||
caseVar.Defn = switchExpr
|
||||
cas.Var = r.localName()
|
||||
cas.Var.Defn = switchExpr
|
||||
}
|
||||
cas.Body = r.stmtList()
|
||||
cases[i] = cas
|
||||
@ -854,7 +902,7 @@ func (r *importReader) node() ir.Node {
|
||||
return r.qualifiedIdent()
|
||||
|
||||
case ir.ONAME:
|
||||
return r.localIdent().Def.(*ir.Name)
|
||||
return r.localName()
|
||||
|
||||
// case OPACK, ONONAME:
|
||||
// unreachable - should have been resolved by typechecking
|
||||
@ -991,16 +1039,11 @@ func (r *importReader) node() ir.Node {
|
||||
// --------------------------------------------------------------------
|
||||
// statements
|
||||
case ir.ODCL:
|
||||
pos := r.pos()
|
||||
lhs := ir.NewDeclNameAt(pos, ir.ONAME, r.localIdent())
|
||||
lhs.SetType(r.typ())
|
||||
|
||||
Declare(lhs, ir.PAUTO)
|
||||
|
||||
var stmts ir.Nodes
|
||||
stmts.Append(ir.NewDecl(base.Pos, ir.ODCL, lhs))
|
||||
stmts.Append(ir.NewAssignStmt(base.Pos, lhs, nil))
|
||||
return ir.NewBlockStmt(pos, stmts)
|
||||
n := r.localName()
|
||||
stmts.Append(ir.NewDecl(n.Pos(), ir.ODCL, n))
|
||||
stmts.Append(ir.NewAssignStmt(n.Pos(), n, nil))
|
||||
return ir.NewBlockStmt(n.Pos(), stmts)
|
||||
|
||||
// case OAS, OASWB:
|
||||
// unreachable - mapped to OAS case below by exporter
|
||||
|
@ -24,7 +24,6 @@ var inimport bool // set during import
|
||||
var TypecheckAllowed bool
|
||||
|
||||
var (
|
||||
NeedFuncSym = func(*types.Sym) {}
|
||||
NeedITab = func(t, itype *types.Type) {}
|
||||
NeedRuntimeType = func(*types.Type) {}
|
||||
)
|
||||
@ -789,9 +788,6 @@ func typecheck1(n ir.Node, top int) ir.Node {
|
||||
n := n.(*ir.UnaryExpr)
|
||||
return tcSPtr(n)
|
||||
|
||||
case ir.OCLOSUREREAD:
|
||||
return n
|
||||
|
||||
case ir.OCFUNC:
|
||||
n := n.(*ir.UnaryExpr)
|
||||
n.X = Expr(n.X)
|
||||
@ -1143,12 +1139,6 @@ func typecheckMethodExpr(n *ir.SelectorExpr) (res ir.Node) {
|
||||
n.SetOp(ir.OMETHEXPR)
|
||||
n.Selection = m
|
||||
n.SetType(NewMethodType(m.Type, n.X.Type()))
|
||||
|
||||
// Issue 25065. Make sure that we emit the symbol for a local method.
|
||||
if base.Ctxt.Flag_dynlink && !inimport && (t.Sym() == nil || t.Sym().Pkg == types.LocalPkg) {
|
||||
NeedFuncSym(n.FuncName().Sym())
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
@ -1684,13 +1674,22 @@ func CheckMapKeys() {
|
||||
mapqueue = nil
|
||||
}
|
||||
|
||||
// typegen tracks the number of function-scoped defined types that
|
||||
// have been declared. It's used to generate unique linker symbols for
|
||||
// their runtime type descriptors.
|
||||
var typegen int32
|
||||
|
||||
func typecheckdeftype(n *ir.Name) {
|
||||
if base.EnableTrace && base.Flag.LowerT {
|
||||
defer tracePrint("typecheckdeftype", n)(nil)
|
||||
}
|
||||
|
||||
t := types.NewNamed(n)
|
||||
t.Vargen = n.Vargen
|
||||
if n.Curfn != nil {
|
||||
typegen++
|
||||
t.Vargen = typegen
|
||||
}
|
||||
|
||||
if n.Pragma()&ir.NotInHeap != 0 {
|
||||
t.SetNotInHeap(true)
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ func AlgType(t *Type) (AlgKind, *Type) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
base.Fatalf("algtype1: unexpected type %v", t)
|
||||
base.Fatalf("algtype: unexpected type %v", t)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
|
@ -12,126 +12,108 @@ import (
|
||||
"cmd/internal/src"
|
||||
)
|
||||
|
||||
// Closure is called in a separate phase after escape analysis.
|
||||
// It transform closure bodies to properly reference captured variables.
|
||||
func Closure(fn *ir.Func) {
|
||||
lno := base.Pos
|
||||
base.Pos = fn.Pos()
|
||||
// directClosureCall rewrites a direct call of a function literal into
|
||||
// a normal function call with closure variables passed as arguments.
|
||||
// This avoids allocation of a closure object.
|
||||
//
|
||||
// For illustration, the following call:
|
||||
//
|
||||
// func(a int) {
|
||||
// println(byval)
|
||||
// byref++
|
||||
// }(42)
|
||||
//
|
||||
// becomes:
|
||||
//
|
||||
// func(byval int, &byref *int, a int) {
|
||||
// println(byval)
|
||||
// (*&byref)++
|
||||
// }(byval, &byref, 42)
|
||||
func directClosureCall(n *ir.CallExpr) {
|
||||
clo := n.X.(*ir.ClosureExpr)
|
||||
clofn := clo.Func
|
||||
|
||||
if fn.ClosureCalled() {
|
||||
// If the closure is directly called, we transform it to a plain function call
|
||||
// with variables passed as args. This avoids allocation of a closure object.
|
||||
// Here we do only a part of the transformation. Walk of OCALLFUNC(OCLOSURE)
|
||||
// will complete the transformation later.
|
||||
// For illustration, the following closure:
|
||||
// func(a int) {
|
||||
// println(byval)
|
||||
// byref++
|
||||
// }(42)
|
||||
// becomes:
|
||||
// func(byval int, &byref *int, a int) {
|
||||
// println(byval)
|
||||
// (*&byref)++
|
||||
// }(byval, &byref, 42)
|
||||
|
||||
// f is ONAME of the actual function.
|
||||
f := fn.Nname
|
||||
|
||||
// We are going to insert captured variables before input args.
|
||||
var params []*types.Field
|
||||
var decls []*ir.Name
|
||||
for _, v := range fn.ClosureVars {
|
||||
if !v.Byval() {
|
||||
// If v of type T is captured by reference,
|
||||
// we introduce function param &v *T
|
||||
// and v remains PAUTOHEAP with &v heapaddr
|
||||
// (accesses will implicitly deref &v).
|
||||
addr := typecheck.NewName(typecheck.Lookup("&" + v.Sym().Name))
|
||||
addr.SetType(types.NewPtr(v.Type()))
|
||||
v.Heapaddr = addr
|
||||
v = addr
|
||||
}
|
||||
|
||||
v.Class = ir.PPARAM
|
||||
decls = append(decls, v)
|
||||
|
||||
fld := types.NewField(src.NoXPos, v.Sym(), v.Type())
|
||||
fld.Nname = v
|
||||
params = append(params, fld)
|
||||
}
|
||||
|
||||
if len(params) > 0 {
|
||||
// Prepend params and decls.
|
||||
f.Type().Params().SetFields(append(params, f.Type().Params().FieldSlice()...))
|
||||
fn.Dcl = append(decls, fn.Dcl...)
|
||||
}
|
||||
|
||||
types.CalcSize(f.Type())
|
||||
fn.Nname.SetType(f.Type()) // update type of ODCLFUNC
|
||||
} else {
|
||||
// The closure is not called, so it is going to stay as closure.
|
||||
var body []ir.Node
|
||||
offset := int64(types.PtrSize)
|
||||
for _, v := range fn.ClosureVars {
|
||||
// cv refers to the field inside of closure OSTRUCTLIT.
|
||||
typ := v.Type()
|
||||
if !v.Byval() {
|
||||
typ = types.NewPtr(typ)
|
||||
}
|
||||
offset = types.Rnd(offset, int64(typ.Align))
|
||||
cr := ir.NewClosureRead(typ, offset)
|
||||
offset += typ.Width
|
||||
|
||||
if v.Byval() && v.Type().Width <= int64(2*types.PtrSize) {
|
||||
// If it is a small variable captured by value, downgrade it to PAUTO.
|
||||
v.Class = ir.PAUTO
|
||||
fn.Dcl = append(fn.Dcl, v)
|
||||
body = append(body, ir.NewAssignStmt(base.Pos, v, cr))
|
||||
} else {
|
||||
// Declare variable holding addresses taken from closure
|
||||
// and initialize in entry prologue.
|
||||
addr := typecheck.NewName(typecheck.Lookup("&" + v.Sym().Name))
|
||||
addr.SetType(types.NewPtr(v.Type()))
|
||||
addr.Class = ir.PAUTO
|
||||
addr.SetUsed(true)
|
||||
addr.Curfn = fn
|
||||
fn.Dcl = append(fn.Dcl, addr)
|
||||
v.Heapaddr = addr
|
||||
var src ir.Node = cr
|
||||
if v.Byval() {
|
||||
src = typecheck.NodAddr(cr)
|
||||
}
|
||||
body = append(body, ir.NewAssignStmt(base.Pos, addr, src))
|
||||
}
|
||||
}
|
||||
|
||||
if len(body) > 0 {
|
||||
typecheck.Stmts(body)
|
||||
fn.Enter = body
|
||||
fn.SetNeedctxt(true)
|
||||
}
|
||||
if ir.IsTrivialClosure(clo) {
|
||||
return // leave for walkClosure to handle
|
||||
}
|
||||
|
||||
base.Pos = lno
|
||||
// We are going to insert captured variables before input args.
|
||||
var params []*types.Field
|
||||
var decls []*ir.Name
|
||||
for _, v := range clofn.ClosureVars {
|
||||
if !v.Byval() {
|
||||
// If v of type T is captured by reference,
|
||||
// we introduce function param &v *T
|
||||
// and v remains PAUTOHEAP with &v heapaddr
|
||||
// (accesses will implicitly deref &v).
|
||||
|
||||
addr := ir.NewNameAt(clofn.Pos(), typecheck.Lookup("&"+v.Sym().Name))
|
||||
addr.Curfn = clofn
|
||||
addr.SetType(types.NewPtr(v.Type()))
|
||||
v.Heapaddr = addr
|
||||
v = addr
|
||||
}
|
||||
|
||||
v.Class = ir.PPARAM
|
||||
decls = append(decls, v)
|
||||
|
||||
fld := types.NewField(src.NoXPos, v.Sym(), v.Type())
|
||||
fld.Nname = v
|
||||
params = append(params, fld)
|
||||
}
|
||||
|
||||
// f is ONAME of the actual function.
|
||||
f := clofn.Nname
|
||||
typ := f.Type()
|
||||
|
||||
// Create new function type with parameters prepended, and
|
||||
// then update type and declarations.
|
||||
typ = types.NewSignature(typ.Pkg(), nil, append(params, typ.Params().FieldSlice()...), typ.Results().FieldSlice())
|
||||
f.SetType(typ)
|
||||
clofn.Dcl = append(decls, clofn.Dcl...)
|
||||
|
||||
// Rewrite call.
|
||||
n.X = f
|
||||
n.Args.Prepend(closureArgs(clo)...)
|
||||
|
||||
// Update the call expression's type. We need to do this
|
||||
// because typecheck gave it the result type of the OCLOSURE
|
||||
// node, but we only rewrote the ONAME node's type. Logically,
|
||||
// they're the same, but the stack offsets probably changed.
|
||||
if typ.NumResults() == 1 {
|
||||
n.SetType(typ.Results().Field(0).Type)
|
||||
} else {
|
||||
n.SetType(typ.Results())
|
||||
}
|
||||
|
||||
// Add to Closures for enqueueFunc. It's no longer a proper
|
||||
// closure, but we may have already skipped over it in the
|
||||
// functions list as a non-trivial closure, so this just
|
||||
// ensures it's compiled.
|
||||
ir.CurFunc.Closures = append(ir.CurFunc.Closures, clofn)
|
||||
}
|
||||
|
||||
func walkClosure(clo *ir.ClosureExpr, init *ir.Nodes) ir.Node {
|
||||
fn := clo.Func
|
||||
clofn := clo.Func
|
||||
|
||||
// If no closure vars, don't bother wrapping.
|
||||
if ir.IsTrivialClosure(clo) {
|
||||
if base.Debug.Closure > 0 {
|
||||
base.WarnfAt(clo.Pos(), "closure converted to global")
|
||||
}
|
||||
return fn.Nname
|
||||
return clofn.Nname
|
||||
}
|
||||
|
||||
// The closure is not trivial or directly called, so it's going to stay a closure.
|
||||
ir.ClosureDebugRuntimeCheck(clo)
|
||||
clofn.SetNeedctxt(true)
|
||||
ir.CurFunc.Closures = append(ir.CurFunc.Closures, clofn)
|
||||
|
||||
typ := typecheck.ClosureType(clo)
|
||||
|
||||
clos := ir.NewCompLitExpr(base.Pos, ir.OCOMPLIT, ir.TypeNode(typ), nil)
|
||||
clos.SetEsc(clo.Esc())
|
||||
clos.List = append([]ir.Node{ir.NewUnaryExpr(base.Pos, ir.OCFUNC, fn.Nname)}, closureArgs(clo)...)
|
||||
clos.List = append([]ir.Node{ir.NewUnaryExpr(base.Pos, ir.OCFUNC, clofn.Nname)}, closureArgs(clo)...)
|
||||
|
||||
addr := typecheck.NodAddr(clos)
|
||||
addr.SetEsc(clo.Esc())
|
||||
|
@ -52,19 +52,15 @@ func walkExpr(n ir.Node, init *ir.Nodes) ir.Node {
|
||||
base.Fatalf("expression has untyped type: %+v", n)
|
||||
}
|
||||
|
||||
if n.Op() == ir.ONAME && n.(*ir.Name).Class == ir.PAUTOHEAP {
|
||||
n := n.(*ir.Name)
|
||||
nn := ir.NewStarExpr(base.Pos, n.Heapaddr)
|
||||
nn.X.MarkNonNil()
|
||||
return walkExpr(typecheck.Expr(nn), init)
|
||||
}
|
||||
|
||||
n = walkExpr1(n, init)
|
||||
|
||||
// Eagerly compute sizes of all expressions for the back end.
|
||||
if typ := n.Type(); typ != nil && typ.Kind() != types.TBLANK && !typ.IsFuncArgStruct() {
|
||||
types.CheckSize(typ)
|
||||
}
|
||||
if n, ok := n.(*ir.Name); ok && n.Heapaddr != nil {
|
||||
types.CheckSize(n.Heapaddr.Type())
|
||||
}
|
||||
if ir.IsConst(n, constant.String) {
|
||||
// Emit string symbol now to avoid emitting
|
||||
// any concurrently during the backend.
|
||||
@ -166,7 +162,7 @@ func walkExpr1(n ir.Node, init *ir.Nodes) ir.Node {
|
||||
n := n.(*ir.CallExpr)
|
||||
return mkcall("gorecover", n.Type(), init, typecheck.NodAddr(ir.RegFP))
|
||||
|
||||
case ir.OCLOSUREREAD, ir.OCFUNC:
|
||||
case ir.OCFUNC:
|
||||
return n
|
||||
|
||||
case ir.OCALLINTER, ir.OCALLFUNC, ir.OCALLMETH:
|
||||
@ -493,23 +489,7 @@ func walkCall(n *ir.CallExpr, init *ir.Nodes) ir.Node {
|
||||
}
|
||||
|
||||
if n.Op() == ir.OCALLFUNC && n.X.Op() == ir.OCLOSURE {
|
||||
// Transform direct call of a closure to call of a normal function.
|
||||
// transformclosure already did all preparation work.
|
||||
|
||||
// Prepend captured variables to argument list.
|
||||
clo := n.X.(*ir.ClosureExpr)
|
||||
n.Args.Prepend(closureArgs(clo)...)
|
||||
|
||||
// Replace OCLOSURE with ONAME/PFUNC.
|
||||
n.X = clo.Func.Nname
|
||||
|
||||
// Update type of OCALLFUNC node.
|
||||
// Output arguments had not changed, but their offsets could.
|
||||
if n.X.Type().NumResults() == 1 {
|
||||
n.SetType(n.X.Type().Results().Field(0).Type)
|
||||
} else {
|
||||
n.SetType(n.X.Type().Results())
|
||||
}
|
||||
directClosureCall(n)
|
||||
}
|
||||
|
||||
walkCall1(n, init)
|
||||
|
@ -481,7 +481,7 @@ func calcHasCall(n ir.Node) bool {
|
||||
n := n.(*ir.SelectorExpr)
|
||||
return n.X.HasCall()
|
||||
|
||||
case ir.OGETG, ir.OCLOSUREREAD, ir.OMETHEXPR:
|
||||
case ir.OGETG, ir.OMETHEXPR:
|
||||
return false
|
||||
|
||||
// TODO(rsc): These look wrong in various ways but are what calcHasCall has always done.
|
||||
|
@ -1,4 +1,4 @@
|
||||
// errorcheck -0 -live -l -d=compilelater
|
||||
// errorcheck -0 -live -l
|
||||
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
|
@ -9,11 +9,17 @@
|
||||
|
||||
package main
|
||||
|
||||
type Big = [400e6]byte
|
||||
|
||||
func f() { // GC_ERROR "stack frame too large"
|
||||
var x [800e6]byte
|
||||
g(x)
|
||||
return
|
||||
// Note: This test relies on the fact that we currently always
|
||||
// spill function-results to the stack, even if they're so
|
||||
// large that we would normally heap allocate them. If we ever
|
||||
// improve the backend to spill temporaries to the heap, this
|
||||
// test will probably need updating to find some new way to
|
||||
// construct an overly large stack frame.
|
||||
g(h(), h())
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func g([800e6]byte) {}
|
||||
func g(Big, Big)
|
||||
func h() Big
|
||||
|
62
test/fixedbugs/issue20780b.go
Normal file
62
test/fixedbugs/issue20780b.go
Normal file
@ -0,0 +1,62 @@
|
||||
// +build cgo,linux,amd64
|
||||
// run -race
|
||||
|
||||
// Copyright 2021 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.
|
||||
|
||||
// Test that CL 281293 doesn't interfere with race detector
|
||||
// instrumentation.
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
const N = 2e6
|
||||
|
||||
type Big = [N]int
|
||||
|
||||
var sink interface{}
|
||||
|
||||
func main() {
|
||||
g(0, f(0))
|
||||
|
||||
x1 := f(1)
|
||||
sink = &x1
|
||||
g(1, x1)
|
||||
g(7, f(7))
|
||||
g(1, x1)
|
||||
|
||||
x3 := f(3)
|
||||
sink = &x3
|
||||
g(1, x1)
|
||||
g(3, x3)
|
||||
|
||||
h(f(0), x1, f(2), x3, f(4))
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func f(k int) (x Big) {
|
||||
for i := range x {
|
||||
x[i] = k*N + i
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func g(k int, x Big) {
|
||||
for i := range x {
|
||||
if x[i] != k*N+i {
|
||||
panic(fmt.Sprintf("x%d[%d] = %d", k, i, x[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func h(x0, x1, x2, x3, x4 Big) {
|
||||
g(0, x0)
|
||||
g(1, x1)
|
||||
g(2, x2)
|
||||
g(3, x3)
|
||||
g(4, x4)
|
||||
}
|
28
test/fixedbugs/issue43633.dir/a.go
Normal file
28
test/fixedbugs/issue43633.dir/a.go
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2021 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 a
|
||||
|
||||
func F() bool {
|
||||
{
|
||||
x := false
|
||||
_ = x
|
||||
}
|
||||
if false {
|
||||
_ = func(x bool) {}
|
||||
}
|
||||
x := true
|
||||
return x
|
||||
}
|
||||
|
||||
func G() func() bool {
|
||||
x := true
|
||||
return func() bool {
|
||||
{
|
||||
x := false
|
||||
_ = x
|
||||
}
|
||||
return x
|
||||
}
|
||||
}
|
18
test/fixedbugs/issue43633.dir/main.go
Normal file
18
test/fixedbugs/issue43633.dir/main.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2021 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 main
|
||||
|
||||
import "./a"
|
||||
|
||||
var g = a.G()
|
||||
|
||||
func main() {
|
||||
if !a.F() {
|
||||
panic("FAIL")
|
||||
}
|
||||
if !g() {
|
||||
panic("FAIL")
|
||||
}
|
||||
}
|
7
test/fixedbugs/issue43633.go
Normal file
7
test/fixedbugs/issue43633.go
Normal file
@ -0,0 +1,7 @@
|
||||
// rundir
|
||||
|
||||
// Copyright 2021 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 ignored
|
@ -41,7 +41,7 @@ func bufferNoEscape3(xs []string) string { // ERROR "xs does not escape$"
|
||||
|
||||
func bufferNoEscape4() []byte {
|
||||
var b bytes.Buffer
|
||||
b.Grow(64) // ERROR "bufferNoEscape4 ignoring self-assignment in bytes.b.buf = bytes.b.buf\[:bytes.m·3\]$" "inlining call to bytes.\(\*Buffer\).Grow$"
|
||||
b.Grow(64) // ERROR "bufferNoEscape4 ignoring self-assignment in bytes.b.buf = bytes.b.buf\[:bytes.m\]$" "inlining call to bytes.\(\*Buffer\).Grow$"
|
||||
useBuffer(&b)
|
||||
return b.Bytes() // ERROR "inlining call to bytes.\(\*Buffer\).Bytes$"
|
||||
}
|
||||
|
@ -214,14 +214,6 @@ func p1() byte {
|
||||
return p[5] // ERROR "removed nil check"
|
||||
}
|
||||
|
||||
// make sure not to do nil check for access of PAUTOHEAP
|
||||
//go:noinline
|
||||
func (p *Struct) m() {}
|
||||
func c1() {
|
||||
var x Struct
|
||||
func() { x.m() }() // ERROR "removed nil check"
|
||||
}
|
||||
|
||||
type SS struct {
|
||||
x byte
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user