La semántica de implementación de subprocesos estáticos está por debajo del nivel IL, en el compilador .NET jit. Los compiladores que emiten a IL como VB.NET y C# no necesitan saber nada sobre Win32 TLS para emitir código IL que pueda leer y escribir una variable que tenga el atributo ThreadStatic. No hay nada especial acerca de la variable en lo que C# conoce: es solo un lugar para leer y escribir cosas. El hecho de que tenga un atributo no tiene ninguna consecuencia para C#. C# solo necesita saber para emitir instrucciones de lectura o escritura de IL para ese nombre de símbolo.
La 'carga pesada' es realizada por el núcleo CLR que es responsable de hacer que IL funcione en una arquitectura de hardware particular.
Eso también explicaría por qué poner el atributo en un símbolo inapropiado (no estático) no obtiene una reacción del compilador. El compilador no sabe qué semántica especial requiere el atributo. Las herramientas de análisis de código como FX/Cop, sin embargo, deberían saberlo.
Otra forma de verlo: CIL define un conjunto de ámbitos de almacenamiento: almacenamiento estático (global), almacenamiento de miembros y almacenamiento de pila. TLS no está en esa lista, muy probablemente porque TLS no necesita estar en esa lista. Si las instrucciones de lectura y escritura de IL son suficientes para acceder a TLS cuando el símbolo está etiquetado con un atributo TLS, ¿por qué IL debe tener alguna representación o tratamiento especial para TLS? No es necesario.
Si el IL generado es el mismo (que de hecho lo es), el tiempo de ejecución debe codificarse específicamente para saber cómo asignar y leer el valor cuando llega a dicho campo decorado. Parece un truco :) –