github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/coverage/cover.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package coverage 6 7 // This package contains support routines for coverage "fixup" in the 8 // compiler, which happens when compiling a package whose source code 9 // has been run through "cmd/cover" to add instrumentation. The two 10 // important entry points are FixupVars (called prior to package init 11 // generation) and FixupInit (called following package init 12 // generation). 13 14 import ( 15 "strconv" 16 "strings" 17 18 "github.com/go-asm/go/cmd/compile/base" 19 "github.com/go-asm/go/cmd/compile/ir" 20 "github.com/go-asm/go/cmd/compile/typecheck" 21 "github.com/go-asm/go/cmd/compile/types" 22 "github.com/go-asm/go/cmd/objabi" 23 "github.com/go-asm/go/coverage" 24 ) 25 26 // names records state information collected in the first fixup 27 // phase so that it can be passed to the second fixup phase. 28 type names struct { 29 MetaVar *ir.Name 30 PkgIdVar *ir.Name 31 InitFn *ir.Func 32 CounterMode coverage.CounterMode 33 CounterGran coverage.CounterGranularity 34 } 35 36 // Fixup adds calls to the pkg init function as appropriate to 37 // register coverage-related variables with the runtime. 38 // 39 // It also reclassifies selected variables (for example, tagging 40 // coverage counter variables with flags so that they can be handled 41 // properly downstream). 42 func Fixup() { 43 if base.Flag.Cfg.CoverageInfo == nil { 44 return // not using coverage 45 } 46 47 metaVarName := base.Flag.Cfg.CoverageInfo.MetaVar 48 pkgIdVarName := base.Flag.Cfg.CoverageInfo.PkgIdVar 49 counterMode := base.Flag.Cfg.CoverageInfo.CounterMode 50 counterGran := base.Flag.Cfg.CoverageInfo.CounterGranularity 51 counterPrefix := base.Flag.Cfg.CoverageInfo.CounterPrefix 52 var metavar *ir.Name 53 var pkgidvar *ir.Name 54 55 ckTypSanity := func(nm *ir.Name, tag string) { 56 if nm.Type() == nil || nm.Type().HasPointers() { 57 base.Fatalf("unsuitable %s %q mentioned in coveragecfg, improper type '%v'", tag, nm.Sym().Name, nm.Type()) 58 } 59 } 60 61 for _, nm := range typecheck.Target.Externs { 62 s := nm.Sym() 63 switch s.Name { 64 case metaVarName: 65 metavar = nm 66 ckTypSanity(nm, "metavar") 67 nm.MarkReadonly() 68 continue 69 case pkgIdVarName: 70 pkgidvar = nm 71 ckTypSanity(nm, "pkgidvar") 72 nm.SetCoverageAuxVar(true) 73 s := nm.Linksym() 74 s.Type = objabi.SCOVERAGE_AUXVAR 75 continue 76 } 77 if strings.HasPrefix(s.Name, counterPrefix) { 78 ckTypSanity(nm, "countervar") 79 nm.SetCoverageCounter(true) 80 s := nm.Linksym() 81 s.Type = objabi.SCOVERAGE_COUNTER 82 } 83 } 84 cm := coverage.ParseCounterMode(counterMode) 85 if cm == coverage.CtrModeInvalid { 86 base.Fatalf("bad setting %q for covermode in coveragecfg:", 87 counterMode) 88 } 89 var cg coverage.CounterGranularity 90 switch counterGran { 91 case "perblock": 92 cg = coverage.CtrGranularityPerBlock 93 case "perfunc": 94 cg = coverage.CtrGranularityPerFunc 95 default: 96 base.Fatalf("bad setting %q for covergranularity in coveragecfg:", 97 counterGran) 98 } 99 100 cnames := names{ 101 MetaVar: metavar, 102 PkgIdVar: pkgidvar, 103 CounterMode: cm, 104 CounterGran: cg, 105 } 106 107 for _, fn := range typecheck.Target.Funcs { 108 if ir.FuncName(fn) == "init" { 109 cnames.InitFn = fn 110 break 111 } 112 } 113 if cnames.InitFn == nil { 114 panic("unexpected (no init func for -cover build)") 115 } 116 117 hashv, len := metaHashAndLen() 118 if cnames.CounterMode != coverage.CtrModeTestMain { 119 registerMeta(cnames, hashv, len) 120 } 121 if base.Ctxt.Pkgpath == "main" { 122 addInitHookCall(cnames.InitFn, cnames.CounterMode) 123 } 124 } 125 126 func metaHashAndLen() ([16]byte, int) { 127 128 // Read meta-data hash from config entry. 129 mhash := base.Flag.Cfg.CoverageInfo.MetaHash 130 if len(mhash) != 32 { 131 base.Fatalf("unexpected: got metahash length %d want 32", len(mhash)) 132 } 133 var hv [16]byte 134 for i := 0; i < 16; i++ { 135 nib := string(mhash[i*2 : i*2+2]) 136 x, err := strconv.ParseInt(nib, 16, 32) 137 if err != nil { 138 base.Fatalf("metahash bad byte %q", nib) 139 } 140 hv[i] = byte(x) 141 } 142 143 // Return hash and meta-data len 144 return hv, base.Flag.Cfg.CoverageInfo.MetaLen 145 } 146 147 func registerMeta(cnames names, hashv [16]byte, mdlen int) { 148 // Materialize expression for hash (an array literal) 149 pos := cnames.InitFn.Pos() 150 elist := make([]ir.Node, 0, 16) 151 for i := 0; i < 16; i++ { 152 elem := ir.NewInt(base.Pos, int64(hashv[i])) 153 elist = append(elist, elem) 154 } 155 ht := types.NewArray(types.Types[types.TUINT8], 16) 156 hashx := ir.NewCompLitExpr(pos, ir.OCOMPLIT, ht, elist) 157 158 // Materalize expression corresponding to address of the meta-data symbol. 159 mdax := typecheck.NodAddr(cnames.MetaVar) 160 mdauspx := typecheck.ConvNop(mdax, types.Types[types.TUNSAFEPTR]) 161 162 // Materialize expression for length. 163 lenx := ir.NewInt(base.Pos, int64(mdlen)) // untyped 164 165 // Generate a call to runtime.addCovMeta, e.g. 166 // 167 // pkgIdVar = runtime.addCovMeta(&sym, len, hash, pkgpath, pkid, cmode, cgran) 168 // 169 fn := typecheck.LookupRuntime("addCovMeta") 170 pkid := coverage.HardCodedPkgID(base.Ctxt.Pkgpath) 171 pkIdNode := ir.NewInt(base.Pos, int64(pkid)) 172 cmodeNode := ir.NewInt(base.Pos, int64(cnames.CounterMode)) 173 cgranNode := ir.NewInt(base.Pos, int64(cnames.CounterGran)) 174 pkPathNode := ir.NewString(base.Pos, base.Ctxt.Pkgpath) 175 callx := typecheck.Call(pos, fn, []ir.Node{mdauspx, lenx, hashx, 176 pkPathNode, pkIdNode, cmodeNode, cgranNode}, false) 177 assign := callx 178 if pkid == coverage.NotHardCoded { 179 assign = typecheck.Stmt(ir.NewAssignStmt(pos, cnames.PkgIdVar, callx)) 180 } 181 182 // Tack the call onto the start of our init function. We do this 183 // early in the init since it's possible that instrumented function 184 // bodies (with counter updates) might be inlined into init. 185 cnames.InitFn.Body.Prepend(assign) 186 } 187 188 // addInitHookCall generates a call to runtime/coverage.initHook() and 189 // inserts it into the package main init function, which will kick off 190 // the process for coverage data writing (emit meta data, and register 191 // an exit hook to emit counter data). 192 func addInitHookCall(initfn *ir.Func, cmode coverage.CounterMode) { 193 typecheck.InitCoverage() 194 pos := initfn.Pos() 195 istest := cmode == coverage.CtrModeTestMain 196 initf := typecheck.LookupCoverage("initHook") 197 istestNode := ir.NewBool(base.Pos, istest) 198 args := []ir.Node{istestNode} 199 callx := typecheck.Call(pos, initf, args, false) 200 initfn.Body.Append(callx) 201 }