Skip to content

Commit b46e44a

Browse files
committed
cmd/compile: enable deadcode of unreferenced large global maps
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 <[email protected]> Reviewed-by: Cherry Mui <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 103f374 commit b46e44a

File tree

7 files changed

+274
-1
lines changed

7 files changed

+274
-1
lines changed

src/cmd/compile/internal/base/debug.go

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ type DebugFlags struct {
5151
PGOInlineCDFThreshold string `help:"cummulative threshold percentage for determining call sites as hot candidates for inlining" concurrent:"ok"`
5252
PGOInlineBudget int `help:"inline budget for hot functions" concurrent:"ok"`
5353
PGOInline int `help:"debug profile-guided inlining"`
54+
WrapGlobalMapDbg int "help:\"debug trace output for global map init wrapping\""
55+
WrapGlobalMapStress int "help:\"run global map init wrap in stress mode (no size cutoff)\""
5456

5557
ConcurrentOk bool // true if only concurrentOk flags seen
5658
}

src/cmd/compile/internal/base/flag.go

+2
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ type CmdFlags struct {
123123
TraceProfile string "help:\"write an execution trace to `file`\""
124124
TrimPath string "help:\"remove `prefix` from recorded source file paths\""
125125
WB bool "help:\"enable write barrier\"" // TODO: remove
126+
WrapGlobalMapInit bool "help:\"wrap global map large inits in their own functions (to permit deadcode)\""
126127
PgoProfile string "help:\"read profile from `file`\""
127128

128129
// Configuration derived from flags; not a flag itself.
@@ -163,6 +164,7 @@ func ParseFlags() {
163164
Flag.LinkShared = &Ctxt.Flag_linkshared
164165
Flag.Shared = &Ctxt.Flag_shared
165166
Flag.WB = true
167+
Flag.WrapGlobalMapInit = true
166168

167169
Debug.ConcurrentOk = true
168170
Debug.InlFuncsWithClosures = 1

src/cmd/compile/internal/gc/compile.go

+9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"cmd/compile/internal/liveness"
1616
"cmd/compile/internal/objw"
1717
"cmd/compile/internal/ssagen"
18+
"cmd/compile/internal/staticinit"
1819
"cmd/compile/internal/typecheck"
1920
"cmd/compile/internal/types"
2021
"cmd/compile/internal/walk"
@@ -84,6 +85,14 @@ func prepareFunc(fn *ir.Func) {
8485
// (e.g. in MarkTypeUsedInInterface).
8586
ir.InitLSym(fn, true)
8687

88+
// If this function is a compiler-generated outlined global map
89+
// initializer function, register its LSym for later processing.
90+
if staticinit.MapInitToVar != nil {
91+
if _, ok := staticinit.MapInitToVar[fn]; ok {
92+
ssagen.RegisterMapInitLsym(fn.Linksym())
93+
}
94+
}
95+
8796
// Calculate parameter offsets.
8897
types.CalcSize(fn.Type())
8998

src/cmd/compile/internal/gc/main.go

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"cmd/compile/internal/reflectdata"
2323
"cmd/compile/internal/ssa"
2424
"cmd/compile/internal/ssagen"
25+
"cmd/compile/internal/staticinit"
2526
"cmd/compile/internal/typecheck"
2627
"cmd/compile/internal/types"
2728
"cmd/internal/dwarf"
@@ -330,6 +331,11 @@ func Main(archInit func(*ssagen.ArchInfo)) {
330331
ssagen.NoWriteBarrierRecCheck()
331332
}
332333

334+
// Add keep relocations for global maps.
335+
if base.Flag.WrapGlobalMapInit {
336+
staticinit.AddKeepRelocations()
337+
}
338+
333339
// Finalize DWARF inline routine DIEs, then explicitly turn off
334340
// DWARF inlining gen so as to avoid problems with generated
335341
// method wrappers.

src/cmd/compile/internal/pkginit/init.go

+20-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"cmd/compile/internal/types"
1515
"cmd/internal/obj"
1616
"cmd/internal/src"
17+
"fmt"
18+
"os"
1719
)
1820

1921
// MakeInit creates a synthetic init function to handle any
@@ -38,9 +40,16 @@ func MakeInit() {
3840
typecheck.InitTodoFunc.Dcl = nil
3941
fn.SetIsPackageInit(true)
4042

43+
// Outline (if legal/profitable) global map inits.
44+
newfuncs := []*ir.Func{}
45+
nf, newfuncs = staticinit.OutlineMapInits(nf)
46+
4147
// Suppress useless "can inline" diagnostics.
4248
// Init functions are only called dynamically.
4349
fn.SetInlinabilityChecked(true)
50+
for _, nfn := range newfuncs {
51+
nfn.SetInlinabilityChecked(true)
52+
}
4453

4554
fn.Body = nf
4655
typecheck.FinishFuncBody()
@@ -50,6 +59,16 @@ func MakeInit() {
5059
typecheck.Stmts(nf)
5160
})
5261
typecheck.Target.Decls = append(typecheck.Target.Decls, fn)
62+
if base.Debug.WrapGlobalMapDbg > 1 {
63+
fmt.Fprintf(os.Stderr, "=-= len(newfuncs) is %d for %v\n",
64+
len(newfuncs), fn)
65+
}
66+
for _, nfn := range newfuncs {
67+
if base.Debug.WrapGlobalMapDbg > 1 {
68+
fmt.Fprintf(os.Stderr, "=-= add to target.decls %v\n", nfn)
69+
}
70+
typecheck.Target.Decls = append(typecheck.Target.Decls, ir.Node(nfn))
71+
}
5372

5473
// Prepend to Inits, so it runs first, before any user-declared init
5574
// functions.
@@ -110,7 +129,7 @@ func Task() *ir.Name {
110129
name := noder.Renameinit()
111130
fnInit := typecheck.DeclFunc(name, nil, nil, nil)
112131

113-
// Get an array of intrumented global variables.
132+
// Get an array of instrumented global variables.
114133
globals := instrumentGlobals(fnInit)
115134

116135
// Call runtime.asanregisterglobals function to poison redzones.

src/cmd/compile/internal/ssagen/pgen.go

+52
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
package ssagen
66

77
import (
8+
"fmt"
89
"internal/buildcfg"
10+
"os"
911
"sort"
1012
"sync"
1113

@@ -208,10 +210,60 @@ func Compile(fn *ir.Func, worker int) {
208210
}
209211

210212
pp.Flush() // assemble, fill in boilerplate, etc.
213+
214+
// If we're compiling the package init function, search for any
215+
// relocations that target global map init outline functions and
216+
// turn them into weak relocs.
217+
if base.Flag.WrapGlobalMapInit && fn.IsPackageInit() {
218+
weakenGlobalMapInitRelocs(fn)
219+
}
220+
211221
// fieldtrack must be called after pp.Flush. See issue 20014.
212222
fieldtrack(pp.Text.From.Sym, fn.FieldTrack)
213223
}
214224

225+
// globalMapInitLsyms records the LSym of each map.init.NNN outlined
226+
// map initializer function created by the compiler.
227+
var globalMapInitLsyms map[*obj.LSym]struct{}
228+
229+
// RegisterMapInitLsym records "s" in the set of outlined map initializer
230+
// functions.
231+
func RegisterMapInitLsym(s *obj.LSym) {
232+
if globalMapInitLsyms == nil {
233+
globalMapInitLsyms = make(map[*obj.LSym]struct{})
234+
}
235+
globalMapInitLsyms[s] = struct{}{}
236+
}
237+
238+
// weakenGlobalMapInitRelocs walks through all of the relocations on a
239+
// given a package init function "fn" and looks for relocs that target
240+
// outlined global map initializer functions; if it finds any such
241+
// relocs, it flags them as R_WEAK.
242+
func weakenGlobalMapInitRelocs(fn *ir.Func) {
243+
// Disabled until next patch.
244+
if true {
245+
return
246+
}
247+
if globalMapInitLsyms == nil {
248+
return
249+
}
250+
for i := range fn.LSym.R {
251+
tgt := fn.LSym.R[i].Sym
252+
if tgt == nil {
253+
continue
254+
}
255+
if _, ok := globalMapInitLsyms[tgt]; !ok {
256+
continue
257+
}
258+
if base.Debug.WrapGlobalMapDbg > 1 {
259+
fmt.Fprintf(os.Stderr, "=-= weakify fn %v reloc %d %+v\n", fn, i,
260+
fn.LSym.R[i])
261+
}
262+
// set the R_WEAK bit, leave rest of reloc type intact
263+
fn.LSym.R[i].Type |= objabi.R_WEAK
264+
}
265+
}
266+
215267
// StackOffset returns the stack location of a LocalSlot relative to the
216268
// stack pointer, suitable for use in a DWARF location entry. This has nothing
217269
// to do with its offset in the user variable.

src/cmd/compile/internal/staticinit/sched.go

+183
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"go/constant"
1010
"go/token"
11+
"os"
1112

1213
"cmd/compile/internal/base"
1314
"cmd/compile/internal/ir"
@@ -16,6 +17,7 @@ import (
1617
"cmd/compile/internal/typecheck"
1718
"cmd/compile/internal/types"
1819
"cmd/internal/obj"
20+
"cmd/internal/objabi"
1921
"cmd/internal/src"
2022
)
2123

@@ -55,6 +57,28 @@ func (s *Schedule) StaticInit(n ir.Node) {
5557
}
5658
}
5759

60+
// varToMapInit holds book-keeping state for global map initialization;
61+
// it records the init function created by the compiler to host the
62+
// initialization code for the map in question.
63+
var varToMapInit map[*ir.Name]*ir.Func
64+
65+
// MapInitToVar is the inverse of VarToMapInit; it maintains a mapping
66+
// from a compiler-generated init function to the map the function is
67+
// initializing.
68+
var MapInitToVar map[*ir.Func]*ir.Name
69+
70+
// recordFuncForVar establishes a mapping between global map var "v" and
71+
// outlined init function "fn" (and vice versa); so that we can use
72+
// the mappings later on to update relocations.
73+
func recordFuncForVar(v *ir.Name, fn *ir.Func) {
74+
if varToMapInit == nil {
75+
varToMapInit = make(map[*ir.Name]*ir.Func)
76+
MapInitToVar = make(map[*ir.Func]*ir.Name)
77+
}
78+
varToMapInit[v] = fn
79+
MapInitToVar[fn] = v
80+
}
81+
5882
// tryStaticInit attempts to statically execute an initialization
5983
// statement and reports whether it succeeded.
6084
func (s *Schedule) tryStaticInit(nn ir.Node) bool {
@@ -887,3 +911,162 @@ func truncate(c *ir.ConstExpr, t *types.Type) (*ir.ConstExpr, bool) {
887911
c.SetType(t)
888912
return c, true
889913
}
914+
915+
const wrapGlobalMapInitSizeThreshold = 20
916+
917+
// tryWrapGlobalMapInit examines the node 'n' to see if it is a map
918+
// variable initialization, and if so, possibly returns the mapvar
919+
// being assigned, a new function containing the init code, and a call
920+
// to the function passing the mapvar. Returns will be nil if the
921+
// assignment is not to a map, or the map init is not big enough,
922+
// or if the expression being assigned to the map has side effects.
923+
func tryWrapGlobalMapInit(n ir.Node) (mapvar *ir.Name, genfn *ir.Func, call ir.Node) {
924+
// Look for "X = ..." where X has map type.
925+
// FIXME: might also be worth trying to look for cases where
926+
// the LHS is of interface type but RHS is map type.
927+
if n.Op() != ir.OAS {
928+
return nil, nil, nil
929+
}
930+
as := n.(*ir.AssignStmt)
931+
if ir.IsBlank(as.X) || as.X.Op() != ir.ONAME {
932+
return nil, nil, nil
933+
}
934+
nm := as.X.(*ir.Name)
935+
if !nm.Type().IsMap() {
936+
return nil, nil, nil
937+
}
938+
939+
// Determine size of RHS.
940+
rsiz := 0
941+
ir.Any(as.Y, func(n ir.Node) bool {
942+
rsiz++
943+
return false
944+
})
945+
if base.Debug.WrapGlobalMapDbg > 0 {
946+
fmt.Fprintf(os.Stderr, "=-= mapassign %s %v rhs size %d\n",
947+
base.Ctxt.Pkgpath, n, rsiz)
948+
}
949+
950+
// Reject smaller candidates if not in stress mode.
951+
if rsiz < wrapGlobalMapInitSizeThreshold && base.Debug.WrapGlobalMapStress == 0 {
952+
if base.Debug.WrapGlobalMapDbg > 1 {
953+
fmt.Fprintf(os.Stderr, "=-= skipping %v size too small at %d\n",
954+
nm, rsiz)
955+
}
956+
return nil, nil, nil
957+
}
958+
959+
// Reject right hand sides with side effects.
960+
if AnySideEffects(as.Y) {
961+
if base.Debug.WrapGlobalMapDbg > 0 {
962+
fmt.Fprintf(os.Stderr, "=-= rejected %v due to side effects\n", nm)
963+
}
964+
return nil, nil, nil
965+
}
966+
967+
if base.Debug.WrapGlobalMapDbg > 1 {
968+
fmt.Fprintf(os.Stderr, "=-= committed for: %+v\n", n)
969+
}
970+
971+
// Create a new function that will (eventually) have this form:
972+
//
973+
// func map.init.%d() {
974+
// globmapvar = <map initialization>
975+
// }
976+
//
977+
minitsym := typecheck.LookupNum("map.init.", mapinitgen)
978+
mapinitgen++
979+
newfn := typecheck.DeclFunc(minitsym, nil, nil, nil)
980+
if base.Debug.WrapGlobalMapDbg > 0 {
981+
fmt.Fprintf(os.Stderr, "=-= generated func is %v\n", newfn)
982+
}
983+
984+
// NB: we're relying on this phase being run before inlining;
985+
// if for some reason we need to move it after inlining, we'll
986+
// need code here that relocates or duplicates inline temps.
987+
988+
// Insert assignment into function body; mark body finished.
989+
newfn.Body = append(newfn.Body, as)
990+
typecheck.FinishFuncBody()
991+
992+
typecheck.Func(newfn)
993+
994+
const no = `
995+
// Register new function with decls.
996+
typecheck.Target.Decls = append(typecheck.Target.Decls, newfn)
997+
`
998+
999+
// Create call to function, passing mapvar.
1000+
fncall := ir.NewCallExpr(n.Pos(), ir.OCALL, newfn.Nname, nil)
1001+
1002+
if base.Debug.WrapGlobalMapDbg > 1 {
1003+
fmt.Fprintf(os.Stderr, "=-= mapvar is %v\n", nm)
1004+
fmt.Fprintf(os.Stderr, "=-= newfunc is %+v\n", newfn)
1005+
fmt.Fprintf(os.Stderr, "=-= call is %+v\n", fncall)
1006+
}
1007+
1008+
return nm, newfn, typecheck.Stmt(fncall)
1009+
}
1010+
1011+
// mapinitgen is a counter used to uniquify compiler-generated
1012+
// map init functions.
1013+
var mapinitgen int
1014+
1015+
// AddKeepRelocations adds a dummy "R_KEEP" relocation from each
1016+
// global map variable V to its associated outlined init function.
1017+
// These relocation ensure that if the map var itself is determined to
1018+
// be reachable at link time, we also mark the init function as
1019+
// reachable.
1020+
func AddKeepRelocations() {
1021+
if varToMapInit == nil {
1022+
return
1023+
}
1024+
for k, v := range varToMapInit {
1025+
// Add R_KEEP relocation from map to init function.
1026+
fs := v.Linksym()
1027+
if fs == nil {
1028+
base.Fatalf("bad: func %v has no linksym", v)
1029+
}
1030+
vs := k.Linksym()
1031+
if vs == nil {
1032+
base.Fatalf("bad: mapvar %v has no linksym", k)
1033+
}
1034+
r := obj.Addrel(vs)
1035+
r.Sym = fs
1036+
r.Type = objabi.R_KEEP
1037+
if base.Debug.WrapGlobalMapDbg > 1 {
1038+
fmt.Fprintf(os.Stderr, "=-= add R_KEEP relo from %s to %s\n",
1039+
vs.Name, fs.Name)
1040+
}
1041+
}
1042+
varToMapInit = nil
1043+
}
1044+
1045+
// OutlineMapInits walks through a list of init statements (candidates
1046+
// for inclusion in the package "init" function) and returns an
1047+
// updated list in which items corresponding to map variable
1048+
// initializations have been replaced with calls to outline "map init"
1049+
// functions (if legal/profitable). Return value is an updated list
1050+
// and a list of any newly generated "map init" functions.
1051+
func OutlineMapInits(stmts []ir.Node) ([]ir.Node, []*ir.Func) {
1052+
if !base.Flag.WrapGlobalMapInit {
1053+
return stmts, nil
1054+
}
1055+
newfuncs := []*ir.Func{}
1056+
for i := range stmts {
1057+
s := stmts[i]
1058+
// Call the helper tryWrapGlobalMapInit to see if the LHS of
1059+
// this assignment is to a map var, and if so whether the RHS
1060+
// should be outlined into a separate init function. If the
1061+
// outline goes through, then replace the original init
1062+
// statement with the call to the outlined func, and append
1063+
// the new outlined func to our return list.
1064+
if mapvar, genfn, call := tryWrapGlobalMapInit(s); call != nil {
1065+
stmts[i] = call
1066+
newfuncs = append(newfuncs, genfn)
1067+
recordFuncForVar(mapvar, genfn)
1068+
}
1069+
}
1070+
1071+
return stmts, newfuncs
1072+
}

0 commit comments

Comments
 (0)