Commit ea084e9519

David Rubin <daviru007@icloud.com>
2024-05-29 07:10:51
riscv: `@atomicLoad` and `@atomicStore`
1 parent d404d8a
Changed files (5)
src/arch/riscv64/CodeGen.zig
@@ -2087,19 +2087,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);
 
-        switch (ty.zigTypeTag(zcu)) {
-            .Bool => {
-                const operand_reg = blk: {
-                    if (operand == .register) break :blk operand.register;
-                    break :blk try func.copyToTmpRegister(ty, 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;
+        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 => {
                 _ = try func.addInst(.{
                     .tag = .pseudo,
                     .ops = .pseudo_not,
@@ -2110,12 +2108,34 @@ fn airNot(func: *Func, inst: Air.Inst.Index) !void {
                         },
                     },
                 });
+            },
+            .Int => {
+                const size = ty.bitSize(zcu);
+                if (!math.isPowerOfTwo(size))
+                    return func.fail("TODO: airNot non-pow 2 int size", .{});
 
-                break :result .{ .register = dst_reg };
+                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,
+                }
             },
-            .Int => return func.fail("TODO: airNot ints", .{}),
             else => unreachable,
         }
+
+        break :result .{ .register = dst_reg };
     };
     return func.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
@@ -5600,17 +5620,24 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
     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 dst_reg_class = reg.class();
 
     switch (src_mcv) {
-        .dead => unreachable,
-        .unreach, .none => return, // Nothing to do.
+        .unreach,
+        .none,
+        .dead,
+        => unreachable,
         .undef => {
             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;
+
+            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);
@@ -6047,14 +6074,82 @@ fn airAtomicRmw(func: *Func, inst: Air.Inst.Index) !void {
 }
 
 fn airAtomicLoad(func: *Func, inst: Air.Inst.Index) !void {
-    _ = inst;
-    return func.fail("TODO implement airAtomicLoad for {}", .{func.target.cpu.arch});
+    const zcu = func.bin_file.comp.module.?;
+    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);
+
+    if (order == .seq_cst) {
+        _ = try func.addInst(.{
+            .tag = .fence,
+            .ops = .fence,
+            .data = .{
+                .fence = .{
+                    .pred = .rw,
+                    .succ = .rw,
+                },
+            },
+        });
+    }
+
+    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 = .fence,
+                .ops = .fence,
+                .data = .{
+                    .fence = .{
+                        .pred = .r,
+                        .succ = .rw,
+                    },
+                },
+            });
+        },
+        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 = .fence,
+                .ops = .fence,
+                .data = .{
+                    .fence = .{
+                        .pred = .rw,
+                        .succ = .w,
+                    },
+                },
+            });
+        },
+        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 {
src/arch/riscv64/encoder.zig
@@ -1,12 +1,13 @@
 pub const Instruction = struct {
     encoding: Encoding,
-    ops: [3]Operand = .{.none} ** 3,
+    ops: [4]Operand = .{.none} ** 4,
 
     pub const Operand = union(enum) {
         none,
         reg: Register,
         mem: Memory,
         imm: Immediate,
+        barrier: Mir.Barrier,
     };
 
     pub fn new(mnemonic: Encoding.Mnemonic, ops: []const Operand) !Instruction {
@@ -20,7 +21,7 @@ pub const Instruction = struct {
             return error.InvalidInstruction;
         };
 
-        var result_ops: [3]Operand = .{.none} ** 3;
+        var result_ops: [4]Operand = .{.none} ** 4;
         @memcpy(result_ops[0..ops.len], ops);
 
         return .{
@@ -54,6 +55,7 @@ pub const Instruction = struct {
                 .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
+                .barrier => |barrier| try writer.writeAll(@tagName(barrier)),
             }
         }
     }
src/arch/riscv64/Encoding.zig
@@ -2,25 +2,30 @@ mnemonic: Mnemonic,
 data: Data,
 
 const OpCode = enum(u7) {
-    OP = 0b0110011,
+    LOAD = 0b0000011,
+    LOAD_FP = 0b0000111,
+    MISC_MEM = 0b0001111,
     OP_IMM = 0b0010011,
+    AUIPC = 0b0010111,
     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,
-    AUIPC = 0b0010111,
+    AMO = 0b0101111,
+    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) {
@@ -28,7 +33,9 @@ const Fmt = enum(u2) {
     S = 0b00,
     /// 64-bit double-precision
     D = 0b01,
-    _reserved = 0b10,
+
+    // H = 0b10, unused in the G extension
+
     /// 128-bit quad-precision
     Q = 0b11,
 };
@@ -192,6 +199,9 @@ pub const Mnemonic = enum {
     fsgnjnd,
     fsgnjxd,
 
+    // MISC
+    fence,
+
     pub fn encoding(mnem: Mnemonic) Enc {
         return switch (mnem) {
             // zig fmt: off
@@ -366,6 +376,10 @@ pub const Mnemonic = enum {
             
             .unimp   => .{ .opcode = .NONE, .data = .{ .f = .{ .funct3 = 0b000 } } },
 
+            // MISC_MEM
+
+            .fence   => .{ .opcode = .MISC_MEM, .data = .{ .f = .{ .funct3 = 0b000 } } },
+        
 
             // zig fmt: on
         };
@@ -380,7 +394,7 @@ pub const InstEnc = enum {
     B,
     U,
     J,
-
+    fence,
     /// extras that have unusual op counts
     system,
 
@@ -509,20 +523,24 @@ pub const InstEnc = enum {
             .ebreak,
             .unimp,
             => .system,
+
+            .fence,
+            => .fence,
         };
     }
 
     pub fn opsList(enc: InstEnc) [4]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 },
+            .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 },
+            .fence  => .{ .barrier, .barrier, .none, .none },
             // zig fmt: on
         };
     }
@@ -584,6 +602,15 @@ pub const Data = union(InstEnc) {
         imm1_10: u10,
         imm20: u1,
     },
+    fence: packed struct {
+        opcode: u7,
+        rd: u5 = 0,
+        funct3: u3,
+        rs1: u5 = 0,
+        succ: u4,
+        pred: u4,
+        _ignored: u4 = 0,
+    },
     system: void,
 
     pub fn toU32(self: Data) u32 {
@@ -596,6 +623,7 @@ pub const Data = union(InstEnc) {
             .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| @bitCast(v),
             .system => unreachable,
             // zig fmt: on
         };
@@ -748,6 +776,22 @@ pub const Data = union(InstEnc) {
                     },
                 };
             },
+            .fence => {
+                assert(ops.len == 2);
+
+                const succ = ops[0];
+                const pred = ops[1];
+
+                return .{
+                    .fence = .{
+                        .succ = @intFromEnum(succ.barrier),
+                        .pred = @intFromEnum(pred.barrier),
+
+                        .opcode = @intFromEnum(enc.opcode),
+                        .funct3 = enc.data.f.funct3,
+                    },
+                };
+            },
 
             else => std.debug.panic("TODO: construct {s}", .{@tagName(inst_enc)}),
         }
src/arch/riscv64/Lower.zig
@@ -378,7 +378,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) },
@@ -447,6 +454,10 @@ fn generic(lower: *Lower, inst: Mir.Inst) Error!void {
             .{ .reg = inst.data.r_type.rs1 },
             .{ .reg = inst.data.r_type.rs2 },
         },
+        .fence => &.{
+            .{ .barrier = inst.data.fence.succ },
+            .{ .barrier = inst.data.fence.pred },
+        },
         else => return lower.fail("TODO: generic lower ops {s}", .{@tagName(inst.ops)}),
     });
 }
src/arch/riscv64/Mir.zig
@@ -31,6 +31,7 @@ pub const Inst = struct {
         @"and",
         andi,
 
+        xori,
         xor,
         @"or",
 
@@ -38,6 +39,8 @@ pub const Inst = struct {
         ecall,
         unimp,
 
+        fence,
+
         add,
         addw,
         sub,
@@ -246,6 +249,11 @@ pub const Inst = struct {
             atom_index: u32,
             sym_index: u32,
         },
+
+        fence: struct {
+            pred: Barrier,
+            succ: Barrier,
+        },
     };
 
     pub const Ops = enum {
@@ -326,10 +334,15 @@ 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,
+
+        /// IORW, IORW
+        fence,
     };
 
     // Make sure we don't accidentally make instructions bigger than expected.
@@ -365,6 +378,12 @@ pub const FrameLoc = struct {
     disp: i32,
 };
 
+pub const Barrier = enum(u4) {
+    r = 0b0001,
+    w = 0b0010,
+    rw = 0b0011,
+};
+
 /// 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 } {