diff options
| author | Luuk de Gram <luuk@degram.dev> | 2023-05-06 17:10:48 +0200 |
|---|---|---|
| committer | Luuk de Gram <luuk@degram.dev> | 2023-05-19 20:18:59 +0200 |
| commit | e20976b7f209a768cb55a37e6a58ed177d76013e (patch) | |
| tree | 53a20b15a36ac7423d2e36a1f743b8b44c0243b6 /src/arch/wasm/CodeGen.zig | |
| parent | d353d208e295a01d6f844ccdb7e641a94e6fcb11 (diff) | |
| download | zig-e20976b7f209a768cb55a37e6a58ed177d76013e.tar.gz zig-e20976b7f209a768cb55a37e6a58ed177d76013e.zip | |
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.
Diffstat (limited to 'src/arch/wasm/CodeGen.zig')
| -rw-r--r-- | src/arch/wasm/CodeGen.zig | 62 |
1 files changed, 55 insertions, 7 deletions
diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index afe66c504e..799c4a40a2 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -2523,10 +2523,34 @@ fn airBinOp(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void { const bin_op = func.air.instructions.items(.data)[inst].bin_op; const lhs = try func.resolveInst(bin_op.lhs); const rhs = try func.resolveInst(bin_op.rhs); - const ty = func.air.typeOf(bin_op.lhs); + const lhs_ty = func.air.typeOf(bin_op.lhs); + const rhs_ty = func.air.typeOf(bin_op.rhs); + + // For certain operations, such as shifting, the types are different. + // When converting this to a WebAssembly type, they *must* match to perform + // an operation. For this reason we verify if the WebAssembly type is different, in which + // case we first coerce the operands to the same type before performing the operation. + // For big integers we can ignore this as we will call into compiler-rt which handles this. + const result = switch (op) { + .shr, .shl => res: { + const lhs_wasm_bits = toWasmBits(@intCast(u16, lhs_ty.bitSize(func.target))) orelse { + return func.fail("TODO: implement '{s}' for types larger than 128 bits", .{@tagName(op)}); + }; + const rhs_wasm_bits = toWasmBits(@intCast(u16, rhs_ty.bitSize(func.target))).?; + const new_rhs = if (lhs_wasm_bits != rhs_wasm_bits and lhs_wasm_bits != 128) blk: { + const tmp = try func.intcast(rhs, rhs_ty, lhs_ty); + break :blk try tmp.toLocal(func, lhs_ty); + } else rhs; + const stack_result = try func.binOp(lhs, new_rhs, lhs_ty, op); + break :res try stack_result.toLocal(func, lhs_ty); + }, + else => res: { + const stack_result = try func.binOp(lhs, rhs, lhs_ty, op); + break :res try stack_result.toLocal(func, lhs_ty); + }, + }; - const stack_value = try func.binOp(lhs, rhs, ty, op); - func.finishAir(inst, try stack_value.toLocal(func, ty), &.{ bin_op.lhs, bin_op.rhs }); + func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); } /// 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 { const lhs = try func.resolveInst(bin_op.lhs); const rhs = try func.resolveInst(bin_op.rhs); - const ty = func.air.typeOf(bin_op.lhs); + const lhs_ty = func.air.typeOf(bin_op.lhs); + const rhs_ty = func.air.typeOf(bin_op.rhs); - if (ty.zigTypeTag() == .Vector) { + if (lhs_ty.zigTypeTag() == .Vector or rhs_ty.zigTypeTag() == .Vector) { return func.fail("TODO: Implement wrapping arithmetic for vectors", .{}); } - const result = try (try func.wrapBinOp(lhs, rhs, ty, op)).toLocal(func, ty); - func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); + // For certain operations, such as shifting, the types are different. + // When converting this to a WebAssembly type, they *must* match to perform + // an operation. For this reason we verify if the WebAssembly type is different, in which + // case we first coerce the operands to the same type before performing the operation. + // For big integers we can ignore this as we will call into compiler-rt which handles this. + const result = switch (op) { + .shr, .shl => res: { + const lhs_wasm_bits = toWasmBits(@intCast(u16, lhs_ty.bitSize(func.target))) orelse { + return func.fail("TODO: implement '{s}' for types larger than 128 bits", .{@tagName(op)}); + }; + const rhs_wasm_bits = toWasmBits(@intCast(u16, rhs_ty.bitSize(func.target))).?; + const new_rhs = if (lhs_wasm_bits != rhs_wasm_bits and lhs_wasm_bits != 128) blk: { + const tmp = try func.intcast(rhs, rhs_ty, lhs_ty); + break :blk try tmp.toLocal(func, lhs_ty); + } else rhs; + const stack_result = try func.wrapBinOp(lhs, new_rhs, lhs_ty, op); + break :res try stack_result.toLocal(func, lhs_ty); + }, + else => res: { + const stack_result = try func.wrapBinOp(lhs, rhs, lhs_ty, op); + break :res try stack_result.toLocal(func, lhs_ty); + }, + }; + + return func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); } /// Performs a wrapping binary operation. |
