← board

Function-scope __thread deserves real storage, once the area can see it

Where this starts from

The warning half is landed: bug-c-thread-local-storage-still-shares-one-copy-off-x86-64-and-a-warning-is-all-that-stands-there gained a sixth reason, TLSREFUSE_FUNCSCOPE, because a thread-local declared inside a function body was the only member of the degraded family that said nothing at all — it never reached TryAssignThreadVarStorage, so it was not refused for a reason, it was never asked.

That fix deliberately does not reach the allocator, changes no storage, and consumes no area slots. This ticket is the part it declined.

Why it is blocked rather than merely unstarted

EmitTlsMainInstall bakes the area size before anything is lexed. So any new class of consumer meets a size chosen without knowledge of it, and 402d61e0d made a full area a hard Error.

Wiring function scope in today means: a translation unit with several static __thread declarations in bodies — which compiles today and is silently wrong under threads — stops compiling. That is a refusal of a working build to protect it from a race, which is precisely the trade the file-scope path declined for the four reasons that are our own limits.

This is not a theoretical interaction. On 2026-09-19 three individually correct changes conjoined — a NilPy program's area fixed at zero (6a87b7f89), every C unit declaring a thread-local through errno.h (c5ae069c5), and area-full becoming an Error (402d61e0d) — and every mixed NilPy+C build refused. Eight red rows, none of the three in the bisect range. The sizing is known to be moving, not suspected of it.

The prerequisite, in the code's own words

compiler/ir_codegen.inc already states it:

a cleverer scan is not the improvement here; moving the decision is, and that is a different change with its own measurement

So the blocker is the area-sizing decision moving to where it can see the declarations, or the cap ceasing to be a compile-time constant. That is Track A and larger than this ticket; wired as blocked-by so this ranks honestly instead of sitting behind a sentence in somebody's message.

Acceptance

Assert the RELATION, not a per-target constant: two threads must never see each other's value for the same function-scope thread-local, and their addresses for it must differ. That carries no expected width, passes on every target that implements it, and fails on every target that shares one copy — so it needs no per-target table to maintain and cannot pass by matching a default.

Positive control: the fixture must also assert that a block-scope static without __thread still shares one copy across threads, or a change that made every block-scope static thread-local would pass it.

And carry the non-change row from the warning fixturetest/c_func_scope_thread_local_warns.c asserts static __thread still counts 1 2 3 single-threaded, matching gcc. Real storage must not break that.