Source file src/cmd/compile/internal/staticdata/data.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 staticdata
     6  
     7  import (
     8  	"encoding/base64"
     9  	"fmt"
    10  	"go/constant"
    11  	"io"
    12  	"os"
    13  	"slices"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  
    18  	"cmd/compile/internal/base"
    19  	"cmd/compile/internal/ir"
    20  	"cmd/compile/internal/objw"
    21  	"cmd/compile/internal/types"
    22  	"cmd/internal/hash"
    23  	"cmd/internal/obj"
    24  	"cmd/internal/objabi"
    25  	"cmd/internal/src"
    26  )
    27  
    28  // InitAddrOffset writes the static name symbol lsym to n, it does not modify n.
    29  // It's the caller responsibility to make sure lsym is from ONAME/PEXTERN node.
    30  func InitAddrOffset(n *ir.Name, noff int64, lsym *obj.LSym, off int64) {
    31  	if n.Op() != ir.ONAME {
    32  		base.Fatalf("InitAddr n op %v", n.Op())
    33  	}
    34  	if n.Sym() == nil {
    35  		base.Fatalf("InitAddr nil n sym")
    36  	}
    37  	s := n.Linksym()
    38  	s.WriteAddr(base.Ctxt, noff, types.PtrSize, lsym, off)
    39  }
    40  
    41  // InitAddr is InitAddrOffset, with offset fixed to 0.
    42  func InitAddr(n *ir.Name, noff int64, lsym *obj.LSym) {
    43  	InitAddrOffset(n, noff, lsym, 0)
    44  }
    45  
    46  // InitSlice writes a static slice symbol {lsym, lencap, lencap} to n+noff, it does not modify n.
    47  // It's the caller responsibility to make sure lsym is from ONAME node.
    48  func InitSlice(n *ir.Name, noff int64, lsym *obj.LSym, lencap int64) {
    49  	s := n.Linksym()
    50  	s.WriteAddr(base.Ctxt, noff, types.PtrSize, lsym, 0)
    51  	s.WriteInt(base.Ctxt, noff+types.SliceLenOffset, types.PtrSize, lencap)
    52  	s.WriteInt(base.Ctxt, noff+types.SliceCapOffset, types.PtrSize, lencap)
    53  }
    54  
    55  func InitSliceBytes(nam *ir.Name, off int64, s string) {
    56  	if nam.Op() != ir.ONAME {
    57  		base.Fatalf("InitSliceBytes %v", nam)
    58  	}
    59  	InitSlice(nam, off, slicedata(nam.Pos(), s), int64(len(s)))
    60  }
    61  
    62  const (
    63  	stringSymPrefix  = "go:string."
    64  	stringSymPattern = ".gostring.%d.%s"
    65  )
    66  
    67  // shortHashString converts the hash to a string for use with stringSymPattern.
    68  // We cut it to 16 bytes and then base64-encode to make it even smaller.
    69  func shortHashString(hash []byte) string {
    70  	return base64.StdEncoding.EncodeToString(hash[:16])
    71  }
    72  
    73  // StringSym returns a symbol containing the string s.
    74  // The symbol contains the string data, not a string header.
    75  func StringSym(pos src.XPos, s string) (data *obj.LSym) {
    76  	var symname string
    77  	if len(s) > 100 {
    78  		// Huge strings are hashed to avoid long names in object files.
    79  		// Indulge in some paranoia by writing the length of s, too,
    80  		// as protection against length extension attacks.
    81  		// Same pattern is known to fileStringSym below.
    82  		h := hash.New32()
    83  		io.WriteString(h, s)
    84  		symname = fmt.Sprintf(stringSymPattern, len(s), shortHashString(h.Sum(nil)))
    85  	} else {
    86  		// Small strings get named directly by their contents.
    87  		symname = strconv.Quote(s)
    88  	}
    89  
    90  	symdata := base.Ctxt.Lookup(stringSymPrefix + symname)
    91  	if !symdata.OnList() {
    92  		off := dstringdata(symdata, 0, s, pos, "string")
    93  		objw.Global(symdata, int32(off), obj.DUPOK|obj.RODATA|obj.LOCAL)
    94  		symdata.Set(obj.AttrContentAddressable, true)
    95  	}
    96  
    97  	return symdata
    98  }
    99  
   100  // StringSymNoCommon is like StringSym, but produces a symbol that is not content-
   101  // addressable. This symbol is not supposed to appear in the final binary, it is
   102  // only used to pass string arguments to the linker like R_USENAMEDMETHOD does.
   103  func StringSymNoCommon(s string) (data *obj.LSym) {
   104  	var nameSym obj.LSym
   105  	nameSym.WriteString(base.Ctxt, 0, len(s), s)
   106  	objw.Global(&nameSym, int32(len(s)), obj.RODATA)
   107  	return &nameSym
   108  }
   109  
   110  // maxFileSize is the maximum file size permitted by the linker
   111  // (see issue #9862).
   112  const maxFileSize = int64(2e9)
   113  
   114  // fileStringSym returns a symbol for the contents and the size of file.
   115  // If readonly is true, the symbol shares storage with any literal string
   116  // or other file with the same content and is placed in a read-only section.
   117  // If readonly is false, the symbol is a read-write copy separate from any other,
   118  // for use as the backing store of a []byte.
   119  // The content hash of file is copied into hashBytes. (If hash is nil, nothing is copied.)
   120  // The returned symbol contains the data itself, not a string header.
   121  func fileStringSym(pos src.XPos, file string, readonly bool, hashBytes []byte) (*obj.LSym, int64, error) {
   122  	f, err := os.Open(file)
   123  	if err != nil {
   124  		return nil, 0, err
   125  	}
   126  	defer f.Close()
   127  	info, err := f.Stat()
   128  	if err != nil {
   129  		return nil, 0, err
   130  	}
   131  	if !info.Mode().IsRegular() {
   132  		return nil, 0, fmt.Errorf("not a regular file")
   133  	}
   134  	size := info.Size()
   135  	if size <= 1*1024 {
   136  		data, err := io.ReadAll(f)
   137  		if err != nil {
   138  			return nil, 0, err
   139  		}
   140  		if int64(len(data)) != size {
   141  			return nil, 0, fmt.Errorf("file changed between reads")
   142  		}
   143  		var sym *obj.LSym
   144  		if readonly {
   145  			sym = StringSym(pos, string(data))
   146  		} else {
   147  			sym = slicedata(pos, string(data))
   148  		}
   149  		if len(hashBytes) > 0 {
   150  			sum := hash.Sum32(data)
   151  			copy(hashBytes, sum[:])
   152  		}
   153  		return sym, size, nil
   154  	}
   155  	if size > maxFileSize {
   156  		// ggloblsym takes an int32,
   157  		// and probably the rest of the toolchain
   158  		// can't handle such big symbols either.
   159  		// See golang.org/issue/9862.
   160  		return nil, 0, fmt.Errorf("file too large (%d bytes > %d bytes)", size, maxFileSize)
   161  	}
   162  
   163  	// File is too big to read and keep in memory.
   164  	// Compute hashBytes if needed for read-only content hashing or if the caller wants it.
   165  	var sum []byte
   166  	if readonly || len(hashBytes) > 0 {
   167  		h := hash.New32()
   168  		n, err := io.Copy(h, f)
   169  		if err != nil {
   170  			return nil, 0, err
   171  		}
   172  		if n != size {
   173  			return nil, 0, fmt.Errorf("file changed between reads")
   174  		}
   175  		sum = h.Sum(nil)
   176  		copy(hashBytes, sum)
   177  	}
   178  
   179  	var symdata *obj.LSym
   180  	if readonly {
   181  		symname := fmt.Sprintf(stringSymPattern, size, shortHashString(sum))
   182  		symdata = base.Ctxt.Lookup(stringSymPrefix + symname)
   183  		if !symdata.OnList() {
   184  			info := symdata.NewFileInfo()
   185  			info.Name = file
   186  			info.Size = size
   187  			objw.Global(symdata, int32(size), obj.DUPOK|obj.RODATA|obj.LOCAL)
   188  			// Note: AttrContentAddressable cannot be set here,
   189  			// because the content-addressable-handling code
   190  			// does not know about file symbols.
   191  		}
   192  	} else {
   193  		// Emit a zero-length data symbol
   194  		// and then fix up length and content to use file.
   195  		symdata = slicedata(pos, "")
   196  		symdata.Size = size
   197  		symdata.Type = objabi.SNOPTRDATA
   198  		info := symdata.NewFileInfo()
   199  		info.Name = file
   200  		info.Size = size
   201  	}
   202  
   203  	return symdata, size, nil
   204  }
   205  
   206  var slicedataGen int
   207  
   208  func slicedata(pos src.XPos, s string) *obj.LSym {
   209  	slicedataGen++
   210  	symname := fmt.Sprintf(".gobytes.%d", slicedataGen)
   211  	lsym := types.LocalPkg.Lookup(symname).LinksymABI(obj.ABI0)
   212  	off := dstringdata(lsym, 0, s, pos, "slice")
   213  	objw.Global(lsym, int32(off), obj.NOPTR|obj.LOCAL)
   214  
   215  	return lsym
   216  }
   217  
   218  func dstringdata(s *obj.LSym, off int, t string, pos src.XPos, what string) int {
   219  	// Objects that are too large will cause the data section to overflow right away,
   220  	// causing a cryptic error message by the linker. Check for oversize objects here
   221  	// and provide a useful error message instead.
   222  	if int64(len(t)) > 2e9 {
   223  		base.ErrorfAt(pos, 0, "%v with length %v is too big", what, len(t))
   224  		return 0
   225  	}
   226  
   227  	s.WriteString(base.Ctxt, int64(off), len(t), t)
   228  	return off + len(t)
   229  }
   230  
   231  var (
   232  	funcsymsmu sync.Mutex // protects funcsyms and associated package lookups (see func funcsym)
   233  	funcsyms   []*ir.Name // functions that need function value symbols
   234  )
   235  
   236  // FuncLinksym returns n·f, the function value symbol for n.
   237  func FuncLinksym(n *ir.Name) *obj.LSym {
   238  	if n.Op() != ir.ONAME || n.Class != ir.PFUNC {
   239  		base.Fatalf("expected func name: %v", n)
   240  	}
   241  	s := n.Sym()
   242  
   243  	// funcsymsmu here serves to protect not just mutations of funcsyms (below),
   244  	// but also the package lookup of the func sym name,
   245  	// since this function gets called concurrently from the backend.
   246  	// There are no other concurrent package lookups in the backend,
   247  	// except for the types package, which is protected separately.
   248  	// Reusing funcsymsmu to also cover this package lookup
   249  	// avoids a general, broader, expensive package lookup mutex.
   250  	funcsymsmu.Lock()
   251  	sf, existed := s.Pkg.LookupOK(ir.FuncSymName(s))
   252  	if !existed {
   253  		funcsyms = append(funcsyms, n)
   254  	}
   255  	funcsymsmu.Unlock()
   256  
   257  	return sf.Linksym()
   258  }
   259  
   260  func GlobalLinksym(n *ir.Name) *obj.LSym {
   261  	if n.Op() != ir.ONAME || n.Class != ir.PEXTERN {
   262  		base.Fatalf("expected global variable: %v", n)
   263  	}
   264  	return n.Linksym()
   265  }
   266  
   267  func WriteFuncSyms() {
   268  	slices.SortFunc(funcsyms, func(a, b *ir.Name) int {
   269  		return strings.Compare(a.Linksym().Name, b.Linksym().Name)
   270  	})
   271  	for _, nam := range funcsyms {
   272  		s := nam.Sym()
   273  		sf := s.Pkg.Lookup(ir.FuncSymName(s)).Linksym()
   274  
   275  		// While compiling package runtime, we might try to create
   276  		// funcsyms for functions from both types.LocalPkg and
   277  		// ir.Pkgs.Runtime.
   278  		if base.Flag.CompilingRuntime && sf.OnList() {
   279  			continue
   280  		}
   281  
   282  		// Function values must always reference ABIInternal
   283  		// entry points.
   284  		target := s.Linksym()
   285  		if target.ABI() != obj.ABIInternal {
   286  			base.Fatalf("expected ABIInternal: %v has %v", target, target.ABI())
   287  		}
   288  		objw.SymPtr(sf, 0, target, 0)
   289  		objw.Global(sf, int32(types.PtrSize), obj.DUPOK|obj.RODATA)
   290  	}
   291  }
   292  
   293  // InitConst writes the static literal c to n.
   294  // Neither n nor c is modified.
   295  func InitConst(n *ir.Name, noff int64, c ir.Node, wid int) {
   296  	if n.Op() != ir.ONAME {
   297  		base.Fatalf("InitConst n op %v", n.Op())
   298  	}
   299  	if n.Sym() == nil {
   300  		base.Fatalf("InitConst nil n sym")
   301  	}
   302  	if c.Op() == ir.ONIL {
   303  		return
   304  	}
   305  	if c.Op() != ir.OLITERAL {
   306  		base.Fatalf("InitConst c op %v", c.Op())
   307  	}
   308  	s := n.Linksym()
   309  	switch u := c.Val(); u.Kind() {
   310  	case constant.Bool:
   311  		i := int64(obj.Bool2int(constant.BoolVal(u)))
   312  		s.WriteInt(base.Ctxt, noff, wid, i)
   313  
   314  	case constant.Int:
   315  		s.WriteInt(base.Ctxt, noff, wid, ir.IntVal(c.Type(), u))
   316  
   317  	case constant.Float:
   318  		f, _ := constant.Float64Val(u)
   319  		switch c.Type().Kind() {
   320  		case types.TFLOAT32:
   321  			s.WriteFloat32(base.Ctxt, noff, float32(f))
   322  		case types.TFLOAT64:
   323  			s.WriteFloat64(base.Ctxt, noff, f)
   324  		}
   325  
   326  	case constant.Complex:
   327  		re, _ := constant.Float64Val(constant.Real(u))
   328  		im, _ := constant.Float64Val(constant.Imag(u))
   329  		switch c.Type().Kind() {
   330  		case types.TCOMPLEX64:
   331  			s.WriteFloat32(base.Ctxt, noff, float32(re))
   332  			s.WriteFloat32(base.Ctxt, noff+4, float32(im))
   333  		case types.TCOMPLEX128:
   334  			s.WriteFloat64(base.Ctxt, noff, re)
   335  			s.WriteFloat64(base.Ctxt, noff+8, im)
   336  		}
   337  
   338  	case constant.String:
   339  		i := constant.StringVal(u)
   340  		symdata := StringSym(n.Pos(), i)
   341  		s.WriteAddr(base.Ctxt, noff, types.PtrSize, symdata, 0)
   342  		s.WriteInt(base.Ctxt, noff+int64(types.PtrSize), types.PtrSize, int64(len(i)))
   343  
   344  	default:
   345  		base.Fatalf("InitConst unhandled OLITERAL %v", c)
   346  	}
   347  }
   348  

View as plain text