From 50f731a6b32c6999b215a36027af71aba7407ce9 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Mon, 5 Oct 2026 00:56:16 +0100 Subject: [PATCH] for await runs its body one iteration at a time (#498) The body of a `for await` loop ran as concurrent tasks: mlirGen (ForStatement) wrapped each iteration's body in an async.execute, added its token to an async.group made before the loop, and waited for the whole group once, with async.await_all, after the loop. The bodies ran together on the async runtime's threads: iterations interleaved (`<7<8><9>`), and writes were lost (`keep.push` from one iteration, `log += ...` from another), under gc, rc and none; one run printed nothing at all. Each iteration now awaits its body's token before going on, as `await` does since #440, so the next value is asked for only once the body is done, as in TypeScript. The group and the await_all are gone. The body stays an async.execute: an `await` in it needs one. 00for_await_sequential.ts checks the bodies' writes and awaits run in order over eight iterations; on main it failed in every variant, three runs out of three. Closes #498 Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenStatements.cpp | 38 ++----------------- tslang/test/tester/CMakeLists.txt | 3 ++ .../tester/tests/00for_await_sequential.ts | 30 +++++++++++++++ 3 files changed, 37 insertions(+), 34 deletions(-) create mode 100644 tslang/test/tester/tests/00for_await_sequential.ts diff --git a/tslang/lib/TypeScript/MLIRGenStatements.cpp b/tslang/lib/TypeScript/MLIRGenStatements.cpp index 4c9164b33..d2a156754 100644 --- a/tslang/lib/TypeScript/MLIRGenStatements.cpp +++ b/tslang/lib/TypeScript/MLIRGenStatements.cpp @@ -815,17 +815,6 @@ namespace mlirgen SmallVector types; SmallVector operands; - mlir::Value asyncGroupResult; - if (hasAwait) - { - auto groupType = mlir::async::GroupType::get(builder.getContext()); - auto blockSize = builder.create(location, builder.getIndexAttr(0)); - auto asyncGroupOp = builder.create(location, groupType, blockSize); - asyncGroupResult = asyncGroupOp.getResult(); - // operands.push_back(asyncGroupOp); - // types.push_back(groupType); - } - auto forOp = builder.create(location, types, operands); if (!label.empty()) { @@ -908,10 +897,10 @@ namespace mlirgen return mlir::failure(); } - // add to group - auto rankType = mlir::IndexType::get(builder.getContext()); - // TODO: should i replace with value from arg0? - builder.create(location, rankType, asyncExecOp.getToken(), asyncGroupResult); + // 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 { @@ -929,25 +918,6 @@ namespace mlirgen builder.setInsertionPointAfter(forOp); - if (hasAwait) - { - // Not helping - /* - // async await all, see convert-to-llvm.mlir - auto asyncExecAwaitAllOp = - builder.create(location, mlir::TypeRange{}, mlir::ValueRange{}, - mlir::ValueRange{}, - [&](mlir::OpBuilder &builder, mlir::Location location, - mlir::ValueRange values) { builder.create(location, asyncGroupResult); - builder.create(location, - mlir::ValueRange{}); - }); - */ - - // Wait for the completion of all subtasks. - builder.create(location, asyncGroupResult); - } - return mlir::success(); } diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 4c589daf7..11cf1224b 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -352,6 +352,7 @@ tslang_add_test(NAME test-compile-00-tuple-runtime-arrays COMMAND test-runner "$ tslang_add_test(NAME test-compile-00-record-to-class COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00record_to_class.ts") 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-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") @@ -861,6 +862,7 @@ tslang_add_test(NAME test-jit-00-tuple-runtime-arrays COMMAND test-runner -jit " tslang_add_test(NAME test-jit-00-record-to-class COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00record_to_class.ts") 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-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") @@ -1699,6 +1701,7 @@ set(TSLANG_CORPUS 00record_to_class.ts 00array_insert_functions.ts 00await_void.ts + 00for_await_sequential.ts 00const_record_owned_fields.ts 00array_length_valid.ts 00string_view_copy.ts diff --git a/tslang/test/tester/tests/00for_await_sequential.ts b/tslang/test/tester/tests/00for_await_sequential.ts new file mode 100644 index 000000000..a1ac2f164 --- /dev/null +++ b/tslang/test/tester/tests/00for_await_sequential.ts @@ -0,0 +1,30 @@ +// The body of a `for await` runs one iteration at a time, in order (#498): each body was a task +// of its own, all awaited together after the loop, so iterations interleaved and lost each +// other's writes. +let log = ""; +let keep: number[] = []; +async function* numbers() { + for (let i = 1; i <= 8; i++) { + yield i; + } +} +async function note(s: string) { + log += s; +} +async function main() { + for await (const x of numbers()) { + log += "<" + x; + keep.push(x); + log += ">"; + } + assert(log == "<1><2><3><4><5><6><7><8>", "each body whole, in order"); + assert(keep.length == 8, "every push kept"); + + log = ""; + for await (const x of numbers()) { + await note("[" + x); + await note("]"); + } + assert(log == "[1][2][3][4][5][6][7][8]", "awaits in the body, in order"); + print("done."); +}