From 9fba0b514f2823e8b2cc1fe9a198bb46907b9b13 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Mon, 5 Oct 2026 09:15:26 +0100 Subject: [PATCH] break and continue in a for await body, and nested for await (#502) `break` or `continue` in the body of a `for await`, and a `for await` nested in another, crashed the compiler (0xC0000005, no message), under every memory model. mlirGen(ForStatement) generated a `for await` body inside an async.execute region of its own (awaited per iteration since #498): a ts.Break / ts.Continue in it had no loop in its region, which the lowering crashed on, and the nested loop crashed MLIR generation. The body is now generated in the loop as any other loop's is. The region was there only so that an `await` in the body had an async region, and an `await` makes its own async.execute and waits for it wherever it is (as in a plain for...of in an async function), so the body still runs one iteration at a time, in order. The ForAwait flag no longer changes the loop's code. 00for_await_control.ts: break, continue, a nested for await, and awaits with continue and break in one body, under every model; on main each crashed the compiler. A for await at module level runs as well. Closes #502 Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenStatements.cpp | 65 ++----------------- tslang/test/tester/CMakeLists.txt | 3 + .../test/tester/tests/00for_await_control.ts | 50 ++++++++++++++ 3 files changed, 59 insertions(+), 59 deletions(-) create mode 100644 tslang/test/tester/tests/00for_await_control.ts diff --git a/tslang/lib/TypeScript/MLIRGenStatements.cpp b/tslang/lib/TypeScript/MLIRGenStatements.cpp index d2a156754..38d6de5c7 100644 --- a/tslang/lib/TypeScript/MLIRGenStatements.cpp +++ b/tslang/lib/TypeScript/MLIRGenStatements.cpp @@ -788,8 +788,6 @@ namespace mlirgen auto location = loc(forStatementAST); - auto hasAwait = InternalFlags::ForAwait == (forStatementAST->internalFlags & InternalFlags::ForAwait); - // initializer // TODO: why do we have ForInitialier if (isExpression(forStatementAST->initializer)) @@ -851,63 +849,12 @@ namespace mlirgen // body builder.setInsertionPointToStart(&forOp.getBody().front()); - if (hasAwait) - { - if (forStatementAST->statement == SyntaxKind::Block) - { - auto firstStatement = forStatementAST->statement.as()->statements.front(); - auto result = mlirGen(firstStatement, loopGenContext); - EXIT_IF_FAILED(result) - } - - // TODO: we need to strip metadata to fix issue with debug info - // async body - auto isFailed = false; - auto asyncExecOp = builder.create( - stripMetadata(location), mlir::TypeRange{}, mlir::ValueRange{}, mlir::ValueRange{}, - [&](mlir::OpBuilder &builder, mlir::Location location, mlir::ValueRange values) { - GenContext execOpBodyGenContext(loopGenContext); - DITableScopeT debugAsyncCodeScope(debugScope); - MLIRDebugInfoHelper mdi(builder, debugScope); - - // TODO: temp hack to break wrong chain on scopes because 'await' create extra function wrap - mdi.clearDebugScope(); - mdi.setLexicalBlock(location); - - if (forStatementAST->statement == SyntaxKind::Block) - { - if (mlir::failed(mlirGen(forStatementAST->statement.as(), execOpBodyGenContext, 1))) - { - isFailed = true; - } - } - else - { - if (mlir::failed(mlirGen(forStatementAST->statement, execOpBodyGenContext))) - { - isFailed = true; - } - } - - builder.create(location, mlir::ValueRange{}); - }); - - if (isFailed) - { - return mlir::failure(); - } - - // One iteration at a time, as `for await` runs its body: the next value is asked for only - // once this body is done. Each body was added to a group awaited after the loop, so the - // bodies ran as concurrent tasks - interleaved, losing each other's writes (#498). - builder.create(location, asyncExecOp.getToken()); - } - else - { - // default - auto result = mlirGen(forStatementAST->statement, loopGenContext); - EXIT_IF_FAILED(result) - } + // A `for await` body is generated in the loop as any other: an `await` in it makes its own + // async.execute and waits for it, so one iteration runs at a time (#498). Generated inside an + // async.execute of its own, a `break` or `continue` in it had no loop in its region, and a + // nested `for await` crashed the compiler (#502). + auto bodyResult = mlirGen(forStatementAST->statement, loopGenContext); + EXIT_IF_FAILED(bodyResult) builder.create(location); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 1af2da335..10b0fa007 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -354,6 +354,7 @@ tslang_add_test(NAME test-compile-00-record-variable-to-class COMMAND test-runne tslang_add_test(NAME test-compile-00-array-insert-functions COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_insert_functions.ts") tslang_add_test(NAME test-compile-00-await-void COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00await_void.ts") tslang_add_test(NAME test-compile-00-for-await-sequential COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_sequential.ts") +tslang_add_test(NAME test-compile-00-for-await-control COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_control.ts") tslang_add_test(NAME test-compile-00-const-record-owned-fields COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_record_owned_fields.ts") tslang_add_test(NAME test-compile-00-array-length-valid COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts") tslang_add_test(NAME test-compile-00-string-view-copy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_view_copy.ts") @@ -865,6 +866,7 @@ tslang_add_test(NAME test-jit-00-record-variable-to-class COMMAND test-runner -j tslang_add_test(NAME test-jit-00-array-insert-functions COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_insert_functions.ts") tslang_add_test(NAME test-jit-00-await-void COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00await_void.ts") tslang_add_test(NAME test-jit-00-for-await-sequential COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_sequential.ts") +tslang_add_test(NAME test-jit-00-for-await-control COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_control.ts") tslang_add_test(NAME test-jit-00-const-record-owned-fields COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_record_owned_fields.ts") tslang_add_test(NAME test-jit-00-array-length-valid COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts") tslang_add_test(NAME test-jit-00-string-view-copy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_view_copy.ts") @@ -1705,6 +1707,7 @@ set(TSLANG_CORPUS 00array_insert_functions.ts 00await_void.ts 00for_await_sequential.ts + 00for_await_control.ts 00const_record_owned_fields.ts 00array_length_valid.ts 00string_view_copy.ts diff --git a/tslang/test/tester/tests/00for_await_control.ts b/tslang/test/tester/tests/00for_await_control.ts new file mode 100644 index 000000000..f1e0600e2 --- /dev/null +++ b/tslang/test/tester/tests/00for_await_control.ts @@ -0,0 +1,50 @@ +// `break` and `continue` in a `for await` body, and a `for await` nested in another (#502): the +// body was generated in an async.execute region of its own, where a `break` had no loop to leave, +// and the compiler crashed. +let log = ""; +async function* numbers() { + yield 1; + yield 2; + yield 3; + yield 4; +} +async function* letters() { + yield "a"; + yield "b"; +} +async function note(s: string) { + log += s; +} +async function main() { + for await (const x of numbers()) { + if (x == 3) break; + log += x; + } + assert(log == "12", "break"); + + log = ""; + for await (const x of numbers()) { + if (x == 2) continue; + log += x; + } + assert(log == "134", "continue"); + + log = ""; + for await (const x of numbers()) { + for await (const y of letters()) { + log += x + y; + } + } + assert(log == "1a1b2a2b3a3b4a4b", "nested"); + + log = ""; + for await (const x of numbers()) { + await note("<" + x); + if (x == 2) continue; + if (x == 4) break; + await note(">"); + } + assert(log == "<1><2<3><4", "awaits, continue and break"); + + print("done."); +}