Class: Rubycc::Backend::X86_64

Inherits:
Object
  • Object
show all
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