riscv: implement slices

This commit is contained in:
David Rubin 2024-03-29 06:48:41 -07:00
parent 350ad90cee
commit 3bf008a3d0
8 changed files with 232 additions and 91 deletions

View File

@ -775,14 +775,14 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace, ret_addr
}
if (builtin.zig_backend == .stage2_riscv64) {
// asm volatile ("ecall"
// :
// : [number] "{a7}" (64),
// [arg1] "{a0}" (1),
// [arg2] "{a1}" (@intFromPtr(msg.ptr)),
// [arg3] "{a2}" (msg.len),
// : "rcx", "r11", "memory"
// );
asm volatile ("ecall"
:
: [number] "{a7}" (64),
[arg1] "{a0}" (1),
[arg2] "{a1}" (@intFromPtr(msg.ptr)),
[arg3] "{a2}" (msg.len),
: "rcx", "r11", "memory"
);
std.posix.exit(127);
}

View File

@ -122,9 +122,10 @@ const MCValue = union(enum) {
/// A pointer-sized integer that fits in a register.
/// If the type is a pointer, this is the pointer address in virtual address space.
immediate: u64,
/// The value is in memory at an address not-yet-allocated by the linker.
/// This traditionally corresponds to a relocation emitted in a relocatable object file.
/// The value doesn't exist in memory yet.
load_symbol: SymbolOffset,
/// The address of the memory location not-yet-allocated by the linker.
addr_symbol: SymbolOffset,
/// The value is in a target-specific register.
register: Register,
/// The value is split across two registers
@ -169,6 +170,7 @@ const MCValue = union(enum) {
.indirect,
.undef,
.load_symbol,
.addr_symbol,
.air_ref,
=> false,
@ -188,10 +190,14 @@ const MCValue = union(enum) {
.immediate,
.ptr_stack_offset,
.register_offset,
.register_pair,
.register,
.undef,
.air_ref,
.addr_symbol,
=> unreachable, // not in memory
.load_symbol => |sym_off| .{ .addr_symbol = sym_off },
.memory => |addr| .{ .immediate = addr },
.stack_offset => |off| .{ .ptr_stack_offset = off },
.indirect => |reg_off| switch (reg_off.off) {
@ -219,6 +225,7 @@ const MCValue = union(enum) {
.ptr_stack_offset => |off| .{ .stack_offset = off },
.register => |reg| .{ .indirect = .{ .reg = reg } },
.register_offset => |reg_off| .{ .indirect = reg_off },
.addr_symbol => |sym_off| .{ .load_symbol = sym_off },
};
}
@ -235,6 +242,7 @@ const MCValue = union(enum) {
.indirect,
.stack_offset,
.load_symbol,
.addr_symbol,
=> switch (off) {
0 => mcv,
else => unreachable, // not offsettable
@ -801,6 +809,43 @@ fn ensureProcessDeathCapacity(self: *Self, additional_count: usize) !void {
try table.ensureUnusedCapacity(self.gpa, additional_count);
}
fn splitType(self: *Self, ty: Type) ![2]Type {
const mod = self.bin_file.comp.module.?;
const classes = mem.sliceTo(&abi.classifySystemV(ty, mod), .none);
var parts: [2]Type = undefined;
if (classes.len == 2) for (&parts, classes, 0..) |*part, class, part_i| {
part.* = switch (class) {
.integer => switch (part_i) {
0 => Type.u64,
1 => part: {
const elem_size = ty.abiAlignment(mod).minStrict(.@"8").toByteUnitsOptional().?;
const elem_ty = try mod.intType(.unsigned, @intCast(elem_size * 8));
break :part switch (@divExact(ty.abiSize(mod) - 8, elem_size)) {
1 => elem_ty,
else => |len| try mod.arrayType(.{ .len = len, .child = elem_ty.toIntern() }),
};
},
else => unreachable,
},
else => break,
};
} else if (parts[0].abiSize(mod) + parts[1].abiSize(mod) == ty.abiSize(mod)) return parts;
return std.debug.panic("TODO implement splitType for {}", .{ty.fmt(mod)});
}
fn symbolIndex(self: *Self) !u32 {
const mod = self.bin_file.comp.module.?;
const decl_index = mod.funcOwnerDeclIndex(self.func_index);
return switch (self.bin_file.tag) {
.elf => blk: {
const elf_file = self.bin_file.cast(link.File.Elf).?;
const atom_index = try elf_file.zigObjectPtr().?.getOrCreateMetadataForDecl(elf_file, decl_index);
break :blk atom_index;
},
else => return self.fail("TODO genSetReg load_symbol for {s}", .{@tagName(self.bin_file.tag)}),
};
}
fn allocMem(self: *Self, inst: Air.Inst.Index, abi_size: u32, abi_align: Alignment) !u32 {
self.stack_align = self.stack_align.max(abi_align);
// TODO find a free slot instead of always appending
@ -1610,40 +1655,41 @@ fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) !void {
fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
const mcv = try self.resolveInst(ty_op.operand);
break :result try self.slicePtr(mcv);
const result = result: {
const src_mcv = try self.resolveInst(ty_op.operand);
if (self.reuseOperand(inst, ty_op.operand, 0, src_mcv)) break :result src_mcv;
const dst_mcv = try self.allocRegOrMem(inst, true);
const dst_ty = self.typeOfIndex(inst);
try self.genCopy(dst_ty, dst_mcv, src_mcv);
break :result dst_mcv;
};
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
}
fn slicePtr(self: *Self, mcv: MCValue) !MCValue {
switch (mcv) {
.dead, .unreach, .none => unreachable,
.register => unreachable, // a slice doesn't fit in one register
.stack_offset => |off| {
return MCValue{ .stack_offset = off };
},
.memory => |addr| {
return MCValue{ .memory = addr };
},
else => return self.fail("TODO slicePtr {s}", .{@tagName(mcv)}),
}
}
fn airSliceLen(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
const ptr_bits = 64;
const ptr_bytes = @divExact(ptr_bits, 8);
const mcv = try self.resolveInst(ty_op.operand);
switch (mcv) {
.dead, .unreach, .none => unreachable,
.register => unreachable, // a slice doesn't fit in one register
const src_mcv = try self.resolveInst(ty_op.operand);
switch (src_mcv) {
.stack_offset => |off| {
break :result MCValue{ .stack_offset = off + ptr_bytes };
const len_mcv: MCValue = .{ .stack_offset = off + 8 };
if (self.reuseOperand(inst, ty_op.operand, 0, src_mcv)) break :result len_mcv;
const dst_mcv = try self.allocRegOrMem(inst, true);
try self.genCopy(Type.usize, dst_mcv, len_mcv);
break :result dst_mcv;
},
else => return self.fail("TODO airSliceLen for {}", .{mcv}),
.register_pair => |pair| {
const len_mcv: MCValue = .{ .register = pair[1] };
if (self.reuseOperand(inst, ty_op.operand, 0, src_mcv)) break :result len_mcv;
const dst_mcv = try self.allocRegOrMem(inst, true);
try self.genCopy(Type.usize, dst_mcv, len_mcv);
break :result dst_mcv;
},
else => return self.fail("TODO airSliceLen for {}", .{src_mcv}),
}
};
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
@ -1978,6 +2024,7 @@ fn load(self: *Self, dst_mcv: MCValue, ptr_mcv: MCValue, ptr_ty: Type) InnerErro
.register,
.register_offset,
.ptr_stack_offset,
.addr_symbol,
=> try self.genCopy(dst_ty, dst_mcv, ptr_mcv.deref()),
.memory,
@ -2019,11 +2066,6 @@ fn store(self: *Self, pointer: MCValue, value: MCValue, ptr_ty: Type, value_ty:
log.debug("storing {}:{} in {}:{}", .{ value, value_ty.fmt(mod), pointer, ptr_ty.fmt(mod) });
if (value_ty.isSlice(mod)) {
// cheat a bit by loading in two parts
}
switch (pointer) {
.none => unreachable,
.undef => unreachable,
@ -2192,7 +2234,11 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void {
const dst_mcv = switch (src_mcv) {
.register => |src_reg| dst: {
try self.register_manager.getReg(src_reg, null);
self.register_manager.getRegAssumeFree(src_reg, null);
break :dst src_mcv;
},
.register_pair => |pair| dst: {
for (pair) |reg| self.register_manager.getRegAssumeFree(reg, null);
break :dst src_mcv;
},
else => return self.fail("TODO: airArg {s}", .{@tagName(src_mcv)}),
@ -3056,6 +3102,8 @@ fn iterateBigTomb(self: *Self, inst: Air.Inst.Index, operand_count: usize) !BigT
/// Sets the value without any modifications to register allocation metadata or stack allocation metadata.
fn genCopy(self: *Self, ty: Type, dst_mcv: MCValue, src_mcv: MCValue) !void {
const mod = self.bin_file.comp.module.?;
// There isn't anything to store
if (dst_mcv == .none) return;
@ -3066,7 +3114,6 @@ fn genCopy(self: *Self, ty: Type, dst_mcv: MCValue, src_mcv: MCValue) !void {
switch (dst_mcv) {
.register => |reg| return self.genSetReg(ty, reg, src_mcv),
.register_pair => |pair| return self.genSetRegPair(ty, pair, src_mcv),
.register_offset => |dst_reg_off| try self.genSetReg(ty, dst_reg_off.reg, switch (src_mcv) {
.none,
.unreach,
@ -3084,7 +3131,47 @@ fn genCopy(self: *Self, ty: Type, dst_mcv: MCValue, src_mcv: MCValue) !void {
}),
.stack_offset => |off| return self.genSetStack(ty, off, src_mcv),
.memory => |addr| return self.genSetMem(ty, addr, src_mcv),
else => return self.fail("TODO: genCopy {s} with {s}", .{ @tagName(dst_mcv), @tagName(src_mcv) }),
.register_pair => |dst_regs| {
const src_info: ?struct { addr_reg: Register, addr_lock: RegisterLock } = switch (src_mcv) {
.register_pair, .memory, .indirect, .stack_offset => null,
.load_symbol => src: {
const src_addr_reg, const src_addr_lock = try self.allocReg();
errdefer self.register_manager.unlockReg(src_addr_lock);
try self.genSetReg(Type.usize, src_addr_reg, src_mcv.address());
break :src .{ .addr_reg = src_addr_reg, .addr_lock = src_addr_lock };
},
.air_ref => |src_ref| return self.genCopy(
ty,
dst_mcv,
try self.resolveInst(src_ref),
),
else => return self.fail("TODO implement genCopy for {s} of {}", .{
@tagName(src_mcv), ty.fmt(mod),
}),
};
defer if (src_info) |info| self.register_manager.unlockReg(info.addr_lock);
switch (ty.zigTypeTag(mod)) {
.Optional => return,
else => {},
}
var part_disp: i32 = 0;
for (dst_regs, try self.splitType(ty), 0..) |dst_reg, dst_ty, part_i| {
try self.genSetReg(dst_ty, dst_reg, switch (src_mcv) {
.register_pair => |src_regs| .{ .register = src_regs[part_i] },
.memory, .indirect, .stack_offset => src_mcv.address().offset(part_disp).deref(),
.load_symbol => .{ .indirect = .{
.reg = src_info.?.addr_reg,
.off = part_disp,
} },
else => unreachable,
});
part_disp += @intCast(dst_ty.abiSize(mod));
}
},
else => return std.debug.panic("TODO: genCopy {s} with {s}", .{ @tagName(dst_mcv), @tagName(src_mcv) }),
}
}
@ -3168,14 +3255,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, src_mcv: MCValue) Inner
try self.genSetReg(ptr_ty, src_reg, .{ .ptr_stack_offset = offset });
},
.load_symbol => |sym_off| {
const atom_index = atom: {
const decl_index = mod.funcOwnerDeclIndex(self.func_index);
if (self.bin_file.cast(link.File.Elf)) |elf_file| {
const atom_index = try elf_file.zigObjectPtr().?.getOrCreateMetadataForDecl(elf_file, decl_index);
break :atom atom_index;
} else return self.fail("TODO genSetStack for {s}", .{@tagName(self.bin_file.tag)});
};
const atom_index = try self.symbolIndex();
// setup the src pointer
_ = try self.addInst(.{
@ -3443,16 +3523,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
.load_symbol => |sym_off| {
assert(sym_off.off == 0);
const decl_index = mod.funcOwnerDeclIndex(self.func_index);
const atom_index = try self.symbolIndex();
const atom_index = switch (self.bin_file.tag) {
.elf => blk: {
const elf_file = self.bin_file.cast(link.File.Elf).?;
const atom_index = try elf_file.zigObjectPtr().?.getOrCreateMetadataForDecl(elf_file, decl_index);
break :blk atom_index;
},
else => return self.fail("TODO genSetReg load_symbol for {s}", .{@tagName(self.bin_file.tag)}),
};
_ = try self.addInst(.{
.tag = .load_symbol,
.data = .{
@ -3485,27 +3557,23 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
},
});
},
else => return self.fail("TODO: genSetReg {s}", .{@tagName(src_mcv)}),
}
}
.addr_symbol => |sym_off| {
assert(sym_off.off == 0);
fn genSetRegPair(self: *Self, ty: Type, pair: [2]Register, src_mcv: MCValue) InnerError!void {
const mod = self.bin_file.comp.module.?;
const abi_size: u32 = @intCast(ty.abiSize(mod));
const atom_index = try self.symbolIndex();
assert(abi_size > 8 and abi_size <= 16); // must fit only fit into two registers
switch (src_mcv) {
.air_ref => |ref| return self.genSetRegPair(ty, pair, try self.resolveInst(ref)),
.load_symbol => |sym_off| {
_ = sym_off;
// return self.fail("TODO: genSetRegPair load_symbol", .{});
// commented out just for testing.
// plan here is to load the address into a temporary register and
// copy into the pair.
_ = try self.addInst(.{
.tag = .load_symbol,
.data = .{
.payload = try self.addExtra(Mir.LoadSymbolPayload{
.register = reg.id(),
.atom_index = atom_index,
.sym_index = sym_off.sym,
}),
},
});
},
else => return self.fail("TODO: genSetRegPair {s}", .{@tagName(src_mcv)}),
else => return self.fail("TODO: genSetReg {s}", .{@tagName(src_mcv)}),
}
}

View File

@ -398,7 +398,7 @@ fn mirPsuedo(emit: *Emit, inst: Mir.Inst.Index) !void {
.j => {
const offset = @as(i64, @intCast(emit.code_offset_mapping.get(data.inst).?)) - @as(i64, @intCast(emit.code.items.len));
try emit.writeInstruction(Instruction.jal(.s0, @intCast(offset)));
try emit.writeInstruction(Instruction.jal(.zero, @intCast(offset)));
},
else => unreachable,
@ -443,27 +443,40 @@ fn mirLoadSymbol(emit: *Emit, inst: Mir.Inst.Index) !void {
const data = emit.mir.extraData(Mir.LoadSymbolPayload, payload).data;
const reg = @as(Register, @enumFromInt(data.register));
const end_offset = @as(u32, @intCast(emit.code.items.len));
const start_offset = @as(u32, @intCast(emit.code.items.len));
try emit.writeInstruction(Instruction.lui(reg, 0));
try emit.writeInstruction(Instruction.lw(reg, 0, reg));
switch (emit.bin_file.tag) {
.elf => {
const elf_file = emit.bin_file.cast(link.File.Elf).?;
const atom_ptr = elf_file.symbol(data.atom_index).atom(elf_file).?;
const sym_index = elf_file.zigObjectPtr().?.symbol(data.sym_index);
const sym = elf_file.symbol(sym_index);
const hi_r_type = @intFromEnum(std.elf.R_RISCV.HI20);
var hi_r_type: u32 = @intFromEnum(std.elf.R_RISCV.HI20);
var lo_r_type: u32 = @intFromEnum(std.elf.R_RISCV.LO12_I);
if (sym.flags.needs_zig_got) {
_ = try sym.getOrCreateZigGotEntry(sym_index, elf_file);
hi_r_type = Elf.R_ZIG_GOT_HI20;
lo_r_type = Elf.R_ZIG_GOT_LO12;
// we need to deref once if we are getting from zig_got, as itll
// reloc an address of the address in the got.
try emit.writeInstruction(Instruction.ld(reg, 0, reg));
} else {
try emit.writeInstruction(Instruction.addi(reg, reg, 0));
}
try atom_ptr.addReloc(elf_file, .{
.r_offset = end_offset,
.r_offset = start_offset,
.r_info = (@as(u64, @intCast(data.sym_index)) << 32) | hi_r_type,
.r_addend = 0,
});
const lo_r_type = @intFromEnum(std.elf.R_RISCV.LO12_I);
try atom_ptr.addReloc(elf_file, .{
.r_offset = end_offset + 4,
.r_offset = start_offset + 4,
.r_info = (@as(u64, @intCast(data.sym_index)) << 32) | lo_r_type,
.r_addend = 0,
});
@ -587,6 +600,7 @@ const bits = @import("bits.zig");
const abi = @import("abi.zig");
const link = @import("../../link.zig");
const Module = @import("../../Module.zig");
const Elf = @import("../../link/Elf.zig");
const ErrorMsg = Module.ErrorMsg;
const assert = std.debug.assert;
const Instruction = bits.Instruction;

View File

@ -118,7 +118,9 @@ pub const Inst = struct {
/// function epilogue
psuedo_epilogue,
// TODO: add description
/// Loads the address of a value that hasn't yet been allocated in memory.
///
/// uses the Mir.LoadSymbolPayload payload.
load_symbol,
// TODO: add description

View File

@ -5,7 +5,7 @@ const RegisterManagerFn = @import("../../register_manager.zig").RegisterManager;
const Type = @import("../../type.zig").Type;
const Module = @import("../../Module.zig");
pub const Class = enum { memory, byval, integer, double_integer, fields };
pub const Class = enum { memory, byval, integer, double_integer, fields, none };
pub fn classifyType(ty: Type, mod: *Module) Class {
const target = mod.getTarget();
@ -91,6 +91,37 @@ pub fn classifyType(ty: Type, mod: *Module) Class {
}
}
/// There are a maximum of 8 possible return slots. Returned values are in
/// the beginning of the array; unused slots are filled with .none.
pub fn classifySystemV(ty: Type, mod: *Module) [8]Class {
const memory_class = [_]Class{
.memory, .none, .none, .none,
.none, .none, .none, .none,
};
var result = [1]Class{.none} ** 8;
switch (ty.zigTypeTag(mod)) {
.Pointer => switch (ty.ptrSize(mod)) {
.Slice => {
result[0] = .integer;
result[1] = .integer;
return result;
},
else => {
result[0] = .integer;
return result;
},
},
.Optional => {
if (ty.isPtrLikeOptional(mod)) {
result[0] = .integer;
return result;
}
return memory_class;
},
else => return result,
}
}
pub const callee_preserved_regs = [_]Register{
.s0, .s1, .s2, .s3, .s4, .s5, .s6, .s7, .s8, .s9, .s10, .s11,
};

View File

@ -11132,6 +11132,7 @@ fn lowerFnRetTy(o: *Object, fn_info: InternPool.Key.FuncType) Allocator.Error!Bu
}
return o.builder.structType(.normal, types[0..types_len]);
},
.none => unreachable,
}
},
// TODO investigate C ABI for other architectures
@ -11389,6 +11390,7 @@ const ParamTypeIterator = struct {
it.llvm_index += it.types_len - 1;
return .multiple_llvm_types;
},
.none => unreachable,
}
},
// TODO investigate C ABI for other architectures

View File

@ -6409,6 +6409,8 @@ const RelaSectionTable = std.AutoArrayHashMapUnmanaged(u32, RelaSection);
// TODO: add comptime check we don't clobber any reloc for any ISA
pub const R_ZIG_GOT32: u32 = 0xff00;
pub const R_ZIG_GOTPCREL: u32 = 0xff01;
pub const R_ZIG_GOT_HI20: u32 = 0xff02;
pub const R_ZIG_GOT_LO12: u32 = 0xff03;
fn defaultEntrySymbolName(cpu_arch: std.Target.Cpu.Arch) []const u8 {
return switch (cpu_arch) {

View File

@ -2025,7 +2025,15 @@ const riscv = struct {
.SUB32,
=> {},
else => try atom.reportUnhandledRelocError(rel, elf_file),
else => |x| switch (@intFromEnum(x)) {
Elf.R_ZIG_GOT_HI20,
Elf.R_ZIG_GOT_LO12,
=> {
assert(symbol.flags.has_zig_got);
},
else => try atom.reportUnhandledRelocError(rel, elf_file),
},
}
}
@ -2046,7 +2054,6 @@ const riscv = struct {
const P, const A, const S, const GOT, const G, const TP, const DTP, const ZIG_GOT = args;
_ = TP;
_ = DTP;
_ = ZIG_GOT;
switch (r_type) {
.NONE => unreachable,
@ -2136,7 +2143,22 @@ const riscv = struct {
}
},
else => try atom.reportUnhandledRelocError(rel, elf_file),
else => |x| switch (@intFromEnum(x)) {
// Zig custom relocations
Elf.R_ZIG_GOT_HI20 => {
assert(target.flags.has_zig_got);
const disp: u32 = @bitCast(math.cast(i32, G + ZIG_GOT + A) orelse return error.Overflow);
riscv_util.writeInstU(code[r_offset..][0..4], disp);
},
Elf.R_ZIG_GOT_LO12 => {
assert(target.flags.has_zig_got);
const value: u32 = @bitCast(math.cast(i32, G + ZIG_GOT + A) orelse return error.Overflow);
riscv_util.writeInstI(code[r_offset..][0..4], value);
},
else => try atom.reportUnhandledRelocError(rel, elf_file),
},
}
}