sync — validation proof

Go’s own sync 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 sync row in Validated Test Packages.

Validated 2026-10-25 · converter 3e3f18925

48 matched · 9 disclosed — Go 1.26.10, windows/amd64, converted package src/core/sync.

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

Verdicts

Test go test go2cs
TestCompareAndSwap_NonExistingKey pass pass
TestConcurrentClear pass pass
TestConcurrentRange pass pass
TestCondBroadcast pass pass
TestCondCopy pass pass
TestCondSignal pass pass
TestCondSignalGenerations pass pass
TestCondSignalStealing pass pass
TestIssue40999 pass pass
TestIssue76126 pass pass
TestMapClearOneAllocation pass fail (disclosed)
TestMapMatchesDeepCopy pass pass
TestMapMatchesHashTrieMap pass pass
TestMapMatchesRWMutex pass pass
TestMapRangeNestedCall pass pass
TestMapRangeNoAllocations pass fail (disclosed)
TestMutex pass pass
TestMutexFairness pass pass
TestMutexMisuse pass pass
TestNilPool pass pass
TestNilPool/Get pass pass
TestNilPool/Put pass pass
TestOnce pass pass
TestOnceFunc pass pass
TestOnceFuncGoexit pass pass
TestOnceFuncPanic pass pass
TestOnceFuncPanicNil pass pass
TestOnceFuncPanicTraceback pass pass
TestOncePanic pass pass
TestOnceValue pass pass
TestOnceValuePanic pass pass
TestOnceValues pass pass
TestOnceValuesPanic pass pass
TestOnceXGC pass fail (disclosed)
TestOnceXGC/OnceFunc pass fail (disclosed)
TestOnceXGC/OnceFunc_panic pass fail (disclosed)
TestOnceXGC/OnceValue pass fail (disclosed)
TestOnceXGC/OnceValue_panic pass fail (disclosed)
TestOnceXGC/OnceValues pass fail (disclosed)
TestOnceXGC/OnceValues_panic pass fail (disclosed)
TestParallelReaders pass pass
TestPool pass pass
TestPoolChain pass pass
TestPoolDequeue pass pass
TestPoolGC pass pass
TestPoolNew pass pass
TestPoolRelease pass pass
TestPoolStress pass pass
TestRLocker pass pass
TestRWMutex pass pass
TestRace pass pass
TestSemaphore pass pass
TestWaitGroup pass pass
TestWaitGroupAlign pass pass
TestWaitGroupGo pass pass
TestWaitGroupMisuse pass pass
TestWaitGroupRace pass pass

Disclosed divergences

A disclosed divergence is a specific Go assertion the managed CLR provably cannot satisfy — not a skipped test and not a tolerance. Each one is pinned by exact failure 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
TestMapClearOneAllocation alloc-count-semantics at-most-one AllocsPerRun assert over sync.Map.Clear (allocs > 1 errors, so the want is at most ONE object per run). The managed shim reported BYTES, not a count: measured 8,960 allocated BYTES over 10 run(s) ... BYTES PER RUN, not an allocation count. The go2cs runtime allocation counter charged none of it, so every object on this path was allocated outside golib (a compiler-emitted closure in converted code, or a BCL internal) and NO COUNT IS AVAILABLE. A byte figure cannot be compared with an object want at any allocation behavior, which is the ladder’s incomparable-unit arm: nothing to retire, and no plan, because the entry does not assert that the path allocates too much – it records that the instrument cannot answer the question the test asks. Note why the shim does not simply report zero: its counter charged nothing, and reporting that zero would be a FALSE PASS against a want of one. SIGNATURE IS WANT-FREE deliberately: this declaration is a 1.24 RENAME of 1.23.12’s TestMapClearNoAllocations and the want moved 0 -> 1 with it, so a signature carrying the want would stop matching the moment sync is reconverted at 1.24.13. READ AT go1.26.10 (2026-10-25, G): 896 bytes per run on windows, 608 on linux – go1.26.10 at d804094d7a (batch 37’s tip), Release with tiering off; windows: 8,960 bytes over 10 runs, no object charged to golib’s counter, alloc-units-windows.tsv line 184 (claude/g-pass2-plan 966562828d); linux: 6,080 bytes over 10 runs, no object charged to golib’s counter, alloc-units-linux.tsv line 171 (claude/p1-alloc-reading 8d9d880ca2); both TSVs under docs/phase4/data/rebank-go1.26.10/; unit BYTES: golib’s counter charged no object on this path, so bytes is the measurable figure.
TestMapRangeNoAllocations alloc-count-semantics want-zero AllocsPerRun assert over sync.Map.Range with a func(key, value any) bool callback. Same incomparable-unit arm as TestMapClearOneAllocation: the shim reported measured 1,520 allocated BYTES over 10 run(s) ... BYTES PER RUN, not an allocation count. The go2cs runtime allocation counter charged none of it, so no object count exists to compare with the want and the figure is byte-derived by construction. Nothing to retire and no plan. Unlike its sibling this declaration is UNCHANGED across 1.23.12 and 1.24.13 (same name, allocs > 0, want 0), so it carries no rename; the signature is still want-free for consistency with the sibling and because a want-bearing signature buys nothing. READ AT go1.26.10 (2026-10-25, G): 152 bytes per run on windows and linux – go1.26.10 at d804094d7a (batch 37’s tip), Release with tiering off; windows: 1,520 bytes over 10 runs, no object charged to golib’s counter, alloc-units-windows.tsv line 185 (claude/g-pass2-plan 966562828d); linux: 1,520 bytes over 10 runs, no object charged to golib’s counter, alloc-units-linux.tsv line 172 (claude/p1-alloc-reading 8d9d880ca2); both TSVs under docs/phase4/data/rebank-go1.26.10/; unit BYTES: golib’s counter charged no object on this path, so bytes is the measurable figure.
TestOnceXGC aggregate no failure text of its own — the roll-up of this test’s disclosed subtests
TestOnceXGC/OnceFunc codegen-liveness The test asserts its own frame’s buf is collectible while the frame is still running. sync.OnceFunc DOES drop the wrapped function (measured: the backing array is released after the first call), but the test’s own f := fn(buf) passes the 32-byte slice header BY VALUE, so the x64 ABI makes the caller materialize an address-exposed stack temp; an address-exposed slot is not lifetime-tracked and the CLR reports it live for the whole method, pinning the array until the subtest RETURNS. Go’s per-safepoint liveness maps release the caller’s copy at its last use. Not satisfiable at any layer go2cs owns — it is the CLR’s GC-info conservatism, and it holds in fully optimized code, not just under the non-optimizing JIT.
TestOnceXGC/OnceFunc_panic codegen-liveness codegen-liveness: the same pin as TestOnceXGC/OnceFunc. The subtest’s constructor returns sync.OnceFunc’s own func, so no closure object is shared, but the test’s own f := fn(buf) passes the 32-byte slice header by value, the caller materializes an address-exposed stack temp, and the CLR reports it live for the whole subtest, pinning buf until the subtest returns; Go’s per-safepoint liveness maps release the caller’s copy at its last use. Here f panics on its first call. The hand-owned sync body clears f in a finally, the analogue of Go’s defer, so the panicking path drops f as the returning path does: MEASURED with the build and the first call each in a noinline helper (Go as its own oracle collects it), C# at Release+TC0 and Release tiered COLLECTED, where it was RETAINED before that clear moved into the finally (GolibTests OnceForeignExceptionTests guards it). With sync releasing f, what remains is the test frame’s temp.
TestOnceXGC/OnceValue codegen-liveness codegen-liveness: Two pins hold buf here, and EITHER alone fails the test. (1) The test’s own frame: f := fn(buf) passes the 32-byte slice header by value, the caller materializes an address-exposed stack temp, and the CLR reports it live for the whole subtest – the cause the TestOnceXGC/OnceFunc entry measures. (2) The shared closure object below. the subtest’s constructor is the literal func(buf []byte) func() { f := sync.OnceValue(func() any { buf[0] = 1; return nil }); return func() { f() } }. C# gives every lambda declared in one scope ONE closure object holding all the variables they capture, so the returned func() { f() } shares the object that holds buf, and buf stays reachable for as long as the returned func is – which the test holds across its check. Go captures each variable separately, so its returned func reaches only f, and sync.OnceValue drops its wrapped function after the first call. MEASURED with every arm built and called in noinline helpers, so no test frame roots buf, each arm also run under Go as its own oracle (all collected after the first call), C# at Release+TC0 and Release tiered alike: the test’s own literal – RETAINED; the same sync.OnceValue call with its adapter in a separate function, so the two lambdas do not share a scope – COLLECTED. The OnceFunc subtests, whose constructor returns sync.OnceFunc’s own func with no adapter, are collected by the same probe. Retires only when BOTH pins go: the frame temp, and the shared object (the emission giving the returned func its own closure object, capturing per variable rather than per scope).
TestOnceXGC/OnceValue_panic codegen-liveness codegen-liveness: Two pins hold buf here, and EITHER alone fails the test. (1) The test’s own frame: f := fn(buf) passes the 32-byte slice header by value, the caller materializes an address-exposed stack temp, and the CLR reports it live for the whole subtest – the cause the TestOnceXGC/OnceFunc entry measures. (2) The shared closure object below. the subtest’s constructor is the literal func(buf []byte) func() { f := sync.OnceValue(func() any { buf[0] = 1; panic(“test panic”) }); return func() { f() } }. C# gives every lambda declared in one scope ONE closure object holding all the variables they capture, so the returned func() { f() } shares the object that holds buf, and buf stays reachable for as long as the returned func is – which the test holds across its check. Go captures each variable separately, so its returned func reaches only f, and sync.OnceValue drops its wrapped function after the first call. Here f panics on its first call; the hand-owned sync body clears f in a finally, the analogue of Go’s defer, so the panicking path drops f as the returning path does (GolibTests OnceForeignExceptionTests guards it). MEASURED with every arm built and called in noinline helpers, so no test frame roots buf, each arm also run under Go as its own oracle (all collected after the first call), C# at Release+TC0 and Release tiered alike: the test’s own literal – RETAINED; the same sync.OnceValue call with its adapter in a separate function, so the two lambdas do not share a scope – COLLECTED. The OnceFunc subtests, whose constructor returns sync.OnceFunc’s own func with no adapter, are collected by the same probe. Retires only when BOTH pins go: the frame temp, and the shared object (the emission giving the returned func its own closure object, capturing per variable rather than per scope).
TestOnceXGC/OnceValues codegen-liveness codegen-liveness: Two pins hold buf here, and EITHER alone fails the test. (1) The test’s own frame: f := fn(buf) passes the 32-byte slice header by value, the caller materializes an address-exposed stack temp, and the CLR reports it live for the whole subtest – the cause the TestOnceXGC/OnceFunc entry measures. (2) The shared closure object below. the subtest’s constructor is the literal func(buf []byte) func() { f := sync.OnceValues(func() (any, any) { buf[0] = 1; return nil, nil }); return func() { f() } }. C# gives every lambda declared in one scope ONE closure object holding all the variables they capture, so the returned func() { f() } shares the object that holds buf, and buf stays reachable for as long as the returned func is – which the test holds across its check. Go captures each variable separately, so its returned func reaches only f, and sync.OnceValues drops its wrapped function after the first call. MEASURED with every arm built and called in noinline helpers, so no test frame roots buf, each arm also run under Go as its own oracle (all collected after the first call), C# at Release+TC0 and Release tiered alike: the test’s own literal – RETAINED; the same sync.OnceValues call with its adapter in a separate function, so the two lambdas do not share a scope – COLLECTED. The OnceFunc subtests, whose constructor returns sync.OnceFunc’s own func with no adapter, are collected by the same probe. Retires only when BOTH pins go: the frame temp, and the shared object (the emission giving the returned func its own closure object, capturing per variable rather than per scope).
TestOnceXGC/OnceValues_panic codegen-liveness codegen-liveness: Two pins hold buf here, and EITHER alone fails the test. (1) The test’s own frame: f := fn(buf) passes the 32-byte slice header by value, the caller materializes an address-exposed stack temp, and the CLR reports it live for the whole subtest – the cause the TestOnceXGC/OnceFunc entry measures. (2) The shared closure object below. the subtest’s constructor is the literal func(buf []byte) func() { f := sync.OnceValues(func() (any, any) { buf[0] = 1; panic(“test panic”) }); return func() { f() } }. C# gives every lambda declared in one scope ONE closure object holding all the variables they capture, so the returned func() { f() } shares the object that holds buf, and buf stays reachable for as long as the returned func is – which the test holds across its check. Go captures each variable separately, so its returned func reaches only f, and sync.OnceValues drops its wrapped function after the first call. Here f panics on its first call; the hand-owned sync body clears f in a finally, the analogue of Go’s defer, so the panicking path drops f as the returning path does (GolibTests OnceForeignExceptionTests guards it). MEASURED with every arm built and called in noinline helpers, so no test frame roots buf, each arm also run under Go as its own oracle (all collected after the first call), C# at Release+TC0 and Release tiered alike: the test’s own literal – RETAINED; the same sync.OnceValues call with its adapter in a separate function, so the two lambdas do not share a scope – COLLECTED. The OnceFunc subtests, whose constructor returns sync.OnceFunc’s own func with no adapter, are collected by the same probe. Retires only when BOTH pins go: the frame temp, and the shared object (the emission giving the returned func its own closure object, capturing per variable rather than per scope).

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.