From 50db447ec700817fdfa6d421ba2c43591cbf8e4b Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Mon, 29 Jul 2019 00:17:56 -0700 Subject: [PATCH] First version, at least it runs :) Change-Id: Id214fff7c9a8656fc64fb59e8040d09170a90ba1 --- src/cmd/compile/internal/gc/gsubr.go | 7 +- src/cmd/compile/internal/gc/init.go | 152 +++++++++++++++++++++----- src/cmd/compile/internal/gc/syntax.go | 31 +++--- src/cmd/link/internal/ld/deadcode.go | 75 ++++++++++++- src/runtime/proc.go | 20 +++- test/fixedbugs/issue29919.dir/a.go | 2 +- 6 files changed, 239 insertions(+), 48 deletions(-) diff --git a/src/cmd/compile/internal/gc/gsubr.go b/src/cmd/compile/internal/gc/gsubr.go index 51c0fffc9ee3a3..6195523002b5dc 100644 --- a/src/cmd/compile/internal/gc/gsubr.go +++ b/src/cmd/compile/internal/gc/gsubr.go @@ -295,7 +295,12 @@ func ggloblnod(nam *Node) { if nam.Type != nil && !types.Haspointers(nam.Type) { flags |= obj.NOPTR } - Ctxt.Globl(s, nam.Type.Width, flags) + ot := int(nam.Type.Width) + if nam.HasInitFunc() { + initfun := lookup("init.var." + nam.Sym.Name) + ot = dsymptr(s, ot, initfun.Linksym(), 0) + } + Ctxt.Globl(s, int64(ot), flags) } func ggloblsym(s *obj.LSym, width int32, flags int16) { diff --git a/src/cmd/compile/internal/gc/init.go b/src/cmd/compile/internal/gc/init.go index 26fd71d70ce620..647d2687cde856 100644 --- a/src/cmd/compile/internal/gc/init.go +++ b/src/cmd/compile/internal/gc/init.go @@ -7,6 +7,7 @@ package gc import ( "cmd/compile/internal/types" "cmd/internal/obj" + "strings" ) // A function named init is a special case. @@ -24,45 +25,109 @@ func renameinit() *types.Sym { return s } +func buildinitfunc(name string, body []*Node) *types.Sym { + lineno = body[0].Pos + initvarfn := lookup(name) + disableExport(initvarfn) + fn := dclfunc(initvarfn, nod(OTFUNC, nil, nil)) + for _, dcl := range dummyInitFn.Func.Dcl { + dcl.Name.Curfn = fn + } + fn.Func.Dcl = append(fn.Func.Dcl, dummyInitFn.Func.Dcl...) + dummyInitFn.Func.Dcl = nil + + fn.Nbody.Set(body) + funcbody() + + fn = typecheck(fn, ctxStmt) + Curfn = fn + typecheckslice(body, ctxStmt) + Curfn = nil + funccompile(fn) + return initvarfn +} + // fninit makes an initialization record for the package. // See runtime/proc.go:initTask for its layout. // The 3 tasks for initialization are: // 1) Initialize all of the packages the current package depends on. // 2) Initialize all the variables that have initializers. -// 3) Run any init functions. +// 3) Run any user-defined init functions. func fninit(n []*Node) { + + // Find all the statements that initialize global variables and need to + // be executed at startup; we will be creating init functions for each of + // these. nf := initOrder(n) - var deps []*obj.LSym // initTask records for packages the current package depends on - var fns []*obj.LSym // functions to call for package initialization + var deps []*obj.LSym // initTask records for packages the current package depends on + var fns []*obj.LSym // functions to call for package initialization + var varfns []*obj.LSym // functions to call to initialize variables (generated) // Find imported packages with init tasks. for _, s := range types.InitSyms { deps = append(deps, s.Linksym()) } - // Make a function that contains all the initialization statements. - if len(nf) > 0 { - lineno = nf[0].Pos // prolog/epilog gets line number of first init stmt - initializers := lookup("init") - disableExport(initializers) - fn := dclfunc(initializers, nod(OTFUNC, nil, nil)) - for _, dcl := range dummyInitFn.Func.Dcl { - dcl.Name.Curfn = fn + var initvarNotPure []*Node + + // For each statement that initializes a global variable, create a init function + // called init.var.NN. We create separate functions (rather than a single one) + // because the linker might be able to remove them, if the corresponding + // variable ends up being unused. + for i := 0; i < len(nf); i++ { + var body []*Node + + // Record that the initialized variable(s) (LHS of the assign statement) + // have an initialization function associated to them. This will allow + // ggloblnod() later to emit relocations for them. + var varname string + switch nf[i].Op { + case OAS: + varname = nf[i].Left.Sym.Name + + // Slice literals might have the backing array allocated statically, + // and assignments are generated to temporary slots (before going + // into the backing store). Treat these as non-pure: we do not + // have a good way of handling them as we don't know which + // variable they refer to. + if strings.HasPrefix(varname, ".stmp_") { + initvarNotPure = append(initvarNotPure, nf[i]) + continue + } + nf[i].Left.SetHasInitFunc(true) + + // Struct literals might generate init functions, in case of large + // structures where only some fields are initialized. In this case, + // we need to generate a single symbol for all the assignments that + // refer to the same variable. + var j int + for j = i + 1; j < len(nf); j++ { + if !(nf[j].Op == OAS && nf[j].Left.Sym.Name == nf[i].Left.Sym.Name) { + break + } + } + body = nf[i:j] + i = j - 1 + case OAS2FUNC, OAS2DOTTYPE, OAS2MAPR, OAS2RECV: + nf[i].List.First().SetHasInitFunc(true) + //FIXME: must refer to same init function, or create wrapper + //nf[i].List.Second().SetHasInitFunc(true) + varname = nf[i].List.First().Sym.Name + body = nf[i : i+1] + default: + Fatalf("unknown init statement: %v", n) } - fn.Func.Dcl = append(fn.Func.Dcl, dummyInitFn.Func.Dcl...) - dummyInitFn.Func.Dcl = nil - - fn.Nbody.Set(nf) - funcbody() - - fn = typecheck(fn, ctxStmt) - Curfn = fn - typecheckslice(nf, ctxStmt) - Curfn = nil - funccompile(fn) - fns = append(fns, initializers.Linksym()) + + initvarfn := buildinitfunc("init.var."+varname, body) + varfns = append(varfns, initvarfn.Linksym()) } + + if len(initvarNotPure) != 0 { + initotherfn := buildinitfunc("init.other", initvarNotPure) + fns = append(fns, initotherfn.Linksym()) + } + if dummyInitFn.Func.Dcl != nil { // We only generate temps using dummyInitFn if there // are package-scope initialization statements, so @@ -76,11 +141,11 @@ func fninit(n []*Node) { fns = append(fns, s.Linksym()) } - if len(deps) == 0 && len(fns) == 0 && localpkg.Name != "main" && localpkg.Name != "runtime" { + if len(deps) == 0 && len(fns) == 0 && len(varfns) == 0 && localpkg.Name != "main" && localpkg.Name != "runtime" { return // nothing to initialize } - // Make an .inittask structure. + // Make an .inittask structure, that will be processed by the runtime sym := lookup(".inittask") nn := newname(sym) nn.Type = types.Types[TUINT8] // dummy type @@ -92,15 +157,52 @@ func fninit(n []*Node) { ot = duintptr(lsym, ot, 0) // state: not initialized yet ot = duintptr(lsym, ot, uint64(len(deps))) ot = duintptr(lsym, ot, uint64(len(fns))) + ot = duintptr(lsym, ot, uint64(len(varfns))) for _, d := range deps { ot = dsymptr(lsym, ot, d, 0) } for _, f := range fns { ot = dsymptr(lsym, ot, f, 0) } + for _, f := range varfns { + ot = dsymptr(lsym, ot, f, 0) + } // An initTask has pointers, but none into the Go heap. // It's not quite read only, the state field must be modifiable. ggloblsym(lsym, int32(ot), obj.NOPTR) + + /* + // For each init.var function, save the corresponding variable name into + // a table. This will be used by the linker to remove init.var functions + // that initialise unused functions. Rather than storing these names within + // .inittask, we use a different symbol so that this table itself will + // be removed by the final binary (as it's only useful to the linker). + sym = lookup(".initvarnames") + nn = newname(sym) + nn.Type = types.Types[TUINT8] // dummy type + nn.SetClass(PEXTERN) + sym.Def = asTypesNode(nn) + exportsym(nn) + lsym = sym.Linksym() + ot = 0 + ot = duintptr(lsym, ot, uint64(len(nf))) + for _, f := range nf { + switch f.Op { + case OAS: + ot = duint8(lsym, ot, 0xcd) // 1 => single variable reference + ot = dsymptr(lsym, ot, f.Left.Sym.Linksym(), 0) + case OAS2FUNC, OAS2DOTTYPE, OAS2MAPR, OAS2RECV: + ot = duint8(lsym, ot, 0xcc) // 2 => double variable reference + ot = dsymptr(lsym, ot, f.List.First().Sym.Linksym(), 0) + ot = dsymptr(lsym, ot, f.List.Second().Sym.Linksym(), 0) + default: + Fatalf("unknown init statement: %v", n) + } + } + // FIXME(rasky): check if obj.NOPTR is required. Probably flags don't + // matter as this symbol is going away during linking. + ggloblsym(lsym, int32(ot), obj.NOPTR) + */ } func (n *Node) checkInitFuncSignature() { diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go index dec72690bf4341..cdd0b791871e90 100644 --- a/src/cmd/compile/internal/gc/syntax.go +++ b/src/cmd/compile/internal/gc/syntax.go @@ -146,20 +146,21 @@ const ( _, nodeAssigned // is the variable ever assigned to _, nodeAddrtaken // address taken, even if not moved to heap _, nodeImplicit - _, nodeIsDDD // is the argument variadic - _, nodeDiag // already printed error about this - _, nodeColas // OAS resulting from := - _, nodeNonNil // guaranteed to be non-nil - _, nodeNoescape // func arguments do not escape; TODO(rsc): move Noescape to Func struct (see CL 7360) - _, nodeBounded // bounds check unnecessary - _, nodeAddable // addressable - _, nodeHasCall // expression contains a function call - _, nodeLikely // if statement condition likely - _, nodeHasVal // node.E contains a Val - _, nodeHasOpt // node.E contains an Opt - _, nodeEmbedded // ODCLFIELD embedded type - _, nodeInlFormal // OPAUTO created by inliner, derived from callee formal - _, nodeInlLocal // OPAUTO created by inliner, derived from callee local + _, nodeIsDDD // is the argument variadic + _, nodeDiag // already printed error about this + _, nodeColas // OAS resulting from := + _, nodeNonNil // guaranteed to be non-nil + _, nodeNoescape // func arguments do not escape; TODO(rsc): move Noescape to Func struct (see CL 7360) + _, nodeBounded // bounds check unnecessary + _, nodeAddable // addressable + _, nodeHasCall // expression contains a function call + _, nodeLikely // if statement condition likely + _, nodeHasVal // node.E contains a Val + _, nodeHasOpt // node.E contains an Opt + _, nodeEmbedded // ODCLFIELD embedded type + _, nodeInlFormal // OPAUTO created by inliner, derived from callee formal + _, nodeInlLocal // OPAUTO created by inliner, derived from callee local + _, nodeHasInitFunc // an init function must be generated for this node ) func (n *Node) Class() Class { return Class(n.flags.get3(nodeClass)) } @@ -188,6 +189,7 @@ func (n *Node) HasOpt() bool { return n.flags&nodeHasOpt != 0 } func (n *Node) Embedded() bool { return n.flags&nodeEmbedded != 0 } func (n *Node) InlFormal() bool { return n.flags&nodeInlFormal != 0 } func (n *Node) InlLocal() bool { return n.flags&nodeInlLocal != 0 } +func (n *Node) HasInitFunc() bool { return n.flags&nodeHasInitFunc != 0 } func (n *Node) SetClass(b Class) { n.flags.set3(nodeClass, uint8(b)) } func (n *Node) SetWalkdef(b uint8) { n.flags.set2(nodeWalkdef, b) } @@ -215,6 +217,7 @@ func (n *Node) SetHasOpt(b bool) { n.flags.set(nodeHasOpt, b) } func (n *Node) SetEmbedded(b bool) { n.flags.set(nodeEmbedded, b) } func (n *Node) SetInlFormal(b bool) { n.flags.set(nodeInlFormal, b) } func (n *Node) SetInlLocal(b bool) { n.flags.set(nodeInlLocal, b) } +func (n *Node) SetHasInitFunc(b bool) { n.flags.set(nodeHasInitFunc, b) } // Val returns the Val for the node. func (n *Node) Val() Val { diff --git a/src/cmd/link/internal/ld/deadcode.go b/src/cmd/link/internal/ld/deadcode.go index f9a0ee0f96605f..984adce4f521d3 100644 --- a/src/cmd/link/internal/ld/deadcode.go +++ b/src/cmd/link/internal/ld/deadcode.go @@ -11,6 +11,7 @@ import ( "fmt" "strings" "unicode" + "unsafe" ) // deadcode marks all reachable symbols. @@ -108,6 +109,9 @@ func deadcode(ctxt *Link) { } } + // Remove unreachable init functions + d.cleanupInitTask() + if ctxt.BuildMode != BuildModeShared { // Keep a itablink if the symbol it points at is being kept. // (When BuildModeShared, always keep itablinks.) @@ -160,6 +164,7 @@ type deadcodepass struct { ifaceMethod map[methodsig]bool // methods declared in reached interfaces markableMethods []methodref // methods of reached types reflectMethod bool + initTasks []string // reachable inittasks } func (d *deadcodepass) cleanupReloc(r *sym.Reloc) { @@ -174,6 +179,33 @@ func (d *deadcodepass) cleanupReloc(r *sym.Reloc) { } } +func (d *deadcodepass) cleanupInitTask() { + for _, it := range d.initTasks { + s := d.ctxt.Syms.ROLookup(it, 0) + + var R []sym.Reloc + for i := range s.R { + r := &s.R[i] + if r.Sym.Attr.Reachable() { + R = append(R, *r) + } else { + // If the symbol is not reachable, we need to nil its pointer + // within inittask and remove the relocation + if s.Attr.ReadOnly() { + // The symbol's content is backed by read-only memory. + // Copy it to writable memory. + s.P = append([]byte(nil), s.P...) + s.Attr.Set(sym.AttrReadOnly, false) + } + for i := r.Off; i < r.Off+int32(r.Siz); i++ { + s.P[i] = 0 + } + } + } + s.R = R + } +} + // mark appends a symbol to the mark queue for flood filling. func (d *deadcodepass) mark(s, parent *sym.Symbol) { if s == nil || s.Attr.Reachable() { @@ -193,6 +225,9 @@ func (d *deadcodepass) mark(s, parent *sym.Symbol) { if d.ctxt.Reachparent != nil { d.ctxt.Reachparent[s] = parent } + if strings.HasSuffix(s.Name, ".inittask") { + d.initTasks = append(d.initTasks, s.Name) + } d.markQueue = append(d.markQueue, s) } @@ -293,13 +328,51 @@ func (d *deadcodepass) flood() { } } + numrelocs := len(s.R) + if strings.HasSuffix(s.Name, ".inittask") { + // When flooding through inittask symbols, ignore variabile initialization + // functions: those need to stay alive only if they're reached + // through the variables they initialize (which contain a relocation + // to them), not just because they're init functions. + type initTask struct { + state uintptr + ndeps uintptr + nfns uintptr + nvarfns uintptr + } + var it = (*initTask)(unsafe.Pointer(&s.P[0])) + numrelocs -= int(it.nvarfns) + println("skipping relocations", s.Name, it.nvarfns) + } + + /* + if strings.HasPrefix(s.Name, "main") { + println("symbol:", s.Name, s.Type.String()) + } + + var skipreloc int = -1 + if strings.Contains(s.Name, ".init.var.") { + println("inspecting", s.Name) + for i := len(s.R)-1; i >= 0; i-- { + if s.R[i].Type == objabi.R_PCREL { + println("found skippable reloc", s.Name, i) + skipreloc = i + break + } + } + } + */ mpos := 0 // 0-3, the R_METHODOFF relocs of runtime.uncommontype var methods []methodref - for i := range s.R { + for i := 0; i < numrelocs; i++ { r := &s.R[i] if r.Sym == nil { continue } + // if i == skipreloc { + // println("removed::", r.Sym.Name) + // continue + // } if r.Type == objabi.R_WEAKADDROFF { // An R_WEAKADDROFF relocation is not reason // enough to mark the pointed-to symbol as diff --git a/src/runtime/proc.go b/src/runtime/proc.go index 93d329d15e7a9c..7c4ba46a2ad956 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -5196,11 +5196,12 @@ func gcd(a, b uint32) uint32 { // An initTask represents the set of initializations that need to be done for a package. type initTask struct { // TODO: pack the first 3 fields more tightly? - state uintptr // 0 = uninitialized, 1 = in progress, 2 = done - ndeps uintptr - nfns uintptr + state uintptr // 0 = uninitialized, 1 = in progress, 2 = done + ndeps uintptr + nfns uintptr + nvarfns uintptr // followed by ndeps instances of an *initTask, one per package depended on - // followed by nfns pcs, one per init function to run + // followed by nfns+nvarfns pcs, one per init function to run } func doInit(t *initTask) { @@ -5212,12 +5213,19 @@ func doInit(t *initTask) { default: // not initialized yet t.state = 1 // initialization in progress for i := uintptr(0); i < t.ndeps; i++ { - p := add(unsafe.Pointer(t), (3+i)*sys.PtrSize) + p := add(unsafe.Pointer(t), (4+i)*sys.PtrSize) t2 := *(**initTask)(p) doInit(t2) } + for i := uintptr(0); i < t.nvarfns; i++ { + p := add(unsafe.Pointer(t), (4+t.ndeps+t.nfns+i)*sys.PtrSize) + if *(*uint64)(unsafe.Pointer(p)) != 0 { + f := *(*func())(unsafe.Pointer(&p)) + f() + } + } for i := uintptr(0); i < t.nfns; i++ { - p := add(unsafe.Pointer(t), (3+t.ndeps+i)*sys.PtrSize) + p := add(unsafe.Pointer(t), (4+t.ndeps+i)*sys.PtrSize) f := *(*func())(unsafe.Pointer(&p)) f() } diff --git a/test/fixedbugs/issue29919.dir/a.go b/test/fixedbugs/issue29919.dir/a.go index 078f973b4b5c70..654b66ecb13a5b 100644 --- a/test/fixedbugs/issue29919.dir/a.go +++ b/test/fixedbugs/issue29919.dir/a.go @@ -58,7 +58,7 @@ func f() int { panic("traceback truncated after f") } f, more = iter.Next() - if f.Function != "a.init" || !strings.HasSuffix(f.File, "a.go") || f.Line != 15 { + if f.Function != "a.init.var.0" || !strings.HasSuffix(f.File, "a.go") || f.Line != 15 { panic(fmt.Sprintf("bad init %v\n", f)) } if !more {