wasm: Resolve feedback (wrapping arbitrary int sizes)

- This ensures we honor the user's integer size when performing wrapping operations.
- Also, instead of using ensureCapacity, we now use ensureUnusedCapacity.
This commit is contained in:
Luuk de Gram 2021-08-01 11:05:15 +02:00
parent a861b7d160
commit 6e139d124b
No known key found for this signature in database
GPG Key ID: A8CFE58E4DC7D664
2 changed files with 104 additions and 6 deletions

View File

@ -634,7 +634,7 @@ pub const Context = struct {
// for each struct field, generate a local
const struct_data: *Module.Struct = ty.castTag(.@"struct").?.data;
const fields_len = @intCast(u32, struct_data.fields.count());
try self.locals.ensureCapacity(self.gpa, self.locals.items.len + fields_len);
try self.locals.ensureUnusedCapacity(self.gpa, fields_len);
for (struct_data.fields.values()) |*value| {
const val_type = try self.genValtype(value.ty);
self.locals.appendAssumeCapacity(val_type);
@ -653,7 +653,7 @@ pub const Context = struct {
// The first local is also used to find the index of the error and payload.
//
// TODO: Add support where the payload is a type that contains multiple locals such as a struct.
try self.locals.ensureCapacity(self.gpa, self.locals.items.len + 2);
try self.locals.ensureUnusedCapacity(self.gpa, 2);
self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32
self.locals.appendAssumeCapacity(val_type);
self.local_index += 2;
@ -670,7 +670,7 @@ pub const Context = struct {
return self.fail("TODO: wasm optional pointer", .{});
}
try self.locals.ensureCapacity(self.gpa, self.locals.items.len + 2);
try self.locals.ensureUnusedCapacity(self.gpa, 2);
self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // optional 'tag' for null-checking is always i32
self.locals.appendAssumeCapacity(try self.genValtype(child_type));
self.local_index += 2;
@ -817,11 +817,11 @@ pub const Context = struct {
const air_tags = self.air.instructions.items(.tag);
return switch (air_tags[inst]) {
.add => self.airBinOp(inst, .add),
.addwrap => self.airBinOp(inst, .add),
.addwrap => self.airWrapBinOp(inst, .add),
.sub => self.airBinOp(inst, .sub),
.subwrap => self.airBinOp(inst, .sub),
.subwrap => self.airWrapBinOp(inst, .sub),
.mul => self.airBinOp(inst, .mul),
.mulwrap => self.airBinOp(inst, .mul),
.mulwrap => self.airWrapBinOp(inst, .mul),
.div => self.airBinOp(inst, .div),
.bit_and => self.airBinOp(inst, .@"and"),
.bit_or => self.airBinOp(inst, .@"or"),
@ -1021,6 +1021,62 @@ pub const Context = struct {
return WValue{ .code_offset = offset };
}
fn airWrapBinOp(self: *Context, inst: Air.Inst.Index, op: Op) InnerError!WValue {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const lhs = self.resolveInst(bin_op.lhs);
const rhs = self.resolveInst(bin_op.rhs);
// it's possible for both lhs and/or rhs to return an offset as well,
// in which case we return the first offset occurance we find.
const offset = blk: {
if (lhs == .code_offset) break :blk lhs.code_offset;
if (rhs == .code_offset) break :blk rhs.code_offset;
break :blk self.code.items.len;
};
try self.emitWValue(lhs);
try self.emitWValue(rhs);
const bin_ty = self.air.typeOf(bin_op.lhs);
const opcode: wasm.Opcode = buildOpcode(.{
.op = op,
.valtype1 = try self.typeToValtype(bin_ty),
.signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
});
try self.code.append(wasm.opcode(opcode));
const int_info = bin_ty.intInfo(self.target);
const bitsize = int_info.bits;
const is_signed = int_info.signedness == .signed;
// if target type bitsize is x < 32 and 32 > x < 64, we perform
// result & ((1<<N)-1) where N = bitsize or bitsize -1 incase of signed.
if (bitsize != 32 and bitsize < 64) {
// first check if we can use a single instruction,
// wasm provides those if the integers are signed and 8/16-bit.
// For arbitrary integer sizes, we use the algorithm mentioned above.
if (is_signed and bitsize == 8) {
try self.code.append(wasm.opcode(.i32_extend8_s));
} else if (is_signed and bitsize == 16) {
try self.code.append(wasm.opcode(.i32_extend16_s));
} else {
const result = (@as(u64, 1) << @intCast(u6, bitsize - @boolToInt(is_signed))) - 1;
if (bitsize < 32) {
try self.code.append(wasm.opcode(.i32_const));
try leb.writeILEB128(self.code.writer(), @bitCast(i32, @intCast(u32, result)));
try self.code.append(wasm.opcode(.i32_and));
} else {
try self.code.append(wasm.opcode(.i64_const));
try leb.writeILEB128(self.code.writer(), @bitCast(i64, result));
try self.code.append(wasm.opcode(.i64_and));
}
}
} else if (int_info.bits > 64) {
return self.fail("TODO wasm: Integer wrapping for bitsizes larger than 64", .{});
}
return WValue{ .code_offset = offset };
}
fn emitConstant(self: *Context, val: Value, ty: Type) InnerError!void {
const writer = self.code.writer();
switch (ty.zigTypeTag()) {

View File

@ -120,6 +120,20 @@ pub fn addCases(ctx: *TestContext) !void {
\\}
, "-2147483648\n");
case.addCompareOutput(
\\pub export fn _start() i32 {
\\ var i: i4 = 7;
\\ return i +% 1;
\\}
, "0\n");
case.addCompareOutput(
\\pub export fn _start() u32 {
\\ var i: u8 = 255;
\\ return i +% 1;
\\}
, "0\n");
case.addCompareOutput(
\\pub export fn _start() u32 {
\\ var i: u32 = 5;
@ -147,6 +161,20 @@ pub fn addCases(ctx: *TestContext) !void {
\\}
, "2147483647\n");
case.addCompareOutput(
\\pub export fn _start() i32 {
\\ var i: i7 = -64;
\\ return i -% 1;
\\}
, "63\n");
case.addCompareOutput(
\\pub export fn _start() u32 {
\\ var i: u4 = 0;
\\ return i -% 1;
\\}
, "15\n");
case.addCompareOutput(
\\pub export fn _start() u32 {
\\ var i: u32 = 5;
@ -178,6 +206,20 @@ pub fn addCases(ctx: *TestContext) !void {
\\}
, "-2\n");
case.addCompareOutput(
\\pub export fn _start() u32 {
\\ var i: u3 = 3;
\\ return i *% 3;
\\}
, "1\n");
case.addCompareOutput(
\\pub export fn _start() i32 {
\\ var i: i4 = 3;
\\ return i *% 3;
\\}
, "1\n");
case.addCompareOutput(
\\pub export fn _start() u32 {
\\ var i: u32 = 352;