riscv: by-value structs + @min

This commit is contained in:
David Rubin 2024-04-16 18:48:33 -07:00
parent a30af172e8
commit 2fd83d8c0a
15 changed files with 174 additions and 35 deletions

View File

@ -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 {

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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 };
{

View File

@ -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 {

View File

@ -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') };

View File

@ -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);

View File

@ -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);

View File

@ -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 {

View File

@ -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])));

View File

@ -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").*) {

View File

@ -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];

View File

@ -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

View File

@ -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 = .{