Commit f028b9f9 authored by David Crawshaw's avatar David Crawshaw

cmd/link, etc: store typelinks as offsets

This is the first in a series of CLs to replace the use of pointers
in binary read-only data with offsets.

In standard Go binaries these CLs have a small effect, shrinking
8-byte pointers to 4-bytes. In position-independent code, it also
saves the dynamic relocation for the pointer. This has a significant
effect on the binary size when building as PIE, c-archive, or
c-shared.

darwin/amd64:
	cmd/go: -12KB (0.1%)
	jujud:  -82KB (0.1%)

linux/amd64 PIE:
	cmd/go:  -86KB (0.7%)
	jujud:  -569KB (0.7%)

For #6853.

Change-Id: Iad5625bbeba58dabfd4d334dbee3fcbfe04b2dcf
Reviewed-on: https://go-review.googlesource.com/21284Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
Run-TryBot: David Crawshaw <crawshaw@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent 23cbfa25
...@@ -171,8 +171,6 @@ var msanpkg *Pkg // package runtime/msan ...@@ -171,8 +171,6 @@ var msanpkg *Pkg // package runtime/msan
var typepkg *Pkg // fake package for runtime type info (headers) var typepkg *Pkg // fake package for runtime type info (headers)
var typelinkpkg *Pkg // fake package for runtime type info (data)
var unsafepkg *Pkg // package unsafe var unsafepkg *Pkg // package unsafe
var trackpkg *Pkg // fake package for field tracking var trackpkg *Pkg // fake package for field tracking
......
...@@ -126,10 +126,6 @@ func Main() { ...@@ -126,10 +126,6 @@ func Main() {
itabpkg.Name = "go.itab" itabpkg.Name = "go.itab"
itabpkg.Prefix = "go.itab" // not go%2eitab itabpkg.Prefix = "go.itab" // not go%2eitab
typelinkpkg = mkpkg("go.typelink")
typelinkpkg.Name = "go.typelink"
typelinkpkg.Prefix = "go.typelink" // not go%2etypelink
itablinkpkg = mkpkg("go.itablink") itablinkpkg = mkpkg("go.itablink")
itablinkpkg.Name = "go.itablink" itablinkpkg.Name = "go.itablink"
itablinkpkg.Prefix = "go.itablink" // not go%2eitablink itablinkpkg.Prefix = "go.itablink" // not go%2eitablink
......
...@@ -321,6 +321,12 @@ func dsymptrLSym(s *obj.LSym, off int, x *obj.LSym, xoff int) int { ...@@ -321,6 +321,12 @@ func dsymptrLSym(s *obj.LSym, off int, x *obj.LSym, xoff int) int {
return off return off
} }
func dsymptrOffLSym(s *obj.LSym, off int, x *obj.LSym, xoff int) int {
s.WriteOff(Ctxt, int64(off), x, int64(xoff))
off += 4
return off
}
func gdata(nam *Node, nr *Node, wid int) { func gdata(nam *Node, nr *Node, wid int) {
if nam.Op != ONAME { if nam.Op != ONAME {
Fatalf("gdata nam op %v", opnames[nam.Op]) Fatalf("gdata nam op %v", opnames[nam.Op])
......
...@@ -879,7 +879,7 @@ func tracksym(t *Type, f *Field) *Sym { ...@@ -879,7 +879,7 @@ func tracksym(t *Type, f *Field) *Sym {
return Pkglookup(Tconv(t, FmtLeft)+"."+f.Sym.Name, trackpkg) return Pkglookup(Tconv(t, FmtLeft)+"."+f.Sym.Name, trackpkg)
} }
func typelinksym(t *Type) *Sym { func typelinkLSym(t *Type) *obj.LSym {
// %-uT is what the generated Type's string field says. // %-uT is what the generated Type's string field says.
// It uses (ambiguous) package names instead of import paths. // It uses (ambiguous) package names instead of import paths.
// %-T is the complete, unambiguous type name. // %-T is the complete, unambiguous type name.
...@@ -889,13 +889,8 @@ func typelinksym(t *Type) *Sym { ...@@ -889,13 +889,8 @@ func typelinksym(t *Type) *Sym {
// ensure the types appear sorted by their string field. The // ensure the types appear sorted by their string field. The
// names are a little long but they are discarded by the linker // names are a little long but they are discarded by the linker
// and do not end up in the symbol table of the final binary. // and do not end up in the symbol table of the final binary.
p := Tconv(t, FmtLeft|FmtUnsigned) + "\t" + Tconv(t, FmtLeft) name := "go.typelink." + Tconv(t, FmtLeft|FmtUnsigned) + "\t" + Tconv(t, FmtLeft)
return obj.Linklookup(Ctxt, name, 0)
s := Pkglookup(p, typelinkpkg)
//print("typelinksym: %s -> %+S\n", p, s);
return s
} }
func typesymprefix(prefix string, t *Type) *Sym { func typesymprefix(prefix string, t *Type) *Sym {
...@@ -1298,9 +1293,9 @@ ok: ...@@ -1298,9 +1293,9 @@ ok:
if t.Sym == nil { if t.Sym == nil {
switch t.Etype { switch t.Etype {
case TPTR32, TPTR64, TARRAY, TCHAN, TFUNC, TMAP, TSTRUCT: case TPTR32, TPTR64, TARRAY, TCHAN, TFUNC, TMAP, TSTRUCT:
slink := typelinksym(t) slink := typelinkLSym(t)
dsymptr(slink, 0, s, 0) dsymptrOffLSym(slink, 0, Linksym(s), 0)
ggloblsym(slink, int32(Widthptr), int16(dupok|obj.RODATA)) ggloblLSym(slink, 4, int16(dupok|obj.RODATA))
} }
} }
......
...@@ -111,17 +111,36 @@ func (s *LSym) WriteInt(ctxt *Link, off int64, siz int, i int64) { ...@@ -111,17 +111,36 @@ func (s *LSym) WriteInt(ctxt *Link, off int64, siz int, i int64) {
// rsym and roff specify the relocation for the address. // rsym and roff specify the relocation for the address.
func (s *LSym) WriteAddr(ctxt *Link, off int64, siz int, rsym *LSym, roff int64) { func (s *LSym) WriteAddr(ctxt *Link, off int64, siz int, rsym *LSym, roff int64) {
if siz != ctxt.Arch.PtrSize { if siz != ctxt.Arch.PtrSize {
ctxt.Diag("WriteAddr: bad address size: %d", siz) ctxt.Diag("WriteAddr: bad address size %d in %s", siz, s.Name)
} }
s.prepwrite(ctxt, off, siz) s.prepwrite(ctxt, off, siz)
r := Addrel(s) r := Addrel(s)
r.Off = int32(off) r.Off = int32(off)
if int64(r.Off) != off {
ctxt.Diag("WriteAddr: off overflow %d in %s", off, s.Name)
}
r.Siz = uint8(siz) r.Siz = uint8(siz)
r.Sym = rsym r.Sym = rsym
r.Type = R_ADDR r.Type = R_ADDR
r.Add = roff r.Add = roff
} }
// WriteOff writes a 4 byte offset to rsym+roff into s at offset off.
// After linking the 4 bytes stored at s+off will be
// rsym+roff-(start of section that s is in).
func (s *LSym) WriteOff(ctxt *Link, off int64, rsym *LSym, roff int64) {
s.prepwrite(ctxt, off, 4)
r := Addrel(s)
r.Off = int32(off)
if int64(r.Off) != off {
ctxt.Diag("WriteOff: off overflow %d in %s", off, s.Name)
}
r.Siz = 4
r.Sym = rsym
r.Type = R_ADDROFF
r.Add = roff
}
// WriteString writes a string of size siz into s at offset off. // WriteString writes a string of size siz into s at offset off.
func (s *LSym) WriteString(ctxt *Link, off int64, siz int, str string) { func (s *LSym) WriteString(ctxt *Link, off int64, siz int, str string) {
if siz < len(str) { if siz < len(str) {
......
...@@ -457,6 +457,9 @@ const ( ...@@ -457,6 +457,9 @@ const (
// R_ADDRMIPS (only used on mips64) resolves to a 32-bit external address, // R_ADDRMIPS (only used on mips64) resolves to a 32-bit external address,
// by loading the address into a register with two instructions (lui, ori). // by loading the address into a register with two instructions (lui, ori).
R_ADDRMIPS R_ADDRMIPS
// R_ADDROFF resolves to an offset from the beginning of the section holding
// the data being relocated to the referenced symbol.
R_ADDROFF
R_SIZE R_SIZE
R_CALL R_CALL
R_CALLARM R_CALLARM
......
...@@ -525,6 +525,9 @@ func relocsym(s *LSym) { ...@@ -525,6 +525,9 @@ func relocsym(s *LSym) {
} }
o = Symaddr(r.Sym) + r.Add - int64(r.Sym.Sect.Vaddr) o = Symaddr(r.Sym) + r.Add - int64(r.Sym.Sect.Vaddr)
case obj.R_ADDROFF:
o = Symaddr(r.Sym) - int64(r.Sym.Sect.Vaddr) + r.Add
// r->sym can be null when CALL $(constant) is transformed from absolute PC to relative PC call. // r->sym can be null when CALL $(constant) is transformed from absolute PC to relative PC call.
case obj.R_CALL, obj.R_GOTPCREL, obj.R_PCREL: case obj.R_CALL, obj.R_GOTPCREL, obj.R_PCREL:
if Linkmode == LinkExternal && r.Sym != nil && r.Sym.Type != obj.SCONST && (r.Sym.Sect != Ctxt.Cursym.Sect || r.Type == obj.R_GOTPCREL) { if Linkmode == LinkExternal && r.Sym != nil && r.Sym.Type != obj.SCONST && (r.Sym.Sect != Ctxt.Cursym.Sect || r.Type == obj.R_GOTPCREL) {
...@@ -1599,6 +1602,10 @@ func dodata() { ...@@ -1599,6 +1602,10 @@ func dodata() {
sect.Vaddr = 0 sect.Vaddr = 0
Linklookup(Ctxt, "runtime.rodata", 0).Sect = sect Linklookup(Ctxt, "runtime.rodata", 0).Sect = sect
Linklookup(Ctxt, "runtime.erodata", 0).Sect = sect Linklookup(Ctxt, "runtime.erodata", 0).Sect = sect
if !UseRelro() {
Linklookup(Ctxt, "runtime.types", 0).Sect = sect
Linklookup(Ctxt, "runtime.etypes", 0).Sect = sect
}
for ; s != nil && s.Type < obj.STYPERELRO; s = s.Next { for ; s != nil && s.Type < obj.STYPERELRO; s = s.Next {
datsize = aligndatsize(datsize, s) datsize = aligndatsize(datsize, s)
s.Sect = sect s.Sect = sect
...@@ -1631,6 +1638,8 @@ func dodata() { ...@@ -1631,6 +1638,8 @@ func dodata() {
sect.Align = maxalign(s, obj.STYPELINK-1) sect.Align = maxalign(s, obj.STYPELINK-1)
datsize = Rnd(datsize, int64(sect.Align)) datsize = Rnd(datsize, int64(sect.Align))
sect.Vaddr = 0 sect.Vaddr = 0
Linklookup(Ctxt, "runtime.types", 0).Sect = sect
Linklookup(Ctxt, "runtime.etypes", 0).Sect = sect
for ; s != nil && s.Type < obj.STYPELINK; s = s.Next { for ; s != nil && s.Type < obj.STYPELINK; s = s.Next {
datsize = aligndatsize(datsize, s) datsize = aligndatsize(datsize, s)
if s.Outer != nil && s.Outer.Sect != nil && s.Outer.Sect != sect { if s.Outer != nil && s.Outer.Sect != nil && s.Outer.Sect != sect {
...@@ -1970,10 +1979,12 @@ func address() { ...@@ -1970,10 +1979,12 @@ func address() {
} else { } else {
rodata = text.Next rodata = text.Next
} }
var relrodata *Section
typelink := rodata.Next typelink := rodata.Next
if UseRelro() { if UseRelro() {
// There is another section (.data.rel.ro) when building a shared // There is another section (.data.rel.ro) when building a shared
// object on elf systems. // object on elf systems.
relrodata = typelink
typelink = typelink.Next typelink = typelink.Next
} }
itablink := typelink.Next itablink := typelink.Next
...@@ -2007,6 +2018,11 @@ func address() { ...@@ -2007,6 +2018,11 @@ func address() {
s.Value = int64(sectSym.Sect.Vaddr + 16) s.Value = int64(sectSym.Sect.Vaddr + 16)
} }
types := relrodata
if types == nil {
types = rodata
}
xdefine("runtime.text", obj.STEXT, int64(text.Vaddr)) xdefine("runtime.text", obj.STEXT, int64(text.Vaddr))
xdefine("runtime.etext", obj.STEXT, int64(text.Vaddr+text.Length)) xdefine("runtime.etext", obj.STEXT, int64(text.Vaddr+text.Length))
if HEADTYPE == obj.Hwindows { if HEADTYPE == obj.Hwindows {
...@@ -2014,6 +2030,8 @@ func address() { ...@@ -2014,6 +2030,8 @@ func address() {
} }
xdefine("runtime.rodata", obj.SRODATA, int64(rodata.Vaddr)) xdefine("runtime.rodata", obj.SRODATA, int64(rodata.Vaddr))
xdefine("runtime.erodata", obj.SRODATA, int64(rodata.Vaddr+rodata.Length)) xdefine("runtime.erodata", obj.SRODATA, int64(rodata.Vaddr+rodata.Length))
xdefine("runtime.types", obj.SRODATA, int64(types.Vaddr))
xdefine("runtime.etypes", obj.SRODATA, int64(types.Vaddr+types.Length))
xdefine("runtime.typelink", obj.SRODATA, int64(typelink.Vaddr)) xdefine("runtime.typelink", obj.SRODATA, int64(typelink.Vaddr))
xdefine("runtime.etypelink", obj.SRODATA, int64(typelink.Vaddr+typelink.Length)) xdefine("runtime.etypelink", obj.SRODATA, int64(typelink.Vaddr+typelink.Length))
xdefine("runtime.itablink", obj.SRODATA, int64(itablink.Vaddr)) xdefine("runtime.itablink", obj.SRODATA, int64(itablink.Vaddr))
......
...@@ -329,6 +329,8 @@ func symtab() { ...@@ -329,6 +329,8 @@ func symtab() {
xdefine("runtime.eitablink", obj.SRODATA, 0) xdefine("runtime.eitablink", obj.SRODATA, 0)
xdefine("runtime.rodata", obj.SRODATA, 0) xdefine("runtime.rodata", obj.SRODATA, 0)
xdefine("runtime.erodata", obj.SRODATA, 0) xdefine("runtime.erodata", obj.SRODATA, 0)
xdefine("runtime.types", obj.SRODATA, 0)
xdefine("runtime.etypes", obj.SRODATA, 0)
xdefine("runtime.noptrdata", obj.SNOPTRDATA, 0) xdefine("runtime.noptrdata", obj.SNOPTRDATA, 0)
xdefine("runtime.enoptrdata", obj.SNOPTRDATA, 0) xdefine("runtime.enoptrdata", obj.SNOPTRDATA, 0)
xdefine("runtime.data", obj.SDATA, 0) xdefine("runtime.data", obj.SDATA, 0)
...@@ -537,6 +539,8 @@ func symtab() { ...@@ -537,6 +539,8 @@ func symtab() {
Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.end", 0)) Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.end", 0))
Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.gcdata", 0)) Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.gcdata", 0))
Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.gcbss", 0)) Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.gcbss", 0))
Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.types", 0))
Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.etypes", 0))
// The typelinks slice // The typelinks slice
Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.typelink", 0)) Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.typelink", 0))
adduint(Ctxt, moduledata, uint64(ntypelinks)) adduint(Ctxt, moduledata, uint64(ntypelinks))
......
...@@ -46,9 +46,11 @@ func FuncLayout(t Type, rcvr Type) (frametype Type, argSize, retOffset uintptr, ...@@ -46,9 +46,11 @@ func FuncLayout(t Type, rcvr Type) (frametype Type, argSize, retOffset uintptr,
func TypeLinks() []string { func TypeLinks() []string {
var r []string var r []string
for _, m := range typelinks() { sections, offset := typelinks()
for _, t := range m { for i, offs := range offset {
r = append(r, t.string) rodata := sections[i]
for _, off := range offs {
r = append(r, rtypeOff(rodata, off).string)
} }
} }
return r return r
......
...@@ -1558,30 +1558,48 @@ func haveIdenticalUnderlyingType(T, V *rtype) bool { ...@@ -1558,30 +1558,48 @@ func haveIdenticalUnderlyingType(T, V *rtype) bool {
} }
// typelinks is implemented in package runtime. // typelinks is implemented in package runtime.
// It returns a slice of all the 'typelink' information in the binary, // It returns a slice of the sections in each module,
// which is to say a slice of known types, sorted by string. // and a slice of *rtype offsets in each module.
//
// The types in each module are sorted by string. That is, the first
// two linked types of the first module are:
//
// d0 := sections[0]
// t1 := (*rtype)(add(d0, offset[0][0]))
// t2 := (*rtype)(add(d0, offset[0][1]))
//
// and
//
// t1.string < t2.string
//
// Note that strings are not unique identifiers for types: // Note that strings are not unique identifiers for types:
// there can be more than one with a given string. // there can be more than one with a given string.
// Only types we might want to look up are included: // Only types we might want to look up are included:
// pointers, channels, maps, slices, and arrays. // pointers, channels, maps, slices, and arrays.
func typelinks() [][]*rtype func typelinks() (sections []unsafe.Pointer, offset [][]int32)
func rtypeOff(section unsafe.Pointer, off int32) *rtype {
return (*rtype)(add(section, uintptr(off)))
}
// typesByString returns the subslice of typelinks() whose elements have // typesByString returns the subslice of typelinks() whose elements have
// the given string representation. // the given string representation.
// It may be empty (no known types with that string) or may have // It may be empty (no known types with that string) or may have
// multiple elements (multiple types with that string). // multiple elements (multiple types with that string).
func typesByString(s string) []*rtype { func typesByString(s string) []*rtype {
typs := typelinks() sections, offset := typelinks()
var ret []*rtype var ret []*rtype
for _, typ := range typs { for offsI, offs := range offset {
section := sections[offsI]
// We are looking for the first index i where the string becomes >= s. // We are looking for the first index i where the string becomes >= s.
// This is a copy of sort.Search, with f(h) replaced by (*typ[h].string >= s). // This is a copy of sort.Search, with f(h) replaced by (*typ[h].string >= s).
i, j := 0, len(typ) i, j := 0, len(offs)
for i < j { for i < j {
h := i + (j-i)/2 // avoid overflow when computing h h := i + (j-i)/2 // avoid overflow when computing h
// i ≤ h < j // i ≤ h < j
if !(typ[h].string >= s) { if !(rtypeOff(section, offs[h]).string >= s) {
i = h + 1 // preserves f(i-1) == false i = h + 1 // preserves f(i-1) == false
} else { } else {
j = h // preserves f(j) == true j = h // preserves f(j) == true
...@@ -1592,17 +1610,12 @@ func typesByString(s string) []*rtype { ...@@ -1592,17 +1610,12 @@ func typesByString(s string) []*rtype {
// Having found the first, linear scan forward to find the last. // Having found the first, linear scan forward to find the last.
// We could do a second binary search, but the caller is going // We could do a second binary search, but the caller is going
// to do a linear scan anyway. // to do a linear scan anyway.
j = i for j := i; j < len(offs); j++ {
for j < len(typ) && typ[j].string == s { typ := rtypeOff(section, offs[j])
j++ if typ.string != s {
} break
if j > i {
if ret == nil {
ret = typ[i:j:j]
} else {
ret = append(ret, typ[i:j]...)
} }
ret = append(ret, typ)
} }
} }
return ret return ret
......
...@@ -477,10 +477,12 @@ func gomcache() *mcache { ...@@ -477,10 +477,12 @@ func gomcache() *mcache {
} }
//go:linkname reflect_typelinks reflect.typelinks //go:linkname reflect_typelinks reflect.typelinks
func reflect_typelinks() [][]*_type { func reflect_typelinks() ([]unsafe.Pointer, [][]int32) {
ret := [][]*_type{firstmoduledata.typelinks} sections := []unsafe.Pointer{unsafe.Pointer(firstmoduledata.types)}
ret := [][]int32{firstmoduledata.typelinks}
for datap := firstmoduledata.next; datap != nil; datap = datap.next { for datap := firstmoduledata.next; datap != nil; datap = datap.next {
sections = append(sections, unsafe.Pointer(datap.types))
ret = append(ret, datap.typelinks) ret = append(ret, datap.typelinks)
} }
return ret return sections, ret
} }
...@@ -127,8 +127,9 @@ type moduledata struct { ...@@ -127,8 +127,9 @@ type moduledata struct {
bss, ebss uintptr bss, ebss uintptr
noptrbss, enoptrbss uintptr noptrbss, enoptrbss uintptr
end, gcdata, gcbss uintptr end, gcdata, gcbss uintptr
types, etypes uintptr
typelinks []*_type typelinks []int32 // offsets from types
itablinks []*itab itablinks []*itab
modulename string modulename string
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment