authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-19 22:15:53+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-21 21:07:55+01:00
logec5220405b0218f892f6a1636ddd04d791017309
treed69aa5d8c4e876a61da6335d79149e8278194dde
parent460b3d39eae8d294efbe2e5762ea38c93c352d63
signature Commit is signed but in an unrecognized format.

wasm: Implement optionals and ensure correct alignment

Rather than writing the alignment in its natural form, wasm binaries encode the alignment of types as the exponent of a power of 2. So rather than performing this encoding during AIR->MIR, we do this while emitting MIR->binary encoding. This allows us to keep alignment logic to its natural form while doing calculations (Which is what we need during linking as well). We also implement optionals and pointers to an optional.

2 files changed, 61 insertions(+), 22 deletions(-)

src/arch/wasm/CodeGen.zig+57-21
......@@ -211,7 +211,8 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
211211 32 => switch (args.valtype1.?) {
212212 .i64 => return .i64_store32,
213213 .i32 => return .i32_store,
214 .f32, .f64 => unreachable,
214 .f32 => return .f32_store,
215 .f64 => unreachable,
215216 },
216217 64 => switch (args.valtype1.?) {
217218 .i64 => return .i64_store,
......@@ -680,6 +681,7 @@ fn typeToValtype(self: *Self, ty: Type) InnerError!wasm.Valtype {
680681 .ErrorSet,
681682 .Struct,
682683 .ErrorUnion,
684 .Optional,
683685 => wasm.Valtype.i32,
684686 else => self.fail("TODO - Wasm valtype for type '{}'", .{ty}),
685687 };
......@@ -878,7 +880,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
878880
879881 const ret_ty = fn_ty.fnReturnType();
880882 switch (ret_ty.zigTypeTag()) {
881 .ErrorUnion => result.return_value = try self.allocLocal(Type.initTag(.i32)),
883 .ErrorUnion, .Optional => result.return_value = try self.allocLocal(Type.initTag(.i32)),
882884 .Int, .Float, .Bool, .Void, .NoReturn => {},
883885 else => return self.fail("TODO: Implement function return type {}", .{ret_ty}),
884886 }
......@@ -1063,7 +1065,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
10631065
10641066 const ret_ty = target.ty.fnReturnType();
10651067 switch (ret_ty.zigTypeTag()) {
1066 .ErrorUnion => {
1068 .ErrorUnion, .Optional => {
10671069 const result_local = try self.allocLocal(ret_ty);
10681070 try self.addLabel(.local_set, result_local.local);
10691071 return result_local;
......@@ -1111,14 +1113,15 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
11111113 .ErrorUnion, .Optional => {
11121114 var buf: Type.Payload.ElemType = undefined;
11131115 const payload_ty = if (ty.zigTypeTag() == .ErrorUnion) ty.errorUnionPayload() else ty.optionalChild(&buf);
1114 const tag_ty = if (ty.zigTypeTag() == .ErrorUnion) ty.errorUnionSet() else Type.initTag(.i8);
1115 const payload_offset = @intCast(u32, tag_ty.abiSize(self.target) / 8);
1116 const tag_ty = if (ty.zigTypeTag() == .ErrorUnion) ty.errorUnionSet() else Type.initTag(.u8);
1117 const payload_offset = @intCast(u32, tag_ty.abiSize(self.target));
1118
11161119 if (rhs == .constant) {
11171120 // constant will contain both tag and payload,
11181121 // so save those in 2 temporary locals before storing them
11191122 // in memory
11201123 try self.emitWValue(rhs);
1121 const tag_local = try self.allocLocal(Type.initTag(.i32));
1124 const tag_local = try self.allocLocal(tag_ty);
11221125 const payload_local = try self.allocLocal(payload_ty);
11231126
11241127 try self.addLabel(.local_set, payload_local.local);
......@@ -1147,8 +1150,14 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
11471150 });
11481151
11491152 // store rhs value at stack pointer's location in memory
1150 const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = offset, .alignment = 0 });
1151 try self.addInst(.{ .tag = Mir.Inst.Tag.fromOpcode(opcode), .data = .{ .payload = mem_arg_index } });
1153 const mem_arg_index = try self.addExtra(Mir.MemArg{
1154 .offset = offset,
1155 .alignment = ty.abiAlignment(self.target),
1156 });
1157 try self.addInst(.{
1158 .tag = Mir.Inst.Tag.fromOpcode(opcode),
1159 .data = .{ .payload = mem_arg_index },
1160 });
11521161}
11531162
11541163fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
......@@ -1157,8 +1166,11 @@ fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
11571166 const ty = self.air.getRefType(ty_op.ty);
11581167
11591168 return switch (ty.zigTypeTag()) {
1160 .Struct, .ErrorUnion => operand,
1161 else => self.load(operand, ty, 0),
1169 .Struct, .ErrorUnion, .Optional => operand, // pass as pointer
1170 else => switch (operand) {
1171 .local_with_offset => |with_offset| try self.load(operand, ty, with_offset.offset),
1172 else => try self.load(operand, ty, 0),
1173 },
11621174 };
11631175}
11641176
......@@ -1174,7 +1186,10 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
11741186 .signedness = signedness,
11751187 });
11761188
1177 const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = offset, .alignment = 0 });
1189 const mem_arg_index = try self.addExtra(Mir.MemArg{
1190 .offset = offset,
1191 .alignment = ty.abiAlignment(self.target),
1192 });
11781193 try self.addInst(.{
11791194 .tag = Mir.Inst.Tag.fromOpcode(opcode),
11801195 .data = .{ .payload = mem_arg_index },
......@@ -1301,7 +1316,7 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
13011316
13021317 // memory instruction followed by their memarg immediate
13031318 // memarg ::== x:u32, y:u32 => {align x, offset y}
1304 const extra_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 0 });
1319 const extra_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 4 });
13051320 try self.addInst(.{ .tag = .i32_load, .data = .{ .payload = extra_index } });
13061321 } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()});
13071322 },
......@@ -1774,7 +1789,10 @@ fn airIsErr(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!W
17741789
17751790 // load the error tag value
17761791 try self.emitWValue(operand);
1777 const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 0 });
1792 const mem_arg_index = try self.addExtra(Mir.MemArg{
1793 .offset = 0,
1794 .alignment = err_ty.abiAlignment(self.target),
1795 });
17781796 try self.addInst(.{
17791797 .tag = .i32_load,
17801798 .data = .{ .payload = mem_arg_index },
......@@ -1830,22 +1848,40 @@ fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!
18301848 const un_op = self.air.instructions.items(.data)[inst].un_op;
18311849 const operand = self.resolveInst(un_op);
18321850
1833 // load the null value which is positioned at local_with_offset's index
1834 try self.emitWValue(.{ .local = operand.local_with_offset.local });
1851 // load the null tag value
1852 try self.emitWValue(operand);
1853 const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 1 });
1854 try self.addInst(.{
1855 .tag = .i32_load8_u,
1856 .data = .{ .payload = mem_arg_index },
1857 });
1858
1859 // Compare the error value with '0'
18351860 try self.addImm32(0);
18361861 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
18371862
1838 // we save the result in a new local
1839 const local = try self.allocLocal(Type.initTag(.i32));
1840 try self.addLabel(.local_set, local.local);
1841
1842 return local;
1863 const is_null_tmp = try self.allocLocal(Type.initTag(.u8));
1864 try self.addLabel(.local_set, is_null_tmp.local);
1865 return is_null_tmp;
18431866}
18441867
18451868fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
18461869 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
18471870 const operand = self.resolveInst(ty_op.operand);
1848 return WValue{ .local = operand.local_with_offset.local + 1 };
1871 const opt_ty = self.air.typeOf(ty_op.operand);
1872
1873 // For pointers we simply return its stack address, rather than
1874 // loading its value
1875 if (opt_ty.zigTypeTag() == .Pointer) {
1876 return WValue{ .local_with_offset = .{ .local = operand.local, .offset = 1 } };
1877 }
1878
1879 if (opt_ty.isPtrLikeOptional()) return operand;
1880
1881 var buf: Type.Payload.ElemType = undefined;
1882 const child_ty = opt_ty.optionalChild(&buf);
1883
1884 return self.load(operand, child_ty, @as(u32, 1)); // null tag is 1 byte
18491885}
18501886
18511887fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
src/arch/wasm/Emit.zig+4-1
......@@ -251,7 +251,10 @@ fn emitMemArg(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
251251 const extra_index = emit.mir.instructions.items(.data)[inst].payload;
252252 const mem_arg = emit.mir.extraData(Mir.MemArg, extra_index).data;
253253 try emit.code.append(@enumToInt(tag));
254 try leb128.writeULEB128(emit.code.writer(), mem_arg.alignment);
254
255 // wasm encodes alignment as power of 2, rather than natural alignment
256 const encoded_alignment = mem_arg.alignment >> 1;
257 try leb128.writeULEB128(emit.code.writer(), encoded_alignment);
255258 try leb128.writeULEB128(emit.code.writer(), mem_arg.offset);
256259}
257260