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
35 changes: 34 additions & 1 deletion tslang/include/TypeScript/LowerToLLVM/UnaryBinLogicalOrHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include "TypeScript/TypeScriptOps.h"

#include "TypeScript/LowerToLLVM/LLVMTypeConverterHelper.h"
#include "TypeScript/MLIRLogic/TypeOfOpHelper.h"
#include "TypeScript/LowerToLLVM/LLVMCodeHelperBase.h"

#include "scanner_enums.h"

Expand Down Expand Up @@ -156,7 +158,38 @@ mlir::Value LogicOp(Operation *binOp, SyntaxKind op, mlir::Value left, mlir::Typ
}
else if (isa<mlir_ts::AnyType>(leftType))
{
if (left.getType() != right.getType())
// the operands are lowered already, so an `any` and a string (or null) are both a `ptr`
// and comparing their types said nothing: the string itself was handed over as the box,
// and its bytes read as one - `let t: any = "text"; t == "text"` was false. Box it by
// its TypeScript type, as a cast to `any` does - on the stack: the box lives for this
// comparison only, and one on the heap was never freed under reference counting.
auto rightElementType = isa<mlir_ts::LiteralType>(rightType) ? cast<mlir_ts::LiteralType>(rightType).getElementType() : rightType;
if (!isa<mlir_ts::AnyType>(rightElementType) && !isa<mlir_ts::UnionType>(rightElementType))
{
auto ctx = builder.getContext();
auto ptrTy = LLVM::LLVMPointerType::get(ctx);
auto llvmIndexType = typeConverter.convertType(builder.getIndexType());
auto boxType = LLVM::LLVMStructType::getLiteral(ctx, {llvmIndexType, ptrTy, right.getType()}, false);

LLVMCodeHelperBase ch(binOp, builder, &typeConverter, compileOptions);
auto box = ch.Alloca(boxType, 1);

auto sizeMLIR = builder.create<mlir_ts::SizeOfOp>(loc, builder.getIndexType(), right.getType());
auto size = builder.create<mlir_ts::DialectCastOp>(loc, llvmIndexType, sizeMLIR);

TypeOfOpHelper toh(builder);
auto typeOfValue = builder.create<mlir_ts::DialectCastOp>(loc, ptrTy, toh.typeOfLogic(loc, rightElementType));

auto fieldAddress = [&](int index) {
return builder.create<LLVM::GEPOp>(loc, ptrTy, boxType, box, ArrayRef<LLVM::GEPArg>{0, index});
};
builder.create<LLVM::StoreOp>(loc, size, fieldAddress(ANY_SIZE));
builder.create<LLVM::StoreOp>(loc, typeOfValue, fieldAddress(ANY_TYPE));
builder.create<LLVM::StoreOp>(loc, right, fieldAddress(ANY_DATA));

right = box;
}
else if (left.getType() != right.getType())
{
right = builder.create<mlir_ts::CastOp>(loc, left.getType(), right);
}
Expand Down
4 changes: 3 additions & 1 deletion tslang/include/TypeScript/LowerToLLVM/UndefLogicHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ class UndefLogicHelper

auto processUndefVale = [&](OpBuilder &builder, Location loc, mlir::Type t1, mlir::Value val1, mlir::Type t2,
mlir::Value val2) {
if (isa<mlir_ts::InterfaceType>(t2) || isa<mlir_ts::ClassType>(t2))
// an `any` may hold undefined, so it is asked at run time (AnyCompareOp) rather
// than answered here: `u == undefined` of an `any` holding undefined was false
if (isa<mlir_ts::InterfaceType>(t2) || isa<mlir_ts::ClassType>(t2) || isa<mlir_ts::AnyType>(t2))
{
auto casted = rewriter.create<mlir_ts::CastOp>(loc, t2, val1);
return LogicOp<StdIOpTy, V1, v1, StdFOpTy, V2, v2>(binOp, opCmpCode, val2, val2.getType(), casted, casted.getType(),
Expand Down
132 changes: 115 additions & 17 deletions tslang/lib/TypeScript/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,55 @@ class AnyCompareOpLowering : public TsLlvmPattern<mlir_ts::AnyCompareOp>
ConversionPatternRewriter &rewriter) const
{
auto i8PtrTy = th.getPtrType();

auto compareResultWith0 = [&](mlir::Value compareResult) -> mlir::Value {
auto const0 = clh.createI32ConstantOf(0);
switch (code)
{
case SyntaxKind::EqualsEqualsToken:
case SyntaxKind::EqualsEqualsEqualsToken:
return rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::eq, compareResult, const0);
case SyntaxKind::ExclamationEqualsToken:
case SyntaxKind::ExclamationEqualsEqualsToken:
return rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::ne, compareResult, const0);
case SyntaxKind::GreaterThanToken:
return rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::sgt, compareResult, const0);
case SyntaxKind::GreaterThanEqualsToken:
return rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::sge, compareResult, const0);
case SyntaxKind::LessThanToken:
return rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::slt, compareResult, const0);
case SyntaxKind::LessThanEqualsToken:
return rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::sle, compareResult, const0);
default:
op->emitError("unsupported 'any' comparison operator");
return mlir::Value();
}
};

// two strings compare by their text: their payload is a pointer, and comparing its bytes
// made two equal strings unequal unless they were the same literal
TypeConverterHelper tch(getTypeConverter());
TypeDescriptorLogic tdl(rewriter, tch, loc);
auto bothStrings = rewriter.create<LLVM::AndOp>(loc, tdl.isKind(al.getTypeOfAny(op1), TYPE_KIND_STRING),
tdl.isKind(al.getTypeOfAny(op2), TYPE_KIND_STRING));
return clh.conditionalExpressionLowering(
loc, th.getLLVMBoolType(), bothStrings,
[&](OpBuilder &, Location) {
auto strcmpFuncOp = ch.getOrInsertFunction("strcmp", th.getFunctionType(th.getI32Type(), {i8PtrTy, i8PtrTy}));
auto compareResult = rewriter.create<LLVM::CallOp>(
loc, strcmpFuncOp, ValueRange{al.UnboxAny(op1, i8PtrTy), al.UnboxAny(op2, i8PtrTy)});
return compareResultWith0(compareResult.getResult());
},
[&](OpBuilder &, Location) { return compareBytes(op, op1, op2, code, loc, al, clh, th, ch, llvmtch, rewriter); });
}

// the payloads' bytes: for everything but a string, whose payload is a pointer
mlir::Value compareBytes(mlir_ts::AnyCompareOp op, mlir::Value op1, mlir::Value op2, SyntaxKind code,
mlir::Location loc, AnyLogic &al, CodeLogicHelper &clh, TypeHelper &th,
LLVMCodeHelper &ch, LLVMTypeConverterHelper &llvmtch,
ConversionPatternRewriter &rewriter) const
{
auto i8PtrTy = th.getPtrType();
auto llvmIndexType = llvmtch.typeConverter->convertType(th.getIndexType());

auto memcmpFuncOp = ch.getOrInsertFunction("memcmp", th.getFunctionType(th.getI32Type(), {i8PtrTy, i8PtrTy, llvmIndexType}));
Expand Down Expand Up @@ -1256,27 +1305,76 @@ class AnyCompareOpLowering : public TsLlvmPattern<mlir_ts::AnyCompareOp>
auto op1 = transformed.getOp1();
auto op2 = transformed.getOp2();

// only loose equality (==/!=) coerces across differing payload kinds; strict
// equality and ordering compare the underlying bytes as before.
if (code != SyntaxKind::EqualsEqualsToken && code != SyntaxKind::ExclamationEqualsToken)
{
auto result = sameKindCompare(op, op1, op2, code, loc, al, clh, th, ch, llvmtch, rewriter);
rewriter.replaceOp(op, result);
return success();
}
auto compareBoxes = [&]() -> mlir::Value {
// only loose equality (==/!=) coerces across differing payload kinds; strict
// equality and ordering compare the underlying bytes as before.
if (code != SyntaxKind::EqualsEqualsToken && code != SyntaxKind::ExclamationEqualsToken)
{
return sameKindCompare(op, op1, op2, code, loc, al, clh, th, ch, llvmtch, rewriter);
}

auto tag1 = al.getTypeOfAny(op1);
auto tag2 = al.getTypeOfAny(op2);
auto tag1 = al.getTypeOfAny(op1);
auto tag2 = al.getTypeOfAny(op2);

auto strcmpFuncOp = ch.getOrInsertFunction("strcmp", th.getFunctionType(th.getI32Type(), {th.getPtrType(), th.getPtrType()}));
auto tagCmp = rewriter.create<LLVM::CallOp>(loc, strcmpFuncOp, ValueRange{tag1, tag2});
auto const0 = clh.createI32ConstantOf(0);
auto tagsEqual = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::eq, tagCmp.getResult(), const0);
auto strcmpFuncOp = ch.getOrInsertFunction("strcmp", th.getFunctionType(th.getI32Type(), {th.getPtrType(), th.getPtrType()}));
auto tagCmp = rewriter.create<LLVM::CallOp>(loc, strcmpFuncOp, ValueRange{tag1, tag2});
auto const0 = clh.createI32ConstantOf(0);
auto tagsEqual = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::eq, tagCmp.getResult(), const0);

return clh.conditionalExpressionLowering(
loc, th.getLLVMBoolType(), tagsEqual,
[&](OpBuilder &builder, Location loc) { return sameKindCompare(op, op1, op2, code, loc, al, clh, th, ch, llvmtch, rewriter); },
[&](OpBuilder &builder, Location loc) { return coerceAndCompareMixedKinds(op1, op2, tag1, tag2, code, loc, al, castLogic, clh, th, ch, tch, rewriter); });
};

// `undefined` and `null` in an `any`, answered before any box is opened: `u == undefined`
// of an `any` holding undefined was false - the box's tag and bytes were compared against
// `undefined` or `null` turned into an `any`, which is a null pointer rather than a box,
// so its tag was read from address 8 - and a zeroed `any` slot is a null pointer too. A
// null pointer is undefined; otherwise the tag's kind says. Loosely, undefined and null
// equal each other and nothing else; strictly, each equals only itself.
auto nullPtr = rewriter.create<LLVM::ZeroOp>(loc, th.getPtrType());
TypeDescriptorLogic tdl(rewriter, tch, loc);
auto kindOf = [&](mlir::Value anyValue) {
auto isNullPtr = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::eq, anyValue, nullPtr);
return clh.conditionalExpressionLowering(
loc, th.getI32Type(), isNullPtr,
[&](OpBuilder &, Location) { return (mlir::Value)clh.createI32ConstantOf(TYPE_KIND_UNDEFINED); },
[&](OpBuilder &, Location) { return tdl.getKindFromTag(al.getTypeOfAny(anyValue)); });
};

auto isNullish = [&](mlir::Value kind) {
auto isUndefined = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::eq, kind, clh.createI32ConstantOf(TYPE_KIND_UNDEFINED));
auto isNull = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::eq, kind, clh.createI32ConstantOf(TYPE_KIND_NULL));
return (mlir::Value)rewriter.create<LLVM::OrOp>(loc, isUndefined, isNull);
};

auto kind1 = kindOf(op1);
auto kind2 = kindOf(op2);
auto nullish1 = isNullish(kind1);
auto nullish2 = isNullish(kind2);
auto eitherNullish = rewriter.create<LLVM::OrOp>(loc, nullish1, nullish2);

auto result = clh.conditionalExpressionLowering(
loc, th.getLLVMBoolType(), tagsEqual,
[&](OpBuilder &builder, Location loc) { return sameKindCompare(op, op1, op2, code, loc, al, clh, th, ch, llvmtch, rewriter); },
[&](OpBuilder &builder, Location loc) { return coerceAndCompareMixedKinds(op1, op2, tag1, tag2, code, loc, al, castLogic, clh, th, ch, tch, rewriter); });
loc, th.getLLVMBoolType(), eitherNullish,
[&](OpBuilder &, Location) -> mlir::Value {
switch (code)
{
case SyntaxKind::EqualsEqualsToken:
return rewriter.create<LLVM::AndOp>(loc, nullish1, nullish2);
case SyntaxKind::ExclamationEqualsToken:
return rewriter.create<LLVM::XOrOp>(loc, rewriter.create<LLVM::AndOp>(loc, nullish1, nullish2),
clh.createI1ConstantOf(true));
case SyntaxKind::EqualsEqualsEqualsToken:
return rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::eq, kind1, kind2);
case SyntaxKind::ExclamationEqualsEqualsToken:
return rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::ne, kind1, kind2);
default:
// an ordering against undefined or null: false, as before, without reading a box
return clh.createI1ConstantOf(false);
}
},
[&](OpBuilder &, Location) { return compareBoxes(); });

rewriter.replaceOp(op, result);

Expand Down
7 changes: 7 additions & 0 deletions tslang/lib/TypeScript/MLIRGenCast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1881,6 +1881,13 @@ namespace mlirgen
ss << "\nif (typeof a == 'undefined') return 'undefined';";
ss << "\nif (typeof a == 'null') return 'null';";
}

// `if (a)` and `!a` of an `any` holding undefined or null: false, as in JavaScript -
// it threw "Can't cast from any type"
if (mlir::isa<mlir_ts::BooleanType>(type) || mlir::isa<mlir_ts::TypePredicateType>(type)) {
ss << "\nif (typeof a == 'undefined') return false;";
ss << "\nif (typeof a == 'null') return false;";
}
}

ss << "\nthrow \"Can't cast from any type\";\n";
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 @@ -435,6 +435,7 @@ tslang_add_test(NAME test-compile-00-rest-args-pack-owned COMMAND test-runner "$
tslang_add_test(NAME test-compile-00-switch-char-string-cases COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00switch_char_string_cases.ts")
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-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-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")
tslang_add_test(NAME test-compile-00-owned-generator-locals COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_generator_locals.ts")
Expand Down Expand Up @@ -897,6 +898,7 @@ tslang_add_test(NAME test-jit-00-rest-args-pack-owned COMMAND test-runner -jit "
tslang_add_test(NAME test-jit-00-switch-char-string-cases COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00switch_char_string_cases.ts")
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-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-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")
tslang_add_test(NAME test-jit-00-owned-generator-locals COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_generator_locals.ts")
Expand Down Expand Up @@ -1583,6 +1585,7 @@ set(TSLANG_CORPUS
00any_generic_equals.ts
00any_types.ts
00any.ts
00any_equality_undefined.ts
00array_assignment5.ts
00array_cond_access.ts
00array_length_grow_zeroed.ts
Expand Down
65 changes: 65 additions & 0 deletions tslang/test/tester/tests/00any_equality_undefined.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Comparing an `any`:
// - with undefined or null was answered at compile time (== false, != true) whatever it held;
// - `null` (and a string) turned into an `any` for the comparison was a pointer, not a box, and
// its "tag" was read from whatever it pointed at;
// - two strings compared the bytes of their pointers, so equal text built at run time differed;
// - `if (a)` / `!a` of an `any` holding undefined or null threw "Can't cast from any type".
function main() {
const u: any = undefined;
assert(u == undefined);
assert(u === undefined);
assert(u == null);
assert(!(u === null));
assert(!(u != undefined));
assert(!u);
if (u) {
assert(false);
}

const n: any = null;
assert(n == null);
assert(n === null);
assert(n == undefined);
assert(!(n === undefined));
assert(!n);

const s: any = "text";
assert(!(s == undefined));
assert(!(s == null));
assert(s != undefined);
assert(s);

// a `let` is not folded into its initializer: the comparison boxes "text" itself
let t: any = "te";
assert(!(t == "text"));
t = "text";
assert(t == "text");
assert(t === "text");
assert(!(t != "text"));

// equal text, built at run time, in two boxes
const built = "te" + "xt";
const a: any = built;
const b: any = "text";
assert(a == b);
assert(a === b);
assert(!(a != b));
const c: any = "other";
assert(!(a == c));

// an `any` holding undefined against another holding null
assert(u == n);
assert(!(u === n));

// many comparisons: the box made for the right-hand side is on the stack
let hits = 0;
const words = ["text", "nope"];
for (let i = 0; i < 100000; i++) {
if (t == words[i % 2]) {
hits++;
}
}
assert(hits == 50000);

print("done.");
}
Loading