[dev.regabi] cmd/compile: separate various from Main

Move various code out of Main itself and into helper functions
that can be moved into other packages as package gc splits up.

Similarly, move order and instrument inside walk to reduce the amount
of API surface needed from the eventual package walk.

Change-Id: I7849258038c6e39625a0385af9c0edd6a3b654a1
Reviewed-on: https://go-review.googlesource.com/c/go/+/279304
Trust: Russ Cox <rsc@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
Russ Cox 2020-12-21 02:08:34 -05:00
parent 3b12c6dc08
commit 572f168ed2
9 changed files with 211 additions and 179 deletions

View file

@ -36,7 +36,13 @@ func TestMain(m *testing.M) {
base.Ctxt.Bso = bufio.NewWriter(os.Stdout) base.Ctxt.Bso = bufio.NewWriter(os.Stdout)
Widthptr = thearch.LinkArch.PtrSize Widthptr = thearch.LinkArch.PtrSize
Widthreg = thearch.LinkArch.RegSize Widthreg = thearch.LinkArch.RegSize
initializeTypesPackage() types.TypeLinkSym = func(t *types.Type) *obj.LSym {
return typenamesym(t).Linksym()
}
types.TypeLinkSym = func(t *types.Type) *obj.LSym {
return typenamesym(t).Linksym()
}
TypecheckInit()
os.Exit(m.Run()) os.Exit(m.Run())
} }

View file

@ -442,6 +442,12 @@ type funcStackEnt struct {
dclcontext ir.Class dclcontext ir.Class
} }
func CheckFuncStack() {
if len(funcStack) != 0 {
base.Fatalf("funcStack is non-empty: %v", len(funcStack))
}
}
// finish the body. // finish the body.
// called in auto-declaration context. // called in auto-declaration context.
// returns in extern-declaration context. // returns in extern-declaration context.

View file

@ -129,8 +129,6 @@ var (
iscmp [ir.OEND]bool iscmp [ir.OEND]bool
) )
var importlist []*ir.Func // imported functions and methods with inlinable bodies
var ( var (
funcsymsmu sync.Mutex // protects funcsyms and associated package lookups (see func funcsym) funcsymsmu sync.Mutex // protects funcsyms and associated package lookups (see func funcsym)
funcsyms []*types.Sym funcsyms []*types.Sym

View file

@ -55,6 +55,26 @@ const (
inlineBigFunctionMaxCost = 20 // Max cost of inlinee when inlining into a "big" function. inlineBigFunctionMaxCost = 20 // Max cost of inlinee when inlining into a "big" function.
) )
func InlinePackage() {
// Find functions that can be inlined and clone them before walk expands them.
visitBottomUp(Target.Decls, func(list []*ir.Func, recursive bool) {
numfns := numNonClosures(list)
for _, n := range list {
if !recursive || numfns > 1 {
// We allow inlining if there is no
// recursion, or the recursion cycle is
// across more than one function.
caninl(n)
} else {
if base.Flag.LowerM > 1 {
fmt.Printf("%v: cannot inline %v: recursive\n", ir.Line(n), n.Nname)
}
}
inlcalls(n)
}
})
}
// Get the function's package. For ordinary functions it's on the ->sym, but for imported methods // Get the function's package. For ordinary functions it's on the ->sym, but for imported methods
// the ->sym can be re-used in the local package, so peel it off the receiver's type. // the ->sym can be re-used in the local package, so peel it off the receiver's type.
func fnpkg(fn *ir.Name) *types.Pkg { func fnpkg(fn *ir.Name) *types.Pkg {

View file

@ -191,24 +191,15 @@ func Main(archInit func(*Arch)) {
IsIntrinsicCall = isIntrinsicCall IsIntrinsicCall = isIntrinsicCall
SSADumpInline = ssaDumpInline SSADumpInline = ssaDumpInline
initSSAEnv()
ssaDump = os.Getenv("GOSSAFUNC") initSSATables()
ssaDir = os.Getenv("GOSSADIR")
if ssaDump != "" {
if strings.HasSuffix(ssaDump, "+") {
ssaDump = ssaDump[:len(ssaDump)-1]
ssaDumpStdout = true
}
spl := strings.Split(ssaDump, ":")
if len(spl) > 1 {
ssaDump = spl[0]
ssaDumpCFG = spl[1]
}
}
Widthptr = thearch.LinkArch.PtrSize Widthptr = thearch.LinkArch.PtrSize
Widthreg = thearch.LinkArch.RegSize Widthreg = thearch.LinkArch.RegSize
MaxWidth = thearch.MAXWIDTH MaxWidth = thearch.MAXWIDTH
types.TypeLinkSym = func(t *types.Type) *obj.LSym {
return typenamesym(t).Linksym()
}
Target = new(ir.Package) Target = new(ir.Package)
@ -216,152 +207,40 @@ func Main(archInit func(*Arch)) {
NeedITab = func(t, iface *types.Type) { itabname(t, iface) } NeedITab = func(t, iface *types.Type) { itabname(t, iface) }
NeedRuntimeType = addsignat // TODO(rsc): typenamesym for lock? NeedRuntimeType = addsignat // TODO(rsc): typenamesym for lock?
// initialize types package
// (we need to do this to break dependencies that otherwise
// would lead to import cycles)
initializeTypesPackage()
dclcontext = ir.PEXTERN
autogeneratedPos = makePos(src.NewFileBase("<autogenerated>", "<autogenerated>"), 1, 0) autogeneratedPos = makePos(src.NewFileBase("<autogenerated>", "<autogenerated>"), 1, 0)
timings.Start("fe", "loadsys") types.TypeLinkSym = func(t *types.Type) *obj.LSym {
loadsys() return typenamesym(t).Linksym()
}
TypecheckInit()
// Parse input.
timings.Start("fe", "parse") timings.Start("fe", "parse")
lines := parseFiles(flag.Args()) lines := parseFiles(flag.Args())
cgoSymABIs() cgoSymABIs()
timings.Stop() timings.Stop()
timings.AddEvent(int64(lines), "lines") timings.AddEvent(int64(lines), "lines")
finishUniverse()
recordPackageName() recordPackageName()
typecheckok = true // Typecheck.
TypecheckPackage()
// Process top-level declarations in phases. // With all user code typechecked, it's now safe to verify unused dot imports.
// Phase 1: const, type, and names and types of funcs.
// This will gather all the information about types
// and methods but doesn't depend on any of it.
//
// We also defer type alias declarations until phase 2
// to avoid cycles like #18640.
// TODO(gri) Remove this again once we have a fix for #25838.
// Don't use range--typecheck can add closures to Target.Decls.
timings.Start("fe", "typecheck", "top1")
for i := 0; i < len(Target.Decls); i++ {
n := Target.Decls[i]
if op := n.Op(); op != ir.ODCL && op != ir.OAS && op != ir.OAS2 && (op != ir.ODCLTYPE || !n.(*ir.Decl).Left().Name().Alias()) {
Target.Decls[i] = typecheck(n, ctxStmt)
}
}
// Phase 2: Variable assignments.
// To check interface assignments, depends on phase 1.
// Don't use range--typecheck can add closures to Target.Decls.
timings.Start("fe", "typecheck", "top2")
for i := 0; i < len(Target.Decls); i++ {
n := Target.Decls[i]
if op := n.Op(); op == ir.ODCL || op == ir.OAS || op == ir.OAS2 || op == ir.ODCLTYPE && n.(*ir.Decl).Left().Name().Alias() {
Target.Decls[i] = typecheck(n, ctxStmt)
}
}
// Phase 3: Type check function bodies.
// Don't use range--typecheck can add closures to Target.Decls.
timings.Start("fe", "typecheck", "func")
var fcount int64
for i := 0; i < len(Target.Decls); i++ {
n := Target.Decls[i]
if n.Op() == ir.ODCLFUNC {
Curfn = n.(*ir.Func)
decldepth = 1
errorsBefore := base.Errors()
typecheckslice(Curfn.Body().Slice(), ctxStmt)
checkreturn(Curfn)
if base.Errors() > errorsBefore {
Curfn.PtrBody().Set(nil) // type errors; do not compile
}
// Now that we've checked whether n terminates,
// we can eliminate some obviously dead code.
deadcode(Curfn)
fcount++
}
}
// Phase 3.11: Check external declarations.
// TODO(mdempsky): This should be handled when type checking their
// corresponding ODCL nodes.
timings.Start("fe", "typecheck", "externdcls")
for i, n := range Target.Externs {
if n.Op() == ir.ONAME {
Target.Externs[i] = typecheck(Target.Externs[i], ctxExpr)
}
}
// Phase 3.14: With all user code type-checked, it's now safe to verify map keys
// and unused dot imports.
checkMapKeys()
checkDotImports() checkDotImports()
base.ExitIfErrors() base.ExitIfErrors()
timings.AddEvent(fcount, "funcs") // Build init task.
if initTask := fninit(); initTask != nil { if initTask := fninit(); initTask != nil {
exportsym(initTask) exportsym(initTask)
} }
// Phase 4: Decide how to capture closed variables. // Inlining
// This needs to run before escape analysis,
// because variables captured by value do not escape.
timings.Start("fe", "capturevars")
for _, n := range Target.Decls {
if n.Op() == ir.ODCLFUNC && n.Func().OClosure != nil {
Curfn = n.(*ir.Func)
capturevars(Curfn)
}
}
capturevarscomplete = true
Curfn = nil
base.ExitIfErrors()
// Phase 5: Inlining
timings.Start("fe", "inlining") timings.Start("fe", "inlining")
if base.Debug.TypecheckInl != 0 {
// Typecheck imported function bodies if Debug.l > 1,
// otherwise lazily when used or re-exported.
for _, n := range importlist {
if n.Inl != nil {
typecheckinl(n)
}
}
base.ExitIfErrors()
}
if base.Flag.LowerL != 0 { if base.Flag.LowerL != 0 {
// Find functions that can be inlined and clone them before walk expands them. InlinePackage()
visitBottomUp(Target.Decls, func(list []*ir.Func, recursive bool) {
numfns := numNonClosures(list)
for _, n := range list {
if !recursive || numfns > 1 {
// We allow inlining if there is no
// recursion, or the recursion cycle is
// across more than one function.
caninl(n)
} else {
if base.Flag.LowerM > 1 {
fmt.Printf("%v: cannot inline %v: recursive\n", ir.Line(n), n.Nname)
}
}
inlcalls(n)
}
})
} }
// Devirtualize.
for _, n := range Target.Decls { for _, n := range Target.Decls {
if n.Op() == ir.ODCLFUNC { if n.Op() == ir.ODCLFUNC {
devirtualize(n.(*ir.Func)) devirtualize(n.(*ir.Func))
@ -369,7 +248,7 @@ func Main(archInit func(*Arch)) {
} }
Curfn = nil Curfn = nil
// Phase 6: Escape analysis. // Escape analysis.
// Required for moving heap allocations onto stack, // Required for moving heap allocations onto stack,
// which in turn is required by the closure implementation, // which in turn is required by the closure implementation,
// which stores the addresses of stack variables into the closure. // which stores the addresses of stack variables into the closure.
@ -388,7 +267,7 @@ func Main(archInit func(*Arch)) {
EnableNoWriteBarrierRecCheck() EnableNoWriteBarrierRecCheck()
} }
// Phase 7: Transform closure bodies to properly reference captured variables. // Transform closure bodies to properly reference captured variables.
// This needs to happen before walk, because closures must be transformed // This needs to happen before walk, because closures must be transformed
// before walk reaches a call of a closure. // before walk reaches a call of a closure.
timings.Start("fe", "xclosures") timings.Start("fe", "xclosures")
@ -410,10 +289,10 @@ func Main(archInit func(*Arch)) {
Curfn = nil Curfn = nil
peekitabs() peekitabs()
// Phase 8: Compile top level functions. // Compile top level functions.
// Don't use range--walk can add functions to Target.Decls. // Don't use range--walk can add functions to Target.Decls.
timings.Start("be", "compilefuncs") timings.Start("be", "compilefuncs")
fcount = 0 fcount := int64(0)
for i := 0; i < len(Target.Decls); i++ { for i := 0; i < len(Target.Decls); i++ {
n := Target.Decls[i] n := Target.Decls[i]
if n.Op() == ir.ODCLFUNC { if n.Op() == ir.ODCLFUNC {
@ -448,21 +327,9 @@ func Main(archInit func(*Arch)) {
dumpasmhdr() dumpasmhdr()
} }
// Check whether any of the functions we have compiled have gigantic stack frames. CheckLargeStacks()
sort.Slice(largeStackFrames, func(i, j int) bool { CheckFuncStack()
return largeStackFrames[i].pos.Before(largeStackFrames[j].pos)
})
for _, large := range largeStackFrames {
if large.callee != 0 {
base.ErrorfAt(large.pos, "stack frame too large (>1GB): %d MB locals + %d MB args + %d MB callee", large.locals>>20, large.args>>20, large.callee>>20)
} else {
base.ErrorfAt(large.pos, "stack frame too large (>1GB): %d MB locals + %d MB args", large.locals>>20, large.args>>20)
}
}
if len(funcStack) != 0 {
base.Fatalf("funcStack is non-empty: %v", len(funcStack))
}
if len(compilequeue) != 0 { if len(compilequeue) != 0 {
base.Fatalf("%d uncompiled functions", len(compilequeue)) base.Fatalf("%d uncompiled functions", len(compilequeue))
} }
@ -480,6 +347,20 @@ func Main(archInit func(*Arch)) {
} }
} }
func CheckLargeStacks() {
// Check whether any of the functions we have compiled have gigantic stack frames.
sort.Slice(largeStackFrames, func(i, j int) bool {
return largeStackFrames[i].pos.Before(largeStackFrames[j].pos)
})
for _, large := range largeStackFrames {
if large.callee != 0 {
base.ErrorfAt(large.pos, "stack frame too large (>1GB): %d MB locals + %d MB args + %d MB callee", large.locals>>20, large.args>>20, large.callee>>20)
} else {
base.ErrorfAt(large.pos, "stack frame too large (>1GB): %d MB locals + %d MB args", large.locals>>20, large.args>>20)
}
}
}
func cgoSymABIs() { func cgoSymABIs() {
// The linker expects an ABI0 wrapper for all cgo-exported // The linker expects an ABI0 wrapper for all cgo-exported
// functions. // functions.
@ -1140,16 +1021,6 @@ func parseLang(s string) (lang, error) {
return lang{major: major, minor: minor}, nil return lang{major: major, minor: minor}, nil
} }
func initializeTypesPackage() {
types.Widthptr = Widthptr
types.Dowidth = dowidth
types.TypeLinkSym = func(t *types.Type) *obj.LSym {
return typenamesym(t).Linksym()
}
initUniverse()
}
// useNewABIWrapGen returns TRUE if the compiler should generate an // useNewABIWrapGen returns TRUE if the compiler should generate an
// ABI wrapper for the function 'f'. // ABI wrapper for the function 'f'.
func useABIWrapGen(f *ir.Func) bool { func useABIWrapGen(f *ir.Func) bool {

View file

@ -222,24 +222,16 @@ func funccompile(fn *ir.Func) {
} }
func compile(fn *ir.Func) { func compile(fn *ir.Func) {
errorsBefore := base.Errors()
order(fn)
if base.Errors() > errorsBefore {
return
}
// Set up the function's LSym early to avoid data races with the assemblers. // 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 // Do this before walk, as walk needs the LSym to set attributes/relocations
// (e.g. in markTypeUsedInInterface). // (e.g. in markTypeUsedInInterface).
initLSym(fn, true) initLSym(fn, true)
errorsBefore := base.Errors()
walk(fn) walk(fn)
if base.Errors() > errorsBefore { if base.Errors() > errorsBefore {
return return
} }
if instrumenting {
instrument(fn)
}
// From this point, there should be no uses of Curfn. Enforce that. // From this point, there should be no uses of Curfn. Enforce that.
Curfn = nil Curfn = nil

View file

@ -12,6 +12,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"strings"
"bufio" "bufio"
"bytes" "bytes"
@ -48,6 +49,22 @@ func ssaDumpInline(fn *ir.Func) {
} }
} }
func initSSAEnv() {
ssaDump = os.Getenv("GOSSAFUNC")
ssaDir = os.Getenv("GOSSADIR")
if ssaDump != "" {
if strings.HasSuffix(ssaDump, "+") {
ssaDump = ssaDump[:len(ssaDump)-1]
ssaDumpStdout = true
}
spl := strings.Split(ssaDump, ":")
if len(spl) > 1 {
ssaDump = spl[0]
ssaDumpCFG = spl[1]
}
}
}
func initssaconfig() { func initssaconfig() {
types_ := ssa.NewTypes() types_ := ssa.NewTypes()
@ -3357,7 +3374,7 @@ type intrinsicKey struct {
fn string fn string
} }
func init() { func initSSATables() {
intrinsics = map[intrinsicKey]intrinsicBuilder{} intrinsics = map[intrinsicKey]intrinsicBuilder{}
var all []*sys.Arch var all []*sys.Arch

View file

@ -20,6 +20,96 @@ var (
NeedRuntimeType = func(*types.Type) {} NeedRuntimeType = func(*types.Type) {}
) )
func TypecheckInit() {
types.Widthptr = Widthptr
types.Dowidth = dowidth
initUniverse()
dclcontext = ir.PEXTERN
timings.Start("fe", "loadsys")
loadsys()
}
func TypecheckPackage() {
finishUniverse()
typecheckok = true
// Process top-level declarations in phases.
// Phase 1: const, type, and names and types of funcs.
// This will gather all the information about types
// and methods but doesn't depend on any of it.
//
// We also defer type alias declarations until phase 2
// to avoid cycles like #18640.
// TODO(gri) Remove this again once we have a fix for #25838.
// Don't use range--typecheck can add closures to Target.Decls.
timings.Start("fe", "typecheck", "top1")
for i := 0; i < len(Target.Decls); i++ {
n := Target.Decls[i]
if op := n.Op(); op != ir.ODCL && op != ir.OAS && op != ir.OAS2 && (op != ir.ODCLTYPE || !n.(*ir.Decl).Left().Name().Alias()) {
Target.Decls[i] = typecheck(n, ctxStmt)
}
}
// Phase 2: Variable assignments.
// To check interface assignments, depends on phase 1.
// Don't use range--typecheck can add closures to Target.Decls.
timings.Start("fe", "typecheck", "top2")
for i := 0; i < len(Target.Decls); i++ {
n := Target.Decls[i]
if op := n.Op(); op == ir.ODCL || op == ir.OAS || op == ir.OAS2 || op == ir.ODCLTYPE && n.(*ir.Decl).Left().Name().Alias() {
Target.Decls[i] = typecheck(n, ctxStmt)
}
}
// Phase 3: Type check function bodies.
// Don't use range--typecheck can add closures to Target.Decls.
timings.Start("fe", "typecheck", "func")
var fcount int64
for i := 0; i < len(Target.Decls); i++ {
n := Target.Decls[i]
if n.Op() == ir.ODCLFUNC {
TypecheckFuncBody(n.(*ir.Func))
fcount++
}
}
// Phase 4: Check external declarations.
// TODO(mdempsky): This should be handled when type checking their
// corresponding ODCL nodes.
timings.Start("fe", "typecheck", "externdcls")
for i, n := range Target.Externs {
if n.Op() == ir.ONAME {
Target.Externs[i] = typecheck(Target.Externs[i], ctxExpr)
}
}
// Phase 5: With all user code type-checked, it's now safe to verify map keys.
checkMapKeys()
// Phase 6: Decide how to capture closed variables.
// This needs to run before escape analysis,
// because variables captured by value do not escape.
timings.Start("fe", "capturevars")
for _, n := range Target.Decls {
if n.Op() == ir.ODCLFUNC && n.Func().OClosure != nil {
Curfn = n.(*ir.Func)
capturevars(Curfn)
}
}
capturevarscomplete = true
Curfn = nil
if base.Debug.TypecheckInl != 0 {
// Typecheck imported function bodies if Debug.l > 1,
// otherwise lazily when used or re-exported.
TypecheckImports()
}
}
func TypecheckAssignExpr(n ir.Node) ir.Node { return typecheck(n, ctxExpr|ctxAssign) } func TypecheckAssignExpr(n ir.Node) ir.Node { return typecheck(n, ctxExpr|ctxAssign) }
func TypecheckExpr(n ir.Node) ir.Node { return typecheck(n, ctxExpr) } func TypecheckExpr(n ir.Node) ir.Node { return typecheck(n, ctxExpr) }
func TypecheckStmt(n ir.Node) ir.Node { return typecheck(n, ctxStmt) } func TypecheckStmt(n ir.Node) ir.Node { return typecheck(n, ctxStmt) }
@ -45,6 +135,30 @@ func TypecheckCallee(n ir.Node) ir.Node {
return typecheck(n, ctxExpr|ctxCallee) return typecheck(n, ctxExpr|ctxCallee)
} }
func TypecheckFuncBody(n *ir.Func) {
Curfn = n
decldepth = 1
errorsBefore := base.Errors()
typecheckslice(n.Body(), ctxStmt)
checkreturn(n)
if base.Errors() > errorsBefore {
n.PtrBody().Set(nil) // type errors; do not compile
}
// Now that we've checked whether n terminates,
// we can eliminate some obviously dead code.
deadcode(n)
}
var importlist []*ir.Func
func TypecheckImports() {
for _, n := range importlist {
if n.Inl != nil {
typecheckinl(n)
}
}
}
// To enable tracing support (-t flag), set enableTrace to true. // To enable tracing support (-t flag), set enableTrace to true.
const enableTrace = false const enableTrace = false

View file

@ -26,6 +26,10 @@ const zeroValSize = 1024 // must match value of runtime/map.go:maxZero
func walk(fn *ir.Func) { func walk(fn *ir.Func) {
Curfn = fn Curfn = fn
errorsBefore := base.Errors() errorsBefore := base.Errors()
order(fn)
if base.Errors() > errorsBefore {
return
}
if base.Flag.W != 0 { if base.Flag.W != 0 {
s := fmt.Sprintf("\nbefore walk %v", Curfn.Sym()) s := fmt.Sprintf("\nbefore walk %v", Curfn.Sym())
@ -80,6 +84,10 @@ func walk(fn *ir.Func) {
s := fmt.Sprintf("enter %v", Curfn.Sym()) s := fmt.Sprintf("enter %v", Curfn.Sym())
ir.DumpList(s, Curfn.Enter) ir.DumpList(s, Curfn.Enter)
} }
if instrumenting {
instrument(fn)
}
} }
func walkstmtlist(s []ir.Node) { func walkstmtlist(s []ir.Node) {