Skip to content

Commit 2c6594e

Browse files
The JIT cache no longer crashes a run that compiles two or more modules (#549)
The JIT cache compiles each module in an MLIRContext of its own, but the built-in types (i32, f32, Opaque, ...) were kept in a function-local static map, made once in the first module's context. Every later module in the run got types from a context already destroyed: 0xC0000005 in the compiler. The default library's declarations name such types, so every program importing a .ts module crashed (native_ts_graphics finding 7). The map is now a member of MLIRGenImpl, made on first use per module. Regression test test-jit-cache-embedded-types runs from an empty cache every time (jit-cache-empty.cmake); a test-runner test keeps its cache beside the source and compiles nothing from its second run on. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent ecea3f4 commit 2c6594e

7 files changed

Lines changed: 81 additions & 7 deletions

File tree

‎docs/bugs/native-ts-graphics-findings.md‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ keeps failing by design. The repros stay here as the record of what was reported
2929
| 4 | Escapes not processed in templates with substitutions | Wrong value, silent | `ntg_template_literal_escape.ts` | [#539](https://github.com/ASDAlexander77/TypeScriptCompiler/pull/539) |
3030
| 5 | `Math.max` of negative numbers returns 2.2e-308 | Wrong value, silent (default lib) | `ntg_math_max_negative.ts` | [DefaultLib#31](https://github.com/ASDAlexander77/TypeScriptCompilerDefaultLib/pull/31), and [#535](https://github.com/ASDAlexander77/TypeScriptCompiler/pull/535) for why its test passed |
3131
| 6 | `boolean && (Class \| null)` as a condition fails to compile | Compile error | `ntg_and_nullable_class.ts` | [#540](https://github.com/ASDAlexander77/TypeScriptCompiler/pull/540), and [#541](https://github.com/ASDAlexander77/TypeScriptCompiler/pull/541) for its misplaced first error |
32-
| 7 | The JIT cache crashes a run that compiles two or more modules (pre-alpha93) | Compiler crash, no message | `ntg_jit_cache_import.ts` | open |
32+
| 7 | The JIT cache crashes a run that compiles two or more modules (pre-alpha93) | Compiler crash, no message | `ntg_jit_cache_import.ts` | [#549](https://github.com/ASDAlexander77/TypeScriptCompiler/pull/549) |
3333

3434
The silent ones (2 to 5) cost the most time: the programs compile and run, and only the output
3535
is wrong.
@@ -226,6 +226,14 @@ per run) every run works.
226226
imports `core/donut.ts`, so every example crashed under the JIT twice before running.
227227
`run_jit.bat` passes `--jit-cache=false`.
228228
229+
**Fixed by #549:** the cache compiles each module in an MLIRContext of its own, and the built-in
230+
types (`i32`, `f32`, `Opaque`, ...) were kept in a `static` map, made once, in the first module's
231+
context. Every later module in the run got types from a context destroyed by then. The map is
232+
now per module. The default library's declarations name such types, which is why every program
233+
importing a module crashed; without it (the suite runs with `--no-default-lib`) a module only
234+
crashed if it named one itself. Regression test: `test-jit-cache-embedded-types`, which runs from
235+
an empty cache every time.
236+
229237
---
230238
231239
## Seen earlier but not reproduced in minimal form

‎tslang/lib/TypeScript/MLIRGenImpl.h‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14055,6 +14055,11 @@ class MLIRGenImpl
1405514055
// read of it loads the field again and casts it, so no value outlives the region it is in
1405614056
llvm::ScopedHashTable<SafeTypeKeyType, mlir::Type> safeTypesMap;
1405714057

14058+
// the built-in types by name (getEmbeddedType), made on first use; not static: they belong to
14059+
// this module's MLIRContext, and one run can compile several modules, each in a context of its
14060+
// own (the JIT cache) - a type kept from an earlier one points into freed memory
14061+
llvm::StringMap<mlir::Type> embeddedTypeMap;
14062+
1405814063
// helper to get line number
1405914064
Parser parser;
1406014065
ts::SourceFile sourceFile;

‎tslang/lib/TypeScript/MLIRGenTypes.cpp‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,7 +1025,7 @@ namespace mlirgen
10251025

10261026
mlir::Type MLIRGenImpl::getEmbeddedTypeBuiltins(mlir::StringRef name)
10271027
{
1028-
static llvm::StringMap<mlir::Type> embeddedTypes {
1028+
if (embeddedTypeMap.empty()) embeddedTypeMap = {
10291029
{"TemplateStringsArray", getArrayType(getStringType()) },
10301030
{"const",getConstType() },
10311031
#ifdef ENABLE_JS_BUILTIN_TYPES
@@ -1082,13 +1082,12 @@ namespace mlirgen
10821082
{"Opaque", getOpaqueType()},
10831083
};
10841084

1085-
auto type = embeddedTypes[name];
1086-
return type;
1085+
return embeddedTypeMap.lookup(name);
10871086
}
10881087

10891088
mlir::Type MLIRGenImpl::getEmbeddedTypeNoBuiltins(mlir::StringRef name)
10901089
{
1091-
static llvm::StringMap<mlir::Type> embeddedTypes {
1090+
if (embeddedTypeMap.empty()) embeddedTypeMap = {
10921091
{"TemplateStringsArray", getArrayType(getStringType()) },
10931092
{"const",getConstType() },
10941093
#ifdef ENABLE_JS_BUILTIN_TYPES
@@ -1146,8 +1145,7 @@ namespace mlirgen
11461145
{"Opaque", getOpaqueType()},
11471146
};
11481147

1149-
auto type = embeddedTypes[name];
1150-
return type;
1148+
return embeddedTypeMap.lookup(name);
11511149
}
11521150

11531151
mlir::Type MLIRGenImpl::getEmbeddedTypeWithParam(mlir::StringRef name, NodeArray<TypeNode> &typeArguments,

‎tslang/test/tester/CMakeLists.txt‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3252,6 +3252,15 @@ add_test(NAME test-jit-cache-reuse-and-invalidate
32523252
"-DSOURCE_DIR=${jit_cache_dir}"
32533253
"-DWORK_DIR=${CMAKE_CURRENT_BINARY_DIR}/jit-cache-reuse"
32543254
-P "${CMAKE_CURRENT_SOURCE_DIR}/jit-cache.cmake")
3255+
# Two modules compiled in one run, both naming built-in types (i32, f32): the second one used to
3256+
# get the types made in the first one's MLIRContext, destroyed by then, and crash the compiler.
3257+
add_test(NAME test-jit-cache-embedded-types
3258+
COMMAND ${CMAKE_COMMAND}
3259+
"-DTSLANG=$<TARGET_FILE:tslang>"
3260+
"-DSOURCE=${jit_cache_dir}/embedded_types.ts"
3261+
"-DWORK_DIR=${CMAKE_CURRENT_BINARY_DIR}/jit-cache-embedded-types"
3262+
"-DEXPECTED=done."
3263+
-P "${CMAKE_CURRENT_SOURCE_DIR}/jit-cache-empty.cmake")
32553264

32563265
# `import { a as b }` and `import * as M` - each used to be "can't resolve name".
32573266
set(import_bindings_dir "${PROJECT_SOURCE_DIR}/test/tester/import-bindings")
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Runs a program under the JIT cache from an empty cache, so the run compiles the program and every
2+
# module it imports - several modules in one process, each in an MLIRContext of its own. (A test
3+
# run by test-runner keeps its cache in __jit beside the source: from the second run on, it
4+
# compiles nothing.)
5+
#
6+
# TSLANG, SOURCE (the program), WORK_DIR (the cache, emptied first), EXPECTED (its output).
7+
# -mm=none and --no-default-lib keep the run to the compiler alone.
8+
9+
cmake_minimum_required(VERSION 3.17.3)
10+
11+
foreach(var TSLANG SOURCE WORK_DIR EXPECTED)
12+
if(NOT DEFINED ${var})
13+
message(FATAL_ERROR "${var} is required")
14+
endif()
15+
endforeach()
16+
17+
file(REMOVE_RECURSE "${WORK_DIR}")
18+
file(MAKE_DIRECTORY "${WORK_DIR}")
19+
20+
execute_process(COMMAND "${TSLANG}" --emit=jit -mm=none --no-default-lib "--jit-cache-dir=${WORK_DIR}" "${SOURCE}"
21+
OUTPUT_VARIABLE out
22+
ERROR_VARIABLE err
23+
RESULT_VARIABLE status)
24+
string(STRIP "${out}" out)
25+
if(NOT status EQUAL 0 OR NOT out STREQUAL EXPECTED)
26+
message(FATAL_ERROR "expected '${EXPECTED}': exit ${status}\n${out}\n${err}")
27+
endif()
28+
29+
file(GLOB objects "${WORK_DIR}/*.o" "${WORK_DIR}/*.obj")
30+
list(LENGTH objects count)
31+
if(count LESS 2)
32+
message(FATAL_ERROR "expected an object for the program and one for each module it imports, found: ${objects}")
33+
endif()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Each module the JIT cache compiles has an MLIRContext of its own. The built-in types (i32,
2+
// f32, Opaque, ...) were kept in a static map, built in the first module's context: the next
3+
// module got types from a context that no longer existed, and the compiler crashed (0xC0000005).
4+
import { widen, half } from './embedded_types_module'
5+
6+
const a: i32 = 21;
7+
const b: i64 = widen(a) * 2;
8+
assert(b == 42);
9+
10+
const c: f32 = 5;
11+
assert(half(c) == 2.5);
12+
13+
print("done.");
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Imported by embedded_types.ts (see there).
2+
export function widen(x: i32): i64 {
3+
return x;
4+
}
5+
6+
export function half(x: f32): f32 {
7+
return x / 2;
8+
}

0 commit comments

Comments
 (0)