Commit 93e9c7a963

David Rubin <daviru007@icloud.com>
2024-07-10 07:53:55
riscv: implement `@clz`
1 parent 8d30fc4
Changed files (5)
src/arch/riscv64/CodeGen.zig
@@ -1123,6 +1123,7 @@ const required_features = [_]Target.riscv.Feature{
     .a,
     .zicsr,
     .v,
+    .zbb,
 };
 
 fn gen(func: *Func) !void {
@@ -2385,20 +2386,24 @@ fn genBinOp(
         .mul,
         .mul_wrap,
         .rem,
+        .div_trunc,
         => {
-            if (!math.isPowerOfTwo(bit_size))
-                return func.fail(
-                    "TODO: genBinOp {s} non-pow 2, found {}",
-                    .{ @tagName(tag), bit_size },
-                );
-
             switch (tag) {
                 .rem,
+                .div_trunc,
                 => {
-                    try func.truncateRegister(lhs_ty, lhs_reg);
-                    try func.truncateRegister(rhs_ty, rhs_reg);
+                    if (!math.isPowerOfTwo(bit_size)) {
+                        try func.truncateRegister(lhs_ty, lhs_reg);
+                        try func.truncateRegister(rhs_ty, rhs_reg);
+                    }
+                },
+                else => {
+                    if (!math.isPowerOfTwo(bit_size))
+                        return func.fail(
+                            "TODO: genBinOp verify {s} non-pow 2, found {}",
+                            .{ @tagName(tag), bit_size },
+                        );
                 },
-                else => {},
             }
 
             switch (lhs_ty.zigTypeTag(zcu)) {
@@ -2420,8 +2425,12 @@ fn genBinOp(
                             else => unreachable,
                         },
                         .rem => switch (bit_size) {
-                            64 => if (is_unsigned) .remu else .rem,
-                            else => if (is_unsigned) .remuw else .remu,
+                            8, 16, 32 => if (is_unsigned) .remuw else .remw,
+                            else => if (is_unsigned) .remu else .rem,
+                        },
+                        .div_trunc => switch (bit_size) {
+                            8, 16, 32 => if (is_unsigned) .divuw else .divw,
+                            else => if (is_unsigned) .divu else .div,
                         },
                         else => unreachable,
                     };
@@ -2455,7 +2464,7 @@ fn genBinOp(
                             64 => .fmuld,
                             else => unreachable,
                         },
-                        else => unreachable,
+                        else => return func.fail("TODO: genBinOp {s} Float", .{@tagName(tag)}),
                     };
 
                     _ = try func.addInst(.{
@@ -2588,46 +2597,6 @@ fn genBinOp(
             }
         },
 
-        .div_trunc,
-        => {
-            if (!math.isPowerOfTwo(bit_size))
-                return func.fail(
-                    "TODO: genBinOp {s} non-pow 2, found {}",
-                    .{ @tagName(tag), bit_size },
-                );
-
-            const mir_tag: Mir.Inst.Tag = switch (tag) {
-                .div_trunc => switch (bit_size) {
-                    8, 16, 32 => if (is_unsigned) .divuw else .divw,
-                    64 => if (is_unsigned) .divu else .div,
-                    else => unreachable,
-                },
-                else => unreachable,
-            };
-
-            _ = try func.addInst(.{
-                .tag = mir_tag,
-                .ops = .rrr,
-                .data = .{
-                    .r_type = .{
-                        .rd = dst_reg,
-                        .rs1 = lhs_reg,
-                        .rs2 = rhs_reg,
-                    },
-                },
-            });
-
-            if (!is_unsigned) {
-                // truncate when the instruction is larger than the bit size.
-                switch (bit_size) {
-                    8, 16 => try func.truncateRegister(lhs_ty, dst_reg),
-                    32 => {}, // divw affects the first 32-bits
-                    64 => {}, // div affects the entire register
-                    else => unreachable,
-                }
-            }
-        },
-
         .shr,
         .shr_exact,
         .shl,
@@ -3740,7 +3709,59 @@ fn airGetUnionTag(func: *Func, inst: Air.Inst.Index) !void {
 
 fn airClz(func: *Func, inst: Air.Inst.Index) !void {
     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 airClz for {}", .{func.target.cpu.arch});
+    const operand = try func.resolveInst(ty_op.operand);
+    const ty = func.typeOf(ty_op.operand);
+
+    const result: MCValue = if (func.liveness.isUnused(inst)) .unreach else result: {
+        const src_reg, const src_lock = try func.promoteReg(ty, operand);
+        defer if (src_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 bit_size = ty.bitSize(func.pt);
+        if (!math.isPowerOfTwo(bit_size)) try func.truncateRegister(ty, src_reg);
+
+        if (bit_size > 64) {
+            return func.fail("TODO: airClz > 64 bits, found {d}", .{bit_size});
+        }
+
+        _ = try func.addInst(.{
+            .tag = switch (bit_size) {
+                32 => .clzw,
+                else => .clz,
+            },
+            .ops = .rrr,
+            .data = .{
+                .r_type = .{
+                    .rs2 = .zero, // rs2 is 0 filled in the spec
+                    .rs1 = src_reg,
+                    .rd = dst_reg,
+                },
+            },
+        });
+
+        if (!(bit_size == 32 or bit_size == 64)) {
+            _ = try func.addInst(.{
+                .tag = .addi,
+                .ops = .rri,
+                .data = .{ .i_type = .{
+                    .rd = dst_reg,
+                    .rs1 = dst_reg,
+                    .imm12 = Immediate.s(-@as(i12, @intCast(64 - bit_size % 64))),
+                } },
+            });
+        }
+
+        break :result .{ .register = dst_reg };
+    };
     return func.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
src/arch/riscv64/Encoding.zig
@@ -135,6 +135,7 @@ const Enc = struct {
     };
 };
 
+// TODO: this is basically a copy of the MIR table, we should be able to de-dupe them somehow.
 pub const Mnemonic = enum {
     // base mnemonics
 
@@ -324,6 +325,10 @@ pub const Mnemonic = enum {
 
     // TODO: Q extension
 
+    // Zbb Extension
+    clz,
+    clzw,
+
     pub fn encoding(mnem: Mnemonic) Enc {
         return switch (mnem) {
             // zig fmt: off
@@ -368,6 +373,7 @@ pub const Mnemonic = enum {
             .srli    => .{ .opcode = .OP_IMM, .data = .{ .sh = .{ .typ = 0b000000, .funct3 = 0b101, .has_5 = true } } },
             .srai    => .{ .opcode = .OP_IMM, .data = .{ .sh = .{ .typ = 0b010000, .funct3 = 0b101, .has_5 = true } } },
 
+            .clz     => .{ .opcode = .OP_IMM, .data = .{ .ff = .{ .funct3 = 0b001, .funct7 = 0b0110000 } } },
 
             // OP_IMM_32
 
@@ -375,6 +381,7 @@ pub const Mnemonic = enum {
             .srliw   => .{ .opcode = .OP_IMM_32, .data = .{ .sh = .{ .typ = 0b000000, .funct3 = 0b101, .has_5 = false } } },
             .sraiw   => .{ .opcode = .OP_IMM_32, .data = .{ .sh = .{ .typ = 0b010000, .funct3 = 0b101, .has_5 = false } } },
 
+            .clzw     => .{ .opcode = .OP_IMM_32, .data = .{ .ff = .{ .funct3 = 0b001, .funct7 = 0b0110000 } } },
 
             // OP_32
 
@@ -722,6 +729,9 @@ pub const InstEnc = enum {
             .vadcvv,
             .vmvvx,
             .vslidedownvx,
+
+            .clz,
+            .clzw,
             => .R,
 
             .ecall,
src/arch/riscv64/Mir.zig
@@ -149,6 +149,10 @@ pub const Inst = struct {
         vfmulvv,
         vslidedownvx,
 
+        // Zbb Extension Instructions
+        clz,
+        clzw,
+
         /// A pseudo-instruction. Used for anything that isn't 1:1 with an
         /// assembly instruction.
         pseudo,
test/behavior/math.zig
@@ -65,7 +65,6 @@ test "@clz" {
     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;
 
     try testClz();
     try comptime testClz();
test/tests.zig
@@ -439,7 +439,7 @@ const test_targets = blk: {
             .target = std.Target.Query.parse(
                 .{
                     .arch_os_abi = "riscv64-linux-musl",
-                    .cpu_features = "baseline+v",
+                    .cpu_features = "baseline+v+zbb",
                 },
             ) catch @panic("OOM"),
             .use_llvm = false,