mirror of
https://github.com/ziglang/zig.git
synced 2026-02-21 16:54:52 +00:00
Merge pull request #8847 from Luukdegram/wasm-struct-switch
stage2: wasm - Structs and switch support
This commit is contained in:
commit
0267abfe9b
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user