diff --git a/tslang/lib/TypeScript/MLIRGenExpressions.cpp b/tslang/lib/TypeScript/MLIRGenExpressions.cpp index 1dd87fd72..43a66f5a0 100644 --- a/tslang/lib/TypeScript/MLIRGenExpressions.cpp +++ b/tslang/lib/TypeScript/MLIRGenExpressions.cpp @@ -346,13 +346,29 @@ namespace mlirgen ValueOrLogicalResult MLIRGenImpl::mlirGen(YieldExpression yieldExpressionAST, const GenContext &genContext) { + auto location = loc(yieldExpressionAST); + + // see SupportedYieldScope + auto supportedYield = supportedYields.find(yieldExpressionAST.operator->()); + if (supportedYield == supportedYields.end()) + { + emitError(location, "a yield inside an expression is not supported: the expression's other operands " + "do not survive the generator resuming; assign the yield to a variable first"); + return mlir::failure(); + } + if (yieldExpressionAST->asteriskToken) { + // it gives back nothing yet: it can only stand as a statement of its own + if (supportedYield->second) + { + emitError(location, "the value of yield* is not supported"); + return mlir::failure(); + } + return mlirGenYieldStar(yieldExpressionAST, genContext); } - auto location = loc(yieldExpressionAST); - if (genContext.passResult) { genContext.passResult->functionReturnTypeShouldBeProvided = true; @@ -404,9 +420,9 @@ namespace mlirgen label << GENERATOR_STATELABELPREFIX << state; builder.create(location, label.str()); - // TODO: yield value to continue, should be loaded from "next(value)" parameter - // return yieldValue; - return mlir::success(); + // TODO: the value passed to next(value); next() takes no argument yet, so a yield evaluates to undefined. + // It is made after the label: the turn that resumes here is the one that uses it. + return V(builder.create(location, getUndefinedType())); } ValueOrLogicalResult MLIRGenImpl::mlirGen(AwaitExpression awaitExpressionAST, const GenContext &genContext) diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index ee0cea73b..202eb7178 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -3613,6 +3613,41 @@ class MLIRGenImpl return mlirGen(forOfStat, genContext); } + // A generator resumes at the label after its yield, so whatever an expression computed before + // the yield is gone when it continues: it was an SSA value of the turn that returned. A yield + // can only stand where nothing is computed before it - `yield x;`, `const v = yield x;` and + // `v = yield x;` - and the statement marks it so while it is generated. + struct SupportedYieldScope + { + SupportedYieldScope(MLIRGenImpl &gen) : gen(gen) {} + + ~SupportedYieldScope() + { + for (auto node : marked) + { + gen.supportedYields.erase(node); + } + } + + void mark(Expression expression, bool valueUsed) + { + while (expression == SyntaxKind::ParenthesizedExpression) + { + expression = expression.as()->expression; + } + + if (expression == SyntaxKind::YieldExpression) + { + const void *node = expression.operator->(); + gen.supportedYields[node] = valueUsed; + marked.push_back(node); + } + } + + MLIRGenImpl &gen; + SmallVector marked; + }; + ValueOrLogicalResult mlirGen(YieldExpression yieldExpressionAST, const GenContext &genContext); ValueOrLogicalResult mlirGen(AwaitExpression awaitExpressionAST, const GenContext &genContext); @@ -14846,6 +14881,11 @@ class MLIRGenImpl // can't leave a function. SmallVector activeLabels; + // The yields standing where nothing is computed before them, marked by the statement around + // them (the AST has no parent links to look up): yield node -> its value is used. See + // SupportedYieldScope. + llvm::DenseMap supportedYields; + private: std::string label; // `a: b: for (...)`: the outer labels of a chain that ends in a loop, each mapped to the innermost diff --git a/tslang/lib/TypeScript/MLIRGenStatements.cpp b/tslang/lib/TypeScript/MLIRGenStatements.cpp index dcbfbf9aa..9a43d5c4f 100644 --- a/tslang/lib/TypeScript/MLIRGenStatements.cpp +++ b/tslang/lib/TypeScript/MLIRGenStatements.cpp @@ -439,6 +439,25 @@ namespace mlirgen mlir::LogicalResult MLIRGenImpl::mlirGen(ExpressionStatement expressionStatementAST, const GenContext &genContext) { + // `yield x;` and `v = yield x;` + SupportedYieldScope supportedYieldScope(*this); + auto expression = expressionStatementAST->expression; + supportedYieldScope.mark(expression, false); + while (expression == SyntaxKind::ParenthesizedExpression) + { + expression = expression.as()->expression; + } + + if (expression == SyntaxKind::BinaryExpression) + { + auto binaryExpression = expression.as(); + if (binaryExpression->operatorToken == SyntaxKind::EqualsToken + && binaryExpression->left == SyntaxKind::Identifier) + { + supportedYieldScope.mark(binaryExpression->right, true); + } + } + auto result = mlirGen(expressionStatementAST->expression, genContext); EXIT_IF_FAILED(result) return mlir::success(); @@ -446,6 +465,16 @@ namespace mlirgen mlir::LogicalResult MLIRGenImpl::mlirGen(VariableStatement variableStatementAST, const GenContext &genContext) { + // `const v = yield x;` + SupportedYieldScope supportedYieldScope(*this); + for (auto declaration : variableStatementAST->declarationList->declarations) + { + if (declaration->initializer && declaration->name == SyntaxKind::Identifier) + { + supportedYieldScope.mark(declaration->initializer, true); + } + } + // we need it for support "export" keyword variableStatementAST->declarationList->parent = variableStatementAST; return mlirGen(variableStatementAST->declarationList, genContext); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index ecaa40bfd..7362c13bd 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -516,6 +516,7 @@ tslang_add_test(NAME test-compile-00-switch-default-fallthrough COMMAND test-run tslang_add_test(NAME test-compile-00-generic-class-constructor-inference COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_class_constructor_inference.ts") tslang_add_test(NAME test-compile-00-generic-ref-inference COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_ref_inference.ts") tslang_add_test(NAME test-compile-00-generator-done-value-zero COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_done_value_zero.ts") +tslang_add_test(NAME test-compile-00-generator-yield-value COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_yield_value.ts") tslang_add_test(NAME test-compile-00-any-equality-undefined COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00any_equality_undefined.ts") tslang_add_test(NAME test-compile-00-owned-generators COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_generators.ts") tslang_add_test(NAME test-compile-00-owned-globals COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_globals.ts") @@ -1075,6 +1076,7 @@ tslang_add_test(NAME test-jit-00-switch-default-fallthrough COMMAND test-runner tslang_add_test(NAME test-jit-00-generic-class-constructor-inference COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_class_constructor_inference.ts") tslang_add_test(NAME test-jit-00-generic-ref-inference COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_ref_inference.ts") tslang_add_test(NAME test-jit-00-generator-done-value-zero COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_done_value_zero.ts") +tslang_add_test(NAME test-jit-00-generator-yield-value COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_yield_value.ts") tslang_add_test(NAME test-jit-00-any-equality-undefined COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00any_equality_undefined.ts") tslang_add_test(NAME test-jit-00-owned-generators COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_generators.ts") tslang_add_test(NAME test-jit-00-owned-globals COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_globals.ts") @@ -2009,6 +2011,7 @@ set(TSLANG_CORPUS 00generator6.ts 00generator7.ts 00generator_done_value_zero.ts + 00generator_yield_value.ts 00generic_arguments_name_conflict.ts 00generic_class_constructor_inference.ts 00generic_ref_inference.ts @@ -4032,6 +4035,25 @@ foreach(property_error_name if const while conditional) FAIL_REGULAR_EXPRESSION "Stack dump|Assertion failed") endforeach() +# A yield inside an expression is an error, not a crash: what the expression computed before the +# yield would be an SSA value of the turn that returned, gone when the generator resumes. +foreach(yield_mm gc rc none) + foreach(yield_case binary call_argument yield_star_value) + add_test(NAME test-compile-${yield_mm}-yield-in-expression-${yield_case} + COMMAND $ --emit=obj --no-default-lib -mm=${yield_mm} + "${PROJECT_SOURCE_DIR}/test/tester/yield-in-expression/${yield_case}.ts" + -o "${CMAKE_CURRENT_BINARY_DIR}/yield-in-expression-${yield_mm}-${yield_case}.obj") + if (yield_case STREQUAL "yield_star_value") + set(yield_message "the value of yield[*] is not supported") + else() + set(yield_message "a yield inside an expression is not supported") + endif() + set_tests_properties(test-compile-${yield_mm}-yield-in-expression-${yield_case} + PROPERTIES PASS_REGULAR_EXPRESSION "${yield_message}" + FAIL_REGULAR_EXPRESSION "Stack dump|Assertion failed") + endforeach() +endforeach() + # The shared-component tier under the other two models. A shared library records the model # it was built under, so both halves of a pair are built with the same flag - which is what # these run. The file pairs are the default model's, verbatim. diff --git a/tslang/test/tester/tests/00generator_yield_value.ts b/tslang/test/tester/tests/00generator_yield_value.ts new file mode 100644 index 000000000..670ccf88c --- /dev/null +++ b/tslang/test/tester/tests/00generator_yield_value.ts @@ -0,0 +1,25 @@ +// A yield's value is undefined (next() takes no argument yet). It was no value at all: `const v = yield x` +// left v as whatever its field held. Whatever comes after the yield is computed in the turn that resumed. +function* gen() { + let count = 0; + for (let i = 0; i < 2; i++) { + const v = yield i; + assert(v === undefined); + const t = "a" + i + v; + assert(t == "a" + i + "undefined"); + count++; + } + + let w: any = 5; + w = (yield 10); + assert(w === undefined); + + yield count; +} + +function main() { + let sum = 0; + for (const v of gen()) sum += v; + assert(sum == 0 + 1 + 10 + 2); + print("done."); +} diff --git a/tslang/test/tester/yield-in-expression/binary.ts b/tslang/test/tester/yield-in-expression/binary.ts new file mode 100644 index 000000000..0e770d0bf --- /dev/null +++ b/tslang/test/tester/yield-in-expression/binary.ts @@ -0,0 +1,12 @@ +// A yield inside an expression: what the expression computed before the yield ("a" + i) would be +// an SSA value of the turn that returned, gone when the generator resumes. It crashed at run time. +function* gen() { + for (let i = 0; i < 2; i++) { + const t = ("a" + i) + (yield 1); + print(t); + } +} + +function main() { + for (const v of gen()) print(v); +} diff --git a/tslang/test/tester/yield-in-expression/call_argument.ts b/tslang/test/tester/yield-in-expression/call_argument.ts new file mode 100644 index 000000000..aa0b501c8 --- /dev/null +++ b/tslang/test/tester/yield-in-expression/call_argument.ts @@ -0,0 +1,8 @@ +// The callee is evaluated before its argument, so the argument cannot be a yield. +function* gen() { + print(yield 1); +} + +function main() { + for (const v of gen()) print(v); +} diff --git a/tslang/test/tester/yield-in-expression/yield_star_value.ts b/tslang/test/tester/yield-in-expression/yield_star_value.ts new file mode 100644 index 000000000..81c0dcd39 --- /dev/null +++ b/tslang/test/tester/yield-in-expression/yield_star_value.ts @@ -0,0 +1,12 @@ +// yield* gives back nothing yet: its value cannot be used. +function* inner() { + yield 1; +} + +function* gen() { + const r = yield* inner(); +} + +function main() { + for (const v of gen()) print(v); +}