go/src/cmd/link/internal/ld/lib.go

1947 lines
47 KiB
Go
Raw Normal View History

// Inferno utils/8l/asm.c
// http://code.google.com/p/inferno-os/source/browse/utils/8l/asm.c
//
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
// Portions Copyright © 1997-1999 Vita Nuova Limited
// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
// Portions Copyright © 2004,2006 Bruce Ellis
// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
// Portions Copyright © 2009 The Go Authors. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package ld
import (
"bufio"
"bytes"
"cmd/internal/obj"
"crypto/sha1"
"debug/elf"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
)
// Data layout and relocation.
// Derived from Inferno utils/6l/l.h
// http://code.google.com/p/inferno-os/source/browse/utils/6l/l.h
//
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
// Portions Copyright © 1997-1999 Vita Nuova Limited
// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
// Portions Copyright © 2004,2006 Bruce Ellis
// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
// Portions Copyright © 2009 The Go Authors. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
type Arch struct {
Thechar int
Ptrsize int
Intsize int
Regsize int
Funcalign int
Maxalign int
Minlc int
Dwarfregsp int
Dwarfreglr int
Linuxdynld string
Freebsddynld string
Netbsddynld string
Openbsddynld string
Dragonflydynld string
Solarisdynld string
Adddynrel func(*LSym, *Reloc)
Archinit func()
Archreloc func(*Reloc, *LSym, *int64) int
Archrelocvariant func(*Reloc, *LSym, int64) int64
Asmb func()
Elfreloc1 func(*Reloc, int64) int
Elfsetupplt func()
Gentext func()
Machoreloc1 func(*Reloc, int64) int
PEreloc1 func(*Reloc, int64) bool
Lput func(uint32)
Wput func(uint16)
Vput func(uint64)
}
type Rpath struct {
set bool
val string
}
func (r *Rpath) Set(val string) error {
r.set = true
r.val = val
return nil
}
func (r *Rpath) String() string {
return r.val
}
var (
Thearch Arch
datap *LSym
Debug [128]int
Lcsize int32
rpath Rpath
Spsize int32
Symsize int32
)
// Terrible but standard terminology.
// A segment describes a block of file to load into memory.
// A section further describes the pieces of that block for
// use in debuggers and such.
const (
MAXIO = 8192
MINFUNC = 16 // minimum size for a function
)
type Segment struct {
Rwx uint8 // permission as usual unix bits (5 = r-x etc)
Vaddr uint64 // virtual address
Length uint64 // length in memory
Fileoff uint64 // file offset
Filelen uint64 // length on disk
Sect *Section
}
type Section struct {
Rwx uint8
Extnum int16
Align int32
Name string
Vaddr uint64
Length uint64
Next *Section
Seg *Segment
Elfsect interface{}
Reloff uint64
Rellen uint64
}
// DynlinkingGo returns whether we are producing Go code that can live
// in separate shared libraries linked together at runtime.
func DynlinkingGo() bool {
return Buildmode == BuildmodeShared || Linkshared
}
var (
Thestring string
Thelinkarch *LinkArch
outfile string
dynexp []*LSym
dynlib []string
ldflag []string
havedynamic int
Funcalign int
iscgo bool
elfglobalsymndx int
flag_installsuffix string
flag_race int
Buildmode BuildMode
Linkshared bool
tracksym string
interpreter string
tmpdir string
extld string
extldflags string
debug_s int // backup old value of debug['s']
Ctxt *Link
HEADR int32
HEADTYPE int32
INITRND int32
INITTEXT int64
INITDAT int64
INITENTRY string /* entry point */
nerrors int
Linkmode int
liveness int64
)
// for dynexport field of LSym
const (
CgoExportDynamic = 1 << 0
CgoExportStatic = 1 << 1
)
var (
Segtext Segment
Segrodata Segment
Segdata Segment
Segdwarf Segment
)
/* set by call to mywhatsys() */
/* whence for ldpkg */
const (
FileObj = 0 + iota
ArchiveObj
Pkgdef
)
var (
headstring string
// buffered output
Bso obj.Biobuf
)
var coutbuf struct {
*bufio.Writer
f *os.File
}
const (
// Whether to assume that the external linker is "gold"
// (http://sourceware.org/ml/binutils/2008-03/msg00162.html).
AssumeGoldLinker = 0
)
const (
symname = "__.GOSYMDEF"
pkgname = "__.PKGDEF"
)
var (
// Set if we see an object compiled by the host compiler that is not
// from a package that is known to support internal linking mode.
externalobj = false
goroot string
goarch string
goos string
theline string
)
func Lflag(arg string) {
Ctxt.Libdir = append(Ctxt.Libdir, arg)
}
// A BuildMode indicates the sort of object we are building:
// "exe": build a main package and everything it imports into an executable.
// "c-shared": build a main package, plus all packages that it imports, into a
// single C shared library. The only callable symbols will be those functions
// marked as exported.
// "shared": combine all packages passed on the command line, and their
// dependencies, into a single shared library that will be used when
// building with the -linkshared option.
type BuildMode uint8
const (
BuildmodeExe BuildMode = iota
BuildmodeCArchive
BuildmodeCShared
BuildmodeShared
)
func (mode *BuildMode) Set(s string) error {
goos := obj.Getgoos()
goarch := obj.Getgoarch()
badmode := func() error {
return fmt.Errorf("buildmode %s not supported on %s/%s", s, goos, goarch)
}
switch s {
default:
return fmt.Errorf("invalid buildmode: %q", s)
case "exe":
*mode = BuildmodeExe
case "c-archive":
switch goos {
case "darwin", "linux":
default:
return badmode()
}
*mode = BuildmodeCArchive
case "c-shared":
if goarch != "amd64" && goarch != "arm" {
return badmode()
}
*mode = BuildmodeCShared
case "shared":
if goos != "linux" || goarch != "amd64" {
return badmode()
}
*mode = BuildmodeShared
}
return nil
}
func (mode *BuildMode) String() string {
switch *mode {
case BuildmodeExe:
return "exe"
case BuildmodeCArchive:
return "c-archive"
case BuildmodeCShared:
return "c-shared"
case BuildmodeShared:
return "shared"
}
return fmt.Sprintf("BuildMode(%d)", uint8(*mode))
}
/*
* Unix doesn't like it when we write to a running (or, sometimes,
* recently run) binary, so remove the output file before writing it.
* On Windows 7, remove() can force a subsequent create() to fail.
* S_ISREG() does not exist on Plan 9.
*/
func mayberemoveoutfile() {
if fi, err := os.Lstat(outfile); err == nil && !fi.Mode().IsRegular() {
return
}
os.Remove(outfile)
}
func libinit() {
Funcalign = Thearch.Funcalign
mywhatsys() // get goroot, goarch, goos
// add goroot to the end of the libdir list.
suffix := ""
suffixsep := ""
if flag_installsuffix != "" {
suffixsep = "_"
suffix = flag_installsuffix
} else if flag_race != 0 {
suffixsep = "_"
suffix = "race"
}
Lflag(fmt.Sprintf("%s/pkg/%s_%s%s%s", goroot, goos, goarch, suffixsep, suffix))
mayberemoveoutfile()
f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0775)
if err != nil {
Exitf("cannot create %s: %v", outfile, err)
}
coutbuf.Writer = bufio.NewWriter(f)
coutbuf.f = f
if INITENTRY == "" {
switch Buildmode {
case BuildmodeCShared, BuildmodeCArchive:
INITENTRY = fmt.Sprintf("_rt0_%s_%s_lib", goarch, goos)
case BuildmodeExe:
INITENTRY = fmt.Sprintf("_rt0_%s_%s", goarch, goos)
case BuildmodeShared:
// No INITENTRY for -buildmode=shared
default:
Diag("unknown INITENTRY for buildmode %v", Buildmode)
}
}
if !DynlinkingGo() {
Linklookup(Ctxt, INITENTRY, 0).Type = obj.SXREF
}
}
func Exitf(format string, a ...interface{}) {
fmt.Fprintf(os.Stderr, os.Args[0]+": "+format+"\n", a...)
if coutbuf.f != nil {
coutbuf.f.Close()
mayberemoveoutfile()
}
Exit(2)
}
func errorexit() {
if coutbuf.f != nil {
if nerrors != 0 {
Cflush()
}
// For rmtemp run at atexit time on Windows.
if err := coutbuf.f.Close(); err != nil {
Exitf("close: %v", err)
}
}
if nerrors != 0 {
if coutbuf.f != nil {
mayberemoveoutfile()
}
Exit(2)
}
Exit(0)
}
func loadinternal(name string) {
found := 0
for i := 0; i < len(Ctxt.Libdir); i++ {
if Linkshared {
shlibname := fmt.Sprintf("%s/%s.shlibname", Ctxt.Libdir[i], name)
if Debug['v'] != 0 {
fmt.Fprintf(&Bso, "searching for %s.a in %s\n", name, shlibname)
}
if obj.Access(shlibname, obj.AEXIST) >= 0 {
addlibpath(Ctxt, "internal", "internal", "", name, shlibname)
found = 1
break
}
}
pname := fmt.Sprintf("%s/%s.a", Ctxt.Libdir[i], name)
if Debug['v'] != 0 {
fmt.Fprintf(&Bso, "searching for %s.a in %s\n", name, pname)
}
if obj.Access(pname, obj.AEXIST) >= 0 {
addlibpath(Ctxt, "internal", "internal", pname, name, "")
found = 1
break
}
}
if found == 0 {
fmt.Fprintf(&Bso, "warning: unable to find %s.a\n", name)
}
}
func loadlib() {
switch Buildmode {
case BuildmodeCShared:
s := Linklookup(Ctxt, "runtime.islibrary", 0)
s.Dupok = 1
Adduint8(Ctxt, s, 1)
case BuildmodeCArchive:
s := Linklookup(Ctxt, "runtime.isarchive", 0)
s.Dupok = 1
Adduint8(Ctxt, s, 1)
}
loadinternal("runtime")
if Thearch.Thechar == '5' {
loadinternal("math")
}
if flag_race != 0 {
loadinternal("runtime/race")
}
var i int
for i = 0; i < len(Ctxt.Library); i++ {
if Debug['v'] > 1 {
fmt.Fprintf(&Bso, "%5.2f autolib: %s (from %s)\n", obj.Cputime(), Ctxt.Library[i].File, Ctxt.Library[i].Objref)
}
iscgo = iscgo || Ctxt.Library[i].Pkg == "runtime/cgo"
if Ctxt.Library[i].Shlib != "" {
ldshlibsyms(Ctxt.Library[i].Shlib)
} else {
objfile(Ctxt.Library[i])
}
}
if Linkmode == LinkAuto {
if iscgo && externalobj {
Linkmode = LinkExternal
} else {
Linkmode = LinkInternal
}
// Force external linking for android.
if goos == "android" {
Linkmode = LinkExternal
}
// cgo on Darwin must use external linking
// we can always use external linking, but then there will be circular
// dependency problems when compiling natively (external linking requires
// runtime/cgo, runtime/cgo requires cmd/cgo, but cmd/cgo needs to be
// compiled using external linking.)
if (Thearch.Thechar == '5' || Thearch.Thechar == '7') && HEADTYPE == obj.Hdarwin && iscgo {
Linkmode = LinkExternal
}
}
// cmd/7l doesn't support cgo internal linking
// This is https://golang.org/issue/10373.
if iscgo && goarch == "arm64" {
Linkmode = LinkExternal
}
if Linkmode == LinkExternal && !iscgo {
// This indicates a user requested -linkmode=external.
// The startup code uses an import of runtime/cgo to decide
// whether to initialize the TLS. So give it one. This could
// be handled differently but it's an unusual case.
loadinternal("runtime/cgo")
if i < len(Ctxt.Library) {
if Ctxt.Library[i].Shlib != "" {
ldshlibsyms(Ctxt.Library[i].Shlib)
} else {
if DynlinkingGo() {
Exitf("cannot implicitly include runtime/cgo in a shared library")
}
objfile(Ctxt.Library[i])
}
}
}
if Linkmode == LinkInternal {
// Drop all the cgo_import_static declarations.
// Turns out we won't be needing them.
for s := Ctxt.Allsym; s != nil; s = s.Allsym {
if s.Type == obj.SHOSTOBJ {
// If a symbol was marked both
// cgo_import_static and cgo_import_dynamic,
// then we want to make it cgo_import_dynamic
// now.
if s.Extname != "" && s.Dynimplib != "" && s.Cgoexport == 0 {
s.Type = obj.SDYNIMPORT
} else {
s.Type = 0
}
}
}
}
tlsg := Linklookup(Ctxt, "runtime.tlsg", 0)
// For most ports, runtime.tlsg is a placeholder symbol for TLS
// relocation. However, the Android and Darwin arm ports need it
// to be a real variable.
//
// TODO(crawshaw): android should require leaving the tlsg->type
// alone (as the runtime-provided SNOPTRBSS) just like darwin/arm.
// But some other part of the linker is expecting STLSBSS.
if tlsg.Type != obj.SDYNIMPORT && (goos != "darwin" || Thearch.Thechar != '5') {
tlsg.Type = obj.STLSBSS
}
tlsg.Size = int64(Thearch.Ptrsize)
tlsg.Reachable = true
Ctxt.Tlsg = tlsg
// Now that we know the link mode, trim the dynexp list.
x := CgoExportDynamic
if Linkmode == LinkExternal {
x = CgoExportStatic
}
w := 0
for i := 0; i < len(dynexp); i++ {
if int(dynexp[i].Cgoexport)&x != 0 {
dynexp[w] = dynexp[i]
w++
}
}
dynexp = dynexp[:w]
// In internal link mode, read the host object files.
if Linkmode == LinkInternal {
hostobjs()
} else {
hostlinksetup()
}
// We've loaded all the code now.
// If there are no dynamic libraries needed, gcc disables dynamic linking.
// Because of this, glibc's dynamic ELF loader occasionally (like in version 2.13)
// assumes that a dynamic binary always refers to at least one dynamic library.
// Rather than be a source of test cases for glibc, disable dynamic linking
// the same way that gcc would.
//
// Exception: on OS X, programs such as Shark only work with dynamic
// binaries, so leave it enabled on OS X (Mach-O) binaries.
// Also leave it enabled on Solaris which doesn't support
// statically linked binaries.
if Buildmode == BuildmodeExe && havedynamic == 0 && HEADTYPE != obj.Hdarwin && HEADTYPE != obj.Hsolaris {
Debug['d'] = 1
}
importcycles()
}
/*
* look for the next file in an archive.
* adapted from libmach.
*/
func nextar(bp *obj.Biobuf, off int64, a *ArHdr) int64 {
if off&1 != 0 {
off++
}
obj.Bseek(bp, off, 0)
buf := make([]byte, SAR_HDR)
if n := obj.Bread(bp, buf); n < len(buf) {
if n >= 0 {
return 0
}
return -1
}
a.name = artrim(buf[0:16])
a.date = artrim(buf[16:28])
a.uid = artrim(buf[28:34])
a.gid = artrim(buf[34:40])
a.mode = artrim(buf[40:48])
a.size = artrim(buf[48:58])
a.fmag = artrim(buf[58:60])
arsize := atolwhex(a.size)
if arsize&1 != 0 {
arsize++
}
return int64(arsize) + SAR_HDR
}
func objfile(lib *Library) {
pkg := pathtoprefix(lib.Pkg)
if Debug['v'] > 1 {
fmt.Fprintf(&Bso, "%5.2f ldobj: %s (%s)\n", obj.Cputime(), lib.File, pkg)
}
Bso.Flush()
var err error
var f *obj.Biobuf
f, err = obj.Bopenr(lib.File)
if err != nil {
Exitf("cannot open file %s: %v", lib.File, err)
}
magbuf := make([]byte, len(ARMAG))
if obj.Bread(f, magbuf) != len(magbuf) || !strings.HasPrefix(string(magbuf), ARMAG) {
/* load it as a regular file */
l := obj.Bseek(f, 0, 2)
obj.Bseek(f, 0, 0)
ldobj(f, pkg, l, lib.File, lib.File, FileObj)
obj.Bterm(f)
return
}
/* skip over optional __.GOSYMDEF and process __.PKGDEF */
off := obj.Boffset(f)
var arhdr ArHdr
l := nextar(f, off, &arhdr)
var pname string
if l <= 0 {
Diag("%s: short read on archive file symbol header", lib.File)
goto out
}
if strings.HasPrefix(arhdr.name, symname) {
off += l
l = nextar(f, off, &arhdr)
if l <= 0 {
Diag("%s: short read on archive file symbol header", lib.File)
goto out
}
}
if !strings.HasPrefix(arhdr.name, pkgname) {
Diag("%s: cannot find package header", lib.File)
goto out
}
if Buildmode == BuildmodeShared {
before := obj.Boffset(f)
pkgdefBytes := make([]byte, atolwhex(arhdr.size))
obj.Bread(f, pkgdefBytes)
hash := sha1.Sum(pkgdefBytes)
lib.hash = hash[:]
obj.Bseek(f, before, 0)
}
off += l
if Debug['u'] != 0 {
ldpkg(f, pkg, atolwhex(arhdr.size), lib.File, Pkgdef)
}
/*
* load all the object files from the archive now.
* this gives us sequential file access and keeps us
* from needing to come back later to pick up more
* objects. it breaks the usual C archive model, but
* this is Go, not C. the common case in Go is that
* we need to load all the objects, and then we throw away
* the individual symbols that are unused.
*
* loading every object will also make it possible to
* load foreign objects not referenced by __.GOSYMDEF.
*/
for {
l = nextar(f, off, &arhdr)
if l == 0 {
break
}
if l < 0 {
Exitf("%s: malformed archive", lib.File)
}
off += l
pname = fmt.Sprintf("%s(%s)", lib.File, arhdr.name)
l = atolwhex(arhdr.size)
ldobj(f, pkg, l, pname, lib.File, ArchiveObj)
}
out:
obj.Bterm(f)
}
type Hostobj struct {
ld func(*obj.Biobuf, string, int64, string)
pkg string
pn string
file string
off int64
length int64
}
var hostobj []Hostobj
// These packages can use internal linking mode.
// Others trigger external mode.
var internalpkg = []string{
"crypto/x509",
"net",
"os/user",
"runtime/cgo",
"runtime/race",
}
func ldhostobj(ld func(*obj.Biobuf, string, int64, string), f *obj.Biobuf, pkg string, length int64, pn string, file string) {
isinternal := false
for i := 0; i < len(internalpkg); i++ {
if pkg == internalpkg[i] {
isinternal = true
break
}
}
// DragonFly declares errno with __thread, which results in a symbol
// type of R_386_TLS_GD or R_X86_64_TLSGD. The Go linker does not
// currently know how to handle TLS relocations, hence we have to
// force external linking for any libraries that link in code that
// uses errno. This can be removed if the Go linker ever supports
// these relocation types.
if HEADTYPE == obj.Hdragonfly {
if pkg == "net" || pkg == "os/user" {
isinternal = false
}
}
if !isinternal {
externalobj = true
}
hostobj = append(hostobj, Hostobj{})
h := &hostobj[len(hostobj)-1]
h.ld = ld
h.pkg = pkg
h.pn = pn
h.file = file
h.off = obj.Boffset(f)
h.length = length
}
func hostobjs() {
var f *obj.Biobuf
var h *Hostobj
for i := 0; i < len(hostobj); i++ {
h = &hostobj[i]
var err error
f, err = obj.Bopenr(h.file)
if f == nil {
Exitf("cannot reopen %s: %v", h.pn, err)
}
obj.Bseek(f, h.off, 0)
h.ld(f, h.pkg, h.length, h.pn)
obj.Bterm(f)
}
}
// provided by lib9
func rmtemp() {
os.RemoveAll(tmpdir)
}
func hostlinksetup() {
if Linkmode != LinkExternal {
return
}
// create temporary directory and arrange cleanup
if tmpdir == "" {
dir, err := ioutil.TempDir("", "go-link-")
if err != nil {
log.Fatal(err)
}
tmpdir = dir
AtExit(rmtemp)
}
// change our output to temporary object file
coutbuf.f.Close()
p := fmt.Sprintf("%s/go.o", tmpdir)
var err error
f, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0775)
if err != nil {
Exitf("cannot create %s: %v", p, err)
}
coutbuf.Writer = bufio.NewWriter(f)
coutbuf.f = f
}
// hostobjCopy creates a copy of the object files in hostobj in a
// temporary directory.
func hostobjCopy() (paths []string) {
for i, h := range hostobj {
f, err := os.Open(h.file)
if err != nil {
Exitf("cannot reopen %s: %v", h.pn, err)
}
if _, err := f.Seek(h.off, 0); err != nil {
Exitf("cannot seek %s: %v", h.pn, err)
}
p := fmt.Sprintf("%s/%06d.o", tmpdir, i)
paths = append(paths, p)
w, err := os.Create(p)
if err != nil {
Exitf("cannot create %s: %v", p, err)
}
if _, err := io.CopyN(w, f, h.length); err != nil {
Exitf("cannot write %s: %v", p, err)
}
if err := w.Close(); err != nil {
Exitf("cannot close %s: %v", p, err)
}
}
return paths
}
// archive builds a .a archive from the hostobj object files.
func archive() {
if Buildmode != BuildmodeCArchive {
return
}
os.Remove(outfile)
argv := []string{"ar", "-q", "-c", outfile}
argv = append(argv, hostobjCopy()...)
argv = append(argv, fmt.Sprintf("%s/go.o", tmpdir))
if Debug['v'] != 0 {
fmt.Fprintf(&Bso, "archive: %s\n", strings.Join(argv, " "))
Bso.Flush()
}
if out, err := exec.Command(argv[0], argv[1:]...).CombinedOutput(); err != nil {
Exitf("running %s failed: %v\n%s", argv[0], err, out)
}
}
func hostlink() {
if Linkmode != LinkExternal || nerrors > 0 {
return
}
if Buildmode == BuildmodeCArchive {
return
}
if extld == "" {
extld = "gcc"
}
var argv []string
argv = append(argv, extld)
switch Thearch.Thechar {
case '8':
argv = append(argv, "-m32")
case '6', '9':
argv = append(argv, "-m64")
case '5':
argv = append(argv, "-marm")
case '7':
// nothing needed
}
if Debug['s'] == 0 && debug_s == 0 {
argv = append(argv, "-gdwarf-2")
} else {
argv = append(argv, "-s")
}
if HEADTYPE == obj.Hdarwin {
argv = append(argv, "-Wl,-no_pie,-pagezero_size,4000000,-headerpad,1144")
}
if HEADTYPE == obj.Hopenbsd {
argv = append(argv, "-Wl,-nopie")
}
if HEADTYPE == obj.Hwindows {
if headstring == "windowsgui" {
argv = append(argv, "-mwindows")
} else {
argv = append(argv, "-mconsole")
}
}
if Iself && AssumeGoldLinker != 0 /*TypeKind(100016)*/ {
argv = append(argv, "-Wl,--rosegment")
}
switch Buildmode {
case BuildmodeCShared:
argv = append(argv, "-Wl,-Bsymbolic")
argv = append(argv, "-shared")
case BuildmodeShared:
// TODO(mwhudson): unless you do this, dynamic relocations fill
// out the findfunctab table and for some reason shared libraries
// and the executable both define a main function and putting the
// address of executable's main into the shared libraries
// findfunctab violates the assumptions of the runtime. TBH, I
// think we may well end up wanting to use -Bsymbolic here
// anyway.
argv = append(argv, "-Wl,-Bsymbolic-functions")
argv = append(argv, "-shared")
}
if Linkshared && Iself {
// We force all symbol resolution to be done at program startup
// because lazy PLT resolution can use large amounts of stack at
// times we cannot allow it to do so.
argv = append(argv, "-Wl,-znow")
}
argv = append(argv, "-o")
argv = append(argv, outfile)
if rpath.val != "" {
argv = append(argv, fmt.Sprintf("-Wl,-rpath,%s", rpath.val))
}
// Force global symbols to be exported for dlopen, etc.
if Iself {
argv = append(argv, "-rdynamic")
}
if strings.Contains(argv[0], "clang") {
argv = append(argv, "-Qunused-arguments")
}
argv = append(argv, hostobjCopy()...)
argv = append(argv, fmt.Sprintf("%s/go.o", tmpdir))
if Linkshared {
for _, shlib := range Ctxt.Shlibs {
dir, base := filepath.Split(shlib.Path)
argv = append(argv, "-L"+dir)
if !rpath.set {
argv = append(argv, "-Wl,-rpath="+dir)
}
base = strings.TrimSuffix(base, ".so")
base = strings.TrimPrefix(base, "lib")
argv = append(argv, "-l"+base)
}
}
argv = append(argv, ldflag...)
for _, p := range strings.Fields(extldflags) {
argv = append(argv, p)
// clang, unlike GCC, passes -rdynamic to the linker
// even when linking with -static, causing a linker
// error when using GNU ld. So take out -rdynamic if
// we added it. We do it in this order, rather than
// only adding -rdynamic later, so that -extldflags
// can override -rdynamic without using -static.
if Iself && p == "-static" {
for i := range argv {
if argv[i] == "-rdynamic" {
argv[i] = "-static"
}
}
}
}
if HEADTYPE == obj.Hwindows {
argv = append(argv, peimporteddlls()...)
}
if Debug['v'] != 0 {
fmt.Fprintf(&Bso, "host link:")
for _, v := range argv {
fmt.Fprintf(&Bso, " %q", v)
}
fmt.Fprintf(&Bso, "\n")
Bso.Flush()
}
if out, err := exec.Command(argv[0], argv[1:]...).CombinedOutput(); err != nil {
Exitf("running %s failed: %v\n%s", argv[0], err, out)
}
if Debug['s'] == 0 && debug_s == 0 && HEADTYPE == obj.Hdarwin {
dsym := fmt.Sprintf("%s/go.dwarf", tmpdir)
if out, err := exec.Command("dsymutil", "-f", outfile, "-o", dsym).CombinedOutput(); err != nil {
Ctxt.Cursym = nil
Exitf("%s: running dsymutil failed: %v\n%s", os.Args[0], err, out)
}
combinedOutput := fmt.Sprintf("%s/go.combined", tmpdir)
if err := machoCombineDwarf(outfile, dsym, combinedOutput); err != nil {
Ctxt.Cursym = nil
Exitf("%s: combining dwarf failed: %v", os.Args[0], err)
}
origOutput := fmt.Sprintf("%s/go.orig", tmpdir)
os.Rename(outfile, origOutput)
if err := os.Rename(combinedOutput, outfile); err != nil {
Ctxt.Cursym = nil
Exitf("%s: rename(%s, %s) failed: %v", os.Args[0], combinedOutput, outfile, err)
}
}
}
func ldobj(f *obj.Biobuf, pkg string, length int64, pn string, file string, whence int) {
eof := obj.Boffset(f) + length
start := obj.Boffset(f)
c1 := obj.Bgetc(f)
c2 := obj.Bgetc(f)
c3 := obj.Bgetc(f)
c4 := obj.Bgetc(f)
obj.Bseek(f, start, 0)
magic := uint32(c1)<<24 | uint32(c2)<<16 | uint32(c3)<<8 | uint32(c4)
if magic == 0x7f454c46 { // \x7F E L F
ldhostobj(ldelf, f, pkg, length, pn, file)
return
}
if magic&^1 == 0xfeedface || magic&^0x01000000 == 0xcefaedfe {
ldhostobj(ldmacho, f, pkg, length, pn, file)
return
}
if c1 == 0x4c && c2 == 0x01 || c1 == 0x64 && c2 == 0x86 {
ldhostobj(ldpe, f, pkg, length, pn, file)
return
}
/* check the header */
line := obj.Brdline(f, '\n')
if line == "" {
if obj.Blinelen(f) > 0 {
Diag("%s: not an object file", pn)
return
}
Diag("truncated object file: %s", pn)
return
}
if !strings.HasPrefix(line, "go object ") {
if strings.HasSuffix(pn, ".go") {
Exitf("%cl: input %s is not .%c file (use %cg to compile .go files)", Thearch.Thechar, pn, Thearch.Thechar, Thearch.Thechar)
}
if line == Thestring {
// old header format: just $GOOS
Diag("%s: stale object file", pn)
return
}
Diag("%s: not an object file", pn)
return
}
// First, check that the basic goos, goarch, and version match.
t := fmt.Sprintf("%s %s %s ", goos, obj.Getgoarch(), obj.Getgoversion())
line = strings.TrimRight(line, "\n")
if !strings.HasPrefix(line[10:]+" ", t) && Debug['f'] == 0 {
Diag("%s: object is [%s] expected [%s]", pn, line[10:], t)
return
}
// Second, check that longer lines match each other exactly,
// so that the Go compiler and write additional information
// that must be the same from run to run.
if len(line) >= len(t)+10 {
if theline == "" {
theline = line[10:]
} else if theline != line[10:] {
Diag("%s: object is [%s] expected [%s]", pn, line[10:], theline)
return
}
}
/* skip over exports and other info -- ends with \n!\n */
import0 := obj.Boffset(f)
c1 = '\n' // the last line ended in \n
c2 = obj.Bgetc(f)
c3 = obj.Bgetc(f)
for c1 != '\n' || c2 != '!' || c3 != '\n' {
c1 = c2
c2 = c3
c3 = obj.Bgetc(f)
if c3 == obj.Beof {
Diag("truncated object file: %s", pn)
return
}
}
import1 := obj.Boffset(f)
obj.Bseek(f, import0, 0)
ldpkg(f, pkg, import1-import0-2, pn, whence) // -2 for !\n
obj.Bseek(f, import1, 0)
ldobjfile(Ctxt, f, pkg, eof-obj.Boffset(f), pn)
}
func readelfsymboldata(f *elf.File, sym *elf.Symbol) []byte {
data := make([]byte, sym.Size)
sect := f.Sections[sym.Section]
if sect.Type != elf.SHT_PROGBITS {
Diag("reading %s from non-PROGBITS section", sym.Name)
}
n, err := sect.ReadAt(data, int64(sym.Value-sect.Offset))
if uint64(n) != sym.Size {
Diag("reading contents of %s: %v", sym.Name, err)
}
return data
}
func ldshlibsyms(shlib string) {
found := false
libpath := ""
for _, libdir := range Ctxt.Libdir {
libpath = filepath.Join(libdir, shlib)
if _, err := os.Stat(libpath); err == nil {
found = true
break
}
}
if !found {
Diag("cannot find shared library: %s", shlib)
return
}
for _, processedlib := range Ctxt.Shlibs {
if processedlib.Path == libpath {
return
}
}
if Ctxt.Debugvlog > 1 && Ctxt.Bso != nil {
fmt.Fprintf(Ctxt.Bso, "%5.2f ldshlibsyms: found library with name %s at %s\n", obj.Cputime(), shlib, libpath)
Ctxt.Bso.Flush()
}
f, err := elf.Open(libpath)
if err != nil {
Diag("cannot open shared library: %s", libpath)
return
}
defer f.Close()
syms, err := f.Symbols()
if err != nil {
Diag("cannot read symbols from shared library: %s", libpath)
return
}
// If a package has a global variable of a type defined in another shared
// library, we need to know the gcmask used by the type, if any. To support
// this, we read all the runtime.gcbits.* symbols, keep a map of address to
// gcmask, and after we're read all the symbols, read the addresses of the
// gcmasks symbols out of the type data to look up the gcmask for each type.
// This depends on the fact that the runtime.gcbits.* symbols are local (so
// the address is actually present in the type data and we don't have to
// search all relocations to find the ones which correspond to gcmasks) and
// also that the shared library we are linking against has not had the symbol
// table removed.
gcmasks := make(map[uint64][]byte)
types := []*LSym{}
var hash []byte
for _, s := range syms {
if elf.ST_TYPE(s.Info) == elf.STT_NOTYPE || elf.ST_TYPE(s.Info) == elf.STT_SECTION {
continue
}
if s.Section == elf.SHN_UNDEF {
continue
}
if strings.HasPrefix(s.Name, "_") {
continue
}
runtime: replace GC programs with simpler encoding, faster decoder Small types record the location of pointers in their memory layout by using a simple bitmap. In Go 1.4 the bitmap held 4-bit entries, and in Go 1.5 the bitmap holds 1-bit entries, but in both cases using a bitmap for a large type containing arrays does not make sense: if someone refers to the type [1<<28]*byte in a program in such a way that the type information makes it into the binary, it would be a waste of space to write a 128 MB (for 4-bit entries) or even 32 MB (for 1-bit entries) bitmap full of 1s into the binary or even to keep one in memory during the execution of the program. For large types containing arrays, it is much more compact to describe the locations of pointers using a notation that can express repetition than to lay out a bitmap of pointers. Go 1.4 included such a notation, called ``GC programs'' but it was complex, required recursion during decoding, and was generally slow. Dmitriy measured the execution of these programs writing directly to the heap bitmap as being 7x slower than copying from a preunrolled 4-bit mask (and frankly that code was not terribly fast either). For some tests, unrollgcprog1 was seen costing as much as 3x more than the rest of malloc combined. This CL introduces a different form for the GC programs. They use a simple Lempel-Ziv-style encoding of the 1-bit pointer information, in which the only operations are (1) emit the following n bits and (2) repeat the last n bits c more times. This encoding can be generated directly from the Go type information (using repetition only for arrays or large runs of non-pointer data) and it can be decoded very efficiently. In particular the decoding requires little state and no recursion, so that the entire decoding can run without any memory accesses other than the reads of the encoding and the writes of the decoded form to the heap bitmap. For recursive types like arrays of arrays of arrays, the inner instructions are only executed once, not n times, so that large repetitions run at full speed. (In contrast, large repetitions in the old programs repeated the individual bit-level layout of the inner data over and over.) The result is as much as 25x faster decoding compared to the old form. Because the old decoder was so slow, Go 1.4 had three (or so) cases for how to set the heap bitmap bits for an allocation of a given type: (1) If the type had an even number of words up to 32 words, then the 4-bit pointer mask for the type fit in no more than 16 bytes; store the 4-bit pointer mask directly in the binary and copy from it. (1b) If the type had an odd number of words up to 15 words, then the 4-bit pointer mask for the type, doubled to end on a byte boundary, fit in no more than 16 bytes; store that doubled mask directly in the binary and copy from it. (2) If the type had an even number of words up to 128 words, or an odd number of words up to 63 words (again due to doubling), then the 4-bit pointer mask would fit in a 64-byte unrolled mask. Store a GC program in the binary, but leave space in the BSS for the unrolled mask. Execute the GC program to construct the mask the first time it is needed, and thereafter copy from the mask. (3) Otherwise, store a GC program and execute it to write directly to the heap bitmap each time an object of that type is allocated. (This is the case that was 7x slower than the other two.) Because the new pointer masks store 1-bit entries instead of 4-bit entries and because using the decoder no longer carries a significant overhead, after this CL (that is, for Go 1.5) there are only two cases: (1) If the type is 128 words or less (no condition about odd or even), store the 1-bit pointer mask directly in the binary and use it to initialize the heap bitmap during malloc. (Implemented in CL 9702.) (2) There is no case 2 anymore. (3) Otherwise, store a GC program and execute it to write directly to the heap bitmap each time an object of that type is allocated. Executing the GC program directly into the heap bitmap (case (3) above) was disabled for the Go 1.5 dev cycle, both to avoid needing to use GC programs for typedmemmove and to avoid updating that code as the heap bitmap format changed. Typedmemmove no longer uses this type information; as of CL 9886 it uses the heap bitmap directly. Now that the heap bitmap format is stable, we reintroduce GC programs and their space savings. Benchmarks for heapBitsSetType, before this CL vs this CL: name old mean new mean delta SetTypePtr 7.59ns × (0.99,1.02) 5.16ns × (1.00,1.00) -32.05% (p=0.000) SetTypePtr8 21.0ns × (0.98,1.05) 21.4ns × (1.00,1.00) ~ (p=0.179) SetTypePtr16 24.1ns × (0.99,1.01) 24.6ns × (1.00,1.00) +2.41% (p=0.001) SetTypePtr32 31.2ns × (0.99,1.01) 32.4ns × (0.99,1.02) +3.72% (p=0.001) SetTypePtr64 45.2ns × (1.00,1.00) 47.2ns × (1.00,1.00) +4.42% (p=0.000) SetTypePtr126 75.8ns × (0.99,1.01) 79.1ns × (1.00,1.00) +4.25% (p=0.000) SetTypePtr128 74.3ns × (0.99,1.01) 77.6ns × (1.00,1.01) +4.55% (p=0.000) SetTypePtrSlice 726ns × (1.00,1.01) 712ns × (1.00,1.00) -1.95% (p=0.001) SetTypeNode1 20.0ns × (0.99,1.01) 20.7ns × (1.00,1.00) +3.71% (p=0.000) SetTypeNode1Slice 112ns × (1.00,1.00) 113ns × (0.99,1.00) ~ (p=0.070) SetTypeNode8 23.9ns × (1.00,1.00) 24.7ns × (1.00,1.01) +3.18% (p=0.000) SetTypeNode8Slice 294ns × (0.99,1.02) 287ns × (0.99,1.01) -2.38% (p=0.015) SetTypeNode64 52.8ns × (0.99,1.03) 51.8ns × (0.99,1.01) ~ (p=0.069) SetTypeNode64Slice 1.13µs × (0.99,1.05) 1.14µs × (0.99,1.00) ~ (p=0.767) SetTypeNode64Dead 36.0ns × (1.00,1.01) 32.5ns × (0.99,1.00) -9.67% (p=0.000) SetTypeNode64DeadSlice 1.43µs × (0.99,1.01) 1.40µs × (1.00,1.00) -2.39% (p=0.001) SetTypeNode124 75.7ns × (1.00,1.01) 79.0ns × (1.00,1.00) +4.44% (p=0.000) SetTypeNode124Slice 1.94µs × (1.00,1.01) 2.04µs × (0.99,1.01) +4.98% (p=0.000) SetTypeNode126 75.4ns × (1.00,1.01) 77.7ns × (0.99,1.01) +3.11% (p=0.000) SetTypeNode126Slice 1.95µs × (0.99,1.01) 2.03µs × (1.00,1.00) +3.74% (p=0.000) SetTypeNode128 85.4ns × (0.99,1.01) 122.0ns × (1.00,1.00) +42.89% (p=0.000) SetTypeNode128Slice 2.20µs × (1.00,1.01) 2.36µs × (0.98,1.02) +7.48% (p=0.001) SetTypeNode130 83.3ns × (1.00,1.00) 123.0ns × (1.00,1.00) +47.61% (p=0.000) SetTypeNode130Slice 2.30µs × (0.99,1.01) 2.40µs × (0.98,1.01) +4.37% (p=0.000) SetTypeNode1024 498ns × (1.00,1.00) 537ns × (1.00,1.00) +7.96% (p=0.000) SetTypeNode1024Slice 15.5µs × (0.99,1.01) 17.8µs × (1.00,1.00) +15.27% (p=0.000) The above compares always using a cached pointer mask (and the corresponding waste of memory) against using the programs directly. Some slowdown is expected, in exchange for having a better general algorithm. The GC programs kick in for SetTypeNode128, SetTypeNode130, SetTypeNode1024, along with the slice variants of those. It is possible that the cutoff of 128 words (bits) should be raised in a followup CL, but even with this low cutoff the GC programs are faster than Go 1.4's "fast path" non-GC program case. Benchmarks for heapBitsSetType, Go 1.4 vs this CL: name old mean new mean delta SetTypePtr 6.89ns × (1.00,1.00) 5.17ns × (1.00,1.00) -25.02% (p=0.000) SetTypePtr8 25.8ns × (0.97,1.05) 21.5ns × (1.00,1.00) -16.70% (p=0.000) SetTypePtr16 39.8ns × (0.97,1.02) 24.7ns × (0.99,1.01) -37.81% (p=0.000) SetTypePtr32 68.8ns × (0.98,1.01) 32.2ns × (1.00,1.01) -53.18% (p=0.000) SetTypePtr64 130ns × (1.00,1.00) 47ns × (1.00,1.00) -63.67% (p=0.000) SetTypePtr126 241ns × (0.99,1.01) 79ns × (1.00,1.01) -67.25% (p=0.000) SetTypePtr128 2.07µs × (1.00,1.00) 0.08µs × (1.00,1.00) -96.27% (p=0.000) SetTypePtrSlice 1.05µs × (0.99,1.01) 0.72µs × (0.99,1.02) -31.70% (p=0.000) SetTypeNode1 16.0ns × (0.99,1.01) 20.8ns × (0.99,1.03) +29.91% (p=0.000) SetTypeNode1Slice 184ns × (0.99,1.01) 112ns × (0.99,1.01) -39.26% (p=0.000) SetTypeNode8 29.5ns × (0.97,1.02) 24.6ns × (1.00,1.00) -16.50% (p=0.000) SetTypeNode8Slice 624ns × (0.98,1.02) 285ns × (1.00,1.00) -54.31% (p=0.000) SetTypeNode64 135ns × (0.96,1.08) 52ns × (0.99,1.02) -61.32% (p=0.000) SetTypeNode64Slice 3.83µs × (1.00,1.00) 1.14µs × (0.99,1.01) -70.16% (p=0.000) SetTypeNode64Dead 134ns × (0.99,1.01) 32ns × (1.00,1.01) -75.74% (p=0.000) SetTypeNode64DeadSlice 3.83µs × (0.99,1.00) 1.40µs × (1.00,1.01) -63.42% (p=0.000) SetTypeNode124 240ns × (0.99,1.01) 79ns × (1.00,1.01) -67.05% (p=0.000) SetTypeNode124Slice 7.27µs × (1.00,1.00) 2.04µs × (1.00,1.00) -71.95% (p=0.000) SetTypeNode126 2.06µs × (0.99,1.01) 0.08µs × (0.99,1.01) -96.23% (p=0.000) SetTypeNode126Slice 64.4µs × (1.00,1.00) 2.0µs × (1.00,1.00) -96.85% (p=0.000) SetTypeNode128 2.09µs × (1.00,1.01) 0.12µs × (1.00,1.00) -94.15% (p=0.000) SetTypeNode128Slice 65.4µs × (1.00,1.00) 2.4µs × (0.99,1.03) -96.39% (p=0.000) SetTypeNode130 2.11µs × (1.00,1.00) 0.12µs × (1.00,1.00) -94.18% (p=0.000) SetTypeNode130Slice 66.3µs × (1.00,1.00) 2.4µs × (0.97,1.08) -96.34% (p=0.000) SetTypeNode1024 16.0µs × (1.00,1.01) 0.5µs × (1.00,1.00) -96.65% (p=0.000) SetTypeNode1024Slice 512µs × (1.00,1.00) 18µs × (0.98,1.04) -96.45% (p=0.000) SetTypeNode124 uses a 124 data + 2 ptr = 126-word allocation. Both Go 1.4 and this CL are using pointer bitmaps for this case, so that's an overall 3x speedup for using pointer bitmaps. SetTypeNode128 uses a 128 data + 2 ptr = 130-word allocation. Both Go 1.4 and this CL are running the GC program for this case, so that's an overall 17x speedup when using GC programs (and I've seen >20x on other systems). Comparing Go 1.4's SetTypeNode124 (pointer bitmap) against this CL's SetTypeNode128 (GC program), the slow path in the code in this CL is 2x faster than the fast path in Go 1.4. The Go 1 benchmarks are basically unaffected compared to just before this CL. Go 1 benchmarks, before this CL vs this CL: name old mean new mean delta BinaryTree17 5.87s × (0.97,1.04) 5.91s × (0.96,1.04) ~ (p=0.306) Fannkuch11 4.38s × (1.00,1.00) 4.37s × (1.00,1.01) -0.22% (p=0.006) FmtFprintfEmpty 90.7ns × (0.97,1.10) 89.3ns × (0.96,1.09) ~ (p=0.280) FmtFprintfString 282ns × (0.98,1.04) 287ns × (0.98,1.07) +1.72% (p=0.039) FmtFprintfInt 269ns × (0.99,1.03) 282ns × (0.97,1.04) +4.87% (p=0.000) FmtFprintfIntInt 478ns × (0.99,1.02) 481ns × (0.99,1.02) +0.61% (p=0.048) FmtFprintfPrefixedInt 399ns × (0.98,1.03) 400ns × (0.98,1.05) ~ (p=0.533) FmtFprintfFloat 563ns × (0.99,1.01) 570ns × (1.00,1.01) +1.37% (p=0.000) FmtManyArgs 1.89µs × (0.99,1.01) 1.92µs × (0.99,1.02) +1.88% (p=0.000) GobDecode 15.2ms × (0.99,1.01) 15.2ms × (0.98,1.05) ~ (p=0.609) GobEncode 11.6ms × (0.98,1.03) 11.9ms × (0.98,1.04) +2.17% (p=0.000) Gzip 648ms × (0.99,1.01) 648ms × (1.00,1.01) ~ (p=0.835) Gunzip 142ms × (1.00,1.00) 143ms × (1.00,1.01) ~ (p=0.169) HTTPClientServer 90.5µs × (0.98,1.03) 91.5µs × (0.98,1.04) +1.04% (p=0.045) JSONEncode 31.5ms × (0.98,1.03) 31.4ms × (0.98,1.03) ~ (p=0.549) JSONDecode 111ms × (0.99,1.01) 107ms × (0.99,1.01) -3.21% (p=0.000) Mandelbrot200 6.01ms × (1.00,1.00) 6.01ms × (1.00,1.00) ~ (p=0.878) GoParse 6.54ms × (0.99,1.02) 6.61ms × (0.99,1.03) +1.08% (p=0.004) RegexpMatchEasy0_32 160ns × (1.00,1.01) 161ns × (1.00,1.00) +0.40% (p=0.000) RegexpMatchEasy0_1K 560ns × (0.99,1.01) 559ns × (0.99,1.01) ~ (p=0.088) RegexpMatchEasy1_32 138ns × (0.99,1.01) 138ns × (1.00,1.00) ~ (p=0.380) RegexpMatchEasy1_1K 877ns × (1.00,1.00) 878ns × (1.00,1.00) ~ (p=0.157) RegexpMatchMedium_32 251ns × (0.99,1.00) 251ns × (1.00,1.01) +0.28% (p=0.021) RegexpMatchMedium_1K 72.6µs × (1.00,1.00) 72.6µs × (1.00,1.00) ~ (p=0.539) RegexpMatchHard_32 3.84µs × (1.00,1.00) 3.84µs × (1.00,1.00) ~ (p=0.378) RegexpMatchHard_1K 117µs × (1.00,1.00) 117µs × (1.00,1.00) ~ (p=0.067) Revcomp 904ms × (0.99,1.02) 904ms × (0.99,1.01) ~ (p=0.943) Template 125ms × (0.99,1.02) 127ms × (0.99,1.01) +1.79% (p=0.000) TimeParse 627ns × (0.99,1.01) 622ns × (0.99,1.01) -0.88% (p=0.000) TimeFormat 655ns × (0.99,1.02) 655ns × (0.99,1.02) ~ (p=0.976) For the record, Go 1 benchmarks, Go 1.4 vs this CL: name old mean new mean delta BinaryTree17 4.61s × (0.97,1.05) 5.91s × (0.98,1.03) +28.35% (p=0.000) Fannkuch11 4.40s × (0.99,1.03) 4.41s × (0.99,1.01) ~ (p=0.212) FmtFprintfEmpty 102ns × (0.99,1.01) 84ns × (0.99,1.02) -18.38% (p=0.000) FmtFprintfString 302ns × (0.98,1.01) 303ns × (0.99,1.02) ~ (p=0.203) FmtFprintfInt 313ns × (0.97,1.05) 270ns × (0.99,1.01) -13.69% (p=0.000) FmtFprintfIntInt 524ns × (0.98,1.02) 477ns × (0.99,1.00) -8.87% (p=0.000) FmtFprintfPrefixedInt 424ns × (0.98,1.02) 386ns × (0.99,1.01) -8.96% (p=0.000) FmtFprintfFloat 652ns × (0.98,1.02) 594ns × (0.97,1.05) -8.97% (p=0.000) FmtManyArgs 2.13µs × (0.99,1.02) 1.94µs × (0.99,1.01) -8.92% (p=0.000) GobDecode 17.1ms × (0.99,1.02) 14.9ms × (0.98,1.03) -13.07% (p=0.000) GobEncode 13.5ms × (0.98,1.03) 11.5ms × (0.98,1.03) -15.25% (p=0.000) Gzip 656ms × (0.99,1.02) 647ms × (0.99,1.01) -1.29% (p=0.000) Gunzip 143ms × (0.99,1.02) 144ms × (0.99,1.01) ~ (p=0.204) HTTPClientServer 88.2µs × (0.98,1.02) 90.8µs × (0.98,1.01) +2.93% (p=0.000) JSONEncode 32.2ms × (0.98,1.02) 30.9ms × (0.97,1.04) -4.06% (p=0.001) JSONDecode 121ms × (0.98,1.02) 110ms × (0.98,1.05) -8.95% (p=0.000) Mandelbrot200 6.06ms × (0.99,1.01) 6.11ms × (0.98,1.04) ~ (p=0.184) GoParse 6.76ms × (0.97,1.04) 6.58ms × (0.98,1.05) -2.63% (p=0.003) RegexpMatchEasy0_32 195ns × (1.00,1.01) 155ns × (0.99,1.01) -20.43% (p=0.000) RegexpMatchEasy0_1K 479ns × (0.98,1.03) 535ns × (0.99,1.02) +11.59% (p=0.000) RegexpMatchEasy1_32 169ns × (0.99,1.02) 131ns × (0.99,1.03) -22.44% (p=0.000) RegexpMatchEasy1_1K 1.53µs × (0.99,1.01) 0.87µs × (0.99,1.02) -43.07% (p=0.000) RegexpMatchMedium_32 334ns × (0.99,1.01) 242ns × (0.99,1.01) -27.53% (p=0.000) RegexpMatchMedium_1K 125µs × (1.00,1.01) 72µs × (0.99,1.03) -42.53% (p=0.000) RegexpMatchHard_32 6.03µs × (0.99,1.01) 3.79µs × (0.99,1.01) -37.12% (p=0.000) RegexpMatchHard_1K 189µs × (0.99,1.02) 115µs × (0.99,1.01) -39.20% (p=0.000) Revcomp 935ms × (0.96,1.03) 926ms × (0.98,1.02) ~ (p=0.083) Template 146ms × (0.97,1.05) 119ms × (0.99,1.01) -18.37% (p=0.000) TimeParse 660ns × (0.99,1.01) 624ns × (0.99,1.02) -5.43% (p=0.000) TimeFormat 670ns × (0.98,1.02) 710ns × (1.00,1.01) +5.97% (p=0.000) This CL is a bit larger than I would like, but the compiler, linker, runtime, and package reflect all need to be in sync about the format of these programs, so there is no easy way to split this into independent changes (at least while keeping the build working at each change). Fixes #9625. Fixes #10524. Change-Id: I9e3e20d6097099d0f8532d1cb5b1af528804989a Reviewed-on: https://go-review.googlesource.com/9888 Reviewed-by: Austin Clements <austin@google.com> Run-TryBot: Russ Cox <rsc@golang.org>
2015-05-08 01:43:18 -04:00
if strings.HasPrefix(s.Name, "runtime.gcbits.") {
gcmasks[s.Value] = readelfsymboldata(f, &s)
}
if s.Name == "go.link.abihashbytes" {
hash = readelfsymboldata(f, &s)
}
if elf.ST_BIND(s.Info) != elf.STB_GLOBAL {
continue
}
lsym := Linklookup(Ctxt, s.Name, 0)
if lsym.Type != 0 && lsym.Dupok == 0 {
Diag(
"Found duplicate symbol %s reading from %s, first found in %s",
s.Name, shlib, lsym.File)
}
lsym.Type = obj.SDYNIMPORT
lsym.ElfType = elf.ST_TYPE(s.Info)
lsym.File = libpath
if strings.HasPrefix(lsym.Name, "type.") {
if f.Sections[s.Section].Type == elf.SHT_PROGBITS {
lsym.P = readelfsymboldata(f, &s)
}
if !strings.HasPrefix(lsym.Name, "type..") {
types = append(types, lsym)
}
}
}
for _, t := range types {
if decodetype_noptr(t) != 0 || decodetype_usegcprog(t) != 0 {
continue
}
addr := decodetype_gcprog_shlib(t)
tgcmask, ok := gcmasks[addr]
if !ok {
Diag("bits not found for %s at %d", t.Name, addr)
}
t.gcmask = tgcmask
}
// We might have overwritten some functions above (this tends to happen for the
// autogenerated type equality/hashing functions) and we don't want to generated
// pcln table entries for these any more so unstitch them from the Textp linked
// list.
var last *LSym
for s := Ctxt.Textp; s != nil; s = s.Next {
if s.Type == obj.SDYNIMPORT {
continue
}
if last == nil {
Ctxt.Textp = s
} else {
last.Next = s
}
last = s
}
if last == nil {
Ctxt.Textp = nil
Ctxt.Etextp = nil
} else {
last.Next = nil
Ctxt.Etextp = last
}
Ctxt.Shlibs = append(Ctxt.Shlibs, Shlib{Path: libpath, Hash: hash})
}
func mywhatsys() {
goroot = obj.Getgoroot()
goos = obj.Getgoos()
goarch = obj.Getgoarch()
if !strings.HasPrefix(goarch, Thestring) {
log.Fatalf("cannot use %cc with GOARCH=%s", Thearch.Thechar, goarch)
}
}
// Copied from ../gc/subr.c:/^pathtoprefix; must stay in sync.
/*
* Convert raw string to the prefix that will be used in the symbol table.
* Invalid bytes turn into %xx. Right now the only bytes that need
* escaping are %, ., and ", but we escape all control characters too.
*
* If you edit this, edit ../gc/subr.c:/^pathtoprefix too.
* If you edit this, edit ../../debug/goobj/read.go:/importPathToPrefix too.
*/
func pathtoprefix(s string) string {
slash := strings.LastIndex(s, "/")
for i := 0; i < len(s); i++ {
c := s[i]
if c <= ' ' || i >= slash && c == '.' || c == '%' || c == '"' || c >= 0x7F {
var buf bytes.Buffer
for i := 0; i < len(s); i++ {
c := s[i]
if c <= ' ' || i >= slash && c == '.' || c == '%' || c == '"' || c >= 0x7F {
fmt.Fprintf(&buf, "%%%02x", c)
continue
}
buf.WriteByte(c)
}
return buf.String()
}
}
return s
}
func addsection(seg *Segment, name string, rwx int) *Section {
var l **Section
for l = &seg.Sect; *l != nil; l = &(*l).Next {
}
sect := new(Section)
sect.Rwx = uint8(rwx)
sect.Name = name
sect.Seg = seg
sect.Align = int32(Thearch.Ptrsize) // everything is at least pointer-aligned
*l = sect
return sect
}
func Le16(b []byte) uint16 {
return uint16(b[0]) | uint16(b[1])<<8
}
func Le32(b []byte) uint32 {
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
}
func Le64(b []byte) uint64 {
return uint64(Le32(b)) | uint64(Le32(b[4:]))<<32
}
func Be16(b []byte) uint16 {
return uint16(b[0])<<8 | uint16(b[1])
}
func Be32(b []byte) uint32 {
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])
}
func Be64(b []byte) uint64 {
return uint64(Be32(b))<<32 | uint64(Be32(b[4:]))
}
type Chain struct {
sym *LSym
up *Chain
limit int // limit on entry to sym
}
var (
morestack *LSym
newstack *LSym
)
// TODO: Record enough information in new object files to
// allow stack checks here.
func haslinkregister() bool {
return Thearch.Thechar == '5' || Thearch.Thechar == '9' || Thearch.Thechar == '7'
}
func callsize() int {
if haslinkregister() {
return 0
}
return Thearch.Regsize
}
func dostkcheck() {
var ch Chain
morestack = Linklookup(Ctxt, "runtime.morestack", 0)
newstack = Linklookup(Ctxt, "runtime.newstack", 0)
// Every splitting function ensures that there are at least StackLimit
// bytes available below SP when the splitting prologue finishes.
// If the splitting function calls F, then F begins execution with
// at least StackLimit - callsize() bytes available.
// Check that every function behaves correctly with this amount
// of stack, following direct calls in order to piece together chains
// of non-splitting functions.
ch.up = nil
ch.limit = obj.StackLimit - callsize()
// Check every function, but do the nosplit functions in a first pass,
// to make the printed failure chains as short as possible.
for s := Ctxt.Textp; s != nil; s = s.Next {
// runtime.racesymbolizethunk is called from gcc-compiled C
// code running on the operating system thread stack.
// It uses more than the usual amount of stack but that's okay.
if s.Name == "runtime.racesymbolizethunk" {
continue
}
if s.Nosplit != 0 {
Ctxt.Cursym = s
ch.sym = s
stkcheck(&ch, 0)
}
}
for s := Ctxt.Textp; s != nil; s = s.Next {
if s.Nosplit == 0 {
Ctxt.Cursym = s
ch.sym = s
stkcheck(&ch, 0)
}
}
}
func stkcheck(up *Chain, depth int) int {
limit := up.limit
s := up.sym
// Don't duplicate work: only need to consider each
// function at top of safe zone once.
if limit == obj.StackLimit-callsize() {
if s.Stkcheck != 0 {
return 0
}
s.Stkcheck = 1
}
if depth > 100 {
Diag("nosplit stack check too deep")
stkbroke(up, 0)
return -1
}
if s.External != 0 || s.Pcln == nil {
// external function.
// should never be called directly.
// only diagnose the direct caller.
// TODO(mwhudson): actually think about this.
if depth == 1 && s.Type != obj.SXREF && !DynlinkingGo() {
Diag("call to external function %s", s.Name)
}
return -1
}
if limit < 0 {
stkbroke(up, limit)
return -1
}
// morestack looks like it calls functions,
// but it switches the stack pointer first.
if s == morestack {
return 0
}
var ch Chain
ch.up = up
// Walk through sp adjustments in function, consuming relocs.
ri := 0
endr := len(s.R)
var ch1 Chain
var pcsp Pciter
var r *Reloc
for pciterinit(Ctxt, &pcsp, &s.Pcln.Pcsp); pcsp.done == 0; pciternext(&pcsp) {
// pcsp.value is in effect for [pcsp.pc, pcsp.nextpc).
// Check stack size in effect for this span.
if int32(limit)-pcsp.value < 0 {
stkbroke(up, int(int32(limit)-pcsp.value))
return -1
}
// Process calls in this span.
for ; ri < endr && uint32(s.R[ri].Off) < pcsp.nextpc; ri++ {
r = &s.R[ri]
switch r.Type {
// Direct call.
case obj.R_CALL, obj.R_CALLARM, obj.R_CALLARM64, obj.R_CALLPOWER:
ch.limit = int(int32(limit) - pcsp.value - int32(callsize()))
ch.sym = r.Sym
if stkcheck(&ch, depth+1) < 0 {
return -1
}
// If this is a call to morestack, we've just raised our limit back
// to StackLimit beyond the frame size.
if strings.HasPrefix(r.Sym.Name, "runtime.morestack") {
limit = int(obj.StackLimit + s.Locals)
if haslinkregister() {
limit += Thearch.Regsize
}
}
// Indirect call. Assume it is a call to a splitting function,
// so we have to make sure it can call morestack.
// Arrange the data structures to report both calls, so that
// if there is an error, stkprint shows all the steps involved.
case obj.R_CALLIND:
ch.limit = int(int32(limit) - pcsp.value - int32(callsize()))
ch.sym = nil
ch1.limit = ch.limit - callsize() // for morestack in called prologue
ch1.up = &ch
ch1.sym = morestack
if stkcheck(&ch1, depth+2) < 0 {
return -1
}
}
}
}
return 0
}
func stkbroke(ch *Chain, limit int) {
Diag("nosplit stack overflow")
stkprint(ch, limit)
}
func stkprint(ch *Chain, limit int) {
var name string
if ch.sym != nil {
name = ch.sym.Name
} else {
name = "function pointer"
}
if ch.up == nil {
// top of chain. ch->sym != nil.
if ch.sym.Nosplit != 0 {
fmt.Printf("\t%d\tassumed on entry to %s\n", ch.limit, name)
} else {
fmt.Printf("\t%d\tguaranteed after split check in %s\n", ch.limit, name)
}
} else {
stkprint(ch.up, ch.limit+callsize())
if !haslinkregister() {
fmt.Printf("\t%d\ton entry to %s\n", ch.limit, name)
}
}
if ch.limit != limit {
fmt.Printf("\t%d\tafter %s uses %d\n", limit, name, ch.limit-limit)
}
}
func Yconv(s *LSym) string {
var fp string
if s == nil {
fp += fmt.Sprintf("<nil>")
} else {
fmt_ := ""
fmt_ += fmt.Sprintf("%s @0x%08x [%d]", s.Name, int64(s.Value), int64(s.Size))
for i := 0; int64(i) < s.Size; i++ {
if i%8 == 0 {
fmt_ += fmt.Sprintf("\n\t0x%04x ", i)
}
fmt_ += fmt.Sprintf("%02x ", s.P[i])
}
fmt_ += fmt.Sprintf("\n")
for i := 0; i < len(s.R); i++ {
fmt_ += fmt.Sprintf("\t0x%04x[%x] %d %s[%x]\n", s.R[i].Off, s.R[i].Siz, s.R[i].Type, s.R[i].Sym.Name, int64(s.R[i].Add))
}
str := fmt_
fp += str
}
return fp
}
func Cflush() {
if err := coutbuf.Writer.Flush(); err != nil {
Exitf("flushing %s: %v", coutbuf.f.Name(), err)
}
}
func Cpos() int64 {
off, err := coutbuf.f.Seek(0, 1)
if err != nil {
Exitf("seeking in output [0, 1]: %v", err)
}
return off + int64(coutbuf.Buffered())
}
func Cseek(p int64) {
Cflush()
if _, err := coutbuf.f.Seek(p, 0); err != nil {
Exitf("seeking in output [0, 1]: %v", err)
}
}
func Cwrite(p []byte) {
coutbuf.Write(p)
}
func Cput(c uint8) {
coutbuf.WriteByte(c)
}
func usage() {
fmt.Fprintf(os.Stderr, "usage: %cl [options] obj.%c\n", Thearch.Thechar, Thearch.Thechar)
obj.Flagprint(2)
Exit(2)
}
func setheadtype(s string) {
h := headtype(s)
if h < 0 {
Exitf("unknown header type -H %s", s)
}
headstring = s
HEADTYPE = int32(headtype(s))
}
func setinterp(s string) {
Debug['I'] = 1 // denote cmdline interpreter override
interpreter = s
}
func doversion() {
Exitf("version %s", obj.Getgoversion())
}
func genasmsym(put func(*LSym, string, int, int64, int64, int, *LSym)) {
// These symbols won't show up in the first loop below because we
// skip STEXT symbols. Normal STEXT symbols are emitted by walking textp.
s := Linklookup(Ctxt, "runtime.text", 0)
if s.Type == obj.STEXT {
put(s, s.Name, 'T', s.Value, s.Size, int(s.Version), nil)
}
s = Linklookup(Ctxt, "runtime.etext", 0)
if s.Type == obj.STEXT {
put(s, s.Name, 'T', s.Value, s.Size, int(s.Version), nil)
}
for s := Ctxt.Allsym; s != nil; s = s.Allsym {
if s.Hide != 0 || (s.Name[0] == '.' && s.Version == 0 && s.Name != ".rathole") {
continue
}
switch s.Type & obj.SMASK {
case obj.SCONST,
obj.SRODATA,
obj.SSYMTAB,
obj.SPCLNTAB,
obj.SINITARR,
obj.SDATA,
obj.SNOPTRDATA,
obj.SELFROSECT,
obj.SMACHOGOT,
obj.STYPE,
obj.SSTRING,
obj.SGOSTRING,
obj.SGOFUNC,
obj.SWINDOWS:
if !s.Reachable {
continue
}
put(s, s.Name, 'D', Symaddr(s), s.Size, int(s.Version), s.Gotype)
case obj.SBSS, obj.SNOPTRBSS:
if !s.Reachable {
continue
}
if len(s.P) > 0 {
Diag("%s should not be bss (size=%d type=%d special=%d)", s.Name, int(len(s.P)), s.Type, s.Special)
}
put(s, s.Name, 'B', Symaddr(s), s.Size, int(s.Version), s.Gotype)
case obj.SFILE:
put(nil, s.Name, 'f', s.Value, 0, int(s.Version), nil)
case obj.SHOSTOBJ:
if HEADTYPE == obj.Hwindows || Iself {
put(s, s.Name, 'U', s.Value, 0, int(s.Version), nil)
}
case obj.SDYNIMPORT:
if !s.Reachable {
continue
}
put(s, s.Extname, 'U', 0, 0, int(s.Version), nil)
case obj.STLSBSS:
if Linkmode == LinkExternal && HEADTYPE != obj.Hopenbsd {
var type_ int
if goos == "android" {
type_ = 'B'
} else {
type_ = 't'
}
put(s, s.Name, type_, Symaddr(s), s.Size, int(s.Version), s.Gotype)
}
}
}
var a *Auto
var off int32
for s := Ctxt.Textp; s != nil; s = s.Next {
put(s, s.Name, 'T', s.Value, s.Size, int(s.Version), s.Gotype)
// NOTE(ality): acid can't produce a stack trace without .frame symbols
put(nil, ".frame", 'm', int64(s.Locals)+int64(Thearch.Ptrsize), 0, 0, nil)
for a = s.Autom; a != nil; a = a.Link {
// Emit a or p according to actual offset, even if label is wrong.
// This avoids negative offsets, which cannot be encoded.
if a.Name != obj.A_AUTO && a.Name != obj.A_PARAM {
continue
}
// compute offset relative to FP
if a.Name == obj.A_PARAM {
off = a.Aoffset
} else {
off = a.Aoffset - int32(Thearch.Ptrsize)
}
// FP
if off >= 0 {
put(nil, a.Asym.Name, 'p', int64(off), 0, 0, a.Gotype)
continue
}
// SP
if off <= int32(-Thearch.Ptrsize) {
put(nil, a.Asym.Name, 'a', -(int64(off) + int64(Thearch.Ptrsize)), 0, 0, a.Gotype)
continue
}
}
}
// Otherwise, off is addressing the saved program counter.
// Something underhanded is going on. Say nothing.
if Debug['v'] != 0 || Debug['n'] != 0 {
fmt.Fprintf(&Bso, "%5.2f symsize = %d\n", obj.Cputime(), uint32(Symsize))
}
Bso.Flush()
}
func Symaddr(s *LSym) int64 {
if !s.Reachable {
Diag("unreachable symbol in symaddr - %s", s.Name)
}
return s.Value
}
func xdefine(p string, t int, v int64) {
s := Linklookup(Ctxt, p, 0)
s.Type = int16(t)
s.Value = v
s.Reachable = true
s.Special = 1
s.Local = true
}
func datoff(addr int64) int64 {
if uint64(addr) >= Segdata.Vaddr {
return int64(uint64(addr) - Segdata.Vaddr + Segdata.Fileoff)
}
if uint64(addr) >= Segtext.Vaddr {
return int64(uint64(addr) - Segtext.Vaddr + Segtext.Fileoff)
}
Diag("datoff %#x", addr)
return 0
}
func Entryvalue() int64 {
a := INITENTRY
if a[0] >= '0' && a[0] <= '9' {
return atolwhex(a)
}
s := Linklookup(Ctxt, a, 0)
if s.Type == 0 {
return INITTEXT
}
if s.Type != obj.STEXT {
Diag("entry not text: %s", s.Name)
}
return s.Value
}
func undefsym(s *LSym) {
var r *Reloc
Ctxt.Cursym = s
for i := 0; i < len(s.R); i++ {
r = &s.R[i]
if r.Sym == nil { // happens for some external ARM relocs
continue
}
if r.Sym.Type == obj.Sxxx || r.Sym.Type == obj.SXREF {
Diag("undefined: %s", r.Sym.Name)
}
if !r.Sym.Reachable {
Diag("use of unreachable symbol: %s", r.Sym.Name)
}
}
}
func undef() {
for s := Ctxt.Textp; s != nil; s = s.Next {
undefsym(s)
}
for s := datap; s != nil; s = s.Next {
undefsym(s)
}
if nerrors > 0 {
errorexit()
}
}
func callgraph() {
if Debug['c'] == 0 {
return
}
var i int
var r *Reloc
for s := Ctxt.Textp; s != nil; s = s.Next {
for i = 0; i < len(s.R); i++ {
r = &s.R[i]
if r.Sym == nil {
continue
}
if (r.Type == obj.R_CALL || r.Type == obj.R_CALLARM || r.Type == obj.R_CALLPOWER) && r.Sym.Type == obj.STEXT {
fmt.Fprintf(&Bso, "%s calls %s\n", s.Name, r.Sym.Name)
}
}
}
}
func Diag(format string, args ...interface{}) {
tn := ""
sep := ""
if Ctxt.Cursym != nil {
tn = Ctxt.Cursym.Name
sep = ": "
}
fmt.Printf("%s%s%s\n", tn, sep, fmt.Sprintf(format, args...))
nerrors++
if nerrors > 20 {
Exitf("too many errors")
}
}
func checkgo() {
if Debug['C'] == 0 {
return
}
// TODO(rsc,khr): Eventually we want to get to no Go-called C functions at all,
// which would simplify this logic quite a bit.
// Mark every Go-called C function with cfunc=2, recursively.
var changed int
var i int
var r *Reloc
var s *LSym
for {
changed = 0
for s = Ctxt.Textp; s != nil; s = s.Next {
if s.Cfunc == 0 || (s.Cfunc == 2 && s.Nosplit != 0) {
for i = 0; i < len(s.R); i++ {
r = &s.R[i]
if r.Sym == nil {
continue
}
if (r.Type == obj.R_CALL || r.Type == obj.R_CALLARM) && r.Sym.Type == obj.STEXT {
if r.Sym.Cfunc == 1 {
changed = 1
r.Sym.Cfunc = 2
}
}
}
}
}
if changed == 0 {
break
}
}
// Complain about Go-called C functions that can split the stack
// (that can be preempted for garbage collection or trigger a stack copy).
for s := Ctxt.Textp; s != nil; s = s.Next {
if s.Cfunc == 0 || (s.Cfunc == 2 && s.Nosplit != 0) {
for i = 0; i < len(s.R); i++ {
r = &s.R[i]
if r.Sym == nil {
continue
}
if (r.Type == obj.R_CALL || r.Type == obj.R_CALLARM) && r.Sym.Type == obj.STEXT {
if s.Cfunc == 0 && r.Sym.Cfunc == 2 && r.Sym.Nosplit == 0 {
fmt.Printf("Go %s calls C %s\n", s.Name, r.Sym.Name)
} else if s.Cfunc == 2 && s.Nosplit != 0 && r.Sym.Nosplit == 0 {
fmt.Printf("Go calls C %s calls %s\n", s.Name, r.Sym.Name)
}
}
}
}
}
}
func Rnd(v int64, r int64) int64 {
if r <= 0 {
return v
}
v += r - 1
c := v % r
if c < 0 {
c += r
}
v -= c
return v
}