]> Cypherpunks.ru repositories - gostls13.git/commitdiff
internal/coverage: add coverage meta-data encoder
authorThan McIntosh <thanm@google.com>
Wed, 29 Sep 2021 20:41:49 +0000 (16:41 -0400)
committerThan McIntosh <thanm@google.com>
Mon, 26 Sep 2022 20:51:52 +0000 (20:51 +0000)
Add a new package with APIs for encoding coverage meta-data. This
provides support for accumulating information about each function
during the compilation process, and then encoding and emitting a
payload for a coverage meta-data symbol.  Not yet connected to the
rest of the coverage machinery (that will appear in a later patch).

Updates #51430.

Change-Id: I61054ce87f205b25fb1bfedaa740fd7425c34de4
Reviewed-on: https://go-review.googlesource.com/c/go/+/353453
Run-TryBot: Than McIntosh <thanm@google.com>
Reviewed-by: David Chase <drchase@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>

src/go/build/deps_test.go
src/internal/coverage/defs.go [new file with mode: 0644]
src/internal/coverage/encodemeta/encode.go [new file with mode: 0644]
src/internal/coverage/encodemeta/encodefile.go [new file with mode: 0644]
src/internal/coverage/stringtab/stringtab.go [new file with mode: 0644]
src/internal/coverage/uleb128/uleb128.go [new file with mode: 0644]

index efd28dfc21939119bcb69fb022268f544cdff04c..2d88053382e52ca625bea240229eba1f577d61e6 100644 (file)
@@ -40,7 +40,8 @@ var depsRules = `
        # No dependencies allowed for any of these packages.
        NONE
        < constraints, container/list, container/ring,
-         internal/cfg, internal/cpu, internal/goarch,
+         internal/cfg, internal/cpu, internal/coverage,
+         internal/coverage/uleb128, internal/goarch,
          internal/goexperiment, internal/goos,
          internal/goversion, internal/nettrace,
          unicode/utf8, unicode/utf16, unicode,
@@ -547,6 +548,13 @@ var depsRules = `
 
        FMT
        < internal/diff, internal/txtar;
+
+    FMT, io, internal/coverage/uleb128
+    < internal/coverage/stringtab;
+
+    FMT, encoding/binary, internal/coverage, internal/coverage/stringtab,
+    io, os, bufio, crypto/md5
+    < internal/coverage/encodemeta;
 `
 
 // listStdPkgs returns the same list of packages as "go list std".
diff --git a/src/internal/coverage/defs.go b/src/internal/coverage/defs.go
new file mode 100644 (file)
index 0000000..4ffbb91
--- /dev/null
@@ -0,0 +1,374 @@
+// Copyright 2022 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 coverage
+
+// Types and constants related to the output files files written
+// by code coverage tooling. When a coverage-instrumented binary
+// is run, it emits two output files: a meta-data output file, and
+// a counter data output file.
+
+//.....................................................................
+//
+// Meta-data definitions:
+//
+// The meta-data file is composed of a file header, a series of
+// meta-data blobs/sections (one per instrumented package), and an offsets
+// area storing the offsets of each section. Format of the meta-data
+// file looks like:
+//
+// --header----------
+//  | magic: [4]byte magic string
+//  | version
+//  | total length of meta-data file in bytes
+//  | numPkgs: number of package entries in file
+//  | hash: [16]byte hash of entire meta-data payload
+//  | offset to string table section
+//  | length of string table
+//  | number of entries in string table
+//  | counter mode
+//  | counter granularity
+//  --package offsets table------
+//  <offset to pkg 0>
+//  <offset to pkg 1>
+//  ...
+//  --package lengths table------
+//  <length of pkg 0>
+//  <length of pkg 1>
+//  ...
+//  --string table------
+//  <uleb128 len> 8
+//  <data> "somestring"
+//  ...
+//  --package payloads------
+//  <meta-symbol for pkg 0>
+//  <meta-symbol for pkg 1>
+//  ...
+//
+// Each package payload is a stand-alone blob emitted by the compiler,
+// and does not depend on anything else in the meta-data file. In
+// particular, each blob has it's own string table. Note that the
+// file-level string table is expected to be very short (most strings
+// will be in the meta-data blobs themselves).
+
+// CovMetaMagic holds the magic string for a meta-data file.
+var CovMetaMagic = [4]byte{'\x00', '\x63', '\x76', '\x6d'}
+
+// MetaFilePref is a prefix used when emitting meta-data files; these
+// files are of the form "covmeta.<hash>", where hash is a hash
+// computed from the hashes of all the package meta-data symbols in
+// the program.
+const MetaFilePref = "covmeta"
+
+// MetaFileVersion contains the current (most recent) meta-data file version.
+const MetaFileVersion = 1
+
+// MetaFileHeader stores file header information for a meta-data file.
+type MetaFileHeader struct {
+       Magic        [4]byte
+       Version      uint32
+       TotalLength  uint64
+       Entries      uint64
+       MetaFileHash [16]byte
+       StrTabOffset uint32
+       StrTabLength uint32
+       CMode        CounterMode
+       CGranularity CounterGranularity
+       _            [6]byte // padding
+}
+
+// MetaSymbolHeader stores header information for a single
+// meta-data blob, e.g. the coverage meta-data payload
+// computed for a given Go package.
+type MetaSymbolHeader struct {
+       Length     uint32 // size of meta-symbol payload in bytes
+       PkgName    uint32 // string table index
+       PkgPath    uint32 // string table index
+       ModulePath uint32 // string table index
+       MetaHash   [16]byte
+       _          byte    // currently unused
+       _          [3]byte // padding
+       NumFiles   uint32
+       NumFuncs   uint32
+}
+
+const CovMetaHeaderSize = 16 + 4 + 4 + 4 + 4 + 4 + 4 + 4 // keep in sync with above
+
+// As an example, consider the following Go package:
+//
+// 01: package p
+// 02:
+// 03: var v, w, z int
+// 04:
+// 05: func small(x, y int) int {
+// 06:   v++
+// 07:   // comment
+// 08:   if y == 0 {
+// 09:     return x
+// 10:   }
+// 11:   return (x << 1) ^ (9 / y)
+// 12: }
+// 13:
+// 14: func Medium(q, r int) int {
+// 15:   s1 := small(q, r)
+// 16:   z += s1
+// 17:   s2 := small(r, q)
+// 18:   w -= s2
+// 19:   return w + z
+// 20: }
+//
+// The meta-data blob for the single package above might look like the
+// following:
+//
+// -- MetaSymbolHeader header----------
+//  | size: size of this blob in bytes
+//  | packagepath: <path to p>
+//  | modulepath: <modpath for p>
+//  | nfiles: 1
+//  | nfunctions: 2
+//  --func offsets table------
+//  <offset to func 0>
+//  <offset to func 1>
+//  --string table (contains all files and functions)------
+//  | <uleb128 len> 4
+//  | <data> "p.go"
+//  | <uleb128 len> 5
+//  | <data> "small"
+//  | <uleb128 len> 6
+//  | <data> "Medium"
+//  --func 0------
+//  | <uleb128> num units: 3
+//  | <uleb128> func name: S1 (index into string table)
+//  | <uleb128> file: S0 (index into string table)
+//  | <unit 0>:  S0   L6     L8    2
+//  | <unit 1>:  S0   L9     L9    1
+//  | <unit 2>:  S0   L11    L11   1
+//  --func 1------
+//  | <uleb128> num units: 1
+//  | <uleb128> func name: S2 (index into string table)
+//  | <uleb128> file: S0 (index into string table)
+//  | <unit 0>:  S0   L15    L19   5
+//  ---end-----------
+
+// The following types and constants used by the meta-data encoder/decoder.
+
+// FuncDesc encapsulates the meta-data definitions for a single Go function.
+// This version assumes that we're looking at a function before inlining;
+// if we want to capture a post-inlining view of the world, the
+// representations of source positions would need to be a good deal more
+// complicated.
+type FuncDesc struct {
+       Funcname string
+       Srcfile  string
+       Units    []CoverableUnit
+       Lit      bool // true if this is a function literal
+}
+
+// CoverableUnit describes the source characteristics of a single
+// program unit for which we want to gather coverage info. Coverable
+// units are either "simple" or "intraline"; a "simple" coverable unit
+// corresponds to a basic block (region of straight-line code with no
+// jumps or control transfers). An "intraline" unit corresponds to a
+// logical clause nested within some other simple unit. A simple unit
+// will have a zero Parent value; for an intraline unit NxStmts will
+// be zero and and Parent will be set to 1 plus the index of the
+// containing simple statement. Example:
+//
+//     L7:   q := 1
+//     L8:   x := (y == 101 || launch() == false)
+//     L9:   r := x * 2
+//
+// For the code above we would have three simple units (one for each
+// line), then an intraline unit describing the "launch() == false"
+// clause in line 8, with Parent pointing to the index of the line 8
+// unit in the units array.
+//
+// Note: in the initial version of the coverage revamp, only simple
+// units will be in use.
+type CoverableUnit struct {
+       StLine, StCol uint32
+       EnLine, EnCol uint32
+       NxStmts       uint32
+       Parent        uint32
+}
+
+// CounterMode tracks the "flavor" of the coverage counters being
+// used in a given coverage-instrumented program.
+type CounterMode uint8
+
+const (
+       CtrModeInvalid  CounterMode = iota
+       CtrModeSet                  // "set" mode
+       CtrModeCount                // "count" mode
+       CtrModeAtomic               // "atomic" mode
+       CtrModeRegOnly              // registration-only pseudo-mode
+       CtrModeTestMain             // testmain pseudo-mode
+)
+
+func (cm CounterMode) String() string {
+       switch cm {
+       case CtrModeSet:
+               return "set"
+       case CtrModeCount:
+               return "count"
+       case CtrModeAtomic:
+               return "atomic"
+       case CtrModeRegOnly:
+               return "regonly"
+       case CtrModeTestMain:
+               return "testmain"
+       }
+       return "<invalid>"
+}
+
+func ParseCounterMode(mode string) CounterMode {
+       var cm CounterMode
+       switch mode {
+       case "set":
+               cm = CtrModeSet
+       case "count":
+               cm = CtrModeCount
+       case "atomic":
+               cm = CtrModeAtomic
+       case "regonly":
+               cm = CtrModeRegOnly
+       case "testmain":
+               cm = CtrModeTestMain
+       default:
+               cm = CtrModeInvalid
+       }
+       return cm
+}
+
+// CounterGranularity tracks the granularity of the coverage counters being
+// used in a given coverage-instrumented program.
+type CounterGranularity uint8
+
+const (
+       CtrGranularityInvalid CounterGranularity = iota
+       CtrGranularityPerBlock
+       CtrGranularityPerFunc
+)
+
+func (cm CounterGranularity) String() string {
+       switch cm {
+       case CtrGranularityPerBlock:
+               return "perblock"
+       case CtrGranularityPerFunc:
+               return "perfunc"
+       }
+       return "<invalid>"
+}
+
+//.....................................................................
+//
+// Counter data definitions:
+//
+
+// A counter data file is composed of a file header followed by one or
+// more "segments" (each segment representing a given run or partial
+// run of a give binary) followed by a footer.
+
+// CovCounterMagic holds the magic string for a coverage counter-data file.
+var CovCounterMagic = [4]byte{'\x00', '\x63', '\x77', '\x6d'}
+
+// CounterFileVersion stores the most recent counter data file version.
+const CounterFileVersion = 1
+
+// CounterFileHeader stores files header information for a counter-data file.
+type CounterFileHeader struct {
+       Magic     [4]byte
+       Version   uint32
+       MetaHash  [16]byte
+       CFlavor   CounterFlavor
+       BigEndian bool
+       _         [6]byte // padding
+}
+
+// CounterSegmentHeader encapsulates information about a specific
+// segment in a counter data file, which at the moment contains
+// counters data from a single execution of a coverage-instrumented
+// program. Following the segment header will be the string table and
+// args table, and then (possibly) padding bytes to bring the byte
+// size of the preamble up to a multiple of 4. Immediately following
+// that will be the counter payloads.
+//
+// The "args" section of a segment is used to store annotations
+// describing where the counter data came from; this section is
+// basically a series of key-value pairs (can be thought of as an
+// encoded 'map[string]string'). At the moment we only write os.Args()
+// data to this section, using pairs of the form "argc=<integer>",
+// "argv0=<os.Args[0]>", "argv1=<os.Args[1]>", and so on. In the
+// future the args table may also include things like GOOS/GOARCH
+// values, and/or tags indicating which tests were run to generate the
+// counter data.
+type CounterSegmentHeader struct {
+       FcnEntries uint64
+       StrTabLen  uint32
+       ArgsLen    uint32
+}
+
+// CounterFileFooter appears at the tail end of a counter data file,
+// and stores the number of segments it contains.
+type CounterFileFooter struct {
+       Magic       [4]byte
+       _           [4]byte // padding
+       NumSegments uint32
+       _           [4]byte // padding
+}
+
+// CounterFilePref is the file prefix used when emitting coverage data
+// output files. CounterFileTemplate describes the format of the file
+// name: prefix followed by meta-file hash followed by process ID
+// followed by emit UnixNanoTime.
+const CounterFilePref = "covcounters"
+const CounterFileTempl = "%s.%x.%d.%d"
+const CounterFileRegexp = `^%s\.(\S+)\.(\d+)\.(\d+)+$`
+
+// CounterFlavor describes how function and counters are
+// stored/represented in the counter section of the file.
+type CounterFlavor uint8
+
+const (
+       // "Raw" representation: all values (pkg ID, func ID, num counters,
+       // and counters themselves) are stored as uint32's.
+       CtrRaw CounterFlavor = iota + 1
+
+       // "ULeb" representation: all values (pkg ID, func ID, num counters,
+       // and counters themselves) are stored with ULEB128 encoding.
+       CtrULeb128
+)
+
+func Round4(x int) int {
+       return (x + 3) &^ 3
+}
+
+//.....................................................................
+//
+// Runtime counter data definitions.
+//
+
+// At runtime within a coverage-instrumented program, the "counters"
+// object we associated with instrumented function can be thought of
+// as a struct of the following form:
+//
+// struct {
+//     numCtrs uint32
+//     pkgid uint32
+//     funcid uint32
+//     counterArray [numBlocks]uint32
+// }
+//
+// where "numCtrs" is the number of blocks / coverable units within the
+// function, "pkgid" is the unique index assigned to this package by
+// the runtime, "funcid" is the index of this function within its containing
+// packge, and "counterArray" stores the actual counters.
+//
+// The counter variable itself is created not as a struct but as a flat
+// array of uint32's; we then use the offsets below to index into it.
+
+const NumCtrsOffset = 0
+const PkgIdOffset = 1
+const FuncIdOffset = 2
+const FirstCtrOffset = 3
diff --git a/src/internal/coverage/encodemeta/encode.go b/src/internal/coverage/encodemeta/encode.go
new file mode 100644 (file)
index 0000000..a01e6fc
--- /dev/null
@@ -0,0 +1,215 @@
+// 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 encodemeta
+
+// This package contains APIs and helpers for encoding the meta-data
+// "blob" for a single Go package, created when coverage
+// instrumentation is turned on.
+
+import (
+       "crypto/md5"
+       "encoding/binary"
+       "fmt"
+       "hash"
+       "internal/coverage"
+       "internal/coverage/stringtab"
+       "internal/coverage/uleb128"
+       "io"
+       "os"
+)
+
+type CoverageMetaDataBuilder struct {
+       stab    stringtab.Writer
+       funcs   []funcDesc
+       tmp     []byte // temp work slice
+       h       hash.Hash
+       pkgpath uint32
+       pkgname uint32
+       modpath uint32
+       debug   bool
+       werr    error
+}
+
+func NewCoverageMetaDataBuilder(pkgpath string, pkgname string, modulepath string) (*CoverageMetaDataBuilder, error) {
+       if pkgpath == "" {
+               return nil, fmt.Errorf("invalid empty package path")
+       }
+       x := &CoverageMetaDataBuilder{
+               tmp: make([]byte, 0, 256),
+               h:   md5.New(),
+       }
+       x.stab.InitWriter()
+       x.stab.Lookup("")
+       x.pkgpath = x.stab.Lookup(pkgpath)
+       x.pkgname = x.stab.Lookup(pkgname)
+       x.modpath = x.stab.Lookup(modulepath)
+       io.WriteString(x.h, pkgpath)
+       io.WriteString(x.h, pkgname)
+       io.WriteString(x.h, modulepath)
+       return x, nil
+}
+
+func h32(x uint32, h hash.Hash, tmp []byte) {
+       tmp = tmp[:0]
+       tmp = append(tmp, []byte{0, 0, 0, 0}...)
+       binary.LittleEndian.PutUint32(tmp, x)
+       h.Write(tmp)
+}
+
+type funcDesc struct {
+       encoded []byte
+}
+
+// AddFunc registers a new function with the meta data builder.
+func (b *CoverageMetaDataBuilder) AddFunc(f coverage.FuncDesc) uint {
+       hashFuncDesc(b.h, &f, b.tmp)
+       fd := funcDesc{}
+       b.tmp = b.tmp[:0]
+       b.tmp = uleb128.AppendUleb128(b.tmp, uint(len(f.Units)))
+       b.tmp = uleb128.AppendUleb128(b.tmp, uint(b.stab.Lookup(f.Funcname)))
+       b.tmp = uleb128.AppendUleb128(b.tmp, uint(b.stab.Lookup(f.Srcfile)))
+       for _, u := range f.Units {
+               b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.StLine))
+               b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.StCol))
+               b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.EnLine))
+               b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.EnCol))
+               b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.NxStmts))
+       }
+       lit := uint(0)
+       if f.Lit {
+               lit = 1
+       }
+       b.tmp = uleb128.AppendUleb128(b.tmp, lit)
+       fd.encoded = make([]byte, len(b.tmp))
+       copy(fd.encoded, b.tmp)
+       rv := uint(len(b.funcs))
+       b.funcs = append(b.funcs, fd)
+       return rv
+}
+
+func (b *CoverageMetaDataBuilder) emitFuncOffsets(w io.WriteSeeker, off int64) int64 {
+       nFuncs := len(b.funcs)
+       var foff int64 = coverage.CovMetaHeaderSize + int64(b.stab.Size()) + int64(nFuncs)*4
+       for idx := 0; idx < nFuncs; idx++ {
+               b.wrUint32(w, uint32(foff))
+               foff += int64(len(b.funcs[idx].encoded))
+       }
+       return off + (int64(len(b.funcs)) * 4)
+}
+
+func (b *CoverageMetaDataBuilder) emitFunc(w io.WriteSeeker, off int64, f funcDesc) (int64, error) {
+       ew := len(f.encoded)
+       if nw, err := w.Write(f.encoded); err != nil {
+               return 0, err
+       } else if ew != nw {
+               return 0, fmt.Errorf("short write emitting coverage meta-data")
+       }
+       return off + int64(ew), nil
+}
+
+func (b *CoverageMetaDataBuilder) reportWriteError(err error) {
+       if b.werr != nil {
+               b.werr = err
+       }
+}
+
+func (b *CoverageMetaDataBuilder) wrUint32(w io.WriteSeeker, v uint32) {
+       b.tmp = b.tmp[:0]
+       b.tmp = append(b.tmp, []byte{0, 0, 0, 0}...)
+       binary.LittleEndian.PutUint32(b.tmp, v)
+       if nw, err := w.Write(b.tmp); err != nil {
+               b.reportWriteError(err)
+       } else if nw != 4 {
+               b.reportWriteError(fmt.Errorf("short write"))
+       }
+}
+
+// Emit writes the meta-data accumulated so far in this builder to 'w'.
+// Returns a hash of the meta-data payload and an error.
+func (b *CoverageMetaDataBuilder) Emit(w io.WriteSeeker) ([16]byte, error) {
+       // Emit header.  Length will initially be zero, we'll
+       // back-patch it later.
+       var digest [16]byte
+       copy(digest[:], b.h.Sum(nil))
+       mh := coverage.MetaSymbolHeader{
+               // hash and length initially zero, will be back-patched
+               PkgPath:    uint32(b.pkgpath),
+               PkgName:    uint32(b.pkgname),
+               ModulePath: uint32(b.modpath),
+               NumFiles:   uint32(b.stab.Nentries()),
+               NumFuncs:   uint32(len(b.funcs)),
+               MetaHash:   digest,
+       }
+       if b.debug {
+               fmt.Fprintf(os.Stderr, "=-= writing header: %+v\n", mh)
+       }
+       if err := binary.Write(w, binary.LittleEndian, mh); err != nil {
+               return digest, fmt.Errorf("error writing meta-file header: %v\n", err)
+       }
+       off := int64(coverage.CovMetaHeaderSize)
+
+       // Write function offsets section
+       off = b.emitFuncOffsets(w, off)
+
+       // Check for any errors up to this point.
+       if b.werr != nil {
+               return digest, b.werr
+       }
+
+       // Write string table.
+       if err := b.stab.Write(w); err != nil {
+               return digest, err
+       }
+       off += int64(b.stab.Size())
+
+       // Write functions
+       for _, f := range b.funcs {
+               var err error
+               off, err = b.emitFunc(w, off, f)
+               if err != nil {
+                       return digest, err
+               }
+       }
+
+       // Back-patch the length.
+       totalLength := uint32(off)
+       if _, err := w.Seek(0, os.SEEK_SET); err != nil {
+               return digest, err
+       }
+       b.wrUint32(w, totalLength)
+       if b.werr != nil {
+               return digest, b.werr
+       }
+       return digest, nil
+}
+
+// HashFuncDesc computes an md5 sum of a coverage.FuncDesc and returns
+// a digest for it.
+func HashFuncDesc(f *coverage.FuncDesc) [16]byte {
+       h := md5.New()
+       tmp := make([]byte, 0, 32)
+       hashFuncDesc(h, f, tmp)
+       var r [16]byte
+       copy(r[:], h.Sum(nil))
+       return r
+}
+
+// hashFuncDesc incorporates a given function 'f' into the hash 'h'.
+func hashFuncDesc(h hash.Hash, f *coverage.FuncDesc, tmp []byte) {
+       io.WriteString(h, f.Funcname)
+       io.WriteString(h, f.Srcfile)
+       for _, u := range f.Units {
+               h32(u.StLine, h, tmp)
+               h32(u.StCol, h, tmp)
+               h32(u.EnLine, h, tmp)
+               h32(u.EnCol, h, tmp)
+               h32(u.NxStmts, h, tmp)
+       }
+       lit := uint32(0)
+       if f.Lit {
+               lit = 1
+       }
+       h32(lit, h, tmp)
+}
diff --git a/src/internal/coverage/encodemeta/encodefile.go b/src/internal/coverage/encodemeta/encodefile.go
new file mode 100644 (file)
index 0000000..d6e0938
--- /dev/null
@@ -0,0 +1,132 @@
+// 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 encodemeta
+
+import (
+       "bufio"
+       "crypto/md5"
+       "encoding/binary"
+       "fmt"
+       "internal/coverage"
+       "internal/coverage/stringtab"
+       "io"
+       "os"
+       "unsafe"
+)
+
+// This package contains APIs and helpers for writing out a meta-data
+// file (composed of a file header, offsets/lengths, and then a series of
+// meta-data blobs emitted by the compiler, one per Go package).
+
+type CoverageMetaFileWriter struct {
+       stab   stringtab.Writer
+       mfname string
+       w      *bufio.Writer
+       tmp    []byte
+       debug  bool
+}
+
+func NewCoverageMetaFileWriter(mfname string, w io.Writer) *CoverageMetaFileWriter {
+       r := &CoverageMetaFileWriter{
+               mfname: mfname,
+               w:      bufio.NewWriter(w),
+               tmp:    make([]byte, 64),
+       }
+       r.stab.InitWriter()
+       r.stab.Lookup("")
+       return r
+}
+
+func (m *CoverageMetaFileWriter) Write(finalHash [16]byte, blobs [][]byte, mode coverage.CounterMode, granularity coverage.CounterGranularity) error {
+       mhsz := uint64(unsafe.Sizeof(coverage.MetaFileHeader{}))
+       stSize := m.stab.Size()
+       stOffset := mhsz + uint64(16*len(blobs))
+       preambleLength := stOffset + uint64(stSize)
+
+       if m.debug {
+               fmt.Fprintf(os.Stderr, "=+= sizeof(MetaFileHeader)=%d\n", mhsz)
+               fmt.Fprintf(os.Stderr, "=+= preambleLength=%d stSize=%d\n", preambleLength, stSize)
+       }
+
+       // Compute total size
+       tlen := preambleLength
+       for i := 0; i < len(blobs); i++ {
+               tlen += uint64(len(blobs[i]))
+       }
+
+       // Emit header
+       mh := coverage.MetaFileHeader{
+               Magic:        coverage.CovMetaMagic,
+               Version:      coverage.MetaFileVersion,
+               TotalLength:  tlen,
+               Entries:      uint64(len(blobs)),
+               MetaFileHash: finalHash,
+               StrTabOffset: uint32(stOffset),
+               StrTabLength: stSize,
+               CMode:        mode,
+               CGranularity: granularity,
+       }
+       var err error
+       if err = binary.Write(m.w, binary.LittleEndian, mh); err != nil {
+               return fmt.Errorf("error writing %s: %v\n", m.mfname, err)
+       }
+
+       if m.debug {
+               fmt.Fprintf(os.Stderr, "=+= len(blobs) is %d\n", mh.Entries)
+       }
+
+       // Emit package offsets section followed by package lengths section.
+       off := preambleLength
+       off2 := mhsz
+       buf := make([]byte, 8)
+       for _, blob := range blobs {
+               binary.LittleEndian.PutUint64(buf, off)
+               if _, err = m.w.Write(buf); err != nil {
+                       return fmt.Errorf("error writing %s: %v\n", m.mfname, err)
+               }
+               if m.debug {
+                       fmt.Fprintf(os.Stderr, "=+= pkg offset %d 0x%x\n", off, off)
+               }
+               off += uint64(len(blob))
+               off2 += 8
+       }
+       for _, blob := range blobs {
+               bl := uint64(len(blob))
+               binary.LittleEndian.PutUint64(buf, bl)
+               if _, err = m.w.Write(buf); err != nil {
+                       return fmt.Errorf("error writing %s: %v\n", m.mfname, err)
+               }
+               if m.debug {
+                       fmt.Fprintf(os.Stderr, "=+= pkg len %d 0x%x\n", bl, bl)
+               }
+               off2 += 8
+       }
+
+       // Emit string table
+       if err = m.stab.Write(m.w); err != nil {
+               return err
+       }
+
+       // Now emit blobs themselves.
+       for k, blob := range blobs {
+               if m.debug {
+                       fmt.Fprintf(os.Stderr, "=+= writing blob %d len %d at off=%d hash %s\n", k, len(blob), off2, fmt.Sprintf("%x", md5.Sum(blob)))
+               }
+               if _, err = m.w.Write(blob); err != nil {
+                       return fmt.Errorf("error writing %s: %v\n", m.mfname, err)
+               }
+               if m.debug {
+                       fmt.Fprintf(os.Stderr, "=+= wrote package payload of %d bytes\n",
+                               len(blob))
+               }
+               off2 += uint64(len(blob))
+       }
+
+       // Flush writer, and we're done.
+       if err = m.w.Flush(); err != nil {
+               return fmt.Errorf("error writing %s: %v\n", m.mfname, err)
+       }
+       return nil
+}
diff --git a/src/internal/coverage/stringtab/stringtab.go b/src/internal/coverage/stringtab/stringtab.go
new file mode 100644 (file)
index 0000000..2aba332
--- /dev/null
@@ -0,0 +1,89 @@
+// Copyright 2022 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 stringtab
+
+import (
+       "fmt"
+       "internal/coverage/uleb128"
+       "io"
+)
+
+// This package implements a string table writer utility for use in
+// emitting coverage meta-data and counter-data files.
+
+type Writer struct {
+       stab   map[string]uint32
+       strs   []string
+       tmp    []byte
+       frozen bool
+}
+
+// InitWriter initializes a stringtab.Writer.
+func (stw *Writer) InitWriter() {
+       stw.stab = make(map[string]uint32)
+       stw.tmp = make([]byte, 64)
+}
+
+// Nentries returns the number of strings interned so far.
+func (stw *Writer) Nentries() uint32 {
+       return uint32(len(stw.strs))
+}
+
+// Lookup looks up string 's' in the writer's table, adding
+// a new entry if need be, and returning an index into the table.
+func (stw *Writer) Lookup(s string) uint32 {
+       if idx, ok := stw.stab[s]; ok {
+               return idx
+       }
+       idx := uint32(len(stw.strs))
+       stw.stab[s] = idx
+       stw.strs = append(stw.strs, s)
+       return idx
+}
+
+// Size computes the memory in bytes needed for the serialized
+// version of a stringtab.Writer.
+func (stw *Writer) Size() uint32 {
+       rval := uint32(0)
+       stw.tmp = stw.tmp[:0]
+       stw.tmp = uleb128.AppendUleb128(stw.tmp, uint(len(stw.strs)))
+       rval += uint32(len(stw.tmp))
+       for _, s := range stw.strs {
+               stw.tmp = stw.tmp[:0]
+               slen := uint(len(s))
+               stw.tmp = uleb128.AppendUleb128(stw.tmp, slen)
+               rval += uint32(len(stw.tmp)) + uint32(slen)
+       }
+       return rval
+}
+
+// Write writes the string table in serialized form to the specified
+// io.Writer.
+func (stw *Writer) Write(w io.Writer) error {
+       wr128 := func(v uint) error {
+               stw.tmp = stw.tmp[:0]
+               stw.tmp = uleb128.AppendUleb128(stw.tmp, v)
+               if nw, err := w.Write(stw.tmp); err != nil {
+                       return fmt.Errorf("writing string table: %v", err)
+               } else if nw != len(stw.tmp) {
+                       return fmt.Errorf("short write emitting stringtab uleb")
+               }
+               return nil
+       }
+       if err := wr128(uint(len(stw.strs))); err != nil {
+               return err
+       }
+       for _, s := range stw.strs {
+               if err := wr128(uint(len(s))); err != nil {
+                       return err
+               }
+               if nw, err := w.Write([]byte(s)); err != nil {
+                       return fmt.Errorf("writing string table: %v\n", err)
+               } else if nw != len([]byte(s)) {
+                       return fmt.Errorf("short write emitting stringtab")
+               }
+       }
+       return nil
+}
diff --git a/src/internal/coverage/uleb128/uleb128.go b/src/internal/coverage/uleb128/uleb128.go
new file mode 100644 (file)
index 0000000..e5cd92a
--- /dev/null
@@ -0,0 +1,20 @@
+// 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 uleb128
+
+func AppendUleb128(b []byte, v uint) []byte {
+       for {
+               c := uint8(v & 0x7f)
+               v >>= 7
+               if v != 0 {
+                       c |= 0x80
+               }
+               b = append(b, c)
+               if c&0x80 == 0 {
+                       break
+               }
+       }
+       return b
+}