Skip to content

Commit 02ab8d1

Browse files
committed
cmd/compile, runtime: emit only GC data for stack objects
Currently, for stack objects, the compiler emits metadata that includes the offset and type descriptor for each object. The type descriptor symbol has many fields, and it references many other symbols, e.g. field/element types, equality functions, names. Observe that what we actually need at runtime is only the GC metadata that are needed to scan the object, and the GC metadata are "leaf" symbols (which doesn't reference other symbols). Emit only the GC data instead. This avoids bringing live the type descriptor as well as things referenced by it (if it is not otherwise live). This reduces binary sizes: old new hello (println) 1187776 1133856 (-4.5%) hello (fmt) 1902448 1844416 (-3.1%) cmd/compile 22670432 22438576 (-1.0%) cmd/link 6346272 6225408 (-1.9%) No significant change in compiler speed. name old time/op new time/op delta Template 184ms ± 2% 186ms ± 5% ~ (p=0.905 n=9+10) Unicode 78.4ms ± 5% 76.3ms ± 3% -2.60% (p=0.009 n=10+10) GoTypes 1.09s ± 1% 1.08s ± 1% -0.73% (p=0.027 n=10+8) Compiler 85.6ms ± 3% 84.6ms ± 4% ~ (p=0.143 n=10+10) SSA 7.23s ± 1% 7.25s ± 1% ~ (p=0.780 n=10+9) Flate 116ms ± 5% 115ms ± 6% ~ (p=0.912 n=10+10) GoParser 201ms ± 4% 195ms ± 1% ~ (p=0.089 n=10+10) Reflect 455ms ± 1% 458ms ± 2% ~ (p=0.050 n=9+9) Tar 155ms ± 2% 155ms ± 3% ~ (p=0.436 n=10+10) XML 202ms ± 2% 200ms ± 2% ~ (p=0.053 n=10+9) Change-Id: I33a7f383d79afba1a482cac6da0cf5b7de9c0ec4 Reviewed-on: https://go-review.googlesource.com/c/go/+/313514 Trust: Cherry Zhang <[email protected]> Reviewed-by: Than McIntosh <[email protected]>
1 parent a9705e1 commit 02ab8d1

File tree

5 files changed

+128
-48
lines changed

5 files changed

+128
-48
lines changed

src/cmd/compile/internal/liveness/plive.go

+20-2
Original file line numberDiff line numberDiff line change
@@ -1431,8 +1431,26 @@ func (lv *liveness) emitStackObjects() *obj.LSym {
14311431
// Note: arguments and return values have non-negative Xoffset,
14321432
// in which case the offset is relative to argp.
14331433
// Locals have a negative Xoffset, in which case the offset is relative to varp.
1434-
off = objw.Uintptr(x, off, uint64(v.FrameOffset()))
1435-
off = objw.SymPtr(x, off, reflectdata.TypeLinksym(v.Type()), 0)
1434+
// We already limit the frame size, so the offset and the object size
1435+
// should not be too big.
1436+
frameOffset := v.FrameOffset()
1437+
if frameOffset != int64(int32(frameOffset)) {
1438+
base.Fatalf("frame offset too big: %v %d", v, frameOffset)
1439+
}
1440+
off = objw.Uint32(x, off, uint32(frameOffset))
1441+
1442+
t := v.Type()
1443+
sz := t.Width
1444+
if sz != int64(int32(sz)) {
1445+
base.Fatalf("stack object too big: %v of type %v, size %d", v, t, sz)
1446+
}
1447+
lsym, useGCProg, ptrdata := reflectdata.GCSym(t)
1448+
if useGCProg {
1449+
ptrdata = -ptrdata
1450+
}
1451+
off = objw.Uint32(x, off, uint32(sz))
1452+
off = objw.Uint32(x, off, uint32(ptrdata))
1453+
off = objw.SymPtr(x, off, lsym, 0)
14361454
}
14371455

14381456
if base.Flag.Live != 0 {

src/cmd/compile/internal/reflectdata/reflect.go

+52-12
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ var (
5252
signatset = make(map[*types.Type]struct{})
5353
signatslice []*types.Type
5454

55+
gcsymmu sync.Mutex // protects gcsymset and gcsymslice
56+
gcsymset = make(map[*types.Type]struct{})
57+
5558
itabs []itabEntry
5659
ptabs []*ir.Name
5760
)
@@ -694,7 +697,8 @@ func dcommontype(lsym *obj.LSym, t *types.Type) int {
694697
sptr = writeType(tptr)
695698
}
696699

697-
gcsym, useGCProg, ptrdata := dgcsym(t)
700+
gcsym, useGCProg, ptrdata := dgcsym(t, true)
701+
delete(gcsymset, t)
698702

699703
// ../../../../reflect/type.go:/^type.rtype
700704
// actual type structure
@@ -1321,6 +1325,16 @@ func WriteRuntimeTypes() {
13211325
}
13221326
}
13231327
}
1328+
1329+
// Emit GC data symbols.
1330+
gcsyms := make([]typeAndStr, 0, len(gcsymset))
1331+
for t := range gcsymset {
1332+
gcsyms = append(gcsyms, typeAndStr{t: t, short: types.TypeSymName(t), regular: t.String()})
1333+
}
1334+
sort.Sort(typesByString(gcsyms))
1335+
for _, ts := range gcsyms {
1336+
dgcsym(ts.t, true)
1337+
}
13241338
}
13251339

13261340
func WriteTabs() {
@@ -1490,29 +1504,46 @@ func (a typesByString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
14901504
//
14911505
const maxPtrmaskBytes = 2048
14921506

1493-
// dgcsym emits and returns a data symbol containing GC information for type t,
1494-
// along with a boolean reporting whether the UseGCProg bit should be set in
1495-
// the type kind, and the ptrdata field to record in the reflect type information.
1496-
func dgcsym(t *types.Type) (lsym *obj.LSym, useGCProg bool, ptrdata int64) {
1507+
// GCSym returns a data symbol containing GC information for type t, along
1508+
// with a boolean reporting whether the UseGCProg bit should be set in the
1509+
// type kind, and the ptrdata field to record in the reflect type information.
1510+
// GCSym may be called in concurrent backend, so it does not emit the symbol
1511+
// content.
1512+
func GCSym(t *types.Type) (lsym *obj.LSym, useGCProg bool, ptrdata int64) {
1513+
// Record that we need to emit the GC symbol.
1514+
gcsymmu.Lock()
1515+
if _, ok := gcsymset[t]; !ok {
1516+
gcsymset[t] = struct{}{}
1517+
}
1518+
gcsymmu.Unlock()
1519+
1520+
return dgcsym(t, false)
1521+
}
1522+
1523+
// dgcsym returns a data symbol containing GC information for type t, along
1524+
// with a boolean reporting whether the UseGCProg bit should be set in the
1525+
// type kind, and the ptrdata field to record in the reflect type information.
1526+
// When write is true, it writes the symbol data.
1527+
func dgcsym(t *types.Type, write bool) (lsym *obj.LSym, useGCProg bool, ptrdata int64) {
14971528
ptrdata = types.PtrDataSize(t)
14981529
if ptrdata/int64(types.PtrSize) <= maxPtrmaskBytes*8 {
1499-
lsym = dgcptrmask(t)
1530+
lsym = dgcptrmask(t, write)
15001531
return
15011532
}
15021533

15031534
useGCProg = true
1504-
lsym, ptrdata = dgcprog(t)
1535+
lsym, ptrdata = dgcprog(t, write)
15051536
return
15061537
}
15071538

15081539
// dgcptrmask emits and returns the symbol containing a pointer mask for type t.
1509-
func dgcptrmask(t *types.Type) *obj.LSym {
1540+
func dgcptrmask(t *types.Type, write bool) *obj.LSym {
15101541
ptrmask := make([]byte, (types.PtrDataSize(t)/int64(types.PtrSize)+7)/8)
15111542
fillptrmask(t, ptrmask)
15121543
p := fmt.Sprintf("runtime.gcbits.%x", ptrmask)
15131544

15141545
lsym := base.Ctxt.Lookup(p)
1515-
if !lsym.OnList() {
1546+
if write && !lsym.OnList() {
15161547
for i, x := range ptrmask {
15171548
objw.Uint8(lsym, i, x)
15181549
}
@@ -1549,14 +1580,14 @@ func fillptrmask(t *types.Type, ptrmask []byte) {
15491580
// [types.PtrDataSize(t), t.Width]).
15501581
// In practice, the size is types.PtrDataSize(t) except for non-trivial arrays.
15511582
// For non-trivial arrays, the program describes the full t.Width size.
1552-
func dgcprog(t *types.Type) (*obj.LSym, int64) {
1583+
func dgcprog(t *types.Type, write bool) (*obj.LSym, int64) {
15531584
types.CalcSize(t)
15541585
if t.Width == types.BADWIDTH {
15551586
base.Fatalf("dgcprog: %v badwidth", t)
15561587
}
15571588
lsym := TypeLinksymPrefix(".gcprog", t)
15581589
var p gcProg
1559-
p.init(lsym)
1590+
p.init(lsym, write)
15601591
p.emit(t, 0)
15611592
offset := p.w.BitIndex() * int64(types.PtrSize)
15621593
p.end()
@@ -1570,11 +1601,17 @@ type gcProg struct {
15701601
lsym *obj.LSym
15711602
symoff int
15721603
w gcprog.Writer
1604+
write bool
15731605
}
15741606

1575-
func (p *gcProg) init(lsym *obj.LSym) {
1607+
func (p *gcProg) init(lsym *obj.LSym, write bool) {
15761608
p.lsym = lsym
1609+
p.write = write && !lsym.OnList()
15771610
p.symoff = 4 // first 4 bytes hold program length
1611+
if !write {
1612+
p.w.Init(func(byte) {})
1613+
return
1614+
}
15781615
p.w.Init(p.writeByte)
15791616
if base.Debug.GCProg > 0 {
15801617
fmt.Fprintf(os.Stderr, "compile: start GCProg for %v\n", lsym)
@@ -1588,6 +1625,9 @@ func (p *gcProg) writeByte(x byte) {
15881625

15891626
func (p *gcProg) end() {
15901627
p.w.End()
1628+
if !p.write {
1629+
return
1630+
}
15911631
objw.Uint32(p.lsym, 0, uint32(p.symoff-4))
15921632
objw.Global(p.lsym, int32(p.symoff), obj.DUPOK|obj.RODATA|obj.LOCAL)
15931633
p.lsym.Set(obj.AttrContentAddressable, true)

src/runtime/mgcmark.go

+14-14
Original file line numberDiff line numberDiff line change
@@ -792,24 +792,24 @@ func scanstack(gp *g, gcw *gcWork) {
792792
if obj == nil {
793793
continue
794794
}
795-
t := obj.typ
796-
if t == nil {
795+
r := obj.r
796+
if r == nil {
797797
// We've already scanned this object.
798798
continue
799799
}
800-
obj.setType(nil) // Don't scan it again.
800+
obj.setRecord(nil) // Don't scan it again.
801801
if stackTraceDebug {
802802
printlock()
803-
print(" live stkobj at", hex(state.stack.lo+uintptr(obj.off)), "of type", t.string())
803+
print(" live stkobj at", hex(state.stack.lo+uintptr(obj.off)), "of size", obj.size)
804804
if conservative {
805805
print(" (conservative)")
806806
}
807807
println()
808808
printunlock()
809809
}
810-
gcdata := t.gcdata
810+
gcdata := r.gcdata
811811
var s *mspan
812-
if t.kind&kindGCProg != 0 {
812+
if r.useGCProg() {
813813
// This path is pretty unlikely, an object large enough
814814
// to have a GC program allocated on the stack.
815815
// We need some space to unpack the program into a straight
@@ -819,15 +819,15 @@ func scanstack(gp *g, gcw *gcWork) {
819819
// to change from a Lempel-Ziv style program to something else.
820820
// Or we can forbid putting objects on stacks if they require
821821
// a gc program (see issue 27447).
822-
s = materializeGCProg(t.ptrdata, gcdata)
822+
s = materializeGCProg(r.ptrdata(), gcdata)
823823
gcdata = (*byte)(unsafe.Pointer(s.startAddr))
824824
}
825825

826826
b := state.stack.lo + uintptr(obj.off)
827827
if conservative {
828-
scanConservative(b, t.ptrdata, gcdata, gcw, &state)
828+
scanConservative(b, r.ptrdata(), gcdata, gcw, &state)
829829
} else {
830-
scanblock(b, t.ptrdata, gcdata, gcw, &state)
830+
scanblock(b, r.ptrdata(), gcdata, gcw, &state)
831831
}
832832

833833
if s != nil {
@@ -843,10 +843,10 @@ func scanstack(gp *g, gcw *gcWork) {
843843
if stackTraceDebug {
844844
for i := 0; i < x.nobj; i++ {
845845
obj := &x.obj[i]
846-
if obj.typ == nil { // reachable
846+
if obj.r == nil { // reachable
847847
continue
848848
}
849-
println(" dead stkobj at", hex(gp.stack.lo+uintptr(obj.off)), "of type", obj.typ.string())
849+
println(" dead stkobj at", hex(gp.stack.lo+uintptr(obj.off)), "of size", obj.r.size)
850850
// Note: not necessarily really dead - only reachable-from-ptr dead.
851851
}
852852
}
@@ -927,7 +927,7 @@ func scanframeworker(frame *stkframe, state *stackScanState, gcw *gcWork) {
927927
// varp is 0 for defers, where there are no locals.
928928
// In that case, there can't be a pointer to its args, either.
929929
// (And all args would be scanned above anyway.)
930-
for _, obj := range objs {
930+
for i, obj := range objs {
931931
off := obj.off
932932
base := frame.varp // locals base pointer
933933
if off >= 0 {
@@ -939,9 +939,9 @@ func scanframeworker(frame *stkframe, state *stackScanState, gcw *gcWork) {
939939
continue
940940
}
941941
if stackTraceDebug {
942-
println("stkobj at", hex(ptr), "of type", obj.typ.string())
942+
println("stkobj at", hex(ptr), "of size", obj.size)
943943
}
944-
state.addObject(ptr, obj.typ)
944+
state.addObject(ptr, &objs[i])
945945
}
946946
}
947947
}

src/runtime/mgcstack.go

+11-11
Original file line numberDiff line numberDiff line change
@@ -150,19 +150,19 @@ func init() {
150150
//
151151
//go:notinheap
152152
type stackObject struct {
153-
off uint32 // offset above stack.lo
154-
size uint32 // size of object
155-
typ *_type // type info (for ptr/nonptr bits). nil if object has been scanned.
156-
left *stackObject // objects with lower addresses
157-
right *stackObject // objects with higher addresses
153+
off uint32 // offset above stack.lo
154+
size uint32 // size of object
155+
r *stackObjectRecord // info of the object (for ptr/nonptr bits). nil if object has been scanned.
156+
left *stackObject // objects with lower addresses
157+
right *stackObject // objects with higher addresses
158158
}
159159

160-
// obj.typ = typ, but with no write barrier.
160+
// obj.r = r, but with no write barrier.
161161
//go:nowritebarrier
162-
func (obj *stackObject) setType(typ *_type) {
162+
func (obj *stackObject) setRecord(r *stackObjectRecord) {
163163
// Types of stack objects are always in read-only memory, not the heap.
164164
// So not using a write barrier is ok.
165-
*(*uintptr)(unsafe.Pointer(&obj.typ)) = uintptr(unsafe.Pointer(typ))
165+
*(*uintptr)(unsafe.Pointer(&obj.r)) = uintptr(unsafe.Pointer(r))
166166
}
167167

168168
// A stackScanState keeps track of the state used during the GC walk
@@ -271,7 +271,7 @@ func (s *stackScanState) getPtr() (p uintptr, conservative bool) {
271271
}
272272

273273
// addObject adds a stack object at addr of type typ to the set of stack objects.
274-
func (s *stackScanState) addObject(addr uintptr, typ *_type) {
274+
func (s *stackScanState) addObject(addr uintptr, r *stackObjectRecord) {
275275
x := s.tail
276276
if x == nil {
277277
// initial setup
@@ -294,8 +294,8 @@ func (s *stackScanState) addObject(addr uintptr, typ *_type) {
294294
obj := &x.obj[x.nobj]
295295
x.nobj++
296296
obj.off = uint32(addr - s.stack.lo)
297-
obj.size = uint32(typ.size)
298-
obj.setType(typ)
297+
obj.size = uint32(r.size)
298+
obj.setRecord(r)
299299
// obj.left and obj.right will be initialized by buildIndex before use.
300300
s.nobjs++
301301
}

src/runtime/stack.go

+31-9
Original file line numberDiff line numberDiff line change
@@ -702,15 +702,15 @@ func adjustframe(frame *stkframe, arg unsafe.Pointer) bool {
702702
// we call into morestack.)
703703
continue
704704
}
705-
t := obj.typ
706-
gcdata := t.gcdata
705+
ptrdata := obj.ptrdata()
706+
gcdata := obj.gcdata
707707
var s *mspan
708-
if t.kind&kindGCProg != 0 {
708+
if obj.useGCProg() {
709709
// See comments in mgcmark.go:scanstack
710-
s = materializeGCProg(t.ptrdata, gcdata)
710+
s = materializeGCProg(ptrdata, gcdata)
711711
gcdata = (*byte)(unsafe.Pointer(s.startAddr))
712712
}
713-
for i := uintptr(0); i < t.ptrdata; i += sys.PtrSize {
713+
for i := uintptr(0); i < ptrdata; i += sys.PtrSize {
714714
if *addb(gcdata, i/(8*sys.PtrSize))>>(i/sys.PtrSize&7)&1 != 0 {
715715
adjustpointer(adjinfo, unsafe.Pointer(p+i))
716716
}
@@ -1346,20 +1346,42 @@ var (
13461346
abiRegArgsType *_type = efaceOf(&abiRegArgsEface)._type
13471347
methodValueCallFrameObjs = []stackObjectRecord{
13481348
{
1349-
off: -int(alignUp(abiRegArgsType.size, 8)), // It's always the highest address local.
1350-
typ: abiRegArgsType,
1349+
off: -int32(alignUp(abiRegArgsType.size, 8)), // It's always the highest address local.
1350+
size: int32(abiRegArgsType.size),
1351+
_ptrdata: int32(abiRegArgsType.ptrdata),
1352+
gcdata: abiRegArgsType.gcdata,
13511353
},
13521354
}
13531355
)
13541356

1357+
func init() {
1358+
if abiRegArgsType.kind&kindGCProg != 0 {
1359+
throw("abiRegArgsType needs GC Prog, update methodValueCallFrameObjs")
1360+
}
1361+
}
1362+
13551363
// A stackObjectRecord is generated by the compiler for each stack object in a stack frame.
13561364
// This record must match the generator code in cmd/compile/internal/liveness/plive.go:emitStackObjects.
13571365
type stackObjectRecord struct {
13581366
// offset in frame
13591367
// if negative, offset from varp
13601368
// if non-negative, offset from argp
1361-
off int
1362-
typ *_type
1369+
off int32
1370+
size int32
1371+
_ptrdata int32 // ptrdata, or -ptrdata is GC prog is used
1372+
gcdata *byte // pointer map or GC prog of the type
1373+
}
1374+
1375+
func (r *stackObjectRecord) useGCProg() bool {
1376+
return r._ptrdata < 0
1377+
}
1378+
1379+
func (r *stackObjectRecord) ptrdata() uintptr {
1380+
x := r._ptrdata
1381+
if x < 0 {
1382+
return uintptr(-x)
1383+
}
1384+
return uintptr(x)
13631385
}
13641386

13651387
// This is exported as ABI0 via linkname so obj can call it.

0 commit comments

Comments
 (0)