diff --git a/tslang/lib/TypeScript/LowerToLLVM.cpp b/tslang/lib/TypeScript/LowerToLLVM.cpp index 77ab06349..e77719b1a 100644 --- a/tslang/lib/TypeScript/LowerToLLVM.cpp +++ b/tslang/lib/TypeScript/LowerToLLVM.cpp @@ -4005,6 +4005,13 @@ struct ArithmeticBinaryOpLowering : public TsLlvmPattern( + arithmeticBinaryOp, transformed.getOperand1(), maskShiftAmount(arithmeticBinaryOp->getLoc(), transformed.getOperand2(), rewriter), rewriter); + } + return BinOp( arithmeticBinaryOp, transformed.getOperand1(), maskShiftAmount(arithmeticBinaryOp->getLoc(), transformed.getOperand2(), rewriter), rewriter); diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index ff6123a2c..7130e7dfb 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -5744,7 +5744,9 @@ class MLIRGenImpl result = leftInt << rightInt.urem(leftInt.getBitWidth()); break; case SyntaxKind::GreaterThanGreaterThanToken: - result = leftInt.ashr(rightInt.urem(leftInt.getBitWidth())); + result = resultType.isUnsignedInteger() + ? leftInt.lshr(rightInt.urem(leftInt.getBitWidth())) + : leftInt.ashr(rightInt.urem(leftInt.getBitWidth())); break; case SyntaxKind::GreaterThanGreaterThanGreaterThanToken: result = leftInt.lshr(rightInt.urem(leftInt.getBitWidth())); @@ -6733,18 +6735,31 @@ class MLIRGenImpl case SyntaxKind::AmpersandToken: case SyntaxKind::BarToken: case SyntaxKind::CaretToken: - // cast to int - if (leftExpressionValue.getType() != builder.getI32Type()) + { + // cast to int: JavaScript's Int32 coercion, but a 64-bit integer left operand keeps its + // type (and signedness), the right one is cast to it. Cast to i32, `u64 << 40` shifted + // by 40 mod 32 and dropped the upper half. + mlir::Type intType = builder.getI32Type(); + if (auto leftIntType = dyn_cast(leftExpressionValue.getType())) { - CAST(leftExpressionValue, location, builder.getI32Type(), leftExpressionValue, genContext); + if (leftIntType.getWidth() > 32) + { + intType = leftIntType; + } } - if (rightExpressionValue.getType() != builder.getI32Type()) + if (leftExpressionValue.getType() != intType) { - CAST(rightExpressionValue, location, builder.getI32Type(), rightExpressionValue, genContext); + CAST(leftExpressionValue, location, intType, leftExpressionValue, genContext); + } + + if (rightExpressionValue.getType() != intType) + { + CAST(rightExpressionValue, location, intType, rightExpressionValue, genContext); } break; + } case SyntaxKind::SlashToken: case SyntaxKind::PercentToken: case SyntaxKind::AsteriskAsteriskToken: diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index f1d97fc0d..105610c15 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -271,6 +271,7 @@ tslang_add_test(NAME test-compile-01-enums COMMAND test-runner "${PROJECT_SOURCE tslang_add_test(NAME test-compile-00-numbers COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00numbers.ts") tslang_add_test(NAME test-compile-00-equals COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00equals.ts") tslang_add_test(NAME test-compile-00-bool-arith-ops COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00bool_arith_ops.ts") +tslang_add_test(NAME test-compile-00-int64-shift-bitwise COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00int64_shift_bitwise.ts") tslang_add_test(NAME test-compile-00-bool-const-array COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00bool_const_array.ts") tslang_add_test(NAME test-compile-00-mixed-type-ops COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00mixed_type_ops.ts") tslang_add_test(NAME test-compile-00-generator-manual-next COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_manual_next.ts") @@ -802,6 +803,7 @@ tslang_add_test(NAME test-jit-01-enums COMMAND test-runner -jit "${PROJECT_SOURC tslang_add_test(NAME test-jit-00-numbers COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00numbers.ts") tslang_add_test(NAME test-jit-00-equals COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00equals.ts") tslang_add_test(NAME test-jit-00-bool-arith-ops COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00bool_arith_ops.ts") +tslang_add_test(NAME test-jit-00-int64-shift-bitwise COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00int64_shift_bitwise.ts") tslang_add_test(NAME test-jit-00-bool-const-array COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00bool_const_array.ts") tslang_add_test(NAME test-jit-00-mixed-type-ops COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00mixed_type_ops.ts") tslang_add_test(NAME test-jit-00-generator-manual-next COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_manual_next.ts") @@ -1789,6 +1791,7 @@ set(TSLANG_CORPUS 00async_gc_threading.ts 00async_result_types.ts 00bool_arith_ops.ts + 00int64_shift_bitwise.ts 00bool_const_array.ts 00break_continue_scope_exit.ts 00break_continue.ts diff --git a/tslang/test/tester/tests/00int64_shift_bitwise.ts b/tslang/test/tester/tests/00int64_shift_bitwise.ts new file mode 100644 index 000000000..d83939679 --- /dev/null +++ b/tslang/test/tester/tests/00int64_shift_bitwise.ts @@ -0,0 +1,70 @@ +// regression test: shifts and bitwise operators on 64-bit integers keep the 64-bit width. +// +// bug found and fixed: `<<`, `>>`, `>>>`, `&`, `|` and `^` cast both operands to i32 (the +// JavaScript Int32 coercion of `number`) whatever their type, so `u64 << 40` shifted by +// 40 mod 32 = 8 and gave 256, and the upper half of a u64 was lost in `&`/`|`/`^`. Also `>>` +// of an unsigned operand shifted the sign bit in. + +function main() { + let a: u64 = 1; + const twoTo40: u64 = 1099511627776; + const twoTo33: u64 = 8589934592; + + const b = a << 40; + print(b); + assert(b == twoTo40); + + let c: u64 = 1; + c = c << 33; + assert(c == twoTo33); + + let g: u64 = 1; + g <<= 40; + assert(g == twoTo40); + + let i: s64 = 1; + const twoTo40s: s64 = 1099511627776; + assert(i << 40 == twoTo40s); + + // right shifts of a 64-bit value + const one: u64 = 1; + let f: u64 = 1099511627776; + const r1: u64 = 128; + assert(f >> 33 == r1); + assert(f >>> 33 == r1); + + // an unsigned `>>` shifts zeros in + let h: u64 = 1; + h = h << 63; + assert(h >> 63 == one); + assert(h >>> 63 == one); + + const three: u64 = 3; + let k: u64 = 3; + assert(k << 62 >> 62 == three); + + // a signed `>>` keeps the sign, `>>>` doesn't + let s: s64 = -8; + const minusFour: s64 = -4; + const fifteen: s64 = 15; + assert(s >> 1 == minusFour); + assert(s >>> 60 == fifteen); + + // bitwise operators keep the upper half + let m: u64 = 0xFFFFFFFFFF; + const upper: u64 = 0xFF00000000; + const zero: u64 = 0; + assert((m & 0xFF00000000) == upper); + assert((m | 1) == m); + assert((m ^ m) == zero); + + // a `number` operand still shifts by its count mod 32, as in JavaScript + let n = 1; + assert(n << 33 == 2); + assert(1 << 33 == 2); + let x = -8; + assert(x >> 1 == -4); + assert(x >>> 28 == 15); + + print("done."); +}