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
27 changes: 24 additions & 3 deletions tslang/lib/TypeScript/MLIRGenExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,10 @@ namespace mlirgen
EXIT_IF_FAILED_OR_NO_VALUE(result)

auto condValue = V(result);

// only the branch that runs is generated when the condition is known, see mlirGenSkippedBranch
auto staticCondition = getStaticBoolean(condValue);

if (condValue.getType() != getBooleanType())
{
CAST(condValue, location, getBooleanType(), condValue, genContext);
Expand All @@ -656,6 +660,7 @@ namespace mlirgen

ElseSafeCase elseSafeCase;
mlir::Value resultTrue;
if (staticCondition.value_or(true))
{
// check if we do safe-cast here
SymbolTableScopeT varScope(symbolTable);
Expand All @@ -666,21 +671,28 @@ namespace mlirgen
{
EXIT_IF_FAILED_OR_NO_VALUE(result)
}

resultTrue = V(result);
}
else
{
resultTrue = mlirGenSkippedBranch(location, whenTrueExpression, [&](const GenContext &evalGenContext) {
checkSafeCast(conditionalExpressionAST->condition, V(result), nullptr, evalGenContext);
}, genContext);
}

builder.setInsertionPointToStart(&ifOp.getElseRegion().front());
auto whenFalseExpression = conditionalExpressionAST->whenFalse;

mlir::Value resultFalse;
if (!staticCondition.value_or(false))
{
SymbolTableScopeT varScope(symbolTable);
if (elseSafeCase.safeType)
{
addSafeCastStatement(elseSafeCase.expr, elseSafeCase.safeType, false, nullptr, genContext);
}
}

auto result2 = mlirGen(whenFalseExpression, genContext);
if (!genContext.allowPartialResolve)
{
Expand All @@ -689,6 +701,15 @@ namespace mlirgen

resultFalse = V(result2);
}
else
{
resultFalse = mlirGenSkippedBranch(location, whenFalseExpression, [&](const GenContext &evalGenContext) {
if (elseSafeCase.safeType)
{
addSafeCastStatement(elseSafeCase.expr, elseSafeCase.safeType, false, nullptr, evalGenContext);
}
}, genContext);
}

if (resultTrue && resultFalse)
{
Expand Down
85 changes: 84 additions & 1 deletion tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4345,14 +4345,37 @@ class MLIRGenImpl

CAST_A(condValue, location, getBooleanType(), leftExpressionValue, genContext);

// `false && right`: right never runs and is not generated, see mlirGenSkippedBranch
auto rightSkipped = andOp && !getStaticBoolean(leftExpressionValue).value_or(true);
mlir::Type skippedRightType;
if (rightSkipped)
{
skippedRightType = evaluateSkippedBranch(rightExpression, [&](const GenContext &evalGenContext) {
checkSafeCast(leftExpression, leftExpressionValue, nullptr, evalGenContext);
}, genContext);

// a boolean `false && right` is the constant false itself, so an enclosing `if` or `while`
// sees that its condition is known too
if (!saveResult && skippedRightType
&& getUnionType(location, skippedRightType, leftExpressionValue.getType()) == getBooleanType())
{
return condValue;
}
}

auto ifOp = builder.create<mlir_ts::IfOp>(location, mlir::TypeRange{leftExpressionValue.getType()}, condValue, true);

builder.setInsertionPointToStart(&ifOp.getThenRegion().front());

ElseSafeCase elseSafeCase;
mlir::Value resultTrue;
{
if (andOp)
if (rightSkipped)
{
resultTrue = builder.create<mlir_ts::UndefOp>(
location, skippedRightType ? skippedRightType : leftExpressionValue.getType());
}
else if (andOp)
{
// check if we do safe-cast here
SymbolTableScopeT varScope(symbolTable);
Expand Down Expand Up @@ -7698,6 +7721,66 @@ class MLIRGenImpl
return V(builder.create<mlir_ts::ConstantOp>(location, literalType, literalType.getValue()));
}

// The value of a condition known at compile time: a boolean literal, also seen through casts to
// boolean - a folded `typeof x === "name"`, or an `&&` whose left side folded to false.
std::optional<bool> getStaticBoolean(mlir::Value value)
{
while (value)
{
if (auto litType = dyn_cast<mlir_ts::LiteralType>(value.getType()))
{
if (auto boolVal = dyn_cast<mlir::BoolAttr>(litType.getValue()))
{
return boolVal.getValue();
}

return std::nullopt;
}

auto castOp = value.getDefiningOp<mlir_ts::CastOp>();
if (!castOp || !isa<mlir_ts::BooleanType>(value.getType()))
{
return std::nullopt;
}

value = castOp.getIn();
}

return std::nullopt;
}

// A branch whose condition is known at compile time never runs and is not generated: its narrowing
// (see mlirGen(IfStatement)) would cast the tested value to a type the value cannot have. The branch
// still contributes its type to the expression, so it is evaluated, narrowing included, in the temporary
// module, and yields an undefined value of that type. Returns no value when the branch does not resolve.
mlir::Value mlirGenSkippedBranch(mlir::Location location, Expression expr,
std::function<void(const GenContext &)> narrow, const GenContext &genContext)
{
auto type = evaluateSkippedBranch(expr, narrow, genContext);
if (!type)
{
return mlir::Value();
}

return builder.create<mlir_ts::UndefOp>(location, type);
}

mlir::Type evaluateSkippedBranch(Expression expr, std::function<void(const GenContext &)> narrow,
const GenContext &genContext)
{
TempModuleScope tempModuleScope(*this);
SymbolTableScopeT varScope(symbolTable);
SafeTypesMapScopeT safeTypesMapScope(safeTypesMap);

GenContext evalGenContext(genContext);
evalGenContext.allowPartialResolve = true;
evalGenContext.funcOp = tempFuncOp;
narrow(evalGenContext);
auto result = mlirGen(expr, evalGenContext);
auto value = V(result);
return value ? value.getType() : mlir::Type();
}

// `typeof x ==/===/!=/!== "name"` (either operand order) where `typeof x` is a TypeDescriptor, i.e.
// x's type is known at compile time. At run time the descriptor's name - typeOfAsString of its
// type, see TypeDescriptorOpLowering - is compared with the literal by content, so the result is
Expand Down
29 changes: 15 additions & 14 deletions tslang/lib/TypeScript/MLIRGenStatements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -501,14 +501,7 @@ namespace mlirgen
auto condValue = V(result);

// special case: in case of LiteralValue do not process If value is False
std::optional<bool> literalValue;
if (auto litType = mlir::dyn_cast<mlir_ts::LiteralType>(condValue.getType()))
{
if (auto boolVal = mlir::dyn_cast<mlir::BoolAttr>(litType.getValue()))
{
literalValue = boolVal.getValue();
}
}
auto literalValue = getStaticBoolean(condValue);

// default implementation of IfOp
if (condValue.getType() != getBooleanType())
Expand Down Expand Up @@ -639,6 +632,10 @@ namespace mlirgen
EXIT_IF_FAILED_OR_NO_VALUE(result)
auto conditionValue = V(result);

// a condition known to be false: the body never runs and, as in mlirGen(IfStatement), is not
// generated, so its narrowing cannot cast the tested value to a type it cannot have
auto processBody = getStaticBoolean(conditionValue).value_or(true);

if (conditionValue.getType() != getBooleanType())
{
CAST(conditionValue, location, getBooleanType(), conditionValue, loopGenContext);
Expand All @@ -649,13 +646,17 @@ namespace mlirgen
// body
builder.setInsertionPointToStart(&whileOp.getBody().front());

// check if we do safe-cast here
SymbolTableScopeT varScopeBody(symbolTable);
SafeTypesMapScopeT safeTypesMapScope(safeTypesMap);
checkSafeCast(whileStatementAST->expression, conditionValue, nullptr, loopGenContext);
if (processBody)
{
// check if we do safe-cast here
SymbolTableScopeT varScopeBody(symbolTable);
SafeTypesMapScopeT safeTypesMapScope(safeTypesMap);
checkSafeCast(whileStatementAST->expression, conditionValue, nullptr, loopGenContext);

auto result2 = mlirGen(whileStatementAST->statement, loopGenContext);
EXIT_IF_FAILED(result2)
}

auto result2 = mlirGen(whileStatementAST->statement, loopGenContext);
EXIT_IF_FAILED(result2)
builder.create<mlir_ts::ResultOp>(location);

builder.setInsertionPointAfter(whileOp);
Expand Down
3 changes: 3 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ add_test(NAME test-compile-00-funcs-nesting-generic COMMAND test-runner "${PROJE
add_test(NAME test-compile-00-funcs-nesting-capture COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_nesting_capture.ts")
add_test(NAME test-compile-00-funcs-hybrid-null-this COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_hybrid_null_this.ts")
add_test(NAME test-compile-00-typeof-static-fold COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold.ts")
add_test(NAME test-compile-00-typeof-static-fold-conditions COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold_conditions.ts")
add_test(NAME test-compile-00-array-move-wide-elements COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_move_wide_elements.ts")
add_test(NAME test-compile-00-funcs-expression-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_expression_generic.ts")
add_test(NAME test-compile-00-funcs-expression-iterator COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_expression_iterator.ts")
Expand Down Expand Up @@ -588,6 +589,7 @@ add_test(NAME test-jit-00-funcs-nesting-generic COMMAND test-runner -jit "${PROJ
add_test(NAME test-jit-00-funcs-nesting-capture COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_nesting_capture.ts")
add_test(NAME test-jit-00-funcs-hybrid-null-this COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_hybrid_null_this.ts")
add_test(NAME test-jit-00-typeof-static-fold COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold.ts")
add_test(NAME test-jit-00-typeof-static-fold-conditions COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold_conditions.ts")
add_test(NAME test-jit-00-array-move-wide-elements COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_move_wide_elements.ts")
add_test(NAME test-jit-00-funcs-expression-generic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_expression_generic.ts")
add_test(NAME test-jit-00-funcs-expression-iterator COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_expression_iterator.ts")
Expand Down Expand Up @@ -1575,6 +1577,7 @@ set(TSLANG_CORPUS
00type_guard_function.ts
00typed_array.ts
00typeof_static_fold.ts
00typeof_static_fold_conditions.ts
00types_indexedaccesstype.ts
00types_keyof_enum.ts
00types_mappedtype.ts
Expand Down
44 changes: 44 additions & 0 deletions tslang/test/tester/tests/00typeof_static_fold_conditions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// A `?:`, `&&` or `while` whose condition folds at compile time (a `typeof` of a value whose type
// is known) does not generate the branch that never runs. That branch narrows the tested value, so
// for an array narrowed to `string` it cast the array to a string and crashed the compiler.
function conditional<T>(x: T) {
return typeof x === "string" ? x.length : -1;
}

function conditionalElse<T>(x: T) {
return typeof x !== "string" ? -1 : x.length;
}

function and<T>(x: T) {
return typeof x === "string" && x.length > 1;
}

function loop<T>(x: T) {
let n = 0;
while (typeof x === "string" && n < x.length) {
n++;
}

return n;
}

function main() {
assert(conditional("abc") == 3, "?: string");
assert(conditional([1, 2]) == -1, "?: array");
assert(conditionalElse("abcd") == 4, "?: else string");
assert(conditionalElse(<string[]>["a"]) == -1, "?: else array");

assert(and("abc"), "&& string");
assert(!and([1, 2]), "&& array");

assert(loop("ab") == 2, "while string");
assert(loop([1, 2, 3]) == 0, "while array");

// the branch that is not generated still gives the expression its type
const text = "abc";
let v = typeof text === "string" ? 1 : "one";
v = "one";
assert(v == "one", "?: keeps the type of both branches");

print("done.");
}
Loading