Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions tslang/lib/TypeScript/MLIRGenExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -404,9 +420,9 @@ namespace mlirgen
label << GENERATOR_STATELABELPREFIX << state;
builder.create<mlir_ts::StateLabelOp>(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<mlir_ts::UndefOp>(location, getUndefinedType()));
}

ValueOrLogicalResult MLIRGenImpl::mlirGen(AwaitExpression awaitExpressionAST, const GenContext &genContext)
Expand Down
40 changes: 40 additions & 0 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<ParenthesizedExpression>()->expression;
}

if (expression == SyntaxKind::YieldExpression)
{
const void *node = expression.operator->();
gen.supportedYields[node] = valueUsed;
marked.push_back(node);
}
}

MLIRGenImpl &gen;
SmallVector<const void *> marked;
};

ValueOrLogicalResult mlirGen(YieldExpression yieldExpressionAST, const GenContext &genContext);

ValueOrLogicalResult mlirGen(AwaitExpression awaitExpressionAST, const GenContext &genContext);
Expand Down Expand Up @@ -14846,6 +14881,11 @@ class MLIRGenImpl
// can't leave a function.
SmallVector<std::string> 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<const void *, bool> supportedYields;

private:
std::string label;
// `a: b: for (...)`: the outer labels of a chain that ends in a loop, each mapped to the innermost
Expand Down
29 changes: 29 additions & 0 deletions tslang/lib/TypeScript/MLIRGenStatements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -439,13 +439,42 @@ 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<ParenthesizedExpression>()->expression;
}

if (expression == SyntaxKind::BinaryExpression)
{
auto binaryExpression = expression.as<BinaryExpression>();
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();
}

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);
Expand Down
22 changes: 22 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 $<TARGET_FILE:tslang> --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.
Expand Down
25 changes: 25 additions & 0 deletions tslang/test/tester/tests/00generator_yield_value.ts
Original file line number Diff line number Diff line change
@@ -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.");
}
12 changes: 12 additions & 0 deletions tslang/test/tester/yield-in-expression/binary.ts
Original file line number Diff line number Diff line change
@@ -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);
}
8 changes: 8 additions & 0 deletions tslang/test/tester/yield-in-expression/call_argument.ts
Original file line number Diff line number Diff line change
@@ -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);
}
12 changes: 12 additions & 0 deletions tslang/test/tester/yield-in-expression/yield_star_value.ts
Original file line number Diff line number Diff line change
@@ -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);
}
Loading