authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-05-06 17:10:48+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-05-19 20:18:59+02:00
loge20976b7f209a768cb55a37e6a58ed177d76013e
tree53a20b15a36ac7423d2e36a1f743b8b44c0243b6
parentd353d208e295a01d6f844ccdb7e641a94e6fcb11
signaturelock-open Commit is signed but in an unrecognized format.

wasm: fix miscompilation for shifting

This fix ensures that when we are shifting left or right, both operands have the same WebAssembly type. e.g. it's not possible to shift a 64 bit integer and 32 bit integer together and will fail WebAssembly's validator. By first coercing the values to the same type, we ensure we satisfy the validator.

1 files changed, 55 insertions(+), 7 deletions(-)

src/arch/wasm/CodeGen.zig+55-7
...@@ -2523,10 +2523,34 @@ fn airBinOp(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void {...@@ -2523,10 +2523,34 @@ fn airBinOp(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void {
2523 const bin_op = func.air.instructions.items(.data)[inst].bin_op;2523 const bin_op = func.air.instructions.items(.data)[inst].bin_op;
2524 const lhs = try func.resolveInst(bin_op.lhs);2524 const lhs = try func.resolveInst(bin_op.lhs);
2525 const rhs = try func.resolveInst(bin_op.rhs);2525 const rhs = try func.resolveInst(bin_op.rhs);
2526 const ty = func.air.typeOf(bin_op.lhs);2526 const lhs_ty = func.air.typeOf(bin_op.lhs);
2527 const rhs_ty = func.air.typeOf(bin_op.rhs);
2528
2529 // For certain operations, such as shifting, the types are different.
2530 // When converting this to a WebAssembly type, they *must* match to perform
2531 // an operation. For this reason we verify if the WebAssembly type is different, in which
2532 // case we first coerce the operands to the same type before performing the operation.
2533 // For big integers we can ignore this as we will call into compiler-rt which handles this.
2534 const result = switch (op) {
2535 .shr, .shl => res: {
2536 const lhs_wasm_bits = toWasmBits(@intCast(u16, lhs_ty.bitSize(func.target))) orelse {
2537 return func.fail("TODO: implement '{s}' for types larger than 128 bits", .{@tagName(op)});
2538 };
2539 const rhs_wasm_bits = toWasmBits(@intCast(u16, rhs_ty.bitSize(func.target))).?;
2540 const new_rhs = if (lhs_wasm_bits != rhs_wasm_bits and lhs_wasm_bits != 128) blk: {
2541 const tmp = try func.intcast(rhs, rhs_ty, lhs_ty);
2542 break :blk try tmp.toLocal(func, lhs_ty);
2543 } else rhs;
2544 const stack_result = try func.binOp(lhs, new_rhs, lhs_ty, op);
2545 break :res try stack_result.toLocal(func, lhs_ty);
2546 },
2547 else => res: {
2548 const stack_result = try func.binOp(lhs, rhs, lhs_ty, op);
2549 break :res try stack_result.toLocal(func, lhs_ty);
2550 },
2551 };
25272552
2528 const stack_value = try func.binOp(lhs, rhs, ty, op);2553 func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
2529 func.finishAir(inst, try stack_value.toLocal(func, ty), &.{ bin_op.lhs, bin_op.rhs });
2530}2554}
25312555
2532/// Performs a binary operation on the given `WValue`'s2556/// Performs a binary operation on the given `WValue`'s
...@@ -2769,14 +2793,38 @@ fn airWrapBinOp(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void {...@@ -2769,14 +2793,38 @@ fn airWrapBinOp(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void {
27692793
2770 const lhs = try func.resolveInst(bin_op.lhs);2794 const lhs = try func.resolveInst(bin_op.lhs);
2771 const rhs = try func.resolveInst(bin_op.rhs);2795 const rhs = try func.resolveInst(bin_op.rhs);
2772 const ty = func.air.typeOf(bin_op.lhs);2796 const lhs_ty = func.air.typeOf(bin_op.lhs);
2797 const rhs_ty = func.air.typeOf(bin_op.rhs);
27732798
2774 if (ty.zigTypeTag() == .Vector) {2799 if (lhs_ty.zigTypeTag() == .Vector or rhs_ty.zigTypeTag() == .Vector) {
2775 return func.fail("TODO: Implement wrapping arithmetic for vectors", .{});2800 return func.fail("TODO: Implement wrapping arithmetic for vectors", .{});
2776 }2801 }
27772802
2778 const result = try (try func.wrapBinOp(lhs, rhs, ty, op)).toLocal(func, ty);2803 // For certain operations, such as shifting, the types are different.
2779 func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });2804 // When converting this to a WebAssembly type, they *must* match to perform
2805 // an operation. For this reason we verify if the WebAssembly type is different, in which
2806 // case we first coerce the operands to the same type before performing the operation.
2807 // For big integers we can ignore this as we will call into compiler-rt which handles this.
2808 const result = switch (op) {
2809 .shr, .shl => res: {
2810 const lhs_wasm_bits = toWasmBits(@intCast(u16, lhs_ty.bitSize(func.target))) orelse {
2811 return func.fail("TODO: implement '{s}' for types larger than 128 bits", .{@tagName(op)});
2812 };
2813 const rhs_wasm_bits = toWasmBits(@intCast(u16, rhs_ty.bitSize(func.target))).?;
2814 const new_rhs = if (lhs_wasm_bits != rhs_wasm_bits and lhs_wasm_bits != 128) blk: {
2815 const tmp = try func.intcast(rhs, rhs_ty, lhs_ty);
2816 break :blk try tmp.toLocal(func, lhs_ty);
2817 } else rhs;
2818 const stack_result = try func.wrapBinOp(lhs, new_rhs, lhs_ty, op);
2819 break :res try stack_result.toLocal(func, lhs_ty);
2820 },
2821 else => res: {
2822 const stack_result = try func.wrapBinOp(lhs, rhs, lhs_ty, op);
2823 break :res try stack_result.toLocal(func, lhs_ty);
2824 },
2825 };
2826
2827 return func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
2780}2828}
27812829
2782/// Performs a wrapping binary operation.2830/// Performs a wrapping binary operation.