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),...@@ -514,6 +514,9 @@ func_type_data: ArrayList(u8),
514/// NOTE: arguments share the index with locals therefore the first variable514/// NOTE: arguments share the index with locals therefore the first variable
515/// will have the index that comes after the last argument's index515/// will have the index that comes after the last argument's index
516local_index: u32 = 0,516local_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,
517/// If codegen fails, an error messages will be allocated and saved in `err_msg`520/// If codegen fails, an error messages will be allocated and saved in `err_msg`
518err_msg: *Module.ErrorMsg,521err_msg: *Module.ErrorMsg,
519/// Current block depth. Used to calculate the relative difference between a break522/// Current block depth. Used to calculate the relative difference between a break
...@@ -537,6 +540,13 @@ mir_extra: std.ArrayListUnmanaged(u32) = .{},...@@ -537,6 +540,13 @@ mir_extra: std.ArrayListUnmanaged(u32) = .{},
537/// When a function is executing, we store the the current stack pointer's value within this local.540/// When a function is executing, we store the the current stack pointer's value within this local.
538/// This value is then used to restore the stack pointer to the original value at the return of the function.541/// This value is then used to restore the stack pointer to the original value at the return of the function.
539initial_stack_value: WValue = .none,542initial_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
541const InnerError = error{551const InnerError = error{
542 OutOfMemory,552 OutOfMemory,
...@@ -736,17 +746,8 @@ fn genFunctype(self: *Self) InnerError!void {...@@ -736,17 +746,8 @@ fn genFunctype(self: *Self) InnerError!void {
736 .Void, .NoReturn => try leb.writeULEB128(writer, @as(u32, 0)),746 .Void, .NoReturn => try leb.writeULEB128(writer, @as(u32, 0)),
737 .Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),747 .Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),
738 .Optional => return self.fail("TODO: Implement optionals as return type for wasm", .{}),748 .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 },
747 else => {749 else => {
748 try leb.writeULEB128(writer, @as(u32, 1));750 try leb.writeULEB128(writer, @as(u32, 1));
749 // Can we maybe get the source index of the return type?
750 const val_type = try self.genValtype(return_type);751 const val_type = try self.genValtype(return_type);
751 try writer.writeByte(val_type);752 try writer.writeByte(val_type);
752 },753 },
...@@ -757,6 +758,12 @@ pub fn genFunc(self: *Self) InnerError!Result {...@@ -757,6 +758,12 @@ pub fn genFunc(self: *Self) InnerError!Result {
757 try self.genFunctype();758 try self.genFunctype();
758 // TODO: check for and handle death of instructions759 // 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
760 // Generate MIR for function body767 // Generate MIR for function body
761 try self.genBody(self.air.getMainBody());768 try self.genBody(self.air.getMainBody());
762 // End of function body769 // End of function body
...@@ -836,9 +843,67 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {...@@ -836,9 +843,67 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
836 }843 }
837}844}
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
839/// Retrieves the stack pointer's value from the global variable and stores902/// Retrieves the stack pointer's value from the global variable and stores
840/// it in a local903/// it in a local
904/// Asserts `initial_stack_value` is `.none`
841fn initializeStack(self: *Self) !void {905fn initializeStack(self: *Self) !void {
906 assert(self.initial_stack_value == .none);
842 // reserve space for immediate value907 // reserve space for immediate value
843 // get stack pointer global908 // get stack pointer global
844 // TODO: For now, we hardcode the stack pointer to index '0',909 // TODO: For now, we hardcode the stack pointer to index '0',
...@@ -917,8 +982,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {...@@ -917,8 +982,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
917 .dbg_stmt => WValue.none,982 .dbg_stmt => WValue.none,
918 .intcast => self.airIntcast(inst),983 .intcast => self.airIntcast(inst),
919984
920 .is_err => self.airIsErr(inst, .i32_ne),985 .is_err => self.airIsErr(inst, .i32_eq),
921 .is_non_err => self.airIsErr(inst, .i32_eq),986 .is_non_err => self.airIsErr(inst, .i32_ne),
922987
923 .is_null => self.airIsNull(inst, .i32_ne),988 .is_null => self.airIsNull(inst, .i32_ne),
924 .is_non_null => self.airIsNull(inst, .i32_eq),989 .is_non_null => self.airIsNull(inst, .i32_eq),
...@@ -960,7 +1025,14 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -960,7 +1025,14 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
960fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {1025fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
961 const un_op = self.air.instructions.items(.data)[inst].un_op;1026 const un_op = self.air.instructions.items(.data)[inst].un_op;
962 const operand = self.resolveInst(un_op);1027 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 }
964 try self.restoreStackPointer();1036 try self.restoreStackPointer();
965 try self.addTag(.@"return");1037 try self.addTag(.@"return");
966 return .none;1038 return .none;
...@@ -988,7 +1060,16 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -988,7 +1060,16 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
988 }1060 }
9891061
990 try self.addLabel(.call, target.link.wasm.symbol_index);1062 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 }
992}1073}
9931074
994fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {1075fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
...@@ -1021,6 +1102,11 @@ fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1021,6 +1102,11 @@ fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1021 else => 0,1102 else => 0,
1022 };1103 };
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 {
1024 switch (ty.zigTypeTag()) {1110 switch (ty.zigTypeTag()) {
1025 .ErrorUnion, .Optional => {1111 .ErrorUnion, .Optional => {
1026 var buf: Type.Payload.ElemType = undefined;1112 var buf: Type.Payload.ElemType = undefined;
...@@ -1039,22 +1125,18 @@ fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1039,22 +1125,18 @@ fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1039 try self.addLabel(.local_set, tag_local.local);1125 try self.addLabel(.local_set, tag_local.local);
10401126
1041 try self.store(lhs, tag_local, tag_ty, 0);1127 try self.store(lhs, tag_local, tag_ty, 0);
1042 try self.store(lhs, payload_local, payload_ty, payload_offset);1128 return 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);
1046 } else {1129 } else {
1047 // payload is being set1130 // Load values from `rhs` stack position and store in `lhs` instead
1048 try self.store(lhs, rhs, payload_ty, payload_offset);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);
1049 }1136 }
1050 },1137 },
1051 else => try self.store(lhs, rhs, ty, offset),1138 else => {},
1052 }1139 }
1053
1054 return .none;
1055}
1056
1057fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerError!void {
1058 try self.emitWValue(lhs);1140 try self.emitWValue(lhs);
1059 try self.emitWValue(rhs);1141 try self.emitWValue(rhs);
1060 const valtype = try self.typeToValtype(ty);1142 const valtype = try self.typeToValtype(ty);