wasm: 128 bit intcast and binary operations

Also fixes some bugs in 128-bit binary comparisons where we checked
if the lsb were equal, rather than msb.
This commit is contained in:
Luuk de Gram 2022-05-12 21:53:57 +02:00
parent 167d3089ea
commit 03a3ea2c15

View File

@ -1958,25 +1958,31 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const lhs = try self.resolveInst(bin_op.lhs);
const rhs = try self.resolveInst(bin_op.rhs);
const operand_ty = self.air.typeOfIndex(inst);
const ty = self.air.typeOf(bin_op.lhs);
if (isByRef(operand_ty, self.target)) {
return self.fail("TODO: Implement binary operation for type: {}", .{operand_ty.fmtDebug()});
}
return self.binOp(lhs, rhs, ty, op);
}
fn binOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError!WValue {
try self.emitWValue(lhs);
try self.emitWValue(rhs);
if (isByRef(ty, self.target)) {
if (ty.zigTypeTag() == .Int) {
return self.binOpBigInt(lhs, rhs, ty, op);
} else {
return self.fail(
"TODO: Implement binary operation for type: {}",
.{ty.fmt(self.bin_file.base.options.module.?)},
);
}
}
const opcode: wasm.Opcode = buildOpcode(.{
.op = op,
.valtype1 = typeToValtype(ty, self.target),
.signedness = if (ty.isSignedInt()) .signed else .unsigned,
});
try self.emitWValue(lhs);
try self.emitWValue(rhs);
try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
// save the result in a temporary
@ -1985,6 +1991,37 @@ fn binOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError!WVa
return bin_local;
}
fn binOpBigInt(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError!WValue {
if (ty.intInfo(self.target).bits != 128) {
return self.fail("TODO: Implement binary operation for big integer", .{});
}
if (op != .add and op != .sub) {
return self.fail("TODO: Implement binary operation for big integers", .{});
}
const result = try self.allocStack(ty);
const lhs_high_bit = try self.load(lhs, Type.u64, 0);
const lhs_low_bit = try self.load(lhs, Type.u64, 8);
const rhs_high_bit = try self.load(rhs, Type.u64, 0);
const rhs_low_bit = try self.load(rhs, Type.u64, 8);
const low_op_res = try self.binOp(rhs_low_bit, lhs_low_bit, Type.u64, op);
const high_op_res = try self.binOp(lhs_high_bit, rhs_high_bit, Type.u64, op);
const lt = if (op == .add) blk: {
break :blk try self.cmp(high_op_res, rhs_high_bit, Type.u64, .lt);
} else if (op == .sub) blk: {
break :blk try self.cmp(rhs_high_bit, lhs_high_bit, Type.u64, .lt);
} else unreachable;
const tmp = try self.intcast(lt, Type.u32, Type.u64);
const tmp_op = try self.binOp(low_op_res, tmp, Type.u64, op);
try self.store(result, high_op_res, Type.u64, 0);
try self.store(result, tmp_op, Type.u64, 8);
return result;
}
fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const lhs = try self.resolveInst(bin_op.lhs);
@ -1993,10 +2030,6 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
const ty = self.air.typeOf(bin_op.lhs);
if (ty.zigTypeTag() == .Vector) {
return self.fail("TODO: Implement wrapping arithmetic for vectors", .{});
} else if (ty.abiSize(self.target) > 8 and op == .mul) {
return self.fail("TODO: Implement wrapping multiplication for bitsize > 64", .{});
} else if (ty.abiSize(self.target) > 16) {
return self.fail("TODO: Implement wrapping arithmetic for bitsize > 128", .{});
}
return self.wrapBinOp(lhs, rhs, ty, op);
@ -2007,30 +2040,9 @@ fn wrapBinOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError
var wasm_bits = toWasmBits(bit_size) orelse {
return self.fail("TODO: Implement wrapping arithmetic for integers with bitsize: {d}\n", .{bit_size});
};
if (wasm_bits == 128) {
if (op == .mul) return self.fail("TODO: Implement wrapping multiplication for 128bit integers", .{});
if (bit_size != wasm_bits) return self.fail("TODO: Implement wrapping arithmetic for integers > 64 & < 128", .{});
const result = try self.allocStack(ty);
const lhs_high_bit = try self.load(lhs, Type.u64, 0);
const lhs_low_bit = try self.load(lhs, Type.u64, 8);
const rhs_high_bit = try self.load(rhs, Type.u64, 0);
const rhs_low_bit = try self.load(rhs, Type.u64, 8);
const low_op_res = try self.binOp(rhs_low_bit, lhs_low_bit, Type.u64, op);
const high_op_res = try self.binOp(lhs_high_bit, rhs_high_bit, Type.u64, op);
const lt = if (op == .add) blk: {
break :blk try self.binOp(high_op_res, rhs_high_bit, Type.u64, .lt);
} else if (op == .sub) blk: {
break :blk try self.binOp(rhs_high_bit, lhs_high_bit, Type.u64, .lt);
} else unreachable;
const tmp = try self.intcast(lt, Type.u32, Type.u64);
const tmp_op = try self.binOp(low_op_res, tmp, Type.u64, op);
try self.store(result, high_op_res, Type.u64, 0);
try self.store(result, tmp_op, Type.u64, 8);
return result;
return self.binOp(lhs, rhs, ty, op);
}
const opcode: wasm.Opcode = buildOpcode(.{
@ -2044,6 +2056,10 @@ fn wrapBinOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError
try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
const bin_local = try self.allocLocal(ty);
try self.addLabel(.local_set, bin_local.local);
if (wasm_bits == bit_size) {
return bin_local;
}
return self.wrapOperand(bin_local, ty);
}
@ -2994,7 +3010,7 @@ fn intcast(self: *Self, operand: WValue, given: Type, wanted: Type) InnerError!W
try self.store(stack_ptr, .{ .imm64 = 0 }, Type.u64, 8);
}
return stack_ptr;
} else return self.fail("todo Wasm @intCast to 128bit integers", .{});
} else return self.load(operand, wanted, 0);
const result = try self.allocLocal(wanted);
try self.addLabel(.local_set, result.local);
@ -3760,20 +3776,20 @@ fn cmpBigInt(self: *Self, lhs: WValue, rhs: WValue, operand_ty: Type, op: std.ma
const or_result = try self.binOp(xor_high, xor_low, Type.u64, .@"or");
switch (op) {
.eq => return self.cmp(or_result, .{ .imm32 = 0 }, Type.u32, .eq),
.neq => return self.cmp(or_result, .{ .imm32 = 0 }, Type.u32, .neq),
.eq => return self.cmp(or_result, .{ .imm64 = 0 }, Type.u64, .eq),
.neq => return self.cmp(or_result, .{ .imm64 = 0 }, Type.u64, .neq),
else => unreachable,
}
},
else => {
const ty = if (operand_ty.isSignedInt()) Type.i64 else Type.u64;
const low_bit_eql = try self.cmp(lhs_low_bit, rhs_low_bit, ty, .eq);
const high_bit_eql = try self.cmp(lhs_high_bit, rhs_high_bit, ty, .eq);
const high_bit_cmp = try self.cmp(lhs_high_bit, rhs_high_bit, ty, op);
const low_bit_cmp = try self.cmp(lhs_low_bit, rhs_low_bit, ty, op);
try self.emitWValue(low_bit_cmp);
try self.emitWValue(high_bit_cmp);
try self.emitWValue(low_bit_eql);
try self.emitWValue(high_bit_eql);
try self.addTag(.select);
},
}