authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-08-01 20:33:55-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-08-01 20:33:55-04:00
logd5f173d28f2991e05dad399ed4d688297e0a5ca7
treeb5b23dc68d6b6f07537f304ee95cced125ddfa23
parentddf14323ea9b2c75ac5ed286525d27730a192b53
parent6e139d124be92cfddef01adaa166ce09691cd5cc
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #9496 from Luukdegram/stage2-wasm

stage2: wasm - Wrapping, intcast and optionals

3 files changed, 326 insertions(+), 7 deletions(-)

lib/std/wasm.zig+8-2
......@@ -162,8 +162,14 @@ pub const Opcode = enum(u8) {
162162 i32_wrap_i64 = 0xA7,
163163 i32_trunc_f32_s = 0xA8,
164164 i32_trunc_f32_u = 0xA9,
165 i32_trunc_f64_s = 0xB0,
166 i32_trunc_f64_u = 0xB1,
165 i32_trunc_f64_s = 0xAA,
166 i32_trunc_f64_u = 0xAB,
167 i64_extend_i32_s = 0xAC,
168 i64_extend_i32_u = 0xAD,
169 i64_trunc_f32_s = 0xAE,
170 i64_trunc_f32_u = 0xAF,
171 i64_trunc_f64_s = 0xB0,
172 i64_trunc_f64_u = 0xB1,
167173 f32_convert_i32_s = 0xB2,
168174 f32_convert_i32_u = 0xB3,
169175 f32_convert_i64_s = 0xB4,
src/codegen/wasm.zig+190-5
......@@ -590,8 +590,8 @@ pub const Context = struct {
590590 .Pointer,
591591 .ErrorSet,
592592 => wasm.Valtype.i32,
593 .Struct, .ErrorUnion => unreachable, // Multi typed, must be handled individually.
594 else => self.fail("TODO - Wasm valtype for type '{s}'", .{ty.zigTypeTag()}),
593 .Struct, .ErrorUnion, .Optional => unreachable, // Multi typed, must be handled individually.
594 else => |tag| self.fail("TODO - Wasm valtype for type '{s}'", .{tag}),
595595 };
596596 }
597597
......@@ -634,7 +634,7 @@ pub const Context = struct {
634634 // for each struct field, generate a local
635635 const struct_data: *Module.Struct = ty.castTag(.@"struct").?.data;
636636 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);
638638 for (struct_data.fields.values()) |*value| {
639639 const val_type = try self.genValtype(value.ty);
640640 self.locals.appendAssumeCapacity(val_type);
......@@ -653,7 +653,7 @@ pub const Context = struct {
653653 // The first local is also used to find the index of the error and payload.
654654 //
655655 // 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);
657657 self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32
658658 self.locals.appendAssumeCapacity(val_type);
659659 self.local_index += 2;
......@@ -663,6 +663,23 @@ pub const Context = struct {
663663 .count = 2,
664664 } };
665665 },
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 },
666683 else => {
667684 const valtype = try self.genValtype(ty);
668685 try self.locals.append(self.gpa, valtype);
......@@ -800,8 +817,11 @@ pub const Context = struct {
800817 const air_tags = self.air.instructions.items(.tag);
801818 return switch (air_tags[inst]) {
802819 .add => self.airBinOp(inst, .add),
820 .addwrap => self.airWrapBinOp(inst, .add),
803821 .sub => self.airBinOp(inst, .sub),
822 .subwrap => self.airWrapBinOp(inst, .sub),
804823 .mul => self.airBinOp(inst, .mul),
824 .mulwrap => self.airWrapBinOp(inst, .mul),
805825 .div => self.airBinOp(inst, .div),
806826 .bit_and => self.airBinOp(inst, .@"and"),
807827 .bit_or => self.airBinOp(inst, .@"or"),
......@@ -826,8 +846,16 @@ pub const Context = struct {
826846 .cond_br => self.airCondBr(inst),
827847 .constant => unreachable,
828848 .dbg_stmt => WValue.none,
849 .intcast => self.airIntcast(inst),
850
829851 .is_err => self.airIsErr(inst, .i32_ne),
830852 .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
831859 .load => self.airLoad(inst),
832860 .loop => self.airLoop(inst),
833861 .not => self.airNot(inst),
......@@ -836,8 +864,13 @@ pub const Context = struct {
836864 .struct_field_ptr => self.airStructFieldPtr(inst),
837865 .switch_br => self.airSwitchBr(inst),
838866 .unreach => self.airUnreachable(inst),
867 .wrap_optional => self.airWrapOptional(inst),
868
839869 .unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst),
840870 .wrap_errunion_payload => self.airWrapErrUnionPayload(inst),
871
872 .optional_payload => self.airOptionalPayload(inst),
873 .optional_payload_ptr => self.airOptionalPayload(inst),
841874 else => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
842875 };
843876 }
......@@ -922,6 +955,22 @@ pub const Context = struct {
922955 try leb.writeULEB128(writer, multi_value.index + i - 1);
923956 }
924957 },
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 },
925974 else => unreachable,
926975 },
927976 .local => |local| {
......@@ -972,6 +1021,62 @@ pub const Context = struct {
9721021 return WValue{ .code_offset = offset };
9731022 }
9741023
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
9751080 fn emitConstant(self: *Context, val: Value, ty: Type) InnerError!void {
9761081 const writer = self.code.writer();
9771082 switch (ty.zigTypeTag()) {
......@@ -1084,6 +1189,31 @@ pub const Context = struct {
10841189 try self.emitConstant(data, payload_type);
10851190 }
10861191 },
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 },
10871217 else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),
10881218 }
10891219 }
......@@ -1184,7 +1314,6 @@ pub const Context = struct {
11841314 const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];
11851315 const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
11861316 const writer = self.code.writer();
1187
11881317 // TODO: Handle death instructions for then and else body
11891318
11901319 // insert blocks at the position of `offset` so
......@@ -1494,4 +1623,60 @@ pub const Context = struct {
14941623 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
14951624 return self.resolveInst(ty_op.operand);
14961625 }
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 }
14971682};
test/stage2/wasm.zig+128
......@@ -113,6 +113,27 @@ pub fn addCases(ctx: *TestContext) !void {
113113 \\}
114114 , "25\n");
115115
116 case.addCompareOutput(
117 \\pub export fn _start() i32 {
118 \\ var i: i32 = 2147483647;
119 \\ return i +% 1;
120 \\}
121 , "-2147483648\n");
122
123 case.addCompareOutput(
124 \\pub export fn _start() i32 {
125 \\ var i: i4 = 7;
126 \\ return i +% 1;
127 \\}
128 , "0\n");
129
130 case.addCompareOutput(
131 \\pub export fn _start() u32 {
132 \\ var i: u8 = 255;
133 \\ return i +% 1;
134 \\}
135 , "0\n");
136
116137 case.addCompareOutput(
117138 \\pub export fn _start() u32 {
118139 \\ var i: u32 = 5;
......@@ -133,6 +154,27 @@ pub fn addCases(ctx: *TestContext) !void {
133154 \\}
134155 , "15\n");
135156
157 case.addCompareOutput(
158 \\pub export fn _start() i32 {
159 \\ var i: i32 = -2147483648;
160 \\ return i -% 1;
161 \\}
162 , "2147483647\n");
163
164 case.addCompareOutput(
165 \\pub export fn _start() i32 {
166 \\ var i: i7 = -64;
167 \\ return i -% 1;
168 \\}
169 , "63\n");
170
171 case.addCompareOutput(
172 \\pub export fn _start() u32 {
173 \\ var i: u4 = 0;
174 \\ return i -% 1;
175 \\}
176 , "15\n");
177
136178 case.addCompareOutput(
137179 \\pub export fn _start() u32 {
138180 \\ var i: u32 = 5;
......@@ -157,6 +199,27 @@ pub fn addCases(ctx: *TestContext) !void {
157199 \\}
158200 , "350\n");
159201
202 case.addCompareOutput(
203 \\pub export fn _start() i32 {
204 \\ var i: i32 = 2147483647;
205 \\ return i *% 2;
206 \\}
207 , "-2\n");
208
209 case.addCompareOutput(
210 \\pub export fn _start() u32 {
211 \\ var i: u3 = 3;
212 \\ return i *% 3;
213 \\}
214 , "1\n");
215
216 case.addCompareOutput(
217 \\pub export fn _start() i32 {
218 \\ var i: i4 = 3;
219 \\ return i *% 3;
220 \\}
221 , "1\n");
222
160223 case.addCompareOutput(
161224 \\pub export fn _start() u32 {
162225 \\ var i: u32 = 352;
......@@ -612,4 +675,69 @@ pub fn addCases(ctx: *TestContext) !void {
612675 \\}
613676 , "42\n");
614677 }
678
679 {
680 var case = ctx.exe("wasm integer widening", wasi);
681
682 case.addCompareOutput(
683 \\pub export fn _start() u64 {
684 \\ var x: u32 = 5;
685 \\ return x;
686 \\}
687 , "5\n");
688 }
689
690 {
691 var case = ctx.exe("wasm optionals", wasi);
692
693 case.addCompareOutput(
694 \\pub export fn _start() u32 {
695 \\ var x: ?u32 = 5;
696 \\ var y: u32 = 0;
697 \\ if (x) |val| {
698 \\ y = val;
699 \\ }
700 \\ return y;
701 \\}
702 , "5\n");
703
704 case.addCompareOutput(
705 \\pub export fn _start() u32 {
706 \\ var x: ?u32 = null;
707 \\ var y: u32 = 0;
708 \\ if (x) |val| {
709 \\ y = val;
710 \\ }
711 \\ return y;
712 \\}
713 , "0\n");
714
715 case.addCompareOutput(
716 \\pub export fn _start() u32 {
717 \\ var x: ?u32 = 5;
718 \\ return x.?;
719 \\}
720 , "5\n");
721
722 case.addCompareOutput(
723 \\pub export fn _start() u32 {
724 \\ var x: u32 = 5;
725 \\ var y: ?u32 = x;
726 \\ return y.?;
727 \\}
728 , "5\n");
729
730 case.addCompareOutput(
731 \\pub export fn _start() u32 {
732 \\ var val: ?u32 = 5;
733 \\ while (val) |*v| {
734 \\ v.* -= 1;
735 \\ if (v.* == 2) {
736 \\ val = null;
737 \\ }
738 \\ }
739 \\ return 0;
740 \\}
741 , "0\n");
742 }
615743}