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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +56 -0
- data/LICENSE.txt +21 -0
- data/NOTICE +52 -0
- data/README.md +208 -0
- data/data/README.md +117 -0
- data/data/r10_corpus_scan.json +4082 -0
- data/data/r10_manual_classification.json +5342 -0
- data/data/r10_verification_m4a.json +1531 -0
- data/data/r10_verification_m4b.json +1818 -0
- data/data/r10_verification_m4c.json +1489 -0
- data/data/r10_verification_m4d.json +318 -0
- data/data/r10_verification_rbs.json +192 -0
- data/data/verified_gems.json +397 -0
- data/exe/rmake +16 -0
- data/exe/rubycc +9 -0
- data/exe/rubycc-ar +114 -0
- data/exe/rubycc-doctor +14 -0
- data/exe/rubycc-pkgconf +8 -0
- data/include/float.h +87 -0
- data/include/iso646.h +18 -0
- data/include/libc/alloca.h +18 -0
- data/include/libc/arpa/inet.h +61 -0
- data/include/libc/assert.h +43 -0
- data/include/libc/dirent.h +72 -0
- data/include/libc/dlfcn.h +58 -0
- data/include/libc/features.h +205 -0
- data/include/libc/glibc/aarch64/ctype.h +108 -0
- data/include/libc/glibc/aarch64/endian.h +54 -0
- data/include/libc/glibc/aarch64/errno.h +154 -0
- data/include/libc/glibc/aarch64/fcntl.h +144 -0
- data/include/libc/glibc/aarch64/inttypes.h +182 -0
- data/include/libc/glibc/aarch64/limits.h +63 -0
- data/include/libc/glibc/aarch64/pthread.h +131 -0
- data/include/libc/glibc/aarch64/setjmp.h +84 -0
- data/include/libc/glibc/aarch64/stdint.h +174 -0
- data/include/libc/glibc/aarch64/sys/epoll.h +88 -0
- data/include/libc/glibc/aarch64/sys/fcntl.h +19 -0
- data/include/libc/glibc/aarch64/sys/select.h +75 -0
- data/include/libc/glibc/aarch64/sys/stat.h +136 -0
- data/include/libc/glibc/aarch64/sys/syscall.h +167 -0
- data/include/libc/glibc/aarch64/sys/time.h +74 -0
- data/include/libc/glibc/aarch64/sys/types.h +127 -0
- data/include/libc/glibc/aarch64/time.h +113 -0
- data/include/libc/glibc/x86_64/ctype.h +108 -0
- data/include/libc/glibc/x86_64/endian.h +54 -0
- data/include/libc/glibc/x86_64/errno.h +154 -0
- data/include/libc/glibc/x86_64/fcntl.h +134 -0
- data/include/libc/glibc/x86_64/inttypes.h +182 -0
- data/include/libc/glibc/x86_64/limits.h +61 -0
- data/include/libc/glibc/x86_64/pthread.h +111 -0
- data/include/libc/glibc/x86_64/setjmp.h +82 -0
- data/include/libc/glibc/x86_64/stdint.h +163 -0
- data/include/libc/glibc/x86_64/sys/epoll.h +91 -0
- data/include/libc/glibc/x86_64/sys/fcntl.h +19 -0
- data/include/libc/glibc/x86_64/sys/select.h +75 -0
- data/include/libc/glibc/x86_64/sys/stat.h +130 -0
- data/include/libc/glibc/x86_64/sys/syscall.h +188 -0
- data/include/libc/glibc/x86_64/sys/time.h +74 -0
- data/include/libc/glibc/x86_64/sys/types.h +123 -0
- data/include/libc/glibc/x86_64/time.h +113 -0
- data/include/libc/grp.h +50 -0
- data/include/libc/langinfo.h +126 -0
- data/include/libc/link.h +18 -0
- data/include/libc/locale.h +83 -0
- data/include/libc/math.h +191 -0
- data/include/libc/netinet/in.h +126 -0
- data/include/libc/netinet/tcp.h +44 -0
- data/include/libc/poll.h +46 -0
- data/include/libc/pwd.h +58 -0
- data/include/libc/regex.h +53 -0
- data/include/libc/sched.h +35 -0
- data/include/libc/signal.h +204 -0
- data/include/libc/stdio.h +157 -0
- data/include/libc/stdlib.h +92 -0
- data/include/libc/string.h +74 -0
- data/include/libc/strings.h +25 -0
- data/include/libc/sys/cdefs.h +126 -0
- data/include/libc/sys/inotify.h +111 -0
- data/include/libc/sys/ioctl.h +36 -0
- data/include/libc/sys/mman.h +65 -0
- data/include/libc/sys/param.h +41 -0
- data/include/libc/sys/resource.h +109 -0
- data/include/libc/sys/socket.h +167 -0
- data/include/libc/sys/statfs.h +82 -0
- data/include/libc/sys/timerfd.h +55 -0
- data/include/libc/sys/uio.h +40 -0
- data/include/libc/sys/un.h +25 -0
- data/include/libc/sys/utsname.h +35 -0
- data/include/libc/sys/wait.h +135 -0
- data/include/libc/termios.h +179 -0
- data/include/libc/unistd.h +194 -0
- data/include/stdalign.h +16 -0
- data/include/stdarg.h +31 -0
- data/include/stdatomic.h +158 -0
- data/include/stdbool.h +15 -0
- data/include/stdckdint.h +28 -0
- data/include/stddef.h +60 -0
- data/include/stdnoreturn.h +18 -0
- data/include/x86intrin.h +16 -0
- data/lib/rubycc/backend/aarch64.rb +1724 -0
- data/lib/rubycc/backend/x86_64.rb +1369 -0
- data/lib/rubycc/compile_error.rb +36 -0
- data/lib/rubycc/compiler.rb +305 -0
- data/lib/rubycc/doctor/builder.rb +151 -0
- data/lib/rubycc/doctor/cli.rb +190 -0
- data/lib/rubycc/doctor/fetcher.rb +84 -0
- data/lib/rubycc/doctor/gemfile.rb +115 -0
- data/lib/rubycc/doctor/verified_gems.rb +114 -0
- data/lib/rubycc/doctor.rb +16 -0
- data/lib/rubycc/driver.rb +463 -0
- data/lib/rubycc/front/ast.rb +528 -0
- data/lib/rubycc/front/constant_evaluator.rb +631 -0
- data/lib/rubycc/front/initializer_resolver.rb +592 -0
- data/lib/rubycc/front/lexeme_reader.rb +460 -0
- data/lib/rubycc/front/lexer.rb +232 -0
- data/lib/rubycc/front/parser.rb +4122 -0
- data/lib/rubycc/front/token.rb +49 -0
- data/lib/rubycc/ir/call_convention.rb +486 -0
- data/lib/rubycc/ir/generator.rb +6036 -0
- data/lib/rubycc/ir/ir.rb +417 -0
- data/lib/rubycc/link/compat_runtime.rb +89 -0
- data/lib/rubycc/link/errors.rb +13 -0
- data/lib/rubycc/link/executable_linker.rb +405 -0
- data/lib/rubycc/link/library_resolver.rb +437 -0
- data/lib/rubycc/link/partial_linker.rb +546 -0
- data/lib/rubycc/link/shared_linker.rb +1732 -0
- data/lib/rubycc/mkmf_shim.rb +83 -0
- data/lib/rubycc/objfile/ar_archive.rb +437 -0
- data/lib/rubycc/objfile/elf_reader.rb +646 -0
- data/lib/rubycc/objfile/elf_writer.rb +891 -0
- data/lib/rubycc/objfile/relocatable_writer.rb +376 -0
- data/lib/rubycc/pkgconf/cli.rb +139 -0
- data/lib/rubycc/pkgconf/errors.rb +39 -0
- data/lib/rubycc/pkgconf/model.rb +28 -0
- data/lib/rubycc/pkgconf/parser.rb +109 -0
- data/lib/rubycc/pkgconf/pkgconf.rb +15 -0
- data/lib/rubycc/pkgconf/resolver.rb +70 -0
- data/lib/rubycc/pkgconf/search_path.rb +38 -0
- data/lib/rubycc/pkgconf/system_path_filter.rb +131 -0
- data/lib/rubycc/preprocess/constant_expression.rb +219 -0
- data/lib/rubycc/preprocess/glibc_version.rb +124 -0
- data/lib/rubycc/preprocess/pp_token.rb +62 -0
- data/lib/rubycc/preprocess/preprocessor.rb +2020 -0
- data/lib/rubycc/preprocess/scanner.rb +290 -0
- data/lib/rubycc/preprocess/token_converter.rb +157 -0
- data/lib/rubycc/rmake/cli.rb +143 -0
- data/lib/rubycc/rmake/errors.rb +71 -0
- data/lib/rubycc/rmake/executor.rb +818 -0
- data/lib/rubycc/rmake/expander.rb +251 -0
- data/lib/rubycc/rmake/makefile.rb +352 -0
- data/lib/rubycc/rmake/model.rb +137 -0
- data/lib/rubycc/rmake/parser.rb +226 -0
- data/lib/rubycc/rmake/rmake.rb +15 -0
- data/lib/rubycc/type.rb +1236 -0
- data/lib/rubycc/version.rb +5 -0
- data/lib/rubycc.rb +36 -0
- data/lib/rubygems_plugin.rb +102 -0
- 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
|