Commit 2fd83d8c0a

David Rubin <daviru007@icloud.com>
2024-04-17 03:48:33
riscv: by-value structs + `@min`
1 parent a30af17
src/arch/riscv64/abi.zig
@@ -3,6 +3,7 @@ const bits = @import("bits.zig");
 const Register = bits.Register;
 const RegisterManagerFn = @import("../../register_manager.zig").RegisterManager;
 const Type = @import("../../type.zig").Type;
+const InternPool = @import("../../InternPool.zig");
 const Module = @import("../../Module.zig");
 const assert = std.debug.assert;
 
@@ -97,7 +98,10 @@ pub fn classifyType(ty: Type, mod: *Module) Class {
 pub fn classifySystem(ty: Type, zcu: *Module) [8]Class {
     const ip = zcu.intern_pool;
     var result = [1]Class{.none} ** 8;
-
+    const memory_class = [_]Class{
+        .memory, .none, .none, .none,
+        .none,   .none, .none, .none,
+    };
     switch (ty.zigTypeTag(zcu)) {
         .Bool, .Void, .NoReturn => {
             result[0] = .integer;
@@ -146,7 +150,12 @@ pub fn classifySystem(ty: Type, zcu: *Module) [8]Class {
             // anyerror!void can fit into one register
             if (payload_bits == 0) return result;
 
-            std.debug.panic("support ErrorUnion payload {}", .{payload_ty.fmt(zcu)});
+            if (payload_bits <= 64) {
+                result[1] = .integer;
+                return result;
+            }
+
+            std.debug.panic("TODO: classifySystem ErrorUnion > 64 bit payload", .{});
         },
         .Struct => {
             const loaded_struct = ip.loadStructType(ty.toIntern());
@@ -158,13 +167,75 @@ pub fn classifySystem(ty: Type, zcu: *Module) [8]Class {
                 if (ty_size > 8) result[1] = .integer;
                 return result;
             }
+            if (ty_size > 64)
+                return memory_class;
 
-            std.debug.panic("support Struct in classifySystem", .{});
+            var byte_offset: u64 = 0;
+            classifyStruct(&result, &byte_offset, loaded_struct, zcu);
+
+            return result;
         },
         else => |bad_ty| std.debug.panic("classifySystem {s}", .{@tagName(bad_ty)}),
     }
 }
 
+fn classifyStruct(
+    result: *[8]Class,
+    byte_offset: *u64,
+    loaded_struct: InternPool.LoadedStructType,
+    zcu: *Module,
+) void {
+    const ip = &zcu.intern_pool;
+    var field_it = loaded_struct.iterateRuntimeOrder(ip);
+
+    while (field_it.next()) |field_index| {
+        const field_ty = Type.fromInterned(loaded_struct.field_types.get(ip)[field_index]);
+        const field_align = loaded_struct.fieldAlign(ip, field_index);
+        byte_offset.* = std.mem.alignForward(
+            u64,
+            byte_offset.*,
+            field_align.toByteUnits() orelse field_ty.abiAlignment(zcu).toByteUnits().?,
+        );
+        if (zcu.typeToStruct(field_ty)) |field_loaded_struct| {
+            if (field_loaded_struct.layout != .@"packed") {
+                classifyStruct(result, byte_offset, field_loaded_struct, zcu);
+                continue;
+            }
+        }
+        const field_class = std.mem.sliceTo(&classifySystem(field_ty, zcu), .none);
+        const field_size = field_ty.abiSize(zcu);
+
+        combine: {
+            const result_class = &result[@intCast(byte_offset.* / 8)];
+            if (result_class.* == field_class[0]) {
+                break :combine;
+            }
+
+            if (result_class.* == .none) {
+                result_class.* = field_class[0];
+                break :combine;
+            }
+            assert(field_class[0] != .none);
+
+            // "If one of the classes is MEMORY, the result is the MEMORY class."
+            if (result_class.* == .memory or field_class[0] == .memory) {
+                result_class.* = .memory;
+                break :combine;
+            }
+
+            // "If one of the classes is INTEGER, the result is the INTEGER."
+            if (result_class.* == .integer or field_class[0] == .integer) {
+                result_class.* = .integer;
+                break :combine;
+            }
+
+            result_class.* = .integer;
+        }
+        @memcpy(result[@intCast(byte_offset.* / 8 + 1)..][0 .. field_class.len - 1], field_class[1..]);
+        byte_offset.* += field_size;
+    }
+}
+
 pub const callee_preserved_regs = [_]Register{
     // .s0 is ommited to be used as a frame pointer
     .s1, .s2, .s3, .s4, .s5, .s6, .s7, .s8, .s9, .s10, .s11,
src/arch/riscv64/CodeGen.zig
@@ -1800,8 +1800,95 @@ fn airNot(self: *Self, inst: Air.Inst.Index) !void {
 }
 
 fn airMin(self: *Self, inst: Air.Inst.Index) !void {
+    const zcu = self.bin_file.comp.module.?;
     const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
-    const result: MCValue = if (self.liveness.isUnused(inst)) .unreach else return self.fail("TODO implement min for {}", .{self.target.cpu.arch});
+
+    const result: MCValue = if (self.liveness.isUnused(inst)) .unreach else result: {
+        const lhs = try self.resolveInst(bin_op.lhs);
+        const rhs = try self.resolveInst(bin_op.rhs);
+        const lhs_ty = self.typeOf(bin_op.lhs);
+        const rhs_ty = self.typeOf(bin_op.rhs);
+
+        const int_info = lhs_ty.intInfo(zcu);
+
+        if (int_info.bits > 64) return self.fail("TODO: > 64 bit @min", .{});
+
+        const lhs_reg, const lhs_lock = blk: {
+            if (lhs == .register) break :blk .{ lhs.register, null };
+
+            const lhs_reg, const lhs_lock = try self.allocReg();
+            try self.genSetReg(lhs_ty, lhs_reg, lhs);
+            break :blk .{ lhs_reg, lhs_lock };
+        };
+        defer if (lhs_lock) |lock| self.register_manager.unlockReg(lock);
+
+        const rhs_reg, const rhs_lock = blk: {
+            if (rhs == .register) break :blk .{ rhs.register, null };
+
+            const rhs_reg, const rhs_lock = try self.allocReg();
+            try self.genSetReg(rhs_ty, rhs_reg, rhs);
+            break :blk .{ rhs_reg, rhs_lock };
+        };
+        defer if (rhs_lock) |lock| self.register_manager.unlockReg(lock);
+
+        const mask_reg, const mask_lock = try self.allocReg();
+        defer self.register_manager.unlockReg(mask_lock);
+
+        const result_reg, const result_lock = try self.allocReg();
+        defer self.register_manager.unlockReg(result_lock);
+
+        _ = try self.addInst(.{
+            .tag = if (int_info.signedness == .unsigned) .sltu else .slt,
+            .ops = .rrr,
+            .data = .{ .r_type = .{
+                .rd = mask_reg,
+                .rs1 = lhs_reg,
+                .rs2 = rhs_reg,
+            } },
+        });
+
+        _ = try self.addInst(.{
+            .tag = .sub,
+            .ops = .rrr,
+            .data = .{ .r_type = .{
+                .rd = mask_reg,
+                .rs1 = .zero,
+                .rs2 = mask_reg,
+            } },
+        });
+
+        _ = try self.addInst(.{
+            .tag = .xor,
+            .ops = .rrr,
+            .data = .{ .r_type = .{
+                .rd = result_reg,
+                .rs1 = lhs_reg,
+                .rs2 = rhs_reg,
+            } },
+        });
+
+        _ = try self.addInst(.{
+            .tag = .@"and",
+            .ops = .rrr,
+            .data = .{ .r_type = .{
+                .rd = mask_reg,
+                .rs1 = result_reg,
+                .rs2 = mask_reg,
+            } },
+        });
+
+        _ = try self.addInst(.{
+            .tag = .xor,
+            .ops = .rrr,
+            .data = .{ .r_type = .{
+                .rd = result_reg,
+                .rs1 = rhs_reg,
+                .rs2 = mask_reg,
+            } },
+        });
+
+        break :result .{ .register = result_reg };
+    };
     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
 }
 
@@ -3513,17 +3600,9 @@ fn genCall(
                                     .imm12 = Immediate.s(0),
                                 } },
                             });
-                        } else if (self.bin_file.cast(link.File.Coff)) |_| {
-                            return self.fail("TODO implement calling in COFF for {}", .{self.target.cpu.arch});
-                        } else if (self.bin_file.cast(link.File.MachO)) |_| {
-                            unreachable; // unsupported architecture for MachO
-                        } else if (self.bin_file.cast(link.File.Plan9)) |_| {
-                            return self.fail("TODO implement call on plan9 for {}", .{self.target.cpu.arch});
                         } else unreachable;
                     },
-                    .extern_func => {
-                        return self.fail("TODO: extern func calls", .{});
-                    },
+                    .extern_func => return self.fail("TODO: extern func calls", .{}),
                     else => return self.fail("TODO implement calling bitcasted functions", .{}),
                 }
             } else {
src/arch/riscv64/Encoding.zig
@@ -11,7 +11,6 @@ pub const Mnemonic = enum {
     lb,
     lbu,
     sltiu,
-    sltu,
     xori,
     andi,
     slli,
@@ -38,9 +37,11 @@ pub const Mnemonic = enum {
 
     // R Type
     add,
+    @"and",
     sub,
     slt,
     mul,
+    sltu,
     xor,
 
     // System
@@ -52,6 +53,8 @@ pub const Mnemonic = enum {
         return switch (mnem) {
             // zig fmt: off
             .add    => .{ .opcode = 0b0110011, .funct3 = 0b000, .funct7 = 0b0000000 },
+            .sltu   => .{ .opcode = 0b0110011, .funct3 = 0b011, .funct7 = 0b0000000 },
+            .@"and" => .{ .opcode = 0b0110011, .funct3 = 0b111, .funct7 = 0b0000000 },
             .sub    => .{ .opcode = 0b0110011, .funct3 = 0b000, .funct7 = 0b0100000 }, 
 
             .ld     => .{ .opcode = 0b0000011, .funct3 = 0b011, .funct7 = null      },
@@ -84,7 +87,6 @@ pub const Mnemonic = enum {
             .beq    => .{ .opcode = 0b1100011, .funct3 = 0b000, .funct7 = null      },
 
             .slt    => .{ .opcode = 0b0110011, .funct3 = 0b010, .funct7 = 0b0000000 },
-            .sltu   => .{ .opcode = 0b0110011, .funct3 = 0b011, .funct7 = 0b0000000 },
 
             .xor    => .{ .opcode = 0b0110011, .funct3 = 0b100, .funct7 = 0b0000000 },
 
@@ -149,6 +151,7 @@ pub const InstEnc = enum {
             .xor,
             .add,
             .sub,
+            .@"and",
             => .R,
 
             .ecall,
src/arch/riscv64/Mir.zig
@@ -32,6 +32,9 @@ pub const Inst = struct {
         lui,
         mv,
 
+        @"and",
+        xor,
+
         ebreak,
         ecall,
         unimp,
@@ -49,6 +52,9 @@ pub const Inst = struct {
         /// Absolute Value, uses i_type payload.
         abs,
 
+        sltu,
+        slt,
+
         /// Immediate Logical Right Shift, uses i_type payload
         srli,
         /// Immediate Logical Left Shift, uses i_type payload
test/behavior/array.zig
@@ -75,7 +75,6 @@ test "array concat with tuple" {
     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 array: [2]u8 = .{ 1, 2 };
     {
test/behavior/basic.zig
@@ -593,7 +593,6 @@ test "equality compare fn ptrs" {
 
 test "self reference through fn ptr field" {
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     const S = struct {
         const A = struct {
test/behavior/cast.zig
@@ -2073,7 +2073,6 @@ test "peer type resolution: empty tuple pointer and slice" {
     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: [:0]const u8 = "Hello";
     var b = &.{};
@@ -2095,7 +2094,6 @@ test "peer type resolution: tuple pointer and slice" {
     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: [:0]const u8 = "Hello";
     var b = &.{ @as(u8, 'x'), @as(u8, 'y'), @as(u8, 'z') };
test/behavior/fn.zig
@@ -191,7 +191,6 @@ test "function with complex callconv and return type expressions" {
 
 test "pass by non-copying value" {
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     try expect(addPointCoords(Point{ .x = 1, .y = 2 }) == 3);
 }
@@ -219,7 +218,6 @@ fn addPointCoordsVar(pt: anytype) !i32 {
 
 test "pass by non-copying value as method" {
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     var pt = Point2{ .x = 1, .y = 2 };
     try expect(pt.addPointCoords() == 3);
@@ -236,7 +234,6 @@ const Point2 = struct {
 
 test "pass by non-copying value as method, which is generic" {
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     var pt = Point3{ .x = 1, .y = 2 };
     try expect(pt.addPointCoords(i32) == 3);
test/behavior/fn_delegation.zig
@@ -34,7 +34,6 @@ fn custom(comptime T: type, comptime num: u64) fn (T) u64 {
 test "fn delegation" {
     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 foo = Foo{};
     try expect(foo.one() == 11);
test/behavior/generics.zig
@@ -395,7 +395,6 @@ test "extern function used as generic parameter" {
 
 test "generic struct as parameter type" {
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     const S = struct {
         fn doTheTest(comptime Int: type, thing: struct { int: Int }) !void {
test/behavior/pointers.zig
@@ -434,7 +434,6 @@ test "indexing array with sentinel returns correct 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;
 
     var s: [:0]const u8 = "abc";
     try testing.expectEqualSlices(u8, "*const u8", @typeName(@TypeOf(&s[0])));
test/behavior/sizeof_and_typeof.zig
@@ -412,7 +412,6 @@ test "Extern function calls, dereferences and field access in @TypeOf" {
     if (builtin.zig_backend == .stage2_aarch64) 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 Test = struct {
         fn test_fn_1(a: c_long) @TypeOf(c_fopen("test", "r").*) {
test/behavior/slice.zig
@@ -939,7 +939,6 @@ test "modify slice length at comptime" {
     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;
-    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     const arr: [2]u8 = .{ 10, 20 };
     comptime var s: []const u8 = arr[0..0];
test/behavior/struct.zig
@@ -176,7 +176,6 @@ const MemberFnTestFoo = struct {
 
 test "call member function directly" {
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     const instance = MemberFnTestFoo{ .x = 1234 };
     const result = MemberFnTestFoo.member(instance);
@@ -185,7 +184,6 @@ test "call member function directly" {
 
 test "store member function in variable" {
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     const instance = MemberFnTestFoo{ .x = 1234 };
     const memberFn = MemberFnTestFoo.member;
@@ -1561,7 +1559,6 @@ test "discarded struct initialization works as expected" {
 test "function pointer in struct returns the struct" {
     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 A = struct {
         const A = @This();
@@ -1784,8 +1781,6 @@ fn countFields(v: anytype) usize {
 }
 
 test "struct init with no result pointer sets field result types" {
-    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
-
     const S = struct {
         // A function parameter has a result type, but no result pointer.
         fn f(s: struct { x: u32 }) u32 {
@@ -1933,8 +1928,6 @@ test "circular dependency through pointer field of a struct" {
 }
 
 test "field calls do not force struct field init resolution" {
-    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
-
     const S = struct {
         x: u32 = blk: {
             _ = @TypeOf(make().dummyFn()); // runtime field call - S not fully resolved - dummyFn call should not force field init resolution
test/behavior/type.zig
@@ -203,7 +203,6 @@ test "Type.Opaque" {
     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 Opaque = @Type(.{
         .Opaque = .{