| ... | @@ -590,8 +590,8 @@ pub const Context = struct { | ... | @@ -590,8 +590,8 @@ pub const Context = struct { |
| 590 | .Pointer, | 590 | .Pointer, |
| 591 | .ErrorSet, | 591 | .ErrorSet, |
| 592 | => wasm.Valtype.i32, | 592 | => wasm.Valtype.i32, |
| 593 | .Struct, .ErrorUnion => unreachable, // Multi typed, must be handled individually. | 593 | .Struct, .ErrorUnion, .Optional => unreachable, // Multi typed, must be handled individually. |
| 594 | else => self.fail("TODO - Wasm valtype for type '{s}'", .{ty.zigTypeTag()}), | 594 | else => |tag| self.fail("TODO - Wasm valtype for type '{s}'", .{tag}), |
| 595 | }; | 595 | }; |
| 596 | } | 596 | } |
| 597 | | 597 | |
| ... | @@ -634,7 +634,7 @@ pub const Context = struct { | ... | @@ -634,7 +634,7 @@ pub const Context = struct { |
| 634 | // for each struct field, generate a local | 634 | // for each struct field, generate a local |
| 635 | const struct_data: *Module.Struct = ty.castTag(.@"struct").?.data; | 635 | const struct_data: *Module.Struct = ty.castTag(.@"struct").?.data; |
| 636 | const fields_len = @intCast(u32, struct_data.fields.count()); | 636 | const fields_len = @intCast(u32, struct_data.fields.count()); |
| 637 | try self.locals.ensureCapacity(self.gpa, self.locals.items.len + fields_len); | 637 | try self.locals.ensureUnusedCapacity(self.gpa, fields_len); |
| 638 | for (struct_data.fields.values()) |*value| { | 638 | for (struct_data.fields.values()) |*value| { |
| 639 | const val_type = try self.genValtype(value.ty); | 639 | const val_type = try self.genValtype(value.ty); |
| 640 | self.locals.appendAssumeCapacity(val_type); | 640 | self.locals.appendAssumeCapacity(val_type); |
| ... | @@ -653,7 +653,7 @@ pub const Context = struct { | ... | @@ -653,7 +653,7 @@ pub const Context = struct { |
| 653 | // The first local is also used to find the index of the error and payload. | 653 | // The first local is also used to find the index of the error and payload. |
| 654 | // | 654 | // |
| 655 | // TODO: Add support where the payload is a type that contains multiple locals such as a struct. | 655 | // TODO: Add support where the payload is a type that contains multiple locals such as a struct. |
| 656 | try self.locals.ensureCapacity(self.gpa, self.locals.items.len + 2); | 656 | try self.locals.ensureUnusedCapacity(self.gpa, 2); |
| 657 | self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32 | 657 | self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32 |
| 658 | self.locals.appendAssumeCapacity(val_type); | 658 | self.locals.appendAssumeCapacity(val_type); |
| 659 | self.local_index += 2; | 659 | self.local_index += 2; |
| ... | @@ -663,6 +663,23 @@ pub const Context = struct { | ... | @@ -663,6 +663,23 @@ pub const Context = struct { |
| 663 | .count = 2, | 663 | .count = 2, |
| 664 | } }; | 664 | } }; |
| 665 | }, | 665 | }, |
| | 666 | .Optional => { |
| | 667 | var opt_buf: Type.Payload.ElemType = undefined; |
| | 668 | const child_type = ty.optionalChild(&opt_buf); |
| | 669 | if (ty.isPtrLikeOptional()) { |
| | 670 | return self.fail("TODO: wasm optional pointer", .{}); |
| | 671 | } |
| | 672 | |
| | 673 | try self.locals.ensureUnusedCapacity(self.gpa, 2); |
| | 674 | self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // optional 'tag' for null-checking is always i32 |
| | 675 | self.locals.appendAssumeCapacity(try self.genValtype(child_type)); |
| | 676 | self.local_index += 2; |
| | 677 | |
| | 678 | return WValue{ .multi_value = .{ |
| | 679 | .index = initial_index, |
| | 680 | .count = 2, |
| | 681 | } }; |
| | 682 | }, |
| 666 | else => { | 683 | else => { |
| 667 | const valtype = try self.genValtype(ty); | 684 | const valtype = try self.genValtype(ty); |
| 668 | try self.locals.append(self.gpa, valtype); | 685 | try self.locals.append(self.gpa, valtype); |
| ... | @@ -800,8 +817,11 @@ pub const Context = struct { | ... | @@ -800,8 +817,11 @@ pub const Context = struct { |
| 800 | const air_tags = self.air.instructions.items(.tag); | 817 | const air_tags = self.air.instructions.items(.tag); |
| 801 | return switch (air_tags[inst]) { | 818 | return switch (air_tags[inst]) { |
| 802 | .add => self.airBinOp(inst, .add), | 819 | .add => self.airBinOp(inst, .add), |
| | 820 | .addwrap => self.airWrapBinOp(inst, .add), |
| 803 | .sub => self.airBinOp(inst, .sub), | 821 | .sub => self.airBinOp(inst, .sub), |
| | 822 | .subwrap => self.airWrapBinOp(inst, .sub), |
| 804 | .mul => self.airBinOp(inst, .mul), | 823 | .mul => self.airBinOp(inst, .mul), |
| | 824 | .mulwrap => self.airWrapBinOp(inst, .mul), |
| 805 | .div => self.airBinOp(inst, .div), | 825 | .div => self.airBinOp(inst, .div), |
| 806 | .bit_and => self.airBinOp(inst, .@"and"), | 826 | .bit_and => self.airBinOp(inst, .@"and"), |
| 807 | .bit_or => self.airBinOp(inst, .@"or"), | 827 | .bit_or => self.airBinOp(inst, .@"or"), |
| ... | @@ -826,8 +846,16 @@ pub const Context = struct { | ... | @@ -826,8 +846,16 @@ pub const Context = struct { |
| 826 | .cond_br => self.airCondBr(inst), | 846 | .cond_br => self.airCondBr(inst), |
| 827 | .constant => unreachable, | 847 | .constant => unreachable, |
| 828 | .dbg_stmt => WValue.none, | 848 | .dbg_stmt => WValue.none, |
| | 849 | .intcast => self.airIntcast(inst), |
| | 850 | |
| 829 | .is_err => self.airIsErr(inst, .i32_ne), | 851 | .is_err => self.airIsErr(inst, .i32_ne), |
| 830 | .is_non_err => self.airIsErr(inst, .i32_eq), | 852 | .is_non_err => self.airIsErr(inst, .i32_eq), |
| | 853 | |
| | 854 | .is_null => self.airIsNull(inst, .i32_ne), |
| | 855 | .is_non_null => self.airIsNull(inst, .i32_eq), |
| | 856 | .is_null_ptr => self.airIsNull(inst, .i32_ne), |
| | 857 | .is_non_null_ptr => self.airIsNull(inst, .i32_eq), |
| | 858 | |
| 831 | .load => self.airLoad(inst), | 859 | .load => self.airLoad(inst), |
| 832 | .loop => self.airLoop(inst), | 860 | .loop => self.airLoop(inst), |
| 833 | .not => self.airNot(inst), | 861 | .not => self.airNot(inst), |
| ... | @@ -836,8 +864,13 @@ pub const Context = struct { | ... | @@ -836,8 +864,13 @@ pub const Context = struct { |
| 836 | .struct_field_ptr => self.airStructFieldPtr(inst), | 864 | .struct_field_ptr => self.airStructFieldPtr(inst), |
| 837 | .switch_br => self.airSwitchBr(inst), | 865 | .switch_br => self.airSwitchBr(inst), |
| 838 | .unreach => self.airUnreachable(inst), | 866 | .unreach => self.airUnreachable(inst), |
| | 867 | .wrap_optional => self.airWrapOptional(inst), |
| | 868 | |
| 839 | .unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst), | 869 | .unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst), |
| 840 | .wrap_errunion_payload => self.airWrapErrUnionPayload(inst), | 870 | .wrap_errunion_payload => self.airWrapErrUnionPayload(inst), |
| | 871 | |
| | 872 | .optional_payload => self.airOptionalPayload(inst), |
| | 873 | .optional_payload_ptr => self.airOptionalPayload(inst), |
| 841 | else => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}), | 874 | else => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}), |
| 842 | }; | 875 | }; |
| 843 | } | 876 | } |
| ... | @@ -922,6 +955,22 @@ pub const Context = struct { | ... | @@ -922,6 +955,22 @@ pub const Context = struct { |
| 922 | try leb.writeULEB128(writer, multi_value.index + i - 1); | 955 | try leb.writeULEB128(writer, multi_value.index + i - 1); |
| 923 | } | 956 | } |
| 924 | }, | 957 | }, |
| | 958 | .local => { |
| | 959 | // This can occur when we wrap a single value into a multi-value, |
| | 960 | // such as wrapping a non-optional value into an optional. |
| | 961 | // This means we must zero the null-tag, and set the payload. |
| | 962 | assert(multi_value.count == 2); |
| | 963 | // set null-tag |
| | 964 | try writer.writeByte(wasm.opcode(.i32_const)); |
| | 965 | try leb.writeULEB128(writer, @as(u32, 0)); |
| | 966 | try writer.writeByte(wasm.opcode(.local_set)); |
| | 967 | try leb.writeULEB128(writer, multi_value.index); |
| | 968 | |
| | 969 | // set payload |
| | 970 | try self.emitWValue(rhs); |
| | 971 | try writer.writeByte(wasm.opcode(.local_set)); |
| | 972 | try leb.writeULEB128(writer, multi_value.index + 1); |
| | 973 | }, |
| 925 | else => unreachable, | 974 | else => unreachable, |
| 926 | }, | 975 | }, |
| 927 | .local => |local| { | 976 | .local => |local| { |
| ... | @@ -972,6 +1021,62 @@ pub const Context = struct { | ... | @@ -972,6 +1021,62 @@ pub const Context = struct { |
| 972 | return WValue{ .code_offset = offset }; | 1021 | return WValue{ .code_offset = offset }; |
| 973 | } | 1022 | } |
| 974 | | 1023 | |
| | 1024 | fn airWrapBinOp(self: *Context, inst: Air.Inst.Index, op: Op) InnerError!WValue { |
| | 1025 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; |
| | 1026 | const lhs = self.resolveInst(bin_op.lhs); |
| | 1027 | const rhs = self.resolveInst(bin_op.rhs); |
| | 1028 | |
| | 1029 | // it's possible for both lhs and/or rhs to return an offset as well, |
| | 1030 | // in which case we return the first offset occurance we find. |
| | 1031 | const offset = blk: { |
| | 1032 | if (lhs == .code_offset) break :blk lhs.code_offset; |
| | 1033 | if (rhs == .code_offset) break :blk rhs.code_offset; |
| | 1034 | break :blk self.code.items.len; |
| | 1035 | }; |
| | 1036 | |
| | 1037 | try self.emitWValue(lhs); |
| | 1038 | try self.emitWValue(rhs); |
| | 1039 | |
| | 1040 | const bin_ty = self.air.typeOf(bin_op.lhs); |
| | 1041 | const opcode: wasm.Opcode = buildOpcode(.{ |
| | 1042 | .op = op, |
| | 1043 | .valtype1 = try self.typeToValtype(bin_ty), |
| | 1044 | .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned, |
| | 1045 | }); |
| | 1046 | try self.code.append(wasm.opcode(opcode)); |
| | 1047 | |
| | 1048 | const int_info = bin_ty.intInfo(self.target); |
| | 1049 | const bitsize = int_info.bits; |
| | 1050 | const is_signed = int_info.signedness == .signed; |
| | 1051 | // if target type bitsize is x < 32 and 32 > x < 64, we perform |
| | 1052 | // result & ((1<<N)-1) where N = bitsize or bitsize -1 incase of signed. |
| | 1053 | if (bitsize != 32 and bitsize < 64) { |
| | 1054 | // first check if we can use a single instruction, |
| | 1055 | // wasm provides those if the integers are signed and 8/16-bit. |
| | 1056 | // For arbitrary integer sizes, we use the algorithm mentioned above. |
| | 1057 | if (is_signed and bitsize == 8) { |
| | 1058 | try self.code.append(wasm.opcode(.i32_extend8_s)); |
| | 1059 | } else if (is_signed and bitsize == 16) { |
| | 1060 | try self.code.append(wasm.opcode(.i32_extend16_s)); |
| | 1061 | } else { |
| | 1062 | const result = (@as(u64, 1) << @intCast(u6, bitsize - @boolToInt(is_signed))) - 1; |
| | 1063 | if (bitsize < 32) { |
| | 1064 | try self.code.append(wasm.opcode(.i32_const)); |
| | 1065 | try leb.writeILEB128(self.code.writer(), @bitCast(i32, @intCast(u32, result))); |
| | 1066 | try self.code.append(wasm.opcode(.i32_and)); |
| | 1067 | } else { |
| | 1068 | try self.code.append(wasm.opcode(.i64_const)); |
| | 1069 | try leb.writeILEB128(self.code.writer(), @bitCast(i64, result)); |
| | 1070 | try self.code.append(wasm.opcode(.i64_and)); |
| | 1071 | } |
| | 1072 | } |
| | 1073 | } else if (int_info.bits > 64) { |
| | 1074 | return self.fail("TODO wasm: Integer wrapping for bitsizes larger than 64", .{}); |
| | 1075 | } |
| | 1076 | |
| | 1077 | return WValue{ .code_offset = offset }; |
| | 1078 | } |
| | 1079 | |
| 975 | fn emitConstant(self: *Context, val: Value, ty: Type) InnerError!void { | 1080 | fn emitConstant(self: *Context, val: Value, ty: Type) InnerError!void { |
| 976 | const writer = self.code.writer(); | 1081 | const writer = self.code.writer(); |
| 977 | switch (ty.zigTypeTag()) { | 1082 | switch (ty.zigTypeTag()) { |
| ... | @@ -1084,6 +1189,31 @@ pub const Context = struct { | ... | @@ -1084,6 +1189,31 @@ pub const Context = struct { |
| 1084 | try self.emitConstant(data, payload_type); | 1189 | try self.emitConstant(data, payload_type); |
| 1085 | } | 1190 | } |
| 1086 | }, | 1191 | }, |
| | 1192 | .Optional => { |
| | 1193 | var buf: Type.Payload.ElemType = undefined; |
| | 1194 | const payload_type = ty.optionalChild(&buf); |
| | 1195 | if (ty.isPtrLikeOptional()) { |
| | 1196 | return self.fail("Wasm TODO: emitConstant for optional pointer", .{}); |
| | 1197 | } |
| | 1198 | |
| | 1199 | // When constant has value 'null', set is_null local to '1' |
| | 1200 | // and payload to '0' |
| | 1201 | if (val.tag() == .null_value) { |
| | 1202 | try writer.writeByte(wasm.opcode(.i32_const)); |
| | 1203 | try leb.writeILEB128(writer, @as(i32, 1)); |
| | 1204 | |
| | 1205 | const opcode: wasm.Opcode = buildOpcode(.{ |
| | 1206 | .op = .@"const", |
| | 1207 | .valtype1 = try self.typeToValtype(payload_type), |
| | 1208 | }); |
| | 1209 | try writer.writeByte(wasm.opcode(opcode)); |
| | 1210 | try leb.writeULEB128(writer, @as(u32, 0)); |
| | 1211 | } else { |
| | 1212 | try writer.writeByte(wasm.opcode(.i32_const)); |
| | 1213 | try leb.writeILEB128(writer, @as(i32, 0)); |
| | 1214 | try self.emitConstant(val, payload_type); |
| | 1215 | } |
| | 1216 | }, |
| 1087 | else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}), | 1217 | else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}), |
| 1088 | } | 1218 | } |
| 1089 | } | 1219 | } |
| ... | @@ -1184,7 +1314,6 @@ pub const Context = struct { | ... | @@ -1184,7 +1314,6 @@ pub const Context = struct { |
| 1184 | const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len]; | 1314 | const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len]; |
| 1185 | const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; | 1315 | const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; |
| 1186 | const writer = self.code.writer(); | 1316 | const writer = self.code.writer(); |
| 1187 | | | |
| 1188 | // TODO: Handle death instructions for then and else body | 1317 | // TODO: Handle death instructions for then and else body |
| 1189 | | 1318 | |
| 1190 | // insert blocks at the position of `offset` so | 1319 | // insert blocks at the position of `offset` so |
| ... | @@ -1494,4 +1623,60 @@ pub const Context = struct { | ... | @@ -1494,4 +1623,60 @@ pub const Context = struct { |
| 1494 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | 1623 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 1495 | return self.resolveInst(ty_op.operand); | 1624 | return self.resolveInst(ty_op.operand); |
| 1496 | } | 1625 | } |
| | 1626 | |
| | 1627 | fn airIntcast(self: *Context, inst: Air.Inst.Index) InnerError!WValue { |
| | 1628 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| | 1629 | const ty = self.air.getRefType(ty_op.ty); |
| | 1630 | const operand = self.resolveInst(ty_op.operand); |
| | 1631 | const ref_ty = self.air.typeOf(ty_op.operand); |
| | 1632 | const ref_info = ref_ty.intInfo(self.target); |
| | 1633 | const op_bits = ref_info.bits; |
| | 1634 | const wanted_bits = ty.intInfo(self.target).bits; |
| | 1635 | |
| | 1636 | try self.emitWValue(operand); |
| | 1637 | if (op_bits > 32 and wanted_bits <= 32) { |
| | 1638 | try self.code.append(wasm.opcode(.i32_wrap_i64)); |
| | 1639 | } else if (op_bits <= 32 and wanted_bits > 32) { |
| | 1640 | try self.code.append(wasm.opcode(switch (ref_info.signedness) { |
| | 1641 | .signed => .i64_extend_i32_s, |
| | 1642 | .unsigned => .i64_extend_i32_u, |
| | 1643 | })); |
| | 1644 | } |
| | 1645 | |
| | 1646 | // other cases are no-op |
| | 1647 | return .none; |
| | 1648 | } |
| | 1649 | |
| | 1650 | fn airIsNull(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue { |
| | 1651 | const un_op = self.air.instructions.items(.data)[inst].un_op; |
| | 1652 | const operand = self.resolveInst(un_op); |
| | 1653 | // const offset = self.code.items.len; |
| | 1654 | const writer = self.code.writer(); |
| | 1655 | |
| | 1656 | // load the null value which is positioned at multi_value's index |
| | 1657 | try self.emitWValue(.{ .local = operand.multi_value.index }); |
| | 1658 | // Compare the null value with '0' |
| | 1659 | try writer.writeByte(wasm.opcode(.i32_const)); |
| | 1660 | try leb.writeILEB128(writer, @as(i32, 0)); |
| | 1661 | |
| | 1662 | try writer.writeByte(@enumToInt(opcode)); |
| | 1663 | |
| | 1664 | // we save the result in a new local |
| | 1665 | const local = try self.allocLocal(Type.initTag(.i32)); |
| | 1666 | try writer.writeByte(wasm.opcode(.local_set)); |
| | 1667 | try leb.writeULEB128(writer, local.local); |
| | 1668 | |
| | 1669 | return local; |
| | 1670 | } |
| | 1671 | |
| | 1672 | fn airOptionalPayload(self: *Context, inst: Air.Inst.Index) InnerError!WValue { |
| | 1673 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| | 1674 | const operand = self.resolveInst(ty_op.operand); |
| | 1675 | return WValue{ .local = operand.multi_value.index + 1 }; |
| | 1676 | } |
| | 1677 | |
| | 1678 | fn airWrapOptional(self: *Context, inst: Air.Inst.Index) InnerError!WValue { |
| | 1679 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| | 1680 | return self.resolveInst(ty_op.operand); |
| | 1681 | } |
| 1497 | }; | 1682 | }; |