summaryrefslogtreecommitdiffstats
path: root/src/cmd/internal/gcprog/gcprog.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
commit43a123c1ae6613b3efeed291fa552ecd909d3acf (patch)
treefd92518b7024bc74031f78a1cf9e454b65e73665 /src/cmd/internal/gcprog/gcprog.go
parentInitial commit. (diff)
downloadgolang-1.20-upstream.tar.xz
golang-1.20-upstream.zip
Adding upstream version 1.20.14.upstream/1.20.14upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/internal/gcprog/gcprog.go')
-rw-r--r--src/cmd/internal/gcprog/gcprog.go296
1 files changed, 296 insertions, 0 deletions
diff --git a/src/cmd/internal/gcprog/gcprog.go b/src/cmd/internal/gcprog/gcprog.go
new file mode 100644
index 0000000..eeea53d
--- /dev/null
+++ b/src/cmd/internal/gcprog/gcprog.go
@@ -0,0 +1,296 @@
+// 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
+}