Skip to content

Windows: modules other than gc share the process heap; attributes named once; Debug DEBUG_TYPE fix - #412

Merged
ASDAlexander77 merged 3 commits into
mainfrom
windows-process-heap
Sep 30, 2026
Merged

ASDAlexander77 merged 3 commits into
mainfrom
windows-process-heap

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Summary

Three fixes found by running the Debug build locally on the cross-module (-shared, async, @linkname) tests. Those tests had 19 failures in Debug.

1. Windows: every module but gc allocates from the process heap

A block made in one module is often freed in another. Two examples:

  • under -mm=rc and -mm=own, an object of a library's class is made in the library and destroyed by the program;
  • a coroutine frame comes from the runtime and goes back through the program.

On Windows the C runtime does not give these modules one allocator:

  • each module links its own copy of the static CRT;
  • a debug CRT keeps its block list per copy, so a cross-module free asserts or corrupts the heap;
  • with the prebuilt LLVM, malloc in tslang.exe and TypeScriptRuntime.dll is rpmalloc.

The fix:

  • A new ProcessHeapPass runs on Windows (not wasm) when the model is not gc; gc already has GCPass.
    • It renames malloc, calloc, realloc, free, aligned_alloc and aligned_free to __tslang_heap_*. It changes both calls and address-of uses, such as a destructor slot.
    • The helpers take the same arguments as the C functions, so this is a rename, not a rewrite.
  • include/TypeScript/ProcessHeap.h is header-only and wraps HeapAlloc, HeapReAlloc and HeapFree:
    • realloc(p, 0) frees the block and returns null, as UCRT does;
    • aligned_alloc fails for an alignment above MEMORY_ALLOCATION_ALIGNMENT rather than return under-aligned memory.
  • lib/ProcessHeapExports.inc exports the helpers from three places, all from the same code:
    • the async runtime library that programs and libraries link;
    • TypeScriptRuntime.dll (listed in the .def);
    • the JIT, where a symbol table replaces the earlier ucrtbase lookup.
  • MemRuntime.cpp (Alloc, AlignedAlloc, Free, AlignedFree) uses the process heap on Windows in every build.

The pass must mark the helpers as allocators. The first version renamed the functions and nothing else. After that, DefaultLib under rc double-freed. The renamed functions had lost what LLVM knows about malloc by name, so LLVM stopped deleting allocations whose only use is being freed, and that exposed double frees the deletion had been hiding. Each helper now gets the same attributes the C functions get:

  • allockind;
  • "alloc-family"="malloc";
  • allocptr on the argument of free and realloc;
  • memory effects.

2. Debug assert: "DictionaryAttr element names must be unique"

processFunctionAttributes could add the same attribute twice, for example dllname on a @linkname declaration used from a -shared module. Each attribute name is now added once.

  • New test: test-declare-linkname-attribute-once.

3. Debug build: MLIRGenCast.cpp defines DEBUG_TYPE again after the RTTI helper

The RTTI helper undefines DEBUG_TYPE on its way out, which broke the Debug build. This is the same commit as in #411; whichever PR merges second will have nothing to apply for it.

Test results

  • Windows Release ctest: 3254/3254
  • Windows Debug, cross-module tests (shared|async|linkname): 352/352 (19 failures before)
  • Linux (WSL): 3240/3240
  • DefaultLib gc: 157/157
  • DefaultLib rc: 52 failures of 314 runs, the same with or without this change. They are flaky Set/Map/string/regex tests (heap corruption, 0xC0000409, busy loops). They are not caused by this PR and will be looked at separately.

🤖 Generated with Claude Code

ASDAlexander77 and others added 3 commits September 30, 2026 00:57
…helper

#407 included MLIRRTTIHelperVC.h after MLIRGenImpl.h, and the helper
undefines DEBUG_TYPE on the way out, so every LLVM_DEBUG in the file was
an error in a Debug build (Release compiles LLVM_DEBUG away, so CI never
saw it). MLIRGenStatements.cpp does the same after the same include.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`@dllname("strlen") @Linkname("strlen")` both become DLL_NAME, and
processFunctionAttributes pushed it once per decorator. Two entries of one
name are an assert in a Debug build ("DictionaryAttr element names must be
unique", test-{compile,jit}-declare-linkname-shared-symbol) and print twice
in Release. checkLinkNameDecorators has already made sure they agree, so the
first stands; the same holds for any decorator written twice.

New test: --emit=mlir of declare_linkname_shared_symbol.ts fails on a
repeated dllname.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 879bf3b into main Sep 30, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the windows-process-heap branch September 30, 2026 07:45
ASDAlexander77 added a commit that referenced this pull request Oct 6, 2026
The -mm=none frame allocator check looked for aligned_alloc(i32, i32).
Since #412 ProcessHeapPass renames it on Windows, under every model but
gc, to the process-heap allocator __tslang_heap_aligned_alloc, which
keeps the 32-bit size_t signature AsyncTargetWidthPass gave it. The
check now requires that name with both parameters i32.

await_order expected "after await" before "in g", recorded when an
await of a void async function did not wait. Since #497 main waits on
g()'s token, so "in g" always prints first (300/300 runs, x86 and x64).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant