diff --git a/lib/std/simd.zig b/lib/std/simd.zig index d2b85f4ad0..4daef151bb 100644 --- a/lib/std/simd.zig +++ b/lib/std/simd.zig @@ -1,7 +1,7 @@ //! SIMD (Single Instruction; Multiple Data) convenience functions. //! //! May offer a potential boost in performance on some targets by performing -//! the same operations on multiple elements at once. +//! the same operation on multiple elements at once. //! //! Some functions are known to not work on MIPS. @@ -10,7 +10,6 @@ const builtin = @import("builtin"); pub fn suggestVectorLengthForCpu(comptime T: type, comptime cpu: std.Target.Cpu) ?comptime_int { // This is guesswork, if you have better suggestions can add it or edit the current here - // This can run in comptime only, but stage 1 fails at it, stage 2 can understand it const element_bit_size = @max(8, std.math.ceilPowerOfTwo(u16, @bitSizeOf(T)) catch unreachable); const vector_bit_size: u16 = blk: { if (cpu.arch.isX86()) { @@ -37,8 +36,37 @@ pub fn suggestVectorLengthForCpu(comptime T: type, comptime cpu: std.Target.Cpu) // the 2048 bits or using just 64 per vector or something in between if (std.Target.mips.featureSetHas(cpu.features, std.Target.mips.Feature.mips3d)) break :blk 64; } else if (cpu.arch.isRISCV()) { - // in risc-v the Vector Extension allows configurable vector sizes, but a standard size of 128 is a safe estimate - if (std.Target.riscv.featureSetHas(cpu.features, .v)) break :blk 128; + // In RISC-V Vector Registers are length agnostic so there's no good way to determine the best size. + // The usual vector length in most RISC-V cpus is 256 bits, however it can get to multiple kB. + if (std.Target.riscv.featureSetHas(cpu.features, .v)) { + var vec_bit_length: u32 = 256; + if (std.Target.riscv.featureSetHas(cpu.features, .zvl32b)) { + vec_bit_length = 32; + } else if (std.Target.riscv.featureSetHas(cpu.features, .zvl64b)) { + vec_bit_length = 64; + } else if (std.Target.riscv.featureSetHas(cpu.features, .zvl128b)) { + vec_bit_length = 128; + } else if (std.Target.riscv.featureSetHas(cpu.features, .zvl256b)) { + vec_bit_length = 256; + } else if (std.Target.riscv.featureSetHas(cpu.features, .zvl512b)) { + vec_bit_length = 512; + } else if (std.Target.riscv.featureSetHas(cpu.features, .zvl1024b)) { + vec_bit_length = 1024; + } else if (std.Target.riscv.featureSetHas(cpu.features, .zvl2048b)) { + vec_bit_length = 2048; + } else if (std.Target.riscv.featureSetHas(cpu.features, .zvl4096b)) { + vec_bit_length = 4096; + } else if (std.Target.riscv.featureSetHas(cpu.features, .zvl8192b)) { + vec_bit_length = 8192; + } else if (std.Target.riscv.featureSetHas(cpu.features, .zvl16384b)) { + vec_bit_length = 16384; + } else if (std.Target.riscv.featureSetHas(cpu.features, .zvl32768b)) { + vec_bit_length = 32768; + } else if (std.Target.riscv.featureSetHas(cpu.features, .zvl65536b)) { + vec_bit_length = 65536; + } + break :blk vec_bit_length; + } } else if (cpu.arch.isSPARC()) { // TODO: Test Sparc capability to handle bigger vectors // In theory Sparc have 32 registers of 64 bits which can use in parallel diff --git a/lib/std/start.zig b/lib/std/start.zig index 0f169f112d..adcfb9d71b 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -221,7 +221,26 @@ fn riscv_start() callconv(.C) noreturn { } break :ret root.main(); }, - else => @compileError("expected return type of main to be 'void', 'noreturn', 'u8'"), + .ErrorUnion => ret: { + const result = root.main() catch { + const stderr = std.io.getStdErr().writer(); + stderr.writeAll("failed with error\n") catch { + @panic("failed to print when main returned error"); + }; + break :ret 1; + }; + switch (@typeInfo(@TypeOf(result))) { + .Void => break :ret 0, + .Int => |info| { + if (info.bits != 8 or info.signedness == .signed) { + @compileError(bad_main_ret); + } + return result; + }, + else => @compileError(bad_main_ret), + } + }, + else => @compileError(bad_main_ret), }); } diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index c5ae354f54..6e203161c2 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -1,8 +1,12 @@ const std = @import("std"); const builtin = @import("builtin"); +const build_options = @import("build_options"); + const mem = std.mem; const math = std.math; const assert = std.debug.assert; +const Allocator = mem.Allocator; + const Air = @import("../../Air.zig"); const Mir = @import("Mir.zig"); const Emit = @import("Emit.zig"); @@ -14,18 +18,16 @@ const Zcu = @import("../../Zcu.zig"); const Package = @import("../../Package.zig"); const InternPool = @import("../../InternPool.zig"); const Compilation = @import("../../Compilation.zig"); +const trace = @import("../../tracy.zig").trace; +const codegen = @import("../../codegen.zig"); + const ErrorMsg = Zcu.ErrorMsg; const Target = std.Target; -const Allocator = mem.Allocator; -const trace = @import("../../tracy.zig").trace; -const DW = std.dwarf; -const leb128 = std.leb; + const log = std.log.scoped(.riscv_codegen); const tracking_log = std.log.scoped(.tracking); const verbose_tracking_log = std.log.scoped(.verbose_tracking); const wip_mir_log = std.log.scoped(.wip_mir); -const build_options = @import("build_options"); -const codegen = @import("../../codegen.zig"); const Alignment = InternPool.Alignment; const CodeGenError = codegen.CodeGenError; @@ -37,6 +39,7 @@ const abi = @import("abi.zig"); const Lower = @import("Lower.zig"); const Register = bits.Register; +const CSR = bits.CSR; const Immediate = bits.Immediate; const Memory = bits.Memory; const FrameIndex = bits.FrameIndex; @@ -45,15 +48,16 @@ const RegisterLock = RegisterManager.RegisterLock; const InnerError = CodeGenError || error{OutOfRegisters}; -gpa: Allocator, pt: Zcu.PerThread, air: Air, -mod: *Package.Module, liveness: Liveness, +zcu: *Zcu, bin_file: *link.File, +gpa: Allocator, + +mod: *Package.Module, target: *const std.Target, func_index: InternPool.Index, -code: *std.ArrayList(u8), debug_output: DebugInfoOutput, err_msg: ?*ErrorMsg, args: []MCValue, @@ -62,9 +66,7 @@ fn_type: Type, arg_index: usize, src_loc: Zcu.LazySrcLoc, -/// MIR Instructions mir_instructions: std.MultiArrayList(Mir.Inst) = .{}, -/// MIR extra data mir_extra: std.ArrayListUnmanaged(u32) = .{}, /// Byte offset within the source file of the ending curly. @@ -87,6 +89,10 @@ exitlude_jump_relocs: std.ArrayListUnmanaged(usize) = .{}, /// across each runtime branch upon joining. branch_stack: *std.ArrayList(Branch), +// Currently set vector properties, null means they haven't been set yet in the function. +avl: ?u64, +vtype: ?bits.VType, + // Key is the block instruction blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .{}, register_manager: RegisterManager = .{}, @@ -117,8 +123,9 @@ const MCValue = union(enum) { /// No more references to this value remain. /// The payload is the value of scope_generation at the point where the death occurred dead: u32, - /// The value is undefined. - undef, + /// The value is undefined. Contains a symbol index to an undefined constant. Null means + /// set the undefined value via immediate instead of a load. + undef: ?u32, /// A pointer-sized integer that fits in a register. /// If the type is a pointer, this is the pointer address in virtual address space. immediate: u64, @@ -725,16 +732,16 @@ pub fn generate( } try branch_stack.append(.{}); - var function = Func{ + var function: Func = .{ .gpa = gpa, .air = air, .pt = pt, .mod = mod, + .zcu = zcu, + .bin_file = bin_file, .liveness = liveness, .target = target, - .bin_file = bin_file, .func_index = func_index, - .code = code, .debug_output = debug_output, .err_msg = null, .args = undefined, // populated after `resolveCallingConventionValues` @@ -746,6 +753,8 @@ pub fn generate( .end_di_line = func.rbrace_line, .end_di_column = func.rbrace_column, .scope_generation = 0, + .avl = null, + .vtype = null, }; defer { function.frame_allocs.deinit(gpa); @@ -817,7 +826,7 @@ pub fn generate( else => |e| return e, }; - var mir = Mir{ + var mir: Mir = .{ .instructions = function.mir_instructions.toOwnedSlice(), .extra = try function.mir_extra.toOwnedSlice(gpa), .frame_locs = function.frame_locs.toOwnedSlice(), @@ -825,7 +834,6 @@ pub fn generate( defer mir.deinit(gpa); var emit: Emit = .{ - .bin_file = bin_file, .lower = .{ .pt = pt, .allocator = gpa, @@ -836,6 +844,7 @@ pub fn generate( .link_mode = comp.config.link_mode, .pic = mod.pic, }, + .bin_file = bin_file, .debug_output = debug_output, .code = code, .prev_di_pc = 0, @@ -896,7 +905,7 @@ fn formatWipMir( .pic = comp.root_mod.pic, }; var first = true; - for ((lower.lowerMir(data.inst) catch |err| switch (err) { + for ((lower.lowerMir(data.inst, .{ .allow_frame_locs = false }) catch |err| switch (err) { error.LowerFail => { defer { lower.err_msg.?.deinit(data.func.gpa); @@ -924,7 +933,7 @@ fn fmtWipMir(func: *Func, inst: Mir.Inst.Index) std.fmt.Formatter(formatWipMir) } const FormatDeclData = struct { - mod: *Zcu, + zcu: *Zcu, decl_index: InternPool.DeclIndex, }; fn formatDecl( @@ -933,11 +942,11 @@ fn formatDecl( _: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { - try writer.print("{}", .{data.mod.declPtr(data.decl_index).fqn.fmt(&data.mod.intern_pool)}); + try writer.print("{}", .{data.zcu.declPtr(data.decl_index).fqn.fmt(&data.zcu.intern_pool)}); } fn fmtDecl(func: *Func, decl_index: InternPool.DeclIndex) std.fmt.Formatter(formatDecl) { return .{ .data = .{ - .mod = func.pt.zcu, + .zcu = func.zcu, .decl_index = decl_index, } }; } @@ -989,13 +998,9 @@ fn addInst(func: *Func, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index { .pseudo_dbg_prologue_end, .pseudo_dbg_line_column, .pseudo_dbg_epilogue_begin, - .pseudo_store_rm, - .pseudo_load_rm, - .pseudo_lea_rm, - .pseudo_mv, .pseudo_dead, => false, - }) wip_mir_log.debug("{}", .{func.fmtWipMir(result_index)}) else wip_mir_log.debug(" | uses-mem", .{}); + }) wip_mir_log.debug("{}", .{func.fmtWipMir(result_index)}); return result_index; } @@ -1042,9 +1047,84 @@ pub fn addExtraAssumeCapacity(func: *Func, extra: anytype) u32 { return result; } +/// Returns a temporary register that contains the value of the `reg` csr. +/// +/// Caller's duty to lock the return register is needed. +fn getCsr(func: *Func, csr: CSR) !Register { + assert(func.hasFeature(.zicsr)); + const dst_reg = try func.register_manager.allocReg(null, func.regTempClassForType(Type.usize)); + _ = try func.addInst(.{ + .tag = .csrrs, + .ops = .csr, + .data = .{ + .csr = .{ + .csr = csr, + .rd = dst_reg, + .rs1 = .x0, + }, + }, + }); + return dst_reg; +} + +fn setVl(func: *Func, dst_reg: Register, avl: u64, options: bits.VType) !void { + if (func.avl == avl) if (func.vtype) |vtype| { + // it's already set, we don't need to do anything + if (@as(u8, @bitCast(vtype)) == @as(u8, @bitCast(options))) return; + }; + + func.avl = avl; + func.vtype = options; + + if (avl == 0) { + // the caller means to do "vsetvli zero, zero ..." which keeps the avl to whatever it was before + const options_int: u12 = @as(u12, 0) | @as(u8, @bitCast(options)); + _ = try func.addInst(.{ + .tag = .vsetvli, + .ops = .rri, + .data = .{ .i_type = .{ + .rd = dst_reg, + .rs1 = .zero, + .imm12 = Immediate.u(options_int), + } }, + }); + } else { + // if the avl can fit into u5 we can use vsetivli otherwise use vsetvli + if (avl <= std.math.maxInt(u5)) { + const options_int: u12 = (~@as(u12, 0) << 10) | @as(u8, @bitCast(options)); + _ = try func.addInst(.{ + .tag = .vsetivli, + .ops = .rri, + .data = .{ + .i_type = .{ + .rd = dst_reg, + .rs1 = @enumFromInt(avl), + .imm12 = Immediate.u(options_int), + }, + }, + }); + } else { + const options_int: u12 = @as(u12, 0) | @as(u8, @bitCast(options)); + const temp_reg = try func.copyToTmpRegister(Type.usize, .{ .immediate = avl }); + _ = try func.addInst(.{ + .tag = .vsetvli, + .ops = .rri, + .data = .{ .i_type = .{ + .rd = dst_reg, + .rs1 = temp_reg, + .imm12 = Immediate.u(options_int), + } }, + }); + } + } +} + const required_features = [_]Target.riscv.Feature{ .d, .m, + .a, + .zicsr, + .v, }; fn gen(func: *Func) !void { @@ -1102,7 +1182,19 @@ fn gen(func: *Func) !void { const backpatch_ra_restore = try func.addPseudo(.pseudo_dead); const backpatch_fp_restore = try func.addPseudo(.pseudo_dead); const backpatch_stack_alloc_restore = try func.addPseudo(.pseudo_dead); - try func.addPseudoNone(.pseudo_ret); + + // ret + _ = try func.addInst(.{ + .tag = .jalr, + .ops = .rri, + .data = .{ + .i_type = .{ + .rd = .zero, + .rs1 = .ra, + .imm12 = Immediate.s(0), + }, + }, + }); const frame_layout = try func.computeFrameLayout(); const need_save_reg = frame_layout.save_reg_list.count() > 0; @@ -1273,7 +1365,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void { .round, .trunc_float, .neg, - => try func.airUnaryMath(inst), + => try func.airUnaryMath(inst, tag), .add_with_overflow => try func.airAddWithOverflow(inst), .sub_with_overflow => try func.airSubWithOverflow(inst), @@ -1319,7 +1411,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void { .breakpoint => try func.airBreakpoint(), .ret_addr => try func.airRetAddr(inst), .frame_addr => try func.airFrameAddress(inst), - .fence => try func.airFence(), + .fence => try func.airFence(inst), .cond_br => try func.airCondBr(inst), .dbg_stmt => try func.airDbgStmt(inst), .fptrunc => try func.airFptrunc(inst), @@ -1631,7 +1723,7 @@ fn computeFrameLayout(func: *Func) !FrameLayout { // The total frame size is calculated by the amount of s registers you need to save * 8, as each // register is 8 bytes, the total allocation sizes, and 16 more register for the spilled ra and s0 - // register. Finally we align the frame size to the align of the base pointer. + // register. Finally we align the frame size to the alignment of the base pointer. const args_frame_size = frame_size[@intFromEnum(FrameIndex.args_frame)]; const spill_frame_size = frame_size[@intFromEnum(FrameIndex.spill_frame)]; const call_frame_size = frame_size[@intFromEnum(FrameIndex.call_frame)]; @@ -1791,14 +1883,9 @@ fn symbolIndex(func: *Func) !u32 { const pt = func.pt; const zcu = pt.zcu; const decl_index = zcu.funcOwnerDeclIndex(func.func_index); - return switch (func.bin_file.tag) { - .elf => blk: { - const elf_file = func.bin_file.cast(link.File.Elf).?; - const atom_index = try elf_file.zigObjectPtr().?.getOrCreateMetadataForDecl(elf_file, decl_index); - break :blk atom_index; - }, - else => return func.fail("TODO symbolIndex {s}", .{@tagName(func.bin_file.tag)}), - }; + const elf_file = func.bin_file.cast(link.File.Elf).?; + const atom_index = try elf_file.zigObjectPtr().?.getOrCreateMetadataForDecl(elf_file, decl_index); + return atom_index; } fn allocFrameIndex(func: *Func, alloc: FrameAlloc) !FrameIndex { @@ -1843,48 +1930,44 @@ fn typeRegClass(func: *Func, ty: Type) abi.RegisterClass { const zcu = pt.zcu; return switch (ty.zigTypeTag(zcu)) { .Float => .float, - .Vector => @panic("TODO: typeRegClass for Vectors"), - inline else => .int, + .Vector => .vector, + else => .int, }; } fn regGeneralClassForType(func: *Func, ty: Type) RegisterManager.RegisterBitSet { - const pt = func.pt; - const zcu = pt.zcu; - return switch (ty.zigTypeTag(zcu)) { + return switch (ty.zigTypeTag(func.pt.zcu)) { .Float => abi.Registers.Float.general_purpose, - .Vector => @panic("TODO: regGeneralClassForType for Vectors"), + .Vector => abi.Registers.Vector.general_purpose, else => abi.Registers.Integer.general_purpose, }; } fn regTempClassForType(func: *Func, ty: Type) RegisterManager.RegisterBitSet { - const pt = func.pt; - const zcu = pt.zcu; - return switch (ty.zigTypeTag(zcu)) { + return switch (ty.zigTypeTag(func.pt.zcu)) { .Float => abi.Registers.Float.temporary, - .Vector => @panic("TODO: regTempClassForType for Vectors"), + .Vector => abi.Registers.Vector.general_purpose, // there are no temporary vector registers else => abi.Registers.Integer.temporary, }; } fn allocRegOrMem(func: *Func, elem_ty: Type, inst: ?Air.Inst.Index, reg_ok: bool) !MCValue { const pt = func.pt; + const zcu = pt.zcu; - const abi_size = math.cast(u32, elem_ty.abiSize(pt)) orelse { - return func.fail("type '{}' too big to fit into stack frame", .{elem_ty.fmt(pt)}); + const bit_size = elem_ty.bitSize(pt); + const min_size: u64 = switch (elem_ty.zigTypeTag(zcu)) { + .Float => if (func.hasFeature(.d)) 64 else 32, + .Vector => 256, // TODO: calculate it from avl * vsew + else => 64, }; - const min_size: u32 = switch (elem_ty.zigTypeTag(pt.zcu)) { - .Float => 4, - .Vector => @panic("allocRegOrMem Vector"), - else => 8, - }; - - if (reg_ok and abi_size <= min_size) { + if (reg_ok and bit_size <= min_size) { if (func.register_manager.tryAllocReg(inst, func.regGeneralClassForType(elem_ty))) |reg| { return .{ .register = reg }; } + } else if (reg_ok and elem_ty.zigTypeTag(zcu) == .Vector) { + return func.fail("did you forget to extend vector registers before allocating", .{}); } const frame_index = try func.allocFrameIndex(FrameAlloc.initSpill(elem_ty, pt)); @@ -1897,10 +1980,13 @@ fn allocRegOrMem(func: *Func, elem_ty: Type, inst: ?Air.Inst.Index, reg_ok: bool fn allocReg(func: *Func, reg_class: abi.RegisterClass) !struct { Register, RegisterLock } { if (reg_class == .float and !func.hasFeature(.f)) std.debug.panic("allocReg class == float where F isn't enabled", .{}); + if (reg_class == .vector and !func.hasFeature(.v)) + std.debug.panic("allocReg class == vector where V isn't enabled", .{}); const class = switch (reg_class) { .int => abi.Registers.Integer.general_purpose, .float => abi.Registers.Float.general_purpose, + .vector => abi.Registers.Vector.general_purpose, }; const reg = try func.register_manager.allocReg(null, class); @@ -1916,7 +2002,8 @@ fn promoteReg(func: *Func, ty: Type, operand: MCValue) !struct { Register, ?Regi return .{ op_reg, func.register_manager.lockReg(operand.register) }; } - const reg, const lock = try func.allocReg(func.typeRegClass(ty)); + const class = func.typeRegClass(ty); + const reg, const lock = try func.allocReg(class); try func.genSetReg(ty, reg, operand); return .{ reg, lock }; } @@ -2087,19 +2174,17 @@ fn airNot(func: *Func, inst: Air.Inst.Index) !void { const operand = try func.resolveInst(ty_op.operand); const ty = func.typeOf(ty_op.operand); + const operand_reg, const operand_lock = try func.promoteReg(ty, operand); + defer if (operand_lock) |lock| func.register_manager.unlockReg(lock); + + const dst_reg: Register = + if (func.reuseOperand(inst, ty_op.operand, 0, operand) and operand == .register) + operand.register + else + (try func.allocRegOrMem(func.typeOfIndex(inst), inst, true)).register; + switch (ty.zigTypeTag(zcu)) { .Bool => { - const operand_reg = blk: { - if (operand == .register) break :blk operand.register; - break :blk try func.copyToTmpRegister(ty, operand); - }; - - const dst_reg: Register = - if (func.reuseOperand(inst, ty_op.operand, 0, operand) and operand == .register) - operand.register - else - (try func.allocRegOrMem(func.typeOfIndex(inst), inst, true)).register; - _ = try func.addInst(.{ .tag = .pseudo, .ops = .pseudo_not, @@ -2110,12 +2195,34 @@ fn airNot(func: *Func, inst: Air.Inst.Index) !void { }, }, }); - - break :result .{ .register = dst_reg }; }, - .Int => return func.fail("TODO: airNot ints", .{}), + .Int => { + const size = ty.bitSize(pt); + if (!math.isPowerOfTwo(size)) + return func.fail("TODO: airNot non-pow 2 int size", .{}); + + switch (size) { + 32, 64 => { + _ = try func.addInst(.{ + .tag = .xori, + .ops = .rri, + .data = .{ + .i_type = .{ + .rd = dst_reg, + .rs1 = operand_reg, + .imm12 = Immediate.s(-1), + }, + }, + }); + }, + 8, 16 => return func.fail("TODO: airNot 8 or 16, {}", .{size}), + else => unreachable, + } + }, else => unreachable, } + + break :result .{ .register = dst_reg }; }; return func.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } @@ -2205,7 +2312,11 @@ fn binOp( return func.fail("binOp libcall runtime-float ops", .{}); } - if (lhs_ty.bitSize(pt) > 64) return func.fail("TODO: binOp >= 64 bits", .{}); + // don't have support for certain sizes of addition + switch (lhs_ty.zigTypeTag(pt.zcu)) { + .Vector => {}, // works differently and fails in a different place + else => if (lhs_ty.bitSize(pt) > 64) return func.fail("TODO: binOp >= 64 bits", .{}), + } const lhs_mcv = try func.resolveInst(lhs_air); const rhs_mcv = try func.resolveInst(rhs_air); @@ -2353,6 +2464,51 @@ fn genBinOp( }, }); }, + .Vector => { + const num_elem = lhs_ty.vectorLen(zcu); + const elem_size = lhs_ty.childType(zcu).bitSize(pt); + + const child_ty = lhs_ty.childType(zcu); + + const mir_tag: Mir.Inst.Tag = switch (tag) { + .add => switch (child_ty.zigTypeTag(zcu)) { + .Int => .vaddvv, + .Float => .vfaddvv, + else => unreachable, + }, + .sub => switch (child_ty.zigTypeTag(zcu)) { + .Int => .vsubvv, + .Float => .vfsubvv, + else => unreachable, + }, + else => return func.fail("TODO: genBinOp {s} Vector", .{@tagName(tag)}), + }; + + try func.setVl(.zero, num_elem, .{ + .vsew = switch (elem_size) { + 8 => .@"8", + 16 => .@"16", + 32 => .@"32", + 64 => .@"64", + else => unreachable, + }, + .vlmul = .m1, + .vma = true, + .vta = true, + }); + + _ = try func.addInst(.{ + .tag = mir_tag, + .ops = .rrr, + .data = .{ + .r_type = .{ + .rd = dst_reg, + .rs1 = rhs_reg, + .rs2 = lhs_reg, + }, + }, + }); + }, else => unreachable, } }, @@ -2636,78 +2792,55 @@ fn airAddWithOverflow(func: *Func, inst: Air.Inst.Index) !void { const extra = func.air.extraData(Air.Bin, ty_pl.payload).data; const result: MCValue = if (func.liveness.isUnused(inst)) .unreach else result: { - const lhs_ty = func.typeOf(extra.lhs); + const ty = func.typeOf(extra.lhs); + switch (ty.zigTypeTag(zcu)) { + .Vector => return func.fail("TODO implement add with overflow for Vector type", .{}), + .Int => { + const int_info = ty.intInfo(zcu); - const int_info = lhs_ty.intInfo(zcu); + const tuple_ty = func.typeOfIndex(inst); + const result_mcv = try func.allocRegOrMem(tuple_ty, inst, false); + const offset = result_mcv.load_frame; - const tuple_ty = func.typeOfIndex(inst); - const result_mcv = try func.allocRegOrMem(tuple_ty, inst, false); - const offset = result_mcv.load_frame; + if (int_info.bits >= 8 and math.isPowerOfTwo(int_info.bits)) { + const add_result = try func.binOp(null, .add, extra.lhs, extra.rhs); - if (int_info.bits >= 8 and math.isPowerOfTwo(int_info.bits)) { - const add_result = try func.binOp(null, .add, extra.lhs, extra.rhs); - const add_result_reg = try func.copyToTmpRegister(lhs_ty, add_result); - const add_result_reg_lock = func.register_manager.lockRegAssumeUnused(add_result_reg); - defer func.register_manager.unlockReg(add_result_reg_lock); + const add_result_reg = try func.copyToTmpRegister(ty, add_result); + const add_result_reg_lock = func.register_manager.lockRegAssumeUnused(add_result_reg); + defer func.register_manager.unlockReg(add_result_reg_lock); - const shift_amount: u6 = @intCast(Type.usize.bitSize(pt) - int_info.bits); + try func.genSetMem( + .{ .frame = offset.index }, + offset.off + @as(i32, @intCast(tuple_ty.structFieldOffset(0, pt))), + ty, + add_result, + ); - const shift_reg, const shift_lock = try func.allocReg(.int); - defer func.register_manager.unlockReg(shift_lock); + const overflow_reg, const overflow_lock = try func.allocReg(.int); + defer func.register_manager.unlockReg(overflow_lock); - _ = try func.addInst(.{ - .tag = .slli, - .ops = .rri, - .data = .{ - .i_type = .{ - .rd = shift_reg, - .rs1 = add_result_reg, - .imm12 = Immediate.u(shift_amount), - }, - }, - }); + try func.genBinOp( + .cmp_neq, + .{ .register = add_result_reg }, + ty, + .{ .register = add_result_reg }, + ty, + overflow_reg, + ); - _ = try func.addInst(.{ - .tag = if (int_info.signedness == .unsigned) .srli else .srai, - .ops = .rri, - .data = .{ - .i_type = .{ - .rd = shift_reg, - .rs1 = shift_reg, - .imm12 = Immediate.u(shift_amount), - }, - }, - }); + try func.genSetMem( + .{ .frame = offset.index }, + offset.off + @as(i32, @intCast(tuple_ty.structFieldOffset(1, pt))), + Type.u1, + .{ .register = overflow_reg }, + ); - try func.genSetMem( - .{ .frame = offset.index }, - offset.off + @as(i32, @intCast(tuple_ty.structFieldOffset(0, pt))), - lhs_ty, - add_result, - ); - - const overflow_reg, const overflow_lock = try func.allocReg(.int); - defer func.register_manager.unlockReg(overflow_lock); - - try func.genBinOp( - .cmp_neq, - .{ .register = shift_reg }, - lhs_ty, - .{ .register = add_result_reg }, - lhs_ty, - overflow_reg, - ); - - try func.genSetMem( - .{ .frame = offset.index }, - offset.off + @as(i32, @intCast(tuple_ty.structFieldOffset(1, pt))), - Type.u1, - .{ .register = overflow_reg }, - ); - - break :result result_mcv; - } else { - return func.fail("TODO: less than 8 bit or non-pow 2 addition", .{}); + break :result result_mcv; + } else { + return func.fail("TODO: less than 8 bit or non-pow 2 addition", .{}); + } + }, + else => unreachable, } }; @@ -3229,7 +3362,7 @@ fn airWrapErrUnionErr(func: *Func, inst: Air.Inst.Index) !void { const frame_index = try func.allocFrameIndex(FrameAlloc.initSpill(eu_ty, pt)); const pl_off: i32 = @intCast(errUnionPayloadOffset(pl_ty, pt)); const err_off: i32 = @intCast(errUnionErrorOffset(pl_ty, pt)); - try func.genSetMem(.{ .frame = frame_index }, pl_off, pl_ty, .undef); + try func.genSetMem(.{ .frame = frame_index }, pl_off, pl_ty, .{ .undef = null }); const operand = try func.resolveInst(ty_op.operand); try func.genSetMem(.{ .frame = frame_index }, err_off, err_ty, operand); break :result .{ .load_frame = .{ .index = frame_index } }; @@ -3451,20 +3584,54 @@ fn airArrayElemVal(func: *Func, inst: Air.Inst.Index) !void { else => try func.genSetReg(Type.usize, addr_reg, array_mcv.address()), } + const dst_mcv = try func.allocRegOrMem(result_ty, inst, false); + + if (array_ty.isVector(zcu)) { + // we need to load the vector, vslidedown to get the element we want + // and store that element at in a load frame. + + const src_reg, const src_lock = try func.allocReg(.vector); + defer func.register_manager.unlockReg(src_lock); + + // load the vector into a temporary register + try func.genCopy(array_ty, .{ .register = src_reg }, .{ .indirect = .{ .reg = addr_reg } }); + + // we need to construct a 1xbitSize vector because of how lane splitting works in RISC-V + const single_ty = try pt.vectorType(.{ .child = elem_ty.toIntern(), .len = 1 }); + + // we can do a shortcut here where we don't need a vslicedown + // and can just copy to the frame index. + if (!(index_mcv == .immediate and index_mcv.immediate == 0)) { + const index_reg = try func.copyToTmpRegister(Type.usize, index_mcv); + + _ = try func.addInst(.{ + .tag = .vslidedownvx, + .ops = .rrr, + .data = .{ .r_type = .{ + .rd = src_reg, + .rs1 = index_reg, + .rs2 = src_reg, + } }, + }); + } + + try func.genCopy(single_ty, dst_mcv, .{ .register = src_reg }); + break :result dst_mcv; + } + const offset_reg = try func.elemOffset(index_ty, index_mcv, elem_abi_size); const offset_lock = func.register_manager.lockRegAssumeUnused(offset_reg); defer func.register_manager.unlockReg(offset_lock); - - const dst_mcv = try func.allocRegOrMem(result_ty, inst, false); _ = try func.addInst(.{ .tag = .add, .ops = .rrr, .data = .{ .r_type = .{ .rd = addr_reg, - .rs1 = offset_reg, - .rs2 = addr_reg, + .rs1 = addr_reg, + .rs2 = offset_reg, } }, }); + try func.genCopy(elem_ty, dst_mcv, .{ .indirect = .{ .reg = addr_reg } }); break :result dst_mcv; }; @@ -3541,9 +3708,50 @@ fn airSetUnionTag(func: *Func, inst: Air.Inst.Index) !void { } fn airGetUnionTag(func: *Func, inst: Air.Inst.Index) !void { + const pt = func.pt; const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; - const result: MCValue = if (func.liveness.isUnused(inst)) .unreach else return func.fail("TODO implement airGetUnionTag for {}", .{func.target.cpu.arch}); - return func.finishAir(inst, result, .{ ty_op.operand, .none, .none }); + + const tag_ty = func.typeOfIndex(inst); + const union_ty = func.typeOf(ty_op.operand); + const layout = union_ty.unionGetLayout(pt); + + if (layout.tag_size == 0) { + return func.finishAir(inst, .none, .{ ty_op.operand, .none, .none }); + } + + const operand = try func.resolveInst(ty_op.operand); + + const frame_mcv = try func.allocRegOrMem(union_ty, null, false); + try func.genCopy(union_ty, frame_mcv, operand); + + const tag_abi_size = tag_ty.abiSize(pt); + const result_reg, const result_lock = try func.allocReg(.int); + defer func.register_manager.unlockReg(result_lock); + + switch (frame_mcv) { + .load_frame => |frame_addr| { + if (tag_abi_size <= 8) { + const off: i32 = if (layout.tag_align.compare(.lt, layout.payload_align)) + @intCast(layout.payload_size) + else + 0; + + try func.genCopy( + tag_ty, + .{ .register = result_reg }, + .{ .load_frame = .{ .index = frame_addr.index, .off = frame_addr.off + off } }, + ); + } else { + return func.fail( + "TODO implement get_union_tag for ABI larger than 8 bytes and operand {}, tag {}", + .{ frame_mcv, tag_ty.fmt(pt) }, + ); + } + }, + else => return func.fail("TODO: airGetUnionTag {s}", .{@tagName(operand)}), + } + + return func.finishAir(inst, .{ .register = result_reg }, .{ ty_op.operand, .none, .none }); } fn airClz(func: *Func, inst: Air.Inst.Index) !void { @@ -3719,13 +3927,65 @@ fn airBitReverse(func: *Func, inst: Air.Inst.Index) !void { return func.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn airUnaryMath(func: *Func, inst: Air.Inst.Index) !void { - const tag = func.air.instructions.items(.tag)[@intFromEnum(inst)]; +fn airUnaryMath(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { + const pt = func.pt; const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op; - const result: MCValue = if (func.liveness.isUnused(inst)) - .unreach - else - return func.fail("TODO implementairUnaryMath {s} for {}", .{ @tagName(tag), func.target.cpu.arch }); + const result: MCValue = if (func.liveness.isUnused(inst)) .unreach else result: { + const ty = func.typeOf(un_op); + + const operand = try func.resolveInst(un_op); + const operand_bit_size = ty.bitSize(pt); + + if (!math.isPowerOfTwo(operand_bit_size)) + return func.fail("TODO: airUnaryMath non-pow 2", .{}); + + const operand_reg, const operand_lock = try func.promoteReg(ty, operand); + defer if (operand_lock) |lock| func.register_manager.unlockReg(lock); + + const dst_class = func.typeRegClass(ty); + const dst_reg, const dst_lock = try func.allocReg(dst_class); + defer func.register_manager.unlockReg(dst_lock); + + switch (ty.zigTypeTag(pt.zcu)) { + .Float => { + assert(dst_class == .float); + + switch (operand_bit_size) { + 16, 80, 128 => return func.fail("TODO: airUnaryMath Float bit-size {}", .{operand_bit_size}), + 32, 64 => {}, + else => unreachable, + } + + switch (tag) { + .sqrt => { + _ = try func.addInst(.{ + .tag = if (operand_bit_size == 64) .fsqrtd else .fsqrts, + .ops = .rrr, + .data = .{ + .r_type = .{ + .rd = dst_reg, + .rs1 = operand_reg, + .rs2 = .f0, // unused, spec says it's 0 + }, + }, + }); + }, + else => return func.fail("TODO: airUnaryMath Float {s}", .{@tagName(tag)}), + } + }, + .Int => { + assert(dst_class == .int); + + switch (tag) { + else => return func.fail("TODO: airUnaryMath Float {s}", .{@tagName(tag)}), + } + }, + else => return func.fail("TODO: airUnaryMath ty: {}", .{ty.fmt(pt)}), + } + + break :result MCValue{ .register = dst_reg }; + }; + return func.finishAir(inst, result, .{ un_op, .none, .none }); } @@ -3987,6 +4247,10 @@ fn airStructFieldVal(func: *Func, inst: Air.Inst.Index) !void { }); } + if (field_off == 0) { + try func.truncateRegister(field_ty, dst_reg); + } + break :result if (field_off == 0) dst_mcv else try func.copyToNewRegister(inst, dst_mcv); }, .load_frame => { @@ -4121,9 +4385,28 @@ fn airFrameAddress(func: *Func, inst: Air.Inst.Index) !void { return func.finishAir(inst, dst_mcv, .{ .none, .none, .none }); } -fn airFence(func: *Func) !void { - return func.fail("TODO implement fence() for {}", .{func.target.cpu.arch}); - //return func.finishAirBookkeeping(); +fn airFence(func: *Func, inst: Air.Inst.Index) !void { + const order = func.air.instructions.items(.data)[@intFromEnum(inst)].fence; + const pred: Mir.Barrier, const succ: Mir.Barrier = switch (order) { + .unordered, .monotonic => unreachable, + .acquire => .{ .r, .rw }, + .release => .{ .rw, .r }, + .acq_rel => .{ .rw, .rw }, + .seq_cst => .{ .rw, .rw }, + }; + + _ = try func.addInst(.{ + .tag = .pseudo, + .ops = .pseudo_fence, + .data = .{ + .fence = .{ + .pred = pred, + .succ = succ, + .fm = if (order == .acq_rel) .tso else .none, + }, + }, + }); + return func.finishAirBookkeeping(); } fn airCall(func: *Func, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !void { @@ -4374,7 +4657,27 @@ fn airRet(func: *Func, inst: Air.Inst.Index, safety: bool) !void { .none => {}, .register, .register_pair, - => try func.genCopy(ret_ty, func.ret_mcv.short, .{ .air_ref = un_op }), + => { + if (ret_ty.isVector(zcu)) { + const bit_size = ret_ty.totalVectorBits(pt); + + // set the vtype to hold the entire vector's contents in a single element + try func.setVl(.zero, 0, .{ + .vsew = switch (bit_size) { + 8 => .@"8", + 16 => .@"16", + 32 => .@"32", + 64 => .@"64", + else => unreachable, + }, + .vlmul = .m1, + .vma = true, + .vta = true, + }); + } + + try func.genCopy(ret_ty, func.ret_mcv.short, .{ .air_ref = un_op }); + }, .indirect => |reg_off| { try func.register_manager.getReg(reg_off.reg, null); const lock = func.register_manager.lockRegAssumeUnused(reg_off.reg); @@ -5224,8 +5527,6 @@ fn airAsm(func: *Func, inst: Air.Inst.Index) !void { const inputs: []const Air.Inst.Ref = @ptrCast(func.air.extra[extra_i..][0..extra.data.inputs_len]); extra_i += inputs.len; - log.debug("airAsm input: {any}", .{inputs}); - const dead = !is_volatile and func.liveness.isUnused(inst); const result: MCValue = if (dead) .unreach else result: { if (outputs.len > 1) { @@ -5599,18 +5900,34 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError! const zcu = pt.zcu; const abi_size: u32 = @intCast(ty.abiSize(pt)); - if (abi_size > 8) return std.debug.panic("tried to set reg with size {}", .{abi_size}); - + const max_size: u32 = switch (reg.class()) { + .int => 64, + .float => if (func.hasFeature(.d)) 64 else 32, + .vector => 64, // TODO: calculate it from avl * vsew + }; + if (abi_size > max_size) return std.debug.panic("tried to set reg with size {}", .{abi_size}); const dst_reg_class = reg.class(); switch (src_mcv) { - .dead => unreachable, - .unreach, .none => return, // Nothing to do. - .undef => { + .unreach, + .none, + .dead, + => unreachable, + .undef => |sym_index| { if (!func.wantSafety()) - return; // The already existing value will do just fine. - // Write the debug undefined value. - return func.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }); + return; + + if (sym_index) |index| { + return func.genSetReg(ty, reg, .{ .load_symbol = .{ .sym = index } }); + } + + switch (abi_size) { + 1 => return func.genSetReg(ty, reg, .{ .immediate = 0xAA }), + 2 => return func.genSetReg(ty, reg, .{ .immediate = 0xAAAA }), + 3...4 => return func.genSetReg(ty, reg, .{ .immediate = 0xAAAAAAAA }), + 5...8 => return func.genSetReg(ty, reg, .{ .immediate = 0xAAAAAAAAAAAAAAAA }), + else => unreachable, + } }, .immediate => |unsigned_x| { assert(dst_reg_class == .int); @@ -5688,11 +6005,25 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError! if (src_reg.id() == reg.id()) return; - const src_reg_class = src_reg.class(); - - if (src_reg_class == .float and dst_reg_class == .int) { - // to move from float -> int, we use FMV.X.W - return func.fail("TODO: genSetReg float -> int", .{}); + // there is no instruction for loading the contents of a vector register + // into an integer register, however we can cheat a bit by setting the element + // size to the total size of the vector, and vmv.x.s will work then + if (src_reg.class() == .vector) { + try func.setVl(.zero, 0, .{ + .vsew = switch (ty.totalVectorBits(pt)) { + 8 => .@"8", + 16 => .@"16", + 32 => .@"32", + 64 => .@"64", + else => |vec_bits| return func.fail("TODO: genSetReg vec -> {s} bits {d}", .{ + @tagName(reg.class()), + vec_bits, + }), + }, + .vlmul = .m1, + .vta = true, + .vma = true, + }); } // mv reg, src_reg @@ -5707,21 +6038,31 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError! }, .register_pair => return func.fail("genSetReg should we allow reg -> reg_pair?", .{}), .load_frame => |frame| { - _ = try func.addInst(.{ - .tag = .pseudo, - .ops = .pseudo_load_rm, - .data = .{ .rm = .{ - .r = reg, - .m = .{ - .base = .{ .frame = frame.index }, - .mod = .{ - .size = func.memSize(ty), - .unsigned = ty.isUnsignedInt(zcu), - .disp = frame.off, + if (reg.class() == .vector) { + // vectors don't support an offset memory load so we need to put the true + // address into a register before loading from it. + const addr_reg, const addr_lock = try func.allocReg(.int); + defer func.register_manager.unlockReg(addr_lock); + + try func.genCopy(ty, .{ .register = addr_reg }, src_mcv.address()); + try func.genCopy(ty, .{ .register = reg }, .{ .indirect = .{ .reg = addr_reg } }); + } else { + _ = try func.addInst(.{ + .tag = .pseudo, + .ops = .pseudo_load_rm, + .data = .{ .rm = .{ + .r = reg, + .m = .{ + .base = .{ .frame = frame.index }, + .mod = .{ + .size = func.memSize(ty), + .unsigned = ty.isUnsignedInt(zcu), + .disp = frame.off, + }, }, - }, - } }, - }); + } }, + }); + } }, .memory => |addr| { try func.genSetReg(ty, reg, .{ .immediate = addr }); @@ -5740,45 +6081,89 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError! _ = try func.addInst(.{ .tag = .pseudo, .ops = .pseudo_lea_rm, - .data = .{ .rm = .{ - .r = reg, - .m = switch (src_mcv) { - .register_offset => |reg_off| .{ - .base = .{ .reg = reg_off.reg }, - .mod = .{ - .size = func.memSize(ty), - .disp = reg_off.off, - .unsigned = false, + .data = .{ + .rm = .{ + .r = reg, + .m = switch (src_mcv) { + .register_offset => |reg_off| .{ + .base = .{ .reg = reg_off.reg }, + .mod = .{ + .size = .byte, // the size doesn't matter + .disp = reg_off.off, + .unsigned = false, + }, }, - }, - .lea_frame => |frame| .{ - .base = .{ .frame = frame.index }, - .mod = .{ - .size = func.memSize(ty), - .disp = frame.off, - .unsigned = false, + .lea_frame => |frame| .{ + .base = .{ .frame = frame.index }, + .mod = .{ + .size = .byte, // the size doesn't matter + .disp = frame.off, + .unsigned = false, + }, }, + else => unreachable, }, - else => unreachable, }, - } }, + }, }); }, .indirect => |reg_off| { - const float_class = dst_reg_class == .float; + const load_tag: Mir.Inst.Tag = switch (reg.class()) { + .float => switch (abi_size) { + 1 => unreachable, // Zig does not support 8-bit floats + 2 => return func.fail("TODO: genSetReg indirect 16-bit float", .{}), + 4 => .flw, + 8 => .fld, + else => return std.debug.panic("TODO: genSetReg for float size {d}", .{abi_size}), + }, + .int => switch (abi_size) { + 1 => .lb, + 2 => .lh, + 4 => .lw, + 8 => .ld, + else => return std.debug.panic("TODO: genSetReg for int size {d}", .{abi_size}), + }, + .vector => { + assert(reg_off.off == 0); - const load_tag: Mir.Inst.Tag = switch (abi_size) { - 1 => if (float_class) - unreachable // Zig does not support 8-bit floats - else - .lb, - 2 => if (float_class) - return func.fail("TODO: genSetReg indirect 16-bit float", .{}) - else - .lh, - 4 => if (float_class) .flw else .lw, - 8 => if (float_class) .fld else .ld, - else => return std.debug.panic("TODO: genSetReg for size {d}", .{abi_size}), + // There is no vector instruction for loading with an offset to a base register, + // so we need to get an offset register containing the address of the vector first + // and load from it. + const len = ty.vectorLen(zcu); + const elem_ty = ty.childType(zcu); + const elem_size = elem_ty.abiSize(pt); + + try func.setVl(.zero, len, .{ + .vsew = switch (elem_size) { + 1 => .@"8", + 2 => .@"16", + 4 => .@"32", + 8 => .@"64", + else => unreachable, + }, + .vlmul = .m1, + .vma = true, + .vta = true, + }); + + _ = try func.addInst(.{ + .tag = .pseudo, + .ops = .pseudo_load_rm, + .data = .{ .rm = .{ + .r = reg, + .m = .{ + .base = .{ .reg = reg_off.reg }, + .mod = .{ + .size = func.memSize(elem_ty), + .unsigned = false, + .disp = 0, + }, + }, + } }, + }); + + return; + }, }; _ = try func.addInst(.{ @@ -5793,7 +6178,6 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError! }, .lea_symbol => |sym_off| { assert(sym_off.off == 0); - const atom_index = try func.symbolIndex(); _ = try func.addInst(.{ @@ -5826,6 +6210,8 @@ fn genSetMem( src_mcv: MCValue, ) InnerError!void { const pt = func.pt; + const zcu = pt.zcu; + const abi_size: u32 = @intCast(ty.abiSize(pt)); const dst_ptr_mcv: MCValue = switch (base) { .reg => |base_reg| .{ .register_offset = .{ .reg = base_reg, .off = disp } }, @@ -5838,11 +6224,17 @@ fn genSetMem( .dead, .reserved_frame, => unreachable, - .undef => try func.genInlineMemset( - dst_ptr_mcv, - src_mcv, - .{ .immediate = abi_size }, - ), + .undef => |sym_index| { + if (sym_index) |index| { + return func.genSetMem(base, disp, ty, .{ .load_symbol = .{ .sym = index } }); + } + + try func.genInlineMemset( + dst_ptr_mcv, + src_mcv, + .{ .immediate = abi_size }, + ); + }, .register_offset, .memory, .indirect, @@ -5853,12 +6245,12 @@ fn genSetMem( => switch (abi_size) { 0 => {}, 1, 2, 4, 8 => { - // no matter what type, it should use an integer register - const src_reg = try func.copyToTmpRegister(Type.usize, src_mcv); - const src_lock = func.register_manager.lockRegAssumeUnused(src_reg); + const reg = try func.register_manager.allocReg(null, abi.Registers.Integer.temporary); + const src_lock = func.register_manager.lockRegAssumeUnused(reg); defer func.register_manager.unlockReg(src_lock); - try func.genSetMem(base, disp, ty, .{ .register = src_reg }); + try func.genSetReg(ty, reg, src_mcv); + try func.genSetMem(base, disp, ty, .{ .register = reg }); }, else => try func.genInlineMemcpy( dst_ptr_mcv, @@ -5867,6 +6259,44 @@ fn genSetMem( ), }, .register => |reg| { + if (reg.class() == .vector) { + const addr_reg = try func.copyToTmpRegister(Type.usize, dst_ptr_mcv); + + const num_elem = ty.vectorLen(zcu); + const elem_size = ty.childType(zcu).bitSize(pt); + + try func.setVl(.zero, num_elem, .{ + .vsew = switch (elem_size) { + 8 => .@"8", + 16 => .@"16", + 32 => .@"32", + 64 => .@"64", + else => unreachable, + }, + .vlmul = .m1, + .vma = true, + .vta = true, + }); + + _ = try func.addInst(.{ + .tag = .pseudo, + .ops = .pseudo_store_rm, + .data = .{ .rm = .{ + .r = reg, + .m = .{ + .base = .{ .reg = addr_reg }, + .mod = .{ + .disp = 0, + .size = func.memSize(ty.childType(zcu)), + .unsigned = false, + }, + }, + } }, + }); + + return; + } + const mem_size = switch (base) { .frame => |base_fi| mem_size: { assert(disp >= 0); @@ -6042,19 +6472,161 @@ fn airCmpxchg(func: *Func, inst: Air.Inst.Index) !void { } fn airAtomicRmw(func: *Func, inst: Air.Inst.Index) !void { - _ = inst; - return func.fail("TODO implement airCmpxchg for {}", .{func.target.cpu.arch}); + const pt = func.pt; + const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; + const extra = func.air.extraData(Air.AtomicRmw, pl_op.payload).data; + + const op = extra.op(); + const order = extra.ordering(); + + const ptr_ty = func.typeOf(pl_op.operand); + const ptr_mcv = try func.resolveInst(pl_op.operand); + + const val_ty = func.typeOf(extra.operand); + const val_size = val_ty.abiSize(pt); + const val_mcv = try func.resolveInst(extra.operand); + + if (!math.isPowerOfTwo(val_size)) + return func.fail("TODO: airAtomicRmw non-pow 2", .{}); + + switch (val_ty.zigTypeTag(pt.zcu)) { + .Int => {}, + inline .Bool, .Float, .Enum, .Pointer => |ty| return func.fail("TODO: airAtomicRmw {s}", .{@tagName(ty)}), + else => unreachable, + } + + switch (val_size) { + 1, 2 => return func.fail("TODO: airAtomicRmw Int {}", .{val_size}), + 4, 8 => {}, + else => unreachable, + } + + const ptr_register, const ptr_lock = try func.promoteReg(ptr_ty, ptr_mcv); + defer if (ptr_lock) |lock| func.register_manager.unlockReg(lock); + + const val_register, const val_lock = try func.promoteReg(val_ty, val_mcv); + defer if (val_lock) |lock| func.register_manager.unlockReg(lock); + + const result_mcv = try func.allocRegOrMem(val_ty, inst, true); + assert(result_mcv == .register); // should fit into 8 bytes + + const aq, const rl = switch (order) { + .unordered => unreachable, + .monotonic => .{ false, false }, + .acquire => .{ true, false }, + .release => .{ false, true }, + .acq_rel => .{ true, true }, + .seq_cst => .{ true, true }, + }; + + _ = try func.addInst(.{ + .tag = .pseudo, + .ops = .pseudo_amo, + .data = .{ .amo = .{ + .rd = result_mcv.register, + .rs1 = ptr_register, + .rs2 = val_register, + .aq = if (aq) .aq else .none, + .rl = if (rl) .rl else .none, + .op = switch (op) { + .Xchg => .SWAP, + .Add => .ADD, + .Sub => return func.fail("TODO: airAtomicRmw SUB", .{}), + .And => .AND, + .Nand => return func.fail("TODO: airAtomicRmw NAND", .{}), + .Or => .OR, + .Xor => .XOR, + .Max => .MAX, + .Min => .MIN, + }, + .ty = val_ty, + } }, + }); + + return func.finishAir(inst, result_mcv, .{ pl_op.operand, extra.operand, .none }); } fn airAtomicLoad(func: *Func, inst: Air.Inst.Index) !void { - _ = inst; - return func.fail("TODO implement airAtomicLoad for {}", .{func.target.cpu.arch}); + const zcu = func.pt.zcu; + const atomic_load = func.air.instructions.items(.data)[@intFromEnum(inst)].atomic_load; + const order: std.builtin.AtomicOrder = atomic_load.order; + + const ptr_ty = func.typeOf(atomic_load.ptr); + const elem_ty = ptr_ty.childType(zcu); + const ptr_mcv = try func.resolveInst(atomic_load.ptr); + + const result_mcv = try func.allocRegOrMem(elem_ty, inst, true); + assert(result_mcv == .register); // should be less than 8 bytes + + if (order == .seq_cst) { + _ = try func.addInst(.{ + .tag = .pseudo, + .ops = .pseudo_fence, + .data = .{ + .fence = .{ + .pred = .rw, + .succ = .rw, + .fm = .none, + }, + }, + }); + } + + try func.load(result_mcv, ptr_mcv, ptr_ty); + + switch (order) { + // Don't guarnetee other memory operations to be ordered after the load. + .unordered => {}, + .monotonic => {}, + // Make sure all previous reads happen before any reading or writing accurs. + .seq_cst, .acquire => { + _ = try func.addInst(.{ + .tag = .pseudo, + .ops = .pseudo_fence, + .data = .{ + .fence = .{ + .pred = .r, + .succ = .rw, + .fm = .none, + }, + }, + }); + }, + else => unreachable, + } + + return func.finishAir(inst, result_mcv, .{ atomic_load.ptr, .none, .none }); } fn airAtomicStore(func: *Func, inst: Air.Inst.Index, order: std.builtin.AtomicOrder) !void { - _ = inst; - _ = order; - return func.fail("TODO implement airAtomicStore for {}", .{func.target.cpu.arch}); + const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; + + const ptr_ty = func.typeOf(bin_op.lhs); + const ptr_mcv = try func.resolveInst(bin_op.lhs); + + const val_ty = func.typeOf(bin_op.rhs); + const val_mcv = try func.resolveInst(bin_op.rhs); + + switch (order) { + .unordered, .monotonic => {}, + .release, .seq_cst => { + _ = try func.addInst(.{ + .tag = .pseudo, + .ops = .pseudo_fence, + .data = .{ + .fence = .{ + .pred = .rw, + .succ = .w, + .fm = .none, + }, + }, + }); + }, + else => unreachable, + } + + try func.store(ptr_mcv, val_mcv, ptr_ty, val_ty); + return func.finishAir(inst, .unreach, .{ bin_op.lhs, bin_op.rhs, .none }); } fn airMemset(func: *Func, inst: Air.Inst.Index, safety: bool) !void { @@ -6441,18 +7013,39 @@ fn getResolvedInstValue(func: *Func, inst: Air.Inst.Index) *InstTracking { fn genTypedValue(func: *Func, val: Value) InnerError!MCValue { const pt = func.pt; - const zcu = pt.zcu; + const gpa = func.gpa; + + const owner_decl_index = pt.zcu.funcOwnerDeclIndex(func.func_index); + const lf = func.bin_file; + const src_loc = func.src_loc; + + if (val.isUndef(pt.zcu)) { + const local_sym_index = lf.lowerUnnamedConst(pt, val, owner_decl_index) catch |err| { + const msg = try ErrorMsg.create(gpa, src_loc, "lowering unnamed undefined constant failed: {s}", .{@errorName(err)}); + func.err_msg = msg; + return error.CodegenFail; + }; + switch (lf.tag) { + .elf => { + const elf_file = lf.cast(link.File.Elf).?; + const local = elf_file.symbol(local_sym_index); + return MCValue{ .undef = local.esym_index }; + }, + else => unreachable, + } + } + const result = try codegen.genTypedValue( - func.bin_file, + lf, pt, - func.src_loc, + src_loc, val, - zcu.funcOwnerDeclIndex(func.func_index), + owner_decl_index, ); const mcv: MCValue = switch (result) { .mcv => |mcv| switch (mcv) { .none => .none, - .undef => .undef, + .undef => unreachable, .load_symbol => |sym_index| .{ .load_symbol = .{ .sym = sym_index } }, .immediate => |imm| .{ .immediate = imm }, .memory => |addr| .{ .memory = addr }, @@ -6670,9 +7263,7 @@ fn parseRegName(name: []const u8) ?Register { } fn typeOf(func: *Func, inst: Air.Inst.Ref) Type { - const pt = func.pt; - const zcu = pt.zcu; - return func.air.typeOf(inst, &zcu.intern_pool); + return func.air.typeOf(inst, &func.pt.zcu.intern_pool); } fn typeOfIndex(func: *Func, inst: Air.Inst.Index) Type { diff --git a/src/arch/riscv64/Emit.zig b/src/arch/riscv64/Emit.zig index 64798fd255..bc972e86b9 100644 --- a/src/arch/riscv64/Emit.zig +++ b/src/arch/riscv64/Emit.zig @@ -26,7 +26,7 @@ pub fn emitMir(emit: *Emit) Error!void { mir_index, @intCast(emit.code.items.len), ); - const lowered = try emit.lower.lowerMir(mir_index); + const lowered = try emit.lower.lowerMir(mir_index, .{ .allow_frame_locs = true }); var lowered_relocs = lowered.relocs; for (lowered.insts, 0..) |lowered_inst, lowered_index| { const start_offset: u32 = @intCast(emit.code.items.len); @@ -75,7 +75,7 @@ pub fn emitMir(emit: *Emit) Error!void { .r_info = (@as(u64, @intCast(symbol.sym_index)) << 32) | lo_r_type, .r_addend = 0, }); - } else return emit.fail("TODO: load_symbol_reloc non-ELF", .{}); + } else unreachable; }, .call_extern_fn_reloc => |symbol| { if (emit.bin_file.cast(link.File.Elf)) |elf_file| { diff --git a/src/arch/riscv64/Encoding.zig b/src/arch/riscv64/Encoding.zig index b280b8a483..f4684fe42b 100644 --- a/src/arch/riscv64/Encoding.zig +++ b/src/arch/riscv64/Encoding.zig @@ -2,37 +2,55 @@ mnemonic: Mnemonic, data: Data, const OpCode = enum(u7) { - OP = 0b0110011, - OP_IMM = 0b0010011, - OP_IMM_32 = 0b0011011, - OP_32 = 0b0111011, - - BRANCH = 0b1100011, LOAD = 0b0000011, - STORE = 0b0100011, - SYSTEM = 0b1110011, - - OP_FP = 0b1010011, LOAD_FP = 0b0000111, - STORE_FP = 0b0100111, - - JALR = 0b1100111, + MISC_MEM = 0b0001111, + OP_IMM = 0b0010011, AUIPC = 0b0010111, + OP_IMM_32 = 0b0011011, + STORE = 0b0100011, + STORE_FP = 0b0100111, + AMO = 0b0101111, + OP_V = 0b1010111, + OP = 0b0110011, + OP_32 = 0b0111011, LUI = 0b0110111, + MADD = 0b1000011, + MSUB = 0b1000111, + NMSUB = 0b1001011, + NMADD = 0b1001111, + OP_FP = 0b1010011, + OP_IMM_64 = 0b1011011, + BRANCH = 0b1100011, + JALR = 0b1100111, JAL = 0b1101111, - NONE = 0b0000000, + SYSTEM = 0b1110011, + OP_64 = 0b1111011, + NONE = 0b00000000, }; -const Fmt = enum(u2) { +const FpFmt = enum(u2) { /// 32-bit single-precision S = 0b00, /// 64-bit double-precision D = 0b01, - _reserved = 0b10, + + // H = 0b10, unused in the G extension + /// 128-bit quad-precision Q = 0b11, }; +const AmoWidth = enum(u3) { + W = 0b010, + D = 0b011, +}; + +const FenceMode = enum(u4) { + none = 0b0000, + tso = 0b1000, +}; + const Enc = struct { opcode: OpCode, @@ -42,11 +60,19 @@ const Enc = struct { funct3: u3, funct7: u7, }, + amo: struct { + funct5: u5, + width: AmoWidth, + }, + fence: struct { + funct3: u3, + fm: FenceMode, + }, /// funct5 + rm + fmt fmt: struct { funct5: u5, rm: u3, - fmt: Fmt, + fmt: FpFmt, }, /// funct3 f: struct { @@ -58,9 +84,55 @@ const Enc = struct { funct3: u3, has_5: bool, }, + vecls: struct { + width: VecWidth, + umop: Umop, + vm: bool, + mop: Mop, + mew: bool, + nf: u3, + }, + vecmath: struct { + vm: bool, + funct6: u6, + funct3: VecType, + }, /// U-type none, }, + + const Mop = enum(u2) { + unit = 0b00, + unord = 0b01, + stride = 0b10, + ord = 0b11, + }; + + const Umop = enum(u5) { + unit = 0b00000, + whole = 0b01000, + mask = 0b01011, + fault = 0b10000, + }; + + const VecWidth = enum(u3) { + // zig fmt: off + @"8" = 0b000, + @"16" = 0b101, + @"32" = 0b110, + @"64" = 0b111, + // zig fmt: on + }; + + const VecType = enum(u3) { + OPIVV = 0b000, + OPFVV = 0b001, + OPMVV = 0b010, + OPIVI = 0b011, + OPIVX = 0b100, + OPFVF = 0b101, + OPMVX = 0b110, + }; }; pub const Mnemonic = enum { @@ -90,6 +162,9 @@ pub const Mnemonic = enum { addi, jalr, + vsetivli, + vsetvli, + // U Type lui, auipc, @@ -130,6 +205,8 @@ pub const Mnemonic = enum { ebreak, unimp, + csrrs, + // M extension mul, mulw, @@ -192,6 +269,58 @@ pub const Mnemonic = enum { fsgnjnd, fsgnjxd, + // V Extension + vle8v, + vle16v, + vle32v, + vle64v, + + vse8v, + vse16v, + vse32v, + vse64v, + + vsoxei8v, + + vaddvv, + vsubvv, + + vfaddvv, + vfsubvv, + + vadcvv, + + vmvvx, + + vslidedownvx, + + // MISC + fence, + fencetso, + + // AMO + amoswapw, + amoaddw, + amoandw, + amoorw, + amoxorw, + amomaxw, + amominw, + amomaxuw, + amominuw, + + amoswapd, + amoaddd, + amoandd, + amoord, + amoxord, + amomaxd, + amomind, + amomaxud, + amominud, + + // TODO: Q extension + pub fn encoding(mnem: Mnemonic) Enc { return switch (mnem) { // zig fmt: off @@ -322,14 +451,25 @@ pub const Mnemonic = enum { // LOAD_FP .flw => .{ .opcode = .LOAD_FP, .data = .{ .f = .{ .funct3 = 0b010 } } }, - .fld => .{ .opcode = .LOAD_FP, .data = .{ .f = .{ .funct3 = 0b011 } } }, + .fld => .{ .opcode = .LOAD_FP, .data = .{ .f = .{ .funct3 = 0b011 } } }, + + .vle8v => .{ .opcode = .LOAD_FP, .data = .{ .vecls = .{ .width = .@"8", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } }, + .vle16v => .{ .opcode = .LOAD_FP, .data = .{ .vecls = .{ .width = .@"16", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } }, + .vle32v => .{ .opcode = .LOAD_FP, .data = .{ .vecls = .{ .width = .@"32", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } }, + .vle64v => .{ .opcode = .LOAD_FP, .data = .{ .vecls = .{ .width = .@"64", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } }, // STORE_FP - .fsw => .{ .opcode = .STORE_FP, .data = .{ .f = .{ .funct3 = 0b010 } } }, - .fsd => .{ .opcode = .STORE_FP, .data = .{ .f = .{ .funct3 = 0b011 } } }, + .fsw => .{ .opcode = .STORE_FP, .data = .{ .f = .{ .funct3 = 0b010 } } }, + .fsd => .{ .opcode = .STORE_FP, .data = .{ .f = .{ .funct3 = 0b011 } } }, + .vse8v => .{ .opcode = .STORE_FP, .data = .{ .vecls = .{ .width = .@"8", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } }, + .vse16v => .{ .opcode = .STORE_FP, .data = .{ .vecls = .{ .width = .@"16", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } }, + .vse32v => .{ .opcode = .STORE_FP, .data = .{ .vecls = .{ .width = .@"32", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } }, + .vse64v => .{ .opcode = .STORE_FP, .data = .{ .vecls = .{ .width = .@"64", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } }, + + .vsoxei8v => .{ .opcode = .STORE_FP, .data = .{ .vecls = .{ .width = .@"8", .umop = .unit, .vm = true, .mop = .ord, .mew = false, .nf = 0b000 } } }, // JALR @@ -360,6 +500,8 @@ pub const Mnemonic = enum { .ecall => .{ .opcode = .SYSTEM, .data = .{ .f = .{ .funct3 = 0b000 } } }, .ebreak => .{ .opcode = .SYSTEM, .data = .{ .f = .{ .funct3 = 0b000 } } }, + + .csrrs => .{ .opcode = .SYSTEM, .data = .{ .f = .{ .funct3 = 0b010 } } }, // NONE @@ -367,6 +509,52 @@ pub const Mnemonic = enum { .unimp => .{ .opcode = .NONE, .data = .{ .f = .{ .funct3 = 0b000 } } }, + // MISC_MEM + + .fence => .{ .opcode = .MISC_MEM, .data = .{ .fence = .{ .funct3 = 0b000, .fm = .none } } }, + .fencetso => .{ .opcode = .MISC_MEM, .data = .{ .fence = .{ .funct3 = 0b000, .fm = .tso } } }, + + + // AMO + + .amoaddw => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b00000 } } }, + .amoswapw => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b00001 } } }, + // LR.W + // SC.W + .amoxorw => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b00100 } } }, + .amoandw => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b01100 } } }, + .amoorw => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b01000 } } }, + .amominw => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b10000 } } }, + .amomaxw => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b10100 } } }, + .amominuw => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b11000 } } }, + .amomaxuw => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b11100 } } }, + + .amoaddd => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b00000 } } }, + .amoswapd => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b00001 } } }, + // LR.D + // SC.D + .amoxord => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b00100 } } }, + .amoandd => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b01100 } } }, + .amoord => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b01000 } } }, + .amomind => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b10000 } } }, + .amomaxd => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b10100 } } }, + .amominud => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b11000 } } }, + .amomaxud => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b11100 } } }, + + // OP_V + .vsetivli => .{ .opcode = .OP_V, .data = .{ .f = .{ .funct3 = 0b111 } } }, + .vsetvli => .{ .opcode = .OP_V, .data = .{ .f = .{ .funct3 = 0b111 } } }, + .vaddvv => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b000000, .funct3 = .OPIVV } } }, + .vsubvv => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b000010, .funct3 = .OPIVV } } }, + + .vfaddvv => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b000000, .funct3 = .OPFVV } } }, + .vfsubvv => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b000010, .funct3 = .OPFVV } } }, + + .vadcvv => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b010000, .funct3 = .OPMVV } } }, + .vmvvx => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b010111, .funct3 = .OPIVX } } }, + + .vslidedownvx => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b001111, .funct3 = .OPIVX } } }, + // zig fmt: on }; } @@ -380,8 +568,8 @@ pub const InstEnc = enum { B, U, J, - - /// extras that have unusual op counts + fence, + amo, system, pub fn fromMnemonic(mnem: Mnemonic) InstEnc { @@ -410,6 +598,10 @@ pub const InstEnc = enum { .flw, .fld, + + .csrrs, + .vsetivli, + .vsetvli, => .I, .lui, @@ -503,26 +695,73 @@ pub const InstEnc = enum { .fsgnjxs, .fsgnjxd, + + .vle8v, + .vle16v, + .vle32v, + .vle64v, + + .vse8v, + .vse16v, + .vse32v, + .vse64v, + + .vsoxei8v, + + .vaddvv, + .vsubvv, + .vfaddvv, + .vfsubvv, + .vadcvv, + .vmvvx, + .vslidedownvx, => .R, .ecall, .ebreak, .unimp, => .system, + + .fence, + .fencetso, + => .fence, + + .amoswapw, + .amoaddw, + .amoandw, + .amoorw, + .amoxorw, + .amomaxw, + .amominw, + .amomaxuw, + .amominuw, + + .amoswapd, + .amoaddd, + .amoandd, + .amoord, + .amoxord, + .amomaxd, + .amomind, + .amomaxud, + .amominud, + => .amo, }; } - pub fn opsList(enc: InstEnc) [4]std.meta.FieldEnum(Operand) { + pub fn opsList(enc: InstEnc) [5]std.meta.FieldEnum(Operand) { return switch (enc) { // zig fmt: off - .R => .{ .reg, .reg, .reg, .none }, - .R4 => .{ .reg, .reg, .reg, .reg }, - .I => .{ .reg, .reg, .imm, .none }, - .S => .{ .reg, .reg, .imm, .none }, - .B => .{ .reg, .reg, .imm, .none }, - .U => .{ .reg, .imm, .none, .none }, - .J => .{ .reg, .imm, .none, .none }, - .system => .{ .none, .none, .none, .none }, + .R => .{ .reg, .reg, .reg, .none, .none, }, + .R4 => .{ .reg, .reg, .reg, .reg, .none, }, + .I => .{ .reg, .reg, .imm, .none, .none, }, + .S => .{ .reg, .reg, .imm, .none, .none, }, + .B => .{ .reg, .reg, .imm, .none, .none, }, + .U => .{ .reg, .imm, .none, .none, .none, }, + .J => .{ .reg, .imm, .none, .none, .none, }, + .system => .{ .none, .none, .none, .none, .none, }, + .fence => .{ .barrier, .barrier, .none, .none, .none, }, + .amo => .{ .reg, .reg, .reg, .barrier, .barrier }, // zig fmt: on }; } @@ -584,20 +823,38 @@ pub const Data = union(InstEnc) { imm1_10: u10, imm20: u1, }, - system: void, + fence: packed struct { + opcode: u7, + rd: u5 = 0, + funct3: u3, + rs1: u5 = 0, + succ: u4, + pred: u4, + fm: u4, + }, + amo: packed struct { + opcode: u7, + rd: u5, + funct3: u3, + rs1: u5, + rs2: u5, + rl: bool, + aq: bool, + funct5: u5, + }, + system: u32, + + comptime { + for (std.meta.fields(Data)) |field| { + assert(@bitSizeOf(field.type) == 32); + } + } pub fn toU32(self: Data) u32 { return switch (self) { - // zig fmt: off - .R => |v| @bitCast(v), - .R4 => |v| @bitCast(v), - .I => |v| @bitCast(v), - .S => |v| @bitCast(v), - .B => |v| @as(u32, @intCast(v.opcode)) + (@as(u32, @intCast(v.imm11)) << 7) + (@as(u32, @intCast(v.imm1_4)) << 8) + (@as(u32, @intCast(v.funct3)) << 12) + (@as(u32, @intCast(v.rs1)) << 15) + (@as(u32, @intCast(v.rs2)) << 20) + (@as(u32, @intCast(v.imm5_10)) << 25) + (@as(u32, @intCast(v.imm12)) << 31), - .U => |v| @bitCast(v), - .J => |v| @bitCast(v), + .fence => |v| @as(u32, @intCast(v.opcode)) + (@as(u32, @intCast(v.rd)) << 7) + (@as(u32, @intCast(v.funct3)) << 12) + (@as(u32, @intCast(v.rs1)) << 15) + (@as(u32, @intCast(v.succ)) << 20) + (@as(u32, @intCast(v.pred)) << 24) + (@as(u32, @intCast(v.fm)) << 28), + inline else => |v| @bitCast(v), .system => unreachable, - // zig fmt: on }; } @@ -628,6 +885,25 @@ pub const Data = union(InstEnc) { }, }; }, + .csrrs => { + assert(ops.len == 3); + + const csr = ops[0].csr; + const rs1 = ops[1].reg; + const rd = ops[2].reg; + + return .{ + .I = .{ + .rd = rd.encodeId(), + .rs1 = rs1.encodeId(), + + .imm0_11 = @intFromEnum(csr), + + .opcode = @intFromEnum(enc.opcode), + .funct3 = enc.data.f.funct3, + }, + }; + }, else => {}, } @@ -654,6 +930,25 @@ pub const Data = union(InstEnc) { .funct3 = fmt.rm, .funct7 = (@as(u7, fmt.funct5) << 2) | @intFromEnum(fmt.fmt), }, + .vecls => |vec| .{ + .rd = ops[0].reg.encodeId(), + .rs1 = ops[1].reg.encodeId(), + + .rs2 = @intFromEnum(vec.umop), + + .opcode = @intFromEnum(enc.opcode), + .funct3 = @intFromEnum(vec.width), + .funct7 = (@as(u7, vec.nf) << 4) | (@as(u7, @intFromBool(vec.mew)) << 3) | (@as(u7, @intFromEnum(vec.mop)) << 1) | @intFromBool(vec.vm), + }, + .vecmath => |vec| .{ + .rd = ops[0].reg.encodeId(), + .rs1 = ops[1].reg.encodeId(), + .rs2 = ops[2].reg.encodeId(), + + .opcode = @intFromEnum(enc.opcode), + .funct3 = @intFromEnum(vec.funct3), + .funct7 = (@as(u7, vec.funct6) << 1) | @intFromBool(vec.vm), + }, else => unreachable, }, }; @@ -748,7 +1043,48 @@ pub const Data = union(InstEnc) { }, }; }, + .fence => { + assert(ops.len == 2); + const succ = ops[0].barrier; + const pred = ops[1].barrier; + + return .{ + .fence = .{ + .succ = @intFromEnum(succ), + .pred = @intFromEnum(pred), + + .opcode = @intFromEnum(enc.opcode), + .funct3 = enc.data.fence.funct3, + .fm = @intFromEnum(enc.data.fence.fm), + }, + }; + }, + .amo => { + assert(ops.len == 5); + + const rd = ops[0].reg; + const rs1 = ops[1].reg; + const rs2 = ops[2].reg; + const rl = ops[3].barrier; + const aq = ops[4].barrier; + + return .{ + .amo = .{ + .rd = rd.encodeId(), + .rs1 = rs1.encodeId(), + .rs2 = rs2.encodeId(), + + // TODO: https://github.com/ziglang/zig/issues/20113 + .rl = if (rl == .rl) true else false, + .aq = if (aq == .aq) true else false, + + .opcode = @intFromEnum(enc.opcode), + .funct3 = @intFromEnum(enc.data.amo.width), + .funct5 = enc.data.amo.funct5, + }, + }; + }, else => std.debug.panic("TODO: construct {s}", .{@tagName(inst_enc)}), } } diff --git a/src/arch/riscv64/Lower.zig b/src/arch/riscv64/Lower.zig index f71311ff97..012f520485 100644 --- a/src/arch/riscv64/Lower.zig +++ b/src/arch/riscv64/Lower.zig @@ -40,7 +40,9 @@ pub const Reloc = struct { }; /// The returned slice is overwritten by the next call to lowerMir. -pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index) Error!struct { +pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index, options: struct { + allow_frame_locs: bool, +}) Error!struct { insts: []const Instruction, relocs: []const Reloc, } { @@ -69,64 +71,102 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index) Error!struct { .pseudo_load_rm, .pseudo_store_rm => { const rm = inst.data.rm; - const frame_loc = rm.m.toFrameLoc(lower.mir); + const frame_loc: Mir.FrameLoc = if (options.allow_frame_locs) + rm.m.toFrameLoc(lower.mir) + else + .{ .base = .s0, .disp = 0 }; switch (inst.ops) { .pseudo_load_rm => { const dest_reg = rm.r; const dest_reg_class = dest_reg.class(); - const float = dest_reg_class == .float; const src_size = rm.m.mod.size; const unsigned = rm.m.mod.unsigned; - const tag: Encoding.Mnemonic = if (!float) - switch (src_size) { + const tag: Encoding.Mnemonic = switch (dest_reg_class) { + .int => switch (src_size) { .byte => if (unsigned) .lbu else .lb, .hword => if (unsigned) .lhu else .lh, .word => if (unsigned) .lwu else .lw, .dword => .ld, - } - else switch (src_size) { - .byte => unreachable, // Zig does not support 8-bit floats - .hword => return lower.fail("TODO: lowerMir pseudo_load_rm support 16-bit floats", .{}), - .word => .flw, - .dword => .fld, + }, + .float => switch (src_size) { + .byte => unreachable, // Zig does not support 8-bit floats + .hword => return lower.fail("TODO: lowerMir pseudo_load_rm support 16-bit floats", .{}), + .word => .flw, + .dword => .fld, + }, + .vector => switch (src_size) { + .byte => .vle8v, + .hword => .vle32v, + .word => .vle32v, + .dword => .vle64v, + }, }; - try lower.emit(tag, &.{ - .{ .reg = rm.r }, - .{ .reg = frame_loc.base }, - .{ .imm = Immediate.s(frame_loc.disp) }, - }); + switch (dest_reg_class) { + .int, .float => { + try lower.emit(tag, &.{ + .{ .reg = rm.r }, + .{ .reg = frame_loc.base }, + .{ .imm = Immediate.s(frame_loc.disp) }, + }); + }, + .vector => { + assert(frame_loc.disp == 0); + try lower.emit(tag, &.{ + .{ .reg = rm.r }, + .{ .reg = frame_loc.base }, + .{ .reg = .zero }, + }); + }, + } }, .pseudo_store_rm => { const src_reg = rm.r; const src_reg_class = src_reg.class(); - const float = src_reg_class == .float; - // TODO: do we actually need this? are all stores not usize? const dest_size = rm.m.mod.size; - const tag: Encoding.Mnemonic = if (!float) - switch (dest_size) { + const tag: Encoding.Mnemonic = switch (src_reg_class) { + .int => switch (dest_size) { .byte => .sb, .hword => .sh, .word => .sw, .dword => .sd, - } - else switch (dest_size) { - .byte => unreachable, // Zig does not support 8-bit floats - .hword => return lower.fail("TODO: lowerMir pseudo_load_rm support 16-bit floats", .{}), - .word => .fsw, - .dword => .fsd, + }, + .float => switch (dest_size) { + .byte => unreachable, // Zig does not support 8-bit floats + .hword => return lower.fail("TODO: lowerMir pseudo_store_rm support 16-bit floats", .{}), + .word => .fsw, + .dword => .fsd, + }, + .vector => switch (dest_size) { + .byte => .vse8v, + .hword => .vse16v, + .word => .vse32v, + .dword => .vse64v, + }, }; - try lower.emit(tag, &.{ - .{ .reg = frame_loc.base }, - .{ .reg = rm.r }, - .{ .imm = Immediate.s(frame_loc.disp) }, - }); + switch (src_reg_class) { + .int, .float => { + try lower.emit(tag, &.{ + .{ .reg = frame_loc.base }, + .{ .reg = rm.r }, + .{ .imm = Immediate.s(frame_loc.disp) }, + }); + }, + .vector => { + assert(frame_loc.disp == 0); + try lower.emit(tag, &.{ + .{ .reg = rm.r }, + .{ .reg = frame_loc.base }, + .{ .reg = .zero }, + }); + }, + } }, else => unreachable, } @@ -138,34 +178,47 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index) Error!struct { const dst_class = rr.rd.class(); const src_class = rr.rs.class(); - assert(dst_class == src_class); - - switch (dst_class) { - .float => { - try lower.emit(if (lower.hasFeature(.d)) .fsgnjnd else .fsgnjns, &.{ - .{ .reg = rr.rd }, - .{ .reg = rr.rs }, - .{ .reg = rr.rs }, - }); + switch (src_class) { + .float => switch (dst_class) { + .float => { + try lower.emit(if (lower.hasFeature(.d)) .fsgnjnd else .fsgnjns, &.{ + .{ .reg = rr.rd }, + .{ .reg = rr.rs }, + .{ .reg = rr.rs }, + }); + }, + .int, .vector => return lower.fail("TODO: lowerMir pseudo_mv float -> {s}", .{@tagName(dst_class)}), }, - .int => { - try lower.emit(.addi, &.{ - .{ .reg = rr.rd }, - .{ .reg = rr.rs }, - .{ .imm = Immediate.s(0) }, - }); + .int => switch (dst_class) { + .int => { + try lower.emit(.addi, &.{ + .{ .reg = rr.rd }, + .{ .reg = rr.rs }, + .{ .imm = Immediate.s(0) }, + }); + }, + .vector => { + try lower.emit(.vmvvx, &.{ + .{ .reg = rr.rd }, + .{ .reg = rr.rs }, + .{ .reg = .x0 }, + }); + }, + .float => return lower.fail("TODO: lowerMir pseudo_mv int -> {s}", .{@tagName(dst_class)}), + }, + .vector => switch (dst_class) { + .int => { + try lower.emit(.vadcvv, &.{ + .{ .reg = rr.rd }, + .{ .reg = .zero }, + .{ .reg = rr.rs }, + }); + }, + .float, .vector => return lower.fail("TODO: lowerMir pseudo_mv vector -> {s}", .{@tagName(dst_class)}), }, } }, - .pseudo_ret => { - try lower.emit(.jalr, &.{ - .{ .reg = .zero }, - .{ .reg = .ra }, - .{ .imm = Immediate.s(0) }, - }); - }, - .pseudo_j => { try lower.emit(.jal, &.{ .{ .reg = .zero }, @@ -204,7 +257,10 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index) Error!struct { const rm = inst.data.rm; assert(rm.r.class() == .int); - const frame = rm.m.toFrameLoc(lower.mir); + const frame: Mir.FrameLoc = if (options.allow_frame_locs) + rm.m.toFrameLoc(lower.mir) + else + .{ .base = .s0, .disp = 0 }; try lower.emit(.addi, &.{ .{ .reg = rm.r }, @@ -371,6 +427,7 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index) Error!struct { }); }, }, + .vector => return lower.fail("TODO: lowerMir pseudo_cmp vector", .{}), } }, @@ -378,7 +435,14 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index) Error!struct { const rr = inst.data.rr; assert(rr.rs.class() == .int and rr.rd.class() == .int); - try lower.emit(.xori, &.{ + // mask out any other bits that aren't the boolean + try lower.emit(.andi, &.{ + .{ .reg = rr.rs }, + .{ .reg = rr.rs }, + .{ .imm = Immediate.s(1) }, + }); + + try lower.emit(.sltiu, &.{ .{ .reg = rr.rd }, .{ .reg = rr.rs }, .{ .imm = Immediate.s(1) }, @@ -405,6 +469,44 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index) Error!struct { }); }, + .pseudo_amo => { + const amo = inst.data.amo; + const is_d = amo.ty.abiSize(pt) == 8; + const is_un = amo.ty.isUnsignedInt(pt.zcu); + + const mnem: Encoding.Mnemonic = switch (amo.op) { + // zig fmt: off + .SWAP => if (is_d) .amoswapd else .amoswapw, + .ADD => if (is_d) .amoaddd else .amoaddw, + .AND => if (is_d) .amoandd else .amoandw, + .OR => if (is_d) .amoord else .amoorw, + .XOR => if (is_d) .amoxord else .amoxorw, + .MAX => if (is_d) if (is_un) .amomaxud else .amomaxd else if (is_un) .amomaxuw else .amomaxw, + .MIN => if (is_d) if (is_un) .amominud else .amomind else if (is_un) .amominuw else .amominw, + // zig fmt: on + }; + + try lower.emit(mnem, &.{ + .{ .reg = inst.data.amo.rd }, + .{ .reg = inst.data.amo.rs1 }, + .{ .reg = inst.data.amo.rs2 }, + .{ .barrier = inst.data.amo.rl }, + .{ .barrier = inst.data.amo.aq }, + }); + }, + + .pseudo_fence => { + const fence = inst.data.fence; + + try lower.emit(switch (fence.fm) { + .tso => .fencetso, + .none => .fence, + }, &.{ + .{ .barrier = fence.succ }, + .{ .barrier = fence.pred }, + }); + }, + else => return lower.fail("TODO lower: psuedo {s}", .{@tagName(inst.ops)}), }, } @@ -447,6 +549,11 @@ fn generic(lower: *Lower, inst: Mir.Inst) Error!void { .{ .reg = inst.data.r_type.rs1 }, .{ .reg = inst.data.r_type.rs2 }, }, + .csr => &.{ + .{ .csr = inst.data.csr.csr }, + .{ .reg = inst.data.csr.rs1 }, + .{ .reg = inst.data.csr.rd }, + }, else => return lower.fail("TODO: generic lower ops {s}", .{@tagName(inst.ops)}), }); } @@ -473,17 +580,22 @@ fn pushPopRegList(lower: *Lower, comptime spilling: bool, reg_list: Mir.Register while (it.next()) |i| { const frame = lower.mir.frame_locs.get(@intFromEnum(bits.FrameIndex.spill_frame)); const reg = abi.Registers.all_preserved[i]; + const reg_class = reg.class(); - const is_float_reg = reg_class == .float; + const load_inst: Encoding.Mnemonic, const store_inst: Encoding.Mnemonic = switch (reg_class) { + .int => .{ .ld, .sd }, + .float => .{ .fld, .fsd }, + .vector => unreachable, + }; if (spilling) { - try lower.emit(if (is_float_reg) .fsd else .sd, &.{ + try lower.emit(store_inst, &.{ .{ .reg = frame.base }, .{ .reg = abi.Registers.all_preserved[i] }, .{ .imm = Immediate.s(frame.disp + reg_i) }, }); } else { - try lower.emit(if (is_float_reg) .fld else .ld, &.{ + try lower.emit(load_inst, &.{ .{ .reg = abi.Registers.all_preserved[i] }, .{ .reg = frame.base }, .{ .imm = Immediate.s(frame.disp + reg_i) }, diff --git a/src/arch/riscv64/Mir.zig b/src/arch/riscv64/Mir.zig index 80a533d880..1478bf5d5b 100644 --- a/src/arch/riscv64/Mir.zig +++ b/src/arch/riscv64/Mir.zig @@ -31,6 +31,7 @@ pub const Inst = struct { @"and", andi, + xori, xor, @"or", @@ -133,6 +134,19 @@ pub const Inst = struct { fltd, fled, + // Zicsr Extension Instructions + csrrs, + + // V Extension Instructions + vsetvli, + vsetivli, + vsetvl, + vaddvv, + vfaddvv, + vsubvv, + vfsubvv, + vslidedownvx, + /// A pseudo-instruction. Used for anything that isn't 1:1 with an /// assembly instruction. pseudo, @@ -142,91 +156,57 @@ pub const Inst = struct { /// this union. `Ops` determines which union field is active, as well as /// how to interpret the data within. pub const Data = union { - /// No additional data - /// - /// Used by e.g. ebreak nop: void, - /// Another instruction. - /// - /// Used by e.g. b inst: Index, - /// Index into `extra`. Meaning of what can be found there is context-dependent. - /// - /// Used by e.g. load_memory payload: u32, - r_type: struct { rd: Register, rs1: Register, rs2: Register, }, - i_type: struct { rd: Register, rs1: Register, imm12: Immediate, }, - s_type: struct { rs1: Register, rs2: Register, imm5: Immediate, imm7: Immediate, }, - b_type: struct { rs1: Register, rs2: Register, inst: Inst.Index, }, - u_type: struct { rd: Register, imm20: Immediate, }, - j_type: struct { rd: Register, inst: Inst.Index, }, - - /// Debug info: line and column - /// - /// Used by e.g. pseudo_dbg_line pseudo_dbg_line_column: struct { line: u32, column: u32, }, - - // Custom types to be lowered - - /// Register + Memory rm: struct { r: Register, m: Memory, }, - reg_list: Mir.RegisterList, - - /// A register - /// - /// Used by e.g. blr reg: Register, - - /// Two registers - /// - /// Used by e.g. mv rr: struct { rd: Register, rs: Register, }, - fabs: struct { rd: Register, rs: Register, bits: u16, }, - compare: struct { rd: Register, rs1: Register, @@ -241,11 +221,32 @@ pub const Inst = struct { }, ty: Type, }, - reloc: struct { atom_index: u32, sym_index: u32, }, + fence: struct { + pred: Barrier, + succ: Barrier, + fm: enum { + none, + tso, + }, + }, + amo: struct { + rd: Register, + rs1: Register, + rs2: Register, + aq: Barrier, + rl: Barrier, + op: AmoOp, + ty: Type, + }, + csr: struct { + csr: CSR, + rs1: Register, + rd: Register, + }, }; pub const Ops = enum { @@ -270,6 +271,9 @@ pub const Inst = struct { /// Another instruction. inst, + /// Control and Status Register Instruction. + csr, + /// Pseudo-instruction that will generate a backpatched /// function prologue. pseudo_prologue, @@ -298,11 +302,6 @@ pub const Inst = struct { /// Uses `rm` payload. pseudo_lea_rm, - /// Shorthand for returning, aka jumping to ra register. - /// - /// Uses nop payload. - pseudo_ret, - /// Jumps. Uses `inst` payload. pseudo_j, @@ -326,19 +325,19 @@ pub const Inst = struct { pseudo_spill_regs, pseudo_compare, + + /// NOT operation on booleans. Does an `andi reg, reg, 1` to mask out any other bits from the boolean. pseudo_not, /// Generates an auipc + jalr pair, with a R_RISCV_CALL_PLT reloc pseudo_extern_fn_reloc, - }; - // Make sure we don't accidentally make instructions bigger than expected. - // Note that in Debug builds, Zig is allowed to insert a secret field for safety checks. - // comptime { - // if (builtin.mode != .Debug) { - // assert(@sizeOf(Inst) == 8); - // } - // } + /// IORW, IORW + pseudo_fence, + + /// Ordering, Src, Addr, Dest + pseudo_amo, + }; pub fn format( inst: Inst, @@ -365,6 +364,28 @@ pub const FrameLoc = struct { disp: i32, }; +pub const Barrier = enum(u4) { + // Fence + w = 0b0001, + r = 0b0010, + rw = 0b0011, + + // Amo + none, + aq, + rl, +}; + +pub const AmoOp = enum(u5) { + SWAP, + ADD, + AND, + OR, + XOR, + MAX, + MIN, +}; + /// Returns the requested data, as well as the new index which is at the start of the /// trailers for the object. pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } { @@ -437,6 +458,7 @@ const assert = std.debug.assert; const bits = @import("bits.zig"); const Register = bits.Register; +const CSR = bits.CSR; const Immediate = bits.Immediate; const Memory = bits.Memory; const FrameIndex = bits.FrameIndex; diff --git a/src/arch/riscv64/abi.zig b/src/arch/riscv64/abi.zig index 0a41b61d79..3f5f7f6744 100644 --- a/src/arch/riscv64/abi.zig +++ b/src/arch/riscv64/abi.zig @@ -193,6 +193,15 @@ pub fn classifySystem(ty: Type, pt: Zcu.PerThread) [8]SystemClass { } return memory_class; }, + .Vector => { + // we pass vectors through integer registers if they are small enough to fit. + const vec_bits = ty.totalVectorBits(pt); + if (vec_bits <= 64) { + result[0] = .integer; + return result; + } + return memory_class; + }, else => |bad_ty| std.debug.panic("classifySystem {s}", .{@tagName(bad_ty)}), } } @@ -254,15 +263,15 @@ fn classifyStruct( } } -const allocatable_registers = Registers.Integer.all_regs ++ Registers.Float.all_regs; +const allocatable_registers = Registers.Integer.all_regs ++ Registers.Float.all_regs ++ Registers.Vector.all_regs; pub const RegisterManager = RegisterManagerFn(@import("CodeGen.zig"), Register, &allocatable_registers); -// Register classes const RegisterBitSet = RegisterManager.RegisterBitSet; pub const RegisterClass = enum { int, float, + vector, }; pub const Registers = struct { @@ -322,6 +331,19 @@ pub const Registers = struct { pub const all_regs = callee_preserved_regs ++ function_arg_regs ++ temporary_regs; }; + + pub const Vector = struct { + pub const general_purpose = initRegBitSet(Integer.all_regs.len + Float.all_regs.len, all_regs.len); + + // zig fmt: off + pub const all_regs = [_]Register{ + .v0, .v1, .v2, .v3, .v4, .v5, .v6, .v7, + .v8, .v9, .v10, .v11, .v12, .v13, .v14, .v15, + .v16, .v17, .v18, .v19, .v20, .v21, .v22, .v23, + .v24, .v25, .v26, .v27, .v28, .v29, .v30, .v31, + }; + // zig fmt: on + }; }; fn initRegBitSet(start: usize, length: usize) RegisterBitSet { diff --git a/src/arch/riscv64/bits.zig b/src/arch/riscv64/bits.zig index 75c93d7f13..97db43b50e 100644 --- a/src/arch/riscv64/bits.zig +++ b/src/arch/riscv64/bits.zig @@ -41,7 +41,7 @@ pub const Memory = struct { 2...2 => .hword, 3...4 => .word, 5...8 => .dword, - else => unreachable, + else => std.debug.panic("fromByteSize {}", .{size}), }; } @@ -128,6 +128,12 @@ pub const Immediate = union(enum) { } }; +pub const CSR = enum(u12) { + vl = 0xC20, + vtype = 0xC21, + vlenb = 0xC22, +}; + pub const Register = enum(u8) { // zig fmt: off @@ -169,6 +175,13 @@ pub const Register = enum(u8) { f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, f29, f30, f31, + + // V extension registers + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, v15, + v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, + // zig fmt: on /// in RISC-V registers are stored as 5 bit IDs and a register can have @@ -180,11 +193,12 @@ pub const Register = enum(u8) { /// The goal of this function is to return the same ID for `zero` and `x0` but two /// seperate IDs for `x0` and `f0`. We will assume that each register set has 32 registers /// and is repeated twice, once for the named version, once for the number version. - pub fn id(reg: Register) u7 { + pub fn id(reg: Register) u8 { const base = switch (@intFromEnum(reg)) { // zig fmt: off @intFromEnum(Register.zero) ... @intFromEnum(Register.x31) => @intFromEnum(Register.zero), @intFromEnum(Register.ft0) ... @intFromEnum(Register.f31) => @intFromEnum(Register.ft0), + @intFromEnum(Register.v0) ... @intFromEnum(Register.v31) => @intFromEnum(Register.v0), else => unreachable, // zig fmt: on }; @@ -207,6 +221,7 @@ pub const Register = enum(u8) { // zig fmt: off @intFromEnum(Register.zero) ... @intFromEnum(Register.x31) => 64, @intFromEnum(Register.ft0) ... @intFromEnum(Register.f31) => if (Target.riscv.featureSetHas(features, .d)) 64 else 32, + @intFromEnum(Register.v0) ... @intFromEnum(Register.v31) => 256, // TODO: look at suggestVectorSize else => unreachable, // zig fmt: on }; @@ -217,6 +232,7 @@ pub const Register = enum(u8) { // zig fmt: off @intFromEnum(Register.zero) ... @intFromEnum(Register.x31) => .int, @intFromEnum(Register.ft0) ... @intFromEnum(Register.f31) => .float, + @intFromEnum(Register.v0) ... @intFromEnum(Register.v31) => .vector, else => unreachable, // zig fmt: on }; @@ -272,3 +288,27 @@ pub const Symbol = struct { /// Index into the linker's symbol table. sym_index: u32, }; + +pub const VType = packed struct(u8) { + vlmul: VlMul, + vsew: VSew, + vta: bool, + vma: bool, +}; + +const VSew = enum(u3) { + @"8" = 0b000, + @"16" = 0b001, + @"32" = 0b010, + @"64" = 0b011, +}; + +const VlMul = enum(u3) { + mf8 = 0b101, + mf4 = 0b110, + mf2 = 0b111, + m1 = 0b000, + m2 = 0b001, + m4 = 0b010, + m8 = 0b011, +}; diff --git a/src/arch/riscv64/encoder.zig b/src/arch/riscv64/encoder.zig index 54d1549ebe..2ef5ba03ec 100644 --- a/src/arch/riscv64/encoder.zig +++ b/src/arch/riscv64/encoder.zig @@ -1,26 +1,30 @@ pub const Instruction = struct { encoding: Encoding, - ops: [3]Operand = .{.none} ** 3, + ops: [5]Operand = .{.none} ** 5, pub const Operand = union(enum) { none, reg: Register, + csr: CSR, mem: Memory, imm: Immediate, + barrier: Mir.Barrier, }; pub fn new(mnemonic: Encoding.Mnemonic, ops: []const Operand) !Instruction { const encoding = (try Encoding.findByMnemonic(mnemonic, ops)) orelse { - std.log.err("no encoding found for: {s} [{s} {s} {s}]", .{ + std.log.err("no encoding found for: {s} [{s} {s} {s} {s} {s}]", .{ @tagName(mnemonic), @tagName(if (ops.len > 0) ops[0] else .none), @tagName(if (ops.len > 1) ops[1] else .none), @tagName(if (ops.len > 2) ops[2] else .none), + @tagName(if (ops.len > 3) ops[3] else .none), + @tagName(if (ops.len > 4) ops[4] else .none), }); return error.InvalidInstruction; }; - var result_ops: [3]Operand = .{.none} ** 3; + var result_ops: [5]Operand = .{.none} ** 5; @memcpy(result_ops[0..ops.len], ops); return .{ @@ -53,7 +57,9 @@ pub const Instruction = struct { .none => unreachable, // it's sliced out above .reg => |reg| try writer.writeAll(@tagName(reg)), .imm => |imm| try writer.print("{d}", .{imm.asSigned(64)}), - .mem => unreachable, // there is no "mem" operand in the actual instructions + .mem => try writer.writeAll("mem"), + .barrier => |barrier| try writer.writeAll(@tagName(barrier)), + .csr => |csr| try writer.writeAll(@tagName(csr)), } } } @@ -67,6 +73,7 @@ const bits = @import("bits.zig"); const Encoding = @import("Encoding.zig"); const Register = bits.Register; +const CSR = bits.CSR; const Memory = bits.Memory; const Immediate = bits.Immediate; diff --git a/src/arch/x86_64/Lower.zig b/src/arch/x86_64/Lower.zig index 73f4f1d617..bf074ff98f 100644 --- a/src/arch/x86_64/Lower.zig +++ b/src/arch/x86_64/Lower.zig @@ -65,6 +65,8 @@ pub const Reloc = struct { }; }; +const Options = struct { allow_frame_locs: bool }; + /// The returned slice is overwritten by the next call to lowerMir. pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index) Error!struct { insts: []const Instruction, diff --git a/src/codegen.zig b/src/codegen.zig index f279141604..33a0b556f7 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -987,8 +987,9 @@ pub fn genTypedValue( log.debug("genTypedValue: val = {}", .{val.fmtValue(pt)}); - if (val.isUndef(zcu)) + if (val.isUndef(zcu)) { return GenResult.mcv(.undef); + } const owner_decl = zcu.declPtr(owner_decl_index); const namespace = zcu.namespacePtr(owner_decl.src_namespace); diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index e8ec5eb492..8fee0d64df 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -540,8 +540,8 @@ inline fn isGlobal(index: Symbol.Index) bool { pub fn symbol(self: ZigObject, index: Symbol.Index) Symbol.Index { const actual_index = index & symbol_mask; - if (isGlobal(index)) return self.global_symbols.items[actual_index]; - return self.local_symbols.items[actual_index]; + if (isGlobal(index)) return self.globals()[actual_index]; + return self.locals()[actual_index]; } pub fn elfSym(self: *ZigObject, index: Symbol.Index) *elf.Elf64_Sym { @@ -1334,11 +1334,15 @@ fn lowerConst( const sym_index = try self.addAtom(elf_file); - const res = try codegen.generateSymbol(&elf_file.base, pt, src_loc, val, &code_buffer, .{ - .none = {}, - }, .{ - .parent_atom_index = sym_index, - }); + const res = try codegen.generateSymbol( + &elf_file.base, + pt, + src_loc, + val, + &code_buffer, + .{ .none = {} }, + .{ .parent_atom_index = sym_index }, + ); const code = switch (res) { .ok => code_buffer.items, .fail => |em| return .{ .fail = em }, diff --git a/test/behavior/array.zig b/test/behavior/array.zig index f5fa95c770..f6d59ae0fa 100644 --- a/test/behavior/array.zig +++ b/test/behavior/array.zig @@ -580,7 +580,6 @@ test "type coercion of anon struct literal to array" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const S = struct { const U = union { @@ -1011,7 +1010,6 @@ test "union that needs padding bytes inside an array" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const B = union(enum) { D: u8, diff --git a/test/behavior/atomics.zig b/test/behavior/atomics.zig index 87ccc72174..c9d7254ef2 100644 --- a/test/behavior/atomics.zig +++ b/test/behavior/atomics.zig @@ -42,7 +42,6 @@ test "fence" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; var x: i32 = 1234; @fence(.seq_cst); @@ -188,21 +187,6 @@ test "atomic store" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - - var x: u32 = 0; - @atomicStore(u32, &x, 1, .seq_cst); - try expect(@atomicLoad(u32, &x, .seq_cst) == 1); - @atomicStore(u32, &x, 12345678, .seq_cst); - try expect(@atomicLoad(u32, &x, .seq_cst) == 12345678); -} - -test "atomic store comptime" { - if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; try comptime testAtomicStore(); try testAtomicStore(); @@ -451,7 +435,6 @@ test "return @atomicStore, using it as a void value" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const S = struct { const A = struct { diff --git a/test/behavior/bitcast.zig b/test/behavior/bitcast.zig index 6d513a4ac7..cc84a948d1 100644 --- a/test/behavior/bitcast.zig +++ b/test/behavior/bitcast.zig @@ -192,7 +192,6 @@ test "@bitCast packed structs at runtime and comptime" { test "@bitCast extern structs at runtime and comptime" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const Full = extern struct { number: u16, @@ -227,7 +226,6 @@ test "bitcast packed struct to integer and back" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const LevelUpMove = packed struct { move_id: u9, diff --git a/test/behavior/builtin_functions_returning_void_or_noreturn.zig b/test/behavior/builtin_functions_returning_void_or_noreturn.zig index 712a24b950..92617ef3a4 100644 --- a/test/behavior/builtin_functions_returning_void_or_noreturn.zig +++ b/test/behavior/builtin_functions_returning_void_or_noreturn.zig @@ -11,7 +11,6 @@ test { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; var val: u8 = undefined; try testing.expectEqual({}, @atomicStore(u8, &val, 0, .unordered)); diff --git a/test/behavior/enum.zig b/test/behavior/enum.zig index 8b55ff26bc..7972135bfa 100644 --- a/test/behavior/enum.zig +++ b/test/behavior/enum.zig @@ -908,7 +908,6 @@ test "enum literal casting to tagged union" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const Arch = union(enum) { x86_64, diff --git a/test/behavior/error.zig b/test/behavior/error.zig index e0f0b224c1..9b6300e743 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -535,7 +535,6 @@ test "return result loc as peer result loc in inferred error set function" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index 2be355e064..fb916df3cf 100644 --- a/test/behavior/eval.zig +++ b/test/behavior/eval.zig @@ -395,7 +395,6 @@ test "return 0 from function that has u0 return type" { test "statically initialized struct" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; st_init_str_foo.x += 1; try expect(st_init_str_foo.x == 14); @@ -446,7 +445,6 @@ test "binary math operator in partially inlined function" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; var s: [4]u32 = undefined; var b: [16]u8 = undefined; diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig index 65d889776a..f644465d63 100644 --- a/test/behavior/floatop.zig +++ b/test/behavior/floatop.zig @@ -281,7 +281,6 @@ test "@sqrt f32/f64" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; try testSqrt(f32); try comptime testSqrt(f32); diff --git a/test/behavior/inline_switch.zig b/test/behavior/inline_switch.zig index d0621ad198..59dc7096b9 100644 --- a/test/behavior/inline_switch.zig +++ b/test/behavior/inline_switch.zig @@ -49,7 +49,6 @@ test "inline switch unions" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; var x: U = .a; _ = &x; diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 44daec9ed5..73b9b2f62e 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -1269,7 +1269,6 @@ test "@subWithOverflow" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; { var a: u8 = 1; diff --git a/test/behavior/optional.zig b/test/behavior/optional.zig index f9c71d3bea..bb1d9b7027 100644 --- a/test/behavior/optional.zig +++ b/test/behavior/optional.zig @@ -397,7 +397,6 @@ test "array of optional unaligned types" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const Enum = enum { one, two, three }; diff --git a/test/behavior/packed-struct.zig b/test/behavior/packed-struct.zig index 88e5457627..60fcd5e9f6 100644 --- a/test/behavior/packed-struct.zig +++ b/test/behavior/packed-struct.zig @@ -785,7 +785,6 @@ test "nested packed struct field access test" { test "nested packed struct at non-zero offset" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const Pair = packed struct(u24) { a: u16 = 0, diff --git a/test/behavior/reflection.zig b/test/behavior/reflection.zig index f07b5a512e..aea84bc45a 100644 --- a/test/behavior/reflection.zig +++ b/test/behavior/reflection.zig @@ -28,7 +28,6 @@ fn dummy(a: bool, b: i32, c: f32) i32 { test "reflection: @field" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; var f = Foo{ .one = 42, diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index 520c3ff409..97617a1fd1 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -875,7 +875,6 @@ test "packed struct field passed to generic function" { test "anonymous struct literal syntax" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const S = struct { const Point = struct { @@ -985,7 +984,6 @@ test "struct with union field" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const Value = struct { ref: u32 = 2, @@ -1368,7 +1366,6 @@ test "store to comptime field" { test "struct field init value is size of the struct" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const namespace = struct { const S = extern struct { diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index 8c5fcda8c2..1275d0f433 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -256,7 +256,6 @@ test "switch on enum using pointer capture" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; try testSwitchEnumPtrCapture(); try comptime testSwitchEnumPtrCapture(); @@ -693,7 +692,6 @@ test "switch capture copies its payload" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { diff --git a/test/behavior/this.zig b/test/behavior/this.zig index 3638168a4f..3f8fe13316 100644 --- a/test/behavior/this.zig +++ b/test/behavior/this.zig @@ -27,7 +27,6 @@ test "this refer to module call private fn" { test "this refer to container" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; var pt: Point(i32) = undefined; pt.x = 12; diff --git a/test/behavior/tuple.zig b/test/behavior/tuple.zig index 142768454d..5cab5e9375 100644 --- a/test/behavior/tuple.zig +++ b/test/behavior/tuple.zig @@ -131,7 +131,6 @@ test "tuple initializer for var" { test "array-like initializer for tuple types" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const T = @Type(.{ .Struct = .{ diff --git a/test/behavior/type.zig b/test/behavior/type.zig index 1a36f576f1..ba29640774 100644 --- a/test/behavior/type.zig +++ b/test/behavior/type.zig @@ -383,7 +383,6 @@ test "Type.Union" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const Untagged = @Type(.{ .Union = .{ diff --git a/test/behavior/union.zig b/test/behavior/union.zig index d2009f57df..413362caba 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -43,7 +43,6 @@ test "basic unions" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; var foo = Foo{ .int = 1 }; try expect(foo.int == 1); @@ -276,7 +275,6 @@ test "comparison between union and enum literal" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; try testComparison(); try comptime testComparison(); @@ -292,7 +290,6 @@ test "cast union to tag type of union" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; try testCastUnionToTag(); try comptime testCastUnionToTag(); @@ -314,7 +311,6 @@ test "cast tag type of union to union" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; var x: Value2 = Letter2.B; _ = &x; @@ -331,7 +327,6 @@ test "implicit cast union to its tag type" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; var x: Value2 = Letter2.B; _ = &x; @@ -353,7 +348,6 @@ test "constant packed union" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; try testConstPackedUnion(&[_]PackThis{PackThis{ .StringLiteral = 1 }}); } @@ -503,7 +497,6 @@ test "initialize global array of union" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; glbl_array[1] = FooUnion{ .U1 = 2 }; glbl_array[0] = FooUnion{ .U0 = 1 }; @@ -515,7 +508,6 @@ test "update the tag value for zero-sized unions" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const S = union(enum) { U0: void, @@ -636,7 +628,6 @@ test "tagged union with all void fields but a meaningful tag" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const S = struct { const B = union(enum) { @@ -758,7 +749,6 @@ test "@intFromEnum works on unions" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const Bar = union(enum) { A: bool, @@ -874,7 +864,6 @@ test "@unionInit can modify a union type" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const UnionInitEnum = union(enum) { Boolean: bool, @@ -898,7 +887,6 @@ test "@unionInit can modify a pointer value" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const UnionInitEnum = union(enum) { Boolean: bool, @@ -1089,7 +1077,6 @@ test "switching on non exhaustive union" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const S = struct { const E = enum(u8) { @@ -1199,7 +1186,6 @@ test "global variable struct contains union initialized to non-most-aligned fiel if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const T = struct { const U = union(enum) { @@ -1352,7 +1338,6 @@ test "noreturn field in union" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const U = union(enum) { a: u32, @@ -1434,7 +1419,6 @@ test "union field ptr - zero sized payload" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const U = union { foo: void, @@ -1449,7 +1433,6 @@ test "union field ptr - zero sized field" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const U = union { foo: void, @@ -1589,7 +1572,6 @@ test "reinterpreting enum value inside packed union" { test "access the tag of a global tagged union" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const U = union(enum) { a, @@ -1601,7 +1583,6 @@ test "access the tag of a global tagged union" { test "coerce enum literal to union in result loc" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const U = union(enum) { a, @@ -1864,7 +1845,6 @@ test "reinterpret extern union" { test "reinterpret packed union" { if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const U = packed union { foo: u8, @@ -2044,7 +2024,6 @@ test "extern union initialized via reintepreted struct field initializer" { test "packed union initialized via reintepreted struct field initializer" { if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd }; @@ -2065,7 +2044,6 @@ test "packed union initialized via reintepreted struct field initializer" { test "store of comptime reinterpreted memory to extern union" { if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd }; @@ -2088,7 +2066,6 @@ test "store of comptime reinterpreted memory to extern union" { test "store of comptime reinterpreted memory to packed union" { if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd }; diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index 5a4da799c2..2de751f708 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -97,29 +97,40 @@ test "vector int operators" { test "vector float operators" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArmOrThumb()) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| { - const S = struct { - fn doTheTest() !void { - var v: @Vector(4, T) = [4]T{ 10, 20, 30, 40 }; - var x: @Vector(4, T) = [4]T{ 1, 2, 3, 4 }; - _ = .{ &v, &x }; - try expect(mem.eql(T, &@as([4]T, v + x), &[4]T{ 11, 22, 33, 44 })); - try expect(mem.eql(T, &@as([4]T, v - x), &[4]T{ 9, 18, 27, 36 })); - try expect(mem.eql(T, &@as([4]T, v * x), &[4]T{ 10, 40, 90, 160 })); - try expect(mem.eql(T, &@as([4]T, -x), &[4]T{ -1, -2, -3, -4 })); - } - }; - try S.doTheTest(); - try comptime S.doTheTest(); - } + const S = struct { + fn doTheTest(T: type) !void { + var v: @Vector(4, T) = .{ 10, 20, 30, 40 }; + var x: @Vector(4, T) = .{ 1, 2, 3, 4 }; + _ = .{ &v, &x }; + try expectEqual(v + x, .{ 11, 22, 33, 44 }); + try expectEqual(v - x, .{ 9, 18, 27, 36 }); + try expectEqual(v * x, .{ 10, 40, 90, 160 }); + try expectEqual(-x, .{ -1, -2, -3, -4 }); + } + }; + + try S.doTheTest(f32); + try comptime S.doTheTest(f32); + + try S.doTheTest(f64); + try comptime S.doTheTest(f64); + + try S.doTheTest(f16); + try comptime S.doTheTest(f16); + + try S.doTheTest(f80); + try comptime S.doTheTest(f80); + + try S.doTheTest(f128); + try comptime S.doTheTest(f128); } test "vector bit operators" { @@ -1245,7 +1256,6 @@ test "array of vectors is copied" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const Vec3 = @Vector(3, i32); var points = [_]Vec3{ @@ -1316,6 +1326,7 @@ test "zero multiplicand" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const zeros = @Vector(2, u32){ 0.0, 0.0 }; var ones = @Vector(2, u32){ 1.0, 1.0 }; @@ -1410,7 +1421,6 @@ test "store to vector in slice" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; var v = [_]@Vector(3, f32){ .{ 1, 1, 1 }, @@ -1608,7 +1618,6 @@ test "@reduce on bool vector" { test "bitcast vector to array of smaller vectors" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const u8x32 = @Vector(32, u8); const u8x64 = @Vector(64, u8); diff --git a/test/tests.zig b/test/tests.zig index 0862f8deb0..7116233c7e 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -436,11 +436,12 @@ const test_targets = blk: { //}, .{ - .target = .{ - .cpu_arch = .riscv64, - .os_tag = .linux, - .abi = .musl, - }, + .target = std.Target.Query.parse( + .{ + .arch_os_abi = "riscv64-linux-musl", + .cpu_features = "baseline+v", + }, + ) catch @panic("OOM"), .use_llvm = false, .use_lld = false, },