| ... | ... | @@ -28,16 +28,15 @@ const WValue = union(enum) { |
| 28 | 28 | local: u32, |
| 29 | 29 | /// Holds a memoized typed value |
| 30 | 30 | constant: TypedValue, |
| 31 | | /// Offset position in the list of MIR instructions |
| 32 | | mir_offset: usize, |
| 33 | | /// Used for variables that create multiple locals on the stack when allocated |
| 34 | | /// such as structs and optionals. |
| 35 | | multi_value: struct { |
| 36 | | /// The index of the first local variable |
| 37 | | index: u32, |
| 38 | | /// The count of local variables this `WValue` consists of. |
| 39 | | /// i.e. an ErrorUnion has a 'count' of 2. |
| 40 | | count: u32, |
| 31 | /// Used for types that contains of multiple areas within |
| 32 | /// a memory region in the stack. |
| 33 | /// The local represents the position in the stack, |
| 34 | /// whereas the offset represents the offset from that position. |
| 35 | local_with_offset: struct { |
| 36 | /// Index of the local variable |
| 37 | local: u32, |
| 38 | /// The offset from the local's stack position |
| 39 | offset: u32, |
| 41 | 40 | }, |
| 42 | 41 | }; |
| 43 | 42 | |
| ... | ... | @@ -187,7 +186,14 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 187 | 186 | }, |
| 188 | 187 | 32 => switch (args.valtype1.?) { |
| 189 | 188 | .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u, |
| 190 | | .i32, .f32, .f64 => unreachable, |
| 189 | .i32 => return .i32_load, |
| 190 | .f32 => return .f32_load, |
| 191 | .f64 => unreachable, |
| 192 | }, |
| 193 | 64 => switch (args.valtype1.?) { |
| 194 | .i64 => return .i64_load, |
| 195 | .f64 => return .f64_load, |
| 196 | else => unreachable, |
| 191 | 197 | }, |
| 192 | 198 | else => unreachable, |
| 193 | 199 | } else switch (args.valtype1.?) { |
| ... | ... | @@ -210,7 +216,14 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 210 | 216 | }, |
| 211 | 217 | 32 => switch (args.valtype1.?) { |
| 212 | 218 | .i64 => return .i64_store32, |
| 213 | | .i32, .f32, .f64 => unreachable, |
| 219 | .i32 => return .i32_store, |
| 220 | .f32 => return .f32_store, |
| 221 | .f64 => unreachable, |
| 222 | }, |
| 223 | 64 => switch (args.valtype1.?) { |
| 224 | .i64 => return .i64_store, |
| 225 | .f64 => return .f64_store, |
| 226 | else => unreachable, |
| 214 | 227 | }, |
| 215 | 228 | else => unreachable, |
| 216 | 229 | } |
| ... | ... | @@ -499,7 +512,10 @@ gpa: *mem.Allocator, |
| 499 | 512 | /// Table to save `WValue`'s generated by an `Air.Inst` |
| 500 | 513 | values: ValueTable, |
| 501 | 514 | /// Mapping from Air.Inst.Index to block ids |
| 502 | | blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, u32) = .{}, |
| 515 | blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct { |
| 516 | label: u32, |
| 517 | value: WValue, |
| 518 | }) = .{}, |
| 503 | 519 | /// `bytes` contains the wasm bytecode belonging to the 'code' section. |
| 504 | 520 | code: ArrayList(u8), |
| 505 | 521 | /// Contains the generated function type bytecode for the current function |
| ... | ... | @@ -509,6 +525,9 @@ func_type_data: ArrayList(u8), |
| 509 | 525 | /// NOTE: arguments share the index with locals therefore the first variable |
| 510 | 526 | /// will have the index that comes after the last argument's index |
| 511 | 527 | local_index: u32 = 0, |
| 528 | /// The index of the current argument. |
| 529 | /// Used to track which argument is being referenced in `airArg`. |
| 530 | arg_index: u32 = 0, |
| 512 | 531 | /// If codegen fails, an error messages will be allocated and saved in `err_msg` |
| 513 | 532 | err_msg: *Module.ErrorMsg, |
| 514 | 533 | /// Current block depth. Used to calculate the relative difference between a break |
| ... | ... | @@ -529,6 +548,16 @@ global_error_set: std.StringHashMapUnmanaged(Module.ErrorInt), |
| 529 | 548 | mir_instructions: std.MultiArrayList(Mir.Inst) = .{}, |
| 530 | 549 | /// Contains extra data for MIR |
| 531 | 550 | mir_extra: std.ArrayListUnmanaged(u32) = .{}, |
| 551 | /// When a function is executing, we store the the current stack pointer's value within this local. |
| 552 | /// This value is then used to restore the stack pointer to the original value at the return of the function. |
| 553 | initial_stack_value: WValue = .none, |
| 554 | /// Arguments of this function declaration |
| 555 | /// This will be set after `resolveCallingConventionValues` |
| 556 | args: []WValue = undefined, |
| 557 | /// This will only be `.none` if the function returns void, or returns an immediate. |
| 558 | /// When it returns a pointer to the stack, the `.local` tag will be active and must be populated |
| 559 | /// before this function returns its execution to the caller. |
| 560 | return_value: WValue = .none, |
| 532 | 561 | |
| 533 | 562 | const InnerError = error{ |
| 534 | 563 | OutOfMemory, |
| ... | ... | @@ -662,9 +691,11 @@ fn typeToValtype(self: *Self, ty: Type) InnerError!wasm.Valtype { |
| 662 | 691 | .Bool, |
| 663 | 692 | .Pointer, |
| 664 | 693 | .ErrorSet, |
| 694 | .Struct, |
| 695 | .ErrorUnion, |
| 696 | .Optional, |
| 665 | 697 | => wasm.Valtype.i32, |
| 666 | | .Struct, .ErrorUnion, .Optional => unreachable, // Multi typed, must be handled individually. |
| 667 | | else => |tag| self.fail("TODO - Wasm valtype for type '{s}'", .{tag}), |
| 698 | else => self.fail("TODO - Wasm valtype for type '{}'", .{ty}), |
| 668 | 699 | }; |
| 669 | 700 | } |
| 670 | 701 | |
| ... | ... | @@ -686,78 +717,21 @@ fn genBlockType(self: *Self, ty: Type) InnerError!u8 { |
| 686 | 717 | /// Writes the bytecode depending on the given `WValue` in `val` |
| 687 | 718 | fn emitWValue(self: *Self, val: WValue) InnerError!void { |
| 688 | 719 | switch (val) { |
| 689 | | .multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually |
| 690 | | .none, .mir_offset => {}, // no-op |
| 691 | | .local => |idx| { |
| 692 | | try self.addLabel(.local_get, idx); |
| 693 | | }, |
| 720 | .none => {}, // no-op |
| 721 | .local_with_offset => |with_off| try self.addLabel(.local_get, with_off.local), |
| 722 | .local => |idx| try self.addLabel(.local_get, idx), |
| 694 | 723 | .constant => |tv| try self.emitConstant(tv.val, tv.ty), // Creates a new constant on the stack |
| 695 | 724 | } |
| 696 | 725 | } |
| 697 | 726 | |
| 698 | | /// Creates one or multiple locals for a given `Type`. |
| 699 | | /// Returns a corresponding `Wvalue` that can either be of tag |
| 700 | | /// local or multi_value |
| 727 | /// Creates one locals for a given `Type`. |
| 728 | /// Returns a corresponding `Wvalue` with `local` as active tag |
| 701 | 729 | fn allocLocal(self: *Self, ty: Type) InnerError!WValue { |
| 702 | 730 | const initial_index = self.local_index; |
| 703 | | switch (ty.zigTypeTag()) { |
| 704 | | .Struct => { |
| 705 | | // for each struct field, generate a local |
| 706 | | const struct_data: *Module.Struct = ty.castTag(.@"struct").?.data; |
| 707 | | const fields_len = @intCast(u32, struct_data.fields.count()); |
| 708 | | try self.locals.ensureUnusedCapacity(self.gpa, fields_len); |
| 709 | | for (struct_data.fields.values()) |*value| { |
| 710 | | const val_type = try self.genValtype(value.ty); |
| 711 | | self.locals.appendAssumeCapacity(val_type); |
| 712 | | self.local_index += 1; |
| 713 | | } |
| 714 | | return WValue{ .multi_value = .{ |
| 715 | | .index = initial_index, |
| 716 | | .count = fields_len, |
| 717 | | } }; |
| 718 | | }, |
| 719 | | .ErrorUnion => { |
| 720 | | const payload_type = ty.errorUnionPayload(); |
| 721 | | const val_type = try self.genValtype(payload_type); |
| 722 | | |
| 723 | | // we emit the error value as the first local, and the payload as the following. |
| 724 | | // The first local is also used to find the index of the error and payload. |
| 725 | | // |
| 726 | | // TODO: Add support where the payload is a type that contains multiple locals such as a struct. |
| 727 | | try self.locals.ensureUnusedCapacity(self.gpa, 2); |
| 728 | | self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32 |
| 729 | | self.locals.appendAssumeCapacity(val_type); |
| 730 | | self.local_index += 2; |
| 731 | | |
| 732 | | return WValue{ .multi_value = .{ |
| 733 | | .index = initial_index, |
| 734 | | .count = 2, |
| 735 | | } }; |
| 736 | | }, |
| 737 | | .Optional => { |
| 738 | | var opt_buf: Type.Payload.ElemType = undefined; |
| 739 | | const child_type = ty.optionalChild(&opt_buf); |
| 740 | | if (ty.isPtrLikeOptional()) { |
| 741 | | return self.fail("TODO: wasm optional pointer", .{}); |
| 742 | | } |
| 743 | | |
| 744 | | try self.locals.ensureUnusedCapacity(self.gpa, 2); |
| 745 | | self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // optional 'tag' for null-checking is always i32 |
| 746 | | self.locals.appendAssumeCapacity(try self.genValtype(child_type)); |
| 747 | | self.local_index += 2; |
| 748 | | |
| 749 | | return WValue{ .multi_value = .{ |
| 750 | | .index = initial_index, |
| 751 | | .count = 2, |
| 752 | | } }; |
| 753 | | }, |
| 754 | | else => { |
| 755 | | const valtype = try self.genValtype(ty); |
| 756 | | try self.locals.append(self.gpa, valtype); |
| 757 | | self.local_index += 1; |
| 758 | | return WValue{ .local = initial_index }; |
| 759 | | }, |
| 760 | | } |
| 731 | const valtype = try self.genValtype(ty); |
| 732 | try self.locals.append(self.gpa, valtype); |
| 733 | self.local_index += 1; |
| 734 | return WValue{ .local = initial_index }; |
| 761 | 735 | } |
| 762 | 736 | |
| 763 | 737 | fn genFunctype(self: *Self) InnerError!void { |
| ... | ... | @@ -786,17 +760,8 @@ fn genFunctype(self: *Self) InnerError!void { |
| 786 | 760 | .Void, .NoReturn => try leb.writeULEB128(writer, @as(u32, 0)), |
| 787 | 761 | .Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}), |
| 788 | 762 | .Optional => return self.fail("TODO: Implement optionals as return type for wasm", .{}), |
| 789 | | .ErrorUnion => { |
| 790 | | const val_type = try self.genValtype(return_type.errorUnionPayload()); |
| 791 | | |
| 792 | | // write down the amount of return values |
| 793 | | try leb.writeULEB128(writer, @as(u32, 2)); |
| 794 | | try writer.writeByte(wasm.valtype(.i32)); // error code is always an i32 integer. |
| 795 | | try writer.writeByte(val_type); |
| 796 | | }, |
| 797 | 763 | else => { |
| 798 | 764 | try leb.writeULEB128(writer, @as(u32, 1)); |
| 799 | | // Can we maybe get the source index of the return type? |
| 800 | 765 | const val_type = try self.genValtype(return_type); |
| 801 | 766 | try writer.writeByte(val_type); |
| 802 | 767 | }, |
| ... | ... | @@ -807,6 +772,12 @@ pub fn genFunc(self: *Self) InnerError!Result { |
| 807 | 772 | try self.genFunctype(); |
| 808 | 773 | // TODO: check for and handle death of instructions |
| 809 | 774 | |
| 775 | var cc_result = try self.resolveCallingConventionValues(self.decl.ty); |
| 776 | defer cc_result.deinit(self.gpa); |
| 777 | |
| 778 | self.args = cc_result.args; |
| 779 | self.return_value = cc_result.return_value; |
| 780 | |
| 810 | 781 | // Generate MIR for function body |
| 811 | 782 | try self.genBody(self.air.getMainBody()); |
| 812 | 783 | // End of function body |
| ... | ... | @@ -853,7 +824,7 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result { |
| 853 | 824 | if (ty.sentinel()) |sentinel| { |
| 854 | 825 | try self.code.appendSlice(payload.data); |
| 855 | 826 | |
| 856 | | switch (try self.gen(ty.elemType(), sentinel)) { |
| 827 | switch (try self.gen(ty.childType(), sentinel)) { |
| 857 | 828 | .appended => return Result.appended, |
| 858 | 829 | .externally_managed => |data| { |
| 859 | 830 | try self.code.appendSlice(data); |
| ... | ... | @@ -887,6 +858,110 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result { |
| 887 | 858 | } |
| 888 | 859 | } |
| 889 | 860 | |
| 861 | const CallWValues = struct { |
| 862 | args: []WValue, |
| 863 | return_value: WValue, |
| 864 | |
| 865 | fn deinit(self: *CallWValues, gpa: *Allocator) void { |
| 866 | gpa.free(self.args); |
| 867 | self.* = undefined; |
| 868 | } |
| 869 | }; |
| 870 | |
| 871 | fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValues { |
| 872 | const cc = fn_ty.fnCallingConvention(); |
| 873 | const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen()); |
| 874 | defer self.gpa.free(param_types); |
| 875 | fn_ty.fnParamTypes(param_types); |
| 876 | var result: CallWValues = .{ |
| 877 | .args = try self.gpa.alloc(WValue, param_types.len), |
| 878 | .return_value = .none, |
| 879 | }; |
| 880 | errdefer self.gpa.free(result.args); |
| 881 | switch (cc) { |
| 882 | .Naked => return result, |
| 883 | .Unspecified, .C => { |
| 884 | for (param_types) |ty, ty_index| { |
| 885 | if (!ty.hasCodeGenBits()) { |
| 886 | result.args[ty_index] = .{ .none = {} }; |
| 887 | continue; |
| 888 | } |
| 889 | |
| 890 | result.args[ty_index] = .{ .local = self.local_index }; |
| 891 | self.local_index += 1; |
| 892 | } |
| 893 | |
| 894 | const ret_ty = fn_ty.fnReturnType(); |
| 895 | switch (ret_ty.zigTypeTag()) { |
| 896 | .ErrorUnion, .Optional => result.return_value = try self.allocLocal(Type.initTag(.i32)), |
| 897 | .Int, .Float, .Bool, .Void, .NoReturn => {}, |
| 898 | else => return self.fail("TODO: Implement function return type {}", .{ret_ty}), |
| 899 | } |
| 900 | |
| 901 | // Check if we store the result as a pointer to the stack rather than |
| 902 | // by value |
| 903 | if (result.return_value != .none) { |
| 904 | if (self.initial_stack_value == .none) try self.initializeStack(); |
| 905 | const offset = std.math.cast(u32, ret_ty.abiSize(self.target)) catch { |
| 906 | return self.fail("Return type '{}' too big for stack frame", .{ret_ty}); |
| 907 | }; |
| 908 | |
| 909 | try self.moveStack(offset, result.return_value.local); |
| 910 | } |
| 911 | }, |
| 912 | else => return self.fail("TODO implement function parameters for cc '{}' on wasm", .{cc}), |
| 913 | } |
| 914 | return result; |
| 915 | } |
| 916 | |
| 917 | /// Retrieves the stack pointer's value from the global variable and stores |
| 918 | /// it in a local |
| 919 | /// Asserts `initial_stack_value` is `.none` |
| 920 | fn initializeStack(self: *Self) !void { |
| 921 | assert(self.initial_stack_value == .none); |
| 922 | // reserve space for immediate value |
| 923 | // get stack pointer global |
| 924 | // TODO: For now, we hardcode the stack pointer to index '0', |
| 925 | // once the linker is further implemented, we can replace this by inserting |
| 926 | // a relocation and have the linker resolve the correct index to the stack pointer global. |
| 927 | // NOTE: relocations of the type GLOBAL_INDEX_LEB are 5-bytes big |
| 928 | try self.addLabel(.global_get, 0); |
| 929 | |
| 930 | // Reserve a local to store the current stack pointer |
| 931 | // We can later use this local to set the stack pointer back to the value |
| 932 | // we have stored here. |
| 933 | self.initial_stack_value = try self.allocLocal(Type.initTag(.i32)); |
| 934 | |
| 935 | // save the value to the local |
| 936 | try self.addLabel(.local_set, self.initial_stack_value.local); |
| 937 | } |
| 938 | |
| 939 | /// Reads the stack pointer from `Context.initial_stack_value` and writes it |
| 940 | /// to the global stack pointer variable |
| 941 | fn restoreStackPointer(self: *Self) !void { |
| 942 | // only restore the pointer if it was initialized |
| 943 | if (self.initial_stack_value == .none) return; |
| 944 | // Get the original stack pointer's value |
| 945 | try self.emitWValue(self.initial_stack_value); |
| 946 | |
| 947 | // save its value in the global stack pointer |
| 948 | try self.addLabel(.global_set, 0); |
| 949 | } |
| 950 | |
| 951 | /// Moves the stack pointer by given `offset` |
| 952 | /// It does this by retrieving the stack pointer, subtracting `offset` and storing |
| 953 | /// the result back into the stack pointer. |
| 954 | fn moveStack(self: *Self, offset: u32, local: u32) !void { |
| 955 | if (offset == 0) return; |
| 956 | // TODO: Rather than hardcode the stack pointer to position 0, |
| 957 | // have the linker resolve its relocation |
| 958 | try self.addLabel(.global_get, 0); |
| 959 | try self.addImm32(@bitCast(i32, offset)); |
| 960 | try self.addTag(.i32_sub); |
| 961 | try self.addLabel(.local_tee, local); |
| 962 | try self.addLabel(.global_set, 0); |
| 963 | } |
| 964 | |
| 890 | 965 | fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { |
| 891 | 966 | const air_tags = self.air.instructions.items(.tag); |
| 892 | 967 | return switch (air_tags[inst]) { |
| ... | ... | @@ -965,7 +1040,15 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 965 | 1040 | fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 966 | 1041 | const un_op = self.air.instructions.items(.data)[inst].un_op; |
| 967 | 1042 | const operand = self.resolveInst(un_op); |
| 968 | | try self.emitWValue(operand); |
| 1043 | // result must be stored in the stack and we return a pointer |
| 1044 | // to the stack instead |
| 1045 | if (self.return_value != .none) { |
| 1046 | try self.store(self.return_value, operand, self.decl.ty.fnReturnType(), 0); |
| 1047 | try self.emitWValue(self.return_value); |
| 1048 | } else { |
| 1049 | try self.emitWValue(operand); |
| 1050 | } |
| 1051 | try self.restoreStackPointer(); |
| 969 | 1052 | try self.addTag(.@"return"); |
| 970 | 1053 | return .none; |
| 971 | 1054 | } |
| ... | ... | @@ -993,12 +1076,33 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 993 | 1076 | |
| 994 | 1077 | try self.addLabel(.call, target.link.wasm.symbol_index); |
| 995 | 1078 | |
| 996 | | return .none; |
| 1079 | const ret_ty = target.ty.fnReturnType(); |
| 1080 | switch (ret_ty.zigTypeTag()) { |
| 1081 | .Void, .NoReturn => return WValue.none, |
| 1082 | else => { |
| 1083 | const result_local = try self.allocLocal(ret_ty); |
| 1084 | try self.addLabel(.local_set, result_local.local); |
| 1085 | return result_local; |
| 1086 | }, |
| 1087 | } |
| 997 | 1088 | } |
| 998 | 1089 | |
| 999 | 1090 | fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1000 | | const elem_type = self.air.typeOfIndex(inst).elemType(); |
| 1001 | | return self.allocLocal(elem_type); |
| 1091 | const child_type = self.air.typeOfIndex(inst).childType(); |
| 1092 | |
| 1093 | // Initialize the stack |
| 1094 | if (self.initial_stack_value == .none) { |
| 1095 | try self.initializeStack(); |
| 1096 | } |
| 1097 | |
| 1098 | const abi_size = child_type.abiSize(self.target); |
| 1099 | if (abi_size == 0) return WValue{ .none = {} }; |
| 1100 | |
| 1101 | // local, containing the offset to the stack position |
| 1102 | const local = try self.allocLocal(Type.initTag(.i32)); // always pointer therefore i32 |
| 1103 | try self.moveStack(@intCast(u32, abi_size), local.local); |
| 1104 | |
| 1105 | return local; |
| 1002 | 1106 | } |
| 1003 | 1107 | |
| 1004 | 1108 | fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| ... | ... | @@ -1006,56 +1110,149 @@ fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1006 | 1110 | |
| 1007 | 1111 | const lhs = self.resolveInst(bin_op.lhs); |
| 1008 | 1112 | const rhs = self.resolveInst(bin_op.rhs); |
| 1113 | const ty = self.air.typeOf(bin_op.lhs).childType(); |
| 1009 | 1114 | |
| 1010 | | switch (lhs) { |
| 1011 | | .multi_value => |multi_value| switch (rhs) { |
| 1012 | | // When assigning a value to a multi_value such as a struct, |
| 1013 | | // we simply assign the local_index to the rhs one. |
| 1014 | | // This allows us to update struct fields without having to individually |
| 1015 | | // set each local as each field's index will be calculated off the struct's base index |
| 1016 | | .multi_value => self.values.put(self.gpa, Air.refToIndex(bin_op.lhs).?, rhs) catch unreachable, // Instruction does not dominate all uses! |
| 1017 | | .constant, .none => { |
| 1018 | | // emit all values onto the stack if constant |
| 1019 | | try self.emitWValue(rhs); |
| 1020 | | |
| 1021 | | // for each local, pop the stack value into the local |
| 1022 | | // As the last element is on top of the stack, we must populate the locals |
| 1023 | | // in reverse. |
| 1024 | | var i: u32 = multi_value.count; |
| 1025 | | while (i > 0) : (i -= 1) { |
| 1026 | | try self.addLabel(.local_set, multi_value.index + i - 1); |
| 1027 | | } |
| 1028 | | }, |
| 1029 | | .local => { |
| 1030 | | // This can occur when we wrap a single value into a multi-value, |
| 1031 | | // such as wrapping a non-optional value into an optional. |
| 1032 | | // This means we must zero the null-tag, and set the payload. |
| 1033 | | assert(multi_value.count == 2); |
| 1034 | | // set payload |
| 1035 | | try self.emitWValue(rhs); |
| 1036 | | try self.addLabel(.local_set, multi_value.index + 1); |
| 1037 | | }, |
| 1038 | | else => unreachable, |
| 1115 | const offset: u32 = switch (lhs) { |
| 1116 | .local_with_offset => |with_off| with_off.offset, |
| 1117 | else => 0, |
| 1118 | }; |
| 1119 | |
| 1120 | try self.store(lhs, rhs, ty, offset); |
| 1121 | return .none; |
| 1122 | } |
| 1123 | |
| 1124 | fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerError!void { |
| 1125 | switch (ty.zigTypeTag()) { |
| 1126 | .ErrorUnion, .Optional => { |
| 1127 | var buf: Type.Payload.ElemType = undefined; |
| 1128 | const payload_ty = if (ty.zigTypeTag() == .ErrorUnion) ty.errorUnionPayload() else ty.optionalChild(&buf); |
| 1129 | const tag_ty = if (ty.zigTypeTag() == .ErrorUnion) ty.errorUnionSet() else Type.initTag(.u8); |
| 1130 | const payload_offset = if (ty.zigTypeTag() == .ErrorUnion) |
| 1131 | @intCast(u32, tag_ty.abiSize(self.target)) |
| 1132 | else |
| 1133 | @intCast(u32, ty.abiSize(self.target) - payload_ty.abiSize(self.target)); |
| 1134 | |
| 1135 | switch (rhs) { |
| 1136 | .constant => { |
| 1137 | // constant will contain both tag and payload, |
| 1138 | // so save those in 2 temporary locals before storing them |
| 1139 | // in memory |
| 1140 | try self.emitWValue(rhs); |
| 1141 | const tag_local = try self.allocLocal(tag_ty); |
| 1142 | const payload_local = try self.allocLocal(payload_ty); |
| 1143 | |
| 1144 | try self.addLabel(.local_set, payload_local.local); |
| 1145 | try self.addLabel(.local_set, tag_local.local); |
| 1146 | |
| 1147 | try self.store(lhs, tag_local, tag_ty, 0); |
| 1148 | return try self.store(lhs, payload_local, payload_ty, payload_offset); |
| 1149 | }, |
| 1150 | .local => { |
| 1151 | // Load values from `rhs` stack position and store in `lhs` instead |
| 1152 | const tag_local = try self.load(rhs, tag_ty, 0); |
| 1153 | const payload_local = try self.load(rhs, payload_ty, payload_offset); |
| 1154 | |
| 1155 | try self.store(lhs, tag_local, tag_ty, 0); |
| 1156 | return try self.store(lhs, payload_local, payload_ty, payload_offset); |
| 1157 | }, |
| 1158 | .local_with_offset => |with_offset| { |
| 1159 | const tag_local = try self.allocLocal(tag_ty); |
| 1160 | try self.addImm32(0); |
| 1161 | try self.store(lhs, tag_local, tag_ty, 0); |
| 1162 | |
| 1163 | return try self.store( |
| 1164 | lhs, |
| 1165 | .{ .local = with_offset.local }, |
| 1166 | payload_ty, |
| 1167 | with_offset.offset, |
| 1168 | ); |
| 1169 | }, |
| 1170 | else => unreachable, |
| 1171 | } |
| 1039 | 1172 | }, |
| 1040 | | .local => |local| { |
| 1041 | | try self.emitWValue(rhs); |
| 1042 | | try self.addLabel(.local_set, local); |
| 1173 | .Struct => { |
| 1174 | // we are copying a struct with its fields. |
| 1175 | // Replace this with a wasm memcpy instruction once we support that feature. |
| 1176 | const fields_len = ty.structFieldCount(); |
| 1177 | var index: usize = 0; |
| 1178 | while (index < fields_len) : (index += 1) { |
| 1179 | const field_ty = ty.structFieldType(index); |
| 1180 | if (!field_ty.hasCodeGenBits()) continue; |
| 1181 | const field_offset = std.math.cast(u32, ty.structFieldOffset(index, self.target)) catch { |
| 1182 | return self.fail("Field type '{}' too big to fit into stack frame", .{field_ty}); |
| 1183 | }; |
| 1184 | const field_local = try self.load(rhs, field_ty, field_offset); |
| 1185 | try self.store(lhs, field_local, field_ty, field_offset); |
| 1186 | } |
| 1187 | return; |
| 1043 | 1188 | }, |
| 1044 | | else => unreachable, |
| 1189 | else => {}, |
| 1045 | 1190 | } |
| 1046 | | return .none; |
| 1191 | try self.emitWValue(lhs); |
| 1192 | try self.emitWValue(rhs); |
| 1193 | const valtype = try self.typeToValtype(ty); |
| 1194 | const opcode = buildOpcode(.{ |
| 1195 | .valtype1 = valtype, |
| 1196 | .width = @intCast(u8, Type.abiSize(ty, self.target) * 8), // use bitsize instead of byte size |
| 1197 | .op = .store, |
| 1198 | }); |
| 1199 | |
| 1200 | // store rhs value at stack pointer's location in memory |
| 1201 | const mem_arg_index = try self.addExtra(Mir.MemArg{ |
| 1202 | .offset = offset, |
| 1203 | .alignment = ty.abiAlignment(self.target), |
| 1204 | }); |
| 1205 | try self.addInst(.{ |
| 1206 | .tag = Mir.Inst.Tag.fromOpcode(opcode), |
| 1207 | .data = .{ .payload = mem_arg_index }, |
| 1208 | }); |
| 1047 | 1209 | } |
| 1048 | 1210 | |
| 1049 | 1211 | fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1050 | 1212 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 1051 | | return self.resolveInst(ty_op.operand); |
| 1213 | const operand = self.resolveInst(ty_op.operand); |
| 1214 | const ty = self.air.getRefType(ty_op.ty); |
| 1215 | |
| 1216 | return switch (ty.zigTypeTag()) { |
| 1217 | .Struct, .ErrorUnion, .Optional => operand, // pass as pointer |
| 1218 | else => switch (operand) { |
| 1219 | .local_with_offset => |with_offset| try self.load(operand, ty, with_offset.offset), |
| 1220 | else => try self.load(operand, ty, 0), |
| 1221 | }, |
| 1222 | }; |
| 1223 | } |
| 1224 | |
| 1225 | fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue { |
| 1226 | // load local's value from memory by its stack position |
| 1227 | try self.emitWValue(operand); |
| 1228 | // Build the opcode with the right bitsize |
| 1229 | const signedness: std.builtin.Signedness = if (ty.isUnsignedInt()) .unsigned else .signed; |
| 1230 | const opcode = buildOpcode(.{ |
| 1231 | .valtype1 = try self.typeToValtype(ty), |
| 1232 | .width = @intCast(u8, Type.abiSize(ty, self.target) * 8), // use bitsize instead of byte size |
| 1233 | .op = .load, |
| 1234 | .signedness = signedness, |
| 1235 | }); |
| 1236 | |
| 1237 | const mem_arg_index = try self.addExtra(Mir.MemArg{ |
| 1238 | .offset = offset, |
| 1239 | .alignment = ty.abiAlignment(self.target), |
| 1240 | }); |
| 1241 | try self.addInst(.{ |
| 1242 | .tag = Mir.Inst.Tag.fromOpcode(opcode), |
| 1243 | .data = .{ .payload = mem_arg_index }, |
| 1244 | }); |
| 1245 | |
| 1246 | // store the result in a local |
| 1247 | const result = try self.allocLocal(ty); |
| 1248 | try self.addLabel(.local_set, result.local); |
| 1249 | return result; |
| 1052 | 1250 | } |
| 1053 | 1251 | |
| 1054 | 1252 | fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1055 | 1253 | _ = inst; |
| 1056 | | // arguments share the index with locals |
| 1057 | | defer self.local_index += 1; |
| 1058 | | return WValue{ .local = self.local_index }; |
| 1254 | defer self.arg_index += 1; |
| 1255 | return self.args[self.arg_index]; |
| 1059 | 1256 | } |
| 1060 | 1257 | |
| 1061 | 1258 | fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue { |
| ... | ... | @@ -1063,14 +1260,6 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue { |
| 1063 | 1260 | const lhs = self.resolveInst(bin_op.lhs); |
| 1064 | 1261 | const rhs = self.resolveInst(bin_op.rhs); |
| 1065 | 1262 | |
| 1066 | | // it's possible for both lhs and/or rhs to return an offset as well, |
| 1067 | | // in which case we return the first offset occurrence we find. |
| 1068 | | const offset = blk: { |
| 1069 | | if (lhs == .mir_offset) break :blk lhs.mir_offset; |
| 1070 | | if (rhs == .mir_offset) break :blk rhs.mir_offset; |
| 1071 | | break :blk self.mir_instructions.len; |
| 1072 | | }; |
| 1073 | | |
| 1074 | 1263 | try self.emitWValue(lhs); |
| 1075 | 1264 | try self.emitWValue(rhs); |
| 1076 | 1265 | |
| ... | ... | @@ -1081,7 +1270,11 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue { |
| 1081 | 1270 | .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned, |
| 1082 | 1271 | }); |
| 1083 | 1272 | try self.addTag(Mir.Inst.Tag.fromOpcode(opcode)); |
| 1084 | | return WValue{ .mir_offset = offset }; |
| 1273 | |
| 1274 | // save the result in a temporary |
| 1275 | const bin_local = try self.allocLocal(bin_ty); |
| 1276 | try self.addLabel(.local_set, bin_local.local); |
| 1277 | return bin_local; |
| 1085 | 1278 | } |
| 1086 | 1279 | |
| 1087 | 1280 | fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue { |
| ... | ... | @@ -1089,14 +1282,6 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue { |
| 1089 | 1282 | const lhs = self.resolveInst(bin_op.lhs); |
| 1090 | 1283 | const rhs = self.resolveInst(bin_op.rhs); |
| 1091 | 1284 | |
| 1092 | | // it's possible for both lhs and/or rhs to return an offset as well, |
| 1093 | | // in which case we return the first offset occurrence we find. |
| 1094 | | const offset = blk: { |
| 1095 | | if (lhs == .mir_offset) break :blk lhs.mir_offset; |
| 1096 | | if (rhs == .mir_offset) break :blk rhs.mir_offset; |
| 1097 | | break :blk self.mir_instructions.len; |
| 1098 | | }; |
| 1099 | | |
| 1100 | 1285 | try self.emitWValue(lhs); |
| 1101 | 1286 | try self.emitWValue(rhs); |
| 1102 | 1287 | |
| ... | ... | @@ -1135,7 +1320,10 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue { |
| 1135 | 1320 | return self.fail("TODO wasm: Integer wrapping for bitsizes larger than 64", .{}); |
| 1136 | 1321 | } |
| 1137 | 1322 | |
| 1138 | | return WValue{ .mir_offset = offset }; |
| 1323 | // save the result in a temporary |
| 1324 | const bin_local = try self.allocLocal(bin_ty); |
| 1325 | try self.addLabel(.local_set, bin_local.local); |
| 1326 | return bin_local; |
| 1139 | 1327 | } |
| 1140 | 1328 | |
| 1141 | 1329 | fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void { |
| ... | ... | @@ -1176,7 +1364,7 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void { |
| 1176 | 1364 | |
| 1177 | 1365 | // memory instruction followed by their memarg immediate |
| 1178 | 1366 | // memarg ::== x:u32, y:u32 => {align x, offset y} |
| 1179 | | const extra_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 0 }); |
| 1367 | const extra_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 4 }); |
| 1180 | 1368 | try self.addInst(.{ .tag = .i32_load, .data = .{ .payload = extra_index } }); |
| 1181 | 1369 | } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()}); |
| 1182 | 1370 | }, |
| ... | ... | @@ -1289,21 +1477,29 @@ fn airBlock(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1289 | 1477 | const extra = self.air.extraData(Air.Block, ty_pl.payload); |
| 1290 | 1478 | const body = self.air.extra[extra.end..][0..extra.data.body_len]; |
| 1291 | 1479 | |
| 1292 | | try self.startBlock(.block, block_ty, null); |
| 1480 | // if block_ty is non-empty, we create a register to store the temporary value |
| 1481 | const block_result: WValue = if (block_ty != wasm.block_empty) |
| 1482 | try self.allocLocal(self.air.getRefType(ty_pl.ty)) |
| 1483 | else |
| 1484 | WValue.none; |
| 1485 | |
| 1486 | try self.startBlock(.block, wasm.block_empty); |
| 1293 | 1487 | // Here we set the current block idx, so breaks know the depth to jump |
| 1294 | 1488 | // to when breaking out. |
| 1295 | | try self.blocks.putNoClobber(self.gpa, inst, self.block_depth); |
| 1489 | try self.blocks.putNoClobber(self.gpa, inst, .{ |
| 1490 | .label = self.block_depth, |
| 1491 | .value = block_result, |
| 1492 | }); |
| 1296 | 1493 | try self.genBody(body); |
| 1297 | 1494 | try self.endBlock(); |
| 1298 | 1495 | |
| 1299 | | return .none; |
| 1496 | return block_result; |
| 1300 | 1497 | } |
| 1301 | 1498 | |
| 1302 | 1499 | /// appends a new wasm block to the code section and increases the `block_depth` by 1 |
| 1303 | | fn startBlock(self: *Self, block_tag: wasm.Opcode, valtype: u8, with_offset: ?usize) !void { |
| 1500 | fn startBlock(self: *Self, block_tag: wasm.Opcode, valtype: u8) !void { |
| 1304 | 1501 | self.block_depth += 1; |
| 1305 | | const offset = with_offset orelse self.mir_instructions.len; |
| 1306 | | try self.addInstAt(offset, .{ |
| 1502 | try self.addInst(.{ |
| 1307 | 1503 | .tag = Mir.Inst.Tag.fromOpcode(block_tag), |
| 1308 | 1504 | .data = .{ .block_type = valtype }, |
| 1309 | 1505 | }); |
| ... | ... | @@ -1322,7 +1518,7 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1322 | 1518 | |
| 1323 | 1519 | // result type of loop is always 'noreturn', meaning we can always |
| 1324 | 1520 | // emit the wasm type 'block_empty'. |
| 1325 | | try self.startBlock(.loop, wasm.block_empty, null); |
| 1521 | try self.startBlock(.loop, wasm.block_empty); |
| 1326 | 1522 | try self.genBody(body); |
| 1327 | 1523 | |
| 1328 | 1524 | // breaking to the index of a loop block will continue the loop instead |
| ... | ... | @@ -1340,19 +1536,10 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1340 | 1536 | const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; |
| 1341 | 1537 | // TODO: Handle death instructions for then and else body |
| 1342 | 1538 | |
| 1343 | | // insert blocks at the position of `offset` so |
| 1344 | | // the condition can jump to it |
| 1345 | | const offset = switch (condition) { |
| 1346 | | .mir_offset => |offset| offset, |
| 1347 | | else => blk: { |
| 1348 | | const offset = self.mir_instructions.len; |
| 1349 | | try self.emitWValue(condition); |
| 1350 | | break :blk offset; |
| 1351 | | }, |
| 1352 | | }; |
| 1353 | | |
| 1354 | 1539 | // result type is always noreturn, so use `block_empty` as type. |
| 1355 | | try self.startBlock(.block, wasm.block_empty, offset); |
| 1540 | try self.startBlock(.block, wasm.block_empty); |
| 1541 | // emit the conditional value |
| 1542 | try self.emitWValue(condition); |
| 1356 | 1543 | |
| 1357 | 1544 | // we inserted the block in front of the condition |
| 1358 | 1545 | // so now check if condition matches. If not, break outside this block |
| ... | ... | @@ -1369,10 +1556,6 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1369 | 1556 | } |
| 1370 | 1557 | |
| 1371 | 1558 | fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!WValue { |
| 1372 | | // save offset, so potential conditions can insert blocks in front of |
| 1373 | | // the comparison that we can later jump back to |
| 1374 | | const offset = self.mir_instructions.len; |
| 1375 | | |
| 1376 | 1559 | const data: Air.Inst.Data = self.air.instructions.items(.data)[inst]; |
| 1377 | 1560 | const lhs = self.resolveInst(data.bin_op.lhs); |
| 1378 | 1561 | const rhs = self.resolveInst(data.bin_op.rhs); |
| ... | ... | @@ -1401,20 +1584,28 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) Inner |
| 1401 | 1584 | .signedness = signedness, |
| 1402 | 1585 | }); |
| 1403 | 1586 | try self.addTag(Mir.Inst.Tag.fromOpcode(opcode)); |
| 1404 | | return WValue{ .mir_offset = offset }; |
| 1587 | |
| 1588 | const cmp_tmp = try self.allocLocal(lhs_ty); |
| 1589 | try self.addLabel(.local_set, cmp_tmp.local); |
| 1590 | return cmp_tmp; |
| 1405 | 1591 | } |
| 1406 | 1592 | |
| 1407 | 1593 | fn airBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1408 | 1594 | const br = self.air.instructions.items(.data)[inst].br; |
| 1595 | const block = self.blocks.get(br.block_inst).?; |
| 1409 | 1596 | |
| 1410 | 1597 | // if operand has codegen bits we should break with a value |
| 1411 | 1598 | if (self.air.typeOf(br.operand).hasCodeGenBits()) { |
| 1412 | 1599 | try self.emitWValue(self.resolveInst(br.operand)); |
| 1600 | |
| 1601 | if (block.value != .none) { |
| 1602 | try self.addLabel(.local_set, block.value.local); |
| 1603 | } |
| 1413 | 1604 | } |
| 1414 | 1605 | |
| 1415 | 1606 | // We map every block to its block index. |
| 1416 | 1607 | // We then determine how far we have to jump to it by subtracting it from current block depth |
| 1417 | | const idx: u32 = self.block_depth - self.blocks.get(br.block_inst).?; |
| 1608 | const idx: u32 = self.block_depth - block.label; |
| 1418 | 1609 | try self.addLabel(.br, idx); |
| 1419 | 1610 | |
| 1420 | 1611 | return .none; |
| ... | ... | @@ -1422,7 +1613,6 @@ fn airBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1422 | 1613 | |
| 1423 | 1614 | fn airNot(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1424 | 1615 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 1425 | | const offset = self.mir_instructions.len; |
| 1426 | 1616 | |
| 1427 | 1617 | const operand = self.resolveInst(ty_op.operand); |
| 1428 | 1618 | try self.emitWValue(operand); |
| ... | ... | @@ -1432,7 +1622,10 @@ fn airNot(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1432 | 1622 | try self.addImm32(0); |
| 1433 | 1623 | try self.addTag(.i32_eq); |
| 1434 | 1624 | |
| 1435 | | return WValue{ .mir_offset = offset }; |
| 1625 | // save the result in the local |
| 1626 | const not_tmp = try self.allocLocal(self.air.getRefType(ty_op.ty)); |
| 1627 | try self.addLabel(.local_set, not_tmp.local); |
| 1628 | return not_tmp; |
| 1436 | 1629 | } |
| 1437 | 1630 | |
| 1438 | 1631 | fn airBreakpoint(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| ... | ... | @@ -1458,24 +1651,45 @@ fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1458 | 1651 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; |
| 1459 | 1652 | const extra = self.air.extraData(Air.StructField, ty_pl.payload); |
| 1460 | 1653 | const struct_ptr = self.resolveInst(extra.data.struct_operand); |
| 1461 | | return structFieldPtr(struct_ptr, extra.data.field_index); |
| 1654 | const struct_ty = self.air.typeOf(extra.data.struct_operand).childType(); |
| 1655 | const offset = std.math.cast(u32, struct_ty.structFieldOffset(extra.data.field_index, self.target)) catch { |
| 1656 | return self.fail("Field type '{}' too big to fit into stack frame", .{ |
| 1657 | struct_ty.structFieldType(extra.data.field_index), |
| 1658 | }); |
| 1659 | }; |
| 1660 | return structFieldPtr(struct_ptr, offset); |
| 1462 | 1661 | } |
| 1662 | |
| 1463 | 1663 | fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u32) InnerError!WValue { |
| 1464 | 1664 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 1465 | 1665 | const struct_ptr = self.resolveInst(ty_op.operand); |
| 1466 | | return structFieldPtr(struct_ptr, index); |
| 1666 | const struct_ty = self.air.typeOf(ty_op.operand).childType(); |
| 1667 | const offset = std.math.cast(u32, struct_ty.structFieldOffset(index, self.target)) catch { |
| 1668 | return self.fail("Field type '{}' too big to fit into stack frame", .{ |
| 1669 | struct_ty.structFieldType(index), |
| 1670 | }); |
| 1671 | }; |
| 1672 | return structFieldPtr(struct_ptr, offset); |
| 1467 | 1673 | } |
| 1468 | | fn structFieldPtr(struct_ptr: WValue, index: u32) InnerError!WValue { |
| 1469 | | return WValue{ .local = struct_ptr.multi_value.index + index }; |
| 1674 | |
| 1675 | fn structFieldPtr(struct_ptr: WValue, offset: u32) InnerError!WValue { |
| 1676 | return WValue{ .local_with_offset = .{ .local = struct_ptr.local, .offset = offset } }; |
| 1470 | 1677 | } |
| 1471 | 1678 | |
| 1472 | 1679 | fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1473 | 1680 | if (self.liveness.isUnused(inst)) return WValue.none; |
| 1474 | 1681 | |
| 1475 | 1682 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; |
| 1476 | | const extra = self.air.extraData(Air.StructField, ty_pl.payload).data; |
| 1477 | | const struct_multivalue = self.resolveInst(extra.struct_operand).multi_value; |
| 1478 | | return WValue{ .local = struct_multivalue.index + extra.field_index }; |
| 1683 | const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data; |
| 1684 | const struct_ty = self.air.typeOf(struct_field.struct_operand); |
| 1685 | const operand = self.resolveInst(struct_field.struct_operand); |
| 1686 | const field_index = struct_field.field_index; |
| 1687 | const field_ty = struct_ty.structFieldType(field_index); |
| 1688 | if (!field_ty.hasCodeGenBits()) return WValue.none; |
| 1689 | const offset = std.math.cast(u32, struct_ty.structFieldOffset(field_index, self.target)) catch { |
| 1690 | return self.fail("Field type '{}' too big to fit into stack frame", .{field_ty}); |
| 1691 | }; |
| 1692 | return try self.load(operand, field_ty, offset); |
| 1479 | 1693 | } |
| 1480 | 1694 | |
| 1481 | 1695 | fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| ... | ... | @@ -1521,7 +1735,7 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1521 | 1735 | } |
| 1522 | 1736 | |
| 1523 | 1737 | case_list.appendAssumeCapacity(.{ .values = values, .body = case_body }); |
| 1524 | | try self.startBlock(.block, blocktype, null); |
| 1738 | try self.startBlock(.block, blocktype); |
| 1525 | 1739 | } |
| 1526 | 1740 | |
| 1527 | 1741 | // When the highest and lowest values are seperated by '50', |
| ... | ... | @@ -1534,7 +1748,7 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1534 | 1748 | const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len]; |
| 1535 | 1749 | const has_else_body = else_body.len != 0; |
| 1536 | 1750 | if (has_else_body) { |
| 1537 | | try self.startBlock(.block, blocktype, null); |
| 1751 | try self.startBlock(.block, blocktype); |
| 1538 | 1752 | } |
| 1539 | 1753 | |
| 1540 | 1754 | if (!is_sparse) { |
| ... | ... | @@ -1542,7 +1756,7 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1542 | 1756 | // The value 'target' represents the index into the table. |
| 1543 | 1757 | // Each index in the table represents a label to the branch |
| 1544 | 1758 | // to jump to. |
| 1545 | | try self.startBlock(.block, blocktype, null); |
| 1759 | try self.startBlock(.block, blocktype); |
| 1546 | 1760 | try self.emitWValue(target); |
| 1547 | 1761 | if (lowest < 0) { |
| 1548 | 1762 | // since br_table works using indexes, starting from '0', we must ensure all values |
| ... | ... | @@ -1598,7 +1812,7 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1598 | 1812 | try self.addLabel(.br_if, 0); |
| 1599 | 1813 | } else { |
| 1600 | 1814 | // in multi-value prongs we must check if any prongs match the target value. |
| 1601 | | try self.startBlock(.block, blocktype, null); |
| 1815 | try self.startBlock(.block, blocktype); |
| 1602 | 1816 | for (case.values) |value| { |
| 1603 | 1817 | try self.emitWValue(target); |
| 1604 | 1818 | try self.emitConstant(value.value, target_ty); |
| ... | ... | @@ -1629,30 +1843,40 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1629 | 1843 | fn airIsErr(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue { |
| 1630 | 1844 | const un_op = self.air.instructions.items(.data)[inst].un_op; |
| 1631 | 1845 | const operand = self.resolveInst(un_op); |
| 1632 | | const offset = self.mir_instructions.len; |
| 1846 | const err_ty = self.air.typeOf(un_op).errorUnionSet(); |
| 1847 | |
| 1848 | // load the error tag value |
| 1849 | try self.emitWValue(operand); |
| 1850 | const mem_arg_index = try self.addExtra(Mir.MemArg{ |
| 1851 | .offset = 0, |
| 1852 | .alignment = err_ty.abiAlignment(self.target), |
| 1853 | }); |
| 1854 | try self.addInst(.{ |
| 1855 | .tag = .i32_load16_u, |
| 1856 | .data = .{ .payload = mem_arg_index }, |
| 1857 | }); |
| 1633 | 1858 | |
| 1634 | | // load the error value which is positioned at multi_value's index |
| 1635 | | try self.emitWValue(.{ .local = operand.multi_value.index }); |
| 1636 | 1859 | // Compare the error value with '0' |
| 1637 | 1860 | try self.addImm32(0); |
| 1638 | 1861 | try self.addTag(Mir.Inst.Tag.fromOpcode(opcode)); |
| 1639 | 1862 | |
| 1640 | | return WValue{ .mir_offset = offset }; |
| 1863 | const is_err_tmp = try self.allocLocal(err_ty); |
| 1864 | try self.addLabel(.local_set, is_err_tmp.local); |
| 1865 | return is_err_tmp; |
| 1641 | 1866 | } |
| 1642 | 1867 | |
| 1643 | 1868 | fn airUnwrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1644 | 1869 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 1645 | 1870 | const operand = self.resolveInst(ty_op.operand); |
| 1646 | | // The index of multi_value contains the error code. To get the initial index of the payload we get |
| 1647 | | // the following index. Next, convert it to a `WValue.local` |
| 1648 | | // |
| 1649 | | // TODO: Check if payload is a type that requires a multi_value as well and emit that instead. i.e. a struct. |
| 1650 | | return WValue{ .local = operand.multi_value.index + 1 }; |
| 1871 | const err_ty = self.air.typeOf(ty_op.operand); |
| 1872 | const offset = @intCast(u32, err_ty.errorUnionSet().abiSize(self.target)); |
| 1873 | return self.load(operand, err_ty.errorUnionPayload(), offset); |
| 1651 | 1874 | } |
| 1652 | 1875 | |
| 1653 | 1876 | fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1654 | 1877 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 1655 | | return self.resolveInst(ty_op.operand); |
| 1878 | _ = ty_op; |
| 1879 | return self.fail("TODO: wasm airWrapErrUnionPayload", .{}); |
| 1656 | 1880 | } |
| 1657 | 1881 | |
| 1658 | 1882 | fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| ... | ... | @@ -1682,22 +1906,41 @@ fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError! |
| 1682 | 1906 | const un_op = self.air.instructions.items(.data)[inst].un_op; |
| 1683 | 1907 | const operand = self.resolveInst(un_op); |
| 1684 | 1908 | |
| 1685 | | // load the null value which is positioned at multi_value's index |
| 1686 | | try self.emitWValue(.{ .local = operand.multi_value.index }); |
| 1909 | // load the null tag value |
| 1910 | try self.emitWValue(operand); |
| 1911 | const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 1 }); |
| 1912 | try self.addInst(.{ |
| 1913 | .tag = .i32_load8_u, |
| 1914 | .data = .{ .payload = mem_arg_index }, |
| 1915 | }); |
| 1916 | |
| 1917 | // Compare the error value with '0' |
| 1687 | 1918 | try self.addImm32(0); |
| 1688 | 1919 | try self.addTag(Mir.Inst.Tag.fromOpcode(opcode)); |
| 1689 | 1920 | |
| 1690 | | // we save the result in a new local |
| 1691 | | const local = try self.allocLocal(Type.initTag(.i32)); |
| 1692 | | try self.addLabel(.local_set, local.local); |
| 1693 | | |
| 1694 | | return local; |
| 1921 | const is_null_tmp = try self.allocLocal(Type.initTag(.u8)); |
| 1922 | try self.addLabel(.local_set, is_null_tmp.local); |
| 1923 | return is_null_tmp; |
| 1695 | 1924 | } |
| 1696 | 1925 | |
| 1697 | 1926 | fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1698 | 1927 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 1699 | 1928 | const operand = self.resolveInst(ty_op.operand); |
| 1700 | | return WValue{ .local = operand.multi_value.index + 1 }; |
| 1929 | const opt_ty = self.air.typeOf(ty_op.operand); |
| 1930 | |
| 1931 | // For pointers we simply return its stack address, rather than |
| 1932 | // loading its value |
| 1933 | if (opt_ty.zigTypeTag() == .Pointer) { |
| 1934 | return WValue{ .local_with_offset = .{ .local = operand.local, .offset = 1 } }; |
| 1935 | } |
| 1936 | |
| 1937 | if (opt_ty.isPtrLikeOptional()) return operand; |
| 1938 | |
| 1939 | var buf: Type.Payload.ElemType = undefined; |
| 1940 | const child_ty = opt_ty.optionalChild(&buf); |
| 1941 | const offset = opt_ty.abiSize(self.target) - child_ty.abiSize(self.target); |
| 1942 | |
| 1943 | return self.load(operand, child_ty, @intCast(u32, offset)); |
| 1701 | 1944 | } |
| 1702 | 1945 | |
| 1703 | 1946 | fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| ... | ... | @@ -1709,5 +1952,14 @@ fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue |
| 1709 | 1952 | |
| 1710 | 1953 | fn airWrapOptional(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1711 | 1954 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 1712 | | return self.resolveInst(ty_op.operand); |
| 1955 | const operand = self.resolveInst(ty_op.operand); |
| 1956 | |
| 1957 | const op_ty = self.air.typeOf(ty_op.operand); |
| 1958 | const optional_ty = self.air.getRefType(ty_op.ty); |
| 1959 | const offset = optional_ty.abiSize(self.target) - op_ty.abiSize(self.target); |
| 1960 | |
| 1961 | return WValue{ .local_with_offset = .{ |
| 1962 | .local = operand.local, |
| 1963 | .offset = @intCast(u32, offset), |
| 1964 | } }; |
| 1713 | 1965 | } |