| ... | ... | @@ -31,6 +31,9 @@ const WValue = union(enum) { |
| 31 | 31 | code_offset: usize, |
| 32 | 32 | /// The label of the block, used by breaks to find its relative distance |
| 33 | 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 | 39 | /// Wasm ops, but without input/output/signedness information |
| ... | ... | @@ -556,7 +559,7 @@ pub const Context = struct { |
| 556 | 559 | if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64; |
| 557 | 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 | 563 | .Enum => switch (ty.tag()) { |
| 561 | 564 | .enum_simple => wasm.Valtype.i32, |
| 562 | 565 | else => self.typeToValtype( |
| ... | ... | @@ -587,8 +590,9 @@ pub const Context = struct { |
| 587 | 590 | fn emitWValue(self: *Context, val: WValue) InnerError!void { |
| 588 | 591 | const writer = self.code.writer(); |
| 589 | 592 | switch (val) { |
| 590 | | .block_idx => unreachable, |
| 591 | | .none, .code_offset => {}, |
| 593 | .block_idx => unreachable, // block_idx cannot be referenced |
| 594 | .multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually |
| 595 | .none, .code_offset => {}, // no-op |
| 592 | 596 | .local => |idx| { |
| 593 | 597 | try writer.writeByte(wasm.opcode(.local_get)); |
| 594 | 598 | try leb.writeULEB128(writer, idx); |
| ... | ... | @@ -714,8 +718,8 @@ pub const Context = struct { |
| 714 | 718 | .add => self.genBinOp(inst.castTag(.add).?, .add), |
| 715 | 719 | .alloc => self.genAlloc(inst.castTag(.alloc).?), |
| 716 | 720 | .arg => self.genArg(inst.castTag(.arg).?), |
| 717 | | .bitcast => self.genBitcast(inst.castTag(.bitcast).?), |
| 718 | 721 | .bit_and => self.genBinOp(inst.castTag(.bit_and).?, .@"and"), |
| 722 | .bitcast => self.genBitcast(inst.castTag(.bitcast).?), |
| 719 | 723 | .bit_or => self.genBinOp(inst.castTag(.bit_or).?, .@"or"), |
| 720 | 724 | .block => self.genBlock(inst.castTag(.block).?), |
| 721 | 725 | .bool_and => self.genBinOp(inst.castTag(.bool_and).?, .@"and"), |
| ... | ... | @@ -740,7 +744,9 @@ pub const Context = struct { |
| 740 | 744 | .ret => self.genRet(inst.castTag(.ret).?), |
| 741 | 745 | .retvoid => WValue.none, |
| 742 | 746 | .store => self.genStore(inst.castTag(.store).?), |
| 747 | .struct_field_ptr => self.genStructFieldPtr(inst.castTag(.struct_field_ptr).?), |
| 743 | 748 | .sub => self.genBinOp(inst.castTag(.sub).?, .sub), |
| 749 | .switchbr => self.genSwitchBr(inst.castTag(.switchbr).?), |
| 744 | 750 | .unreach => self.genUnreachable(inst.castTag(.unreach).?), |
| 745 | 751 | .xor => self.genBinOp(inst.castTag(.xor).?, .xor), |
| 746 | 752 | else => self.fail(.{ .node_offset = 0 }, "TODO: Implement wasm inst: {s}", .{inst.tag}), |
| ... | ... | @@ -794,11 +800,30 @@ pub const Context = struct { |
| 794 | 800 | |
| 795 | 801 | fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue { |
| 796 | 802 | const elem_type = inst.base.ty.elemType(); |
| 797 | | const valtype = try self.genValtype(inst.base.src, elem_type); |
| 798 | | try self.locals.append(self.gpa, valtype); |
| 799 | | |
| 800 | | defer self.local_index += 1; |
| 801 | | return WValue{ .local = self.local_index }; |
| 803 | const initial_index = self.local_index; |
| 804 | |
| 805 | switch (elem_type.zigTypeTag()) { |
| 806 | .Struct => { |
| 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 | 829 | fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue { |
| ... | ... | @@ -806,10 +831,20 @@ pub const Context = struct { |
| 806 | 831 | |
| 807 | 832 | const lhs = self.resolveInst(inst.lhs); |
| 808 | 833 | const rhs = self.resolveInst(inst.rhs); |
| 809 | | try self.emitWValue(rhs); |
| 810 | 834 | |
| 811 | | try writer.writeByte(wasm.opcode(.local_set)); |
| 812 | | try leb.writeULEB128(writer, lhs.local); |
| 835 | switch (lhs) { |
| 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 | 848 | return .none; |
| 814 | 849 | } |
| 815 | 850 | |
| ... | ... | @@ -827,6 +862,14 @@ pub const Context = struct { |
| 827 | 862 | const lhs = self.resolveInst(inst.lhs); |
| 828 | 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 | 873 | try self.emitWValue(lhs); |
| 831 | 874 | try self.emitWValue(rhs); |
| 832 | 875 | |
| ... | ... | @@ -836,7 +879,7 @@ pub const Context = struct { |
| 836 | 879 | .signedness = if (inst.base.ty.isSignedInt()) .signed else .unsigned, |
| 837 | 880 | }); |
| 838 | 881 | try self.code.append(wasm.opcode(opcode)); |
| 839 | | return .none; |
| 882 | return WValue{ .code_offset = offset }; |
| 840 | 883 | } |
| 841 | 884 | |
| 842 | 885 | fn emitConstant(self: *Context, src: LazySrcLoc, value: Value, ty: Type) InnerError!void { |
| ... | ... | @@ -1090,4 +1133,51 @@ pub const Context = struct { |
| 1090 | 1133 | fn genBitcast(self: *Context, bitcast: *Inst.UnOp) InnerError!WValue { |
| 1091 | 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 | }; |