]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: enable deadcode of unreferenced large global maps
authorThan McIntosh <thanm@google.com>
Tue, 10 Jan 2023 15:21:16 +0000 (10:21 -0500)
committerThan McIntosh <thanm@google.com>
Mon, 6 Feb 2023 20:53:29 +0000 (20:53 +0000)
This patch changes the compiler's pkg init machinery to pick out large
initialization assignments to global maps (e.g.

   var mymap = map[string]int{"foo":1, "bar":2, ... }

and extract the map init code into a separate outlined function, which is
then called from the main init function with a weak relocation:

   var mymap map[string]int   // KEEP reloc -> map.init.0

   func init() {
      map.init.0() // weak relocation
   }

   func map.init.0() {
     mymap = map[string]int{"foo":1, "bar":2}
   }

The map init outlining is done selectively (only in the case where the
RHS code exceeds a size limit of 20 IR nodes).

In order to ensure that a given map.init.NNN function is included when
its corresponding map is live, we add dummy R_KEEP relocation from the
map variable to the map init function.

This first patch includes the main compiler compiler changes, and with
the weak relocation addition disabled. Subsequent patch includes the
requred linker changes along with switching to the call to the
outlined routine to a weak relocation. See the later linker change for
associated compile time performance numbers.

Updates #2559.
Updates #36021.
Updates #14840.

Change-Id: I1fd6fd6397772be1ebd3eb397caf68ae9a3147e9
Reviewed-on: https://go-review.googlesource.com/c/go/+/461315
Run-TryBot: Than McIntosh <thanm@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>

src/cmd/compile/internal/base/debug.go
src/cmd/compile/internal/base/flag.go
src/cmd/compile/internal/gc/compile.go
src/cmd/compile/internal/gc/main.go
src/cmd/compile/internal/pkginit/init.go
src/cmd/compile/internal/ssagen/pgen.go
src/cmd/compile/internal/staticinit/sched.go

index d1b095ad3552f17ff3c52cf1ca51a0f25d64f5e4..04b8469eef71b8d419462026e1352e4100072ef7 100644 (file)
@@ -51,6 +51,8 @@ type DebugFlags struct {
        PGOInlineCDFThreshold string `help:"cummulative threshold percentage for determining call sites as hot candidates for inlining" concurrent:"ok"`
        PGOInlineBudget       int    `help:"inline budget for hot functions" concurrent:"ok"`
        PGOInline             int    `help:"debug profile-guided inlining"`
+       WrapGlobalMapDbg      int    "help:\"debug trace output for global map init wrapping\""
+       WrapGlobalMapStress   int    "help:\"run global map init wrap in stress mode (no size cutoff)\""
 
        ConcurrentOk bool // true if only concurrentOk flags seen
 }
index d6b5b90aaa22bbed331491d29bb0a4013e91e4b8..6eaf89efe3df6c71ca4581ac3826eca03586e472 100644 (file)
@@ -123,6 +123,7 @@ type CmdFlags struct {
        TraceProfile       string       "help:\"write an execution trace to `file`\""
        TrimPath           string       "help:\"remove `prefix` from recorded source file paths\""
        WB                 bool         "help:\"enable write barrier\"" // TODO: remove
+       WrapGlobalMapInit  bool         "help:\"wrap global map large inits in their own functions (to permit deadcode)\""
        PgoProfile         string       "help:\"read profile from `file`\""
 
        // Configuration derived from flags; not a flag itself.
@@ -163,6 +164,7 @@ func ParseFlags() {
        Flag.LinkShared = &Ctxt.Flag_linkshared
        Flag.Shared = &Ctxt.Flag_shared
        Flag.WB = true
+       Flag.WrapGlobalMapInit = true
 
        Debug.ConcurrentOk = true
        Debug.InlFuncsWithClosures = 1
index 6951d7ed5a9ca55a066ca08d779e7f764cb1dcaf..cfce77d8289c7dbffb5ddeaf411279c3e0294576 100644 (file)
@@ -15,6 +15,7 @@ import (
        "cmd/compile/internal/liveness"
        "cmd/compile/internal/objw"
        "cmd/compile/internal/ssagen"
+       "cmd/compile/internal/staticinit"
        "cmd/compile/internal/typecheck"
        "cmd/compile/internal/types"
        "cmd/compile/internal/walk"
@@ -84,6 +85,14 @@ func prepareFunc(fn *ir.Func) {
        // (e.g. in MarkTypeUsedInInterface).
        ir.InitLSym(fn, true)
 
+       // If this function is a compiler-generated outlined global map
+       // initializer function, register its LSym for later processing.
+       if staticinit.MapInitToVar != nil {
+               if _, ok := staticinit.MapInitToVar[fn]; ok {
+                       ssagen.RegisterMapInitLsym(fn.Linksym())
+               }
+       }
+
        // Calculate parameter offsets.
        types.CalcSize(fn.Type())
 
index e391ae7b5a4476ff13a754d5dff283193e1dd089..b2a37b9b024c6f9932d0e3d189bd97bb4751eb78 100644 (file)
@@ -22,6 +22,7 @@ import (
        "cmd/compile/internal/reflectdata"
        "cmd/compile/internal/ssa"
        "cmd/compile/internal/ssagen"
+       "cmd/compile/internal/staticinit"
        "cmd/compile/internal/typecheck"
        "cmd/compile/internal/types"
        "cmd/internal/dwarf"
@@ -330,6 +331,11 @@ func Main(archInit func(*ssagen.ArchInfo)) {
                ssagen.NoWriteBarrierRecCheck()
        }
 
+       // Add keep relocations for global maps.
+       if base.Flag.WrapGlobalMapInit {
+               staticinit.AddKeepRelocations()
+       }
+
        // Finalize DWARF inline routine DIEs, then explicitly turn off
        // DWARF inlining gen so as to avoid problems with generated
        // method wrappers.
index fac1ad790f352af265b1f6d411153d0706376299..84f4c2cfe372c99035ee34811886a17456bb7e2b 100644 (file)
@@ -14,6 +14,8 @@ import (
        "cmd/compile/internal/types"
        "cmd/internal/obj"
        "cmd/internal/src"
+       "fmt"
+       "os"
 )
 
 // MakeInit creates a synthetic init function to handle any
@@ -38,9 +40,16 @@ func MakeInit() {
        typecheck.InitTodoFunc.Dcl = nil
        fn.SetIsPackageInit(true)
 
+       // Outline (if legal/profitable) global map inits.
+       newfuncs := []*ir.Func{}
+       nf, newfuncs = staticinit.OutlineMapInits(nf)
+
        // Suppress useless "can inline" diagnostics.
        // Init functions are only called dynamically.
        fn.SetInlinabilityChecked(true)
+       for _, nfn := range newfuncs {
+               nfn.SetInlinabilityChecked(true)
+       }
 
        fn.Body = nf
        typecheck.FinishFuncBody()
@@ -50,6 +59,16 @@ func MakeInit() {
                typecheck.Stmts(nf)
        })
        typecheck.Target.Decls = append(typecheck.Target.Decls, fn)
+       if base.Debug.WrapGlobalMapDbg > 1 {
+               fmt.Fprintf(os.Stderr, "=-= len(newfuncs) is %d for %v\n",
+                       len(newfuncs), fn)
+       }
+       for _, nfn := range newfuncs {
+               if base.Debug.WrapGlobalMapDbg > 1 {
+                       fmt.Fprintf(os.Stderr, "=-= add to target.decls %v\n", nfn)
+               }
+               typecheck.Target.Decls = append(typecheck.Target.Decls, ir.Node(nfn))
+       }
 
        // Prepend to Inits, so it runs first, before any user-declared init
        // functions.
@@ -110,7 +129,7 @@ func Task() *ir.Name {
                        name := noder.Renameinit()
                        fnInit := typecheck.DeclFunc(name, nil, nil, nil)
 
-                       // Get an array of intrumented global variables.
+                       // Get an array of instrumented global variables.
                        globals := instrumentGlobals(fnInit)
 
                        // Call runtime.asanregisterglobals function to poison redzones.
index ffd51f19c74277cd61e274950aa531e38677fb79..d3b01aceb4249d1a2ffb1da7cb1434b30a5e25d2 100644 (file)
@@ -5,7 +5,9 @@
 package ssagen
 
 import (
+       "fmt"
        "internal/buildcfg"
+       "os"
        "sort"
        "sync"
 
@@ -208,10 +210,60 @@ func Compile(fn *ir.Func, worker int) {
        }
 
        pp.Flush() // assemble, fill in boilerplate, etc.
+
+       // If we're compiling the package init function, search for any
+       // relocations that target global map init outline functions and
+       // turn them into weak relocs.
+       if base.Flag.WrapGlobalMapInit && fn.IsPackageInit() {
+               weakenGlobalMapInitRelocs(fn)
+       }
+
        // fieldtrack must be called after pp.Flush. See issue 20014.
        fieldtrack(pp.Text.From.Sym, fn.FieldTrack)
 }
 
+// globalMapInitLsyms records the LSym of each map.init.NNN outlined
+// map initializer function created by the compiler.
+var globalMapInitLsyms map[*obj.LSym]struct{}
+
+// RegisterMapInitLsym records "s" in the set of outlined map initializer
+// functions.
+func RegisterMapInitLsym(s *obj.LSym) {
+       if globalMapInitLsyms == nil {
+               globalMapInitLsyms = make(map[*obj.LSym]struct{})
+       }
+       globalMapInitLsyms[s] = struct{}{}
+}
+
+// weakenGlobalMapInitRelocs walks through all of the relocations on a
+// given a package init function "fn" and looks for relocs that target
+// outlined global map initializer functions; if it finds any such
+// relocs, it flags them as R_WEAK.
+func weakenGlobalMapInitRelocs(fn *ir.Func) {
+       // Disabled until next patch.
+       if true {
+               return
+       }
+       if globalMapInitLsyms == nil {
+               return
+       }
+       for i := range fn.LSym.R {
+               tgt := fn.LSym.R[i].Sym
+               if tgt == nil {
+                       continue
+               }
+               if _, ok := globalMapInitLsyms[tgt]; !ok {
+                       continue
+               }
+               if base.Debug.WrapGlobalMapDbg > 1 {
+                       fmt.Fprintf(os.Stderr, "=-= weakify fn %v reloc %d %+v\n", fn, i,
+                               fn.LSym.R[i])
+               }
+               // set the R_WEAK bit, leave rest of reloc type intact
+               fn.LSym.R[i].Type |= objabi.R_WEAK
+       }
+}
+
 // StackOffset returns the stack location of a LocalSlot relative to the
 // stack pointer, suitable for use in a DWARF location entry. This has nothing
 // to do with its offset in the user variable.
index 3747656d5870a6ee613b6daa21bbe62b02d1abaf..f954c246f7b89ed227a76831530c95b92ce117b1 100644 (file)
@@ -8,6 +8,7 @@ import (
        "fmt"
        "go/constant"
        "go/token"
+       "os"
 
        "cmd/compile/internal/base"
        "cmd/compile/internal/ir"
@@ -16,6 +17,7 @@ import (
        "cmd/compile/internal/typecheck"
        "cmd/compile/internal/types"
        "cmd/internal/obj"
+       "cmd/internal/objabi"
        "cmd/internal/src"
 )
 
@@ -55,6 +57,28 @@ func (s *Schedule) StaticInit(n ir.Node) {
        }
 }
 
+// varToMapInit holds book-keeping state for global map initialization;
+// it records the init function created by the compiler to host the
+// initialization code for the map in question.
+var varToMapInit map[*ir.Name]*ir.Func
+
+// MapInitToVar is the inverse of VarToMapInit; it maintains a mapping
+// from a compiler-generated init function to the map the function is
+// initializing.
+var MapInitToVar map[*ir.Func]*ir.Name
+
+// recordFuncForVar establishes a mapping between global map var "v" and
+// outlined init function "fn" (and vice versa); so that we can use
+// the mappings later on to update relocations.
+func recordFuncForVar(v *ir.Name, fn *ir.Func) {
+       if varToMapInit == nil {
+               varToMapInit = make(map[*ir.Name]*ir.Func)
+               MapInitToVar = make(map[*ir.Func]*ir.Name)
+       }
+       varToMapInit[v] = fn
+       MapInitToVar[fn] = v
+}
+
 // tryStaticInit attempts to statically execute an initialization
 // statement and reports whether it succeeded.
 func (s *Schedule) tryStaticInit(nn ir.Node) bool {
@@ -887,3 +911,162 @@ func truncate(c *ir.ConstExpr, t *types.Type) (*ir.ConstExpr, bool) {
        c.SetType(t)
        return c, true
 }
+
+const wrapGlobalMapInitSizeThreshold = 20
+
+// tryWrapGlobalMapInit examines the node 'n' to see if it is a map
+// variable initialization, and if so, possibly returns the mapvar
+// being assigned, a new function containing the init code, and a call
+// to the function passing the mapvar. Returns will be nil if the
+// assignment is not to a map, or the map init is not big enough,
+// or if the expression being assigned to the map has side effects.
+func tryWrapGlobalMapInit(n ir.Node) (mapvar *ir.Name, genfn *ir.Func, call ir.Node) {
+       // Look for "X = ..." where X has map type.
+       // FIXME: might also be worth trying to look for cases where
+       // the LHS is of interface type but RHS is map type.
+       if n.Op() != ir.OAS {
+               return nil, nil, nil
+       }
+       as := n.(*ir.AssignStmt)
+       if ir.IsBlank(as.X) || as.X.Op() != ir.ONAME {
+               return nil, nil, nil
+       }
+       nm := as.X.(*ir.Name)
+       if !nm.Type().IsMap() {
+               return nil, nil, nil
+       }
+
+       // Determine size of RHS.
+       rsiz := 0
+       ir.Any(as.Y, func(n ir.Node) bool {
+               rsiz++
+               return false
+       })
+       if base.Debug.WrapGlobalMapDbg > 0 {
+               fmt.Fprintf(os.Stderr, "=-= mapassign %s %v rhs size %d\n",
+                       base.Ctxt.Pkgpath, n, rsiz)
+       }
+
+       // Reject smaller candidates if not in stress mode.
+       if rsiz < wrapGlobalMapInitSizeThreshold && base.Debug.WrapGlobalMapStress == 0 {
+               if base.Debug.WrapGlobalMapDbg > 1 {
+                       fmt.Fprintf(os.Stderr, "=-= skipping %v size too small at %d\n",
+                               nm, rsiz)
+               }
+               return nil, nil, nil
+       }
+
+       // Reject right hand sides with side effects.
+       if AnySideEffects(as.Y) {
+               if base.Debug.WrapGlobalMapDbg > 0 {
+                       fmt.Fprintf(os.Stderr, "=-= rejected %v due to side effects\n", nm)
+               }
+               return nil, nil, nil
+       }
+
+       if base.Debug.WrapGlobalMapDbg > 1 {
+               fmt.Fprintf(os.Stderr, "=-= committed for: %+v\n", n)
+       }
+
+       // Create a new function that will (eventually) have this form:
+       //
+       //    func map.init.%d() {
+       //      globmapvar = <map initialization>
+       //    }
+       //
+       minitsym := typecheck.LookupNum("map.init.", mapinitgen)
+       mapinitgen++
+       newfn := typecheck.DeclFunc(minitsym, nil, nil, nil)
+       if base.Debug.WrapGlobalMapDbg > 0 {
+               fmt.Fprintf(os.Stderr, "=-= generated func is %v\n", newfn)
+       }
+
+       // NB: we're relying on this phase being run before inlining;
+       // if for some reason we need to move it after inlining, we'll
+       // need code here that relocates or duplicates inline temps.
+
+       // Insert assignment into function body; mark body finished.
+       newfn.Body = append(newfn.Body, as)
+       typecheck.FinishFuncBody()
+
+       typecheck.Func(newfn)
+
+       const no = `
+       // Register new function with decls.
+       typecheck.Target.Decls = append(typecheck.Target.Decls, newfn)
+`
+
+       // Create call to function, passing mapvar.
+       fncall := ir.NewCallExpr(n.Pos(), ir.OCALL, newfn.Nname, nil)
+
+       if base.Debug.WrapGlobalMapDbg > 1 {
+               fmt.Fprintf(os.Stderr, "=-= mapvar is %v\n", nm)
+               fmt.Fprintf(os.Stderr, "=-= newfunc is %+v\n", newfn)
+               fmt.Fprintf(os.Stderr, "=-= call is %+v\n", fncall)
+       }
+
+       return nm, newfn, typecheck.Stmt(fncall)
+}
+
+// mapinitgen is a counter used to uniquify compiler-generated
+// map init functions.
+var mapinitgen int
+
+// AddKeepRelocations adds a dummy "R_KEEP" relocation from each
+// global map variable V to its associated outlined init function.
+// These relocation ensure that if the map var itself is determined to
+// be reachable at link time, we also mark the init function as
+// reachable.
+func AddKeepRelocations() {
+       if varToMapInit == nil {
+               return
+       }
+       for k, v := range varToMapInit {
+               // Add R_KEEP relocation from map to init function.
+               fs := v.Linksym()
+               if fs == nil {
+                       base.Fatalf("bad: func %v has no linksym", v)
+               }
+               vs := k.Linksym()
+               if vs == nil {
+                       base.Fatalf("bad: mapvar %v has no linksym", k)
+               }
+               r := obj.Addrel(vs)
+               r.Sym = fs
+               r.Type = objabi.R_KEEP
+               if base.Debug.WrapGlobalMapDbg > 1 {
+                       fmt.Fprintf(os.Stderr, "=-= add R_KEEP relo from %s to %s\n",
+                               vs.Name, fs.Name)
+               }
+       }
+       varToMapInit = nil
+}
+
+// OutlineMapInits walks through a list of init statements (candidates
+// for inclusion in the package "init" function) and returns an
+// updated list in which items corresponding to map variable
+// initializations have been replaced with calls to outline "map init"
+// functions (if legal/profitable). Return value is an updated list
+// and a list of any newly generated "map init" functions.
+func OutlineMapInits(stmts []ir.Node) ([]ir.Node, []*ir.Func) {
+       if !base.Flag.WrapGlobalMapInit {
+               return stmts, nil
+       }
+       newfuncs := []*ir.Func{}
+       for i := range stmts {
+               s := stmts[i]
+               // Call the helper tryWrapGlobalMapInit to see if the LHS of
+               // this assignment is to a map var, and if so whether the RHS
+               // should be outlined into a separate init function. If the
+               // outline goes through, then replace the original init
+               // statement with the call to the outlined func, and append
+               // the new outlined func to our return list.
+               if mapvar, genfn, call := tryWrapGlobalMapInit(s); call != nil {
+                       stmts[i] = call
+                       newfuncs = append(newfuncs, genfn)
+                       recordFuncForVar(mapvar, genfn)
+               }
+       }
+
+       return stmts, newfuncs
+}