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
signature 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) {...@@ -162,8 +162,14 @@ pub const Opcode = enum(u8) {
162 i32_wrap_i64 = 0xA7,162 i32_wrap_i64 = 0xA7,
163 i32_trunc_f32_s = 0xA8,163 i32_trunc_f32_s = 0xA8,
164 i32_trunc_f32_u = 0xA9,164 i32_trunc_f32_u = 0xA9,
165 i32_trunc_f64_s = 0xB0,165 i32_trunc_f64_s = 0xAA,
166 i32_trunc_f64_u = 0xB1,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,
167 f32_convert_i32_s = 0xB2,173 f32_convert_i32_s = 0xB2,
168 f32_convert_i32_u = 0xB3,174 f32_convert_i32_u = 0xB3,
169 f32_convert_i64_s = 0xB4,175 f32_convert_i64_s = 0xB4,
src/codegen/wasm.zig+190-5
...@@ -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 }
597597
...@@ -634,7 +634,7 @@ pub const Context = struct {...@@ -634,7 +634,7 @@ pub const Context = struct {
634 // for each struct field, generate a local634 // 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 i32657 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 }
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
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 body1317 // TODO: Handle death instructions for then and else body
11891318
1190 // insert blocks at the position of `offset` so1319 // 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};
test/stage2/wasm.zig+128
...@@ -113,6 +113,27 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -113,6 +113,27 @@ pub fn addCases(ctx: *TestContext) !void {
113 \\}113 \\}
114 , "25\n");114 , "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
116 case.addCompareOutput(137 case.addCompareOutput(
117 \\pub export fn _start() u32 {138 \\pub export fn _start() u32 {
118 \\ var i: u32 = 5;139 \\ var i: u32 = 5;
...@@ -133,6 +154,27 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -133,6 +154,27 @@ pub fn addCases(ctx: *TestContext) !void {
133 \\}154 \\}
134 , "15\n");155 , "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
136 case.addCompareOutput(178 case.addCompareOutput(
137 \\pub export fn _start() u32 {179 \\pub export fn _start() u32 {
138 \\ var i: u32 = 5;180 \\ var i: u32 = 5;
...@@ -157,6 +199,27 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -157,6 +199,27 @@ pub fn addCases(ctx: *TestContext) !void {
157 \\}199 \\}
158 , "350\n");200 , "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
160 case.addCompareOutput(223 case.addCompareOutput(
161 \\pub export fn _start() u32 {224 \\pub export fn _start() u32 {
162 \\ var i: u32 = 352;225 \\ var i: u32 = 352;
...@@ -612,4 +675,69 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -612,4 +675,69 @@ pub fn addCases(ctx: *TestContext) !void {
612 \\}675 \\}
613 , "42\n");676 , "42\n");
614 }677 }
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 }
615}743}