runtime/pprof — validation proof

Go’s own runtime/pprof test suite, converted to C# by go2cs, built against the converted standard library, run under the Go-semantics test host, and compared verdict for verdict against a clean go test -json baseline of the same sources. This page is generated by the converter from that comparison — it is the evidence behind the runtime/pprof row in Validated Test Packages.

Validated 2026-10-25 · converter b5fc1294d

146 matched · 8 disclosed — Go 1.26.10, windows/amd64, converted package src/core/runtime/pprof.

Measured at Release (tiered JIT off), oracle go version go1.26.10 windows/amd64.

Both runtimes skip 2 of the matched tests identically.

Six host-fatal entries, all of one shape, each earned by its own isolation measurement: the awaitBlockedGoroutine closure described next. A seventh entry, TestGoroutineProfileLabelRace, a hang rather than a crash, was withdrawn with them and has since been retired (its record is the last note). Withdrawing the seven is what made the package measurable at all: the converted host went from 2 verdicts against 183 Go rows to 147 verdicts, 120 of them matching, in 48 seconds.

Six host-fatal exclusions, withdrawn together, are the complete reverse-reachability closure of runtime/pprof’s awaitBlockedGoroutine helper, computed with go/packages under the same conservative any-use-of-a-same-package-function rule the converter’s own capability analysis uses, and each was independently measured to take the host down when run alone. Withdrawing fewer would not make the package measurable: the next member kills the run the same way at the next alphabetical position. Together they carry 22 of the Go side’s 183 verdict rows.

THE MECHANISM. awaitBlockedGoroutine (pprof_test.go:1023) polls runtime.Stack(buf, all=true) for the regexp (?m)^goroutine \d+ [\]:\n(?:.+\n\t.+\n)*runtime/pprof\. -- it waits for a FOREIGN goroutine's own INTERIOR STACK FRAMES to appear in the all-goroutine dump. The CLR offers no way to walk another thread's stack, which the converted runtime.Stack(all) already states rather than hides: a foreign goroutine renders as its header, a ForeignStackPlaceholder line and its `created by` line. So the regexp can never match on any input, the helper's `for { Gosched(); Stack() }` loop never terminates, and the time.AfterFunc(t.Deadline()-1s) timer Go arms for exactly this case fires and panics ON A GOROUTINE. golib reproduces Go's semantics faithfully -- an unrecovered panic in a goroutine kills the binary -- so the process dies and every test after it in its phase is lost. The capability is absent BY CONSTRUCTION, not unimplemented: no increment of runtime/pprof can supply it.

WHY AN EXCLUSION RATHER THAN A DISCLOSED VERDICT. The host already attributes a verdict before it dies – Goroutine.ObserveUnhandledPanic writes <test> fail -- panic on a goroutine started by <test> with the panic text – so a verdict is not what is missing. Survival is, and no per-test capture can supply it: the polling loop keeps spinning whatever is done with the timer’s panic, so containing it converts a bounded crash into an unbounded hang and a package-deadline kill that discards every verdict in the package. That is the measured reflect TestOffsetLock shape core/testing/TestHost.cs carries as the reason its own containment policy was REVERSED, and the reason golib’s Goroutine.ContainUnhandledExceptions refuses a PanicException outright.

MINT RULE, checked: no committed proof page under docs/validation/current names any of the six, so the withdrawal cannot shrink a row some platform runs successfully. SIGNATURE, deliberately empty on all six: a withdrawn test produces no verdict on either side to pin against, and the loader refuses a signature only for this class for exactly that reason. RETIREMENT: all six retire together the day runtime.Stack(all) can render a foreign goroutine’s interior frames; until then the entries simply stop applying if it ever can.

The seventh entry was a hang, the first in this class, and it is retired. TestGoroutineProfileLabelRace did not crash: its /reset subtest spun forever because the converted goroutine profile withheld labels (a measured pointer-staleness defect), and the subtest’s exit condition is a label appearing in the profile text. Measured alone it produced zero verdicts and consumed the whole package deadline (exit 1 after 182 s under -timeout 3m); in the full run it left 36 rows without a C# verdict. It was host-fatal rather than an unsupported-runtime-capability exclusion because that gate admits only something provably unavailable, never something merely unimplemented, and the label half was unimplemented; the host-fatal class admits a crash or a deadline-consuming hang, since a test that never returns costs every test after it in its phase. The labels now reach the profile: golib hands a reference-bearing box’s registered order token out as its unsafe.Pointer number, so the labelMap pointer resolves to its box across a collection instead of naming a moved address, and pprof_impl.cs fills labels[i] from the goroutine registry’s slot. Re-measured with this entry deleted, filtered to TestGoroutineCounts and TestGoroutineProfileLabelRace, Release, tiering off, Go reference go1.23.12: TestGoroutineProfileLabelRace passes in 58 ms (/reset 42 ms, /churn 6 ms) and TestGoroutineCounts passes in 10.7 s with its label half now reached, 4/4 verdicts matching. The entry is deleted rather than annotated because host-fatal entries retire by deletion; this note keeps its record. The six above are unchanged by it: their capability (a foreign goroutine’s interior frames in runtime.Stack(all)) is a different one.

Verdicts

Test go test go2cs
TestAtomicLoadStore64 pass pass
TestBlockProfileBias pass pass
TestCPUProfile pass pass
TestCPUProfileInlining pass skip (disclosed)
TestCPUProfileLabel pass pass
TestCPUProfileMultithreadMagnitude skip skip
TestCPUProfileMultithreaded pass pass
TestCPUProfileRecursion pass skip (disclosed)
TestCPUProfileWithFork pass pass
TestContextLabels pass pass
TestConvertCPUProfile pass pass
TestConvertCPUProfileNoSamples pass pass
TestConvertMemProfile pass pass
TestConvertMemProfile/allocs pass pass
TestConvertMemProfile/heap pass pass
TestDo pass pass
TestEmptyCallStack pass pass
TestEmptyStack pass pass
TestFakeMapping pass pass
TestGenericsHashKeyInPprofBuilder pass fail (disclosed)
TestGenericsInlineLocations pass fail (disclosed)
TestGoroutineCounts pass pass
TestGoroutineLeakProfileConcurrency pass fail (disclosed)
TestGoroutineProfileConcurrency pass pass
TestGoroutineProfileConcurrency/finalizer_not_present pass pass
TestGoroutineProfileConcurrency/finalizer_present pass pass
TestGoroutineProfileConcurrency/goroutine_launches pass pass
TestGoroutineProfileConcurrency/goroutine_launches#01 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#02 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#03 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#04 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#05 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#06 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#07 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#08 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#09 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#10 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#11 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#12 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#13 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#14 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#15 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#16 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#17 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#18 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#19 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#20 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#21 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#22 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#23 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#24 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#25 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#26 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#27 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#28 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#29 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#30 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#31 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#32 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#33 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#34 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#35 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#36 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#37 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#38 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#39 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#40 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#41 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#42 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#43 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#44 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#45 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#46 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#47 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#48 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#49 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#50 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#51 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#52 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#53 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#54 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#55 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#56 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#57 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#58 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#59 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#60 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#61 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#62 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#63 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#64 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#65 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#66 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#67 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#68 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#69 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#70 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#71 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#72 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#73 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#74 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#75 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#76 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#77 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#78 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#79 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#80 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#81 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#82 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#83 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#84 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#85 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#86 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#87 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#88 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#89 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#90 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#91 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#92 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#93 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#94 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#95 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#96 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#97 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#98 pass pass
TestGoroutineProfileConcurrency/goroutine_launches#99 pass pass
TestGoroutineProfileConcurrency/overlapping_profile_requests pass pass
TestGoroutineProfileCoro pass pass
TestGoroutineProfileIssue74090 pass pass
TestGoroutineProfileLabelRace pass pass
TestGoroutineProfileLabelRace/churn pass pass
TestGoroutineProfileLabelRace/reset pass pass
TestGoroutineSwitch pass pass
TestHeapRuntimeFrames pass pass
TestLabelMapStringer pass pass
TestLabelRace pass pass
TestLabelSystemstack pass skip (disclosed)
TestMapping skip skip
TestMathBigDivide pass pass
TestMemoryProfiler pass pass
TestMemoryProfiler/debug=1 pass pass
TestMemoryProfiler/proto pass pass
TestMorestack pass skip (disclosed)
TestMutexBlockFullAggregation pass pass
TestMutexBlockFullAggregation/block pass pass
TestMutexBlockFullAggregation/mutex pass pass
TestProcSelfMaps pass pass
TestProcSelfMaps/Normal pass pass
TestProcSelfMaps/WithDeletedFile pass pass
TestSetGoroutineLabels pass pass
TestTimeVDSO pass pass
TestTracebackAll pass pass
TestTryAdd pass skip (disclosed)
TestWriteToErr pass pass

Disclosed divergences

A disclosed divergence is a specific Go assertion the managed CLR provably cannot satisfy — never a tolerance, and never a test skipped to make a row pass. Each one is pinned by exact signature in the package’s hand-owned go2cs_test_disclosures.json; a disclosed test that fails any other way is still a hard mismatch.

Test Class Pinned reason
TestCPUProfileInlining compiler-property containsInlinedCall(inlinedCaller, 4«10) at pprof_test.go:284 is Go’s own compiler-inlining self-check and the test’s first statement. The check scans the named function’s PC range with runtime.FuncForPC for a PC that resolves to a different function, which exists only where the Go compiler inlined a callee. The converted program carries no inlining decisions: each function owns a synthetic-PC span that resolves to that function by construction (golib/GoSyntheticPC.cs), so the check can never succeed and the test skips at the line Go’s source writes.
TestCPUProfileRecursion runtime-capability Go passes, C# skips. The test demands CPU samples attributed to runtime/pprof.inlinedCallee, a frame that exists only in the Go compiler’s inline tree; the managed sampler resolves physical frames and no inlined callee is ever sampled (0 of 137 samples on windows, 0 of 72 on linux, while recursionCallee and recursionCaller are sampled). testenv.CPUProfilingBroken() stays true, so the test is bounded at Go’s own 10 s and takes Go’s issue-13841 skip path; Go’s per-OS value (false) is unusable here: one measured run took 2,606 s for this one test, dominated by lostProfileEvent, with no clean bound. Re-read on every sweep; retires when the managed profiler can attribute a sample to an inlined Go callee.
TestGenericsHashKeyInPprofBuilder runtime-capability SHAPE-NAME half. Go names each generic instantiation’s frame with its GC shape (genericAllocFunc[go.shape.uint32]); the CLR’s StackFrame.GetMethod() returns the generic method DEFINITION (measured), so every instantiation’s managed frame is genericAllocFunc and the test’s want lines – printed only while the names differ – cannot match. Retires only if frame resolution can recover an instantiation’s shape. The signature is this half’s own evidence line in the printed profile: the uint32 sample with Go’s values [1 128 0 0] under the shape-less name, one of the test’s newest samples near each record’s top. It is never Go’s want = text, which is each record’s LAST line and is cut once the cumulative heap profile (Go’s test order, D4) passes the 64K record cap (TestExecution.MaxRecordCharacters).
TestGenericsHashKeyInPprofBuilder codegen-liveness IN-USE half. Go reads every one of the test’s allocations as freed after its GC (in-use 0 0). The managed profile reports the LAST allocation of each loop still in use – [1 256 1 256] from the uint32 loop (sizes 128, 256) and [1 128 1 128] from the uint64 loop (sizes 64, 128) – because the discarded result of each loop’s last call stays rooted in a frame slot for the frame’s lifetime, where Go’s liveness maps drop it at its last use. The earlier allocation of each loop is freed on both sides, which is what places the pin on the last call rather than on the profiler. The signature is the uint32 loop’s 256-byte line, which no other sample of the test produces. Retires with that liveness gap.
TestGenericsInlineLocations runtime-capability SHAPE NAMES ONLY. Go names each inlined generic frame with its GC shape (nonRecursiveGenericAllocFunction[go.shape.struct {},go.shape.struct { runtime/pprof.buf [128]uint8 }] and its swap); the CLR’s StackFrame.GetMethod() returns the generic method DEFINITION (measured), so both managed frames read nonRecursiveGenericAllocFunction and the expected sample and location cannot match. The pin is the managed stack WITH both generic frames present: until the generic-caller NoInlining rule (claude/g-inline-locations) the JIT inlined them under TieredCompilation=0 and the stack read TestGenericsInlineLocations;storeAlloc, a fixable defect this entry must not absorb – if either frame vanishes again the pin fails. Retires only if frame resolution can recover an instantiation’s shape.
TestGoroutineLeakProfileConcurrency runtime-capability Go passes, C# fails. Go finds leaked goroutines with its own collector, which marks the heap with blocked goroutines’ stacks withheld from the roots (runtime/mgc.go goroutineLeakGC, mgcmark.go, mgc.go:1318). The CLR’s collector roots every thread’s stack and offers no reachability query, so the managed runtime cannot tell which blocked goroutines are unreachable without a heap walk that golib does not have. The profile refuses by name rather than reporting a fabricated zero: writeGoroutineLeak’s first call, the runtime_goroutineLeakGC push, panics with the converter’s refusal (linknamePushTargets), so the test fails at its first leak-profile write and its four subtests never run on the C# side; they are withdrawn under this entry. Planned retirement: answering the profile from a ClrMD per-thread heap walk in its own optional assembly (docs/DESIGN-goroutine-leak-profile.md), which retires this entry on JIT hosts; Native AOT keeps the entry until a walk exists that does not need the DAC.
TestLabelSystemstack runtime-capability Go passes, C# skips. The test requires CPU samples taken on runtime.systemstack carrying the goroutine’s labels; the managed runtime has no system stack, so no systemstack frame is ever sampled (the labelHog samples themselves carry key=value: 2,934 on windows and 2,202 on linux in one measured run). CPUProfilingBroken() stays true and the test takes Go’s issue-13841 skip path, because Go’s per-OS value (false) leaves the profiling tests without a clean time bound here. Re-read on every sweep; retires only if the runtime gains a systemstack frame the sampler can see.
TestMorestack runtime-capability Go passes, C# skips. The test requires samples in runtime.newstack beneath growstack; CLR thread stacks do not grow by copying, so there is no newstack frame to sample (growstack’s own recursion is sampled). CPUProfilingBroken() stays true and the test takes Go’s issue-13841 skip path, because Go’s per-OS value (false) leaves the profiling tests without a clean time bound here. Re-read on every sweep; permanent while CLR stacks are fixed-size.
TestTryAdd compiler-property findInlinedCall(inlinedCallerDump, 4«10) at pprof_test.go:2265 is Go’s own compiler-inlining self-check and the test’s first statement. The check scans the named function’s PC range with runtime.FuncForPC for a PC that resolves to a different function, which exists only where the Go compiler inlined a callee. The converted program carries no inlining decisions: each function owns a synthetic-PC span that resolves to that function by construction (golib/GoSyntheticPC.cs), so the check can never succeed and the test skips at the line Go’s source writes. Its ten t.Run subtests are Go-only by construction (the skip precedes the fan-out) and are withdrawn by name under this entry; a subtest that ever reaches the C# side compares strictly.

TestCPUProfileInlining is a compiler-property skip: go test reports pass and the converted suite reports skip, at the test’s own upstream check for a decision of the Go compiler (inlining) that the converted program does not carry; the owner ruled the family structural. It is pinned to that upstream message, and any subtest Go ran beneath it is listed as withdrawn, never counted as matched.

TestTryAdd is a compiler-property skip: go test reports pass and the converted suite reports skip, at the test’s own upstream check for a decision of the Go compiler (inlining) that the converted program does not carry; the owner ruled the family structural. It is pinned to that upstream message, and any subtest Go ran beneath it is listed as withdrawn, never counted as matched.

TestCPUProfileRecursion is a runtime-capability skip: go test reports pass and the converted suite reports skip, at a check for a runtime capability the managed host does not have. It is pinned to that skip message, so the row moves to a hard mismatch if the converted side ever skips for a different reason, or stops skipping.

TestLabelSystemstack is a runtime-capability skip: go test reports pass and the converted suite reports skip, at a check for a runtime capability the managed host does not have. It is pinned to that skip message, so the row moves to a hard mismatch if the converted side ever skips for a different reason, or stops skipping.

TestMorestack is a runtime-capability skip: go test reports pass and the converted suite reports skip, at a check for a runtime capability the managed host does not have. It is pinned to that skip message, so the row moves to a hard mismatch if the converted side ever skips for a different reason, or stops skipping.

A disclosed test that fails at its root never reaches its own case fan-out, so the subtest verdict rows go test reports underneath it have no converted counterpart to compare. Those rows are withdrawn with their disclosed root — none is claimed by the matched count above:

Excluded declarations

Declarations filtered from both sides of the comparison, and therefore not claimed above: Benchmark, Fuzz and Example declarations the converted host does not execute, plus any test requiring a capability the managed runtime does not provide — a testing member the host has not implemented, or a platform behavior it provably cannot reproduce. Each is named with the capability it needs.