Skip to content

Commit ee08124

Browse files
A global initialised with a constant string concatenation runs as a constructor (#469) (#473)
`let S = "text" + 4;` at module level, read by a function, failed to compile in every model: "ops with side effects not allowed in global initializers". GlobalOpLowering puts an initializer that looks constant into the llvm.mlir.global's own region and anything that runs code into a per-global constructor. Its list knew ts.StringConcat, but at that point the concatenation is still a ts.ArithmeticBinary with a string result, and the number's conversion (`ts.Cast` si32 -> string, a sprintf into a new block) was not on the list either, so both landed in the region, where allocating is not allowed. With a variable or a call among the operands it was already a constructor, and a global nothing reads was dropped before it mattered. Now a string-typed ts.ArithmeticBinary, and a cast to string from anything that is not already a string, make the global a constructor. A plain string constant stays in place. Test: 00global_constant_concat (compile and JIT, and in the rc/none corpus): `"text" + 4`, `"text" + "four"`, a `string | null` and `"" + 6.5` globals read in main, a plain string constant, and a reassignment. It failed to compile before the change. Fixes #469. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 239c004 commit ee08124

3 files changed

Lines changed: 43 additions & 0 deletions

File tree

‎tslang/lib/TypeScript/LowerToLLVM.cpp‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4418,13 +4418,31 @@ struct GlobalOpLowering : public TsLlvmPattern<mlir_ts::GlobalOp>
44184418
{
44194419
createAsGlobalConstructor = true;
44204420
}
4421+
else if (auto binaryOp = dyn_cast<mlir_ts::ArithmeticBinaryOp>(op);
4422+
binaryOp && isa<mlir_ts::StringType>(binaryOp.getResult().getType()))
4423+
{
4424+
// `"text" + 4` is a string concatenation, which allocates, even when every operand
4425+
// is a constant (#469)
4426+
createAsGlobalConstructor = true;
4427+
}
44214428
else if (auto castOp = dyn_cast<mlir_ts::CastOp>(op))
44224429
{
44234430
auto castType = castOp.getRes().getType();
4431+
auto inType = castOp.getIn().getType();
4432+
if (auto literalType = dyn_cast<mlir_ts::LiteralType>(inType))
4433+
{
4434+
inType = literalType.getElementType();
4435+
}
4436+
44244437
if (isa<mlir_ts::ArrayType>(castType) || isa<mlir_ts::TupleType>(castType))
44254438
{
44264439
createAsGlobalConstructor = true;
44274440
}
4441+
else if (isa<mlir_ts::StringType>(castType) && !isa<mlir_ts::StringType>(inType))
4442+
{
4443+
// a number or a boolean made into a string allocates it (#469)
4444+
createAsGlobalConstructor = true;
4445+
}
44284446
else
44294447
{
44304448
// see CreateUnionInstanceOp above: a cast to or from a tagged union lowers to

‎tslang/test/tester/CMakeLists.txt‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ tslang_add_test(NAME test-compile-00-void COMMAND test-runner "${PROJECT_SOURCE_
467467
tslang_add_test(NAME test-compile-00-in COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00in.ts")
468468
tslang_add_test(NAME test-compile-00-in-method-names COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00in_method_names.ts")
469469
tslang_add_test(NAME test-compile-00-class-structural-extends COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_structural_extends.ts")
470+
tslang_add_test(NAME test-compile-00-global-constant-concat COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_constant_concat.ts")
470471
tslang_add_test(NAME test-compile-00-global-object-literal COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_object_literal.ts")
471472
tslang_add_test(NAME test-compile-00-instanceof COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00instanceof.ts")
472473
tslang_add_test(NAME test-compile-00-const-of-let COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_of_let.ts")
@@ -956,6 +957,7 @@ tslang_add_test(NAME test-jit-00-void COMMAND test-runner -jit "${PROJECT_SOURCE
956957
tslang_add_test(NAME test-jit-00-in COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00in.ts")
957958
tslang_add_test(NAME test-jit-00-in-method-names COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00in_method_names.ts")
958959
tslang_add_test(NAME test-jit-00-class-structural-extends COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_structural_extends.ts")
960+
tslang_add_test(NAME test-jit-00-global-constant-concat COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_constant_concat.ts")
959961
tslang_add_test(NAME test-jit-00-global-object-literal COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_object_literal.ts")
960962
tslang_add_test(NAME test-jit-00-instanceof COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00instanceof.ts")
961963
tslang_add_test(NAME test-jit-00-const-of-let COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_of_let.ts")
@@ -1780,6 +1782,7 @@ set(TSLANG_CORPUS
17801782
00infer.ts
17811783
00global_union_from_call.ts
17821784
00instanceof.ts
1785+
00global_constant_concat.ts
17831786
00const_of_let.ts
17841787
00instanceof_namespace_narrowing.ts
17851788
00instanceof_union.ts
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// A module-level variable initialised with a string concatenation of constants, read by a function
2+
// (#469). The initializer looked constant, so it was placed where nothing may run, and the
3+
// concatenation (and the number's conversion to a string) failed to compile there: "ops with side
4+
// effects not allowed in global initializers".
5+
let S = "text" + 4;
6+
const C = "text" + "four";
7+
let U: string | null = "nullable" + 5;
8+
let N = "" + 6.5;
9+
let P = "plain";
10+
11+
function main() {
12+
assert(S == "text4", "a let from a constant concatenation");
13+
assert(C == "textfour", "a const from two string constants");
14+
assert(U == "nullable5", "a nullable string from a constant concatenation");
15+
assert(N == "6.5", "a number converted to a string");
16+
assert(P == "plain", "a plain string constant stays as it is");
17+
18+
S = S + "!";
19+
assert(S == "text4!");
20+
21+
print("done.");
22+
}

0 commit comments

Comments
 (0)