Merge pull request #9496 from Luukdegram/stage2-wasm

stage2: wasm - Wrapping, intcast and optionals
This commit is contained in:
Andrew Kelley 2021-08-01 20:33:55 -04:00 committed by GitHub
commit d5f173d28f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 326 additions and 7 deletions

View File

@ -162,8 +162,14 @@ pub const Opcode = enum(u8) {
i32_wrap_i64 = 0xA7,
i32_trunc_f32_s = 0xA8,
i32_trunc_f32_u = 0xA9,
i32_trunc_f64_s = 0xB0,
i32_trunc_f64_u = 0xB1,
i32_trunc_f64_s = 0xAA,
i32_trunc_f64_u = 0xAB,
i64_extend_i32_s = 0xAC,
i64_extend_i32_u = 0xAD,
i64_trunc_f32_s = 0xAE,
i64_trunc_f32_u = 0xAF,
i64_trunc_f64_s = 0xB0,
i64_trunc_f64_u = 0xB1,
f32_convert_i32_s = 0xB2,
f32_convert_i32_u = 0xB3,
f32_convert_i64_s = 0xB4,

View File

@ -590,8 +590,8 @@ pub const Context = struct {
.Pointer,
.ErrorSet,
=> wasm.Valtype.i32,
.Struct, .ErrorUnion => unreachable, // Multi typed, must be handled individually.
else => self.fail("TODO - Wasm valtype for type '{s}'", .{ty.zigTypeTag()}),
.Struct, .ErrorUnion, .Optional => unreachable, // Multi typed, must be handled individually.
else => |tag| self.fail("TODO - Wasm valtype for type '{s}'", .{tag}),
};
}
@ -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;
@ -663,6 +663,23 @@ pub const Context = struct {
.count = 2,
} };
},
.Optional => {
var opt_buf: Type.Payload.ElemType = undefined;
const child_type = ty.optionalChild(&opt_buf);
if (ty.isPtrLikeOptional()) {
return self.fail("TODO: wasm optional pointer", .{});
}
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;
return WValue{ .multi_value = .{
.index = initial_index,
.count = 2,
} };
},
else => {
const valtype = try self.genValtype(ty);
try self.locals.append(self.gpa, valtype);
@ -800,8 +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.airWrapBinOp(inst, .add),
.sub => self.airBinOp(inst, .sub),
.subwrap => self.airWrapBinOp(inst, .sub),
.mul => 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"),
@ -826,8 +846,16 @@ pub const Context = struct {
.cond_br => self.airCondBr(inst),
.constant => unreachable,
.dbg_stmt => WValue.none,
.intcast => self.airIntcast(inst),
.is_err => self.airIsErr(inst, .i32_ne),
.is_non_err => self.airIsErr(inst, .i32_eq),
.is_null => self.airIsNull(inst, .i32_ne),
.is_non_null => self.airIsNull(inst, .i32_eq),
.is_null_ptr => self.airIsNull(inst, .i32_ne),
.is_non_null_ptr => self.airIsNull(inst, .i32_eq),
.load => self.airLoad(inst),
.loop => self.airLoop(inst),
.not => self.airNot(inst),
@ -836,8 +864,13 @@ pub const Context = struct {
.struct_field_ptr => self.airStructFieldPtr(inst),
.switch_br => self.airSwitchBr(inst),
.unreach => self.airUnreachable(inst),
.wrap_optional => self.airWrapOptional(inst),
.unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst),
.wrap_errunion_payload => self.airWrapErrUnionPayload(inst),
.optional_payload => self.airOptionalPayload(inst),
.optional_payload_ptr => self.airOptionalPayload(inst),
else => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
};
}
@ -922,6 +955,22 @@ pub const Context = struct {
try leb.writeULEB128(writer, multi_value.index + i - 1);
}
},
.local => {
// This can occur when we wrap a single value into a multi-value,
// such as wrapping a non-optional value into an optional.
// This means we must zero the null-tag, and set the payload.
assert(multi_value.count == 2);
// set null-tag
try writer.writeByte(wasm.opcode(.i32_const));
try leb.writeULEB128(writer, @as(u32, 0));
try writer.writeByte(wasm.opcode(.local_set));
try leb.writeULEB128(writer, multi_value.index);
// set payload
try self.emitWValue(rhs);
try writer.writeByte(wasm.opcode(.local_set));
try leb.writeULEB128(writer, multi_value.index + 1);
},
else => unreachable,
},
.local => |local| {
@ -972,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()) {
@ -1084,6 +1189,31 @@ pub const Context = struct {
try self.emitConstant(data, payload_type);
}
},
.Optional => {
var buf: Type.Payload.ElemType = undefined;
const payload_type = ty.optionalChild(&buf);
if (ty.isPtrLikeOptional()) {
return self.fail("Wasm TODO: emitConstant for optional pointer", .{});
}
// When constant has value 'null', set is_null local to '1'
// and payload to '0'
if (val.tag() == .null_value) {
try writer.writeByte(wasm.opcode(.i32_const));
try leb.writeILEB128(writer, @as(i32, 1));
const opcode: wasm.Opcode = buildOpcode(.{
.op = .@"const",
.valtype1 = try self.typeToValtype(payload_type),
});
try writer.writeByte(wasm.opcode(opcode));
try leb.writeULEB128(writer, @as(u32, 0));
} else {
try writer.writeByte(wasm.opcode(.i32_const));
try leb.writeILEB128(writer, @as(i32, 0));
try self.emitConstant(val, payload_type);
}
},
else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),
}
}
@ -1184,7 +1314,6 @@ pub const Context = struct {
const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];
const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
const writer = self.code.writer();
// TODO: Handle death instructions for then and else body
// insert blocks at the position of `offset` so
@ -1494,4 +1623,60 @@ pub const Context = struct {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
return self.resolveInst(ty_op.operand);
}
fn airIntcast(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const ty = self.air.getRefType(ty_op.ty);
const operand = self.resolveInst(ty_op.operand);
const ref_ty = self.air.typeOf(ty_op.operand);
const ref_info = ref_ty.intInfo(self.target);
const op_bits = ref_info.bits;
const wanted_bits = ty.intInfo(self.target).bits;
try self.emitWValue(operand);
if (op_bits > 32 and wanted_bits <= 32) {
try self.code.append(wasm.opcode(.i32_wrap_i64));
} else if (op_bits <= 32 and wanted_bits > 32) {
try self.code.append(wasm.opcode(switch (ref_info.signedness) {
.signed => .i64_extend_i32_s,
.unsigned => .i64_extend_i32_u,
}));
}
// other cases are no-op
return .none;
}
fn airIsNull(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
const un_op = self.air.instructions.items(.data)[inst].un_op;
const operand = self.resolveInst(un_op);
// const offset = self.code.items.len;
const writer = self.code.writer();
// load the null value which is positioned at multi_value's index
try self.emitWValue(.{ .local = operand.multi_value.index });
// Compare the null value with '0'
try writer.writeByte(wasm.opcode(.i32_const));
try leb.writeILEB128(writer, @as(i32, 0));
try writer.writeByte(@enumToInt(opcode));
// we save the result in a new local
const local = try self.allocLocal(Type.initTag(.i32));
try writer.writeByte(wasm.opcode(.local_set));
try leb.writeULEB128(writer, local.local);
return local;
}
fn airOptionalPayload(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand = self.resolveInst(ty_op.operand);
return WValue{ .local = operand.multi_value.index + 1 };
}
fn airWrapOptional(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
return self.resolveInst(ty_op.operand);
}
};

View File

@ -113,6 +113,27 @@ pub fn addCases(ctx: *TestContext) !void {
\\}
, "25\n");
case.addCompareOutput(
\\pub export fn _start() i32 {
\\ var i: i32 = 2147483647;
\\ return i +% 1;
\\}
, "-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;
@ -133,6 +154,27 @@ pub fn addCases(ctx: *TestContext) !void {
\\}
, "15\n");
case.addCompareOutput(
\\pub export fn _start() i32 {
\\ var i: i32 = -2147483648;
\\ return i -% 1;
\\}
, "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;
@ -157,6 +199,27 @@ pub fn addCases(ctx: *TestContext) !void {
\\}
, "350\n");
case.addCompareOutput(
\\pub export fn _start() i32 {
\\ var i: i32 = 2147483647;
\\ return i *% 2;
\\}
, "-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;
@ -612,4 +675,69 @@ pub fn addCases(ctx: *TestContext) !void {
\\}
, "42\n");
}
{
var case = ctx.exe("wasm integer widening", wasi);
case.addCompareOutput(
\\pub export fn _start() u64 {
\\ var x: u32 = 5;
\\ return x;
\\}
, "5\n");
}
{
var case = ctx.exe("wasm optionals", wasi);
case.addCompareOutput(
\\pub export fn _start() u32 {
\\ var x: ?u32 = 5;
\\ var y: u32 = 0;
\\ if (x) |val| {
\\ y = val;
\\ }
\\ return y;
\\}
, "5\n");
case.addCompareOutput(
\\pub export fn _start() u32 {
\\ var x: ?u32 = null;
\\ var y: u32 = 0;
\\ if (x) |val| {
\\ y = val;
\\ }
\\ return y;
\\}
, "0\n");
case.addCompareOutput(
\\pub export fn _start() u32 {
\\ var x: ?u32 = 5;
\\ return x.?;
\\}
, "5\n");
case.addCompareOutput(
\\pub export fn _start() u32 {
\\ var x: u32 = 5;
\\ var y: ?u32 = x;
\\ return y.?;
\\}
, "5\n");
case.addCompareOutput(
\\pub export fn _start() u32 {
\\ var val: ?u32 = 5;
\\ while (val) |*v| {
\\ v.* -= 1;
\\ if (v.* == 2) {
\\ val = null;
\\ }
\\ }
\\ return 0;
\\}
, "0\n");
}
}