diff --git a/tslang/lib/TypeScript/LowerToLLVM.cpp b/tslang/lib/TypeScript/LowerToLLVM.cpp index 5efe68599..33f6c8ab1 100644 --- a/tslang/lib/TypeScript/LowerToLLVM.cpp +++ b/tslang/lib/TypeScript/LowerToLLVM.cpp @@ -4036,6 +4036,13 @@ struct ArithmeticBinaryOpLowering : public TsLlvmPattern(arithmeticBinaryOp, transformed.getOperand1(), + transformed.getOperand2(), rewriter); + } + return BinOp(arithmeticBinaryOp, transformed.getOperand1(), transformed.getOperand2(), rewriter); diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index c4b942158..78def80d8 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -6798,8 +6798,32 @@ class MLIRGenImpl break; } - case SyntaxKind::SlashToken: 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. + auto leftIntType = dyn_cast(leftExpressionValue.getType()); + auto rightIntType = dyn_cast(rightExpressionValue.getType()); + if (leftIntType && rightIntType && (leftIntType.getWidth() > 32 || rightIntType.getWidth() > 32)) + { + mlir::Type intType = leftIntType.getWidth() >= rightIntType.getWidth() ? leftIntType : rightIntType; + if (leftExpressionValue.getType() != intType) + { + CAST(leftExpressionValue, location, intType, leftExpressionValue, genContext); + } + + if (rightExpressionValue.getType() != intType) + { + CAST(rightExpressionValue, location, intType, rightExpressionValue, genContext); + } + + break; + } + + [[fallthrough]]; + } + case SyntaxKind::SlashToken: case SyntaxKind::AsteriskAsteriskToken: // TODO: should it be int type especially PercentToken? diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 891d8b56e..df7c1d891 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -461,6 +461,7 @@ tslang_add_test(NAME test-compile-00-throw-number-untyped-catch COMMAND test-run tslang_add_test(NAME test-compile-00-throw-in-block-debug-info COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_in_block_debug_info.ts") 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-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") @@ -992,6 +993,7 @@ tslang_add_test(NAME test-jit-00-throw-number-untyped-catch COMMAND test-runner tslang_add_test(NAME test-jit-00-throw-in-block-debug-info COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_in_block_debug_info.ts") 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-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") @@ -2048,6 +2050,7 @@ set(TSLANG_CORPUS 00symbol.ts 00throw_in_catch.ts 00throw_bool_bigint.ts + 00int64_remainder.ts 00throw_f32.ts 00throw_u32.ts 00throw_in_block_debug_info.ts diff --git a/tslang/test/tester/tests/00int64_remainder.ts b/tslang/test/tester/tests/00int64_remainder.ts new file mode 100644 index 000000000..113103a49 --- /dev/null +++ b/tslang/test/tester/tests/00int64_remainder.ts @@ -0,0 +1,48 @@ +// regression test: `%` of 64-bit integers is an integer remainder. +// +// bug found and fixed: `%` cast both operands to number (f64), so a u64 remainder was wrong once the +// value passed 2^53 (the low byte of "main_vs\0" read 108 instead of 109). An unsigned operand +// also has to take the unsigned remainder. + +function main() { + const BYTE: u64 = 256; + let w: u64 = 32499774568096109; // the bytes of "main_vs\0", little-endian + const m: u64 = 109; + assert(w % BYTE == m); + assert(w % 256 == m); + + let r: u64 = 32499774568096109; + r %= 256; + assert(r == m); + + // read every byte back + const expected = [109, 97, 105, 110, 95, 118, 115, 0]; + let word: u64 = 32499774568096109; + for (const e of expected) { + const byte: u64 = e; + assert(word % BYTE == byte); + word = word >> 8; + } + + // all 64 bits set: unsigned, not -1 + let max: u64 = 18446744073709551615; + const five: u64 = 5; + const one: u64 = 1; + assert(max % 10 == five); + assert(max % 7 == one); + + // signed: the remainder takes the dividend's sign + let s: s64 = -32499774568096109; + const minus109: s64 = -109; + const minus340597691: s64 = -340597691; + assert(s % 256 == minus109); + assert(s % 1000000007 == minus340597691); + + // narrower integers keep the number path + let i: s32 = 1000; + assert(i % 7 == 6); + let n: number = 7.5; + assert(n % 2 == 1.5); + + print("done."); +}