authorgravatar for paul.verigo@gmail.comPavel Verigo <paul.verigo@gmail.com> 2024-07-18 18:01:06+02:00
committergravatar for paul.verigo@gmail.comPavel Verigo <paul.verigo@gmail.com> 2024-07-18 18:01:06+02:00
log56d535dd24a16bcd827b54d151d6858a2c3ec3b5
tree75e1206499fd0d8cc614b218ad8f3c1595c9a29e
parentdc3176d6287955d2ecdaaa26f46f5577d5316d36

stage2-wasm: improve @shlWithOverflow for <= 128 bits

Additionally fixed a bug for shr on signed big ints

2 files changed, 63 insertions(+), 67 deletions(-)

src/arch/wasm/CodeGen.zig+23-21
...@@ -2669,8 +2669,14 @@ fn binOpBigInt(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: Op) Inner...@@ -2669,8 +2669,14 @@ fn binOpBigInt(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: Op) Inner
2669 .signed => return func.callIntrinsic("__udivti3", &.{ ty.toIntern(), ty.toIntern() }, ty, &.{ lhs, rhs }),2669 .signed => return func.callIntrinsic("__udivti3", &.{ ty.toIntern(), ty.toIntern() }, ty, &.{ lhs, rhs }),
2670 .unsigned => return func.callIntrinsic("__divti3", &.{ ty.toIntern(), ty.toIntern() }, ty, &.{ lhs, rhs }),2670 .unsigned => return func.callIntrinsic("__divti3", &.{ ty.toIntern(), ty.toIntern() }, ty, &.{ lhs, rhs }),
2671 },2671 },
2672 .rem => return func.callIntrinsic("__umodti3", &.{ ty.toIntern(), ty.toIntern() }, ty, &.{ lhs, rhs }),2672 .rem => switch (int_info.signedness) {
2673 .shr => return func.callIntrinsic("__lshrti3", &.{ ty.toIntern(), .i32_type }, ty, &.{ lhs, rhs }),2673 .signed => return func.callIntrinsic("__modti3", &.{ ty.toIntern(), ty.toIntern() }, ty, &.{ lhs, rhs }),
2674 .unsigned => return func.callIntrinsic("__umodti3", &.{ ty.toIntern(), ty.toIntern() }, ty, &.{ lhs, rhs }),
2675 },
2676 .shr => switch (int_info.signedness) {
2677 .signed => return func.callIntrinsic("__ashrti3", &.{ ty.toIntern(), .i32_type }, ty, &.{ lhs, rhs }),
2678 .unsigned => return func.callIntrinsic("__lshrti3", &.{ ty.toIntern(), .i32_type }, ty, &.{ lhs, rhs }),
2679 },
2674 .shl => return func.callIntrinsic("__ashlti3", &.{ ty.toIntern(), .i32_type }, ty, &.{ lhs, rhs }),2680 .shl => return func.callIntrinsic("__ashlti3", &.{ ty.toIntern(), .i32_type }, ty, &.{ lhs, rhs }),
2675 .@"and", .@"or", .xor => {2681 .@"and", .@"or", .xor => {
2676 const result = try func.allocStack(ty);2682 const result = try func.allocStack(ty);
...@@ -6055,14 +6061,14 @@ fn airShlWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -6055,14 +6061,14 @@ fn airShlWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
60556061
6056 const lhs = try func.resolveInst(extra.lhs);6062 const lhs = try func.resolveInst(extra.lhs);
6057 const rhs = try func.resolveInst(extra.rhs);6063 const rhs = try func.resolveInst(extra.rhs);
6058 const lhs_ty = func.typeOf(extra.lhs);6064 const ty = func.typeOf(extra.lhs);
6059 const rhs_ty = func.typeOf(extra.rhs);6065 const rhs_ty = func.typeOf(extra.rhs);
60606066
6061 if (lhs_ty.zigTypeTag(mod) == .Vector) {6067 if (ty.zigTypeTag(mod) == .Vector) {
6062 return func.fail("TODO: Implement overflow arithmetic for vectors", .{});6068 return func.fail("TODO: Implement overflow arithmetic for vectors", .{});
6063 }6069 }
60646070
6065 const int_info = lhs_ty.intInfo(mod);6071 const int_info = ty.intInfo(mod);
6066 const wasm_bits = toWasmBits(int_info.bits) orelse {6072 const wasm_bits = toWasmBits(int_info.bits) orelse {
6067 return func.fail("TODO: Implement shl_with_overflow for integer bitsize: {d}", .{int_info.bits});6073 return func.fail("TODO: Implement shl_with_overflow for integer bitsize: {d}", .{int_info.bits});
6068 };6074 };
...@@ -6070,32 +6076,28 @@ fn airShlWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -6070,32 +6076,28 @@ fn airShlWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
6070 // Ensure rhs is coerced to lhs as they must have the same WebAssembly types6076 // Ensure rhs is coerced to lhs as they must have the same WebAssembly types
6071 // before we can perform any binary operation.6077 // before we can perform any binary operation.
6072 const rhs_wasm_bits = toWasmBits(rhs_ty.intInfo(mod).bits).?;6078 const rhs_wasm_bits = toWasmBits(rhs_ty.intInfo(mod).bits).?;
6073 const rhs_final = if (wasm_bits != rhs_wasm_bits) blk: {6079 // If wasm_bits == 128, compiler-rt expects i32 for shift
6074 const rhs_casted = try func.intcast(rhs, rhs_ty, lhs_ty);6080 const rhs_final = if (wasm_bits != rhs_wasm_bits and wasm_bits == 64) blk: {
6075 break :blk try rhs_casted.toLocal(func, lhs_ty);6081 const rhs_casted = try func.intcast(rhs, rhs_ty, ty);
6082 break :blk try rhs_casted.toLocal(func, ty);
6076 } else rhs;6083 } else rhs;
60776084
6078 var shl = try (try func.binOp(lhs, rhs_final, lhs_ty, .shl)).toLocal(func, lhs_ty);6085 var shl = try (try func.wrapBinOp(lhs, rhs_final, ty, .shl)).toLocal(func, ty);
6079 defer shl.free(func);6086 defer shl.free(func);
6080 var result = if (wasm_bits != int_info.bits) blk: {
6081 break :blk try (try func.wrapOperand(shl, lhs_ty)).toLocal(func, lhs_ty);
6082 } else shl;
6083 defer result.free(func); // it's a no-op to free the same local twice (when wasm_bits == int_info.bits)
60846087
6085 const overflow_bit = blk: {6088 const overflow_bit = blk: {
6086 try func.emitWValue(lhs);6089 const shr = try func.binOp(shl, rhs_final, ty, .shr);
6087 const shr = try func.binOp(result, rhs_final, lhs_ty, .shr);6090 break :blk try func.cmp(shr, lhs, ty, .neq);
6088 break :blk try func.cmp(.stack, shr, lhs_ty, .neq);
6089 };6091 };
6090 var overflow_local = try overflow_bit.toLocal(func, Type.u1);6092 var overflow_local = try overflow_bit.toLocal(func, Type.u1);
6091 defer overflow_local.free(func);6093 defer overflow_local.free(func);
60926094
6093 const result_ptr = try func.allocStack(func.typeOfIndex(inst));6095 const result = try func.allocStack(func.typeOfIndex(inst));
6094 try func.store(result_ptr, result, lhs_ty, 0);6096 const offset: u32 = @intCast(ty.abiSize(pt));
6095 const offset = @as(u32, @intCast(lhs_ty.abiSize(pt)));6097 try func.store(result, shl, ty, 0);
6096 try func.store(result_ptr, overflow_local, Type.u1, offset);6098 try func.store(result, overflow_local, Type.u1, offset);
60976099
6098 return func.finishAir(inst, result_ptr, &.{ extra.lhs, extra.rhs });6100 return func.finishAir(inst, result, &.{ extra.lhs, extra.rhs });
6099}6101}
61006102
6101fn airMulWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {6103fn airMulWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
test/behavior/math.zig+40-46
...@@ -1345,62 +1345,56 @@ test "@subWithOverflow > 64 bits" {...@@ -1345,62 +1345,56 @@ test "@subWithOverflow > 64 bits" {
1345 try testSubWithOverflow(i128, maxInt(i128), -2, minInt(i128) + 1, 1);1345 try testSubWithOverflow(i128, maxInt(i128), -2, minInt(i128) + 1, 1);
1346}1346}
13471347
1348fn testShlWithOverflow(comptime T: type, a: T, b: math.Log2Int(T), shl: T, bit: u1) !void {
1349 const ov = @shlWithOverflow(a, b);
1350 try expect(ov[0] == shl);
1351 try expect(ov[1] == bit);
1352}
1353
1348test "@shlWithOverflow" {1354test "@shlWithOverflow" {
1349 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO1355 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1350 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1356 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1351 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;1357 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1352 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;1358 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
13531359
1354 {1360 try testShlWithOverflow(u4, 2, 1, 4, 0);
1355 var a: u4 = 2;1361 try testShlWithOverflow(u4, 2, 3, 0, 1);
1356 _ = &a;
1357 var b: u2 = 1;
1358 var ov = @shlWithOverflow(a, b);
1359 try expect(ov[0] == 4);
1360 try expect(ov[1] == 0);
13611362
1362 b = 3;1363 try testShlWithOverflow(i9, 127, 1, 254, 0);
1363 ov = @shlWithOverflow(a, b);1364 try testShlWithOverflow(i9, 127, 2, -4, 1);
1364 try expect(ov[0] == 0);
1365 try expect(ov[1] == 1);
1366 }
13671365
1368 {1366 try testShlWithOverflow(u16, 0b0010111111111111, 3, 0b0111111111111000, 1);
1369 var a: i9 = 127;1367 try testShlWithOverflow(u16, 0b0010111111111111, 2, 0b1011111111111100, 0);
1370 _ = &a;
1371 var b: u4 = 1;
1372 var ov = @shlWithOverflow(a, b);
1373 try expect(ov[0] == 254);
1374 try expect(ov[1] == 0);
13751368
1376 b = 2;1369 try testShlWithOverflow(u16, 0b0000_0000_0000_0011, 15, 0b1000_0000_0000_0000, 1);
1377 ov = @shlWithOverflow(a, b);1370 try testShlWithOverflow(u16, 0b0000_0000_0000_0011, 14, 0b1100_0000_0000_0000, 0);
1378 try expect(ov[0] == -4);1371}
1379 try expect(ov[1] == 1);
1380 }
13811372
1382 {1373test "@shlWithOverflow > 64 bits" {
1383 const ov = @shlWithOverflow(@as(u16, 0b0010111111111111), 3);1374 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1384 try expect(ov[0] == 0b0111111111111000);1375 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1385 try expect(ov[1] == 1);1376 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1386 }1377 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1387 {1378
1388 const ov = @shlWithOverflow(@as(u16, 0b0010111111111111), 2);1379 try testShlWithOverflow(u65, 0x0_0100_0000_0000_0000, 7, 0x0_8000_0000_0000_0000, 0);
1389 try expect(ov[0] == 0b1011111111111100);1380 try testShlWithOverflow(u65, 0x0_0100_0000_0000_0000, 8, 0x1_0000_0000_0000_0000, 0);
1390 try expect(ov[1] == 0);1381 try testShlWithOverflow(u65, 0x0_0100_0000_0000_0000, 9, 0, 1);
1391 }1382 try testShlWithOverflow(u65, 0x0_0100_0000_0000_0000, 10, 0, 1);
1392 {1383
1393 var a: u16 = 0b0000_0000_0000_0011;1384 try testShlWithOverflow(u128, 0x0100_0000_0000_0000_0000000000000000, 6, 0x4000_0000_0000_0000_0000000000000000, 0);
1394 _ = &a;1385 try testShlWithOverflow(u128, 0x0100_0000_0000_0000_0000000000000000, 7, 0x8000_0000_0000_0000_0000000000000000, 0);
1395 var b: u4 = 15;1386 try testShlWithOverflow(u128, 0x0100_0000_0000_0000_0000000000000000, 8, 0, 1);
1396 var ov = @shlWithOverflow(a, b);1387 try testShlWithOverflow(u128, 0x0100_0000_0000_0000_0000000000000000, 9, 0, 1);
1397 try expect(ov[0] == 0b1000_0000_0000_0000);1388
1398 try expect(ov[1] == 1);1389 try testShlWithOverflow(i65, 0x0_0100_0000_0000_0000, 7, 0x0_8000_0000_0000_0000, 0);
1399 b = 14;1390 try testShlWithOverflow(i65, 0x0_0100_0000_0000_0000, 8, minInt(i65), 1);
1400 ov = @shlWithOverflow(a, b);1391 try testShlWithOverflow(i65, 0x0_0100_0000_0000_0000, 9, 0, 1);
1401 try expect(ov[0] == 0b1100_0000_0000_0000);1392 try testShlWithOverflow(i65, 0x0_0100_0000_0000_0000, 10, 0, 1);
1402 try expect(ov[1] == 0);1393
1403 }1394 try testShlWithOverflow(i128, 0x0100_0000_0000_0000_0000000000000000, 6, 0x4000_0000_0000_0000_0000000000000000, 0);
1395 try testShlWithOverflow(i128, 0x0100_0000_0000_0000_0000000000000000, 7, minInt(i128), 1);
1396 try testShlWithOverflow(i128, 0x0100_0000_0000_0000_0000000000000000, 8, 0, 1);
1397 try testShlWithOverflow(i128, 0x0100_0000_0000_0000_0000000000000000, 9, 0, 1);
1404}1398}
14051399
1406test "overflow arithmetic with u0 values" {1400test "overflow arithmetic with u0 values" {