]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: add coverage fixup mode
authorThan McIntosh <thanm@google.com>
Mon, 7 Mar 2022 15:32:51 +0000 (10:32 -0500)
committerThan McIntosh <thanm@google.com>
Tue, 27 Sep 2022 10:30:53 +0000 (10:30 +0000)
Adds a -coveragecfg=<configfile> command line option to the compiler
to help support a cooperative "tool and compiler" mode for coverage
instrumentation. In this mode the cmd/cover tool generates most of the
counter instrumentation via source-to-source rewriting, but the
compiler fixes up the result if passed the "-coveragecfg" option. The
fixups include:

  - reclassifying counter variables (special storage class)
  - marking meta-data variables are read-only
  - adding in an init call to do registation

Updates #51430.

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

12 files changed:
src/cmd/compile/internal/base/flag.go
src/cmd/compile/internal/coverage/cover.go [new file with mode: 0644]
src/cmd/compile/internal/gc/main.go
src/cmd/compile/internal/ir/symtab.go
src/cmd/compile/internal/pkginit/init.go
src/cmd/compile/internal/typecheck/builtin.go
src/cmd/compile/internal/typecheck/builtin/coverage.go [new file with mode: 0644]
src/cmd/compile/internal/typecheck/builtin/runtime.go
src/cmd/compile/internal/typecheck/mkbuiltin.go
src/cmd/compile/internal/typecheck/syms.go
src/cmd/dist/buildtool.go
src/internal/coverage/cmddefs.go [new file with mode: 0644]

index a005d2cdf26aca370a03bbc902da12e215ff7775..459ebf3ba4946134f73ed74201f5e39a7cd71de7 100644 (file)
@@ -9,6 +9,8 @@ import (
        "flag"
        "fmt"
        "internal/buildcfg"
+       "internal/coverage"
+       "io/ioutil"
        "log"
        "os"
        "reflect"
@@ -110,6 +112,7 @@ type CmdFlags struct {
        MemProfileRate     int          "help:\"set runtime.MemProfileRate to `rate`\""
        MutexProfile       string       "help:\"write mutex profile to `file`\""
        NoLocalImports     bool         "help:\"reject local (relative) imports\""
+       CoverageCfg        func(string) "help:\"read coverage configuration from `file`\""
        Pack               bool         "help:\"write to file.a instead of file.o\""
        Race               bool         "help:\"enable race detector\""
        Shared             *bool        "help:\"generate code that can be linked into a shared library\"" // &Ctxt.Flag_shared, set below
@@ -127,10 +130,11 @@ type CmdFlags struct {
                        Patterns map[string][]string
                        Files    map[string]string
                }
-               ImportDirs   []string          // appended to by -I
-               ImportMap    map[string]string // set by -importcfg
-               PackageFile  map[string]string // set by -importcfg; nil means not in use
-               SpectreIndex bool              // set by -spectre=index or -spectre=all
+               ImportDirs   []string                   // appended to by -I
+               ImportMap    map[string]string          // set by -importcfg
+               PackageFile  map[string]string          // set by -importcfg; nil means not in use
+               CoverageInfo *coverage.CoverFixupConfig // set by -coveragecfg
+               SpectreIndex bool                       // set by -spectre=index or -spectre=all
                // Whether we are adding any sort of code instrumentation, such as
                // when the race detector is enabled.
                Instrumenting bool
@@ -154,6 +158,7 @@ func ParseFlags() {
        Flag.EmbedCfg = readEmbedCfg
        Flag.GenDwarfInl = 2
        Flag.ImportCfg = readImportCfg
+       Flag.CoverageCfg = readCoverageCfg
        Flag.LinkShared = &Ctxt.Flag_linkshared
        Flag.Shared = &Ctxt.Flag_shared
        Flag.WB = true
@@ -430,6 +435,18 @@ func readImportCfg(file string) {
        }
 }
 
+func readCoverageCfg(file string) {
+       var cfg coverage.CoverFixupConfig
+       data, err := ioutil.ReadFile(file)
+       if err != nil {
+               log.Fatalf("-coveragecfg: %v", err)
+       }
+       if err := json.Unmarshal(data, &cfg); err != nil {
+               log.Fatalf("error reading -coveragecfg file %q: %v", file, err)
+       }
+       Flag.Cfg.CoverageInfo = &cfg
+}
+
 func readEmbedCfg(file string) {
        data, err := os.ReadFile(file)
        if err != nil {
diff --git a/src/cmd/compile/internal/coverage/cover.go b/src/cmd/compile/internal/coverage/cover.go
new file mode 100644 (file)
index 0000000..6538807
--- /dev/null
@@ -0,0 +1,189 @@
+// 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
+
+import (
+       "cmd/compile/internal/base"
+       "cmd/compile/internal/ir"
+       "cmd/compile/internal/typecheck"
+       "cmd/compile/internal/types"
+       "cmd/internal/objabi"
+       "internal/coverage"
+       "strconv"
+       "strings"
+)
+
+// Fixup is the main entry point for coverage compiler fixup. It
+// collects and reclassifies the variables mentioned in the
+// -coveragecfg file, then adds calls to the pkg init function as
+// appropriate to register the proper variables with the runtime.
+func Fixup() {
+       metavar, pkgIdVar, initfn, covermode, covergran :=
+               fixupMetaAndCounterVariables()
+       hashv, len := metaHashAndLen()
+       if covermode != coverage.CtrModeTestMain {
+               registerMeta(metavar, initfn, hashv, len,
+                       pkgIdVar, covermode, covergran)
+       }
+       if base.Ctxt.Pkgpath == "main" {
+               addInitHookCall(initfn, covermode)
+       }
+}
+
+// fixupMetaAndCounterVariables collects and returns the package ID
+// and meta-data variables being used for this "-cover" build, along
+// with the init function for the package and the coverage mode. It
+// also reclassifies certain variables (for example, tagging coverage
+// counter variables with flags so that they can be handled properly
+// downstream).
+func fixupMetaAndCounterVariables() (*ir.Name, *ir.Name, *ir.Func, coverage.CounterMode, coverage.CounterGranularity) {
+       metaVarName := base.Flag.Cfg.CoverageInfo.MetaVar
+       pkgIdVarName := base.Flag.Cfg.CoverageInfo.PkgIdVar
+       counterMode := base.Flag.Cfg.CoverageInfo.CounterMode
+       counterGran := base.Flag.Cfg.CoverageInfo.CounterGranularity
+       counterPrefix := base.Flag.Cfg.CoverageInfo.CounterPrefix
+       var metavar *ir.Name
+       var pkgidvar *ir.Name
+       var initfn *ir.Func
+
+       ckTypSanity := func(nm *ir.Name, tag string) {
+               if nm.Type() == nil || nm.Type().HasPointers() {
+                       base.Fatalf("unsuitable %s %q mentioned in coveragecfg, improper type '%v'", tag, nm.Sym().Name, nm.Type())
+               }
+       }
+
+       for _, n := range typecheck.Target.Decls {
+               if fn, ok := n.(*ir.Func); ok && ir.FuncName(fn) == "init" {
+                       if initfn != nil {
+                               panic("unexpected")
+                       }
+                       initfn = fn
+                       continue
+               }
+               as, ok := n.(*ir.AssignStmt)
+               if !ok {
+                       continue
+               }
+               nm, ok := as.X.(*ir.Name)
+               if !ok {
+                       continue
+               }
+               s := nm.Sym()
+               switch s.Name {
+               case metaVarName:
+                       metavar = nm
+                       ckTypSanity(nm, "metavar")
+                       nm.MarkReadonly()
+                       continue
+               case pkgIdVarName:
+                       pkgidvar = nm
+                       ckTypSanity(nm, "pkgidvar")
+                       nm.SetCoverageAuxVar(true)
+                       s := nm.Linksym()
+                       s.Type = objabi.SCOVERAGE_AUXVAR
+                       continue
+               }
+               if strings.HasPrefix(s.Name, counterPrefix) {
+                       ckTypSanity(nm, "countervar")
+                       nm.SetCoverageCounter(true)
+                       s := nm.Linksym()
+                       s.Type = objabi.SCOVERAGE_COUNTER
+               }
+       }
+       cm := coverage.ParseCounterMode(counterMode)
+       if cm == coverage.CtrModeInvalid {
+               base.Fatalf("bad setting %q for covermode in coveragecfg:",
+                       counterMode)
+       }
+       var cg coverage.CounterGranularity
+       switch counterGran {
+       case "perblock":
+               cg = coverage.CtrGranularityPerBlock
+       case "perfunc":
+               cg = coverage.CtrGranularityPerFunc
+       default:
+               base.Fatalf("bad setting %q for covergranularity in coveragecfg:",
+                       counterGran)
+       }
+
+       return metavar, pkgidvar, initfn, cm, cg
+}
+
+func metaHashAndLen() ([16]byte, int) {
+
+       // Read meta-data hash from config entry.
+       mhash := base.Flag.Cfg.CoverageInfo.MetaHash
+       if len(mhash) != 32 {
+               base.Fatalf("unexpected: got metahash length %d want 32", len(mhash))
+       }
+       var hv [16]byte
+       for i := 0; i < 16; i++ {
+               nib := string(mhash[i*2 : i*2+2])
+               x, err := strconv.ParseInt(nib, 16, 32)
+               if err != nil {
+                       base.Fatalf("metahash bad byte %q", nib)
+               }
+               hv[i] = byte(x)
+       }
+
+       // Return hash and meta-data len
+       return hv, base.Flag.Cfg.CoverageInfo.MetaLen
+}
+
+func registerMeta(mdname *ir.Name, initfn *ir.Func, hash [16]byte, mdlen int, pkgIdVar *ir.Name, cmode coverage.CounterMode, cgran coverage.CounterGranularity) {
+       // Materialize expression for hash (an array literal)
+       pos := initfn.Pos()
+       elist := make([]ir.Node, 0, 16)
+       for i := 0; i < 16; i++ {
+               elem := ir.NewInt(int64(hash[i]))
+               elist = append(elist, elem)
+       }
+       ht := types.NewArray(types.Types[types.TUINT8], 16)
+       hashx := ir.NewCompLitExpr(pos, ir.OCOMPLIT, ht, elist)
+
+       // Materalize expression corresponding to address of the meta-data symbol.
+       mdax := typecheck.NodAddr(mdname)
+       mdauspx := typecheck.ConvNop(mdax, types.Types[types.TUNSAFEPTR])
+
+       // Materialize expression for length.
+       lenx := ir.NewInt(int64(mdlen)) // untyped
+
+       // Generate a call to runtime.addCovMeta, e.g.
+       //
+       //   pkgIdVar = runtime.addCovMeta(&sym, len, hash, pkgpath, pkid, cmode, cgran)
+       //
+       fn := typecheck.LookupRuntime("addCovMeta")
+       pkid := coverage.HardCodedPkgID(base.Ctxt.Pkgpath)
+       pkIdNode := ir.NewInt(int64(pkid))
+       cmodeNode := ir.NewInt(int64(cmode))
+       cgranNode := ir.NewInt(int64(cgran))
+       pkPathNode := ir.NewString(base.Ctxt.Pkgpath)
+       callx := typecheck.Call(pos, fn, []ir.Node{mdauspx, lenx, hashx,
+               pkPathNode, pkIdNode, cmodeNode, cgranNode}, false)
+       assign := callx
+       if pkid == coverage.NotHardCoded {
+               assign = typecheck.Stmt(ir.NewAssignStmt(pos, pkgIdVar, callx))
+       }
+
+       // Tack the call onto the start of our init function. We do this
+       // early in the init since it's possible that instrumented function
+       // bodies (with counter updates) might be inlined into init.
+       initfn.Body.Prepend(assign)
+}
+
+// addInitHookCall generates a call to runtime/coverage.initHook() and
+// inserts it into the package main init function, which will kick off
+// the process for coverage data writing (emit meta data, and register
+// an exit hook to emit counter data).
+func addInitHookCall(initfn *ir.Func, cmode coverage.CounterMode) {
+       typecheck.InitCoverage()
+       pos := initfn.Pos()
+       istest := cmode == coverage.CtrModeTestMain
+       initf := typecheck.LookupCoverage("initHook")
+       istestNode := ir.NewBool(istest)
+       args := []ir.Node{istestNode}
+       callx := typecheck.Call(pos, initf, args, false)
+       initfn.Body.Append(callx)
+}
index c9493bf5934e1dc45175c54f5bf25792c055135b..570f632eec8218e3d6b1b2d317e8e27929bbdb26 100644 (file)
@@ -8,6 +8,7 @@ import (
        "bufio"
        "bytes"
        "cmd/compile/internal/base"
+       "cmd/compile/internal/coverage"
        "cmd/compile/internal/deadcode"
        "cmd/compile/internal/devirtualize"
        "cmd/compile/internal/dwarfgen"
@@ -97,6 +98,10 @@ func Main(archInit func(*ssagen.ArchInfo)) {
        // pseudo-package used for methods with anonymous receivers
        ir.Pkgs.Go = types.NewPkg("go", "")
 
+       // pseudo-package for use with code coverage instrumentation.
+       ir.Pkgs.Coverage = types.NewPkg("go.coverage", "runtime/coverage")
+       ir.Pkgs.Coverage.Prefix = "runtime/coverage"
+
        // Record flags that affect the build result. (And don't
        // record flags that don't, since that would cause spurious
        // changes in the binary.)
@@ -207,6 +212,11 @@ func Main(archInit func(*ssagen.ArchInfo)) {
        // removal can skew the results (e.g., #43444).
        pkginit.MakeInit()
 
+       // Fix up init routines if building for code coverage.
+       if base.Flag.Cfg.CoverageInfo != nil {
+               coverage.Fixup()
+       }
+
        // Eliminate some obviously dead code.
        // Must happen after typechecking.
        for _, n := range typecheck.Target.Decls {
index 148edb2c885e4250409b6f1236715fe250966f0f..2c89e677b480fcf04d8d374b00cfb8b550d47a77 100644 (file)
@@ -69,7 +69,8 @@ var Syms struct {
 
 // Pkgs holds known packages.
 var Pkgs struct {
-       Go      *types.Pkg
-       Itab    *types.Pkg
-       Runtime *types.Pkg
+       Go       *types.Pkg
+       Itab     *types.Pkg
+       Runtime  *types.Pkg
+       Coverage *types.Pkg
 }
index 8c60e3bfd616eb993f37f470b514bb9d64e00e00..e13a7fbfe0944516b9b1fbe3d8222b0ef86361cc 100644 (file)
@@ -195,3 +195,18 @@ func Task() *ir.Name {
        objw.Global(lsym, int32(ot), obj.NOPTR)
        return task
 }
+
+// initRequiredForCoverage returns TRUE if we need to force creation
+// of an init function for the package so as to insert a coverage
+// runtime registration call.
+func initRequiredForCoverage(l []ir.Node) bool {
+       if base.Flag.Cfg.CoverageInfo == nil {
+               return false
+       }
+       for _, n := range l {
+               if n.Op() == ir.ODCLFUNC {
+                       return true
+               }
+       }
+       return false
+}
index 7718985aae7bbb32319ce655d2dd0f069fc09d1d..fab7df78837db01c079624b73757f89fa5c4a186 100644 (file)
@@ -7,6 +7,21 @@ import (
        "cmd/internal/src"
 )
 
+// Not inlining this function removes a significant chunk of init code.
+//
+//go:noinline
+func newSig(params, results []*types.Field) *types.Type {
+       return types.NewSignature(types.NoPkg, nil, nil, params, results)
+}
+
+func params(tlist ...*types.Type) []*types.Field {
+       flist := make([]*types.Field, len(tlist))
+       for i, typ := range tlist {
+               flist[i] = types.NewField(src.NoXPos, nil, typ)
+       }
+       return flist
+}
+
 var runtimeDecls = [...]struct {
        name string
        tag  int
@@ -210,6 +225,7 @@ var runtimeDecls = [...]struct {
        {"libfuzzerTraceConstCmp8", funcTag, 149},
        {"libfuzzerHookStrCmp", funcTag, 150},
        {"libfuzzerHookEqualFold", funcTag, 150},
+       {"addCovMeta", funcTag, 152},
        {"x86HasPOPCNT", varTag, 6},
        {"x86HasSSE41", varTag, 6},
        {"x86HasFMA", varTag, 6},
@@ -217,23 +233,8 @@ var runtimeDecls = [...]struct {
        {"arm64HasATOMICS", varTag, 6},
 }
 
-// Not inlining this function removes a significant chunk of init code.
-//
-//go:noinline
-func newSig(params, results []*types.Field) *types.Type {
-       return types.NewSignature(types.NoPkg, nil, nil, params, results)
-}
-
-func params(tlist ...*types.Type) []*types.Field {
-       flist := make([]*types.Field, len(tlist))
-       for i, typ := range tlist {
-               flist[i] = types.NewField(src.NoXPos, nil, typ)
-       }
-       return flist
-}
-
 func runtimeTypes() []*types.Type {
-       var typs [151]*types.Type
+       var typs [153]*types.Type
        typs[0] = types.ByteType
        typs[1] = types.NewPtr(typs[0])
        typs[2] = types.Types[types.TANY]
@@ -385,5 +386,22 @@ func runtimeTypes() []*types.Type {
        typs[148] = newSig(params(typs[62], typs[62], typs[15]), nil)
        typs[149] = newSig(params(typs[24], typs[24], typs[15]), nil)
        typs[150] = newSig(params(typs[28], typs[28], typs[15]), nil)
+       typs[151] = types.NewArray(typs[0], 16)
+       typs[152] = newSig(params(typs[7], typs[62], typs[151], typs[28], typs[15], typs[66], typs[66]), params(typs[62]))
+       return typs[:]
+}
+
+var coverageDecls = [...]struct {
+       name string
+       tag  int
+       typ  int
+}{
+       {"initHook", funcTag, 1},
+}
+
+func coverageTypes() []*types.Type {
+       var typs [2]*types.Type
+       typs[0] = types.Types[types.TBOOL]
+       typs[1] = newSig(params(typs[0]), nil)
        return typs[:]
 }
diff --git a/src/cmd/compile/internal/typecheck/builtin/coverage.go b/src/cmd/compile/internal/typecheck/builtin/coverage.go
new file mode 100644 (file)
index 0000000..ea4462d
--- /dev/null
@@ -0,0 +1,14 @@
+// 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.
+
+// NOTE: If you change this file you must run "go generate"
+// to update builtin.go. This is not done automatically
+// to avoid depending on having a working compiler binary.
+
+//go:build ignore
+// +build ignore
+
+package coverage
+
+func initHook(istest bool)
index b862594c92ac4c7b44d67248348120048a824a62..310148ce00ca4e0f1256b6c022faa15beea564c3 100644 (file)
@@ -273,6 +273,8 @@ func libfuzzerTraceConstCmp8(uint64, uint64, int)
 func libfuzzerHookStrCmp(string, string, int)
 func libfuzzerHookEqualFold(string, string, int)
 
+func addCovMeta(p unsafe.Pointer, len uint32, hash [16]byte, pkpath string, pkgId int, cmode uint8, cgran uint8) uint32
+
 // architecture variants
 var x86HasPOPCNT bool
 var x86HasSSE41 bool
index 692d78df89b078d905098c7f7cef7057979bbd11..aa3a94a19a4f92ebf6077c25fbfdf11e14b1f4ec 100644 (file)
@@ -26,6 +26,7 @@ import (
 )
 
 var stdout = flag.Bool("stdout", false, "write to stdout instead of builtin.go")
+var nofmt = flag.Bool("nofmt", false, "skip formatting builtin.go")
 
 func main() {
        flag.Parse()
@@ -40,11 +41,32 @@ func main() {
        fmt.Fprintln(&b, `      "cmd/internal/src"`)
        fmt.Fprintln(&b, `)`)
 
+       fmt.Fprintln(&b, `
+// Not inlining this function removes a significant chunk of init code.
+//go:noinline
+func newSig(params, results []*types.Field) *types.Type {
+       return types.NewSignature(types.NoPkg, nil, nil, params, results)
+}
+
+func params(tlist ...*types.Type) []*types.Field {
+       flist := make([]*types.Field, len(tlist))
+       for i, typ := range tlist {
+               flist[i] = types.NewField(src.NoXPos, nil, typ)
+       }
+       return flist
+}
+`)
+
        mkbuiltin(&b, "runtime")
+       mkbuiltin(&b, "coverage")
 
-       out, err := format.Source(b.Bytes())
-       if err != nil {
-               log.Fatal(err)
+       var err error
+       out := b.Bytes()
+       if !*nofmt {
+               out, err = format.Source(out)
+               if err != nil {
+                       log.Fatal(err)
+               }
        }
        if *stdout {
                _, err = os.Stdout.Write(out)
@@ -102,22 +124,6 @@ func mkbuiltin(w io.Writer, name string) {
        }
        fmt.Fprintln(w, "}")
 
-       fmt.Fprintln(w, `
-// Not inlining this function removes a significant chunk of init code.
-//
-//go:noinline
-func newSig(params, results []*types.Field) *types.Type {
-       return types.NewSignature(types.NoPkg, nil, nil, params, results)
-}
-
-func params(tlist ...*types.Type) []*types.Field {
-       flist := make([]*types.Field, len(tlist))
-       for i, typ := range tlist {
-               flist[i] = types.NewField(src.NoXPos, nil, typ)
-       }
-       return flist
-}`)
-
        fmt.Fprintln(w)
        fmt.Fprintf(w, "func %sTypes() []*types.Type {\n", name)
        fmt.Fprintf(w, "var typs [%d]*types.Type\n", len(interner.typs))
index 1f60f318510a9c27776275953db78384d3a9a315..7fe649faaa26656c22cec9adb358baa675296e67 100644 (file)
@@ -101,3 +101,32 @@ func LookupRuntimeVar(name string) *obj.LSym {
 func LookupRuntimeABI(name string, abi obj.ABI) *obj.LSym {
        return base.PkgLinksym("runtime", name, abi)
 }
+
+// InitCoverage loads the definitions for routines called
+// by code coverage instrumentation (similar to InitRuntime above).
+func InitCoverage() {
+       typs := coverageTypes()
+       for _, d := range &coverageDecls {
+               sym := ir.Pkgs.Coverage.Lookup(d.name)
+               typ := typs[d.typ]
+               switch d.tag {
+               case funcTag:
+                       importfunc(src.NoXPos, sym, typ)
+               case varTag:
+                       importvar(src.NoXPos, sym, typ)
+               default:
+                       base.Fatalf("unhandled declaration tag %v", d.tag)
+               }
+       }
+}
+
+// LookupCoverage looks up the Go function 'name' in package
+// runtime/coverage. This function must follow the internal calling
+// convention.
+func LookupCoverage(name string) *ir.Name {
+       sym := ir.Pkgs.Coverage.Lookup(name)
+       if sym == nil {
+               base.Fatalf("LookupCoverage: can't find runtime/coverage.%s", name)
+       }
+       return ir.AsNode(sym.Def).(*ir.Name)
+}
index 0725039cda863e08690680adf5615db45f39eb3f..755ec61affe51cf412bb7b8015c5a2eefd3e80a5 100644 (file)
@@ -60,6 +60,7 @@ var bootstrapDirs = []string{
        "debug/macho",
        "debug/pe",
        "go/constant",
+       "internal/coverage",
        "internal/buildcfg",
        "internal/goexperiment",
        "internal/goversion",
diff --git a/src/internal/coverage/cmddefs.go b/src/internal/coverage/cmddefs.go
new file mode 100644 (file)
index 0000000..a146ca5
--- /dev/null
@@ -0,0 +1,66 @@
+// 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
+
+// CoverPkgConfig is a bundle of information passed from the Go
+// command to the cover command during "go build -cover" runs. The
+// Go command creates and fills in a struct as below, then passes
+// file containing the encoded JSON for the struct to the "cover"
+// tool when instrumenting the source files in a Go package.
+type CoverPkgConfig struct {
+       // File into which cmd/cover should emit summary info
+       // when instrumentation is complete.
+       OutConfig string
+
+       // Import path for the package being instrumented.
+       PkgPath string
+
+       // Package name.
+       PkgName string
+
+       // Instrumentation granularity: one of "perfunc" or "perblock" (default)
+       Granularity string
+
+       // Module path for this package (empty if no go.mod in use)
+       ModulePath string
+}
+
+// CoverFixupConfig contains annotations/notes generated by the
+// cmd/cover tool (during instrumentation) to be passed on to the
+// compiler when the instrumented code is compiled. The cmd/cover tool
+// creates a struct of this type, JSON-encodes it, and emits the
+// result to a file, which the Go command then passes to the compiler
+// when the instrumented package is built.
+type CoverFixupConfig struct {
+       // Name of the variable (created by cmd/cover) containing the
+       // encoded meta-data for the package.
+       MetaVar string
+
+       // Length of the meta-data.
+       MetaLen int
+
+       // Hash computed by cmd/cover of the meta-data.
+       MetaHash string
+
+       // Instrumentation strategy. For now this is always set to
+       // "normal", but in the future we may add new values (for example,
+       // if panic paths are instrumented, or if the instrumenter
+       // eliminates redundant counters).
+       Strategy string
+
+       // Prefix assigned to the names of counter variables generated
+       // during instrumentation by cmd/cover.
+       CounterPrefix string
+
+       // Name chosen for the package ID variable generated during
+       // instrumentation.
+       PkgIdVar string
+
+       // Counter mode (e.g. set/count/atomic)
+       CounterMode string
+
+       // Counter granularity (perblock or perfunc).
+       CounterGranularity string
+}