rubycc 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +56 -0
  3. data/LICENSE.txt +21 -0
  4. data/NOTICE +52 -0
  5. data/README.md +208 -0
  6. data/data/README.md +117 -0
  7. data/data/r10_corpus_scan.json +4082 -0
  8. data/data/r10_manual_classification.json +5342 -0
  9. data/data/r10_verification_m4a.json +1531 -0
  10. data/data/r10_verification_m4b.json +1818 -0
  11. data/data/r10_verification_m4c.json +1489 -0
  12. data/data/r10_verification_m4d.json +318 -0
  13. data/data/r10_verification_rbs.json +192 -0
  14. data/data/verified_gems.json +397 -0
  15. data/exe/rmake +16 -0
  16. data/exe/rubycc +9 -0
  17. data/exe/rubycc-ar +114 -0
  18. data/exe/rubycc-doctor +14 -0
  19. data/exe/rubycc-pkgconf +8 -0
  20. data/include/float.h +87 -0
  21. data/include/iso646.h +18 -0
  22. data/include/libc/alloca.h +18 -0
  23. data/include/libc/arpa/inet.h +61 -0
  24. data/include/libc/assert.h +43 -0
  25. data/include/libc/dirent.h +72 -0
  26. data/include/libc/dlfcn.h +58 -0
  27. data/include/libc/features.h +205 -0
  28. data/include/libc/glibc/aarch64/ctype.h +108 -0
  29. data/include/libc/glibc/aarch64/endian.h +54 -0
  30. data/include/libc/glibc/aarch64/errno.h +154 -0
  31. data/include/libc/glibc/aarch64/fcntl.h +144 -0
  32. data/include/libc/glibc/aarch64/inttypes.h +182 -0
  33. data/include/libc/glibc/aarch64/limits.h +63 -0
  34. data/include/libc/glibc/aarch64/pthread.h +131 -0
  35. data/include/libc/glibc/aarch64/setjmp.h +84 -0
  36. data/include/libc/glibc/aarch64/stdint.h +174 -0
  37. data/include/libc/glibc/aarch64/sys/epoll.h +88 -0
  38. data/include/libc/glibc/aarch64/sys/fcntl.h +19 -0
  39. data/include/libc/glibc/aarch64/sys/select.h +75 -0
  40. data/include/libc/glibc/aarch64/sys/stat.h +136 -0
  41. data/include/libc/glibc/aarch64/sys/syscall.h +167 -0
  42. data/include/libc/glibc/aarch64/sys/time.h +74 -0
  43. data/include/libc/glibc/aarch64/sys/types.h +127 -0
  44. data/include/libc/glibc/aarch64/time.h +113 -0
  45. data/include/libc/glibc/x86_64/ctype.h +108 -0
  46. data/include/libc/glibc/x86_64/endian.h +54 -0
  47. data/include/libc/glibc/x86_64/errno.h +154 -0
  48. data/include/libc/glibc/x86_64/fcntl.h +134 -0
  49. data/include/libc/glibc/x86_64/inttypes.h +182 -0
  50. data/include/libc/glibc/x86_64/limits.h +61 -0
  51. data/include/libc/glibc/x86_64/pthread.h +111 -0
  52. data/include/libc/glibc/x86_64/setjmp.h +82 -0
  53. data/include/libc/glibc/x86_64/stdint.h +163 -0
  54. data/include/libc/glibc/x86_64/sys/epoll.h +91 -0
  55. data/include/libc/glibc/x86_64/sys/fcntl.h +19 -0
  56. data/include/libc/glibc/x86_64/sys/select.h +75 -0
  57. data/include/libc/glibc/x86_64/sys/stat.h +130 -0
  58. data/include/libc/glibc/x86_64/sys/syscall.h +188 -0
  59. data/include/libc/glibc/x86_64/sys/time.h +74 -0
  60. data/include/libc/glibc/x86_64/sys/types.h +123 -0
  61. data/include/libc/glibc/x86_64/time.h +113 -0
  62. data/include/libc/grp.h +50 -0
  63. data/include/libc/langinfo.h +126 -0
  64. data/include/libc/link.h +18 -0
  65. data/include/libc/locale.h +83 -0
  66. data/include/libc/math.h +191 -0
  67. data/include/libc/netinet/in.h +126 -0
  68. data/include/libc/netinet/tcp.h +44 -0
  69. data/include/libc/poll.h +46 -0
  70. data/include/libc/pwd.h +58 -0
  71. data/include/libc/regex.h +53 -0
  72. data/include/libc/sched.h +35 -0
  73. data/include/libc/signal.h +204 -0
  74. data/include/libc/stdio.h +157 -0
  75. data/include/libc/stdlib.h +92 -0
  76. data/include/libc/string.h +74 -0
  77. data/include/libc/strings.h +25 -0
  78. data/include/libc/sys/cdefs.h +126 -0
  79. data/include/libc/sys/inotify.h +111 -0
  80. data/include/libc/sys/ioctl.h +36 -0
  81. data/include/libc/sys/mman.h +65 -0
  82. data/include/libc/sys/param.h +41 -0
  83. data/include/libc/sys/resource.h +109 -0
  84. data/include/libc/sys/socket.h +167 -0
  85. data/include/libc/sys/statfs.h +82 -0
  86. data/include/libc/sys/timerfd.h +55 -0
  87. data/include/libc/sys/uio.h +40 -0
  88. data/include/libc/sys/un.h +25 -0
  89. data/include/libc/sys/utsname.h +35 -0
  90. data/include/libc/sys/wait.h +135 -0
  91. data/include/libc/termios.h +179 -0
  92. data/include/libc/unistd.h +194 -0
  93. data/include/stdalign.h +16 -0
  94. data/include/stdarg.h +31 -0
  95. data/include/stdatomic.h +158 -0
  96. data/include/stdbool.h +15 -0
  97. data/include/stdckdint.h +28 -0
  98. data/include/stddef.h +60 -0
  99. data/include/stdnoreturn.h +18 -0
  100. data/include/x86intrin.h +16 -0
  101. data/lib/rubycc/backend/aarch64.rb +1724 -0
  102. data/lib/rubycc/backend/x86_64.rb +1369 -0
  103. data/lib/rubycc/compile_error.rb +36 -0
  104. data/lib/rubycc/compiler.rb +305 -0
  105. data/lib/rubycc/doctor/builder.rb +151 -0
  106. data/lib/rubycc/doctor/cli.rb +190 -0
  107. data/lib/rubycc/doctor/fetcher.rb +84 -0
  108. data/lib/rubycc/doctor/gemfile.rb +115 -0
  109. data/lib/rubycc/doctor/verified_gems.rb +114 -0
  110. data/lib/rubycc/doctor.rb +16 -0
  111. data/lib/rubycc/driver.rb +463 -0
  112. data/lib/rubycc/front/ast.rb +528 -0
  113. data/lib/rubycc/front/constant_evaluator.rb +631 -0
  114. data/lib/rubycc/front/initializer_resolver.rb +592 -0
  115. data/lib/rubycc/front/lexeme_reader.rb +460 -0
  116. data/lib/rubycc/front/lexer.rb +232 -0
  117. data/lib/rubycc/front/parser.rb +4122 -0
  118. data/lib/rubycc/front/token.rb +49 -0
  119. data/lib/rubycc/ir/call_convention.rb +486 -0
  120. data/lib/rubycc/ir/generator.rb +6036 -0
  121. data/lib/rubycc/ir/ir.rb +417 -0
  122. data/lib/rubycc/link/compat_runtime.rb +89 -0
  123. data/lib/rubycc/link/errors.rb +13 -0
  124. data/lib/rubycc/link/executable_linker.rb +405 -0
  125. data/lib/rubycc/link/library_resolver.rb +437 -0
  126. data/lib/rubycc/link/partial_linker.rb +546 -0
  127. data/lib/rubycc/link/shared_linker.rb +1732 -0
  128. data/lib/rubycc/mkmf_shim.rb +83 -0
  129. data/lib/rubycc/objfile/ar_archive.rb +437 -0
  130. data/lib/rubycc/objfile/elf_reader.rb +646 -0
  131. data/lib/rubycc/objfile/elf_writer.rb +891 -0
  132. data/lib/rubycc/objfile/relocatable_writer.rb +376 -0
  133. data/lib/rubycc/pkgconf/cli.rb +139 -0
  134. data/lib/rubycc/pkgconf/errors.rb +39 -0
  135. data/lib/rubycc/pkgconf/model.rb +28 -0
  136. data/lib/rubycc/pkgconf/parser.rb +109 -0
  137. data/lib/rubycc/pkgconf/pkgconf.rb +15 -0
  138. data/lib/rubycc/pkgconf/resolver.rb +70 -0
  139. data/lib/rubycc/pkgconf/search_path.rb +38 -0
  140. data/lib/rubycc/pkgconf/system_path_filter.rb +131 -0
  141. data/lib/rubycc/preprocess/constant_expression.rb +219 -0
  142. data/lib/rubycc/preprocess/glibc_version.rb +124 -0
  143. data/lib/rubycc/preprocess/pp_token.rb +62 -0
  144. data/lib/rubycc/preprocess/preprocessor.rb +2020 -0
  145. data/lib/rubycc/preprocess/scanner.rb +290 -0
  146. data/lib/rubycc/preprocess/token_converter.rb +157 -0
  147. data/lib/rubycc/rmake/cli.rb +143 -0
  148. data/lib/rubycc/rmake/errors.rb +71 -0
  149. data/lib/rubycc/rmake/executor.rb +818 -0
  150. data/lib/rubycc/rmake/expander.rb +251 -0
  151. data/lib/rubycc/rmake/makefile.rb +352 -0
  152. data/lib/rubycc/rmake/model.rb +137 -0
  153. data/lib/rubycc/rmake/parser.rb +226 -0
  154. data/lib/rubycc/rmake/rmake.rb +15 -0
  155. data/lib/rubycc/type.rb +1236 -0
  156. data/lib/rubycc/version.rb +5 -0
  157. data/lib/rubycc.rb +36 -0
  158. data/lib/rubygems_plugin.rb +102 -0
  159. metadata +219 -0
@@ -0,0 +1,1369 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../ir/ir"
4
+
5
+ module Rubycc
6
+ module Backend
7
+ # x86_64 code generator using a spill-everything strategy: every virtual
8
+ # register lives in its own 8-byte stack slot at [rbp - 8*(n+1)], and each
9
+ # IR instruction loads its operands into eax/ecx, computes, and stores the
10
+ # result back. Arithmetic on a 4-byte-or-narrower type stays 32-bit (using
11
+ # eax/ecx), whose natural wrap-around reproduces C's semantics for free;
12
+ # `long`/`unsigned long`/pointer arithmetic is 64-bit (a REX.W prefix,
13
+ # selected by an IR op's size == 8). Slots are always read and written 64
14
+ # bits at a time so a pointer value survives intact (see #load_reg /
15
+ # #store_reg).
16
+ #
17
+ # Value representation: an integer value narrower than 8 bytes is held in
18
+ # its slot's low 32 bits, extended to 32 bits following its type's
19
+ # signedness (sign-extended when signed, zero-extended when unsigned); the
20
+ # slot's bits 32..63 are indeterminate for such a value. An 8-byte value
21
+ # (long/unsigned long/pointer) uses the whole 64-bit slot. Every change of
22
+ # width happens only at two boundaries: a memory access (:load sign-extends,
23
+ # :uload zero-extends, :store truncates to `size` bytes) and an explicit
24
+ # widening/narrowing op (:sext / :zext, whose `size` is the source width).
25
+ # Same-width, sign-only reinterpretations (int <-> unsigned int) need no
26
+ # code, since the two share a bit pattern.
27
+ #
28
+ # A floating value follows the same slot discipline: a `float` lives in its
29
+ # slot's low 4 bytes as an IEEE754 single-precision bit pattern, a `double`
30
+ # in the whole 8-byte slot as a double-precision one; a `float`'s bits 32..63
31
+ # are indeterminate, exactly like a narrow integer's. The floating ops read
32
+ # and write these slots with movss/movsd through xmm0/xmm1 (scratch), so a
33
+ # floating constant materialized by :const (its bit pattern as an integer
34
+ # immediate) is picked up unchanged, and int<->float conversions (:itof,
35
+ # :ftoi, :ftof) move between a GP slot and an xmm register with the cvt*
36
+ # family.
37
+ #
38
+ # System V AMD64 calling convention: an integer/pointer result comes back in
39
+ # eax/rax and a float/double one in xmm0 (:ret's float width and a :call's
40
+ # `ret` class select movss/movsd through it); a void function's ":ret" (a nil
41
+ # operand) leaves both unset. Arguments are classified per parameter: an
42
+ # integer/pointer takes the next of edi,esi,edx,ecx,r8d,r9d, a float/double
43
+ # the next of xmm0..7, and whatever class overflows its registers spills to
44
+ # the stack (each an eightbyte, the low bits carrying either class). A
45
+ # variadic call sets al to the number of xmm registers it used, and a
46
+ # variadic definition's prologue saves all six integer and all eight xmm
47
+ # argument registers into a 176-byte register-save area so __builtin_va_arg
48
+ # can reach the variable part.
49
+ class X86_64
50
+ # Result of compiling one function: `bytes` is the machine code (an
51
+ # ASCII-8BIT String), `symbols` is an array of
52
+ # { name:, offset:, size: } describing the emitted function symbols, and
53
+ # `relocations` is an array of relocation records the linker must resolve
54
+ # (each `offset` is relative to the start of this function's code). Every
55
+ # record carries a `kind`:
56
+ # * { kind: :call, offset:, symbol: } — a "call rel32" site, resolved
57
+ # against the named symbol (R_X86_64_PLT32);
58
+ # * { kind: :string, offset:, string_id: } — a "lea rip" displacement
59
+ # addressing read-only string `string_id` (R_X86_64_PC32 against the
60
+ # .rodata section);
61
+ # * { kind: :global, offset:, symbol: } — a "lea rip" displacement
62
+ # addressing the named file-scope variable `symbol` (R_X86_64_PC32
63
+ # against that symbol);
64
+ # * { kind: :func, offset:, symbol: } — a "lea rip" displacement taking
65
+ # the address of the function `symbol` (a function designator or
66
+ # "&f"), resolved like a `call` against that (defined or undefined)
67
+ # symbol;
68
+ # * { kind: :got, offset:, symbol: } — a "mov rax, [rip+disp32]" that
69
+ # loads `symbol`'s address from its Global Offset Table slot (the PIC
70
+ # form of :global/:func for a symbol this unit does not define),
71
+ # resolved as R_X86_64_REX_GOTPCRELX against that symbol.
72
+ Result = Data.define(:bytes, :symbols, :relocations)
73
+
74
+ # Register numbers. For eax/ecx/edx these are the low 3 bits of the
75
+ # ModR/M reg field; edi/esi likewise (6, 7); r8d/r9d are 8/9 and need a
76
+ # REX.R prefix with the low 3 bits going into the reg field.
77
+ EAX = 0
78
+ ECX = 1
79
+ EDX = 2
80
+ ESI = 6
81
+ EDI = 7
82
+ R8D = 8
83
+ R9D = 9
84
+ # r10 is a System V caller-saved scratch register that is not an argument
85
+ # register, so it can hold an indirect call's target without clobbering
86
+ # any argument already loaded into edi..r9d.
87
+ R10 = 10
88
+ # The stack pointer's register number (its ModR/M reg/rm field). It is only
89
+ # ever named as the source of a "mov [rbp+disp], rsp" that captures the
90
+ # post-alloca rsp as the block's base address.
91
+ RSP = 4
92
+
93
+ # The two vector (xmm) scratch registers the floating ops use. Their
94
+ # numbers 0/1 double as the ModR/M reg/rm fields, so no REX.R is ever
95
+ # needed to name them. Every floating value round-trips through a GP stack
96
+ # slot, so these hold nothing across instructions.
97
+ XMM0 = 0
98
+ XMM1 = 1
99
+
100
+ # System V AMD64 integer argument registers, in order. A call with N
101
+ # arguments passes the first six here; any beyond that go on the stack.
102
+ ARG_REGISTERS = [EDI, ESI, EDX, ECX, R8D, R9D].freeze
103
+
104
+ # The registers an aggregate result comes back in, in eightbyte order: an
105
+ # INTEGER eightbyte fills rax then rdx, an SSE eightbyte fills xmm0 then
106
+ # xmm1 (psABI 3.2.3). A mixed struct uses one from each list in the order
107
+ # its eightbytes are classified.
108
+ GP_RETURN_REGISTERS = [EAX, EDX].freeze
109
+ SSE_RETURN_REGISTERS = [XMM0, XMM1].freeze
110
+
111
+ # IR comparison op -> setcc opcode (second byte of the 0F 9x encoding).
112
+ # The result is materialized into eax as an int 0/1 by movzx. The signed
113
+ # forms (setl/setle/setg/setge) test the sign/overflow flags; the unsigned
114
+ # forms (setb/setbe/seta/setae) test the carry flag, which is what an
115
+ # unsigned or pointer comparison needs.
116
+ SETCC_OPCODES = {
117
+ eq: 0x94, # sete
118
+ ne: 0x95, # setne
119
+ lt: 0x9C, # setl
120
+ le: 0x9E, # setle
121
+ gt: 0x9F, # setg
122
+ ge: 0x9D, # setge
123
+ ult: 0x92, # setb (below, unsigned <)
124
+ ule: 0x96, # setbe (below or equal, unsigned <=)
125
+ ugt: 0x97, # seta (above, unsigned >)
126
+ uge: 0x93 # setae (above or equal, unsigned >=)
127
+ }.freeze
128
+
129
+ def compile(ir_func)
130
+ @code = +"".b
131
+ # Control-flow bookkeeping: `@labels` maps a label id to its resolved
132
+ # code offset; `@fixups` collects [patch_offset, label_id] pairs whose
133
+ # rel32 field is overwritten once every label offset is known.
134
+ @labels = {}
135
+ @fixups = []
136
+ # Each `call` and each string-literal reference records a kind-tagged
137
+ # relocation here (see Result) so the object writer can emit a
138
+ # .rela.text entry once this function's base in .text is known.
139
+ @relocations = []
140
+ # Kept for :va_start, which derives its gp_offset/fp_offset seeds and
141
+ # overflow start from the named parameters' register classes.
142
+ @param_kinds = ir_func.param_kinds
143
+ emit_prologue(ir_func.vreg_count, ir_func.param_count, ir_func.param_kinds,
144
+ ir_func.stack_objects, ir_func.variadic)
145
+ ir_func.insts.each { |inst| emit_instruction(inst) }
146
+ resolve_fixups
147
+
148
+ Result.new(
149
+ bytes: @code,
150
+ symbols: [{ name: ir_func.name, offset: 0, size: @code.bytesize }],
151
+ relocations: @relocations
152
+ )
153
+ end
154
+
155
+ private
156
+
157
+ # Frame layout, from rbp downward: first the virtual-register slots
158
+ # (8 bytes each, rounded up to a 16-byte region), then the stack objects,
159
+ # and last — for a variadic function — a 176-byte register-save area below
160
+ # everything else. Each object is placed at a 16-byte-aligned size below
161
+ # the previous one, and @object_offsets[id] records the rbp-relative
162
+ # displacement of the object's base (its lowest address, i.e. element 0).
163
+ def emit_prologue(vreg_count, param_count, param_kinds, stack_objects, variadic)
164
+ vreg_region = align16(vreg_count * 8)
165
+ @object_offsets = []
166
+ running = vreg_region
167
+ stack_objects.each do |object_size|
168
+ running += align16(object_size)
169
+ @object_offsets << -running
170
+ end
171
+ # A variadic function reserves a 176-byte register-save area at the very
172
+ # bottom of the frame; :va_start points reg_save_area here and the
173
+ # prologue spills the six integer argument registers (48 bytes) followed
174
+ # by the eight xmm registers (128 bytes, one 16-byte slot each) into it,
175
+ # the System V psABI layout __builtin_va_arg reads back. 176 is a
176
+ # multiple of 16, so the frame stays 16-aligned.
177
+ @reg_save_area_offset = nil
178
+ if variadic
179
+ running += 176
180
+ @reg_save_area_offset = -running
181
+ end
182
+ frame_size = align16(running)
183
+ emit(0x55) # push rbp
184
+ emit(0x48, 0x89, 0xE5) # mov rbp, rsp
185
+ emit(0x48, 0x81, 0xEC) # sub rsp, imm32
186
+ emit_bytes([frame_size].pack("L<"))
187
+ spill_parameters(param_kinds)
188
+ if variadic
189
+ emit_save_gp_registers
190
+ emit_save_xmm_registers
191
+ end
192
+ end
193
+
194
+ # Brings each incoming argument into its parameter slot (the first
195
+ # `param_count` vregs, one per param_kinds entry) so it reads back like any
196
+ # other vreg. The generator has already fixed where each parameter arrives:
197
+ # a :gp in the next integer argument register (spilled directly), an
198
+ # :sse4/:sse8 in the next xmm (spilled with movss/movsd), and a :mem on the
199
+ # stack — pushed by the caller and now sitting above the return address at
200
+ # [rbp + 16 + 8*k] (k the :mem running index), copied down through rax as a
201
+ # whole eightbyte. A kind that would overrun its register file is a
202
+ # generator contract violation and raises.
203
+ def spill_parameters(param_kinds)
204
+ next_gp = 0
205
+ next_sse = 0
206
+ next_stack = 0
207
+ param_kinds.each_with_index do |kind, i|
208
+ case kind
209
+ when :gp
210
+ raise "parameter :gp overruns the integer registers" if next_gp >= ARG_REGISTERS.size
211
+
212
+ store_reg(ARG_REGISTERS[next_gp], i)
213
+ next_gp += 1
214
+ when :sse4, :sse8
215
+ raise "parameter #{kind} overruns the xmm registers" if next_sse >= 8
216
+
217
+ store_xmm(next_sse, i, kind == :sse8 ? 8 : 4)
218
+ next_sse += 1
219
+ when :mem
220
+ emit(0x48, 0x8B) # mov rax, [rbp + disp]
221
+ emit_modrm_rbp_disp(EAX, 16 + 8 * next_stack)
222
+ store_reg(EAX, i)
223
+ next_stack += 1
224
+ when :pad_stack
225
+ # A 16-alignment pad occupies one incoming stack eightbyte with no
226
+ # bound parameter, so its slot is left unwritten and only the counter
227
+ # advances (see the caller's matching gap in #emit_call_args).
228
+ next_stack += 1
229
+ else
230
+ raise "unknown parameter kind #{kind.inspect}"
231
+ end
232
+ end
233
+ end
234
+
235
+ # Spills all six System V integer argument registers into the variadic
236
+ # register-save area, in ABI order from its base, so :va_start's
237
+ # reg_save_area pointer plus a gp_offset reaches each one. The spill reads
238
+ # the argument registers (unmodified by the parameter spilling above, which
239
+ # only writes their values out to slots), so every register still holds its
240
+ # incoming argument here.
241
+ def emit_save_gp_registers
242
+ ARG_REGISTERS.each_with_index do |reg, i|
243
+ emit(0x48 | (reg >= 8 ? 0x04 : 0)) # REX.W (+ REX.R for r8/r9)
244
+ emit(0x89) # mov [rbp + disp], r64
245
+ emit_modrm_rbp_disp(reg & 7, @reg_save_area_offset + 8 * i)
246
+ end
247
+ end
248
+
249
+ # Spills all eight xmm argument registers into the variadic register-save
250
+ # area, each into the low 8 bytes of its 16-byte psABI slot (the slots
251
+ # start at offset 48, just past the six saved GP registers). All eight are
252
+ # saved unconditionally rather than guarded by al: this subset's va_arg only
253
+ # ever reads back a double's 8 bytes, so saving the low half of each slot is
254
+ # enough, and saving every register keeps the emitted code fixed regardless
255
+ # of the actual argument count (a determinism the al-guarded form would give
256
+ # up for a branch this backend does not need).
257
+ def emit_save_xmm_registers
258
+ 8.times do |i|
259
+ emit(0xF2, 0x0F, 0x11) # movsd [rbp + disp], xmm_i
260
+ emit_modrm_rbp_disp(i, @reg_save_area_offset + 48 + 16 * i)
261
+ end
262
+ end
263
+
264
+ def emit_instruction(inst)
265
+ case inst.op
266
+ when :const
267
+ emit_const(inst.dst, inst.a, inst.size)
268
+ when :copy
269
+ load_reg(EAX, inst.a)
270
+ store_reg(EAX, inst.dst)
271
+ when :add
272
+ emit_binary(inst.dst, inst.a, inst.b, [0x01, 0xC8], inst.size) # add eax, ecx
273
+ when :sub
274
+ emit_binary(inst.dst, inst.a, inst.b, [0x29, 0xC8], inst.size) # sub eax, ecx
275
+ when :mul
276
+ emit_binary(inst.dst, inst.a, inst.b, [0x0F, 0xAF, 0xC1], inst.size) # imul eax, ecx
277
+ when :mulhi
278
+ emit_mulhi(inst.dst, inst.a, inst.b) # high 64 bits of an unsigned 64x64 product
279
+ when :div
280
+ emit_divmod(inst.dst, inst.a, inst.b, EAX, inst.size) # quotient in eax
281
+ when :mod
282
+ emit_divmod(inst.dst, inst.a, inst.b, EDX, inst.size) # remainder in edx
283
+ when :udiv
284
+ emit_udivmod(inst.dst, inst.a, inst.b, EAX, inst.size) # quotient in eax
285
+ when :umod
286
+ emit_udivmod(inst.dst, inst.a, inst.b, EDX, inst.size) # remainder in edx
287
+ when :and
288
+ emit_binary(inst.dst, inst.a, inst.b, [0x21, 0xC8], inst.size) # and eax, ecx
289
+ when :or
290
+ emit_binary(inst.dst, inst.a, inst.b, [0x09, 0xC8], inst.size) # or eax, ecx
291
+ when :xor
292
+ emit_binary(inst.dst, inst.a, inst.b, [0x31, 0xC8], inst.size) # xor eax, ecx
293
+ when :shl
294
+ # shl eax, cl (D3 /4): the count comes from cl, which #emit_binary
295
+ # loads into ecx alongside the value in eax. A size-8 operand takes a
296
+ # REX.W prefix (shl rax, cl). The x86 shift masks the count to 5 bits
297
+ # for a 32-bit operand, 6 for a 64-bit one.
298
+ emit_binary(inst.dst, inst.a, inst.b, [0xD3, 0xE0], inst.size)
299
+ when :sar
300
+ # sar eax, cl (D3 /7): the arithmetic (sign-preserving) right shift,
301
+ # so a negative value shifts in copies of its sign bit, matching C's
302
+ # implementation-defined ">>" on a signed value.
303
+ emit_binary(inst.dst, inst.a, inst.b, [0xD3, 0xF8], inst.size)
304
+ when :shr
305
+ # shr eax, cl (D3 /5): the logical (zero-filling) right shift, the
306
+ # unsigned counterpart of :sar.
307
+ emit_binary(inst.dst, inst.a, inst.b, [0xD3, 0xE8], inst.size)
308
+ when :eq, :ne, :lt, :le, :gt, :ge, :ult, :ule, :ugt, :uge
309
+ emit_comparison(inst.dst, inst.a, inst.b, SETCC_OPCODES.fetch(inst.op), inst.size)
310
+ when :fadd
311
+ emit_float_binary(inst.dst, inst.a, inst.b, inst.size, 0x58) # addss/addsd
312
+ when :fsub
313
+ emit_float_binary(inst.dst, inst.a, inst.b, inst.size, 0x5C) # subss/subsd
314
+ when :fmul
315
+ emit_float_binary(inst.dst, inst.a, inst.b, inst.size, 0x59) # mulss/mulsd
316
+ when :fdiv
317
+ emit_float_binary(inst.dst, inst.a, inst.b, inst.size, 0x5E) # divss/divsd
318
+ when :feq, :fne, :flt, :fle, :fgt, :fge
319
+ emit_float_comparison(inst.op, inst.dst, inst.a, inst.b, inst.size)
320
+ when :itof
321
+ emit_itof(inst.dst, inst.a, inst.b, inst.size)
322
+ when :ftoi
323
+ emit_ftoi(inst.dst, inst.a, inst.b, inst.size)
324
+ when :ftof
325
+ emit_ftof(inst.dst, inst.a, inst.size)
326
+ when :neg
327
+ load_reg(EAX, inst.a)
328
+ emit(0x48) if inst.size == 8 # REX.W for neg rax
329
+ emit(0xF7, 0xD8) # neg eax/rax
330
+ store_reg(EAX, inst.dst)
331
+ when :sext
332
+ emit_sext(inst.dst, inst.a, inst.size)
333
+ when :zext
334
+ emit_zext(inst.dst, inst.a, inst.size)
335
+ when :string_addr
336
+ emit_string_addr(inst.dst, inst.a)
337
+ when :global_addr
338
+ emit_global_addr(inst.dst, inst.a)
339
+ when :got_addr
340
+ emit_got_addr(inst.dst, inst.a)
341
+ when :label
342
+ @labels[inst.a] = @code.bytesize
343
+ when :jump
344
+ emit_jump(inst.a)
345
+ when :jump_if_zero
346
+ emit_jump_if_zero(inst.a, inst.b)
347
+ when :call
348
+ emit_call(inst.dst, inst.a, inst.b, inst.size)
349
+ when :call_indirect
350
+ emit_call_indirect(inst.dst, inst.a, inst.b, inst.size)
351
+ when :func_addr
352
+ emit_func_addr(inst.dst, inst.a)
353
+ when :addr_of
354
+ emit_addr_of(inst.dst, inst.a)
355
+ when :object_addr
356
+ emit_object_addr(inst.dst, inst.a)
357
+ when :load
358
+ emit_load(inst.dst, inst.a, inst.size)
359
+ when :uload
360
+ emit_uload(inst.dst, inst.a, inst.size)
361
+ when :store
362
+ emit_store(inst.a, inst.b, inst.size)
363
+ when :memcpy
364
+ emit_memcpy(inst.a, inst.b, inst.size)
365
+ when :va_start
366
+ emit_va_start(inst.a, inst.b)
367
+ when :alloca
368
+ emit_alloca(inst.dst, inst.a)
369
+ when :bit_scan
370
+ emit_bit_scan(inst.dst, inst.a, inst.b, inst.size)
371
+ when :atomic_load
372
+ emit_atomic_load(inst.dst, inst.a, inst.size)
373
+ when :atomic_store
374
+ emit_atomic_store(inst.a, inst.b, inst.size)
375
+ when :atomic_rmw
376
+ emit_atomic_rmw(inst.dst, inst.a, inst.b[0], inst.b[1], inst.size)
377
+ when :atomic_cas
378
+ emit_atomic_cas(inst.dst, inst.a, inst.b[0], inst.b[1], inst.size)
379
+ when :atomic_fence
380
+ emit_atomic_fence
381
+ when :ret
382
+ emit_ret(inst.a, inst.size)
383
+ else
384
+ raise "unsupported IR op: #{inst.op}"
385
+ end
386
+ end
387
+
388
+ # Emits a direct call: place the arguments (see #emit_call_args), set al for
389
+ # a variadic callee (see #emit_variadic_al), then "call rel32" with a zero
390
+ # displacement placeholder recorded as a relocation, undo any stack-argument
391
+ # adjustment, and read the result back (see #store_call_result). `size` is
392
+ # the [fixed, ret] descriptor (or nil).
393
+ def emit_call(dst, name, args, size)
394
+ fixed, ret = size || [nil, nil]
395
+ reclaim, sse_count = emit_call_args(args)
396
+ emit_variadic_al(fixed, sse_count)
397
+ emit(0xE8) # call rel32
398
+ @relocations << { kind: :call, offset: @code.bytesize, symbol: name }
399
+ emit_bytes([0].pack("l<")) # linker patches this via R_X86_64_PLT32
400
+ emit_reclaim_stack_args(reclaim)
401
+ store_call_result(dst, ret)
402
+ end
403
+
404
+ # Emits an indirect call through a function-pointer value: place the
405
+ # arguments, load the target address into r10 (a non-argument scratch, so
406
+ # it survives the argument setup), set al for a variadic callee, "call r10",
407
+ # then undo any stack-argument adjustment and read the result. The al load
408
+ # comes after the r10 load so it lands right before the call, and r10 (a
409
+ # distinct register) is not disturbed by it.
410
+ def emit_call_indirect(dst, target_vreg, args, size)
411
+ fixed, ret = size || [nil, nil]
412
+ reclaim, sse_count = emit_call_args(args)
413
+ load_reg(R10, target_vreg) # mov r10, [rbp + disp]
414
+ emit_variadic_al(fixed, sse_count)
415
+ emit(0x41, 0xFF, 0xD2) # call r10
416
+ emit_reclaim_stack_args(reclaim)
417
+ store_call_result(dst, ret)
418
+ end
419
+
420
+ # Reads a call's result into its destination. An in-register struct result
421
+ # (`ret` a [buffer_vreg, pieces] array) is scattered into the caller's
422
+ # scratch buffer (dst is unused, the value being that buffer's address); a
423
+ # float/double comes back in xmm0 (movss/movsd, `ret` being :sse4/:sse8);
424
+ # every other value — including a MEMORY struct's hidden pointer — in rax.
425
+ def store_call_result(dst, ret)
426
+ if ret.is_a?(Array)
427
+ store_struct_call_result(ret)
428
+ elsif ret
429
+ store_xmm(XMM0, dst, ret == :sse8 ? 8 : 4)
430
+ else
431
+ store_reg(EAX, dst)
432
+ end
433
+ end
434
+
435
+ # Scatters an in-register struct result into the caller's scratch buffer.
436
+ # `ret` is [buffer_vreg, pieces]; buffer_vreg's slot (kept live across the
437
+ # call) holds the buffer address, loaded into rcx — a register the return
438
+ # values (rax/rdx, xmm0/xmm1) never occupy, so loading it clobbers none of
439
+ # them. Each eightbyte is stored at its piece's offset from its return
440
+ # register: INTEGER eightbytes from rax then rdx, SSE eightbytes from xmm0
441
+ # then xmm1, taken in eightbyte order.
442
+ def store_struct_call_result(ret)
443
+ buffer_vreg, pieces = ret
444
+ load_reg(ECX, buffer_vreg) # rcx = buffer address
445
+ each_return_eightbyte(pieces) do |offset, reg, sse|
446
+ if sse
447
+ emit(0xF2, 0x0F, 0x11) # movsd [rcx + disp8], xmm
448
+ emit(0x40 | (reg << 3) | 0x01, offset)
449
+ else
450
+ emit(0x48, 0x89) # mov [rcx + disp8], r64
451
+ emit(0x40 | (reg << 3) | 0x01, offset)
452
+ end
453
+ end
454
+ end
455
+
456
+ # Yields each result eightbyte's [byte offset in the buffer, register,
457
+ # sse?], handing out the INTEGER (rax, rdx) and SSE (xmm0, xmm1) return
458
+ # registers in classification order. Shared by the caller-side scatter
459
+ # (#store_struct_call_result) and the callee-side gather (#emit_struct_ret).
460
+ #
461
+ # A System V aggregate piece is always a whole eightbyte, so the movsd /
462
+ # mov r64 below carry it entire and only the piece's offset is read here;
463
+ # its width needs no attention the way an AAPCS64 HFA member's would.
464
+ def each_return_eightbyte(pieces)
465
+ next_gp = 0
466
+ next_sse = 0
467
+ pieces.each do |piece|
468
+ if piece.kind == :gp
469
+ yield piece.offset, GP_RETURN_REGISTERS[next_gp], false
470
+ next_gp += 1
471
+ else
472
+ yield piece.offset, SSE_RETURN_REGISTERS[next_sse], true
473
+ next_sse += 1
474
+ end
475
+ end
476
+ end
477
+
478
+ # Sets al before a variadic call, as the System V AMD64 ABI requires: al
479
+ # holds the number of vector (xmm) registers used to pass arguments, which
480
+ # #emit_call_args counted while classifying them. `fixed` is nil for a
481
+ # non-variadic callee (nothing emitted) and the fixed parameter count
482
+ # otherwise (its value unused — only its non-nil-ness marks the call
483
+ # variadic). "mov al, imm8" writes just al; eax's upper bytes held only a
484
+ # scratch relay for the stack arguments, so overwriting al harms nothing,
485
+ # while the argument registers (edi..r9d, xmm0..7) are already loaded.
486
+ def emit_variadic_al(fixed, sse_count)
487
+ return if fixed.nil?
488
+
489
+ emit(0xB0, sse_count) # mov al, imm8
490
+ end
491
+
492
+ # Places a call's arguments (each a [vreg, kind] pair the generator has
493
+ # already assigned a class) for the System V AMD64 convention and returns
494
+ # [reclaim_bytes, sse_count]: the bytes the caller must drop from the stack
495
+ # afterwards, and the number of xmm registers used (for a variadic al). A
496
+ # :gp argument goes to its integer register, an :sse4/:sse8 to its xmm and a
497
+ # :mem to the stack (see #classify_call_args). The :mem arguments are pushed
498
+ # in reverse so the first ends up at the lowest address ([rsp] at the call),
499
+ # each a whole eightbyte (the slot's low bits carry its value). When their
500
+ # count is odd an extra 8-byte pad is
501
+ # pushed first so rsp stays 16-aligned at the call, as the ABI requires (the
502
+ # prologue already leaves it 16-aligned). All arguments live in rbp-relative
503
+ # slots, so the rsp changes never disturb a not-yet-loaded one.
504
+ def emit_call_args(args)
505
+ gp_args, sse_args, stack_args = classify_call_args(args)
506
+ pad = stack_args.size.odd? ? 8 : 0
507
+ emit_sub_rsp(pad) if pad.positive?
508
+ stack_args.reverse_each do |vreg|
509
+ if vreg == :pad_stack
510
+ emit_sub_rsp(8) # reserve a 16-alignment gap, no value stored
511
+ else
512
+ load_reg(EAX, vreg) # rax = argument value (whole eightbyte)
513
+ emit(0x50) # push rax
514
+ end
515
+ end
516
+ gp_args.each { |reg, vreg| load_reg(reg, vreg) }
517
+ sse_args.each { |xmm, vreg, width| load_xmm(xmm, vreg, width) }
518
+ [stack_args.size * 8 + pad, sse_args.size]
519
+ end
520
+
521
+ # Routes each [vreg, kind] argument to its register class, returning
522
+ # [gp_args, sse_args, stack_args]: gp_args are [reg, vreg] pairs bound to
523
+ # edi..r9d, sse_args are [xmm, vreg, width] triples bound to xmm0..7, and
524
+ # stack_args are the vregs (in left-to-right order) passed on the stack. The
525
+ # generator has already fixed the placement: a :gp takes the next integer
526
+ # register, an :sse4/:sse8 the next xmm and a :mem the next stack eightbyte,
527
+ # each class handed out strictly in order. A kind that would overrun its
528
+ # register file (say a seventh :gp) is a generator contract violation, not a
529
+ # user error, so it raises rather than silently spilling.
530
+ def classify_call_args(args)
531
+ next_gp = 0
532
+ next_sse = 0
533
+ gp_args = []
534
+ sse_args = []
535
+ stack_args = []
536
+ args.each do |vreg, kind|
537
+ case kind
538
+ when :gp
539
+ raise "call argument :gp overruns the integer registers" if next_gp >= ARG_REGISTERS.size
540
+
541
+ gp_args << [ARG_REGISTERS[next_gp], vreg]
542
+ next_gp += 1
543
+ when :sse4, :sse8
544
+ raise "call argument #{kind} overruns the xmm registers" if next_sse >= 8
545
+
546
+ sse_args << [next_sse, vreg, kind == :sse8 ? 8 : 4]
547
+ next_sse += 1
548
+ when :mem
549
+ stack_args << vreg
550
+ when :pad_stack
551
+ # A 16-alignment pad is a stack eightbyte carrying no value; it holds
552
+ # its place in the stacked-argument order so the aggregate behind it
553
+ # lands on a 16-byte boundary (see the gap in #emit_call_args).
554
+ stack_args << :pad_stack
555
+ else
556
+ raise "unknown call argument kind #{kind.inspect}"
557
+ end
558
+ end
559
+ [gp_args, sse_args, stack_args]
560
+ end
561
+
562
+ # Reclaims `bytes` of stack space (the pushed arguments and any alignment
563
+ # pad) after a call returns; a no-op when nothing was pushed.
564
+ def emit_reclaim_stack_args(bytes)
565
+ return if bytes.zero?
566
+
567
+ emit(0x48, 0x81, 0xC4) # add rsp, imm32
568
+ emit_bytes([bytes].pack("L<"))
569
+ end
570
+
571
+ # sub rsp, imm32 — reserves `bytes` of stack space (the pre-call alignment
572
+ # pad for an odd number of stack arguments).
573
+ def emit_sub_rsp(bytes)
574
+ emit(0x48, 0x81, 0xEC) # sub rsp, imm32
575
+ emit_bytes([bytes].pack("L<"))
576
+ end
577
+
578
+ # :ret — loads the return value into its ABI register(s), then "leave; ret".
579
+ # `size` is nil for an integer/pointer return (rax, via load_reg), 4/8 for a
580
+ # floating one (movss/movsd into xmm0), or a piece array for an in-register
581
+ # struct return, whose eightbytes are gathered from the buffer `value_vreg`
582
+ # points at into the return registers. A void return (a nil operand) loads
583
+ # nothing.
584
+ def emit_ret(value_vreg, size)
585
+ if value_vreg.nil?
586
+ nil
587
+ elsif size.is_a?(Array)
588
+ emit_struct_ret(value_vreg, size)
589
+ elsif size
590
+ load_xmm(XMM0, value_vreg, size)
591
+ else
592
+ load_reg(EAX, value_vreg)
593
+ end
594
+ emit(0xC9) # leave
595
+ emit(0xC3) # ret
596
+ end
597
+
598
+ # Gathers an in-register struct return into its result registers. `buffer_vreg`
599
+ # holds the address of the value (a stack object the generator filled), loaded
600
+ # into rcx; each eightbyte at its piece's offset is loaded into its return register —
601
+ # INTEGER eightbytes into rax then rdx, SSE into xmm0 then xmm1 — leaving them
602
+ # set for "leave; ret". rcx is loaded first and never itself a return register.
603
+ def emit_struct_ret(buffer_vreg, pieces)
604
+ load_reg(ECX, buffer_vreg) # rcx = buffer address
605
+ each_return_eightbyte(pieces) do |offset, reg, sse|
606
+ if sse
607
+ emit(0xF2, 0x0F, 0x10) # movsd xmm, [rcx + disp8]
608
+ emit(0x40 | (reg << 3) | 0x01, offset)
609
+ else
610
+ emit(0x48, 0x8B) # mov r64, [rcx + disp8]
611
+ emit(0x40 | (reg << 3) | 0x01, offset)
612
+ end
613
+ end
614
+ end
615
+
616
+ # :func_addr — lea rax, [rip + disp32] takes the address of the named
617
+ # function, like :global_addr but recorded as a { kind: :func } relocation
618
+ # the compiler resolves against the function symbol (defined here or an
619
+ # undefined external), giving a usable function-pointer value.
620
+ def emit_func_addr(dst, name)
621
+ emit(0x48, 0x8D, 0x05) # REX.W lea rax, [rip + disp32]
622
+ @relocations << { kind: :func, offset: @code.bytesize, symbol: name }
623
+ emit_bytes([0].pack("l<"))
624
+ store_reg(EAX, dst)
625
+ end
626
+
627
+ # :string_addr — lea rax, [rip + disp32] materializes the address of
628
+ # read-only string `string_id`. The disp32 is a zero placeholder recorded
629
+ # as a { kind: :string } relocation; the linker patches it PC-relatively
630
+ # (R_X86_64_PC32) against the .rodata section. A 64-bit store then parks
631
+ # the resulting char * in the destination slot.
632
+ def emit_string_addr(dst, string_id)
633
+ emit(0x48, 0x8D, 0x05) # REX.W lea rax, [rip + disp32]
634
+ @relocations << { kind: :string, offset: @code.bytesize, string_id: string_id }
635
+ emit_bytes([0].pack("l<"))
636
+ store_reg(EAX, dst)
637
+ end
638
+
639
+ # :global_addr — lea rax, [rip + disp32] materializes the address of the
640
+ # named file-scope variable `symbol`. Like :string_addr, the disp32 is a
641
+ # zero placeholder recorded as a relocation ({ kind: :global }); the linker
642
+ # patches it PC-relatively (R_X86_64_PC32) against that symbol, and a
643
+ # 64-bit store parks the resulting address in the destination slot.
644
+ def emit_global_addr(dst, symbol)
645
+ emit(0x48, 0x8D, 0x05) # REX.W lea rax, [rip + disp32]
646
+ @relocations << { kind: :global, offset: @code.bytesize, symbol: symbol }
647
+ emit_bytes([0].pack("l<"))
648
+ store_reg(EAX, dst)
649
+ end
650
+
651
+ # :got_addr — the PIC form of :global_addr / :func_addr. "mov rax, [rip +
652
+ # disp32]" (48 8B 05) loads the address of `symbol` from its Global Offset
653
+ # Table slot rather than forming it PC-relatively: the slot holds the
654
+ # symbol's run-time address, so the load yields a usable pointer directly
655
+ # and any following load/store through it is unchanged. The disp32 is a
656
+ # zero placeholder recorded as a { kind: :got } relocation; the linker
657
+ # patches it PC-relatively against the symbol's GOT entry
658
+ # (R_X86_64_REX_GOTPCRELX). Emitted only under -fPIC, and only for a symbol
659
+ # this translation unit does not itself define, so a definition in another
660
+ # shared object can interpose on it.
661
+ def emit_got_addr(dst, symbol)
662
+ emit(0x48, 0x8B, 0x05) # REX.W mov rax, [rip + disp32]
663
+ @relocations << { kind: :got, offset: @code.bytesize, symbol: symbol }
664
+ emit_bytes([0].pack("l<"))
665
+ store_reg(EAX, dst)
666
+ end
667
+
668
+ # "&x": lea rax, [rbp+disp] computes the absolute address of the operand's
669
+ # slot, which a 64-bit store then parks in the destination slot.
670
+ def emit_addr_of(dst, slot_vreg)
671
+ emit(0x48, 0x8D) # REX.W lea rax, [rbp+disp]
672
+ emit_modrm_rbp_disp(EAX, slot_disp(slot_vreg))
673
+ store_reg(EAX, dst)
674
+ end
675
+
676
+ # :object_addr — lea rax, [rbp+disp] loads a stack object's base address
677
+ # (an array's first element) into rax, then a 64-bit store parks it in the
678
+ # destination slot, giving the decayed pointer value.
679
+ def emit_object_addr(dst, object_id)
680
+ emit(0x48, 0x8D) # REX.W lea rax, [rbp+disp]
681
+ emit_modrm_rbp_disp(EAX, @object_offsets[object_id])
682
+ store_reg(EAX, dst)
683
+ end
684
+
685
+ # :sext — sign-extend a's low `size` bytes to the full 64-bit register.
686
+ # size 4 is a movsxd rax, eax (the classic int -> long widening, so
687
+ # pointer-offset scaling sees a correct, possibly negative index); size 2
688
+ # a movsx rax, ax and size 1 a movsx rax, al, which re-derive a signed
689
+ # short/char value from just its stored low bytes. The 64-bit store writes
690
+ # the widened value back.
691
+ def emit_sext(dst, src_vreg, size)
692
+ load_reg(EAX, src_vreg) # rax = value
693
+ case size
694
+ when 1
695
+ emit(0x48, 0x0F, 0xBE, 0xC0) # REX.W movsx rax, al
696
+ when 2
697
+ emit(0x48, 0x0F, 0xBF, 0xC0) # REX.W movsx rax, ax
698
+ else # 4
699
+ emit(0x48, 0x63, 0xC0) # REX.W movsxd rax, eax
700
+ end
701
+ store_reg(EAX, dst)
702
+ end
703
+
704
+ # :zext — zero-extend a's low `size` bytes to the full 64-bit register.
705
+ # size 1/2 are a movzx eax, al / movzx eax, ax; size 4 a plain mov eax,
706
+ # eax. Each writes eax, which x86-64 defines to clear the upper 32 bits of
707
+ # rax, so all three leave a clean 64-bit zero-extended value.
708
+ def emit_zext(dst, src_vreg, size)
709
+ load_reg(EAX, src_vreg) # rax = value
710
+ case size
711
+ when 1
712
+ emit(0x0F, 0xB6, 0xC0) # movzx eax, al
713
+ when 2
714
+ emit(0x0F, 0xB7, 0xC0) # movzx eax, ax
715
+ else # 4
716
+ emit(0x89, 0xC0) # mov eax, eax
717
+ end
718
+ store_reg(EAX, dst)
719
+ end
720
+
721
+ # :bit_scan — count the zero bits of a's value (__builtin_ctz/clz). The
722
+ # value is loaded into rcx and the result computed into rax. A :forward
723
+ # scan is `bsf eax, ecx` (0F BC /r), which yields the trailing zero count
724
+ # directly. A :reverse scan is `bsr eax, ecx` (0F BD /r), giving the index
725
+ # of the highest set bit; xor-ing that with (bits-1) turns it into the
726
+ # leading zero count (since the index lies in 0..bits-1, the xor equals
727
+ # (bits-1) - index). A size-8 operand adds a REX.W prefix throughout. A zero
728
+ # operand is undefined, so no guard is emitted.
729
+ def emit_bit_scan(dst, src_vreg, direction, size)
730
+ load_reg(ECX, src_vreg) # rcx = value to scan
731
+ if direction == :forward
732
+ emit(0x48) if size == 8 # REX.W: 64-bit bsf
733
+ emit(0x0F, 0xBC, 0xC1) # bsf eax, ecx (trailing zero count)
734
+ else
735
+ emit(0x48) if size == 8 # REX.W: 64-bit bsr
736
+ emit(0x0F, 0xBD, 0xC1) # bsr eax, ecx (index of highest set bit)
737
+ emit(0x48) if size == 8 # REX.W: 64-bit xor
738
+ emit(0x83, 0xF0, size * 8 - 1) # xor eax, bits-1 -> (bits-1) - index
739
+ end
740
+ store_reg(EAX, dst)
741
+ end
742
+
743
+ # --- atomics -----------------------------------------------------------
744
+ #
745
+ # The four atomic ops all lower at sequential consistency, the strongest
746
+ # order (the IR carries no weaker one — see IR::Generator#gen_builtin_atomic
747
+ # for why strengthening is always sound). `size` is only ever 4 or 8, the
748
+ # generator having diagnosed every other width, so each helper distinguishes
749
+ # exactly those two: an 8-byte form is the 4-byte one with a REX.W prefix.
750
+ #
751
+ # x86-64's memory model (Intel SDM 3A §8.2, "loads are not reordered with
752
+ # other loads, stores are not reordered with other stores, and a store is
753
+ # not reordered with an *older* load") already provides everything acquire
754
+ # and release need, so a seq_cst load is a plain mov. Only the one
755
+ # store-then-load reordering the model does permit has to be closed for a
756
+ # seq_cst *store*, which the implicitly locked `xchg` does — a lock-prefixed
757
+ # instruction is a full barrier — and which is why a store is an exchange
758
+ # here rather than a mov. Every read-modify-write carries an explicit `lock`
759
+ # (F0) except `xchg` with a memory operand, whose lock is architecturally
760
+ # implicit; adding a redundant F0 there would only make the encoding longer.
761
+
762
+ # :atomic_fence — a full sequentially-consistent fence. The encoding is
763
+ # MFENCE (0f ae f0); unlike an empty inline-asm memory clobber this also
764
+ # constrains the hardware's store/load ordering.
765
+ def emit_atomic_fence
766
+ emit(0x0F, 0xAE, 0xF0)
767
+ end
768
+
769
+ # :atomic_load — a sequentially consistent read. On this target that is an
770
+ # ordinary aligned mov (see the section comment), so the code is exactly
771
+ # #emit_load's 4/8-byte case; it is spelled out separately because the
772
+ # *reason* it is a plain mov is a property of x86-64's model, not of the IR.
773
+ def emit_atomic_load(dst, ptr_vreg, size)
774
+ load_reg(EAX, ptr_vreg) # rax = pointer value
775
+ emit(0x48) if size == 8 # REX.W
776
+ emit(0x8B, 0x00) # mov eax/rax, [rax]
777
+ store_reg(EAX, dst)
778
+ end
779
+
780
+ # :atomic_store — a sequentially consistent write, lowered as an exchange
781
+ # whose result is thrown away. `xchg r/m, r` (87 /r) is atomic and, being
782
+ # implicitly locked, is also the full barrier a seq_cst store needs; a mov
783
+ # followed by an mfence would be equivalent and longer.
784
+ def emit_atomic_store(ptr_vreg, value_vreg, size)
785
+ load_reg(EAX, ptr_vreg) # rax = destination address
786
+ load_reg(ECX, value_vreg) # rcx = value
787
+ emit(0x48) if size == 8 # REX.W
788
+ emit(0x87, 0x08) # xchg [rax], ecx/rcx
789
+ end
790
+
791
+ # :atomic_rmw — the read-modify-write family. Three shapes cover the six
792
+ # kinds:
793
+ #
794
+ # :exchange `xchg [rax], ecx` leaves the previous value in ecx.
795
+ # the add family `lock xadd [rax], ecx` (F0 0F C1 /r) writes the sum and
796
+ # leaves the previous value in ecx, which is :fetch_add outright.
797
+ # :fetch_sub negates the operand first, since subtraction is addition of
798
+ # the negation and the machine has no `xsub`. The two "_fetch" forms want
799
+ # the *new* value instead: rather than a compare-exchange loop they
800
+ # recompute it from the exchange-add's own result (old + operand == new),
801
+ # which is both shorter and lock-free by construction — and is what gcc
802
+ # itself emits. The operand is kept in edx across the xadd for that.
803
+ # :or_fetch has no exchange-or instruction to derive from, so it is the
804
+ # one kind that needs a compare-exchange retry loop (see below).
805
+ def emit_atomic_rmw(dst, ptr_vreg, value_vreg, kind, size)
806
+ return emit_atomic_or_fetch(dst, ptr_vreg, value_vreg, size) if kind == :or_fetch
807
+
808
+ load_reg(EAX, ptr_vreg) # rax = address
809
+ load_reg(ECX, value_vreg) # rcx = operand
810
+ negate = kind == :fetch_sub || kind == :sub_fetch
811
+ keep = kind == :add_fetch || kind == :sub_fetch
812
+ if negate
813
+ emit(0x48) if size == 8
814
+ emit(0xF7, 0xD9) # neg ecx/rcx
815
+ end
816
+ if keep
817
+ emit(0x48) if size == 8
818
+ emit(0x89, 0xCA) # mov edx/rdx, ecx/rcx (save the addend)
819
+ end
820
+ if kind == :exchange
821
+ emit(0x48) if size == 8
822
+ emit(0x87, 0x08) # xchg [rax], ecx/rcx
823
+ else
824
+ emit(0xF0) # lock
825
+ emit(0x48) if size == 8
826
+ emit(0x0F, 0xC1, 0x08) # xadd [rax], ecx/rcx (ecx <- old)
827
+ end
828
+ if keep
829
+ emit(0x48) if size == 8
830
+ emit(0x01, 0xD1) # add ecx/rcx, edx/rdx -> the new value
831
+ end
832
+ store_reg(ECX, dst)
833
+ end
834
+
835
+ # :atomic_rmw with kind :or_fetch — the one member of the family with no
836
+ # single-instruction form, so it retries a compare-exchange until it wins:
837
+ #
838
+ # mov eax, [rdi] ; eax = the value we are betting on
839
+ # loop:
840
+ # mov edx, eax ; edx = that value
841
+ # or edx, esi ; ... with the operand or-ed in
842
+ # lock cmpxchg [rdi], edx ; if [rdi] is still eax, store edx and set ZF;
843
+ # ; otherwise reload eax with what is there now
844
+ # jne loop
845
+ # mov eax, edx ; the value stored is the result
846
+ #
847
+ # cmpxchg reads and writes eax implicitly, so the address and the operand
848
+ # are held in rdi/rsi (which no other operand of this instruction needs)
849
+ # rather than in the usual rax/rcx scratch pair. The retry branch's
850
+ # displacement is computed from the emitted byte count rather than recorded
851
+ # as a fixup, because both ends are inside this one instruction's code.
852
+ def emit_atomic_or_fetch(dst, ptr_vreg, value_vreg, size)
853
+ load_reg(EDI, ptr_vreg) # rdi = address
854
+ load_reg(ESI, value_vreg) # rsi = operand
855
+ emit(0x48) if size == 8
856
+ emit(0x8B, 0x07) # mov eax/rax, [rdi]
857
+ loop_start = @code.bytesize
858
+ emit(0x48) if size == 8
859
+ emit(0x89, 0xC2) # mov edx/rdx, eax/rax
860
+ emit(0x48) if size == 8
861
+ emit(0x09, 0xF2) # or edx/rdx, esi/rsi
862
+ emit(0xF0) # lock
863
+ emit(0x48) if size == 8
864
+ emit(0x0F, 0xB1, 0x17) # cmpxchg [rdi], edx/rdx
865
+ emit(0x75) # jne rel8
866
+ emit((loop_start - (@code.bytesize + 1)) & 0xFF)
867
+ emit(0x48) if size == 8
868
+ emit(0x89, 0xD0) # mov eax/rax, edx/rdx (the value stored)
869
+ store_reg(EAX, dst)
870
+ end
871
+
872
+ # :atomic_cas — __atomic_compare_exchange_n. `lock cmpxchg [rdi], edx`
873
+ # (F0 0F B1 /r) compares [rdi] against eax: on a match it stores edx and
874
+ # sets ZF, otherwise it loads what was really there into eax and clears ZF.
875
+ #
876
+ # mov eax, [rsi] ; eax = *expected
877
+ # lock cmpxchg [rdi], edx
878
+ # sete cl ; cl = the boolean result (leaves the flags)
879
+ # je skip
880
+ # mov [rsi], eax ; the failing path reports the value it saw
881
+ # skip:
882
+ # movzx eax, cl
883
+ #
884
+ # The write-back is guarded by the branch rather than done unconditionally
885
+ # (which would store the same bits on the winning path) for one reason: an
886
+ # `expected` that aliases the atomic object itself would otherwise have the
887
+ # freshly exchanged value overwritten by the old one. `sete` does not
888
+ # disturb the flags, so the `je` still reads cmpxchg's ZF.
889
+ def emit_atomic_cas(dst, ptr_vreg, expected_vreg, desired_vreg, size)
890
+ load_reg(EDI, ptr_vreg) # rdi = the atomic object's address
891
+ load_reg(ESI, expected_vreg) # rsi = &expected
892
+ load_reg(EDX, desired_vreg) # rdx = the value to store
893
+ emit(0x48) if size == 8
894
+ emit(0x8B, 0x06) # mov eax/rax, [rsi]
895
+ emit(0xF0) # lock
896
+ emit(0x48) if size == 8
897
+ emit(0x0F, 0xB1, 0x17) # cmpxchg [rdi], edx/rdx
898
+ emit(0x0F, 0x94, 0xC1) # sete cl
899
+ emit(0x74) # je skip
900
+ skip_patch = @code.bytesize
901
+ emit(0x00) # placeholder, filled in below
902
+ emit(0x48) if size == 8
903
+ emit(0x89, 0x06) # mov [rsi], eax/rax
904
+ @code.setbyte(skip_patch, @code.bytesize - (skip_patch + 1))
905
+ emit(0x0F, 0xB6, 0xC1) # movzx eax, cl -> the _Bool result
906
+ store_reg(EAX, dst)
907
+ end
908
+
909
+ # "*p" read, sign-extending: load the pointer from its slot into rax, then
910
+ # read through it. An 8-byte load moves a full pointer/long (mov rax,
911
+ # [rax]); a 4-byte load reads an int (mov eax, [rax]), whose upper bits the
912
+ # write to eax zeroes; a 2/1-byte load reads a short/char and sign-extends
913
+ # it to the register (movsx), so the slot holds a promoted signed value.
914
+ def emit_load(dst, ptr_vreg, size)
915
+ load_reg(EAX, ptr_vreg) # rax = pointer value
916
+ case size
917
+ when 8
918
+ emit(0x48, 0x8B, 0x00) # mov rax, [rax]
919
+ when 2
920
+ emit(0x0F, 0xBF, 0x00) # movsx eax, word [rax]
921
+ when 1
922
+ emit(0x0F, 0xBE, 0x00) # movsx eax, byte [rax]
923
+ else # 4
924
+ emit(0x8B, 0x00) # mov eax, [rax]
925
+ end
926
+ store_reg(EAX, dst)
927
+ end
928
+
929
+ # "*p" read, zero-extending: the unsigned counterpart of #emit_load for an
930
+ # unsigned char/short (and _Bool). The 2/1-byte forms use movzx; the 4/8
931
+ # forms are identical to a signed load (a plain mov already zero-extends a
932
+ # 32-bit read, and an 8-byte value has no spare bits).
933
+ def emit_uload(dst, ptr_vreg, size)
934
+ load_reg(EAX, ptr_vreg) # rax = pointer value
935
+ case size
936
+ when 8
937
+ emit(0x48, 0x8B, 0x00) # mov rax, [rax]
938
+ when 2
939
+ emit(0x0F, 0xB7, 0x00) # movzx eax, word [rax]
940
+ when 1
941
+ emit(0x0F, 0xB6, 0x00) # movzx eax, byte [rax]
942
+ else # 4
943
+ emit(0x8B, 0x00) # mov eax, [rax]
944
+ end
945
+ store_reg(EAX, dst)
946
+ end
947
+
948
+ # "*p = v": rax holds the destination address, rcx the value, then rcx is
949
+ # written through the address. An 8-byte store writes a full pointer/long
950
+ # (mov [rax], rcx); a 4-byte store writes an int (mov [rax], ecx); a
951
+ # 2-byte store writes a word (a 66 operand-size prefix + mov [rax], cx);
952
+ # a 1-byte store writes just the low byte (mov [rax], cl). The narrower
953
+ # writes are exactly the truncation a narrow lvalue needs.
954
+ def emit_store(ptr_vreg, value_vreg, size)
955
+ load_reg(EAX, ptr_vreg) # rax = destination address
956
+ load_reg(ECX, value_vreg) # rcx = value
957
+ case size
958
+ when 8
959
+ emit(0x48, 0x89, 0x08) # mov [rax], rcx
960
+ when 2
961
+ emit(0x66, 0x89, 0x08) # mov [rax], cx
962
+ when 1
963
+ emit(0x88, 0x08) # mov [rax], cl
964
+ else # 4
965
+ emit(0x89, 0x08) # mov [rax], ecx
966
+ end
967
+ end
968
+
969
+ # :memcpy — a whole-struct copy "s = t". Load the destination address
970
+ # into rdi and the source into rsi, the byte count into ecx (a struct's
971
+ # size fits well within 32 bits), then "rep movsb" copies count bytes
972
+ # forward. cld clears the direction flag first so the copy runs upward;
973
+ # the System V ABI already guarantees DF is clear on entry, but clearing
974
+ # it costs one byte and keeps this instruction self-contained. rdi/rsi/rcx
975
+ # are all caller-saved scratch here, so nothing needs preserving.
976
+ def emit_memcpy(dest_vreg, src_vreg, byte_count)
977
+ load_reg(EDI, dest_vreg) # rdi = destination address
978
+ load_reg(ESI, src_vreg) # rsi = source address
979
+ emit(0xB9) # mov ecx, imm32
980
+ emit_bytes([byte_count].pack("L<"))
981
+ emit(0xFC) # cld
982
+ emit(0xF3, 0xA4) # rep movsb
983
+ end
984
+
985
+ # :va_start — initializes the four System V va_list fields at the address
986
+ # in `ap_vreg`. The named parameters' fixed classes (from @param_kinds, the
987
+ # generator having already resolved register-vs-stack placement) count how
988
+ # far the GP and SSE registers were consumed: `gp_named` of the six integer
989
+ # registers arrived as :gp and `sse_named` of the eight xmm ones as
990
+ # :sse4/:sse8, while `stack_named` (the :mem parameters) spilled to the
991
+ # stack. rax holds the __va_list_tag address and r10 is a second scratch for
992
+ # the two pointer fields:
993
+ # [rax+0] gp_offset = 8 * gp_named (GP registers consumed)
994
+ # [rax+4] fp_offset = 48 + 16 * sse_named (past the saved GP block,
995
+ # then the xmm registers the named
996
+ # parameters consumed)
997
+ # [rax+8] overflow_arg_area = rbp + 16 + 8*stack_named (the first stacked
998
+ # variable argument, past any
999
+ # named parameter that spilled)
1000
+ # [rax+16] reg_save_area = rbp + @reg_save_area_offset
1001
+ def emit_va_start(ap_vreg, _named)
1002
+ gp_named = @param_kinds.count(:gp)
1003
+ sse_named = @param_kinds.count(:sse4) + @param_kinds.count(:sse8)
1004
+ # A 16-alignment pad consumes a stacked slot like a spilled named
1005
+ # parameter, so the variable part begins past it too.
1006
+ stack_named = @param_kinds.count(:mem) + @param_kinds.count(:pad_stack)
1007
+
1008
+ load_reg(EAX, ap_vreg) # rax = &__va_list_tag
1009
+ emit(0xC7, 0x40, 0x00) # mov dword [rax+0], imm32
1010
+ emit_bytes([8 * gp_named].pack("l<"))
1011
+ emit(0xC7, 0x40, 0x04) # mov dword [rax+4], imm32
1012
+ emit_bytes([48 + 16 * sse_named].pack("l<"))
1013
+ # overflow_arg_area
1014
+ emit(0x4C, 0x8D) # REX.WR lea r10, [rbp + disp]
1015
+ emit_modrm_rbp_disp(R10 & 7, 16 + 8 * stack_named)
1016
+ emit(0x4C, 0x89, 0x50, 0x08) # mov [rax+8], r10
1017
+ # reg_save_area
1018
+ emit(0x4C, 0x8D) # REX.WR lea r10, [rbp + disp]
1019
+ emit_modrm_rbp_disp(R10 & 7, @reg_save_area_offset)
1020
+ emit(0x4C, 0x89, 0x50, 0x10) # mov [rax+16], r10
1021
+ end
1022
+
1023
+ # :alloca — dynamic stack allocation (__builtin_alloca). Loads the
1024
+ # requested byte count, rounds it up to a 16-byte multiple (add 15; and
1025
+ # -16), lowers rsp by that amount, and captures the resulting rsp as the
1026
+ # block's base address. The rounding keeps rsp 16-aligned — so the block is
1027
+ # 16-byte aligned as gcc guarantees, and a later call still meets the ABI's
1028
+ # alignment — while the storage lives until the function's "leave" (mov
1029
+ # rsp, rbp) reclaims the whole frame on return. Every vreg slot and stack
1030
+ # object is rbp-relative, so the moved rsp disturbs none of them; a call's
1031
+ # push-based argument setup works off this lowered rsp and restores it
1032
+ # afterwards, leaving the block intact across the call.
1033
+ def emit_alloca(dst, size_vreg)
1034
+ load_reg(EAX, size_vreg) # rax = requested byte count
1035
+ emit(0x48, 0x83, 0xC0, 0x0F) # add rax, 15
1036
+ emit(0x48, 0x83, 0xE0, 0xF0) # and rax, -16 (round up to 16)
1037
+ emit(0x48, 0x29, 0xC4) # sub rsp, rax
1038
+ store_reg(RSP, dst) # dst = rsp (block base address)
1039
+ end
1040
+
1041
+ # A size of 8 compares full 64-bit pointer values (REX.W cmp rax, rcx);
1042
+ # otherwise the 32-bit int compare is used. The signed setcc still suits
1043
+ # pointer ordering here, since stack addresses stay within the positive
1044
+ # half of the 64-bit range.
1045
+ def emit_comparison(dst, a, b, setcc_opcode, size = nil)
1046
+ load_reg(EAX, a)
1047
+ load_reg(ECX, b)
1048
+ emit(0x48) if size == 8 # REX.W widens the following cmp
1049
+ emit(0x39, 0xC8) # cmp eax, ecx (rax, rcx when REX.W)
1050
+ emit(0x0F, setcc_opcode, 0xC0) # setcc al
1051
+ emit(0x0F, 0xB6, 0xC0) # movzx eax, al
1052
+ store_reg(EAX, dst)
1053
+ end
1054
+
1055
+ # A floating binary op (:fadd/:fsub/:fmul/:fdiv). The operands are loaded
1056
+ # into xmm0/xmm1 from their slots, combined in place, and stored back. The
1057
+ # mandatory prefix selects the scalar-single (F3, size 4) or scalar-double
1058
+ # (F2, size 8) form of the shared 0F <opcode> encoding.
1059
+ def emit_float_binary(dst, a, b, size, opcode)
1060
+ load_xmm(XMM0, a, size)
1061
+ load_xmm(XMM1, b, size)
1062
+ emit(size == 8 ? 0xF2 : 0xF3)
1063
+ emit(0x0F, opcode)
1064
+ emit(modrm_reg(XMM0, XMM1)) # op xmm0, xmm1
1065
+ store_xmm(XMM0, dst, size)
1066
+ end
1067
+
1068
+ # A floating comparison (:feq..:fge), materialized into eax as an int 0/1
1069
+ # like an integer comparison but through ucomiss/ucomisd, whose result is
1070
+ # read from the flags with NaN awareness. The ordering ops reduce to a
1071
+ # single seta/setae: an "above" test is false whenever ucomis leaves the
1072
+ # carry set, which it does for a greater-than *or an unordered* compare, so
1073
+ # a NaN operand yields 0. To make that reduction work for "<"/"<=", whose
1074
+ # natural test would be "below" (true on NaN), the operands are swapped so
1075
+ # "a < b" is emitted as "b > a" (still seta) — false on NaN. Equality needs
1076
+ # two flags combined (see #emit_float_equality).
1077
+ def emit_float_comparison(op, dst, a, b, size)
1078
+ case op
1079
+ when :feq then return emit_float_equality(dst, a, b, size, equal: true)
1080
+ when :fne then return emit_float_equality(dst, a, b, size, equal: false)
1081
+ when :fgt then first, second, setcc = a, b, 0x97 # seta (a > b)
1082
+ when :fge then first, second, setcc = a, b, 0x93 # setae (a >= b)
1083
+ when :flt then first, second, setcc = b, a, 0x97 # seta (b > a == a < b)
1084
+ when :fle then first, second, setcc = b, a, 0x93 # setae (b >= a == a <= b)
1085
+ end
1086
+ emit_ucomis(first, second, size)
1087
+ emit(0x0F, setcc, 0xC0) # setcc al
1088
+ emit(0x0F, 0xB6, 0xC0) # movzx eax, al
1089
+ store_reg(EAX, dst)
1090
+ end
1091
+
1092
+ # Floating equality/inequality, which a single setcc cannot express because
1093
+ # ucomis reports an unordered (NaN) compare with ZF=PF=1, the same ZF a true
1094
+ # equality sets. "==" is therefore "equal AND ordered" (sete AND setnp) and
1095
+ # "!=" its negation "unequal OR unordered" (setne OR setp), so a NaN operand
1096
+ # makes "==" 0 and "!=" 1, as C requires.
1097
+ def emit_float_equality(dst, a, b, size, equal:)
1098
+ emit_ucomis(a, b, size)
1099
+ if equal
1100
+ emit(0x0F, 0x94, 0xC0) # sete al (ZF: equal or unordered)
1101
+ emit(0x0F, 0x9B, 0xC1) # setnp cl (not unordered)
1102
+ emit(0x20, 0xC8) # and al, cl
1103
+ else
1104
+ emit(0x0F, 0x95, 0xC0) # setne al (not-equal, false on NaN)
1105
+ emit(0x0F, 0x9A, 0xC1) # setp cl (unordered)
1106
+ emit(0x08, 0xC8) # or al, cl
1107
+ end
1108
+ emit(0x0F, 0xB6, 0xC0) # movzx eax, al
1109
+ store_reg(EAX, dst)
1110
+ end
1111
+
1112
+ # ucomiss xmm0, xmm1 (size 4) / ucomisd (size 8, a 66 prefix), the ordered
1113
+ # scalar compare that sets ZF/PF/CF for the setcc that follows. The two
1114
+ # operands are loaded into xmm0/xmm1 from their slots first.
1115
+ def emit_ucomis(a, b, size)
1116
+ load_xmm(XMM0, a, size)
1117
+ load_xmm(XMM1, b, size)
1118
+ emit(0x66) if size == 8 # ucomisd operand-size prefix
1119
+ emit(0x0F, 0x2E) # ucomiss/ucomisd
1120
+ emit(modrm_reg(XMM0, XMM1))
1121
+ end
1122
+
1123
+ # :itof — cvtsi2ss/cvtsi2sd converts a signed integer in a GP register to
1124
+ # a floating value in xmm0, stored back to `dst`. `int_desc` is the source
1125
+ # [width, signed?]; the mandatory prefix (F3 for a float destination, F2
1126
+ # for double) picks the format, and REX.W treats the source as 64-bit. The
1127
+ # 64-bit source is used for a `long` and, because cvtsi2s* is signed-only,
1128
+ # for an `unsigned int` too — whose slot is zero-extended into the high 32
1129
+ # bits by construction, so the signed 64-bit conversion is exact for its
1130
+ # full 0..2^32-1 range. Every narrower or signed 32-bit source is already
1131
+ # sign/zero-extended within its low 32 bits, so the 32-bit conversion reads
1132
+ # the correct value directly. (An `unsigned long` source never reaches the
1133
+ # backend; the generator rejects it.)
1134
+ def emit_itof(dst, src_vreg, int_desc, float_size)
1135
+ int_width, signed = int_desc
1136
+ wide = int_width == 8 || (int_width == 4 && !signed)
1137
+ load_reg(EAX, src_vreg) # rax = integer value
1138
+ emit(float_size == 8 ? 0xF2 : 0xF3)
1139
+ emit(0x48) if wide # REX.W: 64-bit integer source
1140
+ emit(0x0F, 0x2A)
1141
+ emit(modrm_reg(XMM0, EAX)) # cvtsi2s* xmm0, eax/rax
1142
+ store_xmm(XMM0, dst, float_size)
1143
+ end
1144
+
1145
+ # :ftoi — cvttss2si/cvttsd2si truncates a floating value in xmm0 toward
1146
+ # zero to a signed integer in a GP register, stored back to `dst`. The
1147
+ # mandatory prefix follows the *source* float width (F3 from float, F2 from
1148
+ # double); REX.W produces a 64-bit result for a `long` destination and for
1149
+ # an `unsigned int`, whose full 0..2^32-1 range does not fit the signed
1150
+ # 32-bit form. A destination narrower than the 32-bit result is re-ranged
1151
+ # by the generator afterwards. (An `unsigned long` destination is rejected
1152
+ # upstream.)
1153
+ def emit_ftoi(dst, src_vreg, int_desc, float_size)
1154
+ int_width, signed = int_desc
1155
+ load_xmm(XMM0, src_vreg, float_size)
1156
+ emit(float_size == 8 ? 0xF2 : 0xF3)
1157
+ wide = int_width == 8 || (int_width == 4 && !signed)
1158
+ emit(0x48) if wide # REX.W: 64-bit signed conversion
1159
+ emit(0x0F, 0x2C)
1160
+ emit(modrm_reg(EAX, XMM0)) # cvttss2si/cvttsd2si eax/rax, xmm0
1161
+ store_reg(EAX, dst)
1162
+ end
1163
+
1164
+ # :ftof — a float<->double width change. cvtss2sd (F3, from float) widens
1165
+ # and cvtsd2ss (F2, from double) narrows, in place in xmm0; `size` is the
1166
+ # source width, so the destination is stored at the opposite width.
1167
+ def emit_ftof(dst, src_vreg, src_size)
1168
+ load_xmm(XMM0, src_vreg, src_size)
1169
+ emit(src_size == 8 ? 0xF2 : 0xF3)
1170
+ emit(0x0F, 0x5A)
1171
+ emit(modrm_reg(XMM0, XMM0)) # cvtss2sd/cvtsd2ss xmm0, xmm0
1172
+ store_xmm(XMM0, dst, src_size == 8 ? 4 : 8)
1173
+ end
1174
+
1175
+ # movss/movsd xmm, [rbp + disp]: loads a floating value from its slot into
1176
+ # an xmm register. The F3 (size 4) / F2 (size 8) prefix selects the scalar
1177
+ # single/double form; the rbp-relative ModR/M reuses the integer helper,
1178
+ # the xmm number sitting in its reg field (0..7 for the scratch pair and the
1179
+ # eight argument registers alike, all within the 3-bit field, so no REX.R).
1180
+ def load_xmm(xmm, vreg, size)
1181
+ emit(size == 8 ? 0xF2 : 0xF3)
1182
+ emit(0x0F, 0x10)
1183
+ emit_modrm_rbp_disp(xmm, slot_disp(vreg))
1184
+ end
1185
+
1186
+ # movss/movsd [rbp + disp], xmm: stores an xmm register into a slot, the
1187
+ # counterpart of #load_xmm (opcode 0x11 writes memory from the register).
1188
+ def store_xmm(xmm, vreg, size)
1189
+ emit(size == 8 ? 0xF2 : 0xF3)
1190
+ emit(0x0F, 0x11)
1191
+ emit_modrm_rbp_disp(xmm, slot_disp(vreg))
1192
+ end
1193
+
1194
+ # Emits "jmp rel32" with a zero placeholder and records a fixup so the
1195
+ # displacement can be patched once the target label offset is known.
1196
+ def emit_jump(label_id)
1197
+ emit(0xE9) # jmp rel32
1198
+ record_fixup(label_id)
1199
+ end
1200
+
1201
+ def emit_jump_if_zero(cond, label_id)
1202
+ load_reg(EAX, cond)
1203
+ emit(0x85, 0xC0) # test eax, eax
1204
+ emit(0x0F, 0x84) # je rel32
1205
+ record_fixup(label_id)
1206
+ end
1207
+
1208
+ # Remembers the current offset as a rel32 patch site and reserves four
1209
+ # bytes for the displacement.
1210
+ def record_fixup(label_id)
1211
+ @fixups << [@code.bytesize, label_id]
1212
+ emit_bytes([0].pack("l<"))
1213
+ end
1214
+
1215
+ # Overwrites each reserved rel32 with the signed distance from the end of
1216
+ # the branch instruction to its target label.
1217
+ def resolve_fixups
1218
+ @fixups.each do |patch_offset, label_id|
1219
+ target = @labels[label_id]
1220
+ raise "unresolved label #{label_id}" unless target
1221
+
1222
+ rel = target - (patch_offset + 4)
1223
+ @code[patch_offset, 4] = [rel].pack("l<")
1224
+ end
1225
+ end
1226
+
1227
+ # A size of 8 materializes a full 64-bit immediate (movabs rax, imm64),
1228
+ # needed for a `long`/`unsigned long` constant that does not fit — or
1229
+ # would not sign-extend correctly — in 32 bits. Otherwise a 32-bit
1230
+ # "mov eax, imm32" suffices: it fills the low 32 bits (the whole value of
1231
+ # a 4-byte-or-narrower type) and zeroes the upper half of rax.
1232
+ def emit_const(dst, value, size = nil)
1233
+ if size == 8
1234
+ emit(0x48, 0xB8) # movabs rax, imm64
1235
+ emit_bytes([value & 0xFFFFFFFFFFFFFFFF].pack("Q<"))
1236
+ else
1237
+ emit(0xB8) # mov eax, imm32
1238
+ emit_bytes([value & 0xFFFFFFFF].pack("L<"))
1239
+ end
1240
+ store_reg(EAX, dst)
1241
+ end
1242
+
1243
+ # A size of 8 prefixes REX.W so the operation runs on the full 64-bit
1244
+ # rax/rcx (pointer arithmetic and index scaling); otherwise it stays a
1245
+ # 32-bit int operation. The opcode bytes are identical either way.
1246
+ def emit_binary(dst, a, b, opcode_bytes, size = nil)
1247
+ load_reg(EAX, a)
1248
+ load_reg(ECX, b)
1249
+ emit(0x48) if size == 8
1250
+ opcode_bytes.each { |byte| emit(byte) }
1251
+ store_reg(EAX, dst)
1252
+ end
1253
+
1254
+ # :mulhi — the unsigned high 64 bits of a 64x64 product, the piece a
1255
+ # synthesized __int128 multiply needs beyond the low 64 that :mul gives.
1256
+ # `mul rcx` (REX.W F7 /4) multiplies rax by rcx into rdx:rax; the high half
1257
+ # lands in rdx, which is stored to the destination. The one-operand `mul`
1258
+ # is the unsigned multiply, so this is the unsigned high product regardless
1259
+ # of the operands' declared signedness (the low 64 bits, and hence a full
1260
+ # 128-bit low result, are identical for signed and unsigned).
1261
+ def emit_mulhi(dst, a, b)
1262
+ load_reg(EAX, a) # rax = a
1263
+ load_reg(ECX, b) # rcx = b
1264
+ emit(0x48, 0xF7, 0xE1) # mul rcx -> rdx:rax = rax * rcx
1265
+ store_reg(EDX, dst) # dst = high 64 bits
1266
+ end
1267
+
1268
+ # A size of 8 does a 64-bit signed division (REX.W cqo + REX.W idiv rcx),
1269
+ # used for pointer differences; otherwise the 32-bit int division.
1270
+ def emit_divmod(dst, a, b, result_reg, size = nil)
1271
+ load_reg(EAX, a)
1272
+ load_reg(ECX, b)
1273
+ if size == 8
1274
+ emit(0x48, 0x99) # cqo: sign-extend rax into rdx:rax
1275
+ emit(0x48, 0xF7, 0xF9) # idiv rcx
1276
+ else
1277
+ emit(0x99) # cdq: sign-extend eax into edx:eax
1278
+ emit(0xF7, 0xF9) # idiv ecx
1279
+ end
1280
+ store_reg(result_reg, dst)
1281
+ end
1282
+
1283
+ # Unsigned division/remainder. Unlike the signed form, the high half of
1284
+ # the dividend is zeroed (xor edx, edx, which also clears the upper 32
1285
+ # bits of rdx for the 64-bit case) rather than sign-extended, and the
1286
+ # unsigned `div` opcode is used. size 8 divides the full 64-bit rax by
1287
+ # rcx; otherwise the 32-bit division. Quotient in eax, remainder in edx.
1288
+ def emit_udivmod(dst, a, b, result_reg, size = nil)
1289
+ load_reg(EAX, a)
1290
+ load_reg(ECX, b)
1291
+ emit(0x31, 0xD2) # xor edx, edx
1292
+ if size == 8
1293
+ emit(0x48, 0xF7, 0xF1) # div rcx
1294
+ else
1295
+ emit(0xF7, 0xF1) # div ecx
1296
+ end
1297
+ store_reg(result_reg, dst)
1298
+ end
1299
+
1300
+ # mov r64, [rbp + disp]: slots are always moved 64 bits at a time so a
1301
+ # pointer value is not truncated to 32 bits. This is safe for ints too:
1302
+ # every int is produced by a 32-bit write to eax, which x86-64 defines to
1303
+ # zero the upper 32 bits of rax, so the slot's high half is already zero.
1304
+ # The REX prefix carries W (64-bit operand) plus R for r8/r9 (>= 8), whose
1305
+ # low 3 bits go into the ModR/M reg field.
1306
+ def load_reg(reg, vreg)
1307
+ emit(0x48 | (reg >= 8 ? 0x04 : 0)) # REX.W (+ REX.R for r8/r9)
1308
+ emit(0x8B)
1309
+ emit_modrm_rbp_disp(reg & 7, slot_disp(vreg))
1310
+ end
1311
+
1312
+ # mov [rbp + disp], r64. See load_reg for the 64-bit and REX rationale.
1313
+ def store_reg(reg, vreg)
1314
+ emit(0x48 | (reg >= 8 ? 0x04 : 0)) # REX.W (+ REX.R for r8/r9)
1315
+ emit(0x89)
1316
+ emit_modrm_rbp_disp(reg & 7, slot_disp(vreg))
1317
+ end
1318
+
1319
+ def slot_disp(vreg)
1320
+ -8 * (vreg + 1)
1321
+ end
1322
+
1323
+ # Emits the ModR/M byte (+ displacement bytes) for a memory operand of
1324
+ # the form [rbp + disp] with the given reg field. Uses an 8-bit
1325
+ # displacement form when it fits, otherwise 32-bit. Emits directly to
1326
+ # @code instead of building and concatenating pack() strings, since
1327
+ # this runs on the instruction-encoding hot path.
1328
+ def emit_modrm_rbp_disp(reg, disp)
1329
+ rbp_rm = 0x05
1330
+ if disp >= -128 && disp <= 127
1331
+ emit(0x40 | (reg << 3) | rbp_rm) # mod=01 (disp8)
1332
+ emit(disp & 0xFF)
1333
+ else
1334
+ emit(0x80 | (reg << 3) | rbp_rm) # mod=10 (disp32)
1335
+ emit((disp >> 0) & 0xFF, (disp >> 8) & 0xFF, (disp >> 16) & 0xFF, (disp >> 24) & 0xFF)
1336
+ end
1337
+ end
1338
+
1339
+ # A register-direct ModR/M byte (mod=11) with the given reg and rm fields,
1340
+ # used by the floating ops to name two xmm registers (or an xmm and a GP
1341
+ # register in a cvt*). Both fields are 0/1/eax here, so no REX extension
1342
+ # bit is ever required.
1343
+ def modrm_reg(reg, rm)
1344
+ 0xC0 | (reg << 3) | rm
1345
+ end
1346
+
1347
+ def align16(value)
1348
+ (value + 15) & ~15
1349
+ end
1350
+
1351
+ # Fixed-arity on purpose: this is the instruction-encoding hot path, and
1352
+ # a splat would allocate a new Array on every call. Four bytes covers
1353
+ # the longest fixed-count call site in this file.
1354
+ def emit(byte1, byte2 = nil, byte3 = nil, byte4 = nil)
1355
+ @code << byte1
1356
+ @code << byte2 if byte2
1357
+ @code << byte3 if byte3
1358
+ @code << byte4 if byte4
1359
+ end
1360
+
1361
+ # Callers always pass the result of Array#pack (or a concatenation of
1362
+ # such results), which is already ASCII-8BIT, so no re-encoding copy
1363
+ # is needed here.
1364
+ def emit_bytes(string)
1365
+ @code << string
1366
+ end
1367
+ end
1368
+ end
1369
+ end