aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/internal
diff options
context:
space:
mode:
authorMichael Pratt <mpratt@google.com>2024-03-04 13:29:39 -0500
committerGopher Robot <gobot@golang.org>2024-03-27 20:20:01 +0000
commit63deaf00ea6058d1422f0b435e475666cba5743e (patch)
tree20ea11d36b1c627fd99993ea6eaff8835e698ffc /src/cmd/internal
parent2860e01853174e278900ef6907b1941b16fb1645 (diff)
downloadgo-63deaf00ea6058d1422f0b435e475666cba5743e.tar.xz
cmd/compile,cmd/preprofile: move logic to shared common package
The processing performed in cmd/preprofile is a simple version of the same initial processing performed by cmd/compile/internal/pgo. Refactor this processing into the new IR-independent cmd/internal/pgo package. Now cmd/preprofile and cmd/compile run the same code for initial processing of a pprof profile, guaranteeing that they always stay in sync. Since it is now trivial, this CL makes one change to the serialization format: the entries are ordered by weight. This allows us to avoid sorting ByWeight on deserialization. Impact on PGO parsing when compiling cmd/compile with PGO: * Without preprocessing: PGO parsing ~13.7% of CPU time * With preprocessing (unsorted): ~2.9% of CPU time (sorting ~1.7%) * With preprocessing (sorted): ~1.3% of CPU time The remaining 1.3% of CPU time approximately breaks down as: * ~0.5% parsing the preprocessed profile * ~0.7% building weighted IR call graph * ~0.5% walking function IR to find direct calls * ~0.2% performing lookups for indirect calls targets For #58102. Change-Id: Iaba425ea30b063ca195fb2f7b29342961c8a64c2 Reviewed-on: https://go-review.googlesource.com/c/go/+/569337 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Michael Pratt <mpratt@google.com> Reviewed-by: Cherry Mui <cherryyz@google.com>
Diffstat (limited to 'src/cmd/internal')
-rw-r--r--src/cmd/internal/pgo/deserialize.go102
-rw-r--r--src/cmd/internal/pgo/pgo.go55
-rw-r--r--src/cmd/internal/pgo/pprof.go140
-rw-r--r--src/cmd/internal/pgo/serialize.go79
-rw-r--r--src/cmd/internal/pgo/serialize_test.go190
-rw-r--r--src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/12fcf136fcb7463c2
-rw-r--r--src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/2055d314024c8d6c2
-rw-r--r--src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/b615162315f7b72c2
-rw-r--r--src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/fdc60117b431bbae2
9 files changed, 574 insertions, 0 deletions
diff --git a/src/cmd/internal/pgo/deserialize.go b/src/cmd/internal/pgo/deserialize.go
new file mode 100644
index 0000000000..4b075b8daf
--- /dev/null
+++ b/src/cmd/internal/pgo/deserialize.go
@@ -0,0 +1,102 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package pgo
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "strings"
+ "strconv"
+)
+
+// IsSerialized returns true if r is a serialized Profile.
+//
+// IsSerialized only peeks at r, so seeking back after calling is not
+// necessary.
+func IsSerialized(r *bufio.Reader) (bool, error) {
+ hdr, err := r.Peek(len(serializationHeader))
+ if err == io.EOF {
+ // Empty file.
+ return false, nil
+ } else if err != nil {
+ return false, fmt.Errorf("error reading profile header: %w", err)
+ }
+
+ return string(hdr) == serializationHeader, nil
+}
+
+// FromSerialized parses a profile from serialization output of Profile.WriteTo.
+func FromSerialized(r io.Reader) (*Profile, error) {
+ d := emptyProfile()
+
+ scanner := bufio.NewScanner(r)
+ scanner.Split(bufio.ScanLines)
+
+ if !scanner.Scan() {
+ if err := scanner.Err(); err != nil {
+ return nil, fmt.Errorf("error reading preprocessed profile: %w", err)
+ }
+ return nil, fmt.Errorf("preprocessed profile missing header")
+ }
+ if gotHdr := scanner.Text() + "\n"; gotHdr != serializationHeader {
+ return nil, fmt.Errorf("preprocessed profile malformed header; got %q want %q", gotHdr, serializationHeader)
+ }
+
+ for scanner.Scan() {
+ readStr := scanner.Text()
+
+ callerName := readStr
+
+ if !scanner.Scan() {
+ if err := scanner.Err(); err != nil {
+ return nil, fmt.Errorf("error reading preprocessed profile: %w", err)
+ }
+ return nil, fmt.Errorf("preprocessed profile entry missing callee")
+ }
+ calleeName := scanner.Text()
+
+ if !scanner.Scan() {
+ if err := scanner.Err(); err != nil {
+ return nil, fmt.Errorf("error reading preprocessed profile: %w", err)
+ }
+ return nil, fmt.Errorf("preprocessed profile entry missing weight")
+ }
+ readStr = scanner.Text()
+
+ split := strings.Split(readStr, " ")
+
+ if len(split) != 2 {
+ return nil, fmt.Errorf("preprocessed profile entry got %v want 2 fields", split)
+ }
+
+ co, err := strconv.Atoi(split[0])
+ if err != nil {
+ return nil, fmt.Errorf("preprocessed profile error processing call line: %w", err)
+ }
+
+ edge := NamedCallEdge{
+ CallerName: callerName,
+ CalleeName: calleeName,
+ CallSiteOffset: co,
+ }
+
+ weight, err := strconv.ParseInt(split[1], 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("preprocessed profile error processing call weight: %w", err)
+ }
+
+ if _, ok := d.NamedEdgeMap.Weight[edge]; ok {
+ return nil, fmt.Errorf("preprocessed profile contains duplicate edge %+v", edge)
+ }
+
+ d.NamedEdgeMap.ByWeight = append(d.NamedEdgeMap.ByWeight, edge) // N.B. serialization is ordered.
+ d.NamedEdgeMap.Weight[edge] += weight
+ d.TotalWeight += weight
+ }
+
+ return d, nil
+
+}
diff --git a/src/cmd/internal/pgo/pgo.go b/src/cmd/internal/pgo/pgo.go
new file mode 100644
index 0000000000..1d2cb880f7
--- /dev/null
+++ b/src/cmd/internal/pgo/pgo.go
@@ -0,0 +1,55 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package pgo contains the compiler-agnostic portions of PGO profile handling.
+// Notably, parsing pprof profiles and serializing/deserializing from a custom
+// intermediate representation.
+package pgo
+
+// Profile contains the processed data from the PGO profile.
+type Profile struct {
+ // TotalWeight is the aggregated edge weights across the profile. This
+ // helps us determine the percentage threshold for hot/cold
+ // partitioning.
+ TotalWeight int64
+
+ // NamedEdgeMap contains all unique call edges in the profile and their
+ // edge weight.
+ NamedEdgeMap NamedEdgeMap
+}
+
+// NamedCallEdge identifies a call edge by linker symbol names and call site
+// offset.
+type NamedCallEdge struct {
+ CallerName string
+ CalleeName string
+ CallSiteOffset int // Line offset from function start line.
+}
+
+// NamedEdgeMap contains all unique call edges in the profile and their
+// edge weight.
+type NamedEdgeMap struct {
+ Weight map[NamedCallEdge]int64
+
+ // ByWeight lists all keys in Weight, sorted by edge weight from
+ // highest to lowest.
+ ByWeight []NamedCallEdge
+}
+
+func emptyProfile() *Profile {
+ // Initialize empty maps/slices for easier use without a requiring a
+ // nil check.
+ return &Profile{
+ NamedEdgeMap: NamedEdgeMap{
+ ByWeight: make([]NamedCallEdge, 0),
+ Weight: make(map[NamedCallEdge]int64),
+ },
+ }
+}
+
+// WeightInPercentage converts profile weights to a percentage.
+func WeightInPercentage(value int64, total int64) float64 {
+ return (float64(value) / float64(total)) * 100
+}
+
diff --git a/src/cmd/internal/pgo/pprof.go b/src/cmd/internal/pgo/pprof.go
new file mode 100644
index 0000000000..5e61a11141
--- /dev/null
+++ b/src/cmd/internal/pgo/pprof.go
@@ -0,0 +1,140 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package pgo contains the compiler-agnostic portions of PGO profile handling.
+// Notably, parsing pprof profiles and serializing/deserializing from a custom
+// intermediate representation.
+package pgo
+
+import (
+ "errors"
+ "fmt"
+ "internal/profile"
+ "io"
+ "sort"
+)
+
+// FromPProf parses Profile from a pprof profile.
+func FromPProf(r io.Reader) (*Profile, error) {
+ p, err := profile.Parse(r)
+ if errors.Is(err, profile.ErrNoData) {
+ // Treat a completely empty file the same as a profile with no
+ // samples: nothing to do.
+ return emptyProfile(), nil
+ } else if err != nil {
+ return nil, fmt.Errorf("error parsing profile: %w", err)
+ }
+
+ if len(p.Sample) == 0 {
+ // We accept empty profiles, but there is nothing to do.
+ return emptyProfile(), nil
+ }
+
+ valueIndex := -1
+ for i, s := range p.SampleType {
+ // Samples count is the raw data collected, and CPU nanoseconds is just
+ // a scaled version of it, so either one we can find is fine.
+ if (s.Type == "samples" && s.Unit == "count") ||
+ (s.Type == "cpu" && s.Unit == "nanoseconds") {
+ valueIndex = i
+ break
+ }
+ }
+
+ if valueIndex == -1 {
+ return nil, fmt.Errorf(`profile does not contain a sample index with value/type "samples/count" or cpu/nanoseconds"`)
+ }
+
+ g := profile.NewGraph(p, &profile.Options{
+ SampleValue: func(v []int64) int64 { return v[valueIndex] },
+ })
+
+ namedEdgeMap, totalWeight, err := createNamedEdgeMap(g)
+ if err != nil {
+ return nil, err
+ }
+
+ if totalWeight == 0 {
+ return emptyProfile(), nil // accept but ignore profile with no samples.
+ }
+
+ return &Profile{
+ TotalWeight: totalWeight,
+ NamedEdgeMap: namedEdgeMap,
+ }, nil
+}
+
+// createNamedEdgeMap builds a map of callsite-callee edge weights from the
+// profile-graph.
+//
+// Caller should ignore the profile if totalWeight == 0.
+func createNamedEdgeMap(g *profile.Graph) (edgeMap NamedEdgeMap, totalWeight int64, err error) {
+ seenStartLine := false
+
+ // Process graph and build various node and edge maps which will
+ // be consumed by AST walk.
+ weight := make(map[NamedCallEdge]int64)
+ for _, n := range g.Nodes {
+ seenStartLine = seenStartLine || n.Info.StartLine != 0
+
+ canonicalName := n.Info.Name
+ // Create the key to the nodeMapKey.
+ namedEdge := NamedCallEdge{
+ CallerName: canonicalName,
+ CallSiteOffset: n.Info.Lineno - n.Info.StartLine,
+ }
+
+ for _, e := range n.Out {
+ totalWeight += e.WeightValue()
+ namedEdge.CalleeName = e.Dest.Info.Name
+ // Create new entry or increment existing entry.
+ weight[namedEdge] += e.WeightValue()
+ }
+ }
+
+ if !seenStartLine {
+ // TODO(prattmic): If Function.start_line is missing we could
+ // fall back to using absolute line numbers, which is better
+ // than nothing.
+ return NamedEdgeMap{}, 0, fmt.Errorf("profile missing Function.start_line data (Go version of profiled application too old? Go 1.20+ automatically adds this to profiles)")
+ }
+ return postProcessNamedEdgeMap(weight, totalWeight)
+}
+
+func sortByWeight(edges []NamedCallEdge, weight map[NamedCallEdge]int64) {
+ sort.Slice(edges, func(i, j int) bool {
+ ei, ej := edges[i], edges[j]
+ if wi, wj := weight[ei], weight[ej]; wi != wj {
+ return wi > wj // want larger weight first
+ }
+ // same weight, order by name/line number
+ if ei.CallerName != ej.CallerName {
+ return ei.CallerName < ej.CallerName
+ }
+ if ei.CalleeName != ej.CalleeName {
+ return ei.CalleeName < ej.CalleeName
+ }
+ return ei.CallSiteOffset < ej.CallSiteOffset
+ })
+}
+
+func postProcessNamedEdgeMap(weight map[NamedCallEdge]int64, weightVal int64) (edgeMap NamedEdgeMap, totalWeight int64, err error) {
+ if weightVal == 0 {
+ return NamedEdgeMap{}, 0, nil // accept but ignore profile with no samples.
+ }
+ byWeight := make([]NamedCallEdge, 0, len(weight))
+ for namedEdge := range weight {
+ byWeight = append(byWeight, namedEdge)
+ }
+ sortByWeight(byWeight, weight)
+
+ edgeMap = NamedEdgeMap{
+ Weight: weight,
+ ByWeight: byWeight,
+ }
+
+ totalWeight = weightVal
+
+ return edgeMap, totalWeight, nil
+}
diff --git a/src/cmd/internal/pgo/serialize.go b/src/cmd/internal/pgo/serialize.go
new file mode 100644
index 0000000000..caf67ce485
--- /dev/null
+++ b/src/cmd/internal/pgo/serialize.go
@@ -0,0 +1,79 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package pgo
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+)
+
+// Serialization of a Profile allows go tool preprofile to construct the edge
+// map only once (rather than once per compile process). The compiler processes
+// then parse the pre-processed data directly from the serialized format.
+//
+// The format of the serialized output is as follows.
+//
+// GO PREPROFILE V1
+// caller_name
+// callee_name
+// "call site offset" "call edge weight"
+// ...
+// caller_name
+// callee_name
+// "call site offset" "call edge weight"
+//
+// Entries are sorted by "call edge weight", from highest to lowest.
+
+const serializationHeader = "GO PREPROFILE V1\n"
+
+// WriteTo writes a serialized representation of Profile to w.
+//
+// FromSerialized can parse the format back to Profile.
+//
+// WriteTo implements io.WriterTo.Write.
+func (d *Profile) WriteTo(w io.Writer) (int64, error) {
+ bw := bufio.NewWriter(w)
+
+ var written int64
+
+ // Header
+ n, err := bw.WriteString(serializationHeader)
+ written += int64(n)
+ if err != nil {
+ return written, err
+ }
+
+ for _, edge := range d.NamedEdgeMap.ByWeight {
+ weight := d.NamedEdgeMap.Weight[edge]
+
+ n, err = fmt.Fprintln(bw, edge.CallerName)
+ written += int64(n)
+ if err != nil {
+ return written, err
+ }
+
+ n, err = fmt.Fprintln(bw, edge.CalleeName)
+ written += int64(n)
+ if err != nil {
+ return written, err
+ }
+
+ n, err = fmt.Fprintf(bw, "%d %d\n", edge.CallSiteOffset, weight)
+ written += int64(n)
+ if err != nil {
+ return written, err
+ }
+ }
+
+ if err := bw.Flush(); err != nil {
+ return written, err
+ }
+
+ // No need to serialize TotalWeight, it can be trivially recomputed
+ // during parsing.
+
+ return written, nil
+}
diff --git a/src/cmd/internal/pgo/serialize_test.go b/src/cmd/internal/pgo/serialize_test.go
new file mode 100644
index 0000000000..b24163d1e2
--- /dev/null
+++ b/src/cmd/internal/pgo/serialize_test.go
@@ -0,0 +1,190 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package pgo
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+// equal returns an error if got and want are not equal.
+func equal(got, want *Profile) error {
+ if got.TotalWeight != want.TotalWeight {
+ return fmt.Errorf("got.TotalWeight %d != want.TotalWeight %d", got.TotalWeight, want.TotalWeight)
+ }
+ if !reflect.DeepEqual(got.NamedEdgeMap.ByWeight, want.NamedEdgeMap.ByWeight) {
+ return fmt.Errorf("got.NamedEdgeMap.ByWeight != want.NamedEdgeMap.ByWeight\ngot = %+v\nwant = %+v", got.NamedEdgeMap.ByWeight, want.NamedEdgeMap.ByWeight)
+ }
+ if !reflect.DeepEqual(got.NamedEdgeMap.Weight, want.NamedEdgeMap.Weight) {
+ return fmt.Errorf("got.NamedEdgeMap.Weight != want.NamedEdgeMap.Weight\ngot = %+v\nwant = %+v", got.NamedEdgeMap.Weight, want.NamedEdgeMap.Weight)
+ }
+
+ return nil
+}
+
+func testRoundTrip(t *testing.T, d *Profile) []byte {
+ var buf bytes.Buffer
+ n, err := d.WriteTo(&buf)
+ if err != nil {
+ t.Fatalf("WriteTo got err %v want nil", err)
+ }
+ if n != int64(buf.Len()) {
+ t.Errorf("WriteTo got n %d want %d", n, int64(buf.Len()))
+ }
+
+ b := buf.Bytes()
+
+ got, err := FromSerialized(&buf)
+ if err != nil {
+ t.Fatalf("processSerialized got err %v want nil", err)
+ }
+ if err := equal(got, d); err != nil {
+ t.Errorf("processSerialized output does not match input: %v", err)
+ }
+
+ return b
+}
+
+func TestEmpty(t *testing.T) {
+ d := emptyProfile()
+ b := testRoundTrip(t, d)
+
+ // Contents should consist of only a header.
+ if string(b) != serializationHeader {
+ t.Errorf("WriteTo got %q want %q", string(b), serializationHeader)
+ }
+}
+
+func TestRoundTrip(t *testing.T) {
+ d := &Profile{
+ TotalWeight: 3,
+ NamedEdgeMap: NamedEdgeMap{
+ ByWeight: []NamedCallEdge{
+ {
+ CallerName: "a",
+ CalleeName: "b",
+ CallSiteOffset: 14,
+ },
+ {
+ CallerName: "c",
+ CalleeName: "d",
+ CallSiteOffset: 15,
+ },
+ },
+ Weight: map[NamedCallEdge]int64{
+ {
+ CallerName: "a",
+ CalleeName: "b",
+ CallSiteOffset: 14,
+ }: 2,
+ {
+ CallerName: "c",
+ CalleeName: "d",
+ CallSiteOffset: 15,
+ }: 1,
+ },
+ },
+ }
+
+ testRoundTrip(t, d)
+}
+
+func constructFuzzProfile(t *testing.T, b []byte) *Profile {
+ // The fuzzer can't construct an arbitrary structure, so instead we
+ // consume bytes from b to act as our edge data.
+ r := bytes.NewReader(b)
+ consumeString := func() (string, bool) {
+ // First byte: how many bytes to read for this string? We only
+ // use a byte to avoid making humongous strings.
+ length, err := r.ReadByte()
+ if err != nil {
+ return "", false
+ }
+ if length == 0 {
+ return "", false
+ }
+
+ b := make([]byte, length)
+ _, err = r.Read(b)
+ if err != nil {
+ return "", false
+ }
+
+ return string(b), true
+ }
+ consumeInt64 := func() (int64, bool) {
+ b := make([]byte, 8)
+ _, err := r.Read(b)
+ if err != nil {
+ return 0, false
+ }
+
+ return int64(binary.LittleEndian.Uint64(b)), true
+ }
+
+ d := emptyProfile()
+
+ for {
+ caller, ok := consumeString()
+ if !ok {
+ break
+ }
+ if strings.ContainsAny(caller, " \r\n") {
+ t.Skip("caller contains space or newline")
+ }
+
+ callee, ok := consumeString()
+ if !ok {
+ break
+ }
+ if strings.ContainsAny(callee, " \r\n") {
+ t.Skip("callee contains space or newline")
+ }
+
+ line, ok := consumeInt64()
+ if !ok {
+ break
+ }
+ weight, ok := consumeInt64()
+ if !ok {
+ break
+ }
+
+ edge := NamedCallEdge{
+ CallerName: caller,
+ CalleeName: callee,
+ CallSiteOffset: int(line),
+ }
+
+ if _, ok := d.NamedEdgeMap.Weight[edge]; ok {
+ t.Skip("duplicate edge")
+ }
+
+ d.NamedEdgeMap.Weight[edge] = weight
+ d.TotalWeight += weight
+ }
+
+ byWeight := make([]NamedCallEdge, 0, len(d.NamedEdgeMap.Weight))
+ for namedEdge := range d.NamedEdgeMap.Weight {
+ byWeight = append(byWeight, namedEdge)
+ }
+ sortByWeight(byWeight, d.NamedEdgeMap.Weight)
+ d.NamedEdgeMap.ByWeight = byWeight
+
+ return d
+}
+
+func FuzzRoundTrip(f *testing.F) {
+ f.Add([]byte("")) // empty profile
+
+ f.Fuzz(func(t *testing.T, b []byte) {
+ d := constructFuzzProfile(t, b)
+ testRoundTrip(t, d)
+ })
+}
diff --git a/src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/12fcf136fcb7463c b/src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/12fcf136fcb7463c
new file mode 100644
index 0000000000..31e3552bdc
--- /dev/null
+++ b/src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/12fcf136fcb7463c
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd00000000\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd0")
diff --git a/src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/2055d314024c8d6c b/src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/2055d314024c8d6c
new file mode 100644
index 0000000000..b44370f012
--- /dev/null
+++ b/src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/2055d314024c8d6c
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("\x00\x040000000000000")
diff --git a/src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/b615162315f7b72c b/src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/b615162315f7b72c
new file mode 100644
index 0000000000..094fc10fd6
--- /dev/null
+++ b/src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/b615162315f7b72c
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("\b00000000\x01\n000000000")
diff --git a/src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/fdc60117b431bbae b/src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/fdc60117b431bbae
new file mode 100644
index 0000000000..4f9af7b90a
--- /dev/null
+++ b/src/cmd/internal/pgo/testdata/fuzz/FuzzRoundTrip/fdc60117b431bbae
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("\x010\x01\r000000000")