Skip to content

feat(coverage): Go-side line coverage built on the debug hook (LCOV) - #35

Merged
wolfy-j merged 7 commits into
wippyai:mainfrom
xepozz:feature/coverage-collector
Oct 31, 2026
Merged

wolfy-j merged 7 commits into
wippyai:mainfrom
xepozz:feature/coverage-collector

Conversation

@xepozz

@xepozz xepozz commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

Adds opt-in Go-side line coverage through the existing VM hook dispatcher, with deterministic LCOV output and a process-wide, mutex-protected collector shared by states and coroutines. The denominator comes from prototype debug positions, including unexecuted nested functions; both existing precompiled-prototype loaders register restored code, so warm bytecode does not disappear from reports.

Depends on debug hooks #34. Current main and its reviewed hook repair are integrated. Coverage consumes dispatches independently of the user's installed hook mask and callback reentrancy: clearing or replacing a debug callback cannot silently disable collection, and callback bodies themselves are recorded. The former constructor-specific arming path is removed. Normal debug mask/gethook behavior stays intact. The dispatcher checks the opt-in coverage flag; no zero-overhead performance claim is made without current measurements.

The added regressions reproduce lost hits for equal-number lines in distinct chunks, disabled collection after debug.sethook(nil), and absent restored-bytecode sources. Fixed controls also verify real callback body lines, zero credit for an unexecuted function, and preservation of the user's count-only hook mask. Test source identities are isolated across repeated executions. All hook/coverage controls pass three race-detector repetitions with WIPPY_COVERAGE=1. The existing CI test matrix explicitly enables coverage for these opt-in tests; default-mode skips do not supply passing credit.

Reporting snapshots are captured under the existing collector lock. Caller filters and file I/O run after it is released, so reentrant reporting cannot deadlock or block VM recording on file writes. Reentrant filter controls fail on the original implementation; the repaired controls and concurrent execution/reporting checks pass three race repetitions.

Debug-hook dependency #34 is merged as bfc0f00. The exact current head is b2728c443a1a630ddd296ecb4169b90d86698802; full CI 41239692484 is running. The isolated release candidate is 5c2f4918f199a21bf8fbaaa36409503f2ccc7c09 (CI 41239756455), based on published v1.6.12 with only the fifteen reviewed feature/test/CI files. The 98 unrelated main compiler/type-checker files are excluded. Every selected file matches this reviewed head byte for byte. No tag or release is created until full checks pass.

Disabled-instrumentation microbenchmarks, interleaved against v1.6.12 on the same pinned CPU with ten samples, show +0.74% geomean, individual results from -6.22% to +4.60%. These limited VM microbenchmarks are not a zero-overhead or production-throughput claim. No Runtime #553 implementation or dependency adoption is included.

Landed release receipt

Merged and released in Go-Lua v1.7.0, from isolated stable commit 5c2f4918f199a21bf8fbaaa36409503f2ccc7c09. All twelve hosted checks passed on both the final coverage PR and the stable candidate, including both explicitly enabled coverage-regression steps. The release's fifteen selected files were independently downloaded from the public Go module proxy and verified byte for byte against the tested source. No unrelated main compiler/type-checker changes or Runtime dependency adoption are included.

xepozz added 2 commits August 3, 2026 22:02
Adds line and count debug hooks, the primitive test-coverage tooling
(LuaCov-style, LCOV output) is built on. debug.sethook/gethook were already
declared in the type stub (compiler/stdlib/debug.go) but had no runtime
implementation, so any coverage instrumentation was impossible.

- hook.go: sethook/gethook + the callHook/fireHook engine. All state lives on
  LState (hook, hookMask, hookCount, hookCounter, hookLastLine, inHook) — the
  hot callFrame struct is left untouched, so there is no per-frame size cost on
  the call path.
- vm.go: one guarded call at the dispatch-loop head; when no hook is installed
  it is a single uint8 compare. Measured (benchstat, n=10, paired vs main):
  every VM benchmark within noise, geomean -0.10% — no measurable overhead.
- Line events fire when the current source line changes (tracked on LState).
  This is a coverage-oriented simplification of PUC's per-frame oldpc tracking:
  every executed line is reported at least once; it does not re-fire a line on
  a same-frame loop back-edge. Happy to switch to full per-frame semantics if
  preferred.
- Reentrancy is blocked via LState.inHook so a hook cannot trigger itself; the
  register top is saved/restored around the call.
- Call/return ('c'/'r') masks are rejected explicitly rather than silently
  ignored — line/count only for now.

hook_test.go covers coverage collection, count firing, gethook round-trip,
nested calls, reentrancy, sethook(nil) clearing, and c/r rejection.
Full suite (2199 tests) green, go vet clean. No generated files touched.
Coverage is a consumer of the debug hook (this branch is based on the
sethook/gethook branch), not a separate instrumentation path:

- WIPPY_COVERAGE arms every LState's line hook via covArm (hookMask |=
  HookMaskLine) in newLState — no debug.sethook call needed, no separate
  dispatch-loop guard;
- the hook's line-event path in callHook records each executing source line
  (numerator), reusing the hook's hookLastLine dedup;
- CompileWithOptions registers every prototype's coverable lines (walking nested
  FunctionPrototypes' DbgSourcePositions) = exact denominator incl. never-run
  functions;
- WriteCoverageLCOV / CoverageSummary emit a standard LCOV tracefile.

No-op when WIPPY_COVERAGE is unset (covArm and covRegisterProto both bail).
Full suite green, go vet clean; smoke confirms covArm arms the hook and hits are
recorded with no explicit sethook.
@wolfy-j

wolfy-j commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

we can keep it for v1 for now since v2 already have coverage, will be reviewed, thank you

@wolfy-j wolfy-j assigned skhaz and unassigned skhaz Sep 9, 2026
@wolfy-j
wolfy-j merged commit 393a519 into wippyai:main Oct 31, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants