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
60 changes: 55 additions & 5 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -8593,14 +8593,64 @@ class MLIRGenImpl
}
case SyntaxKind::PercentToken:
{
// `%` of two integers, one of them 64-bit, is done in that 64-bit type: through a
// number (f64) it was wrong once the value passed 2^53. Narrower integers keep the
// number path, which is exact for them.
// `%` of two integers is an integer remainder, and so is its result: through a number
// (f64) a 64-bit value was wrong once it passed 2^53, and a narrower one, though exact,
// was an fmod call and two conversions where one instruction does it (a hash modulo the
// length of a table, on every lookup of a Map). A zero divisor is then undefined, as
// integer division by zero is in C - a fault on x86, not the NaN a number gives - and so is
// the least value of a signed type `% -1`.
// The type: an integer literal that fits takes the other operand's; otherwise the wider
// one, and of two of a width the left one. A signed and an unsigned one that an s64
// holds both of are done in s64, where neither changes its value.
auto leftIntType = dyn_cast<mlir::IntegerType>(leftExpressionValue.getType());
auto rightIntType = dyn_cast<mlir::IntegerType>(rightExpressionValue.getType());
if (leftIntType && rightIntType && (leftIntType.getWidth() > 32 || rightIntType.getWidth() > 32))
if (leftIntType && rightIntType && leftIntType.getWidth() >= 8 && rightIntType.getWidth() >= 8)
{
mlir::Type intType = leftIntType.getWidth() >= rightIntType.getWidth() ? leftIntType : rightIntType;
// a literal that `type` holds as it is
auto literalFits = [&](mlir::Value value, mlir::IntegerType type) {
auto constantOp = value.getDefiningOp<mlir_ts::ConstantOp>();
if (!constantOp)
{
return false;
}

auto intAttr = dyn_cast<mlir::IntegerAttr>(constantOp.getValue());
if (!intAttr)
{
return false;
}

auto literal = intAttr.getValue();
auto literalIsUnsigned = value.getType().isUnsignedInteger();
if (literalIsUnsigned ? !literal.isIntN(63) : literal.isNegative())
{
// negative, or past what an s64 says: only its own type holds it
return false;
}

return literal.getActiveBits() <= (type.isUnsigned() ? type.getWidth() : type.getWidth() - 1);
};

mlir::Type intType;
if (literalFits(rightExpressionValue, leftIntType))
{
intType = leftIntType;
}
else if (literalFits(leftExpressionValue, rightIntType))
{
intType = rightIntType;
}
else
{
auto wider = leftIntType.getWidth() >= rightIntType.getWidth() ? leftIntType : rightIntType;
auto narrower = wider == leftIntType ? rightIntType : leftIntType;
// an unsigned type does not hold a signed operand's negative values, nor a
// signed one an unsigned operand of its own width
auto losesValues = wider.isUnsigned() != narrower.isUnsigned()
&& (wider.isUnsigned() || wider.getWidth() == narrower.getWidth());
intType = losesValues && wider.getWidth() <= 32 ? static_cast<mlir::Type>(SInt(64)) : wider;
}

if (leftExpressionValue.getType() != intType)
{
CAST(leftExpressionValue, location, intType, leftExpressionValue, genContext);
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 @@ -545,6 +545,7 @@ tslang_add_test(NAME test-compile-00-throw-in-block-debug-info COMMAND test-runn
tslang_add_test(NAME test-compile-00-catch-untyped-value COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00catch_untyped_value.ts")
tslang_add_test(NAME test-compile-00-throw-bool-bigint COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_bool_bigint.ts")
tslang_add_test(NAME test-compile-00-int64-remainder COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00int64_remainder.ts")
tslang_add_test(NAME test-compile-00-int-remainder COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00int_remainder.ts")
tslang_add_test(NAME test-compile-00-throw-f32 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_f32.ts")
tslang_add_test(NAME test-compile-00-throw-u32 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_u32.ts")
tslang_add_test(NAME test-compile-00-cast-to-f32 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00cast_to_f32.ts")
Expand Down Expand Up @@ -1224,6 +1225,7 @@ tslang_add_test(NAME test-jit-00-throw-in-block-debug-info COMMAND test-runner -
tslang_add_test(NAME test-jit-00-catch-untyped-value COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00catch_untyped_value.ts")
tslang_add_test(NAME test-jit-00-throw-bool-bigint COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_bool_bigint.ts")
tslang_add_test(NAME test-jit-00-int64-remainder COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00int64_remainder.ts")
tslang_add_test(NAME test-jit-00-int-remainder COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00int_remainder.ts")
tslang_add_test(NAME test-jit-00-throw-f32 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_f32.ts")
tslang_add_test(NAME test-jit-00-throw-u32 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_u32.ts")
tslang_add_test(NAME test-jit-00-cast-to-f32 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00cast_to_f32.ts")
Expand Down Expand Up @@ -2871,6 +2873,7 @@ set(TSLANG_CORPUS
00throw_in_catch.ts
00throw_bool_bigint.ts
00int64_remainder.ts
00int_remainder.ts
00throw_f32.ts
00throw_u32.ts
00throw_in_block_debug_info.ts
Expand Down
3 changes: 2 additions & 1 deletion tslang/test/tester/own/own_interface_view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ function make(k: number): I {
function main() {
let t = 0;
for (let i = 0; i < 20000; i++) {
const n = i % 10;
// a number: `i % 10` of two integers is an integer, and the field `x` below is the interface's number
const n: number = i % 10;

// a temporary class instance given as an interface
t += show(new C(n));
Expand Down
2 changes: 1 addition & 1 deletion tslang/test/tester/tests/00int64_remainder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function main() {
assert(s % 256 == minus109);
assert(s % 1000000007 == minus340597691);

// narrower integers keep the number path
// narrower integers: an integer remainder too (00int_remainder.ts)
let i: s32 = 1000;
assert(i % 7 == 6);
let n: number = 7.5;
Expand Down
93 changes: 93 additions & 0 deletions tslang/test/tester/tests/00int_remainder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// regression test: `%` of two integers of any width is an integer remainder with an integer result.
//
// bug found and fixed: only a 64-bit operand made `%` an integer remainder (00int64_remainder.ts);
// two narrower ones were cast to number, so a hash modulo the length of a table - every lookup of a
// Map - was an fmod call and two conversions, and modulo a length of 0 it was NaN made into an index.

function bucket(hashCode: int, buckets: int[]): int {
return buckets[<uint>hashCode % <uint>buckets.length];
}

function main() {
// signed: the remainder takes the dividend's sign
let i: s32 = 1000;
assert(i % 7 == 6);
let minus: s32 = -1000;
assert(minus % 7 == -6);
assert(i % -7 == 6);
assert(minus % -7 == -6);

// the result is an integer: it indexes, and it stays one in an integer
const r = i % 7;
const values: s32[] = [0, 1, 2, 3, 4, 5, 6];
assert(values[r] == 6);
let kept: s32 = i % 7;
assert(kept == 6);

// unsigned: all 32 bits set is not -1
let max: u32 = 4294967295;
assert(max % 10 == 5);
assert(max % 7 == 3);
let top: u32 = 2147483648;
const big: u32 = 3000000000;
assert(big % top == 852516352);

// a literal takes the other operand's type, on either side
let u: u32 = 4000000000;
assert(u % 1000 == 0);
assert(u % 7 == 3);
let seven: u32 = 7;
assert(4000000000 % seven == 3);

// a signed and an unsigned one: neither changes its value
const three: u32 = 3;
assert(minus % three == -1);
assert(max % minus == 295);
assert(u % -7 == 3);

// what `>>>` gives is unsigned: all 32 bits set, not -1
let all: s32 = -1;
assert((all >>> 0) % 5 == 0);
assert((all >>> 0) % seven == 3);
assert((all >>> 0) % i == 295);

// narrower ones
let b: u8 = 250;
let c: u8 = 7;
assert(b % c == 5);
let small: s8 = -100;
assert(small % 7 == -2);
let h: s16 = -30000;
let k: u16 = 60000;
assert(h % 7 == -5);
assert(k % 7 == 3);
assert(h % k == -30000);

// of different widths: the wider one
let wide: s64 = -5000000000;
assert(wide % i == 0);
assert(i % wide == 1000);

// compound
let acc: s32 = 1000;
acc %= 7;
assert(acc == 6);
let uacc: u32 = 4000000000;
uacc %= 7;
assert(uacc == 3);

// a hash modulo the length of a table
const buckets: int[] = [10, 11, 12, 13, 14];
assert(bucket(7, buckets) == 12);
assert(bucket(-1, buckets) == 10); // 4294967295 % 5

// a number on either side is still a number's remainder
let n: number = 7.5;
assert(n % 2 == 1.5);
assert(i % 2.5 == 0);
assert(7 % n == 7);
const zero: number = 0;
assert(isNaN(n % zero));

print("done.");
}
Loading