authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-28 20:25:33+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-28 20:25:33+01:00
logdd49eca34274cd2396cbe06a199fe8db9e8faf79
tree928ab49cdd2021db17a83f7bb5c5f3cb19e10bef
parent7226ad2670f267b4d90b84d0e104fbb1fa41fe49
signaturelock-open Commit is signed but in an unrecognized format.

wasm: Implement 'zig test'

- This implements the required codegen for decl types such as pointers, arrays, structs and more. - Wasm's start function can now use both a 'u8' and 'void' as return type. This will help us with writing tests using the stage2 testing backend. (Until all tests of behavioural tests pass). - Now correctly generates relocations for function pointers. - Also implements unwrapping error union error, as well as return pointers.

4 files changed, 202 insertions(+), 31 deletions(-)

lib/std/start.zig+13-2
...@@ -101,8 +101,19 @@ fn callMain2() noreturn {...@@ -101,8 +101,19 @@ fn callMain2() noreturn {
101}101}
102102
103fn wasmMain2() u8 {103fn wasmMain2() u8 {
104 root.main();104 switch (@typeInfo(@typeInfo(@TypeOf(root.main)).Fn.return_type.?)) {
105 return 0;105 .Void => {
106 root.main();
107 return 0;
108 },
109 .Int => |info| {
110 if (info.bits != 8 or info.signedness == .signed) {
111 @compileError(bad_main_ret);
112 }
113 return root.main();
114 },
115 else => @compileError("Bad return type main"),
116 }
106}117}
107118
108fn wWinMainCRTStartup2() callconv(.C) noreturn {119fn wWinMainCRTStartup2() callconv(.C) noreturn {
src/arch/wasm/CodeGen.zig+186-24
...@@ -692,6 +692,7 @@ fn typeToValtype(self: *Self, ty: Type) InnerError!wasm.Valtype {...@@ -692,6 +692,7 @@ fn typeToValtype(self: *Self, ty: Type) InnerError!wasm.Valtype {
692 .Struct,692 .Struct,
693 .ErrorUnion,693 .ErrorUnion,
694 .Optional,694 .Optional,
695 .Fn,
695 => wasm.Valtype.i32,696 => wasm.Valtype.i32,
696 else => self.fail("TODO - Wasm valtype for type '{}'", .{ty}),697 else => self.fail("TODO - Wasm valtype for type '{}'", .{ty}),
697 };698 };
...@@ -809,23 +810,52 @@ pub fn genFunc(self: *Self) InnerError!Result {...@@ -809,23 +810,52 @@ pub fn genFunc(self: *Self) InnerError!Result {
809}810}
810811
811/// Generates the wasm bytecode for the declaration belonging to `Context`812/// Generates the wasm bytecode for the declaration belonging to `Context`
812pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {813pub fn genDecl(self: *Self, ty: Type, val: Value) InnerError!Result {
814 if (val.isUndef()) {
815 try self.code.appendNTimes(0xaa, ty.abiSize(self.target));
816 return Result.appended;
817 }
813 switch (ty.zigTypeTag()) {818 switch (ty.zigTypeTag()) {
814 .Fn => {819 .Fn => {
815 if (val.tag() == .extern_fn) {820 const fn_decl = switch (val.tag()) {
816 var func_type = try self.genFunctype(self.decl.ty);821 .extern_fn => val.castTag(.extern_fn).?.data,
817 defer func_type.deinit(self.gpa);822 .function => val.castTag(.function).?.data.owner_decl,
818 self.decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);823 else => unreachable,
819 return Result.appended; // don't need code body for extern functions824 };
825 return try self.lowerDeclRef(fn_decl);
826 },
827 .Optional => {
828 var opt_buf: Type.Payload.ElemType = undefined;
829 const payload_type = ty.optionalChild(&opt_buf);
830 if (ty.isPtrLikeOptional()) {
831 if (val.castTag(.opt_payload)) |payload| {
832 return try self.genDecl(payload_type, payload.data);
833 } else if (!val.isNull()) {
834 return try self.genDecl(payload_type, val);
835 } else {
836 try self.code.appendNTimes(0, ty.abiSize(self.target));
837 return Result.appended;
838 }
839 }
840 // `null-tag` byte
841 try self.code.appendNTimes(@boolToInt(!val.isNull()), 4);
842 const pl_result = try self.genDecl(
843 payload_type,
844 if (val.castTag(.opt_payload)) |pl| pl.data else Value.initTag(.undef),
845 );
846 switch (pl_result) {
847 .appended => {},
848 .externally_managed => |payload| try self.code.appendSlice(payload),
820 }849 }
821 return self.fail("TODO implement wasm codegen for function pointers", .{});850 return Result.appended;
822 },851 },
823 .Array => {852 .Array => switch (val.tag()) {
824 if (val.castTag(.bytes)) |payload| {853 .bytes => {
854 const payload = val.castTag(.bytes).?;
825 if (ty.sentinel()) |sentinel| {855 if (ty.sentinel()) |sentinel| {
826 try self.code.appendSlice(payload.data);856 try self.code.appendSlice(payload.data);
827857
828 switch (try self.gen(ty.childType(), sentinel)) {858 switch (try self.genDecl(ty.childType(), sentinel)) {
829 .appended => return Result.appended,859 .appended => return Result.appended,
830 .externally_managed => |data| {860 .externally_managed => |data| {
831 try self.code.appendSlice(data);861 try self.code.appendSlice(data);
...@@ -834,16 +864,33 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {...@@ -834,16 +864,33 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
834 }864 }
835 }865 }
836 return Result{ .externally_managed = payload.data };866 return Result{ .externally_managed = payload.data };
837 } else return self.fail("TODO implement gen for more kinds of arrays", .{});867 },
868 .array => {
869 const elem_vals = val.castTag(.array).?.data;
870 const elem_ty = ty.elemType();
871 for (elem_vals) |elem_val| {
872 switch (try self.genDecl(elem_ty, elem_val)) {
873 .appended => {},
874 .externally_managed => |data| {
875 try self.code.appendSlice(data);
876 },
877 }
878 }
879 return Result.appended;
880 },
881 else => return self.fail("TODO implement genDecl for array type value: {s}", .{@tagName(val.tag())}),
838 },882 },
839 .Int => {883 .Int => {
840 const info = ty.intInfo(self.target);884 const info = ty.intInfo(self.target);
841 if (info.bits == 8 and info.signedness == .unsigned) {885 const abi_size = ty.abiSize(self.target);
842 const int_byte = val.toUnsignedInt();886 // todo: Implement integer sizes larger than 64bits
843 try self.code.append(@intCast(u8, int_byte));887 if (info.bits > 64) return self.fail("TODO: Implement genDecl for integer bit size: {d}", .{info.bits});
844 return Result.appended;888 var buf: [8]u8 = undefined;
845 }889 if (info.signedness == .unsigned) {
846 return self.fail("TODO: Implement codegen for int type: '{}'", .{ty});890 std.mem.writeIntLittle(u64, &buf, val.toUnsignedInt());
891 } else std.mem.writeIntLittle(i64, &buf, val.toSignedInt());
892 try self.code.appendSlice(buf[0..abi_size]);
893 return Result.appended;
847 },894 },
848 .Enum => {895 .Enum => {
849 try self.emitConstant(val, ty);896 try self.emitConstant(val, ty);
...@@ -855,15 +902,83 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {...@@ -855,15 +902,83 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
855 return Result.appended;902 return Result.appended;
856 },903 },
857 .Struct => {904 .Struct => {
858 // TODO write the fields for real905 const field_vals = val.castTag(.@"struct").?.data;
859 const abi_size = try std.math.cast(usize, ty.abiSize(self.target));906 for (field_vals) |field_val, index| {
907 const field_ty = ty.structFieldType(index);
908 if (!field_ty.hasCodeGenBits()) continue;
909
910 switch (try self.genDecl(field_ty, field_val)) {
911 .appended => {},
912 .externally_managed => |payload| try self.code.appendSlice(payload),
913 }
914 }
915 return Result.appended;
916 },
917 .Union => {
918 // TODO: Implement Union declarations
919 const abi_size = ty.abiSize(self.target);
860 try self.code.writer().writeByteNTimes(0xaa, abi_size);920 try self.code.writer().writeByteNTimes(0xaa, abi_size);
861 return Result{ .appended = {} };921 return Result.appended;
922 },
923 .Pointer => switch (val.tag()) {
924 .variable => {
925 const decl = val.castTag(.variable).?.data.owner_decl;
926 return try self.lowerDeclRef(decl);
927 },
928 .decl_ref => {
929 const decl = val.castTag(.decl_ref).?.data;
930 return try self.lowerDeclRef(decl);
931 },
932 .slice => {
933 const slice = val.castTag(.slice).?.data;
934 var buf: Type.SlicePtrFieldTypeBuffer = undefined;
935 const ptr_ty = ty.slicePtrFieldType(&buf);
936 switch (try self.genDecl(ptr_ty, slice.ptr)) {
937 .externally_managed => |data| try self.code.appendSlice(data),
938 .appended => {},
939 }
940 switch (try self.genDecl(Type.usize, slice.len)) {
941 .externally_managed => |data| try self.code.appendSlice(data),
942 .appended => {},
943 }
944 return Result.appended;
945 },
946 else => return self.fail("TODO: Implement zig decl gen for pointer type value: '{s}'", .{@tagName(val.tag())}),
862 },947 },
863 else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),948 else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),
864 }949 }
865}950}
866951
952fn lowerDeclRef(self: *Self, decl: *Module.Decl) InnerError!Result {
953 decl.alive = true;
954
955 const offset = @intCast(u32, self.code.items.len);
956 const atom = &self.decl.link.wasm;
957 const target_sym_index = decl.link.wasm.sym_index;
958
959 if (decl.ty.zigTypeTag() == .Fn) {
960 // We found a function pointer, so add it to our table,
961 // as function pointers are not allowed to be stored inside the data section,
962 // but rather in a function table which are called by index
963 try self.bin_file.addTableFunction(target_sym_index);
964 try atom.relocs.append(self.gpa, .{
965 .index = target_sym_index,
966 .offset = offset,
967 .relocation_type = .R_WASM_TABLE_INDEX_I32,
968 });
969 } else {
970 try atom.relocs.append(self.gpa, .{
971 .index = target_sym_index,
972 .offset = offset,
973 .relocation_type = .R_WASM_MEMORY_ADDR_I32,
974 });
975 }
976 const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8;
977 try self.code.appendNTimes(0xaa, ptr_width);
978
979 return Result.appended;
980}
981
867const CallWValues = struct {982const CallWValues = struct {
868 args: []WValue,983 args: []WValue,
869 return_value: WValue,984 return_value: WValue,
...@@ -1015,6 +1130,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {...@@ -1015,6 +1130,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
1015 .loop => self.airLoop(inst),1130 .loop => self.airLoop(inst),
1016 .not => self.airNot(inst),1131 .not => self.airNot(inst),
1017 .ret => self.airRet(inst),1132 .ret => self.airRet(inst),
1133 .ret_ptr => self.airRetPtr(inst),
1134 .ret_load => self.airRetLoad(inst),
1018 .slice_len => self.airSliceLen(inst),1135 .slice_len => self.airSliceLen(inst),
1019 .slice_elem_val => self.airSliceElemVal(inst),1136 .slice_elem_val => self.airSliceElemVal(inst),
1020 .store => self.airStore(inst),1137 .store => self.airStore(inst),
...@@ -1029,6 +1146,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {...@@ -1029,6 +1146,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
1029 .wrap_optional => self.airWrapOptional(inst),1146 .wrap_optional => self.airWrapOptional(inst),
10301147
1031 .unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst),1148 .unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst),
1149 .unwrap_errunion_err => self.airUnwrapErrUnionError(inst),
1032 .wrap_errunion_payload => self.airWrapErrUnionPayload(inst),1150 .wrap_errunion_payload => self.airWrapErrUnionPayload(inst),
10331151
1034 .optional_payload => self.airOptionalPayload(inst),1152 .optional_payload => self.airOptionalPayload(inst),
...@@ -1061,6 +1179,34 @@ fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1061,6 +1179,34 @@ fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1061 return .none;1179 return .none;
1062}1180}
10631181
1182fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1183 const child_type = self.air.typeOfIndex(inst).childType();
1184
1185 // Initialize the stack
1186 if (self.initial_stack_value == .none) {
1187 try self.initializeStack();
1188 }
1189
1190 const abi_size = child_type.abiSize(self.target);
1191 if (abi_size == 0) return WValue{ .none = {} };
1192
1193 // local, containing the offset to the stack position
1194 const local = try self.allocLocal(Type.initTag(.i32)); // always pointer therefore i32
1195 try self.moveStack(@intCast(u32, abi_size), local.local);
1196
1197 return local;
1198}
1199
1200fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1201 const un_op = self.air.instructions.items(.data)[inst].un_op;
1202 const operand = self.resolveInst(un_op);
1203 const result = try self.load(operand, self.air.typeOf(un_op), 0);
1204 try self.addLabel(.local_get, result.local);
1205 try self.restoreStackPointer();
1206 try self.addTag(.@"return");
1207 return .none;
1208}
1209
1064fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {1210fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1065 const pl_op = self.air.instructions.items(.data)[inst].pl_op;1211 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
1066 const extra = self.air.extraData(Air.Call, pl_op.payload);1212 const extra = self.air.extraData(Air.Call, pl_op.payload);
...@@ -1096,6 +1242,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1096,6 +1242,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1096 // so load its value onto the stack1242 // so load its value onto the stack
1097 std.debug.assert(ty.zigTypeTag() == .Pointer);1243 std.debug.assert(ty.zigTypeTag() == .Pointer);
1098 const operand = self.resolveInst(pl_op.operand);1244 const operand = self.resolveInst(pl_op.operand);
1245 try self.emitWValue(operand);
1099 const result = try self.load(operand, fn_ty, operand.local_with_offset.offset);1246 const result = try self.load(operand, fn_ty, operand.local_with_offset.offset);
1100 try self.addLabel(.local_get, result.local);1247 try self.addLabel(.local_get, result.local);
11011248
...@@ -1229,6 +1376,8 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro...@@ -1229,6 +1376,8 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
1229 // that is portable across the backend, rather than copying logic.1376 // that is portable across the backend, rather than copying logic.
1230 const abi_size = if ((ty.isInt() or ty.isAnyFloat()) and ty.abiSize(self.target) <= 8)1377 const abi_size = if ((ty.isInt() or ty.isAnyFloat()) and ty.abiSize(self.target) <= 8)
1231 @intCast(u8, ty.abiSize(self.target))1378 @intCast(u8, ty.abiSize(self.target))
1379 else if (ty.zigTypeTag() == .ErrorSet or ty.zigTypeTag() == .Enum)
1380 @intCast(u8, ty.abiSize(self.target))
1232 else1381 else
1233 @as(u8, 4);1382 @as(u8, 4);
1234 const opcode = buildOpcode(.{1383 const opcode = buildOpcode(.{
...@@ -1272,6 +1421,8 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {...@@ -1272,6 +1421,8 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
1272 // that is portable across the backend, rather than copying logic.1421 // that is portable across the backend, rather than copying logic.
1273 const abi_size = if ((ty.isInt() or ty.isAnyFloat()) and ty.abiSize(self.target) <= 8)1422 const abi_size = if ((ty.isInt() or ty.isAnyFloat()) and ty.abiSize(self.target) <= 8)
1274 @intCast(u8, ty.abiSize(self.target))1423 @intCast(u8, ty.abiSize(self.target))
1424 else if (ty.zigTypeTag() == .ErrorSet or ty.zigTypeTag() == .Enum)
1425 @intCast(u8, ty.abiSize(self.target))
1275 else1426 else
1276 @as(u8, 4);1427 @as(u8, 4);
12771428
...@@ -1920,6 +2071,15 @@ fn airUnwrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue...@@ -1920,6 +2071,15 @@ fn airUnwrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue
1920 return try self.load(operand, payload_ty, offset);2071 return try self.load(operand, payload_ty, offset);
1921}2072}
19222073
2074fn airUnwrapErrUnionError(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2075 if (self.liveness.isUnused(inst)) return WValue.none;
2076
2077 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2078 const operand = self.resolveInst(ty_op.operand);
2079 const err_ty = self.air.typeOf(ty_op.operand);
2080 return try self.load(operand, err_ty.errorUnionSet(), 0);
2081}
2082
1923fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {2083fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1924 const ty_op = self.air.instructions.items(.data)[inst].ty_op;2084 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1925 _ = ty_op;2085 _ = ty_op;
...@@ -1935,18 +2095,20 @@ fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1935,18 +2095,20 @@ fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1935 const op_bits = ref_info.bits;2095 const op_bits = ref_info.bits;
1936 const wanted_bits = ty.intInfo(self.target).bits;2096 const wanted_bits = ty.intInfo(self.target).bits;
19372097
1938 try self.emitWValue(operand);
1939 if (op_bits > 32 and wanted_bits <= 32) {2098 if (op_bits > 32 and wanted_bits <= 32) {
2099 try self.emitWValue(operand);
1940 try self.addTag(.i32_wrap_i64);2100 try self.addTag(.i32_wrap_i64);
1941 } else if (op_bits <= 32 and wanted_bits > 32) {2101 } else if (op_bits <= 32 and wanted_bits > 32) {
2102 try self.emitWValue(operand);
1942 try self.addTag(switch (ref_info.signedness) {2103 try self.addTag(switch (ref_info.signedness) {
1943 .signed => .i64_extend_i32_s,2104 .signed => .i64_extend_i32_s,
1944 .unsigned => .i64_extend_i32_u,2105 .unsigned => .i64_extend_i32_u,
1945 });2106 });
1946 }2107 } else return operand;
19472108
1948 // other cases are no-op2109 const result = try self.allocLocal(ty);
1949 return .none;2110 try self.addLabel(.local_set, result.local);
2111 return result;
1950}2112}
19512113
1952fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {2114fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
src/arch/wasm/Emit.zig+1-1
...@@ -257,7 +257,7 @@ fn emitMemArg(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {...@@ -257,7 +257,7 @@ fn emitMemArg(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
257 try emit.code.append(@enumToInt(tag));257 try emit.code.append(@enumToInt(tag));
258258
259 // wasm encodes alignment as power of 2, rather than natural alignment259 // wasm encodes alignment as power of 2, rather than natural alignment
260 const encoded_alignment = mem_arg.alignment >> 1;260 const encoded_alignment = @ctz(u32, mem_arg.alignment);
261 try leb128.writeULEB128(emit.code.writer(), encoded_alignment);261 try leb128.writeULEB128(emit.code.writer(), encoded_alignment);
262 try leb128.writeULEB128(emit.code.writer(), mem_arg.offset);262 try leb128.writeULEB128(emit.code.writer(), mem_arg.offset);
263}263}
src/link/Wasm/Atom.zig+2-4
...@@ -83,7 +83,7 @@ pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void {...@@ -83,7 +83,7 @@ pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void {
8383
84 for (self.relocs.items) |reloc| {84 for (self.relocs.items) |reloc| {
85 const value = try relocationValue(reloc, wasm_bin);85 const value = try relocationValue(reloc, wasm_bin);
86 log.debug("Relocating '{s}' referenced in '{s}' offset=0x{x:0>8} value={d}\n", .{86 log.debug("Relocating '{s}' referenced in '{s}' offset=0x{x:0>8} value={d}", .{
87 wasm_bin.symbols.items[reloc.index].name,87 wasm_bin.symbols.items[reloc.index].name,
88 symbol.name,88 symbol.name,
89 reloc.offset,89 reloc.offset,
...@@ -152,9 +152,7 @@ fn relocationValue(relocation: types.Relocation, wasm_bin: *const Wasm) !u64 {...@@ -152,9 +152,7 @@ fn relocationValue(relocation: types.Relocation, wasm_bin: *const Wasm) !u64 {
152 target_atom = target_atom.next orelse break;152 target_atom = target_atom.next orelse break;
153 }153 }
154 const segment = wasm_bin.segments.items[atom_index];154 const segment = wasm_bin.segments.items[atom_index];
155 const base = wasm_bin.base.options.global_base orelse 1024;155 break :blk target_atom.offset + segment.offset + (relocation.addend orelse 0);
156 const offset = target_atom.offset + segment.offset;
157 break :blk offset + base + (relocation.addend orelse 0);
158 },156 },
159 .R_WASM_EVENT_INDEX_LEB => symbol.index,157 .R_WASM_EVENT_INDEX_LEB => symbol.index,
160 .R_WASM_SECTION_OFFSET_I32,158 .R_WASM_SECTION_OFFSET_I32,