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
signature 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 {
101101}
102102
103103fn wasmMain2() u8 {
104 root.main();
105 return 0;
104 switch (@typeInfo(@typeInfo(@TypeOf(root.main)).Fn.return_type.?)) {
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 }
106117}
107118
108119fn wWinMainCRTStartup2() callconv(.C) noreturn {
src/arch/wasm/CodeGen.zig+186-24
......@@ -692,6 +692,7 @@ fn typeToValtype(self: *Self, ty: Type) InnerError!wasm.Valtype {
692692 .Struct,
693693 .ErrorUnion,
694694 .Optional,
695 .Fn,
695696 => wasm.Valtype.i32,
696697 else => self.fail("TODO - Wasm valtype for type '{}'", .{ty}),
697698 };
......@@ -809,23 +810,52 @@ pub fn genFunc(self: *Self) InnerError!Result {
809810}
810811
811812/// 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 }
813818 switch (ty.zigTypeTag()) {
814819 .Fn => {
815 if (val.tag() == .extern_fn) {
816 var func_type = try self.genFunctype(self.decl.ty);
817 defer func_type.deinit(self.gpa);
818 self.decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
819 return Result.appended; // don't need code body for extern functions
820 const fn_decl = switch (val.tag()) {
821 .extern_fn => val.castTag(.extern_fn).?.data,
822 .function => val.castTag(.function).?.data.owner_decl,
823 else => unreachable,
824 };
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),
820849 }
821 return self.fail("TODO implement wasm codegen for function pointers", .{});
850 return Result.appended;
822851 },
823 .Array => {
824 if (val.castTag(.bytes)) |payload| {
852 .Array => switch (val.tag()) {
853 .bytes => {
854 const payload = val.castTag(.bytes).?;
825855 if (ty.sentinel()) |sentinel| {
826856 try self.code.appendSlice(payload.data);
827857
828 switch (try self.gen(ty.childType(), sentinel)) {
858 switch (try self.genDecl(ty.childType(), sentinel)) {
829859 .appended => return Result.appended,
830860 .externally_managed => |data| {
831861 try self.code.appendSlice(data);
......@@ -834,16 +864,33 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
834864 }
835865 }
836866 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())}),
838882 },
839883 .Int => {
840884 const info = ty.intInfo(self.target);
841 if (info.bits == 8 and info.signedness == .unsigned) {
842 const int_byte = val.toUnsignedInt();
843 try self.code.append(@intCast(u8, int_byte));
844 return Result.appended;
845 }
846 return self.fail("TODO: Implement codegen for int type: '{}'", .{ty});
885 const abi_size = ty.abiSize(self.target);
886 // todo: Implement integer sizes larger than 64bits
887 if (info.bits > 64) return self.fail("TODO: Implement genDecl for integer bit size: {d}", .{info.bits});
888 var buf: [8]u8 = undefined;
889 if (info.signedness == .unsigned) {
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;
847894 },
848895 .Enum => {
849896 try self.emitConstant(val, ty);
......@@ -855,15 +902,83 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
855902 return Result.appended;
856903 },
857904 .Struct => {
858 // TODO write the fields for real
859 const abi_size = try std.math.cast(usize, ty.abiSize(self.target));
905 const field_vals = val.castTag(.@"struct").?.data;
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);
860920 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())}),
862947 },
863948 else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),
864949 }
865950}
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
867982const CallWValues = struct {
868983 args: []WValue,
869984 return_value: WValue,
......@@ -1015,6 +1130,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
10151130 .loop => self.airLoop(inst),
10161131 .not => self.airNot(inst),
10171132 .ret => self.airRet(inst),
1133 .ret_ptr => self.airRetPtr(inst),
1134 .ret_load => self.airRetLoad(inst),
10181135 .slice_len => self.airSliceLen(inst),
10191136 .slice_elem_val => self.airSliceElemVal(inst),
10201137 .store => self.airStore(inst),
......@@ -1029,6 +1146,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
10291146 .wrap_optional => self.airWrapOptional(inst),
10301147
10311148 .unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst),
1149 .unwrap_errunion_err => self.airUnwrapErrUnionError(inst),
10321150 .wrap_errunion_payload => self.airWrapErrUnionPayload(inst),
10331151
10341152 .optional_payload => self.airOptionalPayload(inst),
......@@ -1061,6 +1179,34 @@ fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
10611179 return .none;
10621180}
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
10641210fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
10651211 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
10661212 const extra = self.air.extraData(Air.Call, pl_op.payload);
......@@ -1096,6 +1242,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
10961242 // so load its value onto the stack
10971243 std.debug.assert(ty.zigTypeTag() == .Pointer);
10981244 const operand = self.resolveInst(pl_op.operand);
1245 try self.emitWValue(operand);
10991246 const result = try self.load(operand, fn_ty, operand.local_with_offset.offset);
11001247 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
12291376 // that is portable across the backend, rather than copying logic.
12301377 const abi_size = if ((ty.isInt() or ty.isAnyFloat()) and ty.abiSize(self.target) <= 8)
12311378 @intCast(u8, ty.abiSize(self.target))
1379 else if (ty.zigTypeTag() == .ErrorSet or ty.zigTypeTag() == .Enum)
1380 @intCast(u8, ty.abiSize(self.target))
12321381 else
12331382 @as(u8, 4);
12341383 const opcode = buildOpcode(.{
......@@ -1272,6 +1421,8 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
12721421 // that is portable across the backend, rather than copying logic.
12731422 const abi_size = if ((ty.isInt() or ty.isAnyFloat()) and ty.abiSize(self.target) <= 8)
12741423 @intCast(u8, ty.abiSize(self.target))
1424 else if (ty.zigTypeTag() == .ErrorSet or ty.zigTypeTag() == .Enum)
1425 @intCast(u8, ty.abiSize(self.target))
12751426 else
12761427 @as(u8, 4);
12771428
......@@ -1920,6 +2071,15 @@ fn airUnwrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue
19202071 return try self.load(operand, payload_ty, offset);
19212072}
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
19232083fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
19242084 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
19252085 _ = ty_op;
......@@ -1935,18 +2095,20 @@ fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
19352095 const op_bits = ref_info.bits;
19362096 const wanted_bits = ty.intInfo(self.target).bits;
19372097
1938 try self.emitWValue(operand);
19392098 if (op_bits > 32 and wanted_bits <= 32) {
2099 try self.emitWValue(operand);
19402100 try self.addTag(.i32_wrap_i64);
19412101 } else if (op_bits <= 32 and wanted_bits > 32) {
2102 try self.emitWValue(operand);
19422103 try self.addTag(switch (ref_info.signedness) {
19432104 .signed => .i64_extend_i32_s,
19442105 .unsigned => .i64_extend_i32_u,
19452106 });
1946 }
2107 } else return operand;
19472108
1948 // other cases are no-op
1949 return .none;
2109 const result = try self.allocLocal(ty);
2110 try self.addLabel(.local_set, result.local);
2111 return result;
19502112}
19512113
19522114fn 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 {
257257 try emit.code.append(@enumToInt(tag));
258258
259259 // 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);
261261 try leb128.writeULEB128(emit.code.writer(), encoded_alignment);
262262 try leb128.writeULEB128(emit.code.writer(), mem_arg.offset);
263263}
src/link/Wasm/Atom.zig+2-4
......@@ -83,7 +83,7 @@ pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void {
8383
8484 for (self.relocs.items) |reloc| {
8585 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}", .{
8787 wasm_bin.symbols.items[reloc.index].name,
8888 symbol.name,
8989 reloc.offset,
......@@ -152,9 +152,7 @@ fn relocationValue(relocation: types.Relocation, wasm_bin: *const Wasm) !u64 {
152152 target_atom = target_atom.next orelse break;
153153 }
154154 const segment = wasm_bin.segments.items[atom_index];
155 const base = wasm_bin.base.options.global_base orelse 1024;
156 const offset = target_atom.offset + segment.offset;
157 break :blk offset + base + (relocation.addend orelse 0);
155 break :blk target_atom.offset + segment.offset + (relocation.addend orelse 0);
158156 },
159157 .R_WASM_EVENT_INDEX_LEB => symbol.index,
160158 .R_WASM_SECTION_OFFSET_I32,