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

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package staticinit
     6  
     7  import (
     8  	"fmt"
     9  	"go/constant"
    10  	"go/token"
    11  	"os"
    12  	"strings"
    13  
    14  	"cmd/compile/internal/base"
    15  	"cmd/compile/internal/ir"
    16  	"cmd/compile/internal/reflectdata"
    17  	"cmd/compile/internal/staticdata"
    18  	"cmd/compile/internal/typecheck"
    19  	"cmd/compile/internal/types"
    20  	"cmd/internal/obj"
    21  	"cmd/internal/objabi"
    22  	"cmd/internal/src"
    23  )
    24  
    25  type Entry struct {
    26  	Xoffset int64   // struct, array only
    27  	Expr    ir.Node // bytes of run-time computed expressions
    28  }
    29  
    30  type Plan struct {
    31  	E []Entry
    32  }
    33  
    34  // An Schedule is used to decompose assignment statements into
    35  // static and dynamic initialization parts. Static initializations are
    36  // handled by populating variables' linker symbol data, while dynamic
    37  // initializations are accumulated to be executed in order.
    38  type Schedule struct {
    39  	// Out is the ordered list of dynamic initialization
    40  	// statements.
    41  	Out []ir.Node
    42  
    43  	Plans map[ir.Node]*Plan
    44  	Temps map[ir.Node]*ir.Name
    45  
    46  	// seenMutation tracks whether we've seen an initialization
    47  	// expression that may have modified other package-scope variables
    48  	// within this package.
    49  	seenMutation bool
    50  }
    51  
    52  func (s *Schedule) append(n ir.Node) {
    53  	s.Out = append(s.Out, n)
    54  }
    55  
    56  // StaticInit adds an initialization statement n to the schedule.
    57  func (s *Schedule) StaticInit(n ir.Node) {
    58  	if !s.tryStaticInit(n) {
    59  		if base.Flag.Percent != 0 {
    60  			ir.Dump("StaticInit failed", n)
    61  		}
    62  		s.append(n)
    63  	}
    64  }
    65  
    66  // varToMapInit holds book-keeping state for global map initialization;
    67  // it records the init function created by the compiler to host the
    68  // initialization code for the map in question.
    69  var varToMapInit map[*ir.Name]*ir.Func
    70  
    71  // MapInitToVar is the inverse of VarToMapInit; it maintains a mapping
    72  // from a compiler-generated init function to the map the function is
    73  // initializing.
    74  var MapInitToVar map[*ir.Func]*ir.Name
    75  
    76  // recordFuncForVar establishes a mapping between global map var "v" and
    77  // outlined init function "fn" (and vice versa); so that we can use
    78  // the mappings later on to update relocations.
    79  func recordFuncForVar(v *ir.Name, fn *ir.Func) {
    80  	if varToMapInit == nil {
    81  		varToMapInit = make(map[*ir.Name]*ir.Func)
    82  		MapInitToVar = make(map[*ir.Func]*ir.Name)
    83  	}
    84  	varToMapInit[v] = fn
    85  	MapInitToVar[fn] = v
    86  }
    87  
    88  // allBlank reports whether every node in exprs is blank.
    89  func allBlank(exprs []ir.Node) bool {
    90  	for _, expr := range exprs {
    91  		if !ir.IsBlank(expr) {
    92  			return false
    93  		}
    94  	}
    95  	return true
    96  }
    97  
    98  // tryStaticInit attempts to statically execute an initialization
    99  // statement and reports whether it succeeded.
   100  func (s *Schedule) tryStaticInit(n ir.Node) bool {
   101  	var lhs []ir.Node
   102  	var rhs ir.Node
   103  
   104  	switch n.Op() {
   105  	default:
   106  		base.FatalfAt(n.Pos(), "unexpected initialization statement: %v", n)
   107  	case ir.OAS:
   108  		n := n.(*ir.AssignStmt)
   109  		lhs, rhs = []ir.Node{n.X}, n.Y
   110  	case ir.OAS2:
   111  		// Usually OAS2 has been rewritten to separate OASes by types2.
   112  		// What's left here is "var a, b = tmp1, tmp2" as a result from rewriting
   113  		// "var a, b = f()" that needs type conversion, which is not static.
   114  		n := n.(*ir.AssignListStmt)
   115  		for _, rhs := range n.Rhs {
   116  			for rhs.Op() == ir.OCONVNOP {
   117  				rhs = rhs.(*ir.ConvExpr).X
   118  			}
   119  			if name, ok := rhs.(*ir.Name); !ok || !name.AutoTemp() {
   120  				base.FatalfAt(n.Pos(), "unexpected rhs, not an autotmp: %+v", rhs)
   121  			}
   122  		}
   123  		return false
   124  	case ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV:
   125  		n := n.(*ir.AssignListStmt)
   126  		if len(n.Lhs) < 2 || len(n.Rhs) != 1 {
   127  			base.FatalfAt(n.Pos(), "unexpected shape for %v: %v", n.Op(), n)
   128  		}
   129  		lhs, rhs = n.Lhs, n.Rhs[0]
   130  	case ir.OCALLFUNC:
   131  		return false // outlined map init call; no mutations
   132  	}
   133  
   134  	if !s.seenMutation {
   135  		s.seenMutation = mayModifyPkgVar(rhs)
   136  	}
   137  
   138  	if allBlank(lhs) && !AnySideEffects(rhs) {
   139  		return true // discard
   140  	}
   141  
   142  	// Only worry about simple "l = r" assignments. The OAS2*
   143  	// assignments mostly necessitate dynamic execution anyway.
   144  	if len(lhs) > 1 {
   145  		return false
   146  	}
   147  
   148  	lno := ir.SetPos(n)
   149  	defer func() { base.Pos = lno }()
   150  
   151  	nam := lhs[0].(*ir.Name)
   152  	return s.StaticAssign(nam, 0, rhs, nam.Type())
   153  }
   154  
   155  // like staticassign but we are copying an already
   156  // initialized value r.
   157  func (s *Schedule) staticcopy(l *ir.Name, loff int64, rn *ir.Name, typ *types.Type) bool {
   158  	if rn.Class == ir.PFUNC {
   159  		// TODO if roff != 0 { panic }
   160  		staticdata.InitAddr(l, loff, staticdata.FuncLinksym(rn))
   161  		return true
   162  	}
   163  	if rn.Class != ir.PEXTERN || rn.Sym().Pkg != types.LocalPkg {
   164  		return false
   165  	}
   166  	if rn.Defn == nil {
   167  		// No explicit initialization value. Probably zeroed but perhaps
   168  		// supplied externally and of unknown value.
   169  		return false
   170  	}
   171  	if rn.Defn.Op() != ir.OAS {
   172  		return false
   173  	}
   174  	if rn.Type().IsString() { // perhaps overwritten by cmd/link -X (#34675)
   175  		return false
   176  	}
   177  	if rn.Embed != nil {
   178  		return false
   179  	}
   180  	orig := rn
   181  	r := rn.Defn.(*ir.AssignStmt).Y
   182  	if r == nil {
   183  		// types2.InitOrder doesn't include default initializers.
   184  		base.Fatalf("unexpected initializer: %v", rn.Defn)
   185  	}
   186  
   187  	// Variable may have been reassigned by a user-written function call
   188  	// that was invoked to initialize another global variable (#51913).
   189  	if s.seenMutation {
   190  		if base.Debug.StaticCopy != 0 {
   191  			base.WarnfAt(l.Pos(), "skipping static copy of %v+%v with %v", l, loff, r)
   192  		}
   193  		return false
   194  	}
   195  
   196  	for r.Op() == ir.OCONVNOP && !types.Identical(r.Type(), typ) {
   197  		r = r.(*ir.ConvExpr).X
   198  	}
   199  
   200  	switch r.Op() {
   201  	case ir.OMETHEXPR:
   202  		r = r.(*ir.SelectorExpr).FuncName()
   203  		fallthrough
   204  	case ir.ONAME:
   205  		r := r.(*ir.Name)
   206  		if s.staticcopy(l, loff, r, typ) {
   207  			return true
   208  		}
   209  		// We may have skipped past one or more OCONVNOPs, so
   210  		// use conv to ensure r is assignable to l (#13263).
   211  		dst := ir.Node(l)
   212  		if loff != 0 || !types.Identical(typ, l.Type()) {
   213  			dst = ir.NewNameOffsetExpr(base.Pos, l, loff, typ)
   214  		}
   215  		s.append(ir.NewAssignStmt(base.Pos, dst, typecheck.Conv(r, typ)))
   216  		return true
   217  
   218  	case ir.ONIL:
   219  		return true
   220  
   221  	case ir.OLITERAL:
   222  		if ir.IsZero(r) {
   223  			return true
   224  		}
   225  		staticdata.InitConst(l, loff, r, int(typ.Size()))
   226  		return true
   227  
   228  	case ir.OADDR:
   229  		r := r.(*ir.AddrExpr)
   230  		if a, ok := r.X.(*ir.Name); ok && a.Op() == ir.ONAME {
   231  			staticdata.InitAddr(l, loff, staticdata.GlobalLinksym(a))
   232  			return true
   233  		}
   234  
   235  	case ir.OPTRLIT:
   236  		r := r.(*ir.AddrExpr)
   237  		switch r.X.Op() {
   238  		case ir.OARRAYLIT, ir.OSLICELIT, ir.OSTRUCTLIT, ir.OMAPLIT:
   239  			// copy pointer
   240  			staticdata.InitAddr(l, loff, staticdata.GlobalLinksym(s.Temps[r]))
   241  			return true
   242  		}
   243  
   244  	case ir.OSLICELIT:
   245  		r := r.(*ir.CompLitExpr)
   246  		// copy slice
   247  		staticdata.InitSlice(l, loff, staticdata.GlobalLinksym(s.Temps[r]), r.Len)
   248  		return true
   249  
   250  	case ir.OARRAYLIT, ir.OSTRUCTLIT:
   251  		r := r.(*ir.CompLitExpr)
   252  		p := s.Plans[r]
   253  		for i := range p.E {
   254  			e := &p.E[i]
   255  			typ := e.Expr.Type()
   256  			if e.Expr.Op() == ir.OLITERAL || e.Expr.Op() == ir.ONIL {
   257  				staticdata.InitConst(l, loff+e.Xoffset, e.Expr, int(typ.Size()))
   258  				continue
   259  			}
   260  			x := e.Expr
   261  			if x.Op() == ir.OMETHEXPR {
   262  				x = x.(*ir.SelectorExpr).FuncName()
   263  			}
   264  			if x.Op() == ir.ONAME && s.staticcopy(l, loff+e.Xoffset, x.(*ir.Name), typ) {
   265  				continue
   266  			}
   267  			// Requires computation, but we're
   268  			// copying someone else's computation.
   269  			ll := ir.NewNameOffsetExpr(base.Pos, l, loff+e.Xoffset, typ)
   270  			rr := ir.NewNameOffsetExpr(base.Pos, orig, e.Xoffset, typ)
   271  			ir.SetPos(rr)
   272  			s.append(ir.NewAssignStmt(base.Pos, ll, rr))
   273  		}
   274  
   275  		return true
   276  	}
   277  
   278  	return false
   279  }
   280  
   281  func (s *Schedule) StaticAssign(l *ir.Name, loff int64, r ir.Node, typ *types.Type) bool {
   282  	// If we're building for FIPS, avoid global data relocations
   283  	// by treating all address-of operations as non-static.
   284  	// See ../../../internal/obj/fips.go for more context.
   285  	// We do this even in non-PIE mode to avoid generating
   286  	// static temporaries that would go into SRODATAFIPS
   287  	// but need relocations. We can't handle that in the verification.
   288  	disableGlobalAddrs := base.Ctxt.IsFIPS()
   289  
   290  	if r == nil {
   291  		// No explicit initialization value. Either zero or supplied
   292  		// externally.
   293  		return true
   294  	}
   295  	for r.Op() == ir.OCONVNOP {
   296  		r = r.(*ir.ConvExpr).X
   297  	}
   298  
   299  	assign := func(pos src.XPos, a *ir.Name, aoff int64, v ir.Node) {
   300  		if s.StaticAssign(a, aoff, v, v.Type()) {
   301  			return
   302  		}
   303  		var lhs ir.Node
   304  		if ir.IsBlank(a) {
   305  			// Don't use NameOffsetExpr with blank (#43677).
   306  			lhs = ir.BlankNode
   307  		} else {
   308  			lhs = ir.NewNameOffsetExpr(pos, a, aoff, v.Type())
   309  		}
   310  		s.append(ir.NewAssignStmt(pos, lhs, v))
   311  	}
   312  
   313  	switch r.Op() {
   314  	case ir.ONAME:
   315  		if disableGlobalAddrs {
   316  			return false
   317  		}
   318  		r := r.(*ir.Name)
   319  		return s.staticcopy(l, loff, r, typ)
   320  
   321  	case ir.OMETHEXPR:
   322  		if disableGlobalAddrs {
   323  			return false
   324  		}
   325  		r := r.(*ir.SelectorExpr)
   326  		return s.staticcopy(l, loff, r.FuncName(), typ)
   327  
   328  	case ir.ONIL:
   329  		return true
   330  
   331  	case ir.OLITERAL:
   332  		if ir.IsZero(r) {
   333  			return true
   334  		}
   335  		if disableGlobalAddrs && r.Type().IsString() {
   336  			return false
   337  		}
   338  		staticdata.InitConst(l, loff, r, int(typ.Size()))
   339  		return true
   340  
   341  	case ir.OADDR:
   342  		if disableGlobalAddrs {
   343  			return false
   344  		}
   345  		r := r.(*ir.AddrExpr)
   346  		if name, offset, ok := StaticLoc(r.X); ok && name.Class == ir.PEXTERN {
   347  			staticdata.InitAddrOffset(l, loff, name.Linksym(), offset)
   348  			return true
   349  		}
   350  		fallthrough
   351  
   352  	case ir.OPTRLIT:
   353  		if disableGlobalAddrs {
   354  			return false
   355  		}
   356  		r := r.(*ir.AddrExpr)
   357  		switch r.X.Op() {
   358  		case ir.OARRAYLIT, ir.OSLICELIT, ir.OMAPLIT, ir.OSTRUCTLIT:
   359  			// Init pointer.
   360  			a := StaticName(r.X.Type())
   361  
   362  			s.Temps[r] = a
   363  			staticdata.InitAddr(l, loff, a.Linksym())
   364  
   365  			// Init underlying literal.
   366  			assign(base.Pos, a, 0, r.X)
   367  			return true
   368  		}
   369  		//dump("not static ptrlit", r);
   370  
   371  	case ir.OSTR2BYTES:
   372  		if disableGlobalAddrs {
   373  			return false
   374  		}
   375  		r := r.(*ir.ConvExpr)
   376  		if l.Class == ir.PEXTERN && r.X.Op() == ir.OLITERAL {
   377  			sval := ir.StringVal(r.X)
   378  			staticdata.InitSliceBytes(l, loff, sval)
   379  			return true
   380  		}
   381  
   382  	case ir.OSLICELIT:
   383  		if disableGlobalAddrs {
   384  			return false
   385  		}
   386  		r := r.(*ir.CompLitExpr)
   387  		s.initplan(r)
   388  		// Init slice.
   389  		ta := types.NewArray(r.Type().Elem(), r.Len)
   390  		ta.SetNoalg(true)
   391  		a := StaticName(ta)
   392  		s.Temps[r] = a
   393  		staticdata.InitSlice(l, loff, a.Linksym(), r.Len)
   394  		// Fall through to init underlying array.
   395  		l = a
   396  		loff = 0
   397  		fallthrough
   398  
   399  	case ir.OARRAYLIT, ir.OSTRUCTLIT:
   400  		r := r.(*ir.CompLitExpr)
   401  		s.initplan(r)
   402  
   403  		p := s.Plans[r]
   404  		for i := range p.E {
   405  			e := &p.E[i]
   406  			if e.Expr.Op() == ir.OLITERAL && !disableGlobalAddrs || e.Expr.Op() == ir.ONIL {
   407  				staticdata.InitConst(l, loff+e.Xoffset, e.Expr, int(e.Expr.Type().Size()))
   408  				continue
   409  			}
   410  			ir.SetPos(e.Expr)
   411  			assign(base.Pos, l, loff+e.Xoffset, e.Expr)
   412  		}
   413  
   414  		return true
   415  
   416  	case ir.OMAPLIT:
   417  		break
   418  
   419  	case ir.OCLOSURE:
   420  		if disableGlobalAddrs {
   421  			return false
   422  		}
   423  		r := r.(*ir.ClosureExpr)
   424  		if !r.Func.IsClosure() {
   425  			if base.Debug.Closure > 0 {
   426  				base.WarnfAt(r.Pos(), "closure converted to global")
   427  			}
   428  			// Closures with no captured variables are globals,
   429  			// so the assignment can be done at link time.
   430  			// TODO if roff != 0 { panic }
   431  			staticdata.InitAddr(l, loff, staticdata.FuncLinksym(r.Func.Nname))
   432  			return true
   433  		}
   434  		ir.ClosureDebugRuntimeCheck(r)
   435  
   436  	case ir.OCONVIFACE:
   437  		// This logic is mirrored in isStaticCompositeLiteral.
   438  		// If you change something here, change it there, and vice versa.
   439  
   440  		if disableGlobalAddrs {
   441  			return false
   442  		}
   443  
   444  		// Determine the underlying concrete type and value we are converting from.
   445  		r := r.(*ir.ConvExpr)
   446  		val := ir.Node(r)
   447  		for val.Op() == ir.OCONVIFACE {
   448  			val = val.(*ir.ConvExpr).X
   449  		}
   450  
   451  		if val.Type().IsInterface() {
   452  			// val is an interface type.
   453  			// If val is nil, we can statically initialize l;
   454  			// both words are zero and so there no work to do, so report success.
   455  			// If val is non-nil, we have no concrete type to record,
   456  			// and we won't be able to statically initialize its value, so report failure.
   457  			return val.Op() == ir.ONIL
   458  		}
   459  
   460  		if val.Type().HasShape() {
   461  			// See comment in cmd/compile/internal/walk/convert.go:walkConvInterface
   462  			return false
   463  		}
   464  
   465  		reflectdata.MarkTypeUsedInInterface(val.Type(), l.Linksym())
   466  
   467  		var itab *ir.AddrExpr
   468  		if typ.IsEmptyInterface() {
   469  			itab = reflectdata.TypePtrAt(base.Pos, val.Type())
   470  		} else {
   471  			itab = reflectdata.ITabAddrAt(base.Pos, val.Type(), typ)
   472  		}
   473  
   474  		// Create a copy of l to modify while we emit data.
   475  
   476  		// Emit itab, advance offset.
   477  		staticdata.InitAddr(l, loff, itab.X.(*ir.LinksymOffsetExpr).Linksym)
   478  
   479  		// Emit data.
   480  		if types.IsDirectIface(val.Type()) {
   481  			if val.Op() == ir.ONIL {
   482  				// Nil is zero, nothing to do.
   483  				return true
   484  			}
   485  			// Copy val directly into n.
   486  			ir.SetPos(val)
   487  			assign(base.Pos, l, loff+int64(types.PtrSize), val)
   488  		} else {
   489  			// Construct temp to hold val, write pointer to temp into n.
   490  			a := StaticName(val.Type())
   491  			s.Temps[val] = a
   492  			assign(base.Pos, a, 0, val)
   493  			staticdata.InitAddr(l, loff+int64(types.PtrSize), a.Linksym())
   494  		}
   495  
   496  		return true
   497  
   498  	case ir.OINLCALL:
   499  		if disableGlobalAddrs {
   500  			return false
   501  		}
   502  		r := r.(*ir.InlinedCallExpr)
   503  		return s.staticAssignInlinedCall(l, loff, r, typ)
   504  	}
   505  
   506  	if base.Flag.Percent != 0 {
   507  		ir.Dump("not static", r)
   508  	}
   509  	return false
   510  }
   511  
   512  func (s *Schedule) initplan(n ir.Node) {
   513  	if s.Plans[n] != nil {
   514  		return
   515  	}
   516  	p := new(Plan)
   517  	s.Plans[n] = p
   518  	switch n.Op() {
   519  	default:
   520  		base.Fatalf("initplan")
   521  
   522  	case ir.OARRAYLIT, ir.OSLICELIT:
   523  		n := n.(*ir.CompLitExpr)
   524  		var k int64
   525  		for _, a := range n.List {
   526  			if a.Op() == ir.OKEY {
   527  				kv := a.(*ir.KeyExpr)
   528  				k = typecheck.IndexConst(kv.Key)
   529  				a = kv.Value
   530  			}
   531  			s.addvalue(p, k*n.Type().Elem().Size(), a)
   532  			k++
   533  		}
   534  
   535  	case ir.OSTRUCTLIT:
   536  		n := n.(*ir.CompLitExpr)
   537  		for _, a := range n.List {
   538  			if a.Op() != ir.OSTRUCTKEY {
   539  				base.Fatalf("initplan structlit")
   540  			}
   541  			a := a.(*ir.StructKeyExpr)
   542  			if a.Sym().IsBlank() {
   543  				continue
   544  			}
   545  			s.addvalue(p, a.Field.Offset, a.Value)
   546  		}
   547  
   548  	case ir.OMAPLIT:
   549  		n := n.(*ir.CompLitExpr)
   550  		for _, a := range n.List {
   551  			if a.Op() != ir.OKEY {
   552  				base.Fatalf("initplan maplit")
   553  			}
   554  			a := a.(*ir.KeyExpr)
   555  			s.addvalue(p, -1, a.Value)
   556  		}
   557  	}
   558  }
   559  
   560  func (s *Schedule) addvalue(p *Plan, xoffset int64, n ir.Node) {
   561  	// special case: zero can be dropped entirely
   562  	if ir.IsZero(n) {
   563  		return
   564  	}
   565  
   566  	// special case: inline struct and array (not slice) literals
   567  	if isvaluelit(n) {
   568  		s.initplan(n)
   569  		q := s.Plans[n]
   570  		for _, qe := range q.E {
   571  			// qe is a copy; we are not modifying entries in q.E
   572  			qe.Xoffset += xoffset
   573  			p.E = append(p.E, qe)
   574  		}
   575  		return
   576  	}
   577  
   578  	// add to plan
   579  	p.E = append(p.E, Entry{Xoffset: xoffset, Expr: n})
   580  }
   581  
   582  func (s *Schedule) staticAssignInlinedCall(l *ir.Name, loff int64, call *ir.InlinedCallExpr, typ *types.Type) bool {
   583  	if base.Debug.InlStaticInit == 0 {
   584  		return false
   585  	}
   586  
   587  	// Handle the special case of an inlined call of
   588  	// a function body with a single return statement,
   589  	// which turns into a single assignment plus a goto.
   590  	//
   591  	// For example code like this:
   592  	//
   593  	//	type T struct{ x int }
   594  	//	func F(x int) *T { return &T{x} }
   595  	//	var Global = F(400)
   596  	//
   597  	// turns into IR like this:
   598  	//
   599  	// 	INLCALL-init
   600  	// 	.   AS2-init
   601  	// 	.   .   DCL # x.go:18:13
   602  	// 	.   .   .   NAME-p.x Class:PAUTO Offset:0 InlFormal OnStack Used int tc(1) # x.go:14:9,x.go:18:13
   603  	// 	.   AS2 Def tc(1) # x.go:18:13
   604  	// 	.   AS2-Lhs
   605  	// 	.   .   NAME-p.x Class:PAUTO Offset:0 InlFormal OnStack Used int tc(1) # x.go:14:9,x.go:18:13
   606  	// 	.   AS2-Rhs
   607  	// 	.   .   LITERAL-400 int tc(1) # x.go:18:14
   608  	// 	.   INLMARK Index:1 # +x.go:18:13
   609  	// 	INLCALL PTR-*T tc(1) # x.go:18:13
   610  	// 	INLCALL-Body
   611  	// 	.   BLOCK tc(1) # x.go:18:13
   612  	// 	.   BLOCK-List
   613  	// 	.   .   DCL tc(1) # x.go:18:13
   614  	// 	.   .   .   NAME-p.~R0 Class:PAUTO Offset:0 OnStack Used PTR-*T tc(1) # x.go:18:13
   615  	// 	.   .   AS2 tc(1) # x.go:18:13
   616  	// 	.   .   AS2-Lhs
   617  	// 	.   .   .   NAME-p.~R0 Class:PAUTO Offset:0 OnStack Used PTR-*T tc(1) # x.go:18:13
   618  	// 	.   .   AS2-Rhs
   619  	// 	.   .   .   INLINED RETURN ARGUMENT HERE
   620  	// 	.   .   GOTO p..i1 tc(1) # x.go:18:13
   621  	// 	.   LABEL p..i1 # x.go:18:13
   622  	// 	INLCALL-ReturnVars
   623  	// 	.   NAME-p.~R0 Class:PAUTO Offset:0 OnStack Used PTR-*T tc(1) # x.go:18:13
   624  	//
   625  	// If the init values are side-effect-free and each either only
   626  	// appears once in the function body or is safely repeatable,
   627  	// then we inline the value expressions into the return argument
   628  	// and then call StaticAssign to handle that copy.
   629  	//
   630  	// This handles simple cases like
   631  	//
   632  	//	var myError = errors.New("mine")
   633  	//
   634  	// where errors.New is
   635  	//
   636  	//	func New(text string) error {
   637  	//		return &errorString{text}
   638  	//	}
   639  	//
   640  	// We could make things more sophisticated but this kind of initializer
   641  	// is the most important case for us to get right.
   642  
   643  	init := call.Init()
   644  	if len(init) != 2 || init[0].Op() != ir.OAS2 || init[1].Op() != ir.OINLMARK {
   645  		return false
   646  	}
   647  	as2init := init[0].(*ir.AssignListStmt)
   648  
   649  	if len(call.Body) != 2 || call.Body[0].Op() != ir.OBLOCK || call.Body[1].Op() != ir.OLABEL {
   650  		return false
   651  	}
   652  	label := call.Body[1].(*ir.LabelStmt).Label
   653  	block := call.Body[0].(*ir.BlockStmt)
   654  	list := block.List
   655  	if len(list) != 3 ||
   656  		list[0].Op() != ir.ODCL ||
   657  		list[1].Op() != ir.OAS2 ||
   658  		list[2].Op() != ir.OGOTO ||
   659  		list[2].(*ir.BranchStmt).Label != label {
   660  		return false
   661  	}
   662  	dcl := list[0].(*ir.Decl)
   663  	as2body := list[1].(*ir.AssignListStmt)
   664  	if len(as2body.Lhs) != 1 || as2body.Lhs[0] != dcl.X {
   665  		return false
   666  	}
   667  
   668  	// Can't remove the parameter variables if an address is taken.
   669  	for _, v := range as2init.Lhs {
   670  		if v.(*ir.Name).Addrtaken() {
   671  			return false
   672  		}
   673  	}
   674  	// Can't move the computation of the args if they have side effects.
   675  	for _, r := range as2init.Rhs {
   676  		if AnySideEffects(r) {
   677  			return false
   678  		}
   679  	}
   680  
   681  	// Can only substitute arg for param if param is used
   682  	// at most once or is repeatable.
   683  	count := make(map[*ir.Name]int)
   684  	for _, x := range as2init.Lhs {
   685  		count[x.(*ir.Name)] = 0
   686  	}
   687  
   688  	hasClosure := false
   689  	ir.Visit(as2body.Rhs[0], func(n ir.Node) {
   690  		if name, ok := n.(*ir.Name); ok {
   691  			if c, ok := count[name]; ok {
   692  				count[name] = c + 1
   693  			}
   694  		}
   695  		if clo, ok := n.(*ir.ClosureExpr); ok {
   696  			hasClosure = hasClosure || clo.Func.IsClosure()
   697  		}
   698  	})
   699  
   700  	// If there's a closure, it has captured the param,
   701  	// so we can't substitute arg for param.
   702  	if hasClosure {
   703  		return false
   704  	}
   705  
   706  	for name, c := range count {
   707  		if c > 1 {
   708  			// Check whether corresponding initializer can be repeated.
   709  			// Something like 1 can be; make(chan int) or &T{} cannot,
   710  			// because they need to evaluate to the same result in each use.
   711  			for i, n := range as2init.Lhs {
   712  				if n == name && !canRepeat(as2init.Rhs[i]) {
   713  					return false
   714  				}
   715  			}
   716  		}
   717  	}
   718  
   719  	// Possible static init.
   720  	// Build tree with args substituted for params and try it.
   721  	args := make(map[*ir.Name]ir.Node)
   722  	for i, v := range as2init.Lhs {
   723  		if ir.IsBlank(v) {
   724  			continue
   725  		}
   726  		args[v.(*ir.Name)] = as2init.Rhs[i]
   727  	}
   728  	r, ok := subst(as2body.Rhs[0], args)
   729  	if !ok {
   730  		return false
   731  	}
   732  	ok = s.StaticAssign(l, loff, r, typ)
   733  
   734  	if ok && base.Flag.Percent != 0 {
   735  		ir.Dump("static inlined-LEFT", l)
   736  		ir.Dump("static inlined-ORIG", call)
   737  		ir.Dump("static inlined-RIGHT", r)
   738  	}
   739  	return ok
   740  }
   741  
   742  // from here down is the walk analysis
   743  // of composite literals.
   744  // most of the work is to generate
   745  // data statements for the constant
   746  // part of the composite literal.
   747  
   748  var statuniqgen int // name generator for static temps
   749  
   750  // StaticName returns a name backed by a (writable) static data symbol.
   751  func StaticName(t *types.Type) *ir.Name {
   752  	// Don't use LookupNum; it interns the resulting string, but these are all unique.
   753  	sym := typecheck.Lookup(fmt.Sprintf("%s%d", obj.StaticNamePrefix, statuniqgen))
   754  	statuniqgen++
   755  
   756  	n := ir.NewNameAt(base.Pos, sym, t)
   757  	sym.Def = n
   758  
   759  	n.Class = ir.PEXTERN
   760  	typecheck.Target.Externs = append(typecheck.Target.Externs, n)
   761  
   762  	n.Linksym().Set(obj.AttrStatic, true)
   763  	return n
   764  }
   765  
   766  // StaticLoc returns the static address of n, if n has one, or else nil.
   767  func StaticLoc(n ir.Node) (name *ir.Name, offset int64, ok bool) {
   768  	if n == nil {
   769  		return nil, 0, false
   770  	}
   771  
   772  	switch n.Op() {
   773  	case ir.ONAME:
   774  		n := n.(*ir.Name)
   775  		return n, 0, true
   776  
   777  	case ir.OMETHEXPR:
   778  		n := n.(*ir.SelectorExpr)
   779  		return StaticLoc(n.FuncName())
   780  
   781  	case ir.ODOT:
   782  		n := n.(*ir.SelectorExpr)
   783  		if name, offset, ok = StaticLoc(n.X); !ok {
   784  			break
   785  		}
   786  		offset += n.Offset()
   787  		return name, offset, true
   788  
   789  	case ir.OINDEX:
   790  		n := n.(*ir.IndexExpr)
   791  		if n.X.Type().IsSlice() {
   792  			break
   793  		}
   794  		if name, offset, ok = StaticLoc(n.X); !ok {
   795  			break
   796  		}
   797  		l := getlit(n.Index)
   798  		if l < 0 {
   799  			break
   800  		}
   801  
   802  		// Check for overflow.
   803  		if n.Type().Size() != 0 && types.MaxWidth/n.Type().Size() <= int64(l) {
   804  			break
   805  		}
   806  		offset += int64(l) * n.Type().Size()
   807  		return name, offset, true
   808  	}
   809  
   810  	return nil, 0, false
   811  }
   812  
   813  func isSideEffect(n ir.Node) bool {
   814  	switch n.Op() {
   815  	// Assume side effects unless we know otherwise.
   816  	default:
   817  		return true
   818  
   819  	// No side effects here (arguments are checked separately).
   820  	case ir.ONAME,
   821  		ir.ONONAME,
   822  		ir.OTYPE,
   823  		ir.OLITERAL,
   824  		ir.ONIL,
   825  		ir.OADD,
   826  		ir.OSUB,
   827  		ir.OOR,
   828  		ir.OXOR,
   829  		ir.OADDSTR,
   830  		ir.OADDR,
   831  		ir.OANDAND,
   832  		ir.OBYTES2STR,
   833  		ir.ORUNES2STR,
   834  		ir.OSTR2BYTES,
   835  		ir.OSTR2RUNES,
   836  		ir.OCAP,
   837  		ir.OCOMPLIT,
   838  		ir.OMAPLIT,
   839  		ir.OSTRUCTLIT,
   840  		ir.OARRAYLIT,
   841  		ir.OSLICELIT,
   842  		ir.OPTRLIT,
   843  		ir.OCONV,
   844  		ir.OCONVIFACE,
   845  		ir.OCONVNOP,
   846  		ir.ODOT,
   847  		ir.OEQ,
   848  		ir.ONE,
   849  		ir.OLT,
   850  		ir.OLE,
   851  		ir.OGT,
   852  		ir.OGE,
   853  		ir.OKEY,
   854  		ir.OSTRUCTKEY,
   855  		ir.OLEN,
   856  		ir.OMUL,
   857  		ir.OLSH,
   858  		ir.ORSH,
   859  		ir.OAND,
   860  		ir.OANDNOT,
   861  		ir.ONEW,
   862  		ir.ONOT,
   863  		ir.OBITNOT,
   864  		ir.OPLUS,
   865  		ir.ONEG,
   866  		ir.OOROR,
   867  		ir.OPAREN,
   868  		ir.ORUNESTR,
   869  		ir.OREAL,
   870  		ir.OIMAG,
   871  		ir.OCOMPLEX:
   872  		return false
   873  
   874  	// Only possible side effect is division by zero.
   875  	case ir.ODIV, ir.OMOD:
   876  		n := n.(*ir.BinaryExpr)
   877  		if n.Y.Op() != ir.OLITERAL || constant.Sign(n.Y.Val()) == 0 {
   878  			return true
   879  		}
   880  
   881  	// Only possible side effect is panic on invalid size,
   882  	// but many makechan and makemap use size zero, which is definitely OK.
   883  	case ir.OMAKECHAN, ir.OMAKEMAP:
   884  		n := n.(*ir.MakeExpr)
   885  		if !ir.IsConst(n.Len, constant.Int) || constant.Sign(n.Len.Val()) != 0 {
   886  			return true
   887  		}
   888  
   889  	// Only possible side effect is panic on invalid size.
   890  	// TODO(rsc): Merge with previous case (probably breaks toolstash -cmp).
   891  	case ir.OMAKESLICE, ir.OMAKESLICECOPY:
   892  		return true
   893  	}
   894  	return false
   895  }
   896  
   897  // AnySideEffects reports whether n contains any operations that could have observable side effects.
   898  func AnySideEffects(n ir.Node) bool {
   899  	return ir.Any(n, isSideEffect)
   900  }
   901  
   902  // mayModifyPkgVar reports whether expression n may modify any
   903  // package-scope variables declared within the current package.
   904  func mayModifyPkgVar(n ir.Node) bool {
   905  	// safeLHS reports whether the assigned-to variable lhs is either a
   906  	// local variable or a global from another package.
   907  	safeLHS := func(lhs ir.Node) bool {
   908  		outer := ir.OuterValue(lhs)
   909  		// "*p = ..." should be safe if p is a local variable.
   910  		// TODO: Should ir.OuterValue handle this?
   911  		for outer.Op() == ir.ODEREF {
   912  			outer = outer.(*ir.StarExpr).X
   913  		}
   914  		v, ok := outer.(*ir.Name)
   915  		return ok && v.Op() == ir.ONAME && !(v.Class == ir.PEXTERN && v.Sym().Pkg == types.LocalPkg)
   916  	}
   917  
   918  	return ir.Any(n, func(n ir.Node) bool {
   919  		switch n.Op() {
   920  		case ir.OCALLFUNC, ir.OCALLINTER:
   921  			return !ir.IsFuncPCIntrinsic(n.(*ir.CallExpr))
   922  
   923  		case ir.OAPPEND, ir.OCLEAR, ir.OCOPY:
   924  			return true // could mutate a global array
   925  
   926  		case ir.OASOP:
   927  			n := n.(*ir.AssignOpStmt)
   928  			if !safeLHS(n.X) {
   929  				return true
   930  			}
   931  
   932  		case ir.OAS:
   933  			n := n.(*ir.AssignStmt)
   934  			if !safeLHS(n.X) {
   935  				return true
   936  			}
   937  
   938  		case ir.OAS2, ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV:
   939  			n := n.(*ir.AssignListStmt)
   940  			for _, lhs := range n.Lhs {
   941  				if !safeLHS(lhs) {
   942  					return true
   943  				}
   944  			}
   945  		}
   946  
   947  		return false
   948  	})
   949  }
   950  
   951  // canRepeat reports whether executing n multiple times has the same effect as
   952  // assigning n to a single variable and using that variable multiple times.
   953  func canRepeat(n ir.Node) bool {
   954  	bad := func(n ir.Node) bool {
   955  		if isSideEffect(n) {
   956  			return true
   957  		}
   958  		switch n.Op() {
   959  		case ir.OMAKECHAN,
   960  			ir.OMAKEMAP,
   961  			ir.OMAKESLICE,
   962  			ir.OMAKESLICECOPY,
   963  			ir.OMAPLIT,
   964  			ir.ONEW,
   965  			ir.OPTRLIT,
   966  			ir.OSLICELIT,
   967  			ir.OSTR2BYTES,
   968  			ir.OSTR2RUNES:
   969  			return true
   970  		}
   971  		return false
   972  	}
   973  	return !ir.Any(n, bad)
   974  }
   975  
   976  func getlit(lit ir.Node) int {
   977  	if ir.IsSmallIntConst(lit) {
   978  		return int(ir.Int64Val(lit))
   979  	}
   980  	return -1
   981  }
   982  
   983  func isvaluelit(n ir.Node) bool {
   984  	return n.Op() == ir.OARRAYLIT || n.Op() == ir.OSTRUCTLIT
   985  }
   986  
   987  func subst(n ir.Node, m map[*ir.Name]ir.Node) (ir.Node, bool) {
   988  	valid := true
   989  	var edit func(ir.Node) ir.Node
   990  	edit = func(x ir.Node) ir.Node {
   991  		switch x.Op() {
   992  		case ir.ONAME:
   993  			x := x.(*ir.Name)
   994  			if v, ok := m[x]; ok {
   995  				return ir.DeepCopy(v.Pos(), v)
   996  			}
   997  			return x
   998  		case ir.ONONAME, ir.OLITERAL, ir.ONIL, ir.OTYPE:
   999  			return x
  1000  		}
  1001  		x = ir.Copy(x)
  1002  		ir.EditChildrenWithHidden(x, edit)
  1003  
  1004  		// TODO: handle more operations, see details discussion in go.dev/cl/466277.
  1005  		switch x.Op() {
  1006  		case ir.OCONV:
  1007  			x := x.(*ir.ConvExpr)
  1008  			if x.X.Op() == ir.OLITERAL {
  1009  				if x, ok := truncate(x.X, x.Type()); ok {
  1010  					return x
  1011  				}
  1012  				valid = false
  1013  				return x
  1014  			}
  1015  		case ir.OADDSTR:
  1016  			return addStr(x.(*ir.AddStringExpr))
  1017  		}
  1018  		return x
  1019  	}
  1020  	n = edit(n)
  1021  	return n, valid
  1022  }
  1023  
  1024  // truncate returns the result of force converting c to type t,
  1025  // truncating its value as needed, like a conversion of a variable.
  1026  // If the conversion is too difficult, truncate returns nil, false.
  1027  func truncate(c ir.Node, t *types.Type) (ir.Node, bool) {
  1028  	ct := c.Type()
  1029  	cv := c.Val()
  1030  	if ct.Kind() != t.Kind() {
  1031  		switch {
  1032  		default:
  1033  			// Note: float -> float/integer and complex -> complex are valid but subtle.
  1034  			// For example a float32(float64 1e300) evaluates to +Inf at runtime
  1035  			// and the compiler doesn't have any concept of +Inf, so that would
  1036  			// have to be left for runtime code evaluation.
  1037  			// For now
  1038  			return nil, false
  1039  
  1040  		case ct.IsInteger() && t.IsInteger():
  1041  			// truncate or sign extend
  1042  			bits := t.Size() * 8
  1043  			cv = constant.BinaryOp(cv, token.AND, constant.MakeUint64(1<<bits-1))
  1044  			if t.IsSigned() && constant.Compare(cv, token.GEQ, constant.MakeUint64(1<<(bits-1))) {
  1045  				cv = constant.BinaryOp(cv, token.OR, constant.MakeInt64(-1<<(bits-1)))
  1046  			}
  1047  		}
  1048  	}
  1049  	c = ir.NewConstExpr(cv, c)
  1050  	c.SetType(t)
  1051  	return c, true
  1052  }
  1053  
  1054  func addStr(n *ir.AddStringExpr) ir.Node {
  1055  	// Merge adjacent constants in the argument list.
  1056  	s := n.List
  1057  	need := 0
  1058  	for i := 0; i < len(s); i++ {
  1059  		if i == 0 || !ir.IsConst(s[i-1], constant.String) || !ir.IsConst(s[i], constant.String) {
  1060  			// Can't merge s[i] into s[i-1]; need a slot in the list.
  1061  			need++
  1062  		}
  1063  	}
  1064  	if need == len(s) {
  1065  		return n
  1066  	}
  1067  	if need == 1 {
  1068  		var strs []string
  1069  		for _, c := range s {
  1070  			strs = append(strs, ir.StringVal(c))
  1071  		}
  1072  		return ir.NewConstExpr(constant.MakeString(strings.Join(strs, "")), n)
  1073  	}
  1074  	newList := make([]ir.Node, 0, need)
  1075  	for i := 0; i < len(s); i++ {
  1076  		if ir.IsConst(s[i], constant.String) && i+1 < len(s) && ir.IsConst(s[i+1], constant.String) {
  1077  			// merge from i up to but not including i2
  1078  			var strs []string
  1079  			i2 := i
  1080  			for i2 < len(s) && ir.IsConst(s[i2], constant.String) {
  1081  				strs = append(strs, ir.StringVal(s[i2]))
  1082  				i2++
  1083  			}
  1084  
  1085  			newList = append(newList, ir.NewConstExpr(constant.MakeString(strings.Join(strs, "")), s[i]))
  1086  			i = i2 - 1
  1087  		} else {
  1088  			newList = append(newList, s[i])
  1089  		}
  1090  	}
  1091  
  1092  	nn := ir.Copy(n).(*ir.AddStringExpr)
  1093  	nn.List = newList
  1094  	return nn
  1095  }
  1096  
  1097  const wrapGlobalMapInitSizeThreshold = 20
  1098  
  1099  // tryWrapGlobalInit returns a new outlined function to contain global
  1100  // initializer statement n, if possible and worthwhile. Otherwise, it
  1101  // returns nil.
  1102  //
  1103  // Currently, it outlines map assignment statements with large,
  1104  // side-effect-free RHS expressions.
  1105  func tryWrapGlobalInit(n ir.Node) *ir.Func {
  1106  	// Look for "X = ..." where X has map type.
  1107  	// FIXME: might also be worth trying to look for cases where
  1108  	// the LHS is of interface type but RHS is map type.
  1109  	if n.Op() != ir.OAS {
  1110  		return nil
  1111  	}
  1112  	as := n.(*ir.AssignStmt)
  1113  	if ir.IsBlank(as.X) || as.X.Op() != ir.ONAME {
  1114  		return nil
  1115  	}
  1116  	nm := as.X.(*ir.Name)
  1117  	if !nm.Type().IsMap() {
  1118  		return nil
  1119  	}
  1120  
  1121  	// Determine size of RHS.
  1122  	rsiz := 0
  1123  	ir.Any(as.Y, func(n ir.Node) bool {
  1124  		rsiz++
  1125  		return false
  1126  	})
  1127  	if base.Debug.WrapGlobalMapDbg > 0 {
  1128  		fmt.Fprintf(os.Stderr, "=-= mapassign %s %v rhs size %d\n",
  1129  			base.Ctxt.Pkgpath, n, rsiz)
  1130  	}
  1131  
  1132  	// Reject smaller candidates if not in stress mode.
  1133  	if rsiz < wrapGlobalMapInitSizeThreshold && base.Debug.WrapGlobalMapCtl != 2 {
  1134  		if base.Debug.WrapGlobalMapDbg > 1 {
  1135  			fmt.Fprintf(os.Stderr, "=-= skipping %v size too small at %d\n",
  1136  				nm, rsiz)
  1137  		}
  1138  		return nil
  1139  	}
  1140  
  1141  	// Reject right hand sides with side effects.
  1142  	if AnySideEffects(as.Y) {
  1143  		if base.Debug.WrapGlobalMapDbg > 0 {
  1144  			fmt.Fprintf(os.Stderr, "=-= rejected %v due to side effects\n", nm)
  1145  		}
  1146  		return nil
  1147  	}
  1148  
  1149  	if base.Debug.WrapGlobalMapDbg > 1 {
  1150  		fmt.Fprintf(os.Stderr, "=-= committed for: %+v\n", n)
  1151  	}
  1152  
  1153  	// Create a new function that will (eventually) have this form:
  1154  	//
  1155  	//	func map.init.%d() {
  1156  	//		globmapvar = <map initialization>
  1157  	//	}
  1158  	//
  1159  	// Note: cmd/link expects the function name to contain "map.init".
  1160  	minitsym := typecheck.LookupNum("map.init.", mapinitgen)
  1161  	mapinitgen++
  1162  
  1163  	fn := ir.NewFunc(n.Pos(), n.Pos(), minitsym, types.NewSignature(nil, nil, nil))
  1164  	fn.SetInlinabilityChecked(true) // suppress inlining (which would defeat the point)
  1165  	typecheck.DeclFunc(fn)
  1166  	if base.Debug.WrapGlobalMapDbg > 0 {
  1167  		fmt.Fprintf(os.Stderr, "=-= generated func is %v\n", fn)
  1168  	}
  1169  
  1170  	// NB: we're relying on this phase being run before inlining;
  1171  	// if for some reason we need to move it after inlining, we'll
  1172  	// need code here that relocates or duplicates inline temps.
  1173  
  1174  	// Insert assignment into function body; mark body finished.
  1175  	fn.Body = []ir.Node{as}
  1176  	typecheck.FinishFuncBody()
  1177  
  1178  	if base.Debug.WrapGlobalMapDbg > 1 {
  1179  		fmt.Fprintf(os.Stderr, "=-= mapvar is %v\n", nm)
  1180  		fmt.Fprintf(os.Stderr, "=-= newfunc is %+v\n", fn)
  1181  	}
  1182  
  1183  	recordFuncForVar(nm, fn)
  1184  
  1185  	return fn
  1186  }
  1187  
  1188  // mapinitgen is a counter used to uniquify compiler-generated
  1189  // map init functions.
  1190  var mapinitgen int
  1191  
  1192  // AddKeepRelocations adds a dummy "R_KEEP" relocation from each
  1193  // global map variable V to its associated outlined init function.
  1194  // These relocation ensure that if the map var itself is determined to
  1195  // be reachable at link time, we also mark the init function as
  1196  // reachable.
  1197  func AddKeepRelocations() {
  1198  	if varToMapInit == nil {
  1199  		return
  1200  	}
  1201  	for k, v := range varToMapInit {
  1202  		// Add R_KEEP relocation from map to init function.
  1203  		fs := v.Linksym()
  1204  		if fs == nil {
  1205  			base.Fatalf("bad: func %v has no linksym", v)
  1206  		}
  1207  		vs := k.Linksym()
  1208  		if vs == nil {
  1209  			base.Fatalf("bad: mapvar %v has no linksym", k)
  1210  		}
  1211  		vs.AddRel(base.Ctxt, obj.Reloc{Type: objabi.R_KEEP, Sym: fs})
  1212  		if base.Debug.WrapGlobalMapDbg > 1 {
  1213  			fmt.Fprintf(os.Stderr, "=-= add R_KEEP relo from %s to %s\n",
  1214  				vs.Name, fs.Name)
  1215  		}
  1216  	}
  1217  	varToMapInit = nil
  1218  }
  1219  
  1220  // OutlineMapInits replaces global map initializers with outlined
  1221  // calls to separate "map init" functions (where possible and
  1222  // profitable), to facilitate better dead-code elimination by the
  1223  // linker.
  1224  func OutlineMapInits(fn *ir.Func) {
  1225  	if base.Debug.WrapGlobalMapCtl == 1 {
  1226  		return
  1227  	}
  1228  
  1229  	outlined := 0
  1230  	for i, stmt := range fn.Body {
  1231  		// Attempt to outline stmt. If successful, replace it with a call
  1232  		// to the returned wrapper function.
  1233  		if wrapperFn := tryWrapGlobalInit(stmt); wrapperFn != nil {
  1234  			ir.WithFunc(fn, func() {
  1235  				fn.Body[i] = typecheck.Call(stmt.Pos(), wrapperFn.Nname, nil, false)
  1236  			})
  1237  			outlined++
  1238  		}
  1239  	}
  1240  
  1241  	if base.Debug.WrapGlobalMapDbg > 1 {
  1242  		fmt.Fprintf(os.Stderr, "=-= outlined %v map initializations\n", outlined)
  1243  	}
  1244  }
  1245  

View as plain text