x86_64: add frame indices

This allows abstract references to the stack frame which enables:
 * deferred frame layout until after the function has been processed
 * frame compaction (sorting by alignment)
 * being able to overalign the stack
 * references that change based on an overaligned stack
 * moving callee saved spills to the usual part (smaller code size)
 * shared call frame (avoids stack adjustments before and after calls)
This commit is contained in:
Jacob Young 2023-04-23 22:25:53 -04:00
parent f618398b24
commit 26d4f9b69e
7 changed files with 2527 additions and 2566 deletions

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,9 @@ prev_di_pc: usize,
code_offset_mapping: std.AutoHashMapUnmanaged(Mir.Inst.Index, usize) = .{},
relocs: std.ArrayListUnmanaged(Reloc) = .{},
pub const Error = Lower.Error || error{EmitFail};
pub const Error = Lower.Error || error{
EmitFail,
};
pub fn emitMir(emit: *Emit) Error!void {
for (0..emit.lower.mir.instructions.len) |i| {
@ -22,7 +24,7 @@ pub fn emitMir(emit: *Emit) Error!void {
const start_offset = @intCast(u32, emit.code.items.len);
try emit.code_offset_mapping.putNoClobber(emit.lower.allocator, index, start_offset);
for (try emit.lower.lowerMir(inst)) |lower_inst| try lower_inst.encode(emit.code.writer());
for (try emit.lower.lowerMir(inst)) |lower_inst| try lower_inst.encode(emit.code.writer(), .{});
const end_offset = @intCast(u32, emit.code.items.len);
switch (inst.tag) {
@ -120,11 +122,10 @@ pub fn emitMir(emit: *Emit) Error!void {
.length = 6,
}),
.dbg_line => {
const dbg_line_column =
emit.lower.mir.extraData(Mir.DbgLineColumn, inst.data.payload).data;
try emit.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column);
},
.dbg_line => try emit.dbgAdvancePCAndLine(
inst.data.line_column.line,
inst.data.line_column.column,
),
.dbg_prologue_end => {
switch (emit.debug_output) {

View File

@ -47,17 +47,8 @@ pub fn findByMnemonic(
},
else => {},
} else false;
const rex_extended = for (ops) |op| switch (op) {
.reg => |r| if (r.isExtended()) break true,
.mem => |m| {
if (m.base()) |base| {
if (base.isExtended()) break true;
}
if (m.scaleIndex()) |si| {
if (si.index.isExtended()) break true;
}
},
else => {},
const rex_extended = for (ops) |op| {
if (op.isBaseExtended() or op.isIndexExtended()) break true;
} else false;
if ((rex_required or rex_extended) and rex_invalid) return error.CannotEncode;
@ -544,7 +535,7 @@ fn estimateInstructionLength(prefix: Prefix, encoding: Encoding, ops: []const Op
std.mem.copy(Operand, &inst.ops, ops);
var cwriter = std.io.countingWriter(std.io.null_writer);
inst.encode(cwriter.writer()) catch unreachable; // Not allowed to fail here unless OOM.
inst.encode(cwriter.writer(), .{ .allow_frame_loc = true }) catch unreachable; // Not allowed to fail here unless OOM.
return @intCast(usize, cwriter.bytes_written);
}

View File

@ -136,7 +136,8 @@ pub fn lowerMir(lower: *Lower, inst: Mir.Inst) Error![]const Instruction {
.setcc => try lower.mirSetcc(inst),
.jcc => try lower.emit(.none, mnem_cc(.j, inst.data.inst_cc.cc), &.{.{ .imm = Immediate.s(0) }}),
.push_regs, .pop_regs => try lower.mirPushPopRegisterList(inst),
.push_regs => try lower.mirPushPopRegisterList(inst, .push),
.pop_regs => try lower.mirPushPopRegisterList(inst, .pop),
.dbg_line,
.dbg_prologue_end,
@ -190,7 +191,7 @@ fn imm(lower: Lower, ops: Mir.Inst.Ops, i: u32) Immediate {
}
fn mem(lower: Lower, ops: Mir.Inst.Ops, payload: u32) Memory {
return switch (ops) {
return lower.mir.resolveFrameLoc(switch (ops) {
.rm_sib,
.rm_sib_cc,
.m_sib,
@ -227,7 +228,7 @@ fn mem(lower: Lower, ops: Mir.Inst.Ops, payload: u32) Memory {
=> lower.mir.extraData(Mir.MemoryMoffs, payload).data.decode(),
else => unreachable,
};
});
}
fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand) Error!void {
@ -417,23 +418,15 @@ fn mirSetcc(lower: *Lower, inst: Mir.Inst) Error!void {
}
}
fn mirPushPopRegisterList(lower: *Lower, inst: Mir.Inst) Error!void {
const save_reg_list = lower.mir.extraData(Mir.SaveRegisterList, inst.data.payload).data;
const base = @intToEnum(Register, save_reg_list.base_reg);
var disp: i32 = -@intCast(i32, save_reg_list.stack_end);
const reg_list = Mir.RegisterList.fromInt(save_reg_list.register_list);
fn mirPushPopRegisterList(lower: *Lower, inst: Mir.Inst, comptime mnemonic: Mnemonic) Error!void {
const reg_list = Mir.RegisterList.fromInt(inst.data.payload);
const callee_preserved_regs = abi.getCalleePreservedRegs(lower.target.*);
for (callee_preserved_regs) |callee_preserved_reg| {
if (!reg_list.isSet(callee_preserved_regs, callee_preserved_reg)) continue;
const reg_op = Operand{ .reg = callee_preserved_reg };
const mem_op = Operand{ .mem = Memory.sib(.qword, .{ .base = base, .disp = disp }) };
try lower.emit(.none, .mov, switch (inst.tag) {
.push_regs => &.{ mem_op, reg_op },
.pop_regs => &.{ reg_op, mem_op },
else => unreachable,
});
disp += 8;
}
var it = reg_list.iterator(.{ .direction = switch (mnemonic) {
.push => .reverse,
.pop => .forward,
else => unreachable,
} });
while (it.next()) |i| try lower.emit(.none, mnemonic, &.{.{ .reg = callee_preserved_regs[i] }});
}
fn mirLeaLinker(lower: *Lower, inst: Mir.Inst) Error!void {

View File

@ -23,6 +23,7 @@ const Register = bits.Register;
instructions: std.MultiArrayList(Inst).Slice,
/// The meaning of this data is determined by `Inst.Tag` value.
extra: []const u32,
frame_locs: std.MultiArrayList(FrameLoc).Slice,
pub const Inst = struct {
tag: Tag,
@ -241,13 +242,13 @@ pub const Inst = struct {
/// Start of epilogue
dbg_epilogue_begin,
/// Update debug line
/// Uses `payload` payload with data of type `DbgLineColumn`.
/// Uses `line_column` payload containing the line and column.
dbg_line,
/// Push registers
/// Uses `payload` payload with data of type `SaveRegisterList`.
/// Uses `payload` payload containing `RegisterList.asInt` directly.
push_regs,
/// Pop registers
/// Uses `payload` payload with data of type `SaveRegisterList`.
/// Uses `payload` payload containing `RegisterList.asInt` directly.
pop_regs,
/// Tombstone
@ -500,6 +501,11 @@ pub const Inst = struct {
/// Index into the linker's symbol table.
sym_index: u32,
},
/// Debug line and column position
line_column: struct {
line: u32,
column: u32,
},
/// Index into `extra`. Meaning of what can be found there is context-dependent.
payload: u32,
};
@ -522,12 +528,11 @@ pub const LeaRegisterReloc = struct {
sym_index: u32,
};
/// Used in conjunction with `SaveRegisterList` payload to transfer a list of used registers
/// in a compact manner.
/// Used in conjunction with payload to transfer a list of used registers in a compact manner.
pub const RegisterList = struct {
bitset: BitSet = BitSet.initEmpty(),
const BitSet = IntegerBitSet(@ctz(@as(u32, 0)));
const BitSet = IntegerBitSet(32);
const Self = @This();
fn getIndexForReg(registers: []const Register, reg: Register) BitSet.MaskInt {
@ -547,6 +552,10 @@ pub const RegisterList = struct {
return self.bitset.isSet(index);
}
pub fn iterator(self: Self, comptime options: std.bit_set.IteratorOptions) BitSet.Iterator(options) {
return self.bitset.iterator(options);
}
pub fn asInt(self: Self) u32 {
return self.bitset.mask;
}
@ -562,14 +571,6 @@ pub const RegisterList = struct {
}
};
pub const SaveRegisterList = struct {
/// Base register
base_reg: u32,
/// Use `RegisterList` to populate.
register_list: u32,
stack_end: u32,
};
pub const Imm64 = struct {
msb: u32,
lsb: u32,
@ -593,41 +594,51 @@ pub const Imm64 = struct {
pub const MemorySib = struct {
/// Size of the pointer.
ptr_size: u32,
/// Base register. -1 means null, or no base register.
base: i32,
/// Scale for index register. -1 means null, or no scale.
/// This has to be in sync with `index` field.
scale: i32,
/// Index register. -1 means null, or no index register.
/// This has to be in sync with `scale` field.
index: i32,
/// Base register tag of type Memory.Base.Tag
base_tag: u32,
/// Base register of type Register or FrameIndex
base: u32,
/// Scale starting at bit 0 and index register starting at bit 4.
scale_index: u32,
/// Displacement value.
disp: i32,
pub fn encode(mem: Memory) MemorySib {
const sib = mem.sib;
assert(sib.scale_index.scale == 0 or std.math.isPowerOfTwo(sib.scale_index.scale));
return .{
.ptr_size = @enumToInt(sib.ptr_size),
.base = if (sib.base) |r| @enumToInt(r) else -1,
.scale = if (sib.scale_index) |si| si.scale else -1,
.index = if (sib.scale_index) |si| @enumToInt(si.index) else -1,
.base_tag = @enumToInt(@as(Memory.Base.Tag, sib.base)),
.base = switch (sib.base) {
.none => undefined,
.reg => |r| @enumToInt(r),
.frame => |fi| @enumToInt(fi),
},
.scale_index = @as(u32, sib.scale_index.scale) << 0 |
@as(u32, if (sib.scale_index.scale > 0)
@enumToInt(sib.scale_index.index)
else
undefined) << 4,
.disp = sib.disp,
};
}
pub fn decode(msib: MemorySib) Memory {
const base: ?Register = if (msib.base == -1) null else @intToEnum(Register, msib.base);
const scale_index: ?Memory.ScaleIndex = if (msib.index == -1) null else .{
.scale = @intCast(u4, msib.scale),
.index = @intToEnum(Register, msib.index),
};
const mem: Memory = .{ .sib = .{
const scale = @truncate(u4, msib.scale_index);
assert(scale == 0 or std.math.isPowerOfTwo(scale));
return .{ .sib = .{
.ptr_size = @intToEnum(Memory.PtrSize, msib.ptr_size),
.base = base,
.scale_index = scale_index,
.base = switch (@intToEnum(Memory.Base.Tag, msib.base_tag)) {
.none => .none,
.reg => .{ .reg = @intToEnum(Register, msib.base) },
.frame => .{ .frame = @intToEnum(bits.FrameIndex, msib.base) },
},
.scale_index = .{
.scale = scale,
.index = if (scale > 0) @intToEnum(Register, msib.scale_index >> 4) else undefined,
},
.disp = msib.disp,
} };
return mem;
}
};
@ -676,14 +687,10 @@ pub const MemoryMoffs = struct {
}
};
pub const DbgLineColumn = struct {
line: u32,
column: u32,
};
pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void {
mir.instructions.deinit(gpa);
gpa.free(mir.extra);
mir.frame_locs.deinit(gpa);
mir.* = undefined;
}
@ -704,3 +711,22 @@ pub fn extraData(mir: Mir, comptime T: type, index: u32) struct { data: T, end:
.end = i,
};
}
pub const FrameLoc = struct {
base: Register,
disp: i32,
};
pub fn resolveFrameLoc(mir: Mir, mem: Memory) Memory {
return switch (mem) {
.sib => |sib| switch (sib.base) {
.none, .reg => mem,
.frame => |index| if (mir.frame_locs.len > 0) Memory.sib(sib.ptr_size, .{
.base = .{ .reg = mir.frame_locs.items(.base)[@enumToInt(index)] },
.disp = mir.frame_locs.items(.disp)[@enumToInt(index)] + sib.disp,
.scale_index = mem.scaleIndex(),
}) else mem,
},
.rip, .moffs => mem,
};
}

View File

@ -405,14 +405,69 @@ test "Register classes" {
try expect(Register.fs.class() == .segment);
}
pub const FrameIndex = enum(u32) {
// This index refers to the start of the arguments passed to this function
args_frame,
// This index refers to the return address pushed by a `call` and popped by a `ret`.
ret_addr,
// This index refers to the base pointer pushed in the prologue and popped in the epilogue.
base_ptr,
// This index refers to the entire stack frame.
stack_frame,
// This index refers to the start of the call frame for arguments passed to called functions
call_frame,
// Other indices are used for local variable stack slots
_,
pub const named_count = @typeInfo(FrameIndex).Enum.fields.len;
pub fn isNamed(fi: FrameIndex) bool {
return @enumToInt(fi) < named_count;
}
pub fn format(
fi: FrameIndex,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) @TypeOf(writer).Error!void {
try writer.writeAll("FrameIndex");
if (fi.isNamed()) {
try writer.writeByte('.');
try writer.writeAll(@tagName(fi));
} else {
try writer.writeByte('(');
try std.fmt.formatType(@enumToInt(fi), fmt, options, writer, 0);
try writer.writeByte(')');
}
}
};
pub const Memory = union(enum) {
sib: Sib,
rip: Rip,
moffs: Moffs,
pub const ScaleIndex = packed struct {
pub const Base = union(enum) {
none,
reg: Register,
frame: FrameIndex,
pub const Tag = @typeInfo(Base).Union.tag_type.?;
pub fn isExtended(self: Base) bool {
return switch (self) {
.none, .frame => false, // neither rsp nor rbp are extended
.reg => |reg| reg.isExtended(),
};
}
};
pub const ScaleIndex = struct {
scale: u4,
index: Register,
const none = ScaleIndex{ .scale = 0, .index = undefined };
};
pub const PtrSize = enum {
@ -460,8 +515,8 @@ pub const Memory = union(enum) {
pub const Sib = struct {
ptr_size: PtrSize,
base: ?Register,
scale_index: ?ScaleIndex,
base: Base,
scale_index: ScaleIndex,
disp: i32,
};
@ -482,7 +537,7 @@ pub const Memory = union(enum) {
pub fn sib(ptr_size: PtrSize, args: struct {
disp: i32 = 0,
base: ?Register = null,
base: Base = .none,
scale_index: ?ScaleIndex = null,
}) Memory {
if (args.scale_index) |si| assert(std.math.isPowerOfTwo(si.scale));
@ -490,7 +545,7 @@ pub const Memory = union(enum) {
.base = args.base,
.disp = args.disp,
.ptr_size = ptr_size,
.scale_index = args.scale_index,
.scale_index = if (args.scale_index) |si| si else ScaleIndex.none,
} };
}
@ -502,22 +557,25 @@ pub const Memory = union(enum) {
return switch (mem) {
.moffs => true,
.rip => false,
.sib => |s| if (s.base) |r| r.class() == .segment else false,
.sib => |s| switch (s.base) {
.none, .frame => false,
.reg => |reg| reg.class() == .segment,
},
};
}
pub fn base(mem: Memory) ?Register {
pub fn base(mem: Memory) Base {
return switch (mem) {
.moffs => |m| m.seg,
.moffs => |m| .{ .reg = m.seg },
.sib => |s| s.base,
.rip => null,
.rip => .none,
};
}
pub fn scaleIndex(mem: Memory) ?ScaleIndex {
return switch (mem) {
.moffs, .rip => null,
.sib => |s| s.scale_index,
.sib => |s| if (s.scale_index.scale > 0) s.scale_index else null,
};
}

View File

@ -54,6 +54,21 @@ pub const Instruction = struct {
};
}
pub fn isBaseExtended(op: Operand) bool {
return switch (op) {
.none, .imm => false,
.reg => |reg| reg.isExtended(),
.mem => |mem| mem.base().isExtended(),
};
}
pub fn isIndexExtended(op: Operand) bool {
return switch (op) {
.none, .reg, .imm => false,
.mem => |mem| if (mem.scaleIndex()) |si| si.index.isExtended() else false,
};
}
fn format(
op: Operand,
comptime unused_format_string: []const u8,
@ -98,17 +113,24 @@ pub const Instruction = struct {
try writer.print("{s} ptr ", .{@tagName(sib.ptr_size)});
if (mem.isSegmentRegister()) {
return writer.print("{s}:0x{x}", .{ @tagName(sib.base.?), sib.disp });
return writer.print("{s}:0x{x}", .{ @tagName(sib.base.reg), sib.disp });
}
try writer.writeByte('[');
var any = false;
if (sib.base) |base| {
try writer.print("{s}", .{@tagName(base)});
any = true;
switch (sib.base) {
.none => {},
.reg => |reg| {
try writer.print("{s}", .{@tagName(reg)});
any = true;
},
.frame => |frame| {
try writer.print("{}", .{frame});
any = true;
},
}
if (sib.scale_index) |si| {
if (mem.scaleIndex()) |si| {
if (any) try writer.writeAll(" + ");
try writer.print("{s} * {d}", .{ @tagName(si.index), si.scale });
any = true;
@ -182,8 +204,8 @@ pub const Instruction = struct {
}
}
pub fn encode(inst: Instruction, writer: anytype) !void {
const encoder = Encoder(@TypeOf(writer)){ .writer = writer };
pub fn encode(inst: Instruction, writer: anytype, comptime opts: Options) !void {
const encoder = Encoder(@TypeOf(writer), opts){ .writer = writer };
const enc = inst.encoding;
const data = enc.data;
@ -269,22 +291,24 @@ pub const Instruction = struct {
const segment_override: ?Register = switch (op_en) {
.i, .zi, .o, .oi, .d, .np => null,
.fd => inst.ops[1].mem.base().?,
.td => inst.ops[0].mem.base().?,
.rm, .rmi => if (inst.ops[1].isSegmentRegister()) blk: {
break :blk switch (inst.ops[1]) {
.reg => |r| r,
.mem => |m| m.base().?,
.fd => inst.ops[1].mem.base().reg,
.td => inst.ops[0].mem.base().reg,
.rm, .rmi => if (inst.ops[1].isSegmentRegister())
switch (inst.ops[1]) {
.reg => |reg| reg,
.mem => |mem| mem.base().reg,
else => unreachable,
};
} else null,
.m, .mi, .m1, .mc, .mr, .mri, .mrc => if (inst.ops[0].isSegmentRegister()) blk: {
break :blk switch (inst.ops[0]) {
.reg => |r| r,
.mem => |m| m.base().?,
}
else
null,
.m, .mi, .m1, .mc, .mr, .mri, .mrc => if (inst.ops[0].isSegmentRegister())
switch (inst.ops[0]) {
.reg => |reg| reg,
.mem => |mem| mem.base().reg,
else => unreachable,
};
} else null,
}
else
null,
};
if (segment_override) |seg| {
legacy.setSegmentOverride(seg);
@ -307,27 +331,17 @@ pub const Instruction = struct {
const r_op = switch (op_en) {
.rm, .rmi => inst.ops[0],
.mr, .mri, .mrc => inst.ops[1],
else => null,
else => .none,
};
if (r_op) |op| {
rex.r = op.reg.isExtended();
}
rex.r = r_op.isBaseExtended();
const b_x_op = switch (op_en) {
.rm, .rmi => inst.ops[1],
.m, .mi, .m1, .mc, .mr, .mri, .mrc => inst.ops[0],
else => unreachable,
};
switch (b_x_op) {
.reg => |r| {
rex.b = r.isExtended();
},
.mem => |mem| {
rex.b = if (mem.base()) |base| base.isExtended() else false;
rex.x = if (mem.scaleIndex()) |si| si.index.isExtended() else false;
},
else => unreachable,
}
rex.b = b_x_op.isBaseExtended();
rex.x = b_x_op.isIndexExtended();
},
}
@ -348,72 +362,75 @@ pub const Instruction = struct {
switch (mem) {
.moffs => unreachable,
.sib => |sib| {
if (sib.base) |base| {
if (base.class() == .segment) {
// TODO audit this wrt SIB
try encoder.modRm_SIBDisp0(operand_enc);
if (sib.scale_index) |si| {
const scale = math.log2_int(u4, si.scale);
try encoder.sib_scaleIndexDisp32(scale, si.index.lowEnc());
} else {
try encoder.sib_disp32();
}
try encoder.disp32(sib.disp);
} else {
assert(base.class() == .general_purpose);
const dst = base.lowEnc();
const src = operand_enc;
if (dst == 4 or sib.scale_index != null) {
if (sib.disp == 0 and dst != 5) {
try encoder.modRm_SIBDisp0(src);
if (sib.scale_index) |si| {
const scale = math.log2_int(u4, si.scale);
try encoder.sib_scaleIndexBase(scale, si.index.lowEnc(), dst);
} else {
try encoder.sib_base(dst);
}
} else if (math.cast(i8, sib.disp)) |_| {
try encoder.modRm_SIBDisp8(src);
if (sib.scale_index) |si| {
const scale = math.log2_int(u4, si.scale);
try encoder.sib_scaleIndexBaseDisp8(scale, si.index.lowEnc(), dst);
} else {
try encoder.sib_baseDisp8(dst);
}
try encoder.disp8(@truncate(i8, sib.disp));
} else {
try encoder.modRm_SIBDisp32(src);
if (sib.scale_index) |si| {
const scale = math.log2_int(u4, si.scale);
try encoder.sib_scaleIndexBaseDisp32(scale, si.index.lowEnc(), dst);
} else {
try encoder.sib_baseDisp32(dst);
}
try encoder.disp32(sib.disp);
}
} else {
if (sib.disp == 0 and dst != 5) {
try encoder.modRm_indirectDisp0(src, dst);
} else if (math.cast(i8, sib.disp)) |_| {
try encoder.modRm_indirectDisp8(src, dst);
try encoder.disp8(@truncate(i8, sib.disp));
} else {
try encoder.modRm_indirectDisp32(src, dst);
try encoder.disp32(sib.disp);
}
}
}
} else {
.sib => |sib| switch (sib.base) {
.none => {
try encoder.modRm_SIBDisp0(operand_enc);
if (sib.scale_index) |si| {
if (mem.scaleIndex()) |si| {
const scale = math.log2_int(u4, si.scale);
try encoder.sib_scaleIndexDisp32(scale, si.index.lowEnc());
} else {
try encoder.sib_disp32();
}
try encoder.disp32(sib.disp);
}
},
.reg => |base| if (base.class() == .segment) {
// TODO audit this wrt SIB
try encoder.modRm_SIBDisp0(operand_enc);
if (mem.scaleIndex()) |si| {
const scale = math.log2_int(u4, si.scale);
try encoder.sib_scaleIndexDisp32(scale, si.index.lowEnc());
} else {
try encoder.sib_disp32();
}
try encoder.disp32(sib.disp);
} else {
assert(base.class() == .general_purpose);
const dst = base.lowEnc();
const src = operand_enc;
if (dst == 4 or mem.scaleIndex() != null) {
if (sib.disp == 0 and dst != 5) {
try encoder.modRm_SIBDisp0(src);
if (mem.scaleIndex()) |si| {
const scale = math.log2_int(u4, si.scale);
try encoder.sib_scaleIndexBase(scale, si.index.lowEnc(), dst);
} else {
try encoder.sib_base(dst);
}
} else if (math.cast(i8, sib.disp)) |_| {
try encoder.modRm_SIBDisp8(src);
if (mem.scaleIndex()) |si| {
const scale = math.log2_int(u4, si.scale);
try encoder.sib_scaleIndexBaseDisp8(scale, si.index.lowEnc(), dst);
} else {
try encoder.sib_baseDisp8(dst);
}
try encoder.disp8(@truncate(i8, sib.disp));
} else {
try encoder.modRm_SIBDisp32(src);
if (mem.scaleIndex()) |si| {
const scale = math.log2_int(u4, si.scale);
try encoder.sib_scaleIndexBaseDisp32(scale, si.index.lowEnc(), dst);
} else {
try encoder.sib_baseDisp32(dst);
}
try encoder.disp32(sib.disp);
}
} else {
if (sib.disp == 0 and dst != 5) {
try encoder.modRm_indirectDisp0(src, dst);
} else if (math.cast(i8, sib.disp)) |_| {
try encoder.modRm_indirectDisp8(src, dst);
try encoder.disp8(@truncate(i8, sib.disp));
} else {
try encoder.modRm_indirectDisp32(src, dst);
try encoder.disp32(sib.disp);
}
}
},
.frame => if (@TypeOf(encoder).options.allow_frame_loc) {
try encoder.modRm_indirectDisp32(operand_enc, undefined);
try encoder.disp32(undefined);
} else return error.CannotEncode,
},
.rip => |rip| {
try encoder.modRm_RIPDisp32(operand_enc);
@ -482,11 +499,14 @@ pub const LegacyPrefixes = packed struct {
}
};
fn Encoder(comptime T: type) type {
pub const Options = struct { allow_frame_loc: bool = false };
fn Encoder(comptime T: type, comptime opts: Options) type {
return struct {
writer: T,
const Self = @This();
pub const options = opts;
// --------
// Prefixes