| ... | @@ -31,6 +31,9 @@ const WValue = union(enum) { | ... | @@ -31,6 +31,9 @@ const WValue = union(enum) { |
| 31 | code_offset: usize, | 31 | code_offset: usize, |
| 32 | /// The label of the block, used by breaks to find its relative distance | 32 | /// The label of the block, used by breaks to find its relative distance |
| 33 | block_idx: u32, | 33 | block_idx: u32, |
| | 34 | /// Used for variables that create multiple locals on the stack when allocated |
| | 35 | /// such as structs and optionals. |
| | 36 | multi_value: u32, |
| 34 | }; | 37 | }; |
| 35 | | 38 | |
| 36 | /// Wasm ops, but without input/output/signedness information | 39 | /// Wasm ops, but without input/output/signedness information |
| ... | @@ -556,7 +559,7 @@ pub const Context = struct { | ... | @@ -556,7 +559,7 @@ pub const Context = struct { |
| 556 | if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64; | 559 | if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64; |
| 557 | return self.fail(src, "Integer bit size not supported by wasm: '{d}'", .{info.bits}); | 560 | return self.fail(src, "Integer bit size not supported by wasm: '{d}'", .{info.bits}); |
| 558 | }, | 561 | }, |
| 559 | .Bool, .Pointer => wasm.Valtype.i32, | 562 | .Bool, .Pointer, .Struct => wasm.Valtype.i32, |
| 560 | .Enum => switch (ty.tag()) { | 563 | .Enum => switch (ty.tag()) { |
| 561 | .enum_simple => wasm.Valtype.i32, | 564 | .enum_simple => wasm.Valtype.i32, |
| 562 | else => self.typeToValtype( | 565 | else => self.typeToValtype( |
| ... | @@ -587,8 +590,9 @@ pub const Context = struct { | ... | @@ -587,8 +590,9 @@ pub const Context = struct { |
| 587 | fn emitWValue(self: *Context, val: WValue) InnerError!void { | 590 | fn emitWValue(self: *Context, val: WValue) InnerError!void { |
| 588 | const writer = self.code.writer(); | 591 | const writer = self.code.writer(); |
| 589 | switch (val) { | 592 | switch (val) { |
| 590 | .block_idx => unreachable, | 593 | .block_idx => unreachable, // block_idx cannot be referenced |
| 591 | .none, .code_offset => {}, | 594 | .multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually |
| | 595 | .none, .code_offset => {}, // no-op |
| 592 | .local => |idx| { | 596 | .local => |idx| { |
| 593 | try writer.writeByte(wasm.opcode(.local_get)); | 597 | try writer.writeByte(wasm.opcode(.local_get)); |
| 594 | try leb.writeULEB128(writer, idx); | 598 | try leb.writeULEB128(writer, idx); |
| ... | @@ -714,8 +718,8 @@ pub const Context = struct { | ... | @@ -714,8 +718,8 @@ pub const Context = struct { |
| 714 | .add => self.genBinOp(inst.castTag(.add).?, .add), | 718 | .add => self.genBinOp(inst.castTag(.add).?, .add), |
| 715 | .alloc => self.genAlloc(inst.castTag(.alloc).?), | 719 | .alloc => self.genAlloc(inst.castTag(.alloc).?), |
| 716 | .arg => self.genArg(inst.castTag(.arg).?), | 720 | .arg => self.genArg(inst.castTag(.arg).?), |
| 717 | .bitcast => self.genBitcast(inst.castTag(.bitcast).?), | | |
| 718 | .bit_and => self.genBinOp(inst.castTag(.bit_and).?, .@"and"), | 721 | .bit_and => self.genBinOp(inst.castTag(.bit_and).?, .@"and"), |
| | 722 | .bitcast => self.genBitcast(inst.castTag(.bitcast).?), |
| 719 | .bit_or => self.genBinOp(inst.castTag(.bit_or).?, .@"or"), | 723 | .bit_or => self.genBinOp(inst.castTag(.bit_or).?, .@"or"), |
| 720 | .block => self.genBlock(inst.castTag(.block).?), | 724 | .block => self.genBlock(inst.castTag(.block).?), |
| 721 | .bool_and => self.genBinOp(inst.castTag(.bool_and).?, .@"and"), | 725 | .bool_and => self.genBinOp(inst.castTag(.bool_and).?, .@"and"), |
| ... | @@ -740,7 +744,9 @@ pub const Context = struct { | ... | @@ -740,7 +744,9 @@ pub const Context = struct { |
| 740 | .ret => self.genRet(inst.castTag(.ret).?), | 744 | .ret => self.genRet(inst.castTag(.ret).?), |
| 741 | .retvoid => WValue.none, | 745 | .retvoid => WValue.none, |
| 742 | .store => self.genStore(inst.castTag(.store).?), | 746 | .store => self.genStore(inst.castTag(.store).?), |
| | 747 | .struct_field_ptr => self.genStructFieldPtr(inst.castTag(.struct_field_ptr).?), |
| 743 | .sub => self.genBinOp(inst.castTag(.sub).?, .sub), | 748 | .sub => self.genBinOp(inst.castTag(.sub).?, .sub), |
| | 749 | .switchbr => self.genSwitchBr(inst.castTag(.switchbr).?), |
| 744 | .unreach => self.genUnreachable(inst.castTag(.unreach).?), | 750 | .unreach => self.genUnreachable(inst.castTag(.unreach).?), |
| 745 | .xor => self.genBinOp(inst.castTag(.xor).?, .xor), | 751 | .xor => self.genBinOp(inst.castTag(.xor).?, .xor), |
| 746 | else => self.fail(.{ .node_offset = 0 }, "TODO: Implement wasm inst: {s}", .{inst.tag}), | 752 | else => self.fail(.{ .node_offset = 0 }, "TODO: Implement wasm inst: {s}", .{inst.tag}), |
| ... | @@ -794,11 +800,30 @@ pub const Context = struct { | ... | @@ -794,11 +800,30 @@ pub const Context = struct { |
| 794 | | 800 | |
| 795 | fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue { | 801 | fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue { |
| 796 | const elem_type = inst.base.ty.elemType(); | 802 | const elem_type = inst.base.ty.elemType(); |
| 797 | const valtype = try self.genValtype(inst.base.src, elem_type); | 803 | const initial_index = self.local_index; |
| 798 | try self.locals.append(self.gpa, valtype); | 804 | |
| 799 | | 805 | switch (elem_type.zigTypeTag()) { |
| 800 | defer self.local_index += 1; | 806 | .Struct => { |
| 801 | return WValue{ .local = self.local_index }; | 807 | // for each struct field, generate a local |
| | 808 | const struct_data: *Module.Struct = elem_type.castTag(.@"struct").?.data; |
| | 809 | try self.locals.ensureCapacity(self.gpa, self.locals.items.len + struct_data.fields.count()); |
| | 810 | for (struct_data.fields.items()) |entry| { |
| | 811 | const val_type = try self.genValtype( |
| | 812 | .{ .node_offset = struct_data.node_offset }, |
| | 813 | entry.value.ty, |
| | 814 | ); |
| | 815 | self.locals.appendAssumeCapacity(val_type); |
| | 816 | self.local_index += 1; |
| | 817 | } |
| | 818 | return WValue{ .multi_value = initial_index }; |
| | 819 | }, |
| | 820 | else => { |
| | 821 | const valtype = try self.genValtype(inst.base.src, elem_type); |
| | 822 | try self.locals.append(self.gpa, valtype); |
| | 823 | self.local_index += 1; |
| | 824 | return WValue{ .local = initial_index }; |
| | 825 | }, |
| | 826 | } |
| 802 | } | 827 | } |
| 803 | | 828 | |
| 804 | fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue { | 829 | fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue { |
| ... | @@ -806,10 +831,20 @@ pub const Context = struct { | ... | @@ -806,10 +831,20 @@ pub const Context = struct { |
| 806 | | 831 | |
| 807 | const lhs = self.resolveInst(inst.lhs); | 832 | const lhs = self.resolveInst(inst.lhs); |
| 808 | const rhs = self.resolveInst(inst.rhs); | 833 | const rhs = self.resolveInst(inst.rhs); |
| 809 | try self.emitWValue(rhs); | | |
| 810 | | 834 | |
| 811 | try writer.writeByte(wasm.opcode(.local_set)); | 835 | switch (lhs) { |
| 812 | try leb.writeULEB128(writer, lhs.local); | 836 | // When assigning a value to a multi_value such as a struct, |
| | 837 | // we simply assign the local_index to the rhs one. |
| | 838 | // This allows us to update struct fields without having to individually |
| | 839 | // set each local as each field's index will be calculated off the struct's base index |
| | 840 | .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses! |
| | 841 | .local => |local| { |
| | 842 | try self.emitWValue(rhs); |
| | 843 | try writer.writeByte(wasm.opcode(.local_set)); |
| | 844 | try leb.writeULEB128(writer, lhs.local); |
| | 845 | }, |
| | 846 | else => unreachable, |
| | 847 | } |
| 813 | return .none; | 848 | return .none; |
| 814 | } | 849 | } |
| 815 | | 850 | |
| ... | @@ -827,6 +862,14 @@ pub const Context = struct { | ... | @@ -827,6 +862,14 @@ pub const Context = struct { |
| 827 | const lhs = self.resolveInst(inst.lhs); | 862 | const lhs = self.resolveInst(inst.lhs); |
| 828 | const rhs = self.resolveInst(inst.rhs); | 863 | const rhs = self.resolveInst(inst.rhs); |
| 829 | | 864 | |
| | 865 | // it's possible for both lhs and/or rhs to return an offset as well, |
| | 866 | // in which case we return the first offset occurance we find. |
| | 867 | const offset = blk: { |
| | 868 | if (lhs == .code_offset) break :blk lhs.code_offset; |
| | 869 | if (rhs == .code_offset) break :blk rhs.code_offset; |
| | 870 | break :blk self.code.items.len; |
| | 871 | }; |
| | 872 | |
| 830 | try self.emitWValue(lhs); | 873 | try self.emitWValue(lhs); |
| 831 | try self.emitWValue(rhs); | 874 | try self.emitWValue(rhs); |
| 832 | | 875 | |
| ... | @@ -836,7 +879,7 @@ pub const Context = struct { | ... | @@ -836,7 +879,7 @@ pub const Context = struct { |
| 836 | .signedness = if (inst.base.ty.isSignedInt()) .signed else .unsigned, | 879 | .signedness = if (inst.base.ty.isSignedInt()) .signed else .unsigned, |
| 837 | }); | 880 | }); |
| 838 | try self.code.append(wasm.opcode(opcode)); | 881 | try self.code.append(wasm.opcode(opcode)); |
| 839 | return .none; | 882 | return WValue{ .code_offset = offset }; |
| 840 | } | 883 | } |
| 841 | | 884 | |
| 842 | fn emitConstant(self: *Context, src: LazySrcLoc, value: Value, ty: Type) InnerError!void { | 885 | fn emitConstant(self: *Context, src: LazySrcLoc, value: Value, ty: Type) InnerError!void { |
| ... | @@ -1090,4 +1133,51 @@ pub const Context = struct { | ... | @@ -1090,4 +1133,51 @@ pub const Context = struct { |
| 1090 | fn genBitcast(self: *Context, bitcast: *Inst.UnOp) InnerError!WValue { | 1133 | fn genBitcast(self: *Context, bitcast: *Inst.UnOp) InnerError!WValue { |
| 1091 | return self.resolveInst(bitcast.operand); | 1134 | return self.resolveInst(bitcast.operand); |
| 1092 | } | 1135 | } |
| | 1136 | |
| | 1137 | fn genStructFieldPtr(self: *Context, inst: *Inst.StructFieldPtr) InnerError!WValue { |
| | 1138 | const struct_ptr = self.resolveInst(inst.struct_ptr); |
| | 1139 | |
| | 1140 | return WValue{ .local = struct_ptr.multi_value + @intCast(u32, inst.field_index) }; |
| | 1141 | } |
| | 1142 | |
| | 1143 | fn genSwitchBr(self: *Context, inst: *Inst.SwitchBr) InnerError!WValue { |
| | 1144 | const target = self.resolveInst(inst.target); |
| | 1145 | const target_ty = inst.target.ty; |
| | 1146 | const valtype = try self.typeToValtype(.{ .node_offset = 0 }, target_ty); |
| | 1147 | const blocktype = try self.genBlockType(inst.base.src, inst.base.ty); |
| | 1148 | |
| | 1149 | const signedness: std.builtin.Signedness = blk: { |
| | 1150 | // by default we tell the operand type is unsigned (i.e. bools and enum values) |
| | 1151 | if (target_ty.zigTypeTag() != .Int) break :blk .unsigned; |
| | 1152 | |
| | 1153 | // incase of an actual integer, we emit the correct signedness |
| | 1154 | break :blk target_ty.intInfo(self.target).signedness; |
| | 1155 | }; |
| | 1156 | for (inst.cases) |case| { |
| | 1157 | // create a block for each case, when the condition does not match we break out of it |
| | 1158 | try self.startBlock(.block, blocktype, null); |
| | 1159 | try self.emitWValue(target); |
| | 1160 | try self.emitConstant(.{ .node_offset = 0 }, case.item, target_ty); |
| | 1161 | const opcode = buildOpcode(.{ |
| | 1162 | .valtype1 = valtype, |
| | 1163 | .op = .ne, // not equal because we jump out the block if it does not match the condition |
| | 1164 | .signedness = signedness, |
| | 1165 | }); |
| | 1166 | try self.code.append(wasm.opcode(opcode)); |
| | 1167 | try self.code.append(wasm.opcode(.br_if)); |
| | 1168 | try leb.writeULEB128(self.code.writer(), @as(u32, 0)); |
| | 1169 | |
| | 1170 | // emit our block code |
| | 1171 | try self.genBody(case.body); |
| | 1172 | |
| | 1173 | // end the block we created earlier |
| | 1174 | try self.endBlock(); |
| | 1175 | } |
| | 1176 | |
| | 1177 | // finally, emit the else case if it exists. Here we will not have to |
| | 1178 | // check for a condition, so also no need to emit a block. |
| | 1179 | try self.genBody(inst.else_body); |
| | 1180 | |
| | 1181 | return .none; |
| | 1182 | } |
| 1093 | }; | 1183 | }; |