SAOC 2026: ImportC Weekly Update #3

Niyati Jain niyati.jainn15 at gmail.com
Wed Oct 7 16:16:18 UTC 2026


**SAOC 2026: ImportC Weekly Update #3**

This week I spent most of it in Windows ABI details, stack 
behavior, and how dmd's ImportC actually generates code versus 
real cl.exe. It was a genuinely good week for actually 
understanding D's runtime internals rather than just working 
around them.

With this, I closed out the Windows half of Milestone 1: got 
sljit's zero-wrapper example building and running correctly via 
ImportC on Windows, found and fixed the actual root cause, and 
spent most of the week isolating a second, much harder bug down 
to a precise, actionable finding in **sljit's own unmodified test 
suite**.

**The real root cause of the Windows link failures:** 
`sljitLir.c` includes `<windows.h>` but never includes 
`importc_msvc_builtins.h`, and druntime's own auto-included 
`importc.h` doesn't pull that in either, so the 
MSVC-intrinsic-to-D-template redirect in `__builtins_msvc.d` 
never fires. `__cpuidex`, `_umul128`, the `__readgs*` family, 
etc. all fell through as plain unresolved externs at link time, 
18 symbols total. Fixed by force-including the header with cl's 
real `/FI` flag (not gcc's `-include`, since dmd shells out to 
the actual cl.exe preprocessor on Windows), with zero changes to 
sljit's vendored source. That resolved 16 of 18.
The remaining 2 turned out to be a separate issue: 
`RtlSetVolatileMemory`, `__arch_inverted`, and `__arch_avx10ver` 
aren't real linkable symbols at all, even in real MSVC builds, 
they're compiler-recognized pseudo-intrinsics that cl.exe expands 
inline, and `__builtins_msvc.d` doesn't cover them yet. Wrote 
real implementations for those three myself. `MapViewOfFileNuma2` 
just needed `mincore.lib` linked, found by grepping every system 
`.lib` on the runner for the actual export rather than guessing.

With both fixes in, the zero-wrapper example builds and runs 
clean: it JIT-compiles a function at runtime and calls it 
successfully, confirmed in real CI, no modifications to sljit's 
source.

**Challenges, and working through them:**

The harder part was Milestone 1's other ask: confirm sljit's real 
test suite runs unmodified from D. Once the link errors above 
were fixed, the compiled test suite started exiting with code 
`127` part-way through its 138 tests, non-deterministically, a 
different test failing on every run.

I bisected by inserting checkpoint prints between test calls, and 
the crash point genuinely moved between runs, once around test 
11-29 broadly, once with test 11 completing entirely internally, 
once making it all the way to test 17. That pointed at real 
**memory corruption** rather than one deterministic bug, so I 
pulled in `cdb` and ran the actual test binary under it. That 
gave a real exception record for the first time: 
`STATUS_STACK_OVERFLOW (0xc00000fd)`.

I ruled out the obvious explanations one at a time against real 
CI runs: a missing `_chkstk` stack-probe call (tested directly 
with an 8KB buffer and then a deliberate write to byte 65535 of 
an untouched 64KB buffer, both ran clean under ImportC, so 
ImportC's codegen for large single frames checked out fine in 
isolation), optimized vs. unoptimized codegen (identical crash 
either way), and a bigger linked stack (made it crash earlier 
instead of fixing it, which was itself informative).

I asked my mentor, Nicholas Wilson, about it, and his suggestion 
to try AddressSanitizer via LDC was the right instinct for 
intermittent corruption. Porting it over surfaced a separate 
finding along the way: LDC's own `importc.h` has no equivalent of 
dmd's MSVC-intrinsic redirect mechanism at all, confirmed by 
grepping its entire druntime source for `__cpuidex` and finding 
nothing. He advised that porting dmd's `#pragma 
function_decl(ignore, ...)` mechanism to LDC wasn't worth the 
detour, so I dropped that path.

The real breakthrough came from going back to basics: I rebuilt 
the identical, unmodified sljit source with real `cl.exe` 
directly, completely bypassing D and ImportC. At first it crashed 
the exact same way, which told me the problem wasn't specific to 
ImportC, but it also wasn't sljit's own logic, since applying 
sljit's real `CMakeLists.txt` flags fixed it. Their build sets 
`target_compile_definitions(sljit_test PRIVATE 
SLJIT_HAVE_CONFIG_PRE)` and `/STACK:0x400000` on MSVC, two things 
our from-source build was never setting since we compile the 
three files directly rather than through their CMake config. 
Applying both exactly: **the native cl.exe build now runs all 138 
tests clean end to end**, with a real, meaningful exit, and only 
one genuine logic-level failure (`test34`, unrelated to the 
crash). The exact same source and exact same flags under dmd's 
ImportC, though, still segfault. Same everything except the 
compiler, which isolates this to a real codegen bug in dmd's 
ImportC specifically.

Thank you to Nicholas for the steady direction through this one, 
especially the nudge toward ASan and the questions that kept me 
from chasing unrelated paths.

If anyone's run into ImportC codegen differing from real cl.exe 
on a large translation unit like this before, or has a lead on 
where to start looking in dmd's backend, I'd be glad to hear it, 
here or once I open the dmd issue.

Confirmed — the most recent run (37641216210) has the 
zero-wrapper example passing cleanly. Here's the Status section 
to paste:

**Status:**
- sljit standalone package with the fix applied: 
https://github.com/niy-ati/sljit
- Windows CI, zero-wrapper example passing: 
https://github.com/niy-ati/importC/actions/runs/37641216210

**Milestone 1 status:** everything is done except this one piece. 
The zero-wrapper example, the CI matrix, and the earlier dmd 
fixes are all in. Sljit's own test suite running unmodified is 
the one requirement still open, and it's now blocked on a real 
dmd bug rather than anything on our side, so next step is filing 
that properly rather than more workarounds.

**Next week:** write up the ImportC codegen bug with a minimal 
repro and file it upstream against dmd, upstream the three 
Windows intrinsic gaps 
(`__arch_inverted`/`__arch_avx10ver`/`RtlSetVolatileMemory`) as a 
real PR against `__builtins_msvc.d`, and get the sljit dub 
package published now that the Windows zero-wrapper example 
itself is fully green.


More information about the Digitalmars-d mailing list