diff --git a/tslang/include/TypeScript/Defines.h b/tslang/include/TypeScript/Defines.h index f0f31810a..a6af46f5c 100644 --- a/tslang/include/TypeScript/Defines.h +++ b/tslang/include/TypeScript/Defines.h @@ -152,6 +152,15 @@ #define SHARED_LIB_OWN_FACTS "__tsown_" // a module attribute, so it has to carry the dialect's prefix #define SHARED_LIB_OWN_NO_DROPS_ATTR_NAME "ts.own_imported_no_drops" + +// The process-heap allocator of a Windows module (TypeScript/ProcessHeap.h), which ProcessHeapPass +// calls instead of the C runtime's. Defined by the async runtime library a program links, +// TypeScriptRuntime.dll and the JIT. +#define PROCESS_HEAP_MALLOC "__tslang_heap_malloc" +#define PROCESS_HEAP_CALLOC "__tslang_heap_calloc" +#define PROCESS_HEAP_REALLOC "__tslang_heap_realloc" +#define PROCESS_HEAP_FREE "__tslang_heap_free" +#define PROCESS_HEAP_ALIGNED_ALLOC "__tslang_heap_aligned_alloc" #define DLL_EXPORT "dllexport" #define DLL_IMPORT "dllimport" #define DLL_NAME "dllname" diff --git a/tslang/include/TypeScript/Passes.h b/tslang/include/TypeScript/Passes.h index 81cf46239..c7a4a7638 100644 --- a/tslang/include/TypeScript/Passes.h +++ b/tslang/include/TypeScript/Passes.h @@ -47,6 +47,7 @@ std::unique_ptr createOwnedReturnConsumptionPass(CompileOptions&); /// GC Pass to replace malloc, realloc, free with GC_malloc, GC_realloc, GC_free std::unique_ptr createGCPass(CompileOptions&); +std::unique_ptr createProcessHeapPass(); /// MemAlloc Pass to replace ts_malloc, ts_realloc, ts_free std::unique_ptr createMemAllocPass(CompileOptions&); diff --git a/tslang/include/TypeScript/ProcessHeap.h b/tslang/include/TypeScript/ProcessHeap.h new file mode 100644 index 000000000..29add6a09 --- /dev/null +++ b/tslang/include/TypeScript/ProcessHeap.h @@ -0,0 +1,95 @@ +#ifndef TYPESCRIPT_PROCESS_HEAP_H_ +#define TYPESCRIPT_PROCESS_HEAP_H_ + +// The allocator every module tslang builds on Windows uses, under every memory model but gc +// (ProcessHeapPass renames the module's malloc, calloc, realloc, free and aligned_alloc to these). +// +// A block made by one module is freed by another - an object of a library's class under -mm=rc or +// -mm=own is made in the library and destroyed by the program, a coroutine frame comes from the +// runtime and goes back through the program - so they have to share one allocator, and the C +// runtime's is not one: each module links its own copy of the static CRT, and a debug CRT keeps a +// list of its blocks per copy, while tslang.exe's own `malloc` is rpmalloc when LLVM is the +// prebuilt package. The process heap is the same heap in every module and every build. +// +// Header-only, so each place that has to provide them - the async runtime library a program links, +// TypeScriptRuntime.dll and the JIT - compiles the same code. + +#ifdef _WIN32 + +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include + +#include +#include + +namespace typescript +{ +namespace process_heap +{ + +// What HeapAlloc guarantees: 16 bytes on x64, 8 on x86. +constexpr size_t alignment = MEMORY_ALLOCATION_ALIGNMENT; + +inline void *allocate(size_t size) +{ + // malloc(0) hands back a block of its own too + return HeapAlloc(GetProcessHeap(), 0, size ? size : 1); +} + +inline void *allocateZeroed(size_t count, size_t size) +{ + if (size != 0 && count > SIZE_MAX / size) + { + return nullptr; + } + + auto bytes = count * size; + return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bytes ? bytes : 1); +} + +inline void release(void *ptr) +{ + if (ptr) + { + HeapFree(GetProcessHeap(), 0, ptr); + } +} + +inline void *reallocate(void *ptr, size_t size) +{ + if (!ptr) + { + return allocate(size); + } + + // as the C runtime's realloc: a block resized to nothing is freed, and there is none + if (size == 0) + { + release(ptr); + return nullptr; + } + + return HeapReAlloc(GetProcessHeap(), 0, ptr, size); +} + +// Every request anything makes fits what HeapAlloc aligns to (the coroutine frame asks for 8). A +// stricter one cannot be served and still go back through release(); handing back under-aligned +// memory silently would be the worse failure, so it fails. +inline void *allocateAligned(size_t align, size_t size) +{ + if (align > alignment) + { + return nullptr; + } + + return allocate(size); +} + +} // namespace process_heap +} // namespace typescript + +#endif // _WIN32 + +#endif // TYPESCRIPT_PROCESS_HEAP_H_ diff --git a/tslang/lib/ProcessHeapExports.inc b/tslang/lib/ProcessHeapExports.inc new file mode 100644 index 000000000..a7acadeda --- /dev/null +++ b/tslang/lib/ProcessHeapExports.inc @@ -0,0 +1,34 @@ +// The process-heap allocator under the names ProcessHeapPass calls (PROCESS_HEAP_* in +// TypeScript/Defines.h). Included by the async runtime library, which a program and a library link, +// and by TypeScriptRuntime.dll, which the JIT loads; see TypeScript/ProcessHeap.h for why. + +#ifdef _WIN32 + +#include "TypeScript/ProcessHeap.h" + +extern "C" void *__tslang_heap_malloc(size_t size) +{ + return typescript::process_heap::allocate(size); +} + +extern "C" void *__tslang_heap_calloc(size_t count, size_t size) +{ + return typescript::process_heap::allocateZeroed(count, size); +} + +extern "C" void *__tslang_heap_realloc(void *ptr, size_t size) +{ + return typescript::process_heap::reallocate(ptr, size); +} + +extern "C" void __tslang_heap_free(void *ptr) +{ + typescript::process_heap::release(ptr); +} + +extern "C" void *__tslang_heap_aligned_alloc(size_t alignment, size_t size) +{ + return typescript::process_heap::allocateAligned(alignment, size); +} + +#endif // _WIN32 diff --git a/tslang/lib/TypeScript/CMakeLists.txt b/tslang/lib/TypeScript/CMakeLists.txt index 49b8be4f6..4bd8f28ba 100644 --- a/tslang/lib/TypeScript/CMakeLists.txt +++ b/tslang/lib/TypeScript/CMakeLists.txt @@ -36,6 +36,7 @@ add_mlir_dialect_library(MLIRTypeScript OwnershipSignaturePass.cpp OwnedReturnConsumptionPass.cpp GCPass.cpp + ProcessHeapPass.cpp AsyncTargetWidthPass.cpp ObjDumper.cpp DeclarationPrinter.cpp diff --git a/tslang/lib/TypeScript/MLIRGenCast.cpp b/tslang/lib/TypeScript/MLIRGenCast.cpp index 3810287bd..d96512926 100644 --- a/tslang/lib/TypeScript/MLIRGenCast.cpp +++ b/tslang/lib/TypeScript/MLIRGenCast.cpp @@ -3,6 +3,10 @@ #include "MLIRGenImpl.h" #include "TypeScript/MLIRLogic/MLIRRTTIHelperVC.h" +// the RTTI helper undefines it on the way out +#undef DEBUG_TYPE +#define DEBUG_TYPE "mlir" + namespace typescript { namespace mlirgen diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 5b103ed13..c944d256c 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -2676,15 +2676,26 @@ class MLIRGenImpl #endif // add decorations, "noinline, optnone" + // An attribute list may name each attribute once (a DictionaryAttr asserts it). A + // declaration can say the same thing twice - `@dllname("f") @linkname("f")` both become + // DLL_NAME, and checkLinkNameDecorators has made sure they agree - so the first stands. + auto addAttr = [&](StringRef name, mlir::Attribute value) { + auto present = llvm::any_of(attrs, [&](mlir::NamedAttribute &attr) { return attr.getName() == name; }); + if (!present) + { + attrs.push_back({mlir::StringAttr::get(builder.getContext(), name), value}); + } + }; + iterateDecorators(functionLikeDeclarationBaseAST, genContext, [&](StringRef name, SmallVector args) { if (isFuncAttr(name)) { - attrs.push_back({mlir::StringAttr::get(builder.getContext(), name), mlir::UnitAttr::get(builder.getContext())}); + addAttr(name, mlir::UnitAttr::get(builder.getContext())); } if (name == "varargs") { - attrs.push_back({mlir::StringAttr::get(builder.getContext(), "func.varargs"), mlir::BoolAttr::get(builder.getContext(), true)}); + addAttr("func.varargs", mlir::BoolAttr::get(builder.getContext(), true)); } if (name == "used") { @@ -2693,7 +2704,7 @@ class MLIRGenImpl if (name == DLL_NAME && args.size() > 0) { - attrs.push_back({mlir::StringAttr::get(builder.getContext(), DLL_NAME), mlir::StringAttr::get(builder.getContext(), args.front())}); + addAttr(DLL_NAME, mlir::StringAttr::get(builder.getContext(), args.front())); } }); diff --git a/tslang/lib/TypeScript/ProcessHeapPass.cpp b/tslang/lib/TypeScript/ProcessHeapPass.cpp new file mode 100644 index 000000000..5bcfaf093 --- /dev/null +++ b/tslang/lib/TypeScript/ProcessHeapPass.cpp @@ -0,0 +1,186 @@ +#include "mlir/Pass/Pass.h" + +#include "TypeScript/TypeScriptDialect.h" +#include "TypeScript/TypeScriptOps.h" +#include "TypeScript/Passes.h" +#include "TypeScript/Pass/ModulePass.h" +#include "TypeScript/Defines.h" + +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" + +#include "llvm/Support/Debug.h" + +#define DEBUG_TYPE "pass" + +using namespace ::typescript; +namespace mlir_ts = mlir::typescript; + +namespace +{ + +// On Windows, under every memory model but gc: the module's malloc, calloc, realloc, free and +// aligned_alloc become the process-heap allocator of TypeScript/ProcessHeap.h. The C runtime's are +// not one allocator across modules - each links its own copy of the static CRT, a debug CRT keeps +// its blocks' list per copy, and with the prebuilt LLVM tslang.exe's `malloc` is rpmalloc - while a +// block made in one module is routinely freed in another (a library's object destroyed by the +// program, a coroutine frame from the runtime). gc has GCPass, which renames the same set to the +// collector's. +// +// Renamed, not rewritten: the helpers take what the C functions take. The names are not ones LLVM +// knows, so the allocating ones are marked as allocators, as GCPass marks GC_malloc - without that, +// two calls with equal arguments and nothing between them merge into one block. +class ProcessHeapPass : public mlir::PassWrapper +{ + public: + MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(ProcessHeapPass) + + void runOnModule() override + { + auto m = getModule(); + + llvm::SmallVector declarations; + m.walk([&](mlir::Operation *op) { + if (auto funcOp = dyn_cast(op)) + { + if (funcOp.getBody().empty() && !mapName(funcOp.getSymName()).empty()) + { + declarations.push_back(funcOp); + } + + return; + } + + if (auto callOp = dyn_cast(op)) + { + if (auto callee = callOp.getCallee()) + { + if (auto newName = mapName(*callee); !newName.empty()) + { + callOp.setCalleeAttr(mlir::FlatSymbolRefAttr::get(op->getContext(), newName)); + } + } + + return; + } + + // a function's address taken - a destructor slot, say + if (auto addressOfOp = dyn_cast(op)) + { + if (auto newName = mapName(addressOfOp.getGlobalName()); !newName.empty()) + { + addressOfOp.setGlobalNameAttr(mlir::FlatSymbolRefAttr::get(op->getContext(), newName)); + } + } + }); + + for (auto funcOp : declarations) + { + auto newName = mapName(funcOp.getSymName()); + + // `free` and `aligned_free` both become the one release; the first declaration stays + if (auto existing = m.lookupSymbol(newName); existing && existing != funcOp) + { + funcOp.erase(); + continue; + } + + funcOp.setSymName(newName); + markAsAllocator(newName, funcOp); + } + } + + private: + static llvm::StringRef mapName(llvm::StringRef name) + { + return llvm::StringSwitch(name) + .Case("malloc", PROCESS_HEAP_MALLOC) + .Case("calloc", PROCESS_HEAP_CALLOC) + .Case("realloc", PROCESS_HEAP_REALLOC) + .Case("free", PROCESS_HEAP_FREE) + .Case("aligned_alloc", PROCESS_HEAP_ALIGNED_ALLOC) + .Case("aligned_free", PROCESS_HEAP_FREE) + .Default(""); + } + + // What LLVM knows of malloc, calloc, realloc and free by name, said of the helpers: they are + // one allocator family, each allocation is a block of its own, the release takes the block + // back, and the allocator's own state is memory nobody else reads. It is not only that two equal + // allocations must not merge: optimization has to keep treating the program as it treated the C + // functions - an allocation that is only released, say, is deleted together with its releases. + static void markAsAllocator(llvm::StringRef name, LLVM::LLVMFuncOp funcOp) + { + // AllocFnKind: Alloc = 1 << 0, Realloc = 1 << 1, Free = 1 << 2, Uninitialized = 1 << 3, + // Zeroed = 1 << 4 + uint64_t allocKind; + auto takesBlock = false; + if (name == PROCESS_HEAP_MALLOC || name == PROCESS_HEAP_ALIGNED_ALLOC) + { + allocKind = 1 | (1 << 3); + } + else if (name == PROCESS_HEAP_CALLOC) + { + allocKind = 1 | (1 << 4); + } + else if (name == PROCESS_HEAP_REALLOC) + { + allocKind = (1 << 1) | (1 << 3); + takesBlock = true; + } + else if (name == PROCESS_HEAP_FREE) + { + allocKind = 1 << 2; + takesBlock = true; + } + else + { + return; + } + + auto *context = funcOp->getContext(); + + // an allocation writes only the block it makes (other memory, as GCPass says it of + // GC_malloc) and the allocator's state; a release or a resize reads and writes the block it + // is given as well + auto memoryEffects = + takesBlock ? LLVM::MemoryEffectsAttr::get(context, LLVM::ModRefInfo::NoModRef, LLVM::ModRefInfo::ModRef, + LLVM::ModRefInfo::ModRef, LLVM::ModRefInfo::NoModRef, + LLVM::ModRefInfo::NoModRef, LLVM::ModRefInfo::NoModRef) + : LLVM::MemoryEffectsAttr::get(context, LLVM::ModRefInfo::Mod, LLVM::ModRefInfo::NoModRef, + LLVM::ModRefInfo::ModRef, LLVM::ModRefInfo::NoModRef, + LLVM::ModRefInfo::NoModRef, LLVM::ModRefInfo::NoModRef); + funcOp.setMemoryEffectsAttr(memoryEffects); + + if (takesBlock && funcOp.getNumArguments() > 0) + { + funcOp.setArgAttr(0, LLVM::LLVMDialect::getAllocatedPointerAttrName(), mlir::UnitAttr::get(context)); + } + + auto entry = [&](llvm::StringRef key, llvm::StringRef value) { + return mlir::ArrayAttr::get(context, {mlir::StringAttr::get(context, key), mlir::StringAttr::get(context, value)}); + }; + + llvm::SmallVector passthrough; + if (auto existing = funcOp.getPassthroughAttr()) + { + passthrough.append(existing.begin(), existing.end()); + } + + for (auto added : {entry("allockind", std::to_string(allocKind)), entry("alloc-family", "malloc")}) + { + if (!llvm::is_contained(passthrough, added)) + { + passthrough.push_back(added); + } + } + + funcOp.setPassthroughAttr(mlir::ArrayAttr::get(context, passthrough)); + } +}; +} // end anonymous namespace + +#undef DEBUG_TYPE + +std::unique_ptr mlir_ts::createProcessHeapPass() +{ + return std::make_unique(); +} diff --git a/tslang/lib/TypeScriptAsyncRuntime/CMakeLists.txt b/tslang/lib/TypeScriptAsyncRuntime/CMakeLists.txt index 589acb315..4f7ea1168 100644 --- a/tslang/lib/TypeScriptAsyncRuntime/CMakeLists.txt +++ b/tslang/lib/TypeScriptAsyncRuntime/CMakeLists.txt @@ -1,6 +1,7 @@ add_mlir_library(TypeScriptAsyncRuntime STATIC AsyncRuntime.cpp + ProcessHeap.cpp DynamicRuntime.cpp EXCLUDE_FROM_LIBMLIR diff --git a/tslang/lib/TypeScriptAsyncRuntime/ProcessHeap.cpp b/tslang/lib/TypeScriptAsyncRuntime/ProcessHeap.cpp new file mode 100644 index 000000000..a570ce54e --- /dev/null +++ b/tslang/lib/TypeScriptAsyncRuntime/ProcessHeap.cpp @@ -0,0 +1,2 @@ +// The process-heap allocator a program and a library built by tslang call on Windows. +#include "../ProcessHeapExports.inc" diff --git a/tslang/lib/TypeScriptRuntime/MemRuntime.cpp b/tslang/lib/TypeScriptRuntime/MemRuntime.cpp index 3017ea585..e39c911e4 100644 --- a/tslang/lib/TypeScriptRuntime/MemRuntime.cpp +++ b/tslang/lib/TypeScriptRuntime/MemRuntime.cpp @@ -15,10 +15,6 @@ #include #else #include "malloc.h" -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include #endif // _WIN32 #include @@ -27,6 +23,9 @@ #include "llvm/ADT/StringMap.h" +// the process-heap allocator the JIT'd program calls (ProcessHeapPass), and heapMalloc/heapFree +#include "../ProcessHeapExports.inc" + //===----------------------------------------------------------------------===// // Async runtime API. //===----------------------------------------------------------------------===// @@ -36,31 +35,14 @@ namespace mlir namespace runtime { -// The heap these hand out: the release CRT's, on the process heap, which is also what JIT-compiled -// code gets as `malloc` and `free` (tslang/jit.cpp, jitHeapFunction) - a coroutine frame this -// allocates, the program frees with plain `free`. This DLL's own `malloc` need not be that heap: -// linked against the prebuilt LLVM, LLVMSupport makes it rpmalloc (see -// scripts/llvm_prebuilt_common.ps1), and a block of one freed by the other corrupts the heap. A -// debug build keeps its own, as the JIT does: the debug CRT puts a header in front of each block. -#if defined(_WIN32) && !defined(_DEBUG) -template static F crtFunction(const char *name, F ownFunction) -{ - static auto ucrt = LoadLibraryW(L"ucrtbase.dll"); - auto function = ucrt ? reinterpret_cast(GetProcAddress(ucrt, name)) : nullptr; - return function ? function : ownFunction; -} - -static void *heapMalloc(size_t size) -{ - static auto function = crtFunction("malloc", &malloc); - return function(size); -} +// The heap these hand out is the process heap, which is what every module tslang builds allocates +// from and frees to (TypeScript/ProcessHeap.h): a coroutine frame this allocates, the program frees. +// This DLL's own `malloc` need not be that heap - a debug CRT keeps its blocks per copy, and against +// the prebuilt LLVM, LLVMSupport makes it rpmalloc. +#ifdef _WIN32 +static void *heapMalloc(size_t size) { return typescript::process_heap::allocate(size); } -static void heapFree(void *ptr) -{ - static auto function = crtFunction("free", &free); - function(ptr); -} +static void heapFree(void *ptr) { typescript::process_heap::release(ptr); } #else static void *heapMalloc(size_t size) { return malloc(size); } diff --git a/tslang/lib/TypeScriptRuntime/TypeScriptRuntime.def b/tslang/lib/TypeScriptRuntime/TypeScriptRuntime.def index 0f60d133e..930b76985 100644 --- a/tslang/lib/TypeScriptRuntime/TypeScriptRuntime.def +++ b/tslang/lib/TypeScriptRuntime/TypeScriptRuntime.def @@ -35,6 +35,13 @@ EXPORTS aligned_alloc=AlignedAlloc aligned_free=AlignedFree + ; --- the process-heap allocator (ProcessHeapExports.inc) a JIT'd program calls on Windows --- + __tslang_heap_malloc + __tslang_heap_calloc + __tslang_heap_realloc + __tslang_heap_free + __tslang_heap_aligned_alloc + ; --- dynamic-library runtime (DynamicRuntime.cpp, extern "C", names already match) --- tslang_load_library_permanently tslang_search_for_address_of_symbol diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 19e138e33..0a51bce65 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -1335,6 +1335,14 @@ tslang_add_test(NAME test-compile-declare-linkname COMMAND test-runner "${PROJEC tslang_add_test(NAME test-jit-declare-linkname COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/declare_linkname.ts") tslang_add_test(NAME test-compile-declare-linkname-shared-symbol COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/declare_linkname_shared_symbol.ts") tslang_add_test(NAME test-jit-declare-linkname-shared-symbol COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/declare_linkname_shared_symbol.ts") +# `@dllname("strlen") @linkname("strlen")` names the attribute once: two entries of one name are an +# assert in a Debug build ("DictionaryAttr element names must be unique") and print twice in Release. +add_test(NAME test-declare-linkname-attribute-once + COMMAND $ --emit=mlir --no-default-lib + "${PROJECT_SOURCE_DIR}/test/tester/tests/declare_linkname_shared_symbol.ts") +set_tests_properties(test-declare-linkname-attribute-once + PROPERTIES PASS_REGULAR_EXPRESSION "dllname = \"strlen\"" + FAIL_REGULAR_EXPRESSION "dllname = \"strlen\", dllname|Assertion failed|Stack dump") # DISABLED: importing a module's `export declare function` bound to a C runtime function crashes, # and did before @linkname (the same with @dllname on a 2026-09-23 build). The importer re-prints # the declaration as `@dllimport @dllname("strlen")` and looks `strlen` up at startup in the loaded diff --git a/tslang/tslang/jit.cpp b/tslang/tslang/jit.cpp index 9493e3184..98af51789 100644 --- a/tslang/tslang/jit.cpp +++ b/tslang/tslang/jit.cpp @@ -349,27 +349,32 @@ static void jitAssertFailed(const char *message, const char *file, unsigned line } #ifdef _WIN32 -// The allocator JIT'd code gets, as `malloc`, `calloc`, `realloc` and `free`. A block it allocates -// can be freed by another module and the other way round - an object of a library's class under -// -mm=rc or -mm=own is made in the library and destroyed by the program - so it has to be the -// allocator those modules use. Every one of them links the static release CRT, whose heap is the -// process heap: a library tslang builds, TypeScriptRuntime.dll. tslang.exe's own `malloc` need not -// be: the prebuilt LLVM brings rpmalloc as the process malloc of whatever links LLVMSupport (see -// scripts/llvm_prebuilt_common.ps1), and a block of one freed by the other faults. ucrtbase.dll's -// are the release CRT's, on the process heap. -// -// Not in a debug build: a debug CRT puts a header of its own in front of every block, and the -// libraries a debug tslang builds link the debug CRT too. -static void *jitHeapFunction(const char *name, void *ownFunction) +// The allocator JIT'd code gets. A block it allocates can be freed by another module and the other +// way round - an object of a library's class under -mm=rc or -mm=own is made in the library and +// destroyed by the program, a coroutine frame comes from TypeScriptRuntime.dll - so it is the one +// every module tslang builds uses: the process heap (TypeScript/ProcessHeap.h). The program calls +// it by the names ProcessHeapPass gives it; `malloc`, `calloc`, `realloc` and `free` are bound to +// it as well, for whatever still calls those. tslang.exe's own would not do: with the prebuilt +// LLVM its `malloc` is rpmalloc, and a debug CRT keeps its blocks per copy of the CRT. +#include "../lib/ProcessHeapExports.inc" + +struct JitHeapFunction { -#ifdef _DEBUG - return ownFunction; -#else - static auto ucrt = LoadLibraryW(L"ucrtbase.dll"); - auto function = ucrt ? reinterpret_cast(GetProcAddress(ucrt, name)) : nullptr; - return function ? function : ownFunction; -#endif -} + const char *name; + void *address; +}; + +static const JitHeapFunction jitHeapFunctions[] = { + {"malloc", (void *)&__tslang_heap_malloc}, + {"calloc", (void *)&__tslang_heap_calloc}, + {"realloc", (void *)&__tslang_heap_realloc}, + {"free", (void *)&__tslang_heap_free}, + {PROCESS_HEAP_MALLOC, (void *)&__tslang_heap_malloc}, + {PROCESS_HEAP_CALLOC, (void *)&__tslang_heap_calloc}, + {PROCESS_HEAP_REALLOC, (void *)&__tslang_heap_realloc}, + {PROCESS_HEAP_FREE, (void *)&__tslang_heap_free}, + {PROCESS_HEAP_ALIGNED_ALLOC, (void *)&__tslang_heap_aligned_alloc}, +}; #endif #ifndef _WIN32 @@ -699,10 +704,10 @@ static int prepareJitProcess(CompileOptions &compileOptions) }; addSym("puts", (void*)&puts); addSym("printf", (void*)&printf); - addSym("malloc", jitHeapFunction("malloc", (void *)&malloc)); - addSym("free", jitHeapFunction("free", (void *)&free)); - addSym("realloc", jitHeapFunction("realloc", (void *)&realloc)); - addSym("calloc", jitHeapFunction("calloc", (void *)&calloc)); + for (auto &function : jitHeapFunctions) + { + addSym(function.name, function.address); + } addSym("memset", (void*)&memset); addSym("memcpy", (void*)&memcpy); addSym("fflush", (void*)&fflush); @@ -859,10 +864,10 @@ static std::unique_ptr createJit(llvm::orc::JITTargetMachineBu }; addOverride("puts", (void *)&puts); addOverride("printf", (void *)&printf); - addOverride("malloc", jitHeapFunction("malloc", (void *)&malloc)); - addOverride("free", jitHeapFunction("free", (void *)&free)); - addOverride("realloc", jitHeapFunction("realloc", (void *)&realloc)); - addOverride("calloc", jitHeapFunction("calloc", (void *)&calloc)); + for (auto &function : jitHeapFunctions) + { + addOverride(function.name, function.address); + } addOverride("memset", (void *)&memset); addOverride("memcpy", (void *)&memcpy); addOverride("fflush", (void *)&fflush); diff --git a/tslang/tslang/transform.cpp b/tslang/tslang/transform.cpp index 9fd3edbf7..abf090e56 100644 --- a/tslang/tslang/transform.cpp +++ b/tslang/tslang/transform.cpp @@ -202,6 +202,11 @@ int runMLIRPasses(mlir::MLIRContext &context, llvm::SourceMgr &sourceMgr, mlir:: { pm.addPass(mlir::typescript::createGCPass(compileOptions)); } + else if (compileOptions.isWindows && !compileOptions.isWasm) + { + // one allocator for every module on Windows - see TypeScript/ProcessHeap.h + pm.addPass(mlir::typescript::createProcessHeapPass()); + } } auto result = 0;