package runtime import "unsafe" // Per-block test coverage for -cover. // // The compiler emits a coverHit(siteID) call at the start of every basic block // and writes a side file mapping each id to "pkg.Func@file:line". An // instrumented build therefore reports exactly which blocks ran; coverBeforeExit // dumps the counters when the program ends - to the file named by // MOXIE_COVER_OUT when that is set and can be opened, stderr otherwise. // // The table and the dump hook are always linked (the hook is a nil check, the // table is allocated lazily on the first coverHit), so a program built without // -cover carries no cost and a coverage build needs no second runtime bake. The // -cover flag only decides whether the compiler emits hits and writes the site // file; the cover build tag is set too, for programs that want to gate their // own code on being instrumented. // maxCoverSites bounds the table. One site is numbered per basic block of the // whole link set, so this only has to exceed the block count of one build. const maxCoverSites = int32(1) << 18 // The table is a slice, not an array: Moxie has no arrays. It is allocated from // the ROOT arena on the first coverHit call rather than in init(), so a build // that records nothing never pays for it, and the root arena outlives every // function frame (a frame arena is released at return). var coverCounters []uint64 var numCoverSites int32 func coverTables() { if coverCounters != nil { return } a := RootArena() if a == nil { return } coverCounters = rawSliceU64(uintptr(ArenaAlloc(a, uintptr(maxCoverSites)*8)), maxCoverSites) } //:noinline func coverHit(siteID int32) { if siteID < 0 || siteID >= maxCoverSites { return } coverTables() if coverCounters == nil { return } coverCounters[siteID]++ if siteID >= numCoverSites { numCoverSites = siteID + 1 } } // PrintCoverCounters writes one line per site that ran: the site id and its // call count. The compiler's .cover_sites.json maps the id to // "pkg.Func@file:line". func PrintCoverCounters() { coverDump(2) } // coverPutStr appends s to buf at n and returns the new length. func coverPutStr(buf []byte, n int32, s string) int32 { for i := int32(0); i < int32(len(s)); i++ { buf[n] = s[i] n++ } return n } // coverPutU64 appends v in decimal to buf at n and returns the new length. func coverPutU64(buf []byte, n int32, v uint64) int32 { if v == 0 { buf[n] = '0' return n + 1 } d := int32(0) for t := v; t > 0; t = t / 10 { d++ } end := n + d for i := end - 1; i >= n; i-- { buf[i] = byte(v%10) + '0' v = v / 10 } return end } // coverDump writes one "coversite count=\n" line per site that ran. // Each line is assembled in one buffer and written with a single write(2): // every domain of a spawn tree appends to the same file, and O_APPEND only // makes a write atomic as a whole. Emitting the line in pieces let domains // exiting at the same time interleave their writes inside one line, which the // reader then could not parse - the site vanished from the report. func coverDump(fd int32) { buf := []byte{:64} for i := int32(0); i < numCoverSites; i++ { c := coverCounters[i] if c == 0 { continue } n := coverPutStr(buf, 0, "coversite ") n = coverPutU64(buf, n, uint64(i)) n = coverPutStr(buf, n, " count=") n = coverPutU64(buf, n, c) buf[n] = '\n' n++ MoxieWrite(fd, unsafe.Pointer(&buf[0]), n) } } // coverBeforeExit dumps the counters when a domain ends. The runtime's exit // paths call it unconditionally: there is no cover_off twin to select, because // in a build the compiler did not instrument the counters are nil and the site // count is zero, which is the hook's entire cost there. Every domain appends to // the same file, because a fork's counters are a private copy of the table made // at fork: a spawn child has to write its own numbers rather than hand them // back, and appending is what makes the dumps of every domain and every process // of a run add up instead of overwrite. func coverBeforeExit() { if coverCounters == nil || numCoverSites == 0 { return } fd := coverOutFD() coverDump(fd) if fd != 2 { moxieCoverClose(fd) } } // coverOutFD (defined in cover_fd_native.mx, stubbed in cover_fd_stub.mx) // returns the descriptor to dump to: the file named by MOXIE_COVER_OUT when it // is set and can be opened, opened for appending, or stderr. //export moxie_cover_close func moxieCoverClose(fd int32) (r int32) // --- exported readers, used by a wasm host --- //export __moxie_cover_counters_ptr func coverCountersPtr() (p uintptr) { if coverCounters == nil { return 0 } return uintptr(unsafe.Pointer(&coverCounters[0])) } //export __moxie_cover_n_sites func coverNSites() (n int32) { return numCoverSites }