1 package runtime
2 3 import "unsafe"
4 5 // Per-block test coverage for -cover.
6 //
7 // The compiler emits a coverHit(siteID) call at the start of every basic block
8 // and writes a side file mapping each id to "pkg.Func@file:line". An
9 // instrumented build therefore reports exactly which blocks ran; coverBeforeExit
10 // dumps the counters when the program ends - to the file named by
11 // MOXIE_COVER_OUT when that is set and can be opened, stderr otherwise.
12 //
13 // The table and the dump hook are always linked (the hook is a nil check, the
14 // table is allocated lazily on the first coverHit), so a program built without
15 // -cover carries no cost and a coverage build needs no second runtime bake. The
16 // -cover flag only decides whether the compiler emits hits and writes the site
17 // file; the cover build tag is set too, for programs that want to gate their
18 // own code on being instrumented.
19 20 // maxCoverSites bounds the table. One site is numbered per basic block of the
21 // whole link set, so this only has to exceed the block count of one build.
22 const maxCoverSites = int32(1) << 18
23 24 // The table is a slice, not an array: Moxie has no arrays. It is allocated from
25 // the ROOT arena on the first coverHit call rather than in init(), so a build
26 // that records nothing never pays for it, and the root arena outlives every
27 // function frame (a frame arena is released at return).
28 var coverCounters []uint64
29 var numCoverSites int32
30 31 func coverTables() {
32 if coverCounters != nil {
33 return
34 }
35 a := RootArena()
36 if a == nil {
37 return
38 }
39 coverCounters = rawSliceU64(uintptr(ArenaAlloc(a, uintptr(maxCoverSites)*8)), maxCoverSites)
40 }
41 42 //:noinline
43 func coverHit(siteID int32) {
44 if siteID < 0 || siteID >= maxCoverSites {
45 return
46 }
47 coverTables()
48 if coverCounters == nil {
49 return
50 }
51 coverCounters[siteID]++
52 if siteID >= numCoverSites {
53 numCoverSites = siteID + 1
54 }
55 }
56 57 // PrintCoverCounters writes one line per site that ran: the site id and its
58 // call count. The compiler's <output>.cover_sites.json maps the id to
59 // "pkg.Func@file:line".
60 func PrintCoverCounters() {
61 coverDump(2)
62 }
63 64 // coverPutStr appends s to buf at n and returns the new length.
65 func coverPutStr(buf []byte, n int32, s string) int32 {
66 for i := int32(0); i < int32(len(s)); i++ {
67 buf[n] = s[i]
68 n++
69 }
70 return n
71 }
72 73 // coverPutU64 appends v in decimal to buf at n and returns the new length.
74 func coverPutU64(buf []byte, n int32, v uint64) int32 {
75 if v == 0 {
76 buf[n] = '0'
77 return n + 1
78 }
79 d := int32(0)
80 for t := v; t > 0; t = t / 10 {
81 d++
82 }
83 end := n + d
84 for i := end - 1; i >= n; i-- {
85 buf[i] = byte(v%10) + '0'
86 v = v / 10
87 }
88 return end
89 }
90 91 // coverDump writes one "coversite <id> count=<n>\n" line per site that ran.
92 // Each line is assembled in one buffer and written with a single write(2):
93 // every domain of a spawn tree appends to the same file, and O_APPEND only
94 // makes a write atomic as a whole. Emitting the line in pieces let domains
95 // exiting at the same time interleave their writes inside one line, which the
96 // reader then could not parse - the site vanished from the report.
97 func coverDump(fd int32) {
98 buf := []byte{:64}
99 for i := int32(0); i < numCoverSites; i++ {
100 c := coverCounters[i]
101 if c == 0 {
102 continue
103 }
104 n := coverPutStr(buf, 0, "coversite ")
105 n = coverPutU64(buf, n, uint64(i))
106 n = coverPutStr(buf, n, " count=")
107 n = coverPutU64(buf, n, c)
108 buf[n] = '\n'
109 n++
110 MoxieWrite(fd, unsafe.Pointer(&buf[0]), n)
111 }
112 }
113 114 // coverBeforeExit dumps the counters when a domain ends. The runtime's exit
115 // paths call it unconditionally: there is no cover_off twin to select, because
116 // in a build the compiler did not instrument the counters are nil and the site
117 // count is zero, which is the hook's entire cost there. Every domain appends to
118 // the same file, because a fork's counters are a private copy of the table made
119 // at fork: a spawn child has to write its own numbers rather than hand them
120 // back, and appending is what makes the dumps of every domain and every process
121 // of a run add up instead of overwrite.
122 func coverBeforeExit() {
123 if coverCounters == nil || numCoverSites == 0 {
124 return
125 }
126 fd := coverOutFD()
127 coverDump(fd)
128 if fd != 2 {
129 moxieCoverClose(fd)
130 }
131 }
132 133 // coverOutFD (defined in cover_fd_native.mx, stubbed in cover_fd_stub.mx)
134 // returns the descriptor to dump to: the file named by MOXIE_COVER_OUT when it
135 // is set and can be opened, opened for appending, or stderr.
136 137 //export moxie_cover_close
138 func moxieCoverClose(fd int32) (r int32)
139 140 // --- exported readers, used by a wasm host ---
141 142 //export __moxie_cover_counters_ptr
143 func coverCountersPtr() (p uintptr) {
144 if coverCounters == nil {
145 return 0
146 }
147 return uintptr(unsafe.Pointer(&coverCounters[0]))
148 }
149 150 //export __moxie_cover_n_sites
151 func coverNSites() (n int32) {
152 return numCoverSites
153 }
154