authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-08-01 11:05:15+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-08-01 21:30:06+02:00
log6e139d124be92cfddef01adaa166ce09691cd5cc
treefcc938b49c45f790f2b0ca5b8d1cba67faf64046
parenta861b7d160737acb60913d2ff7ed316f19af9066
signaturelock-open Commit is signed but in an unrecognized format.

wasm: Resolve feedback (wrapping arbitrary int sizes)

- This ensures we honor the user's integer size when performing wrapping operations. - Also, instead of using ensureCapacity, we now use ensureUnusedCapacity.

2 files changed, 104 insertions(+), 6 deletions(-)

src/codegen/wasm.zig+62-6
......@@ -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;
......@@ -670,7 +670,7 @@ pub const Context = struct {
670670 return self.fail("TODO: wasm optional pointer", .{});
671671 }
672672
673 try self.locals.ensureCapacity(self.gpa, self.locals.items.len + 2);
673 try self.locals.ensureUnusedCapacity(self.gpa, 2);
674674 self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // optional 'tag' for null-checking is always i32
675675 self.locals.appendAssumeCapacity(try self.genValtype(child_type));
676676 self.local_index += 2;
......@@ -817,11 +817,11 @@ pub const Context = struct {
817817 const air_tags = self.air.instructions.items(.tag);
818818 return switch (air_tags[inst]) {
819819 .add => self.airBinOp(inst, .add),
820 .addwrap => self.airBinOp(inst, .add),
820 .addwrap => self.airWrapBinOp(inst, .add),
821821 .sub => self.airBinOp(inst, .sub),
822 .subwrap => self.airBinOp(inst, .sub),
822 .subwrap => self.airWrapBinOp(inst, .sub),
823823 .mul => self.airBinOp(inst, .mul),
824 .mulwrap => self.airBinOp(inst, .mul),
824 .mulwrap => self.airWrapBinOp(inst, .mul),
825825 .div => self.airBinOp(inst, .div),
826826 .bit_and => self.airBinOp(inst, .@"and"),
827827 .bit_or => self.airBinOp(inst, .@"or"),
......@@ -1021,6 +1021,62 @@ pub const Context = struct {
10211021 return WValue{ .code_offset = offset };
10221022 }
10231023
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
10241080 fn emitConstant(self: *Context, val: Value, ty: Type) InnerError!void {
10251081 const writer = self.code.writer();
10261082 switch (ty.zigTypeTag()) {
test/stage2/wasm.zig+42
......@@ -120,6 +120,20 @@ pub fn addCases(ctx: *TestContext) !void {
120120 \\}
121121 , "-2147483648\n");
122122
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
123137 case.addCompareOutput(
124138 \\pub export fn _start() u32 {
125139 \\ var i: u32 = 5;
......@@ -147,6 +161,20 @@ pub fn addCases(ctx: *TestContext) !void {
147161 \\}
148162 , "2147483647\n");
149163
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
150178 case.addCompareOutput(
151179 \\pub export fn _start() u32 {
152180 \\ var i: u32 = 5;
......@@ -178,6 +206,20 @@ pub fn addCases(ctx: *TestContext) !void {
178206 \\}
179207 , "-2\n");
180208
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
181223 case.addCompareOutput(
182224 \\pub export fn _start() u32 {
183225 \\ var i: u32 = 352;