| // Copyright 2015 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 gcprog implements an encoder for packed GC pointer bitmaps, |
| // known as GC programs. |
| // |
| // Program Format |
| // |
| // The GC program encodes a sequence of 0 and 1 bits indicating scalar or pointer words in an object. |
| // The encoding is a simple Lempel-Ziv program, with codes to emit literal bits and to repeat the |
| // last n bits c times. |
| // |
| // The possible codes are: |
| // |
| // 00000000: stop |
| // 0nnnnnnn: emit n bits copied from the next (n+7)/8 bytes, least significant bit first |
| // 10000000 n c: repeat the previous n bits c times; n, c are varints |
| // 1nnnnnnn c: repeat the previous n bits c times; c is a varint |
| // |
| // The numbers n and c, when they follow a code, are encoded as varints |
| // using the same encoding as encoding/binary's Uvarint. |
| // |
| package gcprog |
| |
| import ( |
| "fmt" |
| "io" |
| ) |
| |
| const progMaxLiteral = 127 // maximum n for literal n bit code |
| |
| // A Writer is an encoder for GC programs. |
| // |
| // The typical use of a Writer is to call Init, maybe call Debug, |
| // make a sequence of Ptr, Advance, Repeat, and Append calls |
| // to describe the data type, and then finally call End. |
| type Writer struct { |
| writeByte func(byte) |
| index int64 |
| b [progMaxLiteral]byte |
| nb int |
| debug io.Writer |
| debugBuf []byte |
| } |
| |
| // Init initializes w to write a new GC program |
| // by calling writeByte for each byte in the program. |
| func (w *Writer) Init(writeByte func(byte)) { |
| w.writeByte = writeByte |
| } |
| |
| // Debug causes the writer to print a debugging trace to out |
| // during future calls to methods like Ptr, Advance, and End. |
| // It also enables debugging checks during the encoding. |
| func (w *Writer) Debug(out io.Writer) { |
| w.debug = out |
| } |
| |
| // BitIndex returns the number of bits written to the bit stream so far. |
| func (w *Writer) BitIndex() int64 { |
| return w.index |
| } |
| |
| // byte writes the byte x to the output. |
| func (w *Writer) byte(x byte) { |
| if w.debug != nil { |
| w.debugBuf = append(w.debugBuf, x) |
| } |
| w.writeByte(x) |
| } |
| |
| // End marks the end of the program, writing any remaining bytes. |
| func (w *Writer) End() { |
| w.flushlit() |
| w.byte(0) |
| if w.debug != nil { |
| index := progbits(w.debugBuf) |
| if index != w.index { |
| println("gcprog: End wrote program for", index, "bits, but current index is", w.index) |
| panic("gcprog: out of sync") |
| } |
| } |
| } |
| |
| // Ptr emits a 1 into the bit stream at the given bit index. |
| // that is, it records that the index'th word in the object memory is a pointer. |
| // Any bits between the current index and the new index |
| // are set to zero, meaning the corresponding words are scalars. |
| func (w *Writer) Ptr(index int64) { |
| if index < w.index { |
| println("gcprog: Ptr at index", index, "but current index is", w.index) |
| panic("gcprog: invalid Ptr index") |
| } |
| w.ZeroUntil(index) |
| if w.debug != nil { |
| fmt.Fprintf(w.debug, "gcprog: ptr at %d\n", index) |
| } |
| w.lit(1) |
| } |
| |
| // ShouldRepeat reports whether it would be worthwhile to |
| // use a Repeat to describe c elements of n bits each, |
| // compared to just emitting c copies of the n-bit description. |
| func (w *Writer) ShouldRepeat(n, c int64) bool { |
| // Should we lay out the bits directly instead of |
| // encoding them as a repetition? Certainly if count==1, |
| // since there's nothing to repeat, but also if the total |
| // size of the plain pointer bits for the type will fit in |
| // 4 or fewer bytes, since using a repetition will require |
| // flushing the current bits plus at least one byte for |
| // the repeat size and one for the repeat count. |
| return c > 1 && c*n > 4*8 |
| } |
| |
| // Repeat emits an instruction to repeat the description |
| // of the last n words c times (including the initial description, c+1 times in total). |
| func (w *Writer) Repeat(n, c int64) { |
| if n == 0 || c == 0 { |
| return |
| } |
| w.flushlit() |
| if w.debug != nil { |
| fmt.Fprintf(w.debug, "gcprog: repeat %d × %d\n", n, c) |
| } |
| if n < 128 { |
| w.byte(0x80 | byte(n)) |
| } else { |
| w.byte(0x80) |
| w.varint(n) |
| } |
| w.varint(c) |
| w.index += n * c |
| } |
| |
| // ZeroUntil adds zeros to the bit stream until reaching the given index; |
| // that is, it records that the words from the most recent pointer until |
| // the index'th word are scalars. |
| // ZeroUntil is usually called in preparation for a call to Repeat, Append, or End. |
| func (w *Writer) ZeroUntil(index int64) { |
| if index < w.index { |
| println("gcprog: Advance", index, "but index is", w.index) |
| panic("gcprog: invalid Advance index") |
| } |
| skip := (index - w.index) |
| if skip == 0 { |
| return |
| } |
| if skip < 4*8 { |
| if w.debug != nil { |
| fmt.Fprintf(w.debug, "gcprog: advance to %d by literals\n", index) |
| } |
| for i := int64(0); i < skip; i++ { |
| w.lit(0) |
| } |
| return |
| } |
| |
| if w.debug != nil { |
| fmt.Fprintf(w.debug, "gcprog: advance to %d by repeat\n", index) |
| } |
| w.lit(0) |
| w.flushlit() |
| w.Repeat(1, skip-1) |
| } |
| |
| // Append emits the given GC program into the current output. |
| // The caller asserts that the program emits n bits (describes n words), |
| // and Append panics if that is not true. |
| func (w *Writer) Append(prog []byte, n int64) { |
| w.flushlit() |
| if w.debug != nil { |
| fmt.Fprintf(w.debug, "gcprog: append prog for %d ptrs\n", n) |
| fmt.Fprintf(w.debug, "\t") |
| } |
| n1 := progbits(prog) |
| if n1 != n { |
| panic("gcprog: wrong bit count in append") |
| } |
| // The last byte of the prog terminates the program. |
| // Don't emit that, or else our own program will end. |
| for i, x := range prog[:len(prog)-1] { |
| if w.debug != nil { |
| if i > 0 { |
| fmt.Fprintf(w.debug, " ") |
| } |
| fmt.Fprintf(w.debug, "%02x", x) |
| } |
| w.byte(x) |
| } |
| if w.debug != nil { |
| fmt.Fprintf(w.debug, "\n") |
| } |
| w.index += n |
| } |
| |
| // progbits returns the length of the bit stream encoded by the program p. |
| func progbits(p []byte) int64 { |
| var n int64 |
| for len(p) > 0 { |
| x := p[0] |
| p = p[1:] |
| if x == 0 { |
| break |
| } |
| if x&0x80 == 0 { |
| count := x &^ 0x80 |
| n += int64(count) |
| p = p[(count+7)/8:] |
| continue |
| } |
| nbit := int64(x &^ 0x80) |
| if nbit == 0 { |
| nbit, p = readvarint(p) |
| } |
| var count int64 |
| count, p = readvarint(p) |
| n += nbit * count |
| } |
| if len(p) > 0 { |
| println("gcprog: found end instruction after", n, "ptrs, with", len(p), "bytes remaining") |
| panic("gcprog: extra data at end of program") |
| } |
| return n |
| } |
| |
| // readvarint reads a varint from p, returning the value and the remainder of p. |
| func readvarint(p []byte) (int64, []byte) { |
| var v int64 |
| var nb uint |
| for { |
| c := p[0] |
| p = p[1:] |
| v |= int64(c&^0x80) << nb |
| nb += 7 |
| if c&0x80 == 0 { |
| break |
| } |
| } |
| return v, p |
| } |
| |
| // lit adds a single literal bit to w. |
| func (w *Writer) lit(x byte) { |
| if w.nb == progMaxLiteral { |
| w.flushlit() |
| } |
| w.b[w.nb] = x |
| w.nb++ |
| w.index++ |
| } |
| |
| // varint emits the varint encoding of x. |
| func (w *Writer) varint(x int64) { |
| if x < 0 { |
| panic("gcprog: negative varint") |
| } |
| for x >= 0x80 { |
| w.byte(byte(0x80 | x)) |
| x >>= 7 |
| } |
| w.byte(byte(x)) |
| } |
| |
| // flushlit flushes any pending literal bits. |
| func (w *Writer) flushlit() { |
| if w.nb == 0 { |
| return |
| } |
| if w.debug != nil { |
| fmt.Fprintf(w.debug, "gcprog: flush %d literals\n", w.nb) |
| fmt.Fprintf(w.debug, "\t%v\n", w.b[:w.nb]) |
| fmt.Fprintf(w.debug, "\t%02x", byte(w.nb)) |
| } |
| w.byte(byte(w.nb)) |
| var bits uint8 |
| for i := 0; i < w.nb; i++ { |
| bits |= w.b[i] << uint(i%8) |
| if (i+1)%8 == 0 { |
| if w.debug != nil { |
| fmt.Fprintf(w.debug, " %02x", bits) |
| } |
| w.byte(bits) |
| bits = 0 |
| } |
| } |
| if w.nb%8 != 0 { |
| if w.debug != nil { |
| fmt.Fprintf(w.debug, " %02x", bits) |
| } |
| w.byte(bits) |
| } |
| if w.debug != nil { |
| fmt.Fprintf(w.debug, "\n") |
| } |
| w.nb = 0 |
| } |