Merge pull request #8847 from Luukdegram/wasm-struct-switch

stage2: wasm - Structs and switch support
This commit is contained in:
Andrew Kelley 2021-05-20 18:55:49 -04:00 committed by GitHub
commit 0267abfe9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 218 additions and 12 deletions

View File

@ -31,6 +31,9 @@ const WValue = union(enum) {
code_offset: usize,
/// The label of the block, used by breaks to find its relative distance
block_idx: u32,
/// Used for variables that create multiple locals on the stack when allocated
/// such as structs and optionals.
multi_value: u32,
};
/// Wasm ops, but without input/output/signedness information
@ -556,7 +559,7 @@ pub const Context = struct {
if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;
return self.fail(src, "Integer bit size not supported by wasm: '{d}'", .{info.bits});
},
.Bool, .Pointer => wasm.Valtype.i32,
.Bool, .Pointer, .Struct => wasm.Valtype.i32,
.Enum => switch (ty.tag()) {
.enum_simple => wasm.Valtype.i32,
else => self.typeToValtype(
@ -587,8 +590,9 @@ pub const Context = struct {
fn emitWValue(self: *Context, val: WValue) InnerError!void {
const writer = self.code.writer();
switch (val) {
.block_idx => unreachable,
.none, .code_offset => {},
.block_idx => unreachable, // block_idx cannot be referenced
.multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually
.none, .code_offset => {}, // no-op
.local => |idx| {
try writer.writeByte(wasm.opcode(.local_get));
try leb.writeULEB128(writer, idx);
@ -714,8 +718,8 @@ pub const Context = struct {
.add => self.genBinOp(inst.castTag(.add).?, .add),
.alloc => self.genAlloc(inst.castTag(.alloc).?),
.arg => self.genArg(inst.castTag(.arg).?),
.bitcast => self.genBitcast(inst.castTag(.bitcast).?),
.bit_and => self.genBinOp(inst.castTag(.bit_and).?, .@"and"),
.bitcast => self.genBitcast(inst.castTag(.bitcast).?),
.bit_or => self.genBinOp(inst.castTag(.bit_or).?, .@"or"),
.block => self.genBlock(inst.castTag(.block).?),
.bool_and => self.genBinOp(inst.castTag(.bool_and).?, .@"and"),
@ -740,7 +744,9 @@ pub const Context = struct {
.ret => self.genRet(inst.castTag(.ret).?),
.retvoid => WValue.none,
.store => self.genStore(inst.castTag(.store).?),
.struct_field_ptr => self.genStructFieldPtr(inst.castTag(.struct_field_ptr).?),
.sub => self.genBinOp(inst.castTag(.sub).?, .sub),
.switchbr => self.genSwitchBr(inst.castTag(.switchbr).?),
.unreach => self.genUnreachable(inst.castTag(.unreach).?),
.xor => self.genBinOp(inst.castTag(.xor).?, .xor),
else => self.fail(.{ .node_offset = 0 }, "TODO: Implement wasm inst: {s}", .{inst.tag}),
@ -794,11 +800,30 @@ pub const Context = struct {
fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue {
const elem_type = inst.base.ty.elemType();
const valtype = try self.genValtype(inst.base.src, elem_type);
try self.locals.append(self.gpa, valtype);
const initial_index = self.local_index;
defer self.local_index += 1;
return WValue{ .local = self.local_index };
switch (elem_type.zigTypeTag()) {
.Struct => {
// for each struct field, generate a local
const struct_data: *Module.Struct = elem_type.castTag(.@"struct").?.data;
try self.locals.ensureCapacity(self.gpa, self.locals.items.len + struct_data.fields.count());
for (struct_data.fields.items()) |entry| {
const val_type = try self.genValtype(
.{ .node_offset = struct_data.node_offset },
entry.value.ty,
);
self.locals.appendAssumeCapacity(val_type);
self.local_index += 1;
}
return WValue{ .multi_value = initial_index };
},
else => {
const valtype = try self.genValtype(inst.base.src, elem_type);
try self.locals.append(self.gpa, valtype);
self.local_index += 1;
return WValue{ .local = initial_index };
},
}
}
fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue {
@ -806,10 +831,20 @@ pub const Context = struct {
const lhs = self.resolveInst(inst.lhs);
const rhs = self.resolveInst(inst.rhs);
try self.emitWValue(rhs);
try writer.writeByte(wasm.opcode(.local_set));
try leb.writeULEB128(writer, lhs.local);
switch (lhs) {
// When assigning a value to a multi_value such as a struct,
// we simply assign the local_index to the rhs one.
// This allows us to update struct fields without having to individually
// set each local as each field's index will be calculated off the struct's base index
.multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses!
.local => |local| {
try self.emitWValue(rhs);
try writer.writeByte(wasm.opcode(.local_set));
try leb.writeULEB128(writer, lhs.local);
},
else => unreachable,
}
return .none;
}
@ -827,6 +862,14 @@ pub const Context = struct {
const lhs = self.resolveInst(inst.lhs);
const rhs = self.resolveInst(inst.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);
@ -836,7 +879,7 @@ pub const Context = struct {
.signedness = if (inst.base.ty.isSignedInt()) .signed else .unsigned,
});
try self.code.append(wasm.opcode(opcode));
return .none;
return WValue{ .code_offset = offset };
}
fn emitConstant(self: *Context, src: LazySrcLoc, value: Value, ty: Type) InnerError!void {
@ -1090,4 +1133,51 @@ pub const Context = struct {
fn genBitcast(self: *Context, bitcast: *Inst.UnOp) InnerError!WValue {
return self.resolveInst(bitcast.operand);
}
fn genStructFieldPtr(self: *Context, inst: *Inst.StructFieldPtr) InnerError!WValue {
const struct_ptr = self.resolveInst(inst.struct_ptr);
return WValue{ .local = struct_ptr.multi_value + @intCast(u32, inst.field_index) };
}
fn genSwitchBr(self: *Context, inst: *Inst.SwitchBr) InnerError!WValue {
const target = self.resolveInst(inst.target);
const target_ty = inst.target.ty;
const valtype = try self.typeToValtype(.{ .node_offset = 0 }, target_ty);
const blocktype = try self.genBlockType(inst.base.src, inst.base.ty);
const signedness: std.builtin.Signedness = blk: {
// by default we tell the operand type is unsigned (i.e. bools and enum values)
if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
// incase of an actual integer, we emit the correct signedness
break :blk target_ty.intInfo(self.target).signedness;
};
for (inst.cases) |case| {
// create a block for each case, when the condition does not match we break out of it
try self.startBlock(.block, blocktype, null);
try self.emitWValue(target);
try self.emitConstant(.{ .node_offset = 0 }, case.item, target_ty);
const opcode = buildOpcode(.{
.valtype1 = valtype,
.op = .ne, // not equal because we jump out the block if it does not match the condition
.signedness = signedness,
});
try self.code.append(wasm.opcode(opcode));
try self.code.append(wasm.opcode(.br_if));
try leb.writeULEB128(self.code.writer(), @as(u32, 0));
// emit our block code
try self.genBody(case.body);
// end the block we created earlier
try self.endBlock();
}
// finally, emit the else case if it exists. Here we will not have to
// check for a condition, so also no need to emit a block.
try self.genBody(inst.else_body);
return .none;
}
};

View File

@ -419,4 +419,120 @@ pub fn addCases(ctx: *TestContext) !void {
\\}
, "2\n");
}
{
var case = ctx.exe("wasm structs", wasi);
case.addCompareOutput(
\\const Example = struct { x: u32 };
\\
\\pub export fn _start() u32 {
\\ var example: Example = .{ .x = 5 };
\\ return example.x;
\\}
, "5\n");
case.addCompareOutput(
\\const Example = struct { x: u32 };
\\
\\pub export fn _start() u32 {
\\ var example: Example = .{ .x = 5 };
\\ example.x = 10;
\\ return example.x;
\\}
, "10\n");
case.addCompareOutput(
\\const Example = struct { x: u32, y: u32 };
\\
\\pub export fn _start() u32 {
\\ var example: Example = .{ .x = 5, .y = 10 };
\\ return example.y + example.x;
\\}
, "15\n");
case.addCompareOutput(
\\const Example = struct { x: u32, y: u32 };
\\
\\pub export fn _start() u32 {
\\ var example: Example = .{ .x = 5, .y = 10 };
\\ var example2: Example = .{ .x = 10, .y = 20 };
\\
\\ example = example2;
\\ return example.y + example.x;
\\}
, "30\n");
case.addCompareOutput(
\\const Example = struct { x: u32, y: u32 };
\\
\\pub export fn _start() u32 {
\\ var example: Example = .{ .x = 5, .y = 10 };
\\
\\ example = .{ .x = 10, .y = 20 };
\\ return example.y + example.x;
\\}
, "30\n");
}
{
var case = ctx.exe("wasm switch", wasi);
case.addCompareOutput(
\\pub export fn _start() u32 {
\\ var val: u32 = 1;
\\ var a: u32 = switch (val) {
\\ 0, 1 => 2,
\\ 2 => 3,
\\ 3 => 4,
\\ else => 5,
\\ };
\\
\\ return a;
\\}
, "2\n");
case.addCompareOutput(
\\pub export fn _start() u32 {
\\ var val: u32 = 2;
\\ var a: u32 = switch (val) {
\\ 0, 1 => 2,
\\ 2 => 3,
\\ 3 => 4,
\\ else => 5,
\\ };
\\
\\ return a;
\\}
, "3\n");
case.addCompareOutput(
\\pub export fn _start() u32 {
\\ var val: u32 = 10;
\\ var a: u32 = switch (val) {
\\ 0, 1 => 2,
\\ 2 => 3,
\\ 3 => 4,
\\ else => 5,
\\ };
\\
\\ return a;
\\}
, "5\n");
case.addCompareOutput(
\\const MyEnum = enum { One, Two, Three };
\\
\\pub export fn _start() u32 {
\\ var val: MyEnum = .Two;
\\ var a: u32 = switch (val) {
\\ .One => 1,
\\ .Two => 2,
\\ .Three => 3,
\\ };
\\
\\ return a;
\\}
, "2\n");
}
}