diff --git a/pkg/sentry/pgalloc/save_restore.go b/pkg/sentry/pgalloc/save_restore.go index e65e8419e..6e56e58fa 100644 --- a/pkg/sentry/pgalloc/save_restore.go +++ b/pkg/sentry/pgalloc/save_restore.go @@ -30,6 +30,7 @@ import ( "gvisor.dev/gvisor/pkg/sentry/usage" "gvisor.dev/gvisor/pkg/state" "gvisor.dev/gvisor/pkg/state/statefile" + "gvisor.dev/gvisor/pkg/state/wire" "gvisor.dev/gvisor/pkg/sync" ) @@ -171,6 +172,7 @@ func (f *MemoryFile) SaveTo(ctx context.Context, w io.Writer, pw io.Writer, opts log.Debugf("MemoryFile.SaveTo: saved metadata in %s", time.Since(timeMetadataStart)) // Dump out committed pages. + ww := wire.Writer{Writer: w} timePagesStart := time.Now() savedBytes := uint64(0) for maseg := f.memAcct.FirstSegment(); maseg.Ok(); maseg = maseg.NextSegment() { @@ -178,7 +180,7 @@ func (f *MemoryFile) SaveTo(ctx context.Context, w io.Writer, pw io.Writer, opts continue } // Write a header to distinguish from objects. - if err := state.WriteHeader(w, uint64(maseg.Range().Length()), false); err != nil { + if err := state.WriteHeader(&ww, uint64(maseg.Range().Length()), false); err != nil { return err } // Write out data. @@ -304,6 +306,7 @@ func (f *MemoryFile) LoadFrom(ctx context.Context, r io.Reader, pr *statefile.As defer madviseWG.Wait() // Load committed pages. + wr := wire.Reader{Reader: r} timePagesStart := time.Now() loadedBytes := uint64(0) for maseg := f.memAcct.FirstSegment(); maseg.Ok(); maseg = maseg.NextSegment() { @@ -311,7 +314,7 @@ func (f *MemoryFile) LoadFrom(ctx context.Context, r io.Reader, pr *statefile.As continue } // Verify header. - length, object, err := state.ReadHeader(r) + length, object, err := state.ReadHeader(&wr) if err != nil { return err } diff --git a/pkg/state/decode.go b/pkg/state/decode.go index 79beeb016..fd3787684 100644 --- a/pkg/state/decode.go +++ b/pkg/state/decode.go @@ -18,7 +18,6 @@ import ( "bytes" "context" "fmt" - "io" "math" "reflect" @@ -143,7 +142,7 @@ type decodeState struct { ctx context.Context // r is the input stream. - r io.Reader + r wire.Reader // types is the type database. types typeDecodeDatabase @@ -591,7 +590,7 @@ func (ds *decodeState) Load(obj reflect.Value) { ds.pending.PushBack(rootOds) // Read the number of objects. - numObjects, object, err := ReadHeader(ds.r) + numObjects, object, err := ReadHeader(&ds.r) if err != nil { Failf("header error: %w", err) } @@ -613,7 +612,7 @@ func (ds *decodeState) Load(obj reflect.Value) { // decoding loop in state/pretty/pretty.printer.printStream(). for i := uint64(0); i < numObjects; { // Unmarshal either a type object or object ID. - encoded = wire.Load(ds.r) + encoded = wire.Load(&ds.r) switch we := encoded.(type) { case *wire.Type: ds.types.Register(we) @@ -624,7 +623,7 @@ func (ds *decodeState) Load(obj reflect.Value) { id = objectID(we) i++ // Unmarshal and resolve the actual object. - encoded = wire.Load(ds.r) + encoded = wire.Load(&ds.r) ods = ds.lookup(id) if ods != nil { // Decode the object. @@ -718,7 +717,7 @@ func (ds *decodeState) Load(obj reflect.Value) { // Each object written to the statefile is prefixed with a header. See // WriteHeader for more information; these functions are exported to allow // non-state writes to the file to play nice with debugging tools. -func ReadHeader(r io.Reader) (length uint64, object bool, err error) { +func ReadHeader(r *wire.Reader) (length uint64, object bool, err error) { // Read the header. err = safely(func() { length = wire.LoadUint(r) diff --git a/pkg/state/encode.go b/pkg/state/encode.go index ee1ce3242..861be309f 100644 --- a/pkg/state/encode.go +++ b/pkg/state/encode.go @@ -16,7 +16,6 @@ package state import ( "context" - "io" "reflect" "sort" @@ -62,7 +61,7 @@ type encodeState struct { ctx context.Context // w is the output stream. - w io.Writer + w wire.Writer // types is the type database. types typeEncodeDatabase @@ -781,7 +780,7 @@ func (es *encodeState) Save(obj reflect.Value) { } // Write the header with the number of objects. - if err := WriteHeader(es.w, uint64(len(es.pending)), true); err != nil { + if err := WriteHeader(&es.w, uint64(len(es.pending)), true); err != nil { Failf("error writing header: %w", err) } @@ -791,7 +790,7 @@ func (es *encodeState) Save(obj reflect.Value) { if err := safely(func() { for _, wt := range es.pendingTypes { // Encode the type. - wire.Save(es.w, &wt) + wire.Save(&es.w, &wt) } // Emit objects in ID order. ids := make([]objectID, 0, len(es.pending)) @@ -803,10 +802,10 @@ func (es *encodeState) Save(obj reflect.Value) { }) for _, id := range ids { // Encode the id. - wire.Save(es.w, wire.Uint(id)) + wire.Save(&es.w, wire.Uint(id)) // Marshal the object. oes := es.pending[id] - wire.Save(es.w, oes.encoded) + wire.Save(&es.w, oes.encoded) } }); err != nil { // Include the object and the error. @@ -825,7 +824,7 @@ const objectFlag uint64 = 1 << 63 // order to generate statefiles that play nicely with debugging tools, raw // writes should be prefixed with a header with object set to false and the // appropriate length. This will allow tools to skip these regions. -func WriteHeader(w io.Writer, length uint64, object bool) error { +func WriteHeader(w *wire.Writer, length uint64, object bool) error { // Sanity check the length. if length&objectFlag != 0 { Failf("impossibly huge length: %d", length) diff --git a/pkg/state/pretty/pretty.go b/pkg/state/pretty/pretty.go index 01ffe6cb4..be4ec514b 100644 --- a/pkg/state/pretty/pretty.go +++ b/pkg/state/pretty/pretty.go @@ -196,6 +196,8 @@ func (p *printer) format(graph uint64, depth int, encoded wire.Object) (string, // printStream is the basic print implementation. func (p *printer) printStream(w io.Writer, r io.Reader) (err error) { + wr := wire.Reader{Reader: r} + // current graph ID. var graph uint64 @@ -218,7 +220,7 @@ func (p *printer) printStream(w io.Writer, r io.Reader) (err error) { for { // Find the first object to begin generation. - length, object, err := state.ReadHeader(r) + length, object, err := state.ReadHeader(&wr) if err == io.EOF { // Nothing else to do. break @@ -252,7 +254,7 @@ func (p *printer) printStream(w io.Writer, r io.Reader) (err error) { ) for i := uint64(0); i < length; { // Unmarshal either a type object or object ID. - encoded := wire.Load(r) + encoded := wire.Load(&wr) switch we := encoded.(type) { case *wire.Type: str, _ := p.format(graph, 0, encoded) @@ -270,7 +272,7 @@ func (p *printer) printStream(w io.Writer, r io.Reader) (err error) { // Unmarshal the actual object. objects = append(objects, objectAndID{ id: uint64(we), - obj: wire.Load(r), + obj: wire.Load(&wr), }) i++ default: diff --git a/pkg/state/state.go b/pkg/state/state.go index 135192286..6251ce275 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -92,7 +92,7 @@ func Save(ctx context.Context, w io.Writer, rootPtr any) (Stats, error) { // Create the encoding state. es := encodeState{ ctx: ctx, - w: w, + w: wire.Writer{Writer: w}, types: makeTypeEncodeDatabase(), zeroValues: make(map[reflect.Type]*objectEncodeState), pending: make(map[objectID]*objectEncodeState), @@ -111,7 +111,7 @@ func Load(ctx context.Context, r io.Reader, rootPtr any) (Stats, error) { // Create the decoding state. ds := decodeState{ ctx: ctx, - r: r, + r: wire.Reader{Reader: r}, types: makeTypeDecodeDatabase(), deferred: make(map[objectID]wire.Object), } diff --git a/pkg/state/wire/BUILD b/pkg/state/wire/BUILD index b94f31ba1..2d522242e 100644 --- a/pkg/state/wire/BUILD +++ b/pkg/state/wire/BUILD @@ -13,6 +13,5 @@ go_library( visibility = ["//:sandbox"], deps = [ "//pkg/gohacks", - "//pkg/sync", ], ) diff --git a/pkg/state/wire/wire.go b/pkg/state/wire/wire.go index db2547ae4..657f3dbb5 100644 --- a/pkg/state/wire/wire.go +++ b/pkg/state/wire/wire.go @@ -33,17 +33,48 @@ import ( "math" "gvisor.dev/gvisor/pkg/gohacks" - "gvisor.dev/gvisor/pkg/sync" ) -var oneByteArrayPool = sync.Pool{ - New: func() any { return &[1]byte{} }, +// Reader bundles an io.Reader with a buffer used to implement readByte +// efficiently. +type Reader struct { + io.Reader + + buf [1]byte +} + +// readByte reads a single byte from r.Reader without allocation. It panics on +// error. +func (r *Reader) readByte() byte { + n, err := r.Read(r.buf[:]) + if n != 1 { + panic(err) + } + return r.buf[0] +} + +// Writer bundles an io.Writer with a buffer used to implement writeByte +// efficiently. +type Writer struct { + io.Writer + + buf [1]byte +} + +// writeByte writes a single byte to w.Writer without allocation. It panics on +// error. +func (w *Writer) writeByte(b byte) { + w.buf[0] = b + n, err := w.Write(w.buf[:]) + if n != 1 { + panic(err) + } } // readFull is a utility. The equivalent is not needed for Write, but the API // contract dictates that it must always complete all bytes given or return an // error. -func readFull(r io.Reader, p []byte) { +func readFull(r *Reader, p []byte) { for done := 0; done < len(p); { n, err := r.Read(p[done:]) done += n @@ -58,25 +89,25 @@ type Object interface { // save saves the given object. // // Panic is used for error control flow. - save(io.Writer) + save(*Writer) // load loads a new object of the given type. // // Panic is used for error control flow. - load(io.Reader) Object + load(*Reader) Object } // Bool is a boolean. type Bool bool // loadBool loads an object of type Bool. -func loadBool(r io.Reader) Bool { +func loadBool(r *Reader) Bool { b := loadUint(r) return Bool(b == 1) } // save implements Object.save. -func (b Bool) save(w io.Writer) { +func (b Bool) save(w *Writer) { var v Uint if b { v = 1 @@ -87,7 +118,7 @@ func (b Bool) save(w io.Writer) { } // load implements Object.load. -func (Bool) load(r io.Reader) Object { return loadBool(r) } +func (Bool) load(r *Reader) Object { return loadBool(r) } // Int is a signed integer. // @@ -95,7 +126,7 @@ func (Bool) load(r io.Reader) Object { return loadBool(r) } type Int int64 // loadInt loads an object of type Int. -func loadInt(r io.Reader) Int { +func loadInt(r *Reader) Int { u := loadUint(r) x := Int(u >> 1) if u&1 != 0 { @@ -105,7 +136,7 @@ func loadInt(r io.Reader) Int { } // save implements Object.save. -func (i Int) save(w io.Writer) { +func (i Int) save(w *Writer) { u := Uint(i) << 1 if i < 0 { u = ^u @@ -114,29 +145,19 @@ func (i Int) save(w io.Writer) { } // load implements Object.load. -func (Int) load(r io.Reader) Object { return loadInt(r) } +func (Int) load(r *Reader) Object { return loadInt(r) } // Uint is an unsigned integer. type Uint uint64 -func readByte(r io.Reader) byte { - p := oneByteArrayPool.Get().(*[1]byte) - defer oneByteArrayPool.Put(p) - n, err := r.Read(p[:]) - if n != 1 { - panic(err) - } - return p[0] -} - // loadUint loads an object of type Uint. -func loadUint(r io.Reader) Uint { +func loadUint(r *Reader) Uint { var ( u Uint s uint ) for i := 0; i <= 9; i++ { - b := readByte(r) + b := r.readByte() if b < 0x80 { if i == 9 && b > 1 { panic("overflow") @@ -150,76 +171,66 @@ func loadUint(r io.Reader) Uint { panic("unreachable") } -func writeByte(w io.Writer, b byte) { - p := oneByteArrayPool.Get().(*[1]byte) - defer oneByteArrayPool.Put(p) - p[0] = b - n, err := w.Write(p[:]) - if n != 1 { - panic(err) - } -} - // save implements Object.save. -func (u Uint) save(w io.Writer) { +func (u Uint) save(w *Writer) { for u >= 0x80 { - writeByte(w, byte(u)|0x80) + w.writeByte(byte(u) | 0x80) u >>= 7 } - writeByte(w, byte(u)) + w.writeByte(byte(u)) } // load implements Object.load. -func (Uint) load(r io.Reader) Object { return loadUint(r) } +func (Uint) load(r *Reader) Object { return loadUint(r) } // Float32 is a 32-bit floating point number. type Float32 float32 // loadFloat32 loads an object of type Float32. -func loadFloat32(r io.Reader) Float32 { +func loadFloat32(r *Reader) Float32 { n := loadUint(r) return Float32(math.Float32frombits(uint32(n))) } // save implements Object.save. -func (f Float32) save(w io.Writer) { +func (f Float32) save(w *Writer) { n := Uint(math.Float32bits(float32(f))) n.save(w) } // load implements Object.load. -func (Float32) load(r io.Reader) Object { return loadFloat32(r) } +func (Float32) load(r *Reader) Object { return loadFloat32(r) } // Float64 is a 64-bit floating point number. type Float64 float64 // loadFloat64 loads an object of type Float64. -func loadFloat64(r io.Reader) Float64 { +func loadFloat64(r *Reader) Float64 { n := loadUint(r) return Float64(math.Float64frombits(uint64(n))) } // save implements Object.save. -func (f Float64) save(w io.Writer) { +func (f Float64) save(w *Writer) { n := Uint(math.Float64bits(float64(f))) n.save(w) } // load implements Object.load. -func (Float64) load(r io.Reader) Object { return loadFloat64(r) } +func (Float64) load(r *Reader) Object { return loadFloat64(r) } // Complex64 is a 64-bit complex number. type Complex64 complex128 // loadComplex64 loads an object of type Complex64. -func loadComplex64(r io.Reader) Complex64 { +func loadComplex64(r *Reader) Complex64 { re := loadFloat32(r) im := loadFloat32(r) return Complex64(complex(float32(re), float32(im))) } // save implements Object.save. -func (c *Complex64) save(w io.Writer) { +func (c *Complex64) save(w *Writer) { re := Float32(real(*c)) im := Float32(imag(*c)) re.save(w) @@ -227,7 +238,7 @@ func (c *Complex64) save(w io.Writer) { } // load implements Object.load. -func (*Complex64) load(r io.Reader) Object { +func (*Complex64) load(r *Reader) Object { c := loadComplex64(r) return &c } @@ -236,14 +247,14 @@ func (*Complex64) load(r io.Reader) Object { type Complex128 complex128 // loadComplex128 loads an object of type Complex128. -func loadComplex128(r io.Reader) Complex128 { +func loadComplex128(r *Reader) Complex128 { re := loadFloat64(r) im := loadFloat64(r) return Complex128(complex(float64(re), float64(im))) } // save implements Object.save. -func (c *Complex128) save(w io.Writer) { +func (c *Complex128) save(w *Writer) { re := Float64(real(*c)) im := Float64(imag(*c)) re.save(w) @@ -251,7 +262,7 @@ func (c *Complex128) save(w io.Writer) { } // load implements Object.load. -func (*Complex128) load(r io.Reader) Object { +func (*Complex128) load(r *Reader) Object { c := loadComplex128(r) return &c } @@ -260,7 +271,7 @@ func (*Complex128) load(r io.Reader) Object { type String string // loadString loads an object of type String. -func loadString(r io.Reader) String { +func loadString(r *Reader) String { l := loadUint(r) p := make([]byte, l) readFull(r, p) @@ -268,7 +279,7 @@ func loadString(r io.Reader) String { } // save implements Object.save. -func (s *String) save(w io.Writer) { +func (s *String) save(w *Writer) { l := Uint(len(*s)) l.save(w) p := gohacks.ImmutableBytesFromString(string(*s)) @@ -279,7 +290,7 @@ func (s *String) save(w io.Writer) { } // load implements Object.load. -func (*String) load(r io.Reader) Object { +func (*String) load(r *Reader) Object { s := loadString(r) return &s } @@ -315,7 +326,7 @@ type Ref struct { } // loadRef loads an object of type Ref (abstract). -func loadRef(r io.Reader) Ref { +func loadRef(r *Reader) Ref { ref := Ref{ Root: loadUint(r), } @@ -343,7 +354,7 @@ func loadRef(r io.Reader) Ref { } // save implements Object.save. -func (r *Ref) save(w io.Writer) { +func (r *Ref) save(w *Writer) { r.Root.save(w) l := Uint(len(r.Dots)) l.save(w) @@ -372,7 +383,7 @@ func (r *Ref) save(w io.Writer) { } // load implements Object.load. -func (*Ref) load(r io.Reader) Object { +func (*Ref) load(r *Reader) Object { ref := loadRef(r) return &ref } @@ -381,15 +392,15 @@ func (*Ref) load(r io.Reader) Object { type Nil struct{} // loadNil loads an object of type Nil. -func loadNil(r io.Reader) Nil { +func loadNil(r *Reader) Nil { return Nil{} } // save implements Object.save. -func (Nil) save(w io.Writer) {} +func (Nil) save(w *Writer) {} // load implements Object.load. -func (Nil) load(r io.Reader) Object { return loadNil(r) } +func (Nil) load(r *Reader) Object { return loadNil(r) } // Slice is a slice value. type Slice struct { @@ -399,7 +410,7 @@ type Slice struct { } // loadSlice loads an object of type Slice. -func loadSlice(r io.Reader) Slice { +func loadSlice(r *Reader) Slice { return Slice{ Length: loadUint(r), Capacity: loadUint(r), @@ -408,14 +419,14 @@ func loadSlice(r io.Reader) Slice { } // save implements Object.save. -func (s *Slice) save(w io.Writer) { +func (s *Slice) save(w *Writer) { s.Length.save(w) s.Capacity.save(w) s.Ref.save(w) } // load implements Object.load. -func (*Slice) load(r io.Reader) Object { +func (*Slice) load(r *Reader) Object { s := loadSlice(r) return &s } @@ -426,7 +437,7 @@ type Array struct { } // loadArray loads an object of type Array. -func loadArray(r io.Reader) Array { +func loadArray(r *Reader) Array { l := loadUint(r) if l == 0 { // Note that there isn't a single object available to encode @@ -448,7 +459,7 @@ func loadArray(r io.Reader) Array { } // save implements Object.save. -func (a *Array) save(w io.Writer) { +func (a *Array) save(w *Writer) { l := Uint(len(a.Contents)) l.save(w) if l == 0 { @@ -463,7 +474,7 @@ func (a *Array) save(w io.Writer) { } // load implements Object.load. -func (*Array) load(r io.Reader) Object { +func (*Array) load(r *Reader) Object { a := loadArray(r) return &a } @@ -475,7 +486,7 @@ type Map struct { } // loadMap loads an object of type Map. -func loadMap(r io.Reader) Map { +func loadMap(r *Reader) Map { l := loadUint(r) if l == 0 { // See LoadArray. @@ -499,7 +510,7 @@ func loadMap(r io.Reader) Map { } // save implements Object.save. -func (m *Map) save(w io.Writer) { +func (m *Map) save(w *Writer) { l := Uint(len(m.Keys)) if int(l) != len(m.Values) { panic(fmt.Sprintf("mismatched keys (%d) Aand values (%d)", len(m.Keys), len(m.Values))) @@ -519,7 +530,7 @@ func (m *Map) save(w io.Writer) { } // load implements Object.load. -func (*Map) load(r io.Reader) Object { +func (*Map) load(r *Reader) Object { m := loadMap(r) return &m } @@ -584,7 +595,7 @@ const ( ) // loadTypeSpec loads TypeSpec values. -func loadTypeSpec(r io.Reader) TypeSpec { +func loadTypeSpec(r *Reader) TypeSpec { switch hdr := loadUint(r); hdr { case typeSpecTypeID: return TypeID(loadUint(r)) @@ -615,7 +626,7 @@ func loadTypeSpec(r io.Reader) TypeSpec { } // saveTypeSpec saves TypeSpec values. -func saveTypeSpec(w io.Writer, t TypeSpec) { +func saveTypeSpec(w *Writer, t TypeSpec) { switch x := t.(type) { case TypeID: typeSpecTypeID.save(w) @@ -649,7 +660,7 @@ type Interface struct { } // loadInterface loads an object of type Interface. -func loadInterface(r io.Reader) Interface { +func loadInterface(r *Reader) Interface { return Interface{ Type: loadTypeSpec(r), Value: Load(r), @@ -657,13 +668,13 @@ func loadInterface(r io.Reader) Interface { } // save implements Object.save. -func (i *Interface) save(w io.Writer) { +func (i *Interface) save(w *Writer) { saveTypeSpec(w, i.Type) Save(w, i.Value) } // load implements Object.load. -func (*Interface) load(r io.Reader) Object { +func (*Interface) load(r *Reader) Object { i := loadInterface(r) return &i } @@ -675,7 +686,7 @@ type Type struct { } // loadType loads an object of type Type. -func loadType(r io.Reader) Type { +func loadType(r *Reader) Type { name := string(loadString(r)) l := loadUint(r) fields := make([]string, l) @@ -689,7 +700,7 @@ func loadType(r io.Reader) Type { } // save implements Object.save. -func (t *Type) save(w io.Writer) { +func (t *Type) save(w *Writer) { s := String(t.Name) s.save(w) l := Uint(len(t.Fields)) @@ -701,7 +712,7 @@ func (t *Type) save(w io.Writer) { } // load implements Object.load. -func (*Type) load(r io.Reader) Object { +func (*Type) load(r *Reader) Object { t := loadType(r) return &t } @@ -710,7 +721,7 @@ func (*Type) load(r io.Reader) Object { type multipleObjects []Object // loadMultipleObjects loads a series of objects. -func loadMultipleObjects(r io.Reader) multipleObjects { +func loadMultipleObjects(r *Reader) multipleObjects { l := loadUint(r) m := make(multipleObjects, l) for i := 0; i < int(l); i++ { @@ -720,7 +731,7 @@ func loadMultipleObjects(r io.Reader) multipleObjects { } // save implements Object.save. -func (m *multipleObjects) save(w io.Writer) { +func (m *multipleObjects) save(w *Writer) { l := Uint(len(*m)) l.save(w) for i := 0; i < int(l); i++ { @@ -729,7 +740,7 @@ func (m *multipleObjects) save(w io.Writer) { } // load implements Object.load. -func (*multipleObjects) load(r io.Reader) Object { +func (*multipleObjects) load(r *Reader) Object { m := loadMultipleObjects(r) return &m } @@ -738,13 +749,13 @@ func (*multipleObjects) load(r io.Reader) Object { type noObjects struct{} // loadNoObjects loads a sentinel. -func loadNoObjects(r io.Reader) noObjects { return noObjects{} } +func loadNoObjects(r *Reader) noObjects { return noObjects{} } // save implements Object.save. -func (noObjects) save(w io.Writer) {} +func (noObjects) save(w *Writer) {} // load implements Object.load. -func (noObjects) load(r io.Reader) Object { return loadNoObjects(r) } +func (noObjects) load(r *Reader) Object { return loadNoObjects(r) } // Struct is a basic composite value. type Struct struct { @@ -799,7 +810,7 @@ func (s *Struct) Fields() int { } // loadStruct loads an object of type Struct. -func loadStruct(r io.Reader) Struct { +func loadStruct(r *Reader) Struct { return Struct{ TypeID: TypeID(loadUint(r)), fields: Load(r), @@ -810,13 +821,13 @@ func loadStruct(r io.Reader) Struct { // // Precondition: Alloc must have been called, and the fields all filled in // appropriately. See Alloc and Add for more details. -func (s *Struct) save(w io.Writer) { +func (s *Struct) save(w *Writer) { Uint(s.TypeID).save(w) Save(w, s.fields) } // load implements Object.load. -func (*Struct) load(r io.Reader) Object { +func (*Struct) load(r *Reader) Object { s := loadStruct(r) return &s } @@ -851,7 +862,7 @@ const ( // +checkescape all // // N.B. This function will panic on error. -func Save(w io.Writer, obj Object) { +func Save(w *Writer, obj Object) { switch x := obj.(type) { case Bool: typeBool.save(w) @@ -917,7 +928,7 @@ func Save(w io.Writer, obj Object) { // +checkescape all // // N.B. This function will panic on error. -func Load(r io.Reader) Object { +func Load(r *Reader) Object { switch hdr := loadUint(r); hdr { case typeBool: return loadBool(r) @@ -964,13 +975,13 @@ func Load(r io.Reader) Object { // LoadUint loads a single unsigned integer. // // N.B. This function will panic on error. -func LoadUint(r io.Reader) uint64 { +func LoadUint(r *Reader) uint64 { return uint64(loadUint(r)) } // SaveUint saves a single unsigned integer. // // N.B. This function will panic on error. -func SaveUint(w io.Writer, v uint64) { +func SaveUint(w *Writer, v uint64) { Uint(v).save(w) }