// rexcode · Brendan Punsky (dotbmp@github), original author package rexcode_x86 // ============================================================================= // SECTION: 2. OPERANDS // ============================================================================= // ----------------------------------------------------------------------------- // SECTION: 2.1 Operand Kind Enum // ----------------------------------------------------------------------------- Operand_Kind :: enum u8 { NONE, REGISTER, // register operand MEMORY, // memory operand IMMEDIATE, // immediate value RELATIVE, // relative offset (for jumps/calls) } // ----------------------------------------------------------------------------- // SECTION: 2.2 Memory Operand - Packed Representation // ----------------------------------------------------------------------------- MEM_BASE_RIP :: 30 MEM_BASE_NONE :: 31 MEM_INDEX_NONE :: 31 Memory :: bit_field u64 { base_hw: u8 | 5, base_ext: bool | 1, index_hw: u8 | 5, index_ext: bool | 1, scale_enc: u8 | 2, disp: i32 | 32, segment: u8 | 3, addr_size_override: bool | 1, base_class: u8 | 5, index_class: u8 | 5, // When set, `disp` holds a label id (not a literal displacement): the // encoder emits a REL32 relocation at the disp32's byte offset instead of // the literal, so a RIP-relative operand can reference a label. Uses one of // Memory's 4 spare bits -- no growth, and one bit-test on the hot path. disp_is_label: bool | 1, } @(require_results) mem_make :: proc "contextless" (base: Register, index: Register, scale: u8, disp: i32, segment: Register) -> Memory { mem: Memory = --- mem.base_hw = MEM_BASE_NONE mem.base_ext = false mem.base_class = 0 if base == RIP { mem.base_hw = MEM_BASE_RIP } else if base != NONE { mem.base_hw = reg_hw(base) mem.base_ext = reg_needs_rex(base) mem.base_class = u8((u16(base) >> 8) & 0x1F) } mem.index_hw = MEM_INDEX_NONE mem.index_ext = false mem.index_class = 0 if index != NONE { mem.index_hw = reg_hw(index) mem.index_ext = reg_needs_rex(index) mem.index_class = u8((u16(index) >> 8) & 0x1F) } switch scale { case 1: mem.scale_enc = 0 case 2: mem.scale_enc = 1 case 4: mem.scale_enc = 2 case 8: mem.scale_enc = 3 case: mem.scale_enc = 0 } mem.segment = 0 if segment != NONE && reg_class(segment) == REG_SEG { mem.segment = reg_hw(segment) + 1 } mem.disp = disp // Must be explicitly cleared: `mem` starts uninitialized (`= ---`) and the // encoder reads this on the hot path -- garbage here would spuriously turn a // literal displacement into a label reference. mem_rip_label sets it after. mem.disp_is_label = false return mem } // ----------------------------------------------------------------------------- // SECTION: 2.3 Memory Utility Functions // ----------------------------------------------------------------------------- @(require_results) mem_scale :: #force_inline proc "contextless" (m: Memory) -> u8 { return 1 << m.scale_enc } @(require_results) mem_is_rip_relative :: #force_inline proc "contextless" (m: Memory) -> bool { return m.base_hw == MEM_BASE_RIP } @(require_results) mem_has_base :: #force_inline proc "contextless" (m: Memory) -> bool { return m.base_hw != MEM_BASE_NONE } @(require_results) mem_has_index :: #force_inline proc "contextless" (m: Memory) -> bool { return m.index_hw != MEM_INDEX_NONE } @(require_results) mem_base :: proc "contextless" (m: Memory) -> Register { if m.base_hw == MEM_BASE_NONE { return NONE } if m.base_hw == MEM_BASE_RIP { return RIP } class := u16(m.base_class) << 8 return Register(class | u16(m.base_hw)) } @(require_results) mem_index :: proc "contextless" (m: Memory) -> Register { if m.index_hw == MEM_INDEX_NONE { return NONE } class := u16(m.index_class) << 8 return Register(class | u16(m.index_hw)) } // ----------------------------------------------------------------------------- // SECTION: 2.4 Memory Convenience Constructors // ----------------------------------------------------------------------------- // Convenience constructors for common memory patterns @(require_results) mem_base_only :: #force_inline proc "contextless" (base: Register) -> Memory { return mem_make(base, NONE, 1, 0, NONE) } @(require_results) mem_base_disp :: #force_inline proc "contextless" (base: Register, disp: i32) -> Memory { return mem_make(base, NONE, 1, disp, NONE) } @(require_results) mem_base_index :: #force_inline proc "contextless" (base, index: Register, scale: u8) -> Memory { return mem_make(base, index, scale, 0, NONE) } @(require_results) mem_base_index_disp :: #force_inline proc "contextless" (base, index: Register, scale: u8, disp: i32) -> Memory { return mem_make(base, index, scale, disp, NONE) } @(require_results) mem_rip_disp :: #force_inline proc "contextless" (disp: i32) -> Memory { return mem_make(RIP, NONE, 1, disp, NONE) } // RIP-relative memory operand whose displacement references a label rather than // a literal. The encoder writes a placeholder disp32 and emits a REL32 // relocation for `label_id` at that field's byte offset -- the native way to // express `lea reg, [rip +