From ac5fd47e2ee7c43a3ad7e5d0275d4e745152a9a3 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 16 May 2021 16:15:11 +0200 Subject: Initial support for structs in wasm backend - Creates a 'local' for the struct itself and each field - The index of the local is calculated from the struct's local index + field index --- src/codegen/wasm.zig | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) (limited to 'src/codegen/wasm.zig') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 600185f238..ce896a4f27 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -556,7 +556,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( @@ -714,8 +714,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,6 +740,7 @@ 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), .unreach => self.genUnreachable(inst.castTag(.unreach).?), .xor => self.genBinOp(inst.castTag(.xor).?, .xor), @@ -797,8 +798,28 @@ pub const Context = struct { const valtype = try self.genValtype(inst.base.src, elem_type); try self.locals.append(self.gpa, valtype); - defer self.local_index += 1; - return WValue{ .local = self.local_index }; + const local_value = WValue{ .local = self.local_index }; + self.local_index += 1; + + 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; + } + }, + // TODO: Add more types that require extra locals such as optionals + else => {}, + } + + return local_value; } fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue { @@ -1090,4 +1111,10 @@ 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.local + @intCast(u32, inst.field_index) + 1 }; + } }; -- cgit v1.2.3 From 69626478626aee18953b2fbfa42d509c7dfff1e4 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Mon, 17 May 2021 19:44:14 +0200 Subject: Do not create a local for the struct itself + test cases --- src/codegen/wasm.zig | 12 ++++++------ test/stage2/wasm.zig | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) (limited to 'src/codegen/wasm.zig') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index ce896a4f27..da451b62c2 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -795,11 +795,7 @@ 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 local_value = WValue{ .local = self.local_index }; - self.local_index += 1; switch (elem_type.zigTypeTag()) { .Struct => { @@ -816,7 +812,11 @@ pub const Context = struct { } }, // TODO: Add more types that require extra locals such as optionals - else => {}, + else => { + const valtype = try self.genValtype(inst.base.src, elem_type); + try self.locals.append(self.gpa, valtype); + self.local_index += 1; + }, } return local_value; @@ -1115,6 +1115,6 @@ pub const Context = struct { fn genStructFieldPtr(self: *Context, inst: *Inst.StructFieldPtr) InnerError!WValue { const struct_ptr = self.resolveInst(inst.struct_ptr); - return WValue{ .local = struct_ptr.local + @intCast(u32, inst.field_index) + 1 }; + return WValue{ .local = struct_ptr.local + @intCast(u32, inst.field_index) }; } }; diff --git a/test/stage2/wasm.zig b/test/stage2/wasm.zig index 17da8d6a0d..442f4d917d 100644 --- a/test/stage2/wasm.zig +++ b/test/stage2/wasm.zig @@ -419,4 +419,26 @@ pub fn addCases(ctx: *TestContext) !void { \\} , "2\n"); } + + { + var case = ctx.exe("wasm structs", wasi); + + case.addCompareOutput( + \\const Example = struct { x: u32 }; + \\ + \\export fn _start() u32 { + \\ var example: Example = .{ .x = 5 }; + \\ return example.x; + \\} + , "5\n"); + + case.addCompareOutput( + \\const Example = struct { x: u32, y: u32 }; + \\ + \\export fn _start() u32 { + \\ var example: Example = .{ .x = 5, .y = 10 }; + \\ return example.y + example.x; + \\} + , "15\n"); + } } -- cgit v1.2.3 From 87a9c6946dd536de562bc86ac4819a33df3442df Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Wed, 19 May 2021 17:36:23 +0200 Subject: wasm backend: implement `multi_value` for `WValue` This allows us to differentiate between regular locals and variables that create multiple locals on the stack such as optionals and structs. Now `struct_a = struct_b;` works and only updates a reference, rather than update all local's values. Also created more test cases to test against this. --- src/codegen/wasm.zig | 33 +++++++++++++++++++++++---------- test/stage2/wasm.zig | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 12 deletions(-) (limited to 'src/codegen/wasm.zig') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index da451b62c2..7c3fecd496 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -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 @@ -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); @@ -795,7 +799,7 @@ pub const Context = struct { fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue { const elem_type = inst.base.ty.elemType(); - const local_value = WValue{ .local = self.local_index }; + const initial_index = self.local_index; switch (elem_type.zigTypeTag()) { .Struct => { @@ -810,16 +814,15 @@ pub const Context = struct { self.locals.appendAssumeCapacity(val_type); self.local_index += 1; } + return WValue{ .multi_value = initial_index }; }, - // TODO: Add more types that require extra locals such as optionals 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 }; }, } - - return local_value; } fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue { @@ -827,10 +830,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; } @@ -1115,6 +1128,6 @@ pub const Context = struct { fn genStructFieldPtr(self: *Context, inst: *Inst.StructFieldPtr) InnerError!WValue { const struct_ptr = self.resolveInst(inst.struct_ptr); - return WValue{ .local = struct_ptr.local + @intCast(u32, inst.field_index) }; + return WValue{ .local = struct_ptr.multi_value + @intCast(u32, inst.field_index) }; } }; diff --git a/test/stage2/wasm.zig b/test/stage2/wasm.zig index 442f4d917d..2b99ec5cb7 100644 --- a/test/stage2/wasm.zig +++ b/test/stage2/wasm.zig @@ -426,19 +426,52 @@ pub fn addCases(ctx: *TestContext) !void { case.addCompareOutput( \\const Example = struct { x: u32 }; \\ - \\export fn _start() 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 }; \\ - \\export fn _start() 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"); } } -- cgit v1.2.3 From 81d8fe7558cdae829c30cba3c92dfc373336929c Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Thu, 20 May 2021 14:21:02 +0200 Subject: stage2 wasm: Support basic switches - Adds support for single branches - Allows both enums and integers - Supports 'else' branch --- src/codegen/wasm.zig | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'src/codegen/wasm.zig') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 7c3fecd496..d5e5682909 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -746,6 +746,7 @@ pub const Context = struct { .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}), @@ -1130,4 +1131,45 @@ pub const Context = struct { 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; + } }; -- cgit v1.2.3 From f8d0501f50985b2953dc0eec6a555e7bc91f7798 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Thu, 20 May 2021 16:21:11 +0200 Subject: Also support multi-prong branches --- src/codegen/wasm.zig | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src/codegen/wasm.zig') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index d5e5682909..efaaebadb1 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -862,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); @@ -871,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 { -- cgit v1.2.3