Skip to content

llvm_jit: DLLExport storage on jit_table_at globals (#2802 cache-hit fix)#2811

Merged
borisbat merged 2 commits into
masterfrom
bbatkin/jit-dll-cache-export-fix
May 22, 2026
Merged

llvm_jit: DLLExport storage on jit_table_at globals (#2802 cache-hit fix)#2811
borisbat merged 2 commits into
masterfrom
bbatkin/jit-dll-cache-export-fix

Conversation

@borisbat
Copy link
Copy Markdown
Collaborator

@borisbat borisbat commented May 22, 2026

Fixes the Windows JIT DLL cache crash documented at #2802. Follow-up to #2808 (now merged) — that added the -jit-no-cache opt-out flag, this is the actual cache-side fix so the flag stays a "skip the cache entirely" convenience rather than a workaround for broken behavior.

What was broken

The cached .jitted_scripts/<ns>/<hash>.dll bakes runtime function-pointer addresses into a few global initializers at codegen, then ResolveExternVisitor walks the AST after every DLL load and overwrites each slot via set_glob_address with the current-session address — that handoff is what keeps cache-hit reloads safe across ASLR shifts.

add_table_linkage on the jit_table_at/find/erase globals only set LLVMExternalLinkage, no DLLExport storage class. On Windows that leaves the symbol out of the DLL export table, so set_glob_address (GetProcAddress + write-through-pointer) finds nothing and silently returns false. The codegen-time address — valid in the session that wrote the DLL — survives unchanged into every subsequent session, where ASLR has placed the runtime DLL somewhere else. The JIT'd code loads the stale pointer and crashes inside the first table operation.

dumpbin /exports on a pre-fix cached DLL confirms `jit`table_at`tString` glob is missing while save_address_global's builtin function-pointer globals (which use set_public_linkage → DLLExport) are present.

Fix

  • add_table_linkage mirrors set_public_linkage's DLLExport linkage + DLLExportStorageClass combo. Post-fix dumpbin /exports shows jit_table_at_* in the exports table.
  • LLVM_JIT_CODEGEN_VERSION bumped 0x090x0a so previously-written cached DLLs miss on first run after this lands and get GC'd.

Test

tests/jit_tests/dll_cache.das probe now exercises table[k] (was x + 1) so jit_table_at_tString lands in the emitted globals, exercising the codegen + fixup path the fix targets. The cache-behavior assertions are unchanged.

Verification on Windows

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

…fix)

The cached `.jitted_scripts/<ns>/<hash>.dll` bakes function-pointer addresses
into a few global initializers at codegen, then `ResolveExternVisitor` walks
the AST after every DLL load and overwrites each slot via `set_glob_address`
with the current-session address — that handoff is what keeps cache-hit
reloads safe across ASLR shifts.

`add_table_linkage` on the `jit_table_at/find/erase` globals only set
`LLVMExternalLinkage`, no DLLExport storage class. On Windows that leaves the
symbol out of the DLL export table, so `set_glob_address` (which uses
GetProcAddress to find the slot, then writes through the pointer) finds
nothing and silently returns false. The codegen-time address — valid in the
session that wrote the DLL — survives unchanged into every subsequent
session, where ASLR has placed the runtime DLL somewhere else. The JIT'd
code loads the stale pointer and crashes inside the table operation.

Fix: mirror `set_public_linkage`'s DLLExport linkage + DLLExportStorageClass
combo on the table-accessor globals, matching what `save_address_global`
already does for builtin function pointers (which are reliably re-resolved).

Defense in depth: `set_glob_address` now panics with a precise diagnostic
when the named global is missing from the DLL's export table, instead of
returning false silently. Every caller in `ResolveExternVisitor` is fixing
up a slot that MUST be writable, so a missing export is always a codegen
linkage bug. The panic makes a future regression of this exact shape fail
loud at compile-time instead of crashing in JIT'd code after an unrelated
ASLR shift.

`tests/jit_tests/dll_cache.das` probe now exercises a `table[k]` operation
(was `x + 1`) so `jit_table_at_tString` lands in the emitted globals. Paired
with the new panic, this gives CI a hard-fail signal for the linkage
regression — verified: reverting `add_table_linkage` and re-running the test
produces `set_glob_address: missing DLL export ... — codegen-side linkage
bug, see #2802` and dastest exits non-zero.

`LLVM_JIT_CODEGEN_VERSION` bumped 0x09 → 0x0a so previously-written cached
DLLs miss the cache on the first run after this lands and get GC'd.

Stacks under PR #2808 (which adds the `-jit-no-cache` opt-out flag as a
permanent escape hatch — still useful for users who want to skip the cache
entirely).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 22, 2026 07:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a Windows-specific crash on JIT DLL cache hits by ensuring JIT-emitted table helper globals are exported from cached DLLs (so their baked addresses can be patched on reload), and by making missing exports fail loudly instead of silently producing stale pointers.

Changes:

  • Export jit_table_at/find/erase globals from JIT DLLs on Windows by applying DLLExport linkage + storage class.
  • Make DLLHandle.set_glob_address panic with a precise diagnostic when a required exported global symbol is missing.
  • Bump LLVM_JIT_CODEGEN_VERSION to invalidate previously cached DLLs, and update the DLL cache test probe to exercise table[k] so the relevant globals are emitted.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
tests/jit_tests/dll_cache.das Updates the probe program to use table["a"] so jit_table_at_* globals are emitted and the regression is caught by the new panic.
modules/dasLLVM/daslib/llvm_macro.das Bumps the JIT codegen version to force cache miss/GC of old cached DLLs.
modules/dasLLVM/daslib/llvm_jit_common.das Ensures JIT table helper globals are marked for DLL export (linkage + storage class).
modules/dasLLVM/daslib/llvm_dll_utils.das Converts silent “missing export” behavior into a hard failure with a targeted panic message.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

CI failure on Linux ARM Release / darwin Release shows the panic firing on
`count` (and others) — LLVM globalopt legitimately deletes unused globals,
make_call has a documented `if (global_var == null) return` guard for the
same case (d901e74). set_glob_address can't distinguish "optimizer
removed" from "exported-but-missing" without the LLVM module state, which
is gone by the time ResolveExternVisitor runs.

Revert to the original silent-skip behavior. The DLLExport linkage change
on add_table_linkage is the real bug fix and stays — that prevents the
missing-export case at codegen time, so set_glob_address doesn't need to
catch it at runtime.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@borisbat borisbat merged commit b9add2b into master May 22, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants