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
9 changes: 9 additions & 0 deletions tslang/lib/TypeScript/MLIRGenExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,15 @@ namespace mlirgen
EXIT_IF_FAILED_OR_NO_VALUE(result2)
auto rightExpressionValue = V(result2);

// `typeof x === "name"` where x has a type known at compile time: the descriptor's name is
// known now, and the run-time compare is a plain string compare, so fold it. The boolean
// literal lets `if` skip a branch that can never run - in a generic specialised for an array,
// such a branch can hold casts an array cannot take, which crash LLVM lowering.
if (auto folded = foldStaticTypeOfCompare(location, opCode, leftExpressionValue, rightExpressionValue))
{
return *folded;
}

// check if const expr.
if (genContext.allowConstEval)
{
Expand Down
51 changes: 51 additions & 0 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -7698,6 +7698,57 @@ class MLIRGenImpl
return V(builder.create<mlir_ts::ConstantOp>(location, literalType, literalType.getValue()));
}

// `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
// known here. Returns nothing for any other comparison (an `any`, a union tag, a stored typeof).
std::optional<ValueOrLogicalResult> foldStaticTypeOfCompare(mlir::Location location, SyntaxKind opCode,
mlir::Value leftValue, mlir::Value rightValue)
{
auto isEquals = opCode == SyntaxKind::EqualsEqualsToken || opCode == SyntaxKind::EqualsEqualsEqualsToken;
auto isNotEquals = opCode == SyntaxKind::ExclamationEqualsToken || opCode == SyntaxKind::ExclamationEqualsEqualsToken;
if (!isEquals && !isNotEquals)
{
return std::nullopt;
}

auto stringLiteralOf = [](mlir::Value value) -> std::optional<llvm::StringRef> {
if (auto constOp = value.getDefiningOp<mlir_ts::ConstantOp>())
{
if (auto strAttr = dyn_cast<mlir::StringAttr>(constOp.getValue()))
{
return strAttr.getValue();
}
}

return std::nullopt;
};

auto descriptorOp = leftValue.getDefiningOp<mlir_ts::TypeDescriptorOp>();
auto literal = stringLiteralOf(rightValue);
if (!descriptorOp)
{
descriptorOp = rightValue.getDefiningOp<mlir_ts::TypeDescriptorOp>();
literal = stringLiteralOf(leftValue);
}

if (!descriptorOp || !literal)
{
return std::nullopt;
}

TypeOfOpHelper toh(builder);
auto namesMatch = toh.typeOfAsString(descriptorOp.getDescriptorType()) == *literal;

// the typeof value was built for this comparison only
if (descriptorOp->use_empty())
{
descriptorOp->erase();
}

return mlirGenBooleanValue(location, isEquals ? namesMatch : !namesMatch);
}

ValueOrLogicalResult mlirGen(TrueLiteral trueLiteral, const GenContext &genContext);

ValueOrLogicalResult mlirGen(FalseLiteral falseLiteral, const GenContext &genContext);
Expand Down
20 changes: 13 additions & 7 deletions tslang/lib/TypeScript/MLIRGenStatements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,16 +520,21 @@ namespace mlirgen

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

// Narrowing (safe-cast) is added only to a branch that is generated: it emits a cast of the tested
// value, and for a statically decided test that cast can be one the value cannot take - an array
// narrowed to `string` by `typeof x === "string"`. Under --di the narrowed variable's debug record
// keeps such a cast alive into LLVM lowering even though the branch body was skipped.
ElseSafeCase elseSafeCase{};
{
// check if we do safe-cast here
SymbolTableScopeT varScope(symbolTable);
SafeTypesMapScopeT safeTypesMapScope(safeTypesMap);
checkSafeCast(ifStatementAST->expression, V(result), hasElse ? &elseSafeCase : nullptr, genContext);

auto processIf = !literalValue.has_value() || literalValue.value();
if (processIf)
{
// check if we do safe-cast here
checkSafeCast(ifStatementAST->expression, V(result), hasElse ? &elseSafeCase : nullptr, genContext);

auto result = mlirGen(ifStatementAST->thenStatement, genContext);
EXIT_IF_FAILED(result)
}
Expand All @@ -539,15 +544,16 @@ namespace mlirgen
{
builder.setInsertionPointToStart(&ifOp.getElseRegion().front());
SymbolTableScopeT varScope(symbolTable);
if (elseSafeCase.safeType)
{
// add case statement
addSafeCastStatement(elseSafeCase.expr, elseSafeCase.safeType, false, nullptr, genContext);
}

auto processIf = !literalValue.has_value() || !literalValue.value();
if (processIf)
{
if (elseSafeCase.safeType)
{
// add case statement
addSafeCastStatement(elseSafeCase.expr, elseSafeCase.safeType, false, nullptr, genContext);
}

auto result = mlirGen(ifStatementAST->elseStatement, genContext);
EXIT_IF_FAILED(result)
}
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 @@ -170,6 +170,7 @@ add_test(NAME test-compile-00-funcs-nesting COMMAND test-runner "${PROJECT_SOURC
add_test(NAME test-compile-00-funcs-nesting-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_nesting_generic.ts")
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-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")
add_test(NAME test-compile-00-arrow-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00arrow_generic.ts")
Expand Down Expand Up @@ -585,6 +586,7 @@ add_test(NAME test-jit-00-funcs-nesting COMMAND test-runner -jit "${PROJECT_SOUR
add_test(NAME test-jit-00-funcs-nesting-generic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_nesting_generic.ts")
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-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")
add_test(NAME test-jit-00-arrow-generic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00arrow_generic.ts")
Expand Down Expand Up @@ -1569,6 +1571,7 @@ set(TSLANG_CORPUS
00type_aliases_in_generics.ts
00type_guard_function.ts
00typed_array.ts
00typeof_static_fold.ts
00types_indexedaccesstype.ts
00types_keyof_enum.ts
00types_mappedtype.ts
Expand Down
35 changes: 35 additions & 0 deletions tslang/test/tester/tests/00typeof_static_fold.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// `typeof x === "<name>"` on a value whose type is known at compile time is folded to a
// constant, so a branch that can never run is not generated. Before the fold, the branch
// below that prints an array as a string reached LLVM lowering and crashed the compiler.
function describe<T>(x: T) {
if (typeof x === "string") {
print("string: ", x);
return 1;
}

if ("array" === typeof x) {
return 2;
}

if (typeof x !== "boolean") {
return 3;
}

return 4;
}

function main() {
assert(describe("abc") == 1, "string");
assert(describe([]) == 2, "array");
assert(describe(<string[]>["a", "b"]) == 2, "string array");
assert(describe(2.5) == 3, "number");
assert(describe(true) == 4, "boolean");

// `any` is only known at run time and stays a run-time check
let a: any = "text";
assert(typeof a === "string", "any holding a string");
a = 2.5;
assert(typeof a !== "string", "any holding a number");

print("done.");
}
Loading