| // Copyright 2009 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 staticinit | |
| import ( | |
| "fmt" | |
| "go/constant" | |
| "go/token" | |
| "os" | |
| "strings" | |
| "cmd/compile/internal/base" | |
| "cmd/compile/internal/ir" | |
| "cmd/compile/internal/reflectdata" | |
| "cmd/compile/internal/staticdata" | |
| "cmd/compile/internal/typecheck" | |
| "cmd/compile/internal/types" | |
| "cmd/internal/obj" | |
| "cmd/internal/objabi" | |
| "cmd/internal/src" | |
| ) | |
| type Entry struct { | |
| Xoffset int64 // struct, array only | |
| Expr ir.Node // bytes of run-time computed expressions | |
| } | |
| type Plan struct { | |
| E []Entry | |
| } | |
| func canWriteStatic(off int64, typ *types.Type) bool { | |
| if off < 0 || off >= obj.MaxDataOffset { | |
| return false | |
| } | |
| // Arrays and structs are checked one plan entry at a time. | |
| return typ.IsArray() || typ.IsStruct() || typ.Size() <= obj.MaxDataOffset-off | |
| } | |
| // An Schedule is used to decompose assignment statements into | |
| // static and dynamic initialization parts. Static initializations are | |
| // handled by populating variables' linker symbol data, while dynamic | |
| // initializations are accumulated to be executed in order. | |
| type Schedule struct { | |
| // Out is the ordered list of dynamic initialization | |
| // statements. | |
| Out []ir.Node | |
| Plans map[ir.Node]*Plan | |
| Temps map[ir.Node]*ir.Name | |
| // seenMutation tracks whether we've seen an initialization | |
| // expression that may have modified other package-scope variables | |
| // within this package. | |
| seenMutation bool | |
| } | |
| func (s *Schedule) append(n ir.Node) { | |
| s.Out = append(s.Out, n) | |
| } | |
| // StaticInit adds an initialization statement n to the schedule. | |
| func (s *Schedule) StaticInit(curfunc *ir.Func, n ir.Node) { | |
| if !s.tryStaticInit(curfunc, n) { | |
| if base.Flag.Percent != 0 { | |
| ir.Dump("StaticInit failed", n) | |
| } | |
| s.append(n) | |
| } | |
| } | |
| // 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 | |
| } | |
| // allBlank reports whether every node in exprs is blank. | |
| func allBlank(exprs []ir.Node) bool { | |
| for _, expr := range exprs { | |
| if !ir.IsBlank(expr) { | |
| return false | |
| } | |
| } | |
| return true | |
| } | |
| // tryStaticInit attempts to statically execute an initialization | |
| // statement and reports whether it succeeded. | |
| func (s *Schedule) tryStaticInit(curfunc *ir.Func, n ir.Node) bool { | |
| var lhs []ir.Node | |
| var rhs ir.Node | |
| switch n.Op() { | |
| default: | |
| base.FatalfAt(n.Pos(), "unexpected initialization statement: %v", n) | |
| case ir.OAS: | |
| n := n.(*ir.AssignStmt) | |
| lhs, rhs = []ir.Node{n.X}, n.Y | |
| case ir.OAS2: | |
| // Usually OAS2 has been rewritten to separate OASes by types2. | |
| // What's left here is "var a, b = tmp1, tmp2" as a result from rewriting | |
| // "var a, b = f()" that needs type conversion, which is not static. | |
| n := n.(*ir.AssignListStmt) | |
| for _, rhs := range n.Rhs { | |
| for rhs.Op() == ir.OCONVNOP || rhs.Op() == ir.OCONVIFACE { | |
| rhs = rhs.(*ir.ConvExpr).X | |
| } | |
| if name, ok := rhs.(*ir.Name); !ok || !name.AutoTemp() { | |
| base.FatalfAt(n.Pos(), "unexpected rhs, not an autotmp: %+v", rhs) | |
| } | |
| } | |
| return false | |
| case ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV: | |
| n := n.(*ir.AssignListStmt) | |
| if len(n.Lhs) < 2 || len(n.Rhs) != 1 { | |
| base.FatalfAt(n.Pos(), "unexpected shape for %v: %v", n.Op(), n) | |
| } | |
| lhs, rhs = n.Lhs, n.Rhs[0] | |
| case ir.OCALLFUNC: | |
| return false // outlined map init call; no mutations | |
| } | |
| if !s.seenMutation { | |
| s.seenMutation = mayModifyPkgVar(rhs) | |
| } | |
| if allBlank(lhs) && !AnySideEffects(rhs) { | |
| return true // discard | |
| } | |
| // Only worry about simple "l = r" assignments. The OAS2* | |
| // assignments mostly necessitate dynamic execution anyway. | |
| if len(lhs) > 1 { | |
| return false | |
| } | |
| lno := ir.SetPos(n) | |
| defer func() { base.Pos = lno }() | |
| nam := lhs[0].(*ir.Name) | |
| return s.StaticAssign(curfunc, nam, 0, rhs, nam.Type()) | |
| } | |
| // like staticassign but we are copying an already | |
| // initialized value r. | |
| func (s *Schedule) staticcopy(curfunc *ir.Func, l *ir.Name, loff int64, rn *ir.Name, typ *types.Type) bool { | |
| if !canWriteStatic(loff, typ) { | |
| return false | |
| } | |
| if rn.Class == ir.PFUNC { | |
| // TODO if roff != 0 { panic } | |
| staticdata.InitAddr(l, loff, staticdata.FuncLinksym(rn)) | |
| return true | |
| } | |
| if rn.Class != ir.PEXTERN || rn.Sym().Pkg != types.LocalPkg { | |
| return false | |
| } | |
| if rn.Defn == nil { | |
| // No explicit initialization value. Probably zeroed but perhaps | |
| // supplied externally and of unknown value. | |
| return false | |
| } | |
| if rn.Defn.Op() != ir.OAS { | |
| return false | |
| } | |
| if rn.Type().IsString() { // perhaps overwritten by cmd/link -X (#34675) | |
| return false | |
| } | |
| if rn.Embed != nil { | |
| return false | |
| } | |
| orig := rn | |
| r := rn.Defn.(*ir.AssignStmt).Y | |
| if r == nil { | |
| // types2.InitOrder doesn't include default initializers. | |
| base.Fatalf("unexpected initializer: %v", rn.Defn) | |
| } | |
| // Variable may have been reassigned by a user-written function call | |
| // that was invoked to initialize another global variable (#51913). | |
| if s.seenMutation { | |
| if base.Debug.StaticCopy != 0 { | |
| base.WarnfAt(l.Pos(), "skipping static copy of %v+%v with %v", l, loff, r) | |
| } | |
| return false | |
| } | |
| for r.Op() == ir.OCONVNOP && !types.Identical(r.Type(), typ) { | |
| r = r.(*ir.ConvExpr).X | |
| } | |
| switch r.Op() { | |
| case ir.OMETHEXPR: | |
| r = r.(*ir.SelectorExpr).FuncName() | |
| fallthrough | |
| case ir.ONAME: | |
| r := r.(*ir.Name) | |
| if s.staticcopy(curfunc, l, loff, r, typ) { | |
| return true | |
| } | |
| // We may have skipped past one or more OCONVNOPs, so | |
| // use conv to ensure r is assignable to l (#13263). | |
| dst := ir.Node(l) | |
| if loff != 0 || !types.Identical(typ, l.Type()) { | |
| dst = ir.NewNameOffsetExpr(base.Pos, l, loff, typ) | |
| } | |
| s.append(ir.NewAssignStmt(base.Pos, dst, typecheck.Conv(curfunc, r, typ))) | |
| return true | |
| case ir.ONIL: | |
| return true | |
| case ir.OLITERAL: | |
| if ir.IsZero(r) { | |
| return true | |
| } | |
| staticdata.InitConst(l, loff, r, int(typ.Size())) | |
| return true | |
| case ir.OADDR: | |
| r := r.(*ir.AddrExpr) | |
| if a, ok := r.X.(*ir.Name); ok && a.Op() == ir.ONAME { | |
| if a.Class != ir.PEXTERN { | |
| return false // e.g. local from new(expr) | |
| } | |
| staticdata.InitAddr(l, loff, staticdata.GlobalLinksym(a)) | |
| return true | |
| } | |
| case ir.OPTRLIT: | |
| r := r.(*ir.AddrExpr) | |
| switch r.X.Op() { | |
| case ir.OARRAYLIT, ir.OSLICELIT, ir.OSTRUCTLIT, ir.OMAPLIT: | |
| // copy pointer | |
| staticdata.InitAddr(l, loff, staticdata.GlobalLinksym(s.Temps[r])) | |
| return true | |
| } | |
| case ir.OSLICELIT: | |
| r := r.(*ir.CompLitExpr) | |
| // copy slice | |
| staticdata.InitSlice(l, loff, staticdata.GlobalLinksym(s.Temps[r]), r.Len) | |
| return true | |
| case ir.OARRAYLIT, ir.OSTRUCTLIT: | |
| r := r.(*ir.CompLitExpr) | |
| p := s.Plans[r] | |
| for i := range p.E { | |
| e := &p.E[i] | |
| off := loff + e.Xoffset | |
| typ := e.Expr.Type() | |
| if (e.Expr.Op() == ir.OLITERAL || e.Expr.Op() == ir.ONIL) && canWriteStatic(off, typ) { | |
| staticdata.InitConst(l, off, e.Expr, int(typ.Size())) | |
| continue | |
| } | |
| x := e.Expr | |
| if x.Op() == ir.OMETHEXPR { | |
| x = x.(*ir.SelectorExpr).FuncName() | |
| } | |
| if x.Op() == ir.ONAME && s.staticcopy(curfunc, l, off, x.(*ir.Name), typ) { | |
| continue | |
| } | |
| // Requires computation, but we're | |
| // copying someone else's computation. | |
| ll := ir.NewNameOffsetExpr(base.Pos, l, off, typ) | |
| rr := ir.NewNameOffsetExpr(base.Pos, orig, e.Xoffset, typ) | |
| ir.SetPos(rr) | |
| s.append(ir.NewAssignStmt(base.Pos, ll, rr)) | |
| } | |
| return true | |
| } | |
| return false | |
| } | |
| func (s *Schedule) StaticAssign(curfunc *ir.Func, l *ir.Name, loff int64, r ir.Node, typ *types.Type) bool { | |
| // If we're building for FIPS, avoid global data relocations | |
| // by treating all address-of operations as non-static. | |
| // See ../../../internal/obj/fips.go for more context. | |
| // We do this even in non-PIE mode to avoid generating | |
| // static temporaries that would go into SRODATAFIPS | |
| // but need relocations. We can't handle that in the verification. | |
| disableGlobalAddrs := base.Ctxt.IsFIPS() | |
| if r == nil { | |
| // No explicit initialization value. Either zero or supplied | |
| // externally. | |
| return true | |
| } | |
| for r.Op() == ir.OCONVNOP { | |
| r = r.(*ir.ConvExpr).X | |
| } | |
| if !canWriteStatic(loff, typ) && !ir.IsZero(r) { | |
| return false | |
| } | |
| assign := func(pos src.XPos, a *ir.Name, aoff int64, v ir.Node) { | |
| if s.StaticAssign(curfunc, a, aoff, v, v.Type()) { | |
| return | |
| } | |
| var lhs ir.Node | |
| if ir.IsBlank(a) { | |
| // Don't use NameOffsetExpr with blank (#43677). | |
| lhs = ir.BlankNode | |
| } else { | |
| lhs = ir.NewNameOffsetExpr(pos, a, aoff, v.Type()) | |
| } | |
| s.append(ir.NewAssignStmt(pos, lhs, v)) | |
| } | |
| switch r.Op() { | |
| case ir.ONAME: | |
| if disableGlobalAddrs { | |
| return false | |
| } | |
| r := r.(*ir.Name) | |
| return s.staticcopy(curfunc, l, loff, r, typ) | |
| case ir.OMETHEXPR: | |
| if disableGlobalAddrs { | |
| return false | |
| } | |
| r := r.(*ir.SelectorExpr) | |
| return s.staticcopy(curfunc, l, loff, r.FuncName(), typ) | |
| case ir.ONIL: | |
| return true | |
| case ir.OLITERAL: | |
| if ir.IsZero(r) { | |
| return true | |
| } | |
| if disableGlobalAddrs && r.Type().IsString() { | |
| return false | |
| } | |
| staticdata.InitConst(l, loff, r, int(typ.Size())) | |
| return true | |
| case ir.OADDR: | |
| if disableGlobalAddrs { | |
| return false | |
| } | |
| r := r.(*ir.AddrExpr) | |
| if name, offset, ok := StaticLoc(r.X); ok && name.Class == ir.PEXTERN { | |
| staticdata.InitAddrOffset(l, loff, name.Linksym(), offset) | |
| return true | |
| } | |
| fallthrough | |
| case ir.OPTRLIT: | |
| if disableGlobalAddrs { | |
| return false | |
| } | |
| r := r.(*ir.AddrExpr) | |
| switch r.X.Op() { | |
| case ir.OARRAYLIT, ir.OSLICELIT, ir.OMAPLIT, ir.OSTRUCTLIT: | |
| // Init pointer. | |
| a := StaticName(r.X.Type()) | |
| s.Temps[r] = a | |
| staticdata.InitAddr(l, loff, a.Linksym()) | |
| // Init underlying literal. | |
| assign(base.Pos, a, 0, r.X) | |
| return true | |
| } | |
| //dump("not static ptrlit", r); | |
| case ir.OSTR2BYTES: | |
| if disableGlobalAddrs { | |
| return false | |
| } | |
| r := r.(*ir.ConvExpr) | |
| if l.Class == ir.PEXTERN && r.X.Op() == ir.OLITERAL { | |
| sval := ir.StringVal(r.X) | |
| staticdata.InitSliceBytes(l, loff, sval) | |
| return true | |
| } | |
| case ir.OSLICELIT: | |
| if disableGlobalAddrs { | |
| return false | |
| } | |
| r := r.(*ir.CompLitExpr) | |
| s.initplan(r) | |
| // Init slice. | |
| ta := types.NewArray(r.Type().Elem(), r.Len) | |
| ta.SetNoalg(true) | |
| a := StaticName(ta) | |
| s.Temps[r] = a | |
| staticdata.InitSlice(l, loff, a.Linksym(), r.Len) | |
| // Fall through to init underlying array. | |
| l = a | |
| loff = 0 | |
| fallthrough | |
| case ir.OARRAYLIT, ir.OSTRUCTLIT: | |
| r := r.(*ir.CompLitExpr) | |
| s.initplan(r) | |
| p := s.Plans[r] | |
| for i := range p.E { | |
| e := &p.E[i] | |
| off := loff + e.Xoffset | |
| if (e.Expr.Op() == ir.OLITERAL && !disableGlobalAddrs || e.Expr.Op() == ir.ONIL) && canWriteStatic(off, e.Expr.Type()) { | |
| staticdata.InitConst(l, off, e.Expr, int(e.Expr.Type().Size())) | |
| continue | |
| } | |
| ir.SetPos(e.Expr) | |
| assign(base.Pos, l, off, e.Expr) | |
| } | |
| return true | |
| case ir.OMAPLIT: | |
| break | |
| case ir.OCLOSURE: | |
| if disableGlobalAddrs { | |
| return false | |
| } | |
| r := r.(*ir.ClosureExpr) | |
| if !r.Func.IsClosure() { | |
| if base.Debug.Closure > 0 { | |
| base.WarnfAt(r.Pos(), "closure converted to global") | |
| } | |
| // Closures with no captured variables are globals, | |
| // so the assignment can be done at link time. | |
| // TODO if roff != 0 { panic } | |
| staticdata.InitAddr(l, loff, staticdata.FuncLinksym(r.Func.Nname)) | |
| return true | |
| } | |
| ir.ClosureDebugRuntimeCheck(r) | |
| case ir.OCONVIFACE: | |
| // This logic is mirrored in isStaticCompositeLiteral. | |
| // If you change something here, change it there, and vice versa. | |
| if disableGlobalAddrs { | |
| return false | |
| } | |
| // Determine the underlying concrete type and value we are converting from. | |
| r := r.(*ir.ConvExpr) | |
| val := ir.Node(r) | |
| for val.Op() == ir.OCONVIFACE { | |
| val = val.(*ir.ConvExpr).X | |
| } | |
| if val.Type().IsInterface() { | |
| // val is an interface type. | |
| // If val is nil, we can statically initialize l; | |
| // both words are zero and so there no work to do, so report success. | |
| // If val is non-nil, we have no concrete type to record, | |
| // and we won't be able to statically initialize its value, so report failure. | |
| return val.Op() == ir.ONIL | |
| } | |
| if val.Type().HasShape() { | |
| // See comment in cmd/compile/internal/walk/convert.go:walkConvInterface | |
| return false | |
| } | |
| reflectdata.MarkTypeUsedInInterface(val.Type(), l.Linksym()) | |
| var itab *ir.AddrExpr | |
| if typ.IsEmptyInterface() { | |
| itab = reflectdata.TypePtrAt(curfunc, base.Pos, val.Type()) | |
| } else { | |
| itab = reflectdata.ITabAddrAt(curfunc, base.Pos, val.Type(), typ) | |
| } | |
| // Create a copy of l to modify while we emit data. | |
| // Emit itab, advance offset. | |
| staticdata.InitAddr(l, loff, itab.X.(*ir.LinksymOffsetExpr).Linksym) | |
| // Emit data. | |
| if types.IsDirectIface(val.Type()) { | |
| if val.Op() == ir.ONIL { | |
| // Nil is zero, nothing to do. | |
| return true | |
| } | |
| // Copy val directly into n. | |
| ir.SetPos(val) | |
| assign(base.Pos, l, loff+int64(types.PtrSize), val) | |
| } else { | |
| // Construct temp to hold val, write pointer to temp into n. | |
| a := StaticName(val.Type()) | |
| s.Temps[val] = a | |
| assign(base.Pos, a, 0, val) | |
| staticdata.InitAddr(l, loff+int64(types.PtrSize), a.Linksym()) | |
| } | |
| return true | |
| case ir.OINLCALL: | |
| if disableGlobalAddrs { | |
| return false | |
| } | |
| r := r.(*ir.InlinedCallExpr) | |
| return s.staticAssignInlinedCall(curfunc, l, loff, r, typ) | |
| } | |
| if base.Flag.Percent != 0 { | |
| ir.Dump("not static", r) | |
| } | |
| return false | |
| } | |
| func (s *Schedule) initplan(n ir.Node) { | |
| if s.Plans[n] != nil { | |
| return | |
| } | |
| p := new(Plan) | |
| s.Plans[n] = p | |
| switch n.Op() { | |
| default: | |
| base.Fatalf("initplan") | |
| case ir.OARRAYLIT, ir.OSLICELIT: | |
| n := n.(*ir.CompLitExpr) | |
| var k int64 | |
| for _, a := range n.List { | |
| if a.Op() == ir.OKEY { | |
| kv := a.(*ir.KeyExpr) | |
| k = typecheck.IndexConst(kv.Key) | |
| a = kv.Value | |
| } | |
| s.addvalue(p, k*n.Type().Elem().Size(), a) | |
| k++ | |
| } | |
| case ir.OSTRUCTLIT: | |
| n := n.(*ir.CompLitExpr) | |
| for _, a := range n.List { | |
| if a.Op() != ir.OSTRUCTKEY { | |
| base.Fatalf("initplan structlit") | |
| } | |
| a := a.(*ir.StructKeyExpr) | |
| if a.Sym().IsBlank() { | |
| continue | |
| } | |
| s.addvalue(p, typecheck.FieldOffset(n.Type(), a.Field), a.Value) | |
| } | |
| case ir.OMAPLIT: | |
| n := n.(*ir.CompLitExpr) | |
| for _, a := range n.List { | |
| if a.Op() != ir.OKEY { | |
| base.Fatalf("initplan maplit") | |
| } | |
| a := a.(*ir.KeyExpr) | |
| s.addvalue(p, -1, a.Value) | |
| } | |
| } | |
| } | |
| func (s *Schedule) addvalue(p *Plan, xoffset int64, n ir.Node) { | |
| // special case: zero can be dropped entirely | |
| if ir.IsZero(n) { | |
| return | |
| } | |
| // special case: inline struct and array (not slice) literals | |
| if isvaluelit(n) { | |
| s.initplan(n) | |
| q := s.Plans[n] | |
| for _, qe := range q.E { | |
| // qe is a copy; we are not modifying entries in q.E | |
| qe.Xoffset += xoffset | |
| p.E = append(p.E, qe) | |
| } | |
| return | |
| } | |
| // add to plan | |
| p.E = append(p.E, Entry{Xoffset: xoffset, Expr: n}) | |
| } | |
| func (s *Schedule) staticAssignInlinedCall(curfunc *ir.Func, l *ir.Name, loff int64, call *ir.InlinedCallExpr, typ *types.Type) bool { | |
| if base.Debug.InlStaticInit == 0 { | |
| return false | |
| } | |
| // Handle the special case of an inlined call of | |
| // a function body with a single return statement, | |
| // which turns into a single assignment plus a goto. | |
| // | |
| // For example code like this: | |
| // | |
| // type T struct{ x int } | |
| // func F(x int) *T { return &T{x} } | |
| // var Global = F(400) | |
| // | |
| // turns into IR like this: | |
| // | |
| // INLCALL-init | |
| // . AS2-init | |
| // . . DCL # x.go:18:13 | |
| // . . . NAME-p.x Class:PAUTO Offset:0 InlFormal OnStack Used int tc(1) # x.go:14:9,x.go:18:13 | |
| // . AS2 Def tc(1) # x.go:18:13 | |
| // . AS2-Lhs | |
| // . . NAME-p.x Class:PAUTO Offset:0 InlFormal OnStack Used int tc(1) # x.go:14:9,x.go:18:13 | |
| // . AS2-Rhs | |
| // . . LITERAL-400 int tc(1) # x.go:18:14 | |
| // . INLMARK Index:1 # +x.go:18:13 | |
| // INLCALL PTR-*T tc(1) # x.go:18:13 | |
| // INLCALL-Body | |
| // . BLOCK tc(1) # x.go:18:13 | |
| // . BLOCK-List | |
| // . . DCL tc(1) # x.go:18:13 | |
| // . . . NAME-p.~R0 Class:PAUTO Offset:0 OnStack Used PTR-*T tc(1) # x.go:18:13 | |
| // . . AS2 tc(1) # x.go:18:13 | |
| // . . AS2-Lhs | |
| // . . . NAME-p.~R0 Class:PAUTO Offset:0 OnStack Used PTR-*T tc(1) # x.go:18:13 | |
| // . . AS2-Rhs | |
| // . . . INLINED RETURN ARGUMENT HERE | |
| // . . GOTO p..i1 tc(1) # x.go:18:13 | |
| // . LABEL p..i1 # x.go:18:13 | |
| // INLCALL-ReturnVars | |
| // . NAME-p.~R0 Class:PAUTO Offset:0 OnStack Used PTR-*T tc(1) # x.go:18:13 | |
| // | |
| // If the init values are side-effect-free and each either only | |
| // appears once in the function body or is safely repeatable, | |
| // then we inline the value expressions into the return argument | |
| // and then call StaticAssign to handle that copy. | |
| // | |
| // This handles simple cases like | |
| // | |
| // var myError = errors.New("mine") | |
| // | |
| // where errors.New is | |
| // | |
| // func New(text string) error { | |
| // return &errorString{text} | |
| // } | |
| // | |
| // We could make things more sophisticated but this kind of initializer | |
| // is the most important case for us to get right. | |
| init := call.Init() | |
| if len(init) != 2 || init[0].Op() != ir.OAS2 || init[1].Op() != ir.OINLMARK { | |
| return false | |
| } | |
| as2init := init[0].(*ir.AssignListStmt) | |
| if len(call.Body) != 2 || call.Body[0].Op() != ir.OBLOCK || call.Body[1].Op() != ir.OLABEL { | |
| return false | |
| } | |
| label := call.Body[1].(*ir.LabelStmt).Label | |
| block := call.Body[0].(*ir.BlockStmt) | |
| list := block.List | |
| if len(list) != 3 || | |
| list[0].Op() != ir.ODCL || | |
| list[1].Op() != ir.OAS2 || | |
| list[2].Op() != ir.OGOTO || | |
| list[2].(*ir.BranchStmt).Label != label { | |
| return false | |
| } | |
| dcl := list[0].(*ir.Decl) | |
| as2body := list[1].(*ir.AssignListStmt) | |
| if len(as2body.Lhs) != 1 || as2body.Lhs[0] != dcl.X { | |
| return false | |
| } | |
| // Can't remove the parameter variables if an address is taken. | |
| for _, v := range as2init.Lhs { | |
| if v.(*ir.Name).Addrtaken() { | |
| return false | |
| } | |
| } | |
| // Can't move the computation of the args if they have side effects. | |
| for _, r := range as2init.Rhs { | |
| if AnySideEffects(r) { | |
| return false | |
| } | |
| } | |
| // Can only substitute arg for param if param is used | |
| // at most once or is repeatable. | |
| count := make(map[*ir.Name]int) | |
| for _, x := range as2init.Lhs { | |
| count[x.(*ir.Name)] = 0 | |
| } | |
| hasClosure := false | |
| ir.Visit(as2body.Rhs[0], func(n ir.Node) { | |
| if name, ok := n.(*ir.Name); ok { | |
| if c, ok := count[name]; ok { | |
| count[name] = c + 1 | |
| } | |
| } | |
| if clo, ok := n.(*ir.ClosureExpr); ok { | |
| hasClosure = hasClosure || clo.Func.IsClosure() | |
| } | |
| }) | |
| // If there's a closure, it has captured the param, | |
| // so we can't substitute arg for param. | |
| if hasClosure { | |
| return false | |
| } | |
| for name, c := range count { | |
| if c > 1 { | |
| // Check whether corresponding initializer can be repeated. | |
| // Something like 1 can be; make(chan int) or &T{} cannot, | |
| // because they need to evaluate to the same result in each use. | |
| for i, n := range as2init.Lhs { | |
| if n == name && !canRepeat(as2init.Rhs[i]) { | |
| return false | |
| } | |
| } | |
| } | |
| } | |
| // Possible static init. | |
| // Build tree with args substituted for params and try it. | |
| args := make(map[*ir.Name]ir.Node) | |
| for i, v := range as2init.Lhs { | |
| if ir.IsBlank(v) { | |
| continue | |
| } | |
| args[v.(*ir.Name)] = as2init.Rhs[i] | |
| } | |
| r, ok := subst(as2body.Rhs[0], args) | |
| if !ok { | |
| return false | |
| } | |
| ok = s.StaticAssign(curfunc, l, loff, r, typ) | |
| if ok && base.Flag.Percent != 0 { | |
| ir.Dump("static inlined-LEFT", l) | |
| ir.Dump("static inlined-ORIG", call) | |
| ir.Dump("static inlined-RIGHT", r) | |
| } | |
| return ok | |
| } | |
| // from here down is the walk analysis | |
| // of composite literals. | |
| // most of the work is to generate | |
| // data statements for the constant | |
| // part of the composite literal. | |
| var statuniqgen int // name generator for static temps | |
| // StaticName returns a name backed by a (writable) static data symbol. | |
| func StaticName(t *types.Type) *ir.Name { | |
| // Don't use LookupNum; it interns the resulting string, but these are all unique. | |
| sym := typecheck.Lookup(fmt.Sprintf("%s%d", obj.StaticNamePrefix, statuniqgen)) | |
| statuniqgen++ | |
| n := ir.NewNameAt(base.Pos, sym, t) | |
| sym.Def = n | |
| n.Class = ir.PEXTERN | |
| typecheck.Target.Externs = append(typecheck.Target.Externs, n) | |
| n.Linksym().Set(obj.AttrStatic, true) | |
| n.Linksym().Align = int16(t.Alignment()) | |
| return n | |
| } | |
| // StaticLoc returns the static address of n, if n has one, or else nil. | |
| func StaticLoc(n ir.Node) (name *ir.Name, offset int64, ok bool) { | |
| if n == nil { | |
| return nil, 0, false | |
| } | |
| switch n.Op() { | |
| case ir.ONAME: | |
| n := n.(*ir.Name) | |
| return n, 0, true | |
| case ir.OMETHEXPR: | |
| n := n.(*ir.SelectorExpr) | |
| return StaticLoc(n.FuncName()) | |
| case ir.ODOT: | |
| n := n.(*ir.SelectorExpr) | |
| if name, offset, ok = StaticLoc(n.X); !ok { | |
| break | |
| } | |
| offset += n.Offset() | |
| return name, offset, true | |
| case ir.OINDEX: | |
| n := n.(*ir.IndexExpr) | |
| if n.X.Type().IsSlice() { | |
| break | |
| } | |
| if name, offset, ok = StaticLoc(n.X); !ok { | |
| break | |
| } | |
| l := getlit(n.Index) | |
| if l < 0 { | |
| break | |
| } | |
| // Check for overflow. | |
| if n.Type().Size() != 0 && types.MaxWidth/n.Type().Size() <= int64(l) { | |
| break | |
| } | |
| offset += int64(l) * n.Type().Size() | |
| return name, offset, true | |
| } | |
| return nil, 0, false | |
| } | |
| func isSideEffect(n ir.Node) bool { | |
| switch n.Op() { | |
| // Assume side effects unless we know otherwise. | |
| default: | |
| return true | |
| // No side effects here (arguments are checked separately). | |
| case ir.ONAME, | |
| ir.ONONAME, | |
| ir.OTYPE, | |
| ir.OLITERAL, | |
| ir.ONIL, | |
| ir.OADD, | |
| ir.OSUB, | |
| ir.OOR, | |
| ir.OXOR, | |
| ir.OADDSTR, | |
| ir.OADDR, | |
| ir.OANDAND, | |
| ir.OBYTES2STR, | |
| ir.ORUNES2STR, | |
| ir.OSTR2BYTES, | |
| ir.OSTR2RUNES, | |
| ir.OCAP, | |
| ir.OCOMPLIT, | |
| ir.OMAPLIT, | |
| ir.OSTRUCTLIT, | |
| ir.OARRAYLIT, | |
| ir.OSLICELIT, | |
| ir.OPTRLIT, | |
| ir.OCONV, | |
| ir.OCONVIFACE, | |
| ir.OCONVNOP, | |
| ir.ODOT, | |
| ir.OEQ, | |
| ir.ONE, | |
| ir.OLT, | |
| ir.OLE, | |
| ir.OGT, | |
| ir.OGE, | |
| ir.OKEY, | |
| ir.OSTRUCTKEY, | |
| ir.OLEN, | |
| ir.OMUL, | |
| ir.OLSH, | |
| ir.ORSH, | |
| ir.OAND, | |
| ir.OANDNOT, | |
| ir.ONEW, | |
| ir.ONOT, | |
| ir.OBITNOT, | |
| ir.OPLUS, | |
| ir.ONEG, | |
| ir.OOROR, | |
| ir.OPAREN, | |
| ir.ORUNESTR, | |
| ir.OREAL, | |
| ir.OIMAG, | |
| ir.OCOMPLEX: | |
| return false | |
| // Only possible side effect is division by zero. | |
| case ir.ODIV, ir.OMOD: | |
| n := n.(*ir.BinaryExpr) | |
| if n.Y.Op() != ir.OLITERAL || constant.Sign(n.Y.Val()) == 0 { | |
| return true | |
| } | |
| // Only possible side effect is panic on invalid size, | |
| // but many makechan and makemap use size zero, which is definitely OK. | |
| case ir.OMAKECHAN, ir.OMAKEMAP: | |
| n := n.(*ir.MakeExpr) | |
| if !ir.IsConst(n.Len, constant.Int) || constant.Sign(n.Len.Val()) != 0 { | |
| return true | |
| } | |
| // Only possible side effect is panic on invalid size. | |
| // TODO(rsc): Merge with previous case (probably breaks toolstash -cmp). | |
| case ir.OMAKESLICE, ir.OMAKESLICECOPY: | |
| return true | |
| } | |
| return false | |
| } | |
| // AnySideEffects reports whether n contains any operations that could have observable side effects. | |
| func AnySideEffects(n ir.Node) bool { | |
| return ir.Any(n, isSideEffect) | |
| } | |
| // mayModifyPkgVar reports whether expression n may modify any | |
| // package-scope variables declared within the current package. | |
| func mayModifyPkgVar(n ir.Node) bool { | |
| // safeLHS reports whether the assigned-to variable lhs is either a | |
| // local variable or a global from another package. | |
| safeLHS := func(lhs ir.Node) bool { | |
| outer := ir.OuterValue(lhs) | |
| // "*p = ..." should be safe if p is a local variable. | |
| // TODO: Should ir.OuterValue handle this? | |
| for outer.Op() == ir.ODEREF { | |
| outer = outer.(*ir.StarExpr).X | |
| } | |
| v, ok := outer.(*ir.Name) | |
| return ok && v.Op() == ir.ONAME && !(v.Class == ir.PEXTERN && v.Sym().Pkg == types.LocalPkg) | |
| } | |
| return ir.Any(n, func(n ir.Node) bool { | |
| switch n.Op() { | |
| case ir.OCALLFUNC, ir.OCALLINTER: | |
| return !ir.IsFuncPCIntrinsic(n.(*ir.CallExpr)) | |
| case ir.OAPPEND, ir.OCLEAR, ir.OCOPY: | |
| return true // could mutate a global array | |
| case ir.OASOP: | |
| n := n.(*ir.AssignOpStmt) | |
| if !safeLHS(n.X) { | |
| return true | |
| } | |
| case ir.OAS: | |
| n := n.(*ir.AssignStmt) | |
| if !safeLHS(n.X) { | |
| return true | |
| } | |
| case ir.OAS2, ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV: | |
| n := n.(*ir.AssignListStmt) | |
| for _, lhs := range n.Lhs { | |
| if !safeLHS(lhs) { | |
| return true | |
| } | |
| } | |
| } | |
| return false | |
| }) | |
| } | |
| // canRepeat reports whether executing n multiple times has the same effect as | |
| // assigning n to a single variable and using that variable multiple times. | |
| func canRepeat(n ir.Node) bool { | |
| bad := func(n ir.Node) bool { | |
| if isSideEffect(n) { | |
| return true | |
| } | |
| switch n.Op() { | |
| case ir.OMAKECHAN, | |
| ir.OMAKEMAP, | |
| ir.OMAKESLICE, | |
| ir.OMAKESLICECOPY, | |
| ir.OMAPLIT, | |
| ir.ONEW, | |
| ir.OPTRLIT, | |
| ir.OSLICELIT, | |
| ir.OSTR2BYTES, | |
| ir.OSTR2RUNES: | |
| return true | |
| } | |
| return false | |
| } | |
| return !ir.Any(n, bad) | |
| } | |
| func getlit(lit ir.Node) int { | |
| if ir.IsSmallIntConst(lit) { | |
| return int(ir.Int64Val(lit)) | |
| } | |
| return -1 | |
| } | |
| func isvaluelit(n ir.Node) bool { | |
| return n.Op() == ir.OARRAYLIT || n.Op() == ir.OSTRUCTLIT | |
| } | |
| func subst(n ir.Node, m map[*ir.Name]ir.Node) (ir.Node, bool) { | |
| valid := true | |
| var edit func(ir.Node) ir.Node | |
| edit = func(x ir.Node) ir.Node { | |
| switch x.Op() { | |
| case ir.ONAME: | |
| x := x.(*ir.Name) | |
| if v, ok := m[x]; ok { | |
| return ir.DeepCopy(v.Pos(), v) | |
| } | |
| return x | |
| case ir.ONONAME, ir.OLITERAL, ir.ONIL, ir.OTYPE: | |
| return x | |
| } | |
| x = ir.Copy(x) | |
| ir.EditChildrenWithHidden(x, edit) | |
| // TODO: handle more operations, see details discussion in go.dev/cl/466277. | |
| switch x.Op() { | |
| case ir.OCONV: | |
| x := x.(*ir.ConvExpr) | |
| if x.X.Op() == ir.OLITERAL { | |
| if x, ok := truncate(x.X, x.Type()); ok { | |
| return x | |
| } | |
| valid = false | |
| return x | |
| } | |
| case ir.OADDSTR: | |
| return addStr(x.(*ir.AddStringExpr)) | |
| } | |
| return x | |
| } | |
| n = edit(n) | |
| return n, valid | |
| } | |
| // truncate returns the result of force converting c to type t, | |
| // truncating its value as needed, like a conversion of a variable. | |
| // If the conversion is too difficult, truncate returns nil, false. | |
| func truncate(c ir.Node, t *types.Type) (ir.Node, bool) { | |
| ct := c.Type() | |
| cv := c.Val() | |
| if ct.Kind() != t.Kind() { | |
| switch { | |
| default: | |
| // Note: float -> float/integer and complex -> complex are valid but subtle. | |
| // For example a float32(float64 1e300) evaluates to +Inf at runtime | |
| // and the compiler doesn't have any concept of +Inf, so that would | |
| // have to be left for runtime code evaluation. | |
| // For now | |
| return nil, false | |
| case ct.IsInteger() && t.IsInteger(): | |
| // truncate or sign extend | |
| bits := t.Size() * 8 | |
| cv = constant.BinaryOp(cv, token.AND, constant.MakeUint64(1<<bits-1)) | |
| if t.IsSigned() && constant.Compare(cv, token.GEQ, constant.MakeUint64(1<<(bits-1))) { | |
| cv = constant.BinaryOp(cv, token.OR, constant.MakeInt64(-1<<(bits-1))) | |
| } | |
| } | |
| } | |
| c = ir.NewConstExpr(cv, c) | |
| c.SetType(t) | |
| return c, true | |
| } | |
| func addStr(n *ir.AddStringExpr) ir.Node { | |
| // Merge adjacent constants in the argument list. | |
| s := n.List | |
| need := 0 | |
| for i := 0; i < len(s); i++ { | |
| if i == 0 || !ir.IsConst(s[i-1], constant.String) || !ir.IsConst(s[i], constant.String) { | |
| // Can't merge s[i] into s[i-1]; need a slot in the list. | |
| need++ | |
| } | |
| } | |
| if need == len(s) { | |
| return n | |
| } | |
| if need == 1 { | |
| var strs []string | |
| for _, c := range s { | |
| strs = append(strs, ir.StringVal(c)) | |
| } | |
| return ir.NewConstExpr(constant.MakeString(strings.Join(strs, "")), n) | |
| } | |
| newList := make([]ir.Node, 0, need) | |
| for i := 0; i < len(s); i++ { | |
| if ir.IsConst(s[i], constant.String) && i+1 < len(s) && ir.IsConst(s[i+1], constant.String) { | |
| // merge from i up to but not including i2 | |
| var strs []string | |
| i2 := i | |
| for i2 < len(s) && ir.IsConst(s[i2], constant.String) { | |
| strs = append(strs, ir.StringVal(s[i2])) | |
| i2++ | |
| } | |
| newList = append(newList, ir.NewConstExpr(constant.MakeString(strings.Join(strs, "")), s[i])) | |
| i = i2 - 1 | |
| } else { | |
| newList = append(newList, s[i]) | |
| } | |
| } | |
| nn := ir.Copy(n).(*ir.AddStringExpr) | |
| nn.List = newList | |
| return nn | |
| } | |
| const wrapGlobalMapInitSizeThreshold = 20 | |
| // tryWrapGlobalInit returns a new outlined function to contain global | |
| // initializer statement n, if possible and worthwhile. Otherwise, it | |
| // returns nil. | |
| // | |
| // Currently, it outlines map assignment statements with large, | |
| // side-effect-free RHS expressions. | |
| func tryWrapGlobalInit(n ir.Node) *ir.Func { | |
| // 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 | |
| } | |
| as := n.(*ir.AssignStmt) | |
| if ir.IsBlank(as.X) || as.X.Op() != ir.ONAME { | |
| return nil | |
| } | |
| nm := as.X.(*ir.Name) | |
| if !nm.Type().IsMap() { | |
| return 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.WrapGlobalMapCtl != 2 { | |
| if base.Debug.WrapGlobalMapDbg > 1 { | |
| fmt.Fprintf(os.Stderr, "=-= skipping %v size too small at %d\n", | |
| nm, rsiz) | |
| } | |
| return 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 | |
| } | |
| 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> | |
| // } | |
| // | |
| // Note: cmd/link expects the function name to contain "map.init". | |
| minitsym := typecheck.LookupNum("map.init.", mapinitgen) | |
| mapinitgen++ | |
| fn := ir.NewFunc(n.Pos(), n.Pos(), minitsym, types.NewSignature(nil, nil, nil)) | |
| fn.SetInlinabilityChecked(true) // suppress inlining (which would defeat the point) | |
| typecheck.DeclFunc(fn) | |
| if base.Debug.WrapGlobalMapDbg > 0 { | |
| fmt.Fprintf(os.Stderr, "=-= generated func is %v\n", fn) | |
| } | |
| // 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. | |
| fn.Body = []ir.Node{as} | |
| if base.Debug.WrapGlobalMapDbg > 1 { | |
| fmt.Fprintf(os.Stderr, "=-= mapvar is %v\n", nm) | |
| fmt.Fprintf(os.Stderr, "=-= newfunc is %+v\n", fn) | |
| } | |
| recordFuncForVar(nm, fn) | |
| return fn | |
| } | |
| // 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) | |
| } | |
| vs.AddRel(base.Ctxt, obj.Reloc{Type: objabi.R_KEEP, Sym: fs}) | |
| 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 replaces global map initializers with outlined | |
| // calls to separate "map init" functions (where possible and | |
| // profitable), to facilitate better dead-code elimination by the | |
| // linker. | |
| func OutlineMapInits(fn *ir.Func) { | |
| if base.Debug.WrapGlobalMapCtl == 1 { | |
| return | |
| } | |
| outlined := 0 | |
| for i, stmt := range fn.Body { | |
| // Attempt to outline stmt. If successful, replace it with a call | |
| // to the returned wrapper function. | |
| if wrapperFn := tryWrapGlobalInit(stmt); wrapperFn != nil { | |
| ir.WithPos(fn, func() { | |
| fn.Body[i] = typecheck.Call(fn, stmt.Pos(), wrapperFn.Nname, nil, false) | |
| }) | |
| outlined++ | |
| } | |
| } | |
| if base.Debug.WrapGlobalMapDbg > 1 { | |
| fmt.Fprintf(os.Stderr, "=-= outlined %v map initializations\n", outlined) | |
| } | |
| } | |
| // varInitGen is a counter used to uniquify compiler-generated functions for initializing variables. | |
| var varInitGen int | |
| const varInitFuncPrefix = "init.part." | |
| // GenerateVarInitFunc create a new function that will (eventually) have this form: | |
| // | |
| // func init.part.%d() { | |
| // ... | |
| // } | |
| func GenerateVarInitFunc() *ir.Func { | |
| pos := base.AutogeneratedPos | |
| base.Pos = pos | |
| sym := typecheck.LookupNum(varInitFuncPrefix, varInitGen) | |
| varInitGen++ | |
| fn := ir.NewFunc(pos, pos, sym, types.NewSignature(nil, nil, nil)) | |
| fn.SetInlinabilityChecked(true) // suppress inlining; otherwise, we end up with giant init eventually. | |
| fn.SetWrapper(true) // less disruptive on backtraces. | |
| return fn | |
| } | |
| // CanOptimize reports whether the given fn can be optimized for static assignments. | |
| func CanOptimize(fn *ir.Func) bool { | |
| name := fn.Sym().Name | |
| return name == "init" || strings.HasPrefix(name, varInitFuncPrefix) | |
| } |