From 067c0915644e5936b9b56eba3b69a0757ad28489 Mon Sep 17 00:00:00 2001 From: Michael Pratt Date: Thu, 10 Oct 2024 13:52:26 -0400 Subject: [PATCH] cmd/link,runtime: DWARF/gdb support for swiss maps For #54766. Cq-Include-Trybots: luci.golang.try:gotip-linux-ppc64_power10,gotip-linux-amd64-longtest-swissmap Change-Id: I6695c0b143560d974b710e1d78e7a7d09278f7cc Reviewed-on: https://go-review.googlesource.com/c/go/+/620215 Reviewed-by: Keith Randall Reviewed-by: Keith Randall Auto-Submit: Michael Pratt LUCI-TryBot-Result: Go LUCI --- .../compile/internal/reflectdata/map_swiss.go | 2 +- src/cmd/link/internal/ld/decodesym.go | 5 + src/cmd/link/internal/ld/dwarf.go | 138 +++++++----------- src/runtime/runtime-gdb.py | 60 +++++++- src/runtime/runtime-gdb_test.go | 24 +-- 5 files changed, 131 insertions(+), 98 deletions(-) diff --git a/src/cmd/compile/internal/reflectdata/map_swiss.go b/src/cmd/compile/internal/reflectdata/map_swiss.go index 2cbe580f7f..50e123ddb0 100644 --- a/src/cmd/compile/internal/reflectdata/map_swiss.go +++ b/src/cmd/compile/internal/reflectdata/map_swiss.go @@ -36,7 +36,7 @@ func SwissMapGroupType(t *types.Type) *types.Type { // } slotFields := []*types.Field{ makefield("key", t.Key()), - makefield("typ", t.Elem()), + makefield("elem", t.Elem()), } slot := types.NewStruct(slotFields) slot.SetNoalg(true) diff --git a/src/cmd/link/internal/ld/decodesym.go b/src/cmd/link/internal/ld/decodesym.go index 32271b6f91..9bce4a7a12 100644 --- a/src/cmd/link/internal/ld/decodesym.go +++ b/src/cmd/link/internal/ld/decodesym.go @@ -159,6 +159,11 @@ func decodetypeMapValue(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym) l return decodeRelocSym(ldr, symIdx, &relocs, int32(commonsize(arch))+int32(arch.PtrSize)) // 0x20 / 0x38 } +func decodetypeMapSwissGroup(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym) loader.Sym { + relocs := ldr.Relocs(symIdx) + return decodeRelocSym(ldr, symIdx, &relocs, int32(commonsize(arch))+2*int32(arch.PtrSize)) // 0x24 / 0x40 +} + func decodetypePtrElem(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym) loader.Sym { relocs := ldr.Relocs(symIdx) return decodeRelocSym(ldr, symIdx, &relocs, int32(commonsize(arch))) // 0x1c / 0x30 diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index 45037030f5..0b01946696 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -810,7 +810,7 @@ func (d *dwctxt) findprotodie(ctxt *Link, name string) *dwarf.DWDie { die = prototypedies[name] } if die == nil { - log.Fatalf("internal error: DIE generation failed for %s\nprototypedies: %+v", name, prototypedies) + log.Fatalf("internal error: DIE generation failed for %s\n", name) } return die } @@ -873,101 +873,68 @@ func (d *dwctxt) synthesizemaptypes(ctxt *Link, die *dwarf.DWDie) { } func (d *dwctxt) synthesizemaptypesSwiss(ctxt *Link, die *dwarf.DWDie) { - hash := walktypedef(d.findprotodie(ctxt, "type:internal/runtime/maps.table")) - //bucket := walktypedef(d.findprotodie(ctxt, "type:internal/runtime/maps.Map")) - - if hash == nil { - return - } + mapType := walktypedef(d.findprotodie(ctxt, "type:internal/runtime/maps.Map")) + tableType := walktypedef(d.findprotodie(ctxt, "type:internal/runtime/maps.table")) + tableSliceType := walktypedef(d.findprotodie(ctxt, "type:[]*internal/runtime/maps.table")) + groupsReferenceType := walktypedef(d.findprotodie(ctxt, "type:internal/runtime/maps.groupsReference")) for ; die != nil; die = die.Link { if die.Abbrev != dwarf.DW_ABRV_MAPTYPE { continue } gotype := loader.Sym(getattr(die, dwarf.DW_AT_type).Data.(dwSym)) - keytype := decodetypeMapKey(d.ldr, d.arch, gotype) - valtype := decodetypeMapValue(d.ldr, d.arch, gotype) - //keydata := d.ldr.Data(keytype) - //valdata := d.ldr.Data(valtype) - //keysize, valsize := decodetypeSize(d.arch, keydata), decodetypeSize(d.arch, valdata) - keytype, valtype = d.walksymtypedef(d.defgotype(keytype)), d.walksymtypedef(d.defgotype(valtype)) - - // compute size info like hashmap.c does. - //indirectKey, indirectVal := false, false - //if keysize > abi.SwissMapMaxKeyBytes { - // keysize = int64(d.arch.PtrSize) - // indirectKey = true - //} - //if valsize > abi.SwissMapMaxElemBytes { - // valsize = int64(d.arch.PtrSize) - // indirectVal = true - //} - // Construct type to represent an array of BucketSize keys - // TODO - keyname := d.nameFromDIESym(keytype) - //dwhks := d.mkinternaltype(ctxt, dwarf.DW_ABRV_ARRAYTYPE, "[]key", keyname, "", func(dwhk *dwarf.DWDie) { - // newattr(dwhk, dwarf.DW_AT_byte_size, dwarf.DW_CLS_CONSTANT, abi.SwissMapBucketCount*keysize, 0) - // t := keytype - // if indirectKey { - // t = d.defptrto(keytype) - // } - // d.newrefattr(dwhk, dwarf.DW_AT_type, t) - // fld := d.newdie(dwhk, dwarf.DW_ABRV_ARRAYRANGE, "size") - // newattr(fld, dwarf.DW_AT_count, dwarf.DW_CLS_CONSTANT, abi.SwissMapBucketCount, 0) - // d.newrefattr(fld, dwarf.DW_AT_type, d.uintptrInfoSym) - //}) + keyType := decodetypeMapKey(d.ldr, d.arch, gotype) + valType := decodetypeMapValue(d.ldr, d.arch, gotype) + groupType := decodetypeMapSwissGroup(d.ldr, d.arch, gotype) + + keyType = d.walksymtypedef(d.defgotype(keyType)) + valType = d.walksymtypedef(d.defgotype(valType)) + groupType = d.walksymtypedef(d.defgotype(groupType)) + + keyName := d.nameFromDIESym(keyType) + valName := d.nameFromDIESym(valType) + + // Construct groupsReference[K,V] + dwGroupsReference := d.mkinternaltype(ctxt, dwarf.DW_ABRV_STRUCTTYPE, "groupReference", keyName, valName, func(dwh *dwarf.DWDie) { + d.copychildren(ctxt, dwh, groupsReferenceType) + // data *group[K,V] + // + // This is actually a pointer to an array + // *[lengthMask+1]group[K,V], but the length is + // variable, so we can't statically record the length. + d.substitutetype(dwh, "data", d.defptrto(groupType)) + newattr(dwh, dwarf.DW_AT_byte_size, dwarf.DW_CLS_CONSTANT, getattr(groupsReferenceType, dwarf.DW_AT_byte_size).Value, nil) + newattr(dwh, dwarf.DW_AT_go_kind, dwarf.DW_CLS_CONSTANT, int64(abi.Struct), 0) + }) - // Construct type to represent an array of BucketSize values - // TODO - valname := d.nameFromDIESym(valtype) - //dwhvs := d.mkinternaltype(ctxt, dwarf.DW_ABRV_ARRAYTYPE, "[]val", valname, "", func(dwhv *dwarf.DWDie) { - // newattr(dwhv, dwarf.DW_AT_byte_size, dwarf.DW_CLS_CONSTANT, abi.SwissMapBucketCount*valsize, 0) - // t := valtype - // if indirectVal { - // t = d.defptrto(valtype) - // } - // d.newrefattr(dwhv, dwarf.DW_AT_type, t) - // fld := d.newdie(dwhv, dwarf.DW_ABRV_ARRAYRANGE, "size") - // newattr(fld, dwarf.DW_AT_count, dwarf.DW_CLS_CONSTANT, abi.SwissMapBucketCount, 0) - // d.newrefattr(fld, dwarf.DW_AT_type, d.uintptrInfoSym) - //}) + // Construct table[K,V] + dwTable := d.mkinternaltype(ctxt, dwarf.DW_ABRV_STRUCTTYPE, "table", keyName, valName, func(dwh *dwarf.DWDie) { + d.copychildren(ctxt, dwh, tableType) + d.substitutetype(dwh, "groups", dwGroupsReference) + newattr(dwh, dwarf.DW_AT_byte_size, dwarf.DW_CLS_CONSTANT, getattr(tableType, dwarf.DW_AT_byte_size).Value, nil) + newattr(dwh, dwarf.DW_AT_go_kind, dwarf.DW_CLS_CONSTANT, int64(abi.Struct), 0) + }) - // Construct bucket - // TODO - //dwhbs := d.mkinternaltype(ctxt, dwarf.DW_ABRV_STRUCTTYPE, "bucket", keyname, valname, func(dwhb *dwarf.DWDie) { - // // Copy over all fields except the field "data" from the generic - // // bucket. "data" will be replaced with keys/values below. - // d.copychildrenexcept(ctxt, dwhb, bucket, findchild(bucket, "data")) - - // fld := d.newdie(dwhb, dwarf.DW_ABRV_STRUCTFIELD, "keys") - // d.newrefattr(fld, dwarf.DW_AT_type, dwhks) - // newmemberoffsetattr(fld, abi.SwissMapBucketCount) - // fld = d.newdie(dwhb, dwarf.DW_ABRV_STRUCTFIELD, "values") - // d.newrefattr(fld, dwarf.DW_AT_type, dwhvs) - // newmemberoffsetattr(fld, abi.SwissMapBucketCount+abi.SwissMapBucketCount*int32(keysize)) - // fld = d.newdie(dwhb, dwarf.DW_ABRV_STRUCTFIELD, "overflow") - // d.newrefattr(fld, dwarf.DW_AT_type, d.defptrto(d.dtolsym(dwhb.Sym))) - // newmemberoffsetattr(fld, abi.SwissMapBucketCount+abi.SwissMapBucketCount*(int32(keysize)+int32(valsize))) - // if d.arch.RegSize > d.arch.PtrSize { - // fld = d.newdie(dwhb, dwarf.DW_ABRV_STRUCTFIELD, "pad") - // d.newrefattr(fld, dwarf.DW_AT_type, d.uintptrInfoSym) - // newmemberoffsetattr(fld, abi.SwissMapBucketCount+abi.SwissMapBucketCount*(int32(keysize)+int32(valsize))+int32(d.arch.PtrSize)) - // } - - // newattr(dwhb, dwarf.DW_AT_byte_size, dwarf.DW_CLS_CONSTANT, abi.SwissMapBucketCount+abi.SwissMapBucketCount*keysize+abi.SwissMapBucketCount*valsize+int64(d.arch.RegSize), 0) - //}) + // Construct type to represent []*table[K,V]. + dwTableSlice := d.mkinternaltype(ctxt, dwarf.DW_ABRV_SLICETYPE, "[]*table", keyName, valName, func(dwh *dwarf.DWDie) { + d.copychildren(ctxt, dwh, tableSliceType) + d.substitutetype(dwh, "array", d.defptrto(d.defptrto(dwTable))) + d.newrefattr(dwh, dwarf.DW_AT_go_elem, d.defptrto(dwTable)) + newattr(dwh, dwarf.DW_AT_byte_size, dwarf.DW_CLS_CONSTANT, getattr(tableSliceType, dwarf.DW_AT_byte_size).Value, nil) + newattr(dwh, dwarf.DW_AT_go_kind, dwarf.DW_CLS_CONSTANT, int64(abi.Slice), 0) + }) - // Construct hash - dwhs := d.mkinternaltype(ctxt, dwarf.DW_ABRV_STRUCTTYPE, "hash", keyname, valname, func(dwh *dwarf.DWDie) { - d.copychildren(ctxt, dwh, hash) - //d.substitutetype(dwh, "buckets", d.defptrto(dwhbs)) - //d.substitutetype(dwh, "oldbuckets", d.defptrto(dwhbs)) - newattr(dwh, dwarf.DW_AT_byte_size, dwarf.DW_CLS_CONSTANT, getattr(hash, dwarf.DW_AT_byte_size).Value, nil) + // Construct map[K,V] + dwMap := d.mkinternaltype(ctxt, dwarf.DW_ABRV_STRUCTTYPE, "map", keyName, valName, func(dwh *dwarf.DWDie) { + d.copychildren(ctxt, dwh, mapType) + d.substitutetype(dwh, "directory", dwTableSlice) + newattr(dwh, dwarf.DW_AT_byte_size, dwarf.DW_CLS_CONSTANT, getattr(mapType, dwarf.DW_AT_byte_size).Value, nil) + newattr(dwh, dwarf.DW_AT_go_kind, dwarf.DW_CLS_CONSTANT, int64(abi.Struct), 0) }) - // make map type a pointer to hash - d.newrefattr(die, dwarf.DW_AT_type, d.defptrto(dwhs)) + // make map type a pointer to map[K,V] + d.newrefattr(die, dwarf.DW_AT_type, d.defptrto(dwMap)) } } @@ -1882,7 +1849,10 @@ func dwarfGenerateDebugInfo(ctxt *Link) { "type:runtime.hchan": nil, } if buildcfg.Experiment.SwissMap { + prototypedies["type:internal/runtime/maps.Map"] = nil prototypedies["type:internal/runtime/maps.table"] = nil + prototypedies["type:[]*internal/runtime/maps.table"] = nil + prototypedies["type:internal/runtime/maps.groupsReference"] = nil } else { prototypedies["type:runtime.hmap"] = nil prototypedies["type:runtime.bmap"] = nil diff --git a/src/runtime/runtime-gdb.py b/src/runtime/runtime-gdb.py index 8618ebbc3b..b0c96e594f 100644 --- a/src/runtime/runtime-gdb.py +++ b/src/runtime/runtime-gdb.py @@ -141,7 +141,6 @@ class SliceTypePrinter: yield ('[{0}]'.format(idx), item) -# TODO(go.dev/issue/54766): Support swisstable maps. class MapTypePrinter: """Pretty print map[K]V types. @@ -161,7 +160,56 @@ class MapTypePrinter: return str(self.val.type) def children(self): - MapBucketCount = 8 # see internal/abi.go:MapBucketCount + fields = [f.name for f in self.val.type.strip_typedefs().target().fields()] + if 'buckets' in fields: + yield from self.old_map_children() + else: + yield from self.swiss_map_children() + + def swiss_map_children(self): + SwissMapGroupSlots = 8 # see internal/abi:SwissMapGroupSlots + + cnt = 0 + directory = SliceValue(self.val['directory']) + for table in directory: + table = table.dereference() + groups = table['groups']['data'] + length = table['groups']['lengthMask'] + 1 + + # The linker DWARF generation + # (cmd/link/internal/ld.(*dwctxt).synthesizemaptypesSwiss) records + # groups.data as a *group[K,V], but it is actually a pointer to + # variable length array *[length]group[K,V]. + # + # N.B. array() takes an _inclusive_ upper bound. + + # group[K,V] + group_type = groups.type.target() + # [length]group[K,V] + array_group_type = group_type.array(length-1) + # *[length]group[K,V] + ptr_array_group_type = array_group_type.pointer() + # groups = (*[length]group[K,V])(groups.data) + groups = groups.cast(ptr_array_group_type) + groups = groups.dereference() + + for i in xrange(length): + group = groups[i] + ctrl = group['ctrl'] + + for i in xrange(SwissMapGroupSlots): + c = (ctrl >> (8*i)) & 0xff + if (c & 0x80) != 0: + # Empty or deleted + continue + + # Full + yield str(cnt), group['slots'][i]['key'] + yield str(cnt+1), group['slots'][i]['elem'] + + + def old_map_children(self): + MapBucketCount = 8 # see internal/abi:OldMapBucketCount B = self.val['B'] buckets = self.val['buckets'] oldbuckets = self.val['oldbuckets'] @@ -386,7 +434,7 @@ goobjfile.pretty_printers.append(ifacematcher) class GoLenFunc(gdb.Function): "Length of strings, slices, maps or channels" - how = ((StringTypePrinter, 'len'), (SliceTypePrinter, 'len'), (MapTypePrinter, 'count'), (ChanTypePrinter, 'qcount')) + how = ((StringTypePrinter, 'len'), (SliceTypePrinter, 'len'), (MapTypePrinter, 'used'), (ChanTypePrinter, 'qcount')) def __init__(self): gdb.Function.__init__(self, "len") @@ -395,6 +443,12 @@ class GoLenFunc(gdb.Function): typename = str(obj.type) for klass, fld in self.how: if klass.pattern.match(typename) or paramtypematch(obj.type, klass.pattern): + if klass == MapTypePrinter: + fields = [f.name for f in self.val.type.strip_typedefs().target().fields()] + if 'buckets' in fields: + # Old maps. + fld = 'count' + return obj[fld] diff --git a/src/runtime/runtime-gdb_test.go b/src/runtime/runtime-gdb_test.go index ef01d6a194..d31db52234 100644 --- a/src/runtime/runtime-gdb_test.go +++ b/src/runtime/runtime-gdb_test.go @@ -186,9 +186,6 @@ func TestGdbPythonCgo(t *testing.T) { } func testGdbPython(t *testing.T, cgo bool) { - if goexperiment.SwissMap { - t.Skip("TODO(prattmic): swissmap DWARF") - } if cgo { testenv.MustHaveCGO(t) } @@ -531,10 +528,6 @@ func main() { // TestGdbAutotmpTypes ensures that types of autotmp variables appear in .debug_info // See bug #17830. func TestGdbAutotmpTypes(t *testing.T) { - if goexperiment.SwissMap { - t.Skip("TODO(prattmic): swissmap DWARF") - } - checkGdbEnvironment(t) t.Parallel() checkGdbVersion(t) @@ -584,10 +577,21 @@ func TestGdbAutotmpTypes(t *testing.T) { // Check that the backtrace matches the source code. types := []string{ "[]main.astruct", - "bucket", - "hash", "main.astruct", - "hash * map[string]main.astruct", + } + if goexperiment.SwissMap { + types = append(types, []string{ + "groupReference", + "table", + "map", + "map * map[string]main.astruct", + }...) + } else { + types = append(types, []string{ + "bucket", + "hash", + "hash * map[string]main.astruct", + }...) } for _, name := range types { if !strings.Contains(sgot, name) { -- 2.48.1