Class: Rubycc::Backend::X86_64
- Inherits:
-
Object
- Object
- Rubycc::Backend::X86_64
- Defined in:
- lib/rubycc/backend/x86_64.rb
Overview
x86_64 code generator using a spill-everything strategy: every virtual
register lives in its own 8-byte stack slot at [rbp - 8*(n+1)], and each
IR instruction loads its operands into eax/ecx, computes, and stores the
result back. Arithmetic on a 4-byte-or-narrower type stays 32-bit (using
eax/ecx), whose natural wrap-around reproduces C's semantics for free;
long/unsigned long/pointer arithmetic is 64-bit (a REX.W prefix,
selected by an IR op's size == 8). Slots are always read and written 64
bits at a time so a pointer value survives intact (see #load_reg /
#store_reg).
Value representation: an integer value narrower than 8 bytes is held in
its slot's low 32 bits, extended to 32 bits following its type's
signedness (sign-extended when signed, zero-extended when unsigned); the
slot's bits 32..63 are indeterminate for such a value. An 8-byte value
(long/unsigned long/pointer) uses the whole 64-bit slot. Every change of
width happens only at two boundaries: a memory access (:load sign-extends,
:uload zero-extends, :store truncates to size bytes) and an explicit
widening/narrowing op (:sext / :zext, whose size is the source width).
Same-width, sign-only reinterpretations (int <-> unsigned int) need no
code, since the two share a bit pattern.
A floating value follows the same slot discipline: a float lives in its
slot's low 4 bytes as an IEEE754 single-precision bit pattern, a double
in the whole 8-byte slot as a double-precision one; a float's bits 32..63
are indeterminate, exactly like a narrow integer's. The floating ops read
and write these slots with movss/movsd through xmm0/xmm1 (scratch), so a
floating constant materialized by :const (its bit pattern as an integer
immediate) is picked up unchanged, and int<->float conversions (:itof,
:ftoi, :ftof) move between a GP slot and an xmm register with the cvt*
family.
System V AMD64 calling convention: an integer/pointer result comes back in
eax/rax and a float/double one in xmm0 (:ret's float width and a :call's
ret class select movss/movsd through it); a void function's ":ret" (a nil
operand) leaves both unset. Arguments are classified per parameter: an
integer/pointer takes the next of edi,esi,edx,ecx,r8d,r9d, a float/double
the next of xmm0..7, and whatever class overflows its registers spills to
the stack (each an eightbyte, the low bits carrying either class). A
variadic call sets al to the number of xmm registers it used, and a
variadic definition's prologue saves all six integer and all eight xmm
argument registers into a 176-byte register-save area so __builtin_va_arg
can reach the variable part.
Defined Under Namespace
Classes: Result
Constant Summary collapse
- EAX =
Register numbers. For eax/ecx/edx these are the low 3 bits of the ModR/M reg field; edi/esi likewise (6, 7); r8d/r9d are 8/9 and need a REX.R prefix with the low 3 bits going into the reg field.
0- ECX =
1- EDX =
2- ESI =
6- EDI =
7- R8D =
8- R9D =
9- R10 =
r10 is a System V caller-saved scratch register that is not an argument register, so it can hold an indirect call's target without clobbering any argument already loaded into edi..r9d.
10- RSP =
The stack pointer's register number (its ModR/M reg/rm field). It is only ever named as the source of a "mov [rbp+disp], rsp" that captures the post-alloca rsp as the block's base address.
4- XMM0 =
The two vector (xmm) scratch registers the floating ops use. Their numbers 0/1 double as the ModR/M reg/rm fields, so no REX.R is ever needed to name them. Every floating value round-trips through a GP stack slot, so these hold nothing across instructions.
0- XMM1 =
1- ARG_REGISTERS =
System V AMD64 integer argument registers, in order. A call with N arguments passes the first six here; any beyond that go on the stack.
[EDI, ESI, EDX, ECX, R8D, R9D].freeze
- GP_RETURN_REGISTERS =
The registers an aggregate result comes back in, in eightbyte order: an INTEGER eightbyte fills rax then rdx, an SSE eightbyte fills xmm0 then xmm1 (psABI 3.2.3). A mixed struct uses one from each list in the order its eightbytes are classified.
[EAX, EDX].freeze
- SSE_RETURN_REGISTERS =
[XMM0, XMM1].freeze
- SETCC_OPCODES =
IR comparison op -> setcc opcode (second byte of the 0F 9x encoding). The result is materialized into eax as an int 0/1 by movzx. The signed forms (setl/setle/setg/setge) test the sign/overflow flags; the unsigned forms (setb/setbe/seta/setae) test the carry flag, which is what an unsigned or pointer comparison needs.
{ eq: 0x94, # sete ne: 0x95, # setne lt: 0x9C, # setl le: 0x9E, # setle gt: 0x9F, # setg ge: 0x9D, # setge ult: 0x92, # setb (below, unsigned <) ule: 0x96, # setbe (below or equal, unsigned <=) ugt: 0x97, # seta (above, unsigned >) uge: 0x93 # setae (above or equal, unsigned >=) }.freeze
Instance Method Summary collapse
Instance Method Details
#compile(ir_func) ⇒ Object
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/rubycc/backend/x86_64.rb', line 129 def compile(ir_func) @code = +"".b # Control-flow bookkeeping: `@labels` maps a label id to its resolved # code offset; `@fixups` collects [patch_offset, label_id] pairs whose # rel32 field is overwritten once every label offset is known. @labels = {} @fixups = [] # Each `call` and each string-literal reference records a kind-tagged # relocation here (see Result) so the object writer can emit a # .rela.text entry once this function's base in .text is known. @relocations = [] # Kept for :va_start, which derives its gp_offset/fp_offset seeds and # overflow start from the named parameters' register classes. @param_kinds = ir_func.param_kinds emit_prologue(ir_func.vreg_count, ir_func.param_count, ir_func.param_kinds, ir_func.stack_objects, ir_func.variadic) ir_func.insts.each { |inst| emit_instruction(inst) } resolve_fixups Result.new( bytes: @code, symbols: [{ name: ir_func.name, offset: 0, size: @code.bytesize }], relocations: @relocations ) end |