dotnet / dotnet/runtime

Checked build JIT assertion (`pfnHelper != NULL`) when accessing `__declspec(thread)` TLS from IJW/C++CLI managed entry points

Open
#133,538 3 comments 0 reactions 0 assignees View on GitHub
area-VM-coreclr
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Description

When a mixed-mode (IJW/C++/CLI) assembly declares a native, file-scope `__declspec(thread)` global (wrapped in `#pragma unmanaged`/`#pragma managed`) and that global is accessed from a `public ref class` static method, calling into that method from managed code under a **Checked** CoreCLR build crashes with a JIT assertion:

```
Assert failure(PID ####): pfnHelper != (PCODE)NULL

CORECLR! CEECodeGenInfo::getHelperFtn + 0x4E7
CLRJIT! Compiler::compGetHelperFtn + 0x35
CLRJIT! Lowering::LowerDirectCall + 0xA5
CLRJIT! Lowering::LowerCall + 0x429
CLRJIT! Lowering::LowerNode + 0x32C
CLRJIT! Lowering::DoPhase + 0x13B
CLRJIT! Phase::Run + 0x76
CLRJIT! Compiler::compCompile + 0x1AD2
CLRJIT! Compiler::compCompileHelper + 0xC51
CLRJIT! Compiler::compCompileAfterInit + 0x7B1
File: src\coreclr\vm\jitinterface.cpp:11353
```

The assertion fires in `CEECodeGenInfo::getHelperFtn` (`src/coreclr/vm/jitinterface.cpp:11404`, in a `FEATURE_PORTABLE_ENTRYPOINTS` code path), where the JIT's `Lowering::LowerDirectCall` requests a JIT helper function pointer and the helper table entry is unpopulated.

### Repro

Minimal repro (Windows, x64, Checked build):

`test.cpp` (compiled as an IJW/C++CLI assembly, e.g. via `/clr`):
```cpp
#pragma unmanaged
__declspec(thread) int s_tlsFieldData = 51966;
#pragma managed

public ref class TlsTest
{
public:
static int Test()
{
int value = s_tlsFieldData;
s_tlsFieldData = value;
s_tlsFieldData = 100;
return s_tlsFieldData;
}
};
```

Managed caller (any invocation mechanism — both plain reflection `MethodInfo.Invoke` and `UnsafeAccessor` reproduce identically):
```csharp
Assembly a = Assembly.Load("TestTLSNative");
int result = (int)a.GetType("TlsTest").GetMethod("Test").Invoke(null, null);
```

Running this against a Checked CoreCLR build crashes with the assertion above. The same code runs successfully against Debug and Release builds. Switching the native storage from `__declspec(thread)` to a managed `[System::ThreadStatic]` field on the `ref class` avoids the crash entirely.

### Analysis so far

- The crash is independent of the managed→native call mechanism: it reproduces identically whether the native method is invoked via reflection (`MethodInfo.Invoke`) or via `[UnsafeAccessor]`/`[UnsafeAccessorType]`.
- The crash is tied specifically to `__declspec(thread)` TLS storage accessed through the IJW-hosted managed entry point; using `[System::ThreadStatic]` instead does not reproduce the crash.
- This was discovered while converting `src/tests` regression tests that originally used IL `.data tls` declarations to C++/CLI (see dotnet/runtime#133533), where a reviewer requested using true native `__declspec(thread)` TLS (rather than `[System::ThreadStatic]`) "to actually be testing the right feature." Reintroducing `__declspec(thread)` reproduced this crash in all four converted tests.

### Configuration

- OS: Windows x64
- Build: Checked
- Repro requires `/clr` (IJW/C++CLI) mixed-mode assembly

> [!NOTE]
> This issue was filed with assistance from GitHub Copilot.

Contributor guide

Open the contributing guide

Research direction

Reproduce the Windows x64 Checked-build failure with the provided C++/CLI IJW sample and managed caller. Inspect src/coreclr/vm/jitinterface.cpp around CEECodeGenInfo::getHelperFtn and the FEATURE_PORTABLE_ENTRYPOINTS helper-table path, then compare the __declspec(thread) case with [System::ThreadStatic]. Done means the assertion no longer occurs and the regression scenario runs successfully.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, csharp
Domain
compilers, operating-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.