Skip to content

Shader coverage: test the coalescing exit analysis directly in slang-static-unit-test #12845

Description

@jvepsalainen-nv

Problem Description

Four properties of the coverage coalescing analysis are only testable end-to-end today, and three of them cannot be expressed in Slang source at all. Each came up during review of #12683 and had to be recorded as untestable rather than covered.

The analysis lives in source/slang/slang-ir-coverage-instrument.cpp:

  • CoverageFunctionExitAnalysis — decides whether a call can abandon the invocation, which is what splits a coalescing region.
  • assignCoverageCounterSlots — groups markers into regions and picks which one emits the runtime probe.

What cannot be tested through a .slang test

  1. GenericAsm counts as a normal exit. This is what stops a call to ordinary builtin math from splitting every region. It cannot be isolated from Slang source: sqrt, lerp and max all contain an ordinary return_val alongside their GenericAsm, so sawNormalExit is already true and flipping the clause would not change their classification. A regression here would defeat coalescing for essentially all numeric code with no test failing.

  2. The probe sits at the last marker of a region. The design depends on this: reaching the probe must prove every earlier marker in the region executed. Within a straight-line block, first-marker and last-marker placement are count-equivalent, so every existing test passes either way. Nothing would catch a regression that moved it.

  3. kIROp_Abort splits a region. It shares mayNotFallThrough with discard, which is covered, but is not reachable on its own from Slang source.

  4. Mutual recursion is broken conservatively. Coalesce line coverage counters per straight-line region #12683 fixed a real soundness bug here: an optimistic cycle break escaped into other functions' memoized results, so one partner of a mutually recursive pair could be cached as "returns normally" when it does not, with the outcome depending on traversal order. The fix is covered only indirectly.

Preferred Solution

Test these directly in slang-static-unit-test, which exists for exactly this — non-exported compiler entry points exercised on hand-built IR. IRFixtureBuilder already offers what is needed: build a function whose only exit is a GenericAsm, or a mutually recursive pair where one partner never returns, and assert what the analysis concludes. None of those modules can be produced by the frontend.

For the probe-placement property, call assignCoverageCounterSlots on a known marker sequence and assert which marker carries the probe, rather than inferring it from a slot count.

Cost, and the reason this is not free

Both targets currently have internal linkage:

static IRFunc* getStaticallyResolvedCallee(...)   // slang-ir-coverage-instrument.cpp
struct CoverageFunctionExitAnalysis               // file-local
static void assignCoverageCounterSlots(...)

Static linking reaches non-exported symbols, not static-qualified ones, so this needs assignCoverageCounterSlots and the analysis declared in slang-ir-coverage-instrument.h. That widens a header for testability, which is worth weighing against the four properties gained — the codebase is otherwise wary of changes that exist to make tests pass.

An alternative worth considering first: keep the header unchanged and test through instrumentCoverage on hand-built IR, asserting on the emitted probes rather than calling the internals directly. That covers properties 1, 3 and 4 without a linkage change, though probably not 2.

Additional Context

Raised during review of #12683 (line coverage coalescing), where each of these was answered with "cannot be isolated by test" rather than a fix. The static suite landed in #12347 after that work started.

Test Plan

  • GenericAsm-only exit is treated as returning normally.
  • A function with no reachable return splits its caller's region; one with a conditional discard splits through the abandons-the-invocation path instead.
  • kIROp_Abort mid-region splits it.
  • Mutually recursive A/B where A never returns yields the same conservative answer whichever partner the walk reaches first.
  • Given a marker sequence in one block, the last marker carries the probe.

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions