| author | |
| committer | |
| log | 571aa694fd150b9f76d78a6ac96c25163f46091a |
| tree | 46944915fe868cfb566fab2304c6f88a9995d6e1 |
| parent | f2301ba896bd522483e987ecebe72bdcd86705ba |
| signature |
8 files changed, 551 insertions(+), 169 deletions(-)
src/arch/riscv64/CodeGen.zig+201-41| ... | ... | @@ -37,6 +37,7 @@ const abi = @import("abi.zig"); |
| 37 | 37 | const Lower = @import("Lower.zig"); |
| 38 | 38 | |
| 39 | 39 | const Register = bits.Register; |
| 40 | const CSR = bits.CSR; | |
| 40 | 41 | const Immediate = bits.Immediate; |
| 41 | 42 | const Memory = bits.Memory; |
| 42 | 43 | const FrameIndex = bits.FrameIndex; |
| ... | ... | @@ -87,6 +88,9 @@ exitlude_jump_relocs: std.ArrayListUnmanaged(usize) = .{}, |
| 87 | 88 | /// across each runtime branch upon joining. |
| 88 | 89 | branch_stack: *std.ArrayList(Branch), |
| 89 | 90 | |
| 91 | // The current bit length of vector registers. | |
| 92 | vec_len: u32, | |
| 93 | ||
| 90 | 94 | // Key is the block instruction |
| 91 | 95 | blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .{}, |
| 92 | 96 | register_manager: RegisterManager = .{}, |
| ... | ... | @@ -747,6 +751,7 @@ pub fn generate( |
| 747 | 751 | .end_di_line = func.rbrace_line, |
| 748 | 752 | .end_di_column = func.rbrace_column, |
| 749 | 753 | .scope_generation = 0, |
| 754 | .vec_len = 16 * 8, // TODO: set this per cpu | |
| 750 | 755 | }; |
| 751 | 756 | defer { |
| 752 | 757 | function.frame_allocs.deinit(gpa); |
| ... | ... | @@ -1040,10 +1045,60 @@ pub fn addExtraAssumeCapacity(func: *Func, extra: anytype) u32 { |
| 1040 | 1045 | return result; |
| 1041 | 1046 | } |
| 1042 | 1047 | |
| 1048 | /// Returns a temporary register that contains the value of the `reg` csr. | |
| 1049 | /// | |
| 1050 | /// Caller's duty to lock the return register is needed. | |
| 1051 | fn getCsr(func: *Func, csr: CSR) !Register { | |
| 1052 | assert(func.hasFeature(.zicsr)); | |
| 1053 | const dst_reg = try func.register_manager.allocReg(null, func.regTempClassForType(Type.usize)); | |
| 1054 | _ = try func.addInst(.{ | |
| 1055 | .tag = .csrrs, | |
| 1056 | .ops = .csr, | |
| 1057 | .data = .{ | |
| 1058 | .csr = .{ | |
| 1059 | .csr = csr, | |
| 1060 | .rd = dst_reg, | |
| 1061 | .rs1 = .x0, | |
| 1062 | }, | |
| 1063 | }, | |
| 1064 | }); | |
| 1065 | return dst_reg; | |
| 1066 | } | |
| 1067 | ||
| 1068 | fn setVl(func: *Func, dst_reg: Register, avl: u5, options: bits.VType) !void { | |
| 1069 | if (avl == 0) { | |
| 1070 | const options_int: u12 = @as(u12, 0) | @as(u8, @bitCast(options)); | |
| 1071 | _ = try func.addInst(.{ | |
| 1072 | .tag = .vsetvli, | |
| 1073 | .ops = .rri, | |
| 1074 | .data = .{ .i_type = .{ | |
| 1075 | .rd = dst_reg, | |
| 1076 | .rs1 = .zero, | |
| 1077 | .imm12 = Immediate.u(options_int), | |
| 1078 | } }, | |
| 1079 | }); | |
| 1080 | } else { | |
| 1081 | const options_int: u12 = (~@as(u12, 0) << 10) | @as(u8, @bitCast(options)); | |
| 1082 | _ = try func.addInst(.{ | |
| 1083 | .tag = .vsetivli, | |
| 1084 | .ops = .rri, | |
| 1085 | .data = .{ | |
| 1086 | .i_type = .{ | |
| 1087 | .rd = dst_reg, | |
| 1088 | .rs1 = @enumFromInt(avl), | |
| 1089 | .imm12 = Immediate.u(options_int), | |
| 1090 | }, | |
| 1091 | }, | |
| 1092 | }); | |
| 1093 | } | |
| 1094 | } | |
| 1095 | ||
| 1043 | 1096 | const required_features = [_]Target.riscv.Feature{ |
| 1044 | 1097 | .d, |
| 1045 | 1098 | .m, |
| 1046 | 1099 | .a, |
| 1100 | .zicsr, | |
| 1101 | .v, | |
| 1047 | 1102 | }; |
| 1048 | 1103 | |
| 1049 | 1104 | fn gen(func: *Func) !void { |
| ... | ... | @@ -1101,7 +1156,19 @@ fn gen(func: *Func) !void { |
| 1101 | 1156 | const backpatch_ra_restore = try func.addPseudo(.pseudo_dead); |
| 1102 | 1157 | const backpatch_fp_restore = try func.addPseudo(.pseudo_dead); |
| 1103 | 1158 | const backpatch_stack_alloc_restore = try func.addPseudo(.pseudo_dead); |
| 1104 | try func.addPseudoNone(.pseudo_ret); | |
| 1159 | ||
| 1160 | // ret | |
| 1161 | _ = try func.addInst(.{ | |
| 1162 | .tag = .jalr, | |
| 1163 | .ops = .rri, | |
| 1164 | .data = .{ | |
| 1165 | .i_type = .{ | |
| 1166 | .rd = .zero, | |
| 1167 | .rs1 = .ra, | |
| 1168 | .imm12 = Immediate.s(0), | |
| 1169 | }, | |
| 1170 | }, | |
| 1171 | }); | |
| 1105 | 1172 | |
| 1106 | 1173 | const frame_layout = try func.computeFrameLayout(); |
| 1107 | 1174 | const need_save_reg = frame_layout.save_reg_list.count() > 0; |
| ... | ... | @@ -1842,8 +1909,8 @@ fn typeRegClass(func: *Func, ty: Type) abi.RegisterClass { |
| 1842 | 1909 | const zcu = pt.zcu; |
| 1843 | 1910 | return switch (ty.zigTypeTag(zcu)) { |
| 1844 | 1911 | .Float => .float, |
| 1845 | .Vector => @panic("TODO: typeRegClass for Vectors"), | |
| 1846 | inline else => .int, | |
| 1912 | .Vector => .vector, | |
| 1913 | else => .int, | |
| 1847 | 1914 | }; |
| 1848 | 1915 | } |
| 1849 | 1916 | |
| ... | ... | @@ -1852,7 +1919,7 @@ fn regGeneralClassForType(func: *Func, ty: Type) RegisterManager.RegisterBitSet |
| 1852 | 1919 | const zcu = pt.zcu; |
| 1853 | 1920 | return switch (ty.zigTypeTag(zcu)) { |
| 1854 | 1921 | .Float => abi.Registers.Float.general_purpose, |
| 1855 | .Vector => @panic("TODO: regGeneralClassForType for Vectors"), | |
| 1922 | .Vector => abi.Registers.Vector.general_purpose, | |
| 1856 | 1923 | else => abi.Registers.Integer.general_purpose, |
| 1857 | 1924 | }; |
| 1858 | 1925 | } |
| ... | ... | @@ -1862,7 +1929,7 @@ fn regTempClassForType(func: *Func, ty: Type) RegisterManager.RegisterBitSet { |
| 1862 | 1929 | const zcu = pt.zcu; |
| 1863 | 1930 | return switch (ty.zigTypeTag(zcu)) { |
| 1864 | 1931 | .Float => abi.Registers.Float.temporary, |
| 1865 | .Vector => @panic("TODO: regTempClassForType for Vectors"), | |
| 1932 | .Vector => abi.Registers.Vector.general_purpose, // there are no temporary vector registers | |
| 1866 | 1933 | else => abi.Registers.Integer.temporary, |
| 1867 | 1934 | }; |
| 1868 | 1935 | } |
| ... | ... | @@ -1870,20 +1937,19 @@ fn regTempClassForType(func: *Func, ty: Type) RegisterManager.RegisterBitSet { |
| 1870 | 1937 | fn allocRegOrMem(func: *Func, elem_ty: Type, inst: ?Air.Inst.Index, reg_ok: bool) !MCValue { |
| 1871 | 1938 | const pt = func.pt; |
| 1872 | 1939 | |
| 1873 | const abi_size = math.cast(u32, elem_ty.abiSize(pt)) orelse { | |
| 1874 | return func.fail("type '{}' too big to fit into stack frame", .{elem_ty.fmt(pt)}); | |
| 1940 | const bit_size = elem_ty.bitSize(pt); | |
| 1941 | const min_size: u64 = switch (elem_ty.zigTypeTag(pt.zcu)) { | |
| 1942 | .Float => if (func.hasFeature(.d)) 64 else 32, | |
| 1943 | .Vector => func.vec_len, | |
| 1944 | else => 64, | |
| 1875 | 1945 | }; |
| 1876 | 1946 | |
| 1877 | const min_size: u32 = switch (elem_ty.zigTypeTag(pt.zcu)) { | |
| 1878 | .Float => 4, | |
| 1879 | .Vector => @panic("allocRegOrMem Vector"), | |
| 1880 | else => 8, | |
| 1881 | }; | |
| 1882 | ||
| 1883 | if (reg_ok and abi_size <= min_size) { | |
| 1947 | if (reg_ok and bit_size <= min_size) { | |
| 1884 | 1948 | if (func.register_manager.tryAllocReg(inst, func.regGeneralClassForType(elem_ty))) |reg| { |
| 1885 | 1949 | return .{ .register = reg }; |
| 1886 | 1950 | } |
| 1951 | } else if (reg_ok and elem_ty.zigTypeTag(pt.zcu) == .Vector) { | |
| 1952 | return func.fail("did you forget to extend vector registers before allocating", .{}); | |
| 1887 | 1953 | } |
| 1888 | 1954 | |
| 1889 | 1955 | const frame_index = try func.allocFrameIndex(FrameAlloc.initSpill(elem_ty, pt)); |
| ... | ... | @@ -1896,10 +1962,13 @@ fn allocRegOrMem(func: *Func, elem_ty: Type, inst: ?Air.Inst.Index, reg_ok: bool |
| 1896 | 1962 | fn allocReg(func: *Func, reg_class: abi.RegisterClass) !struct { Register, RegisterLock } { |
| 1897 | 1963 | if (reg_class == .float and !func.hasFeature(.f)) |
| 1898 | 1964 | std.debug.panic("allocReg class == float where F isn't enabled", .{}); |
| 1965 | if (reg_class == .vector and !func.hasFeature(.v)) | |
| 1966 | std.debug.panic("allocReg class == vector where V isn't enabled", .{}); | |
| 1899 | 1967 | |
| 1900 | 1968 | const class = switch (reg_class) { |
| 1901 | 1969 | .int => abi.Registers.Integer.general_purpose, |
| 1902 | 1970 | .float => abi.Registers.Float.general_purpose, |
| 1971 | .vector => abi.Registers.Vector.general_purpose, | |
| 1903 | 1972 | }; |
| 1904 | 1973 | |
| 1905 | 1974 | const reg = try func.register_manager.allocReg(null, class); |
| ... | ... | @@ -1915,7 +1984,8 @@ fn promoteReg(func: *Func, ty: Type, operand: MCValue) !struct { Register, ?Regi |
| 1915 | 1984 | return .{ op_reg, func.register_manager.lockReg(operand.register) }; |
| 1916 | 1985 | } |
| 1917 | 1986 | |
| 1918 | const reg, const lock = try func.allocReg(func.typeRegClass(ty)); | |
| 1987 | const class = func.typeRegClass(ty); | |
| 1988 | const reg, const lock = try func.allocReg(class); | |
| 1919 | 1989 | try func.genSetReg(ty, reg, operand); |
| 1920 | 1990 | return .{ reg, lock }; |
| 1921 | 1991 | } |
| ... | ... | @@ -2372,6 +2442,42 @@ fn genBinOp( |
| 2372 | 2442 | }, |
| 2373 | 2443 | }); |
| 2374 | 2444 | }, |
| 2445 | .Vector => { | |
| 2446 | const mir_tag: Mir.Inst.Tag = switch (tag) { | |
| 2447 | .add => .vaddvv, | |
| 2448 | else => return func.fail("TODO: genBinOp {s} Vector", .{@tagName(tag)}), | |
| 2449 | }; | |
| 2450 | ||
| 2451 | const num_elem: u5 = math.cast(u5, lhs_ty.vectorLen(zcu)) orelse { | |
| 2452 | return func.fail("TODO: genBinOp use vsetvli for larger avl sizes", .{}); | |
| 2453 | }; | |
| 2454 | const elem_size = lhs_ty.childType(zcu).bitSize(pt); | |
| 2455 | ||
| 2456 | try func.setVl(.zero, num_elem, .{ | |
| 2457 | .vlmul = .mf2, | |
| 2458 | .vsew = switch (elem_size) { | |
| 2459 | 8 => .@"8", | |
| 2460 | 16 => .@"16", | |
| 2461 | 32 => .@"32", | |
| 2462 | 64 => .@"64", | |
| 2463 | else => unreachable, | |
| 2464 | }, | |
| 2465 | .vma = true, | |
| 2466 | .vta = true, | |
| 2467 | }); | |
| 2468 | ||
| 2469 | _ = try func.addInst(.{ | |
| 2470 | .tag = mir_tag, | |
| 2471 | .ops = .rrr, | |
| 2472 | .data = .{ | |
| 2473 | .r_type = .{ | |
| 2474 | .rd = dst_reg, | |
| 2475 | .rs1 = lhs_reg, | |
| 2476 | .rs2 = rhs_reg, | |
| 2477 | }, | |
| 2478 | }, | |
| 2479 | }); | |
| 2480 | }, | |
| 2375 | 2481 | else => unreachable, |
| 2376 | 2482 | } |
| 2377 | 2483 | }, |
| ... | ... | @@ -3560,13 +3666,12 @@ fn airSetUnionTag(func: *Func, inst: Air.Inst.Index) !void { |
| 3560 | 3666 | } |
| 3561 | 3667 | |
| 3562 | 3668 | fn airGetUnionTag(func: *Func, inst: Air.Inst.Index) !void { |
| 3563 | const zcu = func.bin_file.comp.module.?; | |
| 3564 | const mod = func.bin_file.comp.module.?; | |
| 3669 | const pt = func.pt; | |
| 3565 | 3670 | const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 3566 | 3671 | |
| 3567 | 3672 | const tag_ty = func.typeOfIndex(inst); |
| 3568 | 3673 | const union_ty = func.typeOf(ty_op.operand); |
| 3569 | const layout = union_ty.unionGetLayout(mod); | |
| 3674 | const layout = union_ty.unionGetLayout(pt); | |
| 3570 | 3675 | |
| 3571 | 3676 | if (layout.tag_size == 0) { |
| 3572 | 3677 | return func.finishAir(inst, .none, .{ ty_op.operand, .none, .none }); |
| ... | ... | @@ -3577,7 +3682,7 @@ fn airGetUnionTag(func: *Func, inst: Air.Inst.Index) !void { |
| 3577 | 3682 | const frame_mcv = try func.allocRegOrMem(union_ty, null, false); |
| 3578 | 3683 | try func.genCopy(union_ty, frame_mcv, operand); |
| 3579 | 3684 | |
| 3580 | const tag_abi_size = tag_ty.abiSize(mod); | |
| 3685 | const tag_abi_size = tag_ty.abiSize(pt); | |
| 3581 | 3686 | const result_reg, const result_lock = try func.allocReg(.int); |
| 3582 | 3687 | defer func.register_manager.unlockReg(result_lock); |
| 3583 | 3688 | |
| ... | ... | @@ -3597,7 +3702,7 @@ fn airGetUnionTag(func: *Func, inst: Air.Inst.Index) !void { |
| 3597 | 3702 | } else { |
| 3598 | 3703 | return func.fail( |
| 3599 | 3704 | "TODO implement get_union_tag for ABI larger than 8 bytes and operand {}, tag {}", |
| 3600 | .{ frame_mcv, tag_ty.fmt(zcu) }, | |
| 3705 | .{ frame_mcv, tag_ty.fmt(pt) }, | |
| 3601 | 3706 | ); |
| 3602 | 3707 | } |
| 3603 | 3708 | }, |
| ... | ... | @@ -3781,13 +3886,13 @@ fn airBitReverse(func: *Func, inst: Air.Inst.Index) !void { |
| 3781 | 3886 | } |
| 3782 | 3887 | |
| 3783 | 3888 | fn airUnaryMath(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { |
| 3784 | const zcu = func.bin_file.comp.module.?; | |
| 3889 | const pt = func.pt; | |
| 3785 | 3890 | const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op; |
| 3786 | 3891 | const result: MCValue = if (func.liveness.isUnused(inst)) .unreach else result: { |
| 3787 | 3892 | const ty = func.typeOf(un_op); |
| 3788 | 3893 | |
| 3789 | 3894 | const operand = try func.resolveInst(un_op); |
| 3790 | const operand_bit_size = ty.bitSize(zcu); | |
| 3895 | const operand_bit_size = ty.bitSize(pt); | |
| 3791 | 3896 | |
| 3792 | 3897 | if (!math.isPowerOfTwo(operand_bit_size)) |
| 3793 | 3898 | return func.fail("TODO: airUnaryMath non-pow 2", .{}); |
| ... | ... | @@ -3799,7 +3904,7 @@ fn airUnaryMath(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { |
| 3799 | 3904 | const dst_reg, const dst_lock = try func.allocReg(dst_class); |
| 3800 | 3905 | defer func.register_manager.unlockReg(dst_lock); |
| 3801 | 3906 | |
| 3802 | switch (ty.zigTypeTag(zcu)) { | |
| 3907 | switch (ty.zigTypeTag(pt.zcu)) { | |
| 3803 | 3908 | .Float => { |
| 3804 | 3909 | assert(dst_class == .float); |
| 3805 | 3910 | |
| ... | ... | @@ -3833,7 +3938,7 @@ fn airUnaryMath(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { |
| 3833 | 3938 | else => return func.fail("TODO: airUnaryMath Float {s}", .{@tagName(tag)}), |
| 3834 | 3939 | } |
| 3835 | 3940 | }, |
| 3836 | else => return func.fail("TODO: airUnaryMath ty: {}", .{ty.fmt(zcu)}), | |
| 3941 | else => return func.fail("TODO: airUnaryMath ty: {}", .{ty.fmt(pt)}), | |
| 3837 | 3942 | } |
| 3838 | 3943 | |
| 3839 | 3944 | break :result MCValue{ .register = dst_reg }; |
| ... | ... | @@ -4510,7 +4615,27 @@ fn airRet(func: *Func, inst: Air.Inst.Index, safety: bool) !void { |
| 4510 | 4615 | .none => {}, |
| 4511 | 4616 | .register, |
| 4512 | 4617 | .register_pair, |
| 4513 | => try func.genCopy(ret_ty, func.ret_mcv.short, .{ .air_ref = un_op }), | |
| 4618 | => { | |
| 4619 | if (ret_ty.isVector(zcu)) { | |
| 4620 | const bit_size = ret_ty.totalVectorBits(pt); | |
| 4621 | ||
| 4622 | // set the vtype to hold the entire vector's contents in a single element | |
| 4623 | try func.setVl(.zero, 0, .{ | |
| 4624 | .vsew = switch (bit_size) { | |
| 4625 | 8 => .@"8", | |
| 4626 | 16 => .@"16", | |
| 4627 | 32 => .@"32", | |
| 4628 | 64 => .@"64", | |
| 4629 | else => unreachable, | |
| 4630 | }, | |
| 4631 | .vlmul = .m1, | |
| 4632 | .vma = true, | |
| 4633 | .vta = true, | |
| 4634 | }); | |
| 4635 | } | |
| 4636 | ||
| 4637 | try func.genCopy(ret_ty, func.ret_mcv.short, .{ .air_ref = un_op }); | |
| 4638 | }, | |
| 4514 | 4639 | .indirect => |reg_off| { |
| 4515 | 4640 | try func.register_manager.getReg(reg_off.reg, null); |
| 4516 | 4641 | const lock = func.register_manager.lockRegAssumeUnused(reg_off.reg); |
| ... | ... | @@ -5735,7 +5860,12 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError! |
| 5735 | 5860 | const zcu = pt.zcu; |
| 5736 | 5861 | const abi_size: u32 = @intCast(ty.abiSize(pt)); |
| 5737 | 5862 | |
| 5738 | if (abi_size > 8) return std.debug.panic("tried to set reg with size {}", .{abi_size}); | |
| 5863 | const max_size: u32 = switch (reg.class()) { | |
| 5864 | .int => 64, | |
| 5865 | .float => if (func.hasFeature(.d)) 64 else 32, | |
| 5866 | .vector => func.vec_len, | |
| 5867 | }; | |
| 5868 | if (abi_size > max_size) return std.debug.panic("tried to set reg with size {}", .{abi_size}); | |
| 5739 | 5869 | const dst_reg_class = reg.class(); |
| 5740 | 5870 | |
| 5741 | 5871 | switch (src_mcv) { |
| ... | ... | @@ -5835,13 +5965,6 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError! |
| 5835 | 5965 | if (src_reg.id() == reg.id()) |
| 5836 | 5966 | return; |
| 5837 | 5967 | |
| 5838 | const src_reg_class = src_reg.class(); | |
| 5839 | ||
| 5840 | if (src_reg_class == .float and dst_reg_class == .int) { | |
| 5841 | // to move from float -> int, we use FMV.X.W | |
| 5842 | return func.fail("TODO: genSetReg float -> int", .{}); | |
| 5843 | } | |
| 5844 | ||
| 5845 | 5968 | // mv reg, src_reg |
| 5846 | 5969 | _ = try func.addInst(.{ |
| 5847 | 5970 | .tag = .pseudo, |
| ... | ... | @@ -5854,6 +5977,43 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError! |
| 5854 | 5977 | }, |
| 5855 | 5978 | .register_pair => return func.fail("genSetReg should we allow reg -> reg_pair?", .{}), |
| 5856 | 5979 | .load_frame => |frame| { |
| 5980 | if (reg.class() == .vector) { | |
| 5981 | if (abi_size > 8) | |
| 5982 | return func.fail("TODO: genSetReg vectors > 8", .{}); | |
| 5983 | ||
| 5984 | const temp_reg = try func.register_manager.allocReg(null, abi.Registers.Integer.temporary); | |
| 5985 | const temp_lock = func.register_manager.lockRegAssumeUnused(temp_reg); | |
| 5986 | defer func.register_manager.unlockReg(temp_lock); | |
| 5987 | ||
| 5988 | try func.setVl(.zero, 1, .{ | |
| 5989 | .vsew = switch (abi_size) { | |
| 5990 | 1 => .@"8", | |
| 5991 | 2 => .@"16", | |
| 5992 | 4 => .@"32", | |
| 5993 | 8 => .@"64", | |
| 5994 | else => unreachable, | |
| 5995 | }, | |
| 5996 | .vlmul = .m1, | |
| 5997 | .vma = true, | |
| 5998 | .vta = true, | |
| 5999 | }); | |
| 6000 | ||
| 6001 | try func.genCopy(ty, .{ .register = temp_reg }, .{ .load_frame = frame }); | |
| 6002 | ||
| 6003 | _ = try func.addInst(.{ | |
| 6004 | .tag = .pseudo, | |
| 6005 | .ops = .pseudo_mv, | |
| 6006 | .data = .{ | |
| 6007 | .rr = .{ | |
| 6008 | .rd = reg, | |
| 6009 | .rs = temp_reg, | |
| 6010 | }, | |
| 6011 | }, | |
| 6012 | }); | |
| 6013 | ||
| 6014 | return; | |
| 6015 | } | |
| 6016 | ||
| 5857 | 6017 | _ = try func.addInst(.{ |
| 5858 | 6018 | .tag = .pseudo, |
| 5859 | 6019 | .ops = .pseudo_load_rm, |
| ... | ... | @@ -6195,7 +6355,7 @@ fn airCmpxchg(func: *Func, inst: Air.Inst.Index) !void { |
| 6195 | 6355 | } |
| 6196 | 6356 | |
| 6197 | 6357 | fn airAtomicRmw(func: *Func, inst: Air.Inst.Index) !void { |
| 6198 | const zcu = func.pt.zcu; | |
| 6358 | const pt = func.pt; | |
| 6199 | 6359 | const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; |
| 6200 | 6360 | const extra = func.air.extraData(Air.AtomicRmw, pl_op.payload).data; |
| 6201 | 6361 | |
| ... | ... | @@ -6206,13 +6366,13 @@ fn airAtomicRmw(func: *Func, inst: Air.Inst.Index) !void { |
| 6206 | 6366 | const ptr_mcv = try func.resolveInst(pl_op.operand); |
| 6207 | 6367 | |
| 6208 | 6368 | const val_ty = func.typeOf(extra.operand); |
| 6209 | const val_size = val_ty.abiSize(func.pt); | |
| 6369 | const val_size = val_ty.abiSize(pt); | |
| 6210 | 6370 | const val_mcv = try func.resolveInst(extra.operand); |
| 6211 | 6371 | |
| 6212 | 6372 | if (!math.isPowerOfTwo(val_size)) |
| 6213 | 6373 | return func.fail("TODO: airAtomicRmw non-pow 2", .{}); |
| 6214 | 6374 | |
| 6215 | switch (val_ty.zigTypeTag(zcu)) { | |
| 6375 | switch (val_ty.zigTypeTag(pt.zcu)) { | |
| 6216 | 6376 | .Int => {}, |
| 6217 | 6377 | inline .Bool, .Float, .Enum, .Pointer => |ty| return func.fail("TODO: airAtomicRmw {s}", .{@tagName(ty)}), |
| 6218 | 6378 | else => unreachable, |
| ... | ... | @@ -6735,15 +6895,15 @@ fn getResolvedInstValue(func: *Func, inst: Air.Inst.Index) *InstTracking { |
| 6735 | 6895 | } |
| 6736 | 6896 | |
| 6737 | 6897 | fn genTypedValue(func: *Func, val: Value) InnerError!MCValue { |
| 6738 | const zcu = func.pt.zcu; | |
| 6898 | const pt = func.pt; | |
| 6739 | 6899 | const gpa = func.gpa; |
| 6740 | 6900 | |
| 6741 | const owner_decl_index = zcu.funcOwnerDeclIndex(func.func_index); | |
| 6901 | const owner_decl_index = pt.zcu.funcOwnerDeclIndex(func.func_index); | |
| 6742 | 6902 | const lf = func.bin_file; |
| 6743 | 6903 | const src_loc = func.src_loc; |
| 6744 | 6904 | |
| 6745 | if (val.isUndef(zcu)) { | |
| 6746 | const local_sym_index = lf.lowerUnnamedConst(func.pt, val, owner_decl_index) catch |err| { | |
| 6905 | if (val.isUndef(pt.zcu)) { | |
| 6906 | const local_sym_index = lf.lowerUnnamedConst(pt, val, owner_decl_index) catch |err| { | |
| 6747 | 6907 | const msg = try ErrorMsg.create(gpa, src_loc, "lowering unnamed undefined constant failed: {s}", .{@errorName(err)}); |
| 6748 | 6908 | func.err_msg = msg; |
| 6749 | 6909 | return error.CodegenFail; |
| ... | ... | @@ -6760,7 +6920,7 @@ fn genTypedValue(func: *Func, val: Value) InnerError!MCValue { |
| 6760 | 6920 | |
| 6761 | 6921 | const result = try codegen.genTypedValue( |
| 6762 | 6922 | lf, |
| 6763 | func.pt, | |
| 6923 | pt, | |
| 6764 | 6924 | src_loc, |
| 6765 | 6925 | val, |
| 6766 | 6926 | owner_decl_index, |
src/arch/riscv64/Encoding.zig+133-13| ... | ... | @@ -11,6 +11,7 @@ const OpCode = enum(u7) { |
| 11 | 11 | STORE = 0b0100011, |
| 12 | 12 | STORE_FP = 0b0100111, |
| 13 | 13 | AMO = 0b0101111, |
| 14 | OP_V = 0b1010111, | |
| 14 | 15 | OP = 0b0110011, |
| 15 | 16 | OP_32 = 0b0111011, |
| 16 | 17 | LUI = 0b0110111, |
| ... | ... | @@ -83,9 +84,51 @@ const Enc = struct { |
| 83 | 84 | funct3: u3, |
| 84 | 85 | has_5: bool, |
| 85 | 86 | }, |
| 87 | vecls: struct { | |
| 88 | width: VecWidth, | |
| 89 | umop: Umop, | |
| 90 | vm: bool, | |
| 91 | mop: Mop, | |
| 92 | mew: bool, | |
| 93 | nf: u3, | |
| 94 | }, | |
| 95 | vecmath: struct { | |
| 96 | vm: bool, | |
| 97 | funct6: u6, | |
| 98 | funct3: VecType, | |
| 99 | }, | |
| 86 | 100 | /// U-type |
| 87 | 101 | none, |
| 88 | 102 | }, |
| 103 | ||
| 104 | const Mop = enum(u2) { | |
| 105 | unit = 0b00, | |
| 106 | unord = 0b01, | |
| 107 | stride = 0b10, | |
| 108 | ord = 0b11, | |
| 109 | }; | |
| 110 | ||
| 111 | const Umop = enum(u5) { | |
| 112 | unit = 0b00000, | |
| 113 | whole = 0b01000, | |
| 114 | mask = 0b01011, | |
| 115 | fault = 0b10000, | |
| 116 | }; | |
| 117 | ||
| 118 | const VecWidth = enum(u3) { | |
| 119 | @"32" = 0b110, | |
| 120 | @"64" = 0b111, | |
| 121 | }; | |
| 122 | ||
| 123 | const VecType = enum(u3) { | |
| 124 | OPIVV = 0b000, | |
| 125 | OPFVV = 0b001, | |
| 126 | OPMVV = 0b010, | |
| 127 | OPIVI = 0b011, | |
| 128 | OPIVX = 0b100, | |
| 129 | OPFVF = 0b101, | |
| 130 | OPMVX = 0b110, | |
| 131 | }; | |
| 89 | 132 | }; |
| 90 | 133 | |
| 91 | 134 | pub const Mnemonic = enum { |
| ... | ... | @@ -115,6 +158,9 @@ pub const Mnemonic = enum { |
| 115 | 158 | addi, |
| 116 | 159 | jalr, |
| 117 | 160 | |
| 161 | vsetivli, | |
| 162 | vsetvli, | |
| 163 | ||
| 118 | 164 | // U Type |
| 119 | 165 | lui, |
| 120 | 166 | auipc, |
| ... | ... | @@ -155,6 +201,8 @@ pub const Mnemonic = enum { |
| 155 | 201 | ebreak, |
| 156 | 202 | unimp, |
| 157 | 203 | |
| 204 | csrrs, | |
| 205 | ||
| 158 | 206 | // M extension |
| 159 | 207 | mul, |
| 160 | 208 | mulw, |
| ... | ... | @@ -217,6 +265,17 @@ pub const Mnemonic = enum { |
| 217 | 265 | fsgnjnd, |
| 218 | 266 | fsgnjxd, |
| 219 | 267 | |
| 268 | // V Extension | |
| 269 | vle32v, | |
| 270 | vle64v, | |
| 271 | ||
| 272 | vse32v, | |
| 273 | vse64v, | |
| 274 | ||
| 275 | vaddvv, | |
| 276 | vadcxv, | |
| 277 | vadcvx, | |
| 278 | ||
| 220 | 279 | // MISC |
| 221 | 280 | fence, |
| 222 | 281 | fencetso, |
| ... | ... | @@ -373,6 +432,9 @@ pub const Mnemonic = enum { |
| 373 | 432 | |
| 374 | 433 | .flw => .{ .opcode = .LOAD_FP, .data = .{ .f = .{ .funct3 = 0b010 } } }, |
| 375 | 434 | .fld => .{ .opcode = .LOAD_FP, .data = .{ .f = .{ .funct3 = 0b011 } } }, |
| 435 | ||
| 436 | .vle32v => .{ .opcode = .LOAD_FP, .data = .{ .vecls = .{ .width = .@"32", .umop = .unit, .vm = true, .mop = .unit, .mew = true, .nf = 0b000 } } }, | |
| 437 | .vle64v => .{ .opcode = .LOAD_FP, .data = .{ .vecls = .{ .width = .@"64", .umop = .unit, .vm = true, .mop = .unit, .mew = true, .nf = 0b000 } } }, | |
| 376 | 438 | |
| 377 | 439 | |
| 378 | 440 | // STORE_FP |
| ... | ... | @@ -380,6 +442,8 @@ pub const Mnemonic = enum { |
| 380 | 442 | .fsw => .{ .opcode = .STORE_FP, .data = .{ .f = .{ .funct3 = 0b010 } } }, |
| 381 | 443 | .fsd => .{ .opcode = .STORE_FP, .data = .{ .f = .{ .funct3 = 0b011 } } }, |
| 382 | 444 | |
| 445 | .vse32v => .{ .opcode = .STORE_FP, .data = .{ .vecls = .{ .width = .@"32", .umop = .unit, .vm = true, .mop = .unit, .mew = true, .nf = 0b000 } } }, | |
| 446 | .vse64v => .{ .opcode = .STORE_FP, .data = .{ .vecls = .{ .width = .@"64", .umop = .unit, .vm = true, .mop = .unit, .mew = true, .nf = 0b000 } } }, | |
| 383 | 447 | |
| 384 | 448 | // JALR |
| 385 | 449 | |
| ... | ... | @@ -410,6 +474,8 @@ pub const Mnemonic = enum { |
| 410 | 474 | |
| 411 | 475 | .ecall => .{ .opcode = .SYSTEM, .data = .{ .f = .{ .funct3 = 0b000 } } }, |
| 412 | 476 | .ebreak => .{ .opcode = .SYSTEM, .data = .{ .f = .{ .funct3 = 0b000 } } }, |
| 477 | ||
| 478 | .csrrs => .{ .opcode = .SYSTEM, .data = .{ .f = .{ .funct3 = 0b010 } } }, | |
| 413 | 479 | |
| 414 | 480 | |
| 415 | 481 | // NONE |
| ... | ... | @@ -449,7 +515,13 @@ pub const Mnemonic = enum { |
| 449 | 515 | .amominud => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b11000 } } }, |
| 450 | 516 | .amomaxud => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b11100 } } }, |
| 451 | 517 | |
| 452 | ||
| 518 | // OP_V | |
| 519 | .vsetivli => .{ .opcode = .OP_V, .data = .{ .f = .{ .funct3 = 0b111 } } }, | |
| 520 | .vsetvli => .{ .opcode = .OP_V, .data = .{ .f = .{ .funct3 = 0b111 } } }, | |
| 521 | .vaddvv => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b000000, .funct3 = .OPIVV } } }, | |
| 522 | .vadcxv => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b010000, .funct3 = .OPMVX } } }, | |
| 523 | .vadcvx => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b010000, .funct3 = .OPMVV } } }, | |
| 524 | ||
| 453 | 525 | // zig fmt: on |
| 454 | 526 | }; |
| 455 | 527 | } |
| ... | ... | @@ -465,7 +537,6 @@ pub const InstEnc = enum { |
| 465 | 537 | J, |
| 466 | 538 | fence, |
| 467 | 539 | amo, |
| 468 | /// extras that have unusual op counts | |
| 469 | 540 | system, |
| 470 | 541 | |
| 471 | 542 | pub fn fromMnemonic(mnem: Mnemonic) InstEnc { |
| ... | ... | @@ -494,6 +565,10 @@ pub const InstEnc = enum { |
| 494 | 565 | |
| 495 | 566 | .flw, |
| 496 | 567 | .fld, |
| 568 | ||
| 569 | .csrrs, | |
| 570 | .vsetivli, | |
| 571 | .vsetvli, | |
| 497 | 572 | => .I, |
| 498 | 573 | |
| 499 | 574 | .lui, |
| ... | ... | @@ -587,6 +662,14 @@ pub const InstEnc = enum { |
| 587 | 662 | |
| 588 | 663 | .fsgnjxs, |
| 589 | 664 | .fsgnjxd, |
| 665 | ||
| 666 | .vle32v, | |
| 667 | .vle64v, | |
| 668 | .vse32v, | |
| 669 | .vse64v, | |
| 670 | .vaddvv, | |
| 671 | .vadcxv, | |
| 672 | .vadcvx, | |
| 590 | 673 | => .R, |
| 591 | 674 | |
| 592 | 675 | .ecall, |
| ... | ... | @@ -757,6 +840,25 @@ pub const Data = union(InstEnc) { |
| 757 | 840 | }, |
| 758 | 841 | }; |
| 759 | 842 | }, |
| 843 | .csrrs => { | |
| 844 | assert(ops.len == 3); | |
| 845 | ||
| 846 | const csr = ops[0].csr; | |
| 847 | const rs1 = ops[1].reg; | |
| 848 | const rd = ops[2].reg; | |
| 849 | ||
| 850 | return .{ | |
| 851 | .I = .{ | |
| 852 | .rd = rd.encodeId(), | |
| 853 | .rs1 = rs1.encodeId(), | |
| 854 | ||
| 855 | .imm0_11 = @intFromEnum(csr), | |
| 856 | ||
| 857 | .opcode = @intFromEnum(enc.opcode), | |
| 858 | .funct3 = enc.data.f.funct3, | |
| 859 | }, | |
| 860 | }; | |
| 861 | }, | |
| 760 | 862 | else => {}, |
| 761 | 863 | } |
| 762 | 864 | |
| ... | ... | @@ -783,6 +885,25 @@ pub const Data = union(InstEnc) { |
| 783 | 885 | .funct3 = fmt.rm, |
| 784 | 886 | .funct7 = (@as(u7, fmt.funct5) << 2) | @intFromEnum(fmt.fmt), |
| 785 | 887 | }, |
| 888 | .vecls => |vec| .{ | |
| 889 | .rd = ops[0].reg.encodeId(), | |
| 890 | .rs1 = ops[1].reg.encodeId(), | |
| 891 | ||
| 892 | .rs2 = @intFromEnum(vec.umop), | |
| 893 | ||
| 894 | .opcode = @intFromEnum(enc.opcode), | |
| 895 | .funct3 = @intFromEnum(vec.width), | |
| 896 | .funct7 = (@as(u7, vec.nf) << 4) | (@as(u7, @intFromBool(vec.mew)) << 3) | (@as(u7, @intFromEnum(vec.mop)) << 1) | @intFromBool(vec.vm), | |
| 897 | }, | |
| 898 | .vecmath => |vec| .{ | |
| 899 | .rd = ops[0].reg.encodeId(), | |
| 900 | .rs1 = ops[1].reg.encodeId(), | |
| 901 | .rs2 = ops[2].reg.encodeId(), | |
| 902 | ||
| 903 | .opcode = @intFromEnum(enc.opcode), | |
| 904 | .funct3 = @intFromEnum(vec.funct3), | |
| 905 | .funct7 = (@as(u7, vec.funct6) << 1) | @intFromBool(vec.vm), | |
| 906 | }, | |
| 786 | 907 | else => unreachable, |
| 787 | 908 | }, |
| 788 | 909 | }; |
| ... | ... | @@ -897,21 +1018,21 @@ pub const Data = union(InstEnc) { |
| 897 | 1018 | .amo => { |
| 898 | 1019 | assert(ops.len == 5); |
| 899 | 1020 | |
| 900 | const rd = ops[0]; | |
| 901 | const rs1 = ops[1]; | |
| 902 | const rs2 = ops[2]; | |
| 903 | const rl = ops[3]; | |
| 904 | const aq = ops[4]; | |
| 1021 | const rd = ops[0].reg; | |
| 1022 | const rs1 = ops[1].reg; | |
| 1023 | const rs2 = ops[2].reg; | |
| 1024 | const rl = ops[3].barrier; | |
| 1025 | const aq = ops[4].barrier; | |
| 905 | 1026 | |
| 906 | 1027 | return .{ |
| 907 | 1028 | .amo = .{ |
| 908 | .rd = rd.reg.encodeId(), | |
| 909 | .rs1 = rs1.reg.encodeId(), | |
| 910 | .rs2 = rs2.reg.encodeId(), | |
| 1029 | .rd = rd.encodeId(), | |
| 1030 | .rs1 = rs1.encodeId(), | |
| 1031 | .rs2 = rs2.encodeId(), | |
| 911 | 1032 | |
| 912 | 1033 | // TODO: https://github.com/ziglang/zig/issues/20113 |
| 913 | .rl = if (rl.barrier == .rl) true else false, | |
| 914 | .aq = if (aq.barrier == .aq) true else false, | |
| 1034 | .rl = if (rl == .rl) true else false, | |
| 1035 | .aq = if (aq == .aq) true else false, | |
| 915 | 1036 | |
| 916 | 1037 | .opcode = @intFromEnum(enc.opcode), |
| 917 | 1038 | .funct3 = @intFromEnum(enc.data.amo.width), |
| ... | ... | @@ -919,7 +1040,6 @@ pub const Data = union(InstEnc) { |
| 919 | 1040 | }, |
| 920 | 1041 | }; |
| 921 | 1042 | }, |
| 922 | ||
| 923 | 1043 | else => std.debug.panic("TODO: construct {s}", .{@tagName(inst_enc)}), |
| 924 | 1044 | } |
| 925 | 1045 | } |
src/arch/riscv64/Lower.zig+124-56| ... | ... | @@ -80,58 +80,99 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index, options: struct { |
| 80 | 80 | .pseudo_load_rm => { |
| 81 | 81 | const dest_reg = rm.r; |
| 82 | 82 | const dest_reg_class = dest_reg.class(); |
| 83 | const float = dest_reg_class == .float; | |
| 84 | 83 | |
| 85 | 84 | const src_size = rm.m.mod.size; |
| 86 | 85 | const unsigned = rm.m.mod.unsigned; |
| 87 | 86 | |
| 88 | const tag: Encoding.Mnemonic = if (!float) | |
| 89 | switch (src_size) { | |
| 87 | const tag: Encoding.Mnemonic = switch (dest_reg_class) { | |
| 88 | .int => switch (src_size) { | |
| 90 | 89 | .byte => if (unsigned) .lbu else .lb, |
| 91 | 90 | .hword => if (unsigned) .lhu else .lh, |
| 92 | 91 | .word => if (unsigned) .lwu else .lw, |
| 93 | 92 | .dword => .ld, |
| 94 | } | |
| 95 | else switch (src_size) { | |
| 96 | .byte => unreachable, // Zig does not support 8-bit floats | |
| 97 | .hword => return lower.fail("TODO: lowerMir pseudo_load_rm support 16-bit floats", .{}), | |
| 98 | .word => .flw, | |
| 99 | .dword => .fld, | |
| 93 | }, | |
| 94 | .float => switch (src_size) { | |
| 95 | .byte => unreachable, // Zig does not support 8-bit floats | |
| 96 | .hword => return lower.fail("TODO: lowerMir pseudo_load_rm support 16-bit floats", .{}), | |
| 97 | .word => .flw, | |
| 98 | .dword => .fld, | |
| 99 | }, | |
| 100 | .vector => switch (src_size) { | |
| 101 | .byte, | |
| 102 | .hword, | |
| 103 | => return lower.fail( | |
| 104 | "TODO: lowerMir pseudo_load_rm support {s} vector", | |
| 105 | .{@tagName(src_size)}, | |
| 106 | ), | |
| 107 | .word => .vle32v, | |
| 108 | .dword => .vle64v, | |
| 109 | }, | |
| 100 | 110 | }; |
| 101 | 111 | |
| 102 | try lower.emit(tag, &.{ | |
| 103 | .{ .reg = rm.r }, | |
| 104 | .{ .reg = frame_loc.base }, | |
| 105 | .{ .imm = Immediate.s(frame_loc.disp) }, | |
| 106 | }); | |
| 112 | switch (dest_reg_class) { | |
| 113 | .int, .float => { | |
| 114 | try lower.emit(tag, &.{ | |
| 115 | .{ .reg = rm.r }, | |
| 116 | .{ .reg = frame_loc.base }, | |
| 117 | .{ .imm = Immediate.s(frame_loc.disp) }, | |
| 118 | }); | |
| 119 | }, | |
| 120 | .vector => { | |
| 121 | try lower.emit(tag, &.{ | |
| 122 | .{ .reg = rm.r }, | |
| 123 | .{ .reg = frame_loc.base }, | |
| 124 | .{ .reg = .x0 }, | |
| 125 | }); | |
| 126 | }, | |
| 127 | } | |
| 107 | 128 | }, |
| 108 | 129 | .pseudo_store_rm => { |
| 109 | 130 | const src_reg = rm.r; |
| 110 | 131 | const src_reg_class = src_reg.class(); |
| 111 | const float = src_reg_class == .float; | |
| 112 | 132 | |
| 113 | // TODO: do we actually need this? are all stores not usize? | |
| 114 | 133 | const dest_size = rm.m.mod.size; |
| 115 | 134 | |
| 116 | const tag: Encoding.Mnemonic = if (!float) | |
| 117 | switch (dest_size) { | |
| 135 | const tag: Encoding.Mnemonic = switch (src_reg_class) { | |
| 136 | .int => switch (dest_size) { | |
| 118 | 137 | .byte => .sb, |
| 119 | 138 | .hword => .sh, |
| 120 | 139 | .word => .sw, |
| 121 | 140 | .dword => .sd, |
| 122 | } | |
| 123 | else switch (dest_size) { | |
| 124 | .byte => unreachable, // Zig does not support 8-bit floats | |
| 125 | .hword => return lower.fail("TODO: lowerMir pseudo_load_rm support 16-bit floats", .{}), | |
| 126 | .word => .fsw, | |
| 127 | .dword => .fsd, | |
| 141 | }, | |
| 142 | .float => switch (dest_size) { | |
| 143 | .byte => unreachable, // Zig does not support 8-bit floats | |
| 144 | .hword => return lower.fail("TODO: lowerMir pseudo_store_rm support 16-bit floats", .{}), | |
| 145 | .word => .fsw, | |
| 146 | .dword => .fsd, | |
| 147 | }, | |
| 148 | .vector => switch (dest_size) { | |
| 149 | .byte, | |
| 150 | .hword, | |
| 151 | => return lower.fail( | |
| 152 | "TODO: lowerMir pseudo_load_rm support {s} vector", | |
| 153 | .{@tagName(dest_size)}, | |
| 154 | ), | |
| 155 | .word => .vse32v, | |
| 156 | .dword => .vse64v, | |
| 157 | }, | |
| 128 | 158 | }; |
| 129 | 159 | |
| 130 | try lower.emit(tag, &.{ | |
| 131 | .{ .reg = frame_loc.base }, | |
| 132 | .{ .reg = rm.r }, | |
| 133 | .{ .imm = Immediate.s(frame_loc.disp) }, | |
| 134 | }); | |
| 160 | switch (src_reg_class) { | |
| 161 | .int, .float => { | |
| 162 | try lower.emit(tag, &.{ | |
| 163 | .{ .reg = frame_loc.base }, | |
| 164 | .{ .reg = rm.r }, | |
| 165 | .{ .imm = Immediate.s(frame_loc.disp) }, | |
| 166 | }); | |
| 167 | }, | |
| 168 | .vector => { | |
| 169 | try lower.emit(tag, &.{ | |
| 170 | .{ .reg = frame_loc.base }, | |
| 171 | .{ .reg = rm.r }, | |
| 172 | .{ .reg = .x0 }, | |
| 173 | }); | |
| 174 | }, | |
| 175 | } | |
| 135 | 176 | }, |
| 136 | 177 | else => unreachable, |
| 137 | 178 | } |
| ... | ... | @@ -143,34 +184,47 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index, options: struct { |
| 143 | 184 | const dst_class = rr.rd.class(); |
| 144 | 185 | const src_class = rr.rs.class(); |
| 145 | 186 | |
| 146 | assert(dst_class == src_class); | |
| 147 | ||
| 148 | switch (dst_class) { | |
| 149 | .float => { | |
| 150 | try lower.emit(if (lower.hasFeature(.d)) .fsgnjnd else .fsgnjns, &.{ | |
| 151 | .{ .reg = rr.rd }, | |
| 152 | .{ .reg = rr.rs }, | |
| 153 | .{ .reg = rr.rs }, | |
| 154 | }); | |
| 187 | switch (src_class) { | |
| 188 | .float => switch (dst_class) { | |
| 189 | .float => { | |
| 190 | try lower.emit(if (lower.hasFeature(.d)) .fsgnjnd else .fsgnjns, &.{ | |
| 191 | .{ .reg = rr.rd }, | |
| 192 | .{ .reg = rr.rs }, | |
| 193 | .{ .reg = rr.rs }, | |
| 194 | }); | |
| 195 | }, | |
| 196 | .int, .vector => return lower.fail("TODO: lowerMir pseudo_mv float -> {s}", .{@tagName(dst_class)}), | |
| 197 | }, | |
| 198 | .int => switch (dst_class) { | |
| 199 | .int => { | |
| 200 | try lower.emit(.addi, &.{ | |
| 201 | .{ .reg = rr.rd }, | |
| 202 | .{ .reg = rr.rs }, | |
| 203 | .{ .imm = Immediate.s(0) }, | |
| 204 | }); | |
| 205 | }, | |
| 206 | .vector => { | |
| 207 | try lower.emit(.vadcxv, &.{ | |
| 208 | .{ .reg = rr.rd }, | |
| 209 | .{ .reg = rr.rs }, | |
| 210 | .{ .reg = .zero }, | |
| 211 | }); | |
| 212 | }, | |
| 213 | .float => return lower.fail("TODO: lowerMir pseudo_mv int -> {s}", .{@tagName(dst_class)}), | |
| 155 | 214 | }, |
| 156 | .int => { | |
| 157 | try lower.emit(.addi, &.{ | |
| 158 | .{ .reg = rr.rd }, | |
| 159 | .{ .reg = rr.rs }, | |
| 160 | .{ .imm = Immediate.s(0) }, | |
| 161 | }); | |
| 215 | .vector => switch (dst_class) { | |
| 216 | .int => { | |
| 217 | try lower.emit(.vadcvx, &.{ | |
| 218 | .{ .reg = rr.rd }, | |
| 219 | .{ .reg = rr.rs }, | |
| 220 | .{ .reg = .zero }, | |
| 221 | }); | |
| 222 | }, | |
| 223 | .float, .vector => return lower.fail("TODO: lowerMir pseudo_mv vector -> {s}", .{@tagName(dst_class)}), | |
| 162 | 224 | }, |
| 163 | 225 | } |
| 164 | 226 | }, |
| 165 | 227 | |
| 166 | .pseudo_ret => { | |
| 167 | try lower.emit(.jalr, &.{ | |
| 168 | .{ .reg = .zero }, | |
| 169 | .{ .reg = .ra }, | |
| 170 | .{ .imm = Immediate.s(0) }, | |
| 171 | }); | |
| 172 | }, | |
| 173 | ||
| 174 | 228 | .pseudo_j => { |
| 175 | 229 | try lower.emit(.jal, &.{ |
| 176 | 230 | .{ .reg = .zero }, |
| ... | ... | @@ -209,7 +263,10 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index, options: struct { |
| 209 | 263 | const rm = inst.data.rm; |
| 210 | 264 | assert(rm.r.class() == .int); |
| 211 | 265 | |
| 212 | const frame = rm.m.toFrameLoc(lower.mir); | |
| 266 | const frame: Mir.FrameLoc = if (options.allow_frame_locs) | |
| 267 | rm.m.toFrameLoc(lower.mir) | |
| 268 | else | |
| 269 | .{ .base = .s0, .disp = 0 }; | |
| 213 | 270 | |
| 214 | 271 | try lower.emit(.addi, &.{ |
| 215 | 272 | .{ .reg = rm.r }, |
| ... | ... | @@ -376,6 +433,7 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index, options: struct { |
| 376 | 433 | }); |
| 377 | 434 | }, |
| 378 | 435 | }, |
| 436 | .vector => return lower.fail("TODO: lowerMir pseudo_cmp vector", .{}), | |
| 379 | 437 | } |
| 380 | 438 | }, |
| 381 | 439 | |
| ... | ... | @@ -497,6 +555,11 @@ fn generic(lower: *Lower, inst: Mir.Inst) Error!void { |
| 497 | 555 | .{ .reg = inst.data.r_type.rs1 }, |
| 498 | 556 | .{ .reg = inst.data.r_type.rs2 }, |
| 499 | 557 | }, |
| 558 | .csr => &.{ | |
| 559 | .{ .csr = inst.data.csr.csr }, | |
| 560 | .{ .reg = inst.data.csr.rs1 }, | |
| 561 | .{ .reg = inst.data.csr.rd }, | |
| 562 | }, | |
| 500 | 563 | else => return lower.fail("TODO: generic lower ops {s}", .{@tagName(inst.ops)}), |
| 501 | 564 | }); |
| 502 | 565 | } |
| ... | ... | @@ -523,17 +586,22 @@ fn pushPopRegList(lower: *Lower, comptime spilling: bool, reg_list: Mir.Register |
| 523 | 586 | while (it.next()) |i| { |
| 524 | 587 | const frame = lower.mir.frame_locs.get(@intFromEnum(bits.FrameIndex.spill_frame)); |
| 525 | 588 | const reg = abi.Registers.all_preserved[i]; |
| 589 | ||
| 526 | 590 | const reg_class = reg.class(); |
| 527 | const is_float_reg = reg_class == .float; | |
| 591 | const load_inst: Encoding.Mnemonic, const store_inst: Encoding.Mnemonic = switch (reg_class) { | |
| 592 | .int => .{ .ld, .sd }, | |
| 593 | .float => .{ .fld, .fsd }, | |
| 594 | .vector => unreachable, | |
| 595 | }; | |
| 528 | 596 | |
| 529 | 597 | if (spilling) { |
| 530 | try lower.emit(if (is_float_reg) .fsd else .sd, &.{ | |
| 598 | try lower.emit(store_inst, &.{ | |
| 531 | 599 | .{ .reg = frame.base }, |
| 532 | 600 | .{ .reg = abi.Registers.all_preserved[i] }, |
| 533 | 601 | .{ .imm = Immediate.s(frame.disp + reg_i) }, |
| 534 | 602 | }); |
| 535 | 603 | } else { |
| 536 | try lower.emit(if (is_float_reg) .fld else .ld, &.{ | |
| 604 | try lower.emit(load_inst, &.{ | |
| 537 | 605 | .{ .reg = abi.Registers.all_preserved[i] }, |
| 538 | 606 | .{ .reg = frame.base }, |
| 539 | 607 | .{ .imm = Immediate.s(frame.disp + reg_i) }, |
src/arch/riscv64/Mir.zig+19-51| ... | ... | @@ -134,7 +134,16 @@ pub const Inst = struct { |
| 134 | 134 | fltd, |
| 135 | 135 | fled, |
| 136 | 136 | |
| 137 | /// A Extension Instructions | |
| 137 | // Zicsr Extension Instructions | |
| 138 | csrrs, | |
| 139 | ||
| 140 | // V Extension Instructions | |
| 141 | vsetvli, | |
| 142 | vsetivli, | |
| 143 | vsetvl, | |
| 144 | vaddvv, | |
| 145 | ||
| 146 | // A Extension Instructions | |
| 138 | 147 | amo, |
| 139 | 148 | |
| 140 | 149 | /// A pseudo-instruction. Used for anything that isn't 1:1 with an |
| ... | ... | @@ -146,91 +155,57 @@ pub const Inst = struct { |
| 146 | 155 | /// this union. `Ops` determines which union field is active, as well as |
| 147 | 156 | /// how to interpret the data within. |
| 148 | 157 | pub const Data = union { |
| 149 | /// No additional data | |
| 150 | /// | |
| 151 | /// Used by e.g. ebreak | |
| 152 | 158 | nop: void, |
| 153 | /// Another instruction. | |
| 154 | /// | |
| 155 | /// Used by e.g. b | |
| 156 | 159 | inst: Index, |
| 157 | /// Index into `extra`. Meaning of what can be found there is context-dependent. | |
| 158 | /// | |
| 159 | /// Used by e.g. load_memory | |
| 160 | 160 | payload: u32, |
| 161 | ||
| 162 | 161 | r_type: struct { |
| 163 | 162 | rd: Register, |
| 164 | 163 | rs1: Register, |
| 165 | 164 | rs2: Register, |
| 166 | 165 | }, |
| 167 | ||
| 168 | 166 | i_type: struct { |
| 169 | 167 | rd: Register, |
| 170 | 168 | rs1: Register, |
| 171 | 169 | imm12: Immediate, |
| 172 | 170 | }, |
| 173 | ||
| 174 | 171 | s_type: struct { |
| 175 | 172 | rs1: Register, |
| 176 | 173 | rs2: Register, |
| 177 | 174 | imm5: Immediate, |
| 178 | 175 | imm7: Immediate, |
| 179 | 176 | }, |
| 180 | ||
| 181 | 177 | b_type: struct { |
| 182 | 178 | rs1: Register, |
| 183 | 179 | rs2: Register, |
| 184 | 180 | inst: Inst.Index, |
| 185 | 181 | }, |
| 186 | ||
| 187 | 182 | u_type: struct { |
| 188 | 183 | rd: Register, |
| 189 | 184 | imm20: Immediate, |
| 190 | 185 | }, |
| 191 | ||
| 192 | 186 | j_type: struct { |
| 193 | 187 | rd: Register, |
| 194 | 188 | inst: Inst.Index, |
| 195 | 189 | }, |
| 196 | ||
| 197 | /// Debug info: line and column | |
| 198 | /// | |
| 199 | /// Used by e.g. pseudo_dbg_line | |
| 200 | 190 | pseudo_dbg_line_column: struct { |
| 201 | 191 | line: u32, |
| 202 | 192 | column: u32, |
| 203 | 193 | }, |
| 204 | ||
| 205 | // Custom types to be lowered | |
| 206 | ||
| 207 | /// Register + Memory | |
| 208 | 194 | rm: struct { |
| 209 | 195 | r: Register, |
| 210 | 196 | m: Memory, |
| 211 | 197 | }, |
| 212 | ||
| 213 | 198 | reg_list: Mir.RegisterList, |
| 214 | ||
| 215 | /// A register | |
| 216 | /// | |
| 217 | /// Used by e.g. blr | |
| 218 | 199 | reg: Register, |
| 219 | ||
| 220 | /// Two registers | |
| 221 | /// | |
| 222 | /// Used by e.g. mv | |
| 223 | 200 | rr: struct { |
| 224 | 201 | rd: Register, |
| 225 | 202 | rs: Register, |
| 226 | 203 | }, |
| 227 | ||
| 228 | 204 | fabs: struct { |
| 229 | 205 | rd: Register, |
| 230 | 206 | rs: Register, |
| 231 | 207 | bits: u16, |
| 232 | 208 | }, |
| 233 | ||
| 234 | 209 | compare: struct { |
| 235 | 210 | rd: Register, |
| 236 | 211 | rs1: Register, |
| ... | ... | @@ -245,12 +220,10 @@ pub const Inst = struct { |
| 245 | 220 | }, |
| 246 | 221 | ty: Type, |
| 247 | 222 | }, |
| 248 | ||
| 249 | 223 | reloc: struct { |
| 250 | 224 | atom_index: u32, |
| 251 | 225 | sym_index: u32, |
| 252 | 226 | }, |
| 253 | ||
| 254 | 227 | fence: struct { |
| 255 | 228 | pred: Barrier, |
| 256 | 229 | succ: Barrier, |
| ... | ... | @@ -259,7 +232,6 @@ pub const Inst = struct { |
| 259 | 232 | tso, |
| 260 | 233 | }, |
| 261 | 234 | }, |
| 262 | ||
| 263 | 235 | amo: struct { |
| 264 | 236 | rd: Register, |
| 265 | 237 | rs1: Register, |
| ... | ... | @@ -269,6 +241,11 @@ pub const Inst = struct { |
| 269 | 241 | op: AmoOp, |
| 270 | 242 | ty: Type, |
| 271 | 243 | }, |
| 244 | csr: struct { | |
| 245 | csr: CSR, | |
| 246 | rs1: Register, | |
| 247 | rd: Register, | |
| 248 | }, | |
| 272 | 249 | }; |
| 273 | 250 | |
| 274 | 251 | pub const Ops = enum { |
| ... | ... | @@ -293,6 +270,9 @@ pub const Inst = struct { |
| 293 | 270 | /// Another instruction. |
| 294 | 271 | inst, |
| 295 | 272 | |
| 273 | /// Control and Status Register Instruction. | |
| 274 | csr, | |
| 275 | ||
| 296 | 276 | /// Pseudo-instruction that will generate a backpatched |
| 297 | 277 | /// function prologue. |
| 298 | 278 | pseudo_prologue, |
| ... | ... | @@ -321,11 +301,6 @@ pub const Inst = struct { |
| 321 | 301 | /// Uses `rm` payload. |
| 322 | 302 | pseudo_lea_rm, |
| 323 | 303 | |
| 324 | /// Shorthand for returning, aka jumping to ra register. | |
| 325 | /// | |
| 326 | /// Uses nop payload. | |
| 327 | pseudo_ret, | |
| 328 | ||
| 329 | 304 | /// Jumps. Uses `inst` payload. |
| 330 | 305 | pseudo_j, |
| 331 | 306 | |
| ... | ... | @@ -363,14 +338,6 @@ pub const Inst = struct { |
| 363 | 338 | pseudo_amo, |
| 364 | 339 | }; |
| 365 | 340 | |
| 366 | // Make sure we don't accidentally make instructions bigger than expected. | |
| 367 | // Note that in Debug builds, Zig is allowed to insert a secret field for safety checks. | |
| 368 | // comptime { | |
| 369 | // if (builtin.mode != .Debug) { | |
| 370 | // assert(@sizeOf(Inst) == 8); | |
| 371 | // } | |
| 372 | // } | |
| 373 | ||
| 374 | 341 | pub fn format( |
| 375 | 342 | inst: Inst, |
| 376 | 343 | comptime fmt: []const u8, |
| ... | ... | @@ -490,6 +457,7 @@ const assert = std.debug.assert; |
| 490 | 457 | |
| 491 | 458 | const bits = @import("bits.zig"); |
| 492 | 459 | const Register = bits.Register; |
| 460 | const CSR = bits.CSR; | |
| 493 | 461 | const Immediate = bits.Immediate; |
| 494 | 462 | const Memory = bits.Memory; |
| 495 | 463 | const FrameIndex = bits.FrameIndex; |
src/arch/riscv64/abi.zig+24-2| ... | ... | @@ -193,6 +193,15 @@ pub fn classifySystem(ty: Type, pt: Zcu.PerThread) [8]SystemClass { |
| 193 | 193 | } |
| 194 | 194 | return memory_class; |
| 195 | 195 | }, |
| 196 | .Vector => { | |
| 197 | // we pass vectors through integer registers if they are small enough to fit. | |
| 198 | const vec_bits = ty.totalVectorBits(pt); | |
| 199 | if (vec_bits <= 64) { | |
| 200 | result[0] = .integer; | |
| 201 | return result; | |
| 202 | } | |
| 203 | return memory_class; | |
| 204 | }, | |
| 196 | 205 | else => |bad_ty| std.debug.panic("classifySystem {s}", .{@tagName(bad_ty)}), |
| 197 | 206 | } |
| 198 | 207 | } |
| ... | ... | @@ -254,15 +263,15 @@ fn classifyStruct( |
| 254 | 263 | } |
| 255 | 264 | } |
| 256 | 265 | |
| 257 | const allocatable_registers = Registers.Integer.all_regs ++ Registers.Float.all_regs; | |
| 266 | const allocatable_registers = Registers.Integer.all_regs ++ Registers.Float.all_regs ++ Registers.Vector.all_regs; | |
| 258 | 267 | pub const RegisterManager = RegisterManagerFn(@import("CodeGen.zig"), Register, &allocatable_registers); |
| 259 | 268 | |
| 260 | // Register classes | |
| 261 | 269 | const RegisterBitSet = RegisterManager.RegisterBitSet; |
| 262 | 270 | |
| 263 | 271 | pub const RegisterClass = enum { |
| 264 | 272 | int, |
| 265 | 273 | float, |
| 274 | vector, | |
| 266 | 275 | }; |
| 267 | 276 | |
| 268 | 277 | pub const Registers = struct { |
| ... | ... | @@ -322,6 +331,19 @@ pub const Registers = struct { |
| 322 | 331 | |
| 323 | 332 | pub const all_regs = callee_preserved_regs ++ function_arg_regs ++ temporary_regs; |
| 324 | 333 | }; |
| 334 | ||
| 335 | pub const Vector = struct { | |
| 336 | pub const general_purpose = initRegBitSet(Integer.all_regs.len + Float.all_regs.len, all_regs.len); | |
| 337 | ||
| 338 | // zig fmt: off | |
| 339 | pub const all_regs = [_]Register{ | |
| 340 | .v0, .v1, .v2, .v3, .v4, .v5, .v6, .v7, | |
| 341 | .v8, .v9, .v10, .v11, .v12, .v13, .v14, .v15, | |
| 342 | .v16, .v17, .v18, .v19, .v20, .v21, .v22, .v23, | |
| 343 | .v24, .v25, .v26, .v27, .v28, .v29, .v30, .v31, | |
| 344 | }; | |
| 345 | // zig fmt: on | |
| 346 | }; | |
| 325 | 347 | }; |
| 326 | 348 | |
| 327 | 349 | fn initRegBitSet(start: usize, length: usize) RegisterBitSet { |
src/arch/riscv64/bits.zig+41-1| ... | ... | @@ -128,6 +128,12 @@ pub const Immediate = union(enum) { |
| 128 | 128 | } |
| 129 | 129 | }; |
| 130 | 130 | |
| 131 | pub const CSR = enum(u12) { | |
| 132 | vl = 0xC20, | |
| 133 | vtype = 0xC21, | |
| 134 | vlenb = 0xC22, | |
| 135 | }; | |
| 136 | ||
| 131 | 137 | pub const Register = enum(u8) { |
| 132 | 138 | // zig fmt: off |
| 133 | 139 | |
| ... | ... | @@ -169,6 +175,13 @@ pub const Register = enum(u8) { |
| 169 | 175 | f16, f17, f18, f19, f20, f21, f22, f23, |
| 170 | 176 | f24, f25, f26, f27, f28, f29, f30, f31, |
| 171 | 177 | |
| 178 | ||
| 179 | // V extension registers | |
| 180 | v0, v1, v2, v3, v4, v5, v6, v7, | |
| 181 | v8, v9, v10, v11, v12, v13, v14, v15, | |
| 182 | v16, v17, v18, v19, v20, v21, v22, v23, | |
| 183 | v24, v25, v26, v27, v28, v29, v30, v31, | |
| 184 | ||
| 172 | 185 | // zig fmt: on |
| 173 | 186 | |
| 174 | 187 | /// in RISC-V registers are stored as 5 bit IDs and a register can have |
| ... | ... | @@ -180,11 +193,12 @@ pub const Register = enum(u8) { |
| 180 | 193 | /// The goal of this function is to return the same ID for `zero` and `x0` but two |
| 181 | 194 | /// seperate IDs for `x0` and `f0`. We will assume that each register set has 32 registers |
| 182 | 195 | /// and is repeated twice, once for the named version, once for the number version. |
| 183 | pub fn id(reg: Register) u7 { | |
| 196 | pub fn id(reg: Register) u8 { | |
| 184 | 197 | const base = switch (@intFromEnum(reg)) { |
| 185 | 198 | // zig fmt: off |
| 186 | 199 | @intFromEnum(Register.zero) ... @intFromEnum(Register.x31) => @intFromEnum(Register.zero), |
| 187 | 200 | @intFromEnum(Register.ft0) ... @intFromEnum(Register.f31) => @intFromEnum(Register.ft0), |
| 201 | @intFromEnum(Register.v0) ... @intFromEnum(Register.v31) => @intFromEnum(Register.v0), | |
| 188 | 202 | else => unreachable, |
| 189 | 203 | // zig fmt: on |
| 190 | 204 | }; |
| ... | ... | @@ -207,6 +221,7 @@ pub const Register = enum(u8) { |
| 207 | 221 | // zig fmt: off |
| 208 | 222 | @intFromEnum(Register.zero) ... @intFromEnum(Register.x31) => 64, |
| 209 | 223 | @intFromEnum(Register.ft0) ... @intFromEnum(Register.f31) => if (Target.riscv.featureSetHas(features, .d)) 64 else 32, |
| 224 | @intFromEnum(Register.v0) ... @intFromEnum(Register.v31) => 1024, // TODO: look at suggestVectorSize | |
| 210 | 225 | else => unreachable, |
| 211 | 226 | // zig fmt: on |
| 212 | 227 | }; |
| ... | ... | @@ -217,6 +232,7 @@ pub const Register = enum(u8) { |
| 217 | 232 | // zig fmt: off |
| 218 | 233 | @intFromEnum(Register.zero) ... @intFromEnum(Register.x31) => .int, |
| 219 | 234 | @intFromEnum(Register.ft0) ... @intFromEnum(Register.f31) => .float, |
| 235 | @intFromEnum(Register.v0) ... @intFromEnum(Register.v31) => .vector, | |
| 220 | 236 | else => unreachable, |
| 221 | 237 | // zig fmt: on |
| 222 | 238 | }; |
| ... | ... | @@ -272,3 +288,27 @@ pub const Symbol = struct { |
| 272 | 288 | /// Index into the linker's symbol table. |
| 273 | 289 | sym_index: u32, |
| 274 | 290 | }; |
| 291 | ||
| 292 | pub const VType = packed struct(u8) { | |
| 293 | vlmul: VlMul, | |
| 294 | vsew: VSew, | |
| 295 | vta: bool, | |
| 296 | vma: bool, | |
| 297 | }; | |
| 298 | ||
| 299 | const VSew = enum(u3) { | |
| 300 | @"8" = 0b000, | |
| 301 | @"16" = 0b001, | |
| 302 | @"32" = 0b010, | |
| 303 | @"64" = 0b011, | |
| 304 | }; | |
| 305 | ||
| 306 | const VlMul = enum(u3) { | |
| 307 | mf8 = 0b101, | |
| 308 | mf4 = 0b110, | |
| 309 | mf2 = 0b111, | |
| 310 | m1 = 0b000, | |
| 311 | m2 = 0b001, | |
| 312 | m4 = 0b010, | |
| 313 | m8 = 0b011, | |
| 314 | }; |
src/arch/riscv64/encoder.zig+3| ... | ... | @@ -5,6 +5,7 @@ pub const Instruction = struct { |
| 5 | 5 | pub const Operand = union(enum) { |
| 6 | 6 | none, |
| 7 | 7 | reg: Register, |
| 8 | csr: CSR, | |
| 8 | 9 | mem: Memory, |
| 9 | 10 | imm: Immediate, |
| 10 | 11 | barrier: Mir.Barrier, |
| ... | ... | @@ -58,6 +59,7 @@ pub const Instruction = struct { |
| 58 | 59 | .imm => |imm| try writer.print("{d}", .{imm.asSigned(64)}), |
| 59 | 60 | .mem => try writer.writeAll("mem"), |
| 60 | 61 | .barrier => |barrier| try writer.writeAll(@tagName(barrier)), |
| 62 | .csr => |csr| try writer.writeAll(@tagName(csr)), | |
| 61 | 63 | } |
| 62 | 64 | } |
| 63 | 65 | } |
| ... | ... | @@ -71,6 +73,7 @@ const bits = @import("bits.zig"); |
| 71 | 73 | const Encoding = @import("Encoding.zig"); |
| 72 | 74 | |
| 73 | 75 | const Register = bits.Register; |
| 76 | const CSR = bits.CSR; | |
| 74 | 77 | const Memory = bits.Memory; |
| 75 | 78 | const Immediate = bits.Immediate; |
| 76 | 79 |
test/tests.zig+6-5| ... | ... | @@ -436,11 +436,12 @@ const test_targets = blk: { |
| 436 | 436 | //}, |
| 437 | 437 | |
| 438 | 438 | .{ |
| 439 | .target = .{ | |
| 440 | .cpu_arch = .riscv64, | |
| 441 | .os_tag = .linux, | |
| 442 | .abi = .musl, | |
| 443 | }, | |
| 439 | .target = std.Target.Query.parse( | |
| 440 | .{ | |
| 441 | .arch_os_abi = "riscv64-linux-musl", | |
| 442 | .cpu_features = "baseline+v", | |
| 443 | }, | |
| 444 | ) catch @panic("OOM"), | |
| 444 | 445 | .use_llvm = false, |
| 445 | 446 | .use_lld = false, |
| 446 | 447 | }, |