authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-18 22:24:19+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-21 21:07:55+01:00
log460b3d39eae8d294efbe2e5762ea38c93c352d63
treec2cebc0b85ecee5c4cdc6bf680c6653d070b3351
parentb2221e564490421265f9e3b2e398a89bbdfb0516
signaturelock-open Commit is signed but in an unrecognized format.

wasm: Implement error unions as well as returning them

This implements basic calling convention resolving. This means that for types such as an error union, we will now allocate space on the stack to store the result. This result will then be saved in a temporary local at the callsite.

1 files changed, 107 insertions(+), 25 deletions(-)

src/arch/wasm/CodeGen.zig+107-25
......@@ -514,6 +514,9 @@ func_type_data: ArrayList(u8),
514514/// NOTE: arguments share the index with locals therefore the first variable
515515/// will have the index that comes after the last argument's index
516516local_index: u32 = 0,
517/// The index of the current argument.
518/// Used to track which argument is being referenced in `airArg`.
519arg_index: u32 = 0,
517520/// If codegen fails, an error messages will be allocated and saved in `err_msg`
518521err_msg: *Module.ErrorMsg,
519522/// Current block depth. Used to calculate the relative difference between a break
......@@ -537,6 +540,13 @@ mir_extra: std.ArrayListUnmanaged(u32) = .{},
537540/// When a function is executing, we store the the current stack pointer's value within this local.
538541/// This value is then used to restore the stack pointer to the original value at the return of the function.
539542initial_stack_value: WValue = .none,
543/// Arguments of this function declaration
544/// This will be set after `resolveCallingConventionValues`
545args: []WValue = undefined,
546/// This will only be `.none` if the function returns void, or returns an immediate.
547/// When it returns a pointer to the stack, the `.local` tag will be active and must be populated
548/// before this function returns its execution to the caller.
549return_value: WValue = .none,
540550
541551const InnerError = error{
542552 OutOfMemory,
......@@ -736,17 +746,8 @@ fn genFunctype(self: *Self) InnerError!void {
736746 .Void, .NoReturn => try leb.writeULEB128(writer, @as(u32, 0)),
737747 .Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),
738748 .Optional => return self.fail("TODO: Implement optionals as return type for wasm", .{}),
739 .ErrorUnion => {
740 const val_type = try self.genValtype(return_type.errorUnionPayload());
741
742 // write down the amount of return values
743 try leb.writeULEB128(writer, @as(u32, 2));
744 try writer.writeByte(wasm.valtype(.i32)); // error code is always an i32 integer.
745 try writer.writeByte(val_type);
746 },
747749 else => {
748750 try leb.writeULEB128(writer, @as(u32, 1));
749 // Can we maybe get the source index of the return type?
750751 const val_type = try self.genValtype(return_type);
751752 try writer.writeByte(val_type);
752753 },
......@@ -757,6 +758,12 @@ pub fn genFunc(self: *Self) InnerError!Result {
757758 try self.genFunctype();
758759 // TODO: check for and handle death of instructions
759760
761 var cc_result = try self.resolveCallingConventionValues(self.decl.ty);
762 defer cc_result.deinit(self.gpa);
763
764 self.args = cc_result.args;
765 self.return_value = cc_result.return_value;
766
760767 // Generate MIR for function body
761768 try self.genBody(self.air.getMainBody());
762769 // End of function body
......@@ -836,9 +843,67 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
836843 }
837844}
838845
846const CallWValues = struct {
847 args: []WValue,
848 return_value: WValue,
849
850 fn deinit(self: *CallWValues, gpa: *Allocator) void {
851 gpa.free(self.args);
852 self.* = undefined;
853 }
854};
855
856fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValues {
857 const cc = fn_ty.fnCallingConvention();
858 const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen());
859 defer self.gpa.free(param_types);
860 fn_ty.fnParamTypes(param_types);
861 var result: CallWValues = .{
862 .args = try self.gpa.alloc(WValue, param_types.len),
863 .return_value = .none,
864 };
865 errdefer self.gpa.free(result.args);
866 switch (cc) {
867 .Naked => return result,
868 .Unspecified, .C => {
869 for (param_types) |ty, ty_index| {
870 if (!ty.hasCodeGenBits()) {
871 result.args[ty_index] = .{ .none = {} };
872 continue;
873 }
874
875 result.args[ty_index] = .{ .local = self.local_index };
876 self.local_index += 1;
877 }
878
879 const ret_ty = fn_ty.fnReturnType();
880 switch (ret_ty.zigTypeTag()) {
881 .ErrorUnion => result.return_value = try self.allocLocal(Type.initTag(.i32)),
882 .Int, .Float, .Bool, .Void, .NoReturn => {},
883 else => return self.fail("TODO: Implement function return type {}", .{ret_ty}),
884 }
885
886 // Check if we store the result as a pointer to the stack rather than
887 // by value
888 if (result.return_value != .none) {
889 if (self.initial_stack_value == .none) try self.initializeStack();
890 const offset = std.math.cast(u32, ret_ty.abiSize(self.target)) catch {
891 return self.fail("Return type '{}' too big for stack frame", .{ret_ty});
892 };
893
894 try self.moveStack(offset, result.return_value.local);
895 }
896 },
897 else => return self.fail("TODO implement function parameters for cc '{}' on wasm", .{cc}),
898 }
899 return result;
900}
901
839902/// Retrieves the stack pointer's value from the global variable and stores
840903/// it in a local
904/// Asserts `initial_stack_value` is `.none`
841905fn initializeStack(self: *Self) !void {
906 assert(self.initial_stack_value == .none);
842907 // reserve space for immediate value
843908 // get stack pointer global
844909 // TODO: For now, we hardcode the stack pointer to index '0',
......@@ -917,8 +982,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
917982 .dbg_stmt => WValue.none,
918983 .intcast => self.airIntcast(inst),
919984
920 .is_err => self.airIsErr(inst, .i32_ne),
921 .is_non_err => self.airIsErr(inst, .i32_eq),
985 .is_err => self.airIsErr(inst, .i32_eq),
986 .is_non_err => self.airIsErr(inst, .i32_ne),
922987
923988 .is_null => self.airIsNull(inst, .i32_ne),
924989 .is_non_null => self.airIsNull(inst, .i32_eq),
......@@ -960,7 +1025,14 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
9601025fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
9611026 const un_op = self.air.instructions.items(.data)[inst].un_op;
9621027 const operand = self.resolveInst(un_op);
963 try self.emitWValue(operand);
1028 // result must be stored in the stack and we return a pointer
1029 // to the stack instead
1030 if (self.return_value != .none) {
1031 try self.store(self.return_value, operand, self.decl.ty.fnReturnType(), 0);
1032 try self.emitWValue(self.return_value);
1033 } else {
1034 try self.emitWValue(operand);
1035 }
9641036 try self.restoreStackPointer();
9651037 try self.addTag(.@"return");
9661038 return .none;
......@@ -988,7 +1060,16 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
9881060 }
9891061
9901062 try self.addLabel(.call, target.link.wasm.symbol_index);
991 return .none;
1063
1064 const ret_ty = target.ty.fnReturnType();
1065 switch (ret_ty.zigTypeTag()) {
1066 .ErrorUnion => {
1067 const result_local = try self.allocLocal(ret_ty);
1068 try self.addLabel(.local_set, result_local.local);
1069 return result_local;
1070 },
1071 else => return WValue.none,
1072 }
9921073}
9931074
9941075fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
......@@ -1021,6 +1102,11 @@ fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
10211102 else => 0,
10221103 };
10231104
1105 try self.store(lhs, rhs, ty, offset);
1106 return .none;
1107}
1108
1109fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerError!void {
10241110 switch (ty.zigTypeTag()) {
10251111 .ErrorUnion, .Optional => {
10261112 var buf: Type.Payload.ElemType = undefined;
......@@ -1039,22 +1125,18 @@ fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
10391125 try self.addLabel(.local_set, tag_local.local);
10401126
10411127 try self.store(lhs, tag_local, tag_ty, 0);
1042 try self.store(lhs, payload_local, payload_ty, payload_offset);
1043 } else if (offset == 0) {
1044 // tag is being set
1045 try self.store(lhs, rhs, tag_ty, 0);
1128 return try self.store(lhs, payload_local, payload_ty, payload_offset);
10461129 } else {
1047 // payload is being set
1048 try self.store(lhs, rhs, payload_ty, payload_offset);
1130 // Load values from `rhs` stack position and store in `lhs` instead
1131 const tag_local = try self.load(rhs, tag_ty, 0);
1132 const payload_local = try self.load(rhs, payload_ty, payload_offset);
1133
1134 try self.store(lhs, tag_local, tag_ty, 0);
1135 return try self.store(lhs, payload_local, payload_ty, payload_offset);
10491136 }
10501137 },
1051 else => try self.store(lhs, rhs, ty, offset),
1138 else => {},
10521139 }
1053
1054 return .none;
1055}
1056
1057fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerError!void {
10581140 try self.emitWValue(lhs);
10591141 try self.emitWValue(rhs);
10601142 const valtype = try self.typeToValtype(ty);