mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
CL 40693 added concurrent backend compilation support, and used it for user-provided functions. Autogenerated functions were still compiled serially. This CL brings them into the fold. As of this CL, when requested, no functions are compiled serially. There generally aren't many autogenerated functions. When there are, this CL can help a lot, because autogenerated functions are usually short. Many short functions is the best case scenario for concurrent compilation; see CL 41192. One example of such a package comes from Dave Cheney's benchjuju: github.com/juju/govmomi/vim25/types. It has thousands of autogenerated functions. This CL improves performance on the entire benchmark by around a second on my machine at c=8, or about ~5%. Updates #15756 Change-Id: Ia21e302b2469a9ed743df02244ec7ebde55b32f3 Reviewed-on: https://go-review.googlesource.com/41503 Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Robert Griesemer <gri@golang.org>
480 lines
11 KiB
Go
480 lines
11 KiB
Go
// 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 gc
|
|
|
|
import (
|
|
"cmd/compile/internal/types"
|
|
"cmd/internal/bio"
|
|
"cmd/internal/obj"
|
|
"cmd/internal/objabi"
|
|
"cmd/internal/src"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
)
|
|
|
|
// architecture-independent object file output
|
|
const ArhdrSize = 60
|
|
|
|
func formathdr(arhdr []byte, name string, size int64) {
|
|
copy(arhdr[:], fmt.Sprintf("%-16s%-12d%-6d%-6d%-8o%-10d`\n", name, 0, 0, 0, 0644, size))
|
|
}
|
|
|
|
// These modes say which kind of object file to generate.
|
|
// The default use of the toolchain is to set both bits,
|
|
// generating a combined compiler+linker object, one that
|
|
// serves to describe the package to both the compiler and the linker.
|
|
// In fact the compiler and linker read nearly disjoint sections of
|
|
// that file, though, so in a distributed build setting it can be more
|
|
// efficient to split the output into two files, supplying the compiler
|
|
// object only to future compilations and the linker object only to
|
|
// future links.
|
|
//
|
|
// By default a combined object is written, but if -linkobj is specified
|
|
// on the command line then the default -o output is a compiler object
|
|
// and the -linkobj output is a linker object.
|
|
const (
|
|
modeCompilerObj = 1 << iota
|
|
modeLinkerObj
|
|
)
|
|
|
|
func dumpobj() {
|
|
if !dolinkobj {
|
|
dumpobj1(outfile, modeCompilerObj)
|
|
return
|
|
}
|
|
if linkobj == "" {
|
|
dumpobj1(outfile, modeCompilerObj|modeLinkerObj)
|
|
return
|
|
}
|
|
dumpobj1(outfile, modeCompilerObj)
|
|
dumpobj1(linkobj, modeLinkerObj)
|
|
}
|
|
|
|
func dumpobj1(outfile string, mode int) {
|
|
bout, err := bio.Create(outfile)
|
|
if err != nil {
|
|
flusherrors()
|
|
fmt.Printf("can't create %s: %v\n", outfile, err)
|
|
errorexit()
|
|
}
|
|
defer bout.Close()
|
|
bout.WriteString("!<arch>\n")
|
|
|
|
if mode&modeCompilerObj != 0 {
|
|
start := startArchiveEntry(bout)
|
|
dumpCompilerObj(bout)
|
|
finishArchiveEntry(bout, start, "__.PKGDEF")
|
|
}
|
|
if mode&modeLinkerObj != 0 {
|
|
start := startArchiveEntry(bout)
|
|
dumpLinkerObj(bout)
|
|
finishArchiveEntry(bout, start, "_go_.o")
|
|
}
|
|
}
|
|
|
|
func printObjHeader(bout *bio.Writer) {
|
|
fmt.Fprintf(bout, "go object %s %s %s %s\n", objabi.GOOS, objabi.GOARCH, objabi.Version, objabi.Expstring())
|
|
if buildid != "" {
|
|
fmt.Fprintf(bout, "build id %q\n", buildid)
|
|
}
|
|
if localpkg.Name == "main" {
|
|
fmt.Fprintf(bout, "main\n")
|
|
}
|
|
if safemode {
|
|
fmt.Fprintf(bout, "safe\n")
|
|
} else {
|
|
fmt.Fprintf(bout, "----\n") // room for some other tool to write "safe"
|
|
}
|
|
fmt.Fprintf(bout, "\n") // header ends with blank line
|
|
}
|
|
|
|
func startArchiveEntry(bout *bio.Writer) int64 {
|
|
var arhdr [ArhdrSize]byte
|
|
bout.Write(arhdr[:])
|
|
return bout.Offset()
|
|
}
|
|
|
|
func finishArchiveEntry(bout *bio.Writer, start int64, name string) {
|
|
bout.Flush()
|
|
size := bout.Offset() - start
|
|
if size&1 != 0 {
|
|
bout.WriteByte(0)
|
|
}
|
|
bout.Seek(start-ArhdrSize, 0)
|
|
|
|
var arhdr [ArhdrSize]byte
|
|
formathdr(arhdr[:], name, size)
|
|
bout.Write(arhdr[:])
|
|
bout.Flush()
|
|
bout.Seek(start+size+(size&1), 0)
|
|
}
|
|
|
|
func dumpCompilerObj(bout *bio.Writer) {
|
|
printObjHeader(bout)
|
|
dumpexport(bout)
|
|
}
|
|
|
|
func dumpLinkerObj(bout *bio.Writer) {
|
|
printObjHeader(bout)
|
|
|
|
if pragcgobuf != "" {
|
|
// write empty export section; must be before cgo section
|
|
fmt.Fprintf(bout, "\n$$\n\n$$\n\n")
|
|
fmt.Fprintf(bout, "\n$$ // cgo\n")
|
|
fmt.Fprintf(bout, "%s\n$$\n\n", pragcgobuf)
|
|
}
|
|
|
|
fmt.Fprintf(bout, "\n!\n")
|
|
|
|
externs := len(externdcl)
|
|
|
|
dumpglobls()
|
|
addptabs()
|
|
addsignats(externdcl)
|
|
dumpsignats()
|
|
dumptabs()
|
|
dumpimportstrings()
|
|
dumpbasictypes()
|
|
|
|
// The first call to dumpsignats can generate functions,
|
|
// like method wrappers and hash and equality routines.
|
|
compileFunctions()
|
|
|
|
// Process any new signats added during compilation.
|
|
// No need to loop here; signats from compiling the generated
|
|
// functions should not themselves generate new functions.
|
|
// If they do, we'll know about it; the sanity check of
|
|
// len(compilequeue) in gc.Main will fail.
|
|
dumpsignats()
|
|
|
|
// Dump extra globals.
|
|
tmp := externdcl
|
|
|
|
if externdcl != nil {
|
|
externdcl = externdcl[externs:]
|
|
}
|
|
dumpglobls()
|
|
externdcl = tmp
|
|
|
|
if zerosize > 0 {
|
|
zero := mappkg.Lookup("zero")
|
|
ggloblsym(zero.Linksym(), int32(zerosize), obj.DUPOK|obj.RODATA)
|
|
}
|
|
|
|
addGCLocals()
|
|
|
|
obj.WriteObjFile(Ctxt, bout.Writer)
|
|
}
|
|
|
|
func addptabs() {
|
|
if !Ctxt.Flag_dynlink || localpkg.Name != "main" {
|
|
return
|
|
}
|
|
for _, exportn := range exportlist {
|
|
s := exportn.Sym
|
|
n := asNode(s.Def)
|
|
if n == nil {
|
|
continue
|
|
}
|
|
if n.Op != ONAME {
|
|
continue
|
|
}
|
|
if !exportname(s.Name) {
|
|
continue
|
|
}
|
|
if s.Pkg.Name != "main" {
|
|
continue
|
|
}
|
|
if n.Type.Etype == TFUNC && n.Class() == PFUNC {
|
|
// function
|
|
ptabs = append(ptabs, ptabEntry{s: s, t: asNode(s.Def).Type})
|
|
} else {
|
|
// variable
|
|
ptabs = append(ptabs, ptabEntry{s: s, t: types.NewPtr(asNode(s.Def).Type)})
|
|
}
|
|
}
|
|
}
|
|
|
|
func dumpGlobal(n *Node) {
|
|
if n.Type == nil {
|
|
Fatalf("external %v nil type\n", n)
|
|
}
|
|
if n.Class() == PFUNC {
|
|
return
|
|
}
|
|
if n.Sym.Pkg != localpkg {
|
|
return
|
|
}
|
|
dowidth(n.Type)
|
|
ggloblnod(n)
|
|
}
|
|
|
|
func dumpGlobalConst(n *Node) {
|
|
// only export typed constants
|
|
t := n.Type
|
|
if t == nil {
|
|
return
|
|
}
|
|
if n.Sym.Pkg != localpkg {
|
|
return
|
|
}
|
|
// only export integer constants for now
|
|
switch t.Etype {
|
|
case TINT8:
|
|
case TINT16:
|
|
case TINT32:
|
|
case TINT64:
|
|
case TINT:
|
|
case TUINT8:
|
|
case TUINT16:
|
|
case TUINT32:
|
|
case TUINT64:
|
|
case TUINT:
|
|
case TUINTPTR:
|
|
// ok
|
|
case TIDEAL:
|
|
if !Isconst(n, CTINT) {
|
|
return
|
|
}
|
|
x := n.Val().U.(*Mpint)
|
|
if x.Cmp(minintval[TINT]) < 0 || x.Cmp(maxintval[TINT]) > 0 {
|
|
return
|
|
}
|
|
// Ideal integers we export as int (if they fit).
|
|
t = types.Types[TINT]
|
|
default:
|
|
return
|
|
}
|
|
Ctxt.DwarfIntConst(myimportpath, n.Sym.Name, typesymname(t), n.Int64())
|
|
}
|
|
|
|
func dumpglobls() {
|
|
// add globals
|
|
for _, n := range externdcl {
|
|
switch n.Op {
|
|
case ONAME:
|
|
dumpGlobal(n)
|
|
case OLITERAL:
|
|
dumpGlobalConst(n)
|
|
}
|
|
}
|
|
|
|
obj.SortSlice(funcsyms, func(i, j int) bool {
|
|
return funcsyms[i].LinksymName() < funcsyms[j].LinksymName()
|
|
})
|
|
for _, s := range funcsyms {
|
|
sf := s.Pkg.Lookup(funcsymname(s)).Linksym()
|
|
dsymptr(sf, 0, s.Linksym(), 0)
|
|
ggloblsym(sf, int32(Widthptr), obj.DUPOK|obj.RODATA)
|
|
}
|
|
|
|
// Do not reprocess funcsyms on next dumpglobls call.
|
|
funcsyms = nil
|
|
}
|
|
|
|
// addGCLocals adds gcargs and gclocals symbols to Ctxt.Data.
|
|
// It takes care not to add any duplicates.
|
|
// Though the object file format handles duplicates efficiently,
|
|
// storing only a single copy of the data,
|
|
// failure to remove these duplicates adds a few percent to object file size.
|
|
func addGCLocals() {
|
|
seen := make(map[string]bool)
|
|
for _, s := range Ctxt.Text {
|
|
if s.Func == nil {
|
|
continue
|
|
}
|
|
for _, gcsym := range []*obj.LSym{&s.Func.GCArgs, &s.Func.GCLocals} {
|
|
if seen[gcsym.Name] {
|
|
continue
|
|
}
|
|
Ctxt.Data = append(Ctxt.Data, gcsym)
|
|
seen[gcsym.Name] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
func duintxx(s *obj.LSym, off int, v uint64, wid int) int {
|
|
if off&(wid-1) != 0 {
|
|
Fatalf("duintxxLSym: misaligned: v=%d wid=%d off=%d", v, wid, off)
|
|
}
|
|
s.WriteInt(Ctxt, int64(off), wid, int64(v))
|
|
return off + wid
|
|
}
|
|
|
|
func duint8(s *obj.LSym, off int, v uint8) int {
|
|
return duintxx(s, off, uint64(v), 1)
|
|
}
|
|
|
|
func duint16(s *obj.LSym, off int, v uint16) int {
|
|
return duintxx(s, off, uint64(v), 2)
|
|
}
|
|
|
|
func duint32(s *obj.LSym, off int, v uint32) int {
|
|
return duintxx(s, off, uint64(v), 4)
|
|
}
|
|
|
|
func duintptr(s *obj.LSym, off int, v uint64) int {
|
|
return duintxx(s, off, v, Widthptr)
|
|
}
|
|
|
|
func dbvec(s *obj.LSym, off int, bv bvec) int {
|
|
// Runtime reads the bitmaps as byte arrays. Oblige.
|
|
for j := 0; int32(j) < bv.n; j += 8 {
|
|
word := bv.b[j/32]
|
|
off = duint8(s, off, uint8(word>>(uint(j)%32)))
|
|
}
|
|
return off
|
|
}
|
|
|
|
func stringsym(pos src.XPos, s string) (data *obj.LSym) {
|
|
var symname string
|
|
if len(s) > 100 {
|
|
// Huge strings are hashed to avoid long names in object files.
|
|
// Indulge in some paranoia by writing the length of s, too,
|
|
// as protection against length extension attacks.
|
|
h := sha256.New()
|
|
io.WriteString(h, s)
|
|
symname = fmt.Sprintf(".gostring.%d.%x", len(s), h.Sum(nil))
|
|
} else {
|
|
// Small strings get named directly by their contents.
|
|
symname = strconv.Quote(s)
|
|
}
|
|
|
|
const prefix = "go.string."
|
|
symdataname := prefix + symname
|
|
|
|
symdata := Ctxt.Lookup(symdataname)
|
|
|
|
if !symdata.SeenGlobl() {
|
|
// string data
|
|
off := dsname(symdata, 0, s, pos, "string")
|
|
ggloblsym(symdata, int32(off), obj.DUPOK|obj.RODATA|obj.LOCAL)
|
|
}
|
|
|
|
return symdata
|
|
}
|
|
|
|
var slicebytes_gen int
|
|
|
|
func slicebytes(nam *Node, s string, len int) {
|
|
slicebytes_gen++
|
|
symname := fmt.Sprintf(".gobytes.%d", slicebytes_gen)
|
|
sym := localpkg.Lookup(symname)
|
|
sym.Def = asTypesNode(newname(sym))
|
|
|
|
lsym := sym.Linksym()
|
|
off := dsname(lsym, 0, s, nam.Pos, "slice")
|
|
ggloblsym(lsym, int32(off), obj.NOPTR|obj.LOCAL)
|
|
|
|
if nam.Op != ONAME {
|
|
Fatalf("slicebytes %v", nam)
|
|
}
|
|
nsym := nam.Sym.Linksym()
|
|
off = int(nam.Xoffset)
|
|
off = dsymptr(nsym, off, lsym, 0)
|
|
off = duintptr(nsym, off, uint64(len))
|
|
duintptr(nsym, off, uint64(len))
|
|
}
|
|
|
|
func dsname(s *obj.LSym, off int, t string, pos src.XPos, what string) int {
|
|
// Objects that are too large will cause the data section to overflow right away,
|
|
// causing a cryptic error message by the linker. Check for oversize objects here
|
|
// and provide a useful error message instead.
|
|
if int64(len(t)) > 2e9 {
|
|
yyerrorl(pos, "%v with length %v is too big", what, len(t))
|
|
return 0
|
|
}
|
|
|
|
s.WriteString(Ctxt, int64(off), len(t), t)
|
|
return off + len(t)
|
|
}
|
|
|
|
func dsymptr(s *obj.LSym, off int, x *obj.LSym, xoff int) int {
|
|
off = int(Rnd(int64(off), int64(Widthptr)))
|
|
s.WriteAddr(Ctxt, int64(off), Widthptr, x, int64(xoff))
|
|
off += Widthptr
|
|
return off
|
|
}
|
|
|
|
func dsymptrOff(s *obj.LSym, off int, x *obj.LSym) int {
|
|
s.WriteOff(Ctxt, int64(off), x, 0)
|
|
off += 4
|
|
return off
|
|
}
|
|
|
|
func dsymptrWeakOff(s *obj.LSym, off int, x *obj.LSym) int {
|
|
s.WriteWeakOff(Ctxt, int64(off), x, 0)
|
|
off += 4
|
|
return off
|
|
}
|
|
|
|
func gdata(nam *Node, nr *Node, wid int) {
|
|
if nam.Op != ONAME {
|
|
Fatalf("gdata nam op %v", nam.Op)
|
|
}
|
|
if nam.Sym == nil {
|
|
Fatalf("gdata nil nam sym")
|
|
}
|
|
s := nam.Sym.Linksym()
|
|
|
|
switch nr.Op {
|
|
case OLITERAL:
|
|
switch u := nr.Val().U.(type) {
|
|
case bool:
|
|
i := int64(obj.Bool2int(u))
|
|
s.WriteInt(Ctxt, nam.Xoffset, wid, i)
|
|
|
|
case *Mpint:
|
|
s.WriteInt(Ctxt, nam.Xoffset, wid, u.Int64())
|
|
|
|
case *Mpflt:
|
|
f := u.Float64()
|
|
switch nam.Type.Etype {
|
|
case TFLOAT32:
|
|
s.WriteFloat32(Ctxt, nam.Xoffset, float32(f))
|
|
case TFLOAT64:
|
|
s.WriteFloat64(Ctxt, nam.Xoffset, f)
|
|
}
|
|
|
|
case *Mpcplx:
|
|
r := u.Real.Float64()
|
|
i := u.Imag.Float64()
|
|
switch nam.Type.Etype {
|
|
case TCOMPLEX64:
|
|
s.WriteFloat32(Ctxt, nam.Xoffset, float32(r))
|
|
s.WriteFloat32(Ctxt, nam.Xoffset+4, float32(i))
|
|
case TCOMPLEX128:
|
|
s.WriteFloat64(Ctxt, nam.Xoffset, r)
|
|
s.WriteFloat64(Ctxt, nam.Xoffset+8, i)
|
|
}
|
|
|
|
case string:
|
|
symdata := stringsym(nam.Pos, u)
|
|
s.WriteAddr(Ctxt, nam.Xoffset, Widthptr, symdata, 0)
|
|
s.WriteInt(Ctxt, nam.Xoffset+int64(Widthptr), Widthptr, int64(len(u)))
|
|
|
|
default:
|
|
Fatalf("gdata unhandled OLITERAL %v", nr)
|
|
}
|
|
|
|
case OADDR:
|
|
if nr.Left.Op != ONAME {
|
|
Fatalf("gdata ADDR left op %v", nr.Left.Op)
|
|
}
|
|
to := nr.Left
|
|
s.WriteAddr(Ctxt, nam.Xoffset, wid, to.Sym.Linksym(), to.Xoffset)
|
|
|
|
case ONAME:
|
|
if nr.Class() != PFUNC {
|
|
Fatalf("gdata NAME not PFUNC %d", nr.Class())
|
|
}
|
|
s.WriteAddr(Ctxt, nam.Xoffset, wid, funcsym(nr.Sym).Linksym(), nr.Xoffset)
|
|
|
|
default:
|
|
Fatalf("gdata unhandled op %v %v\n", nr, nr.Op)
|
|
}
|
|
}
|