Skip to content

Commit 830621b

Browse files
committed
internal/pkgbits: add Version type
Adds a new Version type to pkgbits to represent the version of the bitstream. Versions let readers and writers know when different data is expected to be present or not in the bitstream. These different pieces of data are called Fields, as an analogy with fields of a struct. Fields can be added, removed or changed in a Version. Extends Encoder and Decoder to report which version they are. Updates #68778 Change-Id: Iaffa1828544fb4cbc47a905de853449bc8e5b91f Reviewed-on: https://go-review.googlesource.com/c/go/+/605655 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: David Chase <drchase@google.com> Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
1 parent 54c948d commit 830621b

File tree

4 files changed

+169
-21
lines changed

4 files changed

+169
-21
lines changed

src/internal/pkgbits/decoder.go

+12-10
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
// export data.
2222
type PkgDecoder struct {
2323
// version is the file format version.
24-
version uint32
24+
version Version
2525

2626
// sync indicates whether the file uses sync markers.
2727
sync bool
@@ -68,8 +68,6 @@ func (pr *PkgDecoder) SyncMarkers() bool { return pr.sync }
6868
// NewPkgDecoder returns a PkgDecoder initialized to read the Unified
6969
// IR export data from input. pkgPath is the package path for the
7070
// compilation unit that produced the export data.
71-
//
72-
// TODO(mdempsky): Remove pkgPath parameter; unneeded since CL 391014.
7371
func NewPkgDecoder(pkgPath, input string) PkgDecoder {
7472
pr := PkgDecoder{
7573
pkgPath: pkgPath,
@@ -80,14 +78,15 @@ func NewPkgDecoder(pkgPath, input string) PkgDecoder {
8078

8179
r := strings.NewReader(input)
8280

83-
assert(binary.Read(r, binary.LittleEndian, &pr.version) == nil)
81+
var ver uint32
82+
assert(binary.Read(r, binary.LittleEndian, &ver) == nil)
83+
pr.version = Version(ver)
8484

85-
switch pr.version {
86-
default:
87-
panicf("unsupported version: %v", pr.version)
88-
case 0:
89-
// no flags
90-
case 1:
85+
if pr.version >= V2 { // TODO(taking): Switch to numVersions.
86+
panic(fmt.Errorf("cannot decode %q, export data version %d is too new", pkgPath, pr.version))
87+
}
88+
89+
if pr.version.Has(Flags) {
9190
var flags uint32
9291
assert(binary.Read(r, binary.LittleEndian, &flags) == nil)
9392
pr.sync = flags&flagSyncMarkers != 0
@@ -513,3 +512,6 @@ func (pr *PkgDecoder) PeekObj(idx Index) (string, string, CodeObj) {
513512

514513
return path, name, tag
515514
}
515+
516+
// Version reports the version of the bitstream.
517+
func (w *Decoder) Version() Version { return w.common.version }

src/internal/pkgbits/encoder.go

+11-11
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,15 @@ import (
1515
"strings"
1616
)
1717

18-
// currentVersion is the current version number.
19-
//
20-
// - v0: initial prototype
21-
//
22-
// - v1: adds the flags uint32 word
23-
//
24-
// TODO(mdempsky): For the next version bump:
25-
// - remove the legacy "has init" bool from the public root
26-
// - remove obj's "derived func instance" bool
27-
const currentVersion uint32 = 1
18+
// currentVersion is the current version number written.
19+
const currentVersion = V1
2820

2921
// A PkgEncoder provides methods for encoding a package's Unified IR
3022
// export data.
3123
type PkgEncoder struct {
24+
// version of the bitstream.
25+
version Version
26+
3227
// elems holds the bitstream for previously encoded elements.
3328
elems [numRelocs][]string
3429

@@ -54,6 +49,8 @@ func (pw *PkgEncoder) SyncMarkers() bool { return pw.syncFrames >= 0 }
5449
// negative, then sync markers are omitted entirely.
5550
func NewPkgEncoder(syncFrames int) PkgEncoder {
5651
return PkgEncoder{
52+
// TODO(taking): Change NewPkgEncoder to take a version as an argument, and remove currentVersion.
53+
version: currentVersion,
5754
stringsIdx: make(map[string]Index),
5855
syncFrames: syncFrames,
5956
}
@@ -69,7 +66,7 @@ func (pw *PkgEncoder) DumpTo(out0 io.Writer) (fingerprint [8]byte) {
6966
assert(binary.Write(out, binary.LittleEndian, x) == nil)
7067
}
7168

72-
writeUint32(currentVersion)
69+
writeUint32(uint32(pw.version))
7370

7471
var flags uint32
7572
if pw.SyncMarkers() {
@@ -392,3 +389,6 @@ func (w *Encoder) bigFloat(v *big.Float) {
392389
b := v.Append(nil, 'p', -1)
393390
w.String(string(b)) // TODO: More efficient encoding.
394391
}
392+
393+
// Version reports the version of the bitstream.
394+
func (w *Encoder) Version() Version { return w.p.version }

src/internal/pkgbits/pkgbits_test.go

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package pkgbits_test
6+
7+
import (
8+
"internal/pkgbits"
9+
"strings"
10+
"testing"
11+
)
12+
13+
func TestRoundTrip(t *testing.T) {
14+
pw := pkgbits.NewPkgEncoder(-1)
15+
w := pw.NewEncoder(pkgbits.RelocMeta, pkgbits.SyncPublic)
16+
w.Flush()
17+
18+
var b strings.Builder
19+
_ = pw.DumpTo(&b)
20+
input := b.String()
21+
22+
pr := pkgbits.NewPkgDecoder("package_id", input)
23+
r := pr.NewDecoder(pkgbits.RelocMeta, pkgbits.PublicRootIdx, pkgbits.SyncPublic)
24+
25+
if r.Version() != w.Version() {
26+
t.Errorf("Expected reader version %q to be the writer version %q", r.Version(), w.Version())
27+
}
28+
}
29+
30+
// Type checker to enforce that know V* have the constant values they must have.
31+
var _ [0]bool = [pkgbits.V0]bool{}
32+
var _ [1]bool = [pkgbits.V1]bool{}
33+
34+
func TestVersions(t *testing.T) {
35+
type vfpair struct {
36+
v pkgbits.Version
37+
f pkgbits.Field
38+
}
39+
40+
// has field tests
41+
for _, c := range []vfpair{
42+
{pkgbits.V1, pkgbits.Flags},
43+
{pkgbits.V2, pkgbits.Flags},
44+
{pkgbits.V0, pkgbits.HasInit},
45+
{pkgbits.V1, pkgbits.HasInit},
46+
{pkgbits.V0, pkgbits.DerivedFuncInstance},
47+
{pkgbits.V1, pkgbits.DerivedFuncInstance},
48+
{pkgbits.V2, pkgbits.AliasTypeParamNames},
49+
} {
50+
if !c.v.Has(c.f) {
51+
t.Errorf("Expected version %v to have field %v", c.v, c.f)
52+
}
53+
}
54+
55+
// does not have field tests
56+
for _, c := range []vfpair{
57+
{pkgbits.V0, pkgbits.Flags},
58+
{pkgbits.V2, pkgbits.HasInit},
59+
{pkgbits.V2, pkgbits.DerivedFuncInstance},
60+
{pkgbits.V0, pkgbits.AliasTypeParamNames},
61+
{pkgbits.V1, pkgbits.AliasTypeParamNames},
62+
} {
63+
if c.v.Has(c.f) {
64+
t.Errorf("Expected version %v to not have field %v", c.v, c.f)
65+
}
66+
}
67+
}

src/internal/pkgbits/version.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package pkgbits
6+
7+
// Version indicates a version of a unified IR bitstream.
8+
// Each Version indicates the addition, removal, or change of
9+
// new data in the bitstream.
10+
//
11+
// These are serialized to disk and the interpretation remains fixed.
12+
type Version uint32
13+
14+
const (
15+
// V0: initial prototype.
16+
//
17+
// All data that is not assigned a Field is in version V0
18+
// and has not been deprecated.
19+
V0 Version = iota
20+
21+
// V1: adds the Flags uint32 word
22+
V1
23+
24+
// V2: removes unused legacy fields and supports type parameters for aliases.
25+
// - remove the legacy "has init" bool from the public root
26+
// - remove obj's "derived func instance" bool
27+
// - add a TypeParamNames field to ObjAlias
28+
V2
29+
30+
numVersions = iota
31+
)
32+
33+
// Field denotes a unit of data in the serialized unified IR bitstream.
34+
// It is conceptually a like field in a structure.
35+
//
36+
// We only really need Fields when the data may or may not be present
37+
// in a stream based on the Version of the bitstream.
38+
//
39+
// Unlike much of pkgbits, Fields are not serialized and
40+
// can change values as needed.
41+
type Field int
42+
43+
const (
44+
// Flags in a uint32 in the header of a bitstream
45+
// that is used to indicate whether optional features are enabled.
46+
Flags Field = iota
47+
48+
// Deprecated: HasInit was a bool indicating whether a package
49+
// has any init functions.
50+
HasInit
51+
52+
// Deprecated: DerivedFuncInstance was a bool indicating
53+
// whether an object was a function instance.
54+
DerivedFuncInstance
55+
56+
// ObjAlias has a list of TypeParamNames.
57+
AliasTypeParamNames
58+
59+
numFields = iota
60+
)
61+
62+
// introduced is the version a field was added.
63+
var introduced = [numFields]Version{
64+
Flags: V1,
65+
AliasTypeParamNames: V2,
66+
}
67+
68+
// removed is the version a field was removed in or 0 for fields
69+
// that have not yet been deprecated.
70+
// (So removed[f]-1 is the last version it is included in.)
71+
var removed = [numFields]Version{
72+
HasInit: V2,
73+
DerivedFuncInstance: V2,
74+
}
75+
76+
// Has reports whether field f is present in a bitstream at version v.
77+
func (v Version) Has(f Field) bool {
78+
return introduced[f] <= v && (v < removed[f] || removed[f] == V0)
79+
}

0 commit comments

Comments
 (0)