riscv: vectors part 1

This commit is contained in:
David Rubin 2024-06-02 19:25:53 -07:00
parent f2301ba896
commit 571aa694fd
No known key found for this signature in database
GPG Key ID: A4390FEB5F00C0A5
8 changed files with 551 additions and 169 deletions

View File

@ -37,6 +37,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;
@ -87,6 +88,9 @@ exitlude_jump_relocs: std.ArrayListUnmanaged(usize) = .{},
/// across each runtime branch upon joining.
branch_stack: *std.ArrayList(Branch),
// The current bit length of vector registers.
vec_len: u32,
// Key is the block instruction
blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .{},
register_manager: RegisterManager = .{},
@ -747,6 +751,7 @@ pub fn generate(
.end_di_line = func.rbrace_line,
.end_di_column = func.rbrace_column,
.scope_generation = 0,
.vec_len = 16 * 8, // TODO: set this per cpu
};
defer {
function.frame_allocs.deinit(gpa);
@ -1040,10 +1045,60 @@ 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: u5, options: bits.VType) !void {
if (avl == 0) {
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 {
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),
},
},
});
}
}
const required_features = [_]Target.riscv.Feature{
.d,
.m,
.a,
.zicsr,
.v,
};
fn gen(func: *Func) !void {
@ -1101,7 +1156,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;
@ -1842,8 +1909,8 @@ 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,
};
}
@ -1852,7 +1919,7 @@ fn regGeneralClassForType(func: *Func, ty: Type) RegisterManager.RegisterBitSet
const zcu = pt.zcu;
return switch (ty.zigTypeTag(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,
};
}
@ -1862,7 +1929,7 @@ fn regTempClassForType(func: *Func, ty: Type) RegisterManager.RegisterBitSet {
const zcu = pt.zcu;
return switch (ty.zigTypeTag(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,
};
}
@ -1870,20 +1937,19 @@ fn regTempClassForType(func: *Func, ty: Type) RegisterManager.RegisterBitSet {
fn allocRegOrMem(func: *Func, elem_ty: Type, inst: ?Air.Inst.Index, reg_ok: bool) !MCValue {
const pt = func.pt;
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(pt.zcu)) {
.Float => if (func.hasFeature(.d)) 64 else 32,
.Vector => func.vec_len,
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(pt.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));
@ -1896,10 +1962,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);
@ -1915,7 +1984,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 };
}
@ -2372,6 +2442,42 @@ fn genBinOp(
},
});
},
.Vector => {
const mir_tag: Mir.Inst.Tag = switch (tag) {
.add => .vaddvv,
else => return func.fail("TODO: genBinOp {s} Vector", .{@tagName(tag)}),
};
const num_elem: u5 = math.cast(u5, lhs_ty.vectorLen(zcu)) orelse {
return func.fail("TODO: genBinOp use vsetvli for larger avl sizes", .{});
};
const elem_size = lhs_ty.childType(zcu).bitSize(pt);
try func.setVl(.zero, num_elem, .{
.vlmul = .mf2,
.vsew = switch (elem_size) {
8 => .@"8",
16 => .@"16",
32 => .@"32",
64 => .@"64",
else => unreachable,
},
.vma = true,
.vta = true,
});
_ = try func.addInst(.{
.tag = mir_tag,
.ops = .rrr,
.data = .{
.r_type = .{
.rd = dst_reg,
.rs1 = lhs_reg,
.rs2 = rhs_reg,
},
},
});
},
else => unreachable,
}
},
@ -3560,13 +3666,12 @@ fn airSetUnionTag(func: *Func, inst: Air.Inst.Index) !void {
}
fn airGetUnionTag(func: *Func, inst: Air.Inst.Index) !void {
const zcu = func.bin_file.comp.module.?;
const mod = func.bin_file.comp.module.?;
const pt = func.pt;
const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
const tag_ty = func.typeOfIndex(inst);
const union_ty = func.typeOf(ty_op.operand);
const layout = union_ty.unionGetLayout(mod);
const layout = union_ty.unionGetLayout(pt);
if (layout.tag_size == 0) {
return func.finishAir(inst, .none, .{ ty_op.operand, .none, .none });
@ -3577,7 +3682,7 @@ fn airGetUnionTag(func: *Func, inst: Air.Inst.Index) !void {
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(mod);
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);
@ -3597,7 +3702,7 @@ fn airGetUnionTag(func: *Func, inst: Air.Inst.Index) !void {
} else {
return func.fail(
"TODO implement get_union_tag for ABI larger than 8 bytes and operand {}, tag {}",
.{ frame_mcv, tag_ty.fmt(zcu) },
.{ frame_mcv, tag_ty.fmt(pt) },
);
}
},
@ -3781,13 +3886,13 @@ fn airBitReverse(func: *Func, inst: Air.Inst.Index) !void {
}
fn airUnaryMath(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
const zcu = func.bin_file.comp.module.?;
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 result: {
const ty = func.typeOf(un_op);
const operand = try func.resolveInst(un_op);
const operand_bit_size = ty.bitSize(zcu);
const operand_bit_size = ty.bitSize(pt);
if (!math.isPowerOfTwo(operand_bit_size))
return func.fail("TODO: airUnaryMath non-pow 2", .{});
@ -3799,7 +3904,7 @@ fn airUnaryMath(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
const dst_reg, const dst_lock = try func.allocReg(dst_class);
defer func.register_manager.unlockReg(dst_lock);
switch (ty.zigTypeTag(zcu)) {
switch (ty.zigTypeTag(pt.zcu)) {
.Float => {
assert(dst_class == .float);
@ -3833,7 +3938,7 @@ fn airUnaryMath(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
else => return func.fail("TODO: airUnaryMath Float {s}", .{@tagName(tag)}),
}
},
else => return func.fail("TODO: airUnaryMath ty: {}", .{ty.fmt(zcu)}),
else => return func.fail("TODO: airUnaryMath ty: {}", .{ty.fmt(pt)}),
}
break :result MCValue{ .register = dst_reg };
@ -4510,7 +4615,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);
@ -5735,7 +5860,12 @@ 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 => func.vec_len,
};
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) {
@ -5835,13 +5965,6 @@ 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", .{});
}
// mv reg, src_reg
_ = try func.addInst(.{
.tag = .pseudo,
@ -5854,6 +5977,43 @@ 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| {
if (reg.class() == .vector) {
if (abi_size > 8)
return func.fail("TODO: genSetReg vectors > 8", .{});
const temp_reg = try func.register_manager.allocReg(null, abi.Registers.Integer.temporary);
const temp_lock = func.register_manager.lockRegAssumeUnused(temp_reg);
defer func.register_manager.unlockReg(temp_lock);
try func.setVl(.zero, 1, .{
.vsew = switch (abi_size) {
1 => .@"8",
2 => .@"16",
4 => .@"32",
8 => .@"64",
else => unreachable,
},
.vlmul = .m1,
.vma = true,
.vta = true,
});
try func.genCopy(ty, .{ .register = temp_reg }, .{ .load_frame = frame });
_ = try func.addInst(.{
.tag = .pseudo,
.ops = .pseudo_mv,
.data = .{
.rr = .{
.rd = reg,
.rs = temp_reg,
},
},
});
return;
}
_ = try func.addInst(.{
.tag = .pseudo,
.ops = .pseudo_load_rm,
@ -6195,7 +6355,7 @@ fn airCmpxchg(func: *Func, inst: Air.Inst.Index) !void {
}
fn airAtomicRmw(func: *Func, inst: Air.Inst.Index) !void {
const zcu = func.pt.zcu;
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;
@ -6206,13 +6366,13 @@ fn airAtomicRmw(func: *Func, inst: Air.Inst.Index) !void {
const ptr_mcv = try func.resolveInst(pl_op.operand);
const val_ty = func.typeOf(extra.operand);
const val_size = val_ty.abiSize(func.pt);
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(zcu)) {
switch (val_ty.zigTypeTag(pt.zcu)) {
.Int => {},
inline .Bool, .Float, .Enum, .Pointer => |ty| return func.fail("TODO: airAtomicRmw {s}", .{@tagName(ty)}),
else => unreachable,
@ -6735,15 +6895,15 @@ fn getResolvedInstValue(func: *Func, inst: Air.Inst.Index) *InstTracking {
}
fn genTypedValue(func: *Func, val: Value) InnerError!MCValue {
const zcu = func.pt.zcu;
const pt = func.pt;
const gpa = func.gpa;
const owner_decl_index = zcu.funcOwnerDeclIndex(func.func_index);
const owner_decl_index = pt.zcu.funcOwnerDeclIndex(func.func_index);
const lf = func.bin_file;
const src_loc = func.src_loc;
if (val.isUndef(zcu)) {
const local_sym_index = lf.lowerUnnamedConst(func.pt, val, owner_decl_index) catch |err| {
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;
@ -6760,7 +6920,7 @@ fn genTypedValue(func: *Func, val: Value) InnerError!MCValue {
const result = try codegen.genTypedValue(
lf,
func.pt,
pt,
src_loc,
val,
owner_decl_index,

View File

@ -11,6 +11,7 @@ const OpCode = enum(u7) {
STORE = 0b0100011,
STORE_FP = 0b0100111,
AMO = 0b0101111,
OP_V = 0b1010111,
OP = 0b0110011,
OP_32 = 0b0111011,
LUI = 0b0110111,
@ -83,9 +84,51 @@ 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) {
@"32" = 0b110,
@"64" = 0b111,
};
const VecType = enum(u3) {
OPIVV = 0b000,
OPFVV = 0b001,
OPMVV = 0b010,
OPIVI = 0b011,
OPIVX = 0b100,
OPFVF = 0b101,
OPMVX = 0b110,
};
};
pub const Mnemonic = enum {
@ -115,6 +158,9 @@ pub const Mnemonic = enum {
addi,
jalr,
vsetivli,
vsetvli,
// U Type
lui,
auipc,
@ -155,6 +201,8 @@ pub const Mnemonic = enum {
ebreak,
unimp,
csrrs,
// M extension
mul,
mulw,
@ -217,6 +265,17 @@ pub const Mnemonic = enum {
fsgnjnd,
fsgnjxd,
// V Extension
vle32v,
vle64v,
vse32v,
vse64v,
vaddvv,
vadcxv,
vadcvx,
// MISC
fence,
fencetso,
@ -373,6 +432,9 @@ pub const Mnemonic = enum {
.flw => .{ .opcode = .LOAD_FP, .data = .{ .f = .{ .funct3 = 0b010 } } },
.fld => .{ .opcode = .LOAD_FP, .data = .{ .f = .{ .funct3 = 0b011 } } },
.vle32v => .{ .opcode = .LOAD_FP, .data = .{ .vecls = .{ .width = .@"32", .umop = .unit, .vm = true, .mop = .unit, .mew = true, .nf = 0b000 } } },
.vle64v => .{ .opcode = .LOAD_FP, .data = .{ .vecls = .{ .width = .@"64", .umop = .unit, .vm = true, .mop = .unit, .mew = true, .nf = 0b000 } } },
// STORE_FP
@ -380,6 +442,8 @@ pub const Mnemonic = enum {
.fsw => .{ .opcode = .STORE_FP, .data = .{ .f = .{ .funct3 = 0b010 } } },
.fsd => .{ .opcode = .STORE_FP, .data = .{ .f = .{ .funct3 = 0b011 } } },
.vse32v => .{ .opcode = .STORE_FP, .data = .{ .vecls = .{ .width = .@"32", .umop = .unit, .vm = true, .mop = .unit, .mew = true, .nf = 0b000 } } },
.vse64v => .{ .opcode = .STORE_FP, .data = .{ .vecls = .{ .width = .@"64", .umop = .unit, .vm = true, .mop = .unit, .mew = true, .nf = 0b000 } } },
// JALR
@ -410,6 +474,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
@ -449,7 +515,13 @@ pub const Mnemonic = enum {
.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 } } },
.vadcxv => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b010000, .funct3 = .OPMVX } } },
.vadcvx => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b010000, .funct3 = .OPMVV } } },
// zig fmt: on
};
}
@ -465,7 +537,6 @@ pub const InstEnc = enum {
J,
fence,
amo,
/// extras that have unusual op counts
system,
pub fn fromMnemonic(mnem: Mnemonic) InstEnc {
@ -494,6 +565,10 @@ pub const InstEnc = enum {
.flw,
.fld,
.csrrs,
.vsetivli,
.vsetvli,
=> .I,
.lui,
@ -587,6 +662,14 @@ pub const InstEnc = enum {
.fsgnjxs,
.fsgnjxd,
.vle32v,
.vle64v,
.vse32v,
.vse64v,
.vaddvv,
.vadcxv,
.vadcvx,
=> .R,
.ecall,
@ -757,6 +840,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 => {},
}
@ -783,6 +885,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,
},
};
@ -897,21 +1018,21 @@ pub const Data = union(InstEnc) {
.amo => {
assert(ops.len == 5);
const rd = ops[0];
const rs1 = ops[1];
const rs2 = ops[2];
const rl = ops[3];
const aq = ops[4];
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.reg.encodeId(),
.rs1 = rs1.reg.encodeId(),
.rs2 = rs2.reg.encodeId(),
.rd = rd.encodeId(),
.rs1 = rs1.encodeId(),
.rs2 = rs2.encodeId(),
// TODO: https://github.com/ziglang/zig/issues/20113
.rl = if (rl.barrier == .rl) true else false,
.aq = if (aq.barrier == .aq) true else false,
.rl = if (rl == .rl) true else false,
.aq = if (aq == .aq) true else false,
.opcode = @intFromEnum(enc.opcode),
.funct3 = @intFromEnum(enc.data.amo.width),
@ -919,7 +1040,6 @@ pub const Data = union(InstEnc) {
},
};
},
else => std.debug.panic("TODO: construct {s}", .{@tagName(inst_enc)}),
}
}

View File

@ -80,58 +80,99 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index, options: struct {
.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,
.hword,
=> return lower.fail(
"TODO: lowerMir pseudo_load_rm support {s} vector",
.{@tagName(src_size)},
),
.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 => {
try lower.emit(tag, &.{
.{ .reg = rm.r },
.{ .reg = frame_loc.base },
.{ .reg = .x0 },
});
},
}
},
.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,
.hword,
=> return lower.fail(
"TODO: lowerMir pseudo_load_rm support {s} vector",
.{@tagName(dest_size)},
),
.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 => {
try lower.emit(tag, &.{
.{ .reg = frame_loc.base },
.{ .reg = rm.r },
.{ .reg = .x0 },
});
},
}
},
else => unreachable,
}
@ -143,34 +184,47 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index, options: 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(.vadcxv, &.{
.{ .reg = rr.rd },
.{ .reg = rr.rs },
.{ .reg = .zero },
});
},
.float => return lower.fail("TODO: lowerMir pseudo_mv int -> {s}", .{@tagName(dst_class)}),
},
.vector => switch (dst_class) {
.int => {
try lower.emit(.vadcvx, &.{
.{ .reg = rr.rd },
.{ .reg = rr.rs },
.{ .reg = .zero },
});
},
.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 },
@ -209,7 +263,10 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index, options: 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 },
@ -376,6 +433,7 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index, options: struct {
});
},
},
.vector => return lower.fail("TODO: lowerMir pseudo_cmp vector", .{}),
}
},
@ -497,6 +555,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)}),
});
}
@ -523,17 +586,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) },

View File

@ -134,7 +134,16 @@ pub const Inst = struct {
fltd,
fled,
/// A Extension Instructions
// Zicsr Extension Instructions
csrrs,
// V Extension Instructions
vsetvli,
vsetivli,
vsetvl,
vaddvv,
// A Extension Instructions
amo,
/// A pseudo-instruction. Used for anything that isn't 1:1 with an
@ -146,91 +155,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,
@ -245,12 +220,10 @@ pub const Inst = struct {
},
ty: Type,
},
reloc: struct {
atom_index: u32,
sym_index: u32,
},
fence: struct {
pred: Barrier,
succ: Barrier,
@ -259,7 +232,6 @@ pub const Inst = struct {
tso,
},
},
amo: struct {
rd: Register,
rs1: Register,
@ -269,6 +241,11 @@ pub const Inst = struct {
op: AmoOp,
ty: Type,
},
csr: struct {
csr: CSR,
rs1: Register,
rd: Register,
},
};
pub const Ops = enum {
@ -293,6 +270,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,
@ -321,11 +301,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,
@ -363,14 +338,6 @@ pub const Inst = struct {
pseudo_amo,
};
// 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);
// }
// }
pub fn format(
inst: Inst,
comptime fmt: []const u8,
@ -490,6 +457,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;

View File

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

View File

@ -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) => 1024, // 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,
};

View File

@ -5,6 +5,7 @@ pub const Instruction = struct {
pub const Operand = union(enum) {
none,
reg: Register,
csr: CSR,
mem: Memory,
imm: Immediate,
barrier: Mir.Barrier,
@ -58,6 +59,7 @@ pub const Instruction = struct {
.imm => |imm| try writer.print("{d}", .{imm.asSigned(64)}),
.mem => try writer.writeAll("mem"),
.barrier => |barrier| try writer.writeAll(@tagName(barrier)),
.csr => |csr| try writer.writeAll(@tagName(csr)),
}
}
}
@ -71,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;

View File

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