// export data.
type PkgDecoder struct {
// version is the file format version.
- version uint32
+ version Version
// sync indicates whether the file uses sync markers.
sync bool
// NewPkgDecoder returns a PkgDecoder initialized to read the Unified
// IR export data from input. pkgPath is the package path for the
// compilation unit that produced the export data.
-//
-// TODO(mdempsky): Remove pkgPath parameter; unneeded since CL 391014.
func NewPkgDecoder(pkgPath, input string) PkgDecoder {
pr := PkgDecoder{
pkgPath: pkgPath,
r := strings.NewReader(input)
- assert(binary.Read(r, binary.LittleEndian, &pr.version) == nil)
+ var ver uint32
+ assert(binary.Read(r, binary.LittleEndian, &ver) == nil)
+ pr.version = Version(ver)
- switch pr.version {
- default:
- panicf("unsupported version: %v", pr.version)
- case 0:
- // no flags
- case 1:
+ if pr.version >= V2 { // TODO(taking): Switch to numVersions.
+ panic(fmt.Errorf("cannot decode %q, export data version %d is too new", pkgPath, pr.version))
+ }
+
+ if pr.version.Has(Flags) {
var flags uint32
assert(binary.Read(r, binary.LittleEndian, &flags) == nil)
pr.sync = flags&flagSyncMarkers != 0
return path, name, tag
}
+
+// Version reports the version of the bitstream.
+func (w *Decoder) Version() Version { return w.common.version }
"strings"
)
-// currentVersion is the current version number.
-//
-// - v0: initial prototype
-//
-// - v1: adds the flags uint32 word
-//
-// TODO(mdempsky): For the next version bump:
-// - remove the legacy "has init" bool from the public root
-// - remove obj's "derived func instance" bool
-const currentVersion uint32 = 1
+// currentVersion is the current version number written.
+const currentVersion = V1
// A PkgEncoder provides methods for encoding a package's Unified IR
// export data.
type PkgEncoder struct {
+ // version of the bitstream.
+ version Version
+
// elems holds the bitstream for previously encoded elements.
elems [numRelocs][]string
// negative, then sync markers are omitted entirely.
func NewPkgEncoder(syncFrames int) PkgEncoder {
return PkgEncoder{
+ // TODO(taking): Change NewPkgEncoder to take a version as an argument, and remove currentVersion.
+ version: currentVersion,
stringsIdx: make(map[string]Index),
syncFrames: syncFrames,
}
assert(binary.Write(out, binary.LittleEndian, x) == nil)
}
- writeUint32(currentVersion)
+ writeUint32(uint32(pw.version))
var flags uint32
if pw.SyncMarkers() {
b := v.Append(nil, 'p', -1)
w.String(string(b)) // TODO: More efficient encoding.
}
+
+// Version reports the version of the bitstream.
+func (w *Encoder) Version() Version { return w.p.version }
--- /dev/null
+// 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 pkgbits_test
+
+import (
+ "internal/pkgbits"
+ "strings"
+ "testing"
+)
+
+func TestRoundTrip(t *testing.T) {
+ pw := pkgbits.NewPkgEncoder(-1)
+ w := pw.NewEncoder(pkgbits.RelocMeta, pkgbits.SyncPublic)
+ w.Flush()
+
+ var b strings.Builder
+ _ = pw.DumpTo(&b)
+ input := b.String()
+
+ pr := pkgbits.NewPkgDecoder("package_id", input)
+ r := pr.NewDecoder(pkgbits.RelocMeta, pkgbits.PublicRootIdx, pkgbits.SyncPublic)
+
+ if r.Version() != w.Version() {
+ t.Errorf("Expected reader version %q to be the writer version %q", r.Version(), w.Version())
+ }
+}
+
+// Type checker to enforce that know V* have the constant values they must have.
+var _ [0]bool = [pkgbits.V0]bool{}
+var _ [1]bool = [pkgbits.V1]bool{}
+
+func TestVersions(t *testing.T) {
+ type vfpair struct {
+ v pkgbits.Version
+ f pkgbits.Field
+ }
+
+ // has field tests
+ for _, c := range []vfpair{
+ {pkgbits.V1, pkgbits.Flags},
+ {pkgbits.V2, pkgbits.Flags},
+ {pkgbits.V0, pkgbits.HasInit},
+ {pkgbits.V1, pkgbits.HasInit},
+ {pkgbits.V0, pkgbits.DerivedFuncInstance},
+ {pkgbits.V1, pkgbits.DerivedFuncInstance},
+ {pkgbits.V2, pkgbits.AliasTypeParamNames},
+ } {
+ if !c.v.Has(c.f) {
+ t.Errorf("Expected version %v to have field %v", c.v, c.f)
+ }
+ }
+
+ // does not have field tests
+ for _, c := range []vfpair{
+ {pkgbits.V0, pkgbits.Flags},
+ {pkgbits.V2, pkgbits.HasInit},
+ {pkgbits.V2, pkgbits.DerivedFuncInstance},
+ {pkgbits.V0, pkgbits.AliasTypeParamNames},
+ {pkgbits.V1, pkgbits.AliasTypeParamNames},
+ } {
+ if c.v.Has(c.f) {
+ t.Errorf("Expected version %v to not have field %v", c.v, c.f)
+ }
+ }
+}
--- /dev/null
+// Copyright 2021 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 pkgbits
+
+// Version indicates a version of a unified IR bitstream.
+// Each Version indicates the addition, removal, or change of
+// new data in the bitstream.
+//
+// These are serialized to disk and the interpretation remains fixed.
+type Version uint32
+
+const (
+ // V0: initial prototype.
+ //
+ // All data that is not assigned a Field is in version V0
+ // and has not been deprecated.
+ V0 Version = iota
+
+ // V1: adds the Flags uint32 word
+ V1
+
+ // V2: removes unused legacy fields and supports type parameters for aliases.
+ // - remove the legacy "has init" bool from the public root
+ // - remove obj's "derived func instance" bool
+ // - add a TypeParamNames field to ObjAlias
+ V2
+
+ numVersions = iota
+)
+
+// Field denotes a unit of data in the serialized unified IR bitstream.
+// It is conceptually a like field in a structure.
+//
+// We only really need Fields when the data may or may not be present
+// in a stream based on the Version of the bitstream.
+//
+// Unlike much of pkgbits, Fields are not serialized and
+// can change values as needed.
+type Field int
+
+const (
+ // Flags in a uint32 in the header of a bitstream
+ // that is used to indicate whether optional features are enabled.
+ Flags Field = iota
+
+ // Deprecated: HasInit was a bool indicating whether a package
+ // has any init functions.
+ HasInit
+
+ // Deprecated: DerivedFuncInstance was a bool indicating
+ // whether an object was a function instance.
+ DerivedFuncInstance
+
+ // ObjAlias has a list of TypeParamNames.
+ AliasTypeParamNames
+
+ numFields = iota
+)
+
+// introduced is the version a field was added.
+var introduced = [numFields]Version{
+ Flags: V1,
+ AliasTypeParamNames: V2,
+}
+
+// removed is the version a field was removed in or 0 for fields
+// that have not yet been deprecated.
+// (So removed[f]-1 is the last version it is included in.)
+var removed = [numFields]Version{
+ HasInit: V2,
+ DerivedFuncInstance: V2,
+}
+
+// Has reports whether field f is present in a bitstream at version v.
+func (v Version) Has(f Field) bool {
+ return introduced[f] <= v && (v < removed[f] || removed[f] == V0)
+}